feat: Die Buchungsliste zeigt die Notiz und laesst waehlen, welche Arten erscheinen
Die Notiz stand nur im Bearbeiten-Dialog, obwohl man sie schreibt, um sie spaeter wiederzufinden. Jetzt hat sie eine eigene Spalte mit eigenem Filter; der ganze Text steht im Tooltip. Das Feld im Dialog ist wieder einzeilig und laesst sich bei Bedarf aufziehen. Statt einer Einfachauswahl gibt es drei Haken: Ausgaben, Einnahmen, Uebertraege. Man will oft zwei Arten gleichzeitig sehen und eine dritte weglassen. Und "nur Einnahmen" zeigt keine Uebertraege mehr. Eine verknuepfte Buchung ist weder Ausgabe noch Einnahme, sie verschiebt nur Geld - nach ihrem rohen Typ gefiltert stand sie trotzdem unter den Einnahmen, und weil der Server nur eine Seite des Paares lieferte, sogar einzeln statt als eine Zeile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
50fc0e3353
commit
498774b0da
@@ -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.
|
||||
? <span className="ellipsis" title={tx.notes}>{tx.notes}</span>
|
||||
: <span className="muted">—</span>),
|
||||
},
|
||||
{
|
||||
// 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) : "" })} />
|
||||
<select value={filter.type} onChange={(e) => setFilter({ ...filter, type: e.target.value })}>
|
||||
<option value="">{t("transactions.allTypes")}</option>
|
||||
<option value="expense">{t("transactions.expense")}</option>
|
||||
<option value="income">{t("transactions.income")}</option>
|
||||
<option value="transfer">{t("transactions.transfer")}</option>
|
||||
</select>
|
||||
<div className="art-filter">
|
||||
{[["expense", "filterExpenses"], ["income", "filterIncome"],
|
||||
["uebertrag", "filterTransfers"]].map(([art, schluessel]) => (
|
||||
<label className="checkline" key={art}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={filter.arten[art]}
|
||||
onChange={(e) => setFilter({
|
||||
...filter,
|
||||
arten: { ...filter.arten, [art]: e.target.checked },
|
||||
})}
|
||||
/>
|
||||
<span>{t(`transactions.${schluessel}`)}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
@@ -802,7 +831,7 @@ export default function Transactions() {
|
||||
Freier Text, weil sich das nicht in Schubladen fassen laesst. */}
|
||||
<label>
|
||||
<span>{t("transactions.notes")}</span>
|
||||
<textarea rows={2} value={form.notes}
|
||||
<textarea rows={1} value={form.notes}
|
||||
placeholder={t("transactions.notesPlaceholder")}
|
||||
onChange={(e) => setForm({ ...form, notes: e.target.value })} />
|
||||
</label>
|
||||
|
||||
Reference in New Issue
Block a user