Files
certain/web/src/components/UebertragDialog.jsx
T
GuidoandClaude Opus 5 8cb82da63c fix: Uebertrag buchen tat sichtbar nichts – useToast falsch ausgepackt
useToast() liefert die Melde-Funktion selbst, kein Objekt. In drei Dateien aus
dieser Sitzung stand "const { toast } = useToast()", also undefined. Der Aufruf
steht fast immer in einem catch – dort verschluckt der TypeError die
eigentliche Meldung, und nach aussen passiert gar nichts.

Das erklaert beide Beschwerden:

- "Als Uebertrag buchen" schien wirkungslos: Die Buchung lief durch, dann warf
  toast(), der catch warf beim Melden erneut, der Dialog blieb offen.
- "Braucht ein Reload": Im Eingang stand toast() VOR laden() – der Dialog
  schloss noch, das Neuladen kam nie. Die Cache-Kopfzeile, die ich dafuer
  gesetzt habe, war nicht die Ursache; richtig ist sie trotzdem.

Am Ende in der laufenden Oberflaeche nachgeklickt: Dialog schliesst, Meldung
erscheint, die Zeile wird ohne Neuladen zum Uebertrag, die Gegenseite liegt auf
dem Zielkonto, beide Salden stimmen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 11:15:43 +02:00

100 lines
3.4 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { api } from "../api";
import AccountSelect from "./AccountSelect";
import Modal from "./Modal";
import Money from "./Money";
import { useToast } from "../toast";
import { useConfirm } from "../confirm";
import { formatDate } from "../format";
// Etwas als Kontoübertrag buchen. Zwei Aufrufer – die Arbeitsliste und die
// Buchungsliste –, deshalb eine Komponente: Die Nachfrage bei einem
// geschätzten Betrag ist zu leicht falsch nachgebaut.
//
// `vorgang` beschreibt, was umgewidmet wird:
// { art: "vorschlag" | "buchung", id, kontoId, betrag (vorzeichenbehaftet),
// waehrung, text, datum }
export default function UebertragDialog({
vorgang, accounts, accountById, baseCurrency, onClose, onFertig,
}) {
const { t } = useTranslation();
const toast = useToast();
const confirm = useConfirm();
const [ziel, setZiel] = useState("");
const [busy, setBusy] = useState(false);
async function buchen() {
if (!ziel) return;
const senden = (trotzdem) => (vorgang.art === "vorschlag"
? api.bookInboxAsTransfer(vorgang.id, Number(ziel), trotzdem)
: api.makeTransactionTransfer(vorgang.id, Number(ziel), trotzdem));
setBusy(true);
try {
try {
await senden(false);
} catch (e) {
// 409 heisst nachfragen: Der Betrag der Gegenseite ist geschätzt und
// das Zielkonto meldet den Zugang selbst. Die Begründung kommt vom
// Server – dort steht das Wissen, nicht hier.
if (e.status !== 409) { toast(e.message, "error"); return; }
if (!(await confirm({
title: t("inbox.transferTitle"),
message: e.message,
confirmLabel: t("inbox.asTransfer"),
}))) return;
await senden(true);
}
toast(t("inbox.transferBooked"));
onClose();
await onFertig();
} catch (e) {
toast(e.message, "error");
} finally {
setBusy(false);
}
}
return (
<Modal
title={t("inbox.transferTitle")}
onClose={onClose}
footer={
<>
<button className="btn" onClick={onClose}>{t("common.cancel")}</button>
<button className="btn primary" disabled={!ziel || busy} onClick={buchen}>
{t("inbox.asTransfer")}
</button>
</>
}
>
<p className="muted small">{t("inbox.transferIntro")}</p>
<div className="account-row" style={{ borderBottom: "none" }}>
<div className="named-text grow">
<div><strong>{vorgang.text || t("inbox.noText")}</strong></div>
<div className="named-sub">
{formatDate(vorgang.datum)} · {accountById[vorgang.kontoId]?.name || "—"}
</div>
</div>
<Money cents={Math.abs(vorgang.betrag)} currency={vorgang.waehrung}
sign={vorgang.betrag < 0 ? "neg" : "pos"} baseCurrency={baseCurrency} />
</div>
<label>
{/* Die Frage hängt an der Richtung – „wohin" bei einem Abgang,
„woher" bei einem Zugang. */}
<span>
{vorgang.betrag < 0
? t("inbox.transferTargetOut")
: t("inbox.transferTargetIn")}
</span>
<AccountSelect
accounts={accounts.filter((a) => a.id !== vorgang.kontoId)}
value={ziel}
onChange={(id) => setZiel(id ?? "")}
/>
</label>
</Modal>
);
}