// produkter-editor.js - Product edit modal
function editProduct(id) {
const p = catalogProducts.find(x => x.id === id);
if(!p) return;
const cats = [{v:'solceller',l:'Solceller'},{v:'batteri',l:'Batteri'},{v:'batteri_utbyggnad',l:'Batteri Utbyggnad'},{v:'laddbox',l:'Laddbox'},{v:'fonster',l:'Fönster'},{v:'dorrar',l:'Dörrar'},{v:'tak',l:'Tak'},{v:'varmepump',l:'Värmepump'},{v:'taktvatt',l:'Taktvätt'},{v:'isolering',l:'Isolering'},{v:'tjanster',l:'Tjänster'},{v:'tillbehor',l:'Tillbehör'}];
const stocks = [{v:'I lager',c:'green'},{v:'Få kvar',c:'yellow'},{v:'Beställning',c:'blue'},{v:'Slut',c:'red'}];
_peVariants = (p.specs && p.specs.variants) ? JSON.parse(JSON.stringify(p.specs.variants)) : [];
_peSpecsType = (p.specs && p.specs.type) || '';
_peStyles = (p.specs && p.specs.styles) ? JSON.parse(JSON.stringify(p.specs.styles)) : [];
_peSizes = (p.specs && p.specs.sizes) ? JSON.parse(JSON.stringify(p.specs.sizes)) : [];
_peSpecsMeta = p.specs ? JSON.parse(JSON.stringify(p.specs)) : {};
_peWinModels = (p.specs && p.specs.models) ? JSON.parse(JSON.stringify(p.specs.models)) : [];
const modal = document.createElement('div');
modal.id = 'productEditModal';
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:1520px;width:min(1520px,96vw);max-height:95vh;overflow-y:auto;box-shadow:0 25px 60px rgba(0,0,0,.3)">'
+'<div style="padding:16px 24px;border-bottom:1px solid #f1f5f9;display:flex;justify-content:space-between;align-items:center">'
+'<h2 style="font-size:18px;font-weight:700;color:#1a1a1a;margin:0">Redigera: '+p.name+'</h2>'
+'<button onclick="this.closest(\'#productEditModal\').remove()" style="background:none;border:none;cursor:pointer;padding:4px"><svg viewBox="0 0 24 24" style="width:20px;height:20px;stroke:#94a3b8;fill:none;stroke-width:2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>'
+'</div>'
+'<div style="padding:10px 24px 0">'
+'<div style="display:flex;gap:8px;flex-wrap:wrap">'
+'<a href="/maps/view.php?file=pages/productlist.php" target="_blank" rel="noreferrer" style="display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border:1px solid #dbe7ef;border-radius:999px;background:#f8fbfd;color:#1f5160;text-decoration:none;font-size:12px;font-weight:700;font-family:inherit">productlist.php <span style="color:#6b8793;font-weight:800">DEV-0DB99B18</span></a>'
+'<a href="/maps/view.php?file=js/product-editor.js" target="_blank" rel="noreferrer" style="display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border:1px solid #dbe7ef;border-radius:999px;background:#f8fbfd;color:#1f5160;text-decoration:none;font-size:12px;font-weight:700;font-family:inherit">product-editor.js <span style="color:#6b8793;font-weight:800">DEV-E7734603</span></a>'
+'<a href="/maps/view.php?file=css/productlist.css" target="_blank" rel="noreferrer" style="display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border:1px solid #dbe7ef;border-radius:999px;background:#f8fbfd;color:#1f5160;text-decoration:none;font-size:12px;font-weight:700;font-family:inherit">productlist.css <span style="color:#6b8793;font-weight:800">DEV-4059252E</span></a>'
+'</div>'
+'</div>'
+'<style>'
+'#productEditModal input:not([type=checkbox]):not([type=file]),#productEditModal select,#productEditModal textarea{max-width:100%;min-width:0;box-sizing:border-box}'
+'#productEditModal .pe-layout{padding:20px 24px;display:grid;grid-template-columns:minmax(260px,300px) minmax(0,1fr);gap:18px;align-items:start}'
+'#productEditModal .pe-left{min-width:0;padding-right:16px;border-right:1px solid #e5e7eb;overflow:hidden}'
+'#productEditModal .pe-right{min-width:0;overflow:hidden}'
+'#productEditModal .pe-form-grid{display:grid;grid-template-columns:72px minmax(0,1fr);gap:8px 10px;align-items:center;font-size:13px}'
+'@media (max-width: 1180px){#productEditModal .pe-layout{grid-template-columns:1fr}#productEditModal .pe-left{padding-right:0;border-right:none;border-bottom:1px solid #e5e7eb;padding-bottom:16px}#productEditModal .pe-right{padding-top:4px}}'
+'</style>'
+'<div class="pe-layout">'
// VÄNSTER: Grundinfo
+'<div class="pe-left">'
+'<div style="margin-bottom:16px">'
+'<div id="peImgPreview" style="width:100%;aspect-ratio:16/9;background:#f8f9fa;border-radius:10px;border:2px dashed #e5e7eb;display:flex;align-items:center;justify-content:center;overflow:hidden;cursor:pointer;margin-bottom:6px" onclick="document.getElementById(\'peFileInput\').click()">'
+(p.img ? '<img src="'+p.img+'" style="width:100%;height:100%;object-fit:cover">' : '<div style="text-align:center;color:#94a3b8;font-size:12px">Klicka för bild</div>')
+'</div>'
+'<input type="file" id="peFileInput" accept="image/*" style="display:none" onchange="previewProductImage(this)">'
+'<div id="peUploadStatus" style="font-size:11px;color:#94a3b8"></div>'
+'</div>'
+'<div class="pe-form-grid">'
+'<label style="font-weight:600;color:#64748b">ID</label>'
+'<input id="peId" value="'+p.id+'" readonly style="padding:7px 10px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;background:#f8f9fa;color:#94a3b8;font-family:inherit">'
+'<label style="font-weight:600;color:#64748b">Namn</label>'
+'<input id="peName" value="'+p.name.replace(/"/g,'"')+'" style="padding:7px 10px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit">'
+'<label style="font-weight:600;color:#64748b">Kategori</label>'
+'<select id="peCat" style="padding:7px 10px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit">'+cats.map(c=>'<option value="'+c.v+'"'+(c.v===p.cat?' selected':'')+'>'+c.l+'</option>').join('')+'</select>'
+'<label style="font-weight:600;color:#64748b">Pris (kr)</label>'
+'<input id="pePrice" type="number" value="'+p.price+'" style="padding:7px 10px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit">'
+'<label style="font-weight:600;color:#64748b">Inköpspris</label>'
+'<div style="display:flex;gap:4px"><input id="peCostPrice" type="number" value="'+(p.costPrice||'')+'" style="padding:7px 10px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit;flex:1"><select id="peCostCurrency" style="padding:7px 6px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;width:70px">'+(function(){var cs=['SEK','EUR','USD','DKK','NOK'];return cs.map(function(c){return '<option value="'+c+'"'+((p.costCurrency||'SEK')===c?' selected':'')+'>'+c+'</option>';}).join('');}())+'</select></div>'
+'<label style="font-weight:600;color:#64748b">Lager</label>'
+'<select id="peStock" style="padding:7px 10px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit">'+stocks.map(s=>'<option value="'+s.v+'" data-class="'+s.c+'"'+(s.v===p.stock?' selected':'')+'>'+s.v+'</option>').join('')+'</select>'
+'<label style="font-weight:600;color:#64748b">Leverantör</label>'
+'<select id="peSupplier" style="padding:7px 10px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit"><option value="">– Ingen –</option>'+_suppliers.map(s=>'<option value="'+s.id+'"'+(s.id==p.supplierId?' selected':'')+'>'+s.name+'</option>').join('')+'</select>'
+'<label style="font-weight:600;color:#64748b">Enhet</label>'
+'<select id="peUnit" style="padding:7px 10px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit">'
+['st','kvm','m²','löpmeter','panel','mil','kWh','W','paket','tim','m'].map(u=>'<option value="'+u+'"'+(u===p.unit?' selected':'')+'>'+u+'</option>').join('')
+'</select>'
+'<label style="font-weight:600;color:#64748b">Påslag</label>'
+'<div style="display:flex;gap:6px"><select id="peMarkupType" style="padding:7px 10px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit;flex:1"><option value="percent"'+(p.markupType==='percent'?' selected':'')+'>Procent (%)</option><option value="amount"'+(p.markupType==='amount'?' selected':'')+'>Belopp (kr)</option></select>'
+'<input id="peMarkupValue" type="number" step="0.01" value="'+(p.markupValue||0)+'" style="padding:7px 10px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit;width:100px" placeholder="0"></div>'
+'<label style="font-weight:600;color:#64748b">Beskrivning</label>'
+'<textarea id="peDesc" rows="2" style="padding:7px 10px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;font-family:inherit;resize:vertical">'+(p.desc||'').replace(/</g,'<')+'</textarea>'
+'</div>'
+'<div style="margin-top:10px;display:flex;flex-direction:column;gap:6px">'
+'<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px"><input type="checkbox" id="peGreenTech" '+(p.greenTechEligible?'checked':'')+' style="width:16px;height:16px;accent-color:#059669">Grönt teknik-avdrag</label>'
+'<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px"><input type="checkbox" id="peRotEligible" '+(p.rotEligible?'checked':'')+' style="width:16px;height:16px;accent-color:#2563eb">ROT-avdrag</label>'
+'<div style="border-top:1px solid #e5e7eb;padding-top:8px;margin-top:8px">'
+'<div data-tillval-label style="font-size:11px;font-weight:600;color:#94a3b8;margin-bottom:6px">NÄR PRODUKTEN ANVÄNDS SOM TILLVAL</div>'
+'<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px"><input type="checkbox" id="peTillvalOblig" '+(p.tillvalObligatorisk?'checked':'')+' style="width:16px;height:16px;accent-color:#f59e0b">Obligatorisk (alltid inkluderad)</label>'
+'<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;margin-top:4px"><input type="checkbox" id="peTillvalHidden" '+(p.tillvalHidden?'checked':'')+' style="width:16px;height:16px;accent-color:#f59e0b">Dold (syns ej som valbar)</label>'
+'</div>'
+'</div>'
+'<div style="display:flex;gap:8px;margin-top:16px">'
+'<button onclick="saveProduct(\''+p.id+'\')" style="flex:1;padding:10px;background:#024550;color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">Spara</button>'
+'<button onclick="deleteProduct(\''+p.id+'\')" style="padding:10px 14px;background:#fee2e2;color:#dc2626;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">Ta bort</button>'
+'</div>'
+'</div>'
// HÖGER: Fliksystem
+'<div class="pe-right">'
// Flik-navigation
+'<div style="display:flex;gap:0;border-bottom:2px solid #e5e7eb;margin-bottom:12px">'
+'<button onclick="peSetTab(\'varianter\')" class="pe-tab" data-tab="varianter" style="padding:8px 16px;font-size:12px;font-weight:600;border:none;background:none;cursor:pointer;border-bottom:2px solid #024550;color:#024550;margin-bottom:-2px;font-family:inherit">Varianter</button>'
+'<button onclick="peSetTab(\'tjanster\')" class="pe-tab" data-tab="tjanster" style="padding:8px 16px;font-size:12px;font-weight:600;border:none;background:none;cursor:pointer;border-bottom:2px solid transparent;color:#64748b;margin-bottom:-2px;font-family:inherit">Tillval</button>'
+'<button onclick="peSetTab(\'bilder\')" class="pe-tab" data-tab="bilder" style="padding:8px 16px;font-size:12px;font-weight:600;border:none;background:none;cursor:pointer;border-bottom:2px solid transparent;color:#64748b;margin-bottom:-2px;font-family:inherit">Bilder</button>'
+'<button onclick="peSetTab(\'spec\')" class="pe-tab" data-tab="spec" style="padding:8px 16px;font-size:12px;font-weight:600;border:none;background:none;cursor:pointer;border-bottom:2px solid transparent;color:#64748b;margin-bottom:-2px;font-family:inherit">Specifikation</button>'
+'<button onclick="peSetTab(\'dokument\')" class="pe-tab" data-tab="dokument" style="padding:8px 16px;font-size:12px;font-weight:600;border:none;background:none;cursor:pointer;border-bottom:2px solid transparent;color:#64748b;margin-bottom:-2px;font-family:inherit">Dokument</button>'
+'</div>'
// Flik: Varianter
+'<div id="peTabVarianter" class="pe-tab-panel">'
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">'
+'<h3 style="font-size:14px;font-weight:700;margin:0;color:#1a1a1a">Varianter</h3>'
+'<button onclick="peAddVariant()" style="padding:5px 12px;background:#024550;color:#fff;border:none;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit">+ Lägg till</button>'
+'</div>'
+'<div id="peVariantsTable" style="max-height:300px;overflow-y:auto;border:1px solid #e5e7eb;border-radius:8px"></div>'
+'<div style="margin-top:16px;display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">'
+'<h3 style="font-size:14px;font-weight:700;margin:0;color:#1a1a1a">Regler <span style="font-weight:400;color:#94a3b8;font-size:11px;cursor:pointer" onclick="document.getElementById(\'peRulesHelp\').style.display=document.getElementById(\'peRulesHelp\').style.display===\'none\'?\'block\':\'none\'">?</span></h3>'
+'<button onclick="peAddRule()" style="padding:5px 12px;background:#024550;color:#fff;border:none;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit">+ Lägg till</button>'
+'</div>'
+'<div id="peRulesHelp" style="display:none;margin-bottom:8px;padding:10px;background:#f0f9ff;border:1px solid #bae6fd;border-radius:8px;font-size:11px;line-height:1.6;color:#334155"><strong style="color:#0284c7">OM</strong> = villkor, <strong style="color:#0284c7">SÅ</strong> = åtgärd, <strong style="color:#0284c7">±</strong> = operator (= sätter, + adderar, − subtraherar, × multiplicerar). Formler: <code>{w}</code> <code>{h}</code> <code>{TB_FODER_width}</code></div>'
+'<div id="peRulesTable" style="max-height:300px;overflow-y:auto;border:1px solid #e5e7eb;border-radius:8px"></div>'
+'</div>'
// Flik: Tjänster + Tillbehör
+'<div id="peTabTjanster" class="pe-tab-panel" style="display:none">'
+'<div style="display:flex;flex-direction:column;gap:12px">'
+'<div>'
+'<h3 style="font-size:14px;font-weight:700;margin:0 0 8px;color:#1a1a1a">Kopplade tjänster</h3>'
+'<div id="peServicesTable" style="max-height:260px;overflow-y:auto;border:1px solid #e5e7eb;border-radius:8px"><div style="padding:12px;color:#94a3b8;font-size:12px">Laddar...</div></div>'
+'</div>'
+'<div>'
+'<h3 style="font-size:14px;font-weight:700;margin:0 0 8px;color:#1a1a1a">Kopplade tillbehör</h3>'
+'<div id="peAccessoriesTable" style="max-height:260px;overflow-y:auto;border:1px solid #e5e7eb;border-radius:8px"><div style="padding:12px;color:#94a3b8;font-size:12px">Laddar...</div></div>'
+'</div>'
+'</div>'
+'<div id="peUsedByBlock" style="margin-top:16px;display:none">'
+'<h3 style="font-size:14px;font-weight:700;margin:0 0 8px;color:#1a1a1a">Denna produkt är kopplad som tillval till</h3>'
+'<div id="peUsedByTable" style="max-height:200px;overflow-y:auto;border:1px solid #e5e7eb;border-radius:8px"></div>'
+'</div>'
+'</div>'
// Flik: Bilder
+'<div id="peTabBilder" class="pe-tab-panel" style="display:none">'
+'<h3 style="font-size:14px;font-weight:700;margin:0 0 8px;color:#1a1a1a">Galleri</h3>'
+'<div id="peGalleryGrid" style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-bottom:12px"></div>'
+'<button onclick="document.getElementById(\'peGalleryInput\').click()" style="padding:8px 16px;background:#024550;color:#fff;border:none;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit">+ Lägg till bilder</button>'
+'<input type="file" id="peGalleryInput" accept="image/*" multiple style="display:none" onchange="peUploadGalleryImages(\''+p.id+'\')">'
+'</div>'
// Flik: Specifikation
+'<div id="peTabSpec" class="pe-tab-panel" style="display:none">'
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">'
+'<h3 style="font-size:14px;font-weight:700;margin:0;color:#1a1a1a">Specifikationer</h3>'
+'<button onclick="peAddAttribute()" style="padding:5px 12px;background:#024550;color:#fff;border:none;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit">+ Lägg till</button>'
+'</div>'
+'<div id="peAttributesTable" style="max-height:400px;overflow-y:auto;border:1px solid #e5e7eb;border-radius:8px"></div>'
+'</div>'
// Flik: Dokument
+'<div id="peTabDokument" class="pe-tab-panel" style="display:none">'
+'<h3 style="font-size:14px;font-weight:700;margin:0 0 8px;color:#1a1a1a">Dokument</h3>'
+'<div id="peDocumentsList" style="max-height:300px;overflow-y:auto;border:1px solid #e5e7eb;border-radius:8px;margin-bottom:12px"></div>'
+'<div style="display:flex;gap:8px;align-items:center">'
+'<select id="peDocType" style="padding:6px 10px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit"><option value="manual">Manual</option><option value="datablad">Datablad</option><option value="certifikat">Certifikat</option><option value="ovrigt">Övrigt</option></select>'
+'<button onclick="document.getElementById(\'peDocInput\').click()" style="padding:8px 16px;background:#024550;color:#fff;border:none;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit">+ Ladda upp dokument</button>'
+'<input type="file" id="peDocInput" accept=".pdf,.doc,.docx" style="display:none" onchange="peUploadDocument(\''+p.id+'\')">'
+'</div>'
+'</div>'
+'</div>'
+'</div></div>';
document.body.appendChild(modal);
peRenderVariants();
peLoadServices(p.id);
peLoadUsedBy(p.id);
_peCroppedFile = null;
peInitGallery(p.id, p.gallery);
peInitAttributes(p.specs);
peInitRules(p.specs);
peInitDocuments(p.documents);
peRenderRules();
}
let _peSpecsType = '';
let _peStyles = [];
let _peSizes = [];
let _peSpecsMeta = {};
let _peWinModels = []; // Dynamic model columns for window_sizes
function peRenderVariants() {
const el = document.getElementById('peVariantsTable');
if(!el) return;
// === STYLE + WIDTH VARIANTS ===
if(_peSpecsType === 'style_width_variants' && _peStyles.length) {
let html = '<div style="max-height:500px;overflow-y:auto">';
_peStyles.forEach((style, si) => {
const isOpen = _peStyleExpanded && _peStyleExpanded[si];
const pricesWithVal = style.variants.filter(v=>v.price!=null).length;
const totalVars = style.variants.length;
html += '<div style="margin-bottom:8px;border:1px solid #e5e7eb;border-radius:8px;overflow:hidden">'
+'<div style="padding:8px 12px;background:#f8f9fa;display:flex;justify-content:space-between;align-items:center;cursor:pointer" onclick="peToggleStyle('+si+')">'
+'<div style="display:flex;align-items:center;gap:8px">'
+'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="2.5" style="transition:transform .2s;transform:rotate('+(isOpen?'0':'-90')+'deg)"><polyline points="6 9 12 15 18 9"/></svg>'
+'<strong style="font-size:13px">'+style.style+'</strong>'
+'<span style="font-size:11px;color:#94a3b8">'+(style.desc||'')+'</span>'
+'<span style="font-size:10px;padding:2px 8px;border-radius:10px;background:#e0f2fe;color:#0284c7;font-weight:600">'+totalVars+' varianter</span>'
+(pricesWithVal<totalVars?'<span style="font-size:10px;padding:2px 8px;border-radius:10px;background:#fef3c7;color:#d97706;font-weight:600">'+(totalVars-pricesWithVal)+' saknar pris</span>':'')
+'</div>'
+'<button onclick="event.stopPropagation();peAddStyleVariant('+si+')" style="padding:3px 10px;background:#024550;color:#fff;border:none;border-radius:4px;font-size:11px;cursor:pointer;font-family:inherit">+ Bredd</button>'
+'</div>'
+'<div id="peStyleBody-'+si+'" style="'+(isOpen?'':'display:none')+'">'
+'<table style="width:100%;border-collapse:collapse;font-size:12px"><thead><tr style="background:#f1f5f9">'
+'<th style="padding:4px 8px;text-align:left;font-weight:600;color:#64748b">Bredd (mm)</th>'
+'<th style="padding:4px 8px;text-align:right;font-weight:600;color:#64748b">Pris (kr)</th>'
+'<th style="padding:4px 8px;text-align:left;font-weight:600;color:#64748b">Label</th>'
+'<th style="padding:4px 4px;width:30px"></th>'
+'</tr></thead><tbody>'
+ style.variants.map((v, vi) => '<tr style="border-top:1px solid #f1f5f9'+(v.price==null?';background:#fffbeb':'')+'">'
+'<td style="padding:2px 4px"><input type="number" value="'+(v.width_mm||v.length_mm||'')+'" onchange="peUpdateStyleVar('+si+','+vi+',\'width_mm\',this.value)" style="width:80px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:12px;font-family:inherit"></td>'
+'<td style="padding:2px 4px;text-align:right"><input type="number" step="0.01" value="'+(v.price!=null?v.price:'')+'" placeholder="–" onchange="peUpdateStyleVar('+si+','+vi+',\'price\',this.value)" style="width:90px;padding:4px 6px;border:1px solid '+(v.price==null?'#fbbf24':'#e5e7eb')+';border-radius:4px;font-size:12px;font-family:inherit;text-align:right"></td>'
+'<td style="padding:2px 4px"><input type="text" value="'+(v.label||'')+'" onchange="peUpdateStyleVar('+si+','+vi+',\'label\',this.value)" style="width:100%;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:12px;font-family:inherit;box-sizing:border-box"></td>'
+'<td style="padding:2px 4px"><button onclick="peRemoveStyleVar('+si+','+vi+')" style="background:none;border:none;cursor:pointer;color:#dc2626;font-size:16px;padding:2px 4px">×</button></td>'
+'</tr>').join('')
+'</tbody></table></div></div>';
});
html += '<button onclick="peAddStyle()" style="margin-top:8px;padding:6px 14px;background:#f8f9fa;border:1px dashed #e5e7eb;border-radius:6px;font-size:12px;cursor:pointer;font-family:inherit;color:#64748b">+ Ny stil</button>';
html += '</div>';
el.innerHTML = html;
return;
}
// === WIDTH VARIANTS (no styles) ===
if(_peSpecsType === 'width_variants' && _peVariants.length) {
el.innerHTML = '<table style="width:100%;border-collapse:collapse;font-size:12px"><thead><tr style="background:#f8f9fa;position:sticky;top:0">'
+'<th style="padding:6px 8px;text-align:left;font-weight:600;color:#64748b">Bredd/Längd (mm)</th>'
+'<th style="padding:6px 8px;text-align:right;font-weight:600;color:#64748b">Pris (kr)</th>'
+'<th style="padding:6px 8px;text-align:left;font-weight:600;color:#64748b">Label</th>'
+'<th style="padding:6px 4px;width:30px"></th>'
+'</tr></thead><tbody>'
+ _peVariants.map((v, i) => '<tr style="border-top:1px solid #f1f5f9">'
+'<td style="padding:2px 4px"><input type="number" value="'+(v.width_mm||v.length_mm||'')+'" onchange="peUpdateVariant('+i+',\''+(v.width_mm!==undefined?'width_mm':'length_mm')+'\',this.value)" style="width:100px;padding:5px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:12px;font-family:inherit"></td>'
+'<td style="padding:2px 4px;text-align:right"><input type="number" step="0.01" value="'+(v.price!=null?v.price:'')+'" placeholder="–" onchange="peUpdateVariant('+i+',\'price\',this.value)" style="width:100px;padding:5px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:12px;font-family:inherit;text-align:right"></td>'
+'<td style="padding:2px 4px"><input type="text" value="'+(v.label||'')+'" onchange="peUpdateVariant('+i+',\'label\',this.value)" style="width:100%;padding:5px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:12px;font-family:inherit;box-sizing:border-box"></td>'
+'<td style="padding:2px 4px"><button onclick="peRemoveVariant('+i+')" style="background:none;border:none;cursor:pointer;color:#dc2626;font-size:16px;padding:2px 4px">×</button></td>'
+'</tr>').join('')
+'</tbody></table>';
return;
}
// === WINDOW SIZES (with dynamic model columns) ===
if(_peSpecsType === 'window_sizes' && _peSizes.length) {
var models = _peWinModels.length ? _peWinModels : [{key:'price',label:'Pris'}];
var hasModels = _peWinModels.length > 0;
const widths = [...new Set(_peSizes.map(s=>s.w))].sort((a,b)=>a-b);
const filterW = _peWinFilterW || '';
const filtered = filterW ? _peSizes.filter(s => s.w === parseInt(filterW)) : _peSizes;
var thModels = models.map(function(m,mi){
return '<th style="padding:5px 4px;text-align:right;font-weight:600;color:#024550;font-size:10px;min-width:70px;position:relative">'
+m.label
+(hasModels ? '<button onclick="peRemoveWinModel('+mi+')" style="position:absolute;top:0;right:2px;background:none;border:none;color:#dc2626;cursor:pointer;font-size:10px;padding:0" title="Ta bort modell">\u00d7</button>' : '')
+'</th>';
}).join('');
var bodyRows = filtered.map(function(s,i){
var realIdx = _peSizes.indexOf(s);
var priceCells = models.map(function(m){
var val = s[m.key];
return '<td style="padding:1px 2px"><input type="number" step="1" value="'+(val!=null&&val!==''?val:'')+'" placeholder="\u2013" onchange="peUpdateWinSize('+realIdx+',\''+m.key+'\',this.value)" style="width:70px;padding:3px 4px;border:1px solid #e5e7eb;border-radius:4px;font-size:11px;text-align:right;font-family:inherit"></td>';
}).join('');
return '<tr style="border-top:1px solid #f1f5f9">'
+'<td style="padding:2px 4px;text-align:right;font-weight:600;font-size:11px">'+s.w+'</td>'
+'<td style="padding:2px 4px;text-align:right;font-weight:600;font-size:11px">'+s.h+'</td>'
+priceCells
+'<td style="padding:2px 2px"><button onclick="peRemoveWinSize('+realIdx+')" style="background:none;border:none;cursor:pointer;color:#dc2626;font-size:13px;padding:0">\u00d7</button></td>'
+'</tr>';
}).join('');
el.innerHTML = '<div>'
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;gap:8px;flex-wrap:wrap">'
+'<div style="display:flex;align-items:center;gap:8px">'
+'<span style="font-size:12px;font-weight:600;color:#334155">'+_peSizes.length+' storlekar</span>'
+'<select id="peWinFilterW" onchange="_peWinFilterW=this.value;peRenderVariants()" style="padding:4px 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:11px;font-family:inherit">'
+'<option value="">Alla bredder</option>'
+widths.map(w=>'<option value="'+w+'"'+(filterW==w?' selected':'')+'>'+w+' dm</option>').join('')
+'</select>'
+'<span style="font-size:11px;color:#94a3b8">Visar '+filtered.length+' st</span>'
+'</div>'
+'<div style="display:flex;gap:6px;flex-wrap:wrap">'
+'<button onclick="peAddWinModel()" style="padding:4px 10px;background:#059669;color:#fff;border:none;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit">+ L\u00e4gg till modell</button>'
+'<label style="padding:4px 10px;background:#024550;color:#fff;border:none;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;display:flex;align-items:center;gap:4px">'
+'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>'
+'Importera Excel'
+'<input type="file" accept=".xlsx,.xls,.csv" style="display:none" onchange="peImportWindowExcel(this)">'
+'</label>'
+'<button onclick="peAddWindowSize()" style="padding:4px 10px;background:#f8f9fa;border:1px solid #e5e7eb;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit">+ L\u00e4gg till storlek</button>'
+'</div></div>'
+'<div style="max-height:500px;overflow:auto;border:1px solid #e5e7eb;border-radius:8px">'
+'<table style="width:100%;border-collapse:collapse;font-size:11px">'
+'<thead><tr style="background:#f8f9fa;position:sticky;top:0;z-index:1">'
+'<th style="padding:5px 6px;text-align:right;font-weight:600;color:#64748b">Bredd</th>'
+'<th style="padding:5px 6px;text-align:right;font-weight:600;color:#64748b">H\u00f6jd</th>'
+thModels
+'<th style="padding:5px 2px;width:20px"></th>'
+'</tr></thead><tbody>'
+bodyRows
+'</tbody></table></div></div>';
return;
}
// === DEFAULT (old format) ===
if(!_peVariants.length) {
el.innerHTML = '<div style="padding:12px;color:#94a3b8;font-size:12px;text-align:center">Inga varianter. Klicka "+ Lägg till" för att skapa.</div>';
return;
}
const keys = Object.keys(_peVariants[0]);
el.innerHTML = '<table style="width:100%;border-collapse:collapse;font-size:12px">'
+'<thead><tr style="background:#f8f9fa;position:sticky;top:0">'
+ keys.map(k=>'<th style="padding:6px 8px;text-align:left;font-weight:600;color:#64748b;white-space:nowrap">'+k+'</th>').join('')
+'<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:60px">Förvald</th>'
+'<th style="padding:6px 4px;width:30px"></th>'
+'</tr></thead><tbody>'
+ _peVariants.map((v,i)=>'<tr style="border-top:1px solid #f1f5f9">'
+ keys.map(k=>'<td style="padding:2px 4px"><input type="'+(typeof v[k]==='number'?'number':'text')+'" value="'+(v[k]!=null?v[k]:'')+'" onchange="peUpdateVariant('+i+',\''+k+'\',this.value)" style="width:100%;padding:5px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:12px;font-family:inherit;box-sizing:border-box"></td>').join('')
+'<td style="padding:2px 4px;text-align:center"><input type="checkbox" '+(v.is_default?'checked':'')+' onchange="peSetDefaultVariant('+i+',this.checked)" style="width:16px;height:16px;accent-color:#024550"></td>'
+'<td style="padding:2px 4px"><button onclick="peRemoveVariant('+i+')" style="background:none;border:none;cursor:pointer;color:#dc2626;font-size:16px;padding:2px 4px" title="Ta bort">×</button></td>'
+'</tr>').join('')
+'</tbody></table>';
}
let _peWinFilterW = '';
function peUpdateWinSize(idx, key, val) {
if(key === 'note' || key === 'article_id') { _peSizes[idx][key] = val; }
else { var n = parseFloat(val); _peSizes[idx][key] = val===''?null:(isNaN(n)?null:n); }
}
function peRemoveWinSize(idx) { _peSizes.splice(idx, 1); peRenderVariants(); }
function peAddWinModel() {
var name = prompt('Modellnamn (t.ex. Fast, 1-Luft, 2-Luft, Sidoh\u00e4ngd):');
if(!name) return;
var key = name.toLowerCase().replace(/[^a-z0-9]/g,'_').replace(/_+/g,'_');
if(_peWinModels.find(function(m){return m.key===key;})) { alert('Modellen finns redan'); return; }
// If first model and legacy 'price' data exists, migrate it
if(_peWinModels.length === 0 && _peSizes.length && _peSizes[0].price !== undefined) {
// Ask what the existing price column should be called
var oldName = prompt('Befintliga priser \u00e4r sparade som \"Pris\". Vad ska den kolumnen heta? (t.ex. Fast)', 'Fast');
if(oldName) {
var oldKey = oldName.toLowerCase().replace(/[^a-z0-9]/g,'_').replace(/_+/g,'_');
_peWinModels.push({key: oldKey, label: oldName});
// Migrate price -> oldKey
_peSizes.forEach(function(s){ if(s.price !== undefined) { s[oldKey] = s.price; delete s.price; } });
}
}
_peWinModels.push({key: key, label: name});
peRenderVariants();
}
function peRemoveWinModel(idx) {
if(!confirm('Ta bort modell \u201c' + _peWinModels[idx].label + '\u201d och alla dess priser?')) return;
var key = _peWinModels[idx].key;
_peSizes.forEach(function(s){ delete s[key]; });
_peWinModels.splice(idx, 1);
peRenderVariants();
}
function peAddWindowSize() {
_peSizes.push({w:0, h:0, price:null, note:'', article_id:''});
peRenderVariants();
}
function peImportWindowExcel(input) {
const file = input.files[0];
if(!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
// Parse CSV (simple) or show message for xlsx
if(file.name.endsWith('.csv')) {
const lines = e.target.result.split('\n').filter(l=>l.trim());
const header = lines[0].split(',').map(h=>h.trim().toLowerCase());
let imported = 0;
for(let i=1; i<lines.length; i++) {
const cols = lines[i].split(',').map(c=>c.trim());
if(cols.length < 4) continue;
const w = parseInt(cols[header.indexOf('width')]||cols[2]) || 0;
const h = parseInt(cols[header.indexOf('height')]||cols[3]) || 0;
const price = parseFloat(cols[header.indexOf('price')]||cols[6]) || null;
const note = cols[header.indexOf('note')]||cols[7]||'';
const artId = cols[header.indexOf('modellid')]||cols[0]||'';
// Update existing or add
const existing = _peSizes.find(s => s.w === w && s.h === h);
if(existing) { existing.price = price; if(note) existing.note = note; if(artId) existing.article_id = parseInt(artId)||artId; }
else { _peSizes.push({w, h, price, note, article_id: parseInt(artId)||artId}); }
imported++;
}
alert('Importerade/uppdaterade ' + imported + ' rader');
} else {
alert('Excel-import (.xlsx) kräver att filen först sparas som CSV.\n\nI Excel: Arkiv → Spara som → CSV (kommaavgränsad)\n\nKolumner: ModellID, ProduktTyp, Width, Height, Bredd, Höjd, Price, Note');
}
} catch(err) { alert('Importfel: ' + err.message); }
peRenderVariants();
input.value = '';
};
if(file.name.endsWith('.csv')) reader.readAsText(file);
else reader.readAsArrayBuffer(file);
}
let _peStyleExpanded = {};
function peToggleStyle(si) {
_peStyleExpanded[si] = !_peStyleExpanded[si];
const body = document.getElementById('peStyleBody-'+si);
const arrow = body?.previousElementSibling?.querySelector('svg');
if(body) body.style.display = _peStyleExpanded[si] ? '' : 'none';
if(arrow) arrow.style.transform = 'rotate('+(_peStyleExpanded[si]?'0':'-90')+'deg)';
}
function peUpdateStyleVar(si, vi, key, val) {
if(key === 'price') { const n = parseFloat(val); _peStyles[si].variants[vi][key] = val===''?null:(isNaN(n)?null:n); }
else if(key === 'width_mm' || key === 'length_mm') { _peStyles[si].variants[vi][key] = parseInt(val)||0; }
else { _peStyles[si].variants[vi][key] = val; }
}
function peRemoveStyleVar(si, vi) { _peStyles[si].variants.splice(vi, 1); peRenderVariants(); }
function peAddStyleVariant(si) { _peStyles[si].variants.push({width_mm:0,price:null}); peRenderVariants(); }
function peAddStyle() { _peStyles.push({style:'Ny stil',desc:'',variants:[{width_mm:0,price:null}]}); peRenderVariants(); }
function peAddVariant() {
if(_peVariants.length) {
const tmpl = {};
Object.keys(_peVariants[0]).forEach(k=>tmpl[k]=0);
_peVariants.push(tmpl);
} else {
_peVariants.push({label:'',pris:0});
}
peRenderVariants();
}
function peSetDefaultVariant(idx, checked) {
_peVariants.forEach((v,i) => { v.is_default = (i === idx && checked) ? 1 : 0; });
peRenderVariants();
}
function peRemoveVariant(i) {
_peVariants.splice(i, 1);
peRenderVariants();
}
function peUpdateVariant(i, key, val) {
const num = parseFloat(val);
_peVariants[i][key] = isNaN(num) ? val : num;
}
let _peAccessories = [];
function peLinkNumber(value) {
if (value === '' || value === null || value === undefined) return '';
const num = parseFloat(value);
return Number.isNaN(num) ? '' : num;
}
function peCreateLinkState(product, linkedCfg) {
const cfg = linkedCfg || null;
return {
id: product.id,
name: product.name,
price: parseFloat(product.price),
linked: !!cfg,
default_on: parseInt(cfg?.default_on || 0),
hidden: parseInt(cfg?.hidden || 0),
mandatory: parseInt(cfg?.mandatory || 0),
default_variant: cfg?.default_variant || '',
default_qty: peLinkNumber(cfg?.default_qty),
min_qty: peLinkNumber(cfg?.min_qty),
max_qty: peLinkNumber(cfg?.max_qty)
};
}
function peUpdateLinkItem(listName, idx, field, value) {
const list = listName === 'services' ? _peServices : _peAccessories;
if (!list[idx]) return;
if (field === 'linked') {
list[idx].linked = !!value;
if (!value) {
list[idx].default_on = 0;
list[idx].hidden = 0;
list[idx].mandatory = 0;
list[idx].default_variant = '';
list[idx].default_qty = '';
list[idx].min_qty = '';
list[idx].max_qty = '';
}
} else if (['default_on', 'hidden', 'mandatory'].includes(field)) {
list[idx][field] = value ? 1 : 0;
} else {
list[idx][field] = value === '' ? '' : value;
}
if (listName === 'services') peRenderServices();
else peRenderAccessories();
}
function peRenderLinkTable(items, listName, title) {
if (!items.length) {
return '<div style="padding:12px;color:#94a3b8;font-size:12px">Inga ' + title.toLowerCase() + '</div>';
}
const sortedItems = [...items].sort(function(a, b) {
if (!!a.linked !== !!b.linked) return a.linked ? -1 : 1;
return a.name.localeCompare(b.name, 'sv');
});
const accent = listName === 'services' ? '#059669' : '#0284c7';
return '<table style="width:100%;border-collapse:collapse;font-size:12px">'
+ '<thead><tr style="background:#f8f9fa;position:sticky;top:0">'
+ '<th style="padding:6px 8px;width:30px"></th>'
+ '<th style="padding:6px 8px;text-align:left;font-weight:600;color:#64748b">' + title + '</th>'
+ '<th style="padding:6px 8px;text-align:right;font-weight:600;color:#64748b">Pris</th>'
+ '<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:70px">Förvald</th>'
+ '<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:80px">Oblig.</th>'
+ '<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:55px">Dold</th>'
+ '<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:68px">Antal</th>'
+ '<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:68px">Min</th>'
+ '<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:68px">Max</th>'
+ '</tr></thead><tbody>'
+ sortedItems.map(function(item) {
const i = items.indexOf(item);
const linked = !!item.linked;
const disabled = linked ? '' : ' disabled';
const disabledStyle = linked ? '' : ';opacity:.45';
return '<tr style="border-top:1px solid #f1f5f9' + (linked ? ';background:#f0fdf4' : '') + '">'
+ '<td style="padding:4px 8px"><input type="checkbox" ' + (linked ? 'checked' : '') + ' onchange="peUpdateLinkItem(\'' + listName + '\',' + i + ',\'linked\',this.checked)" style="width:15px;height:15px;accent-color:' + accent + '"></td>'
+ '<td style="padding:4px 8px"><button type="button" onclick="event.stopPropagation();editProduct(\'' + item.id + '\')" style="background:none;border:none;padding:0;margin:0;color:#024550;font-weight:700;cursor:pointer;font-size:12px;font-family:inherit;text-align:left">' + item.name + '</button></td>'
+ '<td style="padding:4px 8px;text-align:right">' + item.price.toLocaleString('sv-SE') + ' kr</td>'
+ '<td style="padding:4px 8px;text-align:center' + disabledStyle + '"><input type="checkbox" ' + (item.default_on ? 'checked' : '') + disabled + ' onchange="peUpdateLinkItem(\'' + listName + '\',' + i + ',\'default_on\',this.checked)" style="width:15px;height:15px;accent-color:#059669"></td>'
+ '<td style="padding:4px 8px;text-align:center' + disabledStyle + '"><input type="checkbox" ' + (item.mandatory ? 'checked' : '') + disabled + ' onchange="peUpdateLinkItem(\'' + listName + '\',' + i + ',\'mandatory\',this.checked)" style="width:15px;height:15px;accent-color:#f59e0b"></td>'
+ '<td style="padding:4px 8px;text-align:center' + disabledStyle + '"><input type="checkbox" ' + (item.hidden ? 'checked' : '') + disabled + ' onchange="peUpdateLinkItem(\'' + listName + '\',' + i + ',\'hidden\',this.checked)" style="width:15px;height:15px;accent-color:#ef4444"></td>'
+ '<td style="padding:4px 8px' + disabledStyle + '"><input type="number" min="0" step="1" value="' + (item.default_qty ?? '') + '"' + disabled + ' onchange="peUpdateLinkItem(\'' + listName + '\',' + i + ',\'default_qty\',this.value)" style="width:58px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:11px;text-align:right;font-family:inherit"></td>'
+ '<td style="padding:4px 8px' + disabledStyle + '"><input type="number" min="0" step="1" value="' + (item.min_qty ?? '') + '"' + disabled + ' onchange="peUpdateLinkItem(\'' + listName + '\',' + i + ',\'min_qty\',this.value)" style="width:58px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:11px;text-align:right;font-family:inherit"></td>'
+ '<td style="padding:4px 8px' + disabledStyle + '"><input type="number" min="0" step="1" value="' + (item.max_qty ?? '') + '"' + disabled + ' onchange="peUpdateLinkItem(\'' + listName + '\',' + i + ',\'max_qty\',this.value)" style="width:58px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:11px;text-align:right;font-family:inherit"></td>'
+ '</tr>';
}).join('')
+ '</tbody></table>';
}
async function peLoadServices(productId) {
const el = document.getElementById('peServicesTable');
const elAcc = document.getElementById('peAccessoriesTable');
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 allSvc = svcData.success ? svcData.products : [];
const allAcc = accData.success ? accData.products : [];
const linkedMap = {};
if (linkedData.success) {
(linkedData.services || []).forEach(s => {
linkedMap[s.service_id] = {
default_on: parseInt(s.default_on || 0),
hidden: parseInt(s.hidden || 0),
mandatory: parseInt(s.mandatory || 0),
default_variant: s.default_variant || '',
default_qty: s.default_qty,
min_qty: s.min_qty,
max_qty: s.max_qty
};
});
}
_peServices = allSvc.map(s => peCreateLinkState(s, linkedMap[s.id]));
_peAccessories = allAcc.map(s => peCreateLinkState(s, linkedMap[s.id]));
peRenderServices();
peRenderAccessories();
} catch(e) {
el.innerHTML = '<div style="padding:12px;color:#ef4444;font-size:12px">Kunde inte ladda tjänster</div>';
if(elAcc) elAcc.innerHTML = '<div style="padding:12px;color:#ef4444;font-size:12px">Kunde inte ladda tillbehör</div>';
}
}
function peRenderServices() {
const el = document.getElementById('peServicesTable');
el.innerHTML = peRenderLinkTable(_peServices, 'services', 'Tjänst');
}
function peRenderAccessories() {
const el = document.getElementById('peAccessoriesTable');
if(!el) return;
el.innerHTML = peRenderLinkTable(_peAccessories, 'accessories', 'Tillbehör');
}
// === KOPPLAD TILL (reverse lookup med per-link flags) ===
var _peUsedByData = [];
var _peUsedByServiceId = '';
var _peUsedByVariants = [];
function peUpdateLinkFlag(productId, serviceId, field, value) {
var sendValue;
if (field === 'default_variant') sendValue = value;
else if (['default_qty', 'min_qty', 'max_qty'].includes(field)) sendValue = value === '' ? null : value;
else sendValue = value ? 1 : 0;
fetch('/api/products.php?update_link=1', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({product_id: productId, service_id: serviceId, field: field, value: sendValue}) });
}
function peGetVariantsForProduct(productId) {
var prod = catalogProducts.find(function(x) { return x.id === productId; });
if (!prod || !prod.specs) return [];
var specs = typeof prod.specs === 'string' ? JSON.parse(prod.specs) : prod.specs;
var variants = [];
if (specs.styles && specs.styles.length > 0) {
specs.styles.forEach(function(style) {
if (style.variants && style.variants.length > 0) {
style.variants.forEach(function(v) {
variants.push({label: style.style + ' ' + v.width_mm + 'mm', value: style.style + '|' + v.width_mm, price: v.price});
});
} else {
variants.push({label: style.style, value: style.style, price: null});
}
});
} else if (specs.variants && specs.variants.length > 0) {
specs.variants.forEach(function(v) {
var label = Object.entries(v).filter(function(e) { return e[0] !== 'price'; }).map(function(e) { return e[1]; }).join(' ');
variants.push({label: label, value: label, price: v.price});
});
}
return variants;
}
function peShowVariantPicker(rowIdx, productId) {
var sid = _peUsedByServiceId;
var variants = _peUsedByVariants;
var old = document.getElementById('peVariantPicker'); if (old) old.remove();
var picker = document.createElement('div');
picker.id = 'peVariantPicker';
picker.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.3);z-index:99999;display:flex;align-items:center;justify-content:center;padding:20px';
picker.onclick = function(e) { if (e.target === picker) { _peUsedByData[rowIdx].default_on = 0; _peUsedByData[rowIdx].default_variant = null; peUpdateLinkFlag(productId, sid, 'default_on', false); peRenderUsedBy(); picker.remove(); } };
var currentVariant = _peUsedByData[rowIdx].default_variant || '';
var html = '<div style="background:#fff;border-radius:12px;padding:20px;max-width:400px;width:100%;max-height:70vh;overflow-y:auto;box-shadow:0 10px 40px rgba(0,0,0,.2)">'
+ '<h3 style="margin:0 0 4px;font-size:15px;font-weight:700">Välj förvald variant</h3>'
+ '<p style="margin:0 0 12px;font-size:12px;color:#94a3b8">för ' + _peUsedByData[rowIdx].name + '</p>'
+ '<div style="display:flex;flex-direction:column;gap:4px">';
variants.forEach(function(v) {
var selected = (v.value === currentVariant);
var priceStr = v.price != null ? ' — ' + parseFloat(v.price).toLocaleString('sv-SE') + ' kr' : '';
html += '<button onclick="peSelectVariant(' + rowIdx + ',\'' + productId + '\',\'' + v.value.replace(/'/g, "\\'") + '\')" style="padding:8px 12px;border:1px solid ' + (selected ? '#059669' : '#e5e7eb') + ';border-radius:8px;background:' + (selected ? '#f0fdf4' : '#fff') + ';cursor:pointer;text-align:left;font-size:13px;font-family:inherit">' + v.label + '<span style="color:#94a3b8">' + priceStr + '</span></button>';
});
html += '</div><button onclick="peSelectVariant(' + rowIdx + ',\'' + productId + '\',null)" style="margin-top:10px;padding:6px 12px;border:1px solid #e5e7eb;border-radius:6px;background:#fff;cursor:pointer;font-size:12px;color:#94a3b8;font-family:inherit">Ingen specifik variant</button></div>';
picker.innerHTML = html;
document.body.appendChild(picker);
}
function peSelectVariant(rowIdx, productId, variantValue) {
var sid = _peUsedByServiceId;
_peUsedByData[rowIdx].default_on = 1;
_peUsedByData[rowIdx].default_variant = variantValue;
peUpdateLinkFlag(productId, sid, 'default_on', true);
peUpdateLinkFlag(productId, sid, 'default_variant', variantValue);
peRenderUsedBy();
var picker = document.getElementById('peVariantPicker'); if (picker) picker.remove();
}
function peHandleDefaultOn(rowIdx, checked, productId) {
var sid = _peUsedByServiceId;
if (checked && _peUsedByVariants.length > 0) {
peShowVariantPicker(rowIdx, productId);
} else {
_peUsedByData[rowIdx].default_on = checked ? 1 : 0;
if (!checked) { _peUsedByData[rowIdx].default_variant = null; peUpdateLinkFlag(productId, sid, 'default_variant', null); }
peUpdateLinkFlag(productId, sid, 'default_on', checked);
peRenderUsedBy();
}
}
function peLoadUsedBy(productId) {
var block = document.getElementById('peUsedByBlock');
var el = document.getElementById('peUsedByTable');
if (!block || !el) return;
_peUsedByServiceId = productId;
_peUsedByVariants = peGetVariantsForProduct(productId);
fetch('/api/products.php?used_by=' + productId).then(function(r) { return r.json(); }).then(function(data) {
_peUsedByData = data.success ? data.products : [];
if (!_peUsedByData.length) { block.style.display = 'none'; return; }
block.style.display = 'block';
peRenderUsedBy();
var globOblig = document.getElementById('peTillvalOblig');
var globHidden = document.getElementById('peTillvalHidden');
if (globOblig) { globOblig.disabled = true; globOblig.parentElement.style.opacity = '0.4'; }
if (globHidden) { globHidden.disabled = true; globHidden.parentElement.style.opacity = '0.4'; }
var tillvalLabel = document.querySelector('[data-tillval-label]');
if (tillvalLabel) tillvalLabel.innerHTML = 'NÄR PRODUKTEN ANVÄNDS SOM TILLVAL <span style="font-weight:400;color:#b0b0b0">(hanteras per koppling)</span>';
});
}
function peRenderUsedBy() {
var el = document.getElementById('peUsedByTable');
if (!el) return;
var sid = _peUsedByServiceId;
var hasVariants = _peUsedByVariants.length > 0;
el.innerHTML = '<table style="width:100%;border-collapse:collapse;font-size:12px">'
+ '<thead><tr style="background:#f8f9fa;position:sticky;top:0">'
+ '<th style="padding:6px 8px;text-align:left;font-weight:600;color:#64748b">Produkt</th>'
+ '<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:' + (hasVariants ? '140' : '70') + 'px">Förvald</th>'
+ '<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:80px">Obligatorisk</th>'
+ '<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:50px">Dold</th>'
+ '<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:68px">Antal</th>'
+ '<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:68px">Min</th>'
+ '<th style="padding:6px 8px;text-align:center;font-weight:600;color:#64748b;width:68px">Max</th>'
+ '</tr></thead><tbody>'
+ _peUsedByData.map(function(p, i) {
var defOn = parseInt(p.default_on) === 1;
var mand = parseInt(p.mandatory) === 1;
var hid = parseInt(p.hidden) === 1;
var defVar = p.default_variant || '';
var variantLabel = '';
if (defOn && defVar && hasVariants) {
var found = _peUsedByVariants.find(function(v) { return v.value === defVar; });
variantLabel = found ? found.label : defVar;
}
return '<tr style="border-top:1px solid #f1f5f9">'
+ '<td style="padding:5px 8px"><div style="font-weight:500">' + p.name + '</div><div style="font-size:11px;color:#94a3b8">' + (p.cat_label || '') + '</div></td>'
+ '<td style="padding:5px 8px;text-align:center"><div style="display:flex;align-items:center;justify-content:center;gap:4px">'
+ '<input type="checkbox" ' + (defOn ? 'checked' : '') + ' onchange="peHandleDefaultOn(' + i + ',this.checked,\'' + p.id + '\')" style="width:16px;height:16px;accent-color:#059669;cursor:pointer">'
+ (defOn && variantLabel ? '<span onclick="peShowVariantPicker(' + i + ',\'' + p.id + '\')" style="font-size:10px;color:#059669;cursor:pointer;max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + variantLabel + '">' + variantLabel + '</span>' : '')
+ '</div></td>'
+ '<td style="padding:5px 8px;text-align:center"><input type="checkbox" ' + (mand ? 'checked' : '') + ' onchange="_peUsedByData[' + i + '].mandatory=this.checked?1:0;peUpdateLinkFlag(\'' + p.id + '\',\'' + sid + '\',\'mandatory\',this.checked)" style="width:16px;height:16px;accent-color:#f59e0b;cursor:pointer"></td>'
+ '<td style="padding:5px 8px;text-align:center"><input type="checkbox" ' + (hid ? 'checked' : '') + ' onchange="_peUsedByData[' + i + '].hidden=this.checked?1:0;peUpdateLinkFlag(\'' + p.id + '\',\'' + sid + '\',\'hidden\',this.checked)" style="width:16px;height:16px;accent-color:#ef4444;cursor:pointer"></td>'
+ '<td style="padding:5px 8px"><input type="number" min="0" step="1" value="' + (p.default_qty ?? '') + '" onchange="_peUsedByData[' + i + '].default_qty=this.value;peUpdateLinkFlag(\'' + p.id + '\',\'' + sid + '\',\'default_qty\',this.value)" style="width:58px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:11px;text-align:right;font-family:inherit"></td>'
+ '<td style="padding:5px 8px"><input type="number" min="0" step="1" value="' + (p.min_qty ?? '') + '" onchange="_peUsedByData[' + i + '].min_qty=this.value;peUpdateLinkFlag(\'' + p.id + '\',\'' + sid + '\',\'min_qty\',this.value)" style="width:58px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:11px;text-align:right;font-family:inherit"></td>'
+ '<td style="padding:5px 8px"><input type="number" min="0" step="1" value="' + (p.max_qty ?? '') + '" onchange="_peUsedByData[' + i + '].max_qty=this.value;peUpdateLinkFlag(\'' + p.id + '\',\'' + sid + '\',\'max_qty\',this.value)" style="width:58px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:11px;text-align:right;font-family:inherit"></td>'
+ '</tr>';
}).join('')
+ '</tbody></table>';
}
// === FLIK-SYSTEM ===
function peSetTab(tab) {
document.querySelectorAll('.pe-tab-panel').forEach(p => p.style.display = 'none');
document.querySelectorAll('.pe-tab').forEach(b => { b.style.borderBottomColor = 'transparent'; b.style.color = '#64748b'; });
const panel = document.getElementById('peTab' + tab.charAt(0).toUpperCase() + tab.slice(1));
if(panel) panel.style.display = 'block';
const btn = document.querySelector('.pe-tab[data-tab="'+tab+'"]');
if(btn) { btn.style.borderBottomColor = '#024550'; btn.style.color = '#024550'; }
// Ladda galleri/spec/dokument vid första öppning
if(tab === 'bilder') peRenderGallery();
if(tab === 'spec') peRenderAttributes();
if(tab === 'dokument') peRenderDocuments();
}
// === GALLERI ===
let _peGallery = [];
let _peProductId = '';
function peInitGallery(productId, gallery) {
_peProductId = productId;
_peGallery = Array.isArray(gallery) ? [...gallery] : [];
}
function peRenderGallery() {
const el = document.getElementById('peGalleryGrid');
if(!el) return;
if(!_peGallery.length) { el.innerHTML = '<div style="grid-column:1/-1;padding:20px;text-align:center;color:#94a3b8;font-size:12px;border:1px dashed #e5e7eb;border-radius:8px">Inga galleribilder</div>'; return; }
el.innerHTML = _peGallery.map((img, i) =>
'<div style="position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;border:1px solid #e5e7eb;cursor:pointer" onclick="peSetMainImage('+i+')" title="Klicka för att sätta som huvudbild">'
+'<img src="'+img+'" style="width:100%;height:100%;object-fit:cover">'
+'<button onclick="event.stopPropagation();peRemoveGalleryImage('+i+')" style="position:absolute;top:2px;right:2px;width:20px;height:20px;background:rgba(0,0,0,.6);color:#fff;border:none;border-radius:50%;font-size:12px;cursor:pointer;display:flex;align-items:center;justify-content:center;line-height:1">×</button>'
+'</div>'
).join('');
}
async function peUploadGalleryImages(productId) {
const input = document.getElementById('peGalleryInput');
if(!input || !input.files.length) return;
for(const file of input.files) {
const fd = new FormData();
fd.append('id', productId);
fd.append('image', file);
try {
const res = await fetch('/api/products.php?upload=1&gallery=1', { method:'POST', body: fd });
const data = await res.json();
if(data.success && data.gallery) {
_peGallery = data.gallery;
}
} catch(e) { console.error('Gallery upload error:', e); }
}
peRenderGallery();
input.value = '';
}
async function peRemoveGalleryImage(idx) {
const imgPath = _peGallery[idx];
if(!imgPath) return;
try {
const res = await fetch('/api/products.php?remove_gallery=1', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ id: _peProductId, img_path: imgPath })
});
const data = await res.json();
if(data.success) { _peGallery = data.gallery || []; peRenderGallery(); }
} catch(e) { console.error('Remove gallery error:', e); }
}
async function peSetMainImage(idx) {
const galleryImg = _peGallery[idx];
if(!galleryImg) return;
// Byt: gammal huvudbild → gallery, galleribild → huvudbild
const mainPreview = document.getElementById('peImgPreview');
const currentMain = mainPreview?.querySelector('img')?.src || '';
// Uppdatera preview
mainPreview.innerHTML = '<img src="'+galleryImg+'" style="width:100%;height:100%;object-fit:cover">';
// Byt i gallery-array
_peGallery.splice(idx, 1);
// Lägg gammal huvudbild i gallery om den finns
if(currentMain && !currentMain.includes('data:')) {
// Extrahera relativ path
const url = new URL(currentMain, window.location.origin);
const relPath = url.pathname.replace(/^\//, '');
if(relPath.startsWith('Photo/')) _peGallery.unshift(relPath);
}
// Spara direkt till DB
try {
await fetch('/api/products.php', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ id: _peProductId, img: galleryImg, gallery: _peGallery })
});
} catch(e) { console.error('Set main image error:', e); }
peRenderGallery();
}
// === SPECIFIKATIONER (nyckel-värde) ===
let _peAttributes = [];
function peInitAttributes(specs) {
_peAttributes = (specs && specs.attributes) ? [...specs.attributes] : [];
}
function peRenderAttributes() {
const el = document.getElementById('peAttributesTable');
if(!el) return;
if(!_peAttributes.length) { el.innerHTML = '<div style="padding:20px;text-align:center;color:#94a3b8;font-size:12px">Inga specifikationer. Klicka "+ Lägg till".</div>'; return; }
el.innerHTML = '<table style="width:100%;border-collapse:collapse;font-size:12px">'
+'<thead><tr style="background:#f8f9fa;position:sticky;top:0"><th style="padding:6px 8px;text-align:left;font-weight:600;color:#64748b">Egenskap</th><th style="padding:6px 8px;text-align:left;font-weight:600;color:#64748b">Värde</th><th style="padding:6px 8px;width:30px"></th></tr></thead><tbody>'
+ _peAttributes.map((a, i) =>
'<tr style="border-top:1px solid #f1f5f9">'
+'<td style="padding:4px 6px"><input value="'+((a.key||'').replace(/"/g,'"'))+'" onchange="_peAttributes['+i+'].key=this.value" style="width:100%;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:12px;font-family:inherit" placeholder="t.ex. Vikt"></td>'
+'<td style="padding:4px 6px"><input value="'+((a.value||'').replace(/"/g,'"'))+'" onchange="_peAttributes['+i+'].value=this.value" style="width:100%;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:12px;font-family:inherit" placeholder="t.ex. 45 kg"></td>'
+'<td style="padding:4px 6px;text-align:center"><button onclick="_peAttributes.splice('+i+',1);peRenderAttributes()" style="background:none;border:none;color:#ef4444;cursor:pointer;font-size:14px">×</button></td>'
+'</tr>'
).join('')
+'</tbody></table>';
}
function peAddAttribute() {
_peAttributes.push({ key: '', value: '' });
peRenderAttributes();
}
// === REGLER (tillval ↔ varianter) ===
let _peRules = [];
function peInitRules(specs) {
_peRules = (specs && specs.rules) ? [...specs.rules] : [];
}
function peGetVariantFields() {
if(_peSpecsType === 'window_sizes' && _peSizes.length) return ['w','h','price','note','article_id'];
if(_peSpecsType === 'style_width_variants' && _peStyles.length) return ['style','width_mm','price'];
if(_peSpecsType === 'width_variants' && _peVariants.length) return ['width_mm','price','label'];
if(!_peVariants.length) return [];
return Object.keys(_peVariants[0]).filter(k => k !== '__idx');
}
function peGetTillvalOptions() {
const svc = (_peServices || []).filter(s => s.linked).map(s => ({id: s.id, name: s.name}));
const acc = (_peAccessories || []).filter(s => s.linked).map(s => ({id: s.id, name: s.name}));
return [...svc, ...acc];
}
function peRenderRules() {
const el = document.getElementById('peRulesTable');
if(!el) return;
const fields = peGetVariantFields();
const tillval = peGetTillvalOptions();
const operators = [{v:'*',l:'Alla'},{v:'==',l:'='},{v:'!=',l:'≠'},{v:'<',l:'<'},{v:'<=',l:'≤'},{v:'>',l:'>'},{v:'>=',l:'≥'}];
const actions = [
{v:'max',l:'Max antal',g:'Antal'},
{v:'min',l:'Min antal',g:'Antal'},
{v:'berakna_antal',l:'Beräkna antal',g:'Antal'},
{v:'kraver',l:'Kräver',g:'Villkor'},
{v:'exkluderar',l:'Exkluderar',g:'Villkor'},
{v:'inkluderar',l:'Inkluderar',g:'Villkor'},
{v:'satt_pris',l:'Sätt pris',g:'Pris'},
{v:'pris_tillagg',l:'Pristillägg (+/-)',g:'Pris'},
{v:'pris_per_enhet',l:'Pris × enhet',g:'Pris'},
{v:'pris_multiplikator',l:'Pris × faktor',g:'Pris'},
{v:'satt_langd',l:'Längd (mm)',g:'Dimension'},{v:'satt_bredd',l:'Bredd (mm)',g:'Dimension'},{v:'satt_grundpris',l:'Sätt produktpris',g:'Produkt'}
];
let html = '';
if(!_peRules.length) {
html = '<div style="padding:16px;text-align:center;color:#94a3b8;font-size:12px">Inga regler. Klicka "+ Lägg till".</div>';
} else {
html = _peRules.map((r, i) => {
const fieldOpts = fields.map(f => '<option value="'+f+'"'+(f===r.field?' selected':'')+'>'+f+'</option>').join('');
const opOpts = operators.map(o => '<option value="'+o.v+'"'+(o.v===r.operator?' selected':'')+'>'+o.l+'</option>').join('');
const tvOpts = tillval.map(t => '<option value="'+t.id+'"'+(t.id===r.tillval_id?' selected':'')+'>'+t.name+'</option>').join('');
const groups = {};
actions.forEach(a => { if(!groups[a.g]) groups[a.g]=[]; groups[a.g].push(a); });
const actOpts = Object.entries(groups).map(([g,acts]) => '<optgroup label="'+g+'">'+acts.map(a=>'<option value="'+a.v+'"'+(a.v===r.action?' selected':'')+'>'+a.l+'</option>').join('')+'</optgroup>').join('');
const s = 'padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px;font-size:11px;font-family:inherit;background:#fff';
const isProductAction = r.action === 'satt_grundpris';
const isDimension = r.action==='satt_langd'||r.action==='satt_bredd';
const needsFormula = ['berakna_antal','pris_per_enhet','pris_multiplikator','satt_grundpris','satt_langd','satt_bredd'].includes(r.action);
const chainOpHtml = isDimension ? '<select onchange="_peRules['+i+'].chain_op=this.value" style="padding:3px 2px;border:1.5px solid #bae6fd;border-radius:4px;font-size:13px;font-weight:700;text-align:center;color:#0284c7;background:#f0f9ff;width:36px;font-family:inherit;cursor:pointer"><option value="="'+((r.chain_op||'=')=='='?' selected':'')+'>=</option><option value="+"'+(r.chain_op=='+'?' selected':'')+'>+</option><option value="-"'+(r.chain_op=='-'?' selected':'')+'>−</option><option value="*"'+(r.chain_op=='*'?' selected':'')+'>×</option></select>' : '<span style="font-size:10px;color:#64748b">=</span>';
return '<div style="border:1px solid #e5e7eb;border-radius:8px;padding:10px;margin-bottom:6px;background:#fafbfc;position:relative">'
+'<div style="position:absolute;top:6px;left:10px;font-size:10px;font-weight:700;color:#cbd5e1">'+(i+1)+'</div>'
+'<button onclick="_peRules.splice('+i+',1);peRenderRules()" style="position:absolute;top:4px;right:6px;background:none;border:none;color:#ef4444;cursor:pointer;font-size:14px">×</button>'
+'<div style="display:grid;grid-template-columns:28px 1fr 50px 60px;gap:4px;align-items:center;margin-bottom:6px">'
+'<span style="font-size:10px;font-weight:700;color:#64748b;text-transform:uppercase">OM</span>'
+'<select onchange="_peRules['+i+'].field=this.value;peRenderRules()" style="'+s+'"><option value="">Alla</option>'+fieldOpts+'</select>'
+'<select onchange="_peRules['+i+'].operator=this.value" style="'+s+'">'+opOpts+'</select>'
+'<input value="'+((r.value||'').toString().replace(/"/g,'"'))+'" onchange="_peRules['+i+'].value=this.value" style="'+s+'" placeholder="\u2014">'
+'</div>'
+'<div style="display:grid;grid-template-columns:28px 1fr 36px 1fr;gap:4px;align-items:center">'
+'<span style="font-size:10px;font-weight:700;color:#64748b;text-transform:uppercase">S\u00c5</span>'
+'<select onchange="_peRules['+i+'].action=this.value;peRenderRules()" style="'+s+'">'+actOpts+'</select>'
+chainOpHtml
+'<input value="'+((r.action_value||'').toString().replace(/"/g,'"'))+'" onchange="_peRules['+i+'].action_value=this.value" style="'+s+';'+(needsFormula?'font-family:monospace;':'') +'" placeholder="'+(needsFormula?'{w} * 100':'v\u00e4rde')+'">'
+'</div>'
+(isProductAction ? '' : '<div style="margin-top:4px;display:grid;grid-template-columns:28px 1fr;gap:4px;align-items:center"><span style="font-size:10px;font-weight:700;color:#64748b">P\u00c5</span><select onchange="_peRules['+i+'].tillval_id=this.value" style="'+s+'"><option value="">\u2014 egen produkt \u2014</option>'+tvOpts+'</select></div>')
+'<div style="margin-top:4px;padding-left:32px"><input value="'+((r.label||'').replace(/"/g,'"'))+'" onchange="_peRules['+i+'].label=this.value" style="'+s+';width:100%;color:#94a3b8;font-style:italic" placeholder="Notering..."></div>'
+'</div>';
}).join('');
}
// Hjälptext med tillgängliga fält
let helpFields = fields.length ? fields.map(f => '<code style="background:#e0f2fe;padding:1px 4px;border-radius:3px;font-size:10px">{'+f+'}</code>').join(' ') : '<em>Lägg till varianter först</em>';
el.innerHTML = html
+'<div style="padding:8px;border-top:1px solid #e5e7eb;font-size:10px;color:#94a3b8;line-height:1.6">'
+'<strong>Formler:</strong> Använd '+helpFields+' i värdefält. '
+'Exempel: <code style="background:#f1f5f9;padding:1px 4px;border-radius:3px;font-size:10px">Math.floor({bredd} / 40)</code> · '
+'<code style="background:#f1f5f9;padding:1px 4px;border-radius:3px;font-size:10px">{pris_per_m2} * {area}</code> · '
+'<code style="background:#f1f5f9;padding:1px 4px;border-radius:3px;font-size:10px">{totalt} * 1.2</code>'
+'</div>';
}
function peAddRule() {
_peRules.push({ field:'', operator:'*', value:'', tillval_id:'', action:'satt_pris', action_value:'', label:'', chain_op:'=' });
peRenderRules();
}
// Utvärdera en regel-formel med variant-data
function peEvalFormula(formula, variantData) {
if(!formula) return 0;
// Om rent nummer
const num = parseFloat(formula);
if(!isNaN(num) && String(num) === String(formula).trim()) return num;
// Ersätt {fält} med värden
let expr = String(formula).replace(/\{(\w+)\}/g, (_, key) => {
const val = variantData[key];
return val !== undefined && val !== '' ? parseFloat(val) || 0 : 0;
});
try { return Function('"use strict"; return (' + expr + ')')(); }
catch(e) { console.warn('Formelfel:', expr, e); return 0; }
}
// Kör alla regler mot en variant-rad och returnera beräknade tillval
function peApplyRules(rules, variantData, tillvalList) {
const result = {};
(rules || []).forEach(rule => {
// Kolla villkor
if(rule.operator !== '*' && rule.field) {
const fieldVal = parseFloat(variantData[rule.field]) || 0;
const ruleVal = parseFloat(rule.value) || 0;
let match = false;
switch(rule.operator) {
case '==': match = fieldVal == ruleVal; break;
case '!=': match = fieldVal != ruleVal; break;
case '<': match = fieldVal < ruleVal; break;
case '<=': match = fieldVal <= ruleVal; break;
case '>': match = fieldVal > ruleVal; break;
case '>=': match = fieldVal >= ruleVal; break;
}
if(!match) return;
}
const tv = rule.tillval_id || '__product';
if(!result[tv]) result[tv] = { pris: null, antal: null, inkluderad: null, exkluderad: false };
const val = peEvalFormula(rule.action_value, variantData);
switch(rule.action) {
case 'max': result[tv].max = val; break;
case 'min': result[tv].min = val; break;
case 'berakna_antal': result[tv].antal = val; break;
case 'kraver': result[tv].inkluderad = true; result[tv].antal = result[tv].antal || 1; break;
case 'exkluderar': result[tv].exkluderad = true; break;
case 'inkluderar': result[tv].inkluderad = true; break;
case 'satt_pris': result[tv].pris = val; break;
case 'pris_tillagg': result[tv].pris = (result[tv].pris || 0) + val; break;
case 'pris_per_enhet': result[tv].pris = val; break;
case 'pris_multiplikator': result[tv].multiplikator = val; break;
case 'satt_langd': { var op=rule.chain_op||'='; if(op==='+') result[tv].langd=(result[tv].langd||0)+val; else if(op==='-') result[tv].langd=(result[tv].langd||0)-val; else if(op==='*') result[tv].langd=(result[tv].langd||0)*val; else result[tv].langd=val; break; } case 'satt_bredd': { var op=rule.chain_op||'='; if(op==='+') result[tv].bredd=(result[tv].bredd||0)+val; else if(op==='-') result[tv].bredd=(result[tv].bredd||0)-val; else if(op==='*') result[tv].bredd=(result[tv].bredd||0)*val; else result[tv].bredd=val; break; } case 'satt_grundpris': result['__product'] = result['__product'] || {}; result['__product'].pris = val; break;
}
});
return result;
}
// === DOKUMENT ===
let _peDocuments = [];
function peInitDocuments(documents) {
_peDocuments = Array.isArray(documents) ? [...documents] : [];
}
function peRenderDocuments() {
const el = document.getElementById('peDocumentsList');
if(!el) return;
if(!_peDocuments.length) { el.innerHTML = '<div style="padding:20px;text-align:center;color:#94a3b8;font-size:12px">Inga dokument uppladdade.</div>'; return; }
const typeLabels = { manual:'Manual', datablad:'Datablad', certifikat:'Certifikat', ovrigt:'Övrigt' };
el.innerHTML = '<table style="width:100%;border-collapse:collapse;font-size:12px">'
+'<thead><tr style="background:#f8f9fa;position:sticky;top:0"><th style="padding:6px 8px;text-align:left;font-weight:600;color:#64748b">Dokument</th><th style="padding:6px 8px;text-align:left;font-weight:600;color:#64748b">Typ</th><th style="padding:6px 8px;text-align:right;font-weight:600;color:#64748b">Storlek</th><th style="padding:6px 8px;width:60px"></th></tr></thead><tbody>'
+ _peDocuments.map((d, i) => {
const sizeStr = d.size ? (d.size > 1048576 ? (d.size/1048576).toFixed(1)+' MB' : (d.size/1024).toFixed(0)+' KB') : '—';
return '<tr style="border-top:1px solid #f1f5f9">'
+'<td style="padding:6px 8px"><a href="'+d.file+'" target="_blank" style="color:#0284c7;text-decoration:none;font-weight:500">'+escHtml(d.name)+'</a></td>'
+'<td style="padding:6px 8px;color:#64748b"><span style="background:#f1f5f9;padding:2px 8px;border-radius:4px;font-size:11px">'+(typeLabels[d.type]||d.type)+'</span></td>'
+'<td style="padding:6px 8px;text-align:right;color:#94a3b8">'+sizeStr+'</td>'
+'<td style="padding:6px 8px;text-align:center"><button onclick="peRemoveDocument('+i+')" style="background:none;border:none;color:#ef4444;cursor:pointer;font-size:14px" title="Ta bort">×</button></td>'
+'</tr>';
}).join('')
+'</tbody></table>';
}
async function peUploadDocument(productId) {
const input = document.getElementById('peDocInput');
if(!input || !input.files.length) return;
const file = input.files[0];
const docType = document.getElementById('peDocType')?.value || 'manual';
const fd = new FormData();
fd.append('id', productId);
fd.append('document', file);
fd.append('doc_name', file.name.replace(/\.[^.]+$/, ''));
fd.append('doc_type', docType);
try {
const res = await fetch('/api/products.php?upload=1&document=1', { method:'POST', body: fd });
const data = await res.json();
if(data.success && data.documents) {
_peDocuments = data.documents;
peRenderDocuments();
} else {
alert('Fel: ' + (data.error || 'okänt'));
}
} catch(e) { alert('Uppladdningsfel: ' + e.message); }
input.value = '';
}
async function peRemoveDocument(idx) {
const doc = _peDocuments[idx];
if(!doc) return;
try {
const res = await fetch('/api/products.php?remove_document=1', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ id: _peProductId, doc_file: doc.file })
});
const data = await res.json();
if(data.success) { _peDocuments = data.documents || []; peRenderDocuments(); }
} catch(e) { console.error('Remove document error:', e); }
}
let _peCroppedFile = null;
function previewProductImage(input) {
if(!input.files || !input.files[0]) return;
const file = input.files[0];
const reader = new FileReader();
reader.onload = e => openImageCropper(e.target.result, file.name);
reader.readAsDataURL(file);
}
function openImageCropper(dataUrl, fileName) {
// Remove existing cropper
document.getElementById('imageCropperModal')?.remove();
const modal = document.createElement('div');
modal.id = 'imageCropperModal';
modal.style.cssText = 'position:fixed;inset:0;z-index:100001;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;font-family:Inter,sans-serif';
modal.innerHTML = '<div style="background:#fff;border-radius:16px;width:600px;max-width:95vw;overflow:hidden">'
+'<div style="padding:16px 20px;border-bottom:1px solid #e5e7eb;display:flex;justify-content:space-between;align-items:center">'
+'<h3 style="margin:0;font-size:16px;font-weight:700;color:#1a1a1a">Beskär bild</h3>'
+'<button onclick="document.getElementById(\'imageCropperModal\').remove()" style="background:none;border:none;font-size:22px;cursor:pointer;color:#94a3b8;line-height:1">×</button>'
+'</div>'
// Crop area
+'<div style="position:relative;width:100%;aspect-ratio:16/9;overflow:hidden;background:#111;cursor:grab" id="cropContainer">'
+'<img id="cropImage" src="'+dataUrl+'" style="position:absolute;transform-origin:0 0;user-select:none;-webkit-user-drag:none" draggable="false">'
+'</div>'
// Controls
+'<div style="padding:16px 20px">'
+'<div style="display:flex;align-items:center;gap:12px;margin-bottom:16px">'
+'<span style="font-size:12px;color:#64748b;min-width:50px">Zoom</span>'
+'<input type="range" id="cropZoom" min="10" max="300" value="100" style="flex:1;accent-color:#024550" oninput="updateCropZoom(this.value)">'
+'<span id="cropZoomLabel" style="font-size:12px;color:#64748b;min-width:40px;text-align:right">100%</span>'
+'</div>'
+'<div style="display:flex;gap:8px">'
+'<button onclick="applyCrop()" style="flex:1;padding:10px;background:#024550;color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">Använd beskärning</button>'
+'<button onclick="applyCropFull()" style="padding:10px 16px;background:#f1f5f9;color:#334155;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">Hela bilden</button>'
+'<button onclick="document.getElementById(\'imageCropperModal\').remove()" style="padding:10px 16px;background:#fee2e2;color:#dc2626;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit">Avbryt</button>'
+'</div>'
+'</div>'
+'</div>';
document.body.appendChild(modal);
// Init crop state
const img = document.getElementById('cropImage');
const container = document.getElementById('cropContainer');
let scale = 1, posX = 0, posY = 0, dragging = false, startX = 0, startY = 0;
img.onload = function() {
// Fit image to cover container
const cw = container.offsetWidth, ch = container.offsetHeight;
const iw = img.naturalWidth, ih = img.naturalHeight;
const coverScale = Math.max(cw / iw, ch / ih);
scale = coverScale;
posX = (cw - iw * scale) / 2;
posY = (ch - ih * scale) / 2;
img.style.transform = 'translate('+posX+'px,'+posY+'px) scale('+scale+')';
document.getElementById('cropZoom').value = 100;
document.getElementById('cropZoomLabel').textContent = '100%';
// Store base scale
img._baseScale = coverScale;
img._scale = scale;
img._posX = posX;
img._posY = posY;
};
if(img.complete) img.onload();
// Drag
container.onmousedown = function(e) {
dragging = true;
startX = e.clientX - (img._posX || 0);
startY = e.clientY - (img._posY || 0);
container.style.cursor = 'grabbing';
e.preventDefault();
};
document.addEventListener('mousemove', window._cropMouseMove = function(e) {
if(!dragging) return;
img._posX = e.clientX - startX;
img._posY = e.clientY - startY;
img.style.transform = 'translate('+img._posX+'px,'+img._posY+'px) scale('+(img._scale||1)+')';
});
document.addEventListener('mouseup', window._cropMouseUp = function() {
dragging = false;
if(container) container.style.cursor = 'grab';
});
// Touch support
container.ontouchstart = function(e) {
if(e.touches.length === 1) {
dragging = true;
startX = e.touches[0].clientX - (img._posX || 0);
startY = e.touches[0].clientY - (img._posY || 0);
e.preventDefault();
}
};
container.ontouchmove = function(e) {
if(!dragging || e.touches.length !== 1) return;
img._posX = e.touches[0].clientX - startX;
img._posY = e.touches[0].clientY - startY;
img.style.transform = 'translate('+img._posX+'px,'+img._posY+'px) scale('+(img._scale||1)+')';
e.preventDefault();
};
container.ontouchend = function() { dragging = false; };
// Scroll wheel zoom
container.onwheel = function(e) {
e.preventDefault();
const slider = document.getElementById('cropZoom');
let v = parseInt(slider.value) + (e.deltaY < 0 ? 10 : -10);
v = Math.max(10, Math.min(300, v));
slider.value = v;
updateCropZoom(v);
};
// Store filename for later
img._fileName = fileName;
}
function updateCropZoom(val) {
const img = document.getElementById('cropImage');
if(!img || !img._baseScale) return;
const pct = parseInt(val);
img._scale = img._baseScale * (pct / 100);
img.style.transform = 'translate('+(img._posX||0)+'px,'+(img._posY||0)+'px) scale('+img._scale+')';
document.getElementById('cropZoomLabel').textContent = pct + '%';
}
function applyCrop() {
const img = document.getElementById('cropImage');
const container = document.getElementById('cropContainer');
if(!img || !container) return;
const cw = container.offsetWidth, ch = container.offsetHeight;
const canvas = document.createElement('canvas');
canvas.width = cw * 2; // 2x for retina
canvas.height = ch * 2;
const ctx = canvas.getContext('2d');
ctx.scale(2, 2);
// Draw image with current transform
const s = img._scale || 1;
const px = img._posX || 0;
const py = img._posY || 0;
ctx.drawImage(img, px, py, img.naturalWidth * s, img.naturalHeight * s);
canvas.toBlob(function(blob) {
_peCroppedFile = new File([blob], (img._fileName || 'cropped.webp').replace(/\.[^.]+$/, '.webp'), { type: 'image/webp' });
// Update preview
const preview = document.getElementById('peImgPreview');
if(preview) preview.innerHTML = '<img src="'+URL.createObjectURL(blob)+'" style="width:100%;height:100%;object-fit:cover">';
document.getElementById('peUploadStatus').textContent = 'Beskuren bild (' + (blob.size/1024).toFixed(0) + ' KB) — sparas vid klick på Spara';
// Cleanup
document.removeEventListener('mousemove', window._cropMouseMove);
document.removeEventListener('mouseup', window._cropMouseUp);
document.getElementById('imageCropperModal')?.remove();
}, 'image/webp', 0.9);
}
function applyCropFull() {
const img = document.getElementById('cropImage');
if(!img) return;
// Use full original image, just convert to webp at original size
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
canvas.toBlob(function(blob) {
_peCroppedFile = new File([blob], (img._fileName || 'full.webp').replace(/\.[^.]+$/, '.webp'), { type: 'image/webp' });
const preview = document.getElementById('peImgPreview');
if(preview) preview.innerHTML = '<img src="'+URL.createObjectURL(blob)+'" style="width:100%;height:100%;object-fit:cover">';
document.getElementById('peUploadStatus').textContent = 'Hela bilden (' + (blob.size/1024).toFixed(0) + ' KB) — sparas vid klick på Spara';
document.removeEventListener('mousemove', window._cropMouseMove);
document.removeEventListener('mouseup', window._cropMouseUp);
document.getElementById('imageCropperModal')?.remove();
}, 'image/webp', 0.9);
}
async function saveProduct(id) {
const catSel = document.getElementById('peCat');
const stockSel = document.getElementById('peStock');
const catLabel = catSel.options[catSel.selectedIndex].text;
const stockClass = stockSel.options[stockSel.selectedIndex].dataset.class;
// Upload image first if cropped or selected
const fileInput = document.getElementById('peFileInput');
let imgPath = '';
const imageFile = _peCroppedFile || (fileInput && fileInput.files && fileInput.files[0]);
if(imageFile) {
const fd = new FormData();
fd.append('id', id);
fd.append('image', imageFile);
document.getElementById('peUploadStatus').textContent = 'Laddar upp bild...';
try {
const upRes = await fetch('/api/products.php?upload=1', { method:'POST', body: fd });
const upData = await upRes.json();
if(upData.success) {
imgPath = upData.img;
document.getElementById('peUploadStatus').textContent = 'Bild uppladdad!';
} else {
document.getElementById('peUploadStatus').textContent = 'Fel: ' + (upData.error || 'okänt');
return;
}
} catch(e) {
document.getElementById('peUploadStatus').textContent = 'Uppladdningsfel: ' + e.message;
return;
}
}
const body = {
id: id,
name: document.getElementById('peName').value,
cat: catSel.value,
cat_label: catLabel,
description: document.getElementById('peDesc').value,
price: parseFloat(document.getElementById('pePrice').value) || 0,
cost_price: parseFloat(document.getElementById('peCostPrice').value) || null, cost_currency: document.getElementById('peCostCurrency')?.value || 'SEK',
stock: stockSel.value,
stock_class: stockClass,
green_tech_eligible: document.getElementById('peGreenTech').checked ? 1 : 0,
rot_eligible: document.getElementById('peRotEligible')?.checked ? 1 : 0,
supplier_id: parseInt(document.getElementById('peSupplier')?.value) || null,
unit: document.getElementById('peUnit')?.value || 'st',
markup_type: document.getElementById('peMarkupType')?.value || 'percent',
markup_value: parseFloat(document.getElementById('peMarkupValue')?.value) || 0,
tillval_obligatorisk: document.getElementById('peTillvalOblig')?.checked ? 1 : 0,
tillval_hidden: document.getElementById('peTillvalHidden')?.checked ? 1 : 0
};
if(imgPath) body.img = imgPath;
// Spara varianter + attribut i specs
const existingSpecs = catalogProducts.find(x=>x.id===id)?.specs || {};
body.specs = {...existingSpecs};
// Spara rätt specs-typ
if(_peSpecsType === 'style_width_variants') {
body.specs.type = 'style_width_variants';
body.specs.styles = _peStyles;
delete body.specs.variants;
} else if(_peSpecsType === 'width_variants') {
body.specs.type = 'width_variants';
body.specs.variants = _peVariants;
} else if(_peSpecsType === 'window_sizes') {
body.specs.type = 'window_sizes';
body.specs.sizes = _peSizes;
body.specs.models = _peWinModels;
body.specs.widths = _peSpecsMeta.widths;
body.specs.heights = _peSpecsMeta.heights;
body.specs.supplier = _peSpecsMeta.supplier;
body.specs.model = _peSpecsMeta.model;
} else if(_peVariants.length) body.specs.variants = _peVariants;
if(_peAttributes.length) body.specs.attributes = _peAttributes.filter(a => a.key || a.value);
else delete body.specs.attributes;
if(_peRules.length) body.specs.rules = _peRules.filter(r => r.action && r.action_value);
else delete body.specs.rules;
// Spara tjänst- och tillbehörs-kopplingar (båda i product_services)
const linkedSvc = _peServices.filter(s => s.linked).map(s => ({
service_id: s.id,
default_on: s.default_on ? 1 : 0,
hidden: s.hidden ? 1 : 0,
mandatory: s.mandatory ? 1 : 0,
default_variant: s.default_variant || null,
default_qty: s.default_qty === '' ? null : s.default_qty,
min_qty: s.min_qty === '' ? null : s.min_qty,
max_qty: s.max_qty === '' ? null : s.max_qty
}));
const linkedAcc = _peAccessories.filter(s => s.linked).map(s => ({
service_id: s.id,
default_on: s.default_on ? 1 : 0,
hidden: s.hidden ? 1 : 0,
mandatory: s.mandatory ? 1 : 0,
default_variant: s.default_variant || null,
default_qty: s.default_qty === '' ? null : s.default_qty,
min_qty: s.min_qty === '' ? null : s.min_qty,
max_qty: s.max_qty === '' ? null : s.max_qty
}));
body._services = [...linkedSvc, ...linkedAcc];
try {
const res = await fetch('/api/products.php', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify(body)
});
const data = await res.json();
if(data.success) {
document.getElementById('productEditModal')?.remove();
await loadCatalogFromDB();
filterCatalog();
} else {
alert('Fel: ' + (data.error || 'okänt'));
}
} catch(e) {
alert('Fel: ' + e.message);
}
}
async function deleteProduct(id) {
if(!confirm('Ta bort produkt ' + id + '?')) return;
try {
const res = await fetch('/api/products.php?id=' + id, { method: 'DELETE' });
const data = await res.json();
if(data.success) {
document.getElementById('productEditModal')?.remove();
await loadCatalogFromDB();
filterCatalog();
}
} catch(e) { alert('Fel: ' + e.message); }
}
function showAddProductModal() {
const cats = [{v:'solceller',l:'Solceller'},{v:'batteri',l:'Batteri'},{v:'batteri_utbyggnad',l:'Batteri Utbyggnad'},{v:'laddbox',l:'Laddbox'},{v:'fonster',l:'Fönster'},{v:'dorrar',l:'Dörrar'},{v:'tak',l:'Tak'},{v:'varmepump',l:'Värmepump'},{v:'taktvatt',l:'Taktvätt'},{v:'isolering',l:'Isolering'},{v:'tjanster',l:'Tjänster'},{v:'tillbehor',l:'Tillbehör'}];
const stocks = [{v:'I lager',c:'green'},{v:'Få kvar',c:'yellow'},{v:'Beställning',c:'blue'},{v:'Slut',c:'red'}];
const modal = document.createElement('div');
modal.id = 'productEditModal';
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:600px;width:100%;max-height:90vh;overflow-y:auto;box-shadow:0 25px 60px rgba(0,0,0,.3)">'
+'<div style="padding:20px 24px;border-bottom:1px solid #f1f5f9"><h2 style="font-size:18px;font-weight:700;margin:0">Ny produkt</h2></div>'
+'<div style="padding:24px">'
+'<div id="peImgPreview" style="width:100%;aspect-ratio:16/9;background:#f8f9fa;border-radius:10px;border:2px dashed #e5e7eb;display:flex;align-items:center;justify-content:center;overflow:hidden;cursor:pointer;margin-bottom:8px" onclick="document.getElementById(\'peFileInput\').click()">'
+'<div style="text-align:center;color:#94a3b8"><svg viewBox="0 0 24 24" style="width:40px;height:40px;stroke:#cbd5e1;fill:none;stroke-width:1.5;margin:0 auto 8px;display:block"><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 style="font-size:13px">Klicka för att ladda upp bild</div></div>'
+'</div>'
+'<input type="file" id="peFileInput" accept="image/*" style="display:none" onchange="previewProductImage(this)">'
+'<div id="peUploadStatus" style="font-size:11px;color:#94a3b8;margin-bottom:12px"></div>'
+'<div style="display:grid;grid-template-columns:100px 1fr;gap:12px 16px;align-items:center">'
+'<label style="font-size:12px;font-weight:600;color:#64748b">ID</label>'
+'<input id="peId" placeholder="t.ex. SOL05" style="padding:10px 14px;border:1px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit">'
+'<label style="font-size:12px;font-weight:600;color:#64748b">Namn</label>'
+'<input id="peName" placeholder="Produktnamn" style="padding:10px 14px;border:1px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit">'
+'<label style="font-size:12px;font-weight:600;color:#64748b">Kategori</label>'
+'<select id="peCat" style="padding:10px 14px;border:1px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit">'+cats.map(c=>'<option value="'+c.v+'">'+c.l+'</option>').join('')+'</select>'
+'<label style="font-size:12px;font-weight:600;color:#64748b">Pris (kr)</label>'
+'<input id="pePrice" type="number" placeholder="0" style="padding:10px 14px;border:1px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit">'
+'<label style="font-size:12px;font-weight:600;color:#64748b">Watt</label>'
+'<input id="peWatt" type="number" placeholder="Solpaneler" style="padding:10px 14px;border:1px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit">'
+'<label style="font-size:12px;font-weight:600;color:#64748b">kWh</label>'
+'<input id="peKwh" type="number" step="0.1" placeholder="Batteri" style="padding:10px 14px;border:1px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit">'
+'<label style="font-size:12px;font-weight:600;color:#64748b">Lagerstatus</label>'
+'<select id="peStock" style="padding:10px 14px;border:1px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit">'+stocks.map(s=>'<option value="'+s.v+'" data-class="'+s.c+'">'+s.v+'</option>').join('')+'</select>'
+'<label style="font-size:12px;font-weight:600;color:#64748b">Beskrivning</label>'
+'<textarea id="peDesc" rows="3" placeholder="Produktbeskrivning..." style="padding:10px 14px;border:1px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit;resize:vertical"></textarea>'
+'</div>'
+'<div style="display:flex;gap:10px;margin-top:20px">'
+'<button onclick="saveNewProduct()" style="flex:1;padding:12px;background:#024550;color:#fff;border:none;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit">Skapa produkt</button>'
+'<button onclick="this.closest(\'#productEditModal\').remove()" style="padding:12px 20px;background:#f1f5f9;color:#64748b;border:none;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit">Avbryt</button>'
+'</div></div></div>';
document.body.appendChild(modal);
}
async function saveNewProduct() {
const id = document.getElementById('peId').value.trim();
if(!id) { alert('ID krävs'); return; }
const name = document.getElementById('peName').value.trim();
if(!name) { alert('Namn krävs'); return; }
const catSel = document.getElementById('peCat');
const stockSel = document.getElementById('peStock');
// Create product first
const body = {
id: id,
name: name,
cat: catSel.value,
cat_label: catSel.options[catSel.selectedIndex].text,
description: document.getElementById('peDesc').value,
price: parseFloat(document.getElementById('pePrice').value) || 0,
stock: stockSel.value,
stock_class: stockSel.options[stockSel.selectedIndex].dataset.class,
watt: parseInt(document.getElementById('peWatt').value) || null,
kwh_capacity: parseFloat(document.getElementById('peKwh').value) || null
};
try {
const res = await fetch('/api/products.php', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify(body)
});
const data = await res.json();
if(!data.success) { alert('Fel: ' + (data.error||'')); return; }
// Upload image if selected
const fileInput = document.getElementById('peFileInput');
if(fileInput && fileInput.files && fileInput.files[0]) {
const fd = new FormData();
fd.append('id', id);
fd.append('image', fileInput.files[0]);
await fetch('/api/products.php?upload=1', { method:'POST', body: fd });
}
document.getElementById('productEditModal')?.remove();
await loadCatalogFromDB();
filterCatalog();
} catch(e) { alert('Fel: ' + e.message); }
}