538 lines
52 KiB
Markdown
538 lines
52 KiB
Markdown
# 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**
|
||
|
||
```js
|
||
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**
|
||
|
||
```bash
|
||
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.
|
||
|
||
```js
|
||
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**
|
||
|
||
```bash
|
||
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**
|
||
|
||
```bash
|
||
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**
|
||
|
||
```bash
|
||
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**
|
||
|
||
```bash
|
||
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**
|
||
|
||
```bash
|
||
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-label`s 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**
|
||
|
||
```bash
|
||
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:
|
||
```bash
|
||
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.
|