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

Code: DEV-B3A05F89 Size: 32.1 KB Lines: 521 Path: /home/prodconfig.wenesthosting.com/dev.solargroup.wenest.se/backups/2026-03-28-pre-sidebar-refactor/konfigurator-generic.js

Task / Comment

Open report form
// konfigurator-generic.js - Generic framework + sub-configurators

// === GENERISK KALKYL-RAMVERK (alla nya) ===
// =============================================
const CFG_FINANCE_RATE = 0.049;

function cfgMonthly(total, years) {
    if(!total||!years) return 0;
    const r=CFG_FINANCE_RATE/12, n=years*12;
    return Math.round(total*r*Math.pow(1+r,n)/(Math.pow(1+r,n)-1));
}
function cfgDeduct(subtotal, laborRatio, type, owners) {
    const maxPerOwner=50000, maxTotal=owners*maxPerOwner;
    if(type==='green') return Math.min(Math.round(subtotal*0.20), maxTotal);
    if(type==='rot') return Math.min(Math.round(subtotal*laborRatio*0.30), maxTotal);
    return 0;
}
function cfgSetBtns(prefix, cls, activeVal) {
    document.querySelectorAll('.'+cls).forEach(b=>{
        const v=b.dataset.dt||b.dataset.oc||b.dataset.yr;
        if(v===String(activeVal)){b.style.background=prefix==='fin'?'#3b82f6':'#059669';b.style.color='#fff';b.style.borderColor=prefix==='fin'?'#3b82f6':'#059669';}
        else{b.style.background='#fff';b.style.color=prefix==='fin'?'#3b82f6':'#059669';b.style.borderColor=prefix==='fin'?'#bfdbfe':'#bbf7d0';}
    });
}

// === BATTERI (FRISTÅENDE) KONFIGURATOR (kopplad till Produktkatalog) ===
let _batSelected=null, _batPrice=0, _batDeduction='green', _batOwners=1, _batFinYears=15;
let _batProducts=[];
// Keep backward compat vars
let _batBrand=null, _batSize=null, _batSizePrice=0, _batAddons={};

function initBatConfig(){
    _batProducts = (typeof catalogProducts !== 'undefined' ? catalogProducts : []).filter(function(p){ return p.cat === 'batteri' || p.cat === 'batteri_utbyggnad'; });
    renderBatBrands();
    updateBatCalc();
}
function renderBatBrands(){
    var c=document.getElementById('batBrandCards');if(!c)return;
    if(!_batProducts.length) { c.innerHTML='<div style="padding:16px;color:#94a3b8;font-size:13px">Inga batteri-produkter i katalogen</div>'; return; }
    c.innerHTML=_batProducts.map(function(p){
        var selected = _batSelected === p.id;
        var desc = p.desc || '';
        var kwh = p.kwhCapacity ? p.kwhCapacity + ' kWh' : '';
        var greenBadge = p.greenTechEligible ? '<div style="font-size:10px;color:#059669;margin-top:4px">✓ Grön teknik-avdrag</div>' : '';
        return '<div onclick="selectBatProduct(\''+p.id+'\','+p.price+')" style="padding:16px;border:2px solid '+(selected?'#3b82f6':'#e5e7eb')+';border-radius:10px;cursor:pointer;background:'+(selected?'#f0f9ff':'#fff')+'">'
            +'<div style="display:flex;align-items:center;justify-content:space-between">'
            +'<div>'
            +'<div style="font-weight:600;font-size:15px">'+p.name+'</div>'
            +'<div style="font-size:12px;color:#64748b;margin-top:2px">'+desc+(kwh?' — '+kwh:'')+'</div>'
            +greenBadge
            +'</div>'
            +'<div style="text-align:right">'
            +'<div style="font-size:18px;font-weight:700">'+fmt(p.price)+'</div>'
            +(selected?'<div style="color:#3b82f6;font-size:18px;margin-top:2px">✓</div>':'')
            +'</div></div></div>';
    }).join('');
    // Hide size cards since products are complete units
    var sizeC=document.getElementById('batSizeCards');if(sizeC)sizeC.style.display='none';
    var sizeL=document.getElementById('batSizeLabel');if(sizeL)sizeL.style.display='none';
}
function selectBatProduct(id,price){if(_batSelected===id){_batSelected=null;_batPrice=0;}else{_batSelected=id;_batPrice=price;}_batSizePrice=_batPrice;renderBatBrands();updateBatCalc();}
// Keep old functions as aliases
function selectBatBrand(id){selectBatProduct(id,0);}
function selectBatSize(kwh,price){_batSizePrice=price;updateBatCalc();}
function renderBatSizes(){}
function renderBatAddons(){var c=document.getElementById('batAddonCards');if(c)c.style.display='none';}
function toggleBatAddon(id,price,checked){}
function setBatDeduction(t){_batDeduction=t;cfgSetBtns('deduct','bat-deduct-btn',t);updateBatCalc();}
function setBatOwners(n){_batOwners=n;cfgSetBtns('owner','bat-owner-btn',n);updateBatCalc();}
function setBatFinYears(y){_batFinYears=y;cfgSetBtns('fin','bat-fin-btn',y);updateBatCalc();}
function updateBatCalc(){
    var subtotal=_batPrice || _batSizePrice;
    var prod = _batProducts.find(function(p){ return p.id === _batSelected; });
    var hasGreen = prod ? prod.greenTechEligible : false;
    var deductType = hasGreen ? _batDeduction : 'none';
    var deduct=cfgDeduct(subtotal,0.35,deductType,_batOwners);
    var total=subtotal-deduct;
    var monthly=cfgMonthly(total,_batFinYears);
    document.getElementById('batPrBattery').textContent=fmt(subtotal);
    var addonsEl=document.getElementById('batPrAddons');if(addonsEl)addonsEl.textContent='0 kr';
    document.getElementById('batPrSubtotal').textContent=fmt(subtotal);
    document.getElementById('batDeductLabel').textContent=deductType==='green'?'GRÖNT TEKNIK-AVDRAG':'INGET AVDRAG';
    document.getElementById('batDeductKr').textContent=deduct?'-'+fmt(deduct):'0 kr';
    document.getElementById('batPrTotal').textContent=fmt(total);
    document.getElementById('batFinInfo').textContent='('+_batFinYears+' år, 4.9%)';
    document.getElementById('batPrMonthly').textContent=fmt(monthly)+'/mån';
    // Update shared summary
    if(typeof cfgAddItem==='function') { var batName = prod ? prod.name : 'Batteri'; cfgAddItem('batteri', batName, subtotal, deductType, 0.35); }
}

// === LADDBOX KONFIGURATOR (kopplad till Produktkatalog) ===
let _lbSelected=null, _lbPrice=0, _lbDeduction='green', _lbOwners=1, _lbFinYears=15;
let _lbProducts=[];

function initLbConfig(){
    _lbProducts = (typeof catalogProducts !== 'undefined' ? catalogProducts : []).filter(function(p){ return p.cat === 'laddbox'; });
    renderLbChargers();
    updateLbCalc();
}
function renderLbChargers(){
    var c=document.getElementById('lbChargerCards');if(!c)return;
    if(!_lbProducts.length) { c.innerHTML='<div style="padding:16px;color:#94a3b8;font-size:13px">Inga laddbox-produkter i katalogen</div>'; return; }
    c.innerHTML=_lbProducts.map(function(p){
        var selected = _lbSelected === p.id;
        var desc = p.desc || '';
        var greenBadge = p.greenTechEligible ? '<div style="font-size:10px;color:#059669;margin-top:4px">✓ Berättigar till grön teknik-avdrag</div>' : '';
        return '<div onclick="selectLbCharger(\''+p.id+'\','+p.price+')" style="padding:16px;border:2px solid '+(selected?'#3b82f6':'#e5e7eb')+';border-radius:10px;cursor:pointer;background:'+(selected?'#f0f9ff':'#fff')+'">'
            +'<div style="display:flex;align-items:center;justify-content:space-between">'
            +'<div>'
            +'<div style="font-weight:600;font-size:15px">'+p.name+'</div>'
            +'<div style="font-size:12px;color:#64748b;margin-top:2px">'+desc+'</div>'
            +greenBadge
            +'</div>'
            +'<div style="text-align:right">'
            +'<div style="font-size:18px;font-weight:700">'+fmt(p.price)+'</div>'
            +(selected?'<div style="color:#3b82f6;font-size:18px;margin-top:2px">✓</div>':'')
            +'</div></div></div>';
    }).join('');
}
function selectLbCharger(id,price){if(_lbSelected===id){_lbSelected=null;_lbPrice=0;}else{_lbSelected=id;_lbPrice=price;}renderLbChargers();updateLbCalc();}
function setLbDeduction(t){_lbDeduction=t;cfgSetBtns('deduct','lb-deduct-btn',t);updateLbCalc();}
function setLbOwners(n){_lbOwners=n;cfgSetBtns('owner','lb-owner-btn',n);updateLbCalc();}
function setLbFinYears(y){_lbFinYears=y;cfgSetBtns('fin','lb-fin-btn',y);updateLbCalc();}
function updateLbCalc(){
    var subtotal=_lbPrice;
    var prod = _lbProducts.find(function(p){ return p.id === _lbSelected; });
    var hasGreen = prod ? prod.greenTechEligible : false;
    var deductType = hasGreen ? _lbDeduction : 'none';
    var deduct=cfgDeduct(subtotal,0,deductType,_lbOwners);
    var total=subtotal-deduct;
    var monthly=cfgMonthly(total,_lbFinYears);
    document.getElementById('lbPrCharger').textContent=fmt(subtotal);
    document.getElementById('lbPrSubtotal').textContent=fmt(subtotal);
    document.getElementById('lbDeductLabel').textContent=deductType==='green'?'GRÖNT TEKNIK-AVDRAG':'INGET AVDRAG';
    document.getElementById('lbDeductKr').textContent=deduct?'-'+fmt(deduct):'0 kr';
    document.getElementById('lbPrTotal').textContent=fmt(total);
    document.getElementById('lbFinInfo').textContent='('+_lbFinYears+' år, 4.9%)';
    document.getElementById('lbPrMonthly').textContent=fmt(monthly)+'/mån';
    // Update shared summary
    if(typeof cfgAddItem==='function') { var lbName = prod ? prod.name : 'Laddbox'; cfgAddItem('laddbox', lbName, subtotal, deductType, 0); }
}

// === TAKTVÄTT KONFIGURATOR (kopplad till Produktkatalog) ===
let _ttSelectedType=null, _ttPricePerSqm=0, _ttDeduction='rot', _ttOwners=1, _ttFinYears=15;
let _ttProducts=[];

function initTtConfig(){
    // Hämta taktvätt-produkter från katalogen
    _ttProducts = (typeof catalogProducts !== 'undefined' ? catalogProducts : []).filter(function(p){ return p.cat === 'taktvatt'; });
    if(_ttProducts.length === 1) {
        // Bara en produkt — välj den automatiskt
        selectTtType(_ttProducts[0].id, _ttProducts[0].price);
    }
    renderTtTypes();
    updateTtCalc();
}
function renderTtTypes(){
    var c=document.getElementById('ttTypeCards');if(!c)return;
    if(!_ttProducts.length) { c.innerHTML='<div style="padding:16px;color:#94a3b8;font-size:13px">Inga taktvätt-produkter i katalogen</div>'; return; }
    c.innerHTML=_ttProducts.map(function(p){
        var selected = _ttSelectedType === p.id;
        var desc = p.desc || '';
        var unit = p.unit || 'kvm';
        var unitLabel = unit === 'kvm' ? 'm²' : unit;
        var rotBadge = p.rotEligible ? '<div style="font-size:10px;color:#059669;margin-top:4px">✓ ROT-avdrag</div>' : '';
        return '<div onclick="selectTtType(\''+p.id+'\','+p.price+')" style="padding:14px 16px;border:2px solid '+(selected?'#3b82f6':'#e5e7eb')+';border-radius:10px;cursor:pointer;display:flex;align-items:center;gap:12px;background:'+(selected?'#f0f9ff':'#fff')+'">'
            +'<div style="width:20px;height:20px;border:2px solid '+(selected?'#3b82f6':'#d1d5db')+';border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0">'+(selected?'<div style="width:10px;height:10px;background:#3b82f6;border-radius:50%"></div>':'')+'</div>'
            +'<div style="flex:1"><div style="font-weight:600;font-size:14px">'+p.name+'</div><div style="font-size:11px;color:#64748b">'+desc+'</div>'+rotBadge+'</div>'
            +'<div style="font-weight:700;font-size:14px;color:#1a1a1a">'+p.price.toFixed(2)+' kr/'+unitLabel+'</div>'
            +'</div>';
    }).join('');
}
function selectTtType(id,price){_ttSelectedType=id;_ttPricePerSqm=price;renderTtTypes();updateTtCalc();}
function setTtDeduction(t){_ttDeduction=t;cfgSetBtns('deduct','tt-deduct-btn',t);updateTtCalc();}
function setTtOwners(n){_ttOwners=n;cfgSetBtns('owner','tt-owner-btn',n);updateTtCalc();}
function setTtFinYears(y){_ttFinYears=y;cfgSetBtns('fin','tt-fin-btn',y);updateTtCalc();}
function updateTtCalc(){
    var area=parseInt(document.getElementById('ttArea')?.value)||0;
    var subtotal=area*_ttPricePerSqm;
    // Kolla om produkten har ROT-avdrag
    var prod = _ttProducts.find(function(p){ return p.id === _ttSelectedType; });
    var hasRot = prod ? prod.rotEligible : false;
    var deductType = hasRot ? _ttDeduction : 'none';
    var deduct=cfgDeduct(subtotal,1.0,deductType,_ttOwners);
    var total=subtotal-deduct;
    var monthly=cfgMonthly(total,_ttFinYears);
    document.getElementById('ttPrDesc').textContent=area?area+' m² × '+_ttPricePerSqm.toFixed(2)+' kr':'';
    document.getElementById('ttPrWash').textContent=fmt(subtotal);
    document.getElementById('ttPrSubtotal').textContent=fmt(subtotal);
    document.getElementById('ttDeductLabel').textContent=deductType==='rot'?'ROT-AVDRAG':'INGET AVDRAG';
    document.getElementById('ttDeductKr').textContent=deduct?'-'+fmt(deduct):'0 kr';
    document.getElementById('ttPrTotal').textContent=fmt(total);
    document.getElementById('ttFinInfo').textContent='('+_ttFinYears+' år, 4.9%)';
    document.getElementById('ttPrMonthly').textContent=fmt(monthly)+'/mån';
    var rotBtns = document.querySelectorAll('.tt-deduct-btn');
    rotBtns.forEach(function(b){ b.style.display = hasRot ? '' : 'none'; });
    // Update shared summary
    if(typeof cfgAddItem==='function') cfgAddItem('taktvatt', 'Taktvätt' + (area ? ' ('+area+' m²)' : ''), subtotal, deductType, 1.0);
}

// === VÄRMEPUMP KONFIGURATOR (kopplad till Produktkatalog) ===
let _vpSelected=null, _vpPrice=0, _vpInstall=0, _vpDeduction='rot', _vpOwners=1, _vpFinYears=15;
let _vpProducts=[];

function initVpConfig(){
    _vpProducts = (typeof catalogProducts !== 'undefined' ? catalogProducts : []).filter(function(p){ return p.cat === 'varmepump'; });
    renderVpPumps();
    updateVpCalc();
}
function renderVpPumps(){
    var c=document.getElementById('vpPumpCards');if(!c)return;
    if(!_vpProducts.length) { c.innerHTML='<div style="padding:16px;color:#94a3b8;font-size:13px">Inga värmepump-produkter i katalogen</div>'; return; }
    c.innerHTML=_vpProducts.map(function(p){
        var selected = _vpSelected === p.id;
        var desc = p.desc || '';
        var rotBadge = p.rotEligible ? '<div style="font-size:10px;color:#059669;margin-top:4px">✓ ROT-avdrag</div>' : '';
        var greenBadge = p.greenTechEligible ? '<div style="font-size:10px;color:#059669;margin-top:4px">✓ Grön teknik-avdrag</div>' : '';
        return '<div onclick="selectVpPump(\''+p.id+'\','+p.price+')" style="padding:16px;border:2px solid '+(selected?'#3b82f6':'#e5e7eb')+';border-radius:10px;cursor:pointer;background:'+(selected?'#f0f9ff':'#fff')+'">'
            +'<div style="display:flex;align-items:center;justify-content:space-between">'
            +'<div>'
            +'<div style="font-weight:600;font-size:15px">'+p.name+'</div>'
            +'<div style="font-size:12px;color:#64748b;margin-top:2px">'+desc+'</div>'
            +rotBadge+greenBadge
            +'</div>'
            +'<div style="text-align:right">'
            +'<div style="font-size:18px;font-weight:700">'+fmt(p.price)+'</div>'
            +'<div style="font-size:11px;color:#64748b">inkl. installation</div>'
            +(selected?'<div style="color:#3b82f6;font-size:18px;margin-top:2px">✓</div>':'')
            +'</div></div></div>';
    }).join('');
}
function selectVpPump(id,price){if(_vpSelected===id){_vpSelected=null;_vpPrice=0;}else{_vpSelected=id;_vpPrice=price;}renderVpPumps();updateVpCalc();}
function setVpDeduction(t){_vpDeduction=t;cfgSetBtns('deduct','vp-deduct-btn',t);updateVpCalc();}
function setVpOwners(n){_vpOwners=n;cfgSetBtns('owner','vp-owner-btn',n);updateVpCalc();}
function setVpFinYears(y){_vpFinYears=y;cfgSetBtns('fin','vp-fin-btn',y);updateVpCalc();}
function updateVpCalc(){
    var subtotal=_vpPrice;
    var prod = _vpProducts.find(function(p){ return p.id === _vpSelected; });
    var hasRot = prod ? prod.rotEligible : false;
    var hasGreen = prod ? prod.greenTechEligible : false;
    var deductType = hasRot ? (_vpDeduction === 'rot' ? 'rot' : _vpDeduction) : (hasGreen ? (_vpDeduction === 'green' ? 'green' : _vpDeduction) : 'none');
    var laborRatio = 0.5; // Uppskattad arbetsandel
    var deduct=cfgDeduct(subtotal,laborRatio,deductType,_vpOwners);
    var total=subtotal-deduct;
    var monthly=cfgMonthly(total,_vpFinYears);
    document.getElementById('vpPrPump').textContent=fmt(subtotal);
    var installEl = document.getElementById('vpPrInstall');
    if(installEl) installEl.textContent='Ingår';
    document.getElementById('vpPrSubtotal').textContent=fmt(subtotal);
    document.getElementById('vpDeductLabel').textContent=deductType==='green'?'GRÖNT TEKNIK-AVDRAG':deductType==='rot'?'ROT-AVDRAG':'INGET AVDRAG';
    document.getElementById('vpDeductKr').textContent=deduct?'-'+fmt(deduct):'0 kr';
    document.getElementById('vpPrTotal').textContent=fmt(total);
    document.getElementById('vpFinInfo').textContent='('+_vpFinYears+' år, 4.9%)';
    document.getElementById('vpPrMonthly').textContent=fmt(monthly)+'/mån';
    // Update shared summary
    if(typeof cfgAddItem==='function') { var vpName = prod ? prod.name : 'Värmepump'; cfgAddItem('varmepump', vpName, subtotal, deductType, laborRatio); }
}

// === TAK KONFIGURATOR ===
let _takYtor=[], _takTillbehor={}, _takDeduction='rot', _takOwners=1, _takFinYears=15;
const TAK_TYPER=[{v:'Sadeltak',l:'Sadeltak'},{v:'Pulpettak',l:'Pulpettak'},{v:'Mansardtak',l:'Mansardtak'},{v:'Platt tak',l:'Platt tak'}];
const TAK_MATERIAL=[
    {v:'Betongpannor',l:'Betongpannor',price_sqm:850},
    {v:'Tegelpannor',l:'Tegelpannor',price_sqm:1200},
    {v:'Plåttak',l:'Plåttak',price_sqm:650},
    {v:'Papptak',l:'Papptak',price_sqm:450}
];
const TAK_FARGER=['Svart','Tegelröd','Mörkgrå','Brun','Grön'];
const TAK_TILLBEHOR=[
    {id:'takstege',name:'Takstege',price:3500,unit:'st'},
    {id:'snoglidar',name:'Snöglidare',price:180,unit:'löpmeter'},
    {id:'ventilation',name:'Ventilationshuv',price:2800,unit:'st'},
    {id:'takfönster',name:'Takfönster VELUX',price:8900,unit:'st'},
    {id:'hangranna',name:'Hängrännor',price:320,unit:'löpmeter'},
    {id:'stuprör',name:'Stuprör',price:280,unit:'löpmeter'}
];

function initTakConfig(){renderTakYtor();renderTakTillbehor();updateTakCalc();}
let _takNextId=1;
function addTakYta(){_takYtor.push({id:_takNextId++,area:0,type:'',material:'',color:''});renderTakYtor();updateTakCalc();}
function removeTakYta(id){_takYtor=_takYtor.filter(y=>y.id!==id);renderTakYtor();updateTakCalc();}
function updateTakYta(id,field,val){const y=_takYtor.find(y=>y.id===id);if(y){y[field]=field==='area'?parseInt(val)||0:val;}updateTakCalc();}
function renderTakYtor(){
    const c=document.getElementById('takYtorContainer');if(!c)return;
    if(!_takYtor.length){c.innerHTML='<div style="text-align:center;padding:30px;border:2px dashed #e5e7eb;border-radius:10px;color:#94a3b8"><p style="font-size:14px;font-weight:600;margin:0 0 8px">Inga takytor tillagda ännu</p><button onclick="addTakYta()" style="padding:8px 16px;background:#f0f9ff;border:1.5px solid #3b82f6;border-radius:8px;color:#3b82f6;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit">+ Lägg till första takytan</button></div>';return;}
    c.innerHTML=_takYtor.map((y,i)=>`
        <div style="border:1px solid #e5e7eb;border-radius:10px;padding:16px;background:#f8fafc">
            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
                <span style="font-weight:600;font-size:14px">Takyta ${i+1}</span>
                <button onclick="removeTakYta(${y.id})" style="background:none;border:none;cursor:pointer;color:#ef4444;font-size:16px">✕</button>
            </div>
            <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
                <div><label style="font-size:11px;font-weight:600;color:#64748b;display:block;margin-bottom:3px">Takyta (m²)</label><input type="number" value="${y.area||''}" placeholder="100" oninput="updateTakYta(${y.id},'area',this.value)" style="width:100%;padding:8px 10px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:13px;font-family:inherit;box-sizing:border-box"></div>
                <div><label style="font-size:11px;font-weight:600;color:#64748b;display:block;margin-bottom:3px">Taktyp</label><select onchange="updateTakYta(${y.id},'type',this.value)" style="width:100%;padding:8px 10px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:13px;font-family:inherit;background:#fff"><option value="">Välj...</option>${TAK_TYPER.map(t=>'<option value="'+t.v+'"'+(y.type===t.v?' selected':'')+'>'+t.l+'</option>').join('')}</select></div>
                <div><label style="font-size:11px;font-weight:600;color:#64748b;display:block;margin-bottom:3px">Material</label><select onchange="updateTakYta(${y.id},'material',this.value)" style="width:100%;padding:8px 10px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:13px;font-family:inherit;background:#fff"><option value="">Välj...</option>${TAK_MATERIAL.map(m=>'<option value="'+m.v+'"'+(y.material===m.v?' selected':'')+'>'+m.l+' — '+m.price_sqm+' kr/m²</option>').join('')}</select></div>
                <div><label style="font-size:11px;font-weight:600;color:#64748b;display:block;margin-bottom:3px">Färg</label><select onchange="updateTakYta(${y.id},'color',this.value)" style="width:100%;padding:8px 10px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:13px;font-family:inherit;background:#fff"><option value="">Välj...</option>${TAK_FARGER.map(f=>'<option value="'+f+'"'+(y.color===f?' selected':'')+'>'+f+'</option>').join('')}</select></div>
            </div>
        </div>`).join('');
}
function renderTakTillbehor(){
    const c=document.getElementById('takTillbehorCards');if(!c)return;
    c.innerHTML=TAK_TILLBEHOR.map(t=>`
        <div style="display:flex;align-items:center;gap:12px;padding:12px 14px;border:1.5px solid ${_takTillbehor[t.id]?'#3b82f6':'#e5e7eb'};border-radius:10px;background:${_takTillbehor[t.id]?'#f0f9ff':'#fff'}">
            <input type="checkbox" ${_takTillbehor[t.id]?'checked':''} onchange="toggleTakTillb('${t.id}',this.checked)" style="accent-color:#3b82f6;width:18px;height:18px">
            <div style="flex:1"><div style="font-weight:600;font-size:13px">${t.name}</div><div style="font-size:10px;color:#64748b">${fmt(t.price)}/${t.unit}</div></div>
            ${_takTillbehor[t.id]?'<input type="number" min="1" value="'+(_takTillbehor[t.id]?.qty||1)+'" oninput="updateTakTillbQty(\''+t.id+'\',this.value)" style="width:60px;padding:6px;border:1.5px solid #e5e7eb;border-radius:6px;font-size:13px;text-align:center;font-family:inherit">':''}
        </div>`).join('');
}
function toggleTakTillb(id,checked){
    if(checked){const t=TAK_TILLBEHOR.find(x=>x.id===id);_takTillbehor[id]={price:t.price,qty:1};}else delete _takTillbehor[id];
    renderTakTillbehor();updateTakCalc();
}
function updateTakTillbQty(id,val){if(_takTillbehor[id])_takTillbehor[id].qty=parseInt(val)||1;updateTakCalc();}
function setTakDeduction(t){_takDeduction=t;cfgSetBtns('deduct','tak-deduct-btn',t);updateTakCalc();}
function setTakOwners(n){_takOwners=n;cfgSetBtns('owner','tak-owner-btn',n);updateTakCalc();}
function setTakFinYears(y){_takFinYears=y;cfgSetBtns('fin','tak-fin-btn',y);updateTakCalc();}
function updateTakCalc(){
    let ytorTotal=0;
    _takYtor.forEach(y=>{const m=TAK_MATERIAL.find(x=>x.v===y.material);if(m&&y.area)ytorTotal+=y.area*m.price_sqm;});
    let tillbTotal=0;
    Object.values(_takTillbehor).forEach(t=>{tillbTotal+=t.price*(t.qty||1);});
    const subtotal=ytorTotal+tillbTotal;
    const deduct=cfgDeduct(subtotal,0.50,_takDeduction,_takOwners);
    const total=subtotal-deduct;
    const monthly=cfgMonthly(total,_takFinYears);
    document.getElementById('takPrYtor').textContent=fmt(ytorTotal);
    document.getElementById('takPrTillb').textContent=fmt(tillbTotal);
    document.getElementById('takPrSubtotal').textContent=fmt(subtotal);
    document.getElementById('takDeductLabel').textContent=_takDeduction==='rot'?'ROT-AVDRAG':'INGET AVDRAG';
    document.getElementById('takDeductKr').textContent=deduct?'-'+fmt(deduct):'0 kr';
    document.getElementById('takPrTotal').textContent=fmt(total);
    document.getElementById('takFinInfo').textContent='('+_takFinYears+' år, 4.9%)';
    document.getElementById('takPrMonthly').textContent=fmt(monthly)+'/mån';
}

// === ISOLERING KONFIGURATOR ===
let _isoDeduction='rot', _isoOwners=1, _isoFinYears=15;
function initIsoConfig(){updateIsoCalc();}
function setIsoDeduction(t){_isoDeduction=t;cfgSetBtns('deduct','iso-deduct-btn',t);updateIsoCalc();}
function setIsoOwners(n){_isoOwners=n;cfgSetBtns('owner','iso-owner-btn',n);updateIsoCalc();}
function setIsoFinYears(y){_isoFinYears=y;cfgSetBtns('fin','iso-fin-btn',y);updateIsoCalc();}
function updateIsoCalc(){
    const area=parseInt(document.getElementById('isoArea')?.value)||0;
    const type=document.getElementById('isoType')?.value||'';
    const thickness=parseInt(document.getElementById('isoThickness')?.value)||0;
    const price=parseInt(document.getElementById('isoPrice')?.value)||0;
    const subtotal=price;
    const deduct=cfgDeduct(subtotal,0.50,_isoDeduction,_isoOwners);
    const total=subtotal-deduct;
    const monthly=cfgMonthly(total,_isoFinYears);
    document.getElementById('isoPrDesc').textContent=area?area+' m², '+(type||'?')+', '+thickness+'mm':'';
    document.getElementById('isoPrMaterial').textContent=fmt(subtotal);
    document.getElementById('isoPrSubtotal').textContent=fmt(subtotal);
    document.getElementById('isoDeductLabel').textContent=_isoDeduction==='rot'?'ROT-AVDRAG':'INGET AVDRAG';
    document.getElementById('isoDeductKr').textContent=deduct?'-'+fmt(deduct):'0 kr';
    document.getElementById('isoPrTotal').textContent=fmt(total);
    document.getElementById('isoFinInfo').textContent='('+_isoFinYears+' år, 4.9%)';
    document.getElementById('isoPrMonthly').textContent=fmt(monthly)+'/mån';
}

// === GENERISK SPARA/AFFÄR FÖR NYA KONFIGURATORER ===
async function saveCfgQuote(category) {
    const configData = collectCfgData(category);
    const payload = {
        category: category,
        config_data: JSON.stringify(configData),
        total_price: configData.total,
        panel_name: configData.description || category,
        panel_count: 0,
        status: 'utkast',
        created_by: gStaffId || null,
        customer_name: pendingKalkylCustomer?.name || null,
        customer_address: pendingKalkylCustomer?.address || null,
        customer_email: pendingKalkylCustomer?.email || null,
        customer_phone: pendingKalkylCustomer?.phone || null,
        customer_personnummer: (pendingKalkylCustomer?.owner1 && pendingKalkylCustomer.owner1.pnr) || null,
        owner_count: pendingKalkylCustomer?.ownerType === '2' ? 2 : (pendingKalkylCustomer?.ownerType === 'brf' ? 0 : 1),
        prospect_data: pendingKalkylCustomer?.solarData || null
    };
    if(currentQuoteId) payload.id = currentQuoteId;
    try {
        const res = await fetch('/api/quotes.php', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});
        const data = await res.json();
        if(data.error){alert('Fel: '+data.error);return;}
        if(data.id) currentQuoteId = data.id;
        var pIdx2 = (pendingKalkylCustomer||{}).prospectIdx;
        if(pIdx2 >= 0 && faltProspects[pIdx2]){
            if(faltProspects[pIdx2].status !== 'offert') faltProspects[pIdx2].status = 'kalkyl';
            if(!faltProspects[pIdx2].quoteIds) faltProspects[pIdx2].quoteIds = faltProspects[pIdx2].quoteId ? [faltProspects[pIdx2].quoteId] : [];
            if(!faltProspects[pIdx2].quoteIds.includes(currentQuoteId)) faltProspects[pIdx2].quoteIds.push(currentQuoteId);
            faltProspects[pIdx2].quoteId = currentQuoteId;
            localStorage.setItem('faltProspects', JSON.stringify(faltProspects));
            renderFaltMarkers(); renderFaltList(); updateFaltStats(); renderLeadsTable();
        }
        alert('Kalkyl sparad!');
    } catch(e){alert('Fel: '+e.message);}
}
function goToCfgAffar(category){alert('Affär-vy för '+category+' — kommer snart');}
function collectCfgData(cat) {
    switch(cat) {
        case 'batteri': {
            const brand=BAT_BRANDS.find(b=>b.id===_batBrand);
            return {description:(brand?brand.name:'')+' '+(_batSize||'')+'kWh',total:_batSizePrice+Object.values(_batAddons).reduce((s,v)=>s+v,0),battery_brand:_batBrand,battery_kwh:_batSize,battery_price:_batSizePrice,addons:_batAddons,deduction:_batDeduction,owners:_batOwners};
        }
        case 'laddbox': {
            const ch=LB_CHARGERS.find(c=>c.id===_lbSelected);
            return {description:ch?ch.brand+' '+ch.model:'',total:_lbPrice,charger_id:_lbSelected,charger_price:_lbPrice,deduction:_lbDeduction,owners:_lbOwners};
        }
        case 'taktvatt': {
            const area=parseInt(document.getElementById('ttArea')?.value)||0;
            const typ=TT_TYPES.find(t=>t.id===_ttSelectedType);
            return {description:(typ?typ.name:'')+' '+area+'m²',total:area*_ttPricePerSqm,type_id:_ttSelectedType,price_sqm:_ttPricePerSqm,area:area,deduction:_ttDeduction,owners:_ttOwners};
        }
        case 'varmepump': {
            const p=VP_PUMPS.find(x=>x.id===_vpSelected);
            return {description:p?p.brand+' '+p.model:'',total:_vpPrice+_vpInstall,pump_id:_vpSelected,pump_price:_vpPrice,install_price:_vpInstall,deduction:_vpDeduction,owners:_vpOwners};
        }
        case 'tak':
            return {description:_takYtor.length+' takyta(or)',total:0,ytor:_takYtor,tillbehor:_takTillbehor,deduction:_takDeduction,owners:_takOwners};
        case 'isolering': {
            const area=parseInt(document.getElementById('isoArea')?.value)||0;
            const price=parseInt(document.getElementById('isoPrice')?.value)||0;
            return {description:(document.getElementById('isoType')?.value||'Isolering')+' '+area+'m²',total:price,area:area,type:document.getElementById('isoType')?.value,thickness:parseInt(document.getElementById('isoThickness')?.value)||0,price:price,deduction:_isoDeduction,owners:_isoOwners};
        }
        default: return {description:cat,total:0};
    }
}

function closePoForm() {
    var ov = document.getElementById('poFormOverlay');
    if(ov) ov.remove();
    loadInkopDeals();
}

async function saveMeasurements(silent) {
    // Always save measurements to deal_measurements
    try {
        var r = await fetch('api/suppliers.php?action=save_measurements', {
            method:'POST', headers:{'Content-Type':'application/json'},
            body: JSON.stringify({
                deal_id: _poFormDealId,
                measurements: _poFormPositions
            })
        });
        var res = await r.json();
        if(!silent) {
            if(res.success) alert('Inmätning sparad! ('+res.saved+' positioner)');
            else alert(res.error||'Fel vid sparning');
        }
        return res.success;
    } catch(e) {
        if(!silent) alert('Fel: '+e.message);
        return false;
    }
}

async function savePoForm() {
    var suppId = document.getElementById('poLeverantor').value;
    if(!suppId) { alert('Välj leverantör under Leverantörsorder'); return; }

    // Save measurements first
    await saveMeasurements(true);

    // Collect positions with data
    var items = [];
    _poFormPositions.forEach(function(p){
        if(!p.product || !p.width || !p.height) return;
        items.push({
            article_name: p.product + ' ' + p.width + 'x' + p.height,
            category: p.product,
            quantity: parseInt(p.pcs)||1,
            unit: 'st',
            width: parseInt(p.width)||null,
            height: parseInt(p.height)||null,
            notes: [p.model, p.color_in, p.color_out, p.glass, p.profile].filter(Boolean).join(', ')
        });
    });

    // Collect Hedlunds items
    _poHedlundsRows.forEach(function(r){
        if(!r.article || !r.quantity) return;
        items.push({
            article_name: r.article,
            category: r.category,
            quantity: parseInt(r.quantity)||1,
            unit: 'st',
            length: parseInt(r.length)||null,
            notes: r.extra||null,
            unit_price: r.price_per_m ? (parseInt(r.length||0)/1000 * parseFloat(r.price_per_m)) : 0
        });
    });

    var notes = [
        'Kund: ' + (document.getElementById('poKundNamn').value||''),
        'Adress: ' + (document.getElementById('poKundAdress').value||''),
        'Montering: ' + (document.getElementById('poKundMontering').value||''),
        'Byggnadsår: ' + (document.getElementById('poKundByggnar').value||''),
        document.getElementById('poKontrollNotes').value||''
    ].filter(Boolean).join('\n');

    try {
        var r = await fetch('api/suppliers.php?action=create_order', {
            method:'POST', headers:{'Content-Type':'application/json'},
            body: JSON.stringify({
                deal_id: _poFormDealId,
                supplier_id: parseInt(suppId),
                order_date: document.getElementById('poOrderDatum').value,
                expected_delivery: document.getElementById('poLevDatum').value||null,
                notes: notes,
                status: 'draft',
                items: items
            })
        });
        var res = await r.json();
        if(res.success) {
            closePoForm();
            showOrderDetail(res.id);
        } else { alert(res.error||'Fel'); }
    } catch(e) { alert('Fel: '+e.message); }
}