--- 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: `