"use client"; import React from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { RefreshCw } from "lucide-react"; import { useAuth } from "@/components/auth/authContext"; import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect"; import { searchPath } from "@/lib/frontend/routes"; import { isValidBranchParam } from "@/lib/frontend/params"; import { parseSearchUrlState } from "@/lib/frontend/search/urlState"; import { normalizeSearchUrlStateForUser } from "@/lib/frontend/search/normalizeState"; import { mapSearchError } from "@/lib/frontend/search/errorMapping"; import { useSearchQuery } from "@/lib/frontend/search/useSearchQuery"; import { useSearchBranches, BRANCH_LIST_STATE, } from "@/lib/frontend/search/useSearchBranches"; import { buildSearchHref, buildSearchKey, getScopeLabel, needsMultiBranchSelectionHint, buildNextStateForScopeChange, buildNextStateForToggleBranch, buildNextStateForClearAllBranches, buildHrefForSingleBranchSwitch, } from "@/lib/frontend/search/pageHelpers"; import { buildDateFilterValidationError } from "@/lib/frontend/search/dateFilterValidation"; import ExplorerPageShell from "@/components/explorer/ExplorerPageShell"; import ExplorerSectionCard from "@/components/explorer/ExplorerSectionCard"; import ForbiddenView from "@/components/system/ForbiddenView"; import { Button } from "@/components/ui/button"; import SearchForm from "@/components/search/SearchForm"; import SearchResults from "@/components/search/SearchResults"; export default function SearchPage({ branch: routeBranch }) { const router = useRouter(); const searchParams = useSearchParams(); const { status: authStatus, user } = useAuth(); const isAuthenticated = authStatus === "authenticated" && user; const isAdminDev = isAuthenticated && (user.role === "admin" || user.role === "dev"); const parsedUrlState = React.useMemo(() => { return parseSearchUrlState(searchParams, { routeBranch }); }, [searchParams, routeBranch]); const urlState = React.useMemo(() => { return normalizeSearchUrlStateForUser(parsedUrlState, { routeBranch, user, }); }, [parsedUrlState, routeBranch, user]); const searchKey = React.useMemo(() => { return buildSearchKey({ routeBranch, urlState }); }, [routeBranch, urlState]); const [qDraft, setQDraft] = React.useState(urlState.q || ""); React.useEffect(() => { setQDraft(urlState.q || ""); }, [urlState.q]); const branchesQuery = useSearchBranches({ enabled: isAdminDev }); const query = useSearchQuery({ searchKey, urlState, routeBranch, user, limit: urlState.limit, }); const mappedError = React.useMemo( () => mapSearchError(query.error), [query.error], ); const mappedLoadMoreError = React.useMemo( () => mapSearchError(query.loadMoreError), [query.loadMoreError], ); const localDateValidationError = React.useMemo(() => { return buildDateFilterValidationError({ from: urlState.from, to: urlState.to, }); }, [urlState.from, urlState.to]); const mappedLocalDateValidation = React.useMemo(() => { return mapSearchError(localDateValidationError); }, [localDateValidationError]); const formValidationError = mappedError?.kind === "validation" ? mappedError : mappedLocalDateValidation?.kind === "validation" ? mappedLocalDateValidation : null; React.useEffect(() => { if (mappedError?.kind !== "unauthenticated") return; const next = typeof window !== "undefined" ? `${window.location.pathname}${window.location.search}` : searchPath(routeBranch); window.location.replace( buildLoginUrl({ reason: LOGIN_REASONS.EXPIRED, next }), ); }, [mappedError?.kind, routeBranch]); const pushStateToUrl = React.useCallback( (nextState) => { router.push(buildSearchHref({ routeBranch, state: nextState })); }, [router, routeBranch], ); const replaceStateToUrl = React.useCallback( (nextState) => { router.replace(buildSearchHref({ routeBranch, state: nextState })); }, [router, routeBranch], ); const handleSubmit = React.useCallback(() => { pushStateToUrl({ ...urlState, q: qDraft }); }, [pushStateToUrl, urlState, qDraft]); const handleScopeChange = React.useCallback( (nextScope) => { if (!isAdminDev) return; replaceStateToUrl(buildNextStateForScopeChange({ urlState, nextScope })); }, [isAdminDev, urlState, replaceStateToUrl], ); const handleToggleBranch = React.useCallback( (branchId) => { if (!isAdminDev) return; replaceStateToUrl(buildNextStateForToggleBranch({ urlState, branchId })); }, [isAdminDev, urlState, replaceStateToUrl], ); const handleClearAllBranches = React.useCallback(() => { if (!isAdminDev) return; replaceStateToUrl(buildNextStateForClearAllBranches({ urlState })); }, [isAdminDev, urlState, replaceStateToUrl]); const handleLimitChange = React.useCallback( (nextLimit) => { replaceStateToUrl({ ...urlState, limit: nextLimit }); }, [urlState, replaceStateToUrl], ); const handleSingleBranchChange = React.useCallback( (nextBranch) => { if (!isAdminDev) return; if (!isValidBranchParam(nextBranch)) return; const href = buildHrefForSingleBranchSwitch({ nextBranch, urlState }); if (!href) return; router.push(href); }, [isAdminDev, urlState, router], ); const handleDateRangeChange = React.useCallback( ({ from, to }) => { replaceStateToUrl({ ...urlState, from: from ?? null, to: to ?? null, }); }, [urlState, replaceStateToUrl], ); if (mappedError?.kind === "forbidden") { return ; } const actions = ( ); const resultsHeaderRight = urlState.q ? ( {urlState.q} ) : null; const scopeLabel = getScopeLabel({ routeBranch, urlState }); const resultsDescription = urlState.q ? `Suchbereich: ${scopeLabel}` : "Geben Sie einen Suchbegriff ein, um zu starten."; const needsBranchSelection = needsMultiBranchSelectionHint({ isAdminDev, urlState, }); return ( ); }