(function(){
var state = {
categories: [],
configurators: [],
current: null
};
function esc(value){
return String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
function status(message, type){
var box = document.getElementById('calcBuilderStatus');
if(!box) return;
box.textContent = message || '';
box.className = 'config-builder-status' + (type ? ' is-' + type : '');
}
function blankConfigurator(){
return {
id: null,
title: '',
version: '1.0.0',
notes: '',
category: '',
builderMode: 'simple',
headPreset: 'current-default',
footerMode: 'none',
sidebarModule: 'price-summary',
baseSource: 'product-catalog',
sidebarPosition: 'right',
regions: deriveRegions({
builderMode: 'simple',
headPreset: 'current-default',
sidebarModule: 'price-summary'
}),
blocks: [],
code: ''
};
}
var modeHelpText = {
simple: 'Enkel kalkylator använder produktkatalogens färdiga upplägg.',
advanced: 'Avancerad kalkylator använder blockdriven kalkylator. Lägg till block nedanför.',
code: 'Code-läge: skriv eller klistra in egen HTML/JS som content-region. Head, sidebar och footer styrs från Layout-panelen.'
};
var codeSnippets = {
window: '<div class="my-window-config">\n <h3>Fönster-konfigurator</h3>\n <label>Bredd (dm) <input type="number" id="winBredd" min="3" max="30"></label>\n <label>Höjd (dm) <input type="number" id="winHojd" min="3" max="30"></label>\n <button onclick="filterWindowModels()">Visa modeller</button>\n <div id="winModels"></div>\n</div>',
door: '<div class="my-door-config">\n <h3>Dörr-konfigurator</h3>\n <select id="doorModel"><option>Välj modell...</option></select>\n <div id="doorServices"></div>\n</div>'
};
var moduleLabels = {
'customer-block': 'Kundinformation',
'prospect-images': 'Prospektbilder',
'calc-title': 'Kalkylrubrik',
'category-grid': 'Kategorigrid',
'config-blocks': 'Konfiguratorblock',
'overview-block': 'Översikt',
'price-summary': 'Prissammanställning',
'savings-box': 'Besparing',
'actions-box': 'Åtgärder'
};
function deriveRegions(source){
return {
head: (source.headPreset || 'current-default') === 'current-default'
? ['customer-block', 'prospect-images', 'calc-title']
: [],
content: ['config-blocks'],
sidebar: (source.sidebarModule || 'price-summary') === 'none'
? []
: [source.sidebarModule || 'price-summary']
};
}
function normalizeBlock(block, index){
block = block || {};
return {
id: block.id || 'block-' + (index + 1),
type: block.type || 'select',
field: block.field || '',
label: block.label || '',
description: block.description || '',
placeholder: block.placeholder || '',
unit: block.unit || '',
min: block.min != null ? block.min : '',
max: block.max != null ? block.max : '',
step: block.step != null ? block.step : '',
defaultValue: block.defaultValue != null ? block.defaultValue : '',
visibleField: block.visibleIf && block.visibleIf.field ? block.visibleIf.field : '',
visibleOperator: block.visibleIf && block.visibleIf.notEquals != null ? 'notEquals' : 'equals',
visibleValue: block.visibleIf ? (block.visibleIf.equals != null ? block.visibleIf.equals : (block.visibleIf.notEquals != null ? block.visibleIf.notEquals : '')) : '',
optionsText: Array.isArray(block.options)
? block.options.map(function(option){
return [option.value || '', option.label || '', option.description || ''].join('|').replace(/\|+$/,'');
}).join('\n')
: ''
};
}
function parseOptions(text){
return String(text || '').split(/\n+/).map(function(line){
line = line.trim();
if(!line) return null;
var parts = line.split('|');
return {
value: (parts[0] || '').trim(),
label: (parts[1] || parts[0] || '').trim(),
description: (parts[2] || '').trim()
};
}).filter(Boolean);
}
function serializeBlock(raw){
var block = {
id: raw.id || raw.field || ('block-' + Math.random().toString(36).slice(2, 8)),
type: raw.type,
field: raw.field,
label: raw.label,
description: raw.description
};
if(raw.placeholder) block.placeholder = raw.placeholder;
if(raw.unit) block.unit = raw.unit;
if(raw.min !== '') block.min = Number(raw.min);
if(raw.max !== '') block.max = Number(raw.max);
if(raw.step !== '') block.step = Number(raw.step);
if(raw.defaultValue !== '') block.defaultValue = raw.type === 'toggle'
? (String(raw.defaultValue) === 'true')
: (/^-?\d+(\.\d+)?$/.test(String(raw.defaultValue)) ? Number(raw.defaultValue) : raw.defaultValue);
if(raw.type === 'cards' || raw.type === 'select') block.options = parseOptions(raw.optionsText);
if(raw.visibleField && raw.visibleValue !== ''){
block.visibleIf = { field: raw.visibleField };
if(raw.visibleOperator === 'notEquals') block.visibleIf.notEquals = raw.visibleValue;
else block.visibleIf.equals = raw.visibleValue;
}
return block;
}
function getCurrentBlocks(){
return Array.from(document.querySelectorAll('.config-block-card')).map(function(card){
return {
id: card.querySelector('[data-key="id"]').value.trim(),
type: card.querySelector('[data-key="type"]').value,
field: card.querySelector('[data-key="field"]').value.trim(),
label: card.querySelector('[data-key="label"]').value.trim(),
description: card.querySelector('[data-key="description"]').value.trim(),
placeholder: card.querySelector('[data-key="placeholder"]').value.trim(),
unit: card.querySelector('[data-key="unit"]').value.trim(),
min: card.querySelector('[data-key="min"]').value.trim(),
max: card.querySelector('[data-key="max"]').value.trim(),
step: card.querySelector('[data-key="step"]').value.trim(),
defaultValue: card.querySelector('[data-key="defaultValue"]').value.trim(),
visibleField: card.querySelector('[data-key="visibleField"]').value.trim(),
visibleOperator: card.querySelector('[data-key="visibleOperator"]').value,
visibleValue: card.querySelector('[data-key="visibleValue"]').value.trim(),
optionsText: card.querySelector('[data-key="optionsText"]').value
};
});
}
function syncCurrentFromForm(){
if(!state.current) state.current = blankConfigurator();
state.current.title = document.getElementById('calcBuilderTitle').value.trim();
state.current.category = document.getElementById('calcBuilderCategory').value;
state.current.version = document.getElementById('calcBuilderVersion').value.trim() || '1.0.0';
state.current.notes = document.getElementById('calcBuilderNotes').value.trim();
state.current.builderMode = document.getElementById('calcBuilderMode').value || 'simple';
state.current.headPreset = document.getElementById('calcBuilderHeadPreset').value || 'current-default';
state.current.sidebarModule = document.getElementById('calcBuilderSidebarModule').value || 'price-summary';
state.current.footerMode = document.getElementById('calcBuilderFooterMode').value || 'none';
state.current.baseSource = state.current.builderMode === 'advanced'
? 'custom-schema'
: (state.current.builderMode === 'code' ? 'code' : 'product-catalog');
state.current.sidebarPosition = document.getElementById('calcBuilderSidebarPosition').value || 'right';
state.current.regions = deriveRegions(state.current);
state.current.blocks = getCurrentBlocks();
var codeEl = document.getElementById('calcBuilderCode');
if(codeEl) state.current.code = codeEl.value;
}
function renderCategoryOptions(){
var select = document.getElementById('calcBuilderCategory');
if(!select) return;
select.innerHTML = '<option value="">Välj kategori...</option>' + state.categories.map(function(cat){
return '<option value="'+esc(cat.id)+'">'+esc(cat.label || cat.id)+'</option>';
}).join('');
}
function applyCurrentToForm(){
var current = state.current || blankConfigurator();
document.getElementById('calcBuilderTitle').value = current.title || '';
document.getElementById('calcBuilderCategory').value = current.category || '';
document.getElementById('calcBuilderVersion').value = current.version || '1.0.0';
document.getElementById('calcBuilderNotes').value = current.notes || '';
var resolvedMode = current.builderMode || (current.baseSource === 'custom-schema' ? 'advanced' : (current.baseSource === 'code' ? 'code' : 'simple'));
document.getElementById('calcBuilderMode').value = resolvedMode;
document.getElementById('calcBuilderHeadPreset').value = current.headPreset || 'current-default';
document.getElementById('calcBuilderSidebarModule').value = current.sidebarModule || 'price-summary';
document.getElementById('calcBuilderFooterMode').value = current.footerMode || 'none';
document.getElementById('calcBuilderSidebarPosition').value = current.sidebarPosition || 'right';
var codeEl = document.getElementById('calcBuilderCode');
if(codeEl) codeEl.value = current.code || '';
applyModeVisibility();
renderBlocks();
refreshPreview();
refreshCodePreview();
}
function setMode(mode){
if(mode !== 'simple' && mode !== 'advanced' && mode !== 'code') mode = 'simple';
if(!state.current) state.current = blankConfigurator();
syncCurrentFromForm();
state.current.builderMode = mode;
document.getElementById('calcBuilderMode').value = mode;
applyModeVisibility();
refreshPreview();
if(mode === 'code') refreshCodePreview();
}
function applyModeVisibility(){
var currentMode = (document.getElementById('calcBuilderMode').value || 'simple');
document.querySelectorAll('.config-builder-mode-tab').forEach(function(tab){
tab.classList.toggle('is-active', tab.getAttribute('data-mode') === currentMode);
});
var advancedWrap = document.getElementById('calcBuilderAdvancedWrap');
var codeWrap = document.getElementById('calcBuilderCodeWrap');
var advancedBits = document.querySelectorAll('.config-builder-advanced-only');
if(advancedWrap) advancedWrap.classList.toggle('is-hidden', currentMode !== 'advanced');
if(codeWrap) codeWrap.classList.toggle('is-hidden', currentMode !== 'code');
advancedBits.forEach(function(el){
el.classList.toggle('is-hidden', currentMode !== 'advanced');
});
var help = document.getElementById('calcBuilderModeHelp');
if(help) help.textContent = modeHelpText[currentMode] || modeHelpText.simple;
}
// Back-compat alias (called from refreshPreview)
function toggleAdvancedMode(){ applyModeVisibility(); }
function interpolateCode(code, vars){
return String(code || '').replace(/\{\{\s*(\w+)\s*\}\}/g, function(match, key){
return vars[key] != null ? String(vars[key]) : match;
});
}
function refreshCodePreview(){
var preview = document.getElementById('calcBuilderCodePreview');
if(!preview) return;
if(!state.current) state.current = blankConfigurator();
var code = (state.current.code != null ? state.current.code : (document.getElementById('calcBuilderCode') || {value:''}).value) || '';
var vars = {
category: state.current.category || '',
title: state.current.title || '',
version: state.current.version || '1.0.0'
};
var rendered = interpolateCode(code, vars);
var regions = deriveRegions(state.current);
var headList = (regions.head || []).map(function(item){
return '<span class="config-preview-module">' + esc(moduleLabels[item] || item) + '</span>';
}).join('') || '<em style="color:#94a3b8">Ingen head</em>';
var sidebarList = (regions.sidebar || []).map(function(item){
return '<span class="config-preview-module">' + esc(moduleLabels[item] || item) + '</span>';
}).join('') || '<em style="color:#94a3b8">Ingen sidebar</em>';
var footerLabel = state.current.footerMode === 'default' ? 'Standard footer' : 'Ingen footer';
var colClass = state.current.sidebarPosition === 'left' ? 'config-builder-code-preview-columns is-left' : 'config-builder-code-preview-columns';
var contentHtml = '';
try {
contentHtml = '<div class="config-builder-code-preview-content-body">' + rendered + '</div>';
} catch(err){
contentHtml = '<div class="config-builder-code-preview-error">Renderingsfel: ' + esc(err.message || err) + '</div>';
}
preview.innerHTML = ''
+ '<div class="config-builder-code-preview-shell">'
+ '<div class="config-builder-code-preview-head">Head · ' + headList + '</div>'
+ '<div class="' + colClass + '">'
+ '<div class="config-builder-code-preview-content">'
+ '<div style="font-size:11px;font-weight:800;color:#526277;text-transform:uppercase;letter-spacing:.4px">Content (din kod)</div>'
+ contentHtml
+ '</div>'
+ '<div class="config-builder-code-preview-sidebar">'
+ '<div style="font-size:11px;font-weight:800;color:#526277;text-transform:uppercase;letter-spacing:.4px;margin-bottom:8px">Sidebar</div>'
+ sidebarList
+ '</div>'
+ '</div>'
+ '<div class="config-builder-code-preview-footer">Footer · ' + esc(footerLabel) + '</div>'
+ '</div>';
}
function onCodeInput(){
syncCurrentFromForm();
refreshCodePreview();
}
function insertCodeSnippet(key){
var snippet = codeSnippets[key];
if(!snippet) return;
var editor = document.getElementById('calcBuilderCode');
if(!editor) return;
var existing = editor.value || '';
editor.value = existing ? existing + '\n\n' + snippet : snippet;
syncCurrentFromForm();
refreshCodePreview();
}
function renderList(){
var box = document.getElementById('calcBuilderList');
if(!box) return;
var term = (document.getElementById('calcBuilderSearch').value || '').toLowerCase().trim();
var items = state.configurators.filter(function(item){
var hay = [item.title, item.category_label, item.category].join(' ').toLowerCase();
return !term || hay.indexOf(term) !== -1;
});
if(!items.length){
box.innerHTML = '<div class="config-builder-empty">Inga konfiguratorer ännu.</div>';
return;
}
box.innerHTML = items.map(function(item){
var active = state.current && state.current.category === item.category ? ' is-active' : '';
return '<div class="config-builder-item'+active+'">'
+ '<button type="button" class="config-builder-item-main" onclick="CalcBuilder.select(\''+esc(item.category)+'\')">'
+ '<strong>'+esc(item.title || item.category_label || item.category)+'</strong>'
+ '<span>'+esc(item.category_label || item.category)+'</span>'
+ '<span>'+esc((item.builder_mode === 'advanced' ? 'Avancerad' : (item.builder_mode === 'code' ? 'Code' : 'Enkel')) + ' • v' + (item.version || '1.0.0'))+'</span>'
+ '</button>'
+ '<button type="button" class="config-builder-item-preview" onclick="CalcBuilder.previewSaved(\''+esc(item.category)+'\')">Öppna sida</button>'
+ '</div>';
}).join('');
}
function field(label, key, value){
return '<label><span>'+label+'</span><input class="config-builder-input" data-key="'+key+'" value="'+esc(value)+'" oninput="CalcBuilder.refreshPreview()"></label>';
}
function selectField(label, key, value, options){
return '<label><span>'+label+'</span><select class="config-builder-input" data-key="'+key+'" onchange="CalcBuilder.renderBlocks()">'
+ options.map(function(opt){
return '<option value="'+esc(opt.v)+'"' + (String(value) === String(opt.v) ? ' selected' : '') + '>' + esc(opt.l) + '</option>';
}).join('')
+ '</select></label>';
}
function textareaField(label, key, value, hidden){
return '<label class="is-wide"' + (hidden ? ' style="display:none"' : '') + '><span>'+label+'</span><textarea class="config-builder-textarea" data-key="'+key+'" oninput="CalcBuilder.refreshPreview()">'+esc(value)+'</textarea></label>';
}
function blockCard(block, index){
var showOptions = block.type === 'cards' || block.type === 'select';
return '<div class="config-block-card" data-index="'+index+'">'
+ '<div class="config-block-head">'
+ '<div class="config-block-title"><span class="config-block-index">'+(index + 1)+'</span><span>'+esc(block.label || block.field || ('Block ' + (index + 1)))+'</span></div>'
+ '<div class="config-block-actions">'
+ '<button type="button" class="config-builder-btn" onclick="CalcBuilder.moveBlock('+index+', -1)">Upp</button>'
+ '<button type="button" class="config-builder-btn" onclick="CalcBuilder.moveBlock('+index+', 1)">Ner</button>'
+ '<button type="button" class="config-builder-btn" onclick="CalcBuilder.removeBlock('+index+')">Ta bort</button>'
+ '</div>'
+ '</div>'
+ '<div class="config-block-fields">'
+ field('ID', 'id', block.id)
+ field('State-fält', 'field', block.field)
+ field('Titel', 'label', block.label)
+ selectField('Typ', 'type', block.type, [{v:'cards',l:'Cards'},{v:'select',l:'Select'},{v:'number',l:'Number'},{v:'toggle',l:'Toggle'},{v:'range',l:'Range'}])
+ textareaField('Beskrivning', 'description', block.description, false)
+ field('Placeholder', 'placeholder', block.placeholder)
+ field('Enhet', 'unit', block.unit)
+ field('Default', 'defaultValue', block.defaultValue)
+ field('Min', 'min', block.min)
+ field('Max', 'max', block.max)
+ field('Step', 'step', block.step)
+ field('Synlig om fält', 'visibleField', block.visibleField)
+ selectField('Villkor', 'visibleOperator', block.visibleOperator, [{v:'equals',l:'='},{v:'notEquals',l:'!='}])
+ field('Villkorsvärde', 'visibleValue', block.visibleValue)
+ textareaField('Val (value|label|description)', 'optionsText', block.optionsText, !showOptions)
+ '</div>'
+ '</div>';
}
function renderBlocks(){
var box = document.getElementById('calcBuilderBlocks');
if(!box) return;
var blocks = (state.current && state.current.blocks) || [];
if(!blocks.length){
box.innerHTML = '<div class="config-builder-empty">Lägg till första blocket för att börja bygga konfiguratorn.</div>';
refreshPreview();
return;
}
box.innerHTML = blocks.map(function(block, index){
return blockCard(normalizeBlock(block, index), index);
}).join('');
}
function refreshPreview(){
syncCurrentFromForm();
toggleAdvancedMode();
var preview = document.getElementById('calcBuilderPreview');
if(!preview) return;
var schema = { blocks: state.current.blocks.map(serializeBlock) };
var regions = deriveRegions(state.current);
var headModules = (regions.head || []).map(function(item){
return '<span class="config-preview-module">' + esc(moduleLabels[item] || item) + '</span>';
}).join('');
var contentModules = (regions.content || []).map(function(item){
return '<span class="config-preview-module">' + esc(moduleLabels[item] || item) + '</span>';
}).join('');
var sidebarModules = (regions.sidebar || []).map(function(item){
return '<span class="config-preview-module">' + esc(moduleLabels[item] || item) + '</span>';
}).join('');
var configBlocks = state.current.builderMode === 'advanced'
? (schema.blocks.length && window.CalcBlocks ? window.CalcBlocks.render(schema.blocks, {}) : '<div class="config-builder-empty">Inga block ännu.</div>')
: '<div class="config-builder-empty">Enkel kalkylator använder produktkatalogens färdiga kalkyl för vald kategori.</div>';
var columnsClass = state.current.sidebarPosition === 'left' ? 'config-preview-columns is-left' : 'config-preview-columns';
preview.innerHTML = ''
+ '<div class="config-preview-layout">'
+ '<div class="config-preview-head">'
+ '<div class="config-preview-title"><span>Head</span><span>' + esc(state.current.builderMode === 'advanced' ? 'Avancerad kalkylator' : 'Enkel kalkylator') + '</span></div>'
+ '<div class="config-preview-module-list">' + (headModules || '<div class="config-builder-empty">Inga head-moduler.</div>') + '</div>'
+ '</div>'
+ '<div class="' + columnsClass + '">'
+ '<div class="config-preview-content">'
+ '<div class="config-preview-title"><span>Content</span><span>Kategori: ' + esc(state.current.category || 'Ingen vald') + '</span></div>'
+ '<div class="config-preview-module-list">' + (contentModules || '<div class="config-builder-empty">Inga content-moduler.</div>') + '</div>'
+ '<div>' + configBlocks + '</div>'
+ '</div>'
+ '<div class="config-preview-sidebar">'
+ '<div class="config-preview-title"><span>Sidebar</span><span>' + esc(state.current.sidebarPosition === 'left' ? 'Vänster' : 'Höger') + '</span></div>'
+ '<div class="config-preview-module-list">' + (sidebarModules || '<div class="config-builder-empty">Inga sidebar-moduler.</div>') + '</div>'
+ '</div>'
+ '</div>'
+ '<div class="config-preview-head">'
+ '<div class="config-preview-title"><span>Footer</span><span>' + esc(state.current.footerMode === 'default' ? 'Standard footer' : 'Ingen footer') + '</span></div>'
+ '</div>'
+ '</div>';
}
async function loadCategories(){
var res = await fetch('/api/categories.php?all=1');
var data = await res.json();
state.categories = data.categories || [];
renderCategoryOptions();
}
async function loadConfigurators(){
var res = await fetch('/api/calc_builder.php');
var data = await res.json();
state.configurators = data.configurators || [];
renderList();
}
async function previewSaved(categoryId){
await select(categoryId);
openPreview();
}
async function select(categoryId){
if(!categoryId){
createNew();
return;
}
var res = await fetch('/api/calc_builder.php?category=' + encodeURIComponent(categoryId));
var data = await res.json();
if(!data.success || !data.configurator){
status('Kunde inte läsa konfiguratorn.', 'error');
return;
}
state.current = {
id: data.configurator.id || null,
title: data.configurator.title || '',
version: data.configurator.version || '1.0.0',
notes: data.configurator.notes || '',
category: data.configurator.category || '',
builderMode: data.configurator.builder_mode || (data.configurator.base_source === 'custom-schema' ? 'advanced' : 'simple'),
headPreset: data.configurator.head_preset || 'current-default',
footerMode: data.configurator.footer_mode || 'none',
sidebarModule: data.configurator.sidebar_module || ((data.configurator.regions && data.configurator.regions.sidebar && data.configurator.regions.sidebar[0]) || 'price-summary'),
baseSource: data.configurator.base_source || 'product-catalog',
sidebarPosition: data.configurator.sidebar_position || 'right',
regions: data.configurator.regions || deriveRegions(data.configurator),
blocks: (data.configurator.blocks || []).map(normalizeBlock),
code: data.configurator.code || ''
};
applyCurrentToForm();
renderList();
status('Konfigurator laddad.', 'success');
}
function createNew(){
state.current = blankConfigurator();
applyCurrentToForm();
renderList();
status('Ny konfigurator klar att byggas.', 'success');
}
function addBlock(){
if(!state.current) createNew();
syncCurrentFromForm();
state.current.blocks.push(normalizeBlock({}, state.current.blocks.length));
renderBlocks();
refreshPreview();
}
function removeBlock(index){
syncCurrentFromForm();
state.current.blocks.splice(index, 1);
renderBlocks();
refreshPreview();
}
function moveBlock(index, direction){
syncCurrentFromForm();
var next = index + direction;
if(next < 0 || next >= state.current.blocks.length) return;
var temp = state.current.blocks[index];
state.current.blocks[index] = state.current.blocks[next];
state.current.blocks[next] = temp;
renderBlocks();
refreshPreview();
}
function duplicateLastBlock(){
syncCurrentFromForm();
if(!state.current || !state.current.blocks.length){
status('Det finns inga block att duplicera ännu.', 'error');
return;
}
var last = JSON.parse(JSON.stringify(state.current.blocks[state.current.blocks.length - 1]));
last.id = (last.id || 'block') + '-copy';
state.current.blocks.push(last);
renderBlocks();
refreshPreview();
}
function openPreview(){
syncCurrentFromForm();
if(!state.current || !state.current.category){
status('Välj kategori först.', 'error');
return;
}
if(typeof window.navigateTo === 'function') window.navigateTo('konfigurator');
if(typeof window.showKalkylConfig === 'function') window.showKalkylConfig(false, false, true);
setTimeout(function(){
var sel = document.getElementById('categorySelect');
if(sel) sel.value = state.current.category || '';
if(typeof window.changeCategory === 'function') window.changeCategory();
}, 40);
setTimeout(function(){
var sel = document.getElementById('categorySelect');
if(sel && sel.value !== (state.current.category || '')) sel.value = state.current.category || '';
if(typeof window.changeCategory === 'function') window.changeCategory();
}, 180);
}
async function saveCurrent(){
syncCurrentFromForm();
if(!state.current.title || !state.current.category){
status('Namn och kategori krävs.', 'error');
return;
}
var payload = {
id: state.current.id,
title: state.current.title,
category: state.current.category,
version: state.current.version || '1.0.0',
notes: state.current.notes || '',
builder_mode: state.current.builderMode || 'simple',
head_preset: state.current.headPreset || 'current-default',
sidebar_module: state.current.sidebarModule || 'price-summary',
footer_mode: state.current.footerMode || 'none',
base_source: state.current.baseSource || 'product-catalog',
sidebar_position: state.current.sidebarPosition || 'right',
regions: deriveRegions(state.current),
blocks: (state.current.builderMode === 'advanced' ? state.current.blocks.map(serializeBlock) : []),
code: state.current.builderMode === 'code' ? (state.current.code || '') : ''
};
var res = await fetch('/api/calc_builder.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
});
var data = await res.json();
if(!data.success){
status(data.error || 'Kunde inte spara konfiguratorn.', 'error');
return;
}
state.current.id = data.id || state.current.id;
await loadConfigurators();
if(typeof window.loadCalcSchemasFromAPI === 'function') window.loadCalcSchemasFromAPI(true);
status('Konfiguratorn är sparad och kan nu läsas av calc_config.', 'success');
}
async function init(){
if(init._done) return;
init._done = true;
await loadCategories();
await loadConfigurators();
createNew();
}
window.CalcBuilder = {
init: init,
createNew: createNew,
renderList: renderList,
addBlock: addBlock,
removeBlock: removeBlock,
moveBlock: moveBlock,
duplicateLastBlock: duplicateLastBlock,
saveCurrent: saveCurrent,
openPreview: openPreview,
refreshPreview: refreshPreview,
refreshCodePreview: refreshCodePreview,
renderBlocks: renderBlocks,
select: select,
previewSaved: previewSaved,
setMode: setMode,
onCodeInput: onCodeInput,
insertCodeSnippet: insertCodeSnippet
};
})();