"use client"; import { useState, useEffect, useCallback, type FormEvent } from "react"; import Link from "next/link"; import { TextField } from "@nexarch/shl"; import { search, facets, ApiError, type SearchHit, type AppliedFilter, type FacetsResponse } from "../lib/api"; import { splitHighlighted } from "../lib/highlight"; import { HIGHLIGHT_BG_LIGHT, HIGHLIGHT_FG_LIGHT } from "../lib/highlightColors"; import { ActiveFilterChips, FacetPanel } from "./FacetPanel"; import { addFilter, removeFilter } from "../lib/filterState"; function tenantSlug(): string { return process.env.NEXT_PUBLIC_MAIL_TENANT_SLUG ?? ""; } function Snippet({ text }: { text: string }) { if (!text) { return null; } return ( {splitHighlighted(text).map((segment, idx) => segment.matched ? ( {segment.text} ) : ( {segment.text} ) )} ); } function resultHref(hit: SearchHit): string { const params = new URLSearchParams({ subject: hit.subjectSnippet, snippet: hit.bodySnippet, }); return `/mail/${encodeURIComponent(hit.messageId)}?${params.toString()}#fundstelle`; } export default function SearchPage() { const [query, setQuery] = useState(""); const [appliedFilters, setAppliedFilters] = useState([]); const [hits, setHits] = useState(null); const [facetsData, setFacetsData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const runSearch = useCallback( async (q: string, filters: AppliedFilter[]) => { if (!q.trim() && filters.length === 0) { setHits(null); setFacetsData(null); setError(null); return; } setLoading(true); setError(null); try { const [searchResult, facetsResult] = await Promise.all([ search(tenantSlug(), q, filters), facets(tenantSlug(), q, filters), ]); setHits(searchResult.hits); setFacetsData(facetsResult); } catch (err) { // Akzeptanzkriterium 3 (SRC-04, sinngemäß auf Fehlerfall // übertragen): auch ein Suchfehler zeigt einen verständlichen // Hinweis statt einer leeren Fläche. setHits([]); setFacetsData(null); setError(err instanceof ApiError ? err.message : "Suche konnte nicht ausgeführt werden."); } finally { setLoading(false); } }, [] ); function onSubmit(e: FormEvent) { e.preventDefault(); void runSearch(query, appliedFilters); } // SRC-06 Akzeptanzkriterium 2: Trefferzahl je Facettenwert live // aktualisiert — jede Filteränderung löst Such- UND Facettenabfrage // erneut aus. useEffect(() => { void runSearch(query, appliedFilters); // eslint-disable-next-line react-hooks/exhaustive-deps }, [appliedFilters]); function onAddFilter(filter: AppliedFilter) { setAppliedFilters((prev) => addFilter(prev, filter)); } function onRemoveFilter(filter: AppliedFilter) { setAppliedFilters((prev) => removeFilter(prev, filter)); } function onClearFilters() { setAppliedFilters([]); } return (

Mail-Suche

setQuery(e.target.value)} placeholder='z. B. "Quartalsbericht" oder Umsatz -Verlust' /> {error && (

{error}

)} {hits !== null && hits.length === 0 && !error && (

Keine Treffer für diese Suchanfrage.

)} {hits !== null && hits.length > 0 && (
    {hits.map((hit) => (
  • ))}
)}
); }