Files
tickte-system/docs/superpowers/plans/2026-10-05-weisses-design-p7-finanzen.md
T

52 KiB
Raw Blame History

WOMS „Weißes Design“ P7: Finanzen – Umsetzungsplan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Nach P7 sind alle Finanzseiten ohne Karten, Kästen und Icons gestaltet: Übersicht, Einstellungen, Bankrückkehr und der Reiter „Rechnungen“ am Kunden. Die Dialoge „Buchung zuordnen“ und „Rechnung schreiben“ laufen auf Dialog. Kpi ist gelöscht. Funktion, Rückfragen und Texte bleiben.

Architecture:

  • Der Finanzblock in src/styles/global.css (heute ab /* --- Finanzen --- */, ca. 320 Zeilen) wird Region für Region neu geschrieben, jeweils zusammen mit den Komponenten der Region. Er enthält nur Tokens, keine Farbliterale und kein !important. Task 3 rahmt ihn mit zwei Marken, weil P5 und P6 hinter ihm eigene Blöcke anhängen.
  • Die Container-Queries (560 Vorschau, 640 Buchungen, 860 Rechnungen) bleiben. container-type sitzt auf einem neuen Wrapper .fin-body unter der Abschnittsüberschrift, Dialoge stehen nie darin.
  • Neue Datei src/lib/financeUi.js mit zwei reinen Helfern: Vorzeichen-Rot und Meldungsklassen.

Tech Stack: React 18, react-router-dom 6, Vite 5, reines CSS. Tests mit node --test, Screenshot-Werkzeug aus P0.

Spec: docs/superpowers/specs/2026-10-04-weisses-design-design.md, Abschnitte „Gestaltungsregeln“, „Bausteine“ (Abschnitt, Knöpfe, Badge, Tabelle, Schlüsselwerte, Formular, Dialog, Meldungen, Laden), „Bewusste Änderungen“ (Esc), „Risiken“ (Scroll-Anker, Vorschlagslisten in Dialogen) und Paket P7. Dazu Anhang …-umbaukarte.md, Abschnitte 3, 4, 5/P7, 7.8. Globale Regeln und Schnittstellen: 2026-10-05-weisses-design-00-uebersicht.md.

Global Constraints

Es gelten die Regeln der Übersicht. Für P7 kommt hinzu:

  • Befehle: Jeder Run-Block beginnt mit source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh &&. Danach gelten $WT, $W und $TMPDIR. Tests und Build laufen nur über "$W/check.sh", Browser-Läufe nur über "$W/shots.sh". FIN ist die kommagetrennte Liste aller Ansichten-IDs der Tabelle aus Task 2 Step 2 (Shell-Variable, gesetzt in Task 2 Step 4).
  • Gate-Läufe: --gate p7 schaltet icon-svg für die FIN-Ansichten scharf. Die Finanzseite ist erst nach Task 5 ohne Icons, deshalb laufen Task 3 und 4 mit --gate p6 (dieselben übrigen Prüfungen). Ab Task 5 gilt --gate p7.
  • Dateien: nur src/pages/FinancePage.jsx, FinanceSettingsPage.jsx, BankCallbackPage.jsx, alle 17 Dateien in src/components/finance/, src/components/ui/Kpi.jsx (löschen), src/components/ui/index.js (Export entfernen), src/lib/financeUi.js (neu), src/styles/global.css (nur der Finanzblock). Alle src/lib/finance*.js, src/hooks/*, CustomerDetailPage.jsx und alle bestehenden Tests bleiben unberührt.
  • Texte wörtlich. Erlaubt sind nur ▾/▴ statt Chevron-Icons, „+ Position“ (Plus-Icon wird Zeichen), „Entfernen“ in der Positionszeile (heute nur Icon) und „← Zurück zu den Finanzen“. Ladetexte („Speichere …“, „Sende …“, „Rechnungen werden geladen …“) bleiben, nur Spinner und Icons entfallen.
  • Tonnamen muted|info|ok|warn|danger aus financeFormat.js/financeView.js bleiben, gemappt wird nur in Badge.
  • Beträge behalten das Vorzeichen (U+2212). Rot (--red-fg) gilt genau dann, wenn der angezeigte Text mit „−“ beginnt: Klasse .fin-neg, bei Kennzahlen tone="neg". Wo die Lib schon markiert (saldoSicherNegative, saldoErwartetNegative, low.negative, shortfall), gilt diese Markierung weiter.
  • Container-Falle: container-type: inline-size macht das Element zum Containing Block für position: fixed. Es steht nur auf .fin-body, AssignDialog und InvoiceCreateModal sind nie Nachfahren davon.
  • Hygiene in allen P7-Dateien: keine Icon-Importe, kein style={{…}}, kein size="sm", keine Farbliterale, keine Klassen ui-btn, form-control, form-group, form-label, overlay-content, overlay-close, spinner, kpi. lockRef/busyRef und alle window.confirm-Rückfragen bleiben.

Gemeinsame Ersetzungen (Komponenten und CSS):

Alt Neu
Spinner plus Text im .empty <div className="loading" role="status">Text wörtlich</div>
fin-hint / fin-hint-warn / fin-dialog-* msg muted / msg warn (role="alert" bleibt) / Fehler im Dialog msg err
tonabhängige Meldung <p {...noticeProps(tone, 'fin-notice')}>
Button variant="ghost", size="sm" Button (Standard), size entfällt
Aktion in Zeile, Liste oder Meldung <button type="button" className="link">, Links <a className="link">, destruktiv Button variant="danger"
form-control / form-group + form-label input/select/textarea / Field bzw. FormField
CSS-Farben und -Maße --text-muted, --text-faint → --text-2; --warn → --orange-fg; Text---danger → --red-fg (Formularfehler bleibt --danger); --border → --line; Flächen und Kartenrahmen entfallen; Gewicht 700/800 → 600 (Tabellentitel 500); Großbuchstaben entfallen; Media-Queries 640/700/720 → 860

Die CSS-Schritte nennen nur Abweichungen, Spezifitäts- und Reihenfolgeregeln sowie Maße, die ein Gate-Schritt prüft. Werte, die dort fehlen, bleiben wie im heutigen Finanzblock und folgen der Ersetzungstabelle.

Steuerelemente, die erreichbar bleiben (Namen unverändert):

  • Übersicht: „Einstellungen“, „Aktualisieren“, „Aufschlüsselung“, der eine Knopf je Aufgabe (bank-verbinden, bank-aktualisieren, rechnung-oeffnen, zuordnen, kunde-oeffnen, fixkosten-uebernehmen, einstellungen), „Alle N anzeigen“/„Weniger anzeigen“, „Schließen“ (Zuordnen-Hinweis), jedes „Erneut versuchen“, das heute steht. Rechnungen: „Rechnung schreiben“, Filter Offen/Überfällig/Bezahlt/Entwürfe/Alle, Aktionen Zahlungsseite, PDF, Senden, Erinnerung senden, Stornieren, Löschen. Buchungen: „Zuordnen“/„Ändern“. Pager: „Zurück“/„Weiter“.
  • Dialoge: „ד, „Abbrechen“, „Schließen“, „Zuordnen“, „Zuordnung lösen“, „Zurück“, „Ja, als bezahlt markieren“, „Als Entwurf speichern“, „Senden“, „+ Position“, „Entfernen“, „Zur Übersicht“.
  • Einstellungen und Rest: „Speichern“, „Jetzt abrufen“, „Neu verbinden“/„Bank verbinden“, „Neue Position“, „Bearbeiten“, „Aktivieren“/„Auf inaktiv setzen“, „Löschen“, „Abbrechen“, „Zu den Finanzen“, „Daten erfassen“, „← Zurück zu den Finanzen“.

Entscheidungen (die Spec schweigt; nächste Lösung zu heute):

  • Entscheidung: .fin-stack ist ein normaler Block ohne gap und ohne Flex. Der Abstand der Seitenteile ist margin-top: 36px auf jedem Geschwister nach dem ersten (.fin-stack > * + *). Das ist derselbe Wert wie .page section + section aus P1. Beide Regeln setzen dieselbe Eigenschaft am selben Element, also addiert sich nichts. Ein gap: 36px ergäbe zwischen zwei Abschnitten 72 px. Als Block lässt .fin-stack den oberen Rand von h2.section:first-child (8 px, weil Card die Überschrift als erstes Kind in ein <section> setzt) mit dem Abschnittsabstand verschmelzen. Eine Sonderregel für die Überschrift entfällt.
  • Entscheidung: Finanztabellen sind keine tr.row (Zeilen nicht klickbar, Aktionen sind echte Knöpfe und Links). Es gibt kein .hide-sm/.hide-md, schmal stapeln die Container-Queries die Zeilen wie heute.
  • Entscheidung: Die Aufgaben-Aktion je Zeile und „Alle N anzeigen“ sind .link (bis zu sieben Ringknöpfe untereinander wären zu unruhig). „Aktualisieren“, allein stehendes „Erneut versuchen“, „Jetzt abrufen“ und „Neue Position“ sind Standardknöpfe.
  • Entscheidung: Pro Abschnitt höchstens ein schwarzer Knopf (Rechnung schreiben, Speichern, Jetzt abrufen bzw. Bank verbinden, Zuordnen/Senden im Dialog). Die Seitenköpfe haben keinen.
  • Entscheidung: Die Aufschlüsselungszeilen („− Steuer-Rücklage …“) bleiben neutral, weil sie Abzüge sind. Rot sind Summen, Salden sowie Posten- und Buchungsbeträge.
  • Entscheidung: Der Altersstreifen ist <ul class="stats fin-ages"> mit Stat je Gruppe. Er überschreibt das Flex-Layout durch 4 Spalten, ab Container ≤ 860 px 2 Spalten (die Handy-Regel von .stats ergäbe 3 + 1).
  • Entscheidung: Der Dialog-Fuß ist <div className="fin-foot"> (Flex, umbrechend, rechtsbündig, auf dem Handy gleich breite Knöpfe) statt <span className="grow" />. Mit .dialog-foot { flex-wrap: wrap } aus P6 allein stünde der dritte Knopf bei 390 px linksbündig in einer zweiten Zeile.

Review Focus

Aus der Übersicht gehören diese Punkte zu P7:

  • Punkt 2, lange Inhalte am Handy: Testdaten finanz-minus mit Kundenname (60 Zeichen), Verwendungszweck (140 Zeichen ohne Leerzeichen) und Gegenpartei (80 Zeichen). overflow-x ist im Gate p7 für alle Ansichten scharf.
  • Punkt 3, Teilausfall: finanzen-teilausfall (Stripe aus, Abos nicht ladbar, Rechnungen und Buchungen antworten 500). Jeder Block zeigt seine Meldung mit „Erneut versuchen“, die Seite bleibt bedienbar.
  • Punkt 4, nur Tastatur: Esc, Tab-Falle, gesperrtes „ד bei busy, „Enter sendet nie“ und „Esc schließt zuerst die Vorschlagsliste“ sind Schritte in dlg-zuordnen-* und dlg-rechnung-*.
  • Punkt 5, viele Einträge: 120 Buchungen, 60 Rechnungen, 50 Zeilen je Seite (finanzen-pager).

Zusätzlich für P7 (Risiken der Spec): Scroll-Anker finanzen-rechnungen und finanzen-buchungen liegen nicht unter der Mobil-Leiste (finanzen-anker). Die Vorschlagsliste im Kundenfeld wird im scrollenden .dialog-body nicht abgeschnitten (dlg-rechnung-kunde). Negative Beträge sind rot und tragen ihr Minus (finanzen-minus, finanzen-tabellen).


Task 1: Hilfsfunktionen financeUi

Files:

  • Create: src/lib/financeUi.js
  • Test: tests/unit/financeUi.test.mjs (neu)

Interfaces:

  • Consumes: euro, euroWhole, signedEuro aus src/lib/financeFormat.js (nur im Test).

  • Produces:

    • isNegText(text) → boolean: wahr, wenn text ein String ist und mit „−“ (U+2212) beginnt.
    • noticeProps(tone, extra = '') → { className, role }: 'ok' → msg ok/status, 'warn' → msg warn/alert, sonst msg muted/status. extra hängt mit Leerzeichen an className.
  • Step 1: Paketstart und Ausgangslage

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git fetch origin && git merge-base --is-ancestor design-p6 HEAD && git rebase origin/test && git status --short && "$W/check.sh" Expected: Der Rebase läuft ohne Konflikt, git status ist leer, # fail 0. Ist design-p6 kein Vorfahr von HEAD, stoppt der Agent und meldet das. Testzahl N sowie JS- und CSS-Größe aus der check.sh-Ausgabe werden notiert (Vergleich in Task 9).

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git log --oneline origin/test..feature/terminplaner-tp2 -- src/pages/FinancePage.jsx src/pages/FinanceSettingsPage.jsx src/pages/BankCallbackPage.jsx src/components/finance src/components/ui/Kpi.jsx src/components/ui/index.js src/lib/financeUi.js src/styles/global.css Expected: keine Ausgabe. Gibt es Treffer, stoppt der Agent und fragt den Eigentümer. Er empfiehlt dabei, TP2 zuerst nach test zu bringen.

  • Step 2: Failing Test schreiben
import test from 'node:test'
import assert from 'node:assert/strict'
import { loadModule } from './load.mjs'

const { isNegText, noticeProps } = await loadModule('src/lib/financeUi.js')
const { euro, euroWhole, signedEuro } = await loadModule('src/lib/financeFormat.js')

test('isNegText: nur das angezeigte Minus zählt', () => {
  assert.equal(isNegText(euro(-449)), true)
  assert.equal(isNegText(signedEuro(-449)), true)
  assert.equal(isNegText(euroWhole(-123456)), true)
  assert.equal(isNegText(euroWhole(-40)), false) // „0 €“: Rundung auf 0 trägt kein Vorzeichen
  assert.equal(isNegText(signedEuro(16879)), false)
  assert.equal(isNegText(euro(null)), false) // „–“ (U+2013)
  for (const v of ['-4,49 €', '', undefined, null, 5]) assert.equal(isNegText(v), false)
})

test('noticeProps: Klasse und Rolle je Ton', () => {
  assert.deepEqual(noticeProps('ok'), { className: 'msg ok', role: 'status' })
  assert.deepEqual(noticeProps('warn'), { className: 'msg warn', role: 'alert' })
  assert.deepEqual(noticeProps('info'), { className: 'msg muted', role: 'status' })
  assert.deepEqual(noticeProps(undefined), { className: 'msg muted', role: 'status' })
  assert.deepEqual(noticeProps('warn', 'fin-notice'), { className: 'msg warn fin-notice', role: 'alert' })
})
  • Step 3: Test laufen lassen

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/financeUi.test.mjs Expected: FAIL (Modul financeUi.js fehlt).

  • Step 4: financeUi.js implementieren

Zwei benannte Exporte nach der Signatur oben, deutscher Kommentar im Stil der Nachbardateien, keine Abhängigkeit.

  • Step 5: Test grün

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/financeUi.test.mjs && "$W/check.sh" --nur-tests Expected: PASS, # pass = N + 2, # fail 0, Exit 0.

  • Step 6: Commit
git add src/lib/financeUi.js tests/unit/financeUi.test.mjs
git commit -m "Finanzen: Helfer fuer Vorzeichen-Rot und Meldungsklassen" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"

Task 2: Werkzeug – Finanzdaten, Ansichten, Gate p7

Files (unter $W/):

  • Create: fixtures/finanz-minus.mjs, fixtures/finanz-teilausfall.mjs
  • Modify: views.mjs, gates.mjs, bei Bedarf backend.mjs (POST …/finanzen/buchungen/:id/zuordnen → { buchung }, POST …/finanzen/bank/rueckkehr → { ok: true, validUntil: '2027-03-29' })

Interfaces:

  • Consumes: VIEWS- und GATES-Schema aus der Übersicht (id, path, fixtures, scenario, steps(page, ctx)). Schritte werfen bei Abweichung (node:assert/strict), shots.mjs meldet das als step-failed. ctx.backend.calls listet alle beantworteten Requests. Portal-Antworten unter https://portal.test/api/admin/finanzen/….

  • Produces: die Ansichten der Tabelle unten und das Gate p7 (Aufbau nach der Übersicht, Step 3).

  • Step 1: Testdaten anlegen

Beide Dateien leiten von standard.mjs ab und ersetzen nur die Finanz-Antworten (gleiche Form).

  • finanz-minus:

    • Kopf: Kontostand −1.234,56 €, verfuegbarCents < 0, tiefsterSicher negativ, tiefsterErwartet positiv (beweist Rot je Betrag).
    • Vorschau mit 8 Posten und negativen Salden. 9 Aufgaben, darunter je eine mit rechnung-oeffnen ohne hostedUrl, zuordnen, bank-aktualisieren, fixkosten-uebernehmen.
    • Ein Abo past_due (gefährdet), alle 4 Altersgruppen mit Werten.
    • 60 Rechnungen aller Status (eine Abo-Rechnung, ein Kundenname mit 60 Zeichen). 120 Buchungen: Ein- und Ausgänge, PDNG, Verwendungszweck mit 140 Zeichen ohne Leerzeichen, Gegenpartei mit 80 Zeichen, offene Eingänge mit vorschlaege (Rechnung und Auszahlung).
    • Ein fester Kunde mit E-Mail, aber ohne Straße, PLZ und Ort.
  • finanz-teilausfall: stand.stripe = { ok: false, fehler: 'Stripe ist gerade nicht erreichbar.' }, abos = { fehler: 'Die Abos konnten nicht geladen werden.' }, Kopf ohne Kontostand.

  • Step 2: Ansichten anlegen oder anpassen

ID Aufbau Prüfung (Schritt wirft bei Abweichung)
finanzen (vorhanden) /finance Alle Namen aus „Steuerelemente, die erreichbar bleiben“ (Global Constraints) per getByRole auffindbar. Mindestens ein button.link, alle mit transparentem background-color und border-top-width: 0px (so auch jedes button.link-danger in finanzen-tabellen).
finanzen-minus finanz-minus; Klick „Aufschlüsselung“ Text in .stat .v bzw. .fin-low .fin-amount, der mit „−“ beginnt, trägt .v.neg bzw. .fin-neg. Kein .v.neg ohne „−“ (außer in .fin-abo-stats). Kein .fin-breakdown li mit .fin-neg.
finanzen-tabellen finanz-minus .fin-forecast-amount, .fin-tx-amount mit „−“ tragen .fin-neg, die anderen nicht. thead von .fin-forecast, .fin-invoices, .fin-tx ist bei 1440 nicht position: absolute, bei 390 absolute. Dreimal .fin-body mit container-type: inline-size, kein .overlay darin. Zwischen #finanzen-rechnungen und #finanzen-buchungen liegen 36 px (± 1 px), nicht 72. Bei 1440 liegen die Oberkanten der beiden Abschnitte in .fin-cols gleich hoch (± 1 px).
finanzen-pager finanz-minus, zu #finanzen-buchungen, „Weiter“ Fuß „Seite 2 von 3 · 120 Buchungen“, 50 .fin-tx tbody tr.
finanzen-anker finanz-minus, Klick „Zu den Rechnungen“, 800 ms #finanzen-rechnungen hat scroll-margin-top 16px (1440) bzw. 68px (390), seine Oberkante liegt dort (± 1 px).
finanzen-teilausfall finanz-teilausfall, Szenario fehler, /rechnungen und /buchungen → 500 (page.route), Reload Mindestens 2 [role=alert] und 2 „Erneut versuchen“, h1 „Finanzen“ sichtbar, „Aktualisieren“ nicht disabled.
finanzen-fehler Szenario fehler, /uebersicht → 500, Reload [role=alert] und klickbares „Erneut versuchen“.
dlg-rechnung (vorhanden) Klick „Rechnung schreiben“ role=dialog mit Name „Rechnung schreiben“, Klasse dialog-wide. Overlay-Rechteck so groß wie das Fenster, kein Vorfahr .fin-body. Fokus im Dialog.
dlg-rechnung-kunde „Mü“ in #fin-invoice-customer Feldbreite ≥ 90 % von .dialog-body. [role=listbox] bei 1440 ganz im .dialog, bei 390 ≥ 120 px sichtbar. Esc schließt nur die Liste, das zweite Esc den Dialog.
dlg-rechnung-enter Fokus in Beschreibung, Enter; ctx.backend.calls zählt POST …/rechnungen Dialog bleibt, 0 Requests. „Als Entwurf speichern“ ohne Kunde zeigt .msg.err[role=alert] im sichtbaren Teil von .dialog-body.
dlg-zuordnen-zahlung (vorhanden) Klick „Zuordnen“ an erster offener Eingangsbuchung Vorschläge sind label.check mit Radio. 12 × Tab und 12 × Umschalt+Tab bleiben im .dialog.
dlg-zuordnen-bestaetigung Vorschlag mit Rechnung, „Zuordnen“ .fin-confirm nennt „in Stripe als bezahlt markiert“, Fuß „Zurück“ und „Ja, als bezahlt markieren“, ctx.backend.calls enthält kein POST …/zuordnen. Esc schließt ohne Request.
dlg-zuordnen-busy wie oben, POST …/zuordnen 1500 ms verzögert, „Ja, …“ Währenddessen .dialog-close disabled, Esc lässt den Dialog offen. Danach schließt er.
finanzen-einstellungen (vorhanden) /finance/einstellungen h2 „Einstellungen“, „Bankzugang“, „Fixkosten“, Link „← Zurück zu den Finanzen“.
finanzen-einstellungen-fehler Mindestpuffer „abc“, „Speichern“, dann „Neue Position“ [aria-invalid="true"] hat box-shadow mit rgba(192, 18, 60, .msg.err[role=alert] am Feld. h3 „Neue Position“, Fokus in #fin-fix-name.
finanzen-bank-callback, -ok /finanzen/bank-callback, bzw. mit ?code=x&state=y „Zu dieser Seite liegt keine Rückmeldung der Bank vor.“ bzw. .msg.ok mit „Bankzugang erneuert, gültig bis“.
kunde-invoices (vorhanden), kunde-invoices-adresse Kunde ohne Anschrift aus finanz-minus (andere URL als kunde-invoices) .fin-address-warn-page mit Rolle alert und „Zur Übersicht“. „Senden“ eines Entwurfs über 250 € ist disabled.
  • Step 3: Gate-Eintrag

Ein Eintrag nach dem Schema der Übersicht. p7 ist p6 plus icon-svg für die FIN-Ansichten und die aller früheren Pakete. Eine ignore-Liste des Vorgängers bleibt unverändert.

p7: {
  ...GATES.p6,
  scoped: {
    ...GATES.p6.scoped,
    'icon-svg': { ...GATES.p6.scoped['icon-svg'], views: [...GATES.p6.scoped['icon-svg'].views, /* IDs aus FIN */] },
  },
},

Die absichtlichen 500er der zwei Fehleransichten schreibt der Browser als console.error („Failed to load resource“). Deshalb laufen sie im Szenario fehler, dessen Meldungen console-error ausnimmt. Nimmt checks.mjs nur Meldungen der eigenen Fehlerantworten aus, wird die Ausnahme um Meldungen zu …/api/admin/finanzen/ erweitert.

  • Step 4: Vorher-Lauf

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && FIN=finanzen,finanzen-minus,finanzen-tabellen,finanzen-pager,finanzen-anker,finanzen-teilausfall,finanzen-fehler,finanzen-einstellungen,finanzen-einstellungen-fehler,finanzen-bank-callback,finanzen-bank-callback-ok,dlg-rechnung,dlg-rechnung-kunde,dlg-rechnung-enter,dlg-zuordnen-zahlung,dlg-zuordnen-bestaetigung,dlg-zuordnen-busy,kunde-invoices,kunde-invoices-adresse && "$W/build.sh" "$WT" p7 && "$W/shots.sh" p7 "$W/shots/p7-vorher" --only "$FIN" --gate p7 Expected: Exit 1. icon-svg meldet finanzen, finanzen-einstellungen, dlg-rechnung, dlg-zuordnen-zahlung. Die Schritte schlagen an in finanzen, finanzen-minus, finanzen-tabellen, finanzen-anker (390 px), finanzen-einstellungen-fehler, dlg-rechnung* und dlg-zuordnen-zahlung. finanzen-pager, finanzen-teilausfall, finanzen-fehler, dlg-zuordnen-bestaetigung und dlg-zuordnen-busy sind Wächter für heutiges Verhalten und laufen schon vorher durch.

Task 3: Seite, Statuszeile, Liquiditätskopf, Aufgaben; Kpi löschen

Files:

  • Modify: src/pages/FinancePage.jsx, src/components/finance/StatusLine.jsx, LiquidityHead.jsx, TodoList.jsx, src/components/ui/index.js
  • Delete: src/components/ui/Kpi.jsx
  • Modify: src/styles/global.css (Marken um den Finanzblock, Regionen „Allgemein“ bis Block 2: Zu erledigen, .kpi*)

Interfaces:

  • Consumes: Stat({ label, value, sub, tone }), Card, Badge, EmptyState, PageHeader, Button (P1), isNegText, noticeProps (Task 1).

  • Produces: die Klassen .fin-stack, .fin-top, .fin-block, .fin-body, .fin-table-wrap, .fin-notice, .fin-load-error, .fin-cols, .fin-neg, .fin-flag, .fin-amount, .fin-sr für Task 4 bis 8.

  • Step 1: P1-Voraussetzungen prüfen

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "Stat|Dialog" src/components/ui/index.js && rg -n "\.stat \.sub|\.table-foot|\.msg\b|\.loading|\.field-row\b" src/styles/global.css | head Expected: Stat und Dialog sind exportiert, die Klassen definiert. Fehlt ein Export, wird er in index.js ergänzt.

  • Step 2: Finanzblock rahmen, CSS-Region „Allgemein“

Zuerst die Grenzen setzen.

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "^/\* (--- Finanzen|Finanzen: Einstellungen|=====|Kunden-Unterseiten|Projekte \(P5\))" src/styles/global.css Expected: Der Finanzblock beginnt bei /* --- Finanzen --- */ und endet vor dem nächsten Blockkommentar (P6, KOMPAT …) bzw. am Dateiende. Davor kommt die Marke /* ===== FINANZEN (P7) ===== */, dahinter /* ===== ENDE FINANZEN ===== */.

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n -A2 "===== FINANZEN \(P7\)" src/styles/global.css Expected: die Marke, danach .fin-amount.

.fin-amount und .fin-sr bleiben (Werte gleich). Neu oder geändert:

Regel Wert
.fin-neg, .fin-flag color: var(--red-fg), .fin-flag zusätzlich font-weight: 600
.fin-stack keine eigene Regel (Block, kein gap, siehe Entscheidung oben)
.fin-stack > * + * margin-top: 36px
.fin-top display: flex; flex-direction: column; gap: 20px
.fin-block scroll-margin-top: 16px, bei ≤ 860 px 68px (Mobil-Leiste 52 px + 16 px)
.fin-body container-type: inline-size (einzige Stelle)
.fin-table-wrap overflow-x: auto
.fin-notice, .fin-page-notice margin: 0 0 8px, margin-bottom: 16px
.fin-load-error display: grid; gap: 12px; justify-items: start; padding: 16px 0
.fin-cols einspaltig, gap: 0 56px (den Zeilenabstand liefert .page section + section); ab 1100 px minmax(0,1.8fr) minmax(0,1fr), .fin-cols-half 2 × minmax(0,1fr) und zusätzlich .fin-stack .fin-cols > section + section { margin-top: 0 }, damit beide Spalten oben bündig stehen (höhere Spezifität als die globale Regel, kein !important).

Gelöscht: .fin-spin, .fin-hint*, .fin-link*, .fin-stack .kpi-grid. P5 legt den Reset für button.link an. Zeigt die Prüfung in finanzen oder finanzen-tabellen (Task 2) Browser-Rahmen an button.link oder button.link-danger, kommt button.link-danger in dieselbe Regel (background: none; border: 0; padding: 0; font: inherit; cursor: pointer).

  • Step 3: CSS-Blöcke 0, 1, 2

  • Block 0: Die Statuszeile hat keinen Kasten: .fin-status Flex, space-between, umbrechend, Abstand 8px 16px, 13 px, --text-2. .fin-status-items Flex, Abstand 4px 24px. .fin-status-warn --orange-fg, 600.

  • Block 1:

    • .stats.fin-head ist ein Raster repeat(auto-fit, minmax(220px, 1fr)), Abstand 24px 48px, margin: 0. Die Handy-Regel von .stats (3 Spalten) würde die breite dritte Kennzahl abschneiden. Die Doppelklasse schlägt .stats durch Spezifität.
    • .stats.fin-head .sub 13 px --text-2. .fin-breakdown-total mit border-top: 1px solid var(--line), 600.
    • .fin-lows Raster auto auto minmax(0,1fr). .fin-low .fin-amount 18/26 px, 600.
  • Block 2: .fin-todos und .fin-todo mit Haarlinien var(--line). .fin-todo Raster 80px minmax(0,1fr) auto, Abstand 12px 16px, padding: 12px 0, bei ≤ 860 px einspaltig. Die Streifenklassen .fin-todo-hoch/-mittel und .fin-todos-more entfallen.

  • Step 4: Kpi und .kpi* löschen

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git rm -q src/components/ui/Kpi.jsx. Dann die Zeile export { default as Kpi } aus src/components/ui/index.js und alle .kpi-Regeln samt .fin-head .kpi-value aus global.css entfernen (falls P1 sie nicht schon gelöscht hat). Expected: Nach Step 5 liefert rg -n "Kpi|kpi" src keinen Treffer.

  • Step 5: Komponenten umbauen

  • FinancePage:

    • Wurzel .page. Kopf: PageHeader, „Einstellungen“ als Button as={Link} ohne Icon. Ladezustand loading.
    • Ohne Daten: msg err plus Button variant="primary" „Erneut versuchen“, ohne Card und style.
    • Mit Daten: .fin-stack > [Teilfehler: msg warn mit .link „Erneut versuchen“], .fin-top (StatusLine, LiquidityHead), TodoList, .fin-cols (ForecastTable, TaxCard), .fin-cols.fin-cols-half (AboCard), InvoiceTable, TransactionTable.
    • assignNotice als msg warn fin-page-notice mit .link „Schließen“. AssignDialog bleibt am Ende der .page.
  • StatusLine: Zeilen als .fin-status-item (Warnungen .fin-status-warn, ohne Icon). „Aktualisieren“/„Aktualisiere …“ ist ein Standardknopf. Hinweis über noticeProps(hint.limited ? 'info' : 'warn').

  • LiquidityHead:

    • <div className="stats fin-head"> mit drei Stat. Kontostand: tone = isNegText(euroWhole(…)) ? 'neg' : ''. Verfügbar: tone = shortfall ? 'neg' : '', Flagge „Rücklagen nicht gedeckt“ als .fin-flag.
    • Tiefster Stand ohne tone, Beträge in .fin-lows bekommen .fin-neg bei low.negative.
    • „Aufschlüsselung“ ist <button className="link"> mit <span aria-hidden="true">▾/▴</span>, die Zeilen von breakdownRows bleiben neutral.
  • TodoList: Card mit Badge „n offen“ als Aktion. Zeile .fin-todo ohne Zusatzklasse für die Dringlichkeit, Aktion als <button className="link"> („Moment …“ während des Laufs, alle gesperrt), Fehler als msg warn. „Alle N anzeigen“ als .link in <div className="table-foot">.

  • Step 6: Hygiene, Bau, Bild

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|style=\{|size=\"sm\"|ui-btn|Kpi" src/pages/FinancePage.jsx src/components/finance/{StatusLine,LiquidityHead,TodoList}.jsx; "$W/build.sh" "$WT" p7 && "$W/shots.sh" p7 "$W/shots/p7-t3" --only finanzen,finanzen-minus,finanzen-anker,finanzen-teilausfall,finanzen-fehler --gate p6 Expected: rg ohne Treffer, Gate Exit 0. In finanzen-minus.png: drei Kennzahlen nebeneinander, Kontostand und Verfügbar rot, beim „Tiefsten Stand“ nur der negative Betrag, Aufgaben als Haarlinienzeilen ohne Streifen.

  • Step 7: Commit
git add src/styles/global.css src/pages/FinancePage.jsx src/components/finance/StatusLine.jsx src/components/finance/LiquidityHead.jsx src/components/finance/TodoList.jsx src/components/ui/index.js
git commit -m "Finanzen: Seitenkopf, Statuszeile, Kennzahlen und Aufgaben ohne Karten; Kpi entfernt" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"

(git rm hat Kpi.jsx schon gestaged.)

Task 4: Vorschau, Steuer, Abos

Files:

  • Modify: src/components/finance/ForecastTable.jsx, TaxCard.jsx, AboCard.jsx, src/styles/global.css (Blöcke 3, 4, 5)

Interfaces:

  • Consumes: .fin-body, .fin-table-wrap, .fin-neg, .fin-cols (Task 3), Stat, .table, .stats.

  • Produces: Signaturen unverändert (ForecastTable({ vorschau, kontostandCents, stripeDown, abosFehler }), TaxCard({ steuer }), AboCard({ abos })).

  • Step 1: CSS Blöcke 3, 4, 5

  • Vorschau: .fin-forecast-label overflow-wrap: anywhere. Randzellen-Padding, th:first-child und Hover-Regeln entfallen. @container (max-width: 560px) bleibt mit denselben Flächen (date kind amount / label / sicher erwartet), Trenner 1px solid var(--line), padding: 10px 0.

  • Steuer: .fin-facts Raster, Abstand 16 px. .fin-fact mit Haarlinie unten var(--line), der letzte ohne. .fin-fact-big 22/30, 600. .fin-fact-unknown --orange-fg.

  • Abos: .fin-abos und .fin-abo mit Haarlinien var(--line). .fin-abo Raster minmax(0,1fr) auto, padding: 12px 0.

  • Gelöscht: .fin-stats, .fin-stat-*, .fin-todo-card, .fin-forecast-card, .fin-tax-card, .fin-abo-card.

  • Step 2: Komponenten umbauen

  • ForecastTable: Card title ohne className und bodyStyle. Darunter .fin-body > .fin-table-wrap > <table className="table fin-forecast …"> (Rollen, caption.fin-sr, data-label unverändert). Betragszelle mit fin-neg bei isNegText(row.amountText). Hinweise unter der Tabelle als msg warn (role="status") ohne Icon, die Annahme als msg muted.

  • TaxCard: gleiche Struktur ohne Card-Klasse. v.noteText steht als <p className="msg muted fin-tax-note">, Wortlaut unverändert (rechtlich relevant).

  • AboCard: <div className="stats fin-abo-stats"> (Haken für finanzen-minus, ohne eigene Regel) mit Stat „Abo-Einnahmen pro Monat“, „zahlende Kunden“ und, nur wenn gesetzt, „gefährdet“ mit tone="neg". Liste wie bisher, Fehlerfall msg warn ohne Icon.

  • Step 3: Hygiene, Bau, Bild

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|style=\{|bodyStyle|Kpi" src/components/finance/{ForecastTable,TaxCard,AboCard}.jsx; "$W/build.sh" "$WT" p7 && "$W/shots.sh" p7 "$W/shots/p7-t4" --only finanzen,finanzen-minus,finanzen-teilausfall --gate p6 Expected: rg ohne Treffer, Exit 0. Bild: bei 390 px Vorschau-Zeilen dreizeilig (Datum/Art/Betrag, Posten, beide Salden), bei 1440 als Tabelle; Steuer als Liste mit Haarlinien.

  • Step 4: Commit
git add src/styles/global.css src/components/finance/ForecastTable.jsx src/components/finance/TaxCard.jsx src/components/finance/AboCard.jsx
git commit -m "Finanzen: Vorschau, Steuer und Abos ohne Karten, Container-Query auf Wrapper" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"

Task 5: Rechnungen, Buchungen, Blätterleiste, Kundenreiter

Files:

  • Modify: src/components/finance/InvoiceTable.jsx, TransactionTable.jsx, Pager.jsx, AddressWarning.jsx, CustomerInvoices.jsx, src/styles/global.css (Block 6 und 7 ohne Dialoge)

Interfaces:

  • Consumes: .fin-body, .fin-table-wrap, .fin-notice, .fin-load-error, .table, .seg, .toolbar, .table-foot, .stats, Stat, noticeProps, isNegText.

  • Produces: Signaturen unverändert (InvoiceTable, TransactionTable, Pager({ page, pages, total, noun, onPage }), AddressWarning({ customer, onShowOverview, variant }), CustomerInvoices({ customer, onShowOverview })) und export const TRANSACTIONS_ANCHOR = 'finanzen-buchungen'.

  • Step 1: CSS Block 6 und 7

  • .stats.fin-ages: Liste ohne Zeichen, Raster repeat(4, minmax(0,1fr)), Abstand 16px 32px, margin: 0 0 16px; .fin-ages .stat .v 18/26 px; @container (max-width: 860px) 2 Spalten.

  • .fin-block .seg { flex-wrap: wrap; max-width: 100% }, bei ≤ 860 px Knopf-Padding 0 9px. .fin-pager (mit .table-foot): justify-content: center; gap: 16px, .fin-pager-info tabular.

  • Tabellen: Abstands- und Randzellenregeln entfallen (.table aus P1 gilt). Es bleiben .fin-inv-customer und .fin-tx-counterparty mit overflow-wrap: anywhere, .fin-inv-number, .fin-inv-created/-due und .fin-tx-date nowrap, .fin-inv-statusbox Raster 3 px, .fin-actions Flex gap: 4px 14px, .fin-tx-remittance mit 2 Zeilen Klemme, .fin-tx-assign .badge { white-space: normal; height: auto; padding-block: 2px } (zwei Klassen schlagen .badge), .fin-tx-action nowrap rechts.

  • @container (max-width: 860px) (Rechnungen) und (max-width: 640px) (Buchungen) bleiben mit den Flächen number status / customer / created due / total remaining / actions bzw. date amount / what / assign action, Trenner var(--line), padding: 10px 0.

  • .fin-address-warn Flex umbrechend 4px 12px; -page margin: 0 0 16px, -dialog margin: 0.

  • Step 2: InvoiceTable und Pager

  • Aufbau: <section id={…ANCHOR} className="fin-block"> > Card title="Rechnungen" mit schwarzem „Rechnung schreiben“ als Aktion > .fin-body (Altersstreifen, .toolbar mit .seg, Meldung, Inhalt, Pager). InvoiceCreateModal bleibt Geschwister der Card.

  • Filter: .seg mit aria-pressed und Klasse on. Meldung über noticeProps(message.tone, 'fin-notice'). Altersstreifen: Stat label={cell.label} value={cell.sumText} sub={…Rechnung(en)} je <li>.

  • Zeilenaktionen: „Zahlungsseite“ und „PDF“ als <a className="link" target="_blank" rel="noopener noreferrer"> mit unverändertem aria-label, „Senden“ und „Erinnerung senden“ als .link-Knopf, „Stornieren“ und „Löschen“ als Button variant="danger". Sperren, Laufbeschriftungen („Sende …“, „Storniere …“, „Lösche …“) und title am gesperrten „Senden“ bleiben.

  • Tabelle <table className="table fin-invoices" role="table">, Rollen, caption.fin-sr, data-label unverändert. Pager: <nav className="table-foot fin-pager"> mit „Zurück“/„Weiter“ als Button variant="quiet".

  • Step 3: TransactionTable, AddressWarning, CustomerInvoices

  • TransactionTable: Card title="Buchungen" in <section id=TRANSACTIONS_ANCHOR className="fin-block">, Inhalt in .fin-body. Laden loading, Fehler fin-load-error mit msg warn und Standardknopf, Stripe-Hinweis msg warn ohne Icon. Betrag fin-amount fin-tx-amount plus fin-neg bei isNegText, fin-tx-out bleibt. „Zuordnen“/„Ändern“ als .link-Knopf mit aria-label.

  • AddressWarning: <div className="msg warn fin-address-warn fin-address-warn-{variant}" role="alert"> mit <button type="button" className="link"> bzw. Link className="link". Der Knopf trägt immer type="button", weil die Variante dialog in einem Dialog as="form" steht.

  • CustomerInvoices: Sperrfall als EmptyState ohne Card, „Daten erfassen“ bleibt schwarz. Sonst AddressWarning und InvoiceTable.

  • Step 4: Hygiene, Bau, Bild

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|style=\{|size=\"sm\"|bodyStyle|FaSpinner" src/components/finance/{InvoiceTable,TransactionTable,Pager,AddressWarning,CustomerInvoices}.jsx; "$W/build.sh" "$WT" p7 && "$W/shots.sh" p7 "$W/shots/p7-t5" --only finanzen,finanzen-tabellen,finanzen-pager,finanzen-anker,finanzen-teilausfall,kunde-invoices,kunde-invoices-adresse --gate p7 Expected: rg ohne Treffer, Exit 0 (kein overflow-x bei 390 px trotz langer Namen), alle Schritte laufen durch. Bild: Altersstreifen 4 Spalten (1440) bzw. 2 (390), Filter als graue Schiene, Tabellen mit Haarlinien und Textaktionen.

  • Step 5: Commit
git add src/styles/global.css src/components/finance/InvoiceTable.jsx src/components/finance/TransactionTable.jsx src/components/finance/Pager.jsx src/components/finance/AddressWarning.jsx src/components/finance/CustomerInvoices.jsx
git commit -m "Finanzen: Rechnungen und Buchungen als Tabellen ohne Karten, Textaktionen, Blaetterleiste" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"

Task 6: Einstellungen, Bankzugang, Fixkosten, Bankrückkehr, FormField

Files:

  • Modify: src/pages/FinanceSettingsPage.jsx, src/pages/BankCallbackPage.jsx, src/components/finance/SettingsCard.jsx, BankAccessCard.jsx, FixedCostsCard.jsx, FormField.jsx, src/styles/global.css (Region „Finanzen: Einstellungen“)

Interfaces:

  • Consumes: .field, .hint, .field-row, .input/.select/.textarea mit Fehlerring bei [aria-invalid="true"] (P1), .kv, .msg, noticeProps.

  • Produces: FormField({ id, label, hint, error, children }) unverändert. Das Kind bekommt weiter id, aria-invalid, aria-describedby.

  • Step 1: CSS Einstellungen

  • .fin-fieldset Raster gap: 16px, ohne Rand und Innenabstand. .fin-form-field { min-width: 0 }. .fin-input-amount rechts. .fin-counter 12 px --text-2 rechts, margin-top: -8px, .fin-counter-over --danger (Formularfehler), 600. .fin-form-actions Flex, gap: 12px, margin-top: 16px.

  • .fin-fixed-form ohne Rahmen, nur padding-bottom: 24px; margin-bottom: 8px; border-bottom: 1px solid var(--line). .fin-form-title 14/20, 600. .fin-result 16 px, 600.

  • .fin-fixed-list und .fin-fixed mit Haarlinien var(--line). .fin-fixed Raster minmax(0,1fr) auto, padding: 12px 0. .fin-fixed-off Name und Betrag --text-2.

  • Gelöscht: .fin-form-grid, .fin-bank-facts*, .fin-field-error, .fin-form-hint, .fin-form-notice, .fin-form-field .form-control[aria-invalid].

  • Step 2: Komponenten umbauen

  • FormField: .field fin-form-field, Hinweis <div className="hint">, Fehler <div className="msg err" role="alert">. Den Fehlerring liefert allein aria-invalid.

  • SettingsCard: Feldpaare als .field-row, Felder input fin-input-amount, Textarea textarea, Ladefehler fin-load-error mit msg warn. „Speichern“/„Speichere …“ schwarz ohne Icon. Meldung über noticeProps(notice.tone).

  • BankAccessCard: Card mit Badge als Aktion. Fakten als flaches <dl className="kv"> (<dt>/<dd className="num"> ohne <div>-Gruppen). Schwarz ist „Jetzt abrufen“ (bei canRefresh), sonst „Bank verbinden“, der andere Knopf ist Standard. „Rufe ab …“ und „Leite weiter …“ bleiben.

  • FixedCostsCard: „Neue Position“ im Kopf als Standardknopf ohne Plus. Formular mit <h3 id="fin-fix-form-title" className="fin-form-title">, .field-row, „Speichern“ schwarz, „Abbrechen“ Standard. Zeilenaktionen: „Bearbeiten“ und row.toggleLabel als .link, „Löschen“ als Button variant="danger". aria-label, window.confirm und Sperren bleiben.

  • Step 3: Seiten

  • FinanceSettingsPage: PageHeader mit „← Zurück zu den Finanzen“ als Button as={Link} (Standard, Pfeil ist Zeichen). Inhalt .fin-stack mit den drei Abschnitten.

  • BankCallbackPage: ohne Card und style. Ergebnis <p className="msg ok fin-result"> bzw. msg err fin-result mit unveränderter role. Laden loading („Bankzugang wird eingerichtet …“). „Zu den Finanzen“ bleibt schwarz.

  • Step 4: Hygiene, Bau, Bild

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|style=\{|size=\"sm\"|form-control|form-group|FaSpinner" src/pages/FinanceSettingsPage.jsx src/pages/BankCallbackPage.jsx src/components/finance/{SettingsCard,BankAccessCard,FixedCostsCard,FormField}.jsx; "$W/build.sh" "$WT" p7 && "$W/shots.sh" p7 "$W/shots/p7-t6" --only finanzen-einstellungen,finanzen-einstellungen-fehler,finanzen-bank-callback,finanzen-bank-callback-ok --gate p7 Expected: rg ohne Treffer, Exit 0, alle Schritte laufen durch. Bild finanzen-einstellungen-fehler: roter Ring und Fehlertext am Feld, keine Kästen.

  • Step 5: Commit
git add src/styles/global.css src/pages/FinanceSettingsPage.jsx src/pages/BankCallbackPage.jsx src/components/finance/SettingsCard.jsx src/components/finance/BankAccessCard.jsx src/components/finance/FixedCostsCard.jsx src/components/finance/FormField.jsx
git commit -m "Finanzen: Einstellungen, Bankzugang, Fixkosten und Bankrueckkehr ohne Karten" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"

Task 7: AssignDialog auf Dialog

Files:

  • Modify: src/components/finance/AssignDialog.jsx, src/styles/global.css (Dialog-Regionen für die Zuordnung)

Interfaces:

  • Consumes: Dialog({ title, onClose, busy, wide, footer, children }) (P1), Field, .check, .kv, .msg, useScrollIntoView.

  • Produces: AssignDialog({ transaction, invoices, invoicesLoading, invoicesError, invoicesLoaded, onClose, onDone }) unverändert. onClose({ refresh }) und onDone(buchung) verhalten sich wie bisher.

  • Step 1: CSS Zuordnen

  • .fin-foot: Flex, flex: 1, umbrechend, justify-content: flex-end, Abstand 8 px; bei ≤ 860 px .fin-foot .btn { flex: 1 1 auto }.

  • .fin-choices: Raster ohne Rand. .fin-choice (mit .check): volle Breite, padding: 8px 0, Haarlinie unten. Mit gewähltem Radio ist .fin-choice-label 600 (:has(input:checked)). .fin-choice-off opacity: .55; cursor: not-allowed. .fin-choice-detail 13 px --text-2, mit .warn --orange-fg.

  • .fin-confirm 600 in --orange-fg, kein Kasten. .fin-assigned { margin: 0 }. .kv .fin-tx-facts-amount { text-align: left; font-weight: 600 }.

  • Gelöscht: .fin-tx-facts*, .fin-confirm:focus, Kastenregeln von .fin-choice.

  • Step 2: Komponente umbauen

  • Eigener Esc-Listener, useDialogFocus, dialogRef, Overlay-Markup und „ד-Knopf entfallen. Es bleiben lockRef, aliveRef, dirtyRef, useScrollIntoView(errorRef, error) und confirmRef.

  • Gerüst <Dialog title="Buchung zuordnen" onClose={close} busy={disabled} footer={…}>. close() ist parameterlos (das „ד ruft es mit dem Klick-Event), prüft lockRef und ruft onClose({ refresh: dirtyRef.current }).

  • Body: Fakten als flaches <dl className="kv">, Fehler msg err mit ref={errorRef}, Hinweise msg muted/msg warn mit unveränderten Rollen. Vorschläge und Kategorien über radio(o) als <label className="check fin-choice…">. „Andere offene Rechnung“, „Fixkosten“ und „Notiz (optional)“ sind Field mit select/textarea. Beim Bestätigen steht der Text im .fin-confirm (role="group", tabIndex={-1}, Fokus per confirmRef wie heute).

  • Fuß je Zustand: zugeordnet „Schließen“ und, bei canUnassign, „Zuordnung lösen“ (beide Standard). Bestätigend „Zurück“ (Standard) und „Ja, als bezahlt markieren“ (schwarz, „Markiere …“). Sonst „Abbrechen“ (Standard) und „Zuordnen“ (schwarz, „Ordne zu …“, gesperrt ohne Auswahl).

  • Der Zweischritt bleibt: Bei einer Rechnung geht vor der Bestätigung keine Anfrage raus.

  • Step 3: Hygiene, Bau, Prüfung

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|useDialogFocus|keydown|overlay|fin-overlay|fin-close|form-control|form-label" src/components/finance/AssignDialog.jsx; "$W/build.sh" "$WT" p7 && "$W/shots.sh" p7 "$W/shots/p7-t7" --only dlg-zuordnen-zahlung,dlg-zuordnen-bestaetigung,dlg-zuordnen-busy --gate p7 Expected: rg ohne Treffer, Exit 0, alle drei Schritte laufen durch. Bild: weißer Dialog, Vorschläge als Radiozeilen mit Haarlinien, bei 390 px Bottom-Sheet mit umbrechendem Fuß.

  • Step 4: Commit
git add src/styles/global.css src/components/finance/AssignDialog.jsx
git commit -m "Finanzen: Buchung zuordnen auf Dialog-Baustein (Esc, Sperre, Vorschlaege als Zeilen)" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"

Task 8: InvoiceCreateModal auf Dialog

Files:

  • Modify: src/components/finance/InvoiceCreateModal.jsx, src/styles/global.css (Rest der Dialog-Region)

Interfaces:

  • Consumes: Dialog, Field, AutocompleteInput (P1, rendert .input), AddressWarning, .fin-foot (Task 7).

  • Produces: InvoiceCreateModal({ open, onClose, onCreated, fixedCustomer, onShowOverview }) unverändert, samt Vertrag onCreated(rechnung, info).

  • Step 1: CSS Rechnung schreiben

  • .fin-items ohne Rand. .fin-item Raster minmax(0,1fr) 80px 120px 110px auto, Abstand 8px 12px, align-items: end. .fin-item .input { width: 100% } (zwei Klassen schlagen .input), .fin-item .fin-amount rechts. .fin-item-label 12 px --text-2, ab der zweiten Zeile visuell versteckt. .fin-items-foot Flex space-between, margin-top: 8px. .fin-total Flex, gap: 16px, 600.

  • .fin-dialog-row Raster 180px minmax(0,1fr), Abstand 12 px.

  • Bei ≤ 860 px: .fin-item Raster minmax(0,.7fr) minmax(0,1fr) minmax(0,1fr) auto mit Flächen desc desc desc desc / qty price sum remove, Labels sichtbar. .fin-dialog-row einspaltig.

  • .autocomplete .input { width: 100% } legt P6 an (rg -n "\.autocomplete \.input" src/styles/global.css). Fehlt sie, kommt sie hierher (Prüfung in dlg-rechnung-kunde).

  • Alte Hüllenregeln löschen: .fin-close*, .fin-overlay, .fin-dialog samt :focus, .fin-dialog-error, .fin-dialog-warn, .fin-dialog-sub, .fin-dialog-actions, .fin-icon-btn*.

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "fin-(overlay|close|icon-btn)" src; rg -n "fin-dialog" src | rg -v "fin-dialog-row" Expected: Vor Step 2 trifft es noch InvoiceCreateModal.jsx, nach Step 2 höchstens den KOMPAT-Block.

  • Step 2: Komponente umbauen

  • Eigener Esc-Listener, useDialogFocus, dialogRef, Overlay und „ד-Knopf entfallen. Es bleiben lockRef, aliveRef, errorRef mit useScrollIntoView.

  • Gerüst <Dialog as="form" wide title="Rechnung schreiben" onClose={close} busy={disabled} onSubmit={(e) => e.preventDefault()} footer={…}>. Das Formular hat keinen Submit-Knopf. Jeder Knopf darin trägt type="button" (Button setzt keinen Standard-Typ): „Abbrechen“, „Als Entwurf speichern“, „Senden“, „+ Position“, „Entfernen“ und der Knopf in AddressWarning. Deshalb sendet Enter nie. close() prüft lockRef und ruft onClose().

  • Body: zuerst Fehler msg err mit ref={errorRef}. Kunde als Field mit htmlFor="fin-invoice-customer" (fester Kunde oder AutocompleteInput), Hinweise hint/msg warn, AddressWarning variant="dialog" mit dem bisherigen onShowOverview-Handler.

  • Positionen als <fieldset className="fin-items"> mit .fin-item-Zeilen (input, fin-amount, aria-labels unverändert). „Entfernen“ ersetzt den Papierkorb als <button type="button" className="link"> mit aria-label="Position n entfernen", gesperrt bei nur einer Position. „+ Position“ ist ein Standardknopf. Zahlungsziel und Notiz als Field in .fin-dialog-row.

  • Fuß <div className="fin-foot">: „Abbrechen“ und „Als Entwurf speichern“ (Standard), „Senden“ (schwarz, disabled und title wie heute). „Speichere …“ und „Sende …“ bleiben.

  • Esc: Ein offenes Vorschlagslisten-Popover ruft preventDefault, dann schließt Dialog nicht. Ohne Liste schließt Esc über close.

  • Step 3: Hygiene, Bau, Prüfung

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|useDialogFocus|keydown|overlay|form-control|form-group|form-label|FaTrash|FaPlus" src/components/finance/InvoiceCreateModal.jsx; "$W/build.sh" "$WT" p7 && "$W/shots.sh" p7 "$W/shots/p7-t8" --only dlg-rechnung,dlg-rechnung-kunde,dlg-rechnung-enter,kunde-invoices-adresse --gate p7 Expected: rg ohne Treffer, Exit 0, alle Schritte laufen durch (Overlay in Fenstergröße, Esc-Reihenfolge, Enter ohne Request, Feldbreite). Bild bei 390 px: Positionszeilen zweizeilig, Fuß mit umbrechenden Knöpfen, Vorschlagsliste sichtbar.

  • Step 4: Commit
git add src/styles/global.css src/components/finance/InvoiceCreateModal.jsx
git commit -m "Finanzen: Rechnung schreiben auf Dialog-Baustein (breit, Enter sendet nie, Esc schliesst Liste zuerst)" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"

Task 9: Gate P7, Review, Tag

  • Step 1: Rebase

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git fetch origin && git rebase origin/test && "$W/check.sh" Expected: Exit 0, # pass = N + 2 und # fail 0. Die Größen von JS und CSS aus dieser Ausgabe gehen in Step 3.

  • Step 2: Vollständiger Gate-Lauf, Hygiene und tote Klassen

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p7 && "$W/shots.sh" p7 "$W/shots/p7" --gate p7 Expected: Exit 0. Alle Prüfungen aus P1 sind für alle Ansichten leer, icon-svg für alle FIN-Ansichten und die der früheren Pakete.

Run:

source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh || exit 1
F="src/components/finance src/pages/FinancePage.jsx src/pages/FinanceSettingsPage.jsx src/pages/BankCallbackPage.jsx"
finblock() { sed -n '/^\/\* ===== FINANZEN (P7)/,/^\/\* ===== ENDE FINANZEN/p' src/styles/global.css; }
rg -n "react-icons|@tabler|style=\{|size=\"sm\"|ui-btn|form-control|form-group|form-label|overlay-content|overlay-close|spinner|Kpi|kpi|#[0-9a-fA-F]{3,8}\b|rgba?\(" $F
finblock | rg -n "#[0-9a-fA-F]{3,8}\b|rgba?\(|!important|var\(--(surface|border|text-muted|text-faint|r-|shadow-[123]|warn|info|ok)\b"
finblock | rg -o "\.fin-[a-z0-9-]+" | sort -u | sed 's/^\.//' | while read c; do rg -q "(^|[^A-Za-z0-9_-])${c}([^A-Za-z0-9_-]|$)" src --glob '!*.css' || echo "TOT $c"; done
finblock | wc -l; finblock | rg -c "container-type"
git diff --name-only design-p6..HEAD

Expected:

  • Die ersten beiden Befehle geben nichts aus.

  • Der dritte nennt nur fin-address-warn-dialog und fin-address-warn-page (per Template-Präfix gebaut, erlaubt).

  • Der Finanzblock hat 250 bis 360 Zeilen, container-type steht genau einmal da (.fin-body).

  • Der letzte Befehl (git diff --name-only design-p6..HEAD) nennt nur die Dateien aus „Global Constraints / Dateien“ und tests/unit/financeUi.test.mjs.

  • Step 3: Bundle-Größe

Die JS- und CSS-Größe aus der check.sh-Ausgabe von Step 1 wird mit Task 1 Step 1 verglichen. Keiner der beiden Werte ist größer.

  • Step 4: Code-Review (sonnet)

Geprüft wird der Diff design-p6..HEAD gegen die Spec-Abschnitte „Gestaltungsregeln“, „Bausteine“ und Paket P7. Prüfaufträge:

  • Verhalten gleich geblieben: Sperren, window.confirm, Zweischritt in AssignDialog, Rollen und data-label der Tabellen.

  • busy und close() sind in beiden Dialogen richtig angeschlossen, kein Dialog sitzt in .fin-body.

  • Jede Klasse fin-* ist definiert oder bewusst entfallen, Rot nur bei Minus, Texte nur wie in „Global Constraints“ geändert. Wichtige Befunde werden behoben, Kleinigkeiten notiert. Nach Korrekturen kommen Step 1 und Step 2 noch einmal, damit die Bilder den Stand zeigen, der getaggt wird.

  • Step 5: Tag und Übergabe

Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" && git tag design-p7 && git log --oneline origin/test..design-p7 && git rev-parse design-p7

Der Eigentümer bekommt:

  • die Screenshots aus shots/p7/1440x900 und shots/p7/390x844 (Auswahl: finanzen, finanzen-minus, finanzen-teilausfall, dlg-rechnung-kunde, dlg-zuordnen-bestaetigung, finanzen-einstellungen-fehler, kunde-invoices-adresse)

  • den Hinweis, dass negative Beträge jetzt rot sind und beide Dialoge neu auch mit Esc schließen

  • die Push-Zeile git -C /home/webklar/apps/ticket.webklar.com/dev-design push origin 'design-p7^{commit}:refs/heads/test'

  • die Rücknahme (nur solange P7 das jüngste Paket ist): git revert --no-commit design-p6..design-p7, Commit und Push durch den Eigentümer

  • den Hinweis für P10: Die KOMPAT-Zeilen zu .fin-link und .fin-dialog (760 px) sind tot und fallen mit dem Block weg.

  • Step 6: Nach dem Push (erst, wenn der Eigentümer gepusht hat)

  • Im Log von webklar-ticket-build steht „Deployment completed successfully“.

  • curl -s https://ticket.webklar.com/ | grep -o 'assets/index-[^"]*\.js' entspricht dist/index.html des Tags.

  • Ein lesender Sichttest im Chrome des Eigentümers: Finanzen mit Statuszeile und Rechnungsliste, Scroll von „Zu den Rechnungen“. Keinen Dialog öffnen, der etwas sendet.