de06794e67
6 plans covering full pipeline: PDF parsing foundation (Wave 0), inbox providers + export/SMTP services (Wave 1), pipeline orchestration + frontend pages + settings UI (Wave 2). Includes D-06 MailModule DB-config migration and Nyquist validation strategy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
245 lines
26 KiB
Markdown
245 lines
26 KiB
Markdown
---
|
||
phase: 07-dkv-fleet-module
|
||
plan: 05
|
||
type: execute
|
||
wave: 2
|
||
depends_on: ["07-01", "07-02", "07-03"]
|
||
files_modified:
|
||
- apps/web/src/lib/dkv-api.ts
|
||
- apps/web/src/app/(portal)/modules/dkv-fleet/page.tsx
|
||
- apps/web/src/app/(portal)/modules/dkv-fleet/components/StatusBadge.tsx
|
||
- apps/web/src/app/(portal)/modules/dkv-fleet/components/InvoiceHistoryTable.tsx
|
||
- apps/web/src/app/(portal)/modules/dkv-fleet/components/ExportFileList.tsx
|
||
- apps/web/src/app/(portal)/modules/dkv-fleet/settings/page.tsx
|
||
- apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx
|
||
- apps/web/src/app/(portal)/modules/dkv-fleet/vehicles/page.tsx
|
||
- apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.tsx
|
||
- apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/CsvImportButton.tsx
|
||
- apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.test.tsx
|
||
autonomous: true
|
||
requirements: [DKV-01, DKV-03, DKV-04]
|
||
user_setup: []
|
||
|
||
must_haves:
|
||
truths:
|
||
- "User can open /modules/dkv-fleet and see the processing history (Verarbeitungshistorie) and the last export files"
|
||
- "User can click 'Jetzt prüfen' to trigger an inbox poll and the history refetches afterward"
|
||
- "User can download an export file from the history row / export list"
|
||
- "User can configure the inbox (protocol, host, port, encryption, folder, sender filter, interval, credentials, recipient, format string, active) and save it"
|
||
- "User can test the inbox connection and see inline success/error feedback"
|
||
- "User can list, add, inline-edit, and delete vehicles in the vehicle master table"
|
||
- "User can import a CSV of vehicles in merge or replace mode (replace asks for destructive confirmation)"
|
||
artifacts:
|
||
- path: "apps/web/src/lib/dkv-api.ts"
|
||
provides: "Typed fetch client for all /dkv/* routes (config, check-now, history, exports, vehicles CRUD, CSV import, test-connection)"
|
||
contains: "credentials: 'include'"
|
||
- path: "apps/web/src/app/(portal)/modules/dkv-fleet/page.tsx"
|
||
provides: "Surface A — module main page (history + export list + Jetzt prüfen)"
|
||
min_lines: 30
|
||
- path: "apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx"
|
||
provides: "Inbox config form (Surface B inbox tab)"
|
||
min_lines: 40
|
||
- path: "apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.tsx"
|
||
provides: "Vehicle master CRUD table with inline edit"
|
||
min_lines: 40
|
||
- path: "apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.test.tsx"
|
||
provides: "Vitest coverage for vehicle CRUD table (Wave 0 / VALIDATION DKV-03)"
|
||
contains: "vi.mock"
|
||
key_links:
|
||
- from: "apps/web/src/app/(portal)/modules/dkv-fleet/page.tsx"
|
||
to: "apps/web/src/lib/dkv-api.ts (checkNow + fetchHistory)"
|
||
via: "Jetzt prüfen button calls checkNow then refetches history"
|
||
pattern: "checkNow|fetchHistory"
|
||
- from: "apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx"
|
||
to: "apps/web/src/lib/dkv-api.ts (fetchConfig/saveConfig/testConnection)"
|
||
via: "form load + save + test"
|
||
pattern: "saveConfig|testConnection"
|
||
- from: "apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.tsx"
|
||
to: "apps/web/src/lib/dkv-api.ts (vehicles CRUD)"
|
||
via: "list/create/update/delete"
|
||
pattern: "createVehicle|updateVehicle|deleteVehicle"
|
||
---
|
||
|
||
<objective>
|
||
Deliver the DKV Fleet Module frontend (Surfaces A and B from 07-UI-SPEC.md): the typed API client, the module main page (processing history + export download list + "Jetzt prüfen" trigger), the module settings inbox-config form, and the vehicle master CRUD table with CSV import. Consumes the `/dkv/*` REST surface built in Plan 04.
|
||
|
||
Purpose: This is the user-facing half of DKV-01 (inbox config + manual check), DKV-03 (vehicle mapping CSV + manual edit), and DKV-04 (export download + processing history). Each task is a vertical slice: after it, a user can do something concrete in the browser.
|
||
Output: dkv-api client, main page (+ StatusBadge, InvoiceHistoryTable, ExportFileList), settings page (+ InboxConfigForm), vehicles page (+ VehicleTable, CsvImportButton) and a VehicleTable test.
|
||
</objective>
|
||
|
||
## Phase Goal
|
||
|
||
**Als** Administrator **möchte ich** DKV-Tankkarten-Rechnungen automatisch aus einem E-Mail-Postfach verarbeiten lassen, **damit** Flotten-Tankdaten ohne manuelle Eingabe als Excel-Datei exportiert und per SMTP zugestellt werden.
|
||
|
||
This plan makes the pipeline operable from the browser: configure the inbox, manage vehicle→driver mapping, trigger checks, and download exports.
|
||
|
||
> **Requirement mapping note:** The checker brief listed `DKV-03, DKV-05` for this plan. DKV-05 (SMTP in general settings) has no UI here — it is delivered by Plan 07-06. This plan's tasks address DKV-01 (inbox config + check-now UI), DKV-03 (vehicle CRUD + CSV), and DKV-04 (export download + history UI), which is the accurate mapping.
|
||
|
||
<execution_context>
|
||
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||
@$HOME/.claude/gsd-core/templates/summary.md
|
||
</execution_context>
|
||
|
||
<context>
|
||
@.planning/PROJECT.md
|
||
@.planning/phases/07-dkv-fleet-module/07-CONTEXT.md
|
||
@.planning/phases/07-dkv-fleet-module/07-UI-SPEC.md
|
||
@.planning/phases/07-dkv-fleet-module/07-PATTERNS.md
|
||
@apps/web/src/lib/calendar-api.ts
|
||
@apps/web/src/components/settings/calendar-source-form.tsx
|
||
@apps/web/src/app/(portal)/settings/dashboard/calendar/calendar-settings.test.tsx
|
||
@apps/web/src/app/(portal)/modules/domaincheck/page.tsx
|
||
</context>
|
||
|
||
## Artifacts this phase produces (Plan 05 portion)
|
||
|
||
New symbols (exclude from drift verification):
|
||
- File `apps/web/src/lib/dkv-api.ts`: types `DkvConfig`, `DkvHistoryEntry`, `DkvVehicle`, `CreateVehiclePayload`, `UpdateVehiclePayload`; functions `fetchConfig`, `saveConfig`, `testConnection`, `checkNow`, `fetchHistory`, `fetchVehicles`, `createVehicle`, `updateVehicle`, `deleteVehicle`, `importVehiclesCsv`, `exportFileUrl`
|
||
- Components `DkvFleetPage` (default), `StatusBadge`, `InvoiceHistoryTable`, `ExportFileList`, `DkvFleetSettingsPage` (default), `InboxConfigForm`, `DkvFleetVehiclesPage` (default), `VehicleTable`, `CsvImportButton`
|
||
|
||
> **Path note:** The existing API-client convention in this repo is `apps/web/src/lib/<domain>-api.ts` (see `apps/web/src/lib/calendar-api.ts`). The checker brief referenced `lib/api/dkv.ts` with analog `lib/api/calendar.ts`, but that path does not exist in the repo. This plan uses `apps/web/src/lib/dkv-api.ts` to match the real, existing analog. The `@` import alias maps to `apps/web/src`.
|
||
|
||
<tasks>
|
||
|
||
<task type="auto">
|
||
<name>Task 1: dkv-api client + StatusBadge + module main page (Surface A — DKV-04/DKV-01)</name>
|
||
<files>apps/web/src/lib/dkv-api.ts, apps/web/src/app/(portal)/modules/dkv-fleet/components/StatusBadge.tsx, apps/web/src/app/(portal)/modules/dkv-fleet/components/InvoiceHistoryTable.tsx, apps/web/src/app/(portal)/modules/dkv-fleet/components/ExportFileList.tsx, apps/web/src/app/(portal)/modules/dkv-fleet/page.tsx</files>
|
||
<read_first>
|
||
- apps/web/src/lib/calendar-api.ts — API-client convention: `API_URL` const, `credentials: 'include'`, exported typed functions, throw on non-ok (mirror this file exactly for structure)
|
||
- apps/web/src/app/(portal)/modules/domaincheck/page.tsx — page shell: `'use client'`, `useTranslations('dkvFleet')`, `mx-auto max-w-5xl space-y-6 p-6`, card panel
|
||
- 07-UI-SPEC.md "Surface A: Module Main Page" — full layout, InvoiceHistoryTable column spec (6 columns + widths), ExportFileList spec, "Jetzt prüfen" button states, skeleton rows, pagination > 25 rows
|
||
- 07-UI-SPEC.md "StatusBadge Component" — exact OKLCH inline styles per status, rounded-full px-2 py-0.5 text-xs font-medium
|
||
- 07-UI-SPEC.md "Color" + "Typography" + "Spacing Scale" — token usage; accent reserved list
|
||
- 07-PATTERNS.md "dkv-fleet/page.tsx" — analog domaincheck/page.tsx; layout note (max-w-5xl)
|
||
- 07-CONTEXT.md D-20 (history columns), D-15 (last 10 export files)
|
||
</read_first>
|
||
<action>
|
||
Create `apps/web/src/lib/dkv-api.ts` following the calendar-api.ts structure: `const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';`, all calls use `credentials: 'include'` and throw `new Error(...)` on non-ok. Export TypeScript types mirroring the Plan 04 backend contract: `DkvConfig` (protocol, host, port, encryption, folder, senderFilter, pollIntervalMin, isActive, exportRecipient, vehicleFormatString, username, hasPassword boolean — never a password), `DkvHistoryEntry` (id, datumZeit string, rechnungsnummer, anzahlFahrzeuge number, anzahlTransaktionen number, status 'Verarbeitet'|'Fehler'|'Versand fehlgeschlagen', errorMessage?, exportFilename?), `DkvVehicle` (id, kennzeichen, marke, modell, fahrer), `CreateVehiclePayload`, `UpdateVehiclePayload`. Export functions: `fetchConfig()` GET /dkv/config; `saveConfig(payload)` PUT /dkv/config; `testConnection(payload)` POST /dkv/test-connection → `{ success: boolean }`; `checkNow()` POST /dkv/check-now; `fetchHistory(page?, limit?)` GET /dkv/history with URLSearchParams; `fetchVehicles()` GET /dkv/vehicles; `createVehicle(payload)` POST /dkv/vehicles; `updateVehicle(id, payload)` PUT /dkv/vehicles/:id; `deleteVehicle(id)` DELETE /dkv/vehicles/:id; `importVehiclesCsv(file, mode)` POST /dkv/vehicles/import as `multipart/form-data` (FormData with `file` and `mode`, do NOT set Content-Type header manually); and a pure helper `exportFileUrl(filename)` returning `${API_URL}/dkv/exports/${encodeURIComponent(filename)}`.
|
||
Create `StatusBadge.tsx`: props `{ status, errorMessage? }`. Render `<span class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium">` with the exact inline OKLCH color/background per 07-UI-SPEC "StatusBadge Component" for each of the three statuses; add `title={errorMessage}` for Fehler / Versand fehlgeschlagen when provided. Labels come from `t('status.processed'|'status.error'|'status.sendFailed')`.
|
||
Create `InvoiceHistoryTable.tsx`: fetches via `fetchHistory` in useEffect, renders the 6-column table exactly per 07-UI-SPEC (column header style `text-xs font-semibold uppercase tracking-wider text-muted-foreground`, widths and alignments as specified, row hover `hover:bg-muted/50`, container `rounded border border-border overflow-hidden`). Show 5 skeleton rows while loading (`h-4 rounded bg-muted animate-pulse`). Render StatusBadge in the STATUS column and, in the EXPORTDATEI column, a download `<a href={exportFileUrl(filename)} download>` with inline SVG download icon (`text-primary text-sm hover:underline`) when present else an em-dash `<span class="text-muted-foreground">–</span>`. Empty state uses `t('emptyHistory')` heading + `t('emptyHistoryBody')`. Add pagination controls only when total > 25 (per UI-SPEC), wiring `fetchHistory(page, 25)`.
|
||
Create `ExportFileList.tsx`: lists up to 10 export files (derive filenames from history entries' `exportFilename`, newest first, dedup) each as an inline SVG download icon + `<a>` (`text-sm text-primary hover:underline`) using `exportFileUrl`; empty state `t('emptyExports')`.
|
||
Create `page.tsx` (`'use client'`, default export `DkvFleetPage`): `mx-auto max-w-5xl space-y-6 p-6`. Header row: `<h1 class="text-2xl font-semibold tracking-tight">{t('pageTitle')}</h1>` with the "Jetzt prüfen" button right-aligned and the "Zuletzt geprüft" text (`text-sm text-muted-foreground`). "Jetzt prüfen" button uses the exact idle/loading classes from UI-SPEC; on click set loading, call `checkNow()`, on success clear loading and trigger an InvoiceHistoryTable refetch (lift a refresh key or expose a ref/callback), on error show the destructive error banner above the table (`border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive` with a × close button) using `t('errors.pollFailed')`. Render `<h2>Verarbeitungshistorie</h2>` + `<InvoiceHistoryTable />`, then `<h2>Letzte Exportdateien</h2>` + `<ExportFileList />`.
|
||
All copy via `useTranslations('dkvFleet')` keys defined in 07-UI-SPEC (Plan 04 ships these keys). Dark + light mode must both work (use CSS tokens only). Do not introduce any external icon package — inline SVG only.
|
||
</action>
|
||
<verify>
|
||
<automated>pnpm --filter @tessera/web exec tsc --noEmit -p tsconfig.json && grep -q "credentials: 'include'" apps/web/src/lib/dkv-api.ts && grep -q "check-now" apps/web/src/lib/dkv-api.ts && grep -q "useTranslations('dkvFleet')" "apps/web/src/app/(portal)/modules/dkv-fleet/page.tsx"</automated>
|
||
</verify>
|
||
<acceptance_criteria>
|
||
- `dkv-api.ts` exports `checkNow`, `fetchHistory`, `fetchConfig`, `saveConfig`, `testConnection`, vehicle CRUD functions, `importVehiclesCsv`, and `exportFileUrl`; all use `credentials: 'include'`
|
||
- `dkv-api.ts` never declares a returned `password` field on `DkvConfig` (only `hasPassword`)
|
||
- Main page renders an `<h1>` with `t('pageTitle')` and a "Jetzt prüfen" button that calls `checkNow` then refetches history
|
||
- InvoiceHistoryTable renders the 6 columns from UI-SPEC and uses StatusBadge for status
|
||
- Export download links use `exportFileUrl(...)` (server-validated filename) — no client-built path traversal
|
||
- Web type-check / build passes (`tsc --noEmit`)
|
||
</acceptance_criteria>
|
||
<done>The module main page lists processing history + export files and the user can trigger an inbox check from the browser.</done>
|
||
</task>
|
||
|
||
<task type="auto">
|
||
<name>Task 2: Module settings page + InboxConfigForm (Surface B inbox — DKV-01)</name>
|
||
<files>apps/web/src/app/(portal)/modules/dkv-fleet/settings/page.tsx, apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx</files>
|
||
<read_first>
|
||
- apps/web/src/components/settings/calendar-source-form.tsx — form field pattern: `'use client'`, controlled inputs, label `mb-1 block text-sm text-foreground`, input `h-9 w-full max-w-md rounded border border-border bg-background px-3 text-sm`, select pattern, password input, actions row `flex gap-3`, submit `data-testid` + disabled styling, show/hide not present here (add it per UI-SPEC)
|
||
- apps/web/src/app/(portal)/settings/dashboard/page.tsx — settings page heading `mb-6 text-lg font-semibold text-foreground`
|
||
- apps/web/src/lib/dkv-api.ts (Task 1) — fetchConfig / saveConfig / testConnection signatures
|
||
- 07-UI-SPEC.md "Surface B: Module Settings Page" → "Tab 1: Posteingang → InboxConfigForm" — exact field list/order, password show/hide toggle, Active toggle switch, form actions row, inline test feedback colors
|
||
- 07-UI-SPEC.md "Copywriting Contract" + form i18n keys (`form.*`)
|
||
- 07-CONTEXT.md D-01/D-02/D-03 (inbox fields), D-07 (export recipient per module), D-19 (vehicle format string)
|
||
</read_first>
|
||
<action>
|
||
Create `settings/page.tsx` (`'use client'`, default export `DkvFleetSettingsPage`, `useTranslations('dkvFleet')`): heading `<h1 class="text-2xl font-semibold tracking-tight mb-6">{t('settingsTitle')}</h1>`, then render `<InboxConfigForm />`. (Per the checker's route split, vehicle management lives at its own `/modules/dkv-fleet/vehicles` route built in Task 3; add a simple inline link/tab to it here, e.g. a secondary-styled `<Link href="/modules/dkv-fleet/vehicles">{t('tabs.vehicles')}</Link>` styled per the UI-SPEC tab pattern.)
|
||
Create `InboxConfigForm.tsx` (`'use client'`): on mount `fetchConfig()` to populate controlled state; render the fields in the exact order from UI-SPEC Tab 1 — Protokoll (select IMAP/Exchange), Host (text), Port (number), Verschlüsselung (select Keine/STARTTLS/SSL-TLS → values none/starttls/ssl-tls), Ordner (text default "INBOX"), Absenderfilter (email, optional, placeholder rechnung@dkv.com), Abrufintervall (number min=5, suffix "Minuten"), Benutzername (text optional), Passwort (password optional with show/hide toggle button — `type="button"`, inline eye/eye-off SVG, toggles input type between password/text), Export-Empfänger (email required), Fahrzeug-Formatstring (text default `{Marke}/{Modell}/{Kennzeichen}` with help text listing placeholders), Aktiv (toggle switch `role="switch" aria-checked`). Use the calendar-source-form label/input classes. Form actions row `flex gap-3 pt-4 border-t border-border mt-6`: "Verbindung testen" (secondary) → calls `testConnection(currentFormPayload)` and shows inline green/destructive feedback per UI-SPEC; "Einstellungen speichern" (primary) → calls `saveConfig`, disabling the button while saving and showing inline error on failure. Never render the existing password back from the server (config returns `hasPassword`, not the password) — leave the password field blank and only send it when the user types a new one. All copy via `t('form.*')`. Both color themes must work.
|
||
</action>
|
||
<verify>
|
||
<automated>pnpm --filter @tessera/web exec tsc --noEmit -p tsconfig.json && grep -q "saveConfig" "apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx" && grep -q "testConnection" "apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx" && grep -q "role=\"switch\"" "apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx"</automated>
|
||
</verify>
|
||
<acceptance_criteria>
|
||
- Settings page renders `t('settingsTitle')` heading and the InboxConfigForm
|
||
- InboxConfigForm renders all UI-SPEC Tab-1 fields in order, with the show/hide password toggle and the Active `role="switch"` toggle
|
||
- Abrufintervall input enforces `min=5`
|
||
- "Verbindung testen" calls `testConnection`; "Einstellungen speichern" calls `saveConfig`
|
||
- Password field is never pre-filled from server data (only `hasPassword` is known)
|
||
- Web type-check / build passes
|
||
</acceptance_criteria>
|
||
<done>The user can configure and test the inbox connection and save module settings from the browser.</done>
|
||
</task>
|
||
|
||
<task type="auto" tdd="true">
|
||
<name>Task 3: Vehicles page + VehicleTable + CsvImportButton (Surface B vehicles — DKV-03)</name>
|
||
<files>apps/web/src/app/(portal)/modules/dkv-fleet/vehicles/page.tsx, apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.tsx, apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/CsvImportButton.tsx, apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.test.tsx</files>
|
||
<read_first>
|
||
- apps/web/src/app/(portal)/settings/dashboard/calendar/calendar-settings.test.tsx — Vitest+Testing-Library pattern: `vi.mock('next-intl', ...)`, `vi.mock('@/lib/calendar-api', ...)`, dynamic `await import(...)`, `render`, `screen`, `waitFor`, `fireEvent`, `afterEach` cleanup. Mirror this exactly for VehicleTable.test.tsx (mock `@/lib/dkv-api`)
|
||
- apps/web/src/lib/dkv-api.ts (Task 1) — fetchVehicles / createVehicle / updateVehicle / deleteVehicle / importVehiclesCsv
|
||
- apps/web/src/components/settings/calendar-source-form.tsx — input/label/button classes to reuse
|
||
- 07-UI-SPEC.md "Tab 2: Fahrzeuge → VehicleTable + CsvImportButton" — toolbar, columns (KENNZEICHEN/MARKE/MODELL/FAHRER/AKTIONEN), view vs edit row modes, icon button aria-labels + SVG sizes, add-vehicle row, CSV import dialog (merge/replace), destructive replace confirm, success toast
|
||
- 07-UI-SPEC.md "Copywriting Contract" — delete dialog + CSV replace dialog copy; "col.*" header keys
|
||
- apps/web/vitest.config.ts — confirm jsdom env + setup (tests run via `pnpm --filter @tessera/web test`)
|
||
</read_first>
|
||
<behavior>
|
||
VehicleTable.test.tsx (write FIRST — RED):
|
||
- Renders a list of two mocked vehicles (Kennzeichen, Marke, Modell, Fahrer) returned by mocked `fetchVehicles`
|
||
- Shows the empty-state heading (`t('emptyVehicles')` → mocked string) when `fetchVehicles` resolves `[]`
|
||
- Clicking the delete (trash) icon button opens a confirm dialog, and confirming calls `deleteVehicle` with the vehicle id
|
||
- Clicking the edit (pencil) icon button switches the row to inputs; saving calls `updateVehicle` with the id and changed fields
|
||
- Icon-only action buttons expose the UI-SPEC `aria-label`s ("Fahrzeug bearbeiten", "Fahrzeug löschen", "Änderungen speichern", "Bearbeitung abbrechen")
|
||
</behavior>
|
||
<action>
|
||
Write `VehicleTable.test.tsx` first and confirm it fails (RED) before implementing — mirror the calendar-settings.test.tsx mocking approach exactly (mock `next-intl` to echo keys/short strings, mock `@/lib/dkv-api`).
|
||
Create `VehicleTable.tsx` (`'use client'`): on mount `fetchVehicles()`; toolbar `flex justify-between items-center mb-3` with a left count badge (`t('vehicleCount', { count })`) and right `[CsvImportButton]` + "Fahrzeug hinzufügen" (primary) buttons. Table columns per UI-SPEC with the shared header style. Each row has view mode (plain cells + pencil + trash icon buttons) and edit mode (text inputs, Kennzeichen `uppercase`, + checkmark/× icon buttons). All icon-only buttons carry the exact UI-SPEC `aria-label`s and SVG sizes (16×16, inline SVG). "Fahrzeug hinzufügen" appends a new empty edit-mode row. Delete uses an inline confirm dialog with the UI-SPEC delete copy (`deleteVehicle`/`deleteVehicleBody`), confirm button destructive-styled; on confirm call `deleteVehicle(id)` and refetch. Save row calls `createVehicle` (new) or `updateVehicle(id, ...)` (existing); validation: Kennzeichen required, inline `text-destructive text-xs` on error. Wire CsvImportButton's `onImported` to refetch + show a success toast (`importSuccess`).
|
||
Create `CsvImportButton.tsx` (`'use client'`): hidden `<input type="file" accept=".csv">`; the visible "CSV importieren" button triggers it. After a file is selected, show the inline import-mode dialog (Zusammenführen default / Ersetzen) per UI-SPEC; "Ersetzen" → destructive confirm dialog before importing. On confirm call `importVehiclesCsv(file, mode)` then call the `onImported(count)` callback. Validate the selected file is `.csv` client-side; show parse/import errors as `text-destructive text-sm` in the dialog.
|
||
Create `vehicles/page.tsx` (`'use client'`, default export `DkvFleetVehiclesPage`, `useTranslations('dkvFleet')`): heading `<h1 class="text-2xl font-semibold tracking-tight mb-6">{t('tabs.vehicles')}</h1>` (or settingsTitle + subheading), a back link to `/modules/dkv-fleet/settings`, then `<VehicleTable />`. Import VehicleTable/CsvImportButton from `../settings/components/` (UI-SPEC component file map). Both color themes must work; inline SVG icons only.
|
||
</action>
|
||
<verify>
|
||
<automated>pnpm --filter @tessera/web test -- --run VehicleTable && pnpm --filter @tessera/web exec tsc --noEmit -p tsconfig.json</automated>
|
||
</verify>
|
||
<acceptance_criteria>
|
||
- `VehicleTable.test.tsx` exists, mocks `@/lib/dkv-api` + `next-intl`, and passes (was RED before VehicleTable existed)
|
||
- VehicleTable lists vehicles, supports inline edit (calls `updateVehicle`), create (`createVehicle`), and delete with confirm dialog (`deleteVehicle`)
|
||
- All icon-only action buttons carry the UI-SPEC `aria-label`s
|
||
- CsvImportButton offers merge/replace modes and shows a destructive confirm before replace, calling `importVehiclesCsv(file, mode)`
|
||
- `vehicles/page.tsx` renders VehicleTable under a heading with a back link to settings
|
||
- `pnpm --filter @tessera/web test` (VehicleTable) and web type-check pass
|
||
</acceptance_criteria>
|
||
<done>The user can manage the vehicle→driver master list (CRUD + CSV merge/replace) from the browser, covered by a passing Vitest test.</done>
|
||
</task>
|
||
|
||
</tasks>
|
||
|
||
<threat_model>
|
||
## Trust Boundaries
|
||
|
||
| Boundary | Description |
|
||
|----------|-------------|
|
||
| browser form → /dkv/* API | Admin-entered inbox config, credentials, and vehicle/CSV data cross to the backend |
|
||
| API response → React render | Backend-supplied history/vehicle/export data is rendered in the DOM |
|
||
| user file → CSV import | User-selected file uploaded for bulk vehicle import |
|
||
|
||
## STRIDE Threat Register
|
||
|
||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||
|-----------|----------|-----------|-------------|-----------------|
|
||
| T-07-12 | Information Disclosure | InboxConfigForm / dkv-api.ts | mitigate | Password field never pre-filled from server (config returns `hasPassword` only, never the secret — mirrors calendar T-05-09); password sent only when user types a new one; transport is cookie auth via `credentials: 'include'` (V3) |
|
||
| T-07-13 | Tampering | ExportFileList / InvoiceHistoryTable | mitigate | Download URLs built with `exportFileUrl()` + `encodeURIComponent`; the server enforces the `DKV_*.xlsx` filename format and path-traversal guard (Plan 04) — client never constructs raw paths |
|
||
| T-07-14 | Injection (XSS) | InvoiceHistoryTable / VehicleTable | mitigate | All backend strings rendered through React text nodes (auto-escaped); no `dangerouslySetInnerHTML`; StatusBadge tooltip uses the `title` attribute, not raw HTML |
|
||
| T-07-15 | Tampering | CsvImportButton | mitigate | Client restricts selection to `.csv` (`accept=".csv"`) and the replace path requires an explicit destructive confirmation; server-side validation/size limits remain authoritative (Plan 04) |
|
||
| T-07-SC | — | npm/pnpm installs | n/a | No new frontend dependencies are added in this plan (inline SVG icons, existing Vitest/Testing-Library only) |
|
||
</threat_model>
|
||
|
||
<verification>
|
||
- `pnpm --filter @tessera/web exec tsc --noEmit -p tsconfig.json` exits 0
|
||
- `pnpm --filter @tessera/web test -- --run VehicleTable` passes
|
||
- All copy resolves through `useTranslations('dkvFleet')` keys defined in 07-UI-SPEC (shipped by Plan 04)
|
||
- Components honor the 07-UI-SPEC color/typography/spacing tokens in both light and dark mode
|
||
</verification>
|
||
|
||
<success_criteria>
|
||
- DKV-01 inbox-config UI + manual "Jetzt prüfen" trigger operable in the browser
|
||
- DKV-03 vehicle master CRUD + CSV merge/replace import operable, covered by a Vitest test
|
||
- DKV-04 export download links + processing-history display operable
|
||
- All three surfaces follow the 07-UI-SPEC visual contract
|
||
</success_criteria>
|
||
|
||
<output>
|
||
Create `.planning/phases/07-dkv-fleet-module/07-05-SUMMARY.md` when done. Record the InvoiceHistoryTable refetch mechanism chosen, how the password-blank-on-load behavior is implemented, and any UI-SPEC deviations.
|
||
</output>
|