js/leads.js.bak_20260428_185427_merge

Code: DEV-B9326CAA Size: 14.4 KB Lines: 320 Path: /home/prodconfig.wenesthosting.com/dev.solargroup.wenest.se/js/leads.js.bak_20260428_185427_merge

Task / Comment

Open report form
// 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 getLeads(){
        if (Array.isArray(_allLeads) && _allLeads.length) {
            return _allLeads.filter(function(p){ return p.status && p.status !== 'unvisited'; });
        }
        return loadProspects().filter(function(p){ return p.status && p.status !== 'unvisited'; });
    }

    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 ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'})[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">&times;</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 === */
})();