backups/2026-03-28-pre-sidebar-refactor/konfigurator-affar.js

Code: DEV-3297EAD1 Size: 31.9 KB Lines: 614 Path: /home/prodconfig.wenesthosting.com/dev.solargroup.wenest.se/backups/2026-03-28-pre-sidebar-refactor/konfigurator-affar.js

Task / Comment

Open report form
// konfigurator-affar.js - Affär/deal + quotes

// === AFFÄR (DEAL) FUNCTIONS ===
let currentQuoteId = null;
let affarExtraCosts = [];
let affarImages = [];

function goToAffar(){
    if(!currentCalcState.panelCount){
        alert('Välj panel och antal först');
        return;
    }
    // Hide kalkyl views, show affär
    document.getElementById('solarConfigView').style.display='none';
    document.getElementById('affarView').style.display='block';

    // Fill customer banner
    const banner = document.getElementById('affarCustomerBanner');
    const c = pendingKalkylCustomer;
    if(c){
        banner.innerHTML='<div style="background:linear-gradient(135deg,#ecfdf5,#f0f9ff);border:1px solid #bbf7d0;border-radius:12px;padding:14px 18px;display:flex;align-items:center;gap:16px;flex-wrap:wrap">'
            +'<div style="flex:1;min-width:200px"><div style="font-weight:700;font-size:15px;color:#1a1a1a">'+c.name+'</div><div style="font-size:12px;color:#64748b">'+c.address+'</div></div>'
            +(c.phone?'<div style="font-size:13px;color:#334155">'+c.phone+'</div>':'')
            +(c.email?'<div style="font-size:13px;color:#334155">'+c.email+'</div>':'')
            +'</div>';
    } else {
        banner.innerHTML='';
    }

    // Fill config summary
    const cs = currentCalcState;
    const summary = document.getElementById('affarConfigSummary');
    let rows = '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Solpaneler</span><span style="font-size:13px;font-weight:600">'+cs.panelName+' × '+cs.panelCount+' ('+cs.kwp+' kWp)</span></div>';
    if(cs.batteryKwh>0) rows += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Batteri</span><span style="font-size:13px;font-weight:600">'+cs.batteryKwh+' kWh</span></div>';
    if(cs.chargerName) rows += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Laddare</span><span style="font-size:13px;font-weight:600">'+cs.chargerName+'</span></div>';
    rows += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Takyta</span><span style="font-size:13px;font-weight:600">'+cs.roofArea+' m²</span></div>';
    rows += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#ecfdf5;border-radius:8px"><span style="font-size:13px;color:#059669;font-weight:600">Uppsk. produktion</span><span style="font-size:13px;font-weight:700;color:#059669">~'+cs.yearlyKwh.toLocaleString('sv-SE')+' kWh/år</span></div>';
    summary.innerHTML = rows;

    // Reset extras
    affarExtraCosts = [];
    // Överför valda kalkylbilder till affär
    affarImages = kalkylImages.filter(i => i.selected).map(i => ({ url: i.url, type: i.type, created: i.created }));
    currentQuoteId = null;
    document.getElementById('affarNotes').value = '';
    document.getElementById('affarMarginSlider').value = 0;
    renderAffarExtras();
    renderAffarImages();
    updateAffarCalc();
}

function backToKalkylFromAffar(){
    document.getElementById('affarView').style.display='none';
    document.getElementById('solarConfigView').style.display='block';
}

function addExtraCost(){
    affarExtraCosts.push({name:'',amount:0});
    renderAffarExtras();
}

function removeExtraCost(idx){
    affarExtraCosts.splice(idx,1);
    renderAffarExtras();
    updateAffarCalc();
}

function renderAffarExtras(){
    const container = document.getElementById('affarExtraCosts');
    const empty = document.getElementById('affarExtraCostsEmpty');
    if(affarExtraCosts.length===0){
        container.innerHTML='';
        empty.style.display='block';
        return;
    }
    empty.style.display='none';
    container.innerHTML = affarExtraCosts.map((ec,i) =>
        '<div style="display:flex;gap:8px;align-items:center;margin-bottom:8px">'
        +'<input type="text" value="'+(ec.name||'').replace(/"/g,'&quot;')+'" placeholder="Beskrivning (t.ex. Extra kabelarbete)" oninput="affarExtraCosts['+i+'].name=this.value" style="flex:1;padding:8px 12px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:13px;font-family:inherit">'
        +'<input type="number" value="'+(ec.amount||0)+'" placeholder="Belopp" oninput="affarExtraCosts['+i+'].amount=parseInt(this.value)||0;updateAffarCalc()" style="width:120px;padding:8px 12px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:13px;font-family:inherit;text-align:right">'
        +'<span style="font-size:12px;color:#64748b;flex-shrink:0">kr</span>'
        +'<button onclick="removeExtraCost('+i+')" style="background:none;border:none;cursor:pointer;padding:4px;color:#ef4444;flex-shrink:0"><svg viewBox="0 0 24 24" style="width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>'
        +'</div>'
    ).join('');
}

function updateAffarCalc(){
    const cs = currentCalcState;
    if(!cs.subtotal) return;

    const margin = parseInt(document.getElementById('affarMarginSlider').value)||0;
    document.getElementById('affarMarginVal').textContent = margin + '%';

    const extrasTotal = affarExtraCosts.reduce((sum,ec)=>sum+(ec.amount||0),0);
    const baseBeforeMargin = cs.subtotal + extrasTotal;
    const marginAmount = Math.round(baseBeforeMargin * margin / 100);
    const totalBeforeDeduction = baseBeforeMargin + marginAmount;

    // Use same deduction logic as kalkyl based on deductionType
    const dt = cs.deductionType || 'green';
    const sliderMax = cs.greenTechDeduction > 0 ? (cs.total + cs.greenTechDeduction - cs.subtotal + cs.greenTechDeduction) : 50000;
    const ownerMax = (cs.ownerCount || 1) * 50000;
    let totalDeduction = 0;
    const totalLabor = cs.laborCost + Math.round(extrasTotal * SOL_LABOR_RATIO) + Math.round(marginAmount * SOL_LABOR_RATIO);
    const totalMaterial = totalBeforeDeduction - totalLabor;

    if(dt === 'green'){
        totalDeduction = Math.min(Math.round(totalBeforeDeduction * GREEN_TECH_RATE), ownerMax);
    } else if(dt === 'rot'){
        totalDeduction = Math.min(Math.round(totalLabor * ROT_RATE), ownerMax);
    } else if(dt === 'both'){
        const greenCalc = Math.round(totalMaterial * GREEN_TECH_RATE);
        const rotCalc = Math.round(totalLabor * ROT_RATE);
        totalDeduction = Math.min(greenCalc + rotCalc, ownerMax);
    }
    const dealTotal = totalBeforeDeduction - totalDeduction;

    // Update sidebar
    document.getElementById('affarPrMaterial').textContent = fmt(cs.materialCost);
    document.getElementById('affarPrLabor').textContent = fmt(cs.laborCost);
    document.getElementById('affarPrBattery').textContent = cs.batteryPrice > 0 ? fmt(cs.batteryPrice) : '0 kr';
    document.getElementById('affarPrCharger').textContent = cs.chargerPrice > 0 ? fmt(cs.chargerPrice) : '0 kr';

    const extraRow = document.getElementById('affarPrExtraRow');
    if(extrasTotal > 0){ extraRow.style.display=''; document.getElementById('affarPrExtra').textContent = fmt(extrasTotal); }
    else extraRow.style.display='none';

    const marginRow = document.getElementById('affarPrMarginRow');
    if(margin > 0){ marginRow.style.display=''; document.getElementById('affarPrMargin').textContent = fmt(marginAmount); document.getElementById('affarPrMarginPct').textContent='('+margin+'%)'; }
    else marginRow.style.display='none';

    document.getElementById('affarPrSubtotal').textContent = fmt(totalBeforeDeduction);
    document.getElementById('affarPrGreenTech').textContent = totalDeduction > 0 ? '-' + fmt(totalDeduction) : '0 kr';
    // Update deduction label and colors in affär
    const deductLabels = {green:'GRÖNT TEKNIK-AVDRAG', rot:'ROT-AVDRAG', both:'GRÖNT TEKNIK + ROT', none:'INGET AVDRAG'};
    const deductColors = {green:'#059669', rot:'#2563eb', both:'#7c3aed', none:'#94a3b8'};
    const deductBgs = {green:'#ecfdf5', rot:'#eff6ff', both:'#f5f3ff', none:'#f8fafc'};
    const dLabel = document.getElementById('affarDeductLabel');
    const dRow = document.getElementById('affarDeductionRow');
    if(dLabel){ dLabel.textContent = deductLabels[dt] || 'AVDRAG'; dLabel.style.color = deductColors[dt] || '#059669'; }
    if(dRow) dRow.style.background = deductBgs[dt] || '#ecfdf5';
    document.getElementById('affarPrGreenTech').style.color = deductColors[dt] || '#059669';
    document.getElementById('affarPrTotal').textContent = fmt(dealTotal);

    // Monthly
    const r = SOL_FINANCE_RATE / 12;
    const n = SOL_FINANCE_YEARS * 12;
    const monthly = dealTotal > 0 ? Math.round(dealTotal * r * Math.pow(1+r,n) / (Math.pow(1+r,n)-1)) : 0;
    document.getElementById('affarPrMonthly').textContent = monthly.toLocaleString('sv-SE') + ' kr/mån';

    // Margin info
    document.getElementById('affarMarginKr').textContent = fmt(marginAmount);
    const profit = marginAmount + extrasTotal;
    document.getElementById('affarProfit').textContent = fmt(profit);
}

// Hämta prospektbilder från fältsälj
function addProspectPhotosToAffar(){
    const c = pendingKalkylCustomer;
    if(!c || c.prospectIdx === undefined) { alert('Ingen prospekt kopplad'); return; }
    const p = faltProspects[c.prospectIdx];
    if(!p || !p.photos || p.photos.length === 0) { alert('Inga bilder finns på prospektet. Ta bilder i FältSälj först.'); return; }

    let added = 0;
    p.photos.forEach(ph => {
        const url = ph.full || ph.url || ph.dataUrl || ph.thumb;
        if(url && !affarImages.find(ai => ai.url === url)){
            affarImages.push({ url: url, type: ph.type || 'photo', created: ph.created || new Date().toISOString() });
            added++;
        }
    });
    if(added > 0) renderAffarImages();
    else alert('Alla prospektbilder finns redan i offerten.');
}

// Skapa prospektbild (med husbilder + solceller)
async function createProspectImage(){
    const btn = document.getElementById('affarGenImgBtn');
    const cs = currentCalcState;
    const c = pendingKalkylCustomer;
    const addr = c ? c.address : 'Svenskt hus';

    btn.disabled = true;
    btn.innerHTML = '<svg style="width:14px;height:14px;animation:spin 1s linear infinite" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg> Skapar bild...';

    try {
        const prompt = 'Photorealistic image of a Swedish residential house at '+addr+' with '+cs.panelCount+' modern black solar panels installed on the roof. Sunny day, blue sky, Swedish neighborhood. Professional real estate photography style.';
        const resp = await fetch('/api/generate-image.php', {
            method:'POST',
            headers:{'Content-Type':'application/json'},
            body: JSON.stringify({prompt, size:'1024x1024'})
        });
        const data = await resp.json();
        if(data.success && data.image_url){
            affarImages.push({url: data.image_url, type: 'prospect', created: new Date().toISOString()});
            renderAffarImages();
        } else {
            alert('Kunde inte skapa bild: ' + (data.error || 'Okänt fel'));
        }
    } catch(e){
        alert('Kunde inte skapa bild: ' + e.message);
    } finally {
        btn.disabled = false;
        btn.innerHTML = '<svg viewBox="0 0 24 24" style="width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg> Skapa prospektbild';
    }
}

function renderAffarImages(){
    const container = document.getElementById('affarImages');
    const empty = document.getElementById('affarImagesEmpty');
    if(affarImages.length===0){
        container.innerHTML='';
        empty.style.display='block';
        return;
    }
    empty.style.display='none';
    container.innerHTML = affarImages.map((img,i) =>
        '<div style="position:relative;border-radius:10px;overflow:hidden;border:1px solid #e5e7eb">'
        +'<img src="'+img.url+'" style="width:100%;height:140px;object-fit:cover;cursor:pointer" onclick="openLightbox(\''+img.url.replace(/'/g,"\\'")+'\')">'
        +'<button onclick="affarImages.splice('+i+',1);renderAffarImages()" style="position:absolute;top:6px;right:6px;width:24px;height:24px;background:rgba(0,0,0,.5);color:#fff;border:none;border-radius:50%;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center">&times;</button>'
        +'</div>'
    ).join('');
}

async function saveKalkylDraft(){
    const cs = currentCalcState;
    if(!cs.panelCount){ alert('Välj panel och antal först'); return; }
    const c = pendingKalkylCustomer || {};
    const selectedImages = kalkylImages.filter(i => i.selected);

    const payload = {
        customer_name: c.name || null,
        customer_address: c.address || null,
        customer_email: c.email || null,
        customer_phone: c.phone || null,
        customer_personnummer: (c.owner1 && c.owner1.pnr) || null,
        owner_count: cs.ownerCount || 1,
        panel_id: cs.panelId,
        panel_name: cs.panelName,
        panel_count: cs.panelCount,
        panel_watt: cs.panelWatt,
        battery_kwh: cs.batteryKwh || 0,
        battery_price: cs.batteryPrice || 0,
        charger_name: cs.chargerName || null,
        charger_price: cs.chargerPrice || 0,
        material_cost: cs.materialCost,
        labor_cost: cs.laborCost,
        subtotal: cs.subtotal,
        green_tech_deduction: cs.greenTechDeduction || 0,
        total_price: cs.totalPrice || cs.subtotal,
        extra_costs: [],
        margin_percent: 0,
        deal_total: cs.totalPrice || cs.subtotal,
        images: selectedImages,
        notes: null,
        status: 'utkast',
        created_by: gStaffId || null,
        finance_years: SOL_FINANCE_YEARS,
        finance_rate: SOL_FINANCE_RATE,
        yearly_kwh: cs.yearlyKwh,
        roof_area: cs.roofArea,
        prospect_data: c.solarData || null
    };

    if(currentQuoteId) payload.id = currentQuoteId;

    const btn = document.getElementById('saveKalkylBtn');
    btn.disabled = true;
    btn.innerHTML = '<svg style="width:14px;height:14px;animation:spin 1s linear infinite" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg> Sparar...';

    try {
        const resp = await fetch('/api/quotes.php', {
            method:'POST',
            headers:{'Content-Type':'application/json'},
            body: JSON.stringify(payload)
        });
        const data = await resp.json();
        if(data.success){
            currentQuoteId = data.id || currentQuoteId;
            btn.innerHTML = '<svg viewBox="0 0 24 24" style="width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2"><circle cx="12" cy="12" r="10"/><path d="M9 12l2 2 4-4"/></svg> Sparad!';
            btn.style.background = '#059669';
            setTimeout(()=>{ btn.innerHTML='<svg viewBox="0 0 24 24" style="width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg> Spara utkast'; btn.style.background='#f59e0b'; }, 2000);
            // Uppdatera prospektstatus
            const pIdx = (pendingKalkylCustomer||{}).prospectIdx;
            if(pIdx >= 0 && faltProspects[pIdx] && faltProspects[pIdx].status !== 'offert'){
                faltProspects[pIdx].status = 'kalkyl';
                addQuoteToProspect(pIdx, currentQuoteId);
                renderFaltMarkers(); renderFaltList(); updateFaltStats(); renderLeadsTable();
            }
            loadQuotesList();
        } else {
            alert('Kunde inte spara: '+(data.error||'Okänt fel'));
        }
    } catch(e){
        alert('Kunde inte spara: '+e.message);
    } finally {
        btn.disabled = false;
    }
}

// Save as draft (from affär view)
async function saveQuoteAsDraft(){
    const cs = currentCalcState;
    const c = pendingKalkylCustomer || {};
    const margin = parseInt(document.getElementById('affarMarginSlider').value)||0;
    const extrasTotal = affarExtraCosts.reduce((sum,ec)=>sum+(ec.amount||0),0);
    const baseBeforeMargin = cs.subtotal + extrasTotal;
    const marginAmount = Math.round(baseBeforeMargin * margin / 100);
    const totalBeforeDeduction = baseBeforeMargin + marginAmount;

    // Same deduction logic as updateAffarCalc
    const dt = cs.deductionType || 'green';
    const ownerMax = (cs.ownerCount || 1) * 50000;
    const totalLabor = cs.laborCost + Math.round(extrasTotal * SOL_LABOR_RATIO) + Math.round(marginAmount * SOL_LABOR_RATIO);
    const totalMaterial = totalBeforeDeduction - totalLabor;
    let totalDeduction = 0;
    if(dt === 'green') totalDeduction = Math.min(Math.round(totalBeforeDeduction * GREEN_TECH_RATE), ownerMax);
    else if(dt === 'rot') totalDeduction = Math.min(Math.round(totalLabor * ROT_RATE), ownerMax);
    else if(dt === 'both'){ totalDeduction = Math.min(Math.round(totalMaterial * GREEN_TECH_RATE) + Math.round(totalLabor * ROT_RATE), ownerMax); }
    const dealTotal = totalBeforeDeduction - totalDeduction;

    const payload = {
        customer_name: c.name || null,
        customer_address: c.address || null,
        customer_email: c.email || null,
        customer_phone: c.phone || null,
        customer_personnummer: c.personnummer || null,
        owner_count: cs.ownerCount || 1,
        panel_id: cs.panelId,
        panel_name: cs.panelName,
        panel_count: cs.panelCount,
        panel_watt: cs.panelWatt,
        battery_kwh: cs.batteryKwh || 0,
        battery_price: cs.batteryPrice || 0,
        charger_name: cs.chargerName || null,
        charger_price: cs.chargerPrice || 0,
        material_cost: cs.materialCost,
        labor_cost: cs.laborCost,
        subtotal: totalBeforeDeduction,
        green_tech_deduction: totalDeduction,
        total_price: dealTotal,
        extra_costs: affarExtraCosts.filter(ec => ec.name && ec.amount),
        margin_percent: margin,
        deal_total: dealTotal,
        images: affarImages,
        notes: document.getElementById('affarNotes').value || null,
        status: 'utkast',
        created_by: gStaffId || null,
        finance_years: SOL_FINANCE_YEARS,
        finance_rate: SOL_FINANCE_RATE,
        yearly_kwh: cs.yearlyKwh,
        roof_area: cs.roofArea,
        prospect_data: c.solarData || null
    };

    if(currentQuoteId) payload.id = currentQuoteId;

    try {
        const resp = await fetch('/api/quotes.php', {
            method:'POST',
            headers:{'Content-Type':'application/json'},
            body: JSON.stringify(payload)
        });
        const data = await resp.json();
        if(data.success){
            currentQuoteId = data.id || currentQuoteId;
            const statusEl = document.getElementById('affarStatus');
            statusEl.textContent = 'Utkast sparad ✓';
            statusEl.style.background = '#dcfce7';
            statusEl.style.color = '#166534';
            setTimeout(()=>{ statusEl.textContent='Utkast'; statusEl.style.background='#fef3c7'; statusEl.style.color='#92400e'; }, 2000);
            // Uppdatera prospektstatus till "kalkyl"
            const pIdx = (pendingKalkylCustomer||{}).prospectIdx;
            if(pIdx >= 0 && faltProspects[pIdx] && faltProspects[pIdx].status !== 'offert'){
                faltProspects[pIdx].status = 'kalkyl';
                addQuoteToProspect(pIdx, currentQuoteId);
                renderFaltMarkers(); renderFaltList(); updateFaltStats(); renderLeadsTable();
            }
            loadQuotesList();
        } else {
            alert('Kunde inte spara: ' + (data.error || 'Okänt fel'));
        }
    } catch(e){
        alert('Kunde inte spara: ' + e.message);
    }
}

async function createFinalQuote(){
    await saveQuoteAsDraft();
    if(!currentQuoteId) return;
    // Update status to offert
    try {
        await fetch('/api/quotes.php', {
            method:'POST',
            headers:{'Content-Type':'application/json'},
            body: JSON.stringify({id: currentQuoteId, status: 'offert'})
        });
        const statusEl = document.getElementById('affarStatus');
        statusEl.textContent = 'Offert skapad!';
        statusEl.style.background = '#dcfce7';
        statusEl.style.color = '#166534';
        // Uppdatera prospektstatus till "offert"
        const pIdx = (pendingKalkylCustomer||{}).prospectIdx;
        if(pIdx >= 0 && faltProspects[pIdx]){
            faltProspects[pIdx].status = 'offert';
            addQuoteToProspect(pIdx, currentQuoteId);
            renderFaltMarkers(); renderFaltList(); updateFaltStats(); renderLeadsTable();
        }
        loadQuotesList();
        alert('Offert #' + currentQuoteId + ' skapad!\n\nOffert-PDF generering kommer i nästa steg.');
    } catch(e){
        alert('Kunde inte skapa offert: ' + e.message);
    }
}

// Load quotes from DB into list
async function loadQuotesList(){
    const body = document.getElementById('quotesListBody');
    if(!body) return;

    const status = document.getElementById('quotesFilterStatus')?.value || '';
    const search = document.getElementById('quotesSearch')?.value.trim() || '';
    let url = '/api/quotes.php?';
    if(status) url += 'status='+encodeURIComponent(status)+'&';
    if(search) url += 'search='+encodeURIComponent(search)+'&';

    try {
        const resp = await fetch(url);
        const data = await resp.json();
        if(!data.success || !data.quotes || data.quotes.length === 0){
            body.innerHTML = '<tr><td colspan="9" style="padding:40px;text-align:center;color:#94a3b8;font-size:14px">Inga kalkyler hittade</td></tr>';
            return;
        }

        const statusMap = {
            utkast: {bg:'#fef3c7',color:'#92400e',label:'Utkast'},
            skickad: {bg:'#e0f2fe',color:'#0369a1',label:'Skickad'},
            offert: {bg:'#e0f2fe',color:'#0369a1',label:'Offert'},
            'godkänd': {bg:'#dcfce7',color:'#166534',label:'Godkänd'},
            'förlorad': {bg:'#fee2e2',color:'#991b1b',label:'Förlorad'}
        };

        body.innerHTML = data.quotes.map(q => {
            const st = statusMap[q.status] || {bg:'#f1f5f9',color:'#64748b',label:q.status||'—'};
            const date = q.updated_at ? q.updated_at.slice(0,10) : (q.created_at||'').slice(0,10);
            const amount = q.deal_total ? parseInt(q.deal_total).toLocaleString('sv-SE')+' kr' : (q.total_price ? parseInt(q.total_price).toLocaleString('sv-SE')+' kr' : '—');
            const product = q.panel_name ? q.panel_name + (q.panel_count ? ' × '+q.panel_count : '') : '—';
            // Parse images JSON
            let imgs = [];
            try { if(q.images) imgs = typeof q.images === 'string' ? JSON.parse(q.images) : q.images; } catch(e){}
            if(!Array.isArray(imgs)) imgs = [];
            let imgHtml = '';
            if(imgs.length > 0){
                const thumbs = imgs.slice(0,3);
                imgHtml = '<div style="display:flex;gap:4px;align-items:center">' + thumbs.map(function(im){
                    var src = typeof im === 'string' ? im : (im.url||'');
                    return '<img src="'+src+'" onclick="event.stopPropagation();openLightbox(this.src)" style="width:32px;height:32px;border-radius:4px;object-fit:cover;cursor:pointer;border:1px solid #e5e7eb" onerror="this.style.display=\'none\'">';
                }).join('') + (imgs.length > 3 ? '<span style="font-size:10px;color:#94a3b8">+' + (imgs.length-3) + '</span>' : '') + '</div>';
            } else {
                imgHtml = '<span style="color:#cbd5e1;font-size:11px">—</span>';
            }
            return '<tr style="border-bottom:1px solid #f1f5f9;cursor:pointer" onclick="openQuoteFromList('+q.id+')" onmouseover="this.style.background=\'#fafbfc\'" onmouseout="this.style.background=\'\'">'
                +'<td style="padding:10px 14px;font-size:13px;font-weight:600;color:#024550">#'+q.id+'</td>'
                +'<td style="padding:10px 14px;font-size:13px"><div style="font-weight:600;color:#1a1a1a">'+(q.customer_name ? q.customer_name : (q.quote_name ? 'Märkning: '+q.quote_name : 'Ej angiven'))+'</div><div style="font-size:11px;color:#94a3b8;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+(q.customer_address||'')+'</div></td>'
                +'<td style="padding:10px 14px;font-size:12px;color:#64748b">'+product+'</td>'
                +'<td style="padding:10px 14px;font-size:13px;font-weight:700;color:#1a1a1a;text-align:right">'+amount+'</td>'
                +'<td style="padding:10px 14px">'+imgHtml+'</td>'
                +'<td style="padding:10px 14px;font-size:12px;color:#64748b">'+(q.created_by_name||'—')+'</td>'
                +'<td style="padding:10px 14px;font-size:12px;color:#94a3b8">'+date+'</td>'
                +'<td style="padding:10px 14px"><span style="padding:3px 10px;border-radius:10px;font-size:11px;font-weight:600;background:'+st.bg+';color:'+st.color+'">'+st.label+'</span></td>'
                +'<td style="padding:10px 14px"><button onclick="event.stopPropagation();deleteQuote('+q.id+')" style="background:none;border:none;cursor:pointer;color:#dc2626;font-size:16px" title="Ta bort">&times;</button></td>'
                +'</tr>';
        }).join('');
    } catch(e){
        body.innerHTML = '<tr><td colspan="9" style="padding:40px;text-align:center;color:#ef4444;font-size:13px">Kunde inte ladda kalkyler: '+e.message+'</td></tr>';
    }
}

async function openQuoteFromList(id){
    if(kalkylOrigin !== 'faltsalj') kalkylOrigin = 'konfigurator';
    try {
        const resp = await fetch('/api/quotes.php?id='+id);
        const data = await resp.json();
        if(!data.success || !data.quote) { alert('Kunde inte ladda kalkyl'); return; }
        const q = data.quote;
        currentQuoteId = q.id;

        // Produktkatalog-kalkyl → visa produktlista med prissammanställning
        if(q.category === 'produktkatalog' && typeof openProductQuote === 'function') {
            navigateTo('konfigurator');
            openProductQuote(q);
            return;
        }

        // Bygg pendingKalkylCustomer
        pendingKalkylCustomer = {
            prospectIdx: -1,
            name: q.customer_name || '',
            email: q.customer_email || '',
            phone: q.customer_phone || '',
            address: q.customer_address || '',
            ownerType: q.owner_count == 2 ? '2' : (q.owner_count == 0 ? 'brf' : '1'),
            maxDeduction: (q.owner_count||1) * 50000,
            owner1: { name: q.customer_name || '', pnr: q.customer_personnummer || '' },
            owner2: null,
            product: 'Solceller',
            solarData: q.prospect_data || {},
            created: q.created_at
        };

        const savedImages = q.images || [];
        const isOffert = (q.status === 'offert' || q.status === 'skickad' || q.status === 'godkänd');

        // Navigera till konfigurator-sidan (viktigt om vi kommer från fältsälj etc)
        navigateTo('konfigurator');

        // Visa konfiguratorn
        showKalkylConfig();
        populateKalkylFromCustomer();

        // Ladda sparade bilder till kalkylImages
        kalkylImages = savedImages.map(img => ({
            url: img.url || img,
            type: img.type || 'photo',
            selected: true,
            created: img.created || new Date().toISOString()
        }));
        renderKalkylPhotos();

        // Ladda in kalkylinställningar
        setTimeout(() => {
            const catSel = document.getElementById('categorySelect');
            if(catSel){ catSel.value = 'solceller'; changeCategory(); }

            if(q.panel_count){
                const slider = document.getElementById('solPanelCount');
                if(slider) slider.value = q.panel_count;
            }
            if(q.battery_kwh){
                const bSlider = document.getElementById('solBatteryKwh');
                if(bSlider) bSlider.value = q.battery_kwh;
            }
            updateSolarCalc();

            // Bara öppna affär-vyn om det är en offert
            if(isOffert){
                setTimeout(() => {
                    const savedExtras = q.extra_costs || [];
                    const savedNotes = q.notes || '';
                    const savedMargin = q.margin_percent || 0;

                    document.getElementById('solarConfigView').style.display='none';
                    document.getElementById('affarView').style.display='block';

                    const banner = document.getElementById('affarCustomerBanner');
                    const c = pendingKalkylCustomer;
                    if(c && banner){
                        banner.innerHTML='<div style="background:linear-gradient(135deg,#ecfdf5,#f0f9ff);border:1px solid #bbf7d0;border-radius:12px;padding:14px 18px;display:flex;align-items:center;gap:16px;flex-wrap:wrap">'
                            +'<div style="flex:1;min-width:200px"><div style="font-weight:700;font-size:15px;color:#1a1a1a">'+(c.name||'Ej angiven')+'</div><div style="font-size:12px;color:#64748b">'+c.address+'</div></div>'
                            +(c.phone?'<div style="font-size:13px;color:#334155">'+c.phone+'</div>':'')
                            +(c.email?'<div style="font-size:13px;color:#334155">'+c.email+'</div>':'')
                            +'</div>';
                    }

                    const cs = currentCalcState;
                    const summary = document.getElementById('affarConfigSummary');
                    if(summary){
                        let rows = '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Solpaneler</span><span style="font-size:13px;font-weight:600">'+cs.panelName+' × '+cs.panelCount+' ('+cs.kwp+' kWp)</span></div>';
                        if(cs.batteryKwh>0) rows += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Batteri</span><span style="font-size:13px;font-weight:600">'+cs.batteryKwh+' kWh</span></div>';
                        if(cs.chargerName) rows += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Laddare</span><span style="font-size:13px;font-weight:600">'+cs.chargerName+'</span></div>';
                        summary.innerHTML = rows;
                    }

                    affarExtraCosts = savedExtras;
                    affarImages = savedImages;
                    currentQuoteId = q.id;
                    const affarNotesEl = document.getElementById('affarNotes');
                    if(affarNotesEl) affarNotesEl.value = savedNotes;
                    const marginSlider = document.getElementById('affarMarginSlider');
                    if(marginSlider) marginSlider.value = savedMargin;

                    renderAffarExtras();
                    renderAffarImages();
                    updateAffarCalc();
                }, 200);
            }
        }, 100);
    } catch(e){
        alert('Kunde inte öppna kalkyl: '+e.message);
    }
}

async function deleteQuote(id){
    if(!confirm('Ta bort kalkyl #'+id+'?')) return;
    try {
        await fetch('/api/quotes.php?id='+id, {method:'DELETE'});
        // Rensa prospekt-koppling i localStorage
        faltProspects.forEach(function(p){
            if(p.quoteId === id) p.quoteId = null;
            if(p.quoteIds){
                p.quoteIds = p.quoteIds.filter(function(qid){ return qid !== id; });
                if(p.quoteIds.length === 0 && p.status === 'kalkyl') p.status = 'interested';
            } else if(p.quoteId === null && p.status === 'kalkyl'){
                p.status = 'interested';
            }
        });
        localStorage.setItem('faltProspects', JSON.stringify(faltProspects));
        renderFaltMarkers(); renderFaltList(); updateFaltStats(); renderLeadsTable();
        loadQuotesList();
    } catch(e){ alert('Kunde inte ta bort: '+e.message); }
}

// Edit lead note modal