612 lines
57 KiB
Markdown
612 lines
57 KiB
Markdown
# WOMS „Weißes Design“ P6: Kunden-Unterseiten – 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:** Chat, Gutschriften, Abo-Angebote und E-Mail-Speicher sind hell und kastenlos. Die Chat-Liste besteht aus Haarlinien-Zeilen, die drei Listen sind Tabellen mit Spaltenpriorität, das Angebots-Formular ist ein Dialog. Der Chat wechselt bei 860 px zwischen Liste und Verlauf. `admin-areas.css` ist am Ende gelöscht, weil P4 die Kundenseite davon gelöst hat und P6 die letzten Nutzer umbaut.
|
||
|
||
**Architecture:**
|
||
- `chat.css` wird neu geschrieben. Jede Regel für Elemente im Chat-Container trägt das Präfix `.woms-chat` und ist strikt spezifischer als die Fremdregel des Portal-Bausteins. Das Seitenlayout (`.chat-page` usw.) bleibt ohne Präfix.
|
||
- Die Listen nutzen `Tabs`, `.table` mit `.hide-md`/`.hide-sm`, `Badge`, `EmptyState`. `CreditDetail` wird `.two-col` mit `Card`-Abschnitten und `.kv`. `OfferForm` wird ein `Dialog`.
|
||
- Neue reine Helfer liegen in `src/lib` und sind per Test gesichert. Das Screenshot-Werkzeug bekommt den echten Portal-Chat-Baustein, damit die Chat-Regeln gegen das Fremd-CSS geprüft werden.
|
||
|
||
**Tech Stack:** React 18, react-router-dom 6, reines CSS, `node --test`, Prüf- und Screenshot-Werkzeug aus P0 (`env.sh`, `check.sh`, `shots.sh`).
|
||
|
||
**Spec:** `docs/superpowers/specs/2026-10-04-weisses-design-design.md`, Abschnitte „Gestaltungsregeln“, „Bausteine“, „Übergangsschicht KOMPAT“, Paket P6, „Seiten ohne Prototyp-Vorlage“, „Texte“, „Was gleich bleibt“, Risiken (Chat-Composer, Vorschlagslisten). Anhang `…-umbaukarte.md`: Abschnitt 5 (P6), 7.2, 8.1 Punkte 13, 14, 19. Globale Regeln: `2026-10-05-weisses-design-00-uebersicht.md`.
|
||
|
||
## Global Constraints
|
||
|
||
Es gelten die Regeln der Übersicht, besonders „Befehle und Prüfungen“, „CSS-Reihenfolge“, „Parallele Arbeit an WOMS“, „Paketstart und Abnahme-Reihenfolge“ und „Bausteine benutzen“. Jeder Run-Block beginnt mit `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh &&`. Danach gelten `$WT` (Worktree, Arbeitsverzeichnis), `$W` (Werkzeug) und `$TMPDIR`. Für P6 kommt hinzu:
|
||
|
||
- **Voraussetzung:** Tag `design-p5` ist gesetzt. P4 hat `CustomerFunktionen`, `AboStatusCard` und `KundenansichtButton` umgebaut, alle `.funktionen-*`-Regeln nach `global.css` verlegt und die beiden Dateien von `admin-areas.css` gelöst (keine `.area-*`-Klasse, kein Import). Task 1 prüft das.
|
||
- **Dateien:** nur `CustomerChatPage`, `CreditsPage`, `OffersPage`, `StorageTasksPage`, `src/components/{chat,credits,offers}/*`, `src/styles/{chat,admin-areas,global}.css`, `src/lib/{credit,offer,storage}Format.js` samt Tests und `$W`. Nie anfassen: `loadChatComposer.js` samt Test, die Container in `ChatThread.jsx`, das Portal-Repo (nur Lesezugriff für den Snapshot in Task 2), die P4-Dateien.
|
||
- **Ladereihenfolge:** `main.jsx` importiert `App` vor `global.css`, also steht `chat.css` im Bundle vor `global.css`, und das Portal-CSS lädt zur Laufzeit zuletzt. Gleiche Spezifität verliert immer: `.btn` aus `global.css` überstimmt ein `.chat-back { display: none }`. Seitenregeln für Elemente mit globaler Klasse brauchen höhere Spezifität (`.chat-page .chat-back`), nie `!important`.
|
||
- **Texte:** Erlaubt sind nur „← Alle Unterhaltungen“ (Pfeil statt Icon), „Speichert …“ und „Wird geladen …“ (statt Spinner). Alles andere bleibt wörtlich, auch Spaltenköpfe, Hinweisabsätze und alle `confirm`/`alert`-Texte.
|
||
- **Hygiene und Inventur:** In den zehn P6-Dateien gibt es danach kein `react-icons`, kein `size="sm"`, kein `style={{` (heute 26). Treffer je Datei für `window.confirm`, `window.alert`, `busyRef`, `data-storage-done`, `id="calc-*"`, `id="credit-*"` bleiben: `CreditDetail.jsx` 16 (1 confirm, 9 busyRef, 6 IDs), `OfferList.jsx` 4, `OfferForm.jsx` 1, `StorageTasksPage.jsx` 3.
|
||
|
||
**Entscheidungen** (die Spec ist dazu still):
|
||
- **Entscheidung:** Die Prüf-Zeichen `✓`, `!`, `i` der „Automatischen Prüfung“ bleiben als Badge-Text. Die Umbaukarte sagt „ohne Glyphen“, aber die Spec „Texte“ erlaubt keine Textänderung dafür, und sonst trüge nur die Farbe den Status.
|
||
- **Entscheidung:** Die Filter sind `Tabs` ohne Zähler (die Spec nennt keine, es gibt keine neuen Abfragen).
|
||
- **Entscheidung:** Tabellenzeilen sind nicht klickbar (heute auch nicht), kein `tr.row` und damit kein `isRowActivation` (`src/lib/rowClick.js` aus P2). Aktionen stehen als `button.link` bzw. `.link-danger` in der letzten Zelle. Die offene Gutschrift trägt `aria-current="true"`.
|
||
- **Entscheidung:** Spaltenpriorität (immer / `hide-sm` / `hide-md`):
|
||
- Gutschriften: Kunde, Status, Aktion / Eingang, Rechnung / Leistung, Bezahlt bis.
|
||
- Angebote: Kunde, Angebot, Status mit Aktionen / Gültig bis / Angelegt.
|
||
- Speicher: Kunde, Aufgabe, Aktion / Fällig / Bestellt.
|
||
- **Entscheidung:** Der Chat-Zähler ist eine kleine Zahl in `--accent`, fett (wie `.nav-count.hot`), keine Pille. Die aktive Zeile hat `aria-current="true"` und `--accent-soft`.
|
||
- **Entscheidung:** Beim Portal-Composer bleibt die Geometrie (46 px, runde Knöpfe). Überschrieben werden Farben, Ring statt Rand, Hover, Fokus, Deaktiviert und unter 640 px wieder `font-size: 16px` im Eingabefeld (das heutige `font: inherit` hebt die Portal-Regel gegen das iOS-Zoomen auf). Der Datei-Chip ist weiß mit Ring, weil die Blase selbst `--bg-hover` ist. Die Anhang-Menü-Icons bleiben.
|
||
- **Entscheidung:** Lightbox und Kamera bleiben unberührt dunkel (am `body`, vom Präfix nicht erreichbar). Der Kamera-Knopf „Foto verwenden“ bleibt im Portal-Lila und geht als „bewusst offen“ in die Übergabe.
|
||
- **Entscheidung:** `OfferForm` bleibt ohne `<form>` (also kein `Dialog as="form"`, kein Enter-Submit, wie heute). Beide Fußknöpfe tragen trotzdem `type="button"`. „Abbrechen“ ist beim Speichern gesperrt, passend zu „ד und Esc.
|
||
- **Entscheidung:** `admin-areas.css` wird in P6 gelöscht. P4 hat die `.funktionen-*`-Regeln nach `global.css` verlegt und `CustomerFunktionen` und `AboStatusCard` von der Datei gelöst. Die P6-Seiten verlieren ihre `.area-*`-, `.credit-*`-, `.offer-*`- und `.storage-row`-Klassen und den Import. Danach hat die Datei keinen Nutzer mehr (Task 11).
|
||
- **Entscheidung:** Kleine Ergänzungen stehen in einem Block „Kunden-Unterseiten (P6)“ am Ende von `global.css`. `table-wrap` gilt nur für die drei P6-Tabellen, damit P2 bis P5 unberührt bleiben.
|
||
|
||
## Review Focus
|
||
|
||
Aus der Übersicht gehören zu P6:
|
||
- **Punkt 2, lange Inhalte am Handy:** Kundenname mit 60 Zeichen in allen Listen, E-Mail mit 50 Zeichen ohne Leerzeichen, URL mit 80 Zeichen im Chat, Angebotstext und interne Notiz je 200 Zeichen. `overflow-x` bei 390 px in allen P6-Ansichten.
|
||
- **Punkt 3, Teilausfall:** Das Szenario `fehler-portal` lässt alle `/api/team/*`-Anfragen mit 500 antworten. Die vier Seiten zeigen `.msg.err` und bleiben bedienbar, „Erneut versuchen“ lädt neu.
|
||
- **Punkt 4, nur Tastatur:** Tabellenaktion per Tab erreichbar. Esc schließt erst die Vorschlagsliste, dann den Dialog. Tab bleibt im Dialog, „ד und Esc sind beim Speichern gesperrt. Doppelklick auf „Genehmigen & gutschreiben“ löst einen POST aus, der 300-ms-Debounce einen Berechnungs-POST.
|
||
- **Punkt 5, viele Einträge:** `chat-viel` (150 Unterhaltungen, die Liste scrollt in sich) und `gutschriften-viel` (120 Anträge).
|
||
|
||
Dazu gilt für P6:
|
||
- **Fremd-CSS gewinnt nicht:** `chat-verlauf` liest berechnete Stile und vergleicht sie mit Sollwerten (Task 4). `.chat-back` ist bei 1440 px unsichtbar, bei 390 px sichtbar.
|
||
- **Vorschlagsliste im Dialog** ist vollständig erreichbar, der längste Speichern-Text passt bei 390 px in die Fußzeile.
|
||
- **Zwischenstufe 1000 px:** Die Ansichten `*-tablet` zeigen die Spaltenpriorität.
|
||
|
||
---
|
||
|
||
### Task 1: Ausgangslage und Lib-Helfer
|
||
|
||
**Files:**
|
||
- Modify: `src/lib/creditFormat.js`, `src/lib/offerFormat.js`, `src/lib/storageFormat.js` (neue Exporte, Stand vor P6)
|
||
- Test (neu): `tests/unit/creditFormat.test.mjs`, `tests/unit/offerFormat.test.mjs`, `tests/unit/storageFormat.test.mjs`
|
||
|
||
**Interfaces:**
|
||
- Consumes: `fmtDate`, `fmtDateTime`, `formatCents` aus `src/lib/format.js`, `isOverdue`.
|
||
- Produces:
|
||
- `creditActionLabel(status) → 'Prüfen' | 'Ansehen'`
|
||
- `creditStatusNote(r) → string`
|
||
- `checkGlyph(status) → '✓' | '!' | 'i'`
|
||
- `checkTone(status) → 'ok' | 'warn' | 'info' | 'muted'`
|
||
- `filterOffers(offers, filter) → offers[]`
|
||
- `offerRowActions(o) → { sendLabel: '' | 'E-Mail senden' | 'Erneut senden', canWithdraw: boolean }`
|
||
- `offerMailLine(o) → null | { tone: 'muted' | 'warn', text }`
|
||
- `storageDue(t, now) → { tone, label, sub, subMuted }`
|
||
|
||
- [ ] **Step 1: Paketstart und Ausgangslage prüfen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git status --short && git fetch origin && git merge-base --is-ancestor design-p5 HEAD && git rebase origin/test && "$W/check.sh"`
|
||
Expected: Exit 0, `git status --short` zeigt keine geänderten Dateien, `# fail 0`. Die Testzahl wird als N5 notiert. Schlägt `merge-base` fehl, fehlt `design-p5` im Branch: stoppen und melden.
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git log --oneline origin/test..feature/terminplaner-tp2 -- src/pages/CustomerChatPage.jsx src/pages/CreditsPage.jsx src/pages/OffersPage.jsx src/pages/StorageTasksPage.jsx src/components/chat src/components/credits src/components/offers src/lib/creditFormat.js src/lib/offerFormat.js src/lib/storageFormat.js src/styles/chat.css src/styles/admin-areas.css src/styles/global.css`
|
||
Expected: keine Ausgabe. Gibt es Treffer, stoppt der Agent und fragt den Eigentümer. Er empfiehlt, TP2 zuerst nach `test` zu bringen.
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|ui-btn|FaSpinner" src/components/customer/KundenansichtButton.jsx src/components/customer/AboStatusCard.jsx src/components/customer/CustomerFunktionen.jsx; rg -n "admin-areas|\barea-" src/components/customer; rg -n "funktionen-" src/styles/admin-areas.css`
|
||
Expected: keine Treffer. Ein Treffer heißt, dass P4 `CustomerFunktionen` oder `AboStatusCard` nicht vollständig von `admin-areas.css` gelöst hat: stoppen und melden, die P4-Dateien werden in P6 nicht umgebaut.
|
||
|
||
- [ ] **Step 2: Failing Tests schreiben** (Leerzeichen in Beträgen ist U+00A0)
|
||
|
||
`creditFormat.test.mjs` (4 Tests):
|
||
- `creditStatus` pinnt die Tonnamen: `offen` → `{ tone: 'warn', label: 'Zu prüfen' }`, `rueckfrage` → `muted`/„Rückfrage beim Kunden“, `genehmigt` → `ok`/„Genehmigt“, `abgelehnt` → `danger`/„Abgelehnt“, `'neu'` → `muted`/`'neu'`.
|
||
- `creditActionLabel`: `offen`, `rueckfrage` → `'Prüfen'`, sonst `'Ansehen'`.
|
||
- `creditStatusNote`: `{ status: 'genehmigt', amountCents: 1250, appliedAt: '2026-10-01T10:00:00Z' }` → `'12,50 €'`, ohne `appliedAt` → `'12,50 € · nicht gebucht'`, `{ status: 'offen', amountCents: 1250 }` → `''`.
|
||
- `checkGlyph`/`checkTone`: `ok` → `'✓'`/`'ok'`, `warn` → `'!'`/`'warn'`, `info` → `'i'`/`'info'`, `undefined` → `'i'`/`'muted'`.
|
||
|
||
`offerFormat.test.mjs` (4 Tests):
|
||
- `offerStatus`: `offen` → `warn`/„offen“, `aktiv` → `ok`/„aktiv“, `abgelaufen` → `muted`/„abgelaufen“, `zurueckgezogen` → `muted`/„zurückgezogen“.
|
||
- `filterOffers` mit Status `offen, aktiv, abgelaufen, zurueckgezogen` (ids `1` bis `4`): `'open'` → `['1','2']`, `'all'` → alle, `null` → `[]`, Eingabe unverändert.
|
||
- `offerRowActions`: `{ status: 'offen', mailSendable: true }` → `{ sendLabel: 'E-Mail senden', canWithdraw: true }`, mit `notifiedAt` → `'Erneut senden'`, `{ status: 'aktiv', mailSendable: true }` → `canWithdraw: false`, `{ status: 'abgelaufen', mailSendable: false }` → `{ sendLabel: '', canWithdraw: true }`, `{ status: 'zurueckgezogen' }` → `{ sendLabel: '', canWithdraw: false }`.
|
||
- `offerMailLine`: `{ notifiedAt: '2026-09-30T10:05:00.000Z' }` → `{ tone: 'muted', text: 'E-Mail gesendet 30.09.26, 12:05' }`, `{ mailSendable: true }` → `{ tone: 'warn', text: 'E-Mail nicht verschickt' }`, `{ status: 'offen', customerEmail: '' }` → `{ tone: 'warn', text: 'Keine E-Mail-Adresse hinterlegt' }`, `{ status: 'abgelaufen', customerEmail: '' }` und `{ status: 'offen', customerEmail: 'a@b.de' }` → `null`.
|
||
|
||
`storageFormat.test.mjs` (2 Tests, `t0 = Date.parse('2026-10-05T08:00:00Z')`):
|
||
- `storageDue`: `{ kind: 'einrichten', createdAt: '2026-10-01T08:30:00.000Z' }` → `{ tone: 'warn', label: 'jetzt einrichten', sub: 'bestellt 01.10.26, 10:30', subMuted: true }`. `{ kind: 'senken', dueAt: '2026-10-04T00:00:00.000Z' }` → `{ tone: 'warn', label: 'zum Periodenende', sub: '04.10.2026', subMuted: false }`, mit `dueAt: '2026-10-20T00:00:00.000Z'` → `tone: 'muted'`, `sub: '20.10.2026'`.
|
||
- `isOverdue`: vergangenes `dueAt` → `true`, zukünftiges oder fehlendes → `false`.
|
||
|
||
- [ ] **Step 3: Test laufen lassen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/creditFormat.test.mjs tests/unit/offerFormat.test.mjs tests/unit/storageFormat.test.mjs`
|
||
Expected: FAIL (`… is not a function`).
|
||
|
||
- [ ] **Step 4: Implementieren**
|
||
|
||
Die Regeln sind die heutigen Inline-Ausdrücke, herausgezogen: `creditActionLabel` aus `CreditList.jsx` Z. 52, `creditStatusNote` Z. 48, `checkGlyph` und `checkTone` aus `CreditDetail.jsx` Z. 11 und 211, `filterOffers` aus `OfferList.jsx` Z. 54, `offerRowActions` und `offerMailLine` Z. 93 bis 109 (Reihenfolge der Mail-Zeile: `notifiedAt`, `mailSendable`, `offen` ohne Adresse, sonst `null`), `storageDue` aus `StorageTasksPage.jsx` Z. 57 bis 59. Zeichenketten bleiben wörtlich.
|
||
|
||
- [ ] **Step 5: Tests grün und Commit**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" --nur-tests`
|
||
Expected: Exit 0, `# pass` = N5 + 10, `# fail 0`.
|
||
|
||
```bash
|
||
git add src/lib/creditFormat.js src/lib/offerFormat.js src/lib/storageFormat.js tests/unit/creditFormat.test.mjs tests/unit/offerFormat.test.mjs tests/unit/storageFormat.test.mjs
|
||
git commit -m "Design P6: reine Helfer fuer Gutschrift-, Angebots- und Speicherzeilen mit Tests" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 2: Werkzeug: echter Chat-Baustein, Fixtures, Ansichten, Gate p6
|
||
|
||
**Files** (unter `$W/`, außerhalb des Repos, kein Commit): Create `fixtures/portal/{chat-composer.css,chat-composer.js,README.md}`. Modify `backend.mjs`, `fixtures/standard.mjs`, `checks.mjs`, `views.mjs`, `gates.mjs`.
|
||
|
||
**Interfaces:**
|
||
- Consumes: `createBackend`, `VIEWS`, `GATES`, `runChecks` aus P0, Gate `p5`. `ctx.backend.calls` (Liste `{ method, url, body }` aller beantworteten Anfragen).
|
||
- Produces: Szenario `fehler-portal`, die Ansichten unten, Gate `p6`.
|
||
|
||
- [ ] **Step 1: Portal-Baustein als Snapshot**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && cp /home/webklar/apps/kundenbereich.webklar.com/build/public/chat-composer.{css,js} "$W/fixtures/portal/" && sha256sum "$W"/fixtures/portal/chat-composer.*`
|
||
Expected: css `b34fbaeb72922ddfbd57f1074e58e58dffc560cd1e9a31c98b12e66487885647`, js `ebb79581f9b307b303877f169fa5546bb98b4fc66320c40ae8fd78c965bace23`. Die README nennt Quelle, Datum 2026-10-05 und beide Hashes.
|
||
|
||
- [ ] **Step 2: Backend erweitern**
|
||
|
||
- `https://portal.test/chat-composer.css` und `.js` liefern den Snapshot (statt der leeren Dateien aus P0).
|
||
- Anhang-Adressen unter `https://portal.test/api/team/files/*` liefern ein 1×1-PNG bzw. 200 ohne Inhalt.
|
||
- `POST …/chats/:id/attachments` antwortet im Szenario `fehler-portal` und in `chat-anhang-fehler` mit 500.
|
||
- `POST …/offers` und `POST …/takeover-credits/:id/decision` antworten nach 800 ms.
|
||
- Szenario `fehler-portal`: jede Anfrage an `https://portal.test/api/team/*` bekommt 500 im Fehlerformat des Szenarios `fehler`. Deren Konsolenmeldungen gelten als absichtlich.
|
||
|
||
- [ ] **Step 3: Fixtures ergänzen** (Schlüssel nach dem Muster der vorhandenen, Formen laut Lesecode in den `*Format.js` und JSX-Dateien)
|
||
|
||
| Bereich | Fälle |
|
||
|---|---|
|
||
| Chats | 8 Unterhaltungen, 3 mit `unreadFromCustomer` (1, 12, 120), ein 60-Zeichen-Name, ein `lastText` mit 80-Zeichen-URL. Satz `viel` mit 150. |
|
||
| Verlauf Kunde A | 7 Nachrichten: Kundentext, Teamtext (`senderName` „Kenso Grimm“), Bild (`kind: 'image'`, 640×480), PDF (`Angebot-Kuehn-2026.pdf`, 183000 Byte), Sprachnachricht (`kind: 'voice'`, `durationMs: 23000`), Teamtext mit 80-Zeichen-URL, langer Kundentext. |
|
||
| Gutschriften | 5 Anträge (`offen`, `rueckfrage`, `genehmigt` gebucht, `genehmigt` ungebucht, `abgelehnt`), einer mit 60-Zeichen-Name und langem `services`/`provider`/`domain`. Satz `viel` mit 120. `getCredit` für einen offenen und einen ungebuchten genehmigten Antrag: 3 `requiredChecks`, `autoChecks` mit `ok`/`warn`/`info` (mit `detail`), `calc`, laufendes Abo, 1 weiterer Antrag, 2 Dateien, 3 Verlaufseinträge. `POST …/calc` → `{ calc }`. |
|
||
| Angebote | 6 Angebote: `offen` mit `mailSendable` ohne `notifiedAt`, `offen` ohne E-Mail, `aktiv` mit `scope: 'laufend'`, `abgelaufen` mit `mailSendable` und `notifiedAt`, `zurueckgezogen`, eins mit 60-Zeichen-Name, 50-Zeichen-E-Mail und 200-Zeichen-`internalNote`. `getOfferContext` für einen Kunden ohne Abo und einen mit laufendem Wartungs-Abo (`upcomingCharges`). |
|
||
| Speicher | `tasks`: `einrichten`, `senken` überfällig, `senken` später, einer `partial` mit `error`, einer mit 60-Zeichen-Name. `failed` mit 2 Einträgen, `allocatedGb` über `warnGb`. |
|
||
|
||
- [ ] **Step 4: Prüf-Ausnahmen erweitern**
|
||
|
||
- In `checks.mjs`: `dark-surface` nimmt zusätzlich `.woms-chat :is(.chat-send, .chat-audio-play, .chat-file-ext, .chat-tray-remove, .chat-tray-retry, .chat-tray-progress)` aus. Der Szenario-Name `fehler-portal` gilt für `console-error` wie `fehler` (erwartete 500-Meldungen ausgenommen).
|
||
- `icon-svg` ist dauerhaft für alles unter `.woms-chat` ausgenommen (Sprechblasen mit Anhängen zeichnet derselbe fremde Baustein). Das steht im Gate `p6` (Step 5) als `ignore`, nicht in `checks.mjs`.
|
||
|
||
- [ ] **Step 5: Ansichten und Gate eintragen**
|
||
|
||
`views.mjs` bekommt die Hilfe `tablet(page)`: ab 800 px Breite `setViewportSize({ width: 1000, height: 800 })`, sonst nichts. Die Schritte haben die Signatur `steps(page, ctx)`, Anfragen liest man aus `ctx.backend.calls`. Prüfungen in Schritten nutzen `assert` aus `node:assert/strict`. Eine Ausnahme erzeugt den Verstoß `step-failed` mit Ansicht und Meldung (das Gate endet mit Exit 1). Die Kundenseiten-Ansichten tragen die IDs der Übersicht (`kunde-chat`, `kunde-abo`, `kunde-gutschriften`, `kunde-funktionen`), es gibt keine zweite Ansicht für dieselbe Reiter-URL.
|
||
|
||
| ID | Pfad / Schritte | Prüfung im Schritt |
|
||
|---|---|---|
|
||
| `chat-verlauf` | `/customers/chat?kunde=<A>` | `.chat-back` bei 1440 `display: none`, bei 390 sichtbar. `.chat-side` bei 390 ausgeblendet. Stil-Sollwerte aus Task 4. |
|
||
| `chat-menue`, `chat-anhang-fehler` | wie `chat-verlauf`; Klick `.chat-attach-btn` bzw. `setInputFiles` (`x.pdf`) auf `.chat-composer input[type=file]:not([accept])` | `.chat-menu` sichtbar bzw. `.chat-tray-item.is-error` und `.chat-composer-note` sichtbar |
|
||
| `chat-tablet`, `gutschriften-tablet`, `angebote-tablet` | Ansicht mit `tablet` | Chat: Liste und Verlauf nebeneinander, `.chat-input-row` ohne Überlauf. Tabellen: `.hide-md`-Spalten aus, `.hide-sm`-Spalten sichtbar |
|
||
| `chat-viel`, `gutschriften-viel` | Satz `viel` | `.chat-list.scrollHeight > clientHeight` |
|
||
| `chat-fehler`, `gutschriften-fehler`, `angebote-fehler`, `speicher-fehler` | Szenario `fehler-portal` | `.msg.err` sichtbar; bei Chat und Speicher Klick auf „Erneut versuchen“, die Seite lebt |
|
||
| `gutschriften-antrag` | `/customers/gutschriften?antrag=<offen>` | – |
|
||
| `gutschriften-sperre` | wie oben; `page.on('dialog', d => d.accept())`; 3 Kästchen, `#credit-note` füllen, Doppelklick „Genehmigen & gutschreiben“; in `#calc-amount` 3 Ziffern mit 50 ms Abstand | nach 1500 ms genau 1 POST `…/decision` und 1 POST `…/calc` in `ctx.backend.calls` (`method`, `url`) |
|
||
| `angebote-dialog`, `angebote-vorschlag`, `angebote-laufend` | Klick „Neues Angebot“; im Kunden-Feld „Mu“ tippen; Kunde mit laufendem Abo wählen | `[role=dialog]` sichtbar; letzter `.autocomplete-item` per `elementFromPoint` erreichbar; bei 390 px `.dialog-foot.scrollWidth <= clientWidth` |
|
||
| `angebote-tastatur` | `page.on('dialog', d => d.accept())`. Bis zu 15 Mal Tab, bis der Fokus in `table` liegt. Dialog öffnen, Kunden-Feld tippen, Esc, Esc. Erneut öffnen, 30 Mal Tab. Kunde wählen, „Angebot anlegen“, während der 800 ms Esc | Fokus erreicht die Tabelle. Erstes Esc schließt nur die Liste, zweites den Dialog. Fokus bleibt im Dialog. Beim Speichern ist `.dialog-close` `disabled` und der Dialog nach Esc noch da |
|
||
|
||
Dazu die vorhandenen Ansichten `chat`, `gutschriften`, `angebote`, `speicher`, `kunde-chat`, `kunde-abo`, `kunde-gutschriften`, `kunde-funktionen`. In `gates.mjs` entsteht `p6` nach dem Schema der Übersicht: das Gate `p5` plus `icon-svg` für die Ansichten von P6 und aller früheren Pakete.
|
||
|
||
```js
|
||
const P6_VIEWS = ['chat', 'chat-verlauf', 'chat-menue', 'chat-anhang-fehler', 'chat-tablet', 'chat-viel', 'chat-fehler',
|
||
'gutschriften', 'gutschriften-tablet', 'gutschriften-viel', 'gutschriften-fehler', 'gutschriften-antrag', 'gutschriften-sperre',
|
||
'angebote', 'angebote-tablet', 'angebote-fehler', 'angebote-dialog', 'angebote-vorschlag', 'angebote-laufend', 'angebote-tastatur',
|
||
'speicher', 'speicher-fehler', 'kunde-chat', 'kunde-abo', 'kunde-gutschriften', 'kunde-funktionen']
|
||
const prev = GATES.p5.scoped['icon-svg']
|
||
GATES.p6 = {
|
||
...GATES.p5,
|
||
scoped: { ...GATES.p5.scoped, 'icon-svg': { views: [...new Set([...prev.views, ...P6_VIEWS])], ignore: [...(prev.ignore ?? []), '.woms-chat'] } },
|
||
}
|
||
```
|
||
|
||
`step-failed`, `external-request` und `console-error` sind wie in jedem Gate aktiv, `dark-surface`, `low-contrast` und `overflow-x` kommen über `GATES.p5.checks` für alle Ansichten.
|
||
|
||
- [ ] **Step 6: Rot-Beweis**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t2" --only chat-verlauf,angebote --gate p6; echo "exit $?"`
|
||
Expected: `exit 1` mit `icon-svg` mindestens in `angebote` (`FaPlus`). Das Bild `chat-verlauf` zeigt den echten Composer mit Anhängen.
|
||
|
||
### Task 3: Globale Ergänzungen
|
||
|
||
**Files:**
|
||
- Modify: `src/styles/global.css` (neuer Block am Ende)
|
||
|
||
**Interfaces:**
|
||
- Produces: `.table.table-wrap td` (lange Wörter brechen), `.table tr[aria-current="true"] td`, `.dialog-foot { flex-wrap: wrap }`, `.autocomplete .input { width: 100% }` (P7 baut auf die beiden letzten).
|
||
|
||
- [ ] **Step 1: Block anlegen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "aria-current|dialog-foot|\.autocomplete \.input" src/styles/global.css`
|
||
Expected: zeigt, welche Regeln P1 bis P5 schon angelegt haben. Der Block steht am Dateiende, hinter allen anderen Regeln, damit er bei gleicher Spezifität gewinnt. Unter `/* ===== Kunden-Unterseiten (P6) ===== */` stehen nur die fehlenden:
|
||
- `.table.table-wrap td { overflow-wrap: anywhere }`
|
||
- `.table tr[aria-current="true"] td { background: var(--bg-subtle) }`
|
||
- `.dialog-foot { flex-wrap: wrap }`
|
||
- `.autocomplete .input { width: 100% }`
|
||
|
||
- [ ] **Step 2: Bauen und Commit**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" --nur-build`
|
||
Expected: Exit 0, die Bundle-Größen werden ausgegeben.
|
||
|
||
```bash
|
||
git add src/styles/global.css
|
||
git commit -m "Design P6: kleine Tabellen-, Dialog- und Autocomplete-Ergaenzungen" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 4: `chat.css` neu, Fehlerzeile im Verlauf
|
||
|
||
**Files:**
|
||
- Modify: `src/styles/chat.css` (komplett neu), `src/components/chat/ChatThread.jsx` (Import Z. 2, Fehlerzeile Z. 120 bis 125)
|
||
- Test: `tests/unit/chatCss.test.mjs` (neu)
|
||
|
||
**Interfaces:**
|
||
- Consumes: Tokens der Spec (`--bg`, `--bg-hover`, `--bg-subtle`, `--grey-bg`, `--text*`, `--line*`, `--accent*`, `--red-fg`, `--danger`, `--radius*`, `--ring`, `--shadow-pop`), `.msg.err`, `Button`.
|
||
- Produces: `.woms-chat` mit `--chat-bubble-in`, `--chat-bubble-out`, `--chat-track`; Seitenklassen `.chat-page`, `.chat-side`, `.chat-list`, `.chat-list-item`, `.chat-list-top`, `.chat-list-last`, `.chat-list-time`, `.chat-unread`, `.chat-main`, `.chat-back`.
|
||
|
||
- [ ] **Step 1: Failing Test schreiben**
|
||
|
||
`chatCss.test.mjs` liest `chat.css` als Quelltext (Vorbild `adminRoute.test.mjs`), entfernt Kommentare und zerlegt per `/([^{}]+)\{/g` in Selektoren (ohne `@…`, Kommas gesplittet). 5 Tests:
|
||
1. Jeder Selektor mit `/\.chat-(bubble|meta|messages|notice|input-row|icon-btn|send|menu|tray|rec|audio|att|file|composer|drop-active)\b/` beginnt mit `.woms-chat` (`deepEqual(bad, [])`).
|
||
2. Kein `!important`.
|
||
3. `match(/@media \(max-width: 860px\)/)` und `doesNotMatch(/720px/)`.
|
||
4. `ChatThread.jsx` enthält unverändert `className="woms-chat" ref={zoneRef}`, `className="chat-messages" ref={boxRef}` und `<div ref={composerRef} />`.
|
||
5. `match(/\.chat-page \.chat-back\s*\{[^}]*display:\s*none/)`.
|
||
|
||
- [ ] **Step 2: Test laufen lassen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/chatCss.test.mjs`
|
||
Expected: FAIL bei 3 (alter 720-px-Wert) und 5. Test 4 besteht schon.
|
||
|
||
- [ ] **Step 3: `chat.css` neu schreiben**
|
||
|
||
Der Kopfkommentar nennt: Das Portal-CSS lädt zur Laufzeit zuletzt, Gleichstand verliert, deshalb ist jede Regel strikt spezifischer und es gibt kein `!important`. Alle P1-Variablen und -Fixes aus P1 Task 9 Step 2 bleiben unverändert. Neu sind nur `--chat-bubble-in: var(--bg-hover)`, `--chat-bubble-out: var(--grey-bg)` und `--chat-track: rgba(26,31,54,.2)`. Maße, Abstände, Schriftgrößen und Radien folgen dem Prototyp und den Tokens der Spec und entstehen beim Bauen. Verbindlich sind die Spezifitäten und Abweichungen der Tabellen sowie die Messwerte aus Step 6.
|
||
|
||
Überschreibungen des Portal-Bausteins. Die Spalte „Eigen > Fremd“ nennt die Spezifität der eigenen Regel vor der überstimmten Portal-Regel:
|
||
|
||
| Ziel | Selektor (Eigen > Fremd) | Abweichung vom Portal-Stil |
|
||
|---|---|---|
|
||
| Container, Verlauf | `.woms-chat`, `.woms-chat .chat-messages` | Flex-Spalte, `min-width: 0`. Verlauf scrollt in sich (`max-height: 60vh`), ohne Rand und Hintergrund. |
|
||
| Blase, Zeit | `.woms-chat .chat-bubble` (0,2,0 > 0,1,0), `.chat-bubble.mine`, `.chat-meta`, `.chat-notice` | Fremde Blase `--chat-bubble-in`, eigene `--chat-bubble-out` (rechts), kein Rand. `max-width: 78%`, `overflow-wrap: anywhere`. Zeit und Hinweis 12 px in `--text-2`. |
|
||
| Datei-Chip | `.woms-chat .chat-att-file` (0,2,0 > 0,1,0), `:hover` (0,3,0 > 0,2,0) | `var(--bg)`, `border: 0`, `box-shadow: var(--ring)`. Hover `--bg-hover`. |
|
||
| Audio, Fokus | `.chat-audio-bar` (0,2,0 > 0,1,0); `.chat-att-image:focus-visible`, `.chat-audio-bar:focus-visible` (0,3,0 > 0,2,0); `.chat-bubble.mine .chat-audio-play` (0,4,0 > 0,3,0); `.chat-audio-dl:hover` (0,3,0 > 0,2,0); `.chat-audio.is-error .chat-audio-time` (0,4,0 > 0,3,0) | Leiste `--chat-track`. Fokusring `2px solid var(--accent)` statt weiß. Abspielen `--chat-accent` mit `--chat-on-accent`. Download-Hover `--accent-soft`. Fehlerzeit `--red-fg`. |
|
||
| Eingabefeld | `.woms-chat .chat-input-row textarea` (0,2,1 > 0,1,1), `:focus` (0,3,1), Media `max-width: 640px` | `border: 0`, `box-shadow: var(--ring)`, `font: inherit`. Fokus `0 0 0 1px var(--accent), 0 0 0 4px var(--accent-ring)` ohne `outline`. Handy `font-size: 16px` (gegen das iOS-Zoomen). Höhe und Radius des Portals bleiben. |
|
||
| Icon-Knöpfe, Senden | `.chat-icon-btn` (0,2,0 > 0,1,0), `:hover:not(:disabled)` (0,4,0 > 0,3,0), `[aria-expanded="true"]` (0,3,0 > 0,2,0), `.chat-icon-btn:disabled`, `.chat-send:disabled` (0,3,0 > 0,2,0), `.chat-send.is-busy:disabled` (0,4,0 > 0,3,0) | `border: 0`, `box-shadow: var(--ring)`, `var(--bg)`. Hover `--bg-hover`. Offen `--accent-soft` mit 1-px-Akzentring. Deaktiviert `opacity: .55; cursor: not-allowed`, beschäftigt `opacity: .8; cursor: progress`. Senden bleibt über die P1-Variablen schwarz mit weißer Schrift. |
|
||
| Aufnahme | `.chat-rec .chat-icon-btn` (0,3,0 > 0,2,0), `:hover` (0,4,0 > 0,3,0); `.chat-rec-stop`, `.chat-rec-wave` (hinter der Icon-Knopf-Regel), `.chat-rec-dot` (0,2,0 > 0,1,0) | Knöpfe ohne Ring und Hintergrund, Hover `--accent-soft`. Farbe bzw. Punkt `--danger`. |
|
||
| Anhang-Menü | `.chat-menu` (0,2,0 > 0,1,0); `.chat-menu button` (0,2,1 > 0,1,1), `:hover`, `:focus-visible` (0,3,1 > 0,2,1) | Menü ohne Rand, mit `--shadow-pop`. Hover und Fokus `--bg-hover`, Fokus zusätzlich `outline: 2px solid var(--accent); outline-offset: -2px` (die Fremdregel setzt `outline: none`). |
|
||
| Vorschau, Hinweise | `.chat-tray-item` (0,2,0 > 0,1,0); `.chat-tray-item.is-error .chat-file-size` (0,4,0 > 0,3,0); `.chat-composer-note` (0,2,0 > 0,1,0), `.is-info` (0,3,0 > 0,2,0) | Vorschau `--bg-hover` mit transparentem Rand. Die rote Fehlervariante der Fremdregel (gleiche Spezifität, später geladen) bleibt absichtlich. Fehlertexte `--red-fg`, Info `--text-2`. |
|
||
| Ablegen | `.woms-chat.chat-drop-active::after` (0,2,1 > 0,1,1) | helle Fläche `rgba(255,255,255,.92)`, Schrift `--text`, Rand `--text-3` |
|
||
|
||
Seitenlayout ohne Präfix. Es folgt dem Prototyp, verbindlich sind:
|
||
- `.chat-page` ist ein Grid mit 320 px für die Liste und `minmax(0, 1fr)` für den Verlauf. Bei `max-width: 1100px` ist die Liste 260 px breit.
|
||
- `.chat-side` und `.chat-main` haben `min-width: 0`. Zwischen ihnen steht eine senkrechte Haarlinie (`--line`).
|
||
- `.chat-list` scrollt in sich (`max-height: 70vh; overflow-y: auto`). `.chat-list-item` ist eine Haarlinien-Zeile ohne Kasten und Rand, Hover `--bg-subtle`, `[aria-current="true"]` `--accent-soft`.
|
||
- `.chat-list-last` kürzt mit Ellipsis. `.chat-unread` ist eine kleine Zahl in `--accent`, fett, 12 px, ohne Pille.
|
||
- `.chat-page .chat-back { display: none }`, Spezifität (0,2,0) > `.btn` (0,1,0).
|
||
- Bei `max-width: 860px` (nicht 720 px): einspaltig. `.chat-page.has-thread .chat-side` und `.chat-page:not(.has-thread) .chat-main` sind `display: none`, Seite und Haupt ohne Rand und Innenabstand, `.chat-page .chat-back { display: inline-flex }`, `.woms-chat .chat-bubble { max-width: 90% }`.
|
||
|
||
- [ ] **Step 4: Fehlerzeile im Verlauf**
|
||
|
||
In `ChatThread.jsx` bleibt der Zeilencontainer `flex items-center gap-2 wrap`. Der Text wird `<span className="msg err" role="alert">`, der Knopf `<Button onClick={…}>Erneut versuchen</Button>` ohne Icon. Der Import `react-icons/fa6` entfällt. Container, Effekte und `chat-notice muted` bleiben.
|
||
|
||
- [ ] **Step 5: Test grün**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/chatCss.test.mjs tests/unit/loadChatComposer.test.mjs`
|
||
Expected: PASS.
|
||
|
||
- [ ] **Step 6: Gegen das echte Fremd-CSS prüfen**
|
||
|
||
`chat-verlauf` bekommt einen Schritt, der `getComputedStyle` liest und mit `assert.equal` vergleicht:
|
||
- `.chat-bubble.theirs` `background-color` = `rgb(246, 248, 250)`, `.chat-bubble.mine` = `rgb(235, 238, 241)`.
|
||
- `.chat-send` `background-color`/`color` = `rgb(17, 17, 17)`/`rgb(255, 255, 255)`.
|
||
- `.chat-attach-btn` `border-top-width` = `0px`, `box-shadow` ≠ `none`.
|
||
- `.chat-att-file` `background-color` = `rgb(255, 255, 255)`, `border-top-width` = `0px`.
|
||
- `.chat-audio-bar` `background-color` = `rgba(26, 31, 54, 0.2)`.
|
||
- `.chat-input-row textarea` `border-top-width` = `0px`.
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t4" --only chat-verlauf,chat-menue,chat-anhang-fehler --gate p6; echo "exit $?"`
|
||
Expected:
|
||
- Schritt-Prüfungen bestehen (kein `step-failed`). Der Bericht enthält höchstens `icon-svg` am alten Zurück-Pfeil von `CustomerChatPage` (Task 5). Nur dann ist `exit 1` hier erlaubt.
|
||
- `chat-verlauf` bei 1440 px: Blasen hellgrau, Senden schwarz mit weißer Schrift, Icon-Knöpfe weiß mit Ring, Datei-Chip weiß.
|
||
- `chat-anhang-fehler`: rote Fehlerzeile und rot umrandete Vorschau.
|
||
|
||
- [ ] **Step 7: Commit**
|
||
|
||
```bash
|
||
git add src/styles/chat.css src/components/chat/ChatThread.jsx tests/unit/chatCss.test.mjs
|
||
git commit -m "Design P6: chat.css neu (Praefix .woms-chat, Umschaltung bei 860 px), Fehlerzeile ohne Icon" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 5: Chat-Seite
|
||
|
||
**Files:**
|
||
- Modify: `src/pages/CustomerChatPage.jsx` (Z. 3, 61 bis 110), `src/styles/global.css` (KOMPAT-Regel `.side-count` löschen)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `PageHeader`, `Card`, `Button`, `EmptyState`, `AutocompleteInput` (Klasse `input`), `.msg.err`, Klassen aus Task 4, `lastLine`, `sortChats`.
|
||
- Produces: Seite und Adresse `/customers/chat?kunde=<id>` unverändert.
|
||
|
||
- [ ] **Step 1: Seite umbauen**
|
||
|
||
- `react-icons`, `size="sm"` und `Card pad={false}` um die Liste entfallen. Die Liste ist `.chat-list` direkt unter dem Suchfeld.
|
||
- Das Suchfeld bleibt `AutocompleteInput` („Neue Nachricht an Kunde…“, `onPick` öffnet nur die volle Beschriftung).
|
||
- Fehlerzeile: `div.flex.items-center.gap-2.wrap` mit `span.msg.err[role=alert]` und `Button` „Erneut versuchen“. Der Leerzustand „Noch keine Unterhaltungen“ bleibt `EmptyState`.
|
||
- Jede Unterhaltung ist ein `button.chat-list-item` mit `aria-current={c.customerId === kunde ? 'true' : undefined}`. Zeile 1 (`.chat-list-top`): `<strong>` Name und `.chat-list-time`. Zeile 2: `.chat-list-last` mit `lastLine(c)` und bei `unreadFromCustomer > 0` `span.chat-unread`.
|
||
- Der Zurück-Knopf steht vor der Karte: `<Button className="chat-back" onClick={() => setParams({})}>← Alle Unterhaltungen</Button>`, darunter `<Card title={nameOf(kunde)}><ChatThread … /></Card>`. Ohne Auswahl bleibt die Karte mit `EmptyState` („Unterhaltung links auswählen“ / „Oder oben eine neue Nachricht an einen Kunden beginnen.“) und ohne Titel. Eine `Card` ohne Titel und ohne Aktionen rendert kein `h2` (Übersicht).
|
||
|
||
- [ ] **Step 2: `.side-count` löschen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "side-count" src`
|
||
Expected: nur noch die KOMPAT-Regel in `global.css`. Sie wird gelöscht (P6 ist ihr letzter Nutzer), danach 0 Treffer.
|
||
|
||
- [ ] **Step 3: Prüfen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t5" --only chat,chat-verlauf,chat-tablet,chat-viel,chat-fehler,kunde-chat --gate p6`
|
||
Expected: Exit 0. Bei 390 px zeigt `chat` nur die Liste ohne Überlauf, `chat-verlauf` nur den Verlauf mit „← Alle Unterhaltungen“. Bei 1440 px zwei Spalten mit senkrechter Trennlinie, ohne Zurück-Knopf. `chat-tablet`: Liste 260 px.
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add src/pages/CustomerChatPage.jsx src/styles/global.css
|
||
git commit -m "Design P6: Chat-Liste als Haarlinien-Zeilen, Zaehler und Zurueck-Pfeil als Text, side-count entfaellt" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 6: Gutschriften-Liste
|
||
|
||
**Files:**
|
||
- Modify: `src/pages/CreditsPage.jsx` (Z. 10 bis 12), `src/components/credits/CreditsView.jsx` (Z. 25 bis 29), `src/components/credits/CreditList.jsx` (Z. 2, 7, 24 bis 62)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `Tabs`, `Badge`, `EmptyState`; `.table`, `.table-wrap`, `.hide-sm`, `.hide-md`, `.right`, `.num`, `.cell-title`, `.cell-sub`, `.link`, `.loading`, `.msg.err`, `.table-foot`; Helfer aus Task 1.
|
||
- Produces: `CreditList({ customerId, selectedId, onSelect, reloadKey })` mit unveränderten Props.
|
||
|
||
- [ ] **Step 1: `CreditList` umbauen**
|
||
|
||
- Filter `<Tabs tabs={[{ id: 'open', label: 'Offen' }, { id: 'all', label: 'Alle' }]} …/>`. State, Effekt und `listCredits` bleiben.
|
||
- Laden `div.loading` „Wird geladen …“, Fehler `div.msg.err[role=alert]`, leer `EmptyState` mit „Keine offenen Anträge.“ bzw. „Noch keine Anträge.“.
|
||
- `table.table.table-wrap` mit denselben Spalten und Zellinhalten wie heute. `<br />` plus `area-muted` wird `.cell-sub`, der Kundenname `.cell-title`. Klassen auf `th` und `td`: Eingang `hide-sm`, Leistung / bisher bei `hide-md`, Rechnung `hide-sm right num`, Bezahlt bis `hide-md`.
|
||
- Statuszelle: `Badge` und, wenn nicht leer, `.cell-sub` mit `creditStatusNote(r)`. Letzte Zelle ohne Kopftext: `<button type="button" className="link" onClick={() => onSelect(r.id)}>{creditActionLabel(r.status)}</button>`.
|
||
- Die Zeile der offenen Gutschrift trägt `aria-current="true"`. Imports `FaSpinner`, `Card`, `admin-areas.css` und die zwei Inline-Styles entfallen.
|
||
|
||
- [ ] **Step 2: `CreditsView` und `CreditsPage`**
|
||
|
||
`CreditsView`: Das `div` mit `marginBottom` um `CreditDetail` entfällt, die Liste steht in `<div className={selected ? 'mt-3' : undefined}>`. `CreditsPage`: Der Absatz „Übernahmegutschrift: …“ bleibt wörtlich als `<p className="table-foot">` ohne Inline-Style.
|
||
|
||
- [ ] **Step 3: Prüfen und Commit**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t6" --only gutschriften,gutschriften-tablet,gutschriften-viel,gutschriften-fehler,kunde-gutschriften --gate p6`
|
||
Expected: Exit 0. 390 px: Kunde, Status, Aktion, kein Überlauf mit dem 60-Zeichen-Namen. 1000 px: Eingang und Rechnung sichtbar, Leistung und Bezahlt bis aus. 1440 px: alle sieben Spalten.
|
||
|
||
```bash
|
||
git add src/pages/CreditsPage.jsx src/components/credits/CreditsView.jsx src/components/credits/CreditList.jsx
|
||
git commit -m "Design P6: Gutschriften-Liste als Tabelle mit Reitern und Spaltenprioritaet" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 7: `CreditDetail`
|
||
|
||
**Files:**
|
||
- Modify: `src/components/credits/CreditDetail.jsx` (Imports Z. 1 bis 9, Markup Z. 116 bis 284)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `Card`, `Badge`, `Button`, `Field`; `.two-col`, `.kv`, `.field-row`, `.check`, `.list-row`, `.cell-sub`, `.prose`, `.msg`, `.loading`, `.link`; `checkGlyph`, `checkTone`.
|
||
- Produces: `CreditDetail({ id, onClose, onChanged, onOpen })` unverändert.
|
||
|
||
- [ ] **Step 1: Logik unverändert**
|
||
|
||
Nicht angefasst werden `load`, `figures`, `setFigure` (300-ms-Timer), `decide` (`decisionError`, `window.confirm`, `busyRef`), `apply`, `canApprove`, `amountTouched`. Neu ist nur der Zustand `busyAction` (`''`, `'approve'`, `'question'`, `'reject'`, `'apply'`), den `decide` und `apply` neben `busy` setzen und zurücksetzen, damit der gedrückte Knopf „Speichert …“ bzw. „Sendet …“ zeigen kann.
|
||
|
||
- [ ] **Step 2: Markup umbauen**
|
||
|
||
- **Kopf:** `<Card title={`Antrag ${r.ref} · ${c.name || c.companyName}`} actions={<Button onClick={onClose}>Schließen</Button>}>`, darunter `p.muted` „eingegangen … “ mit `Badge` und `p.msg.ok[role=status]` für `message`.
|
||
- **Aufbau:** `div.two-col` mit zwei Spalten-`div`. Jede Gruppe ist ein `<Card title="…">` mit dem heutigen `h4`-Text. Den Abstand zwischen den Karten einer Spalte liefert `.page section + section` aus P1, P6 baut dafür keine eigene Regel. Die Reihenfolge der Abschnitte bleibt: links Antrag, Grund / Nachricht, Nachweise, Verlauf, rechts Kunde & Vertrag, Weitere Anträge, Automatische Prüfung, Prüfung durch dich, Berechnung bzw. Entscheidung.
|
||
- **Ersetzungen:**
|
||
- `dl.area-kv` wird `dl.kv`.
|
||
- Mehrzeiler (`<br />` plus `area-muted`) werden `.cell-sub`.
|
||
- Nachweise, Verlauf, Weitere Anträge und Prüfpunkte werden `.list-row`. Text aus `r.message` und Verlaufstexten ist `.prose`.
|
||
- Alle Links (`domain`, Nachweise) bekommen `className="link"`. Die Nummer in „Weitere Anträge“ ist `<button type="button" className="link" onClick={() => onOpen?.(o.id)}>` statt `<a href="#">`.
|
||
- Prüfpunkte tragen `<Badge tone={checkTone(k.status)}>{checkGlyph(k.status)}</Badge>`.
|
||
- „In Stripe öffnen“ ist `Button as="a" …` ohne Icon, `KundenansichtButton` ohne `variant`.
|
||
- **Formular:** Die manuellen Punkte stehen als `div.field` mit je einem `label.check`. „Berechnung“ besteht aus zwei `.field-row` mit `Field label htmlFor` (IDs `calc-amount`, `calc-months`, `calc-paid-until`, `calc-from`, Klassen `input`/`select`). `calcText` ist `p.strong`, der Hinweis `p.muted.small`. „Gutschrift (€)“ (`#credit-amount`) und das Nachrichtenfeld (`textarea#credit-note.textarea`, `maxLength={4000}`, langes Label wörtlich) sind `Field`.
|
||
- **Knopfreihe** `div.flex.items-center.gap-3.wrap`:
|
||
- `Button variant="primary"` „Genehmigen & gutschreiben“ (`disabled={!canApprove}`),
|
||
- `Button` „Rückfrage senden“,
|
||
- `Button variant="danger"` „Ablehnen“.
|
||
- Bei `busyAction` zeigt der gedrückte Knopf „Speichert …“ (genehmigen, ablehnen) bzw. „Sendet …“ (Rückfrage).
|
||
- „Jetzt in Stripe gutschreiben“ ist `variant="primary"` mit „Speichert …“ bei `apply`.
|
||
- **Meldungen:** `actionError` als `p.msg.err[role=alert]`. Ladefehler `<Card><p className="msg err" role="alert">…</p><Button onClick={onClose}>Schließen</Button></Card>` (ohne Titel und Aktionen, also ohne `h2`). Laden `div.loading`.
|
||
- `react-icons`, `admin-areas.css` und alle 9 Inline-Styles entfallen.
|
||
|
||
- [ ] **Step 3: Prüfen und Commit**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t7" --only gutschriften-antrag,gutschriften-sperre --gate p6`
|
||
Expected: Exit 0, die Sperr-Prüfung besteht (1 POST `…/decision`, 1 POST `…/calc`). `gutschriften-antrag` bei 1440 px zweispaltig mit genau einem schwarzen Knopf und „Ablehnen“ als rotem Text, bei 390 px einspaltig ohne Überlauf.
|
||
|
||
```bash
|
||
git add src/components/credits/CreditDetail.jsx
|
||
git commit -m "Design P6: Gutschrift-Detail zweispaltig mit Abschnitten und kv-Listen, Verhalten unveraendert" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 8: Abo-Angebote: Seite, Ansicht, Liste
|
||
|
||
**Files:**
|
||
- Modify: `src/pages/OffersPage.jsx` (Z. 10), `src/components/offers/OffersView.jsx` (Z. 2, 14 bis 34), `src/components/offers/OfferList.jsx` (Z. 2, 7, 54 bis 118)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `Tabs`, `Badge`, `Button`, `EmptyState`; Klassen wie Task 6; `filterOffers`, `offerRowActions`, `offerMailLine`.
|
||
- Produces: `OfferList({ customerId, reloadKey, onMessage, onChanged })` und `OffersView({ customerId, customerName })` unverändert.
|
||
|
||
- [ ] **Step 1: `OfferList` umbauen**
|
||
|
||
- Tabs „Offen & aktiv“ (`open`) und „Alle“ (`all`), `shown = filterOffers(offers, filter)`. Laden, Fehler und Leerzustand wie bei den Gutschriften („Keine offenen oder aktiven Angebote.“ / „Noch keine Angebote.“).
|
||
- `table.table.table-wrap` mit denselben Spalten und Zellinhalten wie heute, Mehrzeiler als `.cell-sub`. Klassen: Angelegt `hide-md`, Gültig bis `hide-sm`. Stripe-Links `a.link`.
|
||
- Statuszelle: `Badge`, `.cell-sub` „seit …“ / „wartet auf Abschluss“, „Stripe: Coupon · Abo“, danach `offerMailLine(o)` als `.cell-sub` (`warn` als `span.msg.warn`).
|
||
- Aktionen unter den Status-Zeilen, `div.flex.gap-3.wrap.mt-1`, nur wenn `sendLabel` oder `canWithdraw`:
|
||
- `<button type="button" className="link" title="Angebots-E-Mail an … senden" onClick={() => sendMail(o)} disabled={busyId === o.id}>{sendLabel}</button>`
|
||
- `Button variant="danger"` „Zurückziehen“
|
||
- `withdraw` und `sendMail` bleiben unverändert (2 `confirm`, 2 `alert`).
|
||
- Imports `FaSpinner`, `Card`, `admin-areas.css` und die drei Inline-Styles entfallen.
|
||
|
||
- [ ] **Step 2: `OffersView` und `OffersPage`**
|
||
|
||
`OffersView`: Meldung als `<p className="msg ok mb-2" role="status">`, dann `<div className="toolbar"><Button variant="primary" …>Neues Angebot</Button></div>` (ohne `FaPlus`, auch bei offenem Dialog sichtbar), dann `OfferList`, am Ende `{showForm && <OfferForm … />}` ohne Stil-`div`. `OffersPage`: Der Absatz „Ein Angebot legt sofort …“ bleibt wörtlich als `<p className="table-foot">`.
|
||
|
||
- [ ] **Step 3: Prüfen und Commit**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t8" --only angebote,angebote-tablet,angebote-fehler,kunde-abo --gate p6`
|
||
Expected: Exit 0. 390 px: Kunde, Angebot, Status, die 50-Zeichen-E-Mail bricht, „E-Mail senden“ und „Zurückziehen“ sind erreichbar. 1440 px: alle fünf Spalten.
|
||
|
||
```bash
|
||
git add src/pages/OffersPage.jsx src/components/offers/OffersView.jsx src/components/offers/OfferList.jsx
|
||
git commit -m "Design P6: Angebote-Liste als Tabelle mit Reitern, Aktionen als Textlinks" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 9: `OfferForm` als Dialog
|
||
|
||
**Files:**
|
||
- Modify: `src/components/offers/OfferForm.jsx` (Imports Z. 1 bis 8, Markup Z. 85 bis 143)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `Dialog({ title, onClose, busy, footer, children })`, `Field`, `Button`, `AutocompleteInput` (Prop `id`), `.field-row`, `.check`, `.msg.err`.
|
||
- Produces: `OfferForm({ fixedCustomerId, fixedCustomerName, onCreated, onCancel })` unverändert. `onCancel` ist zugleich `onClose` des Dialogs.
|
||
|
||
- [ ] **Step 1: Markup als Dialog**
|
||
|
||
Kontext-Effekt, `evaluation`, `save` (`window.confirm(offerConfirm(…))`), `suggestions` und `onPick` bleiben. Der Rückgabewert wird `<Dialog title="Neues Angebot" onClose={onCancel} busy={busy} footer={…}>` ohne `<form>`. Felder in der heutigen Reihenfolge und mit den heutigen Labeltexten, alle als `Field` mit neuen IDs `offer-*` für die Label-Bindung:
|
||
- „Kunde“: mit `fixedCustomerId` `<p className="strong">{fixedCustomerName || 'Kunde'}</p>`, sonst `AutocompleteInput id="offer-customer"`.
|
||
- `.field-row` „Produkt“ (`select.select`) und „Preis pro Monat im Angebot (€)“ (`input.input`).
|
||
- `.field-row` „für“ (`select.select`) und „Angebot gültig bis“ (`input.input type="date"`).
|
||
- „Hinweis für den Kunden …“ und „Interne Notiz …“ (`input.input`, `maxLength` 500 bzw. 2000).
|
||
- `label.check` „Kunden per E-Mail über das Angebot informieren“.
|
||
- Vorschau: Fehlerzeilen `p.msg.err`, Vorschauzeilen als `p`, die Platzhaltertexte wörtlich. Bei `error` `p.msg.err[role=alert]`.
|
||
|
||
`footer`: `Button variant="primary" type="button"` (`onClick={save}`, `disabled={!evaluation || evaluation.errors.length > 0 || busy}`) mit `busy ? 'Speichert …' : evaluation?.saveLabel || 'Angebot anlegen'` und `Button type="button"` „Abbrechen“ (`onClick={onCancel}`, `disabled={busy}`). `FaSpinner`, `Card`, `admin-areas.css`, die vier Inline-Styles und die Klasse `offer-form` entfallen.
|
||
|
||
- [ ] **Step 2: Clipping und Tastatur prüfen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t9" --only angebote-dialog,angebote-vorschlag,angebote-laufend,angebote-tastatur,kunde-abo --gate p6`
|
||
Expected: Exit 0, alle Schritt-Prüfungen bestehen. `angebote-vorschlag`: Liste vollständig erreichbar. `angebote-laufend` bei 390 px: Der lange Speichern-Text bricht in eine zweite Zeile der Fußzeile. `angebote-tastatur`: Esc schließt erst die Liste, dann den Dialog, beim Speichern wirken „ד und Esc nicht.
|
||
|
||
Besteht die Clipping-Prüfung nicht, bekommt `.dialog-body .autocomplete-list` im Block aus Task 3 `max-height: 200px` (nie `!important`, die Komponente bleibt unberührt).
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add src/components/offers/OfferForm.jsx
|
||
git commit -m "Design P6: Angebots-Formular als Dialog (Esc, Sperre beim Speichern, Vorschlagsliste geprueft)" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
War die Abhilfe nötig, kommt `src/styles/global.css` ausdrücklich in dieses `git add`.
|
||
|
||
### Task 10: E-Mail-Speicher
|
||
|
||
**Files:**
|
||
- Modify: `src/pages/StorageTasksPage.jsx` (Imports Z. 1 bis 8, Markup Z. 38 bis 98)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `PageHeader`, `Card`, `Badge`, `Button`, `EmptyState`; `.table.table-wrap`, `.hide-sm`, `.hide-md`, `.cell-sub`, `.link`, `.msg`, `.list-row`, `.table-foot`, `.loading`; `storageDue`.
|
||
- Produces: Seite `/customers/email-speicher` unverändert.
|
||
|
||
- [ ] **Step 1: Markup umbauen**
|
||
|
||
- Fehler `div.flex.items-center.gap-2.wrap` mit `span.msg.err[role=alert]` und `Button` „Erneut versuchen“ (`onClick={load}`). Laden `div.loading` „Wird geladen …“.
|
||
- Zusammenfassung `<p className={summary.warn ? 'msg warn mb-2' : 'muted mb-2'}>`.
|
||
- Bei null Aufgaben `EmptyState` „Keine offenen Speicher-Aufgaben.“, sonst `table.table.table-wrap` mit den heutigen Spalten und Zellinhalten, Mehrzeiler als `.cell-sub`. Klassen: Fällig `hide-sm`, Bestellt `hide-md`.
|
||
- Fällig-Zelle: `Badge tone={due.tone}` mit `due.label` und `due.sub` (`.cell-sub`, wenn `due.subMuted`). Kundenname `Link className="link"` auf `/customers/${t.customerId}`. Die Teilfehler-Zeile `partialText(t)` ist `span.msg.err`.
|
||
- Letzte Zelle ohne Kopftext: `<button type="button" className="link" data-storage-done={t.id} onClick={() => done(t)} disabled={busyId === t.id}>{busyId === t.id ? 'Speichert …' : 'Erledigt'}</button>`.
|
||
- „Gescheiterte Änderungen (letzte 30 Tage)“ bleibt `Card title`, die Zeilen sind `.list-row` (Text `failedText` als `span.muted`). Der lange Hinweisabsatz bleibt wörtlich als `<p className="table-foot">`.
|
||
- `done`, `window.confirm('Stripe-Menge von Hand korrigiert?')` und `window.alert` bleiben. `react-icons`, `admin-areas.css` und beide Inline-Styles entfallen.
|
||
|
||
- [ ] **Step 2: Prüfen und Commit**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t10" --only speicher,speicher-fehler --gate p6`
|
||
Expected: Exit 0. 390 px: Kunde, Aufgabe und „Erledigt“ ohne Überlauf. 1440 px: alle fünf Spalten, Fällig als Badge mit Datum.
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -c "data-storage-done" src/pages/StorageTasksPage.jsx`
|
||
Expected: `1`.
|
||
|
||
```bash
|
||
git add src/pages/StorageTasksPage.jsx
|
||
git commit -m "Design P6: E-Mail-Speicher als Tabelle, Erledigt als Textlink mit data-storage-done" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 11: `admin-areas.css` löschen, KOMPAT-Reste
|
||
|
||
**Files:**
|
||
- Delete: `src/styles/admin-areas.css`
|
||
- Modify: `src/styles/global.css` (KOMPAT-Zeilen `.credit-cols a`, `.area-rows a`)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `$W/css-klassen.mjs` aus P1. P4 hat die `.funktionen-*`-Regeln nach `global.css` verlegt, die Tasks 6 bis 10 haben die Importzeilen der P6-Dateien entfernt.
|
||
- Produces: keine Datei `admin-areas.css`, keinen Import und keine Klasse aus ihr.
|
||
|
||
- [ ] **Step 1: Restnutzer ermitteln**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "\barea-|credit-(row|cols|files|checks|history|manual|calc|field)|offer-(row|form)|storage-row" src --glob '!*.css'; rg -n "admin-areas" src`
|
||
Expected: keine Treffer, auch nicht in `CustomerFunktionen.jsx` und `AboStatusCard.jsx`. Ein Treffer in einer P6-Datei wird dort behoben. Ein Treffer in einer P4-Datei heißt, dass P4 sie nicht vollständig gelöst hat: stoppen und melden. Das Muster enthält bewusst kein `funktionen-`, denn diese Klassen leben in `global.css`.
|
||
|
||
- [ ] **Step 2: Datei löschen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git rm -q src/styles/admin-areas.css && rg -n "admin-areas" src`
|
||
Expected: keine Treffer.
|
||
|
||
- [ ] **Step 3: KOMPAT-Reste löschen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "credit-cols|area-rows|side-count" src/styles`
|
||
Expected: genau die beiden Zeilen in `global.css` (KOMPAT „Links in Bestandsklassen“) für `.credit-cols a` und `.area-rows a`. Beide werden gelöscht, `.side-count` ist schon seit Task 5 weg. Danach liefert derselbe Befehl keine Treffer.
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node "$W/css-klassen.mjs" "$WT" | node -e "const j=JSON.parse(require('fs').readFileSync(0));console.log(j.undefinedUsed.join('\n'))"`
|
||
Expected: nur die in P1 erlaubten Namen (`p-4`, `btn-sm`, `status-filter`, `supporters-editor`, `timeline`, Bootstrap-Namen in `WorksheetList.jsx`). Meldet das Skript die gelöschte Datei als fehlend, wird sie in der Dateiliste von `css-klassen.mjs` (unter `$W`) gestrichen.
|
||
|
||
- [ ] **Step 4: Bauen und Commit**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t11" --only kunde-abo,kunde-gutschriften,kunde-funktionen,gutschriften,angebote,speicher --gate p6`
|
||
Expected: Exit 0. Die Kundenseiten-Reiter `kunde-abo` und `kunde-funktionen` sehen aus wie nach P4 (Vergleich mit `$W/shots/p4`), die drei Listen wie nach Task 6, 8 und 10.
|
||
|
||
```bash
|
||
git status --short
|
||
git add src/styles/global.css
|
||
git commit -m "Design P6: admin-areas.css geloescht, KOMPAT-Reste entfernt" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
`git status --short` vor dem Commit darf nur `D src/styles/admin-areas.css` (vom `git rm` schon vorgemerkt) und `M src/styles/global.css` zeigen. Die Löschung steht damit im selben Commit.
|
||
|
||
### Task 12: Gate P6, Review, Tag, Übergabe
|
||
|
||
- [ ] **Step 1: Rebase und Ausgangswerte**
|
||
|
||
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, `# fail 0`, `# pass` = N5 + 15 (10 Lib-Tests, 5 `chatCss`). Liegen neue Commits auf `test`, ist die Zahl entsprechend höher und wird notiert. Die Bundle-Größen aus der Ausgabe werden mit P5 verglichen, das CSS sollte kleiner sein (`admin-areas.css` ist weg).
|
||
|
||
- [ ] **Step 2: Vollständiger Gate-Lauf**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6" --gate p6`
|
||
Expected: Exit 0, in allen Ansichten kein Verstoß gegen `dark-surface`, `low-contrast`, `overflow-x`, `console-error`, `external-request`, `step-failed`. In den P6-Ansichten dazu kein `icon-svg` außerhalb von `.woms-chat`.
|
||
|
||
- [ ] **Step 3: Inventur und Hygiene**
|
||
|
||
Run:
|
||
```bash
|
||
source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && for f in src/components/credits/CreditDetail.jsx src/components/offers/OfferList.jsx src/components/offers/OfferForm.jsx src/pages/StorageTasksPage.jsx; do echo "$f $(rg -o 'window\.confirm|window\.alert|busyRef|data-storage-done|id="(calc|credit)-[a-z-]+"' $f | wc -l)"; done
|
||
rg -n "react-icons|size=\"sm\"|style=\{\{|!important" src/pages/{CustomerChat,Credits,Offers,StorageTasks}Page.jsx src/components/{chat,credits,offers} src/styles/chat.css
|
||
rg -n "admin-areas" src
|
||
git diff --stat design-p5..HEAD -- src/lib/loadChatComposer.js tests/unit/loadChatComposer.test.mjs
|
||
test ! -e src/styles/admin-areas.css && echo "admin-areas.css weg"
|
||
```
|
||
Expected: Zählung 16, 4, 1, 3. Die Befehle zwei bis vier sind leer, am Ende steht „admin-areas.css weg“.
|
||
|
||
- [ ] **Step 4: Code-Review (sonnet)**
|
||
|
||
Geprüft wird der Diff `design-p5..HEAD` gegen die Spec-Abschnitte „Bausteine“, „Seiten ohne Prototyp-Vorlage“ und P6. Schwerpunkte:
|
||
- Jede Chat-Regel hat Präfix und strikt höhere Spezifität (Tabelle in Task 4).
|
||
- `admin-areas.css` ist gelöscht, kein Import und keine ihrer Klassen ist übrig, `.funktionen-*` stehen unverändert in `global.css`.
|
||
- `CreditDetail` verhält sich unverändert (`busyRef`, Debounce, `confirm`).
|
||
- Der Textdiff (`git diff design-p5..HEAD -w`) zeigt nur Textänderungen, die die Spec unter „Texte“ erlaubt (darunter „Speichert …“, „Sendet …“, „Wird geladen …“) oder die ein Icon durch ein Zeichen ersetzen (z. B. „← Alle Unterhaltungen“).
|
||
- Jede Aktion der alten Seiten ist erreichbar: Prüfen/Ansehen, Schließen, Kundenansicht öffnen, In Stripe öffnen, Weitere Anträge, Genehmigen, Rückfrage, Ablehnen, Jetzt in Stripe gutschreiben, Neues Angebot, E-Mail senden/Erneut senden, Zurückziehen, Coupon- und Abo-Link, Erledigt, Kundenlink, Erneut versuchen, Neue Nachricht an Kunde, Unterhaltung öffnen, Zurück.
|
||
|
||
Wichtige Befunde werden behoben. Danach kommt der Rebase aus Step 1 noch einmal und Step 2 als Kurzlauf mit `--only chat-verlauf,gutschriften,angebote,speicher`.
|
||
|
||
- [ ] **Step 5: Tag und Übergabe**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" && git tag design-p6 && git log --oneline origin/test..design-p6 && git rev-parse design-p6`
|
||
|
||
Der Eigentümer bekommt:
|
||
- die Screenshots aus `shots/p6/1440x900` und `shots/p6/390x844`. Auswahl: `chat`, `chat-verlauf`, `chat-menue`, `chat-anhang-fehler`, `chat-tablet`, `gutschriften`, `gutschriften-antrag`, `gutschriften-tablet`, `angebote`, `angebote-dialog`, `angebote-vorschlag`, `speicher`, `kunde-chat`, `kunde-abo`.
|
||
- die bewusst offenen Punkte:
|
||
- Lightbox und Kamera bleiben dunkel, „Foto verwenden“ bleibt im Portal-Lila (nicht aus WOMS änderbar).
|
||
- Die Anhang-Menü-Icons des Portal-Bausteins bleiben.
|
||
- Die Blasenfarben `--bg-hover` und `--grey-bg` sind auf Weiß sehr zart. Die Stellschrauben sind `--chat-bubble-in` und `--chat-bubble-out` in `chat.css`.
|
||
- den Hinweis zu tp2: `admin-areas.css` ist gelöscht. Ändert `feature/terminplaner-tp2` die Datei (Anleitungs-Panel der Funktionen), gibt es beim Zusammenführen einen Konflikt, und die Panel-Regeln gehören dann in den Block „Kunden (P4)“ von `global.css`.
|
||
- die Push-Zeile `git -C /home/webklar/apps/ticket.webklar.com/dev-design push origin 'design-p6^{commit}:refs/heads/test'`
|
||
- die Rücknahme `git revert --no-commit design-p5..design-p6` als Commit auf `test`, durch den Eigentümer. Sie gilt nur, solange P6 das jüngste Paket ist.
|
||
|
||
- [ ] **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'` stimmt mit `dist/index.html` des Tags überein.
|
||
- Lesender Sichttest im Chrome des Eigentümers: `/customers/chat` mit geöffnetem Verlauf (Anhang-Menü öffnen), `/customers/gutschriften` und `/customers/angebote`.
|