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:
@@ -1,9 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { Suspense } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { CoverageBanner } from './components/CoverageBanner';
|
||||
import { FilterPanel } from './components/FilterPanel';
|
||||
import { ResultsList } from './components/ResultsList';
|
||||
import { TenderDetail } from './components/TenderDetail';
|
||||
|
||||
/**
|
||||
* Ausschreibungs-Radar module page within the portal.
|
||||
@@ -13,16 +15,34 @@ import { ResultsList } from './components/ResultsList';
|
||||
* FilterPanel writes filter/sort state into the URL, ResultsList reads it
|
||||
* and renders the ~1671 existing DÖE tenders.
|
||||
*
|
||||
* `useSearchParams()` (used by FilterPanel/ResultsList) requires a
|
||||
* Suspense boundary per the Next.js App Router contract — wrapped here
|
||||
* even though this component is loaded via `dynamic(..., { ssr: false })`
|
||||
* (module-loader.ts), per the plan's explicit Next 16 guidance.
|
||||
* Plan 11-04 (UI-02, D-07): the `?tender=<id>` URL param drives an
|
||||
* in-component detail view (TenderDetail) rendered as an overlay on top
|
||||
* of the list — deliberately NOT a separate Next.js sub-route, since this
|
||||
* page is mounted via `dynamic(..., { ssr: false })` under
|
||||
* `[category]/[moduleSlug]` (Anti-Pattern/Assumption A5). A row click in
|
||||
* ResultsList sets the param; closing the detail removes it.
|
||||
*
|
||||
* `useSearchParams()` (used by FilterPanel/ResultsList/this component)
|
||||
* requires a Suspense boundary per the Next.js App Router contract —
|
||||
* wrapped here even though this component is loaded via
|
||||
* `dynamic(..., { ssr: false })` (module-loader.ts), per the plan's
|
||||
* explicit Next 16 guidance.
|
||||
*
|
||||
* Intentional hardcoded German strings: full i18n (next-intl message
|
||||
* keys) is CONFIG-03, planned for Phase 14 (Research Open Question 3,
|
||||
* RESOLVED).
|
||||
*/
|
||||
function TenderRadarContent() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const tenderId = searchParams.get('tender');
|
||||
|
||||
const closeDetail = () => {
|
||||
const next = new URLSearchParams(searchParams.toString());
|
||||
next.delete('tender');
|
||||
router.replace(`?${next.toString()}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl space-y-6 p-6">
|
||||
<div>
|
||||
@@ -34,6 +54,8 @@ function TenderRadarContent() {
|
||||
<CoverageBanner />
|
||||
<FilterPanel />
|
||||
<ResultsList />
|
||||
|
||||
{tenderId && <TenderDetail tenderId={tenderId} onClose={closeDetail} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user