'use client'; import { Suspense, useState } from 'react'; import { useTranslations } from 'next-intl'; import { useRouter, useSearchParams } from 'next/navigation'; import Link from 'next/link'; import { CoverageBanner } from './components/CoverageBanner'; import { FilterPanel } from './components/FilterPanel'; import { PollNowButton } from './components/PollNowButton'; import { ResultsList } from './components/ResultsList'; import { SavedSearchBar } from './components/SavedSearchBar'; import { TenderDetail } from './components/TenderDetail'; /** * Ausschreibungs-Radar module page within the portal. * * Replaces the Phase 10 placeholder (CONFIG-01 MVP stub) with the real * Trefferliste (Plan 11-01): CoverageBanner explains data coverage, * FilterPanel writes filter/sort state into the URL, ResultsList reads it * and renders the ~1671 existing DÖE tenders. * * Plan 11-04 (UI-02, D-07): the `?tender=` 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. * * Plan 11-06 (FILTER-06, D-08/D-11): SavedSearchBar renders above * FilterPanel — it reads/writes the same URL searchParams FilterPanel * uses, so saving/loading a profile is congruent with the filter state the * user currently sees. * * Plan 14-05 (CONFIG-03/UI-06, D-10): all strings render via the * tenderRadar i18n namespace — no more hardcoded German literals. * * Quick 260723-lvg: PollNowButton sits next to the settings gear and * bumps `refreshKey` on a successful poll, driving a ResultsList refetch — * same parent-increments-refreshKey pattern DKV uses for * InvoiceHistoryTable (STATE decision 07-05). */ function TenderRadarContent() { const t = useTranslations('tenderRadar'); const router = useRouter(); const searchParams = useSearchParams(); const tenderId = searchParams.get('tender'); const [refreshKey, setRefreshKey] = useState(0); const closeDetail = () => { const next = new URLSearchParams(searchParams.toString()); next.delete('tender'); router.replace(`?${next.toString()}`); }; return (

{t('page.title')}

{/* Right-side cluster: settings gear + "Jetzt abrufen" trigger */}
{/* * Zahnrad-Link. Absolute literal href (NOT the dynamic * [category]/[moduleSlug] basePath): tender-radar routes only * exist as literal paths — the dynamic settings route returns * "Modul nicht gefunden" for this module, so the gear must point * at the literal path directly. * * Phase 17 Plan 03 (D-03): points at "Meine Quellen", not the * admin-only settings page — that's the page every module user * (not just administrators) is allowed to configure something * on; administrators reach the platform-wide settings from there * with one more click. */} setRefreshKey((k) => k + 1)} />
{tenderId && }
); } function TenderRadarFallback() { const t = useTranslations('tenderRadar'); return (

{t('page.loading')}

); } export default function TenderRadarPage() { return ( }> ); }