'use client' import Link from 'next/link' import { useEffect, useMemo, useState } from 'react' import { useSearchParams } from 'next/navigation' import { Search, Car, Users, CalendarClock, FileText, Receipt, CreditCard, BadgePercent, UserCog, LayoutDashboard } from 'lucide-react' import { apiFetch } from '@/lib/api' type GlobalSearchResultType = | 'page' | 'vehicle' | 'customer' | 'reservation' | 'contract' | 'invoice' | 'payment' | 'offer' | 'team' type GlobalSearchResult = { id: string type: GlobalSearchResultType title: string subtitle: string href: string meta?: string } type GlobalSearchResponse = { query: string results: GlobalSearchResult[] groups: Record total: number } const GROUPS: Array<{ key: GlobalSearchResultType; label: string; icon: typeof Search }> = [ { key: 'page', label: 'Pages', icon: LayoutDashboard }, { key: 'vehicle', label: 'Vehicles', icon: Car }, { key: 'customer', label: 'Customers', icon: Users }, { key: 'reservation', label: 'Reservations', icon: CalendarClock }, { key: 'contract', label: 'Contracts', icon: FileText }, { key: 'invoice', label: 'Invoices', icon: Receipt }, { key: 'payment', label: 'Payments', icon: CreditCard }, { key: 'offer', label: 'Offers', icon: BadgePercent }, { key: 'team', label: 'Team', icon: UserCog }, ] function resultTypeLabel(type: GlobalSearchResultType) { return GROUPS.find((group) => group.key === type)?.label ?? type } export default function GlobalSearchPage() { const searchParams = useSearchParams() const query = (searchParams.get('search') ?? '').trim() const [data, setData] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) useEffect(() => { let cancelled = false setData(null) setError(null) if (!query) { setLoading(false) return () => { cancelled = true } } setLoading(true) const params = new URLSearchParams({ search: query, limit: '6' }) apiFetch(`/search?${params.toString()}`) .then((result) => { if (!cancelled) setData(result) }) .catch((err) => { if (!cancelled) setError(err.message ?? 'Search failed') }) .finally(() => { if (!cancelled) setLoading(false) }) return () => { cancelled = true } }, [query]) const populatedGroups = useMemo(() => { if (!data) return [] return GROUPS.map((group) => ({ ...group, items: data.groups[group.key] ?? [] })).filter((group) => group.items.length > 0) }, [data]) return (

Search

{query ? `${data?.total ?? 0} result${data?.total === 1 ? '' : 's'} for "${query}"` : 'Search vehicles, reservations, customers, billing, offers, team, and pages.'}

{!query ? (

Type a search term in the top bar.

) : loading ? (
) : error ? (
{error}
) : populatedGroups.length === 0 ? (

No results found.

) : (
{populatedGroups.map((group) => { const Icon = group.icon return (

{group.label}

{group.items.map((item) => (

{item.title}

{item.subtitle}

{resultTypeLabel(item.type)}
{item.meta ?

{item.meta}

: null} ))}
) })}
)}
) }