Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { useState, useEffect } from "react"; | |
| import { useRouter } from "next/navigation"; | |
| import { | |
| CommandDialog, | |
| CommandInput, | |
| CommandList, | |
| CommandEmpty, | |
| CommandGroup, | |
| CommandItem, | |
| } from "@/components/ui/command"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Search, Handshake, Users, Building2 } from "lucide-react"; | |
| type SearchResult = { | |
| id: number; | |
| type: "deal" | "contact" | "company"; | |
| title: string; | |
| subtitle: string; | |
| }; | |
| export function GlobalSearch() { | |
| const [open, setOpen] = useState(false); | |
| const [query, setQuery] = useState(""); | |
| const [results, setResults] = useState<SearchResult[]>([]); | |
| const router = useRouter(); | |
| useEffect(() => { | |
| const down = (e: KeyboardEvent) => { | |
| if (e.key === "k" && (e.metaKey || e.ctrlKey)) { | |
| e.preventDefault(); | |
| setOpen((o) => !o); | |
| } | |
| }; | |
| document.addEventListener("keydown", down); | |
| return () => document.removeEventListener("keydown", down); | |
| }, []); | |
| useEffect(() => { | |
| if (!query || query.length < 2) { | |
| setResults([]); | |
| return; | |
| } | |
| const timer = setTimeout(async () => { | |
| const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`); | |
| if (res.ok) setResults(await res.json()); | |
| }, 200); | |
| return () => clearTimeout(timer); | |
| }, [query]); | |
| function handleSelect(result: SearchResult) { | |
| setOpen(false); | |
| setQuery(""); | |
| const paths = { | |
| deal: `/deals/${result.id}`, | |
| contact: `/contacts/${result.id}`, | |
| company: `/companies/${result.id}`, | |
| }; | |
| router.push(paths[result.type]); | |
| } | |
| const icons = { | |
| deal: Handshake, | |
| contact: Users, | |
| company: Building2, | |
| }; | |
| return ( | |
| <> | |
| <Button | |
| variant="outline" | |
| className="relative h-9 w-full max-w-sm justify-start text-sm text-muted-foreground" | |
| onClick={() => setOpen(true)} | |
| > | |
| <Search className="mr-2 h-4 w-4" /> | |
| Search... | |
| <kbd className="pointer-events-none ml-auto inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100"> | |
| <span className="text-xs">⌘</span>K | |
| </kbd> | |
| </Button> | |
| <CommandDialog open={open} onOpenChange={setOpen}> | |
| <CommandInput | |
| placeholder="Search deals, contacts, companies..." | |
| value={query} | |
| onValueChange={setQuery} | |
| /> | |
| <CommandList> | |
| <CommandEmpty>No results found.</CommandEmpty> | |
| {results.length > 0 && ( | |
| <CommandGroup heading="Results"> | |
| {results.map((r) => { | |
| const Icon = icons[r.type]; | |
| return ( | |
| <CommandItem | |
| key={`${r.type}-${r.id}`} | |
| onSelect={() => handleSelect(r)} | |
| > | |
| <Icon className="mr-2 h-4 w-4 text-muted-foreground" /> | |
| <div> | |
| <div className="text-sm font-medium">{r.title}</div> | |
| <div className="text-xs text-muted-foreground">{r.subtitle}</div> | |
| </div> | |
| </CommandItem> | |
| ); | |
| })} | |
| </CommandGroup> | |
| )} | |
| </CommandList> | |
| </CommandDialog> | |
| </> | |
| ); | |
| } | |