// Calc_summary.js — Kalkylsammanställning (egen sida)
// Läser _kalkylItems (låst varukorg). Ögonikon = expand tillval/variant.
var _calcSummaryExpanded = {};
var _calcSummaryCatExpanded = {}; // category-level expand-all toggle
var _calcSummaryOrigin = null;
var _calcSummaryState = { owners: 1, deductType: 'none', sliderValue: null, finYears: 15 };
function _calcSummaryEsc(v){
return String(v == null ? '' : v).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
}
function _calcSummaryNukeOthers(){
// Döljer HELA alla andra page-content (inklusive page-konfigurator)
document.querySelectorAll('.page-content').forEach(function(p){
if(p.id !== 'page-calc-summary'){
p.classList.remove('active');
p.style.display = 'none';
}
});
// Döljer kända inre vyer som kan leaka — men TAR INTE BORT newCalcRenderArea
// så att tillbaka-knappen kan återställa fönster/produkt-konfigurator-STATE.
['summaryProductList','kalkylListView','kalkylConfigView','newCalcRenderArea','newCalcShell','cfgCategoryBar','cfgCategoryGrid','affarView','solarConfigView','genericConfigView','fonsterConfigView','batteriConfigView','laddboxConfigView','taktvatConfigView','varmepumpConfigView','takConfigView','isoleringConfigView'].forEach(function(id){
var el = document.getElementById(id);
if(el){ el.style.display = 'none'; el.classList.remove('active'); }
});
}
function openCalcSummary(){
console.log('[CalcSummary] openCalcSummary()');
// Spara origin-sidan så Tillbaka-knappen kan återställa den
var active = document.querySelector('.page-content.active');
if(active && active.id !== 'page-calc-summary'){
_calcSummaryOrigin = active.id;
}
_calcSummaryNukeOthers();
var page = document.getElementById('page-calc-summary');
if(!page){
console.error('[CalcSummary] page-calc-summary saknas i DOM — är pages/Calc_summary.php inkluderad?');
return;
}
page.classList.add('active');
page.style.display = 'block';
_calcSummaryLoadQuote();
_calcSummaryRenderHeader();
// Re-trigga kategori-add-ons (för sparade kalkyler som öppnas, eller efter cache-invalidate)
if(typeof _prisInvalidateAddonsCache === 'function') _prisInvalidateAddonsCache();
if(typeof _prisRecalculateAddons === 'function') _prisRecalculateAddons();
renderCalcSummary();
try { window.scrollTo(0, 0); } catch(e){}
}
function closeCalcSummary(){
var page = document.getElementById('page-calc-summary');
if(page){ page.classList.remove('active'); page.style.display = 'none'; }
var originId = _calcSummaryOrigin || 'page-konfigurator';
var origin = document.getElementById(originId);
if(origin){ origin.classList.add('active'); origin.style.display = 'block'; }
if(originId === 'page-konfigurator'){
var renderArea = document.getElementById('newCalcRenderArea');
var cfgView = document.getElementById('kalkylConfigView');
var listView = document.getElementById('kalkylListView');
// Om användaren hade en aktiv konfigurator (render-area finns med innehåll) →
// återvisa den så fönsterkalkylen m.m. står kvar med alla val intakta.
if(renderArea && renderArea.innerHTML && renderArea.innerHTML.trim() !== ''){
if(cfgView) cfgView.style.display = 'block';
if(listView) listView.style.display = 'none';
renderArea.style.display = 'block';
} else {
// Ingen pågående konfigurator → visa kalkyl-listan som vanligt
if(listView) listView.style.display = 'block';
if(cfgView) cfgView.style.display = 'none';
if(typeof currentQuoteId !== 'undefined') try{ currentQuoteId = null; }catch(e){}
}
}
document.querySelectorAll('.nav-item').forEach(function(n){
var targetPage = originId.replace(/^page-/, '');
if(n.dataset && n.dataset.page === targetPage) n.classList.add('active');
else n.classList.remove('active');
});
_calcSummaryOrigin = null;
}
function toggleCalcSummaryRow(entryId){
_calcSummaryExpanded[entryId] = !_calcSummaryExpanded[entryId];
renderCalcSummary();
}
function toggleCalcSummaryCat(cat){
_calcSummaryCatExpanded[cat] = !_calcSummaryCatExpanded[cat];
// När kategori expanderas → expandera alla entries i den
var open = _calcSummaryCatExpanded[cat];
if(typeof _kalkylItems !== 'undefined' && _kalkylItems[cat]){
var entries = Array.isArray(_kalkylItems[cat].entries) ? _kalkylItems[cat].entries : [_kalkylItems[cat]];
entries.forEach(function(e){ if(e && e.entry_id) _calcSummaryExpanded[e.entry_id] = open; });
}
renderCalcSummary();
}
window.toggleCalcSummaryCat = toggleCalcSummaryCat;
function _calcSummaryCatLabel(cat){
var labels = typeof _catLabels !== 'undefined' ? _catLabels
: {solceller:'Solpaneler',batteri:'Batteri',laddbox:'Laddbox',taktvatt:'Taktv\u00e4tt',varmepump:'V\u00e4rmepump',tak:'Tak',fonster:'F\u00f6nster',isolering:'Isolering'};
if(cat === '_addons') return 'Övriga installationskostnader';
return labels[cat] || cat;
}
// Grupperar entries hierarkiskt efter info-tillval med ordning 1 (yttre) → 2 (inre) → …
// Ett info-tillval har tillval[i].isInfo===true och .ordning (1/2/3) + .value (t.ex. "Våning 1").
// Entries utan info-tillval hamnar under "(Övrigt)" eller direkt.
function _calcSummaryRenderGrouped(cat, entries){
// Gruppering sker ENDAST om ett tillval har unit='info' och ordning = 1 eller 2.
// ordning 1 = yttre grupp (t.ex. Våning), ordning 2 = inre grupp (t.ex. Rum).
// Obligatoriska tillval utan info/ordning är ren metadata och skapar INGEN gruppering.
function _ord(tv){
if(!tv || !tv.isInfo || tv.ordning == null) return null;
var o = parseInt(tv.ordning);
if(o === 1 || o === 2) return o;
return null;
}
function infoKey(e, ord){
var arr = Array.isArray(e.tillval) ? e.tillval : [];
var hit = arr.find(function(tv){ return _ord(tv) === ord; });
return hit ? String(hit.value || '') : '';
}
// Samla alla unika ordning-nivåer
var levels = [];
entries.forEach(function(e){
(Array.isArray(e.tillval) ? e.tillval : []).forEach(function(tv){
var o = _ord(tv);
if(o != null && levels.indexOf(o) === -1) levels.push(o);
});
});
levels.sort(function(a,b){ return a - b; });
if(!levels.length){
return entries.map(function(e){ return _calcSummaryRenderEntry(cat, e); }).join('');
}
// Rekursiv rendering
function renderLevel(list, levelIdx){
if(levelIdx >= levels.length){
return list.map(function(e){ return _calcSummaryRenderEntry(cat, e); }).join('');
}
var ord = levels[levelIdx];
var isLast = (levelIdx === levels.length - 1);
// Gruppera list efter infoKey(ord)
var groups = {};
var order = [];
list.forEach(function(e){
var k = infoKey(e, ord);
if(!(k in groups)){ groups[k] = []; order.push(k); }
groups[k].push(e);
});
return order.map(function(k){
var label = k || (ord === 1 ? '(Övrigt)' : '');
if(ord === 1){
// Yttersta: bara caps-rubrik utanför boxar
var floorHead = k ? '<div style="font-size:13px;font-weight:800;color:#024550;text-transform:uppercase;letter-spacing:.5px;padding:14px 16px 6px">' + _calcSummaryEsc(label) + '</div>' : '';
return floorHead + renderLevel(groups[k], levelIdx + 1);
}
// Inre nivå (ordning 2+): wrappa i rum-box
var inner = renderLevel(groups[k], levelIdx + 1);
var head = k ? '<div style="font-size:14px;font-weight:700;color:#1a1a1a;padding:12px 16px 8px;border-bottom:1px solid #e5e7eb">' + _calcSummaryEsc(label) + '</div>' : '';
return '<div style="background:#f8fafc;border:1px solid #e5e7eb;border-radius:12px;margin:0 8px 10px;overflow:hidden">'
+ head
+ '<div style="padding:4px 8px">' + inner + '</div>'
+ '</div>';
}).join('');
}
return renderLevel(entries, 0);
}
function _calcSummaryRenderEntry(cat, e){
// Produktraderna visas ALLTID (namn + Våning/Rum). Penna/kryss visas ALLTID.
// Ögat döljer BARA priset och expand-sektionen med tillval-detaljer.
var catOpen = !!_calcSummaryCatExpanded[cat];
var tillval = (Array.isArray(e.tillval) ? e.tillval : []).filter(function(t){ return t && !t.hidden; });
var expandSection = '';
if(catOpen){
var rows = '';
if(e.variant_label){
rows += '<div class="calc-summary-expand-row"><span>Variant: <strong style="color:#1a1a1a">' + _calcSummaryEsc(e.variant_label) + '</strong></span><span></span></div>';
}
tillval.forEach(function(t){
var qtyPart = (t.qty && t.qty !== 1) ? ' × ' + t.qty : '';
var valuePart = t.value ? ': <strong style="color:#1a1a1a">' + _calcSummaryEsc(t.value) + '</strong>' : '';
var nameLabel = _calcSummaryEsc(t.name || '') + valuePart + qtyPart;
rows += '<div class="calc-summary-expand-row"><span>' + nameLabel + '</span><span></span></div>';
});
if(rows) expandSection = '<div class="calc-summary-expand">' + rows + '</div>';
}
// Våning + Rum som subtitle (så användaren ser vilket rum fönstret tillhör även utan öga)
var _roomParts = [];
tillval.forEach(function(t){
if(!t || !t.value) return;
var id = String(t.id || '').toLowerCase();
var nm = String(t.name || '').toLowerCase();
if(id === 'våning' || id === 'vaning' || nm === 'våning') _roomParts.push('Våning: ' + t.value);
else if(id === 'rum' || nm === 'rum') _roomParts.push('Rum: ' + t.value);
});
var roomSub = _roomParts.length ? '<div style="font-size:11px;color:#64748b;margin-top:2px">' + _calcSummaryEsc(_roomParts.join(' · ')) + '</div>' : '';
var gross = parseFloat(e.subtotal) || ((parseFloat(e.total) || 0) + (parseFloat(e.deduction_amount) || 0));
var priceCell = catOpen
? '<div class="calc-summary-row-price">' + gross.toLocaleString('sv-SE') + ' kr</div>'
: '<div class="calc-summary-row-price"></div>';
// Penna + kryss visas ALLTID. Ögat styr bara priset.
var actionBtns = '<button class="calc-summary-edit" onclick="calcSummaryEdit(\'' + _calcSummaryEsc(cat) + '\',\'' + _calcSummaryEsc(e.entry_id) + '\', this)" title="Redigera">'
+ '<svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" fill="none" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>'
+ '</button>'
+ '<button class="calc-summary-del" onclick="calcSummaryRemove(\'' + _calcSummaryEsc(cat) + '\',\'' + _calcSummaryEsc(e.entry_id) + '\')" title="Ta bort">×</button>';
return '<div class="calc-summary-row">'
+ '<div class="calc-summary-row-main">'
+ '<div class="calc-summary-row-name">' + ((parseInt(e.qty)||1) > 1 ? (parseInt(e.qty) + '× ') : '') + _calcSummaryEsc(e.description || e.product_name || cat) + '</div>'
+ '<div class="calc-summary-row-cat">' + _calcSummaryEsc(_calcSummaryCatLabel(cat)) + roomSub + '</div>'
+ '</div>'
+ priceCell
+ actionBtns
+ expandSection
+ '</div>';
}
function renderCalcSummary(){
var content = document.getElementById('calcSummaryContent');
var sessEl = document.getElementById('calcSummarySession');
if(!content) return;
var sid = (typeof prisGetCurrentSessionId === 'function') ? prisGetCurrentSessionId() : '(saknas)';
if(sessEl){
sessEl.innerHTML = '<span class="calc-summary-sid-pill">SESSION: ' + _calcSummaryEsc(sid) + '</span>';
}
var _addBtnHtml = '<div class="calc-summary-add-row">'
+ '<button class="calc-summary-add-btn" onclick="calcSummaryAddProduct()">'
+ '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>'
+ '<span>Lägg till produkt</span>'
+ '</button>'
+ '</div>';
if(typeof _kalkylItems === 'undefined' || !_kalkylItems || !Object.keys(_kalkylItems).length){
content.innerHTML = '<div class="calc-summary-empty">Inga produkter i kalkylen ännu. Lägg till via Ny Kalkyl.</div>' + _addBtnHtml;
_renderCalcSummarySidebar();
return;
}
var html = '';
var catOrder = ['solceller','batteri','laddbox','varmepump','taktvatt','tak','fonster','isolering','_addons'];
var seen = {};
var orderedKeys = [];
// Interna state-nycklar (start med _ och slutar på State) är inte kategorier
function _isMeta(k){ return k === '_addonsState' || (k.charAt(0) === '_' && /State$/.test(k)); }
catOrder.forEach(function(k){ if(_kalkylItems[k] && !_isMeta(k)){ orderedKeys.push(k); seen[k] = true; } });
Object.keys(_kalkylItems).forEach(function(k){ if(!seen[k] && !_isMeta(k)) orderedKeys.push(k); });
orderedKeys.forEach(function(cat){
var item = _kalkylItems[cat];
if(!item) return;
var entriesAll = Array.isArray(item.entries) ? item.entries : [item];
// Filtrera bort dolda addon-rader i UI (men de finns kvar i _kalkylItems för totalt)
var entries = entriesAll.filter(function(e){ return e && !e.hidden; });
if(!entries.length) return;
var catTotal = 0;
entries.forEach(function(e){
var net = parseFloat(e.total) || 0;
var ded = parseFloat(e.deduction_amount) || 0;
var sub = parseFloat(e.subtotal) || (net + ded);
catTotal += sub;
});
var catOpen = !!_calcSummaryCatExpanded[cat];
var catEyeIcon = catOpen
? '<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" fill="none" stroke-width="2"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>'
: '<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" fill="none" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>';
var catAddonsHtml = '';
if(catOpen && cat !== '_addons' && typeof _kalkylItems !== 'undefined' && _kalkylItems['_addons']){
var addonEntries = Array.isArray(_kalkylItems['_addons'].entries) ? _kalkylItems['_addons'].entries : [];
var fromCat = addonEntries.filter(function(a){ return a && a._addon_from_cat === cat; });
if(fromCat.length){
catAddonsHtml = '<div style="margin-top:8px;padding:10px 14px;background:#fef9e8;border:1px solid #fde68a;border-radius:8px">'
+ '<div style="font-size:11px;font-weight:700;color:#92400e;text-transform:uppercase;letter-spacing:.4px;margin-bottom:6px">Kategori-add-ons</div>';
fromCat.forEach(function(a){
var line = '<div style="display:flex;justify-content:space-between;align-items:center;padding:4px 0;font-size:13px">';
line += '<span style="color:#1a1a1a">' + _calcSummaryEsc(a.description || a.product_name || a.product_id);
if(a._addon_unique) line += ' <span style="font-size:10px;color:#059669;font-weight:700;text-transform:uppercase;margin-left:4px">unik</span>';
if(a.hidden) line += ' <span style="font-size:10px;color:#dc2626;font-weight:700;text-transform:uppercase;margin-left:4px">dold</span>';
line += '</span>';
line += '<span style="font-weight:700;color:#024550">' + (parseFloat(a.subtotal||a.total||0)).toLocaleString('sv-SE') + ' kr</span>';
line += '</div>';
catAddonsHtml += line;
});
catAddonsHtml += '</div>';
}
}
html += '<div class="calc-summary-group">'
+ '<div class="calc-summary-group-header" style="display:flex;align-items:center;gap:8px">'
+ '<button onclick="toggleCalcSummaryCat(\'' + _calcSummaryEsc(cat) + '\')" title="Visa/dölj allt" style="background:none;border:none;padding:4px;cursor:pointer;color:#64748b;display:flex;align-items:center">' + catEyeIcon + '</button>'
+ '<span style="flex:1">' + _calcSummaryEsc(_calcSummaryCatLabel(cat)) + '</span>'
+ '<span>' + catTotal.toLocaleString('sv-SE') + ' kr</span>'
+ '</div>'
+ _calcSummaryRenderGrouped(cat, entries)
+ catAddonsHtml
+ '</div>';
});
// "Lägg till produkt"-knapp längst ner — går tillbaka till kategori-valet
html += _addBtnHtml;
content.innerHTML = html;
_renderCalcSummarySidebar();
}
function calcSummaryAddProduct(){
// Stäng Calc_summary och visa CalcBuilder-grid (samma som "Ny Kalkyl" men utan att rensa varukorgen)
var page = document.getElementById('page-calc-summary');
if(page){ page.classList.remove('active'); page.style.display = 'none'; }
if(typeof navigateTo === 'function'){
navigateTo('konfigurator');
} else {
var kp = document.getElementById('page-konfigurator');
if(kp){ kp.classList.add('active'); kp.style.display = 'block'; }
}
var listView = document.getElementById('kalkylListView');
var cfgView = document.getElementById('kalkylConfigView');
if(listView) listView.style.display = 'none';
if(cfgView){ cfgView.style.display = 'block'; }
// Städa undan aktiv konfigurator + gamla gridden
var renderArea = document.getElementById('newCalcRenderArea');
if(renderArea) renderArea.remove();
['affarView','solarConfigView','genericConfigView','fonsterConfigView','batteriConfigView','laddboxConfigView','taktvatConfigView','varmepumpConfigView','takConfigView','isoleringConfigView','cfgCategoryGrid'].forEach(function(id){
var el = document.getElementById(id);
if(el){ el.style.display = 'none'; el.classList.remove('active'); }
});
// Nollställ aktiv kategori-val men BEHÅLL _kalkylItems (varukorgen)
if(typeof _newCalcContext !== 'undefined'){
_newCalcContext.selectedProductId = null;
_newCalcContext.tillval = [];
_newCalcContext.categoryId = '';
}
// Visa CalcBuilder-griden (newCalcShell + newCalcGrid med alla kategori-kort)
if(typeof window._showNewCalcGrid === 'function') window._showNewCalcGrid();
if(typeof window._loadNewCalcCards === 'function') window._loadNewCalcCards();
try { window._newCalcActive = true; } catch(e){}
try { window.scrollTo(0, 0); } catch(e){}
}
function _renderCalcSummarySidebar(){
var el = document.getElementById('calcSummaryPrisSidebar');
var renderer = (typeof renderPriceSummary === 'function') ? renderPriceSummary
: (typeof renderPrisSidebar === 'function') ? renderPrisSidebar : null;
if(!el || !renderer) return;
// Aggregera GROSS subtotal + POTENTIELLT avdrag PER tax_type
var subtotal = 0;
var breakdown = { ROT: 0, GT20: 0, GT50: 0 };
var aggTaxType = 'NONE';
if(typeof _kalkylItems !== 'undefined' && _kalkylItems){
Object.keys(_kalkylItems).forEach(function(k){
if(k === '_addonsState' || (k.charAt(0) === '_' && /State$/.test(k))) return;
var item = _kalkylItems[k];
if(!item) return;
var entries = Array.isArray(item.entries) ? item.entries : [item];
entries.forEach(function(e){
var gross = parseFloat(e.subtotal || 0) || parseFloat(e.total || 0);
subtotal += gross;
var ded = parseFloat(e.deduction_amount) || 0;
var tt = e.tax_type || 'NONE';
if(ded > 0 && breakdown.hasOwnProperty(tt)) breakdown[tt] += ded;
if(aggTaxType === 'NONE' && tt !== 'NONE') aggTaxType = tt;
});
});
}
var potentialDed = breakdown.ROT + breakdown.GT20 + breakdown.GT50;
// Effektivt avdrag = min(potentiellt, owners × maxPerPerson, slider)
var deductType = _calcSummaryState.deductType;
if(!deductType){
deductType = potentialDed > 0 ? ((aggTaxType === 'ROT') ? 'rot' : 'green') : 'none';
}
var owners = _calcSummaryState.owners || 1;
var tax = (typeof taxSettings !== 'undefined') ? taxSettings : { rotMax: 50000, gt50: 50, gt20: 20, rot: 30, moms: 25 };
var maxPerPerson = (aggTaxType === 'ROT') ? (tax.rotMax || 50000) : 50000;
var ownerCap = owners * maxPerPerson;
var sliderMax = ownerCap;
var sliderValue = (_calcSummaryState.sliderValue != null) ? Math.min(parseFloat(_calcSummaryState.sliderValue), sliderMax) : sliderMax;
var effectiveDed = (deductType === 'none') ? 0 : Math.min(potentialDed, sliderValue);
renderer('calcSummaryPrisSidebar', {
lines: [],
subtotal: subtotal,
totalIsAll: true,
taxType: aggTaxType,
taxBreakdown: breakdown, // {ROT, GT20, GT50} — per-typ potentiella avdrag
deductType: deductType,
deductAmount: effectiveDed,
owners: owners,
sliderInteractive: true,
sliderValue: sliderValue,
maxDeduction: sliderMax,
total: subtotal - effectiveDed,
finYears: _calcSummaryState.finYears || 15,
monthly: 0,
category: '_summary',
onDeductChange: 'calcSummarySetDeductType',
onOwnerChange: 'calcSummarySetOwners',
onFinChange: 'calcSummarySetFinYears',
onSliderChange: 'calcSummarySetSliderValue'
});
}
function calcSummarySetOwners(n){ _calcSummaryState.owners = parseInt(n) || 1; _calcSummaryState.sliderValue = null; _renderCalcSummarySidebar(); }
function calcSummarySetDeductType(t){ _calcSummaryState.deductType = t || 'none'; _renderCalcSummarySidebar(); }
function calcSummarySetSliderValue(v){ _calcSummaryState.sliderValue = parseFloat(v) || 0; _renderCalcSummarySidebar(); }
function calcSummarySetFinYears(y){ _calcSummaryState.finYears = parseInt(y) || 0; _renderCalcSummarySidebar(); }
function calcSummaryEdit(cat, entryId, btnEl){
if(typeof editSummaryEntry !== 'function') return;
// Gråa ut knappen direkt så användaren inte klickar igen
if(btnEl){
btnEl.disabled = true;
btnEl.style.opacity = '.35';
btnEl.style.cursor = 'wait';
btnEl.style.pointerEvents = 'none';
}
closeCalcSummary();
// editSummaryEntry returnerar Promise som resolvar när allt laddat + openForEdit körd
setTimeout(function(){
var p = editSummaryEntry(cat, entryId, btnEl);
// Fallback-reset (om editSummaryEntry inte hanterar det)
if(p && typeof p.then === 'function'){
p.then(function(){
if(btnEl){ btnEl.disabled=false; btnEl.style.opacity=''; btnEl.style.cursor=''; btnEl.style.pointerEvents=''; }
});
}
}, 100);
}
function calcSummaryRemove(cat, entryId){
if(!confirm('Ta bort den här raden från kalkylen?')) return;
// Addons (_addons) måste tas bort permanent via disableAddon, annars
// lägger _prisRecalculateAddons tillbaka dem direkt.
if(cat === '_addons' && typeof disableAddon === 'function'){
disableAddon(entryId);
renderCalcSummary();
return;
}
if(typeof removeKalkylEntry === 'function'){
removeKalkylEntry(cat, entryId);
}
renderCalcSummary();
}
// ============================================================
// HEADER: Kundinfo (expand/edit/save) + prospektbilder + actions
// ============================================================
var _calcSummaryQuote = { id:null, quote_number:null, sent_date:null, customer_name:'', customer_address:'', customer_email:'', customer_phone:'', customer_personnummer:'', notes:'', status:'utkast', images:[] };
var _calcSummaryCustomerOpen = false;
var _calcSummaryImagesOpen = false;
var _calcSummarySaveTimer = null;
function _calcSummaryLoadQuote(){
var id = (typeof currentQuoteId !== 'undefined' && currentQuoteId) ? currentQuoteId : null;
_calcSummaryQuote.id = id;
if(!id){
_calcSummaryQuote.quote_number = null;
_calcSummaryQuote.sent_date = null;
_calcSummaryQuote.customer_name = '';
_calcSummaryQuote.customer_address = '';
_calcSummaryQuote.customer_email = '';
_calcSummaryQuote.customer_phone = '';
_calcSummaryQuote.customer_personnummer = '';
_calcSummaryQuote.notes = '';
_calcSummaryQuote.status = 'utkast';
_calcSummaryQuote.images = [];
return;
}
fetch('/api/quotes.php?id=' + encodeURIComponent(id))
.then(function(r){ return r.json(); })
.then(function(d){
var q = (d && d.quote) ? d.quote : (d && d.success && d.quotes && d.quotes[0]) ? d.quotes[0] : null;
if(!q) return;
_calcSummaryQuote.quote_number = q.quote_number || null;
_calcSummaryQuote.sent_date = q.sent_date || null;
_calcSummaryQuote.customer_name = q.customer_name || '';
_calcSummaryQuote.customer_address = q.customer_address || '';
_calcSummaryQuote.customer_email = q.customer_email || '';
_calcSummaryQuote.customer_phone = q.customer_phone || '';
_calcSummaryQuote.customer_personnummer = q.customer_personnummer || '';
_calcSummaryQuote.notes = q.notes || '';
_calcSummaryQuote.status = q.status || 'utkast';
var imgs = q.images;
if(typeof imgs === 'string'){ try{ imgs = JSON.parse(imgs); }catch(e){ imgs = []; } }
_calcSummaryQuote.images = Array.isArray(imgs) ? imgs : [];
_calcSummaryRenderHeader();
})
.catch(function(e){ console.warn('[CalcSummary] loadQuote failed', e); });
}
function _calcSummaryStatusLabel(s){
return ({utkast:'Utkast',offert:'Offert',skickad:'Skickad',accepterad:'Godkänd',avslutad:'Avslutad'})[s] || s || 'Utkast';
}
function _calcSummaryStatusColor(s){
return ({utkast:'#64748b',offert:'#0284c7',skickad:'#d97706',accepterad:'#059669',avslutad:'#4b5563'})[s] || '#64748b';
}
function _calcSummaryRenderHeader(){
var host = document.getElementById('calcSummaryHeader');
if(!host) return;
var q = _calcSummaryQuote;
var hasName = !!(q.customer_name || '').trim();
var custSummary = hasName
? _calcSummaryEsc(q.customer_name) + (q.customer_address ? ' — ' + _calcSummaryEsc(q.customer_address) : '')
: '<span style="color:#94a3b8;font-weight:500">Ej ifylld</span>';
var imgCount = (q.images || []).length;
var imgSummary = imgCount ? (imgCount + ' bild' + (imgCount>1?'er':'')) : '<span style="color:#94a3b8;font-weight:500">Inga bilder</span>';
var statusLbl = _calcSummaryStatusLabel(q.status);
var statusCol = _calcSummaryStatusColor(q.status);
var html = '';
// Kundinformation-rad
html += '<div class="cs-head-block">'
+ '<div class="cs-head-row" onclick="calcSummaryToggleCustomer()">'
+ '<div class="cs-head-left">'
+ '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>'
+ '<strong>Kundinformation</strong>'
+ '<span class="cs-head-summary">' + custSummary + '</span>'
+ '</div>'
+ '<svg class="cs-head-chev" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="2.5" style="transform:rotate(' + (_calcSummaryCustomerOpen?'0':'-90') + 'deg)"><polyline points="6 9 12 15 18 9"/></svg>'
+ '</div>'
+ '<div class="cs-head-body" style="' + (_calcSummaryCustomerOpen?'':'display:none') + '">'
+ _calcSummaryCustomerForm()
+ '</div>'
+ '</div>';
// Prospektbilder-rad
html += '<div class="cs-head-block">'
+ '<div class="cs-head-row" onclick="calcSummaryToggleImages()">'
+ '<div class="cs-head-left">'
+ '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>'
+ '<strong>Prospektbilder</strong>'
+ '<span class="cs-head-summary">' + imgSummary + '</span>'
+ '</div>'
+ '<svg class="cs-head-chev" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="2.5" style="transform:rotate(' + (_calcSummaryImagesOpen?'0':'-90') + 'deg)"><polyline points="6 9 12 15 18 9"/></svg>'
+ '</div>'
+ '<div class="cs-head-body" style="' + (_calcSummaryImagesOpen?'':'display:none') + '">'
+ _calcSummaryImagesPanel()
+ '</div>'
+ '</div>';
// Status + actions
var hasOffert = !!q.quote_number;
var isSent = q.status === 'skickad';
var primaryLabel = hasOffert ? 'Visa offert (PDF)' : 'Skapa offert (PDF)';
var sentTxt = '';
if(isSent && q.sent_date){
var d = String(q.sent_date).replace('T',' ').substring(0,16);
sentTxt = '<span class="cs-sent-date">Skickad ' + _calcSummaryEsc(d) + '</span>';
}
html += '<div class="cs-actions-row">'
+ '<span class="cs-status-badge" style="background:' + statusCol + '">' + _calcSummaryEsc(statusLbl) + '</span>'
+ '<button class="cs-action-btn cs-action-primary" onclick="calcSummaryCreateOffer()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg> ' + primaryLabel + '</button>'
+ '<button class="cs-action-btn" onclick="calcSummarySendOffer()"' + (!hasOffert?' disabled':'') + '><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2 11 13"/><path d="m22 2-7 20-4-9-9-4 20-7Z"/></svg> Skicka</button>'
+ '<button class="cs-action-btn" onclick="calcSummaryPrint()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg> Skriv ut</button>'
+ sentTxt
+ (hasOffert ? '<span class="cs-quote-id">Offert-ID #' + _calcSummaryEsc(q.quote_number) + '</span>' : '')
+ '</div>';
host.innerHTML = html;
}
function _calcSummaryCustomerForm(){
var q = _calcSummaryQuote;
return '<div class="cs-form">'
+ _csField('Namn','customer_name',q.customer_name,'text')
+ _csField('Adress','customer_address',q.customer_address,'text')
+ _csField('Email','customer_email',q.customer_email,'email')
+ _csField('Telefon','customer_phone',q.customer_phone,'tel')
+ _csField('Personnummer','customer_personnummer',q.customer_personnummer,'text')
+ '<div class="cs-field cs-field-wide"><label>Anteckningar</label>'
+ '<textarea rows="3" oninput="calcSummaryPatch(\'notes\',this.value)">' + _calcSummaryEsc(q.notes) + '</textarea>'
+ '</div>'
+ '<div class="cs-field cs-field-wide" style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">'
+ '<button id="csSaveBtn" onclick="calcSummarySaveCustomer()" style="padding:10px 22px;background:#024550;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit">Spara kunduppgifter</button>'
+ '</div>'
+ '</div>';
}
function calcSummarySaveCustomer(){
// Force-flush eventuell pending debounce och spara nu
if(_calcSummarySaveTimer){ clearTimeout(_calcSummarySaveTimer); _calcSummarySaveTimer = null; }
var btn = document.getElementById('csSaveBtn');
if(btn){ btn.disabled = true; btn.textContent = 'Sparar...'; }
return _calcSummaryPersistNow().then(function(ok){
if(btn){
btn.disabled = false;
btn.textContent = ok ? 'Sparat ✓' : 'Försök igen';
btn.style.background = ok ? '#10b981' : '#dc2626';
setTimeout(function(){
if(!btn) return;
btn.textContent = 'Spara kunduppgifter';
btn.style.background = '#024550';
}, 1800);
}
});
}
function _calcSummaryPersistNow(){
// Synkron-aktig save som returnerar Promise<boolean>
var id = _calcSummaryQuote.id || (typeof currentQuoteId !== 'undefined' ? currentQuoteId : null);
if(!id){
alert('Spara kalkylen först (klicka + Lägg till produkt eller Spara).');
return Promise.resolve(false);
}
var keys = ['customer_name','customer_address','customer_email','customer_phone','customer_personnummer','notes'];
var body = { id: id };
keys.forEach(function(k){ body[k] = _calcSummaryQuote[k]; });
return fetch('/api/quotes.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify(body)
}).then(function(r){ return r.json(); }).then(function(d){
return !!(d && d.success);
}).catch(function(e){ console.warn('[CalcSummary] save error', e); return false; });
}
window.calcSummarySaveCustomer = calcSummarySaveCustomer;
function _csField(label,key,val,type){
return '<div class="cs-field"><label>' + _calcSummaryEsc(label) + '</label>'
+ '<input type="' + type + '" value="' + _calcSummaryEsc(val) + '" oninput="calcSummaryPatch(\'' + key + '\',this.value)">'
+ '</div>';
}
function _calcSummaryImagesPanel(){
var imgs = _calcSummaryQuote.images || [];
var html = '<div class="cs-img-grid">';
imgs.forEach(function(im, idx){
var url = (typeof im === 'string') ? im : (im && im.url ? im.url : '');
if(!url) return;
html += '<div class="cs-img-thumb">'
+ '<img src="' + _calcSummaryEsc(url) + '" alt="">'
+ '<button class="cs-img-del" onclick="calcSummaryRemoveImage(' + idx + ')" title="Ta bort">×</button>'
+ '</div>';
});
html += '<label class="cs-img-add"><input type="file" accept="image/*" multiple style="display:none" onchange="calcSummaryUploadImages(this.files)"><span>+ Ladda upp</span></label>';
html += '</div>';
return html;
}
function calcSummaryToggleCustomer(){ _calcSummaryCustomerOpen = !_calcSummaryCustomerOpen; _calcSummaryRenderHeader(); }
function calcSummaryToggleImages(){ _calcSummaryImagesOpen = !_calcSummaryImagesOpen; _calcSummaryRenderHeader(); }
function calcSummaryPatch(key, val){
_calcSummaryQuote[key] = val;
if(_calcSummarySaveTimer) clearTimeout(_calcSummarySaveTimer);
_calcSummarySaveTimer = setTimeout(function(){ _calcSummaryPersist([key]); }, 700);
}
function _calcSummaryPersist(keys){
var id = _calcSummaryQuote.id || (typeof currentQuoteId !== 'undefined' ? currentQuoteId : null);
if(!id){
// Ingen sparad kalkyl än — kräv save först via existing flow
if(typeof saveCfgQuote === 'function'){
saveCfgQuote(_calcSummaryQuote.category || 'solceller');
setTimeout(function(){ _calcSummaryPersist(keys); }, 400);
}
return;
}
_calcSummaryQuote.id = id;
var body = { id: id };
(keys || ['customer_name','customer_address','customer_email','customer_phone','customer_personnummer','notes','status','images']).forEach(function(k){ body[k] = _calcSummaryQuote[k]; });
fetch('/api/quotes.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify(body)
}).then(function(r){ return r.json(); })
.then(function(d){
if(!d || !d.success) console.warn('[CalcSummary] save failed', d);
// Re-render BARA när formulär är stängt — annars tappar input fokus mitt i skrivning.
if(!_calcSummaryCustomerOpen && !_calcSummaryImagesOpen) _calcSummaryRenderHeader();
})
.catch(function(e){ console.warn('[CalcSummary] save error', e); });
}
function calcSummaryUploadImages(files){
if(!files || !files.length) return;
var id = _calcSummaryQuote.id || (typeof currentQuoteId !== 'undefined' ? currentQuoteId : null);
if(!id){ alert('Spara kalkylen först innan du laddar upp bilder.'); return; }
var fd = new FormData();
fd.append('quote_id', id);
for(var i=0;i<files.length;i++) fd.append('images[]', files[i]);
fetch('/api/quote_images.php', { method:'POST', body: fd })
.then(function(r){ return r.json(); })
.then(function(d){
if(d && d.success && Array.isArray(d.urls)){
d.urls.forEach(function(u){ _calcSummaryQuote.images.push(u); });
_calcSummaryPersist(['images']);
_calcSummaryRenderHeader();
} else {
alert('Uppladdning misslyckades: ' + ((d && d.error) || 'okänt fel'));
}
})
.catch(function(e){ alert('Fel vid uppladdning: ' + e); });
}
function calcSummaryRemoveImage(idx){
if(!confirm('Ta bort bilden?')) return;
_calcSummaryQuote.images.splice(idx, 1);
_calcSummaryPersist(['images']);
_calcSummaryRenderHeader();
}
function calcSummaryCreateOffer(){
var id = _calcSummaryQuote.id || (typeof currentQuoteId !== 'undefined' ? currentQuoteId : null);
if(!id){ alert('Spara kalkylen först.'); return; }
// Om offert redan finns (har quote_number) → bara öppna PDF
if(_calcSummaryQuote.quote_number){ _openQuotePdfModal(id); return; }
// Annars: skapa offert (status → offert, backend genererar quote_number) och öppna PDF efter svar
_calcSummaryQuote.status = 'offert';
fetch('/api/quotes.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({ id: id, status: 'offert' })
}).then(function(r){ return r.json(); })
.then(function(d){
if(d && d.quote_number) _calcSummaryQuote.quote_number = d.quote_number;
_calcSummaryRenderHeader();
_openQuotePdfModal(id);
})
.catch(function(e){ console.warn('[CalcSummary] create offer failed', e); _openQuotePdfModal(id); });
}
function _openQuotePdfModal(id){
// Ta bort eventuell existerande modal
var existing = document.getElementById('csPdfModal');
if(existing) existing.remove();
var url = '/api/quote_pdf.php?id=' + id + '&modal=1&t=' + Date.now();
var modal = document.createElement('div');
modal.id = 'csPdfModal';
modal.style.cssText = 'position:fixed;inset:0;z-index:10000;background:rgba(15,23,42,.75);display:flex;align-items:center;justify-content:center;padding:20px;backdrop-filter:blur(4px)';
modal.innerHTML =
'<div style="position:relative;width:min(960px,95vw);height:90vh;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.35);display:flex;flex-direction:column">'
+ '<div style="display:flex;justify-content:space-between;align-items:center;padding:12px 18px;border-bottom:1px solid #e5e7eb;background:#f8fafc">'
+ '<strong style="font-size:14px;color:#024550">Offert #' + id + '</strong>'
+ '<div style="display:flex;gap:8px">'
+ '<a href="' + url + '" target="_blank" rel="noreferrer" style="padding:6px 12px;background:#fff;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-weight:600;color:#024550;text-decoration:none">Öppna i ny flik</a>'
+ '<button onclick="_printQuoteIframe()" style="padding:6px 12px;background:#024550;color:#fff;border:none;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit">Skriv ut</button>'
+ '<button onclick="_closeQuotePdfModal()" style="padding:6px 12px;background:#fff;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit">Stäng</button>'
+ '</div>'
+ '</div>'
+ '<iframe id="csPdfIframe" src="' + url + '" style="flex:1;width:100%;border:none;background:#fff"></iframe>'
+ '</div>';
modal.onclick = function(e){ if(e.target === modal) _closeQuotePdfModal(); };
document.body.appendChild(modal);
document.body.style.overflow = 'hidden';
}
function _closeQuotePdfModal(){
var m = document.getElementById('csPdfModal');
if(m) m.remove();
document.body.style.overflow = '';
}
function _printQuoteIframe(){
var iframe = document.getElementById('csPdfIframe');
if(!iframe) return;
try {
iframe.contentWindow.focus();
iframe.contentWindow.print();
} catch(e){ alert('Kunde inte skriva ut: ' + e.message); }
}
function calcSummarySendOffer(){
var id = _calcSummaryQuote.id || (typeof currentQuoteId !== 'undefined' ? currentQuoteId : null);
if(!id){ alert('Spara kalkylen först.'); return; }
var email = (_calcSummaryQuote.customer_email || '').trim();
if(!email){ alert('Fyll i kundens email först.'); return; }
if(!confirm('Skicka offerten till ' + email + '?')) return;
fetch('/api/quote_send.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({ id: id })
}).then(function(r){ return r.json(); })
.then(function(d){
if(d && d.success){
_calcSummaryQuote.status = 'skickad';
// Backend sätter sent_date=NOW(); reflektera lokalt så headern visar direkt
var now = new Date();
var pad = function(n){ return n<10 ? '0'+n : ''+n; };
_calcSummaryQuote.sent_date = now.getFullYear()+'-'+pad(now.getMonth()+1)+'-'+pad(now.getDate())+' '+pad(now.getHours())+':'+pad(now.getMinutes());
_calcSummaryRenderHeader();
alert('Offert skickad till ' + email);
} else {
alert('Kunde inte skicka: ' + ((d && d.error) || 'okänt fel'));
}
});
}
function calcSummaryPrint(){ window.print(); }
window.calcSummaryToggleCustomer = calcSummaryToggleCustomer;
window.calcSummaryToggleImages = calcSummaryToggleImages;
window.calcSummaryPatch = calcSummaryPatch;
window.calcSummaryUploadImages = calcSummaryUploadImages;
window.calcSummaryRemoveImage = calcSummaryRemoveImage;
window.calcSummaryCreateOffer = calcSummaryCreateOffer;
window.calcSummarySendOffer = calcSummarySendOffer;
window._openQuotePdfModal = _openQuotePdfModal;
window._closeQuotePdfModal = _closeQuotePdfModal;
window._printQuoteIframe = _printQuoteIframe;
window.calcSummaryAddProduct = calcSummaryAddProduct;
window.calcSummaryPrint = calcSummaryPrint;
window.openCalcSummary = openCalcSummary;
window.closeCalcSummary = closeCalcSummary;
window.renderCalcSummary = renderCalcSummary;
window.toggleCalcSummaryRow = toggleCalcSummaryRow;
window.calcSummaryEdit = calcSummaryEdit;
window.calcSummaryRemove = calcSummaryRemove;
window.calcSummarySetOwners = calcSummarySetOwners;
window.calcSummarySetDeductType = calcSummarySetDeductType;
window.calcSummarySetSliderValue = calcSummarySetSliderValue;
window.calcSummarySetFinYears = calcSummarySetFinYears;