// Leads.js - real leads page + legacy note helpers during migration
(function(){
// === Server-aggregated leads (fyller in från ALLA rutter, inte bara lokala) ===
var _allLeads = []; // [{...prospect, _route_id, _route_idx, _assigned_to, _assigned_name, ...}]
var _leadsFetchTimer = null;
var _leadsFetchInflight = null;
function _leadsScope(){
try {
if (typeof getRoleSetting === 'function') {
var s = getRoleSetting('leads.scope');
if (s) return s;
}
} catch(e){}
var role = (window.gUserRole || sessionStorage.getItem('gUserRole') || '');
return (['admin','systemadmin','saljchef','ekonomi'].indexOf(role) >= 0) ? 'all' : 'own';
}
function _leadsStaffId(){
return parseInt(window.gStaffId || sessionStorage.getItem('gStaffId') || 0, 10);
}
function _fetchLeadsFromServer(){
if (_leadsFetchInflight) return _leadsFetchInflight;
var scope = _leadsScope();
var sid = _leadsStaffId();
var url = (scope === 'all') ? '/api/leads.php' : ('/api/leads.php?staff_id=' + sid);
_leadsFetchInflight = fetch(url).then(function(r){ return r.json(); }).then(function(d){
if (d && d.success && Array.isArray(d.leads)) _allLeads = d.leads;
_leadsFetchInflight = null;
return _allLeads;
}).catch(function(e){
console.warn('[leads] server fetch failed', e);
_leadsFetchInflight = null;
return _allLeads;
});
return _leadsFetchInflight;
}
window._leadsFetchAll = _fetchLeadsFromServer;
window._leadsGetAll = function(){ return _allLeads; };
const statusLabels = {
interested:'Intresserad',
callback:'Återbesök',
not_home:'Ej hemma',
not_interested:'Ej intresserad',
kalkyl:'Kalkyl',
offert:'Offert'
};
const statusColors = {
interested:'#10b981',
callback:'#3b82f6',
not_home:'#eab308',
not_interested:'#ef4444',
kalkyl:'#7c3aed',
offert:'#2563eb'
};
let lastSnapshot = '';
function loadProspects(){
try { return JSON.parse(localStorage.getItem('faltProspects') || '[]'); }
catch(e){ return []; }
}
function _leadKey(p){
if (p && p.id) return 'id:' + p.id;
return 'a:' + (p.addr || '') + '|' + (p.created || '') + '|' + (p.lat || '') + ',' + (p.lng || '');
}
function getLeads(){
// Merge server + local, dedupe; server vinner vid kollision (har _route_id-meta).
var server = (Array.isArray(_allLeads) ? _allLeads : []).filter(function(p){
return p && p.status && p.status !== 'unvisited';
});
var local = loadProspects().filter(function(p){
return p && p.status && p.status !== 'unvisited';
});
var seen = {};
server.forEach(function(p){ seen[_leadKey(p)] = true; });
var merged = server.slice();
local.forEach(function(p){
var k = _leadKey(p);
if (!seen[k]) { seen[k] = true; merged.push(p); }
});
return merged;
}
function fmtTime(p){
try {
if (p.checkinTime) return new Date(p.checkinTime).toLocaleString('sv-SE',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'});
} catch(e){}
return p.created || '-';
}
function getSearchHaystack(p){
return [p.kundNamn,p.addr,p.city,p.kundTel,p.note,p.product,p.kundEmail].filter(Boolean).join(' ').toLowerCase();
}
function statusBadge(status){
const color = statusColors[status] || '#94a3b8';
const label = statusLabels[status] || status;
return '<span class="leads-status" style="background:'+color+'15;color:'+color+'"><span class="leads-dot" style="background:'+color+'"></span>'+label+'</span>';
}
function rowActions(p, idx){
const actions = [];
if (p.status === 'interested') {
actions.push('<button class="leads-btn primary" onclick="openLeadNewCalc('+idx+')">Ny kalkyl</button>');
}
if ((p.status === 'kalkyl' || p.status === 'offert') && ((p.quoteIds && p.quoteIds.length) || p.quoteId)) {
actions.push('<button class="leads-btn soft" onclick="showProspectQuotes('+idx+')">Kalkyler</button>');
}
actions.push('<button class="leads-btn" onclick="editLeadNote('+idx+')">Anteckning</button>');
actions.push('<button class="leads-btn" onclick="openLeadInFieldSales('+idx+')">Visa i FieldSales</button>');
return '<div class="leads-actions">'+actions.join('')+'</div>';
}
window.openLeadInFieldSales = function(idx){
if (idx < 0) {
alert('Den här leaden tillhör en annan rutt eller säljare och visas inte i din FieldSales-vy.');
return;
}
if (typeof showPage === 'function') showPage('faltsalj');
setTimeout(function(){
try {
if (typeof focusProspectOnMap === 'function') focusProspectOnMap(idx);
else if (typeof openCheckin === 'function') openCheckin(idx);
} catch(e) { console.error(e); }
}, 250);
};
window.openLeadNewCalc = function(idx){
var p;
if (idx < 0 && Array.isArray(_allLeads)) {
p = _allLeads[-(idx+1)];
} else {
var prospects = loadProspects();
p = prospects[idx];
}
if(!p) return;
var hittaPersons = Array.isArray(p.hittaPersons) ? p.hittaPersons : [];
var owner1Name = (p.owner1 && p.owner1.name) || p.kundNamn || (hittaPersons[0] && hittaPersons[0].name) || '';
var owner2Name = (p.owner2 && p.owner2.name) || (hittaPersons[1] && hittaPersons[1].name) || '';
var ownerType = p.ownerType || (owner2Name ? '2' : '1');
var maxDeduction = parseInt(p.maxDeduction || 0, 10);
if(!maxDeduction && ownerType === '2') maxDeduction = 100000;
if(!maxDeduction && ownerType === 'brf') maxDeduction = 0;
if(!maxDeduction && ownerType !== 'brf') maxDeduction = 50000;
window.pendingKalkylCustomer = {
prospectIdx: idx,
name: p.kundNamn || owner1Name || '',
email: p.kundEmail || '',
phone: p.kundTel || '',
address: [p.addr, p.city].filter(Boolean).join(', '),
ownerType: ownerType,
maxDeduction: maxDeduction,
owner1: {
name: owner1Name,
pnr: (p.owner1 && p.owner1.pnr) || ''
},
owner2: ownerType === '2' ? {
name: owner2Name,
pnr: (p.owner2 && p.owner2.pnr) || ''
} : null,
product: p.product || '',
solarData: {
yearlyKwh: p.yearlyKwh || null,
maxPanels: p.maxPanels || null,
roofArea: p.roofArea || null,
solarScore: p.solarScore || null
},
created: p.created || new Date().toISOString()
};
if (typeof newCalc === 'function') {
newCalc(window.pendingKalkylCustomer);
return;
}
if (typeof createKalkylFromLead === 'function') {
createKalkylFromLead(idx);
}
};
function renderStats(leads){
const counters = {
total: leads.length,
interested: leads.filter(p => ['interested','kalkyl','offert'].includes(p.status)).length,
callback: leads.filter(p => p.status === 'callback').length,
notHome: leads.filter(p => p.status === 'not_home').length,
declined: leads.filter(p => p.status === 'not_interested').length
};
const map = {
leadsStatTotal:counters.total,
leadsStatInterested:counters.interested,
leadsStatCallback:counters.callback,
leadsStatNotHome:counters.notHome,
leadsStatDeclined:counters.declined
};
Object.keys(map).forEach(function(id){
const el = document.getElementById(id);
if (el) el.textContent = map[id];
});
}
function renderTable(leads){
const body = document.getElementById('leadsTableBody');
const wrap = document.getElementById('leadsTableWrap');
const count = document.getElementById('leadsResultCount');
if (!body || !wrap || !count) return;
const query = (document.getElementById('leadsSearchInput')?.value || '').trim().toLowerCase();
const filter = document.getElementById('leadsStatusFilter')?.value || 'all';
const filtered = leads.filter(function(p){
const statusOk = filter === 'all' ? true : p.status === filter;
const searchOk = !query || getSearchHaystack(p).includes(query);
return statusOk && searchOk;
});
count.textContent = filtered.length + ' leads';
if (!filtered.length) {
wrap.style.display = 'block';
body.innerHTML = '<tr><td colspan="7" class="leads-empty-row">Inga leads ännu. Checka in i FieldSales för att skapa leads.</td></tr>';
return;
}
wrap.style.display = 'block';
body.innerHTML = filtered.map(function(p){
const prospects = loadProspects();
var idx = prospects.findIndex(function(item){
return item.id === p.id || (item.addr === p.addr && item.created === p.created);
});
// Om leaden inte finns lokalt (annan rutt/säljare): exponera via _allLeads-idx
// — actions (rowActions) löser därifrån via openLeadByGlobalIdx etc.
var globalIdx = (Array.isArray(_allLeads)) ? _allLeads.indexOf(p) : -1;
if (idx < 0 && globalIdx >= 0) idx = -(globalIdx + 1); // negativt = "global"-marker
const person = '<div class="leads-person">'+(p.kundNamn || 'Okänd kund')+'</div>' + (p.kundEmail ? '<div class="leads-sub">'+p.kundEmail+'</div>' : '');
const address = '<div>'+[p.addr, p.city].filter(Boolean).join(', ')+'</div>' + (p.note ? '<div class="leads-note">'+String(p.note).replace(/[&<>"]/g, function(c){ return ({'&':'&','<':'<','>':'>','"':'"'})[c]; })+'</div>' : '');
const phone = p.kundTel || '-';
const product = p.product || '-';
return '<tr>'
+'<td>'+statusBadge(p.status)+'</td>'
+'<td>'+person+'</td>'
+'<td>'+address+'</td>'
+'<td>'+phone+'</td>'
+'<td>'+product+'</td>'
+'<td><div>'+fmtTime(p)+'</div>'+(p.callbackDate ? '<div class="leads-sub">'+p.callbackDate+' '+(p.callbackTime || '')+'</div>' : '')+'</td>'
+'<td>'+(idx >= 0 ? rowActions(p, idx) : '<span class="leads-sub">Ingen åtgärd</span>')+'</td>'
+'</tr>';
}).join('');
}
function renderLeadsPage(force){
const page = document.getElementById('page-leads');
if (!page) return;
const snapshot = localStorage.getItem('faltProspects') || '[]';
if (!force && snapshot === lastSnapshot && !page.classList.contains('active')) return;
lastSnapshot = snapshot;
// Fetch server leads parallellt — render direkt med cache, sen igen när server svarar
_fetchLeadsFromServer().then(function(){
const leads = getLeads();
renderStats(leads);
renderTable(leads);
});
const leads = getLeads();
renderStats(leads);
renderTable(leads);
}
window.renderLeadsPage = renderLeadsPage;
function bindLeadsControls(){
const search = document.getElementById('leadsSearchInput');
const filter = document.getElementById('leadsStatusFilter');
if (search && !search.dataset.boundLeads) {
search.dataset.boundLeads = '1';
search.addEventListener('input', function(){ renderLeadsPage(true); });
}
if (filter && !filter.dataset.boundLeads) {
filter.dataset.boundLeads = '1';
filter.addEventListener('change', function(){ renderLeadsPage(true); });
}
}
bindLeadsControls();
renderLeadsPage(true);
setTimeout(function(){ bindLeadsControls(); renderLeadsPage(true); }, 150);
document.addEventListener('DOMContentLoaded', function(){
bindLeadsControls();
renderLeadsPage(true);
});
setInterval(function(){
const page = document.getElementById('page-leads');
if (page && page.classList.contains('active')) renderLeadsPage(false);
}, 1200);
window.addEventListener('storage', function(){ renderLeadsPage(true); });
// leads.js - Lead notes
function editLeadNote(idx){
const p = faltProspects[idx];
if(!p) return;
const modal = document.createElement('div');
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:9999;display:flex;align-items:center;justify-content:center;padding:20px';
modal.onclick = e => { if(e.target === modal) modal.remove(); };
modal.innerHTML = '<div style="background:#fff;border-radius:16px;padding:28px;width:440px;max-width:95vw;box-shadow:0 20px 60px rgba(0,0,0,.2)">'
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">'
+'<h3 style="font-size:18px;font-weight:700;margin:0">Anteckning</h3>'
+'<button onclick="this.closest(\'div[style*=fixed]\').remove()" style="background:none;border:none;font-size:22px;cursor:pointer;color:#94a3b8">×</button>'
+'</div>'
+'<div style="font-size:13px;color:#64748b;margin-bottom:12px">'+p.addr+(p.city?', '+p.city:'')+'</div>'
+'<textarea id="leadNoteText" rows="4" style="width:100%;padding:12px;border:1.5px solid #e5e7eb;border-radius:10px;font-size:14px;font-family:inherit;resize:vertical;box-sizing:border-box">'+(p.note||'')+'</textarea>'
+'<div style="display:flex;gap:8px;margin-top:16px;justify-content:flex-end">'
+'<button onclick="this.closest(\'div[style*=fixed]\').remove()" style="padding:10px 20px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:14px;cursor:pointer;background:#fff;font-family:inherit">Avbryt</button>'
+'<button onclick="saveLeadNote('+idx+')" style="padding:10px 20px;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;background:#024550;color:#fff;font-family:inherit">Spara</button>'
+'</div></div>';
document.body.appendChild(modal);
setTimeout(()=>document.getElementById('leadNoteText')?.focus(),100);
}
function saveLeadNote(idx){
const text = document.getElementById('leadNoteText')?.value || '';
faltProspects[idx].note = text;
document.querySelector('div[style*="fixed"][style*="9999"]')?.remove();
renderLeadsTable();
}
function closeModal(){const m=document.getElementById('quoteModal');if(m)m.classList.remove('active');document.body.style.overflow=''}
function submitQuote(e){if(e)e.preventDefault();alert('Tack! Vi återkommer.\n\n(DEMO)');closeModal()}
/* === KUNDER MAP === */
/* === DASHBOARD === */
})();