import type { Metadata } from 'next' import Link from 'next/link' import { db } from '@/lib/db' import { requireUser } from '@/lib/auth/current' import { parseFilters, toSearchParams, SEARCH_KINDS } from '@/lib/search/query' import { searchCases, searchDocuments, searchPersonnel } from '@/lib/search/service' import { builtinViews } from '@/lib/search/views' import { de } from '@/lib/i18n/de' import { StampMark, classificationEdgeStyle } from '@/components/StampMark' import { StatusTags } from '@/components/StatusTags' import { SearchBox } from './SearchBox' import { SavedViews } from './SavedViews' export const metadata: Metadata = { title: de.search.heading } export const dynamic = 'force-dynamic' /** * Suchseite. * * Alles läuft über Adressparameter: das macht jeden Zustand teilbar, den * Zurück-Knopf richtig und eine gespeicherte Ansicht zu dem, was sie ist — * ein Lesezeichen. Siehe lib/search/query.ts. */ export default async function SearchPage({ searchParams, }: { searchParams: Promise> }) { const user = await requireUser() const roh = await searchParams const filters = parseFilters( Object.fromEntries( Object.entries(roh).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v]), ), ) const [levels, gespeichert] = await Promise.all([ db.clearanceLevelConfig.findMany({ orderBy: { level: 'asc' } }), db.savedView.findMany({ where: { userId: user.id }, orderBy: [{ sortOrder: 'asc' }, { createdAt: 'asc' }], select: { id: true, name: true, query: true }, }), ]) const levelName = new Map(levels.map((l) => [l.level, l.name])) const ergebnis = filters.kind === 'document' ? await searchDocuments(user.permissions, filters) : filters.kind === 'personnel' ? await searchPersonnel(user.permissions, filters) : await searchCases(user.permissions, filters) const statusTags = filters.kind === 'personnel' ? await listStatusTags() : [] return (

{de.search.heading}

{de.search.countLabel(ergebnis.total)}
{'facets' in ergebnis ? (
({ ...f, label: de.case.statusNames[f.value] ?? f.value, }))} param="status" filters={filters} /> ({ ...f, label: f.value }))} param="templateKey" filters={filters} /> ({ ...f, label: `${f.value} · ${levelName.get(Number(f.value)) ?? ''}`, }))} param="classification" filters={filters} />
) : null} {ergebnis.truncated ? (

{de.search.truncated}

) : null} {ergebnis.hits.length === 0 ? (

{filters.q ? de.search.empty : de.search.emptyNoQuery}

) : (
    {ergebnis.hits.map((treffer) => (
  • {'caseNumber' in treffer ? ( {treffer.caseNumber} {treffer.title} {de.case.statusNames[treffer.status] ?? treffer.status} ) : 'documentNumber' in treffer ? ( {treffer.documentNumber} {treffer.title} {de.document.statusNames[treffer.status] ?? treffer.status} ) : ( {treffer.displayName} {treffer.rank ? ( {treffer.rank} ) : null} )}
  • ))}
)} {'cursor' in ergebnis && ergebnis.cursor ? (

{de.search.more}

) : null}
) } async function listStatusTags() { return db.personnelStatusTag.findMany({ where: { isActive: true }, orderBy: { sortOrder: 'asc' }, select: { key: true, name: true, colorToken: true }, }) } /** * Eine Facette. Die Zahlen stammen aus derselben Abfrage wie die Liste — ein * Zähler, der mehr zählt als die Liste zeigt, wäre selbst eine Information. */ function Facette({ label, werte, param, filters, }: { label: string werte: { value: string; label: string; count: number }[] param: 'status' | 'templateKey' | 'classification' filters: ReturnType }) { if (werte.length === 0) return null return (

{label}

    {werte.slice(0, 6).map((wert) => { const aktiv = String(filters[param] ?? '') === wert.value return (
  • {wert.label} {wert.count}
  • ) })}
) }