"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, serializeSearchUrlState, SEARCH_SCOPE, } 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 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"; function buildSearchHref({ routeBranch, state }) { const base = searchPath(routeBranch); const qs = serializeSearchUrlState(state); return qs ? `${base}?${qs}` : base; } 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(() => { const qs = serializeSearchUrlState(urlState); return `${routeBranch}|${qs}`; }, [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] ); 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(() => { const nextState = { ...urlState, q: qDraft, }; pushStateToUrl(nextState); }, [urlState, qDraft, pushStateToUrl]); const handleScopeChange = React.useCallback( (nextScope) => { if (!isAdminDev) return; const nextState = { ...urlState, scope: nextScope, branches: nextScope === SEARCH_SCOPE.MULTI ? urlState.branches : [], }; replaceStateToUrl(nextState); }, [isAdminDev, urlState, replaceStateToUrl] ); const handleToggleBranch = React.useCallback( (branchId) => { if (!isAdminDev) return; const current = Array.isArray(urlState.branches) ? urlState.branches : []; const set = new Set(current); if (set.has(branchId)) set.delete(branchId); else set.add(branchId); const nextState = { ...urlState, scope: SEARCH_SCOPE.MULTI, branches: Array.from(set), }; replaceStateToUrl(nextState); }, [isAdminDev, urlState, replaceStateToUrl] ); const handleLimitChange = React.useCallback( (nextLimit) => { const nextState = { ...urlState, limit: nextLimit, }; replaceStateToUrl(nextState); }, [urlState, replaceStateToUrl] ); const handleSingleBranchChange = React.useCallback( (nextBranch) => { if (!isAdminDev) return; if (!isValidBranchParam(nextBranch)) return; const nextState = { ...urlState, scope: SEARCH_SCOPE.SINGLE, branches: [], }; const base = searchPath(nextBranch); const qs = serializeSearchUrlState(nextState); router.push(qs ? `${base}?${qs}` : base); }, [isAdminDev, urlState, router] ); if (mappedError?.kind === "forbidden") { return ; } const actions = ( ); const resultsHeaderRight = urlState.q ? ( {urlState.q} ) : null; const multiCount = Array.isArray(urlState.branches) ? urlState.branches.length : 0; const scopeLabel = urlState.scope === SEARCH_SCOPE.ALL ? "Alle Niederlassungen" : urlState.scope === SEARCH_SCOPE.MULTI ? multiCount > 0 ? `${multiCount} Niederlassung${multiCount === 1 ? "" : "en"}` : "Mehrere Niederlassungen" : `Niederlassung ${routeBranch}`; const resultsDescription = urlState.q ? `Suchbereich: ${scopeLabel}` : "Geben Sie einen Suchbegriff ein, um zu starten."; const needsBranchSelection = isAdminDev && urlState.scope === SEARCH_SCOPE.MULTI && Boolean(urlState.q) && multiCount === 0; return ( ); }