backups/tillval-qty2-20260420_143742/js/solargroup-updates.js

Code: DEV-C1C50B90 Size: 35.7 KB Lines: 590 Path: /home/prodconfig.wenesthosting.com/dev.solargroup.wenest.se/backups/tillval-qty2-20260420_143742/js/solargroup-updates.js

Task / Comment

Open report form
// === SOLARGROUP UPDATES 2026-03-25 ===
// This file ADDS new functionality on top of the existing app

// --- FMT with 2 decimals ---
(function(){
    const origFmt = window.fmt;
    window.fmt = function(p) {
        return p.toLocaleString('sv-SE',{minimumFractionDigits:2,maximumFractionDigits:2})+' kr';
    };
})();

// --- Hide Färg/Spröjs for non-window products ---
(function(){
    const origShowProductModal = window.showProductModal;
    if(origShowProductModal) {
        // Patch: after modal renders, hide color/sprojs for non-window products
        const origInterval = setInterval(function(){
            const modal = document.getElementById('productSalesModal');
            if(modal && window._spProduct) {
                const p = window._spProduct;
                if(!['fonster','dorrar'].includes(p.cat)) {
                    var cs = document.getElementById('spColorSection'); if(cs) cs.style.display='none';
                    var ss = document.getElementById('spSprojsSection'); if(ss) ss.style.display='none';
                }
                clearInterval(origInterval);
            }
        }, 200);
        setTimeout(function(){ clearInterval(origInterval); }, 5000);
    }
})();

// --- Category data (loaded by konfigurator.js) ---
window._cfgCategories = [];

// --- Category Editor ---
window.showCategoryEditor = async function() {
    var oldModal = document.getElementById('catEditorModal'); if(oldModal) oldModal.remove();
    let cats = [];
    try {
        const r = await fetch('/api/categories.php?all=1');
        const d = await r.json();
        if(d.success) cats = d.categories;
    } catch(e){}

    const catCounts = {};
    if(typeof catalogProducts !== 'undefined') {
        catalogProducts.forEach(p => { catCounts[p.cat] = (catCounts[p.cat]||0) + 1; });
    }

    function renderList() {
        const listHtml = cats.map((c,i) => {
            const count = catCounts[c.id] || 0;
            const imgThumb = c.image ? '<img src="'+c.image+'" style="width:80px;height:60px;object-fit:cover;border-radius:8px">' : '<div style="width:80px;height:60px;background:#f1f5f9;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:18px;color:#94a3b8">☆</div>';
            return '<div style="display:flex;align-items:center;gap:16px;padding:16px;border:1px solid #e5e7eb;border-radius:12px;margin-bottom:8px;background:#fff;transition:all .15s" onmouseover="this.style.borderColor=\'#024550\'" onmouseout="this.style.borderColor=\'#e5e7eb\'">'
                +imgThumb
                +'<div style="flex:1;min-width:0">'
                +'<div style="font-weight:600;font-size:14px">'+c.label+'</div>'
                +'<div style="font-size:11px;color:#64748b">'+(c.description||'')+'</div>'
                +'<div style="display:flex;gap:4px;margin-top:3px">'
                +(count?'<span style="font-size:10px;background:#e0f2fe;color:#0284c7;padding:2px 6px;border-radius:4px">'+count+' produkter</span>':'<span style="font-size:10px;background:#f1f5f9;color:#94a3b8;padding:2px 6px;border-radius:4px">0 produkter</span>')
                +(parseInt(c.is_tillval)?'<span style="font-size:10px;background:#fef3c7;color:#92400e;padding:2px 6px;border-radius:4px">Tillval</span>':'')
                +'</div></div>'
                +'<div style="display:flex;align-items:center;gap:6px">'
                +'<div style="display:flex;flex-direction:column;gap:2px">'
                +(i>0?'<button onclick="moveCat('+i+',-1)" style="background:none;border:1px solid #e5e7eb;border-radius:4px;cursor:pointer;padding:2px 6px;font-size:14px;color:#64748b;line-height:1">▲</button>':'<div style="width:28px;height:20px"></div>')
                +(i<cats.length-1?'<button onclick="moveCat('+i+',1)" style="background:none;border:1px solid #e5e7eb;border-radius:4px;cursor:pointer;padding:2px 6px;font-size:14px;color:#64748b;line-height:1">▼</button>':'<div style="width:28px;height:20px"></div>')
                +'</div>'
                +'<button onclick="editCategory('+i+')" style="padding:6px 12px;background:#f8f9fa;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;cursor:pointer;font-family:inherit;font-weight:600;color:#334155">Redigera</button>'
                +'</div></div>';
        }).join('');
        document.getElementById('catEditorList').innerHTML = listHtml;
    }

    window._catEditorCats = cats;

    window.editCategory = function(idx) {
        const c = idx >= 0 ? cats[idx] : {id:'',label:'',description:'',image:'',is_tillval:0,sort_order:cats.length+1};
        const isNew = idx < 0;
        document.getElementById('catEditorContent').innerHTML = '<div style="padding:20px">'
            +'<h3 style="font-size:16px;font-weight:700;margin:0 0 16px">'+(isNew?'Ny kategori':'Redigera: '+c.label)+'</h3>'
            +'<div style="display:grid;grid-template-columns:200px 1fr;gap:16px;align-items:start">'
            +'<div><div id="catEditImgPreview" style="width:200px;height:150px;border-radius:10px;overflow:hidden;border:1px solid #e5e7eb;margin-bottom:6px;cursor:pointer" onclick="document.getElementById(\'catEditImgInput\').click()">'
            +(c.image?'<img src="'+c.image+'" style="width:100%;height:100%;object-fit:cover">':'<div style="width:100%;height:100%;background:#f1f5f9;display:flex;align-items:center;justify-content:center;font-size:12px;color:#94a3b8">Klicka för bild</div>')
            +'</div><input type="file" id="catEditImgInput" accept="image/*" style="display:none" onchange="uploadCatImage(this)"></div>'
            +'<div style="display:grid;gap:10px">'
            +(isNew?'<div><label style="font-size:11px;font-weight:600;color:#64748b">ID</label><input id="catEditId" value="" placeholder="t.ex. varmepump" style="width:100%;padding:8px 12px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit;box-sizing:border-box"></div>':'')
            +'<div><label style="font-size:11px;font-weight:600;color:#64748b">Namn</label><input id="catEditLabel" value="'+c.label.replace(/"/g,'&quot;')+'" style="width:100%;padding:8px 12px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit;box-sizing:border-box"></div>'
            +'<div><label style="font-size:11px;font-weight:600;color:#64748b">Beskrivning</label><input id="catEditDesc" value="'+(c.description||'').replace(/"/g,'&quot;')+'" style="width:100%;padding:8px 12px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit;box-sizing:border-box"></div>'
            +'<div><label style="font-size:11px;font-weight:600;color:#64748b">Sortering</label><input id="catEditSort" type="number" value="'+(c.sort_order||0)+'" style="width:80px;padding:8px 12px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit"></div>'
            +'<label style="display:flex;align-items:center;gap:6px;font-size:13px;cursor:pointer"><input type="checkbox" id="catEditTillval" '+(parseInt(c.is_tillval)?'checked':'')+' style="width:16px;height:16px;accent-color:#f59e0b">Tillval-kategori</label>'
            +'</div></div>'
            +'<input type="hidden" id="catEditImage" value="'+(c.image||'')+'">'
            +'<input type="hidden" id="catEditIdx" value="'+idx+'">'
            +'<div style="display:flex;gap:8px;margin-top:16px">'
            +'<button onclick="saveCatEdit(\''+c.id+'\')" style="padding:10px 20px;background:#024550;color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">Spara</button>'
            +'<button onclick="showCategoryEditor()" style="padding:10px 20px;background:#f1f5f9;color:#64748b;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">Avbryt</button>'
            +(isNew?'':'<div style="flex:1"></div><button onclick="deleteCatEdit(\''+c.id+'\')" style="padding:10px 20px;background:#fee2e2;color:#dc2626;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">Radera</button>')
            +'</div></div>';
    };

    window.uploadCatImage = async function(input) {
        if(!input.files[0]) return;
        const file = input.files[0];
        const reader = new FileReader();
        reader.onload = async function(e) {
            try {
                const r = await fetch('/api/upload-photo.php', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({image:e.target.result,name:'cat_'+file.name})});
                const d = await r.json();
                if(d.success && d.url) {
                    document.getElementById('catEditImage').value = d.url;
                    document.getElementById('catEditImgPreview').innerHTML = '<img src="'+d.url+'" style="width:100%;height:100%;object-fit:cover">';
                }
            } catch(err){ alert('Fel: '+err.message); }
        };
        reader.readAsDataURL(file);
    };

    window.moveCat = async function(idx, dir) {
        const other = idx + dir;
        if(other < 0 || other >= cats.length) return;
        const a = cats[idx], b = cats[other];
        const tmpSort = a.sort_order; a.sort_order = b.sort_order; b.sort_order = tmpSort;
        await fetch('/api/categories.php', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:a.id,label:a.label,description:a.description,image:a.image,is_tillval:a.is_tillval,sort_order:a.sort_order})});
        await fetch('/api/categories.php', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:b.id,label:b.label,description:b.description,image:b.image,is_tillval:b.is_tillval,sort_order:b.sort_order})});
        try { const rr=await fetch('/api/categories.php?all=1');const dd=await rr.json();if(dd.success){cats=dd.categories;window._catEditorCats=cats;} }catch(e){}
        renderList();
        if(typeof loadCfgCategories==='function') loadCfgCategories();
    };

    window.deleteCatEdit = async function(id) {
        if(!confirm('Radera kategori "'+id+'"?')) return;
        await fetch('/api/categories.php?id='+id, {method:'DELETE'});
        try { const rr=await fetch('/api/categories.php?all=1');const dd=await rr.json();if(dd.success){cats=dd.categories;window._catEditorCats=cats;} }catch(e){}
        document.getElementById('catEditorContent').innerHTML = '<div id="catEditorList" style="padding:20px"></div>';
        renderList();
        if(typeof loadCfgCategories==='function') loadCfgCategories();
    };

    window.saveCatEdit = async function(origId) {
        const idx = parseInt(document.getElementById('catEditIdx').value);
        const id = idx < 0 ? (document.getElementById('catEditId')?.value||'').trim() : origId;
        if(!id) { alert('ID krävs'); return; }
        const body = {id:id,label:document.getElementById('catEditLabel').value,description:document.getElementById('catEditDesc').value,image:document.getElementById('catEditImage').value,is_tillval:document.getElementById('catEditTillval').checked?1:0,sort_order:parseInt(document.getElementById('catEditSort').value)||0};
        const r = await fetch('/api/categories.php', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
        const d = await r.json();
        if(d.success) {
            try { const rr=await fetch('/api/categories.php?all=1');const dd=await rr.json();if(dd.success){cats=dd.categories;window._catEditorCats=cats;} }catch(e){}
            document.getElementById('catEditorContent').innerHTML = '<div id="catEditorList" style="padding:20px"></div>';
            renderList();
            if(typeof loadCfgCategories==='function') loadCfgCategories();
        }
    };

    const modal = document.createElement('div');
    modal.id = 'catEditorModal';
    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;width:900px;height:900px;max-width:95vw;max-height:95vh;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.2)">'
        +'<div style="padding:20px 24px;border-bottom:1px solid #f1f5f9;display:flex;justify-content:space-between;align-items:center;flex-shrink:0">'
        +'<h2 style="font-size:18px;font-weight:700;margin:0">Kategorier</h2>'
        +'<div style="display:flex;align-items:center;gap:10px">'
        +'<button onclick="editCategory(-1)" style="padding:8px 16px;background:#059669;color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">+ Lägg till kategori</button>'
        +'<button onclick="document.getElementById(\'catEditorModal\').remove()" style="background:none;border:none;font-size:22px;cursor:pointer;color:#94a3b8">×</button>'
        +'</div></div>'
        +'<div id="catEditorContent" style="flex:1;overflow-y:auto"><div id="catEditorList" style="padding:20px"></div></div>'
        +'</div>';
    document.body.appendChild(modal);
    renderList();
};

// --- Kundpris auto-calc in product editor ---
window.peCalcPrice = function(){
    const cost=parseFloat(document.getElementById('peCostPrice')?.value)||0;
    const mtype=document.getElementById('peMarkupType')?.value||'percent';
    const mval=parseFloat(document.getElementById('peMarkupValue')?.value)||0;
    let price=cost;
    if(mtype==='amount') price=cost+mval;
    else if(mtype==='percent') price=Math.round(cost*(1+mval/100)*100)/100;
    const el=document.getElementById('pePrice');
    if(el) el.value=price;
};

// --- showAddToKalkyl ---
window.showAddToKalkyl = async function(category) {
    let quotes = [];
    try {
        const r = await fetch('/api/quotes.php');
        const d = await r.json();
        if(d.success && d.quotes) quotes = d.quotes;
    } catch(e){}
    const modal = document.createElement('div');
    modal.id = 'addToKalkylModal';
    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(); };
    let listHtml = quotes.slice(0,15).map(q => {
        const customerName = q.customer_name || '';
        const quoteName = q.quote_number || '#' + q.id;
        const cats = q.category ? q.category.split(',').map(c => c.trim()).join(', ') : '';
        const price = q.total_price && parseFloat(q.total_price) > 0 ? Math.round(parseFloat(q.total_price)).toLocaleString('sv-SE')+' kr' : '';
        const status = q.status || 'utkast';
        const statusColor = status === 'offert' ? '#f59e0b' : status === 'order' ? '#10b981' : '#94a3b8';
        return '<div onclick="document.getElementById(\'addToKalkylModal\').remove();currentQuoteId='+q.id+';saveCfgQuote(\''+category+'\')" style="padding:14px 16px;border:1.5px solid #e5e7eb;border-radius:10px;cursor:pointer;margin-bottom:6px;transition:all .15s" onmouseover="this.style.borderColor=\'#024550\';this.style.background=\'#f0fdfa\'" onmouseout="this.style.borderColor=\'#e5e7eb\';this.style.background=\'#fff\'">'
            +'<div style="display:flex;justify-content:space-between;align-items:center">'
            +'<div>'
            +'<div style="font-weight:600;font-size:14px">'+(customerName || quoteName)+'</div>'
            +(customerName ? '<div style="font-size:11px;color:#94a3b8">'+quoteName+'</div>' : '')
            +(cats ? '<div style="font-size:11px;color:#64748b;margin-top:2px">'+cats+'</div>' : '')
            +'</div>'
            +'<div style="text-align:right">'
            +(price ? '<div style="font-weight:700;font-size:14px;color:#024550">'+price+'</div>' : '')
            +'<div style="font-size:10px;font-weight:600;color:'+statusColor+';text-transform:uppercase">'+status+'</div>'
            +'</div></div></div>';
    }).join('');
    modal.innerHTML = '<div style="background:#fff;border-radius:16px;padding:24px;width:480px;max-width:95vw;max-height:80vh;overflow-y:auto">'
        +'<div style="display:flex;justify-content:space-between;margin-bottom:16px"><h3 style="font-size:17px;font-weight:700;margin:0">Lägg till i kalkyl</h3><button onclick="document.getElementById(\'addToKalkylModal\').remove()" style="background:none;border:none;font-size:22px;cursor:pointer;color:#94a3b8">×</button></div>'
        +'<button onclick="document.getElementById(\'addToKalkylModal\').remove();currentQuoteId=null;saveCfgQuote(\''+category+'\')" style="width:100%;padding:14px;background:linear-gradient(135deg,#059669,#10b981);color:#fff;border:none;border-radius:10px;font-size:15px;font-weight:700;cursor:pointer;margin-bottom:16px">+ Ny kalkyl</button>'
        +(quotes.length?'<div style="font-size:11px;font-weight:600;color:#94a3b8;text-transform:uppercase;margin-bottom:8px">Eller lägg till i befintlig</div>'+listHtml:'')
        +'</div>';
    document.body.appendChild(modal);
};

console.log('Solargroup updates loaded');

// Update catalog category order from DB
(function(){
    const origLoadCatalog = window.loadCatalogFromDB;
    if(!origLoadCatalog) return;
    // After categories load, update _catOrder
    fetch('/api/categories.php?all=1').then(r=>r.json()).then(d=>{
        if(d.success && d.categories) {
            window._catOrder = d.categories.map(c=>c.id);
        }
    }).catch(()=>{});
})();

// Update catalog category select from DB
(function(){
    fetch('/api/categories.php?all=1').then(r=>r.json()).then(d=>{
        if(!d.success) return;
        const sel = document.getElementById('catalogCatSelect');
        if(!sel) return;
        const current = sel.value;
        let opts = '<option value="">Alla produkter</option>';
        const main = d.categories.filter(c => !parseInt(c.is_tillval));
        const tillval = d.categories.filter(c => parseInt(c.is_tillval));
        main.forEach(c => { opts += '<option value="'+c.id+'">'+c.label+'</option>'; });
        if(tillval.length) {
            opts += '<optgroup label="Tillval">';
            tillval.forEach(c => { opts += '<option value="'+c.id+'">'+c.label+'</option>'; });
            opts += '</optgroup>';
        }
        sel.innerHTML = opts;
        if(current) sel.value = current;
        // Update sort order
        window._catOrder = d.categories.map(c=>c.id);
    }).catch(()=>{});
})();

// --- Fix tillval: variant select for standard variants (kwh etc) ---
(function(){
    const origRender = window.spRenderTillval;
    if(!origRender) return;
    
    window.spRenderTillval = function() {
        const el = document.getElementById('spTillvalArea');
        if(!el) return;
        const visible = _spTillval.filter(tv => !tv.excluded && !tv.hidden);
        if(!visible.length) { el.innerHTML = ''; return; }
        
        el.innerHTML = '<div style="font-size:11px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px">Tillval</div><div style="display:grid;grid-template-columns:1fr 1fr;gap:6px">'
        + visible.map(tv => {
            const realIdx = _spTillval.indexOf(tv);
            let variantSelector = '';
            
            // Standard variants (kwh, paneler etc) - NEW
            if(tv.specs && tv.specs.variants && tv.specs.variants.length && !tv.specs.type && tv.checked) {
                const first = tv.specs.variants[0];
                let vKey = 'kwh', vSuffix = ' kWh';
                if(first.paneler !== undefined) { vKey='paneler'; vSuffix=' paneler'; }
                else if(first.kvm !== undefined) { vKey='kvm'; vSuffix=' m²'; }
                else if(first.kwh === undefined) { vKey=Object.keys(first)[0]; vSuffix=''; }
                variantSelector = '<div style="margin-top:6px" onclick="event.preventDefault();event.stopPropagation()">'
                    +'<select onchange="spSetTillvalVariant('+realIdx+',this.value)" style="padding:6px 10px;border:1.5px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;width:100%">'
                    +'<option value="">Välj...</option>'
                    + tv.specs.variants.map((v, vi) => {
                        const lbl = v[vKey] + vSuffix;
                        const pr = v.totalt || v.price || v.att_betala;
                        return '<option value="'+vi+'"'+(vi===tv.selectedVariant?' selected':'')+'>'+lbl+(pr?' — '+pr.toLocaleString('sv-SE')+' kr':'')+'</option>';
                    }).join('')
                    +'</select></div>';
            }
            // Style+width variants
            else if(tv.specs && tv.specs.type === 'style_width_variants' && tv.checked) {
                // keep existing behavior - call original
            }
            // Width variants
            else if(tv.specs && tv.specs.type === 'width_variants' && tv.checked) {
                variantSelector = '<div style="margin-top:6px" onclick="event.preventDefault();event.stopPropagation()">'
                    +'<select onchange="spSetTillvalVariant('+realIdx+',this.value)" style="padding:6px 10px;border:1.5px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;width:100%">'
                    +'<option value="">Välj variant...</option>'
                    + tv.specs.variants.map((v, vi) => {
                        const lbl = v.label || (v.width_mm ? v.width_mm + ' mm' : '');
                        const pr = v.price != null ? ' — ' + v.price.toFixed(2) + ' kr' : '';
                        return '<option value="'+vi+'"'+(vi===tv.selectedVariant?' selected':'')+'>'+lbl+pr+'</option>';
                    }).join('')
                    +'</select></div>';
            }
            
            // Qty input for non-st units
            let qtyInput = '';
            if(tv.unit && tv.unit !== 'st' && tv.checked) {
                qtyInput = '<div style="display:flex;align-items:center;gap:6px;margin-top:6px" onclick="event.preventDefault();event.stopPropagation()">'
                    +'<input type="number" min="0" step="0.1" value="'+(tv.userQty||'')+'" placeholder="Antal" oninput="spSetTillvalQty('+realIdx+',this.value)" style="width:70px;padding:5px 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit">'
                    +'<span style="font-size:11px;color:#64748b">'+tv.unit+'</span></div>';
            }
            
            const totalPrice = tv.computedPrice !== null ? tv.computedPrice : tv.price;
            
            return '<div style="padding:10px 14px;background:'+(tv.checked?'#f0fdf4':'#f8f9fa')+';border:1px solid '+(tv.checked?'#bbf7d0':'#e5e7eb')+';border-radius:10px;transition:all .15s">'
                +'<div style="display:flex;align-items:center;gap:8px;cursor:pointer" onclick="spToggleTillval('+realIdx+')">'
                +'<input type="checkbox" '+(tv.checked?'checked':'')+' '+(tv.included?'disabled':'')+' style="width:18px;height:18px;accent-color:#059669;flex-shrink:0;pointer-events:none">'
                +'<span style="flex:1;font-size:13px;font-weight:600;color:#334155">'+tv.name+(tv.unit&&tv.unit!=='st'?' <span style="font-size:10px;color:#94a3b8">('+tv.price+' kr/'+tv.unit+')</span>':'')+'</span>'
                +'<span style="font-size:13px;font-weight:700;color:'+(tv.checked?'#059669':'#94a3b8')+'">'+totalPrice.toLocaleString('sv-SE')+' kr</span>'
                +'</div>'+variantSelector+qtyInput+'</div>';
        }).join('') + '</div>';
        
        spUpdatePrice();
    };
})();

// --- Fix: spSetTillvalQty should NOT re-render (focus loss fix) ---
(function(){
    const orig = window.spSetTillvalQty;
    window.spSetTillvalQty = function(idx, val) {
        _spTillval[idx].userQty = parseFloat(val) || 0;
        spUpdatePrice(); // only update price, don't re-render
    };
})();

// --- Fix: handle standard variant price in spUpdatePrice ---
(function(){
    const origUpdate = window.spUpdatePrice;
    if(!origUpdate) return;
    
    // Patch the tillval price calculation to handle standard variants
    const origCalc = window.spUpdatePrice;
    // We can't easily patch spUpdatePrice without rewriting it
    // Instead, add a helper that the existing code can use
    window._getTillvalPrice = function(tv) {
        let pr = tv.computedPrice !== null ? tv.computedPrice : tv.price;
        if(tv.specs && tv.selectedVariant !== null && tv.selectedVariant !== undefined) {
            if(tv.specs.type === 'style_width_variants') {
                const sv = tv.specs.styles?.[tv.selectedStyle]?.variants?.[tv.selectedVariant];
                if(sv && sv.price != null) pr = sv.price;
            } else if(tv.specs.type === 'width_variants') {
                const wv = tv.specs.variants?.[tv.selectedVariant];
                if(wv && wv.price != null) pr = wv.price;
            } else if(tv.specs.variants && tv.specs.variants[tv.selectedVariant]) {
                const sv = tv.specs.variants[tv.selectedVariant];
                pr = sv.totalt || sv.price || sv.att_betala || pr;
            }
        }
        return pr;
    };
})();

console.log('Tillval patches loaded');

// --- Itemize tillval in price summary ---
(function(){
    const origUpdate = window.spUpdatePrice;
    if(!origUpdate) return;
    
    // After each price update, replace "Tillval +X kr" with itemized list
    const observer = new MutationObserver(function() {
        const el = document.getElementById('spPriceArea');
        if(!el) return;
        
        // Find the "Tillval" line and replace with itemized
        const divs = el.querySelectorAll('div');
        divs.forEach(div => {
            const span = div.querySelector('.price-line-label, span');
            if(span && span.textContent.trim() === 'Tillval') {
                // Get checked non-hidden tillval
                if(typeof _spTillval === 'undefined') return;
                const checked = _spTillval.filter(tv => tv.checked && !tv.excluded && !tv.hidden);
                if(!checked.length) return;
                
                let html = '<div style="font-size:11px;font-weight:600;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px;margin-top:4px">Tillval</div>';
                checked.forEach(tv => {
                    let pr = window._getTillvalPrice ? window._getTillvalPrice(tv) : tv.price;
                    const eq = tv.unit === 'm²' ? (tv.userL * tv.userW) : (tv.unit && tv.unit !== 'st' && tv.userQty > 0 ? tv.userQty : tv.qty || 1);
                    const total = pr * (eq || 1);
                    html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:3px;padding-left:8px">'
                        +'<span style="font-size:12px;color:#64748b">'+tv.name+'</span>'
                        +'<span style="font-size:13px;font-weight:600;color:#334155">+'+total.toLocaleString('sv-SE')+' kr</span>'
                        +'</div>';
                });
                
                div.outerHTML = html;
            }
        });
    });
    
    // Observe price area changes
    setTimeout(function() {
        const el = document.getElementById('spPriceArea');
        if(el) observer.observe(el, {childList: true, subtree: true, characterData: true});
    }, 1000);
})();

// --- Show hidden tillval (installation etc) in price area + load child tillval ---
(function(){
    // Patch spLoadTillval to also load child tillval
    const origLoadTillval = window.spLoadTillval;
    if(!origLoadTillval) return;
    
    window.spLoadTillval = async function(productId) {
        await origLoadTillval(productId);
        
        // Load child tillval for each tillval
        for(const tv of _spTillval) {
            tv.childTillval = [];
            try {
                const r = await fetch('/api/products.php?services_for='+tv.id);
                const d = await r.json();
                if(d.success && d.services && d.services.length) {
                    const allP = await window._spGetAllProducts();
                    const childIds = d.services.map(s=>s.service_id);
                    tv.childTillval = allP.filter(p=>childIds.includes(p.id)).map(p=>({
                        id:p.id, name:p.name, price:parseFloat(p.price),
                        obligatorisk:parseInt(p.tillval_obligatorisk)||0,
                        hidden:parseInt(p.tillval_hidden)||0,
                        rotEligible:parseInt(p.rot_eligible)||0,
                        greenTechEligible:parseInt(p.green_tech_eligible)||0
                    }));
                }
            } catch(e){}
        }
        
        spApplyRulesAndRender();
    };
    
    // Cache for all products
    window._spAllProducts = null;
    window._spGetAllProducts = async function() {
        if(window._spAllProducts) return window._spAllProducts;
        try {
            const [r1,r2,r3] = await Promise.all([
                fetch('/api/products.php?cat=tjanster&active=1'),
                fetch('/api/products.php?cat=tillbehor&active=1'),
                fetch('/api/products.php?active=1')
            ]);
            const d1 = await r1.json(), d2 = await r2.json(), d3 = await r3.json();
            const all = [...(d1.success?d1.products:[]), ...(d2.success?d2.products:[])];
            const ids = new Set(all.map(p=>p.id));
            if(d3.success) d3.products.forEach(p => { if(!ids.has(p.id)) all.push(p); });
            window._spAllProducts = all;
        } catch(e){ window._spAllProducts = []; }
        return window._spAllProducts;
    };
    
    // Override price update to show hidden tillval + child tillval
    const origPriceUpdate = window.spUpdatePrice;
    window.spUpdatePrice = function() {
        origPriceUpdate();
        
        // Now patch the rendered price area
        const el = document.getElementById('spPriceArea');
        if(!el || !window._spProduct) return;
        const p = window._spProduct;
        
        // Find hidden tillval
        const hiddenChecked = _spTillval.filter(tv => tv.checked && !tv.excluded && tv.hidden);
        if(!hiddenChecked.length) return;
        
        // Calculate hidden total
        let hiddenTotal = 0;
        let hiddenItems = [];
        hiddenChecked.forEach(tv => {
            hiddenTotal += tv.price;
            hiddenItems.push({name:tv.name, price:tv.price, rotEligible:tv.rotEligible});
            if(tv.childTillval) tv.childTillval.filter(ch=>ch.obligatorisk).forEach(ch => {
                hiddenTotal += ch.price;
                hiddenItems.push({name:ch.name, price:ch.price, rotEligible:ch.rotEligible, isChild:true});
            });
        });
        
        // Also check child tillval on checked visible tillval
        const visibleChecked = _spTillval.filter(tv => tv.checked && !tv.excluded && !tv.hidden);
        let childItems = [];
        visibleChecked.forEach(tv => {
            if(tv.childTillval) tv.childTillval.filter(ch=>ch.obligatorisk).forEach(ch => {
                childItems.push({name:ch.name, price:ch.price, parentName:tv.name});
            });
        });
        
        // Find "Pris" line and insert hidden items after it
        const firstDiv = el.querySelector('div');
        if(!firstDiv) return;
        
        // Build new content: Product name, hidden items, Totalt
        const allDivs = Array.from(el.children);
        const priceLine = allDivs[0]; // First line is "Pris"
        if(!priceLine || hiddenTotal === 0) return;
        
        // Calculate arbete (product - hidden)
        const variants = (p.specs && p.specs.variants) || [];
        const sv = variants[window._spSelectedVariant] || {};
        let displayPrice = sv.totalt || p.price;
        const spQtyEl = document.getElementById('spQtyInput');
        const spQty = spQtyEl ? parseInt(spQtyEl.value)||1 : 1;
        if(p.unit && p.unit !== 'st' && spQty > 1) displayPrice = displayPrice * spQty;
        
        let arbete = displayPrice - hiddenTotal;
        
        // Build replacement HTML
        let newHtml = '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">'
            +'<span style="font-size:13px;color:#64748b">'+p.name+'</span>'
            +'<span style="font-size:15px;font-weight:600;color:#334155">'+arbete.toLocaleString('sv-SE')+' kr</span></div>';
        
        hiddenItems.forEach(hi => {
            newHtml += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;padding-left:'+(hi.isChild?'12':'0')+'px">'
                +'<span style="font-size:13px;color:#64748b">'+hi.name+'</span>'
                +'<span style="font-size:15px;font-weight:600;color:#334155">'+hi.price.toLocaleString('sv-SE')+' kr</span></div>';
        });
        
        newHtml += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;padding-top:6px;border-top:1px solid #e5e7eb">'
            +'<span style="font-size:13px;font-weight:600;color:#334155">Totalt</span>'
            +'<span style="font-size:15px;font-weight:700;color:#334155">'+displayPrice.toLocaleString('sv-SE')+' kr</span></div>';
        
        // Replace the first "Pris" line with our expanded version
        priceLine.outerHTML = newHtml;
        
        // Add child tillval items after their parent in TILLVAL section
        if(childItems.length) {
            const tillvalHeader = el.querySelector('div[style*="TILLVAL"]') || Array.from(el.querySelectorAll('div')).find(d => d.textContent.trim() === 'Tillval');
            // Just append child items to existing tillval list
        }
    };
})();

console.log('Hidden tillval + cascade patches loaded');

// --- Show "Kopplad till produkt" on Tillval tab ---
(function(){
    const origEditProduct = window.editProduct;
    if(!origEditProduct) return;
    
    // After editProduct renders, add "Kopplad till produkt" section
    const origLoad = window.peLoadServices;
    if(!origLoad) return;
    
    const patchedLoad = async function(productId) {
        await origLoad(productId);
        
        // Fetch which products use THIS product as tillval
        try {
            const r = await fetch('/api/products.php?linked_to='+productId);
            const d = await r.json();
            if(d.success && d.products && d.products.length) {
                const container = document.getElementById('peServicesArea');
                if(!container) return;
                
                let html = '<div style="margin-top:24px;border-top:2px solid #e5e7eb;padding-top:16px">'
                    +'<h4 style="font-size:14px;font-weight:700;color:#1a1a1a;margin:0 0 10px">Kopplad till produkt</h4>'
                    +'<div style="font-size:11px;color:#94a3b8;margin-bottom:8px">Denna produkt används som tillval på följande produkter:</div>'
                    +'<table style="width:100%;font-size:12px;border-collapse:collapse">'
                    +'<tr style="background:#f8f9fa"><th style="padding:6px 10px;text-align:left;color:#64748b;font-weight:600">Produkt</th><th style="padding:6px 10px;text-align:left;color:#64748b;font-weight:600">Kategori</th><th style="padding:6px 10px;text-align:center;color:#64748b;font-weight:600">Dold</th><th style="padding:6px 10px;text-align:center;color:#64748b;font-weight:600">Standard</th></tr>';
                
                d.products.forEach(p => {
                    html += '<tr style="border-top:1px solid #f1f5f9;cursor:pointer" onclick="document.querySelector(\'.pe-modal\')?.remove();editProduct(\''+p.id+'\')">'
                        +'<td style="padding:6px 10px;font-weight:600;color:#024550">'+p.name+'</td>'
                        +'<td style="padding:6px 10px;color:#64748b">'+(p.cat_label||p.cat)+'</td>'
                        +'<td style="padding:6px 10px;text-align:center">'+(parseInt(p.hidden)?'<span style="color:#f59e0b">●</span>':'')+'</td>'
                        +'<td style="padding:6px 10px;text-align:center">'+(parseInt(p.default_on)?'<span style="color:#059669">●</span>':'')+'</td>'
                        +'</tr>';
                });
                
                html += '</table></div>';
                container.insertAdjacentHTML('beforeend', html);
            }
        } catch(e) { console.error(e); }
    };
    
    window.peLoadServices = patchedLoad;
})();

console.log('Linked-to patch loaded');