// sales-product-modal.js - Product detail modal, tillval, sprojs, price
// === SALES PRODUCT MODAL ===
let _spSelectedVariant = 0;
var _spSelectedWinModel = null;
function spSelectWinModel(key, eurPrice) { _spSelectedWinModel = key; _spSelectedWindowSize = Object.assign({}, _spSelectedWindowSize, {price: eurPrice}); spUpdateWindowSize(); }
let _spTillval = [];
let _spProduct = null;
let _spImages = [];
let _spCurrentImage = 0;
async function showProductModal(id) {
const p = catalogProducts.find(x => x.id === id);
if(!p) return;
_spProduct = p;
_spSelectedVariant = 0;
_spImages = [p.img, ...(p.gallery || [])].filter(Boolean);
_spCurrentImage = 0;
const isAdmin = (gUserRole === 'systemadmin' || gUserRole === 'admin' || gUserRole === 'saljchef');
const variants = (p.specs && p.specs.variants) || [];
const attributes = (p.specs && p.specs.attributes) || [];
const documents = p.documents || [];
// Detect variant label key
let variantLabelKey = '', variantLabelSuffix = '';
if(variants.length) {
const first = variants[0];
if(first.kwh !== undefined) { variantLabelKey = 'kwh'; variantLabelSuffix = ' kWh'; }
else if(first.paneler !== undefined) { variantLabelKey = 'paneler'; variantLabelSuffix = ' paneler'; }
else if(first.cm !== undefined) { variantLabelKey = 'cm'; variantLabelSuffix = ' cm'; }
else if(first.material !== undefined) { variantLabelKey = 'material'; variantLabelSuffix = ''; }
else { const keys = Object.keys(first); variantLabelKey = keys[0]; variantLabelSuffix = ''; }
}
// Width-only selector (foderkloss, tröskel, utv råplan etc)
let widthOnlyHtml = '';
if(p.specs && p.specs.type === 'width_variants') {
const wv = p.specs.variants;
widthOnlyHtml = '<div style="margin-bottom:16px">'
+'<div style="font-size:12px;font-weight:600;color:#334155;margin-bottom:8px">Välj variant</div>'
+'<div style="display:flex;flex-wrap:wrap;gap:6px" id="spWidthOnlyChips">'
+ wv.map((v, i) => {
const label = v.label || (v.width_mm ? v.width_mm + ' mm' : (v.length_mm ? v.length_mm + ' mm' : ''));
const priceStr = v.price != null ? v.price.toFixed(2) + ' kr' : '–';
return '<button onclick="spSelectWidthOnly('+i+')" class="sp-wo-chip" data-idx="'+i+'" style="padding:8px 16px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;transition:all .15s;background:#f8f9fa;color:#334155;border:2px solid #e5e7eb;display:flex;flex-direction:column;align-items:center;gap:2px">'
+'<span>'+label+'</span>'
+'<span style="font-size:10px;font-weight:400;color:#64748b">'+priceStr+'</span>'
+'</button>';
}).join('')
+'</div></div>';
}
// Style + width selector (foder, smyg, fönsterbänk etc)
let styleWidthHtml = '';
if(p.specs && p.specs.type === 'style_width_variants') {
const sw = p.specs.styles;
styleWidthHtml = '<div style="margin-bottom:16px" id="spStyleWidth">'
+'<div style="font-size:12px;font-weight:600;color:#334155;margin-bottom:8px">Välj utförande</div>'
+'<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px" id="spStyleChips">'
+ sw.map((s, i) => '<button onclick="spSelectStyle('+i+')" class="sp-style-chip" data-idx="'+i+'" style="padding:8px 16px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;transition:all .15s;'
+(i===0?'background:#024550;color:#fff;border:2px solid #024550':'background:#f8f9fa;color:#334155;border:2px solid #e5e7eb')
+'">'+s.style+(s.desc?' <span style="font-weight:400;opacity:.7;font-size:11px">('+s.desc+')</span>':'')+'</button>').join('')
+'</div>'
+'<div style="font-size:12px;font-weight:600;color:#334155;margin-bottom:4px">Välj bredd</div>'
+'<div id="spWidthChips" style="display:flex;flex-wrap:wrap;gap:6px"></div>'
+'<div id="spStyleResult" style="display:none;margin-top:10px"></div>'
+'</div>';
}
// Window sizes selector
let windowSizesHtml = '';
if(p.specs && p.specs.type === 'window_sizes') {
const ws = p.specs;
windowSizesHtml = '<div style="margin-bottom:16px" id="spWindowSizes">'
+'<div style="font-size:11px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px">Leverantör: '+ws.supplier+' | Modell: '+ws.model+'</div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:12px 0">'
+'<div><label style="font-size:12px;font-weight:600;color:#334155;display:block;margin-bottom:4px">Bredd (dm)</label>'
+'<select id="spWinWidth" onchange="spUpdateWindowSize()" style="width:100%;padding:10px 12px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit;background:#fff">'
+'<option value="">Välj bredd...</option>'
+ ws.widths.map(w => '<option value="'+w+'">'+w+' dm ('+(w*10)+' cm)</option>').join('')
+'</select></div>'
+'<div><label style="font-size:12px;font-weight:600;color:#334155;display:block;margin-bottom:4px">Höjd (dm)</label>'
+'<select id="spWinHeight" onchange="spUpdateWindowSize()" style="width:100%;padding:10px 12px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit;background:#fff">'
+'<option value="">Välj höjd...</option>'
+ ws.heights.map(h => '<option value="'+h+'">'+h+' dm ('+(h*10)+' cm)</option>').join('')
+'</select></div>'
+'</div>'
+'<div style="margin:8px 0"><label style="font-size:12px;font-weight:600;color:#334155;display:block;margin-bottom:4px">Antal</label>'
+'<div style="display:flex;align-items:center;gap:8px">'
+'<button onclick="spWinQty(-1)" style="width:36px;height:36px;border-radius:8px;border:1.5px solid #e5e7eb;background:#f8f9fa;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-family:inherit;color:#334155">−</button>'
+'<input id="spWinAntal" type="number" min="1" value="1" onchange="spUpdateWindowSize()" style="width:60px;padding:8px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:16px;font-weight:700;text-align:center;font-family:inherit">'
+'<button onclick="spWinQty(1)" style="width:36px;height:36px;border-radius:8px;border:1.5px solid #e5e7eb;background:#f8f9fa;font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-family:inherit;color:#334155">+</button>'
+'</div></div>'
+'<div id="spWinResult" style="display:none"></div>'
+'</div>';
}
// Variant chips
let variantChipsHtml = '';
if(variants.length > 1) {
variantChipsHtml = '<div style="margin-bottom:16px">'
+'<div style="font-size:11px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px">Välj variant</div>'
+'<div style="display:flex;flex-wrap:wrap;gap:6px" id="spVariantChips">'
+ variants.map((v, i) => {
const label = v[variantLabelKey] + variantLabelSuffix;
return '<button onclick="spSelectVariant('+i+')" class="sp-variant-chip" data-idx="'+i+'" style="padding:8px 16px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;transition:all .15s;'
+(i===0 ? 'background:#024550;color:#fff;border:2px solid #024550' : 'background:#f8f9fa;color:#334155;border:2px solid #e5e7eb')
+'">'+label+'</button>';
}).join('')
+'</div></div>';
}
// Images
const mainImgSrc = _spImages.length ? _spImages[0] : '';
let imgHtml = '<div style="width:100%;aspect-ratio:4/3;background:#f8f9fa;border-radius:12px;overflow:hidden;display:flex;align-items:center;justify-content:center;margin-bottom:12px;cursor:'+(mainImgSrc?'pointer':'default')+'" id="spMainImageWrap" onclick="spOpenFullscreen()">';
if(mainImgSrc) {
imgHtml += '<img id="spMainImage" src="'+mainImgSrc+'" style="width:100%;height:100%;object-fit:contain">';
} else {
imgHtml += '<div style="text-align:center;color:#cbd5e1"><svg viewBox="0 0 24 24" style="width:48px;height:48px;stroke:currentColor;fill:none;stroke-width:1"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg></div>';
}
imgHtml += '</div>';
// Thumbnails
if(_spImages.length > 1) {
imgHtml += '<div style="display:flex;gap:6px;flex-wrap:wrap" id="spThumbnails">'
+ _spImages.map((img, i) => '<img src="'+img+'" onclick="event.stopPropagation();spSetImage('+i+')" style="width:56px;height:56px;object-fit:cover;border-radius:8px;cursor:pointer;border:2px solid '+(i===0?'#024550':'#e5e7eb')+';transition:border-color .15s">').join('')
+'</div>';
}
// Dots
if(_spImages.length > 1) {
imgHtml += '<div style="display:flex;justify-content:center;gap:6px;margin-top:8px" id="spDots">'
+ _spImages.map((_, i) => '<span onclick="event.stopPropagation();spSetImage('+i+')" style="width:8px;height:8px;border-radius:50%;cursor:pointer;transition:background .15s;background:'+(i===0?'#024550':'#cbd5e1')+'"></span>').join('')
+'</div>';
}
// Badges
const greenBadge = p.greenTechEligible ? '<span style="display:inline-flex;align-items:center;gap:4px;background:linear-gradient(135deg,#059669,#10b981);color:#fff;font-size:11px;font-weight:700;padding:5px 12px;border-radius:8px"><svg viewBox="0 0 24 24" style="width:14px;height:14px;fill:currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>Grönt teknik-avdrag</span>' : '';
const rotBadge = p.rotEligible ? '<span style="display:inline-flex;align-items:center;gap:4px;background:linear-gradient(135deg,#2563eb,#3b82f6);color:#fff;font-size:11px;font-weight:700;padding:5px 12px;border-radius:8px;margin-left:6px">ROT-avdrag</span>' : '';
// Specs table
let specsHtml = '';
if(attributes.length) {
specsHtml = '<div>'
+'<div style="font-size:13px;font-weight:700;color:#1a1a1a;margin-bottom:8px">Specifikationer</div>'
+'<table style="width:100%;border-collapse:collapse;font-size:13px">'
+ attributes.map(a => '<tr style="border-top:1px solid #f1f5f9"><td style="padding:8px 0;color:#64748b;width:40%">'+a.key+'</td><td style="padding:8px 0;font-weight:600">'+a.value+'</td></tr>').join('')
+'</table></div>';
}
// Documents
let docsHtml = '';
if(documents.length) {
const typeLabels = {manual:'Manual',datasheet:'Datablad',certificate:'Certifikat',warranty:'Garanti',other:'Dokument'};
docsHtml = '<div>'
+'<div style="font-size:13px;font-weight:700;color:#1a1a1a;margin-bottom:8px">Dokument</div>'
+ documents.map(d => {
const tl = typeLabels[d.type] || d.type || 'Dokument';
return '<a href="'+d.file+'" target="_blank" download style="display:flex;align-items:center;gap:10px;padding:10px 12px;background:#f8f9fa;border-radius:8px;margin-bottom:6px;text-decoration:none;color:#334155;transition:background .15s" onmouseover="this.style.background=\'#f1f5f9\'" onmouseout="this.style.background=\'#f8f9fa\'">'
+'<svg viewBox="0 0 24 24" style="width:20px;height:20px;stroke:#0284c7;fill:none;stroke-width:2;flex-shrink:0"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>'
+'<div style="flex:1;min-width:0"><div style="font-weight:600;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+(d.name||'Dokument')+'</div>'
+'<span style="font-size:10px;color:#94a3b8">'+tl+'</span></div>'
+'<svg viewBox="0 0 24 24" style="width:16px;height:16px;stroke:#94a3b8;fill:none;stroke-width:2;flex-shrink:0"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>'
+'</a>';
}).join('')
+'</div>';
}
// Bottom section
let bottomHtml = '';
if(specsHtml || docsHtml) {
bottomHtml = '<div style="border-top:1px solid #f1f5f9;padding:20px 24px 24px;display:grid;grid-template-columns:'+(specsHtml && docsHtml ? '1fr 1fr' : '1fr')+';gap:24px">'
+ specsHtml + docsHtml +'</div>';
}
// Remove existing
document.getElementById('productSalesModal')?.remove();
const modal = document.createElement('div');
modal.id = 'productSalesModal';
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;max-width:1100px;width:100%;max-height:95vh;overflow-y:auto;box-shadow:0 25px 60px rgba(0,0,0,.3)">'
// Header
+'<div style="padding:14px 24px;border-bottom:1px solid #f1f5f9;display:flex;justify-content:space-between;align-items:center">'
+'<button onclick="document.getElementById(\'productSalesModal\').remove()" style="display:flex;align-items:center;gap:6px;background:none;border:none;cursor:pointer;color:#64748b;font-size:13px;font-weight:600;font-family:inherit;padding:4px 0"><svg viewBox="0 0 24 24" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2"><polyline points="15 18 9 12 15 6"/></svg>Tillbaka</button>'
+(isAdmin ? '<button onclick="document.getElementById(\'productSalesModal\').remove();editProduct(\''+p.id+'\')" style="display:flex;align-items:center;gap:6px;background:#f8f9fa;border:1px solid #e5e7eb;border-radius:8px;padding:7px 14px;cursor:pointer;font-size:12px;font-weight:600;color:#334155;font-family:inherit"><svg viewBox="0 0 24 24" style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>Redigera</button>' : '')
+'</div>'
// Main: bild + info + tillval vertikalt
+'<div style="padding:24px">'
// TOP ROW: bild + produktinfo
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:24px">'
+'<div>'+imgHtml+'</div>'
+'<div>'
+'<div style="font-size:11px;font-weight:600;color:#94a3b8;text-transform:uppercase;letter-spacing:1px;margin-bottom:4px">'+p.catLabel+'</div>'
+'<h2 style="font-size:24px;font-weight:800;color:#1a1a1a;margin:0 0 8px 0;line-height:1.2">'+p.name+'</h2>'
+(greenBadge || rotBadge ? '<div style="margin-bottom:12px">'+greenBadge+rotBadge+'</div>' : '')
+(p.desc ? '<p style="font-size:14px;color:#64748b;line-height:1.6;margin:0 0 20px 0">'+p.desc+'</p>' : '')
+ (widthOnlyHtml || styleWidthHtml || windowSizesHtml || variantChipsHtml)
+'<div id="spQtyArea"></div>'
+'<div id="spPriceArea" style="background:#f8f9fa;border-radius:12px;padding:16px"></div>'
+'</div>'
+'</div>'
// TILLVAL: 2 kolumner
+'<div id="spTillvalArea" style="margin-bottom:16px"></div>'
+'<div id="spColorSection" style="margin-top:16px">'
+'<div style="font-size:11px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.5px;margin-bottom:10px">F\u00e4rg</div>'
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">'
+'<div style="padding:12px;background:#f8f9fa;border:1.5px solid #e5e7eb;border-radius:10px;cursor:pointer;transition:all .15s" onclick="spOpenColorPicker(\'insida\')" onmouseover="this.style.borderColor=\'#94a3b8\'" onmouseout="this.style.borderColor=\'#e5e7eb\'">'
+'<div style="font-size:11px;font-weight:600;color:#64748b;margin-bottom:6px">F\u00e4rg insida</div>'
+'<div style="display:flex;align-items:center;gap:8px"><div id="spColorInsidaSwatch" style="width:36px;height:36px;border-radius:6px;background:#f0ece4;border:1px solid #ddd"></div><div><div id="spColorInsidaName" style="font-size:12px;font-weight:600;color:#1a1a1a">Vit</div><div id="spColorInsidaCode" style="font-size:10px;color:#94a3b8">RAL 9010</div></div></div>'
+'</div>'
+'<div style="padding:12px;background:#f8f9fa;border:1.5px solid #e5e7eb;border-radius:10px;cursor:pointer;transition:all .15s" onclick="spOpenColorPicker(\'utsida\')" onmouseover="this.style.borderColor=\'#94a3b8\'" onmouseout="this.style.borderColor=\'#e5e7eb\'">'
+'<div style="font-size:11px;font-weight:600;color:#64748b;margin-bottom:6px">F\u00e4rg utsida</div>'
+'<div style="display:flex;align-items:center;gap:8px"><div id="spColorUtsidaSwatch" style="width:36px;height:36px;border-radius:6px;background:#f0ece4;border:1px solid #ddd"></div><div><div id="spColorUtsidaName" style="font-size:12px;font-weight:600;color:#1a1a1a">Vit</div><div id="spColorUtsidaCode" style="font-size:10px;color:#94a3b8">RAL 9010</div></div></div>'
+'</div>'
+'</div>'
+'</div>'
+'<div id="spSprojsSection" style="margin-top:16px">'
+'<div style="font-size:11px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.5px;margin-bottom:10px">Spr\u00f6js</div>'
+'<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px" id="spSprojsGrid">'
+'<div onclick="spSelectSprojs(0)" class="sp-sprojs-opt" data-idx="0" style="border:2px solid #024550;border-radius:10px;padding:10px;text-align:center;cursor:pointer;background:#f0fdf4">'
+'<svg viewBox="0 0 60 80" style="width:48px;height:64px;margin:0 auto 6px"><rect x="2" y="2" width="56" height="76" rx="2" fill="none" stroke="#64748b" stroke-width="2"/><rect x="8" y="8" width="44" height="64" fill="#e0f2fe" stroke="#94a3b8" stroke-width="1"/></svg>'
+'<div style="font-size:11px;font-weight:600;color:#1a1a1a">Inga</div></div>'
+'<div onclick="spSelectSprojs(1)" class="sp-sprojs-opt" data-idx="1" style="border:2px solid #e5e7eb;border-radius:10px;padding:10px;text-align:center;cursor:pointer">'
+'<svg viewBox="0 0 60 80" style="width:48px;height:64px;margin:0 auto 6px"><rect x="2" y="2" width="56" height="76" rx="2" fill="none" stroke="#64748b" stroke-width="2"/><rect x="8" y="8" width="44" height="64" fill="#e0f2fe" stroke="#94a3b8" stroke-width="1"/><line x1="30" y1="8" x2="30" y2="72" stroke="#94a3b8" stroke-width="1.5"/><line x1="8" y1="40" x2="52" y2="40" stroke="#94a3b8" stroke-width="1.5"/></svg>'
+'<div style="font-size:10px;font-weight:500;color:#334155">1V, 1H</div></div>'
+'<div onclick="spSelectSprojs(2)" class="sp-sprojs-opt" data-idx="2" style="border:2px solid #e5e7eb;border-radius:10px;padding:10px;text-align:center;cursor:pointer">'
+'<svg viewBox="0 0 60 80" style="width:48px;height:64px;margin:0 auto 6px"><rect x="2" y="2" width="56" height="76" rx="2" fill="none" stroke="#64748b" stroke-width="2"/><rect x="8" y="8" width="44" height="64" fill="#e0f2fe" stroke="#94a3b8" stroke-width="1"/><line x1="30" y1="8" x2="30" y2="72" stroke="#94a3b8" stroke-width="1.5"/><line x1="8" y1="30" x2="52" y2="30" stroke="#94a3b8" stroke-width="1.5"/><line x1="8" y1="50" x2="52" y2="50" stroke="#94a3b8" stroke-width="1.5"/></svg>'
+'<div style="font-size:10px;font-weight:500;color:#334155">1V, 2H</div></div>'
+'<div onclick="spSelectSprojs(3)" class="sp-sprojs-opt" data-idx="3" style="border:2px solid #e5e7eb;border-radius:10px;padding:10px;text-align:center;cursor:pointer">'
+'<svg viewBox="0 0 60 80" style="width:48px;height:64px;margin:0 auto 6px"><rect x="2" y="2" width="56" height="76" rx="2" fill="none" stroke="#64748b" stroke-width="2"/><rect x="8" y="8" width="44" height="64" fill="#e0f2fe" stroke="#94a3b8" stroke-width="1"/><line x1="22" y1="8" x2="22" y2="72" stroke="#94a3b8" stroke-width="1.5"/><line x1="38" y1="8" x2="38" y2="72" stroke="#94a3b8" stroke-width="1.5"/><line x1="8" y1="30" x2="52" y2="30" stroke="#94a3b8" stroke-width="1.5"/><line x1="8" y1="50" x2="52" y2="50" stroke="#94a3b8" stroke-width="1.5"/></svg>'
+'<div style="font-size:10px;font-weight:500;color:#334155">2V, 2H</div></div>'
+'</div>'
+'</div>'
+'<button onclick="cartAddFromModal()" style="width:100%;padding:12px;background:linear-gradient(135deg,#059669,#10b981);color:#fff;border:none;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;display:flex;align-items:center;justify-content:center;gap:8px;transition:opacity .15s;box-shadow:0 4px 12px rgba(16,185,129,.3)" onmouseover="this.style.opacity=\'0.9\'" onmouseout="this.style.opacity=\'1\'"><svg viewBox="0 0 24 24" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>Lägg till i kalkyl</button>'
+'</div>'
+'</div>'
+ bottomHtml
+'</div>';
document.body.appendChild(modal);
// Load tillval
// Init width-only
_spSelectedWidthOnly = null;
// Init style+width chips
if(p.specs && p.specs.type === 'style_width_variants') {
_spSelectedStyle = 0;
_spSelectedWidth = null;
spRenderWidthChips();
}
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";}
// KVM input for per-unit products
var qtyArea=document.getElementById('spQtyArea');
if(qtyArea && p.unit && p.unit!=='st'){
var unitLabel=p.unit==='kvm'?'m²':p.unit;
qtyArea.innerHTML='<div style="margin-bottom:12px"><label style="font-size:11px;font-weight:600;color:#64748b;display:block;margin-bottom:4px">Antal '+unitLabel+'</label><input type="number" id="spQtyInput" value="1" min="1" placeholder="150" oninput="spUpdatePrice()" style="width:160px;padding:10px 14px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit"></div>';
}
await spLoadTillval(p.id);
spUpdatePrice();
}
async function spLoadTillval(productId) {
try {
const [svcRes, accRes, linkedRes] = await Promise.all([
fetch('/api/products.php?cat=tjanster&active=1'),
fetch('/api/products.php?cat=tillbehor&active=1'),
fetch('/api/products.php?services_for='+productId)
]);
const svcData = await svcRes.json();
const accData = await accRes.json();
const linkedData = await linkedRes.json();
const linkedMap = {}; if(linkedData.success) (linkedData.services||[]).forEach(s=>{linkedMap[s.service_id]={default_on:parseInt(s.default_on),hidden:parseInt(s.hidden||0),mandatory:parseInt(s.mandatory||0),default_variant:s.default_variant||null,default_qty:s.default_qty!=null?parseFloat(s.default_qty):null,min_qty:s.min_qty!=null?parseFloat(s.min_qty):null,max_qty:s.max_qty!=null?parseFloat(s.max_qty):null};});
const linked = Object.keys(linkedMap);
const allProducts = [...(svcData.success ? svcData.products : []), ...(accData.success ? accData.products : [])];
_spTillval = allProducts
.filter(s => linked.includes(s.id))
.map(s => {
let specs = null;
try { if(s.specs) specs = typeof s.specs === 'string' ? JSON.parse(s.specs) : s.specs; } catch(e){}
const lnk=linkedMap[s.id]||{}; const isOblig=parseInt(s.tillval_obligatorisk)||0; const isHid=parseInt(s.tillval_hidden)||0;
const linkMin = lnk.min_qty!=null ? lnk.min_qty : null;
const linkMax = lnk.max_qty!=null ? lnk.max_qty : null;
const linkDef = lnk.default_qty!=null ? lnk.default_qty : null;
const seedUserQty = linkDef!=null ? linkDef : 0;
return { id: s.id, name: s.name, price: parseFloat(s.price), unit: s.unit || 'st', checked: !!(lnk.default_on || isOblig || lnk.mandatory), included: !!(isOblig || lnk.mandatory), hidden: !!(lnk.hidden || isHid), qty: 1, userQty: seedUserQty, userL: 0, userW: 0, computedPrice: null, excluded: false, included: false, specs: specs, selectedStyle: 0, selectedVariant: null, linkMin: linkMin, linkMax: linkMax, linkDefaultQty: linkDef };
});
// Resolve default_variant to selectedStyle + selectedVariant
_spTillval.forEach(function(tv) {
var lnk = linkedMap[tv.id] || {};
if (lnk.default_variant && tv.specs && tv.specs.type === 'style_width_variants' && tv.specs.styles) {
var parts = lnk.default_variant.split('|');
for (var si = 0; si < tv.specs.styles.length; si++) {
if (tv.specs.styles[si].style === parts[0]) {
tv.selectedStyle = si;
if (parts[1]) {
for (var wi = 0; wi < tv.specs.styles[si].variants.length; wi++) {
if (String(tv.specs.styles[si].variants[wi].width_mm) === parts[1]) { tv.selectedVariant = wi; break; }
}
}
break;
}
}
}
});
spApplyRulesAndRender();
} catch(e) { console.error('Load tillval error:', e); }
}
function spApplyRulesAndRender() {
const p = _spProduct;
const variants = (p.specs && p.specs.variants) || [];
const rules = (p.specs && p.specs.rules) || [];
const selectedVariant = _spSelectedWindowSize || variants[_spSelectedVariant] || {};
// 1) Kör föräldraproduktens regler
const ruleResult = peApplyRules(rules, selectedVariant, _spTillval);
// Bygg berikad data med alla tillvals valda bredder
const enrichedVariant = Object.assign({}, selectedVariant);
_spTillval.forEach(tv => {
if(tv.checked && tv.specs && tv.selectedVariant !== null) {
const key = tv.id.replace(/-/g, '_');
if(tv.specs.type === 'style_width_variants') {
const style = tv.specs.styles[tv.selectedStyle] || tv.specs.styles[0];
const v = style && style.variants[tv.selectedVariant];
if(v) { enrichedVariant[key + '_width'] = v.width_mm || 0; enrichedVariant[key + '_style'] = tv.selectedStyle; }
} else if(tv.specs.type === 'width_variants') {
const v = tv.specs.variants[tv.selectedVariant];
if(v) enrichedVariant[key + '_width'] = v.width_mm || 0;
}
}
});
_spTillval.forEach(tv => {
// 2) Kör tillvalets EGNA regler med berikad variant-data (inkl andra tillvals val)
const tvRules = (tv.specs && tv.specs.rules) || [];
const tvResult = tvRules.length ? peApplyRules(tvRules, enrichedVariant, []) : {};
const selfResult = tvResult[''] || tvResult['__product'] || null;
// Merge: förälderns regel först, sedan tillvalets egen
const r = ruleResult[tv.id] || {};
if(selfResult) { Object.keys(selfResult).forEach(k => { if(selfResult[k] !== null && selfResult[k] !== undefined) r[k] = selfResult[k]; }); }
if(Object.keys(r).length) {
tv.excluded = !!r.exkluderad;
tv.included = !!r.inkluderad;
tv.computedPrice = r.pris !== null && r.pris !== undefined ? r.pris : null;
if(r.antal !== null && r.antal !== undefined) tv.qty = r.antal; else tv.qty = 1;
if(r.max !== undefined) tv.max = r.max;
if(r.min !== undefined) tv.min = r.min;
if(r.langd !== undefined) { tv.computedLangd = r.langd; if(tv.unit === "m" || tv.unit === "löpmeter") tv.userQty = Math.round(r.langd) / 1000; }
if(r.bredd !== undefined) tv.computedBredd = r.bredd;
if(tv.included) tv.checked = true;
if(tv.excluded) tv.checked = false;
} else {
tv.excluded = false; tv.included = false; tv.computedPrice = null; tv.qty = 1;
delete tv.max; delete tv.min; delete tv.computedLangd; delete tv.computedBredd;
}
});
spRenderTillval();
spUpdatePrice();
}
function spRenderTillval() {
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 price = tv.computedPrice !== null ? tv.computedPrice : tv.price;
// Override price from selected variant
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) price = sv.price;
} else if(tv.specs.type === 'width_variants') {
const wv = tv.specs.variants[tv.selectedVariant];
if(wv && wv.price != null) price = wv.price;
}
}
const stHasUserQty = tv.unit === 'st' && (tv.linkMax > 1 || tv.linkDefaultQty != null) && tv.userQty > 0;
const effectiveQty = tv.unit === 'm²' ? (tv.userL * tv.userW) : (tv.unit !== 'st' && tv.userQty > 0 ? tv.userQty : (stHasUserQty ? tv.userQty : tv.qty));
const totalPrice = price * (effectiveQty || (tv.unit === 'st' ? 1 : 0));
const unitLabel = tv.unit && tv.unit !== 'st' ? ' <span style="color:#94a3b8;font-weight:400;font-size:11px">('+price.toLocaleString('sv-SE')+' kr/'+tv.unit+')</span>' : '';
// Build variant selector for tillval with specs
let variantSelector = '';
if(tv.specs && tv.specs.type === 'style_width_variants' && tv.checked) {
const styles = tv.specs.styles;
const selStyle = styles[tv.selectedStyle] || styles[0];
variantSelector = '<div style="margin-top:8px;display:flex;flex-direction:column;gap:6px" onclick="event.preventDefault();event.stopPropagation()">'
+'<div style="display:flex;gap:4px;flex-wrap:wrap">'
+ styles.map((s, si) => '<button onclick="event.preventDefault();spSetTillvalStyle('+realIdx+','+si+')" style="padding:4px 10px;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;border:1.5px solid '+(si===tv.selectedStyle?'#024550':'#e5e7eb')+';background:'+(si===tv.selectedStyle?'#024550':'#fff')+';color:'+(si===tv.selectedStyle?'#fff':'#334155')+'">'+s.style+'</button>').join('')
+'</div>'
+'<select onchange="spSetTillvalVariant('+realIdx+',this.value)" style="padding:6px 10px;border:1.5px solid '+(tv.selectedVariant===null?'#f59e0b':'#e5e7eb')+';border-radius:6px;font-size:12px;font-family:inherit;background:'+(tv.selectedVariant===null?'#fffbeb':'#fff')+'">'
+'<option value="">⚠ Välj bredd (obligatorisk)...</option>'
+ selStyle.variants.map((v, vi) => {
const lbl = (v.label || (v.width_mm ? v.width_mm + ' mm' : (v.length_mm ? v.length_mm + ' mm' : '')));
const pr = v.price != null ? ' — ' + v.price.toFixed(2) + ' kr/' + tv.unit : ' — pris saknas';
var isDefault = tv.selectedVariant === null && tv.specs.default_width && v.width_mm == tv.specs.default_width; if(isDefault) tv.selectedVariant = vi;
return '<option value="'+vi+'"'+((vi===tv.selectedVariant)?' selected':'')+'>'+lbl+pr+'</option>';
}).join('')
+'</select></div>';
} else if(tv.specs && tv.specs.type === 'width_variants' && tv.checked) {
variantSelector = '<div style="margin-top:8px" 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;background:#fff;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' : (v.length_mm ? v.length_mm + ' mm' : ''));
const pr = v.price != null ? ' — ' + v.price.toFixed(2) + ' kr' : ' — pris saknas';
return '<option value="'+vi+'"'+(vi===tv.selectedVariant?' selected':'')+'>'+lbl+pr+'</option>';
}).join('')
+'</select></div>';
}
// Build quantity input based on unit
let qtyInput = '';
if(tv.unit === 'm²') {
qtyInput = '<div style="display:flex;align-items:center;gap:4px;margin-top:6px" onclick="event.preventDefault();event.stopPropagation()">'
+'<input type="number" min="0" step="0.1" value="'+(tv.userL||'')+'" placeholder="L" oninput="spSetTillvalDim('+realIdx+',\'L\',this.value)" style="width:56px;padding:5px 6px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;text-align:center">'
+'<span style="font-size:11px;color:#94a3b8">×</span>'
+'<input type="number" min="0" step="0.1" value="'+(tv.userW||'')+'" placeholder="B" oninput="spSetTillvalDim('+realIdx+',\'W\',this.value)" style="width:56px;padding:5px 6px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;text-align:center">'
+'<span style="font-size:11px;color:#64748b">= '+(tv.userL&&tv.userW?(tv.userL*tv.userW).toFixed(1):'0')+' m²</span>'
+'</div>';
} else if(tv.unit !== 'st') {
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>';
} else if(tv.checked && (tv.linkMax > 1 || tv.linkDefaultQty != null)) {
const minAttr = tv.linkMin != null ? tv.linkMin : 1;
const maxAttr = tv.linkMax != null ? ' max="'+tv.linkMax+'"' : '';
const valNow = tv.userQty > 0 ? tv.userQty : (tv.linkDefaultQty != null ? tv.linkDefaultQty : 1);
qtyInput = '<div style="display:flex;align-items:center;gap:6px;margin-top:6px" onclick="event.preventDefault();event.stopPropagation()">'
+'<label style="font-size:11px;color:#64748b">Antal</label>'
+'<input type="number" min="'+minAttr+'"'+maxAttr+' step="1" value="'+valNow+'" oninput="spSetTillvalQty('+realIdx+',this.value)" style="width:70px;padding:5px 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;text-align:center">'
+(tv.linkMax != null ? '<span style="font-size:11px;color:#94a3b8">/ max '+tv.linkMax+'</span>' : '')
+'</div>';
} else if(tv.qty > 1) {
qtyInput = '';
// show qty from rules inline
}
const qtyLabel = (tv.unit === 'st' && tv.qty > 1) ? ' <span style="color:#94a3b8;font-weight:400">×'+tv.qty+'</span>' : '';
return '<div style="padding:10px 14px;background:'+(tv.checked?'#f0fdf4':'#f8f9fa')+';border:1px solid '+(tv.checked?'#bbf7d0':'#e5e7eb')+';border-radius:10px;margin-bottom:6px;transition:all .15s">'
+'<label style="display:flex;align-items:'+(qtyInput?'flex-start':'center')+';gap:10px;cursor:'+(tv.included?'default':'pointer')+'">'
+'<input type="checkbox" '+(tv.checked?'checked':'')+' '+(tv.included?'disabled':'')+' onchange="spToggleTillval('+realIdx+')" style="width:18px;height:18px;accent-color:#059669;flex-shrink:0;margin-top:'+(qtyInput?'2px':'0')+'">'
+'<div style="flex:1"><div style="display:flex;align-items:center;justify-content:space-between">'
+'<span style="font-size:13px;font-weight:500;color:#334155">'+tv.name+qtyLabel+unitLabel+(tv.computedBredd?' <span style="color:#0284c7;font-size:11px;font-weight:400">B:'+Math.round(tv.computedBredd)+' mm</span>':'')+(tv.computedLangd?' <span style="color:#0284c7;font-size:11px;font-weight:400">L:'+Math.round(tv.computedLangd)+' mm</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>'
+'</label>'
+'</div>';
}).join('') + '</div>';
}
function spSetTillvalStyle(idx, styleIdx) {
_spTillval[idx].selectedStyle = styleIdx;
_spTillval[idx].selectedVariant = null;
spApplyRulesAndRender();
}
function spSetTillvalVariant(idx, val) {
_spTillval[idx].selectedVariant = val !== '' ? parseInt(val) : null;
spApplyRulesAndRender();
}
function spSetTillvalQty(idx, val) {
var tv = _spTillval[idx];
var n = parseFloat(val);
if (!isFinite(n)) n = 0;
if (tv.linkMin != null && n < tv.linkMin) n = tv.linkMin;
if (tv.linkMax != null && n > tv.linkMax) n = tv.linkMax;
tv.userQty = n;
spRenderTillval();
spUpdatePrice();
}
function spSetTillvalDim(idx, dim, val) {
if(dim === 'L') _spTillval[idx].userL = parseFloat(val) || 0;
else _spTillval[idx].userW = parseFloat(val) || 0;
spRenderTillval();
spUpdatePrice();
}
function spToggleTillval(idx) {
if(_spTillval[idx].included || _spTillval[idx].excluded) return;
_spTillval[idx].checked = !_spTillval[idx].checked;
spApplyRulesAndRender();
}
function spSelectVariant(idx) {
_spSelectedVariant = idx;
document.querySelectorAll('.sp-variant-chip').forEach(btn => {
const i = parseInt(btn.dataset.idx);
btn.style.background = i===idx ? '#024550' : '#f8f9fa';
btn.style.color = i===idx ? '#fff' : '#334155';
btn.style.borderColor = i===idx ? '#024550' : '#e5e7eb';
});
spApplyRulesAndRender();
}
let _spSelectedWidthOnly = null;
function spSelectWidthOnly(idx) {
_spSelectedWidthOnly = idx;
document.querySelectorAll('.sp-wo-chip').forEach((btn, i) => {
btn.style.background = i===idx ? '#024550' : '#f8f9fa';
btn.style.color = i===idx ? '#fff' : '#334155';
btn.style.borderColor = i===idx ? '#024550' : '#e5e7eb';
});
spUpdatePrice();
}
let _spSelectedWindowSize = null;
let _spSelectedStyle = 0;
let _spSelectedWidth = null;
function spSelectStyle(idx) {
_spSelectedStyle = idx;
_spSelectedWidth = null;
document.querySelectorAll('.sp-style-chip').forEach((btn, i) => {
btn.style.background = i===idx ? '#024550' : '#f8f9fa';
btn.style.color = i===idx ? '#fff' : '#334155';
btn.style.borderColor = i===idx ? '#024550' : '#e5e7eb';
});
spRenderWidthChips();
document.getElementById('spStyleResult').style.display = 'none';
spUpdatePrice();
}
function spRenderWidthChips() {
const el = document.getElementById('spWidthChips');
if(!el || !_spProduct || !_spProduct.specs || _spProduct.specs.type !== 'style_width_variants') return;
const style = _spProduct.specs.styles[_spSelectedStyle];
if(!style) return;
el.innerHTML = style.variants.map((v, i) => {
const label = v.label || (v.width_mm ? v.width_mm + ' mm' : (v.length_mm ? v.length_mm + ' mm' : ''));
const priceStr = v.price != null ? v.price.toFixed(2) + ' kr' : '–';
return '<button onclick="spSelectWidth('+i+')" class="sp-width-chip" data-idx="'+i+'" style="padding:6px 14px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;transition:all .15s;background:#f8f9fa;color:#334155;border:2px solid #e5e7eb;display:flex;flex-direction:column;align-items:center;gap:2px">'
+'<span>'+label+'</span>'
+'<span style="font-size:10px;font-weight:400;color:#64748b">'+priceStr+'</span>'
+'</button>';
}).join('');
}
function spSelectWidth(idx) {
_spSelectedWidth = idx;
document.querySelectorAll('.sp-width-chip').forEach((btn, i) => {
btn.style.background = i===idx ? '#024550' : '#f8f9fa';
btn.style.color = i===idx ? '#fff' : '#334155';
btn.style.borderColor = i===idx ? '#024550' : '#e5e7eb';
});
const style = _spProduct.specs.styles[_spSelectedStyle];
const v = style.variants[idx];
const resEl = document.getElementById('spStyleResult');
if(resEl && v) {
const label = v.label || (v.width_mm ? v.width_mm + ' mm' : (v.length_mm ? v.length_mm + ' mm' : ''));
if(v.price != null) {
resEl.innerHTML = '<div style="background:#f0fdf4;border:1px solid #bbf7d0;border-radius:10px;padding:12px;display:flex;justify-content:space-between;align-items:center">'
+'<div><span style="font-weight:600;color:#334155">'+style.style+' '+label+'</span></div>'
+'<div style="font-size:20px;font-weight:800;color:#024550">'+v.price.toFixed(2)+' kr/'+(_spProduct.unit||'st')+'</div></div>';
} else {
resEl.innerHTML = '<div style="background:#fef3c7;border:1px solid #fde68a;border-radius:10px;padding:12px;text-align:center;color:#d97706;font-weight:600;font-size:13px">Pris ej angivet – kontakta Hedlunds</div>';
}
resEl.style.display = 'block';
}
spUpdatePrice();
}
function spWinQty(delta) {
const el = document.getElementById('spWinAntal');
if(!el) return;
let v = parseInt(el.value) || 1;
v = Math.max(1, v + delta);
el.value = v;
spUpdateWindowSize();
}
function spUpdateWindowSize() {
const p = _spProduct;
if(!p || !p.specs || p.specs.type !== 'window_sizes') return;
const wSel = document.getElementById('spWinWidth');
const hSel = document.getElementById('spWinHeight');
const resEl = document.getElementById('spWinResult');
if(!wSel || !hSel || !resEl) return;
const w = parseInt(wSel.value);
const h = parseInt(hSel.value);
if(!w || !h) {
resEl.style.display = 'none';
_spSelectedWindowSize = null;
spUpdatePrice();
return;
}
// Filter heights based on selected width
const availH = p.specs.sizes.filter(s => s.w === w).map(s => s.h);
Array.from(hSel.options).forEach(opt => {
if(!opt.value) return;
const hv = parseInt(opt.value);
opt.disabled = !availH.includes(hv);
opt.style.color = availH.includes(hv) ? '' : '#ccc';
});
// Filter widths based on selected height
const availW = p.specs.sizes.filter(s => s.h === h).map(s => s.w);
Array.from(wSel.options).forEach(opt => {
if(!opt.value) return;
const wv = parseInt(opt.value);
opt.disabled = !availW.includes(wv);
opt.style.color = availW.includes(wv) ? '' : '#ccc';
});
const match = p.specs.sizes.find(s => s.w === w && s.h === h);
if(match) {
_spSelectedWindowSize = match;
// Set price from selected model or auto-select single model
if(p.specs.models && p.specs.models.length) {
var availModels = p.specs.models.filter(function(m){ return match[m.key] != null; });
if(availModels.length === 1) _spSelectedWinModel = availModels[0].key;
if(_spSelectedWinModel && match[_spSelectedWinModel] != null) _spSelectedWindowSize.price = match[_spSelectedWinModel];
}
let noteHtml = '';
if(match.note && match.note !== 'None' && match.note !== '') {
const noteColor = match.note.toLowerCase().includes('laminated') ? '#d97706' :
match.note.toLowerCase().includes("can't") || match.note.toLowerCase().includes('no guarantee') ? '#dc2626' : '#64748b';
noteHtml = '<div style="margin-top:6px;padding:6px 10px;border-radius:6px;font-size:12px;font-weight:600;color:'+noteColor+';background:'+(noteColor==='#dc2626'?'#fef2f2':'#fffbeb')+'">'+match.note+'</div>';
}
const antal = parseInt(document.getElementById('spWinAntal')?.value) || 1;
const _cr = _currencyRates[p.costCurrency||'EUR'] || _currencyRates['EUR'] || 1;
const _mk = p.markupType==='percent'&&p.markupValue ? (1+p.markupValue/100) : 1;
const _mkA = p.markupType==='amount'&&p.markupValue ? p.markupValue*antal : 0;
if(p.specs.models && p.specs.models.length) {
var modelRows = '';
var hasAny = false;
p.specs.models.forEach(function(m) {
var mp = match[m.key];
if(mp != null) {
hasAny = true;
var mTotal = Math.round(mp * antal * _cr * _mk + _mkA);
var selected = _spSelectedWinModel === m.key;
modelRows += '<div onclick="spSelectWinModel(\''+m.key+'\','+mp+')" style="display:flex;justify-content:space-between;align-items:center;padding:10px 14px;border:2px solid '+(selected?'#3b82f6':'#e5e7eb')+';border-radius:8px;cursor:pointer;background:'+(selected?'#f0f9ff':'#fff')+'">'
+'<div style="font-weight:600;font-size:13px;color:#334155">'+m.label+'</div>'
+'<div style="font-weight:800;font-size:15px;color:#024550">'+mTotal.toLocaleString('sv-SE')+' kr</div>'
+'</div>';
}
});
if(hasAny) {
resEl.innerHTML = '<div style="margin-top:8px">'
+'<div style="font-size:12px;font-weight:600;color:#334155;margin-bottom:6px">'+w+' \u00d7 '+h+' dm ('+(w*10)+' \u00d7 '+(h*10)+' cm) \u2014 v\u00e4lj modell:</div>'
+'<div style="display:grid;gap:6px">'+modelRows+'</div></div>';
} else {
resEl.innerHTML = '<div style="background:#fef2f2;border:1px solid #fecaca;border-radius:10px;padding:14px;margin-top:8px;text-align:center;color:#dc2626;font-size:13px;font-weight:600">Inga modeller i '+w+'\u00d7'+h+'</div>';
}
if(_spSelectedWinModel && _spSelectedWindowSize && _spSelectedWindowSize[_spSelectedWinModel] != null) _spSelectedWindowSize.price = _spSelectedWindowSize[_spSelectedWinModel];
resEl.style.display = 'block';
spApplyRulesAndRender();
return;
}
var unitPrice = match.price || 0;
const totalEur = unitPrice * antal;
const totalSek = Math.round(totalEur * _cr * _mk + _mkA);
resEl.innerHTML = '<div style="background:#f0fdf4;border:1px solid #bbf7d0;border-radius:10px;padding:14px;margin-top:8px">'
+'<div style="display:flex;justify-content:space-between;align-items:center">'
+'<div><div style="font-size:13px;font-weight:600;color:#334155">'+w+' \u00d7 '+h+' dm <span style="color:#94a3b8;font-weight:400">('+(w*10)+' \u00d7 '+(h*10)+' cm)</span></div>'
+'<div style="font-size:11px;color:#64748b;margin-top:2px">Area: '+((w*h)/100).toFixed(2)+' m\u00b2 | Art.nr: '+(match.article_id||'')+'</div></div>'
+'<div style="text-align:right"><div style="font-size:22px;font-weight:800;color:#024550">'+totalSek.toLocaleString('sv-SE')+' kr</div>'
+(antal > 1 ? '<div style="font-size:11px;color:#64748b">'+antal+' st \u00d7 '+Math.round(unitPrice*_cr*_mk).toLocaleString('sv-SE')+' kr</div>' : '')
+'</div></div>'
+noteHtml
resEl.style.display = 'block';
} else {
_spSelectedWindowSize = null;
resEl.innerHTML = '<div style="background:#fef2f2;border:1px solid #fecaca;border-radius:10px;padding:14px;margin-top:8px;text-align:center;color:#dc2626;font-size:13px;font-weight:600">Denna storlekskombination finns ej tillgänglig</div>';
resEl.style.display = 'block';
}
spApplyRulesAndRender();
}
// === SPRÖJS ===
let _spSelectedSprojs = 0;
function spSelectSprojs(idx) {
_spSelectedSprojs = idx;
document.querySelectorAll('.sp-sprojs-opt').forEach(el => {
const i = parseInt(el.dataset.idx);
el.style.borderColor = i === idx ? '#024550' : '#e5e7eb';
el.style.background = i === idx ? '#f0fdf4' : '';
});
}
// === FÄRGVÄLJARE ===
const _spColors = [
{ name:'Vit', code:'RAL 9010', hex:'#f0ece4', group:'standard' },
{ name:'Röd', code:'RAL 3011', hex:'#781f1c', group:'kulör' },
{ name:'Antracitgrå', code:'RAL 7016', hex:'#383e42', group:'kulör' },
{ name:'Matt svart', code:'RAL 9005M', hex:'#1a1a1a', group:'kulör' },
{ name:'Grafitsvart', code:'RAL 9011', hex:'#262626', group:'kulör' },
{ name:'Ljusgrå', code:'RAL 7035', hex:'#b0b0a8', group:'kulör' },
{ name:'Kvartsgrå', code:'RAL 7039', hex:'#6b6560', group:'kulör' },
{ name:'Basaltgrå', code:'RAL 7012', hex:'#585c5e', group:'kulör' },
{ name:'Mörkbrun', code:'RAL 8019', hex:'#3b3332', group:'kulör' },
{ name:'Svart', code:'RAL 9005', hex:'#0e0e10', group:'kulör' },
{ name:'Grön', code:'RAL 6009', hex:'#1f3a28', group:'kulör' },
{ name:'Blå', code:'RAL 5011', hex:'#1a2b3c', group:'kulör' },
];
let _spSelectedColors = { insida: _spColors[0], utsida: _spColors[0] };
function spOpenColorPicker(side) {
document.getElementById('spColorPickerModal')?.remove();
const isInsida = side === 'insida';
const current = _spSelectedColors[side];
const title = isInsida ? 'Kulör insida' : 'Kulör utsida';
const note = isInsida ? 'Insidan är normalt alltid vit.' : 'Välj vilken färg du ska ha på utsidan.';
const modal = document.createElement('div');
modal.id = 'spColorPickerModal';
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:99999;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;max-width:600px;width:100%;max-height:80vh;overflow-y:auto;box-shadow:0 25px 60px rgba(0,0,0,.3)">'
+'<div style="padding:20px 24px;border-bottom:1px solid #f1f5f9;display:flex;justify-content:space-between;align-items:center">'
+'<div><h3 style="font-size:18px;font-weight:700;margin:0;color:#1a1a1a">'+title+'</h3>'
+'<div style="font-size:12px;color:#94a3b8;margin-top:2px">'+current.name+'</div></div>'
+'<button onclick="document.getElementById(\'spColorPickerModal\').remove()" style="display:flex;align-items:center;gap:4px;background:none;border:none;cursor:pointer;font-size:14px;font-weight:700;color:#334155;font-family:inherit">Stäng <svg viewBox="0 0 24 24" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2"><polyline points="18 15 12 9 6 15"/></svg></button>'
+'</div>'
+'<div style="padding:20px 24px">'
+'<p style="font-size:13px;color:#64748b;margin:0 0 16px">'+note+'</p>'
+'<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:10px">'
+ _spColors.map((c, i) => {
const selected = current.code === c.code;
return '<div onclick="spSelectColor(\''+side+'\','+i+')" style="cursor:pointer;border:2px solid '+(selected?'#024550':'#e5e7eb')+';border-radius:10px;padding:8px;text-align:center;transition:all .15s;background:'+(selected?'#f0fdf4':'#fff')+'" onmouseover="this.style.borderColor=\'#94a3b8\'" onmouseout="this.style.borderColor=\''+(selected?'#024550':'#e5e7eb')+'\'">'
+'<div style="width:100%;aspect-ratio:1;background:'+c.hex+';border-radius:6px;border:1px solid rgba(0,0,0,.1);margin-bottom:6px"></div>'
+'<div style="font-size:11px;font-weight:600;color:#1a1a1a">'+c.name+'</div>'
+'<div style="font-size:9px;color:#94a3b8">'+c.code+'</div>'
+'</div>';
}).join('')
+'</div></div></div>';
document.body.appendChild(modal);
}
function spSelectColor(side, idx) {
_spSelectedColors[side] = _spColors[idx];
const c = _spColors[idx];
document.getElementById('spColor'+(side==='insida'?'Insida':'Utsida')+'Swatch').style.background = c.hex;
document.getElementById('spColor'+(side==='insida'?'Insida':'Utsida')+'Name').textContent = c.name;
document.getElementById('spColor'+(side==='insida'?'Insida':'Utsida')+'Code').textContent = c.code;
document.getElementById('spColorPickerModal')?.remove();
}
function spUpdatePrice() {
const el = document.getElementById('spPriceArea');
if(!el) return;
const p = _spProduct;
const variants = (p.specs && p.specs.variants) || [];
const rules = (p.specs && p.specs.rules) || [];
const sv = variants[_spSelectedVariant] || {};
const ruleResult = peApplyRules(rules, sv, _spTillval);
let displayPrice = p.price, greenDeduction = 0, isPerUnit = false, unitLabel = '';
// Width-only override
if(p.specs && p.specs.type === 'width_variants') {
if(_spSelectedWidthOnly !== null) {
const v = p.specs.variants[_spSelectedWidthOnly];
if(v && v.price != null) {
displayPrice = v.price;
unitLabel = 'kr/' + (p.unit || 'st');
isPerUnit = true;
} else {
el.innerHTML = '<div style="text-align:center;color:#d97706;font-size:13px;padding:8px">Pris ej angivet</div>';
return;
}
} else {
el.innerHTML = '<div style="text-align:center;color:#94a3b8;font-size:13px;padding:8px">Välj variant för att se pris</div>';
return;
}
}
// Style+width override
if(p.specs && p.specs.type === 'style_width_variants') {
if(_spSelectedWidth !== null) {
const style = p.specs.styles[_spSelectedStyle];
const v = style && style.variants[_spSelectedWidth];
if(v && v.price != null) {
displayPrice = v.price;
unitLabel = 'kr/' + (p.unit || 'st');
isPerUnit = true;
} else {
el.innerHTML = '<div style="text-align:center;color:#d97706;font-size:13px;padding:8px">Pris ej angivet</div>';
return;
}
} else {
el.innerHTML = '<div style="text-align:center;color:#94a3b8;font-size:13px;padding:8px">Välj utförande och bredd för att se pris</div>';
return;
}
}
// Window sizes override
if(p.specs && p.specs.type === 'window_sizes') {
if(_spSelectedWindowSize) {
// If models exist but none selected, show message
if(p.specs.models && p.specs.models.length && !_spSelectedWinModel) {
el.innerHTML = '<div style="text-align:center;color:#d97706;font-size:13px;padding:12px;background:#fef3c7;border-radius:8px">Välj en modell ovan för att se pris</div>';
return;
}
const winAntal = parseInt(document.getElementById('spWinAntal')?.value) || 1;
let eurPrice = (_spSelectedWindowSize.price || 0) * winAntal;
// Konvertera till SEK
const currency = p.costCurrency || 'EUR';
const rate = _currencyRates[currency] || _currencyRates['EUR'] || 1;
let sekPrice = eurPrice * rate;
// Applicera påslag
if(p.markupType === 'percent' && p.markupValue) {
sekPrice = sekPrice * (1 + p.markupValue / 100);
} else if(p.markupType === 'amount' && p.markupValue) {
sekPrice = sekPrice + (p.markupValue * winAntal);
}
displayPrice = Math.round(sekPrice);
unitLabel = 'kr';
isPerUnit = true;
} else {
el.innerHTML = '<div style="text-align:center;color:#94a3b8;font-size:13px;padding:8px">Välj bredd och höjd för att se pris</div>';
return;
}
}
var spQtyEl=document.getElementById('spQtyInput');var spQty=spQtyEl?parseInt(spQtyEl.value)||1:1;
if(variants.length) {
if(sv.totalt !== undefined) {
displayPrice = parseFloat(sv.totalt) || 0;
if(sv.gron_teknik !== undefined) greenDeduction = parseFloat(sv.gron_teknik) || 0;
} else if(sv.pris_per_m2 !== undefined) {
displayPrice = parseFloat(sv.pris_per_m2) || 0;
isPerUnit = true; unitLabel = 'kr/m²';
} else if(sv.price !== undefined) {
displayPrice = parseFloat(sv.price) || 0;
} else {
displayPrice = p.price;
}
}
if(p.unit && p.unit!=='st' && spQty>1 && !isPerUnit) displayPrice=displayPrice*spQty;
if(ruleResult['__product'] && ruleResult['__product'].pris !== null) {
displayPrice = ruleResult['__product'].pris;
}
let tillvalTotal = 0;
_spTillval.filter(tv => tv.checked && !tv.excluded).forEach(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;
}
}
const stHasUserQty = (tv.unit === 'st' || !tv.unit) && (tv.linkMax > 1 || tv.linkDefaultQty != null) && tv.userQty > 0;
const eq = tv.unit === 'm²' ? (tv.userL * tv.userW) : (tv.unit && tv.unit !== 'st' && tv.userQty > 0 ? tv.userQty : (stHasUserQty ? tv.userQty : tv.qty));
tillvalTotal += pr * (eq || (tv.unit === 'st' || !tv.unit ? 1 : 0));
});
let html = '';
if(isPerUnit) {
var unitTotal = displayPrice * spQty;
html = '<div style="display:flex;justify-content:space-between;align-items:baseline">'
+'<span style="font-size:15px;font-weight:700;color:#1a1a1a">Pris</span>'
+'<span style="font-size:24px;font-weight:800;color:#024550">'+displayPrice.toLocaleString('sv-SE')+' '+unitLabel+'</span>'
+'</div>';
if(tillvalTotal > 0) {
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;padding-top:8px;border-top:1px solid #e5e7eb">'
+'<span style="font-size:13px;color:#64748b">Tillval</span>'
+'<span style="font-size:14px;font-weight:600;color:#334155">+'+tillvalTotal.toLocaleString('sv-SE')+' kr</span>'
+'</div>';
}
// Skatteavdrag baserat på produktens taxType
var tax = typeof getTax === 'function' ? getTax() : {gt20:20, gt50:50, rot:30, rotMax:50000};
var productTaxType = p.taxType || p.tax_type || 'NONE';
var unitTaxDeduction = 0;
var unitTotalWithTillval = unitTotal + tillvalTotal;
if(productTaxType === 'GT20') {
unitTaxDeduction = Math.min(Math.round(unitTotalWithTillval * tax.gt20 / 100), 50000);
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-top:6px"><span style="font-size:13px;color:#059669">Grönt teknik (GT20)</span><span style="font-size:15px;font-weight:600;color:#059669">-'+unitTaxDeduction.toLocaleString('sv-SE')+' kr</span></div>';
} else if(productTaxType === 'GT50') {
unitTaxDeduction = Math.min(Math.round(unitTotalWithTillval * tax.gt50 / 100), 50000);
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-top:6px"><span style="font-size:13px;color:#059669">Skattereduktion (GT50)</span><span style="font-size:15px;font-weight:600;color:#059669">-'+unitTaxDeduction.toLocaleString('sv-SE')+' kr</span></div>';
} else if(productTaxType === 'ROT') {
var laborEst = Math.round(unitTotalWithTillval * 0.5);
unitTaxDeduction = Math.min(Math.round(laborEst * tax.rot / 100), tax.rotMax);
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-top:6px"><span style="font-size:13px;color:#2563eb">ROT-avdrag (' + tax.rot + '% på arbete)</span><span style="font-size:15px;font-weight:600;color:#2563eb">-'+unitTaxDeduction.toLocaleString('sv-SE')+' kr</span></div>';
}
var perUnitGrand = unitTotalWithTillval - unitTaxDeduction;
html += '<div style="border-top:2px solid #e5e7eb;padding-top:10px;margin-top:8px;display:flex;justify-content:space-between;align-items:center">'
+'<span style="font-size:15px;font-weight:700;color:#1a1a1a">Att betala</span>'
+'<span style="font-size:24px;font-weight:800;color:#024550">'+perUnitGrand.toLocaleString('sv-SE')+' kr</span>'
+'</div>';
} else {
// Hidden tillval (installation etc)
var hiddenTotal=0, hiddenItems=[];
_spTillval.filter(function(tv){return tv.checked && !tv.excluded && tv.hidden;}).forEach(function(tv){
var hiPrice=tv.price*(tv.unit===p.unit&&spQty>1?spQty:1); hiddenTotal+=hiPrice; hiddenItems.push({name:tv.name,price:hiPrice});
if(tv.childTillval) tv.childTillval.filter(function(ch){return ch.obligatorisk;}).forEach(function(ch){
hiddenTotal+=ch.price; hiddenItems.push({name:ch.name,price:ch.price,isChild:true});
});
});
// Product name + hidden items + totalt
if(hiddenTotal > 0) {
var arbete = displayPrice - hiddenTotal;
html += '<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(function(hi){
html += '<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>';
});
html += '<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>';
} else {
html += '<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">'+displayPrice.toLocaleString('sv-SE')+' kr</span></div>';
}
// Skatteavdrag baserat på produktens taxType (EN typ per installation)
var tax = typeof getTax === 'function' ? getTax() : {gt20:20, gt50:50, rot:30, rotMax:50000};
var productTaxType = p.taxType || p.tax_type || 'NONE';
var taxDeduction = 0;
var taxLabel = '';
var totalWithTillval = displayPrice + tillvalTotal;
if(productTaxType === 'GT20') {
taxDeduction = Math.min(Math.round(totalWithTillval * tax.gt20 / 100), 50000);
taxLabel = 'Grönt teknik-avdrag (GT20)';
} else if(productTaxType === 'GT50') {
taxDeduction = Math.min(Math.round(totalWithTillval * tax.gt50 / 100), 50000);
taxLabel = 'Skattereduktion (GT50)';
} else if(productTaxType === 'ROT') {
// ROT enbart på arbete — uppskatta 50% arbete
var laborEst = Math.round(totalWithTillval * 0.5);
taxDeduction = Math.min(Math.round(laborEst * tax.rot / 100), tax.rotMax);
taxLabel = 'ROT-avdrag (' + tax.rot + '% på arbete)';
}
// Bakåtkompatibilitet: om greenDeduction kommer från variant-data, använd den
if(greenDeduction > 0 && taxDeduction === 0) {
taxDeduction = greenDeduction;
taxLabel = 'Grönt teknik-avdrag';
}
if(taxDeduction > 0) {
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px"><span style="font-size:13px;color:#059669">'+taxLabel+'</span><span style="font-size:15px;font-weight:600;color:#059669">-'+taxDeduction.toLocaleString('sv-SE')+' kr</span></div>';
}
var rotDeduction = 0; // Behåll variabelnamnet för grandTotal-beräkningen nedan
// Itemized tillval
if(tillvalTotal > 0) {
html += '<div style="font-size:11px;font-weight:600;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px;margin:4px 0">Tillval</div>';
_spTillval.filter(function(tv){return tv.checked && !tv.excluded && !tv.hidden;}).forEach(function(tv){
var tvPr = tv.price;
if(tv.specs && tv.selectedVariant !== null && tv.selectedVariant !== undefined && tv.specs.variants && tv.specs.variants[tv.selectedVariant]) {
var sv2 = tv.specs.variants[tv.selectedVariant];
tvPr = sv2.totalt || sv2.price || sv2.att_betala || tvPr;
}
var stHasUserQty2 = (tv.unit === 'st' || !tv.unit) && (tv.linkMax > 1 || tv.linkDefaultQty != null) && tv.userQty > 0;
var tvEq = (tv.unit && tv.unit !== 'st' && tv.userQty > 0) ? tv.userQty : (stHasUserQty2 ? tv.userQty : (tv.qty||1));
var tvLine = tvPr * tvEq;
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">+'+tvLine.toLocaleString('sv-SE')+' kr</span></div>';
if(tv.childTillval) tv.childTillval.filter(function(ch){return ch.obligatorisk;}).forEach(function(ch){
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:3px;padding-left:16px"><span style="font-size:11px;color:#94a3b8">'+ch.name+'</span><span style="font-size:12px;font-weight:600;color:#94a3b8">+'+ch.price.toLocaleString('sv-SE')+' kr</span></div>';
});
});
}
const grandTotal = displayPrice + tillvalTotal - taxDeduction;
html += '<div style="border-top:2px solid #e5e7eb;padding-top:10px;margin-top:4px;display:flex;justify-content:space-between;align-items:center">'
+'<span style="font-size:15px;font-weight:700;color:#1a1a1a">Att betala</span>'
+'<span style="font-size:24px;font-weight:800;color:#024550">'+grandTotal.toLocaleString('sv-SE')+' kr</span>'
+'</div>';
}
el.innerHTML = html;
}
function spSetImage(idx) {
_spCurrentImage = idx;
const mainImg = document.getElementById('spMainImage');
if(mainImg) mainImg.src = _spImages[idx];
document.querySelectorAll('#spThumbnails img').forEach((t, i) => { t.style.borderColor = i===idx ? '#024550' : '#e5e7eb'; });
document.querySelectorAll('#spDots span').forEach((d, i) => { d.style.background = i===idx ? '#024550' : '#cbd5e1'; });
}
// === KALKYL VARUKORG (CART) ===
let _cartItems = [];