diff --git a/backend/static/css/components.css b/backend/static/css/components.css index 60cdfb4..9e16ea7 100644 --- a/backend/static/css/components.css +++ b/backend/static/css/components.css @@ -7435,3 +7435,84 @@ svg.empty-state-icon { } .exp-icon-btn:hover { color: var(--c-text); border-color: var(--c-text-muted); } .exp-icon-btn--danger:hover { color: var(--c-danger); border-color: var(--c-danger); } + +/* Ausgaben-Formular — Kategorie-Kacheln */ +.exp-kat-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--space-2); +} +.exp-kat-tile { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-1); + padding: var(--space-3) var(--space-2); + border: 1.5px solid var(--c-border); + border-radius: var(--radius-md); + cursor: pointer; + transition: border-color .15s, background .15s; + background: var(--c-surface); + user-select: none; +} +.exp-kat-tile:hover { border-color: var(--c-primary); } +.exp-kat-tile--sel { + border-color: var(--c-primary); + background: var(--c-primary-subtle); +} +.exp-kat-tile-icon { font-size: 1.4rem; line-height: 1; } +.exp-kat-tile-label { + font-size: var(--text-xs); + font-weight: var(--weight-medium); + color: var(--c-text-secondary); + text-align: center; +} +.exp-kat-tile--sel .exp-kat-tile-label { color: var(--c-primary); } + +/* Betrag-Feld mit €-Prefix */ +.exp-betrag-wrap { + position: relative; + display: flex; + align-items: center; +} +.exp-betrag-prefix { + position: absolute; + left: var(--space-3); + color: var(--c-text-muted); + font-weight: var(--weight-semibold); + pointer-events: none; +} +.exp-betrag-input { padding-left: calc(var(--space-3) + 14px + var(--space-2)) !important; } + +/* Form-Label Hint */ +.form-label-hint { color: var(--c-text-muted); font-weight: normal; font-size: var(--text-xs); } + +/* Wiederholungs-Sektion */ +.exp-repeat-section { + margin-top: var(--space-4); + padding-top: var(--space-4); + border-top: 1px solid var(--c-border-light); +} +.exp-repeat-toggle { + display: flex; + align-items: center; + gap: var(--space-2); + cursor: pointer; + font-size: var(--text-sm); + font-weight: var(--weight-medium); + color: var(--c-text); + user-select: none; +} +.exp-repeat-toggle-box { + width: 18px; + height: 18px; + border: 1.5px solid var(--c-border); + border-radius: var(--radius-sm); + background: var(--c-surface); + flex-shrink: 0; + transition: background .15s, border-color .15s; +} +.exp-repeat-toggle input:checked ~ .exp-repeat-toggle-box { + background: var(--c-primary); + border-color: var(--c-primary); +} diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 59bd4d3..850b1a0 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '606'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '607'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.2.1'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; diff --git a/backend/static/js/pages/expenses.js b/backend/static/js/pages/expenses.js index 15d6994..3bed1dd 100644 --- a/backend/static/js/pages/expenses.js +++ b/backend/static/js/pages/expenses.js @@ -641,64 +641,106 @@ window.Page_expenses = (() => { const isEdit = !!entry; const today = new Date().toISOString().split('T')[0]; const formId = 'exp-form'; + const selKat = entry?.kategorie || 'sonstiges'; const dogOptions = (_appState.dogs || []).map(d => `` ).join(''); - const katOptions = KATEGORIEN.map(k => - `` - ).join(''); + // Kategorie-Kacheln statt Dropdown + const katKacheln = KATEGORIEN.map(k => ` + `).join(''); const body = ` -
-
- - -
+ +
- +
${katKacheln}
-
- - + +
+
+ +
+ + +
+
+
+ + +
+ ${dogOptions ? `
- - + ${dogOptions}
` : ''} +
- - Notiz (optional) + + placeholder="z.B. Hundesteuer 2026, Allianz Haftpflicht …">
+ + ${!isEdit ? ` +
+ + +
` : ''} + `; const footer = isEdit ? ` - - - ` : ` + + ` : ` + + `; - const modal = UI.modal.open({ - title: isEdit ? 'Ausgabe bearbeiten' : 'Neue Ausgabe', - body, - footer, + const modal = UI.modal.open({ title: isEdit ? 'Ausgabe bearbeiten' : 'Neue Ausgabe', body, footer }); + + // Kategorie-Kacheln interaktiv + modal.querySelectorAll('.exp-kat-tile').forEach(tile => { + tile.addEventListener('click', () => { + modal.querySelectorAll('.exp-kat-tile').forEach(t => t.classList.remove('exp-kat-tile--sel')); + tile.classList.add('exp-kat-tile--sel'); + }); + }); + + // Wiederholen-Toggle + modal.querySelector('#exp-wiederholen')?.addEventListener('change', e => { + modal.querySelector('#exp-repeat-opts').style.display = e.target.checked ? 'block' : 'none'; }); if (isEdit) { @@ -718,8 +760,8 @@ window.Page_expenses = (() => { modal.querySelector(`#${formId}`)?.addEventListener('submit', async (ev) => { ev.preventDefault(); - const fd = UI.formData(ev.target); - const body = { + const fd = UI.formData(ev.target); + const payload = { kategorie: fd.kategorie, betrag: parseFloat(fd.betrag), datum: fd.datum, @@ -729,11 +771,21 @@ window.Page_expenses = (() => { try { if (isEdit) { - await API.patch(`/expenses/${entry.id}`, body); + await API.patch(`/expenses/${entry.id}`, payload); UI.toast.success('Ausgabe aktualisiert.'); } else { - await API.post('/expenses', body); - UI.toast.success('Ausgabe gespeichert.'); + await API.post('/expenses', payload); + // Auch als Dauerauftrag anlegen wenn gewünscht + if (fd.wiederholen) { + await API.post('/expenses/recurring', { + ...payload, + haeufigkeit: fd.haeufigkeit || 'jaehrlich', + startdatum: fd.datum, + }); + UI.toast.success('Ausgabe + Dauerauftrag gespeichert.'); + } else { + UI.toast.success('Ausgabe gespeichert.'); + } } UI.modal.close(); _invalidateCache(); diff --git a/backend/static/sw.js b/backend/static/sw.js index 1cef344..177e86b 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v606'; +const CACHE_VERSION = 'by-v607'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache