feat: Rueckgaengig gemachte Importe sammelweise entfernen

Die Aktionsspalte im Verlauf ist fest und war zu schmal fuer den
beschrifteten Knopf. Ausserdem sammeln sich nach einer Testreihe schnell
zwanzig rueckgaengig gemachte Eintraege – die einzeln wegzuklicken ist
Fleissarbeit. Neben der Ueberschrift steht jetzt ein Knopf, der alle auf
einmal entfernt; einzeln geht weiterhin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-21 20:30:11 +02:00
co-authored by Claude Opus 5
parent 6322b65306
commit d8c15ca260
3 changed files with 32 additions and 4 deletions
+3 -1
View File
@@ -360,7 +360,9 @@
"removeEntryConfirm": "Den Verlaufs-Eintrag „{{file}}“ entfernen? Die Buchungen sind bereits zurückgenommen.", "removeEntryConfirm": "Den Verlaufs-Eintrag „{{file}}“ entfernen? Die Buchungen sind bereits zurückgenommen.",
"reconMixedHint": "Enthält die Datei mehrere Konten oder Produkte? Revolut legt Giro, Erspartes und Anlagekonto in EINE Datei, jedes mit eigener Kontostand-Spalte. Dann greift der Abgleich erst, wenn du oben den Zeilenfilter setzt (z. B. Produkt = Giro) und die übrigen Produkte einzeln einliest.", "reconMixedHint": "Enthält die Datei mehrere Konten oder Produkte? Revolut legt Giro, Erspartes und Anlagekonto in EINE Datei, jedes mit eigener Kontostand-Spalte. Dann greift der Abgleich erst, wenn du oben den Zeilenfilter setzt (z. B. Produkt = Giro) und die übrigen Produkte einzeln einliest.",
"filterTitle": "Diese Datei enthält mehrere Konten", "filterTitle": "Diese Datei enthält mehrere Konten",
"filterValueOption": "{{value}} ({{count}} Zeilen)" "filterValueOption": "{{value}} ({{count}} Zeilen)",
"removeAllUndone": "Rückgängig gemachte entfernen",
"removeAllUndoneConfirm": "{{n}} rückgängig gemachte Einträge aus dem Verlauf entfernen? Die Buchungen sind bereits weg, es verschwindet nur der Eintrag."
}, },
"password": { "password": {
"show": "Passwort anzeigen", "show": "Passwort anzeigen",
+3 -1
View File
@@ -360,7 +360,9 @@
"removeEntryConfirm": "Remove the history entry “{{file}}”? Its transactions have already been undone.", "removeEntryConfirm": "Remove the history entry “{{file}}”? Its transactions have already been undone.",
"reconMixedHint": "Does the file hold several accounts or products? Revolut puts current, savings and investment into ONE file, each with its own balance column. The check only works once you set the row filter above (e.g. Product = Current) and import the other products separately.", "reconMixedHint": "Does the file hold several accounts or products? Revolut puts current, savings and investment into ONE file, each with its own balance column. The check only works once you set the row filter above (e.g. Product = Current) and import the other products separately.",
"filterTitle": "This file holds several accounts", "filterTitle": "This file holds several accounts",
"filterValueOption": "{{value}} ({{count}} rows)" "filterValueOption": "{{value}} ({{count}} rows)",
"removeAllUndone": "Remove undone entries",
"removeAllUndoneConfirm": "Remove {{n}} undone entries from the history? The bookings are already gone; only the entry disappears."
}, },
"password": { "password": {
"show": "Show password", "show": "Show password",
+26 -2
View File
@@ -206,6 +206,22 @@ export default function Import() {
try { await api.removeImportBatch(b.id); loadStamm(); } catch (e) { toast(e.message, "error"); } try { await api.removeImportBatch(b.id); loadStamm(); } catch (e) { toast(e.message, "error"); }
} }
// Nach einer Reihe von Versuchen stehen schnell zwanzig rueckgaengig gemachte
// Importe im Verlauf. Die einzeln wegzuklicken ist Fleissarbeit ohne Nutzen.
async function alleErledigtenEntfernen() {
const weg = batches.filter((b) => b.status === "undone");
if (!weg.length) return;
if (!(await confirm({
title: t("import.removeAllUndone"),
message: t("import.removeAllUndoneConfirm", { n: weg.length }),
danger: true, confirmLabel: t("common.delete"),
}))) return;
try {
for (const b of weg) await api.removeImportBatch(b.id);
loadStamm();
} catch (e) { toast(e.message, "error"); }
}
async function undo(b) { 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; 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"); } try { await api.undoImportBatch(b.id); loadStamm(); } catch (e) { toast(e.message, "error"); }
@@ -336,7 +352,7 @@ export default function Import() {
render: (b) => <span className="nowrap">{t("import.importedCount", { n: b.imported_count })}</span>, render: (b) => <span className="nowrap">{t("import.importedCount", { n: b.imported_count })}</span>,
}, },
{ {
key: "action", header: "", width: 168, fixed: true, key: "action", header: "", width: 196, fixed: true,
render: (b) => (b.status === "committed" ? ( render: (b) => (b.status === "committed" ? (
<button className="btn sm danger" onClick={() => undo(b)}> <button className="btn sm danger" onClick={() => undo(b)}>
<Icon name="refresh-ccw" size={14} /> {t("import.undo")} <Icon name="refresh-ccw" size={14} /> {t("import.undo")}
@@ -673,7 +689,15 @@ export default function Import() {
{/* Verlauf */} {/* Verlauf */}
{batches.length > 0 && ( {batches.length > 0 && (
<div className="card"> <div className="card">
<h2>{t("import.history")}</h2> <div className="card-head">
<h2>{t("import.history")}</h2>
{batches.some((b) => b.status === "undone") && (
<button className="btn sm ghost" style={{ marginLeft: "auto" }} onClick={alleErledigtenEntfernen}>
<Icon name="trash" size={14} />{" "}
{t("import.removeAllUndone")} ({batches.filter((b) => b.status === "undone").length})
</button>
)}
</div>
<DataTable <DataTable
id="import-history" id="import-history"
rows={batches} rows={batches}