'use client'; import { useState } from 'react'; import { Check, ChevronsUpDown } from 'lucide-react'; import { useQuery } from '@tanstack/react-query'; import { Button } from '@/components/ui/button'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from '@/components/ui/command'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { useDebounce } from '@/hooks/use-debounce'; import { apiFetch } from '@/lib/api/client'; import { cn } from '@/lib/utils'; interface ClientOption { id: string; fullName: string; } interface ClientPickerProps { value: string | null; onChange: (clientId: string | null) => void; placeholder?: string; disabled?: boolean; } export function ClientPicker({ value, onChange, placeholder = 'Select client...', disabled, }: ClientPickerProps) { const [open, setOpen] = useState(false); const [search, setSearch] = useState(''); const debounced = useDebounce(search, 300); const { data } = useQuery<{ data: ClientOption[] }>({ queryKey: ['client-picker', debounced], queryFn: () => apiFetch( `/api/v1/clients?search=${encodeURIComponent(debounced)}&page=1&limit=10&order=desc&includeArchived=false`, ), enabled: open, }); // The search results are paginated and only fetched while the popover // is open, so a `value` set from outside (e.g. an existing reservation) // can't be name-resolved from them. A second query targets the picked // client directly so the trigger label reads as the rep's name, not a // UUID-prefix fallback. const { data: selectedData } = useQuery<{ data: { id: string; fullName: string } }>({ queryKey: ['client-picker', 'selected', value], queryFn: () => apiFetch(`/api/v1/clients/${value}`), enabled: !!value, staleTime: 5 * 60_000, }); const options = data?.data ?? []; const selectedLabel = (() => { if (!value) return placeholder; const match = options.find((o) => o.id === value); return match?.fullName ?? selectedData?.data?.fullName ?? `Client ${value.slice(0, 8)}`; })(); return ( // `modal` is required when this picker is rendered inside a Sheet / // Dialog - without it the CommandInput stays focus-blocked by the // outer Sheet's focus trap and clicks/typing are silently dropped. No clients found. {value ? ( { onChange(null); setOpen(false); }} className="text-muted-foreground" > Clear selection ) : null} {options.map((c) => ( { onChange(c.id); setOpen(false); }} > {c.fullName} ))} ); }