// konfigurator-affar.js - Affär/deal + quotes
// === AFFÄR (DEAL) FUNCTIONS ===
let currentQuoteId = null;
let affarExtraCosts = [];
let affarImages = [];
function goToAffar(){
if(!currentCalcState.panelCount){
alert('Välj panel och antal först');
return;
}
// Hide kalkyl views, show affär
document.getElementById('solarConfigView').style.display='none';
document.getElementById('affarView').style.display='block';
// Fill customer banner
const banner = document.getElementById('affarCustomerBanner');
const c = pendingKalkylCustomer;
if(c){
banner.innerHTML='<div style="background:linear-gradient(135deg,#ecfdf5,#f0f9ff);border:1px solid #bbf7d0;border-radius:12px;padding:14px 18px;display:flex;align-items:center;gap:16px;flex-wrap:wrap">'
+'<div style="flex:1;min-width:200px"><div style="font-weight:700;font-size:15px;color:#1a1a1a">'+c.name+'</div><div style="font-size:12px;color:#64748b">'+c.address+'</div></div>'
+(c.phone?'<div style="font-size:13px;color:#334155">'+c.phone+'</div>':'')
+(c.email?'<div style="font-size:13px;color:#334155">'+c.email+'</div>':'')
+'</div>';
} else {
banner.innerHTML='';
}
// Fill config summary
const cs = currentCalcState;
const summary = document.getElementById('affarConfigSummary');
let rows = '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Solpaneler</span><span style="font-size:13px;font-weight:600">'+cs.panelName+' × '+cs.panelCount+' ('+cs.kwp+' kWp)</span></div>';
if(cs.batteryKwh>0) rows += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Batteri</span><span style="font-size:13px;font-weight:600">'+cs.batteryKwh+' kWh</span></div>';
if(cs.chargerName) rows += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Laddare</span><span style="font-size:13px;font-weight:600">'+cs.chargerName+'</span></div>';
rows += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Takyta</span><span style="font-size:13px;font-weight:600">'+cs.roofArea+' m²</span></div>';
rows += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#ecfdf5;border-radius:8px"><span style="font-size:13px;color:#059669;font-weight:600">Uppsk. produktion</span><span style="font-size:13px;font-weight:700;color:#059669">~'+cs.yearlyKwh.toLocaleString('sv-SE')+' kWh/år</span></div>';
summary.innerHTML = rows;
// Reset extras
affarExtraCosts = [];
// Överför valda kalkylbilder till affär
affarImages = kalkylImages.filter(i => i.selected).map(i => ({ url: i.url, type: i.type, created: i.created }));
currentQuoteId = null;
document.getElementById('affarNotes').value = '';
document.getElementById('affarMarginSlider').value = 0;
renderAffarExtras();
renderAffarImages();
updateAffarCalc();
}
function backToKalkylFromAffar(){
document.getElementById('affarView').style.display='none';
document.getElementById('solarConfigView').style.display='block';
}
function addExtraCost(){
affarExtraCosts.push({name:'',amount:0});
renderAffarExtras();
}
function removeExtraCost(idx){
affarExtraCosts.splice(idx,1);
renderAffarExtras();
updateAffarCalc();
}
function renderAffarExtras(){
const container = document.getElementById('affarExtraCosts');
const empty = document.getElementById('affarExtraCostsEmpty');
if(affarExtraCosts.length===0){
container.innerHTML='';
empty.style.display='block';
return;
}
empty.style.display='none';
container.innerHTML = affarExtraCosts.map((ec,i) =>
'<div style="display:flex;gap:8px;align-items:center;margin-bottom:8px">'
+'<input type="text" value="'+(ec.name||'').replace(/"/g,'"')+'" placeholder="Beskrivning (t.ex. Extra kabelarbete)" oninput="affarExtraCosts['+i+'].name=this.value" style="flex:1;padding:8px 12px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:13px;font-family:inherit">'
+'<input type="number" value="'+(ec.amount||0)+'" placeholder="Belopp" oninput="affarExtraCosts['+i+'].amount=parseInt(this.value)||0;updateAffarCalc()" style="width:120px;padding:8px 12px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:13px;font-family:inherit;text-align:right">'
+'<span style="font-size:12px;color:#64748b;flex-shrink:0">kr</span>'
+'<button onclick="removeExtraCost('+i+')" style="background:none;border:none;cursor:pointer;padding:4px;color:#ef4444;flex-shrink:0"><svg viewBox="0 0 24 24" style="width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>'
+'</div>'
).join('');
}
function updateAffarCalc(){
const cs = currentCalcState;
if(!cs.subtotal) return;
const margin = parseInt(document.getElementById('affarMarginSlider').value)||0;
document.getElementById('affarMarginVal').textContent = margin + '%';
const extrasTotal = affarExtraCosts.reduce((sum,ec)=>sum+(ec.amount||0),0);
const baseBeforeMargin = cs.subtotal + extrasTotal;
const marginAmount = Math.round(baseBeforeMargin * margin / 100);
const totalBeforeDeduction = baseBeforeMargin + marginAmount;
// Use same deduction logic as kalkyl based on deductionType
const dt = cs.deductionType || 'green';
const sliderMax = cs.greenTechDeduction > 0 ? (cs.total + cs.greenTechDeduction - cs.subtotal + cs.greenTechDeduction) : 50000;
const ownerMax = (cs.ownerCount || 1) * 50000;
let totalDeduction = 0;
const totalLabor = cs.laborCost + Math.round(extrasTotal * SOL_LABOR_RATIO) + Math.round(marginAmount * SOL_LABOR_RATIO);
const totalMaterial = totalBeforeDeduction - totalLabor;
if(dt === 'green'){
totalDeduction = Math.min(Math.round(totalBeforeDeduction * GREEN_TECH_RATE), ownerMax);
} else if(dt === 'rot'){
totalDeduction = Math.min(Math.round(totalLabor * ROT_RATE), ownerMax);
} else if(dt === 'both'){
const greenCalc = Math.round(totalMaterial * GREEN_TECH_RATE);
const rotCalc = Math.round(totalLabor * ROT_RATE);
totalDeduction = Math.min(greenCalc + rotCalc, ownerMax);
}
const dealTotal = totalBeforeDeduction - totalDeduction;
// Update sidebar
document.getElementById('affarPrMaterial').textContent = fmt(cs.materialCost);
document.getElementById('affarPrLabor').textContent = fmt(cs.laborCost);
document.getElementById('affarPrBattery').textContent = cs.batteryPrice > 0 ? fmt(cs.batteryPrice) : '0 kr';
document.getElementById('affarPrCharger').textContent = cs.chargerPrice > 0 ? fmt(cs.chargerPrice) : '0 kr';
const extraRow = document.getElementById('affarPrExtraRow');
if(extrasTotal > 0){ extraRow.style.display=''; document.getElementById('affarPrExtra').textContent = fmt(extrasTotal); }
else extraRow.style.display='none';
const marginRow = document.getElementById('affarPrMarginRow');
if(margin > 0){ marginRow.style.display=''; document.getElementById('affarPrMargin').textContent = fmt(marginAmount); document.getElementById('affarPrMarginPct').textContent='('+margin+'%)'; }
else marginRow.style.display='none';
document.getElementById('affarPrSubtotal').textContent = fmt(totalBeforeDeduction);
document.getElementById('affarPrGreenTech').textContent = totalDeduction > 0 ? '-' + fmt(totalDeduction) : '0 kr';
// Update deduction label and colors in affär
const deductLabels = {green:'GRÖNT TEKNIK-AVDRAG', rot:'ROT-AVDRAG', both:'GRÖNT TEKNIK + ROT', none:'INGET AVDRAG'};
const deductColors = {green:'#059669', rot:'#2563eb', both:'#7c3aed', none:'#94a3b8'};
const deductBgs = {green:'#ecfdf5', rot:'#eff6ff', both:'#f5f3ff', none:'#f8fafc'};
const dLabel = document.getElementById('affarDeductLabel');
const dRow = document.getElementById('affarDeductionRow');
if(dLabel){ dLabel.textContent = deductLabels[dt] || 'AVDRAG'; dLabel.style.color = deductColors[dt] || '#059669'; }
if(dRow) dRow.style.background = deductBgs[dt] || '#ecfdf5';
document.getElementById('affarPrGreenTech').style.color = deductColors[dt] || '#059669';
document.getElementById('affarPrTotal').textContent = fmt(dealTotal);
// Monthly
const r = SOL_FINANCE_RATE / 12;
const n = SOL_FINANCE_YEARS * 12;
const monthly = dealTotal > 0 ? Math.round(dealTotal * r * Math.pow(1+r,n) / (Math.pow(1+r,n)-1)) : 0;
document.getElementById('affarPrMonthly').textContent = monthly.toLocaleString('sv-SE') + ' kr/mån';
// Margin info
document.getElementById('affarMarginKr').textContent = fmt(marginAmount);
const profit = marginAmount + extrasTotal;
document.getElementById('affarProfit').textContent = fmt(profit);
}
// Hämta prospektbilder från fältsälj
function addProspectPhotosToAffar(){
const c = pendingKalkylCustomer;
if(!c || c.prospectIdx === undefined) { alert('Ingen prospekt kopplad'); return; }
const p = faltProspects[c.prospectIdx];
if(!p || !p.photos || p.photos.length === 0) { alert('Inga bilder finns på prospektet. Ta bilder i FältSälj först.'); return; }
let added = 0;
p.photos.forEach(ph => {
const url = ph.full || ph.url || ph.dataUrl || ph.thumb;
if(url && !affarImages.find(ai => ai.url === url)){
affarImages.push({ url: url, type: ph.type || 'photo', created: ph.created || new Date().toISOString() });
added++;
}
});
if(added > 0) renderAffarImages();
else alert('Alla prospektbilder finns redan i offerten.');
}
// Skapa prospektbild (med husbilder + solceller)
async function createProspectImage(){
const btn = document.getElementById('affarGenImgBtn');
const cs = currentCalcState;
const c = pendingKalkylCustomer;
const addr = c ? c.address : 'Svenskt hus';
btn.disabled = true;
btn.innerHTML = '<svg style="width:14px;height:14px;animation:spin 1s linear infinite" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg> Skapar bild...';
try {
const prompt = 'Photorealistic image of a Swedish residential house at '+addr+' with '+cs.panelCount+' modern black solar panels installed on the roof. Sunny day, blue sky, Swedish neighborhood. Professional real estate photography style.';
const resp = await fetch('/api/generate-image.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({prompt, size:'1024x1024'})
});
const data = await resp.json();
if(data.success && data.image_url){
affarImages.push({url: data.image_url, type: 'prospect', created: new Date().toISOString()});
renderAffarImages();
} else {
alert('Kunde inte skapa bild: ' + (data.error || 'Okänt fel'));
}
} catch(e){
alert('Kunde inte skapa bild: ' + e.message);
} finally {
btn.disabled = false;
btn.innerHTML = '<svg viewBox="0 0 24 24" style="width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg> Skapa prospektbild';
}
}
function renderAffarImages(){
const container = document.getElementById('affarImages');
const empty = document.getElementById('affarImagesEmpty');
if(affarImages.length===0){
container.innerHTML='';
empty.style.display='block';
return;
}
empty.style.display='none';
container.innerHTML = affarImages.map((img,i) =>
'<div style="position:relative;border-radius:10px;overflow:hidden;border:1px solid #e5e7eb">'
+'<img src="'+img.url+'" style="width:100%;height:140px;object-fit:cover;cursor:pointer" onclick="openLightbox(\''+img.url.replace(/'/g,"\\'")+'\')">'
+'<button onclick="affarImages.splice('+i+',1);renderAffarImages()" style="position:absolute;top:6px;right:6px;width:24px;height:24px;background:rgba(0,0,0,.5);color:#fff;border:none;border-radius:50%;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center">×</button>'
+'</div>'
).join('');
}
async function saveKalkylDraft(){
const cs = currentCalcState;
if(!cs.panelCount){ alert('Välj panel och antal först'); return; }
const c = pendingKalkylCustomer || {};
const selectedImages = kalkylImages.filter(i => i.selected);
const payload = {
customer_name: c.name || null,
customer_address: c.address || null,
customer_email: c.email || null,
customer_phone: c.phone || null,
customer_personnummer: (c.owner1 && c.owner1.pnr) || null,
owner_count: cs.ownerCount || 1,
panel_id: cs.panelId,
panel_name: cs.panelName,
panel_count: cs.panelCount,
panel_watt: cs.panelWatt,
battery_kwh: cs.batteryKwh || 0,
battery_price: cs.batteryPrice || 0,
charger_name: cs.chargerName || null,
charger_price: cs.chargerPrice || 0,
material_cost: cs.materialCost,
labor_cost: cs.laborCost,
subtotal: cs.subtotal,
green_tech_deduction: cs.greenTechDeduction || 0,
total_price: cs.totalPrice || cs.subtotal,
extra_costs: [],
margin_percent: 0,
deal_total: cs.totalPrice || cs.subtotal,
images: selectedImages,
notes: null,
status: 'utkast',
created_by: gStaffId || null,
finance_years: SOL_FINANCE_YEARS,
finance_rate: SOL_FINANCE_RATE,
yearly_kwh: cs.yearlyKwh,
roof_area: cs.roofArea,
prospect_data: c.solarData || null
};
if(currentQuoteId) payload.id = currentQuoteId;
const btn = document.getElementById('saveKalkylBtn');
btn.disabled = true;
btn.innerHTML = '<svg style="width:14px;height:14px;animation:spin 1s linear infinite" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg> Sparar...';
try {
const resp = await fetch('/api/quotes.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify(payload)
});
const data = await resp.json();
if(data.success){
currentQuoteId = data.id || currentQuoteId;
btn.innerHTML = '<svg viewBox="0 0 24 24" style="width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2"><circle cx="12" cy="12" r="10"/><path d="M9 12l2 2 4-4"/></svg> Sparad!';
btn.style.background = '#059669';
setTimeout(()=>{ btn.innerHTML='<svg viewBox="0 0 24 24" style="width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg> Spara utkast'; btn.style.background='#f59e0b'; }, 2000);
// Uppdatera prospektstatus
const pIdx = (pendingKalkylCustomer||{}).prospectIdx;
if(pIdx >= 0 && faltProspects[pIdx] && faltProspects[pIdx].status !== 'offert'){
faltProspects[pIdx].status = 'kalkyl';
addQuoteToProspect(pIdx, currentQuoteId);
renderFaltMarkers(); renderFaltList(); updateFaltStats(); renderLeadsTable();
}
loadQuotesList();
} else {
alert('Kunde inte spara: '+(data.error||'Okänt fel'));
}
} catch(e){
alert('Kunde inte spara: '+e.message);
} finally {
btn.disabled = false;
}
}
// Save as draft (from affär view)
async function saveQuoteAsDraft(){
const cs = currentCalcState;
const c = pendingKalkylCustomer || {};
const margin = parseInt(document.getElementById('affarMarginSlider').value)||0;
const extrasTotal = affarExtraCosts.reduce((sum,ec)=>sum+(ec.amount||0),0);
const baseBeforeMargin = cs.subtotal + extrasTotal;
const marginAmount = Math.round(baseBeforeMargin * margin / 100);
const totalBeforeDeduction = baseBeforeMargin + marginAmount;
// Same deduction logic as updateAffarCalc
const dt = cs.deductionType || 'green';
const ownerMax = (cs.ownerCount || 1) * 50000;
const totalLabor = cs.laborCost + Math.round(extrasTotal * SOL_LABOR_RATIO) + Math.round(marginAmount * SOL_LABOR_RATIO);
const totalMaterial = totalBeforeDeduction - totalLabor;
let totalDeduction = 0;
if(dt === 'green') totalDeduction = Math.min(Math.round(totalBeforeDeduction * GREEN_TECH_RATE), ownerMax);
else if(dt === 'rot') totalDeduction = Math.min(Math.round(totalLabor * ROT_RATE), ownerMax);
else if(dt === 'both'){ totalDeduction = Math.min(Math.round(totalMaterial * GREEN_TECH_RATE) + Math.round(totalLabor * ROT_RATE), ownerMax); }
const dealTotal = totalBeforeDeduction - totalDeduction;
const payload = {
customer_name: c.name || null,
customer_address: c.address || null,
customer_email: c.email || null,
customer_phone: c.phone || null,
customer_personnummer: c.personnummer || null,
owner_count: cs.ownerCount || 1,
panel_id: cs.panelId,
panel_name: cs.panelName,
panel_count: cs.panelCount,
panel_watt: cs.panelWatt,
battery_kwh: cs.batteryKwh || 0,
battery_price: cs.batteryPrice || 0,
charger_name: cs.chargerName || null,
charger_price: cs.chargerPrice || 0,
material_cost: cs.materialCost,
labor_cost: cs.laborCost,
subtotal: totalBeforeDeduction,
green_tech_deduction: totalDeduction,
total_price: dealTotal,
extra_costs: affarExtraCosts.filter(ec => ec.name && ec.amount),
margin_percent: margin,
deal_total: dealTotal,
images: affarImages,
notes: document.getElementById('affarNotes').value || null,
status: 'utkast',
created_by: gStaffId || null,
finance_years: SOL_FINANCE_YEARS,
finance_rate: SOL_FINANCE_RATE,
yearly_kwh: cs.yearlyKwh,
roof_area: cs.roofArea,
prospect_data: c.solarData || null
};
if(currentQuoteId) payload.id = currentQuoteId;
try {
const resp = await fetch('/api/quotes.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify(payload)
});
const data = await resp.json();
if(data.success){
currentQuoteId = data.id || currentQuoteId;
const statusEl = document.getElementById('affarStatus');
statusEl.textContent = 'Utkast sparad ✓';
statusEl.style.background = '#dcfce7';
statusEl.style.color = '#166534';
setTimeout(()=>{ statusEl.textContent='Utkast'; statusEl.style.background='#fef3c7'; statusEl.style.color='#92400e'; }, 2000);
// Uppdatera prospektstatus till "kalkyl"
const pIdx = (pendingKalkylCustomer||{}).prospectIdx;
if(pIdx >= 0 && faltProspects[pIdx] && faltProspects[pIdx].status !== 'offert'){
faltProspects[pIdx].status = 'kalkyl';
addQuoteToProspect(pIdx, currentQuoteId);
renderFaltMarkers(); renderFaltList(); updateFaltStats(); renderLeadsTable();
}
loadQuotesList();
} else {
alert('Kunde inte spara: ' + (data.error || 'Okänt fel'));
}
} catch(e){
alert('Kunde inte spara: ' + e.message);
}
}
async function createFinalQuote(){
await saveQuoteAsDraft();
if(!currentQuoteId) return;
// Update status to offert
try {
await fetch('/api/quotes.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({id: currentQuoteId, status: 'offert'})
});
const statusEl = document.getElementById('affarStatus');
statusEl.textContent = 'Offert skapad!';
statusEl.style.background = '#dcfce7';
statusEl.style.color = '#166534';
// Uppdatera prospektstatus till "offert"
const pIdx = (pendingKalkylCustomer||{}).prospectIdx;
if(pIdx >= 0 && faltProspects[pIdx]){
faltProspects[pIdx].status = 'offert';
addQuoteToProspect(pIdx, currentQuoteId);
renderFaltMarkers(); renderFaltList(); updateFaltStats(); renderLeadsTable();
}
loadQuotesList();
alert('Offert #' + currentQuoteId + ' skapad!\n\nOffert-PDF generering kommer i nästa steg.');
} catch(e){
alert('Kunde inte skapa offert: ' + e.message);
}
}
// Load quotes from DB into list
async function loadQuotesList(){
const body = document.getElementById('quotesListBody');
if(!body) return;
const status = document.getElementById('quotesFilterStatus')?.value || '';
const search = document.getElementById('quotesSearch')?.value.trim() || '';
let url = '/api/quotes.php?';
if(status) url += 'status='+encodeURIComponent(status)+'&';
if(search) url += 'search='+encodeURIComponent(search)+'&';
try {
const resp = await fetch(url);
const data = await resp.json();
if(!data.success || !data.quotes || data.quotes.length === 0){
body.innerHTML = '<tr><td colspan="9" style="padding:40px;text-align:center;color:#94a3b8;font-size:14px">Inga kalkyler hittade</td></tr>';
return;
}
const statusMap = {
utkast: {bg:'#fef3c7',color:'#92400e',label:'Utkast'},
skickad: {bg:'#e0f2fe',color:'#0369a1',label:'Skickad'},
offert: {bg:'#e0f2fe',color:'#0369a1',label:'Offert'},
'godkänd': {bg:'#dcfce7',color:'#166534',label:'Godkänd'},
'förlorad': {bg:'#fee2e2',color:'#991b1b',label:'Förlorad'}
};
body.innerHTML = data.quotes.map(q => {
const st = statusMap[q.status] || {bg:'#f1f5f9',color:'#64748b',label:q.status||'—'};
const date = q.updated_at ? q.updated_at.slice(0,10) : (q.created_at||'').slice(0,10);
const dealTotal = parseFloat(q.deal_total) || 0;
const totalPrice = parseFloat(q.total_price) || 0;
const bestPrice = dealTotal > 0 ? dealTotal : totalPrice;
const amount = bestPrice > 0 ? Math.round(bestPrice).toLocaleString('sv-SE')+' kr' : '—';
// Produkt-info från config_data
var configSummary = '';
try {
var cd = q.config_data ? (typeof q.config_data === 'string' ? JSON.parse(q.config_data) : q.config_data) : {};
if(cd.product_name) configSummary = cd.product_name + (cd.variant_kwh ? ' ' + cd.variant_kwh + ' kWh' : '');
else if(cd.description) configSummary = cd.description;
else if(q.panel_name && q.panel_count) configSummary = q.panel_name + ' × ' + q.panel_count;
else configSummary = q.category || '—';
} catch(e){ configSummary = q.category || '—'; }
// Kund — visa customer_name, annars panel_name som märkning
var kundName = q.customer_name || '';
var kundMarkning = '';
if(!kundName && q.panel_name) { kundMarkning = q.panel_name; }
var kundDisplay = kundName ? kundName : (kundMarkning ? '<span style="color:#64748b">'+kundMarkning+'</span>' : 'Ej angiven');
// Parse images JSON
let imgs = [];
try { if(q.images) imgs = typeof q.images === 'string' ? JSON.parse(q.images) : q.images; } catch(e){}
if(!Array.isArray(imgs)) imgs = [];
let imgHtml = '';
if(imgs.length > 0){
const thumbs = imgs.slice(0,3);
imgHtml = '<div style="display:flex;gap:4px;align-items:center">' + thumbs.map(function(im){
var src = typeof im === 'string' ? im : (im.url||'');
return '<img src="'+src+'" onclick="event.stopPropagation();openLightbox(this.src)" style="width:32px;height:32px;border-radius:4px;object-fit:cover;cursor:pointer;border:1px solid #e5e7eb" onerror="this.style.display=\'none\'">';
}).join('') + (imgs.length > 3 ? '<span style="font-size:10px;color:#94a3b8">+' + (imgs.length-3) + '</span>' : '') + '</div>';
} else {
imgHtml = '<span style="color:#cbd5e1;font-size:11px">—</span>';
}
return '<tr style="border-bottom:1px solid #f1f5f9;cursor:pointer" onclick="openQuoteFromList('+q.id+')" onmouseover="this.style.background=\'#fafbfc\'" onmouseout="this.style.background=\'\'">'
+'<td style="padding:10px 14px;font-size:13px;font-weight:600;color:#024550">#'+q.id+'</td>'
+'<td style="padding:10px 14px;font-size:13px"><div style="font-weight:600;color:#1a1a1a">'+kundDisplay+'</div><div style="font-size:11px;color:#94a3b8;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+(q.customer_address||'')+'</div></td>'
+'<td style="padding:10px 14px;font-size:12px;color:#64748b">'+configSummary+'</td>'
+'<td style="padding:10px 14px;font-size:13px;font-weight:700;color:#1a1a1a;text-align:right">'+amount+'</td>'
+'<td style="padding:10px 14px">'+imgHtml+'</td>'
+'<td style="padding:10px 14px;font-size:12px;color:#64748b">'+(q.created_by_name||'—')+'</td>'
+'<td style="padding:10px 14px;font-size:12px;color:#94a3b8">'+date+'</td>'
+'<td style="padding:10px 14px"><span style="padding:3px 10px;border-radius:10px;font-size:11px;font-weight:600;background:'+st.bg+';color:'+st.color+'">'+st.label+'</span></td>'
+'<td style="padding:10px 14px"><button onclick="event.stopPropagation();deleteQuote('+q.id+')" style="background:none;border:none;cursor:pointer;color:#dc2626;font-size:16px" title="Ta bort">×</button></td>'
+'</tr>';
}).join('');
} catch(e){
body.innerHTML = '<tr><td colspan="9" style="padding:40px;text-align:center;color:#ef4444;font-size:13px">Kunde inte ladda kalkyler: '+e.message+'</td></tr>';
}
}
async function openQuoteFromList(id){
if(kalkylOrigin !== 'faltsalj') kalkylOrigin = 'konfigurator';
try {
const resp = await fetch('/api/quotes.php?id='+id);
const data = await resp.json();
if(!data.success || !data.quote) { alert('Kunde inte ladda kalkyl'); return; }
const q = data.quote;
currentQuoteId = q.id;
// Ladda befintliga items till _kalkylItems
_kalkylItems = {};
try {
var cd = q.config_data ? (typeof q.config_data === 'string' ? JSON.parse(q.config_data) : q.config_data) : {};
if(cd.items) {
Object.keys(cd.items).forEach(function(k){ _kalkylItems[k] = cd.items[k]; });
} else if(cd.total !== undefined && q.category) {
// Gammal platt config — migrera
_kalkylItems[q.category] = cd;
}
} catch(e){}
// Produktkatalog-kalkyl → visa produktlista med prissammanställning
if(q.category === 'produktkatalog' && typeof openProductQuote === 'function') {
navigateTo('konfigurator');
openProductQuote(q);
return;
}
// Återställ HELA kundinfo — först från config_data.customer (fullständig), annars från quote-fält
var savedCustomer = null;
try {
var cd = q.config_data ? (typeof q.config_data === 'string' ? JSON.parse(q.config_data) : q.config_data) : {};
if(cd.customer) savedCustomer = cd.customer;
} catch(e){}
if(savedCustomer) {
pendingKalkylCustomer = savedCustomer;
} else {
pendingKalkylCustomer = {
prospectIdx: -1,
name: q.customer_name || '',
email: q.customer_email || '',
phone: q.customer_phone || '',
address: q.customer_address || '',
ownerType: q.owner_count == 2 ? '2' : (q.owner_count == 0 ? 'brf' : '1'),
maxDeduction: (q.owner_count||1) * 50000,
owner1: { name: q.customer_name || '', pnr: q.customer_personnummer || '' },
owner2: null,
solarData: q.prospect_data || {},
created: q.created_at
};
}
const savedImages = q.images || [];
const isOffert = (q.status === 'offert' || q.status === 'skickad' || q.status === 'godkänd');
// Multi-kategori → direkt till sammanställningssidan
const savedCat = q.category || '';
const categories = savedCat.split(',').map(function(c){ return c.trim(); }).filter(Boolean);
if(categories.length > 1 && typeof showKalkylSummary === 'function') {
showKalkylSummary();
_cfgDirty = false;
return;
}
// Sätt rubrik
var titleEl = document.getElementById('kalkylTitle');
if(titleEl) {
var display = 'Kalkyl';
if(q.customer_name) display += ' - ' + q.customer_name;
display += ' #' + q.id;
titleEl.textContent = display;
}
// En kategori → navigera till konfiguratorn
navigateTo('konfigurator');
showKalkylConfig();
populateKalkylFromCustomer();
kalkylImages = savedImages.map(img => ({
url: img.url || img,
type: img.type || 'photo',
selected: true,
created: img.created || new Date().toISOString()
}));
if(typeof renderKalkylPhotos === 'function') renderKalkylPhotos();
// Ladda in kalkylinställningar
setTimeout(() => {
const firstCat = categories[0] || '';
const catSel = document.getElementById('categorySelect');
if(catSel){ catSel.value = firstCat; changeCategory(); }
// Solceller-specifik: ladda panel/batteri-inställningar
if(firstCat === 'solceller') {
if(q.panel_count){
const slider = document.getElementById('solPanelCount');
if(slider) slider.value = q.panel_count;
}
if(q.battery_kwh){
const bSlider = document.getElementById('solBatteryKwh');
if(bSlider) bSlider.value = q.battery_kwh;
}
if(typeof updateSolarCalc === 'function') updateSolarCalc();
}
// Batteri-specifik: ladda sparade val
if(firstCat === 'batteri') {
restoreBatConfig(q);
}
// Nollställ dirty — vi har bara laddat, inte ändrat
_cfgDirty = false;
// Bara öppna affär-vyn om det är en offert
if(isOffert){
setTimeout(() => {
const savedExtras = q.extra_costs || [];
const savedNotes = q.notes || '';
const savedMargin = q.margin_percent || 0;
document.getElementById('solarConfigView').style.display='none';
document.getElementById('affarView').style.display='block';
const banner = document.getElementById('affarCustomerBanner');
const c = pendingKalkylCustomer;
if(c && banner){
banner.innerHTML='<div style="background:linear-gradient(135deg,#ecfdf5,#f0f9ff);border:1px solid #bbf7d0;border-radius:12px;padding:14px 18px;display:flex;align-items:center;gap:16px;flex-wrap:wrap">'
+'<div style="flex:1;min-width:200px"><div style="font-weight:700;font-size:15px;color:#1a1a1a">'+(c.name||'Ej angiven')+'</div><div style="font-size:12px;color:#64748b">'+c.address+'</div></div>'
+(c.phone?'<div style="font-size:13px;color:#334155">'+c.phone+'</div>':'')
+(c.email?'<div style="font-size:13px;color:#334155">'+c.email+'</div>':'')
+'</div>';
}
const cs = currentCalcState;
const summary = document.getElementById('affarConfigSummary');
if(summary){
let rows = '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Solpaneler</span><span style="font-size:13px;font-weight:600">'+cs.panelName+' × '+cs.panelCount+' ('+cs.kwp+' kWp)</span></div>';
if(cs.batteryKwh>0) rows += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Batteri</span><span style="font-size:13px;font-weight:600">'+cs.batteryKwh+' kWh</span></div>';
if(cs.chargerName) rows += '<div style="display:flex;justify-content:space-between;padding:8px 12px;background:#f8fafc;border-radius:8px"><span style="font-size:13px;color:#64748b">Laddare</span><span style="font-size:13px;font-weight:600">'+cs.chargerName+'</span></div>';
summary.innerHTML = rows;
}
affarExtraCosts = savedExtras;
affarImages = savedImages;
currentQuoteId = q.id;
const affarNotesEl = document.getElementById('affarNotes');
if(affarNotesEl) affarNotesEl.value = savedNotes;
const marginSlider = document.getElementById('affarMarginSlider');
if(marginSlider) marginSlider.value = savedMargin;
renderAffarExtras();
renderAffarImages();
updateAffarCalc();
}, 200);
}
}, 100);
} catch(e){
alert('Kunde inte öppna kalkyl: '+e.message);
}
}
async function deleteQuote(id){
if(!confirm('Ta bort kalkyl #'+id+'?')) return;
try {
await fetch('/api/quotes.php?id='+id, {method:'DELETE'});
// Rensa prospekt-koppling i localStorage
faltProspects.forEach(function(p){
if(p.quoteId === id) p.quoteId = null;
if(p.quoteIds){
p.quoteIds = p.quoteIds.filter(function(qid){ return qid !== id; });
if(p.quoteIds.length === 0 && p.status === 'kalkyl') p.status = 'interested';
} else if(p.quoteId === null && p.status === 'kalkyl'){
p.status = 'interested';
}
});
localStorage.setItem('faltProspects', JSON.stringify(faltProspects));
renderFaltMarkers(); renderFaltList(); updateFaltStats(); renderLeadsTable();
loadQuotesList();
} catch(e){ alert('Kunde inte ta bort: '+e.message); }
}
// Edit lead note modal