(function(){
function qs(el, sel){ return el ? el.querySelector(sel):null; }
function qsa(el, sel){ return Array.prototype.slice.call((el||document).querySelectorAll(sel)); }
function toFloat(v){
var n=parseFloat(String(v).replace(',', '.'));
return isNaN(n) ? 0:n;
}
function roundToStep(val, step){
step=step > 0 ? step:1;
return Math.round(val / step) * step;
}
function parseJSON(raw, fallback){
if(!raw) return fallback;
try { return JSON.parse(raw); } catch(e){ return fallback; }}
function toBool(value, fallback){
if(typeof value==='boolean') return value;
if(value===null||value===undefined||value==='') return !!fallback;
return ['1', 'true', 'yes', 'on'].indexOf(String(value).toLowerCase())!==-1;
}
function numberFormatter(){
try {
return new Intl.NumberFormat('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
} catch(e){
return { format: function(n){ return (Math.round(n * 100) / 100).toFixed(2).replace('.', ','); }};}}
function currencyFormatter(){
try {
return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' });
} catch(e){
return { format: function(n){ return (Math.round(n * 100) / 100).toFixed(2) + ' €'; }};}}
function initBuilder(root){
var ajaxUrl=root.getAttribute('data-ajax-url');
var nonce=root.getAttribute('data-nonce');
var templates=parseJSON(root.getAttribute('data-templates'), {});
var selectedTemplate=String(root.getAttribute('data-selected-template')||'');
var defaultTaxMode=root.getAttribute('data-default-tax-mode')||'without';
var taxSummaryNotes={
without: root.getAttribute('data-tax-summary-without')||'0% MwSt. gemäß §12 Abs. 3 UStG.',
with: root.getAttribute('data-tax-summary-with')||'zzgl. gesetzl. MwSt.'
};
var priceNotes={
without: root.getAttribute('data-price-note-without')||'inkl. 0% MwSt.',
with: root.getAttribute('data-price-note-with')||'zzgl. MwSt.'
};
if(selectedTemplate==='0') selectedTemplate='';
if(defaultTaxMode!=='with') defaultTaxMode='without';
var fmt=currencyFormatter();
var numFmt=numberFormatter();
var defaultWithTaxRate=toFloat(root.getAttribute('data-default-with-tax-rate'));
if(defaultWithTaxRate <=0) defaultWithTaxRate=19;
var items=[];
var categories=[];
var itemsById={};
var itemsCol=qs(root, '.gsqb-col-items');
function refreshCollections(){
items=qsa(root, '.gsqb-item');
categories=qsa(root, '.gsqb-cat');
itemsById={};
items.forEach(function(item, index){
if(!item.hasAttribute('data-gsqb-original-index')){
item.setAttribute('data-gsqb-original-index', String(index));
}
var id=String(parseInt(item.getAttribute('data-id'), 10)||'');
if(id) itemsById[id]=item;
});
categories.forEach(function(cat, index){
if(!cat.hasAttribute('data-gsqb-original-index')){
cat.setAttribute('data-gsqb-original-index', String(index));
}});
}
refreshCollections();
function getTemplateConfig(templateId){
var key=String(templateId||'');
if(!key) return null;
var raw=templates[key]||templates[templateId]||null;
if(!raw||typeof raw!=='object') return null;
if(raw.items&&typeof raw.items==='object'&&!Array.isArray(raw.items)){
return {
items: raw.items,
order: Array.isArray(raw.order) ? raw.order.map(function(id){ return String(parseInt(id, 10)||id||''); }):Object.keys(raw.items)
};}
return {
items: raw,
order: Object.keys(raw)
};}
function getTemplateMap(templateId){
var config=getTemplateConfig(templateId);
return config ? config.items:null;
}
function getTemplateOrderMap(templateId){
var config=getTemplateConfig(templateId);
var orderMap={};
if(!config||!Array.isArray(config.order)) return orderMap;
config.order.forEach(function(id, index){
id=String(parseInt(id, 10)||id||'');
if(id&&!Object.prototype.hasOwnProperty.call(orderMap, id)){
orderMap[id]=index;
}});
return orderMap;
}
function normalizeTemplateEntry(entry){
if(entry&&typeof entry==='object'){
return {
qty: toFloat(entry.qty),
show: Object.prototype.hasOwnProperty.call(entry, 'show') ? toBool(entry.show, true):true,
locked: Object.prototype.hasOwnProperty.call(entry, 'locked') ? toBool(entry.locked, false):false
};}
return { qty: toFloat(entry), show: true, locked: false };}
function setItemLocked(item, locked){
var input=qs(item, '.gsqb-input');
if(!input) return;
var autoSrc=parseInt(item.getAttribute('data-auto-src'), 10)||0;
item.setAttribute('data-template-locked', locked ? '1':'0');
input.disabled = !!autoSrc||!!locked;
}
function resetTemplateLocking(){
items.forEach(function(item){
setItemLocked(item, false);
});
}
function syncTemplateVisibility(templateId){
var map=getTemplateMap(templateId);
var hasTemplate = !!map;
items.forEach(function(item){
var itemId=String(parseInt(item.getAttribute('data-id'), 10)||'');
var show=true;
if(hasTemplate){
if(!Object.prototype.hasOwnProperty.call(map, itemId)){
show=false;
}else{
show=normalizeTemplateEntry(map[itemId]).show;
}}
item.style.display=show ? '':'none';
});
categories.forEach(function(cat){
var hasVisibleItems=qsa(cat, '.gsqb-item').some(function(item){
return item.style.display!=='none';
});
cat.style.display=hasVisibleItems ? '':'none';
});
}
function getCategoryOrderIndex(cat, orderMap, hasOrder){
if(!hasOrder){
return 100000 + (parseInt(cat.getAttribute('data-gsqb-original-index'), 10)||0);
}
var indexes=qsa(cat, '.gsqb-item').reduce(function(out, item){
if(item.style.display==='none') return out;
var id=String(parseInt(item.getAttribute('data-id'), 10)||'');
if(Object.prototype.hasOwnProperty.call(orderMap, id)){
out.push(orderMap[id]);
}
return out;
}, []);
if(indexes.length){
return Math.min.apply(Math, indexes);
}
return 100000 + (parseInt(cat.getAttribute('data-gsqb-original-index'), 10)||0);
}
function reorderTemplateLayout(templateId){
var orderMap=getTemplateOrderMap(templateId);
var hasOrder=Object.keys(orderMap).length > 0;
categories.forEach(function(cat){
var container=qs(cat, '.gsqb-cat-items');
if(!container) return;
var catItems=qsa(container, '.gsqb-item');
catItems.sort(function(a, b){
var aId=String(parseInt(a.getAttribute('data-id'), 10)||'');
var bId=String(parseInt(b.getAttribute('data-id'), 10)||'');
var aHas=hasOrder&&Object.prototype.hasOwnProperty.call(orderMap, aId);
var bHas=hasOrder&&Object.prototype.hasOwnProperty.call(orderMap, bId);
if(aHas&&bHas){
return orderMap[aId] - orderMap[bId];
}
if(aHas!==bHas){
return aHas ? -1:1;
}
return (parseInt(a.getAttribute('data-gsqb-original-index'), 10)||0) - (parseInt(b.getAttribute('data-gsqb-original-index'), 10)||0);
});
catItems.forEach(function(item){
container.appendChild(item);
});
});
if(itemsCol){
var sortedCategories=categories.slice().sort(function(a, b){
var aOrder=getCategoryOrderIndex(a, orderMap, hasOrder);
var bOrder=getCategoryOrderIndex(b, orderMap, hasOrder);
if(aOrder===bOrder){
return (parseInt(a.getAttribute('data-gsqb-original-index'), 10)||0) - (parseInt(b.getAttribute('data-gsqb-original-index'), 10)||0);
}
return aOrder - bOrder;
});
sortedCategories.forEach(function(cat){
itemsCol.appendChild(cat);
});
}
refreshCollections();
}
function getQty(item){
var pricing=item.getAttribute('data-pricing');
var input=qs(item, '.gsqb-input');
if(!input) return 0;
if(pricing==='once'){
return input.checked ? 1:0;
}
return toFloat(input.value);
}
function setQty(item, qty){
var pricing=item.getAttribute('data-pricing');
var input=qs(item, '.gsqb-input');
if(!input) return;
if(pricing==='once'){
input.checked=qty > 0;
return;
}
input.value=qty;
}
function ensureTaxModeSelection(){
var checked=qs(root, '.gsqb-tax-mode:checked');
if(checked) return checked;
var fallback=qs(root, '.gsqb-tax-mode[value="' + defaultTaxMode + '"]')||qs(root, '.gsqb-tax-mode');
if(fallback){
fallback.checked=true;
}
return fallback;
}
function getTaxMode(){
var selected=ensureTaxModeSelection();
var value=selected ? String(selected.value||''):defaultTaxMode;
return value==='with' ? 'with':'without';
}
function getEffectiveVatRate(baseRate){
if(getTaxMode()!=='with') return 0;
baseRate=toFloat(baseRate);
return baseRate > 0 ? baseRate:defaultWithTaxRate;
}
function getTemplateDisplayTotal(card, taxMode){
var netTotal=toFloat(card.getAttribute('data-net-total'));
var grossTotal=toFloat(card.getAttribute('data-gross-total'));
if(taxMode==='with'){
if(grossTotal > netTotal) return grossTotal;
return netTotal * (1 + (defaultWithTaxRate / 100));
}
return netTotal;
}
function updateTemplatePrices(){
var taxMode=getTaxMode();
qsa(root, '.gsqb-template-card').forEach(function(card){
var valueEl=qs(card, '.gsqb-template-price-value');
if(!valueEl) return;
valueEl.textContent=numFmt.format(getTemplateDisplayTotal(card, taxMode));
});
}
function updateTaxUi(){
var taxMode=getTaxMode();
qsa(root, '.gsqb-tax-option').forEach(function(option){
var input=qs(option, '.gsqb-tax-mode');
option.classList.toggle('is-active', !!input&&input.checked);
});
var summaryNote=qs(root, '.gsqb-summary-tax-note');
if(summaryNote){
summaryNote.textContent=taxSummaryNotes[taxMode]||'';
}
qsa(root, '.gsqb-template-price-note').forEach(function(noteEl){
noteEl.textContent=priceNotes[taxMode]||'';
});
updateTemplatePrices();
}
function markSelectedTemplate(templateId){
qsa(root, '.gsqb-template-card').forEach(function(card){
var cardId=String(card.getAttribute('data-template-id')||'');
var btn=qs(card, '.gsqb-template-btn');
var defaultLabel=btn ? (btn.getAttribute('data-default-label')||'Konfigurieren'):'Konfigurieren';
var isSelected=templateId&&cardId===String(templateId);
card.classList.toggle('is-selected', isSelected);
if(btn){
btn.textContent=defaultLabel;
btn.setAttribute('aria-pressed', isSelected ? 'true':'false');
}});
}
function recalc(){
var qtyMap={};
items.forEach(function(item){
var id=parseInt(item.getAttribute('data-id'), 10);
qtyMap[id]=getQty(item);
});
items.forEach(function(item){
var autoSrc=parseInt(item.getAttribute('data-auto-src'), 10)||0;
if(!autoSrc) return;
var factor=toFloat(item.getAttribute('data-auto-factor'));
if(!factor) factor=1;
var baseQty=qtyMap[autoSrc]||0;
var step=toFloat(item.getAttribute('data-step'))||1;
var min=toFloat(item.getAttribute('data-min'))||0;
var max=toFloat(item.getAttribute('data-max'))||999999;
var newQty=roundToStep(baseQty * factor, step);
if(newQty < min) newQty=min;
if(newQty > max) newQty=max;
setQty(item, newQty);
var itemId=parseInt(item.getAttribute('data-id'), 10);
qtyMap[itemId]=newQty;
});
var sumNet=0;
var sumVat=0;
var sumGross=0;
items.forEach(function(item){
var qty=getQty(item);
var pricing=item.getAttribute('data-pricing');
var netUnit=toFloat(item.getAttribute('data-net-unit'));
var vatRate=getEffectiveVatRate(toFloat(item.getAttribute('data-vat')));
var netTotal=0;
if(pricing==='once'){
netTotal=qty > 0 ? netUnit:0;
}else{
netTotal=netUnit * qty;
}
var vatTotal=netTotal * (vatRate / 100);
var grossTotal=netTotal + vatTotal;
sumNet +=netTotal;
sumVat +=vatTotal;
sumGross +=grossTotal;
var lineEl=qs(item, '.gsqb-line-total');
if(lineEl){
lineEl.textContent=fmt.format(netTotal);
}});
var netEl=qs(root, '.gsqb-sum-net');
var vatEl=qs(root, '.gsqb-sum-vat');
var grossEl=qs(root, '.gsqb-sum-gross');
if(netEl) netEl.textContent=fmt.format(sumNet);
if(vatEl) vatEl.textContent=fmt.format(sumVat);
if(grossEl) grossEl.textContent=fmt.format(sumGross);
updateTaxUi();
}
function applyTemplate(templateId, options){
options=options||{};
var key=String(templateId||'');
var config=getTemplateConfig(templateId);
resetTemplateLocking();
if(!config){
selectedTemplate='';
markSelectedTemplate(selectedTemplate);
syncTemplateVisibility('');
reorderTemplateLayout('');
recalc();
return;
}
items.forEach(function(item){ setQty(item, 0); });
Object.keys(config.items).forEach(function(itemId){
var item=itemsById[String(itemId)];
if(!item) return;
var entry=normalizeTemplateEntry(config.items[itemId]);
setQty(item, entry.qty);
setItemLocked(item, entry.locked);
});
selectedTemplate=key;
markSelectedTemplate(selectedTemplate);
syncTemplateVisibility(selectedTemplate);
reorderTemplateLayout(selectedTemplate);
recalc();
if(options.scroll===false) return;
var target=qs(root, '.gsqb-title');
if(target&&typeof target.scrollIntoView==='function'){
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}}
items.forEach(function(item){
var input=qs(item, '.gsqb-input');
if(!input) return;
input.addEventListener('input', recalc);
input.addEventListener('change', recalc);
});
qsa(root, '.gsqb-tax-mode').forEach(function(input){
input.addEventListener('change', function(){
updateTaxUi();
recalc();
});
});
qsa(root, '.gsqb-template-btn').forEach(function(btn){
btn.addEventListener('click', function(event){
event.preventDefault();
applyTemplate(btn.getAttribute('data-template-id'));
});
});
qsa(root, '.gsqb-template-card').forEach(function(card){
card.addEventListener('click', function(event){
if(event.target&&event.target.closest('a, button, input, textarea, select, label')) return;
applyTemplate(card.getAttribute('data-template-id'));
});
});
var btn=qs(root, '.gsqb-submit');
var result=qs(root, '.gsqb-result');
var submitLabel=btn ? (btn.getAttribute('data-label')||btn.textContent):'Angebot erstellen';
function showResult(html){
if(!result) return;
result.innerHTML=html;
result.style.display='block';
}
function collectItems(){
var out=[];
items.forEach(function(item){
var id=parseInt(item.getAttribute('data-id'), 10);
var qty=getQty(item);
if(qty > 0){
out.push({ id: id, qty: qty });
}});
return out;
}
function validateRequiredField(field, emptyMsg, invalidMsg){
if(!field||!field.hasAttribute('required')) return '';
var value=(field.value||'').trim();
if(!value) return emptyMsg;
if(typeof field.checkValidity==='function'&&!field.checkValidity()){
return invalidMsg||emptyMsg;
}
return '';
}
function submit(){
var privacy=qs(root, '.gsqb-privacy-check');
if(privacy&&!privacy.checked){
showResult('Bitte stimmen Sie der Speicherung Ihrer Angaben zu.');
return;
}
var itemsSel=collectItems();
if(!itemsSel.length){
showResult('Bitte wählen Sie mindestens eine Position aus.');
return;
}
var salutationField=qs(root, '.gsqb-customer-salutation');
var nameField=qs(root, '.gsqb-customer-name');
var emailField=qs(root, '.gsqb-customer-email');
var phoneField=qs(root, '.gsqb-customer-phone');
var addressField=qs(root, '.gsqb-customer-address');
var noteField=qs(root, '.gsqb-project-note');
var taxMode=getTaxMode();
var validationError =
validateRequiredField(nameField, 'Bitte geben Sie Ihren Namen an.') ||
validateRequiredField(emailField, 'Bitte geben Sie eine E-Mail-Adresse an.', 'Bitte geben Sie eine gültige E-Mail-Adresse an.') ||
validateRequiredField(phoneField, 'Bitte geben Sie Ihre Telefonnummer an.');
if(validationError){
showResult(validationError);
return;
}
var salutation=salutationField ? (salutationField.value||''):'';
var name=nameField ? (nameField.value||''):'';
var email=emailField ? (emailField.value||''):'';
var phone=phoneField ? (phoneField.value||''):'';
var address=addressField ? (addressField.value||''):'';
var note=noteField ? (noteField.value||''):'';
if(!email){
showResult('Bitte geben Sie eine E-Mail-Adresse an.');
return;
}
if(emailField&&typeof emailField.checkValidity==='function'&&!emailField.checkValidity()){
showResult('Bitte geben Sie eine gültige E-Mail-Adresse an.');
return;
}
if(btn){
btn.disabled=true;
btn.textContent='Bitte warten…';
}
var data=new FormData();
data.append('action', 'gsqb_submit');
data.append('nonce', nonce);
data.append('items', JSON.stringify(itemsSel));
data.append('customer_salutation', salutation);
data.append('customer_name', name);
data.append('customer_email', email);
data.append('customer_phone', phone);
data.append('customer_address', address);
data.append('project_note', note);
data.append('template_id', selectedTemplate||'');
data.append('tax_mode', taxMode);
fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', body: data })
.then(function(r){ return r.json(); })
.then(function(resp){
if(btn){
btn.disabled=false;
btn.textContent=submitLabel;
}
if(!resp||!resp.success){
var msg=(resp&&resp.data&&resp.data.message) ? resp.data.message:'Fehler beim Erstellen des Angebots.';
showResult(msg);
return;
}
var d=resp.data;
var html=''
+ '<div><strong>✅ ' + (d.quote_number||'') + '</strong></div>'
+ '<div style="margin-top:6px;">' + (d.message||'Angebot erstellt.') + '</div>'
+ '<div style="margin-top:10px;display:flex;flex-wrap:wrap;gap:10px;">'
+ (d.pdf_url ? '<a href="' + d.pdf_url + '" target="_blank" rel="noopener">PDF/Druckansicht öffnen</a>':'')
+ (d.accept_url ? '<a href="' + d.accept_url + '" target="_blank" rel="noopener">Angebot annehmen</a>':'')
+ (d.order_url ? '<a href="' + d.order_url + '" target="_blank" rel="noopener">' + (d.order_label||'Angebot online bestellen') + '</a>':'')
+ '</div>';
showResult(html);
})
.catch(function(){
if(btn){
btn.disabled=false;
btn.textContent=submitLabel;
}
showResult('Fehler beim Senden. Bitte versuchen Sie es erneut.');
});
}
if(btn){
btn.addEventListener('click', submit);
}
ensureTaxModeSelection();
if(selectedTemplate&&getTemplateConfig(selectedTemplate)){
applyTemplate(selectedTemplate, { scroll: false });
}else{
resetTemplateLocking();
markSelectedTemplate(selectedTemplate);
syncTemplateVisibility('');
reorderTemplateLayout('');
updateTaxUi();
recalc();
}}
document.addEventListener('DOMContentLoaded', function(){
document.querySelectorAll('.gsqb-builder').forEach(function(root){
try { initBuilder(root); } catch(e){}});
});
})();