Budgetplaner mit Monatswerten je Kategorie, Monatsabschluss mit Prüflauf, Snapshot und Buchungssperre. Dazu drei Vorarbeiten in der Oberfläche. Budgetplaner - Migration 004: budgets, budget_positionen, monatsabschluesse - Zeilen sind der Kategoriebaum, Spalten die Monate; die ganze Tabelle wird in einem Formular gespeichert, damit mehrere Änderungen zusammen ankommen - Zwei Ansichten: Verlauf (Jahressumme, abgelaufene Monate zusammengefasst, laufender Monat mit Budget/Ist/Saldo, danach Folgemonate) und alle Monate - Ausgaben ohne Vorzeichen eingeben, es kommt aus der Kategorieart. Saldo ist Ist minus Budget: positiv heißt besser als geplant, negativ ist die Überschreitung und wird rot ausgewiesen - Übergeordnete Kategorien summieren ihre Nachfahren und sind nicht beplanbar - Ein Monat ohne Position ist "nicht geplant" und damit etwas anderes als "mit 0,00 geplant" - Wert auf Folgemonate übertragen, Budget als Kopie ins neue Jahr Monatsabschluss - Prüflauf über nicht kategorisierte Buchungen, nicht abgeglichene Umsätze und Splitsummen-Differenzen; er hält niemanden auf, sondern zeigt den Stand - Snapshot der Kontosalden und Kategoriesummen als JSON, bleibt auch nach einer Wiedereröffnung stehen - Abgeschlossene Monate sperren Buchungen in beide Richtungen: weder hinein noch heraus verschieben. Abschaltbar über monatsabschluss_sperrt_buchungen Vorarbeiten - .knopf.primaer trug einen eigenen margin-top und stand dadurch in jeder Knopfleiste tiefer als seine Nachbarn - sichtbar an "Abbrechen" neben "Konto anlegen". Der Abstand liegt jetzt allein bei der Knopfleiste - Kontengruppen werden wie Konten und Kategorien auf einer eigenen Formularseite gepflegt statt inline; dort hängt auch die Zuordnung der Konten (ankreuzen statt jedes Konto einzeln zu öffnen) - Der QIF-Export enthält keine Kontengruppen: kein Feld im !Account-Eintrag, nichts in der QSD-Datei. Import-Oberfläche und Doku sagen das jetzt - Die Baum-Einrückung lief über ein style-Attribut und wurde von der Content-Security-Policy verworfen; sie steht jetzt als data-ebene im Markup. Aus demselben Grund blendete app.js Erfolgsmeldungen nie aus Tests: 11_BudgetTest, 12_MonatsabschlussTest (76 Prüfungen), E2E planung.spec.js. Einzelheiten in docs/planung.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
65 lines
2.6 KiB
JavaScript
65 lines
2.6 KiB
JavaScript
/* Finanzmanager – Frontend-Hilfen.
|
||
*
|
||
* Bewusst schlank und ohne Framework.
|
||
*
|
||
* Bestätigungen hängen an <form data-bestaetigen="…"> und nicht als onsubmit
|
||
* im HTML: Die Content-Security-Policy erlaubt keine Inline-Skripte, ein
|
||
* onsubmit-Attribut würde stillschweigend verworfen – und dann löschte der
|
||
* Knopf ohne Rückfrage.
|
||
*/
|
||
|
||
(function () {
|
||
'use strict';
|
||
|
||
// Formulare mit data-bestaetigen fragen vor dem Absenden nach.
|
||
// In der Capture-Phase, damit die Rückfrage auch dann greift, wenn ein
|
||
// anderer Handler das Ereignis unterwegs stoppt.
|
||
document.addEventListener('submit', function (ereignis) {
|
||
const formular = ereignis.target;
|
||
if (!(formular instanceof HTMLFormElement)) {
|
||
return;
|
||
}
|
||
|
||
const frage = formular.dataset.bestaetigen;
|
||
if (frage && !window.confirm(frage)) {
|
||
ereignis.preventDefault();
|
||
ereignis.stopPropagation();
|
||
}
|
||
}, true);
|
||
|
||
// Kategoriebaum im Budgetplaner auf- und zuklappen. Die Zeilen kennen ihre
|
||
// Elternkategorie über data-eltern; von dort aus lässt sich der Teilbaum
|
||
// einsammeln, ohne ihn im HTML zu verschachteln (in einer Tabelle ginge das
|
||
// ohnehin nicht).
|
||
document.querySelectorAll('.klappknopf').forEach(function (knopf) {
|
||
knopf.setAttribute('aria-expanded', 'true');
|
||
|
||
knopf.addEventListener('click', function () {
|
||
const wurzel = knopf.dataset.klappt;
|
||
const offen = knopf.getAttribute('aria-expanded') !== 'false';
|
||
knopf.setAttribute('aria-expanded', offen ? 'false' : 'true');
|
||
|
||
const zeilen = Array.from(document.querySelectorAll('tr.kategoriezeile'));
|
||
const betroffen = new Set([wurzel]);
|
||
|
||
zeilen.forEach(function (zeile) {
|
||
const eltern = zeile.dataset.eltern;
|
||
if (eltern && betroffen.has(eltern)) {
|
||
betroffen.add(zeile.dataset.kategorie);
|
||
zeile.classList.toggle('zugeklappt', offen);
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
// Meldungen nach kurzer Zeit ausblenden – Fehler bleiben stehen.
|
||
// Über eine Klasse, nicht über element.style: Die Content-Security-Policy
|
||
// (style-src 'self') verwirft auch per JavaScript gesetzte Inline-Styles,
|
||
// die Meldung bliebe sonst einfach stehen.
|
||
document.querySelectorAll('.meldung-erfolg').forEach(function (element) {
|
||
window.setTimeout(function () {
|
||
element.classList.add('verblasst');
|
||
window.setTimeout(function () { element.remove(); }, 400);
|
||
}, 5000);
|
||
});
|
||
})();
|