feat: Buchen-Knopf auch unter der Prüfliste + Verlauf aufräumen

- Bei mehreren hundert Zeilen ist der Knopf oben längst aus dem Bild gescrollt;
  unter der Liste steht jetzt derselbe.
- Rückgängig gemachte Importe lassen sich aus dem Verlauf entfernen. Nur diese:
  solange ein Import steht, ist sein Eintrag die einzige Möglichkeit, seine
  Buchungen wieder loszuwerden – der Server lehnt das sonst ab.

Test für beide Fälle des neuen Endpunkts; 92 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-21 09:20:29 +02:00
co-authored by Claude Opus 4.8
parent ab5410ca6f
commit 8d767e728a
7 changed files with 82 additions and 5 deletions
+1
View File
@@ -242,6 +242,7 @@ export const api = {
importCommit: (body) => request("/imports/commit", { method: "POST", body }),
importBatches: () => request("/imports/batches"),
undoImportBatch: (id) => request(`/imports/batches/${id}`, { method: "DELETE" }),
removeImportBatch: (id) => request(`/imports/batches/${id}/record`, { method: "DELETE" }),
// PDF/Bild-Beleg
documentPreview: (formData) => request("/documents/preview", { method: "POST", formData }),
+10
View File
@@ -1077,3 +1077,13 @@ tr.row-dup {
.restore-summary tr:last-child td {
border-bottom: none;
}
/* Zweiter Buchen-Knopf unter der Prüfliste – bei vielen Zeilen ist der obere
längst aus dem Bild gescrollt. */
.commit-footer {
display: flex;
justify-content: flex-end;
padding-top: var(--sp-3);
margin-top: var(--sp-2);
border-top: 1px solid var(--border);
}
+3 -1
View File
@@ -340,7 +340,9 @@
"filterValue": "mit dem Wert",
"filterValuePlaceholder": "z. B. Giro",
"filterNone": "— kein Filter —",
"filterHint": "Revolut legt alle Pockets (Giro, Erspartes, Anlagekonto …) in EINE Datei, jedes mit eigenem Kontostand. Filtere hier auf ein Produkt und lege für die anderen eigene Konten an – sonst zählen die Pocket-Bewegungen doppelt."
"filterHint": "Revolut legt alle Pockets (Giro, Erspartes, Anlagekonto …) in EINE Datei, jedes mit eigenem Kontostand. Filtere hier auf ein Produkt und lege für die anderen eigene Konten an – sonst zählen die Pocket-Bewegungen doppelt.",
"removeEntry": "Eintrag entfernen",
"removeEntryConfirm": "Den Verlaufs-Eintrag „{{file}}“ entfernen? Die Buchungen sind bereits zurückgenommen."
},
"password": {
"show": "Passwort anzeigen",
+3 -1
View File
@@ -340,7 +340,9 @@
"filterValue": "with value",
"filterValuePlaceholder": "e.g. Current",
"filterNone": "— no filter —",
"filterHint": "Revolut puts every pocket (Current, Savings, Investments …) into ONE file, each with its own balance. Filter to one product here and create separate accounts for the others – otherwise pocket movements are counted twice."
"filterHint": "Revolut puts every pocket (Current, Savings, Investments …) into ONE file, each with its own balance. Filter to one product here and create separate accounts for the others – otherwise pocket movements are counted twice.",
"removeEntry": "Remove entry",
"removeEntryConfirm": "Remove the history entry “{{file}}”? Its transactions have already been undone."
},
"password": {
"show": "Show password",
+25 -1
View File
@@ -196,6 +196,15 @@ export default function Import() {
} catch (e) { toast(e.message, "error"); }
}
async function entfernen(b) {
if (!(await confirm({
title: t("import.removeEntry"),
message: t("import.removeEntryConfirm", { file: b.filename || "—" }),
danger: true, confirmLabel: t("common.delete"),
}))) return;
try { await api.removeImportBatch(b.id); loadStamm(); } catch (e) { toast(e.message, "error"); }
}
async function undo(b) {
if (!(await confirm({ title: t("import.undo"), message: t("import.undoConfirm", { file: b.filename }), danger: true, confirmLabel: t("import.undo") }))) return;
try { await api.undoImportBatch(b.id); loadStamm(); } catch (e) { toast(e.message, "error"); }
@@ -325,7 +334,15 @@ export default function Import() {
<button className="btn sm danger" onClick={() => undo(b)}>
<Icon name="refresh-ccw" size={14} /> {t("import.undo")}
</button>
) : <span className="muted small">{t("import.undone")}</span>),
) : (
<span className="nowrap">
<span className="muted small">{t("import.undone")}</span>
<button className="btn sm ghost" title={t("import.removeEntry")}
style={{ marginLeft: 6 }} onClick={() => entfernen(b)}>
<Icon name="trash" size={14} />
</button>
</span>
)),
},
// eslint-disable-next-line react-hooks/exhaustive-deps
], [t, formatDate, accountById]);
@@ -614,6 +631,13 @@ export default function Import() {
rowClassName={(r) => (!r.valid ? "row-invalid" : r.is_duplicate || r.is_counterpart ? "row-dup" : "")}
columns={reviewColumns}
/>
{/* Zweiter Knopf unter der Liste: bei vielen Zeilen muss man zum Buchen
sonst wieder ganz nach oben scrollen. */}
<div className="commit-footer">
<button className="btn primary" onClick={commit} disabled={busy || includeCount === 0}>
<Icon name="download" size={15} /> {t("import.commit", { n: includeCount })}
</button>
</div>
</div>
)}