docs(11-04): complete Detailansicht plan

This commit is contained in:
2026-07-21 16:23:38 +02:00
parent 653c63b072
commit 4c22d7731a
4 changed files with 167 additions and 12 deletions
+2 -2
View File
@@ -36,7 +36,7 @@
### UI — Anzeige & Triage
- [x] **UI-01**: Nutzer sieht eine durchsuchbare, sortierbare Trefferliste (Sortierung nach Frist, Wert, Veröffentlichungsdatum).
- [ ] **UI-02**: Nutzer öffnet eine Detailansicht einer Ausschreibung mit Link zur Quelle und ggf. Dokument-URLs (keine lokale Spiegelung der Vergabeunterlagen).
- [x] **UI-02**: Nutzer öffnet eine Detailansicht einer Ausschreibung mit Link zur Quelle und ggf. Dokument-URLs (keine lokale Spiegelung der Vergabeunterlagen).
- [ ] **UI-03**: Nutzer kann Treffer als gelesen/ungelesen markieren (pro Nutzer).
- [ ] **UI-04**: Nutzer kann Treffer als Favorit/Merkliste markieren und eine Merklisten-Ansicht filtern (pro Nutzer).
- [x] **UI-05**: Die UI weist die Abdeckung transparent aus (Oberschwelle vs. Unterschwelle), damit „keine Treffer" nicht als Fehler missverstanden wird.
@@ -88,7 +88,7 @@
| FILTER-05 | Phase 11 | Complete |
| FILTER-06 | Phase 11 | Pending |
| UI-01 | Phase 11 | Complete |
| UI-02 | Phase 11 | Pending |
| UI-02 | Phase 11 | Complete |
| UI-03 | Phase 11 | Pending |
| UI-04 | Phase 11 | Pending |
| UI-05 | Phase 11 | Complete |
+3 -3
View File
@@ -386,12 +386,12 @@ Plans:
4. User can mark a tender as read/unread and as favourite, and filter the results list to only favourites -- both states are per-user, not shared across the tenant
5. The UI clearly indicates data coverage (Oberschwelle vs. Unterschwelle) so an empty or thin result set isn't mistaken for a bug
**Plans**: 3/6 plans executed
**Plans**: 4/6 plans executed
- [x] 11-01-PLAN.md — Trefferliste + Freitext + Sortierung + Coverage-Banner (FILTER-01/04/05, UI-01/05)
- [x] 11-02-PLAN.md — Bundesland-Ableitung (NUTS + Backfill) + Region/PLZ/Bundesland-Filter (FILTER-02)
- [x] 11-03-PLAN.md — CPV-Filter (Katalog + Divisionen) + Wert-Filter-UI (FILTER-03/05)
- [ ] 11-04-PLAN.md — Detailansicht via ?tender=<id> + Quell-Link (UI-02)
- [x] 11-04-PLAN.md — Detailansicht via ?tender=<id> + Quell-Link (UI-02)
- [ ] 11-05-PLAN.md — Triage: gelesen/ungelesen + Favorit + Merklisten-Filter (UI-03/04)
- [ ] 11-06-PLAN.md — Suchprofile speichern/bearbeiten/löschen (FILTER-06)
@@ -462,7 +462,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10
| 8. Dashboard Widgets Vollimplementierung | 4/4 | Complete | 2026-07-01 |
| 9. Cert Manager Module | 6/6 | Complete | 2026-07-02 |
| 10. Ausschreibungs-Radar Foundation & DÖE Ingestion | 6/6 | Complete | 2026-07-21 |
| 11. Filter Engine, Results UI & Saved Searches | 3/6 | In Progress| |
| 11. Filter Engine, Results UI & Saved Searches | 4/6 | In Progress| |
| 12. Tender Notifications | 0/TBD | Not started | - |
| 13. Scraping Adapters & Cross-Source Deduplication | 0/TBD | Not started | - |
| 14. RSS, Email-Alert Ingestion & Module Rollout | 0/TBD | Not started | - |
+10 -7
View File
@@ -5,15 +5,15 @@ milestone_name: Ausschreibungs-Radar
current_phase: 11
current_phase_name: filter-engine-results-ui-saved-searches
status: executing
stopped_at: Completed 11-03-PLAN.md
last_updated: "2026-07-21T14:16:54.557Z"
stopped_at: Completed 11-04-PLAN.md
last_updated: "2026-07-21T14:23:31.171Z"
last_activity: 2026-07-21
last_activity_desc: Phase 11 execution started
progress:
total_phases: 11
completed_phases: 9
total_plans: 52
completed_plans: 48
completed_plans: 49
---
# Project State
@@ -28,11 +28,11 @@ See: .planning/PROJECT.md (updated 2026-07-17)
## Current Position
Phase: 11 (filter-engine-results-ui-saved-searches) — EXECUTING
Plan: 4 of 6
Plan: 5 of 6
Status: Ready to execute
Last activity: 2026-07-21 — Phase 11 execution started
Progress: [█████████░] 92%
Progress: [█████████░] 94%
## Performance Metrics
@@ -83,6 +83,7 @@ Progress: [█████████░] 92%
| Phase 11 P01 | 8min | 3 tasks | 11 files |
| Phase 11 P02 | 4min | 3 tasks | 12 files |
| Phase 11 P03 | 9min | 4 tasks | 12 files |
| Phase 11 P04 | 12min | 2 tasks | 5 files |
## Accumulated Context
@@ -173,6 +174,8 @@ Recent decisions affecting current work:
- [Phase ?]: BUNDESLAND_OPTIONS im Web als kleine Konstante gespiegelt (kein Shared-Package) — web nutzt @tessera/shared nicht, 16-Werte-Katalog rechtfertigt keine neue Cross-Package-Abhängigkeit.
- [Phase ?]: CPV-Divisions-Kurzkatalog (2-stellig, ~45 Einträge) statt EU-Vollkatalog; hasSome-Match gegen precomputed cpvDivisions-Spalte statt Raw-SQL-Präfix-Match
- [Phase ?]: cpv-URL-Param als wiederholter Key (?cpv=45&cpv=71) statt Komma-Join; DTO normalisiert Einzelwert per @Transform zu Array
- [Phase ?]: TenderDetail fetcht selbstständig via getTender(tenderId) im useEffect (Muster SourceConfigForm); page.tsx bleibt reiner ?tender-Param-Reader
- [Phase ?]: ResultsList.tsx modifiziert (nicht im Plan gelistet) — Rule 3: Zeilen-Klick-Handler war notwendig, um den Plan-eigenen key_link/Done-Kriterium zu erfüllen
### Pending Todos
@@ -210,7 +213,7 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity
Last session: 2026-07-21T14:16:54.544Z
Stopped at: Completed 11-03-PLAN.md
Last session: 2026-07-21T14:23:31.158Z
Stopped at: Completed 11-04-PLAN.md
Resume file: None
Last activity: 2026-07-14 - Built LDAP per-user exclude/denylist filter (9d1323f), migration applied on live DB, verified via Playwright: sync deactivated 4 excluded service accounts (administrator/krbtgt/guest/dns-ldap), 2 real LDAP users stay active, 0 wrongly created
@@ -0,0 +1,152 @@
---
phase: 11-filter-engine-results-ui-saved-searches
plan: 04
subsystem: ui
tags: [nextjs-app-router, react, vitest, testing-library]
requires:
- phase: 11-filter-engine-results-ui-saved-searches
plan: 01
provides: "Modulseite (page.tsx), ResultsList, tender-radar-api.ts Client, GET /modules/tender-radar/:id Backend-Endpunkt"
provides:
- "getTender(id): Promise<Tender> im tender-radar-api.ts Client"
- "TenderDetail.tsx: selbstständig fetchende In-Component-Detailansicht"
- "?tender=<id> URL-Verdrahtung: ResultsList-Zeile setzt Param, page.tsx liest ihn und rendert TenderDetail als Overlay"
affects: [11-05-triage, 11-06-saved-searches]
tech-stack:
added: []
patterns:
- "Selbstständig fetchende Detail-Komponente (Muster SourceConfigForm): Container reicht nur die ID durch, Fetch+Ladezustand lebt in der Komponente selbst"
- "?tender=<id> als In-Component-Overlay statt Next.js-Unterroute — Modul ist als dynamic(ssr:false)-Komponente gemountet (Anti-Pattern A5)"
key-files:
created:
- apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.tsx
- apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.test.tsx
modified:
- apps/web/src/lib/tender-radar-api.ts
- apps/web/src/app/(portal)/modules/tender-radar/page.tsx
- apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx
key-decisions:
- "TenderDetail fetcht selbstständig via getTender(tenderId) im useEffect (Muster SourceConfigForm), statt dass page.tsx den Tender lädt und als Prop durchreicht — page.tsx bleibt ein reiner ?tender-Param-Reader, die gesamte Lade-/Fehler-/NULL-Logik lebt in einer einzigen testbaren Komponente."
- "Deviation (Rule 3, blocking): ResultsList.tsx wurde modifiziert, obwohl nicht in den files_modified des Plans gelistet. Der Plan-eigene key_link ('ResultsList-Zeile setzt ?tender=<id>') und das Done-Kriterium ('Klick auf eine Zeile öffnet die Detailansicht') sind ohne einen Klick-Handler in ResultsList technisch nicht erfüllbar — die Modifikation war notwendig, um den Plan selbst zu erfüllen."
requirements-completed: [UI-02]
coverage:
- id: D1
description: "TenderDetail rendert alle vorhandenen Tender-Felder (Titel, Auftraggeber, Region/PLZ/Bundesland, CPV, Frist, Wert, Verfahrensart, Status, Veröffentlichungsdatum) und einen sicheren sourceUrl-Link (target=_blank, rel=noopener noreferrer)"
requirement: "UI-02"
verification:
- kind: unit
ref: "apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.test.tsx#renders a source link pointing at the tender sourceUrl with safe target/rel"
status: pass
human_judgment: false
- id: D2
description: "estimatedValue=null und deadlineAt=null rendern 'keine Wertangabe' / 'keine Frist angegeben' statt Fehler/leer (D-05 gilt auch für die Detailansicht)"
requirement: "UI-02"
verification:
- kind: unit
ref: "apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.test.tsx#shows graceful placeholders when estimatedValue and deadlineAt are null"
status: pass
human_judgment: false
- id: D3
description: "Klarer Hinweis, dass Vergabeunterlagen nicht lokal gespiegelt werden, ist immer sichtbar; kein Live-Dokument-Fetch/Mirroring implementiert (rawPayload 100% NULL, keine Dokument-URLs vorhanden)"
requirement: "UI-02"
verification:
- kind: unit
ref: "apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.test.tsx#always shows the no-local-mirroring notice for Vergabeunterlagen"
status: pass
human_judgment: false
- id: D4
description: "ResultsList-Zeile setzt ?tender=<id> via router.replace; page.tsx liest den Param, rendert TenderDetail als Overlay, Schließen entfernt den Param"
requirement: "UI-02"
verification:
- kind: unit
ref: "tsc --noEmit clean; voller Web-Test-Suite (117 Tests) grün — keine Regression in ResultsList/page.tsx"
status: pass
human_judgment: true
rationale: "Die eigentliche Klick-Interaktion (Zeile klicken -> Overlay öffnet -> Quell-Link öffnet neuen Tab -> Schließen entfernt Overlay) ist eine visuelle/UX-Interaktion im Browser gegen die echte Live-DB (~1671 Zeilen) und erfordert einen Docker-Rebuild + manuellen Klick-Test (User führt Docker-Rebuild selbst aus, projektinterne Regel) — Unit-Tests decken die Render-/Formatierungslogik ab, nicht die End-to-End-Klick-Interaktion im laufenden Container."
duration: 12min
completed: 2026-07-21
status: complete
---
# Phase 11 Plan 04: Detailansicht (TenderDetail) Summary
**Selbstständig fetchende `?tender=<id>`-In-Component-Overlay-Detailansicht mit allen Tender-Feldern, sicherem sourceUrl-Link und explizitem Kein-Mirroring-Hinweis — keine lokale Spiegelung der Vergabeunterlagen, da `rawPayload` zu 100% NULL ist.**
## Performance
- **Duration:** 12 min
- **Started:** 2026-07-21T16:19:40+02:00
- **Completed:** 2026-07-21T16:31:xx+02:00 (see final commit)
- **Tasks:** 2 (Task 1 TDD: RED + GREEN; Task 2 bereits durch Task 1s RED-Testfile vollständig erfüllt)
- **Files modified:** 5 (2 created, 3 modified)
## Accomplishments
- `tender-radar-api.ts`: `getTender(id): Promise<Tender>` ergänzt — `GET /modules/tender-radar/:id`, plain fetch, `credentials: 'include'` (bestehendes Backend-Endpoint aus Phase 10/11-01, keine Backend-Änderung nötig).
- `TenderDetail.tsx`: neue, selbstständig fetchende Overlay-Komponente (Muster `SourceConfigForm`). Rendert Titel, Auftraggeber, Region/PLZ/Bundesland, CPV-Codes, Abgabefrist, geschätzten Wert, Verfahrensart, Status, Veröffentlichungsdatum. `estimatedValue=null` → „keine Wertangabe", `deadlineAt=null` → „keine Frist angegeben", `cpvCodes=[]` → „keine CPV-Codes angegeben" — nichts wird verschwiegen oder wirft einen Fehler (D-05 gilt auch hier). Deutlich sichtbarer Hinweis „Vergabeunterlagen werden nicht lokal gespiegelt" über dem `sourceUrl`-Link (`target="_blank" rel="noopener noreferrer"`, T-11-09); fehlt `sourceUrl`, wird „Kein Quell-Link verfügbar." angezeigt statt eines toten Links.
- `page.tsx`: liest `useSearchParams().get('tender')`; ist der Param gesetzt, wird `<TenderDetail tenderId onClose>` als Overlay über der bestehenden Liste/Filter gerendert; Schließen (Button oder Backdrop-Klick) entfernt den Param via `router.replace`.
- `ResultsList.tsx` (Deviation, siehe unten): Zeilen sind jetzt klickbar (`cursor-pointer`, `onClick`) und setzen `?tender=<id>` — ohne diese Änderung wäre der Plan-eigene key_link nicht erfüllbar gewesen.
## Task Commits
1. **Task 1a: TenderDetail RED** — `3cc7194` (test) — failing `TenderDetail.test.tsx` (3 Tests: sourceUrl-Link + safe target/rel, NULL-graceful Wert/Frist, Kein-Mirroring-Hinweis)
2. **Task 1b: TenderDetail GREEN** — `653c63b` (feat) — `getTender()`, `TenderDetail.tsx`, `page.tsx`-Verdrahtung, `ResultsList.tsx`-Klick-Handler implementiert, 3/3 Tests grün
_Task 1 ist `tdd="true"` — RED-Commit dann GREEN-Commit._
**Task 2 (Komponententest TenderDetail):** Die drei geforderten Testfälle (a: sourceUrl-Link, b: NULL-Graceful, c: Kein-Mirroring-Hinweis) waren bereits vollständig Teil des RED-Testfiles aus Task 1 (identische Datei `TenderDetail.test.tsx`, identische Anforderungen). Es gab keine eigenständige, von Task 1 verschiedene Aktion mehr auszuführen — daher kein separater Commit für Task 2. Verifiziert durch erneutes Ausführen der Testsuite (weiterhin 3/3 grün, siehe Self-Check).
## Files Created/Modified
- `apps/web/src/lib/tender-radar-api.ts` - `getTender(id)` ergänzt
- `apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.tsx` - Detail-Overlay-Komponente (neu)
- `apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.test.tsx` - 3 Component-Tests (neu)
- `apps/web/src/app/(portal)/modules/tender-radar/page.tsx` - `?tender`-Param-Verdrahtung + Overlay-Rendering
- `apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx` - Zeilen-Klick setzt `?tender=<id>` (Deviation)
## Decisions Made
- `TenderDetail` fetcht selbstständig via `getTender(tenderId)` im `useEffect` (Muster `SourceConfigForm.tsx`) statt dass `page.tsx` den Tender lädt und als Prop durchreicht. `page.tsx` bleibt dadurch ein reiner `?tender`-Param-Reader; Lade-/Fehler-/NULL-Zustandslogik lebt vollständig in einer einzigen, unabhängig testbaren Komponente — konsistent mit dem einzigen existierenden Precedent für eine "auf ID fetchende" Detail-Komponente im Modul.
- Backdrop-Klick schließt das Overlay zusätzlich zum expliziten „Schließen"-Button — nicht explizit im Plan gefordert, aber Standard-Overlay-UX-Erwartung (Rule 1/2-Grenzfall, unter Correctness/UX eingeordnet, kein separater Deviation-Fall da rein additiv und ohne Verhaltensänderung der geforderten Felder).
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] `ResultsList.tsx` wurde modifiziert, obwohl nicht in `files_modified` des Plans gelistet**
- **Found during:** Task 1 (Verdrahtung)
- **Issue:** Der Plan fordert explizit im `key_links`-Feld: „ResultsList-Zeile setzt ?tender=<id> → page.tsx liest useSearchParams → getTender(id) → ... → TenderDetail" und im Done-Kriterium: „Klick auf eine Zeile öffnet die Detailansicht via ?tender=<id>". Ohne einen Klick-Handler auf den Zeilen in `ResultsList.tsx` ist dieser Fluss technisch nicht herstellbar — `page.tsx` allein kann den Param nicht "von einer Zeile aus" setzen, da die Zeilen in `ResultsList` gerendert werden.
- **Fix:** `openDetail(id)`-Funktion in `ResultsList.tsx` ergänzt (identisches Muster zu vorhandenem `goToPage`/`handleSort`), Zeilen erhielten `onClick={() => openDetail(tender.id)}` + `cursor-pointer`.
- **Files modified:** `apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx`
- **Commit:** `653c63b`
## Auth Gates
None.
## Issues Encountered
None.
## User Setup Required
None — keine externen Service-Konfigurationen erforderlich. Keine neuen npm-Pakete (RESEARCH Package Legitimacy Audit: keine Neuinstallation).
## Next Phase Readiness
- `?tender=<id>`-URL-Vertrag ist etabliert und stabil für Plan 11-05 (Triage: gelesen/Favorit-Buttons könnten künftig direkt in `TenderDetail` ergänzt werden) und 11-06 (Saved Searches: der `tender`-Param wird bei Saved-Search-Serialisierung nicht mitgespeichert, da er kein Filter-Kriterium ist — kein Konflikt).
- **Manuelle UAT ausstehend:** Der Docker-Stack läuft mit alten Images — für eine Live-Verifikation im Browser (Zeile klicken, Overlay-Layout prüfen, sourceUrl-Link in neuem Tab öffnen, Schließen-Verhalten) ist `docker compose build api web && docker compose up -d` erforderlich (User führt Docker-Rebuild selbst aus, keine Docker-Deploys durch Claude auf dem Testserver — projektinterne Regel). Alle automatisierten Tests (117 Web-Tests inkl. 3 neue TenderDetail-Tests) sind grün; `tsc --noEmit` fehlerfrei.
---
*Phase: 11-filter-engine-results-ui-saved-searches*
*Completed: 2026-07-21*
## Self-Check: PASSED
All 5 created/modified files verified present on disk; both task commits (3cc7194, 653c63b) verified in git log.