'use client'; import { useEffect, useRef, useState, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { Search, Clock, User, TrendingUp, Anchor } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useSearch } from '@/hooks/use-search'; import { useUIStore } from '@/stores/ui-store'; export function CommandSearch() { const [focused, setFocused] = useState(false); const [query, setQuery] = useState(''); const router = useRouter(); const portSlug = useUIStore((s) => s.currentPortSlug); const wrapperRef = useRef(null); const inputRef = useRef(null); const { results, isLoading, recentSearches } = useSearch(query); const showDropdown = focused && (query.length > 0 || recentSearches.length > 0); const hasQuery = query.length >= 2; const hasResults = results && (results.clients.length > 0 || results.interests.length > 0 || results.berths.length > 0); // Cmd/Ctrl+K focuses the input useEffect(() => { function onKeyDown(e: KeyboardEvent) { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); inputRef.current?.focus(); } } document.addEventListener('keydown', onKeyDown); return () => document.removeEventListener('keydown', onKeyDown); }, []); // Click outside closes dropdown useEffect(() => { if (!focused) return; function onClick(e: MouseEvent) { if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) { setFocused(false); } } document.addEventListener('mousedown', onClick); return () => document.removeEventListener('mousedown', onClick); }, [focused]); const navigate = useCallback( (path: string) => { setFocused(false); setQuery(''); inputRef.current?.blur(); router.push(path); }, [router], ); // Keyboard nav inside dropdown function onInputKeyDown(e: React.KeyboardEvent) { if (e.key === 'Escape') { setFocused(false); inputRef.current?.blur(); } } const iconMap = { client: User, interest: TrendingUp, berth: Anchor } as const; return (
{/* ── Single persistent search bar ── */}
setQuery(e.target.value)} onFocus={() => setFocused(true)} onKeyDown={onInputKeyDown} placeholder="Search..." className="h-8 flex-1 min-w-0 bg-transparent text-sm outline-none ring-0 focus:outline-none focus:ring-0 placeholder:text-muted-foreground" />
{/* ── Results dropdown ── */} {showDropdown && (
{/* No query yet — show recent or hint */} {!hasQuery && recentSearches.length > 0 && (
Recent
{recentSearches.map((term) => ( ))}
)} {!hasQuery && recentSearches.length === 0 && (
Type at least 2 characters to search
)} {/* Loading */} {hasQuery && isLoading && (
Searching...
)} {/* No results */} {hasQuery && !isLoading && !hasResults && (
No results for “{query}”
)} {/* Result groups */} {hasQuery && !isLoading && results && ( <> {results.clients.length > 0 && ( ({ id: c.id, icon: 'client', label: c.fullName, sub: c.companyName, }))} iconMap={iconMap} onSelect={(id) => navigate(`/${portSlug}/clients/${id}`)} /> )} {results.interests.length > 0 && ( ({ id: i.id, icon: 'interest', label: i.clientName, sub: i.berthMooringNumber ?? i.pipelineStage, }))} iconMap={iconMap} onSelect={(id) => navigate(`/${portSlug}/interests/${id}`)} /> )} {results.berths.length > 0 && ( ({ id: b.id, icon: 'berth', label: b.mooringNumber, sub: [b.area, b.status].filter(Boolean).join(' · '), }))} iconMap={iconMap} onSelect={(id) => navigate(`/${portSlug}/berths/${id}`)} /> )} )}
)}
); } function ResultGroup({ heading, items, iconMap, onSelect, }: { heading: string; items: Array<{ id: string; icon: 'client' | 'interest' | 'berth'; label: string; sub?: string | null }>; iconMap: Record; onSelect: (id: string) => void; }) { return (
{heading}
{items.map((item) => { const Icon = iconMap[item.icon]; return ( ); })}
); } // Keep export for backwards compat — it's a no-op export function SearchTrigger() { return null; }