// productlist.js — produktlista (tidigare produkter.js, döpt om 2026-04-22)
// produkter.js - Product catalog, filter, render, drag/drop
let catalogProducts = [];
let _suppliers = [];
async function loadSuppliers() {
try {
const r = await fetch('/api/products.php?suppliers=1');
const d = await r.json();
if(d.success) _suppliers = d.suppliers;
} catch(e){}
}
async function loadCatalogFromDB() {
loadSuppliers();
try {
const res = await fetch('/api/products.php');
const data = await res.json();
if(data.success && data.products) {
catalogProducts = data.products.map(p => {
let specs = null;
try { if(p.specs) specs = typeof p.specs === 'string' ? JSON.parse(p.specs) : p.specs; } catch(e){}
return {
id: p.id, name: p.name, cat: p.cat, catLabel: p.cat_label, subcat: p.subcat || '',
desc: p.description, price: parseFloat(p.price),
costPrice: p.cost_price ? parseFloat(p.cost_price) : null, costCurrency: p.cost_currency || 'SEK',
stock: p.stock, stockClass: p.stock_class, img: p.img || '',
watt: p.watt ? parseInt(p.watt) : null,
kwhCapacity: p.kwh_capacity ? parseFloat(p.kwh_capacity) : null,
greenTechEligible: parseInt(p.green_tech_eligible) === 1,
rotEligible: parseInt(p.rot_eligible) === 1,
tillvalObligatorisk: parseInt(p.tillval_obligatorisk) === 1,
tillvalHidden: parseInt(p.tillval_hidden) === 1,
specs: specs,
supplierId: p.supplier_id ? parseInt(p.supplier_id) : null,
unit: p.unit || 'st', taxType: p.tax_type || 'NONE', fixedPrice: parseInt(p.fixed_price) || 0,
markupType: p.markup_type || 'percent',
markupValue: p.markup_value ? parseFloat(p.markup_value) : 0,
gallery: (() => { try { return p.gallery ? (typeof p.gallery === 'string' ? JSON.parse(p.gallery) : p.gallery) : []; } catch(e){ return []; } })(),
documents: (() => { try { return p.documents ? (typeof p.documents === 'string' ? JSON.parse(p.documents) : p.documents) : []; } catch(e){ return []; } })()
};
});
}
} catch(e) { console.error('Kunde inte ladda produkter:', e); }
// Ladda kategoriordning från DB
try { var catRes = await fetch("/api/categories.php?all=1"); var catData = await catRes.json(); if(catData.success && catData.categories) { _catOrder = catData.categories.map(function(c){ return c.id; }); } } catch(e){}
filterCatalog();
}
let _catOrder = []; // Fylls dynamiskt från categories API
// Will be updated from categories API
function filterCatalog(){
const val=document.getElementById('catalogCatSelect').value;
const model='';
const modelSel=null;
const searchTerm = (document.getElementById('catalogSearch')?.value || '').trim().toLowerCase();
let products=catalogProducts;
// Hide tillval categories by default — men OM det finns söktext, visa alla
const tillvalCats = ['tjanster','tillbehor'];
if(!val && !searchTerm) products = products.filter(p => !tillvalCats.includes(p.cat));
if(val){
if(val.startsWith('g:')){
const prefix=val.substring(2);
products=products.filter(p=>p.cat===prefix||p.cat.startsWith(prefix+'_'));
} else {
products=products.filter(p=>p.cat===val);
}
}
if(searchTerm) {
products = products.filter(p =>
p.name.toLowerCase().includes(searchTerm)
|| p.id.toLowerCase().includes(searchTerm)
|| (p.desc && p.desc.toLowerCase().includes(searchTerm))
|| (p.catLabel && p.catLabel.toLowerCase().includes(searchTerm))
);
}
// Sortera efter kategori-ordning i dropdown, sedan namn
products = [...products].sort((a,b)=>{
const ai=_catOrder.indexOf(a.cat), bi=_catOrder.indexOf(b.cat);
const ca=(ai===-1?999:ai)-(bi===-1?999:bi);
if(ca!==0) return ca;
const sa=(a.subcat||'\uffff'), sb=(b.subcat||'\uffff');
const sc=sa.localeCompare(sb,'sv');
if(sc!==0) return sc;
return a.name.localeCompare(b.name,'sv');
});
const models=[...new Set(products.map(p=>p.name))];
if(modelSel) modelSel.innerHTML='<option value="">Alla modeller</option>'+models.map(m=>'<option value="'+m+'">'+m+'</option>').join('');
renderCatalog(products);
}
let _catalogView = 'grid';
function setCatalogView(v) {
_catalogView = v;
document.getElementById('catViewGrid').style.cssText = 'padding:6px 8px;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;'+(v==='grid'?'background:#024550;color:#fff':'background:#fff;color:#64748b');
document.getElementById('catViewList').style.cssText = 'padding:6px 8px;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;'+(v==='list'?'background:#024550;color:#fff':'background:#fff;color:#64748b');
// Ladda kategoriordning från DB
filterCatalog();
}
function renderCatalog(products){
const grid=document.getElementById('catalogGrid');
if(!grid) return;
document.getElementById('catalogCount').textContent=products.length+' produkter';
const isAdmin = (gUserRole === 'systemadmin' || gUserRole === 'admin' || gUserRole === 'saljchef');
if(_catalogView === 'list') { renderCatalogList(products, grid, isAdmin); return; }
grid.style.display = '';
grid.className = '';
let lastCatSubcat = '';
let html = '<div class="catalog-grid">';
products.forEach(p => {
const groupKey = p.catLabel + '|' + (p.subcat || '');
lastCatSubcat = p.catLabel + '|' + (p.subcat || '');
const imgHtml=p.img?'<img src="'+p.img+'" alt="'+p.name+'">':'<svg viewBox="0 0 24 24"><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>';
const galleryThumbs = (p.gallery && p.gallery.length) ? '<div style="display:flex;gap:4px;padding:4px 8px;background:#f8f9fa;border-top:1px solid #f1f5f9">' + p.gallery.slice(0,4).map(g=>'<img src="'+g+'" style="width:28px;height:28px;object-fit:cover;border-radius:4px">').join('') + (p.gallery.length > 4 ? '<span style="width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;color:#94a3b8;background:#e5e7eb;border-radius:4px">+' + (p.gallery.length - 4) + '</span>' : '') + '</div>' : '';
const editBtn = isAdmin ? '<button onclick="event.stopPropagation();editProduct(\''+p.id+'\')" style="position:absolute;top:8px;right:8px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;padding:6px 8px;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,.1);display:flex;align-items:center;gap:4px;font-size:11px;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>' : '';
const greenBadge = p.greenTechEligible ? '<span style="position:absolute;top:8px;left:8px;background:linear-gradient(135deg,#059669,#10b981);color:#fff;font-size:10px;font-weight:700;padding:4px 8px;border-radius:6px;letter-spacing:.3px;box-shadow:0 2px 6px rgba(16,185,129,.3)">GRÖNT TEKNIK-AVDRAG</span>' : '';
let varHtml = '';
if(p.specs && p.specs.type === 'style_width_variants') {
const styles = p.specs.styles;
const allPrices = styles.flatMap(s=>s.variants.map(v=>v.price)).filter(x=>x!=null);
varHtml = '<div style="margin-top:8px;font-size:11px">'
+'<div style="display:flex;gap:4px;flex-wrap:wrap;margin-bottom:4px">'
+ styles.map(s=>'<span style="background:#f1f5f9;color:#334155;padding:2px 8px;border-radius:10px;font-weight:600;font-size:10px">'+s.style+'</span>').join('')
+'</div>'
+(allPrices.length?'<div style="font-weight:600;color:#024550">'+Math.min(...allPrices).toFixed(0)+' – '+Math.max(...allPrices).toFixed(0)+' kr/'+(p.unit||'st')+'</div>':'<div style="color:#94a3b8">Pris saknas</div>')
+'</div>';
} else if(p.specs && p.specs.type === 'width_variants') {
const allPrices = p.specs.variants.map(v=>v.price).filter(x=>x!=null);
const labels = p.specs.variants.map(v=>v.label||(v.width_mm?v.width_mm+'mm':(v.length_mm?v.length_mm+'mm':''))).slice(0,4);
varHtml = '<div style="margin-top:8px;font-size:11px">'
+'<div style="display:flex;gap:4px;flex-wrap:wrap;margin-bottom:4px">'
+ labels.map(l=>'<span style="background:#f1f5f9;color:#334155;padding:2px 8px;border-radius:10px;font-weight:600;font-size:10px">'+l+'</span>').join('')
+(p.specs.variants.length>4?'<span style="background:#f1f5f9;color:#94a3b8;padding:2px 8px;border-radius:10px;font-size:10px">+'+( p.specs.variants.length-4)+'</span>':'')
+'</div>'
+(allPrices.length?'<div style="font-weight:600;color:#024550">'+Math.min(...allPrices).toFixed(0)+' – '+Math.max(...allPrices).toFixed(0)+' kr/'+(p.unit||'st')+'</div>':'<div style="color:#94a3b8">Pris saknas</div>')
+'</div>';
} else if(p.specs && p.specs.type === 'window_sizes') {
const s = p.specs;
if(!s.sizes || !s.sizes.length) {
varHtml = '<div style="margin-top:8px;font-size:11px;color:#94a3b8">'+(s.models?s.models.length+' modeller':'Inga storlekar')+'</div>';
} else {
// Beräkna widths/heights från sizes om de saknas (fallback för manuellt skapade produkter)
var widths = (s.widths && s.widths.length) ? s.widths : [...new Set(s.sizes.map(function(x){return x.w;}))];
var heights = (s.heights && s.heights.length) ? s.heights : [...new Set(s.sizes.map(function(x){return x.h;}))];
var allPr = s.models && s.models.length ? s.sizes.flatMap(function(x){return s.models.map(function(m){return x[m.key];}).filter(function(v){return v!=null;});}) : s.sizes.map(function(x){return x.price;}).filter(function(v){return v!=null;});
var minP = allPr.length ? Math.min.apply(null,allPr) : 0;
var maxP = allPr.length ? Math.max.apply(null,allPr) : 0;
var sizeCount = s.sizes.length;
var hasLam = s.sizes.some(function(x){return x.note && x.note.toLowerCase().includes('laminated');});
varHtml = '<div style="margin-top:8px;font-size:11px">'
+'<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px">'
+'<span style="background:#e0f2fe;color:#0284c7;padding:2px 8px;border-radius:10px;font-weight:600">'+sizeCount+' storlekar</span>'
+(s.model?'<span style="background:#f0fdf4;color:#16a34a;padding:2px 8px;border-radius:10px;font-weight:600">'+s.model+'</span>':'')
+(hasLam?'<span style="background:#fef3c7;color:#d97706;padding:2px 8px;border-radius:10px;font-weight:600">Laminerat glas</span>':'')
+'</div>'
+'<div style="color:#64748b">Bredd: '+Math.min.apply(null,widths)+'\u2013'+Math.max.apply(null,widths)+' dm | H\u00f6jd: '+Math.min.apply(null,heights)+'\u2013'+Math.max.apply(null,heights)+' dm</div>'
+(allPr.length?(function(){var cur=p.costCurrency||"SEK";var cr=cur==="SEK"?1:(typeof _currencyRates!=="undefined"&&_currencyRates[cur]?_currencyRates[cur]:1);var mk=p.markupType==="percent"&&p.markupValue?(1+p.markupValue/100):1;return "<div style=\"font-weight:600;color:#024550;margin-top:4px\">"+Math.round(minP*cr*mk).toLocaleString("sv-SE")+" \u2013 "+Math.round(maxP*cr*mk).toLocaleString("sv-SE")+" kr</div>";}()):"")
+'</div>';
}
} else if(p.specs && p.specs.variants && p.specs.variants.length) {
const v = p.specs.variants;
const first = v[0];
if(first.kwh !== undefined) {
varHtml = '<table style="width:100%;border-collapse:collapse;margin-top:8px;font-size:11px"><thead><tr style="background:#f1f5f9"><th style="padding:4px 6px;text-align:left;font-weight:600;color:#64748b">kWh</th><th style="padding:4px 6px;text-align:right;font-weight:600;color:#64748b">Att betala</th><th style="padding:4px 6px;text-align:right;font-weight:600;color:#64748b">Grönt teknik</th><th style="padding:4px 6px;text-align:right;font-weight:600;color:#64748b">Totalt</th></tr></thead><tbody>'
+ v.map(r=>'<tr style="border-top:1px solid #f1f5f9"><td style="padding:3px 6px;font-weight:600">'+r.kwh+' kWh</td><td style="padding:3px 6px;text-align:right">'+r.att_betala.toLocaleString('sv-SE')+' kr</td><td style="padding:3px 6px;text-align:right;color:#059669">'+r.gron_teknik.toLocaleString('sv-SE')+' kr</td><td style="padding:3px 6px;text-align:right;font-weight:600">'+r.totalt.toLocaleString('sv-SE')+' kr</td></tr>').join('')
+ '</tbody></table>';
} else if(first.paneler !== undefined) {
const show = v.slice(0,6);
varHtml = '<table style="width:100%;border-collapse:collapse;margin-top:8px;font-size:11px"><thead><tr style="background:#f1f5f9"><th style="padding:4px 6px;text-align:left;font-weight:600;color:#64748b">Paneler</th><th style="padding:4px 6px;text-align:right;font-weight:600;color:#64748b">Att betala</th><th style="padding:4px 6px;text-align:right;font-weight:600;color:#64748b">Grönt teknik</th><th style="padding:4px 6px;text-align:right;font-weight:600;color:#64748b">Totalt</th></tr></thead><tbody>'
+ show.map(r=>'<tr style="border-top:1px solid #f1f5f9"><td style="padding:3px 6px;font-weight:600">'+r.paneler+' st</td><td style="padding:3px 6px;text-align:right">'+r.att_betala.toLocaleString('sv-SE')+' kr</td><td style="padding:3px 6px;text-align:right;color:#059669">'+r.gron_teknik.toLocaleString('sv-SE')+' kr</td><td style="padding:3px 6px;text-align:right;font-weight:600">'+r.totalt.toLocaleString('sv-SE')+' kr</td></tr>').join('')
+ (v.length > 6 ? '<tr><td colspan="4" style="padding:3px 6px;text-align:center;color:#94a3b8;font-size:10px">... '+v.length+' rader totalt (8–'+v[v.length-1].paneler+' paneler)</td></tr>' : '')
+ '</tbody></table>';
} else if(first.cm !== undefined) {
varHtml = '<table style="width:100%;border-collapse:collapse;margin-top:8px;font-size:11px"><thead><tr style="background:#f1f5f9"><th style="padding:4px 6px;text-align:left;font-weight:600;color:#64748b">Tjocklek</th><th style="padding:4px 6px;text-align:right;font-weight:600;color:#64748b">Pris/m²</th></tr></thead><tbody>'
+ v.map(r=>'<tr style="border-top:1px solid #f1f5f9"><td style="padding:3px 6px;font-weight:600">'+r.cm+' cm</td><td style="padding:3px 6px;text-align:right;font-weight:600">'+r.pris_per_m2+' kr/m²</td></tr>').join('')
+ '</tbody></table>';
} else if(first.material !== undefined) {
varHtml = '<table style="width:100%;border-collapse:collapse;margin-top:8px;font-size:11px"><thead><tr style="background:#f1f5f9"><th style="padding:4px 6px;text-align:left;font-weight:600;color:#64748b">Material</th><th style="padding:4px 6px;text-align:right;font-weight:600;color:#64748b">Pris/m²</th></tr></thead><tbody>'
+ v.map(r=>'<tr style="border-top:1px solid #f1f5f9"><td style="padding:3px 6px;font-weight:600">'+r.material+'</td><td style="padding:3px 6px;text-align:right;font-weight:600">'+r.pris_per_m2+' kr/m²</td></tr>').join('')
+ '</tbody></table>';
}
}
const priceHtml = (p.specs && (p.specs.variants || p.specs.type === 'window_sizes')) ? '' : '<div class="catalog-card-price">'+p.price.toLocaleString('sv-SE',{minimumFractionDigits:2,maximumFractionDigits:2})+' kr</div>';
html += '<div class="catalog-card" data-pid="'+p.id+'" style="position:relative;cursor:pointer'+(isAdmin?';transition:transform .15s,box-shadow .15s':'')+'"'
+(isAdmin?' draggable="true" ondragstart="catDragStart(event,\''+p.id+'\')" ondragover="catDragOver(event)" ondragenter="catDragEnter(event)" ondragleave="catDragLeave(event)" ondrop="catDrop(event,\''+p.id+'\')" ondragend="catDragEnd(event)"':'')
+' onclick="showProductModal(\''+p.id+'\')"><div class="catalog-card-img">'+imgHtml+'</div>'+galleryThumbs+greenBadge+editBtn+'<div class="catalog-card-body"><div class="catalog-card-cat">'+p.catLabel+'</div><div class="catalog-card-name">'+p.name+'</div><div class="catalog-card-desc">'+(p.desc||'')+'</div>'+varHtml+'<div class="catalog-card-footer">'+priceHtml+'<span class="catalog-card-badge status-badge '+p.stockClass+'">'+p.stock+'</span></div></div></div>';
});
html += '</div>'; // close grid
grid.innerHTML = html;
}
// === DRAG & DROP REORDER ===
let _catDragId = null;
function catDragStart(e, id) {
_catDragId = id;
e.dataTransfer.effectAllowed = 'move';
e.target.style.opacity = '0.4';
e.target.style.transform = 'scale(0.95)';
}
function catDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }
function catDragEnter(e) {
e.preventDefault();
const card = e.target.closest('.catalog-card');
if(card && card.dataset.pid !== _catDragId) {
card.style.boxShadow = '0 0 0 2px #024550';
}
}
function catDragLeave(e) {
const card = e.target.closest('.catalog-card');
if(card) card.style.boxShadow = '';
}
function catDragEnd(e) {
_catDragId = null;
document.querySelectorAll('.catalog-card').forEach(c => { c.style.opacity = ''; c.style.transform = ''; c.style.boxShadow = ''; });
}
async function catDrop(e, targetId) {
e.preventDefault();
const card = e.target.closest('.catalog-card');
if(card) card.style.boxShadow = '';
if(!_catDragId || _catDragId === targetId) return;
// Swap positions in catalogProducts
const dragIdx = catalogProducts.findIndex(p => p.id === _catDragId);
const targetIdx = catalogProducts.findIndex(p => p.id === targetId);
if(dragIdx === -1 || targetIdx === -1) return;
// Move dragged item to target position
const [moved] = catalogProducts.splice(dragIdx, 1);
catalogProducts.splice(targetIdx, 0, moved);
// Update sort_order for affected products
const updates = [];
catalogProducts.forEach((p, i) => {
const newOrder = i + 1;
if(p.sortOrder !== newOrder) {
updates.push({ id: p.id, sort_order: newOrder });
}
});
// Re-render immediately
// Ladda kategoriordning från DB
filterCatalog();
// Save to DB in background
if(updates.length) {
try {
await Promise.all(updates.map(u =>
fetch('/api/products.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ id: u.id, sort_order: u.sort_order })
})
));
} catch(err) { console.error('Sort save error:', err); }
}
_catDragId = null;
}
function renderCatalogList(products, grid, isAdmin) {
grid.style.display = 'block';
grid.className = '';
let html = '<table style="width:100%;border-collapse:collapse;font-size:13px;background:#fff;border:1px solid #e5e7eb;border-radius:10px;overflow:hidden">'
+'<thead><tr style="background:#f8f9fa">'
+'<th style="padding:10px 14px;width:50px"></th>'
+'<th style="padding:10px 14px;text-align:left;font-weight:600;color:#64748b;font-size:11px;text-transform:uppercase">Produkt</th>'
+'<th style="padding:10px 14px;text-align:left;font-weight:600;color:#64748b;font-size:11px;text-transform:uppercase">Kategori</th>'
+'<th style="padding:10px 14px;text-align:right;font-weight:600;color:#64748b;font-size:11px;text-transform:uppercase">Pris</th>'
+'<th style="padding:10px 14px;text-align:right;font-weight:600;color:#64748b;font-size:11px;text-transform:uppercase">Inköp</th>'
+'<th style="padding:10px 14px;text-align:center;font-weight:600;color:#64748b;font-size:11px;text-transform:uppercase">Enhet</th>'
+'<th style="padding:10px 14px;text-align:center;font-weight:600;color:#64748b;font-size:11px;text-transform:uppercase">Påslag</th>'
+'<th style="padding:10px 14px;text-align:center;font-weight:600;color:#64748b;font-size:11px;text-transform:uppercase">Varianter</th>'
+'<th style="padding:10px 14px;text-align:left;font-weight:600;color:#64748b;font-size:11px;text-transform:uppercase">Lager</th>'
+(isAdmin?'<th style="padding:10px 14px;width:40px"></th>':'')
+'</tr></thead><tbody>';
let lastCat = '';
const colSpan = isAdmin ? 10 : 9;
let lastSubcat2 = null;
products.forEach(p => {
const subcatKey2 = p.cat + '|' + (p.subcat || '');
if(p.catLabel !== lastCat || subcatKey2 !== lastSubcat2) {
if(p.catLabel !== lastCat) {
html += '<tr><td colspan="'+colSpan+'" style="padding:10px 14px 6px;font-size:12px;font-weight:800;color:#024550;letter-spacing:.5px;text-transform:uppercase;background:#f8f9fa;border-top:2px solid #e5e7eb">'+p.catLabel+'</td></tr>';
}
if(p.subcat) {
const sl = p.subcat === 'old' ? '📦 Äldre produkter' : p.subcat === 'fonsterdorrar' ? 'Fönsterdörrar' : p.subcat === 'fonstertillbehor' ? 'Fönster' : p.subcat === 'solpanel' ? 'Solpanel' : p.subcat;
html += '<tr><td colspan="'+colSpan+'" style="padding:6px 14px;font-size:11px;font-weight:600;color:#94a3b8;background:#fafafa;border-top:1px dashed #e5e7eb">'+sl+'</td></tr>';
}
lastCat = p.catLabel;
lastSubcat2 = subcatKey2;
}
const varCount = (p.specs && p.specs.type === 'window_sizes') ? (p.specs.sizes?p.specs.sizes.length:0) : (p.specs && p.specs.variants) ? p.specs.variants.length : 0;
const thumbHtml = p.img ? '<img src="'+p.img+'" style="width:36px;height:36px;object-fit:cover;border-radius:6px">' : '<div style="width:36px;height:36px;background:#f1f5f9;border-radius:6px;display:flex;align-items:center;justify-content:center"><svg viewBox="0 0 24 24" style="width:16px;height:16px;stroke:#cbd5e1;fill:none;stroke-width:1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg></div>';
html += '<tr style="border-top:1px solid #f1f5f9;cursor:pointer" onclick="showProductModal(\''+p.id+'\')">'
+'<td style="padding:4px 14px">'+thumbHtml+'</td>'
+'<td style="padding:8px 14px;font-weight:600">'+p.name+'</td>'
+'<td style="padding:8px 14px;color:#64748b">'+p.catLabel+'</td>'
+'<td style="padding:8px 14px;text-align:right">'+p.price.toLocaleString('sv-SE')+' kr</td>'
+'<td style="padding:8px 14px;text-align:right;color:#94a3b8">'+(p.costPrice ? p.costPrice.toLocaleString('sv-SE')+' kr' : '–')+'</td>'
+'<td style="padding:8px 14px;text-align:center;color:#64748b">'+(p.unit||'st')+'</td>'
+'<td style="padding:8px 14px;text-align:center;color:#64748b">'+(p.markupValue ? (p.markupType==='percent' ? p.markupValue+'%' : p.markupValue.toLocaleString('sv-SE')+' kr') : '–')+'</td>'
+'<td style="padding:8px 14px;text-align:center">'+(varCount ? '<span style="background:#e0f2fe;color:#0284c7;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600">'+varCount+'</span>' : '–')+'</td>'
+'<td style="padding:8px 14px"><span class="status-badge '+p.stockClass+'" style="font-size:11px">'+p.stock+'</span></td>'
+(isAdmin?'<td style="padding:8px 14px"><button onclick="event.stopPropagation();editProduct(\''+p.id+'\')" style="background:none;border:none;cursor:pointer;color:#64748b;padding:4px"><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></button></td>':'')
+'</tr>';
});
html += '</tbody></table>';
grid.innerHTML = html;
}
// === PRODUCT EDIT MODAL ===
let _peVariants = [];
let _peServices = [];