docs(07): UI design contract for DKV Fleet Module
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 `<h1>` — follows `modules/domaincheck/page.tsx` |
|
||||
| Heading | 18px | `text-lg` | 600 (`font-semibold`) | 1.3 | Settings page `<h1>`, 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 `<thead>` 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 ]
|
||||
<h1>DKV Flotte</h1> [Jetzt prüfen] ← right-aligned button
|
||||
<p>Zuletzt geprüft: …</p> ← 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 ]
|
||||
<h2>Verarbeitungshistorie</h2> ← text-lg font-semibold mb-4
|
||||
<InvoiceHistoryTable />
|
||||
|
||||
[ Last Export Files ]
|
||||
<h2>Letzte Exportdateien</h2> ← text-sm font-semibold mb-2 mt-6
|
||||
<ExportFileList />
|
||||
```
|
||||
|
||||
**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 | `<StatusBadge>` 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 `<a href="/api/dkv/exports/{filename}" download>` — `text-primary text-sm hover:underline`
|
||||
- If absent: `<span class="text-muted-foreground">–</span>`
|
||||
|
||||
**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: `<a>` with `text-sm text-primary hover:underline`
|
||||
- Size: `text-xs text-muted-foreground ml-2`
|
||||
- Empty state: `<p class="text-sm text-muted-foreground">Noch keine Exportdateien vorhanden</p>`
|
||||
- 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: `<svg class="mr-2 h-4 w-4 animate-spin" ...>` — 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: `<h1 class="text-2xl font-bold tracking-tight mb-6">Moduleinstellungen</h1>`
|
||||
- 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: `<button role="switch" aria-checked={isActive}>` — pill shape:
|
||||
- On: `bg-primary` with white circle
|
||||
- Off: `bg-muted` with white circle
|
||||
- Width: 40px, height: 22px
|
||||
|
||||
Form actions row: `flex gap-3 pt-4 border-t border-border mt-6`
|
||||
- "Verbindung testen" (secondary button, left) — tests IMAP/Exchange connection
|
||||
- "Speichern" (primary button)
|
||||
- Inline feedback below buttons: success in green (`text-sm` at oklch(0.40 0.15 148)) or error in destructive (`text-sm text-destructive`)
|
||||
|
||||
**Tab 2: Fahrzeuge → VehicleTable + CsvImportButton**
|
||||
|
||||
File: `apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.tsx`
|
||||
|
||||
Table toolbar (above table): `flex justify-between items-center mb-3`
|
||||
- Left: "Fahrzeuge" count badge — `text-sm text-muted-foreground` e.g. "27 Fahrzeuge"
|
||||
- Right: [CSV importieren] (secondary button) + [Fahrzeug hinzufügen] (primary button), `gap-2`
|
||||
|
||||
Table columns (same header style as InvoiceHistoryTable):
|
||||
|
||||
| Column | Width | Input type in edit mode |
|
||||
|--------|-------|------------------------|
|
||||
| KENNZEICHEN | 140px | text, `uppercase` |
|
||||
| MARKE | 140px | text |
|
||||
| MODELL | 180px | text |
|
||||
| FAHRER | 200px | text (format: "Vorname Nachname") |
|
||||
| AKTIONEN | 80px | — |
|
||||
|
||||
**Row modes:**
|
||||
- View mode: plain text cells + pencil icon button + trash icon button in AKTIONEN column
|
||||
- Edit mode: text inputs replace cells (same `h-9 rounded border border-border bg-background px-2 text-sm` but without `max-w-md` constraint) + checkmark (save) + × (cancel) icon buttons
|
||||
|
||||
Icon buttons in AKTIONEN: `p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors`
|
||||
- Pencil: 16×16 inline SVG
|
||||
- Trash: 16×16 inline SVG, `hover:text-destructive`
|
||||
- Checkmark (save): 16×16, `hover:text-primary`
|
||||
- × (cancel): 16×16
|
||||
|
||||
"Add vehicle" row: clicking [Fahrzeug hinzufügen] appends a new empty edit-mode row at bottom of table.
|
||||
|
||||
File: `apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/CsvImportButton.tsx`
|
||||
|
||||
Hidden `<input type="file" accept=".csv">`. Clicking [CSV importieren] triggers the hidden input.
|
||||
|
||||
After file selected → inline dialog (NOT a full modal; a contained `<div>` in the page below the button, with border and padding):
|
||||
```
|
||||
Importmodus:
|
||||
○ Zusammenführen — neue Fahrzeuge hinzufügen, vorhandene behalten (default)
|
||||
● Ersetzen — alle vorhandenen Fahrzeuge löschen und neu importieren
|
||||
↳ [Warning text, text-xs text-destructive]: "Alle vorhandenen Fahrzeuge werden ersetzt."
|
||||
|
||||
[Abbrechen] [Importieren]
|
||||
```
|
||||
"Importieren" with Replace mode selected → destructive confirm dialog (standard browser-style: title + body + [Abbrechen] [Ersetzen])
|
||||
|
||||
Post-import: inline success message `text-sm` at green color + auto-dismiss after 4 seconds + table refresh.
|
||||
|
||||
---
|
||||
|
||||
### Surface C: General Settings SMTP
|
||||
|
||||
**File:** `apps/web/src/app/(portal)/settings/general/smtp/page.tsx`
|
||||
|
||||
**SettingsSidebar extension** (`apps/web/src/components/settings/settings-sidebar.tsx`):
|
||||
|
||||
Add new "Allgemein" section **above** the existing "Dashboard" section:
|
||||
|
||||
```tsx
|
||||
// New section — insert before existing Dashboard items
|
||||
<div className="p-4 pb-2">
|
||||
<h2 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
{t('categoryGeneral')}
|
||||
</h2>
|
||||
</div>
|
||||
<nav className="flex flex-col gap-1 px-3 mb-2">
|
||||
<Link href="/settings/general/smtp" ...>
|
||||
{t('categorySmtp')}
|
||||
</Link>
|
||||
</nav>
|
||||
```
|
||||
|
||||
Active/inactive link styling: identical to existing items in `settings-sidebar.tsx` (copy verbatim).
|
||||
|
||||
**SmtpConfigForm:**
|
||||
|
||||
File: `apps/web/src/app/(portal)/settings/general/smtp/components/SmtpConfigForm.tsx`
|
||||
|
||||
Page heading: `<h1 class="mb-6 text-lg font-semibold text-foreground">SMTP-Konfiguration</h1>` — follows `settings/dashboard/page.tsx` heading pattern exactly.
|
||||
|
||||
Form fields (in order, follow CalendarSourceForm pattern, `max-w-md` on all inputs):
|
||||
|
||||
```
|
||||
Host * [text input, placeholder: "smtp.example.com"]
|
||||
Port * [number input, default 587]
|
||||
Verschlüsselung * [select: Keine | STARTTLS | SSL-TLS, default STARTTLS]
|
||||
Benutzername [text input, optional]
|
||||
Passwort [password input, optional, with show/hide toggle]
|
||||
Absenderadresse * [email input, placeholder: "tessera@example.com"]
|
||||
[help text: "Die E-Mail-Adresse, die als Absender angezeigt wird."]
|
||||
```
|
||||
|
||||
Form actions row: `flex gap-3 pt-2`
|
||||
- "Speichern" (primary button)
|
||||
- "Verbindung testen" (secondary button)
|
||||
|
||||
Connection test feedback: appears below the action row:
|
||||
- Loading: `text-sm text-muted-foreground` — "Verbindung wird getestet..."
|
||||
- Success: `text-sm` green — "Verbindung erfolgreich"
|
||||
- Error: `text-sm text-destructive` — "Verbindung fehlgeschlagen: {fehlermeldung}"
|
||||
- Auto-clears after 6 seconds
|
||||
|
||||
---
|
||||
|
||||
### StatusBadge Component
|
||||
|
||||
**File:** `apps/web/src/app/(portal)/modules/dkv-fleet/components/StatusBadge.tsx`
|
||||
|
||||
```
|
||||
type Status = 'Verarbeitet' | 'Fehler' | 'Versand fehlgeschlagen'
|
||||
props: { status: Status; errorMessage?: string }
|
||||
```
|
||||
|
||||
Shape: `<span class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium">`
|
||||
|
||||
Colors (inline style — not CSS classes, as these OKLCH values are not in Tailwind config):
|
||||
- Verarbeitet: `color: oklch(0.40 0.15 148)`, `background: oklch(0.40 0.15 148 / 0.10)`
|
||||
- Fehler: `color: var(--destructive)`, `background: oklch(0.55 0.2 27 / 0.10)`
|
||||
- Versand fehlgeschlagen: `color: oklch(0.50 0.13 70)`, `background: oklch(0.50 0.13 70 / 0.10)`
|
||||
|
||||
"Fehler" and "Versand fehlgeschlagen" states: add `title={errorMessage}` attribute when `errorMessage` is provided — renders browser tooltip on hover.
|
||||
|
||||
---
|
||||
|
||||
### Interaction Contracts Summary
|
||||
|
||||
| Interaction | Trigger | Feedback | Error handling |
|
||||
|------------|---------|---------|----------------|
|
||||
| "Jetzt prüfen" | button click | loading state on button; table refetch on success | error banner above table; button re-enables |
|
||||
| Delete vehicle | trash icon click | confirm dialog appears | none (server error: inline `text-destructive` below table) |
|
||||
| Edit vehicle row | pencil icon click | row switches to edit-mode inputs inline | validation: Kennzeichen required; inline `text-destructive text-xs` below field |
|
||||
| Save vehicle row | checkmark click | row returns to view mode with new values | server error: `text-destructive text-xs` appears below row |
|
||||
| CSV import | file selected | import mode dialog appears | file parse error: `text-destructive text-sm` in dialog |
|
||||
| Save InboxConfig | "Speichern" click | button disables during save; re-enables on done | inline error below button |
|
||||
| Test inbox connection | "Verbindung testen" click | inline loading → success/error text | shown inline below button |
|
||||
| Save SmtpConfig | "Speichern" click | button disables during save; re-enables | inline error below button |
|
||||
| Test SMTP | "Verbindung testen" click | inline loading → success/error text | shown inline, auto-clears 6s |
|
||||
| Export file download | filename link click | browser download dialog | n/a (HTTP error: browser default) |
|
||||
|
||||
**Toast pattern:** Use only for async success states that don't have a natural location (e.g., CSV import success). Toast: fixed bottom-right, `rounded border border-border bg-card shadow-md px-4 py-3 text-sm`, auto-dismiss 4 seconds.
|
||||
|
||||
---
|
||||
|
||||
### i18n Translation Keys
|
||||
|
||||
New keys required for this phase. Add to `apps/web/src/i18n/messages/{de,en}.json`.
|
||||
|
||||
**Namespace `dkvFleet` (new):**
|
||||
|
||||
```json
|
||||
{
|
||||
"pageTitle": "DKV Flotte",
|
||||
"checkNow": "Jetzt prüfen",
|
||||
"checking": "Wird geprüft...",
|
||||
"lastChecked": "Zuletzt geprüft: {time} Uhr",
|
||||
"neverChecked": "Noch nie geprüft",
|
||||
"settingsTitle": "Moduleinstellungen",
|
||||
"historyTitle": "Verarbeitungshistorie",
|
||||
"exportFilesTitle": "Letzte Exportdateien",
|
||||
"emptyHistory": "Noch keine Rechnungen verarbeitet",
|
||||
"emptyHistoryBody": "Klicke auf 'Jetzt prüfen', um den Posteingang zu prüfen. Neue Rechnungen erscheinen hier automatisch.",
|
||||
"emptyExports": "Noch keine Exportdateien vorhanden",
|
||||
"emptyVehicles": "Keine Fahrzeuge hinterlegt",
|
||||
"emptyVehiclesBody": "Füge Fahrzeuge manuell hinzu oder lade eine CSV-Datei hoch.",
|
||||
"status": {
|
||||
"processed": "Verarbeitet",
|
||||
"error": "Fehler",
|
||||
"sendFailed": "Versand fehlgeschlagen"
|
||||
},
|
||||
"col": {
|
||||
"dateTime": "Datum/Zeit",
|
||||
"invoiceNumber": "Rechnungsnummer",
|
||||
"vehicles": "Fahrzeuge",
|
||||
"transactions": "Transaktionen",
|
||||
"status": "Status",
|
||||
"exportFile": "Exportdatei",
|
||||
"licensePlate": "Kennzeichen",
|
||||
"brand": "Marke",
|
||||
"model": "Modell",
|
||||
"driver": "Fahrer",
|
||||
"actions": "Aktionen"
|
||||
},
|
||||
"addVehicle": "Fahrzeug hinzufügen",
|
||||
"editVehicle": "Fahrzeug bearbeiten",
|
||||
"deleteVehicle": "Fahrzeug löschen",
|
||||
"deleteVehicleBody": "Das Fahrzeug {plate} wird dauerhaft aus der Stammdatenliste entfernt. Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"importCsv": "CSV importieren",
|
||||
"importMode": "Importmodus",
|
||||
"importMerge": "Zusammenführen — neue Fahrzeuge hinzufügen, vorhandene behalten",
|
||||
"importReplace": "Ersetzen — alle vorhandenen Fahrzeuge löschen und neu importieren",
|
||||
"importReplaceWarning": "Alle vorhandenen Fahrzeuge werden ersetzt.",
|
||||
"importReplaceConfirmTitle": "Fahrzeuge ersetzen?",
|
||||
"importReplaceConfirmBody": "Alle vorhandenen Fahrzeuge werden durch die CSV-Daten ersetzt. Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"importReplaceConfirmButton": "Ersetzen",
|
||||
"importSuccess": "{count} Fahrzeuge importiert",
|
||||
"vehicleCount": "{count} Fahrzeuge",
|
||||
"tabs": {
|
||||
"inbox": "Posteingang",
|
||||
"vehicles": "Fahrzeuge"
|
||||
},
|
||||
"form": {
|
||||
"protocol": "Protokoll",
|
||||
"host": "Host",
|
||||
"port": "Port",
|
||||
"encryption": "Verschlüsselung",
|
||||
"encryptionNone": "Keine",
|
||||
"folder": "Ordner",
|
||||
"senderFilter": "Absenderfilter",
|
||||
"pollInterval": "Abrufintervall",
|
||||
"pollIntervalSuffix": "Minuten",
|
||||
"username": "Benutzername",
|
||||
"password": "Passwort",
|
||||
"exportRecipient": "Export-Empfänger",
|
||||
"vehicleFormat": "Fahrzeug-Formatstring",
|
||||
"vehicleFormatHelp": "Verfügbare Platzhalter: {Marke}, {Modell}, {Kennzeichen}, {Fahrer}",
|
||||
"active": "Aktiv",
|
||||
"testConnection": "Verbindung testen",
|
||||
"testTesting": "Verbindung wird getestet...",
|
||||
"testSuccess": "Verbindung erfolgreich",
|
||||
"testFailed": "Verbindung fehlgeschlagen: {error}",
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"showPassword": "Passwort anzeigen",
|
||||
"hidePassword": "Passwort verbergen"
|
||||
},
|
||||
"errors": {
|
||||
"pollFailed": "Posteingang konnte nicht geprüft werden. Überprüfe die Konfiguration unter 'Moduleinstellungen › Posteingang'.",
|
||||
"smtpFailed": "Export wurde erstellt, konnte aber nicht gesendet werden. Die Datei ist weiterhin zum Download verfügbar."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Namespace `settings` (extend existing — add keys):**
|
||||
|
||||
```json
|
||||
{
|
||||
"categoryGeneral": "Allgemein",
|
||||
"categorySmtp": "SMTP",
|
||||
"smtp": {
|
||||
"title": "SMTP-Konfiguration",
|
||||
"host": "Host",
|
||||
"port": "Port",
|
||||
"encryption": "Verschlüsselung",
|
||||
"username": "Benutzername",
|
||||
"password": "Passwort",
|
||||
"fromAddress": "Absenderadresse (Von)",
|
||||
"fromAddressHelp": "Die E-Mail-Adresse, die als Absender angezeigt wird.",
|
||||
"save": "Speichern",
|
||||
"test": "Verbindung testen"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Component File Map
|
||||
|
||||
| Component | File path | Surface | New / Extend |
|
||||
|-----------|-----------|---------|--------------|
|
||||
| InvoiceHistoryTable | `.../modules/dkv-fleet/components/InvoiceHistoryTable.tsx` | A | New |
|
||||
| StatusBadge | `.../modules/dkv-fleet/components/StatusBadge.tsx` | A | New |
|
||||
| ExportFileList | `.../modules/dkv-fleet/components/ExportFileList.tsx` | A | New |
|
||||
| InboxConfigForm | `.../modules/dkv-fleet/settings/components/InboxConfigForm.tsx` | B | New |
|
||||
| VehicleTable | `.../modules/dkv-fleet/settings/components/VehicleTable.tsx` | B | New |
|
||||
| CsvImportButton | `.../modules/dkv-fleet/settings/components/CsvImportButton.tsx` | B | New |
|
||||
| SmtpConfigForm | `.../settings/general/smtp/components/SmtpConfigForm.tsx` | C | New |
|
||||
| SettingsSidebar | `apps/web/src/components/settings/settings-sidebar.tsx` | C | Extend (add Allgemein section) |
|
||||
|
||||
Base path prefix for module components: `apps/web/src/app/(portal)/`
|
||||
|
||||
---
|
||||
|
||||
## Checker Sign-Off
|
||||
|
||||
- [ ] Dimension 1 Copywriting: PASS
|
||||
- [ ] Dimension 2 Visuals: PASS
|
||||
- [ ] Dimension 3 Color: PASS
|
||||
- [ ] Dimension 4 Typography: PASS
|
||||
- [ ] Dimension 5 Spacing: PASS
|
||||
- [ ] Dimension 6 Registry Safety: PASS
|
||||
|
||||
**Approval:** pending
|
||||
|
||||
---
|
||||
|
||||
*Sources: CONTEXT.md (D-01 to D-21), RESEARCH.md (Architecture Patterns, Standard Stack), globals.css (all OKLCH tokens), settings-sidebar.tsx + calendar-source-form.tsx + admin-sidebar.tsx (component patterns), modules/domaincheck/page.tsx + settings/dashboard/page.tsx (heading typography patterns)*
|
||||
Reference in New Issue
Block a user