feat: Buchungstexte in der Offen-Liste kopierbar

body hat user-select:none mit einer Ausnahmeliste; die Buchungsliste steht
als Tabelle darin, die Offen-Seite mit ihren Listenzeilen aber nicht. Damit
liess sich der Empfaengertext nicht markieren – ausgerechnet dort, wo man
ihn nachschlagen will, um zu erkennen, welches Franchise dahintersteckt.

.named-text ist jetzt markierbar, und je Zeile steht ein Kopierknopf.
Ausserdem wird der Verwendungszweck mit angezeigt, wenn er vom Empfaenger
abweicht – oft steht die eigentliche Kennung dort.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-21 20:59:51 +02:00
co-authored by Claude Opus 5
parent 3a44509e77
commit 18368e6e93
4 changed files with 30 additions and 4 deletions
+4 -1
View File
@@ -450,6 +450,9 @@
"allDone": "Alles zugeordnet.", "allDone": "Alles zugeordnet.",
"choose": "Kategorie wählen …", "choose": "Kategorie wählen …",
"noText": "Ohne Text", "noText": "Ohne Text",
"rememberKeyword": "Zweck als Schlagwort merken, damit die nächste gleiche Buchung beim Import gleich zugeordnet wird" "rememberKeyword": "Zweck als Schlagwort merken, damit die nächste gleiche Buchung beim Import gleich zugeordnet wird",
"copy": "Text kopieren",
"copied": "Kopiert",
"copyFailed": "Kopieren nicht möglich – Text lässt sich jetzt aber markieren."
} }
} }
+4 -1
View File
@@ -450,6 +450,9 @@
"allDone": "Everything sorted.", "allDone": "Everything sorted.",
"choose": "Pick a category …", "choose": "Pick a category …",
"noText": "No text", "noText": "No text",
"rememberKeyword": "Remember the text as a keyword so the next matching booking is categorised on import" "rememberKeyword": "Remember the text as a keyword so the next matching booking is categorised on import",
"copy": "Copy text",
"copied": "Copied",
"copyFailed": "Copying failed – but the text can be selected now."
} }
} }
+21 -1
View File
@@ -44,6 +44,17 @@ export default function Offen() {
} }
useEffect(() => { laden(); }, []); useEffect(() => { laden(); }, []);
// Der Empfaengertext ist oft genau das, was man nachschlagen will („welches
// Franchise steckt hinter diesem Namen?"). Abtippen faellt damit weg.
async function kopieren(text) {
try {
await navigator.clipboard.writeText(text);
toast(t("open.copied"));
} catch {
toast(t("open.copyFailed"), "error");
}
}
async function zuordnen(tx, categoryId) { async function zuordnen(tx, categoryId) {
if (categoryId == null) return; if (categoryId == null) return;
try { try {
@@ -90,9 +101,18 @@ export default function Offen() {
<IconChip name={accountById[tx.account_id]?.icon || "wallet"} <IconChip name={accountById[tx.account_id]?.icon || "wallet"}
color={accountById[tx.account_id]?.color} /> color={accountById[tx.account_id]?.color} />
<div className="named-text grow"> <div className="named-text grow">
<div><strong>{tx.payee || tx.description || t("open.noText")}</strong></div> <div className="row" style={{ gap: "var(--sp-2)", alignItems: "center" }}>
<strong>{tx.payee || tx.description || t("open.noText")}</strong>
{(tx.payee || tx.description) && (
<button type="button" className="btn sm ghost" title={t("open.copy")}
onClick={() => kopieren(tx.payee || tx.description)}>
<Icon name="copy" size={14} />
</button>
)}
</div>
<div className="named-sub"> <div className="named-sub">
{formatDate(tx.date)} · {accountById[tx.account_id]?.name || "—"} {formatDate(tx.date)} · {accountById[tx.account_id]?.name || "—"}
{tx.description && tx.payee && tx.description !== tx.payee && ` · ${tx.description}`}
</div> </div>
</div> </div>
<Money cents={tx.amount_cents} currency={accountById[tx.account_id]?.currency} <Money cents={tx.amount_cents} currency={accountById[tx.account_id]?.currency}
+1 -1
View File
@@ -63,7 +63,7 @@ input[type="checkbox"], input[type="radio"], input[type="file"],
wirkt. Inhalte, die man wirklich kopieren will, bleiben markierbar: Tabellen- wirkt. Inhalte, die man wirklich kopieren will, bleiben markierbar: Tabellen-
zellen, Eingabefelder und Code/Token (sowie alles mit .selectable). */ zellen, Eingabefelder und Code/Token (sowie alles mit .selectable). */
body { -webkit-user-select: none; user-select: none; } body { -webkit-user-select: none; user-select: none; }
.table td, input, textarea, [contenteditable="true"], code, pre, .selectable { .table td, input, textarea, [contenteditable="true"], code, pre, .selectable, .named-text {
-webkit-user-select: text; user-select: text; -webkit-user-select: text; user-select: text;
} }