/* App-Controller — UI-Bindings, State-Sync, Excel */ /* global window, document */ (() => { const { Calc, Store, Excel } = window; /** @type {Array} */ let projects = Store.load(); let currentId = Store.getCurrentId(); const $ = (sel) => document.querySelector(sel); const $$ = (sel) => Array.from(document.querySelectorAll(sel)); const fmtEur = (v) => new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, }).format(v || 0); const ensureProject = () => { if (projects.length === 0) { const p = Store.makeDefault('Neues Projekt'); projects.push(p); currentId = p.id; Store.save(projects); Store.setCurrentId(currentId); } else if (!currentId || !projects.find((p) => p.id === currentId)) { currentId = projects[0].id; Store.setCurrentId(currentId); } }; const getCurrent = () => projects.find((p) => p.id === currentId); const renderProjects = () => { const nav = $('#projects'); nav.innerHTML = ''; projects.forEach((p) => { const el = document.createElement('div'); el.className = 'project-item' + (p.id === currentId ? ' active' : ''); el.textContent = p.projectName || 'Unbenannt'; el.title = p.projectName || ''; el.addEventListener('click', () => { currentId = p.id; Store.setCurrentId(currentId); renderProjects(); syncFormFromState(); recalc(); }); nav.appendChild(el); }); }; const syncFormFromState = () => { const p = getCurrent(); if (!p) return; Store.FIELDS.forEach((f) => { const el = document.querySelector(`[data-field="${f}"]`); if (!el) return; el.value = p[f] ?? ''; }); $('#current-project-name').textContent = p.projectName || 'Unbenanntes Projekt'; }; const syncStateFromForm = () => { const p = getCurrent(); if (!p) return; Store.FIELDS.forEach((f) => { const el = document.querySelector(`[data-field="${f}"]`); if (!el) return; const raw = el.value; if (el.type === 'number') { p[f] = raw === '' ? 0 : parseFloat(raw); } else { p[f] = raw; } }); p.updatedAt = new Date().toISOString(); Store.save(projects); $('#current-project-name').textContent = p.projectName || 'Unbenanntes Projekt'; }; const recalc = () => { const p = getCurrent(); if (!p) return; const r = Calc.calculate(p); $('#r-unitNet').textContent = fmtEur(r.unitNet); $('#r-unitGross').textContent = fmtEur(r.unitGross); $('#r-totalGross').textContent = fmtEur(r.totalGross); $('#r-margin').textContent = fmtEur(r.margin); $('#r-c1').textContent = fmtEur(r.materialCost); $('#r-c2').textContent = fmtEur(r.machineCost); $('#r-c3').textContent = fmtEur(r.energyCost); $('#r-c4').textContent = fmtEur(r.postCost); $('#r-c5').textContent = fmtEur(r.totalProduction); $('#r-c6').textContent = fmtEur(r.scrapSurcharge); $('#r-c7').textContent = fmtEur(r.subtotalNet); $('#r-c9').textContent = fmtEur(r.customerNet); }; const attachFieldListeners = () => { $$('[data-field]').forEach((el) => { el.addEventListener('input', () => { syncStateFromForm(); if (el.dataset.field !== 'projectName') { recalc(); } else { renderProjects(); } }); }); }; const newProject = () => { const p = Store.makeDefault('Neues Projekt ' + (projects.length + 1)); projects.push(p); currentId = p.id; Store.save(projects); Store.setCurrentId(currentId); renderProjects(); syncFormFromState(); recalc(); }; const duplicateProject = () => { const cur = getCurrent(); if (!cur) return; const clone = { ...cur, id: 'p_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7), projectName: (cur.projectName || 'Projekt') + ' (Kopie)', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; projects.push(clone); currentId = clone.id; Store.save(projects); Store.setCurrentId(currentId); renderProjects(); syncFormFromState(); recalc(); }; const deleteProject = () => { if (projects.length === 0) return; const cur = getCurrent(); const ok = confirm(`Projekt "${cur?.projectName || 'Unbenannt'}" wirklich loeschen?`); if (!ok) return; projects = projects.filter((p) => p.id !== currentId); if (projects.length === 0) { ensureProject(); } else { currentId = projects[0].id; Store.setCurrentId(currentId); } Store.save(projects); renderProjects(); syncFormFromState(); recalc(); }; const exportCurrent = () => { const p = getCurrent(); if (!p) return; const r = Calc.calculate(p); try { Excel.exportXlsx(p, r); } catch (e) { alert('Export fehlgeschlagen: ' + e.message); } }; const importFile = async (file) => { try { const updates = await Excel.importXlsx(file); const p = getCurrent(); if (!p) return; Object.entries(updates).forEach(([k, v]) => { if (Store.FIELDS.includes(k)) { if (typeof p[k] === 'number' || ['materialCostPerKg','materialUsageG','printTimeH','machineRate','powerKwh','powerPrice','postMin','postRate','packagingCost','shippingCost','setupCost','scrapPct','marginPct','quantity','individualAdjustment','vatPct'].includes(k)) { const n = parseFloat(v); p[k] = Number.isFinite(n) ? n : 0; } else { p[k] = String(v ?? ''); } } }); p.updatedAt = new Date().toISOString(); Store.save(projects); renderProjects(); syncFormFromState(); recalc(); $('#dropzone').classList.remove('visible'); } catch (e) { alert('Import fehlgeschlagen: ' + e.message); } }; const initDragDrop = () => { const dz = $('#dropzone'); const show = () => dz.classList.add('visible'); const hide = () => dz.classList.remove('visible'); ['dragenter', 'dragover'].forEach((ev) => { window.addEventListener(ev, (e) => { if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files')) { e.preventDefault(); show(); dz.classList.add('dragover'); } }); }); ['dragleave', 'dragend'].forEach((ev) => { dz.addEventListener(ev, () => dz.classList.remove('dragover')); }); dz.addEventListener('drop', (e) => { e.preventDefault(); dz.classList.remove('dragover'); hide(); const f = e.dataTransfer?.files?.[0]; if (f) importFile(f); }); window.addEventListener('drop', (e) => { e.preventDefault(); hide(); }); window.addEventListener('dragleave', (e) => { if (e.clientX === 0 && e.clientY === 0) hide(); }); }; const initButtons = () => { $('#btn-new').addEventListener('click', newProject); $('#btn-duplicate').addEventListener('click', duplicateProject); $('#btn-delete').addEventListener('click', deleteProject); $('#btn-export').addEventListener('click', exportCurrent); $('#btn-import').addEventListener('click', () => $('#file-input').click()); $('#file-input').addEventListener('change', (e) => { const f = e.target.files?.[0]; if (f) importFile(f); e.target.value = ''; }); }; const init = () => { ensureProject(); renderProjects(); syncFormFromState(); attachFieldListeners(); initButtons(); initDragDrop(); recalc(); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();