Umsetzungsplaene je Paket mit gemeinsamen Regeln (env.sh/check.sh, Gate-Schema, CSS-Reihenfolge, TP2-Pruefung vor jedem Paket) und den Schnittstellen aus P1. Gegen Spec und untereinander geprueft (zwei Pruefer, Korrekturrunde, Nachpruefung). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
619 lines
55 KiB
Markdown
619 lines
55 KiB
Markdown
# WOMS „Weißes Design“ P4: Kunden – 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 P4 sieht der Kundenbereich wie im Prototyp aus:
|
||
- Kundenliste als Tabelle mit den Reitern Feste Kunden · Leads · Archiviert · Alle samt Zählern, mit Suche und Abzeichen
|
||
- „Neuer Kunde“ und „Neuer Lead“ als Dialoge, auch im Zuordnen-Assistenten
|
||
- Kundenseite mit Brotkrumen, Eyebrow und allen neun Reitern. Übersicht als zwei Spalten mit Schlüsselwerten, Stammdaten weiter direkt bearbeitbar, Tickets-Reiter mit Links
|
||
- Funktionen, Protokoll und Abo-Status ohne Karten und ohne Icons, neuer Grundstil in `admin-areas.css`
|
||
|
||
**Architecture:**
|
||
- Filter, Zähler und Abzeichen sind reine Funktionen in der neuen `src/lib/customerList.js` mit Tests. `lib/leads.js` importiert Appwrite und ist unter Node nicht ladbar. Deshalb ziehen `customerStage` und `isArchived` dorthin um, und `leads.js` re-exportiert sie. Alle bestehenden Importe bleiben gültig.
|
||
- Tabellenzeilen haben als erste Zelle einen echten Link, der Zeilenklick läuft über `isRowActivation` aus `src/lib/rowClick.js` (entsteht in P2, P4 baut den Helfer nicht neu).
|
||
- Die beiden Anlege-Formulare rendern `<Dialog>` aus P1.
|
||
- CSS: ein kleiner Block „Kunden (P4)“ in `global.css`, dazu der neue Grundstil in `src/styles/admin-areas.css`.
|
||
|
||
**Tech Stack:** React 18, react-router-dom 6, reines CSS. Tests mit `node --test`, Screenshot-Werkzeug aus P0.
|
||
|
||
**Spec:** `docs/superpowers/specs/2026-10-04-weisses-design-design.md`, Abschnitte „Gestaltungsregeln“, „Bausteine“ (Seitenkopf, Tabelle, Reiter, Badge, Formular, Dialog, Meldungen, Laden), „Ticket als eigene Seite / Route und Links“, „Texte“, „Risiken“ (Zeile zu `feature/terminplaner-tp2`) und Paket P4. Detail im Anhang `2026-10-04-weisses-design-umbaukarte.md`, Abschnitte 5 (P4), 3, 4 und 7.5. Globale Regeln und gemeinsame Schnittstellen: `2026-10-05-weisses-design-00-uebersicht.md`.
|
||
|
||
## Global Constraints
|
||
|
||
Jeder Run-Block beginnt mit `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh &&`. Danach gelten `$WT` (Worktree, aktuelles Verzeichnis), `$W` (Werkzeug) und `$TMPDIR`. Tests und Build laufen nur über `"$W/check.sh"`, Browser-Läufe nur über `"$W/shots.sh"` (Regeln der Übersicht).
|
||
|
||
Es gelten die Regeln der Übersicht. Für P4 kommt hinzu:
|
||
|
||
- Zeilenangaben gelten für den Stand `603d08c`. Nach P1 bis P3 können sie leicht verschoben sein, maßgeblich sind die genannten Namen.
|
||
- Voraussetzung sind das Tag `design-p3` und aus P2 `ticketPath` und `ticketRef` (`lib/ticketRoutes.js`), `ticketNumber` (`lib/ticketView.js`), `isRowActivation` (`lib/rowClick.js`) und die Klasse `.row-link`.
|
||
- Geändert werden nur `CustomersPage.jsx`, `CustomerDetailPage.jsx`, `components/customer/*`, `admin-areas.css`, ein Block in `global.css` (dazu fällt dort die KOMPAT-Regel `.side-logo`), in `lib/leads.js` zwei Funktionen (Umzug) und eine Stelle in `AssignProjectPage.jsx` (Aufruf von `CreateCustomerForm`). Neu sind `lib/customerList.js` und sein Test.
|
||
- Unverändert bleiben `lib/customerTabs.js` (der Test pinnt `[id, label]`, Zähler kommen beim Rendern), `lib/funktionenView.js`, `useCustomers`, `customerAdminApi`, `teamApi`, `CopyableCredential` und `AssignedProjectCard` (P5), Chat, Gutschriften, Angebote (P6) und `CustomerInvoices` (P7).
|
||
- **tp2 zuerst klären:** `feature/terminplaner-tp2` hat 5 Commits (`6f03712`, `c001554`, `857e001`, `45e5dc3`, `225e2f1`) vor `test`. Sie ändern `CustomerFunktionen.jsx`, `lib/funktionenView.js`, `admin-areas.css` und Rechtliches. P4 baut `CustomerFunktionen.jsx` und `admin-areas.css` neu. Task 1 stoppt deshalb und fragt den Eigentümer. Einzelne Commits werden nicht per Cherry-Pick geholt.
|
||
- Texte bleiben wörtlich, auch die ae/ue-Schreibweisen („Strasse + Nr.“, „Fuer einen Lead …“). Neu sind nur Spaltenköpfe der neuen Tabellen, das Abzeichen „Wichtig“, „Wird geladen …“, „Speichert …“, „Kopieren“ und „Kopiert“.
|
||
- **Erreichbar bleiben** alle heutigen Bedienelemente: Reiter und Suche der Liste, Zeile öffnet die Kundenseite, „Neuer Kunde“ (Admin) und „Neuer Lead“ mit beiden Formularen, auch im Zuordnen-Assistenten. Auf der Kundenseite „Kunde löschen“ (Admin, mit `window.confirm`), „Archivieren“/„Wiederherstellen“, „Kundenansicht öffnen“, der Weg zurück über die Brotkrume, die neun Reiter, „Speichern“, „Zum festen Kunden machen“, „Passwort ändern“/„Zugang anlegen“, Aktivieren und Abschalten mit Notiz, Site-Key kopieren und „Erneut versuchen“. Es gibt keine `data-testid` in diesen Dateien.
|
||
- **Entscheidung (Reiter der Liste):** Reihenfolge Feste Kunden · Leads · Archiviert · Alle, Start bei Feste Kunden, nicht in der URL (wie heute).
|
||
- **Entscheidung (Zähler und Suche):** Die Zähler nutzen dieselbe Funktion wie die Liste und beachten deshalb den Suchtext. Die Zahl im aktiven Reiter ist immer die Zeilenzahl. Die Suche durchsucht Name, Code, Standort, E-Mail wie heute und zusätzlich den Ansprechpartner, weil die Tabelle ihn zeigt (der Prototyp tut es auch).
|
||
- **Entscheidung (Spalten der Liste):** Unternehmen mit Ansprechpartner als `.cell-sub`, Kundennummer (`hide-sm`), Standort, E-Mail und Telefon (`hide-md`). Keine Spalte „Offene Tickets“ (Spec), keine Fußzeile.
|
||
- **Entscheidung (Abzeichen):** Lead grau (`muted`), Abgesagt und Archiviert `plain`, Wichtig orange (`warn`, wie der frühere Stern). „Fester Kunde“ entfällt als Abzeichen, der Reiter sagt es, die Eyebrow der Kundenseite nennt es.
|
||
- **Entscheidung (erste Zelle):** `<div className="cell-title">` mit `<Link className="row-link">` um den Namen und den Abzeichen dahinter, wie in der Ticketliste aus P2.
|
||
- **Entscheidung (Kundenseite):** Eyebrow „Kundennummer · Stufe“ (Stufe als Klartext), h1 nur der Name (Rückfall E-Mail, dann „–“). „Wichtig“ und „Archiviert“ stehen als Abzeichen in der `.sub`-Zeile vor Standort · E-Mail · Telefon. Kopfknöpfe: „Kunde löschen“ (Admin, roter Textlink), „Archivieren“/„Wiederherstellen“, „Kundenansicht öffnen“, keiner schwarz. „Alle Kunden“ wird die Brotkrume „Kunden“. Die Reiter bekommen keine Zähler (zusätzliche Abfragen, die Spec nennt Zähler nur für die Liste).
|
||
- **Entscheidung (Übersicht):** Schwarz ist nur „Speichern“. „Zum festen Kunden machen“ und „Passwort ändern“/„Zugang anlegen“ sind Standardknöpfe. Rückmeldungen laufen über `.msg` (`ok`, `err`, `warn`).
|
||
- **Entscheidung (Tickets-Reiter):** Spalten „Ticket“ (Nummer, Betreff, „Dringend“, darunter die Art roh wie heute) und „Status“, keine zusätzlichen Abfragen.
|
||
- **Entscheidung (Dialoge):** `CreateLeadForm` im Standarddialog mit einer `.field-row`, `CreateCustomerForm` `wide` mit zwei `.field-row-3`. Fuß wie im Prototyp: `<span className="grow" />`, Abbrechen (Standardknopf mit `type="button"`, sonst löst er in `<Dialog as="form">` das Absenden aus), Absenden (schwarz, `type="submit"`). Abbrechen ist wie × und Esc während `busy` gesperrt. Beide Formulare bleiben in `CustomersPage.jsx`, der Export von `CreateCustomerForm` bleibt.
|
||
- **Entscheidung (Funktionen und Protokoll):** Funktionen: eine Tabelle mit einer Projektzeile über den Funktionszeilen, die Bestätigung als eigene Zeile darunter. Dieselbe Zusatzzeile kann später das Panel von tp2 aufnehmen. Protokoll: Tabelle „Zeit“, „Mitarbeiter“, „Aktion“ mit dem Zusatztext als `.cell-sub`.
|
||
- **Bekannt und unverändert:** `CustomerDetailPage.load()` ersetzt die Seite beim Nachladen durch den Ladezustand, die Meldung „Gespeichert“ bleibt deshalb nicht stehen. Das ist heute schon so und kein P4-Thema.
|
||
|
||
## Review Focus
|
||
|
||
Alle fünf Punkte der Übersicht treffen P4. Die Ansichten stehen in Task 3.
|
||
|
||
1. **Altdaten** (`kunden-altdaten*`, `kunde-altdaten`): Kunden ohne Kundennummer, Standort, E-Mail, Telefon und Ansprechpartner zeigen „–“. Ein Kunde ohne Namen zeigt die E-Mail, ohne beides „–“. Im Text steht nie `undefined`, `null` oder `NaN`.
|
||
2. **Lange Inhalte:** Firmenname mit 60 Zeichen, ein Kunde mit den Abzeichen Wichtig, Lead und Archiviert, Funktionen mit langem Projektnamen, langer URL, Site-Key und Notiz. Kein waagerechtes Scrollen bei 390 px.
|
||
3. **Teilausfall** (`fehler-kunde*`): Antworten die Portal-Wege `portal`, `abo`, `protokoll` und `funktionen` mit 500, zeigen Protokoll, Abo und Funktionen `.msg.err`, die Übersicht „nicht abrufbar“. Die Reiter bleiben bedienbar.
|
||
4. **Nur Tastatur** (`kunden-tastatur`, `dlg-*`): Ein Zeilenlink ist per Tab erreichbar, Enter öffnet die Kundenseite. Esc schließt die Dialoge, der erste Tab im offenen Dialog bleibt im Dialog. Ein Klick in die Zeile neben dem Link öffnet genau einmal.
|
||
5. **Viele Einträge** (`kunden-viel`): 300 zusätzliche Kunden, wie heute ohne Seitenwechsel. Die Zähler stimmen in jedem Reiter mit den Zeilen überein.
|
||
|
||
---
|
||
|
||
### Task 1: Paketstart und tp2-Prüfung
|
||
|
||
P4 baut `CustomerFunktionen.jsx` und `admin-areas.css` neu, beide Dateien ändert auch `feature/terminplaner-tp2`. Vor dem ersten Umbau wird deshalb geklärt, in welcher Reihenfolge beide Arbeiten live gehen.
|
||
|
||
**Files:** keine Änderung (nur Lesen und Prüfen).
|
||
|
||
**Interfaces:**
|
||
- Consumes: Tag `design-p3` und die P2-Bausteine `ticketPath`, `ticketRef`, `ticketNumber`, `isRowActivation` und `.row-link`.
|
||
- Produces: die Ausgangs-Testzahl N3 und die Entscheidung des Eigentümers zu tp2.
|
||
|
||
- [ ] **Step 1: Paketstart**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git fetch origin && git merge-base --is-ancestor design-p3 HEAD && git rebase origin/test && "$W/check.sh"`
|
||
Expected:
|
||
- Alle Befehle enden mit Exit 0, `# fail 0`. Schlägt `merge-base` fehl, ist `design-p3` nicht im Branch: Stopp und melden.
|
||
- Die Zahl `# pass` ist N3. Soll nach P4: N3 + 6.
|
||
|
||
- [ ] **Step 2: Parallele Arbeit an `feature/terminplaner-tp2`**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git log --oneline origin/test..origin/feature/terminplaner-tp2 -- src/pages/CustomersPage.jsx src/pages/CustomerDetailPage.jsx src/components/customer src/styles/admin-areas.css src/styles/global.css src/lib/leads.js src/lib/funktionenView.js src/pages/AssignProjectPage.jsx`
|
||
Expected: keine Ausgabe. Gibt der Befehl Zeilen aus (Stand 2026-10-05: `6f03712`, `c001554`, `857e001`, `45e5dc3`), **stoppt der Agent und fragt den Eigentümer**:
|
||
- Er empfiehlt, tp2 zuerst nach `test` zu bringen.
|
||
- Er holt keinen Commit einzeln per Cherry-Pick. `6f03712` allein ließe `CustomerFunktionen.jsx` und `lib/funktionenView.js` in einem Zwischenstand, den die übrigen tp2-Commits wieder umbauen.
|
||
|
||
Weiter geht es erst nach der Antwort:
|
||
- **Fall A, tp2 ist in `test`:** Der Rebase aus Step 1 wird wiederholt, dieser Step ist dann leer.
|
||
- **Fall B, der Eigentümer legt P4 vor tp2:** Er nennt, wer die Konflikte in `CustomerFunktionen.jsx`, `lib/funktionenView.js` und `admin-areas.css` später auflöst. Die Übergabe (Task 8) hält das fest.
|
||
|
||
- [ ] **Step 3: P2-Bausteine sind da**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "export function (ticketPath|ticketRef)" src/lib/ticketRoutes.js && rg -n "export function ticketNumber" src/lib/ticketView.js && rg -n "isRowActivation" src/lib/rowClick.js && rg -m 2 -n "\.row-link" src/styles/global.css`
|
||
Expected: Treffer in allen vier Dateien. Sonst Stopp: P4 darf erst nach P2 laufen.
|
||
|
||
- [ ] **Step 4: Stand von `CustomerFunktionen.jsx`**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -c "abgeschaltetText" src/lib/funktionenView.js; rg -c "AnleitungPanel|TextformPanel" src/components/customer/CustomerFunktionen.jsx`
|
||
Expected:
|
||
- Fall A (tp2 ist in `test`): Treffer in beiden Dateien. Task 6 enthält dann die Zeile „tp2-Zusatz“.
|
||
- Fall B (der Eigentümer hat P4 vor tp2 freigegeben): keine Treffer. In Task 6 entfallen die Zeile „tp2-Zusatz“ und `abgeschaltetText`, und in Task 3 entfällt die Zusicherung zum Abschaltgrund in `kunde-funktionen`.
|
||
|
||
### Task 2: `customerList.js` (Filter, Zähler, Abzeichen)
|
||
|
||
**Files:**
|
||
- Create: `src/lib/customerList.js`
|
||
- Modify: `src/lib/leads.js` (Zeilen 8–18: `customerStage` und `isArchived` samt Kommentar ersetzen)
|
||
- Test: `tests/unit/customerList.test.mjs` (neu)
|
||
|
||
**Interfaces:**
|
||
- Consumes: nichts (reine Datei ohne Importe).
|
||
- Produces, alle aus `src/lib/customerList.js`, die ersten vier auch aus `src/lib/leads.js`:
|
||
- `customerStage(c) → 'lead' | 'lost' | 'customer'` und `isArchived(c) → boolean`, unverändert verschoben
|
||
- `filterCustomers(customers, { stage = 'customer', search = '' } = {}) → Kunde[]`
|
||
- `stageCounts(customers, search = '') → { customer, lead, archived, all }`
|
||
- `CUSTOMER_TABS: [{ id, label }]`
|
||
- `customerBadges(c, { stage = true } = {}) → [{ key, label, tone }]`
|
||
- `stageLabel(stage)`, `customerEyebrow(c)`, `customerTitle(c)` → string
|
||
|
||
- [ ] **Step 1: Failing Tests schreiben**
|
||
|
||
```js
|
||
const { CUSTOMER_TABS, customerStage, isArchived, filterCustomers, stageCounts, customerBadges, stageLabel, customerEyebrow, customerTitle } =
|
||
await loadModule('src/lib/customerList.js')
|
||
|
||
const A = { $id: 'a', name: 'Alpha GmbH', code: 'K-1', location: 'Kaiserslautern', email: 'info@alpha.de', contactName: 'Anna Alt', customerStatus: 'customer' }
|
||
const B = { $id: 'b', name: 'Beta', customerStatus: 'lead' }
|
||
const C = { $id: 'c', name: 'Gamma', customerStatus: 'lost' }
|
||
const D = { $id: 'd', name: 'Delta' } // Bestandsdaten ohne Status gelten als fester Kunde
|
||
const E = { $id: 'e', name: 'Epsilon', customerStatus: 'lead', archived: true, importantCustomer: true }
|
||
const F = { $id: 'f', name: 'Zeta', customerStatus: 'customer', archived: true }
|
||
const ALL = [A, B, C, D, E, F]
|
||
const ids = (list) => list.map((c) => c.$id)
|
||
|
||
test('CUSTOMER_TABS, customerStage, isArchived', () => {
|
||
assert.deepEqual(CUSTOMER_TABS, [
|
||
{ id: 'customer', label: 'Feste Kunden' }, { id: 'lead', label: 'Leads' },
|
||
{ id: 'archived', label: 'Archiviert' }, { id: 'all', label: 'Alle' },
|
||
])
|
||
assert.equal(customerStage({ customerStatus: 'LEAD' }), 'lead')
|
||
assert.equal(customerStage({ customerStatus: 'Lost' }), 'lost')
|
||
for (const c of [{}, { customerStatus: '' }, { customerStatus: 'sonstwas' }, null, undefined]) assert.equal(customerStage(c), 'customer')
|
||
assert.equal(isArchived({ archived: true }), true)
|
||
for (const c of [{}, { archived: false }, null, undefined]) assert.equal(isArchived(c), false)
|
||
})
|
||
|
||
test('filterCustomers: jeder Reiter', () => {
|
||
assert.deepEqual(ids(filterCustomers(ALL, { stage: 'customer' })), ['a', 'd'])
|
||
assert.deepEqual(ids(filterCustomers(ALL, { stage: 'lead' })), ['b'])
|
||
assert.deepEqual(ids(filterCustomers(ALL, { stage: 'archived' })), ['e', 'f'])
|
||
assert.deepEqual(ids(filterCustomers(ALL, { stage: 'all' })), ['a', 'b', 'c', 'd'])
|
||
assert.deepEqual(ids(filterCustomers(ALL)), ['a', 'd'])
|
||
assert.deepEqual(ids(filterCustomers(ALL, { stage: 'x' })), [])
|
||
assert.deepEqual(filterCustomers(undefined, { stage: 'all' }), [])
|
||
})
|
||
|
||
test('filterCustomers: Suche in Name, Code, Standort, E-Mail und Ansprechpartner', () => {
|
||
const find = (search, stage = 'all') => ids(filterCustomers(ALL, { stage, search }))
|
||
for (const s of ['ALPHA', 'k-1', 'kaisers', 'info@alpha', 'anna']) assert.deepEqual(find(s), ['a'], s)
|
||
assert.deepEqual(find('xyz'), [])
|
||
assert.deepEqual(find('eta', 'lead'), ['b'])
|
||
assert.deepEqual(find(''), ['a', 'b', 'c', 'd'])
|
||
assert.deepEqual(ids(filterCustomers([{ $id: 'n' }], { stage: 'all', search: 'undefined' })), [])
|
||
})
|
||
|
||
test('stageCounts: dieselbe Filterfunktion wie die Liste', () => {
|
||
assert.deepEqual(stageCounts(ALL), { customer: 2, lead: 1, archived: 2, all: 4 })
|
||
assert.deepEqual(stageCounts(ALL, 'eta'), { customer: 0, lead: 1, archived: 1, all: 1 })
|
||
assert.deepEqual(stageCounts([]), { customer: 0, lead: 0, archived: 0, all: 0 })
|
||
})
|
||
|
||
test('customerBadges: Reihenfolge, Töne, Option stage', () => {
|
||
assert.deepEqual(customerBadges(E), [
|
||
{ key: 'important', label: 'Wichtig', tone: 'warn' },
|
||
{ key: 'lead', label: 'Lead', tone: 'muted' },
|
||
{ key: 'archived', label: 'Archiviert', tone: 'plain' },
|
||
])
|
||
assert.deepEqual(customerBadges(C), [{ key: 'lost', label: 'Abgesagt', tone: 'plain' }])
|
||
for (const c of [A, D, null]) assert.deepEqual(customerBadges(c), [])
|
||
assert.deepEqual(customerBadges(E, { stage: false }).map((b) => b.key), ['important', 'archived'])
|
||
})
|
||
|
||
test('stageLabel, customerEyebrow, customerTitle', () => {
|
||
assert.deepEqual(['customer', 'lead', 'lost'].map(stageLabel), ['Fester Kunde', 'Lead', 'Abgesagt'])
|
||
assert.equal(customerEyebrow(A), 'K-1 · Fester Kunde')
|
||
assert.equal(customerEyebrow(B), 'Lead')
|
||
assert.equal(customerEyebrow({ code: ' ', customerStatus: 'lost' }), 'Abgesagt')
|
||
assert.equal(customerTitle(A), 'Alpha GmbH')
|
||
assert.equal(customerTitle({ email: 'x@y.de' }), 'x@y.de')
|
||
assert.equal(customerTitle({}), '–')
|
||
})
|
||
```
|
||
|
||
- [ ] **Step 2: Test laufen lassen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/customerList.test.mjs`
|
||
Expected: FAIL, `src/lib/customerList.js` fehlt.
|
||
|
||
- [ ] **Step 3: `customerList.js` implementieren**
|
||
|
||
- `customerStage` und `isArchived` werden Zeile für Zeile aus `leads.js` übernommen.
|
||
- `filterCustomers`:
|
||
- Ohne Liste `[]`, die Reihenfolge der Eingabe bleibt.
|
||
- Der Reiter `archived` liefert nur Archivierte, alle anderen Reiter schließen sie aus.
|
||
- Stufe `lost` erscheint nur im Reiter `all`. `customer` und `lead` liefern ihre Stufe, ein unbekannter Reiter `[]`.
|
||
- Die Suche ist ein Teilstring-Vergleich in Kleinbuchstaben ohne Trim über `${name} ${code} ${location} ${email} ${contactName}`, jedes Feld mit `|| ''`. Eine leere Suche filtert nicht.
|
||
- `stageCounts`: je Eintrag von `CUSTOMER_TABS` die Länge von `filterCustomers(customers, { stage, search })`.
|
||
- `customerBadges`: Wichtig (`importantCustomer`, Ton `warn`), dann die Stufe (`lead` Ton `muted`, `lost` „Abgesagt“ Ton `plain`, `customer` nichts, nur mit `stage: true`), dann Archiviert (Ton `plain`).
|
||
- `customerEyebrow` = `[String(c.code || '').trim(), stageLabel(customerStage(c))].filter(Boolean).join(' · ')`. `customerTitle` = `c.name || c.email || '–'`.
|
||
|
||
- [ ] **Step 4: `leads.js` umstellen**
|
||
|
||
In `src/lib/leads.js` werden die Definitionen von `customerStage` und `isArchived` durch `export { customerStage, isArchived, filterCustomers, stageCounts } from './customerList'` ersetzt. Alles andere bleibt, auch `isInvoiceReady`.
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "from '../lib/leads'" src`
|
||
Expected: drei Importeure (`CustomersPage`, `CustomerDetailPage`, `AssignProjectPage`), alle ohne Änderung gültig.
|
||
|
||
- [ ] **Step 5: Test grün und Build**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/customerList.test.mjs && "$W/check.sh" --nur-build`
|
||
Expected: PASS mit 6 Tests, Build Exit 0.
|
||
|
||
- [ ] **Step 6: Commit**
|
||
|
||
```bash
|
||
git add src/lib/customerList.js src/lib/leads.js tests/unit/customerList.test.mjs
|
||
git commit -m "Kunden: Filter, Zaehler und Abzeichen als reine Funktionen (lib/customerList.js, Tests)" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 3: Testdaten, Ansichten und Gate (Prüfung vor dem Umbau)
|
||
|
||
Alles liegt im Werkzeug außerhalb des Repos, deshalb gibt es keinen Commit.
|
||
|
||
**Files** (unter `$W/`):
|
||
- Modify: `fixtures/standard.mjs`, `fixtures/altdaten.mjs`, `fixtures/viel.mjs`, `backend.mjs`, `views.mjs`, `gates.mjs`
|
||
|
||
**Interfaces:**
|
||
- Consumes: `createBackend`, `VIEWS`, `GATES` aus P0.
|
||
- Produces: die Ansichten aus Step 3 und das Gate `p4`.
|
||
|
||
- [ ] **Step 1: Kunden-Testdaten prüfen und ergänzen**
|
||
|
||
- `standard`: Der „Abgesagte“ trägt `customerStatus: 'lost'` (nicht `crmStatus`). Soll-Zähler: Feste Kunden 6 · Leads 2 · Archiviert 1 · Alle 9.
|
||
- `altdaten`: zusätzlich zu den Standardkunden fünf Kunden:
|
||
1. `Müller-Lüdenscheidt Metallbau- und Anlagentechnik GmbH & Co.` (60 Zeichen), fest, `importantCustomer: true`, ohne Kundennummer, Standort, E-Mail, Telefon, Ansprechpartner
|
||
2. Lead `Frisch Gelead UG` mit `importantCustomer: true`
|
||
3. archivierter Lead `Archivierter Wichtiger Lead` mit `importantCustomer: true`
|
||
4. fester Kunde ohne Namen mit E-Mail `ohne-name@example.test`
|
||
5. fester Kunde mit leerem Namen und ohne E-Mail
|
||
- `viel`: zusätzlich 300 Kunden `Testkunde 001` bis `Testkunde 300`, 200 fest und 100 Leads. Soll-Zähler: 206 · 102 · 1 · 309.
|
||
|
||
- [ ] **Step 2: Portal-Antworten ergänzen**
|
||
|
||
Falls P0 sie nicht liefert (unter `https://portal.test/api/team`):
|
||
|
||
| Pfad | Antwort |
|
||
|---|---|
|
||
| `/customers/:id/portal` | `{ hasAccess: true, lastLoginAt: '2026-10-04T08:15:00.000Z' }` |
|
||
| `/customers/:id/abo` | ein aktives Abo mit Zusatzspeicher, für einen zweiten Kunden `{ status: '' }` („kein Abo“) |
|
||
| `/customers/:id/protokoll` | `{ entries }` mit 5 Einträgen: `geoeffnet`, `news_angelegt` mit Zusatztext, `termin_aktion` mit 120 Zeichen Zusatztext, einer ohne `staffName`, einer mit unbekannter Aktion `xyz` |
|
||
| `/funktionen/:id` | `{ projects }` mit den drei Projekten unten |
|
||
|
||
Projekte der Funktionen:
|
||
1. `Bäckerei Sonnenschein Website` mit `liveUrl`. News `active` mit `ticketWoid: '100131'`, Terminplaner `setup` mit `ticketWoid: '100140'` und `adminNote` von 80 Zeichen.
|
||
2. Projekt mit 70 Zeichen langem Namen und `liveUrl` von 80 Zeichen ohne Leerzeichen. News `off`, Terminplaner `active` mit `siteKey: 'q74ehq7hvnpiymqy'` und `ticketMissing: true`.
|
||
3. `Altes Projekt` ohne `liveUrl`. News `disabled` mit `disabledReason: 'abo_ende'` und `loeschungAm: '2026-11-04'`, Terminplaner `disabled` mit `disabledReason: 'abo_ende_vor_einrichtung'` und `loeschungAm: '2026-11-04'`.
|
||
|
||
Im Szenario `fehler` antworten `/customers/:id/portal`, `/abo`, `/protokoll` und `/funktionen/:id` mit 500.
|
||
|
||
- [ ] **Step 3: Ansichten in `views.mjs` ergänzen**
|
||
|
||
Die Schritte haben die Signatur `steps(page, ctx)` aus der Übersicht. Eine nicht erfüllte Zusicherung wirft einen Fehler (Verstoß `step-failed`, in jedem Gate aktiv). `zaehlerStimmen` klickt jeden der vier Reiter und vergleicht die Zahl im Reiter mit der Zahl der Zeilen `tbody tr.row` (leer: 0). `keinNull` wirft, wenn der Seitentext `undefined`, `null` oder `NaN` enthält.
|
||
|
||
| ID | Daten | Schritte | Zusicherung |
|
||
|---|---|---|---|
|
||
| `kunden` | `standard` | wie P0 | Reiter 6 · 2 · 1 · 9, `zaehlerStimmen` |
|
||
| `kunden-leads`, `kunden-archiv`, `kunden-alle` | `standard` | je der Reiter | 2 Zeilen mit „Lead“ · 1 Zeile mit „Archiviert“ · 9 Zeilen, davon eine mit „Abgesagt“ |
|
||
| `kunden-suche` | `standard` | Suche „Weber“ | 1 Zeile, Reiter 1 · 0 · 0 · 1 |
|
||
| `kunden-altdaten`, `kunden-altdaten-leads`, `kunden-altdaten-archiv` | `altdaten` | Reiter Alle, Leads, Archiviert | `keinNull`, der 60-Zeichen-Name steht vollständig im Link, eine Zeile mit „Wichtig“ und „Lead“, eine mit „Wichtig“, „Lead“ und „Archiviert“ |
|
||
| `kunden-viel` | `viel` | wie P0 | Reiter 206 · 102 · 1 · 309, `zaehlerStimmen` |
|
||
| `kunden-tastatur` | `standard` | Tab, bis ein `tbody tr a.row-link` Fokus hat, dann Enter | Fokusring 2 px `solid`. Pfad beginnt mit `/customers/`. Nach „Zurück“ erhöht ein Klick in die Zeile neben dem Link `history.length` um genau 1 |
|
||
| `dlg-lead-neu`, `dlg-kunde-neu` | `standard` | Klick „Neuer Lead“ bzw. „Neuer Kunde“ | `role="dialog"`, Titel „Neuer Lead (potenzieller Kunde)“ bzw. „Neuer Kunde mit Portal-Zugang“. Ein Tab landet im Dialog (`document.activeElement.closest('.dialog')`). Esc schließt, der Fokus kehrt zum Knopf zurück. Danach erneut öffnen |
|
||
| `dlg-kunde-neu-fehler` | `standard` | Name und E-Mail füllen, Passwort „kurz“, absenden | `.msg.err` mit „Das Portal-Passwort muss mindestens 8 Zeichen haben.“, Dialog bleibt offen |
|
||
| `zuordnen-kunde-neu` | `standard` | `/projects/zuordnen/<id>`, erste Art, „Ja, für einen Kunden“, „Neuer Kunde“ | Dialog „Neuer Kunde mit Portal-Zugang“ über der Seite |
|
||
| `kunde-overview`, `kunde-tickets`, `kunde-invoices`, `kunde-projects`, `kunde-chat`, `kunde-abo`, `kunde-gutschriften`, `kunde-funktionen`, `kunde-protokoll` | `standard` | die neun Ansichten aus P0 (Reiter-IDs aus `src/lib/customerTabs.js`, siehe Übersicht). Fehlende werden als `/customers/<id>?tab=<reiter>` ergänzt. Für dieselbe URL entsteht keine zweite Ansicht | keine eigene Zusicherung, außer bei `kunde-funktionen` (nächste Zeile) |
|
||
| `kunde-funktionen` (zusätzliche Schritte) | `standard` | Tab „Funktionen“, „Abschalten“ in der News-Zeile des ersten Projekts, „Abbrechen“, „Abschalten“ erneut | Nach dem ersten Klick steht die Zeile „News abschalten? Die Website zeigt dann keine Beiträge mehr.“ mit Feld „Notiz (optional)“, „Ja“ und „Abbrechen“. „Abbrechen“ schließt sie, am Ende ist sie offen, damit das Bild sie zeigt. Der Text enthält „Abgeschaltet (Abo beendet), Löschung am 04.11.2026“ (nur Fall A, siehe Task 1 Step 4) |
|
||
| `kunde-lead`, `kunde-archiv`, `kunde-altdaten` | `standard`, `standard`, `altdaten` | Kunde „Gärtnerei Grünblick“, „Blumen Lehmann“, Kunde Nr. 1 aus Step 1 | Eyebrow „Lead“ mit „Zum festen Kunden machen“ · „Archiviert“ mit „Wiederherstellen“ · `keinNull`, langer Name in h1 und Brotkrume |
|
||
| `kunde-nicht-gefunden` | `standard` | `/customers/gibt-es-nicht` | „Kunde nicht gefunden“ und Knopf „Zurück“ |
|
||
| `fehler-kunde`, `fehler-kunde-funktionen`, `fehler-kunde-protokoll`, `fehler-kunde-abo` | `standard`, `fehler` | Übersicht bzw. `?tab=funktionen`, `?tab=protokoll`, `?tab=abo` | „nicht abrufbar“ beim letzten Login bzw. je ein `.msg.err`. Ein Klick auf „Übersicht“ zeigt die Stammdaten |
|
||
|
||
- [ ] **Step 4: Gate `p4` in `gates.mjs` anlegen**
|
||
|
||
`p4` ist `p3` plus die P4-Ansichten im bereichsbezogenen Gate `icon-svg` (Schema der Übersicht). `step-failed`, `external-request` und `console-error` sind ohnehin aktiv. Das Gate deckt auch `gutschriften`, `angebote` und `speicher` ab, die der neue Grundstil in `admin-areas.css` trifft.
|
||
|
||
```js
|
||
const I3 = GATES.p3.scoped['icon-svg']
|
||
GATES.p4 = { ...GATES.p3, scoped: { ...GATES.p3.scoped, 'icon-svg': {
|
||
views: [...I3.views, 'kunden', 'kunden-*', 'dlg-lead-neu', 'dlg-kunde-neu', 'dlg-kunde-neu-fehler', 'kunde-overview', 'kunde-lead', 'kunde-archiv', 'kunde-altdaten', 'kunde-tickets', 'kunde-funktionen', 'kunde-protokoll', 'kunde-nicht-gefunden', 'fehler-kunde', 'fehler-kunde-funktionen', 'fehler-kunde-protokoll'],
|
||
ignore: [...(I3.ignore || []), '.credential'],
|
||
} } }
|
||
```
|
||
|
||
`.credential` (`CopyableCredential`) ist bis P5 ausgenommen. Nicht im `icon-svg`-Umfang sind `kunde-abo` und `fehler-kunde-abo` (Angebote, P6), `kunde-projects` (P5), `kunde-chat`, `kunde-invoices`, `kunde-gutschriften` und `zuordnen-kunde-neu` (fremde Bausteine). Deshalb stehen die `fehler-kunde-*`-Ansichten einzeln in der Liste. Ihre P4-Anteile prüft Task 8 über `rg` auf Icon-Importe.
|
||
|
||
- [ ] **Step 5: Vorher-Lauf (muss scheitern)**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p4-vorher && "$W/shots.sh" p4-vorher "$W/shots/p4-vorher" --only kunden,kunde-overview,kunde-funktionen,kunde-protokoll --gate p4`
|
||
Expected: Exit 1 wegen `icon-svg`. Die Bilder in `shots/p4-vorher/1440` und `390` sind die Vorher-Bilder mit den neuen Testdaten.
|
||
|
||
### Task 4: Kundenliste und Dialoge
|
||
|
||
**Files:**
|
||
- Modify: `src/pages/CustomersPage.jsx` (Zeilen 1–208 ersetzen)
|
||
- Modify: `src/pages/AssignProjectPage.jsx` (Zeilen 275–289: Aufruf von `CreateCustomerForm`)
|
||
- Modify: `src/styles/global.css` (nur die KOMPAT-Regel `.side-logo` löschen)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `PageHeader`, `Tabs`, `Badge`, `Button`, `EmptyState`, `Field`, `Dialog` (P1). `CUSTOMER_TABS`, `filterCustomers`, `stageCounts`, `customerBadges`, `customerTitle` (Task 2). `isRowActivation` aus `src/lib/rowClick.js` (P2). `createLead`, `useCustomers`, `useAuth` unverändert.
|
||
- Produces:
|
||
- `export function CreateCustomerForm({ onCancel, onCreate })` rendert einen `wide` Dialog. Die Signatur bleibt, P5 braucht sie.
|
||
- `CreateLeadForm({ onCancel, onDone })` (nicht exportiert) rendert einen Dialog.
|
||
|
||
- [ ] **Step 1: Seite neu aufbauen**
|
||
|
||
| Element | Umsetzung |
|
||
|---|---|
|
||
| Kopf | `PageHeader title="Kunden" subtitle="Potenzielle Kunden (Leads) und feste Kunden – eine Pflege"`. Aktionen: für Admins `Button variant="primary"` „Neuer Kunde“, für alle `Button` „Neuer Lead“. Beide setzen `showCreate` direkt, kein Umschalten mehr |
|
||
| Reiter | `Tabs` mit `CUSTOMER_TABS` und `count = stageCounts(customers, search)[id]`, bis zum Laden ohne Zähler |
|
||
| Suche | `<div className="toolbar">` mit `<input className="input search" type="search" placeholder="Kunde suchen …" aria-label="Kunde suchen">`, filtert sofort wie heute |
|
||
| Laden, leer | `<div className="loading">Wird geladen …</div>`. Leer: `EmptyState title="Keine Einträge" hint="Keine Kunden in dieser Ansicht."` |
|
||
| Tabelle | `.table`, `<tr className="row">` mit `onClick`, der bei `isRowActivation(e)` zu `/customers/<$id>` navigiert |
|
||
| Dialoge | `showCreate === 'lead'` rendert `CreateLeadForm`, `'customer'` und `isAdmin` rendert `CreateCustomerForm`, mit denselben Handlern wie heute. Bei Erfolg schließt die Seite, setzt bei Kunden den Reiter auf Feste Kunden und lädt neu |
|
||
|
||
Spalten (Klasse gilt für Kopf und Zelle):
|
||
|
||
| Kopf | Klasse | Inhalt |
|
||
|---|---|---|
|
||
| Unternehmen | – | `<div className="cell-title">` mit `<Link className="row-link" to=…>{customerTitle(c)}</Link>` und dahinter den `Badge`s aus `customerBadges(c)`. Darunter `.cell-sub` mit `contactName`, nur wenn vorhanden |
|
||
| Kundennummer | `hide-sm num` | `code`, sonst `<span className="faint">–</span>` |
|
||
| Standort | – | `location`, sonst „–“ |
|
||
| E-Mail | `hide-md muted` | `email`, sonst „–“ |
|
||
| Telefon | `hide-md muted nowrap num` | `phone`, sonst „–“ |
|
||
|
||
Formulare (jedes Feld ist ein `Field` mit `htmlFor` und `id`, `Dialog` bekommt `onClose={onCancel}` und `busy`, Fehler wie in P2 als `.msg.err` oben im Körper):
|
||
- `CreateLeadForm`: `as="form"`, Titel „Neuer Lead (potenzieller Kunde)“. Im Körper der Hinweis „Fuer einen Lead reichen Unternehmen und E-Mail. Es wird automatisch ein Akquise-Ticket erstellt.“ (`.muted .small`), dann `.field-row` mit „Unternehmen“ (`required`) und „E-Mail“ (`type="email"`). Ein Fehler steht als `.msg.err` mit `role="alert"` als erstes Kind des Körpers. Fuß: `<Button type="button">` „Abbrechen“ und `Button variant="primary" type="submit"` „Lead anlegen + Akquise-Ticket“ (bei `busy` „Speichert …“). „Unternehmen erforderlich“ bleibt.
|
||
- `CreateCustomerForm`: `wide`, Titel „Neuer Kunde mit Portal-Zugang“, zwei `.field-row-3`: „Unternehmen / Name“, „E-Mail (Portal-Login)“, „Kundennummer“ und „Telefon“, „Standort“, „Portal-Passwort (mind. 8 Zeichen)“. `name`, `required`, `type` und `autoComplete="new-password"` bleiben, ebenso die Texte „Das Portal-Passwort muss mindestens 8 Zeichen haben.“ und „Kunde konnte nicht angelegt werden.“. Fuß: `<Button type="button">` „Abbrechen“ und `Button variant="primary" type="submit"` „Kunde anlegen“ (bei `busy` „Speichert …“).
|
||
|
||
- [ ] **Step 2: Aufruf im Zuordnen-Assistenten**
|
||
|
||
In `AssignProjectPage.jsx` entfällt nur der Umschlag `<div style={{ marginBottom: 12 }}>` um `CreateCustomerForm`, der Dialog liegt über der Seite. Props und Handler bleiben. Der Rest der Datei gehört zu P5.
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git diff --stat src/pages/AssignProjectPage.jsx`
|
||
Expected: nur wenige Zeilen geändert (der Umschlag).
|
||
|
||
- [ ] **Step 3: Hygiene prüfen, `.side-logo` löschen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|style=\{\{|form-control|ui-card|badge-|\bdot\b" src/pages/CustomersPage.jsx; rg -w side-logo src`
|
||
Expected: Aus `CustomersPage.jsx` keine Treffer. `side-logo` steht nur noch als KOMPAT-Regel in `src/styles/global.css`, keine Datei benutzt es. Die Spec lässt die Regel bis P4 stehen, deshalb wird sie jetzt gelöscht (samt Hover- und Kindregeln).
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -w side-logo src`
|
||
Expected: keine Treffer.
|
||
|
||
- [ ] **Step 4: Bauen und prüfen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p4 && "$W/shots.sh" p4 "$W/shots/p4-t4" --only kunden,kunden-leads,kunden-archiv,kunden-alle,kunden-suche,kunden-altdaten,kunden-altdaten-leads,kunden-altdaten-archiv,kunden-viel,kunden-tastatur,dlg-lead-neu,dlg-kunde-neu,dlg-kunde-neu-fehler,zuordnen-kunde-neu --gate p4`
|
||
Expected: Exit 0 und alle Zusicherungen aus Task 3. Die Bilder zeigen Haarlinien, schwarzes „Neuer Kunde“ und Standard-„Neuer Lead“. Bei 390 px bleiben Unternehmen und Standort.
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add src/pages/CustomersPage.jsx src/pages/AssignProjectPage.jsx src/styles/global.css
|
||
git commit -m "Kundenliste als Tabelle mit Reitern und Zaehlern, Neuer Kunde und Neuer Lead als Dialoge" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 5: Kundenseite und Kundenansicht-Knopf
|
||
|
||
**Files:**
|
||
- Modify: `src/pages/CustomerDetailPage.jsx` (Kopf Zeilen 56–124, `OverviewTab` 126–212, `PortalAccessCard` 214–298, `CustomerTickets` 300–344, `CustomerProjects` 346–370)
|
||
- Modify: `src/components/customer/KundenansichtButton.jsx` (Zeilen 1–36)
|
||
- Modify: `src/styles/global.css` (neuer Block „Kunden (P4)“ vor dem KOMPAT-Block)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `PageHeader` (`crumb`, `eyebrow`, `subtitle`), `Tabs`, `Card`, `Badge`, `Button`, `EmptyState`, `StatusPill`, `UrgentPill` (P1). `customerEyebrow`, `customerBadges`, `customerTitle`, `customerStage`, `isArchived` (Task 2). `ticketPath(ref)`, `ticketRef(ticket)` (P2), `ticketNumber(ticket)` (P2). `isRowActivation` aus `src/lib/rowClick.js` (P2).
|
||
- Produces: `KundenansichtButton({ customer, variant = 'default', checkAccess = true })`. Die Voreinstellung wechselt von `primary` auf `default`. Die anderen Aufrufer (`CreditDetail`, P6) übergeben `ghost` und sehen gleich aus. Sonst nichts Neues, die Aufrufe der Reiter-Inhalte bleiben wie heute.
|
||
|
||
- [ ] **Step 1: Kopf prüfen und umbauen**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "subtitle" src/components/ui/PageHeader.jsx`
|
||
Expected: `subtitle` steht in `.sub` und nimmt einen Knoten an. Die Abzeichen sind `span` und damit auch in einem `<p>` gültig.
|
||
|
||
| Element | Umsetzung |
|
||
|---|---|
|
||
| Laden | `<div className="page"><div className="loading">Wird geladen …</div></div>` |
|
||
| Nicht gefunden | `EmptyState title="Kunde nicht gefunden" hint={error}` mit `Button as={Link} to="/customers"` „Zurück“ (ohne Pfeil) |
|
||
| Kopf | `crumb={[{ label: 'Kunden', to: '/customers' }, { label: customerTitle(customer) }]}`, `eyebrow={customerEyebrow(customer)}`, `title={customerTitle(customer)}`. `.sub`: Abzeichen aus `customerBadges(customer, { stage: false })`, dann `[location, email, phone].filter(Boolean).join(' · ')`, beides leer: kein `.sub` |
|
||
| Aktionen | `Button variant="danger"` „Kunde löschen“ (nur Admin), `Button` „Wiederherstellen“ bzw. „Archivieren“, `KundenansichtButton`. `removeCustomer` (mit `window.confirm`/`window.alert`) und `toggleArchived` bleiben |
|
||
|
||
`KundenansichtButton`: Icon und Spinner entfallen, der Text „Kundenansicht öffnen“ bleibt, bei `busy` steht „Wird geladen …“. `title`, die `disabled`-Regel, `window.alert` und `openInNewTab` bleiben. `PortalAccessCard` übergibt keine `variant` mehr.
|
||
|
||
Reiter: `Tabs tabs={customerTabs(isAdmin)}`, `activeTab`, `?tab=` und `setSearchParams(…, { replace: true })` bleiben. Die neun Reiter:
|
||
|
||
| id | Label | Inhalt |
|
||
|---|---|---|
|
||
| `overview` | Übersicht | `OverviewTab` (P4) |
|
||
| `tickets` | Tickets | `CustomerTickets` (P4) |
|
||
| `invoices` | Rechnungen (nur Admins) | `CustomerInvoices` unverändert (P7) |
|
||
| `projects` | Projekte | `CustomerProjects` mit `AssignedProjectCard` (P5) |
|
||
| `chat` | Chat | `<Card><ChatThread …/></Card>` unverändert (P6) |
|
||
| `abo` | Abo & Angebote | `AboStatusCard` (Task 7) und `OffersView` (P6), ohne Umschlag mit Inline-Style |
|
||
| `gutschriften` | Gutschriften | `CreditsView` unverändert (P6) |
|
||
| `funktionen` | Funktionen | `CustomerFunktionen` (Task 6) |
|
||
| `protokoll` | Protokoll | `CustomerViewLog` (Task 7) |
|
||
|
||
- [ ] **Step 2: Übersicht**
|
||
|
||
Wurzel `<div className="two-col">` mit zwei `<div>`-Spalten. Links `Card title="Stammdaten"` und `Card title="Rechnungsdaten"`, rechts `PortalAccessCard`. State und Handler (`save`, `upgrade`, `savePassword`) bleiben, ebenso alle Texte und dass der Name `companyName` mitschreibt. Aus den `Field`-Zeilen werden `.kv`-Zeilen (`<dt><label htmlFor>`, `<dd>` mit `className="input"`), jedes Feld bekommt eine `id`.
|
||
|
||
| Abschnitt | Inhalt in dieser Reihenfolge |
|
||
|---|---|
|
||
| Stammdaten | Unternehmen / Name, Code, E-Mail (`type="email"`), Telefon, Standort, Ansprechpartner, Notizen (`<textarea className="textarea" rows={3}>`). Darunter `<label className="check">` „Wichtiger Kunde“, dann `.toolbar` mit `Button variant="primary"` „Speichern“ (bei `busy` „Speichert …“), bei Leads `Button` „Zum festen Kunden machen“ und der Meldung |
|
||
| Rechnungsdaten | oben `<Badge tone="warn">Adresse unvollständig</Badge>` bei `!ready`, dann Strasse + Nr., PLZ, Stadt, Land, USt-IdNr. |
|
||
| Portal-Zugang | Kundenportal (`Badge ok` „freigeschaltet“ bzw. `muted` „kein Zugang“), Letzter Login („…“, „nicht abrufbar“ oder `formatLastLogin`), Login (E-Mail) und Passwort als `CopyableCredential`, das Feld „Neues Passwort setzen“ bzw. „Passwort festlegen (min. 8 Zeichen)“, `.toolbar` mit `Button` „Passwort ändern“ bzw. „Zugang anlegen“ (bei `busy` „Speichert …“), `KundenansichtButton`, Meldung, zuletzt der Portal-Hinweis als `<p className="muted small">` |
|
||
|
||
Meldungen: `msg` wird `{ tone, text }` mit den bisherigen Texten. Fehler und Pflichthinweise sind `err` bzw. `warn` (mit `role="alert"`), Erfolge `ok`.
|
||
|
||
- [ ] **Step 3: Tickets- und Projekte-Reiter**
|
||
|
||
- `CustomerTickets`: Abfrage und Zustände bleiben. Laden `.loading`, leer `EmptyState title="Keine Tickets" hint="Fuer diesen Kunden gibt es noch keine Tickets."`.
|
||
- Tabelle „Ticket“ und „Status“:
|
||
- Erste Zelle `<div className="cell-title">` mit `<Link className="row-link" to={ticketPath(ticketRef(t))}>`, darin `<span className="id">{ticketNumber(t)}</span>` und der Betreff (`topic || title || '-'`), daneben `UrgentPill`. Darunter `.cell-sub` mit `t.type`. Die Kurznummer ist nie Linkziel.
|
||
- Zweite Zelle `StatusPill`. `<tr className="row">` mit `isRowActivation` wie in Task 4.
|
||
- `CustomerProjects`: Laden `.loading`, leer wie heute, sonst `Card title={`Projekte (${n})`}` mit `AssignedProjectCard`.
|
||
|
||
- [ ] **Step 4: CSS-Block „Kunden (P4)“**
|
||
|
||
Der Block steht hinter den Bausteinen und vor dem KOMPAT-Block und beginnt mit `/* ===== Kunden (P4) ===== */`:
|
||
- `.kv .textarea { width: 100%; max-width: 360px }` (die Prototypregel gilt nur für `.input`)
|
||
|
||
Den Abstand zwischen aufeinanderfolgenden Abschnitten in den Spalten (`h2.section`) liefert P1 mit `.page section + section { margin-top: 36px }`. P4 baut dafür keine eigene Regel.
|
||
|
||
- [ ] **Step 5: Hygiene und Prüfung**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|style=\{\{|form-control|ui-card|badge badge-|\bdot\b" src/pages/CustomerDetailPage.jsx src/components/customer/KundenansichtButton.jsx`
|
||
Expected: keine Treffer.
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p4 && "$W/shots.sh" p4 "$W/shots/p4-t5" --only kunde-overview,kunde-tickets,kunde-projects,kunde-lead,kunde-archiv,kunde-altdaten,kunde-nicht-gefunden,kunde-chat,kunde-invoices,kunde-gutschriften --gate p4`
|
||
Expected: Exit 0. `kunde-overview` zeigt zwei Spalten mit `.kv`, der Abstand zwischen Stammdaten und Rechnungsdaten beträgt mindestens 24 px (36 px aus P1), der Kopf hat keinen schwarzen Knopf. Bei 390 px stehen die Spalten untereinander, und die Reiter scrollen waagerecht.
|
||
|
||
- [ ] **Step 6: Commit**
|
||
|
||
```bash
|
||
git add src/pages/CustomerDetailPage.jsx src/components/customer/KundenansichtButton.jsx src/styles/global.css
|
||
git commit -m "Kundenseite: Brotkrumen, Eyebrow, Uebersicht in zwei Spalten, Tickets-Reiter mit Links" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 6: Funktionen als Tabelle
|
||
|
||
**Files:**
|
||
- Modify: `src/components/customer/CustomerFunktionen.jsx` (Zeilen 1–130, die Logik bleibt)
|
||
- Modify: `src/styles/global.css` (Block „Kunden (P4)“ aus Task 5: Funktionen-Regeln)
|
||
- Modify: `src/styles/admin-areas.css` (Funktionen-Regeln löschen: Zeilen 24–32, 36 und 60)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `FUNKTIONEN`, `ZUSTAND`, `funktionOf`, `aktionen`, `mergeFunktion`, `siteKeyOf` und, nur in Fall A (Task 1 Step 4), `abgeschaltetText` aus `lib/funktionenView` (Stand von `test`, nicht geändert). `copyText` aus `CopyableCredential`. `getFunktionen`, `setFeature`. `ticketPath` (P2).
|
||
- Produces: `CustomerFunktionen({ customerId })`, gleiche Signatur.
|
||
|
||
- [ ] **Step 1: Markup**
|
||
|
||
State, `load`, `save`, `ask` und `short` bleiben. Jede Bedienung von heute bleibt erreichbar, alle Texte bleiben. Der Import von `admin-areas.css` entfällt, die Funktionen-Regeln wandern nach `global.css`, damit P6 die Datei ohne P4-Nutzer löschen kann.
|
||
|
||
| Element | Umsetzung |
|
||
|---|---|
|
||
| Zustände | Fehler `<div className="msg err" role="alert">`. Laden `.loading` „Wird geladen …“. Leer `EmptyState title="Keine Projekte für diesen Kunden."` |
|
||
| Rahmen | `Card title="Funktionen"` mit `<table className="table funktionen-table">`, Kopf „Funktion“, „Status“, „Ticket“, „Aktionen“ |
|
||
| Projektzeile | je Projekt ein `<tbody>`, dessen erste Zeile `<tr className="funktionen-group"><th colSpan={4} scope="rowgroup">` den Projektnamen trägt. Dahinter `liveUrl` ohne Protokoll als `<a className="link" target="_blank" rel="noreferrer">` |
|
||
| Funktionszeile | je Funktion: `f.label`, Status, Ticket, Aktionen |
|
||
| Status | `Badge tone={tone}` mit `label`. Darunter (Fall A) `.cell-sub` mit `abgeschaltetText(feature)` (wenn nicht leer), `.cell-sub` mit `short(feature.adminNote)` und `title={feature.adminNote}` und bei `siteKeyOf(...)` der `SiteKey` |
|
||
| `SiteKey` | `.funktionen-sitekey` mit „Site-Key“ (`.muted`), `<code>` und `<button type="button" className="link">` „Kopieren“ bzw. „Kopiert“ (1,8 s). `title` ist „Site-Key kopieren“ bzw. „Kopiert“, `aria-label` „Site-Key kopieren“ bzw. „Site-Key kopiert“, beides wie heute. Kein Icon |
|
||
| Ticket | `ticketMissing` → `<Badge tone="danger">Ticket fehlt</Badge>`. Mit `ticketWoid` → `<Link className="link" to={ticketPath(feature.ticketWoid)}>#{feature.ticketWoid}</Link>`. Sonst `<span className="faint">–</span>` |
|
||
| Aktionen | je `aktionen(feature.state)` ein `<button type="button" className="link">` „Aktivieren (ohne Zahlung)“ bzw. „Abschalten“ in `.funktionen-actions`. Solange die Bestätigung dieser Zeile offen ist, bleibt die Zelle leer |
|
||
| Bestätigung | Direkt unter der Funktionszeile `<tr className="funktionen-confirm"><td colSpan={4}>` mit einem `.flex.items-center.gap-2.wrap`: Text `f.bestaetigen[confirm.action]`, `<input className="input funktionen-note" placeholder="Notiz (optional)" maxLength={120} aria-label="Notiz (optional)">`, `Button variant="primary"` „Ja“ (bei `busy` „Speichert …“, `disabled`) und `Button` „Abbrechen“ (`disabled` bei `busy`) |
|
||
| Rückmeldung | unter der Tabelle `<div className={`msg ${status.ok ? 'ok' : 'err'}`}>`, bei Fehler mit `role="alert"` |
|
||
| tp2-Zusatz (nur Fall A) | Die Datei enthält zusätzlich `KopierKnopf`, `AnleitungPanel`, `TextformPanel`, den Knopf „Anleitung“ (`aria-expanded`) und `hindernisText` beim Status. Logik, Zustände und Texte bleiben. Umbau wie oben: Knöpfe ohne `size` und Icons (`Button type="button"` bzw. `button.link`), der Spinner wird „Wird geladen …“, Kopierknöpfe zeigen „Kopiert“ bzw. ihren Namen, ein Icon-Knopf zeigt seinen `aria-label`-Text, `form-control` wird `input`, Inline-Styles werden Klassen im Block „Kunden (P4)“, `.area-*` wird `.msg err`, `.msg warn` bzw. `.muted`. Die Panels stehen als `<tr className="funktionen-extra"><td colSpan={4}>` direkt unter der Funktionszeile, `<details>` bleibt |
|
||
|
||
- [ ] **Step 2: CSS verschieben**
|
||
|
||
Aus `admin-areas.css` entfallen alle `.funktionen-*`-Regeln samt `.funktionen-leer` in den beiden Responsive-Blöcken. In Fall A ziehen auch die `.funktionen-panel*`-Regeln von tp2 um, mit Tokens statt `--surface-*` und `--border` (`--bg-subtle`, `--line`). Im Block „Kunden (P4)“ von `global.css` stehen:
|
||
|
||
| Selektor | Wert |
|
||
|---|---|
|
||
| `.funktionen-group th` | `text-align: left; font-size: 14px; font-weight: 600; color: var(--text); padding-top: 20px; overflow-wrap: anywhere` |
|
||
| `.funktionen-group .link` | `font-size: 13px; font-weight: 400; margin-left: 8px` |
|
||
| `.funktionen-sitekey` | `display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin-top: 4px` |
|
||
| `.funktionen-sitekey code` | `font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; overflow-wrap: anywhere; user-select: all` |
|
||
| `.funktionen-actions` | `display: flex; flex-wrap: wrap; gap: 4px 14px` |
|
||
| `.funktionen-confirm td` | `padding-top: 0` |
|
||
| `.funktionen-note` | `width: 100%; max-width: 260px` |
|
||
|
||
- [ ] **Step 3: Hygiene und Prüfung**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|style=\{\{|form-control|area-|admin-areas|ui-btn|size=" src/components/customer/CustomerFunktionen.jsx; rg -n "funktionen" src/styles/admin-areas.css`
|
||
Expected: keine Treffer.
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p4 && "$W/shots.sh" p4 "$W/shots/p4-t6" --only kunde-funktionen,fehler-kunde-funktionen --gate p4`
|
||
Expected: Exit 0. Das Bild von `kunde-funktionen` zeigt die geöffnete Bestätigungszeile. Bei 390 px läuft nichts über (Site-Key und lange URL brechen um), in Fall A ist „Abgeschaltet (Abo beendet), Löschung am 04.11.2026“ lesbar, und jeder Textlink zeigt den Fokusring.
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add src/components/customer/CustomerFunktionen.jsx src/styles/global.css src/styles/admin-areas.css
|
||
git commit -m "Kunden-Reiter Funktionen als Tabelle mit Textaktionen, Bestaetigung als eigene Zeile" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 7: Abo-Status, Protokoll und Grundstil von `admin-areas.css`
|
||
|
||
**Files:**
|
||
- Modify: `src/components/customer/AboStatusCard.jsx` (Zeilen 1–44)
|
||
- Modify: `src/components/customer/CustomerViewLog.jsx` (Zeilen 1–56)
|
||
- Modify: `src/styles/admin-areas.css` (Zeilen 1–61)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `Card`, `Badge`, `Button`, `EmptyState`. `getCustomerAboStatus`, `getCustomerViewLog`. `formatViewLogTime`, `viewLogLabel`.
|
||
- Produces: den Grundstil der `.area-*`-Klassen für die P6-Seiten. Die P4-Dateien benutzen keine `.area-*`-Klasse und keinen Import von `admin-areas.css` mehr.
|
||
|
||
- [ ] **Step 1: `AboStatusCard`**
|
||
|
||
`Card title="Abo"`. Der Import von `admin-areas.css` entfällt. Laden `.loading`, Fehler `<div className="msg err" role="alert">`, ohne Abo `<p className="muted">kein Abo</p>`. Die Zeilen werden `<dl className="kv">`: „Abo“ (Produktname plus `Badge`, im `<dd className="flex items-center gap-2 wrap">`), „Leistungsbeginn“, „Nächste Abbuchung“ (nur bei `abo.active`), „Gekündigt zum“, „Zusatzspeicher“. Texte und Bedingungen wie heute.
|
||
|
||
- [ ] **Step 2: `CustomerViewLog`**
|
||
|
||
- Fehler: `.msg.err` mit der Meldung und daneben `Button` „Erneut versuchen“ (ohne Icon, `onClick={load}`), beides in einem `.flex.items-center.gap-2.wrap`. Laden `.loading`. Leer wie heute (`EmptyState title="Noch keine Einträge"`).
|
||
- Tabelle „Zeit“ (`formatViewLogTime(e.at)`, `nowrap num`), „Mitarbeiter“ (`e.staffName || '–'`), „Aktion“ (`viewLogLabel(e.action)`, darunter `.cell-sub` mit `e.detail`). Keine Zeilenaktion, keine Links.
|
||
|
||
- [ ] **Step 3: Grundstil von `admin-areas.css`**
|
||
|
||
| Regel | Neu |
|
||
|---|---|
|
||
| `.area-row` | `padding: 12px 0` statt 12px 16px, Haarlinie `var(--line)` |
|
||
| `.area-row.head` | `color: var(--text-2); font-size: 12px; font-weight: 500`, ohne `text-transform` und `letter-spacing` |
|
||
| `.area-row.active` | `background: var(--bg-subtle)` |
|
||
| `.area-muted`, `.area-danger`, `.area-warn` | 13 px in `var(--text-2)`, `var(--red-fg)`, `var(--orange-fg)` |
|
||
| `.area-summary` | ohne Hintergrund, Radius und Innenabstand, `margin-bottom: 12px`, `.warn` färbt die Schrift `var(--orange-fg)` |
|
||
| `.area-message`, `.area-error` | wie `.msg.ok` und `.msg.err`: 13 px, `var(--green-fg)` bzw. `var(--red-fg)`, ohne Kasten, Abstand `8px 0` |
|
||
| `.area-kv dt`, `.credit-calc label`, `.credit-field` | `color: var(--text-2)` |
|
||
| `.area-rows a`, `.area-rows a:hover`, `.credit-cols a` | entfallen. Links ohne Klasse bekommen Farbe und feinen Unterstrich von `a:not([class])` (P1). `admin-areas.css` lädt vor `global.css`, eine eigene Regel mit gleicher Spezifität würde dort verlieren |
|
||
| Responsive | Beide Blöcke (`@media (max-width: 720px)` wird `860px`, und `@container (max-width: 960px)`) stehen am Dateiende hinter allen Spaltenregeln, `!important` entfällt in beiden. Bei gleicher Spezifität gewinnt die spätere Regel. Hat ein Spaltenselektor mehr als eine Klasse, bekommt der Responsive-Selektor dieselben Klassen |
|
||
| Bleiben bis P6 | `.storage-row`, `.credit-row`, `.credit-cols`, `.credit-*`, `.offer-row`, `.area-filter`, `.area-inline`, `.area-kv` |
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "!important|#[0-9a-fA-F]{3,6}\b|rgba\(|--text-muted|--text-faint|--surface|--border|--r-sm|--ok\b|--accent" src/styles/admin-areas.css`
|
||
Expected: keine Treffer.
|
||
|
||
- [ ] **Step 4: Hygiene und Prüfung**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|style=\{\{|form-control|ui-card|area-|admin-areas" src/components/customer`
|
||
Expected: keine Treffer.
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p4 && "$W/shots.sh" p4 "$W/shots/p4-t7" --only kunde-abo,kunde-protokoll,fehler-kunde-protokoll,fehler-kunde-abo,kunde-gutschriften,gutschriften,angebote,speicher --gate p4`
|
||
Expected: Exit 0. Auf `gutschriften`, `angebote` und `speicher` stehen die Zeilen mit Haarlinien ohne Kästen und die Kopfzeilen ohne Großbuchstaben. Sie bleiben lesbar, P6 ersetzt sie.
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add src/components/customer/AboStatusCard.jsx src/components/customer/CustomerViewLog.jsx src/styles/admin-areas.css
|
||
git commit -m "Abo-Status und Protokoll ohne Karten und Icons, neuer Grundstil fuer admin-areas.css" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
|
||
```
|
||
|
||
### Task 8: Gate P4, 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, `# fail 0`, `# pass` = N3 + 6. Liegen neue Commits auf `test`, kann die Zahl höher sein; sie wird notiert.
|
||
|
||
- [ ] **Step 2: Vollständiger Gate-Lauf**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p4 && "$W/shots.sh" p4 "$W/shots/p4" --gate p4`
|
||
Expected:
|
||
- Gate `p4` Exit 0.
|
||
- Keine Verstöße gegen `dark-surface`, `low-contrast`, `overflow-x`, `console-error`, `external-request` und `step-failed` auf allen Ansichten, auch auf `gutschriften`, `angebote` und `speicher`.
|
||
- Die Kundenansichten aus dem `icon-svg`-Umfang haben kein `icon-svg` außerhalb `.credential`.
|
||
|
||
- [ ] **Step 3: Code-Hygiene im umgebauten Bereich**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|@tabler|style=\{\{|/tickets\?woid" src/pages/CustomersPage.jsx src/pages/CustomerDetailPage.jsx src/components/customer; rg -w side-logo src`
|
||
Expected: keine Treffer. `AssignProjectPage.jsx` behält seine Icons bis P5.
|
||
|
||
- [ ] **Step 4: Bundle-Größe**
|
||
|
||
Die Größen stehen in der Ausgabe von `check.sh` (Step 1). Sie werden mit `design-p3` verglichen. Das JS wird kleiner (Icon-Importe in sieben Dateien entfallen).
|
||
|
||
- [ ] **Step 5: Code-Review (sonnet)**
|
||
|
||
Geprüft wird der Diff `design-p3..HEAD` gegen die Spec-Abschnitte „Gestaltungsregeln“, „Bausteine“ und Paket P4. Schwerpunkte:
|
||
- Texte unverändert (Diff-Stichprobe der Formulare und Meldungen)
|
||
- Zeilenklick-Logik und erste Zelle als echter Link
|
||
- Dialog-Verdrahtung (`busy`, Esc, `onClose`)
|
||
- `ticketPath` als einziges Linkziel auf Tickets
|
||
- `admin-areas.css` ohne `!important`
|
||
- `lib/leads.js` exportiert weiter alle bisherigen Namen (`git diff design-p3 -- src/lib/leads.js`)
|
||
- jede Bedienung aus Task 4 bis 7 ist erreichbar (Kopfknöpfe, neun Reiter, beide Formulare, Aktivieren, Abschalten, Bestätigung mit Notiz, Site-Key kopieren, „Erneut versuchen“)
|
||
- „Abbrechen“ in beiden Anlege-Dialogen hat `type="button"`
|
||
|
||
Wichtige Befunde werden behoben, Kleinigkeiten notiert. Danach kommt der Rebase aus Step 1 noch einmal und Step 2 als Kurzlauf mit `--only kunden,kunde-overview,dlg-kunde-neu`.
|
||
|
||
- [ ] **Step 6: Tag und Übergabe**
|
||
|
||
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" && git tag design-p4 && git log --oneline origin/test..design-p4 && git rev-parse design-p4`
|
||
|
||
Der Eigentümer bekommt:
|
||
- die Screenshots aus `shots/p4/1440x900` und `shots/p4/390x844` (Auswahl: `kunden`, `kunden-altdaten-archiv`, `dlg-kunde-neu`, `kunde-overview`, `kunde-tickets`, `kunde-funktionen`, `kunde-protokoll`) und den Vorher-Vergleich aus `shots/p4-vorher`
|
||
- den Hinweis: „Fester Kunde“ gibt es in der Liste nicht mehr als Abzeichen, „Wichtig“ ersetzt den Stern, die Suche findet jetzt auch den Ansprechpartner
|
||
- den tp2-Stand aus Task 1: Fall A (tp2 war vor P4 in `test`, das Anleitungs- und Zustimmungs-Panel ist in die Zusatzzeilen der Tabelle übernommen) oder Fall B (P4 ging vor tp2, die Konflikte in `CustomerFunktionen.jsx`, `lib/funktionenView.js` und `admin-areas.css` löst die vom Eigentümer genannte Stelle beim Rebase von tp2 auf, die Panels müssen dann in die Zusatzzeile `funktionen-extra` der Tabelle)
|
||
- Notizen für spätere Pakete:
|
||
- P5: `CopyableCredential` und `AssignedProjectCard` erscheinen im Portal-Zugang und im Projekte-Reiter noch in alter Form, die Icons von `AssignProjectPage.jsx` sind unberührt.
|
||
- P6: `KundenansichtButton` ist jetzt ein Standardknopf, in `CreditDetail` bleibt „Genehmigen“ der einzige schwarze Knopf. Die Raster `.storage-row`, `.credit-row`, `.offer-row` und `.area-kv` stehen noch in `admin-areas.css`.
|
||
- die Push-Zeile `git -C /home/webklar/apps/ticket.webklar.com/dev-design push origin 'design-p4^{commit}:refs/heads/test'`
|
||
- die Rücknahme: `git revert --no-commit design-p3..design-p4`, Commit und Push durch den Eigentümer (gilt nur, solange P4 das jüngste Paket ist)
|
||
|
||
- [ ] **Step 7: 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: Kundenliste mit Reitern, eine Kundenseite mit Tickets-Reiter und Funktionen.
|