"use client"; import { useState, useEffect, useRef } from "react"; import { useRouter } from "next/navigation"; import { text } from "@/lib/format"; interface Props { /** "link-director-to-company" | "link-company-to-director" */ mode: "link-director" | "link-company"; /** PB id of the parent entity (company for director-mode, director for company-mode) */ parentId: string; /** Display name of the parent entity, shown in the modal heading */ parentName: string; /** Ids of entities already linked — excluded from the dropdown */ alreadyLinkedIds: string[]; open: boolean; onClose: () => void; /** Called after a successful link, so the parent page can refresh */ onLinked: () => void; } interface SearchResult { id: string; label: string; sub: string; } export default function LinkEntityModal({ mode, parentId, parentName, alreadyLinkedIds, open, onClose, onLinked, }: Props) { const router = useRouter(); const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [searching, setSearching] = useState(false); const [linking, setLinking] = useState(null); const [error, setError] = useState(null); const debounceRef = useRef>(); // Search when query changes (debounced 300ms) useEffect(() => { if (!open) return; clearTimeout(debounceRef.current); if (!query.trim()) { setResults([]); return; } debounceRef.current = setTimeout(async () => { setSearching(true); setError(null); try { const collection = mode === "link-director" ? "ysterbaard_companies" : "ysterbaard_directors"; const q = encodeURIComponent(query.trim().replace(/'/g, "''")); const filter = mode === "link-director" ? `(enterprise_name ~ '${q}' || enterprise_no ~ '${q}')` : `(surname ~ '${q}' || first_names ~ '${q}' || id_no ~ '${q}')`; const res = await fetch( `/api/pb/records/list?collection=${collection}&filter=${encodeURIComponent(filter)}&perPage=20&fields=id,enterprise_name,enterprise_no,first_names,surname,id_no` ); const data = await res.json(); const items: SearchResult[] = (data.items ?? []).map((item: Record) => { if (mode === "link-director") { return { id: item.id as string, label: text(item.enterprise_name as string), sub: text(item.enterprise_no as string), }; } else { return { id: item.id as string, label: `${text(item.first_names as string)} ${text(item.surname as string)}`.trim(), sub: text(item.id_no as string), }; } }).filter((r: SearchResult) => !alreadyLinkedIds.includes(r.id)); setResults(items); } catch { setError("Search failed. Try again."); } finally { setSearching(false); } }, 300); return () => clearTimeout(debounceRef.current); }, [query, open, mode, alreadyLinkedIds]); async function handleLink(targetId: string, targetLabel: string) { setLinking(targetId); setError(null); try { const body = mode === "link-director" ? { company: targetId, director: parentId, director_type: "Director" } : { director: targetId, company: parentId, director_type: "Director" }; const res = await fetch("/api/pb/records/link", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!res.ok) { const err = await res.json(); throw new Error(err.error ?? "Link failed"); } setQuery(""); setResults([]); onLinked(); onClose(); router.refresh(); } catch (e: unknown) { setError((e instanceof Error ? e.message : "Link failed")); } finally { setLinking(null); } } if (!open) return null; const isDirectorMode = mode === "link-director"; const heading = isDirectorMode ? `Link company to ${parentName}` : `Link director to ${parentName}`; const placeholder = isDirectorMode ? "Search companies by name or number…" : "Search directors by name or ID…"; return (
e.stopPropagation()} > {/* Header */}

{heading}

{/* Search input */}
setQuery(e.target.value)} placeholder={placeholder} autoFocus className="w-full border border-border rounded-lg bg-surface-warm px-4 py-2.5 text-fg placeholder:text-meta outline-none focus:border-accent transition-colors" />
{/* Results */}
{error && (
{error}
)} {searching &&

Searching…

} {!searching && query && results.length === 0 && (

No matches found.

)} {!searching && !query && (

Type to search…

)} {results.map((r) => ( ))}
); }