--- phase: 07-dkv-fleet-module plan: 05 subsystem: dkv-frontend tags: [dkv, nextjs, react, frontend, crud, tdd, api-client, i18n] dependency_graph: requires: [DkvController REST surface (07-04), dkvFleet i18n namespace (07-04), CalendarSourceForm pattern (05-02)] provides: [dkv-api.ts typed client, DkvFleetPage (Surface A), InboxConfigForm (Surface B Tab 1), VehicleTable + CsvImportButton (Surface B Tab 2)] affects: - 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/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 - apps/web/src/app/(portal)/modules/dkv-fleet/vehicles/page.tsx tech_stack: added: [] patterns: - "refreshKey lift: page.tsx increments refreshKey on checkNow success; InvoiceHistoryTable reruns useEffect" - "onItemsLoaded callback: InvoiceHistoryTable notifies parent of loaded items; parent passes to ExportFileList" - "T-07-12 password-blank-on-load: fetchConfig returns hasPassword boolean only; password field always blank; sent only when user types" - "T-07-13 exportFileUrl: pure helper using encodeURIComponent; no client-built raw paths" - "T-07-14 XSS: all backend strings via React text nodes; title attribute for badge tooltips (no dangerouslySetInnerHTML)" - "T-07-15 CsvImportButton: accept=.csv, replace mode requires explicit destructive confirm before importVehiclesCsv" - "TDD RED/GREEN: VehicleTable.test.tsx committed before VehicleTable.tsx; bug fixed (mockResolvedValueOnce vs mockResolvedValue)" key_files: created: - 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/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 - apps/web/src/app/(portal)/modules/dkv-fleet/vehicles/page.tsx modified: [] decisions: - "InvoiceHistoryTable refetch: refreshKey prop (number) incremented by parent on checkNow success — triggers useEffect re-run; simpler than ref/callback" - "ExportFileList data: derives filenames from history items via onItemsLoaded callback from InvoiceHistoryTable; avoids second fetch" - "Password blank on load: configToForm() always sets password=''; placeholder shows bullet dots as UX hint; T-07-12 compliant" - "VehicleTable test mock fix: mockResolvedValueOnce for initial load (Hans Müller), mockResolvedValue for refetch (Max Mustermann)" - "CsvImportButton replace confirm: inline two-step (click Importieren → show confirm block → click Ersetzen) not a full modal; per UI-SPEC" - "Vehicles route split: settings/page.tsx has tab bar; Fahrzeuge tab is a to /modules/dkv-fleet/vehicles (own route); VehicleTable imported from settings/components/" metrics: duration: 8min completed: "2026-06-26T22:41:27Z" tasks: 3 files_created: 11 files_modified: 0 --- # Phase 07 Plan 05: DKV Fleet Module Frontend — Summary Typed fetch client (dkv-api.ts) and all three UI surfaces delivered: Surface A (module main page with history table + export list + "Jetzt prüfen" trigger), Surface B Tab 1 (InboxConfigForm with show/hide password and Active toggle), and Surface B Tab 2 (VehicleTable CRUD with CsvImportButton merge/replace). All STRIDE threat mitigations from the plan are implemented. VehicleTable covered by 5 passing Vitest tests (TDD RED→GREEN). ## Tasks Completed | Task | Name | Commit | Key Files | |------|------|--------|-----------| | 1 | dkv-api client + StatusBadge + Surface A main page | 2fe7bca | dkv-api.ts, page.tsx, StatusBadge, InvoiceHistoryTable, ExportFileList | | 2 | Settings page + InboxConfigForm (Surface B Tab 1) | 45e9a35 | settings/page.tsx, InboxConfigForm.tsx | | 3 (RED) | VehicleTable failing tests | c739d27 | VehicleTable.test.tsx | | 3 (GREEN) | VehicleTable + CsvImportButton + vehicles/page | 4d81b8b | VehicleTable.tsx, CsvImportButton.tsx, vehicles/page.tsx | ## Architecture Notes ### InvoiceHistoryTable Refetch Mechanism The page holds a `refreshKey` state (number). After a successful `checkNow()` call, the page increments `refreshKey` by 1. InvoiceHistoryTable receives `refreshKey` as a prop and includes it in its `useEffect` dependency array — this triggers a fresh `fetchHistory()` call. Additionally, InvoiceHistoryTable accepts an `onItemsLoaded(items)` callback. After each fetch, it calls this with the loaded items. The page stores these items in `historyItems` state and passes them to `ExportFileList`, which derives the 10 most recent unique export filenames from them. This avoids a second network request for export files. ### Password Blank on Load (T-07-12) `configToForm()` always maps the server response to `password: ''` — the password field is never pre-filled. The server only returns `hasPassword: boolean`. The password field shows `placeholder="••••••••"` as a UX hint when `hasPassword` is true (inferred from the config). The password is only included in the `saveConfig` payload when `form.password !== ''`. ### TDD Gate Compliance - RED commit: `c739d27` (VehicleTable.test.tsx only, fails with import error) - GREEN commit: `4d81b8b` (VehicleTable.tsx + CsvImportButton.tsx + fix) - Tests: 5 passed ✓ ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] VehicleTable test: mockResolvedValue sequencing bug** - **Found during:** Task 3 GREEN phase (first test run) - **Issue:** The edit-row test called `mockFetchVehicles.mockResolvedValue([Hans])` then immediately `mockFetchVehicles.mockResolvedValue([Max])`. The second call overwrote the first, making the initial component mount return Max Mustermann instead of Hans Müller. `waitFor(() => getByText('Hans Müller'))` timed out. - **Fix:** Changed the first `mockResolvedValue` to `mockResolvedValueOnce` so the initial mount uses Hans Müller; subsequent calls (refetch after update) use the Max Mustermann value. - **Files modified:** VehicleTable.test.tsx - **Commit:** 4d81b8b (included in GREEN task commit) ## Verification Results - `pnpm --filter @tessera/web exec tsc --noEmit -p tsconfig.json` exits 0: PASS - `pnpm --filter @tessera/web test -- --run VehicleTable`: 5/5 passed - `grep -q "credentials: 'include'"` dkv-api.ts: PASS - `grep -q "check-now"` dkv-api.ts: PASS - `grep -q "useTranslations('dkvFleet')"` page.tsx: PASS - `grep -q "role=\"switch\""` InboxConfigForm.tsx: PASS - `grep -q "vi.mock"` VehicleTable.test.tsx: PASS - All 11 files present on disk: PASS ## Known Stubs None. All API client functions are fully wired to real `/dkv/*` endpoints (Plan 04). All UI interactions call the corresponding dkv-api functions. The module is fully operable once the backend containers are running. ## Threat Flags None. All T-07-12 to T-07-15 mitigations from the plan's threat register are implemented: - T-07-12: Password blank-on-load in configToForm(); hasPassword-only interface field - T-07-13: exportFileUrl() + encodeURIComponent; no client-built paths - T-07-14: All backend strings via React text nodes; StatusBadge uses title attribute - T-07-15: accept=".csv" on hidden file input; replace requires explicit two-step confirm ## TDD Gate Compliance - RED gate commit: c739d27 ✓ (test(07-05): add failing VehicleTable tests) - GREEN gate commit: 4d81b8b ✓ (feat(07-05): VehicleTable + CsvImportButton + vehicles page) - All tests pass: 5/5 ✓ ## Self-Check: PASSED