// konfigurator-fonster.js - Fönster configurator
// === FÖNSTER KONFIGURATOR (Säljar-kalkyl) ===
var _fkSelectedProduct = null;
var _fkSelectedTillbehor = []; // [{id, name, price, qty}]
var _fkQuoteId = null;
var _fkDeductionType = 'rot';
var _fkOwnerCount = 1;
var _fkFinYears = 15;
var _fkFinRate = 0.049;
var _fkProducts = [];
var _fkFoderTyp = 'gerad'; // gerad | kloss | ingen
var _fkSmygTyp = 'ja'; // ja | nej
var _fkPositions = [{pos:1, rum:'', produkt:'', bredd:1200, hojd:1200, antal:1, glas:'3-glas', notering:''}];
var _fkTillbCategories = [
{key:'sprojs', label:'Spröjs', items:[]},
{key:'persienner', label:'Persienner', items:[]},
{key:'persienner', label:'Persienner', items:[]},
{key:'solskydd', label:'Solskydd', items:[]},
{key:'beslag', label:'Beslag', items:[]},
{key:'ovrigt', label:'Övrigt', items:[]}
];
var _fkCurrentTillbCat = 'sprojs';
async function initFonsterConfig() { var _lbl=document.getElementById("cfgFileLabel");if(_lbl)_lbl.textContent="konfigurator-fonster.js";
try {
var res = await fetch('/api/products.php?cat=fonster');
var data = await res.json();
_fkProducts = data.products || [];
} catch(e) { _fkProducts = []; }
renderFkProductCards();
// Load tillbehör products
try {
var res2 = await fetch('/api/products.php?cat=tillbehor');
var data2 = await res2.json();
var tillbProducts = data2.products || [];
// Also load door products
var res3 = await fetch('/api/products.php?cat=dorrar');
var data3 = await res3.json();
var dorrProducts = data3.products || [];
// Combine into _fkProducts for reference
_fkProducts = _fkProducts.concat(dorrProducts);
// Build door product cards too
buildFkTillbCategories(tillbProducts);
} catch(e){}
renderFkTillbMenu();
updateFkCalc();
}
function renderFkProductCards() {
var container = document.getElementById('fkProductCards');
if(!container) return;
var html = '';
_fkProducts.forEach(function(p){
var sel = _fkSelectedProduct && _fkSelectedProduct.id === p.id;
var borderColor = sel ? '#3b82f6' : '#e5e7eb';
var bg = sel ? '#f0f9ff' : '#fff';
var imgHtml = p.img ? '<img src="'+p.img+'" style="width:48px;height:48px;object-fit:cover;border-radius:8px;flex-shrink:0" onerror="this.style.display=\'none\'">' : '<div style="width:48px;height:48px;background:#f1f5f9;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0"><svg viewBox="0 0 24 24" style="width:24px;height:24px;fill:none;stroke:#94a3b8;stroke-width:1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="12" y1="3" x2="12" y2="21"/></svg></div>';
var radioHtml = sel ? '<div style="width:20px;height:20px;border:2px solid #3b82f6;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0"><div style="width:10px;height:10px;background:#3b82f6;border-radius:50%"></div></div>' : '<div style="width:20px;height:20px;border:2px solid #d1d5db;border-radius:50%;flex-shrink:0"></div>';
html += '<div data-product-id="'+p.id+'" onclick="selectFkProduct(\''+p.id+'\')" style="padding:14px 16px;border:2px solid '+borderColor+';border-radius:10px;cursor:pointer;display:flex;align-items:center;gap:12px;background:'+bg+';transition:all .15s">';
html += radioHtml + imgHtml;
html += '<div style="flex:1"><div style="font-weight:600;font-size:14px">'+p.name+'</div>';
if(p.description) html += '<div style="font-size:11px;color:#64748b;margin-top:2px">'+p.description+'</div>';
html += '</div>';
html += '<div style="font-weight:700;font-size:14px;color:#1a1a1a">'+fmt(parseFloat(p.price)||0)+'</div>';
html += '</div>';
});
if(!html) html = '<div style="padding:20px;text-align:center;color:#94a3b8;font-size:13px">Inga fönsterprodukter i katalogen. Lägg till via Produkter-fliken.</div>';
container.innerHTML = html;
}
function selectFkProduct(id) {
_fkSelectedProduct = _fkProducts.find(function(p){ return p.id === id; }) || null;
renderFkProductCards();
updateFkCalc();
}
function buildFkTillbCategories(products) {
// Reset items
_fkTillbCategories.forEach(function(c){ c.items = []; });
products.forEach(function(p) {
var name = (p.name || '').toLowerCase();
// Foder/smyg/bänk har egna steg - skippa dem i tillbehör
if(name.indexOf('foder')>=0 || name.indexOf('smyg')>=0 || name.indexOf('bänk')>=0 || name.indexOf('bank')>=0) { return; }
if(name.indexOf('spröjs')>=0 || name.indexOf('sprojs')>=0) { _fkTillbCategories[0].items.push(p); }
else if(name.indexOf('persienn')>=0) { _fkTillbCategories[1].items.push(p); }
else if(name.indexOf('solskydd')>=0 || name.indexOf('rull')>=0) { _fkTillbCategories[2].items.push(p); }
else if(name.indexOf('beslag')>=0 || name.indexOf('handtag')>=0) { _fkTillbCategories[3].items.push(p); }
else { _fkTillbCategories[4].items.push(p); }
});
// If all categories empty, add placeholder items
if(_fkTillbCategories.every(function(c){ return c.items.length===0; })) {
var defaultItems = [
{cat:'sprojs', items:[{id:'TB01',name:'Spröjs standard',price:450,description:'Per fönster'},{id:'TB02',name:'Mellanglasspröjs',price:650,description:'Per fönster'}]},
{cat:'foder', items:[{id:'TB03',name:'Fönsterfoder komplett',price:890,description:'Per fönster'},{id:'TB04',name:'Smygbräda',price:320,description:'Per fönster'}]},
{cat:'fonsterbank', items:[{id:'TB05',name:'Fönsterbänk trä',price:550,description:'Per fönster'},{id:'TB06',name:'Fönsterbänk granit',price:1850,description:'Per fönster'}]},
{cat:'persienner', items:[{id:'TB07',name:'Persienn Basic vit',price:2474,description:'Per fönster'},{id:'TB08',name:'Persienn Basic svart',price:2594,description:'Per fönster'}]},
{cat:'solskydd', items:[{id:'TB09',name:'Rullgardin enkel',price:1200,description:'Per fönster'},{id:'TB10',name:'Solfilm',price:800,description:'Per fönster'}]},
{cat:'beslag', items:[{id:'TB11',name:'Handtag standard',price:250,description:'Per fönster'},{id:'TB12',name:'Låsbeslag',price:380,description:'Per fönster'}]},
{cat:'ovrigt', items:[{id:'TB13',name:'Monteringskit',price:450,description:'Per fönster'},{id:'TB14',name:'Isoleringskit',price:320,description:'Per fönster'}]}
];
defaultItems.forEach(function(di) {
var cat = _fkTillbCategories.find(function(c){ return c.key === di.cat; });
if(cat) cat.items = di.items;
});
}
}
function renderFkTillbMenu() {
var menuEl = document.getElementById('fkTillbCatMenu');
var prodsEl = document.getElementById('fkTillbProducts');
if(!menuEl || !prodsEl) return;
// Menu
var menuHtml = '';
_fkTillbCategories.forEach(function(c) {
var active = c.key === _fkCurrentTillbCat;
var style = active ? 'background:#024550;color:#fff;font-weight:600;' : 'background:#f8fafc;color:#334155;';
var count = c.items.length;
menuHtml += '<button onclick="switchFkTillbCat(\''+c.key+'\')" style="padding:8px 12px;border:none;border-radius:6px;font-size:12px;cursor:pointer;text-align:left;font-family:inherit;'+style+(active?'border-left:3px solid #f59e0b;':'border-left:3px solid transparent;')+'">'+c.label+' <span style="opacity:.6;font-size:10px">('+count+')</span></button>';
});
menuEl.innerHTML = menuHtml;
// Products
var cat = _fkTillbCategories.find(function(c){ return c.key === _fkCurrentTillbCat; });
var prodsHtml = '';
if(cat && cat.items.length) {
cat.items.forEach(function(item) {
var isSelected = _fkSelectedTillbehor.some(function(t){ return t.id === item.id; });
var borderCol = isSelected ? '#a855f7' : '#e5e7eb';
var bgCol = isSelected ? '#faf5ff' : '#fff';
var imgHtml = item.img ? '<img src="'+item.img+'" style="width:100%;height:80px;object-fit:cover;border-radius:6px;margin-bottom:6px" onerror="this.style.display=\'none\'">' : '<div style="width:100%;height:80px;background:#f8fafc;border-radius:6px;margin-bottom:6px;display:flex;align-items:center;justify-content:center"><svg viewBox="0 0 24 24" style="width:28px;height:28px;fill:none;stroke:#cbd5e1;stroke-width:1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="12" y1="3" x2="12" y2="21"/></svg></div>';
prodsHtml += '<div onclick="toggleFkTillbehor(\''+item.id+'\',\''+item.name.replace(/'/g,"\\'")+'\','+parseFloat(item.price||0)+')" style="border:2px solid '+borderCol+';border-radius:10px;padding:10px;cursor:pointer;background:'+bgCol+';transition:all .15s">';
prodsHtml += imgHtml;
prodsHtml += '<div style="font-size:12px;font-weight:600;line-height:1.3">'+item.name+'</div>';
prodsHtml += '<div style="font-size:12px;font-weight:700;color:#024550;margin-top:4px">'+fmt(parseFloat(item.price)||0)+'</div>';
if(isSelected) prodsHtml += '<div style="margin-top:4px;font-size:10px;color:#a855f7;font-weight:600">✓ Vald</div>';
prodsHtml += '</div>';
});
} else {
prodsHtml = '<div style="padding:30px;text-align:center;color:#94a3b8;font-size:13px;grid-column:1/-1">Inga produkter i denna kategori</div>';
}
prodsEl.innerHTML = prodsHtml;
// Selected list
renderFkSelectedTillbehor();
}
function switchFkTillbCat(key) {
_fkCurrentTillbCat = key;
renderFkTillbMenu();
}
function toggleFkTillbehor(id, name, price) {
var idx = _fkSelectedTillbehor.findIndex(function(t){ return t.id === id; });
if(idx >= 0) {
_fkSelectedTillbehor.splice(idx, 1);
} else {
_fkSelectedTillbehor.push({id:id, name:name, price:price, qty:1});
}
renderFkTillbMenu();
updateFkCalc();
}
function updateFkTillbQty(id, val) {
var item = _fkSelectedTillbehor.find(function(t){ return t.id === id; });
if(item) { item.qty = Math.max(1, parseInt(val)||1); }
renderFkSelectedTillbehor();
updateFkCalc();
}
function removeFkTillbehor(id) {
_fkSelectedTillbehor = _fkSelectedTillbehor.filter(function(t){ return t.id !== id; });
renderFkTillbMenu();
updateFkCalc();
}
function renderFkSelectedTillbehor() {
var wrap = document.getElementById('fkTillbSelected');
var list = document.getElementById('fkTillbSelectedList');
if(!wrap || !list) return;
if(_fkSelectedTillbehor.length === 0) { wrap.style.display = 'none'; return; }
wrap.style.display = 'block';
var html = '';
_fkSelectedTillbehor.forEach(function(t) {
html += '<div style="display:flex;align-items:center;gap:8px;padding:8px 12px;background:#faf5ff;border:1px solid #e9d5ff;border-radius:8px">';
html += '<div style="flex:1;font-size:12px;font-weight:600;color:#6b21a8">'+t.name+'</div>';
html += '<div style="font-size:11px;color:#64748b">'+fmt(t.price)+'/st</div>';
html += '<input type="number" value="'+t.qty+'" min="1" onchange="updateFkTillbQty(\''+t.id+'\',this.value)" style="width:50px;padding:4px 6px;border:1px solid #d1d5db;border-radius:4px;font-size:12px;text-align:center;font-family:inherit">';
html += '<div style="font-size:12px;font-weight:700;min-width:70px;text-align:right">'+fmt(t.price * t.qty)+'</div>';
html += '<button onclick="removeFkTillbehor(\''+t.id+'\')" style="background:none;border:none;cursor:pointer;color:#ef4444;font-size:16px;padding:2px 4px">×</button>';
html += '</div>';
});
list.innerHTML = html;
}
function setFkFoder(typ) {
_fkFoderTyp = typ;
var cards = document.querySelectorAll('#fkFoderCards label');
cards.forEach(function(lbl) {
var radio = lbl.querySelector('input[type=radio]');
if(radio && radio.value === typ) {
lbl.style.borderColor = '#a855f7'; lbl.style.background = '#faf5ff';
} else {
lbl.style.borderColor = '#e5e7eb'; lbl.style.background = '#fff';
}
});
updateFkCalc();
}
function setFkSmyg(typ) {
_fkSmygTyp = typ;
var cards = document.querySelectorAll('#fkSmygCards label');
cards.forEach(function(lbl) {
var radio = lbl.querySelector('input[type=radio]');
if(radio && radio.value === typ) {
lbl.style.borderColor = '#a855f7'; lbl.style.background = '#faf5ff';
} else {
lbl.style.borderColor = '#e5e7eb'; lbl.style.background = '#fff';
}
});
updateFkCalc();
}
function setFkDeduction(type) {
_fkDeductionType = type;
document.querySelectorAll('.fk-deduct-btn').forEach(function(btn) {
var dt = btn.getAttribute('data-dt');
if(dt === type) { btn.style.background = '#059669'; btn.style.color = '#fff'; btn.style.borderColor = '#059669'; }
else { btn.style.background = '#fff'; btn.style.color = '#059669'; btn.style.borderColor = '#bbf7d0'; }
});
updateFkCalc();
}
function setFkOwners(n) {
_fkOwnerCount = n;
document.querySelectorAll('.fk-owner-btn').forEach(function(btn) {
var oc = parseInt(btn.getAttribute('data-oc'));
if(oc === n) { btn.style.background = '#059669'; btn.style.color = '#fff'; btn.style.borderColor = '#059669'; }
else { btn.style.background = '#fff'; btn.style.color = '#059669'; btn.style.borderColor = '#bbf7d0'; }
});
updateFkCalc();
}
function setFkFinYears(yr) {
_fkFinYears = yr;
document.querySelectorAll('.fk-fin-btn').forEach(function(btn) {
var y = parseInt(btn.getAttribute('data-yr'));
if(y === yr) { btn.style.background = '#3b82f6'; btn.style.color = '#fff'; btn.style.borderColor = '#3b82f6'; }
else { btn.style.background = '#fff'; btn.style.color = '#3b82f6'; btn.style.borderColor = '#bfdbfe'; }
});
var info = document.getElementById('fkFinInfo');
if(info) info.textContent = '('+yr+' \u00e5r, '+((_fkFinRate*100).toFixed(1))+'%)';
updateFkCalc();
}
function updateFkCalc() {
var antal = parseInt(el('fkAntal')?.value) || 0;
var antalDisp = document.getElementById('fkAntalVal');
if(antalDisp) antalDisp.textContent = antal;
var pricePerUnit = _fkSelectedProduct ? parseFloat(_fkSelectedProduct.price) || 0 : 0;
var fonsterCost = antal * pricePerUnit;
var tillbCost = 0;
_fkSelectedTillbehor.forEach(function(t) { tillbCost += (t.price * t.qty); });
// Multiply tillbehör that are per-window by antal
// If qty matches default (1), assume it's per-window
_fkSelectedTillbehor.forEach(function(t) {
// tillbCost already includes qty, but for per-window items we multiply by antal
// Actually just use qty as-is - user sets total quantity manually
});
var montTimmar = parseFloat(el('fkMontTimmar')?.value) || 0;
var montPris = parseFloat(el('fkMontPris')?.value) || 0;
var laborCost = montTimmar * montPris;
var frakt = parseFloat(el('fkFrakt')?.value) || 0;
var marginPct = parseInt(el('fkMarginSlider')?.value) || 0;
var pctDisp = document.getElementById('fkMarginPct');
if(pctDisp) pctDisp.textContent = marginPct + '%';
var subtotal = fonsterCost + tillbCost + laborCost + frakt;
var marginKr = Math.round(subtotal * marginPct / 100);
var totalBeforeDeduct = subtotal + marginKr;
// Deduction
var deductKr = 0;
var maxPerPerson = 50000;
var maxDeduct = maxPerPerson * _fkOwnerCount;
if(_fkDeductionType === 'rot') {
deductKr = Math.min(Math.round(laborCost * 0.30), maxDeduct);
var lbl = document.getElementById('fkDeductLabel');
if(lbl) lbl.textContent = 'ROT-AVDRAG (30% av arbete)';
} else if(_fkDeductionType === 'green') {
deductKr = Math.min(Math.round(totalBeforeDeduct * 0.20), maxDeduct);
var lbl = document.getElementById('fkDeductLabel');
if(lbl) lbl.textContent = 'GR\u00d6NT TEKNIK (20% av allt)';
} else {
var lbl = document.getElementById('fkDeductLabel');
if(lbl) lbl.textContent = 'INGET AVDRAG';
}
var total = totalBeforeDeduct - deductKr;
// Monthly
var r = _fkFinRate / 12;
var n = _fkFinYears * 12;
var monthly = total > 0 && r > 0 ? Math.round(total * r * Math.pow(1+r, n) / (Math.pow(1+r, n) - 1)) : 0;
// Update DOM
var desc = document.getElementById('fkPrDesc');
if(desc) desc.textContent = _fkSelectedProduct ? '('+antal+' x '+_fkSelectedProduct.name+')' : '';
if(el('fkPrFonster')) el('fkPrFonster').textContent = fmt(fonsterCost);
if(el('fkPrTillbehor')) el('fkPrTillbehor').textContent = fmt(tillbCost);
if(el('fkPrMontering')) el('fkPrMontering').textContent = fmt(laborCost);
if(el('fkPrFrakt')) el('fkPrFrakt').textContent = fmt(frakt);
if(el('fkPrSubtotal')) el('fkPrSubtotal').textContent = fmt(totalBeforeDeduct);
if(el('fkMarginKr')) el('fkMarginKr').textContent = fmt(marginKr);
if(el('fkDeductKr')) el('fkDeductKr').textContent = '-' + fmt(deductKr);
if(el('fkPrTotal')) el('fkPrTotal').textContent = fmt(total);
if(el('fkPrMonthly')) el('fkPrMonthly').textContent = fmt(monthly) + '/m\u00e5n';
}
function goToFkAffar() {
alert('Affär-flöde för fönster kommer snart');
}
async function saveFkQuote(status) {
var antal = parseInt(el('fkAntal')?.value) || 0;
var configData = {
product: _fkSelectedProduct,
antal: antal,
glas: el('fkGlas')?.value || '',
profil: el('fkProfil')?.value || '',
bredd: parseInt(el('fkBredd')?.value) || 0,
hojd: parseInt(el('fkHojd')?.value) || 0,
farg_in: el('fkFargIn')?.value || '',
farg_ut: el('fkFargUt')?.value || '',
foder_typ: _fkFoderTyp,
smyg: _fkSmygTyp,
foder_farg_in: el('fkFoderFargIn')?.value || '',
foder_farg_ut: el('fkFoderFargUt')?.value || '',
fonsterbank: document.querySelector('input[name=fkBank]:checked')?.value || 'tra',
tillbehor: _fkSelectedTillbehor,
montering_timmar: parseFloat(el('fkMontTimmar')?.value) || 0,
montering_pris: parseFloat(el('fkMontPris')?.value) || 0,
frakt: parseFloat(el('fkFrakt')?.value) || 0,
margin_percent: parseInt(el('fkMarginSlider')?.value) || 0,
deduction_type: _fkDeductionType,
owner_count: _fkOwnerCount
};
var pricePerUnit = _fkSelectedProduct ? parseFloat(_fkSelectedProduct.price) || 0 : 0;
var fonsterCost = antal * pricePerUnit;
var tillbCost = 0;
_fkSelectedTillbehor.forEach(function(t){ tillbCost += t.price * t.qty; });
var laborCost = configData.montering_timmar * configData.montering_pris;
var subtotal = fonsterCost + tillbCost + laborCost + configData.frakt;
var marginKr = Math.round(subtotal * configData.margin_percent / 100);
var total = subtotal + marginKr;
var desc = [];
if(_fkSelectedProduct) desc.push(_fkSelectedProduct.name + ' x ' + antal);
if(configData.glas) desc.push(configData.glas);
if(configData.profil) desc.push(configData.profil);
var payload = {
category: 'fonster',
customer_name: pendingKalkylCustomer?.name || '',
customer_address: pendingKalkylCustomer?.address || '',
customer_email: pendingKalkylCustomer?.email || '',
customer_phone: pendingKalkylCustomer?.phone || '',
config_data: JSON.stringify(configData),
material_cost: fonsterCost + tillbCost,
labor_cost: laborCost,
subtotal: subtotal,
margin_percent: configData.margin_percent,
deal_total: total,
total_price: total,
panel_name: desc.join(', '),
panel_count: antal,
status: status || 'utkast',
created_by: gStaffId || null,
notes: ''
};
if(_fkQuoteId) payload.id = _fkQuoteId;
try {
var res = await fetch('/api/quotes.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
});
var data = await res.json();
if(data.error) { alert('Fel: '+data.error); return; }
_fkQuoteId = data.id || _fkQuoteId;
alert(status==='offert'?'Offert skapad!':'Kalkyl sparad!');
} catch(e) { alert('Fel: '+e.message); }
}
// =============================================