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:
co-authored by
Claude Opus 5
parent
2ba307f833
commit
637f5f4a70
@@ -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
@@ -781,7 +781,8 @@ tr.row-dup {
|
|||||||
schrumpfen (globale .table td select/input-Regel überschreiben). */
|
schrumpfen (globale .table td select/input-Regel überschreiben). */
|
||||||
.datatable td select,
|
.datatable td select,
|
||||||
.datatable td input[inputmode],
|
.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%; }
|
.datatable td .catsel { width: 100%; }
|
||||||
|
|
||||||
/* ---------- Kategorie-Auswahl (auf-/zuklappbarer Baum, Ersatz für <select>) ---------- */
|
/* ---------- Kategorie-Auswahl (auf-/zuklappbarer Baum, Ersatz für <select>) ---------- */
|
||||||
@@ -1229,16 +1230,6 @@ tr.row-dup {
|
|||||||
font-size: 0.9em;
|
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. */
|
/* Der Rueckweg aus „Ignorieren" – selten gebraucht, aber vorhanden. */
|
||||||
.abgelehnte {
|
.abgelehnte {
|
||||||
margin-top: var(--sp-3);
|
margin-top: var(--sp-3);
|
||||||
|
|||||||
@@ -318,6 +318,7 @@
|
|||||||
"empty": "Noch keine Buchungen.",
|
"empty": "Noch keine Buchungen.",
|
||||||
"needAccount": "Lege zuerst ein Konto an, um Buchungen zu erfassen.",
|
"needAccount": "Lege zuerst ein Konto an, um Buchungen zu erfassen.",
|
||||||
"allAccounts": "Alle Konten",
|
"allAccounts": "Alle Konten",
|
||||||
|
"notesCell": "Notiz …",
|
||||||
"filterExpenses": "Ausgaben",
|
"filterExpenses": "Ausgaben",
|
||||||
"filterIncome": "Einnahmen",
|
"filterIncome": "Einnahmen",
|
||||||
"filterTransfers": "Überträge",
|
"filterTransfers": "Überträge",
|
||||||
|
|||||||
@@ -318,6 +318,7 @@
|
|||||||
"empty": "No transactions yet.",
|
"empty": "No transactions yet.",
|
||||||
"needAccount": "Create an account first to record transactions.",
|
"needAccount": "Create an account first to record transactions.",
|
||||||
"allAccounts": "All accounts",
|
"allAccounts": "All accounts",
|
||||||
|
"notesCell": "Note …",
|
||||||
"filterExpenses": "Expenses",
|
"filterExpenses": "Expenses",
|
||||||
"filterIncome": "Income",
|
"filterIncome": "Income",
|
||||||
"filterTransfers": "Transfers",
|
"filterTransfers": "Transfers",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useConfirm } from "../confirm";
|
|||||||
import { useToast } from "../toast";
|
import { useToast } from "../toast";
|
||||||
import Icon from "../components/Icon";
|
import Icon from "../components/Icon";
|
||||||
import IconChip from "../components/IconChip";
|
import IconChip from "../components/IconChip";
|
||||||
|
import EditableText from "../components/EditableText";
|
||||||
import Money from "../components/Money";
|
import Money from "../components/Money";
|
||||||
import TagSelect from "../components/TagSelect";
|
import TagSelect from "../components/TagSelect";
|
||||||
import Modal from "../components/Modal";
|
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 –
|
// Je Zeile eine Warteschlange. Die Auswahl bleibt nach einem Klick offen –
|
||||||
// man vergibt selten genau einen Tag –, also kommen zwei Klicks schnell
|
// man vergibt selten genau einen Tag –, also kommen zwei Klicks schnell
|
||||||
// hintereinander. Ohne die Reihung ueberholten sich die beiden Anfragen, und
|
// hintereinander. Ohne die Reihung ueberholten sich die beiden Anfragen, und
|
||||||
@@ -468,14 +478,6 @@ export default function Transactions() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{l.sub && <div className="named-sub ellipsis">{l.sub}</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
|
{/* Die fruehere Marke mit dem Gegenkonto ist entfallen: Das
|
||||||
Gegenkonto steht jetzt im Titel, wo man es sucht. */}
|
Gegenkonto steht jetzt im Titel, wo man es sucht. */}
|
||||||
{/* Nachträglich erkannt: Auf einem anderen Konto steht der
|
{/* 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.
|
// will sie aber spaeter sehen, ohne jede Zeile aufzuklappen.
|
||||||
key: "notes", header: t("transactions.notes"), width: 180,
|
key: "notes", header: t("transactions.notes"), width: 180,
|
||||||
filterText: (tx) => tx.notes || "",
|
filterText: (tx) => tx.notes || "",
|
||||||
render: (tx) => (tx.notes
|
render: (tx) => (
|
||||||
// Ganzer Text im Tooltip: Die Spalte ist schmal, die Notiz darf lang sein.
|
<EditableText
|
||||||
? <span className="ellipsis" title={tx.notes}>{tx.notes}</span>
|
value={tx.notes}
|
||||||
: <span className="muted">—</span>),
|
placeholder={t("transactions.notesCell")}
|
||||||
|
onSave={(text) => notizSetzen(tx, text)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Eigene Spalte, damit die Auswahl in der Zeile steht und nicht unter dem
|
// Eigene Spalte, damit die Auswahl in der Zeile steht und nicht unter dem
|
||||||
|
|||||||
Reference in New Issue
Block a user