feat: Die Notiz laesst sich direkt in der Liste schreiben

Bisher musste man dafuer die ganze Buchung aufmachen. Jetzt steht in der
Notiz-Spalte ein Feld: hineinschreiben, Enter oder wegklicken - fertig.
Escape nimmt die Aenderung zurueck, leeren loescht die Notiz.

Und sie steht nicht mehr doppelt da. Die dritte Zeile unter der Buchung
zeigte dieselbe Notiz wie die neue Spalte; sie faellt weg, der
Verwendungszweck darueber bleibt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-19 11:24:13 +02:00
co-authored by Claude Opus 5
parent 2ba307f833
commit 637f5f4a70
5 changed files with 60 additions and 23 deletions
+39
View File
@@ -0,0 +1,39 @@
import { useEffect, useState } from "react";
// Kleines, editierbares Textfeld. Speichert beim Verlassen und bei Enter, aber
// nur wenn sich etwas geändert hat – wie EditableAmount daneben, nur ohne die
// Betragsrechnerei. Escape nimmt die Änderung zurück.
export default function EditableText({ value, onSave, placeholder, maxLength }) {
const [val, setVal] = useState(value || "");
// Von aussen neu geladen (etwa nach dem Speichern): Stand übernehmen.
useEffect(() => { setVal(value || ""); }, [value]);
function commit() {
const neu = val.trim();
if (neu !== (value || "")) onSave(neu);
}
return (
<input
type="text"
value={val}
placeholder={placeholder}
maxLength={maxLength}
// Der ganze Text als Tooltip: Die Spalte ist schmal, die Notiz darf lang sein.
title={val || undefined}
onChange={(e) => setVal(e.target.value)}
onBlur={commit}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
commit();
e.target.blur();
}
if (e.key === "Escape") {
setVal(value || "");
e.target.blur();
}
}}
/>
);
}
+2 -11
View File
@@ -781,7 +781,8 @@ tr.row-dup {
schrumpfen (globale .table td select/input-Regel überschreiben). */
.datatable td select,
.datatable td input[inputmode],
.datatable td input[type="text"] { max-width: none; width: 100%; }
.datatable td input[type="text"] { max-width: none; width: 100%;
margin-top: 0; padding: 5px 8px; }
.datatable td .catsel { width: 100%; }
/* ---------- Kategorie-Auswahl (auf-/zuklappbarer Baum, Ersatz für <select>) ---------- */
@@ -1229,16 +1230,6 @@ tr.row-dup {
font-size: 0.9em;
}
/* Eigene Notiz an einer Buchung – von Hand geschrieben und deshalb
aussagekraeftiger als jeder Banktext. Abgesetzt, damit sie nicht mit dem
Verwendungszweck verwechselt wird. */
.buchungsnotiz {
color: var(--muted);
font-style: italic;
opacity: 0.95;
}
.buchungsnotiz svg { vertical-align: -1px; }
/* Der Rueckweg aus „Ignorieren" – selten gebraucht, aber vorhanden. */
.abgelehnte {
margin-top: var(--sp-3);
+1
View File
@@ -318,6 +318,7 @@
"empty": "Noch keine Buchungen.",
"needAccount": "Lege zuerst ein Konto an, um Buchungen zu erfassen.",
"allAccounts": "Alle Konten",
"notesCell": "Notiz …",
"filterExpenses": "Ausgaben",
"filterIncome": "Einnahmen",
"filterTransfers": "Überträge",
+1
View File
@@ -318,6 +318,7 @@
"empty": "No transactions yet.",
"needAccount": "Create an account first to record transactions.",
"allAccounts": "All accounts",
"notesCell": "Note …",
"filterExpenses": "Expenses",
"filterIncome": "Income",
"filterTransfers": "Transfers",
+17 -12
View File
@@ -6,6 +6,7 @@ import { useConfirm } from "../confirm";
import { useToast } from "../toast";
import Icon from "../components/Icon";
import IconChip from "../components/IconChip";
import EditableText from "../components/EditableText";
import Money from "../components/Money";
import TagSelect from "../components/TagSelect";
import Modal from "../components/Modal";
@@ -134,6 +135,15 @@ export default function Transactions() {
}
}
async function notizSetzen(tx, text) {
try {
const neu = await api.updateTransaction(tx.id, { notes: text || null });
setTxns((r) => r.map((x) => (x.id === tx.id ? neu : x)));
} catch (err) {
toast(err.message, "error");
}
}
// Je Zeile eine Warteschlange. Die Auswahl bleibt nach einem Klick offen –
// man vergibt selten genau einen Tag –, also kommen zwei Klicks schnell
// hintereinander. Ohne die Reihung ueberholten sich die beiden Anfragen, und
@@ -468,14 +478,6 @@ export default function Transactions() {
)}
</div>
{l.sub && <div className="named-sub ellipsis">{l.sub}</div>}
{/* Von Hand geschrieben und deshalb aussagekraeftiger als jeder
Banktext: „Amazon" kann Einrichtung sein, Werkzeug oder ein
Geschenk. */}
{tx.notes && (
<div className="named-sub ellipsis buchungsnotiz" title={tx.notes}>
<Icon name="edit" size={11} /> {tx.notes}
</div>
)}
{/* Die fruehere Marke mit dem Gegenkonto ist entfallen: Das
Gegenkonto steht jetzt im Titel, wo man es sucht. */}
{/* Nachträglich erkannt: Auf einem anderen Konto steht der
@@ -509,10 +511,13 @@ export default function Transactions() {
// 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>),
render: (tx) => (
<EditableText
value={tx.notes}
placeholder={t("transactions.notesCell")}
onSave={(text) => notizSetzen(tx, text)}
/>
),
},
{
// Eigene Spalte, damit die Auswahl in der Zeile steht und nicht unter dem