feat: Zeilenfilter sichtbar, mit auswaehlbaren Werten
Der Filter steckte in einem zugeklappten Bereich und der Wert musste
blind getippt werden – entsprechend importierte man den Revolut-Auszug
mit allen Produkten in ein Konto, was den Saldo-Abgleich unmoeglich
macht.
Die Rohvorschau liefert jetzt je Spalte die wiederkehrenden Werte samt
Zeilenzahl. Hat eine Datei so eine Spalte, steht der Filter offen sichtbar
mit der Ueberschrift "Diese Datei enthaelt mehrere Konten", und der Wert
wird aus einer Liste gewaehlt ("Giro (530 Zeilen)").
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -355,10 +355,12 @@
|
||||
"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 Produkte (Giro, Erspartes, Anlagekonto …) in EINE Datei, jedes mit eigenem Kontostand. Wähle hier eines aus und lies die übrigen mit einem eigenen Konto nochmal ein – sonst zählen die Bewegungen doppelt.",
|
||||
"removeEntry": "Eintrag entfernen",
|
||||
"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",
|
||||
"filterValueOption": "{{value}} ({{count}} Zeilen)"
|
||||
},
|
||||
"password": {
|
||||
"show": "Passwort anzeigen",
|
||||
|
||||
@@ -355,10 +355,12 @@
|
||||
"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 product (current, savings, investment …) into ONE file, each with its own balance. Pick one here and import the others separately into their own accounts – otherwise the movements count twice.",
|
||||
"removeEntry": "Remove entry",
|
||||
"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",
|
||||
"filterValueOption": "{{value}} ({{count}} rows)"
|
||||
},
|
||||
"password": {
|
||||
"show": "Show password",
|
||||
|
||||
+42
-17
@@ -218,6 +218,12 @@ export default function Import() {
|
||||
}));
|
||||
}, [raw]);
|
||||
|
||||
// Spalten, die die Datei in wenige Gruppen teilen (Revolut: „Produkt").
|
||||
const filterSpalten = useMemo(
|
||||
() => colOptions.filter((c) => raw?.filter_values?.[String(c.i)]),
|
||||
[colOptions, raw],
|
||||
);
|
||||
|
||||
const expenseGroups = useMemo(() => gruppenFuer(groups, "expense"), [groups]);
|
||||
const incomeGroups = useMemo(() => gruppenFuer(groups, "income"), [groups]);
|
||||
|
||||
@@ -521,6 +527,42 @@ export default function Import() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Revolut legt alle Produkte in EINE Datei. Ohne Filter zaehlen die
|
||||
Bewegungen doppelt – deshalb steht der Filter offen, sobald die
|
||||
Datei eine Spalte mit wenigen wiederkehrenden Werten hat. */}
|
||||
{filterSpalten.length > 0 && (
|
||||
<div className="alert" style={{ marginTop: "var(--sp-3)", display: "block" }}>
|
||||
<strong>{t("import.filterTitle")}</strong>
|
||||
<p className="muted small">{t("import.filterHint")}</p>
|
||||
<div className="map-grid">
|
||||
<label>
|
||||
<span>{t("import.filterColumn")}</span>
|
||||
<select value={config.filter_column ?? ""}
|
||||
onChange={(e) => setCfg({
|
||||
filter_column: e.target.value === "" ? null : Number(e.target.value),
|
||||
filter_value: "",
|
||||
})}>
|
||||
<option value="">{t("import.filterNone")}</option>
|
||||
{filterSpalten.map((c) => <option key={c.i} value={c.i}>{c.label}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{t("import.filterValue")}</span>
|
||||
<select value={config.filter_value}
|
||||
disabled={config.filter_column == null}
|
||||
onChange={(e) => setCfg({ filter_value: e.target.value })}>
|
||||
<option value="">{t("import.filterNone")}</option>
|
||||
{(raw?.filter_values?.[String(config.filter_column)] || []).map((w) => (
|
||||
<option key={w.value} value={w.value}>
|
||||
{t("import.filterValueOption", { value: w.value, count: w.count })}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<details className="import-more">
|
||||
<summary className="muted small">{t("import.moreColumns")}</summary>
|
||||
<p className="muted small">{t("import.ibanHint")}</p>
|
||||
@@ -542,23 +584,6 @@ export default function Import() {
|
||||
))}
|
||||
</div>
|
||||
<p className="muted small">{t("import.extraColsHint")}</p>
|
||||
<div className="map-grid">
|
||||
<label>
|
||||
<span>{t("import.filterColumn")}</span>
|
||||
<select value={config.filter_column ?? ""}
|
||||
onChange={(e) => setCfg({ filter_column: e.target.value === "" ? null : Number(e.target.value) })}>
|
||||
<option value="">{t("import.filterNone")}</option>
|
||||
{colOptions.map((c) => <option key={c.i} value={c.i}>{c.label}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{t("import.filterValue")}</span>
|
||||
<input value={config.filter_value}
|
||||
placeholder={t("import.filterValuePlaceholder")}
|
||||
onChange={(e) => setCfg({ filter_value: e.target.value })} />
|
||||
</label>
|
||||
</div>
|
||||
<p className="muted small">{t("import.filterHint")}</p>
|
||||
</details>
|
||||
|
||||
<div className="field-inline" style={{ marginTop: "var(--sp-3)" }}>
|
||||
|
||||
Reference in New Issue
Block a user