From 3b26fcdf0b9180e3b1a6a395af40f5d806def37f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 10:29:00 +0000 Subject: [PATCH] Finanzen: Buchung zuordnen auf Dialog-Baustein (Esc, Sperre, Vorschlaege als Zeilen) Co-Authored-By: Claude Sonnet 5.5 --- src/components/finance/AssignDialog.jsx | 302 ++++++++++++------------ src/styles/global.css | 43 ++-- 2 files changed, 164 insertions(+), 181 deletions(-) diff --git a/src/components/finance/AssignDialog.jsx b/src/components/finance/AssignDialog.jsx index 3cbf609..d679b71 100644 --- a/src/components/finance/AssignDialog.jsx +++ b/src/components/finance/AssignDialog.jsx @@ -1,7 +1,5 @@ import { useEffect, useMemo, useRef, useState } from 'react' -import { FaXmark } from 'react-icons/fa6' -import { Badge, Button } from '../ui' -import { useDialogFocus } from '../../hooks/useDialogFocus' +import { Badge, Button, Dialog, Field } from '../ui' import { useScrollIntoView } from '../../hooks/useScrollIntoView' import { assignTransaction, getFixedCosts, unassignTransaction } from '../../lib/financeApi' import { @@ -20,7 +18,8 @@ const NOTE_MAX = 500 * bezahlt markiert und das lässt sich nicht rückgängig machen, deshalb zweistufig: erst „Zuordnen“, dann der * Bestätigungstext mit „Ja, als bezahlt markieren“; vorher geht keine Anfrage raus. Fehler des Servers (409 Betrag * weicht ab, 502 nicht gespeichert …) stehen im Dialog, der offen bleibt. Eine bestehende Zuordnung (außer Rechnung) - * lässt sich mit „Zuordnung lösen“ aufheben. + * lässt sich mit „Zuordnung lösen“ aufheben. × und Esc kommen vom Baustein Dialog und wirken nicht, solange eine + * Anfrage läuft; lockRef sperrt zusätzlich den Augenblick, bevor der Zustand „busy“ gezeichnet ist. * * invoices = offene und überfällige Rechnungen (invoicesLoading / invoicesError für den Ladezustand; invoicesLoaded = * frisch und ohne Fehler geladen, dann sind Vorschläge zu nicht mehr offenen Rechnungen gesperrt). @@ -39,14 +38,12 @@ export default function AssignDialog({ transaction, invoices = [], invoicesLoadi const lockRef = useRef(false) const aliveRef = useRef(true) const dirtyRef = useRef(false) - const dialogRef = useRef(null) const confirmRef = useRef(null) const errorRef = useRef(null) const outgoing = Number.isFinite(tx.amountCents) && tx.amountCents < 0 const unassigned = !tx.category - const onOverlayKeyDown = useDialogFocus(dialogRef) // Der Dialog kann höher sein als das Fenster: eine Fehlermeldung oben soll nicht außerhalb des Bildes stehen useScrollIntoView(errorRef, error) @@ -82,15 +79,6 @@ export default function AssignDialog({ transaction, invoices = [], invoicesLoadi onClose({ refresh: dirtyRef.current }) } - useEffect(() => { - const onKey = (e) => { - // Escape schließt zuerst nur die offene Auswahl (z. B. ein Aufklappmenü): dann nicht den Dialog - if (e.key === 'Escape' && !e.defaultPrevented) close() - } - document.addEventListener('keydown', onKey) - return () => document.removeEventListener('keydown', onKey) - }) - function choose(key) { setSelected(key) setConfirming(false) @@ -154,7 +142,7 @@ export default function AssignDialog({ transaction, invoices = [], invoicesLoadi const fixedSelectValue = model.fixedCosts.some((o) => o.key === selected) ? selected : '' const radio = (o) => ( -