"use client"; // components/TaxRepresentativeSection.tsx // Single-director picker for the company's SARS tax representative. // Pattern mirrors LinkEntityModal: search-as-you-type via /api/pb/records/list, // click to select. The selection is saved directly on the company via PATCH // (no junction table — one tax rep per company, by design). Added 2026-09-16. import { useEffect, useRef, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { pbPatch } from "@/lib/api"; import { text, number } from "@/lib/format"; interface DirectorHit { id: string; first_names: string | null; surname: string | null; id_no: string | null; tax_reference_no: number | null; } interface Props { companyId: string; /** Currently-linked director ID, or empty string. */ initialDirectorId: string; /** Expanded payload for the currently-linked director. May be undefined * if the server-side expand wasn't populated. */ initialDirector?: DirectorHit; } export default function TaxRepresentativeSection({ companyId, initialDirectorId, initialDirector, }: Props) { const router = useRouter(); const [picking, setPicking] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); // Search state — only active when picking=true const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [searching, setSearching] = useState(false); const debounceRef = useRef>(); // Optimistic local state for instant feedback after PATCH const [directorId, setDirectorId] = useState(initialDirectorId); const [director, setDirector] = useState( initialDirector, ); // Search-as-you-type, debounced 300ms (same as LinkEntityModal) useEffect(() => { if (!picking) return; clearTimeout(debounceRef.current); if (!query.trim()) { setResults([]); return; } debounceRef.current = setTimeout(async () => { setSearching(true); setError(null); try { const q = query.trim().replace(/'/g, "''"); const filter = `(surname ~ '${q}' || first_names ~ '${q}' || id_no ~ '${q}')`; const res = await fetch( `/api/pb/records/list?collection=ysterbaard_directors&filter=${encodeURIComponent(filter)}&perPage=20&fields=id,first_names,surname,id_no,tax_reference_no`, ); const data = await res.json(); const items: DirectorHit[] = (data.items ?? []).map( (it: Record) => ({ id: it.id as string, first_names: (it.first_names as string | null) ?? null, surname: (it.surname as string | null) ?? null, id_no: (it.id_no as string | null) ?? null, tax_reference_no: (it.tax_reference_no as number | null) ?? null, }), ); setResults(items); } catch { setError("Search failed. Try again."); } finally { setSearching(false); } }, 300); return () => clearTimeout(debounceRef.current); }, [query, picking]); async function choose(d: DirectorHit) { setSaving(true); setError(null); try { await pbPatch({ collection: "ysterbaard_companies", id: companyId, fields: { tax_representative: d.id }, }); setDirectorId(d.id); setDirector(d); setPicking(false); setQuery(""); setResults([]); router.refresh(); } catch (e: unknown) { setError(e instanceof Error ? e.message : "Could not save"); } finally { setSaving(false); } } async function unlink() { if ( !confirm( "Remove the tax representative? The director will remain in the database.", ) ) return; setSaving(true); setError(null); try { await pbPatch({ collection: "ysterbaard_companies", id: companyId, fields: { tax_representative: "" }, }); setDirectorId(""); setDirector(undefined); router.refresh(); } catch (e: unknown) { setError(e instanceof Error ? e.message : "Could not save"); } finally { setSaving(false); } } const linked = directorId && director; const displayName = (d: DirectorHit) => `${text(d.first_names)} ${text(d.surname)}`.trim(); return (

Tax representative

Director who acts as the SARS tax representative for this company.

{!picking ? (
{linked ? ( ) : null}
) : null}
{error ? (
{error}
) : null} {linked ? (

Name

{text(director.surname) || "—"}

{text(director.first_names) ? (

{text(director.first_names)}

) : null}

ID Number

{text(director.id_no)}

Tax reference

{director.tax_reference_no != null && director.tax_reference_no > 0 ? number(director.tax_reference_no) : "—"}

View director profile →
) : !picking ? (
No tax representative assigned.
) : null} {picking ? (
{searching ? (

Searching…

) : !query.trim() ? (

Type to search…

) : results.length === 0 ? (

No matches found.

) : (
{results.map((d) => ( ))}
)}
) : null}
); }