// new_calc.js — Ny Kalkyl startsida (ersätter kalkyl-ny.js)
// Läser från /api/calc_builder.php och visar endast konfiguratorer som har grid_image + grid_order.
var NEW_CALC_VERSION = '1.0.8';
var NEW_CALC_BUILD = '2026-04-24';
try { window.NEW_CALC_VERSION = NEW_CALC_VERSION; window.NEW_CALC_BUILD = NEW_CALC_BUILD; } catch(e){}
try { console.log('%c[new_calc.js] v' + NEW_CALC_VERSION + ' · ' + NEW_CALC_BUILD, 'color:#024550;font-weight:700'); } catch(e){}
function _newCalcRenderVersionBadge(){
try {
// 1. I kategoriväljarens file-strip
var strip = document.querySelector('#newCalcShell .new-calc-file-strip');
if(strip && !strip.querySelector('.new-calc-version-badge')){
var b1 = document.createElement('span');
b1.className = 'new-calc-version-badge';
b1.title = 'new_calc.js byggd ' + NEW_CALC_BUILD;
b1.style.cssText = 'display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border:1px solid #bfe4b8;border-radius:999px;background:#f2fbee;color:#2a6b1f;font-size:11px;font-weight:700;font-family:inherit;margin-left:auto';
b1.textContent = 'v' + NEW_CALC_VERSION;
strip.appendChild(b1);
}
// 2. Bredvid cfgFileLabel (konfiguratorns header — syns i alla modes)
var lbl = document.getElementById('cfgFileLabel');
if(lbl && lbl.parentNode && !document.getElementById('newCalcVersionPill')){
var b2 = document.createElement('span');
b2.id = 'newCalcVersionPill';
b2.title = 'new_calc.js byggd ' + NEW_CALC_BUILD;
b2.style.cssText = 'margin-left:6px;font-size:10px;color:#2a6b1f;background:#f2fbee;border:1px solid #bfe4b8;padding:3px 8px;border-radius:4px;font-family:monospace;font-weight:700';
b2.textContent = 'v' + NEW_CALC_VERSION;
lbl.parentNode.insertBefore(b2, lbl.nextSibling);
}
} catch(e){}
}
if(document.readyState === 'loading'){ document.addEventListener('DOMContentLoaded', _newCalcRenderVersionBadge); }
else { _newCalcRenderVersionBadge(); }
var _newCalcActive = false;
function newCalc(kundData){
_newCalcActive = true;
// NY KALKYL = nytt sessionID. Alla sub-sessioner + varukorg binds till det.
// Gamla sessioners data ligger kvar men är OSYNLIGA (fel namespace).
if(typeof prisStartNewCalc === 'function'){ prisStartNewCalc(); }
else if(typeof _kalkylItems !== 'undefined') { _kalkylItems = {}; }
if(typeof _cartItems !== 'undefined' && Array.isArray(_cartItems)) _cartItems.length = 0;
try { sessionStorage.removeItem('solarCart'); } catch(e){}
if(typeof currentQuoteId !== 'undefined') currentQuoteId = null;
if(typeof _cfgDirty !== 'undefined') _cfgDirty = false;
if(typeof _vpPumps !== 'undefined') _vpPumps = [];
if(typeof _fkWindows !== 'undefined') _fkWindows = [];
if(typeof _lbTillval !== 'undefined') _lbTillval = [];
if(typeof _lbSelected !== 'undefined'){ _lbSelected = null; _lbPrice = 0; }
if(typeof _vpSelected !== 'undefined'){ _vpSelected = null; _vpPrice = 0; }
if(typeof _batInitialized !== 'undefined') _batInitialized = false;
var titleEl = document.getElementById('kalkylTitle');
if(titleEl) titleEl.textContent = 'Ny Kalkyl';
var fileLabel = document.getElementById('cfgFileLabel');
if(fileLabel) fileLabel.textContent = 'new_calc.js';
// Navigera till konfigurator-sidan
if(typeof currentPage !== 'undefined') currentPage = 'konfigurator';
document.querySelectorAll('.page-content').forEach(function(p){ p.classList.remove('active'); });
var pageEl = document.getElementById('page-konfigurator');
if(pageEl) pageEl.classList.add('active');
document.querySelectorAll('.nav-item').forEach(function(n){
if(n.dataset && n.dataset.page === 'konfigurator') n.classList.add('active');
else n.classList.remove('active');
});
var listView = document.getElementById('kalkylListView');
if(listView) listView.style.display = 'none';
var configView = document.getElementById('kalkylConfigView');
if(configView) configView.style.display = 'block';
_resetCalcView();
if(typeof pendingKalkylCustomer !== 'undefined') pendingKalkylCustomer = kundData || null;
if(typeof populateKalkylFromCustomer === 'function') populateKalkylFromCustomer();
var photosSection = document.getElementById('kalkylPhotosSection');
if(photosSection) photosSection.style.display = '';
_showNewCalcGrid();
_loadNewCalcCards();
setTimeout(_resetCalcView, 100);
setTimeout(_resetCalcView, 500);
}
function _resetCalcView(){
var cs = document.getElementById('categorySelect');
if(cs) cs.value = '';
['solarConfigView','genericConfigView','fonsterConfigView','batteriConfigView',
'laddboxConfigView','taktvatConfigView','varmepumpConfigView','takConfigView',
'isoleringConfigView','summaryProductList'].forEach(function(v){
var el = document.getElementById(v);
if(el) el.style.display = 'none';
});
var afv = document.getElementById('affarView');
if(afv) afv.style.display = 'none';
var pqv = document.getElementById('productQuoteView');
if(pqv) pqv.remove();
// Gamla griden ska ALDRIG visas längre — ersätts av new-calc-grid
var oldGrid = document.getElementById('cfgCategoryGrid');
if(oldGrid) oldGrid.style.display = 'none';
if(typeof setKalkylViewMode === 'function') setKalkylViewMode(_newCalcActive ? 'new' : 'config');
if(_newCalcActive) _showNewCalcGrid();
}
function _showNewCalcGrid(){
var shell = document.getElementById('newCalcShell');
if(!shell) return;
var configView = document.getElementById('kalkylConfigView');
if(configView && shell.parentElement !== configView){
configView.appendChild(shell);
}
// Städa bort förra render-vyn
var renderArea = document.getElementById('newCalcRenderArea');
if(renderArea) renderArea.remove();
var banner = document.getElementById('newCalcLegacyBanner');
if(banner) banner.remove();
// Dölj kalkyl-summary om det är aktivt (NewCalc har sin egen vy)
var sumView = document.getElementById('summaryProductList');
if(sumView) sumView.style.display = 'none';
// Existerande cfgCategoryBar ska synas och peka på CalcBuilder-konfigar
_ensureSwitchBar('');
shell.style.display = 'block';
}
function _hideNewCalcGrid(){
var shell = document.getElementById('newCalcShell');
if(shell) shell.style.display = 'none';
}
function _loadNewCalcCards(){
var grid = document.getElementById('newCalcGrid');
var empty = document.getElementById('newCalcEmpty');
if(!grid) return;
grid.innerHTML = '';
fetch('/api/calc_builder.php')
.then(function(r){ return r.json(); })
.then(function(data){
var items = (data && data.configurators) || [];
// Filtrera: endast de som har grid_image (gridbild uppladdad) OCH grid_order är satt
var cards = items.filter(function(c){
return c.grid_image && c.grid_image !== '' && c.grid_order != null && c.grid_order !== '';
}).sort(function(a, b){
return (parseInt(a.grid_order) || 0) - (parseInt(b.grid_order) || 0);
});
if(!cards.length){
grid.style.display = 'none';
if(empty) empty.style.display = 'block';
return;
}
if(empty) empty.style.display = 'none';
grid.style.display = 'grid';
grid.innerHTML = cards.map(_renderNewCalcCard).join('');
})
.catch(function(err){
console.error('new_calc: load error', err);
grid.style.display = 'none';
if(empty){
empty.style.display = 'block';
var body = empty.querySelector('.new-calc-empty-body');
if(body) body.textContent = 'Kunde inte läsa konfiguratorer från servern.';
}
});
}
function _renderNewCalcCard(cfg){
var cat = (cfg.category || '').replace(/"/g, '"');
var title = (cfg.title || cfg.category_label || cfg.category || '').replace(/</g, '<');
var meta = (cfg.builder_mode === 'advanced' ? 'Avancerad' : (cfg.builder_mode === 'code' ? 'Code' : 'Enkel'));
var img = cfg.grid_image ? '<img class="new-calc-card-image" src="' + cfg.grid_image.replace(/"/g, '"') + '" alt="">'
: '<div class="new-calc-card-image"></div>';
return '<div class="new-calc-card" onclick="openNewCalcCategory(\'' + cat + '\')">'
+ img
+ '<div class="new-calc-card-body">'
+ '<div class="new-calc-card-title">' + title + '</div>'
+ '<div class="new-calc-card-meta">' + meta + '</div>'
+ '</div>'
+ '</div>';
}
// Sub-sessioner ägs av prissammanstallning.js — vi bara använder API:t.
// prisGetSubSession(cat) / prisSaveSubSession(cat, data) / prisCommitSubSession(cat, entry) / prisClearSubSession(cat)
function openNewCalcCategory(categoryId, opts){
// Byter vi från en pågående kalkyl till annan kategori? Varna då.
if(_hasUnsavedCalc() && _newCalcContext.categoryId !== categoryId){
if(!_confirmLeaveCalc()){
// Återställ kategori-dropdownen till nuvarande värde om användaren backar
var sel = document.getElementById('categorySelect');
if(sel) sel.value = _newCalcContext.categoryId || '';
return;
}
// Rensa påbörjad kalkyl innan vi byter
_newCalcContext.selectedProductId = null;
_newCalcContext.tillval = [];
_persistSub();
}
_newCalcActive = false;
_hideNewCalcGrid();
// Garantera att kalkylConfigView är synlig (kan ha dolts av Calc_summary close-flöde)
var _cfgView = document.getElementById('kalkylConfigView');
var _listView = document.getElementById('kalkylListView');
if(_cfgView) _cfgView.style.display = 'block';
if(_listView) _listView.style.display = 'none';
// Ladda/skapa sub-session via prissammanstallning — isolerad per kategori
_newCalcContext = (typeof prisGetSubSession === 'function')
? prisGetSubSession(categoryId)
: { categoryId: categoryId, selectedProductId: null, tillval: [], productQty: 1, selectedVariantIdx: 0, editEntryId: null };
_newCalcContext.categoryId = categoryId;
// Returnera Promise så edit-flödet kan vänta tills rendering är klar INNAN openForEdit.
return fetch('/api/calc_builder.php?category=' + encodeURIComponent(categoryId))
.then(function(r){ return r.json(); })
.then(function(data){
var cfg = data && data.success ? data.configurator : null;
if(cfg && cfg.builder_mode === 'code' && cfg.code){
_renderCalcBuilderCode(cfg);
return;
}
if(cfg && cfg.builder_mode === 'advanced' && (cfg.blocks || []).length){
_renderCalcBuilderAdvanced(cfg);
return;
}
// Simple-mode (eller okänt): rendera produktkatalog för kategorin
_renderCalcBuilderSimple(categoryId, cfg);
})
.catch(function(err){
console.warn('openNewCalcCategory: calc_builder fetch failed', err);
_fallbackToLegacy(categoryId, null);
});
}
function _hideAllConfigViews(){
// OBS: cfgCategoryBar ska INTE döljas — den är vår kalkyl-switch (återanvänds)
['solarConfigView','genericConfigView','fonsterConfigView','batteriConfigView',
'laddboxConfigView','taktvatConfigView','varmepumpConfigView','takConfigView',
'isoleringConfigView','cfgCategoryGrid','summaryProductList'].forEach(function(id){
var el = document.getElementById(id);
if(el) el.style.display = 'none';
});
var afv = document.getElementById('affarView');
if(afv) afv.style.display = 'none';
var existing = document.getElementById('newCalcRenderArea');
if(existing) existing.remove();
}
function _ensureRenderArea(){
var area = document.getElementById('newCalcRenderArea');
if(area) return area;
area = document.createElement('div');
area.id = 'newCalcRenderArea';
area.style.cssText = 'background:#fff;border:1px solid #e5e7eb;border-radius:14px;padding:24px;margin-bottom:16px';
var configView = document.getElementById('kalkylConfigView');
if(configView) configView.appendChild(area);
return area;
}
function _ensureSwitchBar(currentCategoryId){
// Återanvänd existerande #cfgCategoryBar / #categorySelect (från konfigurator.php)
var bar = document.getElementById('cfgCategoryBar');
var sel = document.getElementById('categorySelect');
if(bar) bar.style.display = '';
if(!sel) return;
// Routa om till NewCalc istället för gamla changeCategory
sel.onchange = function(){ if(this.value) openNewCalcCategory(this.value); };
fetch('/api/calc_builder.php')
.then(function(r){ return r.json(); })
.then(function(data){
var items = (data && data.configurators) || [];
sel.innerHTML = '<option value="">— välj kalkyl —</option>'
+ items.map(function(c){
var lbl = (c.title || c.category_label || c.category) + ' (' + c.category + ')';
var selAttr = c.category === currentCategoryId ? ' selected' : '';
return '<option value="' + _esc(c.category) + '"' + selAttr + '>' + _esc(lbl) + '</option>';
}).join('');
}).catch(function(){});
}
function _setCalcFileLabel(text){
var lbl = document.getElementById('cfgFileLabel');
if(lbl) lbl.textContent = text;
_newCalcRenderVersionBadge();
}
function _calcBuilderHeader(cfg, source){
return '';
}
// Receptet: läs Layout-config från cfg och bygg ALLTID samma struktur (head + content + sidebar).
// Gäller alla CalcBuilder-modes (simple/advanced/code) — sidebaren placeras enligt cfg.sidebar_position.
function _calcLayoutSpec(cfg){
var pos = String((cfg && cfg.sidebar_position) || 'right').toLowerCase();
if(pos === 'vänster' || pos === 'vanster') pos = 'left';
if(pos === 'höger' || pos === 'hoger') pos = 'right';
if(pos === 'ingen') pos = 'none';
var mod = (cfg && cfg.sidebar_module) || 'price-summary';
var hasSidebar = pos !== 'none' && mod && mod !== 'none';
return { sidebarPos: pos, sidebarMod: mod, hasSidebar: hasSidebar };
}
function _calcBuildLayout(cfg, contentHtml, modeName){
var spec = _calcLayoutSpec(cfg);
var sw = (cfg && cfg.sidebar_width) ? String(cfg.sidebar_width).trim() : '320px';
if(!/(px|%|rem|em|vw)$/.test(sw)) sw = sw + 'px';
var gridCols = spec.hasSidebar
? (spec.sidebarPos === 'left' ? sw + ' minmax(0,1fr)' : 'minmax(0,1fr) ' + sw)
: 'minmax(0,1fr)';
// Smartfilter-host placeras AV simple/snippet — INTE här (för att ge varje mode kontroll över position).
var contentCol = '<div id="newCalcContent" style="min-width:0">' + contentHtml + '</div>';
var sidebarCol = spec.hasSidebar
? '<aside id="newCalcSidebarCol" style="min-width:0"><div id="newCalcPrisSidebar"></div></aside>'
: '';
return _calcBuilderHeader(cfg, modeName)
+ '<div class="nc-layout-grid" style="display:grid;grid-template-columns:' + gridCols + ';gap:12px;align-items:start">'
+ (spec.sidebarPos === 'left' ? sidebarCol + contentCol : contentCol + sidebarCol)
+ '</div>';
}
// Renderar smartfilter-baren efter layout är i DOM. Anropas från alla mode-renderfunktioner.
function _smartFilterPostRender(cfg){
if (!cfg || !(cfg.smart_filter == 1 || cfg.smart_filter === true || cfg.smart_filter === '1')) return;
if (typeof _renderSmartFilterBar === 'function') {
try { _renderSmartFilterBar(cfg.category, []); } catch(e){ console.warn('smartFilter render', e); }
}
}
function _renderCalcBuilderCode(cfg){
_hideAllConfigViews();
_setCalcFileLabel('CalcBuilder/code · ' + (cfg.title || cfg.category));
_newCalcContext = (typeof prisGetSubSession === 'function')
? prisGetSubSession(cfg.category) : _newCalcContext;
_newCalcContext.categoryId = cfg.category;
_newCalcContext.cfg = cfg;
var html = String(cfg.code || '').replace(/\{\{\s*(\w+)\s*\}\}/g, function(m, k){
var vars = { category: cfg.category || '', title: cfg.title || '', version: cfg.version || '1.0.0' };
return vars[k] != null ? vars[k] : m;
});
var area = _ensureRenderArea();
var content = '<div class="calc-builder-code-content">' + html + '</div>';
area.innerHTML = _calcBuildLayout(cfg, content, 'code-mode');
area.style.display = 'block';
var spec = _calcLayoutSpec(cfg);
if(spec.hasSidebar) _renderNewCalcSidebar();
_smartFilterPostRender(cfg);
// Execute <script>-taggar i cfg.code (innerHTML kör inte scripts av sig själv).
// Tillåter snippets som WindowSnippet.mount(...) att starta sig själva.
var contentHost = area.querySelector('.calc-builder-code-content');
if(contentHost){
var scripts = contentHost.querySelectorAll('script');
scripts.forEach(function(oldScript){
var s = document.createElement('script');
if(oldScript.src){ s.src = oldScript.src; }
else { s.textContent = oldScript.textContent; }
oldScript.parentNode.replaceChild(s, oldScript);
});
}
}
function _renderCalcBuilderAdvanced(cfg){
_hideAllConfigViews();
_setCalcFileLabel('CalcBuilder/advanced · ' + (cfg.title || cfg.category));
_newCalcContext = (typeof prisGetSubSession === 'function')
? prisGetSubSession(cfg.category) : _newCalcContext;
_newCalcContext.categoryId = cfg.category;
_newCalcContext.cfg = cfg;
var area = _ensureRenderArea();
var blocksHtml = '';
if(window.CalcBlocks && typeof window.CalcBlocks.render === 'function'){
blocksHtml = window.CalcBlocks.render(cfg.blocks || [], {});
} else {
blocksHtml = '<div style="padding:12px;background:#fef3c7;border-radius:8px;color:#92400e;font-size:13px">CalcBlocks renderer saknas — kan inte rendera advanced-blocken.</div>';
}
area.innerHTML = _calcBuildLayout(cfg, blocksHtml, 'advanced-mode');
area.style.display = 'block';
var spec = _calcLayoutSpec(cfg);
if(spec.hasSidebar) _renderNewCalcSidebar();
_smartFilterPostRender(cfg);
}
var _newCalcContext = { categoryId: '', cfg: null, selectedProductId: null, tillval: [], productQty: 1, selectedVariantIdx: 0 };
function _renderCalcBuilderSimple(categoryId, cfg){
_hideAllConfigViews();
_setCalcFileLabel('CalcBuilder/simple · ' + categoryId);
_ensureSwitchBar(categoryId);
// Använd sub-session (namespaced per kategori) — uppdatera cfg men behåll state
_newCalcContext = (typeof prisGetSubSession === 'function')
? prisGetSubSession(categoryId)
: _newCalcContext;
_newCalcContext.categoryId = categoryId;
_newCalcContext.cfg = cfg;
var area = _ensureRenderArea();
var title = (cfg && (cfg.title || cfg.category_label)) || categoryId;
var spec = _calcLayoutSpec(cfg);
var _sid = (typeof prisGetCurrentSessionId === 'function') ? prisGetCurrentSessionId() : '(saknas)';
var _subKey = _sid + '_' + categoryId;
var _sessInfo = '<div class="calc-session-pills" style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px;font-family:monospace;font-size:10px">'
+ '<span style="padding:3px 8px;border:1px solid #bfdbfe;background:#eff6ff;color:#1e40af;border-radius:6px">SESSION: ' + _esc(_sid) + '</span>'
+ '<span style="padding:3px 8px;border:1px solid #bbf7d0;background:#f0fdf4;color:#166534;border-radius:6px">SUB: ' + _esc(_subKey) + '</span>'
+ '</div>';
var smartFilterEnabled = !!(cfg && (cfg.smart_filter == 1 || cfg.smart_filter === true || cfg.smart_filter === '1'));
var smartFilterHostHtml = smartFilterEnabled
? '<div id="newCalcSmartFilter" data-cat="' + _esc(categoryId) + '" style="margin:0 0 14px"></div>'
: '';
// Pris-toggle i rubriken (default dold). window._simpleShowPrices[cat] = true/false.
if(!window._simpleShowPrices) window._simpleShowPrices = {};
var _showP = !!window._simpleShowPrices[categoryId];
var _pricesAllowed = (typeof getRoleSetting !== 'function') || getRoleSetting('kalkyler.show_prices') === '1';
var _eyeOn = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" 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 _eyeOff = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" 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>';
var _priceToggleBtn = _pricesAllowed
? '<button type="button" onclick="toggleSimplePrices(\'' + _esc(categoryId) + '\')" title="' + (_showP ? 'Dölj priser' : 'Visa priser') + '" style="background:none;border:1px solid #e5e7eb;border-radius:6px;padding:4px 8px;cursor:pointer;color:' + (_showP ? '#024550' : '#64748b') + ';font-size:12px;vertical-align:middle;margin-left:8px">' + (_showP ? _eyeOn : _eyeOff) + '</button>'
: '';
if(!_pricesAllowed) _showP = false;
var simpleContent = _sessInfo
+ '<div style="display:flex;align-items:center;margin:0 0 4px"><h2 style="font-size:18px;font-weight:700;color:#1a1a1a;margin:0;flex:1">' + _esc(title) + '</h2>' + _priceToggleBtn + '</div>'
+ '<p style="font-size:13px;color:#64748b;margin:0 0 18px">Välj en produkt för att lägga till i kalkylen.</p>'
+ smartFilterHostHtml
+ '<div id="newCalcSimpleGrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px"></div>'
+ '<div id="newCalcProductDetail" style="display:none"></div>';
area.innerHTML = _calcBuildLayout(cfg || { builder_mode:'simple', version:'1.0.0', sidebar_module:'price-summary', sidebar_position:'right', category: categoryId, smart_filter: smartFilterEnabled ? 1 : 0 }, simpleContent, 'simple-mode');
area.style.display = 'block';
if(spec.hasSidebar) _renderNewCalcSidebar();
function paint(){
var grid = document.getElementById('newCalcSimpleGrid');
if(!grid) return;
var src = (typeof catalogProducts !== 'undefined' && Array.isArray(catalogProducts)) ? catalogProducts : [];
var products = src
.filter(function(p){ return p && p.cat === categoryId; })
.sort(function(a,b){ return (a.sort_order||0)-(b.sort_order||0) || String(a.name||'').localeCompare(String(b.name||''),'sv'); });
// Smartfilter: applicera aktivt filter (per säljare)
if(smartFilterEnabled){
_renderSmartFilterBar(categoryId, products);
var f = _smartFilterActive(categoryId);
if(f){
if(Array.isArray(f.products) && f.products.length){
products = products.filter(function(p){ return f.products.indexOf(p.id) !== -1; });
}
if(Array.isArray(f.models) && f.models.length){
products = products.filter(function(p){
var pm = _smartFilterModelLabels(p);
return pm.some(function(m){ return f.models.indexOf(m) !== -1; });
});
}
}
}
if(!products.length){
grid.innerHTML = '<div style="grid-column:1/-1;padding:18px;background:#fef3c7;border:1px solid #f59e0b;border-radius:10px;color:#92400e;font-size:13px">Inga produkter i kategorin <strong>' + _esc(categoryId) + '</strong>. Lägg till produkter via Produktkatalog.</div>';
return;
}
var showPrices = !!(window._simpleShowPrices && window._simpleShowPrices[categoryId]);
grid.innerHTML = products.map(function(p){
var img = p.img ? '<img src="' + _esc(p.img) + '" style="width:100%;height:130px;object-fit:cover;border-radius:8px;background:#f8fafc">'
: '<div style="width:100%;height:130px;border-radius:8px;background:#f8fafc;display:flex;align-items:center;justify-content:center;color:#cbd5e1;font-size:11px">Ingen bild</div>';
var price = (p.price != null) ? Number(p.price).toLocaleString('sv-SE') + ' kr' : '';
return '<div onclick="newCalcOpenProduct(\'' + _esc(p.id) + '\')" style="cursor:pointer;background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:10px;transition:all .15s" onmouseover="this.style.borderColor=\'#024550\';this.style.transform=\'translateY(-2px)\'" onmouseout="this.style.borderColor=\'#e5e7eb\';this.style.transform=\'translateY(0)\'">'
+ img
+ '<div style="margin-top:8px;font-size:13px;font-weight:700;color:#1a1a1a">' + _esc(p.name||'') + '</div>'
+ (showPrices && price ? '<div style="margin-top:4px;font-size:13px;font-weight:700;color:#024550">' + price + '</div>' : '')
+ '</div>';
}).join('');
}
window.toggleSimplePrices = function(cat){
if(!window._simpleShowPrices) window._simpleShowPrices = {};
window._simpleShowPrices[cat] = !window._simpleShowPrices[cat];
// Re-render kategorin
if(typeof openNewCalcCategory === 'function') openNewCalcCategory(cat);
};
var hasCatalog = typeof catalogProducts !== 'undefined' && Array.isArray(catalogProducts) && catalogProducts.length;
if(!hasCatalog && typeof loadCatalogFromDB === 'function'){
Promise.resolve(loadCatalogFromDB()).then(paint).catch(paint);
} else {
paint();
}
// Exponera repaint-funktion så smartfilter kan applicera utan att reboota hela konfiguratorn.
window._newCalcRepaintSimple = function(cat){ if(cat === categoryId) paint(); };
}
function _esc(v){
return String(v == null ? '' : v).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
}
// === Inline produktvy + tillval ===
function newCalcOpenProduct(productId, entryId){
var p = (typeof catalogProducts !== 'undefined' && catalogProducts.find) ? catalogProducts.find(function(x){return x.id===productId;}) : null;
if(!p) return;
var editEntry = null;
if(entryId && typeof _kalkylItems !== 'undefined'){
var item = _kalkylItems[_newCalcContext.categoryId];
var entries = item ? (Array.isArray(item.entries) ? item.entries : [item]) : [];
editEntry = entries.find(function(e){ return e.entry_id === entryId; }) || null;
}
_newCalcContext.selectedProductId = productId;
_newCalcContext.productQty = (editEntry && editEntry.qty) ? parseFloat(editEntry.qty) : 1;
_newCalcContext.selectedVariantIdx = (editEntry && editEntry.variant_idx != null) ? parseInt(editEntry.variant_idx) : 0;
_newCalcContext.editEntryId = entryId || null;
_loadInlineTillval(productId).then(function(){
if(editEntry && Array.isArray(editEntry.tillval)){
var savedById = {};
editEntry.tillval.forEach(function(tv){ if(tv && tv.id != null) savedById[tv.id] = tv; });
_newCalcContext.tillval.forEach(function(tv){
var saved = savedById[tv.id];
if(saved){ tv.checked = true; if(saved.qty != null) tv.userQty = parseFloat(saved.qty) || tv.userQty; }
else if(!tv.included){ tv.checked = false; }
});
}
_persistSub();
_renderInlineProduct(p);
_renderNewCalcSidebar();
});
}
function _hasUnsavedCalc(){
return !!(_newCalcContext && _newCalcContext.selectedProductId);
}
function _confirmLeaveCalc(){
if(!_hasUnsavedCalc()) return true;
var productName = '';
if(typeof catalogProducts !== 'undefined' && catalogProducts.find){
var p = catalogProducts.find(function(x){ return x.id === _newCalcContext.selectedProductId; });
if(p) productName = p.name || '';
}
var subject = productName || 'denna produkt';
return confirm('Du har en påbörjad kalkyl på ' + subject + '.\n\nOm du går härifrån utan att klicka "Lägg till i kalkyl" förlorar du dina val.\n\nVill du fortsätta ändå?');
}
function newCalcBackToGrid(force){
if(!force && !_confirmLeaveCalc()) return;
_newCalcContext.selectedProductId = null;
_newCalcContext.tillval = [];
_persistSub();
var grid = document.getElementById('newCalcSimpleGrid');
var detail = document.getElementById('newCalcProductDetail');
if(grid) grid.style.display = 'grid';
if(detail){ detail.style.display = 'none'; detail.innerHTML = ''; }
_renderNewCalcSidebar();
}
function _loadInlineTillval(productId){
return Promise.all([
fetch('/api/products.php?cat=tjanster&active=1').then(function(r){return r.json();}),
fetch('/api/products.php?cat=tillbehor&active=1').then(function(r){return r.json();}),
fetch('/api/products.php?services_for=' + encodeURIComponent(productId)).then(function(r){return r.json();})
]).then(function(results){
var svcData = results[0], accData = results[1], linkedData = results[2];
var linkedMap = {};
if(linkedData && linkedData.success){
(linkedData.services || []).forEach(function(s){
linkedMap[s.service_id] = {
default_on: parseInt(s.default_on || 0),
hidden: parseInt(s.hidden || 0),
mandatory: parseInt(s.mandatory || 0),
default_qty: s.default_qty != null ? parseFloat(s.default_qty) : null,
min_qty: s.min_qty != null ? parseFloat(s.min_qty) : null,
max_qty: s.max_qty != null ? parseFloat(s.max_qty) : null
};
});
}
var svcProducts = (svcData && svcData.success ? (svcData.products || []) : []).map(function(p){ p._is_service = true; return p; });
var accProducts = (accData && accData.success ? (accData.products || []) : []).map(function(p){ p._is_service = false; return p; });
var allProducts = [].concat(svcProducts, accProducts);
_newCalcContext.tillval = allProducts.filter(function(s){ return linkedMap.hasOwnProperty(s.id); }).map(function(s){
var lnk = linkedMap[s.id] || {};
var seedQty = lnk.default_qty != null ? lnk.default_qty : 0;
return {
id: s.id, name: s.name, price: parseFloat(s.price) || 0, unit: s.unit || 'st',
checked: !!(lnk.default_on || lnk.mandatory),
included: !!lnk.mandatory,
hidden: !!lnk.hidden,
is_service: !!s._is_service,
userQty: seedQty,
linkMin: lnk.min_qty, linkMax: lnk.max_qty, linkDefaultQty: lnk.default_qty,
rotEligible: parseInt(s.rot_eligible) === 1,
greenTechEligible: parseInt(s.green_tech_eligible) === 1,
taxType: s.tax_type || 'NONE'
};
});
}).catch(function(err){ console.warn('newCalc tillval load failed', err); _newCalcContext.tillval = []; });
}
function _getInlineEffectiveTillvalQty(tv){
var stHasUserQty = (tv.unit === 'st' || !tv.unit) && (tv.linkMax > 1 || tv.linkDefaultQty != null) && tv.userQty > 0;
if(tv.unit && tv.unit !== 'st' && tv.userQty > 0) return tv.userQty;
if(stHasUserQty) return tv.userQty;
return 1;
}
function _getVariantInfo(p){
var variants = (p.specs && p.specs.variants) || [];
if(!variants.length) return { hasVariants: false, variants: [], labelKey: '', labelSuffix: '' };
var first = variants[0];
var labelKey = '', labelSuffix = '';
if(first.kwh !== undefined) { labelKey = 'kwh'; labelSuffix = ' kWh'; }
else if(first.paneler !== undefined) { labelKey = 'paneler'; labelSuffix = ' paneler'; }
else if(first.cm !== undefined) { labelKey = 'cm'; labelSuffix = ' cm'; }
else if(first.kvm !== undefined) { labelKey = 'kvm'; labelSuffix = ' m²'; }
else if(first.material !== undefined) { labelKey = 'material'; labelSuffix = ''; }
else { var keys = Object.keys(first).filter(function(k){return ['totalt','price','att_betala','gron_teknik','pris_per_m2','sek_pris','article_id','width_mm','length_mm'].indexOf(k) === -1;}); labelKey = keys[0] || ''; labelSuffix = ''; }
return { hasVariants: true, variants: variants, labelKey: labelKey, labelSuffix: labelSuffix };
}
function _getVariantPrice(p, variant){
if(!variant) return parseFloat(p.price) || 0;
if(variant.totalt !== undefined) return parseFloat(variant.totalt) || 0;
if(variant.att_betala !== undefined && variant.gron_teknik !== undefined) return (parseFloat(variant.att_betala) || 0) + (parseFloat(variant.gron_teknik) || 0);
if(variant.price !== undefined) return parseFloat(variant.price) || 0;
if(variant.pris_per_m2 !== undefined) return parseFloat(variant.pris_per_m2) || 0;
return parseFloat(p.price) || 0;
}
function _getVariantDeduction(p, variant){
if(variant && variant.gron_teknik !== undefined) return parseFloat(variant.gron_teknik) || 0;
return 0;
}
// Härleder tax_type från produkt: explicit p.taxType → rot_eligible → green_tech_eligible.
function _deriveTaxType(p){
if(!p) return 'NONE';
if(p.taxType && p.taxType !== 'NONE') return p.taxType;
if(p.rotEligible) return 'ROT';
if(p.greenTechEligible) return 'GT20';
return 'NONE';
}
function _taxRate(taxType){
var tax = (typeof taxSettings !== 'undefined') ? taxSettings : { rot:30, gt20:20, gt50:50 };
if(taxType === 'ROT') return (tax.rot || 30)/100;
if(taxType === 'GT20') return (tax.gt20 || 20)/100;
if(taxType === 'GT50') return (tax.gt50 || 50)/100;
return 0;
}
function _calcInlineTotal(p){
var vinfo = _getVariantInfo(p);
var selectedVariant = vinfo.hasVariants ? vinfo.variants[_newCalcContext.selectedVariantIdx] || vinfo.variants[0] : null;
var basePrice = vinfo.hasVariants ? _getVariantPrice(p, selectedVariant) : (parseFloat(p.price) || 0);
var variantDed = vinfo.hasVariants ? _getVariantDeduction(p, selectedVariant) : 0;
var qty = parseInt(_newCalcContext.productQty) || 1;
var subtotal = basePrice * qty;
var tillvalTotal = 0;
var rotBase = 0; // underlag för ROT-avdrag
var gtBase = 0; // underlag för Grönt teknik-avdrag
var primaryTax = _deriveTaxType(p);
if(primaryTax === 'ROT') rotBase += subtotal;
else if(primaryTax === 'GT20' || primaryTax === 'GT50') gtBase += subtotal;
_newCalcContext.tillval.forEach(function(tv){
// Dolda tillval räknas om de är checked (oftast via default_on/mandatory).
if(!tv.checked) return;
var eq = _getInlineEffectiveTillvalQty(tv);
var tvSum = (tv.price || 0) * eq;
tillvalTotal += tvSum;
// Härled per tillval — rot_eligible/green_tech_eligible kommer från produktens fält
if(tv.rotEligible || tv.rot_eligible || tv.taxType === 'ROT') rotBase += tvSum;
else if(tv.greenTechEligible || tv.green_tech_eligible || tv.taxType === 'GT20' || tv.taxType === 'GT50') gtBase += tvSum;
});
// Räkna potentiellt avdrag.
var rotDed = Math.round(rotBase * _taxRate('ROT'));
var gtDed = Math.round(gtBase * (primaryTax === 'GT50' ? _taxRate('GT50') : _taxRate('GT20')));
// Variant-deduktion (gron_teknik på fönster-variant) — override om satt.
if(variantDed > 0 && primaryTax === 'NONE'){ primaryTax = 'GT20'; }
var deductionTotal = variantDed > 0 ? (variantDed * qty) : Math.max(rotDed, gtDed);
// Om huvudprodukten är NONE men tillval ger avdrag → uppgradera taxType.
if(primaryTax === 'NONE'){
if(rotBase > 0) primaryTax = 'ROT';
else if(gtBase > 0) primaryTax = 'GT20';
}
var gross = subtotal + tillvalTotal;
return { basePrice: basePrice, qty: qty, subtotal: subtotal, tillvalTotal: tillvalTotal, deduction: deductionTotal, total: gross, gross: gross, taxType: primaryTax, variant: selectedVariant };
}
function _renderInlineProduct(p){
var grid = document.getElementById('newCalcSimpleGrid');
var detail = document.getElementById('newCalcProductDetail');
if(!detail) return;
if(grid) grid.style.display = 'none';
detail.style.display = 'block';
var img = p.img ? '<img src="' + _esc(p.img) + '" style="width:100%;border-radius:10px;object-fit:cover;max-height:320px;background:#f8fafc">'
: '<div style="width:100%;height:240px;border-radius:10px;background:#f8fafc;display:flex;align-items:center;justify-content:center;color:#cbd5e1">Ingen bild</div>';
var unit = p.unit || 'st';
var totals = _calcInlineTotal(p);
var vinfo = _getVariantInfo(p);
var taxBadge = '';
if(p.taxType === 'GT20' || p.taxType === 'GT50' || p.greenTechEligible) {
taxBadge = '<div style="display:inline-flex;align-items:center;gap:6px;background:#10b981;color:#fff;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:600;margin-bottom:10px">✓ Grönt teknik-avdrag</div>';
} else if(p.taxType === 'ROT' || p.rotEligible) {
taxBadge = '<div style="display:inline-flex;align-items:center;gap:6px;background:#3b82f6;color:#fff;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:600;margin-bottom:10px">✓ ROT-avdrag</div>';
}
var variantHtml = '';
if(vinfo.hasVariants && vinfo.variants.length > 1){
variantHtml = '<div style="margin:14px 0">'
+ '<div style="font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px">Välj variant</div>'
+ '<div style="display:flex;flex-wrap:wrap;gap:8px">'
+ vinfo.variants.map(function(v, vi){
var isSel = vi === _newCalcContext.selectedVariantIdx;
var lbl = (v[vinfo.labelKey] != null ? v[vinfo.labelKey] : '') + vinfo.labelSuffix;
return '<button onclick="newCalcSetVariant(' + vi + ')" style="padding:10px 18px;border-radius:8px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;border:1.5px solid ' + (isSel ? '#024550' : '#e5e7eb') + ';background:' + (isSel ? '#024550' : '#fff') + ';color:' + (isSel ? '#fff' : '#334155') + '">' + _esc(lbl) + '</button>';
}).join('')
+ '</div></div>';
}
var isInfoUnit = (unit === 'info');
var qtyHtml = '';
if(!isInfoUnit && (!vinfo.hasVariants || (vinfo.labelKey !== 'kwh' && vinfo.labelKey !== 'paneler' && vinfo.labelKey !== 'kvm'))){
qtyHtml = '<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px">'
+ '<label style="font-size:12px;font-weight:600;color:#64748b">Antal ' + _esc(unit) + '</label>'
+ '<input id="newCalcProductQty" type="number" min="1" step="1" value="' + totals.qty + '" oninput="newCalcSetProductQty(this.value)" style="width:90px;padding:8px 10px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit;text-align:center">'
+ '</div>';
}
detail.innerHTML = '<button onclick="newCalcBackToGrid()" style="display:inline-flex;align-items:center;gap:6px;background:none;border:none;cursor:pointer;color:#64748b;font-size:13px;font-weight:600;padding:0 0 12px;font-family:inherit">'
+ '<svg viewBox="0 0 24 24" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2"><polyline points="15 18 9 12 15 6"/></svg>Tillbaka till valet</button>'
+ '<div class="nc-product-detail" style="display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:18px">'
+ '<div>' + img + '</div>'
+ '<div>'
+ '<div style="font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px">' + _esc(p.catLabel || p.cat || '') + '</div>'
+ '<h3 style="font-size:22px;font-weight:700;color:#1a1a1a;margin:4px 0 8px">' + _esc(p.name || '') + '</h3>'
+ (isInfoUnit ? '' : taxBadge)
+ (p.desc ? '<p style="font-size:13px;color:#64748b;margin:0 0 14px">' + _esc(p.desc) + '</p>' : '')
+ variantHtml
+ qtyHtml
+ (isInfoUnit ? '' : '<div id="newCalcPriceBox" style="background:#f8fafc;border-radius:10px;padding:14px"></div>')
+ '<button onclick="newCalcAddToKalkyl()" style="margin-top:14px;width:100%;padding:12px 16px;background:linear-gradient(135deg,' + (_newCalcContext.editEntryId ? '#024550,#035e6b' : '#059669,#10b981') + ');color:#fff;border:none;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit">' + (_newCalcContext.editEntryId ? 'Uppdatera kalkyl' : 'Lägg till i kalkyl') + '</button>'
+ '</div>'
+ '</div>'
+ '<div id="newCalcInlineTillvalArea"></div>';
if(!isInfoUnit) _renderInlinePriceBox(p);
_renderInlineTillval(p);
}
function _persistSub(){
if(typeof prisSaveSubSession === 'function' && _newCalcContext && _newCalcContext.categoryId){
prisSaveSubSession(_newCalcContext.categoryId, _newCalcContext);
}
}
function newCalcSetVariant(vi){
_newCalcContext.selectedVariantIdx = parseInt(vi) || 0;
_persistSub();
var p = (typeof catalogProducts !== 'undefined' && catalogProducts.find) ? catalogProducts.find(function(x){return x.id===_newCalcContext.selectedProductId;}) : null;
if(p) _renderInlineProduct(p);
}
function newCalcSetProductQty(val){
var n = parseInt(val) || 1;
if(n < 1) n = 1;
_newCalcContext.productQty = n;
_persistSub();
var p = (typeof catalogProducts !== 'undefined' && catalogProducts.find) ? catalogProducts.find(function(x){return x.id===_newCalcContext.selectedProductId;}) : null;
if(p) _renderInlinePriceBox(p);
}
function _renderInlinePriceBox(p){
var box = document.getElementById('newCalcPriceBox');
if(!box) return;
var t = _calcInlineTotal(p);
var taxLabel = t.taxType === 'GT50' ? 'Skattereduktion (GT50)'
: t.taxType === 'GT20' ? 'Grönt teknik-avdrag (GT20)'
: t.taxType === 'ROT' ? 'ROT-avdrag'
: 'Skatteavdrag';
box.innerHTML = '<div style="display:flex;justify-content:space-between;font-size:13px;color:#334155;margin-bottom:6px"><span>' + _esc(p.name) + (t.qty > 1 ? ' × ' + t.qty : '') + '</span><span>' + t.subtotal.toLocaleString('sv-SE') + ' kr</span></div>'
+ (t.tillvalTotal > 0 ? '<div style="display:flex;justify-content:space-between;font-size:13px;color:#334155;margin-bottom:6px"><span>Tillval</span><span>+' + t.tillvalTotal.toLocaleString('sv-SE') + ' kr</span></div>' : '')
+ (t.deduction > 0 ? '<div style="display:flex;justify-content:space-between;font-size:12px;color:#94a3b8;margin-bottom:6px;font-style:italic"><span>Möjligt ' + taxLabel + ': -' + t.deduction.toLocaleString('sv-SE') + ' kr</span><span>(regleras i prissammanställningen)</span></div>' : '')
+ '<div style="display:flex;justify-content:space-between;align-items:baseline;border-top:1px solid #e5e7eb;padding-top:8px;margin-top:6px"><span style="font-size:14px;font-weight:700;color:#1a1a1a">Att betala</span><span style="font-size:22px;font-weight:800;color:#024550">' + t.gross.toLocaleString('sv-SE') + ' kr</span></div>';
}
function _renderInlineTillval(p){
var area = document.getElementById('newCalcInlineTillvalArea');
if(!area) return;
// Dold = aldrig synlig i UI (gäller både tjänster och tillbehör). Dolda tillval räknas
// ändå in i priset/ROT om de är checked (default_on eller mandatory).
var visible = _newCalcContext.tillval.filter(function(tv){ return !tv.hidden; });
if(!visible.length){ area.innerHTML = ''; return; }
area.innerHTML = '<div style="font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px">Tillval</div>'
+ '<div class="nc-tillval-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:8px">'
+ visible.map(function(tv){
var idx = _newCalcContext.tillval.indexOf(tv);
var unitText = tv.unit || 'st';
var unitLabel = '';
var eq = _getInlineEffectiveTillvalQty(tv);
var lineTotal = (tv.price || 0) * eq;
var qtyHtml = '';
if(tv.checked && tv.unit && tv.unit !== 'st'){
qtyHtml = '<div style="margin-top:6px;display:flex;align-items:center;gap:6px" onclick="event.preventDefault();event.stopPropagation()">'
+ '<label style="font-size:11px;color:#64748b">Antal</label>'
+ '<input type="number" min="0" step="0.1" value="' + (tv.userQty || '') + '" oninput="newCalcSetTillvalQty(' + idx + ',this.value)" style="width:80px;padding:5px 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;text-align:center;font-family:inherit">'
+ '<span style="font-size:11px;color:#64748b">' + _esc(tv.unit) + '</span></div>';
} else if(tv.checked && (tv.linkMax > 1 || tv.linkDefaultQty != null)){
var minA = tv.linkMin != null ? tv.linkMin : 1;
var maxA = tv.linkMax != null ? ' max="' + tv.linkMax + '"' : '';
var v = tv.userQty > 0 ? tv.userQty : (tv.linkDefaultQty != null ? tv.linkDefaultQty : 1);
qtyHtml = '<div style="margin-top:6px;display:flex;align-items:center;gap:6px" onclick="event.preventDefault();event.stopPropagation()">'
+ '<label style="font-size:11px;color:#64748b">Antal</label>'
+ '<input type="number" min="' + minA + '"' + maxA + ' step="1" value="' + v + '" oninput="newCalcSetTillvalQty(' + idx + ',this.value)" style="width:80px;padding:5px 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;text-align:center;font-family:inherit">'
+ '<span style="font-size:11px;color:#94a3b8">st' + (tv.linkMax != null ? ' (max ' + tv.linkMax + ')' : '') + '</span></div>';
}
var markerHtml = tv.included
? '<span title="Obligatorisk" style="width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;background:#059669;color:#fff;border-radius:5px;flex-shrink:0;font-size:11px;font-weight:800">✓</span>'
: '<input type="checkbox" ' + (tv.checked ? 'checked' : '') + ' style="width:18px;height:18px;accent-color:#059669;flex-shrink:0;pointer-events:none">';
var mandatoryBadge = tv.included ? ' <span title="Obligatorisk" style="color:#dc2626;font-weight:800;margin-left:4px">*</span>' : '';
return '<div style="padding:10px 14px;background:' + (tv.checked ? '#f0fdf4' : '#f8f9fa') + ';border:1px solid ' + (tv.checked ? '#bbf7d0' : '#e5e7eb') + ';border-radius:10px">'
+ '<div style="display:flex;align-items:center;gap:8px;cursor:' + (tv.included ? 'default' : 'pointer') + '" onclick="' + (tv.included ? '' : 'newCalcToggleTillval(' + idx + ')') + '">'
+ markerHtml
+ '<span style="flex:1;font-size:13px;font-weight:600;color:#334155">' + _esc(tv.name) + unitLabel + mandatoryBadge + '</span>'
+ '</div>' + qtyHtml + '</div>';
}).join('') + '</div>';
}
function newCalcToggleTillval(idx){
var tv = _newCalcContext.tillval[idx];
if(!tv || tv.included) return;
tv.checked = !tv.checked;
_persistSub();
var p = (typeof catalogProducts !== 'undefined' && catalogProducts.find) ? catalogProducts.find(function(x){return x.id===_newCalcContext.selectedProductId;}) : null;
if(p){ _renderInlineTillval(p); _renderInlinePriceBox(p); _renderNewCalcSidebar(); }
}
function newCalcSetTillvalQty(idx, val){
var tv = _newCalcContext.tillval[idx];
if(!tv) return;
var n = parseFloat(val);
if(!isFinite(n)) n = 0;
if(tv.linkMin != null && n < tv.linkMin) n = tv.linkMin;
if(tv.linkMax != null && n > tv.linkMax) n = tv.linkMax;
tv.userQty = n;
_persistSub();
var p = (typeof catalogProducts !== 'undefined' && catalogProducts.find) ? catalogProducts.find(function(x){return x.id===_newCalcContext.selectedProductId;}) : null;
if(p){ _renderInlinePriceBox(p); _renderNewCalcSidebar(); }
}
function newCalcAddToKalkyl(){
var p = (typeof catalogProducts !== 'undefined' && catalogProducts.find) ? catalogProducts.find(function(x){return x.id===_newCalcContext.selectedProductId;}) : null;
if(!p) return;
// Obligatorisk-validering: alla `included` tillval som har valbara varianter (select) måste ha ett val.
var missing = _newCalcContext.tillval.filter(function(tv){
if(!tv || !tv.included) return false;
var hasVariants = Array.isArray(tv.variants) && tv.variants.length > 0;
if(!hasVariants) return false;
return !tv.selectedVariant;
});
if(missing.length){
var names = missing.map(function(tv){ return tv.name; }).join(', ');
alert('Välj alternativ för obligatoriska tillval: ' + names);
return;
}
var t = _calcInlineTotal(p);
var vinfo = _getVariantInfo(p);
var variantLabel = '';
if(vinfo.hasVariants && t.variant && vinfo.labelKey){
var v = t.variant[vinfo.labelKey];
if(v !== undefined && v !== null && v !== '') variantLabel = v + vinfo.labelSuffix;
}
var description = p.name + (variantLabel ? ' - ' + variantLabel : '');
// Alla checked tillval räknas med i entry — inklusive dolda (default_on/mandatory).
var tillvalDetailed = _newCalcContext.tillval.filter(function(tv){ return tv.checked; }).map(function(tv){
var eq = _getInlineEffectiveTillvalQty(tv);
return {
id: tv.id, name: tv.name, price: tv.price, qty: eq, total: tv.price * eq, unit: tv.unit,
value: tv.selectedVariant || '',
hidden: !!tv.hidden,
is_service: !!tv.is_service,
rot_eligible: !!(tv.rotEligible || tv.rot_eligible),
green_tech_eligible: !!(tv.greenTechEligible || tv.green_tech_eligible),
tax_type: tv.taxType || (tv.rotEligible ? 'ROT' : (tv.greenTechEligible ? 'GT20' : 'NONE'))
};
});
var cat = _newCalcContext.categoryId;
var editId = _newCalcContext.editEntryId;
var taxType = t.taxType || 'NONE';
var deductionLabel = '';
if(t.deduction > 0){
if(taxType === 'GT50') deductionLabel = 'Skattereduktion (GT50)';
else if(taxType === 'GT20') deductionLabel = 'Grönt teknik-avdrag (GT20)';
else if(taxType === 'ROT') deductionLabel = 'ROT-avdrag';
else if(taxType === 'RUT') deductionLabel = 'RUT-avdrag';
else deductionLabel = 'Skatteavdrag';
}
var entry = {
description: description,
product_id: p.id,
product_name: p.name,
variant_label: variantLabel,
variant_idx: _newCalcContext.selectedVariantIdx,
qty: t.qty,
unit_price: t.basePrice,
subtotal: t.gross, // GROSS pris (före avdrag)
tillval: tillvalDetailed,
tillvalTotal: t.tillvalTotal,
tax_type: taxType,
deduction_amount: t.deduction || 0, // POTENTIELLT avdrag (sidebaren bestämmer hur mycket som faktiskt dras)
deduction_label: deductionLabel,
total: t.gross // GROSS — sidebaren räknar Att betala = subtotal - effektivt avdrag
};
if(editId && typeof prisUpdateEntry === 'function'){
prisUpdateEntry(cat, editId, entry);
} else if(typeof prisCommitSubSession === 'function'){
prisCommitSubSession(cat, entry);
}
// Ladda in en TOM sub-session för kategorin efter commit
_newCalcContext = (typeof prisGetSubSession === 'function')
? prisGetSubSession(cat)
: { categoryId: cat, selectedProductId: null, tillval: [], productQty: 1, selectedVariantIdx: 0 };
_renderNewCalcSidebar();
if(editId && typeof openCalcSummary === 'function'){
setTimeout(openCalcSummary, 100);
} else {
newCalcBackToGrid();
}
}
function _renderNewCalcSidebar(){
var sidebar = document.getElementById('newCalcPrisSidebar');
if(!sidebar) return;
if(typeof renderPrisSidebar !== 'function'){
sidebar.innerHTML = '<div style="padding:12px;background:#fee;color:#b00;border-radius:8px;font-size:12px">renderPrisSidebar saknas (prissammanstallning.js inte laddad)</div>';
return;
}
// Aggregera GROSS subtotal + POTENTIELLT avdrag från varukorgen
var subtotal = 0;
var potentialDed = 0;
var aggTaxType = 'NONE';
if(typeof _kalkylItems !== 'undefined' && _kalkylItems){
Object.keys(_kalkylItems).forEach(function(k){
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;
potentialDed += parseFloat(e.deduction_amount) || 0;
if(aggTaxType === 'NONE' && e.tax_type && e.tax_type !== 'NONE') aggTaxType = e.tax_type;
});
});
}
// Effektivt avdrag = min(potentiellt, owners × maxPerPerson, slider)
var taxType = aggTaxType;
var deductType = _newCalcContext.deductType;
if(!deductType){
deductType = potentialDed > 0 ? ((taxType === 'ROT') ? 'rot' : 'green') : 'none';
}
var owners = _newCalcContext.owners || 1;
var tax = (typeof taxSettings !== 'undefined') ? taxSettings : { rotMax: 50000, gt50: 50, gt20: 20, rot: 30, moms: 25 };
var maxPerPerson = (taxType === 'ROT') ? (tax.rotMax || 50000) : 50000;
var ownerCap = owners * maxPerPerson;
var sliderMax = ownerCap;
var sliderValue = (_newCalcContext.sliderValue != null) ? Math.min(parseFloat(_newCalcContext.sliderValue), sliderMax) : sliderMax;
var effectiveDed = (deductType === 'none') ? 0 : Math.min(potentialDed, sliderValue);
renderPriceSummary('newCalcPrisSidebar', {
lines: [],
subtotal: subtotal,
totalIsAll: true,
taxType: taxType,
deductType: deductType,
deductAmount: effectiveDed,
owners: owners,
sliderInteractive: true,
sliderValue: sliderValue,
maxDeduction: sliderMax,
total: subtotal - effectiveDed,
finYears: _newCalcContext.finYears || 15,
monthly: 0,
category: _newCalcContext.categoryId,
onDeductChange: 'newCalcSetDeductType',
onOwnerChange: 'newCalcSetOwners',
onFinChange: 'newCalcSetFinYears',
onSliderChange: 'newCalcSetSliderValue'
});
}
window.newCalcOpenProduct = newCalcOpenProduct;
window.newCalcBackToGrid = newCalcBackToGrid;
window.newCalcSetProductQty = newCalcSetProductQty;
window.newCalcSetVariant = newCalcSetVariant;
window.newCalcToggleTillval = newCalcToggleTillval;
window.newCalcSetTillvalQty = newCalcSetTillvalQty;
window.newCalcAddToKalkyl = newCalcAddToKalkyl;
// Sidebar-kontroller: ägare, skatteavdrag-typ, kvarvarande avdrag, finansiering
function newCalcSetOwners(n){
_newCalcContext.owners = parseInt(n) || 1;
// Återställ reglaget när antal personer byts (max-värdet ändras)
_newCalcContext.sliderValue = null;
_persistSub();
_renderNewCalcSidebar();
}
function newCalcSetDeductType(type){
_newCalcContext.deductType = type || 'none';
_persistSub();
_renderNewCalcSidebar();
}
function newCalcSetSliderValue(v){
_newCalcContext.sliderValue = parseFloat(v) || 0;
_persistSub();
_renderNewCalcSidebar();
}
function newCalcSetFinYears(y){
_newCalcContext.finYears = parseInt(y) || 0;
_persistSub();
_renderNewCalcSidebar();
}
window.newCalcSetOwners = newCalcSetOwners;
window.newCalcSetDeductType = newCalcSetDeductType;
window.newCalcSetSliderValue = newCalcSetSliderValue;
window.newCalcSetFinYears = newCalcSetFinYears;
// Varna innan fliken stängs/navigeras om det finns en påbörjad kalkyl
window.addEventListener('beforeunload', function(e){
if(_hasUnsavedCalc()){
e.preventDefault();
e.returnValue = '';
return '';
}
});
// new_calc.js — Spara kalkyl. ÅSIDOSÄTTER legacy saveCfgQuote() som kraschade
// med "Cannot set properties of null" eftersom den läste från gamla _batProduct,
// _lbSelected etc. som ALDRIG sätts av nya flödet. Vi sparar _kalkylItems direkt.
async function newCalcSaveQuote(){
if(typeof _kalkylItems === 'undefined' || !_kalkylItems || !Object.keys(_kalkylItems).length){
alert('Kalkylen är tom — lägg till produkter först.');
return;
}
var items = {};
var cats = [];
var grandTotal = 0;
Object.keys(_kalkylItems).forEach(function(cat){
var item = _kalkylItems[cat];
if(!item || !item.total) return;
items[cat] = item;
// Interna nycklar (_addons, _addonsState, _summary osv) ska inte synas i produkt-listan
if(cat.charAt(0) !== '_') cats.push(cat);
grandTotal += parseFloat(item.total) || 0;
});
if(!cats.length){ alert('Inga produkter i kalkylen.'); return; }
var customer = (typeof pendingKalkylCustomer !== 'undefined' && pendingKalkylCustomer) ? pendingKalkylCustomer : null;
var name = '';
var isNew = !(typeof currentQuoteId !== 'undefined' && currentQuoteId);
if(isNew){
var defaultName = (customer && customer.name) ? customer.name : cats.join(', ');
name = prompt('Ge kalkylen ett namn:', defaultName);
if(!name) return;
}
var description = name || (customer && customer.name) || cats.join(', ');
var payload = {
category: cats.join(','),
config_data: JSON.stringify({ items: items, kalkylName: description, customer: customer }),
total_price: grandTotal,
panel_name: description,
panel_count: 0,
status: 'utkast',
created_by: (typeof gStaffId !== 'undefined' ? gStaffId : null),
customer_name: customer ? customer.name : null,
customer_address: customer ? customer.address : null,
customer_email: customer ? customer.email : null,
customer_phone: customer ? customer.phone : null,
customer_personnummer: (customer && customer.owner1) ? customer.owner1.pnr : null,
owner_count: customer ? (customer.ownerType === '2' ? 2 : (customer.ownerType === 'brf' ? 0 : 1)) : 1,
prospect_data: customer ? (customer.solarData || null) : null
};
if(typeof currentQuoteId !== 'undefined' && currentQuoteId) payload.id = currentQuoteId;
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; }
if(data.id){ currentQuoteId = data.id; }
if(typeof _cfgDirty !== 'undefined') _cfgDirty = false;
alert('Kalkylen "' + description + '" är sparad.');
if(typeof loadCalcList === 'function') loadCalcList();
} catch(e){
console.error('[new_calc] save error', e);
alert('Kunde inte spara: ' + (e.message || e));
}
}
window.newCalcSaveQuote = newCalcSaveQuote;
// Åsidosätt legacy saveCfgQuote — MÅSTE göras EFTER konfigurator-spara.js har laddat
// (annars skriver dess "async function saveCfgQuote(){}" över window.saveCfgQuote igen).
function _newCalcInstallSaveOverride(){
window.saveCfgQuote = newCalcSaveQuote;
}
if(document.readyState === 'complete' || document.readyState === 'interactive'){
setTimeout(_newCalcInstallSaveOverride, 0);
} else {
document.addEventListener('DOMContentLoaded', _newCalcInstallSaveOverride);
}
window.addEventListener('load', _newCalcInstallSaveOverride);
// Routa ALLA showKalkylSummary-anrop (även från legacy openQuoteFromList) till nya
// Calc_summary-sidan. Installeras via DOMContentLoaded så vi vinner över andra script.
function _newCalcInstallSummaryRedirect(){
var origShowKalkylSummary = window.showKalkylSummary;
if(window._showKalkylSummaryRedirected) return;
window._showKalkylSummaryRedirected = true;
window._origShowKalkylSummary = origShowKalkylSummary;
window.showKalkylSummary = function(){
// Städa upp CalcBuilder render-arean
var renderArea = document.getElementById('newCalcRenderArea');
if(renderArea) renderArea.remove();
var banner = document.getElementById('newCalcLegacyBanner');
if(banner) banner.remove();
var lbl = document.getElementById('cfgFileLabel');
if(lbl) lbl.textContent = '';
if(typeof _newCalcContext !== 'undefined'){
_newCalcContext.selectedProductId = null;
_newCalcContext.tillval = [];
}
var bar = document.getElementById('cfgCategoryBar');
if(bar) bar.style.display = 'none';
// Öppna den NYA Kalkylsammanställning-sidan (Calc_summary.php/js/css)
if(typeof openCalcSummary === 'function'){
openCalcSummary();
return;
}
if(typeof origShowKalkylSummary === 'function') origShowKalkylSummary();
};
}
if(document.readyState === 'complete' || document.readyState === 'interactive'){
setTimeout(_newCalcInstallSummaryRedirect, 0);
} else {
document.addEventListener('DOMContentLoaded', _newCalcInstallSummaryRedirect);
}
window.addEventListener('load', _newCalcInstallSummaryRedirect);
// Routa öga/penna i prissammanställningen till nya CalcBuilder-flödet
// Installeras EFTER DOMContentLoaded så kalkyl-summary.js redan har deklarerat sina funktioner
function _newCalcInstallSummaryHooks(){
if(_newCalcInstallSummaryHooks._done) return;
_newCalcInstallSummaryHooks._done = true;
function _setEditBtnLoading(btn, on){
if(!btn) return;
if(on){
btn.disabled = true;
btn.setAttribute('data-loading', '1');
btn.style.opacity = '.35';
btn.style.cursor = 'wait';
btn.style.pointerEvents = 'none';
} else {
btn.disabled = false;
btn.removeAttribute('data-loading');
btn.style.opacity = '';
btn.style.cursor = '';
btn.style.pointerEvents = '';
}
}
function tryNewCalc(cat, productId, entryId, entry, btnEl){
var summaryView = document.getElementById('summaryProductList');
if(summaryView) summaryView.style.display = 'none';
if(!productId){
return Promise.resolve(openNewCalcCategory(cat, { edit: true }));
}
// openNewCalcCategory returnerar Promise som resolvar EFTER render (scripts körda synkront efter fetch).
var p = openNewCalcCategory(cat, { edit: true });
if(!p || typeof p.then !== 'function'){ p = Promise.resolve(); }
return p.then(function(){
// Välj rätt edit-flöde baserat på KATEGORINS builder_mode, inte om WindowSnippet
// råkar vara kvar i global scope från en tidigare fönster-kalkyl.
var isCode = _newCalcContext && _newCalcContext.cfg && _newCalcContext.cfg.builder_mode === 'code';
if(isCode && entry && window.WindowSnippet && typeof window.WindowSnippet.openForEdit === 'function'){
try { window.WindowSnippet.openForEdit(entry); }
catch(e){ console.warn('openForEdit fel', e); }
} else if(typeof newCalcOpenProduct === 'function'){
newCalcOpenProduct(productId, entryId);
}
});
}
window.editFromSummary = function(cat, btnEl){
_setEditBtnLoading(btnEl, true);
return tryNewCalc(cat)
.catch(function(e){ console.warn('editFromSummary fel', e); })
.then(function(){ _setEditBtnLoading(btnEl, false); });
};
window.editSummaryEntry = function(cat, entryId, btnEl){
_setEditBtnLoading(btnEl, true);
// Om vi står i Calc_summary-vyn (prissammanstallningen där), stäng först så konfiguratorn kan visas
var calcSumEl = document.getElementById('page-calc-summary');
var calcSumOpen = calcSumEl && calcSumEl.classList.contains('active');
if(calcSumOpen && typeof closeCalcSummary === 'function'){
try { closeCalcSummary(); } catch(e){}
}
var item = (window._kalkylItems || {})[cat];
var entries = (item && Array.isArray(item.entries)) ? item.entries : (item ? [item] : []);
var entry = entries.find(function(e){ return e.entry_id === entryId; }) || null;
var pid = entry && (entry.product_id || entry.productId);
if(!pid && entry && typeof catalogProducts !== 'undefined' && Array.isArray(catalogProducts)){
var name = entry.product_name || entry.description;
var match = catalogProducts.find(function(p){
return p && p.cat === cat && (p.name === name || (name && p.name && name.indexOf(p.name) !== -1));
});
if(match) pid = match.id;
}
return tryNewCalc(cat, pid, entryId, entry, btnEl)
.catch(function(e){ console.warn('editSummaryEntry fel', e); })
.then(function(){ _setEditBtnLoading(btnEl, false); });
};
window.showSummaryCategory = function(cat, btnEl){
_setEditBtnLoading(btnEl, true);
return tryNewCalc(cat)
.catch(function(e){ console.warn('showSummaryCategory fel', e); })
.then(function(){ _setEditBtnLoading(btnEl, false); });
};
}
// Kör hooks så fort DOM + alla scripts laddats
if(document.readyState === 'complete' || document.readyState === 'interactive'){
setTimeout(_newCalcInstallSummaryHooks, 0);
} else {
document.addEventListener('DOMContentLoaded', _newCalcInstallSummaryHooks);
}
window.addEventListener('load', _newCalcInstallSummaryHooks);
// Gör funktionerna globalt tillgängliga (anropas från onclick-attribut och andra filer)
window.newCalc = newCalc;
window.openNewCalcCategory = openNewCalcCategory;
window._showNewCalcGrid = _showNewCalcGrid;
window._loadNewCalcCards = _loadNewCalcCards;
window._newCalcActive = _newCalcActive;
// ============================================================
// SMARTFILTER — per säljare, sparat i DB (user_calc_filters)
// Aktiveras när cfg.smart_filter = 1 i CalcBuilder.
// ============================================================
var _smartFilterCache = {}; // { catId: [filter,...] }
var _smartFilterActiveId = {}; // { catId: id }
var _smartFilterLoading = {};
function _smartFilterStaffId(){
try {
if (typeof gStaffId !== 'undefined' && gStaffId) return gStaffId;
var email = localStorage.getItem('userEmail') || '';
if (typeof staffList !== 'undefined' && Array.isArray(staffList)) {
var s = staffList.find(function(x){ return x.email === email; });
if (s) return s.id;
}
// Fallback: prova localStorage userId, annars Jim (43) så admin kan testa
var uid = parseInt(localStorage.getItem('userId') || localStorage.getItem('staffId') || '0');
if (uid > 0) return uid;
} catch(e){}
return 43; // Jim = default fallback (testbart även när inte inloggad som säljare)
}
function _smartFilterModelLabels(p){
if (!p || !p.specs) return [];
var labels = {};
if (Array.isArray(p.specs.models)) p.specs.models.forEach(function(m){ if (m && m.label) labels[m.label] = true; });
if (Array.isArray(p.specs.variants)) p.specs.variants.forEach(function(v){
if (!v) return;
var lbl = v.label || v.name || (v.kwh ? v.kwh + ' kWh' : '') || (v.paneler ? v.paneler + ' paneler' : '') || (v.cm ? v.cm + ' cm' : '');
if (lbl) labels[lbl] = true;
});
if (Array.isArray(p.specs.styles)) p.specs.styles.forEach(function(s){ if (s && s.style) labels[s.style] = true; });
return Object.keys(labels);
}
function _smartFilterActive(catId){
var id = _smartFilterActiveId[catId];
if (!id) return null;
var arr = _smartFilterCache[catId] || [];
return arr.find(function(f){ return f.id === id; }) || null;
}
function _smartFilterLoad(catId){
if (_smartFilterCache[catId] || _smartFilterLoading[catId]) return Promise.resolve(_smartFilterCache[catId] || []);
var sid = _smartFilterStaffId();
if (!sid){ _smartFilterCache[catId] = []; return Promise.resolve([]); }
_smartFilterLoading[catId] = true;
return fetch('/api/calc_filters.php?staff_id=' + sid + '&category_id=' + encodeURIComponent(catId))
.then(function(r){ return r.json(); })
.then(function(d){
_smartFilterCache[catId] = (d && d.success && Array.isArray(d.filters)) ? d.filters : [];
_smartFilterLoading[catId] = false;
return _smartFilterCache[catId];
})
.catch(function(){ _smartFilterLoading[catId] = false; _smartFilterCache[catId] = []; return []; });
}
function _renderSmartFilterBar(catId, products){
var host = document.getElementById('newCalcSmartFilter');
if (!host) return;
_smartFilterLoad(catId).then(function(filters){
var activeId = _smartFilterActiveId[catId];
var html = '<div style="display:flex;flex-wrap:wrap;gap:6px;align-items:center">';
html += '<span style="font-size:11px;font-weight:700;color:#92400e;margin-right:6px">FILTER:</span>';
if (filters.length === 0){
html += '<span style="font-size:12px;color:#a16207;font-style:italic">Inga sparade — skapa ett genom att klicka knappen.</span>';
} else {
filters.forEach(function(f){
var active = activeId === f.id;
html += '<span style="display:inline-flex;align-items:center;gap:0">'
+ '<button onclick="smartFilterToggle(\'' + _esc(catId) + '\',' + f.id + ')" style="padding:6px 12px;border:1.5px solid ' + (active ? '#f59e0b' : '#fde68a') + ';background:' + (active ? '#f59e0b' : '#fffbeb') + ';color:' + (active ? '#fff' : '#92400e') + ';border-radius:20px 0 0 20px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit">' + _esc(f.name) + '</button>'
+ '<button onclick="smartFilterDelete(\'' + _esc(catId) + '\',' + f.id + ')" title="Ta bort" style="padding:6px 8px;border:1.5px solid ' + (active ? '#f59e0b' : '#fde68a') + ';border-left:none;background:' + (active ? '#f59e0b' : '#fffbeb') + ';color:' + (active ? '#fff' : '#dc2626') + ';border-radius:0 20px 20px 0;font-size:12px;cursor:pointer;font-family:inherit">×</button>'
+ '</span>';
});
if (activeId){
html += '<button onclick="smartFilterClear(\'' + _esc(catId) + '\')" style="padding:6px 10px;border:1px solid #e5e7eb;background:#fff;color:#64748b;border-radius:20px;font-size:11px;cursor:pointer;font-family:inherit;margin-left:4px">Visa alla</button>';
}
}
html += '<button onclick="smartFilterShowCreate(\'' + _esc(catId) + '\')" style="margin-left:auto;padding:6px 14px;background:#024550;color:#fff;border:none;border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit">+ Skapa filter</button>';
html += '</div>';
host.innerHTML = '<div style="padding:10px 14px;background:#fffbeb;border:1px dashed #fcd34d;border-radius:10px">' + html + '</div>';
});
}
function _smartFilterNotify(catId){
// Re-rendera filter-baren + meddela snippets/simple-paint
_renderSmartFilterBar(catId, []);
try {
document.dispatchEvent(new CustomEvent('smartFilter:changed', { detail: { categoryId: catId, active: _smartFilterActive(catId) } }));
} catch(e){}
// Simple-mode: paint griden om vi är i simple
var grid = document.getElementById('newCalcSimpleGrid');
if (grid && typeof window._newCalcRepaintSimple === 'function'){
try { window._newCalcRepaintSimple(catId); } catch(e){}
}
}
function smartFilterToggle(catId, id){
_smartFilterActiveId[catId] = (_smartFilterActiveId[catId] === id) ? null : id;
_smartFilterNotify(catId);
}
function smartFilterClear(catId){
_smartFilterActiveId[catId] = null;
_smartFilterNotify(catId);
}
function smartFilterDelete(catId, id){
if (!confirm('Ta bort filtret?')) return;
var sid = _smartFilterStaffId();
fetch('/api/calc_filters.php?id=' + id + '&staff_id=' + sid, { method:'DELETE' })
.then(function(r){ return r.json(); })
.then(function(){
// Ta bort direkt ur cache så bar:en uppdateras utan re-fetch race
if (Array.isArray(_smartFilterCache[catId])){
_smartFilterCache[catId] = _smartFilterCache[catId].filter(function(f){ return f.id !== id; });
}
_smartFilterLoading[catId] = false;
if (_smartFilterActiveId[catId] === id) _smartFilterActiveId[catId] = null;
_smartFilterNotify(catId);
});
}
function smartFilterShowCreate(catId){
var src = (typeof catalogProducts !== 'undefined' && Array.isArray(catalogProducts)) ? catalogProducts : [];
var products = src.filter(function(p){ return p && p.cat === catId; })
.sort(function(a,b){ return String(a.name||'').localeCompare(String(b.name||''),'sv'); });
var modelSet = {};
products.forEach(function(p){ _smartFilterModelLabels(p).forEach(function(m){ modelSet[m] = true; }); });
var models = Object.keys(modelSet).sort();
var modal = document.createElement('div');
modal.id = 'smartFilterModal';
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:10000;display:flex;align-items:center;justify-content:center;padding:20px;backdrop-filter:blur(3px)';
modal.onclick = function(e){ if (e.target === modal) modal.remove(); };
var prodChecks = products.map(function(p){
return '<label style="display:flex;align-items:center;gap:8px;padding:6px 0;cursor:pointer;font-size:13px"><input type="checkbox" class="sf-prod" value="' + _esc(p.id) + '" checked style="width:16px;height:16px;accent-color:#024550">' + _esc(p.name||'') + '</label>';
}).join('') || '<div style="color:#94a3b8;font-size:12px">Inga produkter</div>';
var modelChecks = models.map(function(m){
return '<label style="display:flex;align-items:center;gap:8px;padding:6px 0;cursor:pointer;font-size:13px"><input type="checkbox" class="sf-model" value="' + _esc(m) + '" checked style="width:16px;height:16px;accent-color:#3b82f6">' + _esc(m) + '</label>';
}).join('') || '<div style="color:#94a3b8;font-size:12px;font-style:italic">Inga modeller hittades</div>';
modal.innerHTML = '<div style="background:#fff;border-radius:16px;padding:24px;width:560px;max-width:95vw;max-height:85vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,.25)">'
+ '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">'
+ '<h3 style="font-size:18px;font-weight:700;margin:0">Skapa filter</h3>'
+ '<button onclick="document.getElementById(\'smartFilterModal\').remove()" style="background:none;border:none;font-size:22px;cursor:pointer;color:#94a3b8">×</button>'
+ '</div>'
+ '<div style="margin-bottom:16px"><label style="font-size:12px;font-weight:700;color:#64748b;display:block;margin-bottom:4px">FILTERNAMN</label>'
+ '<input id="sfName" placeholder="t.ex. Mitt special" style="width:100%;padding:10px 14px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit;box-sizing:border-box"></div>'
+ '<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px">'
+ '<div><div style="font-size:12px;font-weight:700;color:#64748b;margin-bottom:8px">PRODUKTER</div>' + prodChecks + '</div>'
+ '<div><div style="font-size:12px;font-weight:700;color:#64748b;margin-bottom:8px">MODELLER</div>' + modelChecks + '</div>'
+ '</div>'
+ '<button onclick="smartFilterDoSave(\'' + _esc(catId) + '\')" style="margin-top:20px;width:100%;padding:14px;background:#f59e0b;color:#fff;border:none;border-radius:10px;font-size:15px;font-weight:700;cursor:pointer;font-family:inherit">Spara filter</button>'
+ '</div>';
document.body.appendChild(modal);
setTimeout(function(){ document.getElementById('sfName').focus(); }, 50);
}
function smartFilterDoSave(catId){
var name = document.getElementById('sfName').value.trim();
if (!name){ alert('Ange ett namn för filtret'); return; }
var prods = [], models = [];
document.querySelectorAll('.sf-prod:checked').forEach(function(cb){ prods.push(cb.value); });
document.querySelectorAll('.sf-model:checked').forEach(function(cb){ models.push(cb.value); });
var sid = _smartFilterStaffId();
if (!sid){ alert('Kunde inte identifiera säljare'); return; }
fetch('/api/calc_filters.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({ staff_id: sid, category_id: catId, name: name, products: prods, models: models })
})
.then(function(r){ return r.json(); })
.then(function(d){
if (d && d.success){
// Push direkt i cache så bar:en uppdateras utan att vänta på re-fetch
// (race condition: _smartFilterLoading kan vara stuck → re-fetch returnerar tom)
if (!Array.isArray(_smartFilterCache[catId])) _smartFilterCache[catId] = [];
_smartFilterCache[catId].push({ id: d.id, name: name, products: prods, models: models, sort_order: 0 });
_smartFilterLoading[catId] = false;
_smartFilterActiveId[catId] = d.id;
var m = document.getElementById('smartFilterModal');
if (m) m.remove();
_smartFilterNotify(catId);
} else {
alert('Kunde inte spara: ' + ((d && d.error) || 'okänt fel'));
}
});
}
window.smartFilterToggle = smartFilterToggle;
window.smartFilterClear = smartFilterClear;
window.smartFilterDelete = smartFilterDelete;
window.smartFilterShowCreate = smartFilterShowCreate;
window.smartFilterDoSave = smartFilterDoSave;
// Publik API för code-mode snippets:
// window.smartFilterActive(catId) -> { id, name, products:[], models:[] } eller null
// window.smartFilterApply(catId, products) -> filtrerad array baserat på aktivt filter
// window.smartFilterModels(p) -> array med model-labels på en produkt
window.smartFilterActive = function(catId){ return _smartFilterActive(catId); };
window.smartFilterModels = function(p){ return _smartFilterModelLabels(p); };
window.smartFilterApply = function(catId, arr){
var f = _smartFilterActive(catId);
if (!f) return arr;
var out = arr;
if (Array.isArray(f.products) && f.products.length){
out = out.filter(function(p){ return f.products.indexOf(p.id) !== -1; });
}
if (Array.isArray(f.models) && f.models.length){
out = out.filter(function(p){
var m = _smartFilterModelLabels(p);
return m.some(function(x){ return f.models.indexOf(x) !== -1; });
});
}
return out;
};