diff --git a/.planning/phases/07-dkv-fleet-module/07-UI-SPEC.md b/.planning/phases/07-dkv-fleet-module/07-UI-SPEC.md new file mode 100644 index 0000000..39ae250 --- /dev/null +++ b/.planning/phases/07-dkv-fleet-module/07-UI-SPEC.md @@ -0,0 +1,583 @@ +--- +phase: 7 +slug: dkv-fleet-module +status: draft +shadcn_initialized: false +preset: none +created: 2026-06-26 +--- + +# Phase 7 — UI Design Contract: DKV Fleet Module + +> Visual and interaction contract for frontend phases. Generated by gsd-ui-researcher, verified by gsd-ui-checker. +> +> This module spans **3 frontend surfaces**: module main page (history + exports), +> module settings page (inbox config + vehicle CRUD), and general settings SMTP page. +> Sections beyond the base template are phase-specific extensions required for a +> multi-surface module. + +--- + +## Design System + +| Property | Value | Source | +|----------|-------|--------| +| Tool | manual — shadcn CSS-variable convention; no CLI (`components.json` absent) | codebase audit | +| Preset | not applicable | — | +| Component library | manual hand-written components following shadcn token convention (no Radix dependency) | codebase audit | +| Icon library | inline SVG only — no external icon package | codebase audit (admin-sidebar.tsx) | +| Font | Inter via `var(--font-sans)` — system-ui fallback | globals.css | +| Border radius | `var(--radius)` = 0.5rem (8px) | globals.css | +| Color space | OKLCH throughout | globals.css | + +**Registry safety:** Not applicable — no `components.json`, no CLI registry consumption. +**shadcn gate:** components.json NOT found. Project has established manual approach; do not initialize CLI. All new components must hand-write following existing patterns in `apps/web/src/components/`. + +--- + +## Spacing Scale + +Declared values (must be multiples of 4). Source: codebase audit of existing components. + +| Token | Value | Tailwind class | Usage | +|-------|-------|----------------|-------| +| xs | 4px | `gap-1`, `p-1` | Icon gaps, inline element padding | +| sm | 8px | `gap-2`, `p-2` | Compact element spacing, icon-to-label gap | +| md | 12px | `px-3`, `py-1.5` | Input padding (h-9 pattern), nav item padding | +| lg | 16px | `p-4`, `gap-4` | Default section padding, form field gap | +| xl | 24px | `p-6`, `gap-6` | Card padding, section breaks | +| 2xl | 32px | `gap-8` | Layout column gaps | +| 3xl | 48px | — | Major page-level spacing | + +**Exceptions:** none for this phase. + +**Standard form element heights (from existing `CalendarSourceForm` pattern):** +- Input height: `h-9` (36px) +- Input max-width: `max-w-md` (448px) — do not exceed on settings forms +- Button: `px-4 py-2` (16px / 8px) + +--- + +## Typography + +Declared sizes from codebase audit (`grep -r text-* apps/web/src/components`): + +| Role | Size | Tailwind | Weight | Line Height | Usage | +|------|------|----------|--------|-------------|-------| +| Display | 24px | `text-2xl` | 700 (`font-bold`) | 1.2 | Module page `

` — follows `modules/domaincheck/page.tsx` | +| Heading | 18px | `text-lg` | 600 (`font-semibold`) | 1.3 | Settings page `

`, section titles — follows `settings/dashboard/page.tsx` | +| Body | 14px | `text-sm` | 400 (`font-normal`) | 1.5 | Form labels, table cells, nav items, general text | +| Label | 12px | `text-xs` | 600 (`font-semibold`) | 1.0 | Section category labels (uppercase + tracking-wider), table column headers, status badges | + +**Declared weights:** 400 (regular) and 600 (semibold). Exception: Display uses 700 per existing module page convention (`font-bold`) — do not introduce 700 weight elsewhere in this phase. + +**Section category label pattern** (from `settings-sidebar.tsx` and `admin-sidebar.tsx`): +``` +text-xs font-semibold uppercase tracking-wider text-muted-foreground +``` +Use this verbatim for sidebar category headings and table `` column labels. + +--- + +## Color + +All values from `apps/web/src/app/globals.css`. OKLCH color space. Dual light/dark mode support is mandatory for all new components. + +| Role | CSS Token | Light Value | Dark Value | Usage | +|------|-----------|-------------|------------|-------| +| Dominant (60%) | `var(--background)` | oklch(0.99 0 0) | oklch(0.23 0.01 260) | Page background, main content area | +| Secondary (30%) | `var(--card)` | oklch(1 0 0) | oklch(0.27 0.01 260) | Table container, form panels, export file list | +| — | `var(--sidebar)` | oklch(0.97 0 0) | oklch(0.20 0.01 260) | Settings sidebar background | +| — | `var(--muted)` | oklch(0.96 0 0) | oklch(0.30 0.01 260) | Table row hover, disabled field backgrounds | +| Accent (10%) | `var(--primary)` | oklch(0.91 0.19 102) | oklch(0.91 0.19 102) | Primary CTA only — see reserved list below | +| Destructive | `var(--destructive)` | oklch(0.55 0.2 27) | oklch(0.55 0.2 27) | Delete vehicle, destructive CSV replace, error text | +| Border | `var(--border)` | oklch(0.90 0 0) | oklch(0.30 0.01 260) | Input borders, table borders, dividers | +| Muted text | `var(--muted-foreground)` | oklch(0.55 0 0) | oklch(0.65 0 0) | Placeholder text, secondary info, empty states | + +**Accent (`var(--primary)`) is reserved for:** +- "Jetzt prüfen" button background +- Active state indicator in tab bar (border-bottom line) +- Focus ring (`var(--ring)`) on focused inputs +- Export file download links (text color: `text-primary`) +- Active settings sidebar item (`bg-sidebar-accent text-sidebar-accent-foreground`) + +**Accent is NOT used for:** passive table borders, section headings, helper text, secondary buttons, status indicators. + +### Status Badge Colors (new — not in globals.css) + +These values are defined inline in the `StatusBadge` component. Do NOT add to globals.css for v1. + +| State | Label | Text | Background | +|-------|-------|------|-----------| +| Verarbeitet | "Verarbeitet" | `oklch(0.40 0.15 148)` (green) | `oklch(0.40 0.15 148 / 0.10)` | +| Fehler | "Fehler" | `var(--destructive)` | `oklch(0.55 0.2 27 / 0.10)` | +| Versand fehlgeschlagen | "Versand fehlgeschlagen" | `oklch(0.50 0.13 70)` (amber) | `oklch(0.50 0.13 70 / 0.10)` | + +Badge shape: `rounded-full px-2 py-0.5 text-xs font-medium` + +--- + +## Copywriting Contract + +All UI strings are German (the portal language for this tenant). i18n keys are listed in the Phase-Specific Extensions section below. + +| Element | Copy | +|---------|------| +| Primary CTA (manual trigger) | "Jetzt prüfen" | +| CTA loading state | "Wird geprüft..." | +| Last checked timestamp | "Zuletzt geprüft: {TT.MM.JJJJ, HH:mm} Uhr" | +| Never checked | "Noch nie geprüft" | +| Add vehicle button | "Fahrzeug hinzufügen" | +| CSV import button | "CSV importieren" | +| Save (forms) | "Speichern" | +| Cancel | "Abbrechen" | +| Test SMTP connection | "Verbindung testen" | +| History empty state heading | "Noch keine Rechnungen verarbeitet" | +| History empty state body | "Klicke auf 'Jetzt prüfen', um den Posteingang zu prüfen. Neue Rechnungen erscheinen hier automatisch." | +| Vehicle table empty state heading | "Keine Fahrzeuge hinterlegt" | +| Vehicle table empty state body | "Füge Fahrzeuge manuell hinzu oder lade eine CSV-Datei hoch." | +| Export files empty | "Noch keine Exportdateien vorhanden" | +| Poll error banner | "Posteingang konnte nicht geprüft werden. Überprüfe die Konfiguration unter 'Moduleinstellungen › Posteingang'." | +| SMTP send failure (in history badge tooltip) | "Export wurde erstellt, konnte aber nicht gesendet werden. Die Datei ist weiterhin zum Download verfügbar." | +| SMTP test success | "Verbindung erfolgreich" | +| SMTP test failure | "Verbindung fehlgeschlagen: {fehlermeldung}" | +| Delete vehicle — dialog title | "Fahrzeug löschen" | +| Delete vehicle — dialog body | "Das Fahrzeug {Kennzeichen} wird dauerhaft aus der Stammdatenliste entfernt. Diese Aktion kann nicht rückgängig gemacht werden." | +| Delete vehicle — confirm button | "Löschen" (destructive style) | +| CSV replace — dialog title | "Fahrzeuge ersetzen?" | +| CSV replace — dialog body | "Alle vorhandenen Fahrzeuge werden durch die CSV-Daten ersetzt. Diese Aktion kann nicht rückgängig gemacht werden." | +| CSV replace — confirm button | "Ersetzen" (destructive style) | +| CSV import success toast | "{count} Fahrzeuge importiert" | + +**Destructive action pattern:** Both destructive actions use an inline dialog/modal (not a toast). Confirm button uses `bg-destructive text-destructive-foreground` style. Cancel is always leftmost and uses secondary button style. + +--- + +## Registry Safety + +| Registry | Blocks Used | Safety Gate | +|----------|-------------|-------------| +| shadcn official | none | not required — no CLI | +| third-party | none | not applicable | + +--- + +## Phase-Specific Extensions + +The following sections are required for multi-surface modules. gsd-executor must treat these as binding visual contracts identical to the base sections above. + +--- + +### UI Surfaces Overview + +| Surface | Route | Purpose | +|---------|-------|---------| +| A — Module Main | `/modules/dkv-fleet` | Invoice history table + export download list + "Jetzt prüfen" trigger | +| B — Module Settings | `/modules/dkv-fleet/settings` | Inbox config form (tab 1) + vehicle master CRUD table (tab 2) | +| C — General Settings SMTP | `/settings/general/smtp` | Shared SMTP configuration (extends SettingsSidebar with new category) | + +--- + +### Surface A: Module Main Page + +**File:** `apps/web/src/app/(portal)/modules/dkv-fleet/page.tsx` + +**Layout (top to bottom):** + +``` +[ Page Header Row ] +

DKV Flotte

[Jetzt prüfen] ← right-aligned button +

Zuletzt geprüft: …

← text-sm text-muted-foreground + +[ Error Banner (conditional — only on poll failure) ] + border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive + + × close button (right-aligned in banner) + +[ Invoice Processing History ] +

Verarbeitungshistorie

← text-lg font-semibold mb-4 + + +[ Last Export Files ] +

Letzte Exportdateien

← text-sm font-semibold mb-2 mt-6 + +``` + +**InvoiceHistoryTable specification:** + +Column headers: `text-xs font-semibold uppercase tracking-wider text-muted-foreground` with `px-4 py-3 border-b border-border` + +| # | Column Header | Data Format | Width | Alignment | +|---|---------------|-------------|-------|-----------| +| 1 | DATUM/ZEIT | "26.06.2026, 14:32 Uhr" | 160px | left | +| 2 | RECHNUNGSNUMMER | string (e.g. "26-651566449-001") | 200px | left | +| 3 | FAHRZEUGE | number | 100px | right | +| 4 | TRANSAKTIONEN | number | 120px | right | +| 5 | STATUS | `` component | 180px | left | +| 6 | EXPORTDATEI | download link or "–" em dash | auto | left | + +Table rows: `px-4 py-3 text-sm`, hover: `hover:bg-muted/50 transition-colors` +Table container: `rounded border border-border overflow-hidden` +Initial loading: 5 skeleton rows — each cell is `h-4 rounded bg-muted animate-pulse` +Pagination: show when > 25 rows. Pattern: `"Zeige 1–25 von {total}"` text-sm text-muted-foreground + [← Zurück] [Weiter →] buttons (secondary style, disabled when at limits) + +**Export file in column 6:** +- If present: inline SVG download icon (16×16, currentColor) + filename as `` — `text-primary text-sm hover:underline` +- If absent: `–` + +**ExportFileList specification:** + +Each entry on its own line: +``` +[↓ icon 16×16] DKV_2026-04_26-651566449-001.xlsx (142 KB) +``` +- Icon: inline SVG download arrow, `text-muted-foreground` +- Filename: `` with `text-sm text-primary hover:underline` +- Size: `text-xs text-muted-foreground ml-2` +- Empty state: `

Noch keine Exportdateien vorhanden

` +- Max 10 entries (enforced server-side) + +**"Jetzt prüfen" button states:** + +| State | Classes | Disabled | Content | +|-------|---------|---------|---------| +| Idle | `rounded bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90` | no | "Jetzt prüfen" | +| Loading | `... opacity-80 cursor-not-allowed` | yes | inline spinner SVG (20×20, animate-spin) + "Wird geprüft..." | +| Success | returns to Idle; table re-fetches | no | "Jetzt prüfen" | +| Error | returns to Idle; error banner appears above table | no | "Jetzt prüfen" | + +Spinner SVG: `` — standard circular spinner path, `stroke="currentColor"`. + +--- + +### Surface B: Module Settings Page + +**File:** `apps/web/src/app/(portal)/modules/dkv-fleet/settings/page.tsx` + +**Layout:** +- Page heading: `

Moduleinstellungen

` +- Tab bar immediately below heading (NOT a sub-sidebar — inline tabs) +- Active tab: bottom border `border-b-2 border-primary text-foreground font-medium` +- Inactive tab: `text-muted-foreground hover:text-foreground` +- Tab padding: `px-4 py-2 text-sm` +- Tab bar separator: `border-b border-border mb-6` + +**Tab 1: Posteingang → InboxConfigForm** + +File: `apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx` + +Form fields (in order, all follow CalendarSourceForm pattern): + +``` +Protokoll * [select: IMAP | Exchange] +Host * [text input] +Port * [number input] +Verschlüsselung * [select: Keine | STARTTLS | SSL-TLS] +Ordner * [text input, default "INBOX"] +Absenderfilter [email input, optional — placeholder: "rechnung@dkv.com"] +Abrufintervall * [number input, min=5, suffix label "Minuten"] +Benutzername [text input, optional] +Passwort [password input, optional, with show/hide toggle button] +Export-Empfänger * [email input] +Fahrzeug-Formatstring*[text input, default "{Marke}/{Modell}/{Kennzeichen}"] + [help text below: text-xs text-muted-foreground + "Verfügbare Platzhalter: {Marke}, {Modell}, {Kennzeichen}, {Fahrer}"] +Aktiv [toggle — visual: rounded pill, primary color when on] +``` + +Show/hide password toggle: inline button at right edge of input (`type="button"`, no border, shows eye/eye-off inline SVG 16×16). Input changes `type` attribute between `"password"` and `"text"`. + +Active toggle: `