feat(11-04): TenderDetail component with ?tender=<id> wiring

Adds getTender() to the api client and a self-fetching TenderDetail
overlay (pattern: SourceConfigForm) rendering all Tender fields plus a
safe (rel=noopener noreferrer, target=_blank) sourceUrl link. No local
mirroring of Vergabeunterlagen — rawPayload is 100% NULL in the live DB
(research finding), so only the source link exists; NULL value/deadline
render graceful German placeholders (D-05 applies to the detail view too).

page.tsx reads ?tender=<id> via useSearchParams and renders TenderDetail
as an overlay; closing removes the param. ResultsList row clicks set the
param (deviation: ResultsList.tsx was not listed in the plan's
files_modified but is required by the plan's own done-criteria/key_link
"ResultsList-Zeile setzt ?tender=<id>" — Rule 3 auto-fix, blocking).

3/3 TenderDetail tests pass; full web suite (117 tests) and tsc --noEmit
both clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-21 16:21:54 +02:00
parent 3cc7194d9e
commit 653c63b072
4 changed files with 269 additions and 5 deletions
+15
View File
@@ -136,3 +136,18 @@ export async function fetchCoverage(): Promise<CoverageResponse> {
if (!res.ok) throw new Error('Failed to fetch tender-radar coverage');
return res.json();
}
/**
* Fetch a single tender's full detail (Plan 11-04, UI-02/D-07) for the
* `?tender=<id>` in-component detail view. `rawPayload` is 100% NULL in
* the live DB (Research) — no stored document URLs exist, so the detail
* view only ever links to `sourceUrl`; there is no live document fetch.
* GET /modules/tender-radar/:id
*/
export async function getTender(id: string): Promise<Tender> {
const res = await fetch(`${API_URL}/modules/tender-radar/${id}`, {
credentials: 'include',
});
if (!res.ok) throw new Error('Failed to fetch tender detail');
return res.json();
}