import { useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Search } from "lucide-react"; interface ClientResult { id: string; name: string; email: string | null; phone: string | null; } interface PetResult { id: string; name: string; breed: string | null; clientId: string; ownerName: string; } interface SearchResults { clients: ClientResult[]; pets: PetResult[]; } export function GlobalSearch() { const [query, setQuery] = useState(""); const [results, setResults] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [open, setOpen] = useState(false); const inputRef = useRef(null); const dropdownRef = useRef(null); const debounceRef = useRef | null>(null); const navigate = useNavigate(); // Debounced search useEffect(() => { if (debounceRef.current) clearTimeout(debounceRef.current); const trimmed = query.trim(); if (trimmed.length === 0) { setResults(null); setOpen(false); return; } debounceRef.current = setTimeout(async () => { setLoading(true); setError(null); try { const res = await fetch(`/api/search?q=${encodeURIComponent(trimmed)}`); if (res.ok) { const data: SearchResults = await res.json(); setResults(data); setOpen(true); } else { setError("Search failed. Please try again."); } } catch { setError("Search failed. Please try again."); } finally { setLoading(false); } }, 300); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, [query]); // Close dropdown on outside click useEffect(() => { function handleClick(e: MouseEvent) { if ( inputRef.current && !inputRef.current.contains(e.target as Node) && dropdownRef.current && !dropdownRef.current.contains(e.target as Node) ) { setOpen(false); } } document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); }, []); function handleClientClick(client: ClientResult) { setOpen(false); setQuery(""); navigate(`/admin/clients?highlight=${client.id}`); } function handlePetClick(pet: PetResult) { setOpen(false); setQuery(""); navigate(`/admin/clients?highlight=${pet.clientId}`); } const hasResults = results && (results.clients.length > 0 || results.pets.length > 0); return (
setQuery(e.target.value)} onFocus={() => results && setOpen(true)} style={{ width: "100%", boxSizing: "border-box", height: 44, paddingLeft: 32, paddingRight: 12, fontSize: 13, border: "1px solid #e2e8f0", borderRadius: 8, outline: "none", background: "#f8fafc", color: "#1a202c", }} aria-label="Search clients and pets" aria-expanded={open} aria-haspopup="listbox" role="combobox" aria-autocomplete="list" />
{open && (
{loading && (
Searching…
)} {!loading && error && (
{error}
)} {!loading && !error && !hasResults && (
No results found
)} {!loading && results && results.clients.length > 0 && (
Clients
{results.clients.map((client) => ( ))}
)} {!loading && results && results.pets.length > 0 && (
Pets
{results.pets.map((pet) => ( ))}
)}
)}
); }