Steriles Weiss, kaum Rahmen, kaum Icons, Akzent Schwarz, Ticket als eigene Seite. Pakete P0-P10, jedes einzeln ueber ein Tag live. Gegen den Code geprueft (4 Pruefer + Gegenpruefung, 66 Befunde eingearbeitet). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
542 lines
46 KiB
Markdown
542 lines
46 KiB
Markdown
# WOMS „Weißes Design“ – Konzept
|
||
|
||
Stand 2026-10-04. Freigegeben im Gespräch:
|
||
- Prototyp angesehen, Akzentfarbe „Schwarz passt“.
|
||
- Ticket öffnet als eigene Seite.
|
||
- Paketablauf und alle Standardentscheidungen mit „ja passt“.
|
||
|
||
Danach gegen den Code geprüft: 4 Prüfer, Gegenprüfung jedes Befunds, 66 bestätigte Befunde eingearbeitet.
|
||
|
||
Grundlage:
|
||
- Klick-Prototyp im Repo `WEBklar/ticketsystem-design`, Vorschau `https://ticketsystem-design.project.webklar.com` mit Mitarbeiter-Login. Kopie unter `/root/reports/ticketsystem-design-2026-10-04/quelle/`.
|
||
- Bestandsaufnahme aller 104 Quelldateien in `2026-10-04-weisses-design-umbaukarte.md` (gleicher Ordner). **Bei Widersprüchen gilt dieses Dokument.**
|
||
|
||
## Ziel
|
||
|
||
Das ganze Ticketsystem (ticket.webklar.com) bekommt ein neues Aussehen:
|
||
- steriles Weiß, sehr minimalistisch,
|
||
- kaum Rahmen, kaum Icons,
|
||
- angelehnt an das Stripe-Dashboard und trotzdem übersichtlich.
|
||
|
||
Das dunkle Design entfällt vollständig. Es gibt keinen Umschalter.
|
||
|
||
Der Umbau ist **rein visuell**, mit zwei Ausnahmen:
|
||
- die in „Bewusste Änderungen am Verhalten“ genannten Punkte,
|
||
- vor allem: Ein Ticket öffnet als **eigene Seite** mit eigenem Link (`/tickets/<Nummer>`), statt in der Liste aufzuklappen.
|
||
|
||
Erfolg heißt:
|
||
- Jede Seite folgt den Gestaltungsregeln.
|
||
- Keine heutige Funktion geht verloren, außer den ausdrücklich genannten.
|
||
- Alle Tests sind grün.
|
||
- Jedes Paket geht einzeln live.
|
||
- Das jüngste Paket lässt sich per Revert zurücknehmen.
|
||
|
||
Nicht im Umfang:
|
||
- Dark Mode, neue Funktionen
|
||
- Änderungen am Datenmodell, an Rechten oder am Server
|
||
- das Kundenportal, auch nicht dessen Chat-Composer
|
||
- Terminplaner TP2
|
||
- flächige Rechtschreibkorrekturen (ae/ue) in Bestandstexten
|
||
|
||
## Gestaltungsregeln
|
||
|
||
1. **Weiß überall.**
|
||
- Seite, Menü und Dialoge haben `#ffffff`.
|
||
- Es gibt keine Verläufe, Leuchteffekte, Glas-Effekte oder Hintergrundanimationen.
|
||
- Einzige Ausnahme sind Bildansicht und Kamera des fremden Chat-Composers. Sie bleiben dunkel, weil sie Medienbetrachter sind.
|
||
2. **Kaum Rahmen.**
|
||
- Struktur entsteht durch Weißraum, Schriftgrößen und einzelne 1-px-Haarlinien `#ebeef1`.
|
||
- Haarlinien stehen zwischen Tabellen- und Listenzeilen, unter Reitern, rechts neben dem Menü, zwischen Schlüsselwerten (`.fact`) und zwischen den Tagesspalten im Planboard.
|
||
- Karten oder Kästen um Inhalte gibt es nicht.
|
||
- Eingabefelder und Zweitknöpfe tragen statt Rahmen einen feinen Ring-Schatten.
|
||
3. **Kaum Icons.**
|
||
- Menü, Knöpfe, Überschriften und Badges tragen keine Icons.
|
||
- Handelnde Zeichen werden als Schriftzeichen gesetzt, nicht als Icon-Komponente: Schließen „ד, Pfeile „← →“, Sortierung „↑ ↓“.
|
||
- Am Ende fliegen `react-icons` und `@tabler/icons-react` aus `package.json`.
|
||
4. **Eine Akzentfarbe: Schwarz `#111111`.**
|
||
- Sparsam eingesetzt: Primärknöpfe, aktiver Reiter, aktiver Menüpunkt, heutige Datumspille im Planboard.
|
||
- Pro Seitenkopf höchstens ein schwarzer Knopf.
|
||
- Links sind am feinen Unterstrich erkennbar, nicht an der Farbe.
|
||
5. **Statusfarben** sind leicht getönte Pillen ohne Rahmen: Radius 4 px, Höhe 20 px, Schrift 12 px/500.
|
||
|
||
| Ton | Hintergrund | Schrift | Ticketstatus (gespeicherte Werte) |
|
||
|---|---|---|---|
|
||
| grau | #ebeef1 | #414552 | Offen (`Open`), leer oder unbekannt |
|
||
| blau | #e0f2fe | #075985 | In Arbeit (`Occupied`, `Assigned`, `Added Info`, `In Kontakt`) |
|
||
| orange | #fcedb9 | #a82c00 | Wartet auf Kunde (`Awaiting`, `Halted`) |
|
||
| violett | #efeafe | #5b21b6 | Prüfung (`In Test`) |
|
||
| grün | #d7f7c2 | #006908 | Erledigt (`Closed`, `Zugesagt`) |
|
||
| plain | – | #5e6678 | Storniert (`Cancelled`, `Aborted`, `Abgesagt`) |
|
||
| rot | #ffe7f2 | #b3093c | kein Status: Badge „Dringend“ |
|
||
|
||
6. **Schrift.**
|
||
- Systemschrift `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif`. Lato und `src/assets/fonts` entfallen.
|
||
- Größen: h1 28/34 700 (Handy 24/30), Abschnitt-h2 16/24 600, Dialogtitel 18/26 600, Text 14/20, klein 13, sehr klein 12/16, Kennzahl 22/30 600.
|
||
- Gewicht 800 entfällt. Großbuchstaben mit Sperrung gibt es nur noch für die Wochentage im Planboard.
|
||
- Zahlen und Beträge nutzen `tabular-nums`.
|
||
7. **Breakpoints der WOMS-Seiten: 1100 px und 860 px.**
|
||
- Unter 1100 px werden Nebenspalten ausgeblendet (`.hide-md`).
|
||
- Unter 860 px:
|
||
- Das Menü wird zur Schublade mit Menü-Knopf.
|
||
- Seitenköpfe stapeln sich.
|
||
- Tabellen zeigen nur Kernspalten (`.hide-sm`).
|
||
- Dialoge erscheinen als Bottom-Sheet.
|
||
- Der Chat wechselt zwischen Liste und Verlauf (heute 720 px).
|
||
- Ausgenommen:
|
||
- die Container-Queries der Finanztabellen (560/640/860)
|
||
- die Breakpoints des fremden Chat-Composers (640/880)
|
||
8. **Lesbarkeit.**
|
||
- Text `#1a1f36`, Nebentext `#5e6678` (5,8:1).
|
||
- Tertiärtext `#8a93a3` (3,1:1) nur für Nicht-Essenzielles, nämlich Nummern, „–“ und leere Zustände. Inhalte und Status nutzen ihn nie.
|
||
- Fehlerrot `#c0123c`.
|
||
- Fokus ist immer sichtbar:
|
||
- Knöpfe und Links: 2 px durchgezogen `#111111` mit 2 px Abstand.
|
||
- Felder: 1 px `#111111` plus Ring `rgba(17,17,17,.14)`.
|
||
- Deaktiviert: Deckkraft .55, Cursor `not-allowed`. Schreibgeschützte Felder bekommen den Hintergrund `#f8f9fb`.
|
||
|
||
## Design-Tokens
|
||
|
||
Die Tokens stehen in `:root` in `src/styles/global.css`. Quelle ist der Prototyp mit festem Akzent, ohne `html[data-accent]` und ohne Prototyp-Leiste.
|
||
|
||
```css
|
||
:root {
|
||
--bg: #ffffff; --bg-hover: #f6f8fa; --bg-subtle: #f8f9fb;
|
||
--text: #1a1f36; --text-2: #5e6678; --text-3: #8a93a3;
|
||
--line: #ebeef1; --line-strong: #d8dde3;
|
||
--accent: #111111; --accent-hover: #2b2b2b; --accent-ink: #111111;
|
||
--accent-soft: rgba(17,17,17,.06); --accent-ring: rgba(17,17,17,.14);
|
||
--grey-bg: #ebeef1; --grey-fg: #414552; --blue-bg: #e0f2fe; --blue-fg: #075985;
|
||
--orange-bg: #fcedb9; --orange-fg: #a82c00; --violet-bg: #efeafe; --violet-fg: #5b21b6;
|
||
--green-bg: #d7f7c2; --green-fg: #006908; --red-bg: #ffe7f2; --red-fg: #b3093c;
|
||
--danger: #c0123c; --danger-ring: rgba(192,18,60,.18);
|
||
--radius: 6px; --radius-lg: 12px;
|
||
--ring: 0 0 0 1px rgba(60,66,87,.16), 0 1px 1px rgba(0,0,0,.06);
|
||
--shadow-btn: 0 1px 1px rgba(0,0,0,.12), 0 2px 5px rgba(60,66,87,.08);
|
||
--shadow-pop: 0 0 0 1px rgba(60,66,87,.06), 0 12px 32px rgba(26,31,54,.14), 0 2px 6px rgba(26,31,54,.06);
|
||
--overlay: rgba(26,31,54,.32); --scrim: rgba(26,31,54,.24);
|
||
--nav-dot: #e5a000;
|
||
--appt-akquise: #fdf4e3; --appt-akquise-hover: #fbeccd; --appt-hover: #eef1f4;
|
||
--sidebar-w: 232px;
|
||
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||
}
|
||
html { color-scheme: light; }
|
||
```
|
||
|
||
**Alte Token-Namen bleiben bis P10 als Aliase.** Fremd-CSS und JSX benutzen sie noch, so wird Bestandscode sofort weiß.
|
||
|
||
| Alter Name | Neuer Wert |
|
||
|---|---|
|
||
| `--surface-1`, `--surface-3`, `--surface`, `--dark-bg`, `--dark-card-bg` | `--bg` |
|
||
| `--surface-2`, `--surface-hover` | `--bg-hover` |
|
||
| `--border`, `--gray-700`, `--admin-line` | `--line` |
|
||
| `--border-strong`, `--admin-line-strong` | `--line-strong` |
|
||
| `--border-accent` | `--accent-ring` |
|
||
| `--text-muted`, `--text-faint`, `--gray-800` | `--text-2` (Altcode setzt `--text-faint` auch für Inhalte ein) |
|
||
| `--dark-text`, `--gray-900` | `--text` |
|
||
| `--accent-2`, `--green-secondary` | `--accent-hover` |
|
||
| `--green-primary` | `--accent` |
|
||
| `--info` | `--blue-fg` |
|
||
| `--warn` | `--orange-fg` |
|
||
| `--ok` | `--green-fg` |
|
||
| `--r-sm` | `--radius` |
|
||
| `--r-md`, `--r-lg` | `--radius-lg` |
|
||
| `--r-pill` | 4px |
|
||
| `--shadow-1` | `none` |
|
||
| `--shadow-2` | `--shadow-btn` |
|
||
| `--shadow-3` | `--shadow-pop` |
|
||
| `--sp-1…6` | bleiben unverändert |
|
||
| `--sidebar-w-collapsed` | entfällt |
|
||
|
||
**Diagrammfarben.** Canvas kann kein `var()` lesen. Deshalb stehen die Farben der Diagramme (uPlot) als Konstanten in der neuen Datei `src/components/serverpuls/chartColors.js`. Sie wird schon in P1 angelegt und von `Chart.jsx` und `ServerpulsPage` genutzt.
|
||
|
||
| Element | Farbe |
|
||
|---|---|
|
||
| Linie | `#111111` |
|
||
| Füllung | `rgba(17,17,17,.06)` |
|
||
| Spitze | `#8a93a3`, gestrichelt |
|
||
| Achsen | `#5e6678` |
|
||
| Raster | `#ebeef1`, nur horizontal |
|
||
| Ticks | `#d8dde3` |
|
||
|
||
## Bausteine
|
||
|
||
Die Klassennamen folgen dem Prototyp (`styles.css`). Wo eine Komponente existiert, behält sie ihre Schnittstelle, außer wo unten anders vermerkt. Es ändern sich Markup und Klassen.
|
||
|
||
| Baustein | Umsetzung |
|
||
|---|---|
|
||
| **App-Gerüst** | `.shell` mit Menü `<aside class="sidebar">` (fest, 232 px) und `.main`. In `.main` liegen `.mobile-bar` und `<main id="inhalt">` mit den Routen, ohne eigene Klasse `.page`. Jede Seite behält ihre Wurzel mit Klasse `page`, die den Prototyp-Look bekommt (max. 1160 px, Abstand 36/48/80). `ProjectsPage` wechselt in P1 von `main-content` auf `page`. `AdminPage` wechselt in P1 von `<main className="page admin-page">` auf `<div className="page admin-page">`, die maximale Breite 1480 px von `.admin-page` bleibt. Gescrollt wird das Dokument, ein innerer Scroll-Container entfällt. Der Footer entfällt, sein Text steht künftig im Login-Fuß. Unter 860 px ist das Menü eine Schublade mit `.scrim`. Sie schließt bei Klick daneben, bei Esc (Listener nur aktiv, solange sie offen ist) und bei Wechsel des Pfads. Die Seite springt nur bei Wechsel von `location.pathname` nach oben, nie bei Änderungen am Suchteil der URL. |
|
||
| **Menü** | Reine Textliste `.nav-item` mit Unterpunkten `.sub` und Zählern `.nav-count`. Chat-Ungelesene bekommen `.hot` (neues Feld `hot` am Chat-Eintrag in `navLinks.js`). „Leads“ erscheint grau mit „pausiert“. Serverpuls-Punkt: `.nav-dot` gelb bei `medium`, `.nav-dot.critical` rot bei `critical`. `data-testid="sp-nav-dot"`, Titel, 5-Minuten-Abfrage und `useSidebarCounts` bleiben. Fuß `.sidebar-foot`: Name (gekürzt mit Ellipsis) und „Abmelden“. Das Einklappen entfällt samt `lib/sidebar.js` und Test. Reihenfolge, Texte und Ziele in `navLinks.js` bleiben. |
|
||
| **Logo** | Neue `components/Logo.jsx` mit dem SVG-Zeichen aus dem Prototyp, 24 px im Menü und in der Mobil-Leiste, 40 px im Login. Dazu `public/favicon.svg` und `<link rel="icon">` in `index.html`. `<title>` bleibt. |
|
||
| **Seitenkopf** | `PageHeader` rendert `.page-head` mit h1, `.sub` und `.actions`. Neue optionale Props `eyebrow` und `crumb`. Brotkrumen `.crumb`, z. B. „Kunden / Name“. |
|
||
| **Abschnitt** | `Card` rendert `<section>` mit `<h2 class="section">`: Titel links, Aktion rechts, ohne Hintergrund, Rand, Schatten und Innenabstand. `pad`, `bodyStyle` und `style` wirken nicht mehr. `className` bleibt, Finanzen braucht es für Container-Queries. |
|
||
| **Knöpfe** | `Button`: Standard und `ghost` werden `.btn` (weiß mit Ring), `primary` wird `.btn.btn-primary` (schwarz). Neu ist `quiet` als `.btn.btn-quiet`. `danger` wird `.link-danger` (roter Textlink). Die Prop `size` wird angenommen, wirkt aber nicht: Alle Knöpfe sind 32 px hoch, nur der Login-Knopf 36 px. Zeilenaktionen in Tabellen sind Textlinks `.link`. Während einer Anfrage sind Knöpfe `disabled` und zeigen Text („Speichert …“), keinen Spinner. |
|
||
| **Links** | `a.link` mit feinem Unterstrich (`--line-strong`, beim Überfahren `currentColor`). Basisregel `a:not([class])` mit demselben Unterstrich, damit auch klassenlose Links (Rechtstext-Dateien, AVV-Link der Analyse-Zustimmung u. a.) erkennbar bleiben. Neuer Code vergibt immer eine Klasse. |
|
||
| **Badge** | `<span class="badge {ton}">`. Die Ton-Namen der Logik (`muted, info, ok, warn, danger`) bleiben, Tests pinnen sie. `Badge.jsx` übersetzt: muted → grau, info → blau, ok → grün, warn → orange, danger → rot. Neu sind `violet` und `plain`. Die Prop `dot` entfällt. |
|
||
| **Ticketstatus** | Neue Funktion `statusTone(status)` in `lib/ticketStatus.js` mit den Rückgaben `'' \| 'blue' \| 'orange' \| 'violet' \| 'green' \| 'plain'` nach der Tabelle oben. Leer, `undefined` und unbekannt ergeben `''`. Der Test deckt jeden gespeicherten Wert ab. `StatusPill` rendert `badge {ton}` direkt, ohne das Mapping in `Badge.jsx`. `UrgentPill` rendert „Dringend“ in Rot. `statusClass` entfällt mit P2. |
|
||
| **Reiter** | `Tabs` rendert `.tabs` und `.tab` mit 2-px-Unterstrich und optionalem Zähler `<span class="count">`. Auf dem Handy scrollen die Reiter waagerecht. Zähler werden beim Rendern ergänzt, nie in `lib/customerTabs.js`. |
|
||
| **Umschalter** | `.seg`: graue Schiene, der aktive Teil ist weiß mit Ring, dazu `aria-pressed`. |
|
||
| **Tabelle** | `.table` mit Kopf 12 px/500 und Haarlinien. `tr.row` ist klickbar und wird beim Überfahren `--bg-subtle`. Weitere Klassen: `.cell-title`, `.cell-sub`, `.id`, `.right`, `.num`, `.table-foot`, `.hide-md`, `.hide-sm`. Die erste Zelle enthält immer ein echtes Bedienelement für Tastatur und Mittelklick: einen Link oder, wenn die Zeile einen Dialog öffnet, einen `<button class="link">`. Klicks auf `a`, `button`, `select`, `input` und `label` in der Zeile lösen keine Zeilenaktion aus. |
|
||
| **Liste** | `.list-row` mit Haarlinie. Bei Link-Zeilen färbt sich beim Überfahren der Titel. |
|
||
| **Schlüsselwerte** | `.facts`/`.fact` für den Ticketkopf. `.stats`/`.stat` für Kennzahlen: `.v.warn` orange, neu `.v.neg` rot für negative Beträge. `.kv` für Beschriftung und Wert, mobil einspaltig. Dazu `.two-col` und `.prose` (Fließtext mit Zeilenumbrüchen). Neue Komponente `Stat` mit Prop `tone`, ohne Icon und ohne Farbstreifen. `Kpi` bleibt bis P7 als Export erhalten und rendert `Stat`: `accent` = `var(--danger)` wird `neg`, alle anderen `accent` werden ignoriert. |
|
||
| **Formular** | `.field > label` (500) mit `.input`, `.select` oder `.textarea` (32 px, Ring statt Rand, zweistufiger Fokus) und `.hint`. Mehrspaltige Formulare nutzen `.field-row` und `.field-row-3`, Kästchen `.check`. Fehler zeigt ein roter Ring (`--danger`, `--danger-ring`), auch bei `[aria-invalid="true"]`. `Field`, `FormField`, `DateTimeInput`, `DurationSelect` und `AutocompleteInput` übernehmen die Klassen. Die Vorschlagsliste ist ein weißes Popover mit `--shadow-pop`. |
|
||
| **Dialog** | Neue `ui/Dialog.jsx` mit den Props `title`, `onClose`, `busy`, `wide`, `footer`. Aufbau: `.overlay` (`--overlay`) mit `.dialog` (weiß, Radius 12, `--shadow-pop`, 520 px, mit `wide` 760 px). Der Kopf enthält h3 und den Knopf „ד (`aria-label="Schließen"`), danach folgen `.dialog-body` (scrollt) und `.dialog-foot`. Attribute: `role="dialog"`, `aria-modal`, `aria-labelledby`. Den Fokus regelt `useDialogFocus`. Unter 860 px erscheint der Dialog als Bottom-Sheet. **Schließen geht nur über × und Esc.** Ein Klick auf den Hintergrund schließt nicht, damit lange Eingaben nicht verloren gehen (Abweichung vom Prototyp). Esc greift nur, wenn `!e.defaultPrevented`, so schließt eine offene Vorschlagsliste zuerst sich selbst. Solange `busy` gilt, wirken × und Esc nicht, und × ist `disabled`. Beide Wege rufen genau das `onClose` des Aufrufers. `AssignDialog` übergibt sein `close()` samt `refresh`. Die eigenen Esc-Listener in `InvoiceCreateModal` und `AssignDialog` entfallen. `lockRef` und `busyRef` bleiben als Doppelklick-Sperre. Die native `required`-Prüfung bleibt. |
|
||
| **Leerer Zustand** | `<div class="empty"><strong>Titel</strong>Hinweis</div>` mit optionaler Aktion. Die Prop `icon` entfällt. |
|
||
| **Meldungen** | Ohne Kasten: `.msg.err` (rot, `role="alert"`), `.msg.ok` (grün), `.msg.warn` (orange), alle 13 px. Flüchtige Bestätigungen dürfen `.toast` nutzen. |
|
||
| **Laden** | `.loading` mit Text „Wird geladen …“. `AdminRoute` behält wörtlich `<p>Wird geladen …</p>` ohne Klasse, weil ein Test darauf prüft. Der Seitenabstand kommt dort vom Elternelement. |
|
||
| **Dringend-Schalter** | `UrgentToggle` rendert den kleinen Schalter `.sw` mit dem Wort „Dringend“. Eingeschaltet ist er rot. `aria-pressed` und die Logik bleiben. |
|
||
| **Login** | Aufbau: `.login` > `.login-box` (360 px), Logo, h1 „Anmelden“, `.sub` „Ticketsystem für das WEBklar-Team“, Felder „E-Mail“ und „Passwort“ mit `autocomplete`, Knopf „Weiter“. Danach die Schritte „Code senden“ und „Code eingeben“ (h1 „Code eingeben“, `.code-input`, Links „Code erneut senden“ · „Abbrechen“). Die erklärenden Texte der Code-Schritte bleiben wörtlich wie heute: „Wir schicken dir einen Code an deine E-Mail-Adresse.“, „Wir haben dir einen Code an {E-Mail} geschickt.“, „Code aus der E-Mail“, „Neuer Code ist unterwegs.“. Die Prototyp-Hinweise zum Testbetrieb kommen nicht ins echte System. `.login-foot` zeigt „Webklar Ticket-Plattform · © {Jahr}“. Die Logik (`login`, `sendCode`, `verifyCode`, `cancelMfa`, `NO_STAFF_ACCESS`, `safeReturnPath`) bleibt unverändert. `LetterGlitch.jsx` entfällt. |
|
||
|
||
### Übergangsschicht KOMPAT (P1 bis P10)
|
||
|
||
Ein markierter Block „KOMPAT“ in `global.css` bildet alte Klassen auf den neuen Look ab. So sind noch nicht umgebaute Seiten weiß, kastenlos und lesbar. Er gilt von P1 bis P10.
|
||
|
||
| Alte Klasse | Darstellung im neuen Look |
|
||
|---|---|
|
||
| `.ui-card*` | Abschnitt ohne Kasten, `.ui-card-head h3` wie `h2.section` |
|
||
| `.ui-btn*` | wie `.btn` / `.btn-primary` / `.link-danger` |
|
||
| `.form-group`, `.form-label`, `.form-control` | wie `.field`, Label, `.input` / `.select` / `.textarea` |
|
||
| `.badge-*`, `.status-*`, `.priority-*` | Badge-Töne |
|
||
| `.overlay` (Altform) | behält `overflow-y: auto`, `align-items: flex-start` und 24/16 px Abstand, Hintergrund `--overlay` |
|
||
| `.overlay-content` | weiße Box: `width: 100%`, `max-width: 520px` (Inline-`maxWidth` der Aufrufer wirkt weiter, `.fin-dialog` 760 px), `margin: 0 auto`, Radius 12, `--shadow-pop`, Abstand 24, Schrift `--text` |
|
||
| `.overlay-close` | fest oben rechts im Fenster, 32×32 px, weißer runder Knopf mit „ד in `--text-2` |
|
||
| `.seg button.active` | wie `.seg button.on` |
|
||
| `.bg-red` (mit oder ohne `.text-white`) | kein Hintergrund, Schrift `--red-fg`, 13 px |
|
||
| `.text-white` allein | keine Regel |
|
||
| `.text-grey` | `--text-2` |
|
||
| `.text-red` | `--red-fg` |
|
||
| `.side-count` | kleine graue Zahl, bis P6 (Chat-Liste) |
|
||
| `.side-logo` | Initiale ohne Verlauf, bis P4 (Kundenliste) |
|
||
| `.btn-dark`, `.btn-teal` | wie `.btn-primary` |
|
||
| `.card*`, `.row`, `.col*`, `.dropdown*` | neutral |
|
||
| `.spinner` | neutraler grauer Ring |
|
||
| `.page .page`, `.page .main-content` | Abstand und Breite neutralisiert, falls eine Seite verschachtelt |
|
||
| Links in Bestandsklassen, die bisher `--accent` als Farbe nutzten (`.fin-link`, `.legal-files a`, `.credit-cols a`, `.area-rows a`) | feiner Unterstrich |
|
||
|
||
Alle `!important`-Regeln des Altbestands werden gelöscht, nicht überschrieben. Tote Klassen löscht P1 nur nach einer Liste aus einem Abgleich-Skript:
|
||
- Das Skript vergleicht alle Klassen in `global.css` mit Klassen-Literalen in JSX, mit Template-Präfixen (z. B. `fin-fixed-${…}`, `legal-tone-${…}`) und mit Zeichenketten in `lib/*.js`.
|
||
- Gelöscht wird erst nach Gegenprobe mit `rg -w <name> src --glob '!*.css'`.
|
||
- Dynamisch gebaute Klassen gelten nicht als tot. Beispiele: `fin-neg`, `fin-fixed-off`, `legal-tone-*`, `legal-version-*`, `badge-*`, `status-*`, `ticket-tab-active`, `ui-btn-*`.
|
||
- Die Liste steht im Plan.
|
||
|
||
P10 entfernt den KOMPAT-Block.
|
||
|
||
## Ticket als eigene Seite
|
||
|
||
### Route und Links
|
||
|
||
- Route `/tickets/:ref/:tab?`.
|
||
- Sie ist wie alle anderen eine `ProtectedRoute` und steht vor dem Fallback `*`.
|
||
- `tab` ist `details` (Standard) oder `arbeitsblaetter`. Ein unbekannter `tab` leitet mit `replace` auf Details um.
|
||
- Das Menü markiert „Tickets“ auch auf Unterseiten.
|
||
- Neue `lib/ticketRoutes.js` mit `ticketPath(ref, tab)` und Test.
|
||
- `ref` ist `ticket.woid || ticket.$id`. Reine woid-Aufrufer übergeben die woid.
|
||
- Rückgabe ist `/tickets/` + `encodeURIComponent(ref)`, bei `arbeitsblaetter` mit `/arbeitsblaetter`.
|
||
- Ein leeres `ref` ergibt `/tickets`.
|
||
- Die angezeigte Kurznummer `$id.slice(-5)` ist nie Linkziel.
|
||
- Bestehende Links auf `/tickets?woid=N` gibt es heute an sechs Stellen. Alle nutzen künftig `ticketPath`:
|
||
- `PlanboardPage`
|
||
- `AssignProjectPage` (2×)
|
||
- `ServicesPage`
|
||
- `ProjectContextPanel`
|
||
- `CustomerFunktionen`
|
||
- Neue Links:
|
||
- Zeilen der Ticketliste (P2)
|
||
- Tickets-Reiter der Kundenseite (P4, heute ohne Link)
|
||
- Ticket-Spalte der Projekte (P5, `project.ticketId` ist eine `$id`)
|
||
- Alte Links `/tickets?woid=N` leiten mit `replace` auf `/tickets/N` um.
|
||
- Das bleibt dauerhaft, auch nach P10, weil Portal-Mails so verlinken.
|
||
- Die Umleitung liegt innerhalb der geschützten Route, damit `safeReturnPath` samt Suchteil nach dem Login greift.
|
||
|
||
### Laden
|
||
|
||
Neuer Hook `useWorkorder(ref)`:
|
||
1. Zuerst sucht er per `Query.equal('woid', [String(ref)])`, `Query.orderDesc('$createdAt')`, `Query.limit(1)`.
|
||
2. Findet er nichts, holt er das Ticket per `getDocument(ref)` als `$id`.
|
||
3. 400 und 404 gelten beide als „Ticket nicht gefunden“.
|
||
|
||
Der Hook liefert dasselbe Schreib-API wie `useWorkorders` (`updateWorkorder`). Er kennt die Zustände Laden, Fehler und „Ticket nicht gefunden“.
|
||
|
||
Arbeitsblätter und `CreateWorksheetModal` nutzen immer `ticket.woid` des geladenen Tickets, nie den URL-Parameter. Für Tickets ohne woid zeigt der Reiter Arbeitsblätter den Hinweis „Für dieses Ticket gibt es keine Ticketnummer, Arbeitsblätter sind nicht möglich.“ statt eines Fehlers. `worksheets.woid` ist Pflicht.
|
||
|
||
### Seite `pages/TicketPage.jsx`
|
||
|
||
- **Kopf:**
|
||
- Brotkrumen „Tickets / Nummer“. Der Link „Tickets“ führt mit dem Suchteil zurück, den die Liste per Link-State `listSearch` mitgibt. Ohne State führt er auf `/tickets`.
|
||
- Eyebrow „Nummer · Art“. Die Art kommt aus `ticketTypeLabel`, „Webpage“ bleibt „Webpage“.
|
||
- h1 mit dem Betreff (Rückfall „Kein Betreff“), `.sub` mit dem Kunden als Link und dem Ort.
|
||
- Aktionen: Dringend-Schalter, Status-Auswahl und der schwarze Knopf „Arbeitsblatt hinzufügen“. Der Knopf erscheint für **alle** Ticketarten, auch Akquise, wie heute. Der Prototyp blendet ihn bei Akquise aus, das wird nicht übernommen.
|
||
- **`.facts`:**
|
||
- Zuständig als Auswahl
|
||
- Termin: `startDate` mit `startTime`–`endTime`, ohne Rückfall auf das Erstelldatum, leer „Kein Termin“
|
||
- Fällig: `deadline`, leer „–“
|
||
- Einsatz: `serviceTypeLabel`
|
||
- Angefragt von, leer „–“
|
||
- Erstellt: `formatDistanceToNow`, deutsch, wie heute
|
||
- **Datumsfelder sind Freitext.**
|
||
- Angezeigt wird über `parseGermanDate` und `formatTime` aus `lib/planboard.js`.
|
||
- Ist ein Wert nicht lesbar, steht der Rohtext da.
|
||
- Nie wird `new Date()` oder `format()` direkt auf den gespeicherten Text angewendet.
|
||
- **Reiter:** „Details“ und „Arbeitsblätter (n)“.
|
||
- Details zeigt die Beschreibung als `.prose`, den Projekt-Abschnitt (`TicketProjects`, optisch bis P5 unverändert) und „Weitere Tickets von {Kunde}“ (`ProjectContextPanel`, Sichtbarkeitsregel wie heute).
|
||
- Arbeitsblätter zeigt `.stats` und die Arbeitsblatt-Zeilen.
|
||
- **Status-Auswahl** (natives `<select>`):
|
||
- Sie bietet die sechs Werte aus `TICKET_STATUSES` an, für Akquise ebenfalls.
|
||
- Ist der aktuelle Wert nicht dabei (Akquise- oder Altwert), steht er zusätzlich als gewählte Option darin.
|
||
- Geschrieben wird nur `{ status }`.
|
||
- **Zuständig** (natives `<select>`):
|
||
- Optionen: „Nicht zugewiesen“ und die Mitarbeiter mit vollem Namen.
|
||
- Ist `assignedTo` nicht in der Mitarbeiterliste, steht zusätzlich eine gewählte Option mit `assignedName`, sonst „Unbekannte Person“.
|
||
- Solange die Liste lädt, ist die Auswahl deaktiviert.
|
||
- Geschrieben wird nur `{ assignedTo }`, für niemanden ein leerer String.
|
||
- `useEmployees` läuft einmal pro Seite.
|
||
- **`TicketCard.jsx` entfällt.**
|
||
- Vorher wandert die ungetestete Logik aus `handleCreateWorksheet` in eine Lib-Funktion mit Test: Status zurückschreiben und Akquise-Ergebnis setzen.
|
||
- Testfälle: Akquise + Zugesagt → Kunde fest, Akquise + Abgesagt → verloren, Akquise + In Kontakt → keine Änderung, Nicht-Akquise → nie eine Änderung.
|
||
- `useWorksheets(ticket.woid)` und `useCustomerAbo` laufen einmal pro Seite.
|
||
- `aboHoursAllowed`, `onTogglePlan`, `canEditWorksheetTimes`, `canAddOwnSupport` und die drei Dialoge werden unverändert verdrahtet.
|
||
|
||
### Ticketliste `TicketsPage`
|
||
|
||
- **Reiter mit Zählern:** Aktiv · Offen · In Arbeit · Wartet auf Kunde · Prüfung · Erledigt · Storniert · Alle.
|
||
- Aktiv = `DEFAULT_STATUS_GROUPS` (offen, in Arbeit, wartend, Prüfung).
|
||
- Die übrigen Reiter entsprechen je einer Gruppe aus `STATUS_GROUPS`.
|
||
- Alle bedeutet: kein Statusfilter.
|
||
- „Storniert“ ist neu gegenüber dem Prototyp, damit die heutige Auswahl „nur stornierte“ nicht verloren geht.
|
||
- Startzustand: Reiter Aktiv, Art Aufträge, „Nur dringende“ aus.
|
||
- **Art-Auswahl** (`.select-quiet`) mit allen sechs heutigen `TYPE_FILTERS`: Aufträge (Standard, ohne Akquise) · Webpage · Migration · Projekt · Akquise · „Aufträge und Akquise“ (heute „Alle“).
|
||
- **Kästchen** „Nur dringende“.
|
||
- **Suche:**
|
||
- Sie läuft im Browser über `ticketMatches`, wie heute.
|
||
- Sie startet 300 ms nach der letzten Eingabe oder per Enter.
|
||
- Der Knopf „Anwenden“ entfällt.
|
||
- **Veraltete Antworten:** `useWorkorders` darf als einzige Hook-Änderung veraltete Antworten verwerfen (Anfrage-Zähler). So kann eine ältere Antwort nie eine neuere überschreiben.
|
||
- **URL-Zustand** (`useSearchParams`):
|
||
- Reiter, Art, Dringend, Suche und `limit` (Anzahl geladener Tickets, keine Seiten) stehen in der URL.
|
||
- Geschrieben wird immer mit `replace`, Standardwerte werden nicht geschrieben.
|
||
- Ungültige Werte fallen auf den Standard zurück. `limit` wird auf 10 bis 500 begrenzt.
|
||
- Jede Filteränderung setzt `limit` auf 10 zurück.
|
||
- `?woid=` hat Vorrang und leitet um.
|
||
- Die Helfer stehen in neuer `lib/ticketListUrl.js` mit Test.
|
||
- **Nachladen:**
|
||
- Beim Nachladen bleiben die alten Zeilen sichtbar, im Fuß steht dezent „Wird geladen …“.
|
||
- Den leeren Ladezustand gibt es nur beim allerersten Laden.
|
||
- „Mehr laden“ bleibt (+10), ohne Suche wie heute.
|
||
- **Zähler:**
|
||
- Je Reiter läuft eine Abfrage mit `Query.limit(1)`, die `total` liest. Sie nutzt dieselbe Art und denselben Dringend-Filter wie die Liste, nie den Suchtext.
|
||
- Die Zähler laden beim Öffnen, bei Wechsel von Art oder Dringend und nach dem Anlegen eines Tickets, nicht bei Reiterwechsel oder Tastendruck.
|
||
- Bei aktiver Suche werden sie ausgeblendet, der Fuß zeigt dann die Trefferzahl.
|
||
- Schlägt eine Abfrage fehl, fehlt nur der Zähler.
|
||
- Eigener Hook, `useWorkorders` bleibt dafür unberührt.
|
||
- **Tabellenspalten:**
|
||
- **Ticket:** Nummer, Betreff, Badge „Dringend“, darunter Kunde · Ort.
|
||
- **Status.**
|
||
- **Zuständig:** „Vorname N.“ aus der Mitarbeiterliste, Rückfall `assignedName`, sonst „Unbekannt“. Ohne Zuweisung steht „Niemand“ in Grau.
|
||
- **Termin:** wie in den Facts, Fehlwert „–“.
|
||
- **Zeit:** siehe unten.
|
||
- **Erstellt.**
|
||
- **Spalte „Zeit“:**
|
||
- Wert ist die Summe von `worksheetMinutes` über alle Arbeitsblätter ohne Kommentar. Das ist dieselbe Regel wie „Arbeitszeit gesamt“ auf der Ticketseite. Formatiert wird mit `stepLabel`.
|
||
- Geladen wird für die geladenen Zeilen in Blöcken von höchstens 100 woids: `Query.equal('woid', block)`, `Query.select([...])`, `Query.limit(5000)`, höchstens 3 Blöcke gleichzeitig.
|
||
- Das Ergebnis wird je woid zwischengespeichert, „Mehr laden“ holt nur neue Zeilen.
|
||
- Die Zeit wird höchstens für die ersten 200 Zeilen berechnet. Sind mehr geladen, nennt der Fuß das.
|
||
- Ein Block mit 5000 Treffern könnte abgeschnitten sein. Er und ein Fehler zeigen „–“, nie eine falsche Zahl. Die Liste bleibt bedienbar.
|
||
- Tickets ohne woid zeigen „–“.
|
||
- Die Summenfunktion bekommt einen Test.
|
||
- **Sortierung:** Sie bleibt `$createdAt` absteigend, der Fuß sagt „Neueste zuerst“.
|
||
- **Navigation:**
|
||
- Die Zeilen verlinken mit `state={{ listSearch: location.search }}`.
|
||
- Nach „Neues Ticket“ springt die App auf die neue Ticket-Seite (`createWorkorder` liefert die woid).
|
||
|
||
### Arbeitsblätter (`WorksheetList` neu)
|
||
|
||
- **Aufbau:** flache Zeilen `.ws` in drei Bereichen:
|
||
- links Datum und Uhrzeit
|
||
- in der Mitte Person, Einsatz (bei Kommentaren das Wort „Kommentar“), Nummer, Badges, Text, Unterstützung, „Status danach: X“ und Aktionen
|
||
- rechts die Zeit
|
||
- Es gibt kein Aufklappen, keine Verläufe und keine Animation.
|
||
- Kommentare stehen in Nebentextfarbe.
|
||
- **Git-Push:** ohne Zeit Badge „Zeit offen“ (orange), mit Zeit „Zeit eingetragen“ (grün).
|
||
- **Abo:** Der Umschalter heißt „Abo“ beziehungsweise „+ Abo“. Seine Sichtbarkeitsregel bleibt exakt: `onTogglePlan && !isComment && totalTime > 0 && (aboHoursAllowed || countsToPlan)`.
|
||
- **Bedingungen, die bleiben:** `canEditTimes`, `canAddSupport`, `isGit` und `needsTimeEntry`.
|
||
- **Texte, die bleiben:** „Zeit nachtragen“, „Zeiten bearbeiten“, „Ich habe unterstützt“ und „Meine Unterstützung bearbeiten“. Letzteres erscheint, sobald man schon unterstützt. `OwnSupportModal` behält „Eintrag entfernen“ und `maxLength` 200.
|
||
- **Reihenfolge:** neueste zuerst.
|
||
- **Zeitformat:** überall `stepLabel` („1 h 30 min“, heute „1h 30min“).
|
||
- **Kennzahlen:**
|
||
- Arbeitszeit gesamt = Summe `worksheetMinutes` ohne Kommentare
|
||
- davon Abo-Stunden = dieselbe Summe über `countsToPlan && !isComment`
|
||
- Zeiten offen = Anzahl `needsTimeEntry`, orange bei mehr als 0
|
||
|
||
## Bewusste Änderungen am Verhalten
|
||
|
||
| Bisher | Künftig |
|
||
|---|---|
|
||
| Ticket klappt in der Liste auf | eigene Seite mit Link |
|
||
| Statusfilter als Mehrfachauswahl mit „Anwenden“ | Reiter (Einfachwahl) |
|
||
| Suche nach Klick auf „Anwenden“ | Suche nach Tipp-Pause |
|
||
| Footer auf jeder Seite | Footer entfällt, Text steht im Login-Fuß |
|
||
| Menü einklappbar | nicht mehr einklappbar, auf dem Handy eine Schublade |
|
||
| Dialoge schließen nur mit × | Dialoge schließen mit × und neu mit Esc |
|
||
| Statusfarben: Offen grün, Erledigt grau | Farben wie in der Tabelle oben. Das Team wird beim Live-Gang von P1 informiert. |
|
||
| Kein Link auf die Tickets einer Kundenseite oder in der Projektliste | beide verlinkt |
|
||
|
||
Alle anderen Funktionen bleiben erhalten.
|
||
|
||
## Pakete
|
||
|
||
### Arbeitsweise
|
||
|
||
- **Branch:** Gearbeitet wird im Worktree `dev-design` auf `feature/design-weiss`, abgezweigt von `test` 603d08c.
|
||
- **Commits:** Jedes Paket ist ein eigener Commit-Stapel. `git add` nur mit ausdrücklichen Pfaden.
|
||
- **Rebase:** Vor jedem Paket und unmittelbar vor jeder Abnahme läuft `git fetch origin && git rebase origin/test`, kein Merge.
|
||
- **Tag:** Erst danach wird das Tag `design-p<N>` auf den abgenommenen Stand gesetzt und nicht mehr bewegt. Korrekturen nach dem Push sind Folge-Commits mit eigenem Tag `design-p<N>-fix<k>`.
|
||
- **Live-Gang:** Live geht ein Paket nur über das Tag, nie über die Branch-Spitze. Der Eigentümer führt aus:
|
||
`git -C /home/webklar/apps/ticket.webklar.com/dev-design push origin 'design-p<N>^{commit}:refs/heads/test'`
|
||
Das ist ein reiner Fast-Forward. Seit 2026-09-30 deployt nur `test`. Vor dem Push nennt der Agent die genaue Zeile, den SHA und `git log --oneline origin/test..design-p<N>`.
|
||
- **Nach dem Push:**
|
||
- Der Agent prüft im Log von `webklar-ticket-build` die Zeile „Deployment completed successfully“.
|
||
- Er vergleicht den Asset-Hash von `https://ticket.webklar.com/` mit `dist/index.html`.
|
||
- Er macht einen kurzen Sichttest im Chrome des Eigentümers, nur lesend.
|
||
- **Zurücknehmen:** Per Revert-Commit, immer das jüngste Paket. Ein früheres Paket geht nur zusammen mit allen späteren: `git revert --no-commit design-p<N-1>..design-p<N>`.
|
||
- **Reihenfolge:**
|
||
- Live gehen die Pakete in der Reihenfolge P1 bis P10.
|
||
- P3 bis P5 brauchen `ticketPath` aus P2.
|
||
- P4 liegt vor P5, weil P4 den Aufruf von `CreateCustomerForm` in `AssignProjectPage` umstellt.
|
||
- P7, P8 und P9 hängen fachlich nicht voneinander ab, gehen aber auf demselben Branch in dieser Reihenfolge live.
|
||
- **Parallelarbeit:** Am nächsten Paket wird erst nach dem Tag des vorigen gearbeitet. Die Commits liegen hinter dem Tag und gehen deshalb nicht mit live.
|
||
|
||
### Inhalt
|
||
|
||
| Paket | Umfang | Danach sichtbar |
|
||
|---|---|---|
|
||
| **P0** Vorbereitung | `LeadsPage.jsx` und `hooks/useLeads.js` löschen. Beide sind toter Code. Die `/leads`-Umleitung, der pausierte Menüpunkt und `lib/leads.js` bleiben. `node_modules` wird aus Git genommen (`git rm -r --cached node_modules`; `.gitignore` führt es schon). Der Deploy installiert ohnehin neu. Im Worktree folgt ein echtes `npm ci` unter `systemd-run` mit Speichergrenze, ohne Symlink. Danach muss `git status --short` ohne `??`-Zeilen leer sein. Das Screenshot-Werkzeug entsteht außerhalb des Repos (siehe Abnahme), und es werden Vorher-Bilder erzeugt. Fertig, wenn `npm test` und `npm run build` im Worktree grün laufen und ein Befehl alle Vorher-Bilder erzeugt. | nichts |
|
||
| **P1** Fundament | Tokens, Aliase und KOMPAT, Basis, Gerüst, Menü, Logo, Favicon. Alle `ui/*`-Bausteine samt `Dialog` und `Stat`, außerdem `statusTone` und der neue Login. Löschen: Lato, `Footer.jsx`, `LetterGlitch.jsx`, `lib/sidebar.js` samt Test und tote CSS-Regeln laut Skript-Liste. Pflicht-Fixes gegen Unlesbares: Chat-Variablen in `chat.css` unter `.woms-chat` (`--chat-on-accent: #fff`, `--chat-line: var(--line-strong)`, `--chat-muted: var(--text-2)`, Fehler- und Hinweistexte `--red-fg`/`--text-2`). `.admin-primary-button` bekommt weiße Schrift, Hover `--accent-hover`. Farbliterale umstellen in `AssignedProjectCard`, `PreviewBlock`, `PreviewLinkButton`, `GiteaLinkButton`, `AnalyseRow`, `CreateWorksheetModal` und `WorksheetTimesModal` (`#a0aec0`) sowie die helle Warn- und Fehlerschrift in `admin-areas.css`, `serverpuls.css`, `AdminPage.css` und im Finanz- und Rechtlich-Teil von `global.css`. `chartColors.js` wird angelegt und in `Chart.jsx` und `ServerpulsPage` benutzt. Abschluss-Prüfung: `rg` über alle Farbliterale in `src`. Jedes Literal mit Kontrast unter 3:1 auf Weiß, jede dunkle Schrift auf `--accent` und jedes Weiß-Alpha ist entweder behoben oder im Plan mit Zielpaket als bewusst offen eingetragen. | ganze App weiß, Menü als Text, neues Login, alle Dialoge weiß; Innenlayouts noch alt, aber lesbar |
|
||
| **P2** Tickets | Ticketliste, Ticket-Seite, Route, `ticketRoutes.js`, `ticketListUrl.js` und Arbeitsblätter. Dialoge `CreateTicketModal`, `CreateWorksheetModal`, `WorksheetTimesModal`, `OwnSupportModal`; `SupportersEditor` als Feldblock in den Arbeitsblatt-Dialogen. Native Auswahlfelder statt `StatusDropdown`/`EditorDropdown`, dazu der Dringend-Schalter und „Weitere Tickets“. Die sechs Link-Stellen wechseln auf `ticketPath`. Altklassen `.tcard*`, `.dropdown*`, `.ticket-tab*`, `.worksheet-summary` und `.chevron` löschen. „Neues Ticket“ bleibt inhaltlich wie heute, also automatische Zuweisung und kein Einsatzfeld. | Ticket-Arbeitsplatz wie im Prototyp |
|
||
| **P3** Planboard | `.week` mit sieben Spalten, Haarlinien und Wochenende `--bg-subtle`. Heute als schwarze Datumspille, Termine als Links auf `ticketPath`. Akquise-Termine hell orange, stornierte bleiben sichtbar wie heute. Kopf: „Kalenderwoche N · Datum bis Datum · n Termine“, Aktionen ← · Heute · →. Unter 860 px eine Spalte. | Planboard ohne Karten |
|
||
| **P4** Kunden | Kundenliste mit Reitern Feste Kunden · Leads · Archiviert · Alle. Die Zähler nutzen dieselbe Filterfunktion wie die Liste (`lib/leads.js`). Dazu Suche und Tabelle mit Unternehmen und Ansprechpartner, Kundennummer, Standort, E-Mail, Telefon. Badges „Lead“, „Abgesagt“, „Archiviert“ neben dem Namen wie heute; „Wichtig“ als Badge statt Stern. Kopf: für Admins „Neuer Kunde“ schwarz und „Neuer Lead“ als Standardknopf, für andere nur „Neuer Lead“ als Standardknopf. `CreateCustomerForm` und `CreateLeadForm` werden Dialoge, Hinweis- und Knopftexte bleiben; der Aufruf in `AssignProjectPage` wird mit umgestellt. Kundenseite mit Brotkrumen, Eyebrow und Reitern; ihr Tickets-Reiter verlinkt die Tickets. Übersicht als `.two-col` mit `.kv`, Stammdaten bleiben direkt bearbeitbar. `CustomerFunktionen` als Tabelle, die weiter `abgeschaltetText` zeigt (kommt aus tp2, siehe Risiken). Außerdem `AboStatusCard`, `CustomerViewLog`, `KundenansichtButton` (Standardknopf) und Grundstil von `admin-areas.css`. | Kundenbereich wie im Prototyp |
|
||
| **P5** Projekte | `ProjectsPage` als eine Tabelle: Projekt + Repo, Kunde (Auswahl je Zeile wie heute, deaktiviert bei archiviert oder während der Zuordnung, Rückfrage beim Entfernen), Art, Adresse, Ticket als Link, Status, Zuletzt geändert = `$updatedAt`, Aktionen. Die Aktionen sind Textlinks, nur wenn zutreffend: „Vorschau öffnen“, „In Gitea öffnen“, „Hosten“ (mit Ladezustand), „Aktualisieren“. Die erste Zelle ist ein `<button class="link">` mit dem Projektnamen und öffnet „Projekt bearbeiten“. Auf dem Handy bleiben Projekt, Kunde, Status und Aktionen sichtbar. Die heutigen sechs Ansichten werden 1:1 Reiter: Aktive Projekte · Unzugeordnet · Zugeordnet · Vorlagen (nur Admins) · Archiviert · Alle inkl. Archiv. Dazu Suche, Auswahl Kunde und Mitarbeiter, „Weitere Treffer“ und Hinweiszeile. „Neues Projekt“ als Dialog mit Repo-Name. `PreviewBlock` steht außerhalb des Formulars, der Dialog ist `wide`. Die Liste zeigt „Wird geladen …“ nur beim ersten Laden. Außerdem: `AssignProjectPage` mit Schritten als Abschnitten und Auswahl auch als Text markiert; `ServicesPage`; `TicketProjects`; `AssignedProjectCard`; `AnalyseRow` (Zustimmung als Dialog, Wortlaut unverändert); `PreviewBlock`; `PreviewLinkButton` („Vorschau öffnen“); `GiteaLinkButton` („In Gitea öffnen“); `CopyableCredential` („Anzeigen“/„Verbergen“, „Kopieren“/„Kopiert“). | Projektbereich wie im Prototyp |
|
||
| **P6** Kunden-Unterseiten | Chat: Liste als Haarlinien-Zeilen. Alle Regeln, die Elemente im Verlauf und in der Eingabezeile gestalten, tragen das Präfix `.woms-chat`, bei Bedarf `.woms-chat.woms-chat`, nie `!important`. Das Seitenlayout (`.chat-page`, `.chat-side`, `.chat-list*`, `.chat-main`, `.chat-back`) bleibt ohne Präfix. Fremde Blase `--bg-hover`, eigene `--grey-bg`. `loadChatComposer.js`, sein Test und die Container in `ChatThread.jsx` bleiben unverändert. Gutschriften: Filter als Reiter, Tabelle mit Spaltenpriorität, Detail als `.two-col`, Knöpfe Genehmigen (schwarz), Rückfrage (Standard), Ablehnen (rot als Text). Abo-Angebote: Tabelle, Formular als Dialog. E-Mail-Speicher: Tabelle mit `data-storage-done`. | Chat und Kunden-Unterseiten hell |
|
||
| **P7** Finanzen | Finanz-CSS neu (ca. 320 Zeilen), alle Finanzkomponenten. `LiquidityHead` wechselt von `Kpi` auf `Stat`, der `Kpi`-Export wird gelöscht. Dialoge `AssignDialog` und `InvoiceCreateModal` (`wide`). Bleiben: die Anker `INVOICES_ANCHOR` und `finanzen-buchungen` samt `scroll-margin-top`, die Klassen-Hooks, `data-label`, `role`-Attribute und Container-Queries; `container-type` wandert auf die Wrapper. Beträge behalten ihr Vorzeichen, negative sind rot. | Finanzen ohne Karten |
|
||
| **P8** Rechtliches, Admin | `LegalPage`, `LegalTextsTab`, `ProviderContractsTab` als Tabelle, `ContractForm`/`VersionForm` als Dialoge. `AdminPage` mit Tabelle, Formular als Dialog und Textaktionen „Bearbeiten“/„Löschen“. Fehler erscheinen als `.msg.err` im Dialog, der erst bei Erfolg schließt. Die einmal angezeigten Zugangsdaten bleiben ein Abschnitt auf der Seite. `AdminPage.css` wird neu geschrieben; `.credential*`, `.sr-only` und `.spin` ziehen nach `global.css` um. | Rechtliches und Admin ohne Karten |
|
||
| **P9** Serverpuls | Kopf mit Text-Badge statt Emoji. Warnungen als Listenzeilen mit Stufen-Badge, Kacheln als `.stats` mit Sparkline. Deltas als Textfarbe (rot = gestiegen, grün = gesunken), Umschalter `.seg`. Alle `data-testid` bleiben. | Serverpuls hell |
|
||
| **P10** Aufräumen | `react-icons` und `@tabler/icons-react` deinstallieren, sobald `rg "react-icons\|@tabler" src` 0 Treffer liefert. KOMPAT-Block, Aliase und tote Klassen löschen. `previewEnv.dotColor` samt Test entfernen. Die Kommentare zu Icons in `navLinks.js` und im Test anpassen. Die Bundle-Größe vorher und nachher dokumentieren. Die `?woid=`-Umleitung bleibt. | nichts Neues, kleineres Bundle |
|
||
|
||
### Seiten ohne Prototyp-Vorlage
|
||
|
||
Gemeint sind P6 bis P9, `ServicesPage` und `AssignProjectPage`. Sie entstehen aus denselben Bausteinen nach festen Ersetzungen:
|
||
- Karte → Abschnitt
|
||
- Kachelraster → Tabelle oder Liste
|
||
- farbiger Streifen → Badge
|
||
- Icon-Knopf → Textknopf oder Textlink
|
||
|
||
Der Eigentümer nimmt diese Pakete anhand von Screenshots ab, bevor er pusht.
|
||
|
||
### Texte
|
||
|
||
Texte bleiben wörtlich erhalten. Ausnahmen sind nur diese neuen oder geänderten Texte:
|
||
|
||
**Login:** „Anmelden“, „E-Mail“, „Passwort“, „Weiter“, „Code eingeben“, „Code erneut senden“, „Abbrechen“ sowie der Untertitel „Ticketsystem für das WEBklar-Team“.
|
||
|
||
**Menü und Laden:** „Abmelden“, „Menü“, „Wird geladen …“ statt „Loading...“.
|
||
|
||
**Ticketliste:**
|
||
- Reiter und Art-Eintrag „Aufträge und Akquise“
|
||
- Fuß „Neueste zuerst“
|
||
- „Niemand“, „Unbekannt“
|
||
|
||
**Ticketseite:**
|
||
- „Nicht zugewiesen“ (statt „(Unassigned)“), „Unbekannte Person“
|
||
- „Kein Termin“
|
||
- „Weitere Tickets von {Kunde}“
|
||
- „Status danach: X“
|
||
- Hinweis für Tickets ohne Ticketnummer
|
||
- Zeitformat `stepLabel`
|
||
|
||
**Weitere Seiten:**
|
||
- Planboard-Kopf
|
||
- Badge „Wichtig“
|
||
- Spaltenköpfe der neuen Tabellen
|
||
|
||
**Knöpfe:** Textknöpfe, die bisher Icons waren:
|
||
- „Vorschau öffnen“, „In Gitea öffnen“
|
||
- „Bearbeiten“, „Löschen“, „Entfernen“, „Lösen“
|
||
- „Anzeigen“, „Verbergen“, „Kopieren“, „Kopiert“
|
||
- „Erneut versuchen“, „Aktualisieren“, „Hosten“
|
||
|
||
**Ladetexte in Knöpfen:** „Speichert …“, „Sendet …“, „Wird geladen …“.
|
||
|
||
## Was gleich bleibt
|
||
|
||
**Verhalten:**
|
||
- Alle Hooks und alle Lib-Logik. Einzige Ausnahme: das Verwerfen veralteter Antworten in `useWorkorders`. Neue Hooks sind erlaubt (`useWorkorder`, Zähler, Zeitsummen).
|
||
- Auth und 2FA.
|
||
- Alle Abfrage-Intervalle.
|
||
- Pflichtfelder und 15-Minuten-Schritte.
|
||
- Doppelklick-Sperren.
|
||
- Alle `window.confirm`-Rückfragen.
|
||
- Alle `data-testid`.
|
||
- Die URL-Parameter `?tab=`, `?antrag=`, `?kunde=`.
|
||
|
||
**Daten:**
|
||
- Appwrite-Schema, Rechte und gespeicherte Werte (englische Status, die das Kundenportal auswertet).
|
||
- Server-APIs.
|
||
- Portal-Repo.
|
||
- `.env*`: Latin-1, nicht anfassen.
|
||
|
||
**Texte:**
|
||
- Alle rechtlich relevanten Texte:
|
||
- Zustimmung zur Website-Analyse
|
||
- Abo-, Gutschrift- und § 19-Hinweise
|
||
- Steuer-Hinweis
|
||
- Mail- und Chat-Vorlagen.
|
||
- Fehlermeldungen.
|
||
|
||
## Risiken und Gegenmaßnahmen
|
||
|
||
| Risiko | Gegenmaßnahme |
|
||
|---|---|
|
||
| Ein einziges CSS-Bündel: umbenannte Klassen brechen Seiten, die gerade niemand ansieht | KOMPAT ab P1. Am Ende von P1 ein Skript, das alle in JSX und `lib/*.js` benutzten Klassen gegen die CSS-Regeln abgleicht; Ergebnis im Plan. Screenshot-Pflichtliste nach jedem Paket. |
|
||
| Namensgleiche Klassen mit anderer Bedeutung: `.btn`, `.badge`, `.page`, `.field`, `.seg`, `.toolbar`, `.empty`, `.list-row`, `.urgent-toggle`, `.overlay`, `.id` | in P1 bewusst ersetzen, jede Altverwendung im Screenshot prüfen |
|
||
| 79 `!important` im Altbestand | löschen statt überschreiben |
|
||
| Alt-Dialoge bleiben bis P2/P5/P7/P8 in Betrieb | KOMPAT-Regeln für `.overlay`, `.overlay-content`, `.overlay-close`. P1-Abnahme: jedes der 10 Alt-Dialoge bei 390×700 und 1440×700, Speichern-Knopf erreichbar. |
|
||
| Fremder Chat-Composer, sein CSS lädt nach dem WOMS-CSS | Präfix-Regel und `--chat-*`-Variablen, siehe P1 und P6 |
|
||
| Diagramme auf Canvas | Farbkonstanten statt Variablen, ab P1 hell |
|
||
| Statusfarben ändern ihre Bedeutung | Hinweis an das Team beim Live-Gang von P1 |
|
||
| Dokument-Scroll statt innerem Scroller: `position: sticky` in AdminPage, Scroll-Anker der Finanzen | in der Abnahme gezielt prüfen |
|
||
| Vorschlagslisten in scrollenden Dialogen werden abgeschnitten | in P2, P6 und P7 prüfen |
|
||
| Freitext-Datumsfelder, keine Fehlergrenze: ein Parserfehler legt die App lahm | nur über `parseGermanDate`/`formatTime` anzeigen. Testdaten mit Altwerten: `2.5.2026`, `31.02.2026`, ISO-Datum, leer, ohne woid, unbekannter Status. |
|
||
| Paralleler Branch `feature/terminplaner-tp2`: Commit 6f03712 ändert `CustomerFunktionen.jsx`, `lib/funktionenView.js` (neue Funktion `abgeschaltetText`) und den Test (+4 Tests) | vor P4 nach `test` bringen (Eigentümer) oder per `git cherry-pick 6f03712` in `dev-design` holen. Die Testzahl steigt dann um 4. |
|
||
| `node_modules` aus Git nehmen (P0) | betrifft nur Git, nicht den Deploy, weil `deploy.sh` löscht und neu installiert. Andere Worktrees mit Symlink sind unkritisch. |
|
||
| Speicher knapp | Installation, Build und Screenshots unter `systemd-run` mit Speichergrenze |
|
||
|
||
## Abnahme
|
||
|
||
Ein Paket ist fertig, wenn alles Folgende erfüllt ist:
|
||
|
||
1. **Tests:** `npm test` läuft im Worktree grün. Die Testzahl wird je Paket notiert. Ausgangsstand 312; P1 minus 3 (`sidebar`) plus neue Tests; nach tp2 plus 4. Neue Tests gibt es für `statusTone`, `ticketPath`, `ticketListUrl`, die ausgelagerte Arbeitsblatt-Logik und die Zeitsummen.
|
||
2. **Build:** `npm run build` ist grün, die Bundle-Größe wird notiert.
|
||
3. **Screenshots** mit 1440 px und 390 px über die Pflichtliste:
|
||
- Login inklusive beider Code-Schritte
|
||
- Ticketliste (mit und ohne Suche), Ticket mit beiden Reitern, alle Ticket-Dialoge
|
||
- Planboard
|
||
- Kundenliste und Kundenseite mit allen Reitern
|
||
- Chat
|
||
- Gutschriften, Abo-Angebote, E-Mail-Speicher
|
||
- Projekte mit beiden Dialogen, Zuordnen, Services
|
||
- Zustimmung der Website-Analyse
|
||
- Finanzen mit Dialogen
|
||
- Rechtliches
|
||
- Admin inklusive „Mitarbeiter anlegen“
|
||
- Serverpuls mit Diagrammen
|
||
- in P1 zusätzlich die 10 Alt-Dialoge
|
||
4. **Screenshot-Werkzeug:**
|
||
- Es liegt unter `/root/reports/ticketsystem-design-2026-10-04/werkzeug/`, außerhalb des Repos, und ändert weder `package.json` noch Lock noch `tests/`.
|
||
- Aufbau: esbuild-Bündel und Playwright Headless mit nachgebildetem Appwrite. Es nutzt nur Testdaten, inklusive der Altwerte aus der Risikotabelle, keine Produktionsdaten und keine echten Logins.
|
||
- Es läuft unter `systemd-run` mit `MemoryMax`.
|
||
- Die Ausgabe liegt je Paket unter `shots/p<N>/1440` und `shots/p<N>/390`.
|
||
5. **Code-Hygiene im umgebauten Bereich:**
|
||
- keine Icon-Importe
|
||
- keine neuen `style={{…}}`, außer für Werte, die erst zur Laufzeit feststehen (z. B. berechnete Breiten)
|
||
- Texte nur wie unter „Texte“ geändert
|
||
6. **Code-Review:** Ein Review (sonnet) ist gelaufen, wichtige Befunde sind behoben.
|
||
7. **Freigabe:** Der Eigentümer hat die Screenshots gesehen, bevor er das Tag pusht.
|