diff --git a/web/src/finance.css b/web/src/finance.css index c23bfa3..125d20b 100644 --- a/web/src/finance.css +++ b/web/src/finance.css @@ -286,6 +286,14 @@ } /* Filterleiste. */ +.art-filter { + display: flex; + align-items: center; + gap: var(--sp-3); + flex-wrap: wrap; +} +.art-filter .checkline { margin-bottom: 0; } + .filter-bar { display: flex; gap: var(--sp-3); diff --git a/web/src/locales/de.json b/web/src/locales/de.json index 29b3cd4..4cede12 100644 --- a/web/src/locales/de.json +++ b/web/src/locales/de.json @@ -318,7 +318,9 @@ "empty": "Noch keine Buchungen.", "needAccount": "Lege zuerst ein Konto an, um Buchungen zu erfassen.", "allAccounts": "Alle Konten", - "allTypes": "Alle Typen", + "filterExpenses": "Ausgaben", + "filterIncome": "Einnahmen", + "filterTransfers": "Überträge", "deleteConfirm": "Diese Buchung löschen?", "amountRequired": "Bitte einen Betrag eingeben.", "accountRequired": "Bitte ein Konto wählen.", diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 8a89d1d..a422986 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -318,7 +318,9 @@ "empty": "No transactions yet.", "needAccount": "Create an account first to record transactions.", "allAccounts": "All accounts", - "allTypes": "All types", + "filterExpenses": "Expenses", + "filterIncome": "Income", + "filterTransfers": "Transfers", "deleteConfirm": "Delete this transaction?", "amountRequired": "Please enter an amount.", "accountRequired": "Please choose an account.", diff --git a/web/src/pages/Transactions.jsx b/web/src/pages/Transactions.jsx index b26787f..9115fd8 100644 --- a/web/src/pages/Transactions.jsx +++ b/web/src/pages/Transactions.jsx @@ -59,7 +59,11 @@ export default function Transactions() { const [accounts, setAccounts] = useState([]); const [groups, setGroups] = useState([]); const [tags, setTags] = useState([]); - const [filter, setFilter] = useState({ account_id: "", type: "" }); + // Drei Haken statt eines Typs: Man will oft zwei Arten gleichzeitig sehen + // und eine dritte weglassen, und genau das ging mit einer Einfachauswahl nicht. + const [filter, setFilter] = useState({ + account_id: "", arten: { expense: true, income: true, uebertrag: true }, + }); const [form, setForm] = useState(null); const [editing, setEditing] = useState(null); const [busy, setBusy] = useState(false); @@ -92,7 +96,7 @@ export default function Transactions() { // Verknüpfungen getrennt: Sie sind eine Schicht über den Buchungen, // kein Feld an ihnen – deshalb auch ein eigener Abruf. const [liste, verkn, vorschlaege, aboListe] = await Promise.all([ - api.listTransactions({ account_id: filter.account_id, type: filter.type }), + api.listTransactions({ account_id: filter.account_id }), api.transactionLinks().catch(() => ({})), // Nur fragen, nichts tun: Beim Anschauen der Liste darf nichts // verknüpft werden. @@ -207,7 +211,8 @@ export default function Transactions() { } } useEffect(() => { ladenStamm(); }, []); - useEffect(() => { ladenBuchungen(); }, [filter.account_id, filter.type]); + // Nur das Konto geht an den Server; nach Art wird im Browser gefiltert. + useEffect(() => { ladenBuchungen(); }, [filter.account_id]); const fromAcc = form && accountById[Number(form.account_id)]; const toAcc = form && accountById[Number(form.to_account_id)]; @@ -356,8 +361,12 @@ export default function Transactions() { if (gegenseiteDa) paare[tx.id] = txns.find((x) => x.id === link.other_id); sichtbar.push(tx); } - return { zeilen: sichtbar, paarZu: paare }; - }, [txns, links]); + // Eine verknuepfte Buchung ist weder Ausgabe noch Einnahme – sie verschiebt + // nur Geld (siehe transfers.linkedHint). Nach ihrem rohen `type` zu filtern + // zeigte sie unter „Einnahmen", wo sie niemand sucht. + const art = (t) => (t.type === "transfer" || links[String(t.id)] ? "uebertrag" : t.type); + return { zeilen: sichtbar.filter((t) => filter.arten[art(t)]), paarZu: paare }; + }, [txns, links, filter.arten]); function rowLabel(tx) { const gegen = paarZu[tx.id]; @@ -495,6 +504,16 @@ export default function Transactions() { ); }, }, + { + // Die Notiz stand bisher nur im Bearbeiten-Dialog. Wer sie schreibt, + // will sie aber spaeter sehen, ohne jede Zeile aufzuklappen. + key: "notes", header: t("transactions.notes"), width: 180, + filterText: (tx) => tx.notes || "", + render: (tx) => (tx.notes + // Ganzer Text im Tooltip: Die Spalte ist schmal, die Notiz darf lang sein. + ? {tx.notes} + : —), + }, { // Eigene Spalte, damit die Auswahl in der Zeile steht und nicht unter dem // Text – gestapelt wurden die Zeilen hoch und die Spalte ausgefranst. @@ -654,12 +673,22 @@ export default function Transactions() { placeholder={t("transactions.allAccounts")} showCurrency={false} minWidth={190} onChange={(id) => setFilter({ ...filter, account_id: id != null ? String(id) : "" })} /> - +