diff --git a/src/components/cases/collections-tab.tsx b/src/components/cases/collections-tab.tsx index edd6789..8288ea6 100644 --- a/src/components/cases/collections-tab.tsx +++ b/src/components/cases/collections-tab.tsx @@ -1087,6 +1087,7 @@ function AddCollectionDialog({ clientId, caseTitle, homeowners, + contactHomeowners, existingHomeownerIds, onCreated, }: { @@ -1096,11 +1097,13 @@ function AddCollectionDialog({ clientId: string; caseTitle: string; homeowners: any[]; + contactHomeowners: any[]; existingHomeownerIds: string[]; onCreated: () => void; }) { const { user } = useAuth(); - const [homeownerId, setHomeownerId] = useState(""); + // Picker value uses prefix: "ho:" for homeowner row, "ct:" for contact + const [pickerValue, setPickerValue] = useState(""); const [status, setStatus] = useState("none"); const [notes, setNotes] = useState(""); const [submitting, setSubmitting] = useState(false); @@ -1109,11 +1112,41 @@ function AddCollectionDialog({ (h) => !existingHomeownerIds.includes(h.id), ); + // Build combined options. Skip contacts that already have a matching homeowner row + // attached as a collection (best-effort dedupe by name+unit). + const homeownerKeySet = new Set( + homeowners + .filter((h) => existingHomeownerIds.includes(h.id)) + .map((h) => + `${(h.first_name || "").toLowerCase().trim()}|${(h.last_name || "").toLowerCase().trim()}|${(h.unit_number || "").toLowerCase().trim()}`, + ), + ); + + const splitName = (full: string) => { + const s = (full || "").trim(); + if (!s) return { first: "", last: "" }; + if (s.includes(",")) { + const [last, first] = s.split(",", 2).map((x) => x.trim()); + return { first: first || "", last: last || "" }; + } + const parts = s.split(/\s+/); + return { first: parts.slice(0, -1).join(" ") || parts[0], last: parts.length > 1 ? parts.at(-1)! : "" }; + }; + + const contactOptions = (contactHomeowners ?? []) + .map((c: any) => { + const { first, last } = splitName(c.name); + return { contact: c, first, last }; + }) + .filter(({ first, last }) => { + const key = `${first.toLowerCase()}|${last.toLowerCase()}|`; + return !homeownerKeySet.has(key); + }); + useEffect(() => { if (open) { - // Try to pre-select a homeowner whose surname matches the case title const match = findUniqueHomeownerByTitle(caseTitle, available); - setHomeownerId(match ? match.id : ""); + setPickerValue(match ? `ho:${match.id}` : ""); setStatus("none"); setNotes(""); } @@ -1121,11 +1154,56 @@ function AddCollectionDialog({ }, [open, caseTitle]); const submit = async () => { - if (!homeownerId) { + if (!pickerValue) { toast.error("Pick a homeowner"); return; } setSubmitting(true); + let homeownerId = ""; + + if (pickerValue.startsWith("ho:")) { + homeownerId = pickerValue.slice(3); + } else if (pickerValue.startsWith("ct:")) { + // Find or create a homeowner row for this contact + const contactId = pickerValue.slice(3); + const contact = contactHomeowners.find((c: any) => c.id === contactId); + if (!contact) { + setSubmitting(false); + toast.error("Contact not found"); + return; + } + const { first, last } = splitName(contact.name); + // Try match an existing homeowner on this client by name + const existing = homeowners.find( + (h) => + (h.first_name || "").toLowerCase().trim() === first.toLowerCase() && + (h.last_name || "").toLowerCase().trim() === last.toLowerCase(), + ); + if (existing) { + homeownerId = existing.id; + } else { + const { data: newHo, error: hoErr } = await supabase + .from("homeowners") + .insert({ + client_id: clientId, + first_name: first || contact.name || "Unknown", + last_name: last || "", + email: contact.email || null, + phone: contact.phone || null, + address: contact.address_line1 || null, + created_by: user?.id, + }) + .select() + .single(); + if (hoErr || !newHo) { + setSubmitting(false); + toast.error("Could not create homeowner", { description: hoErr?.message }); + return; + } + homeownerId = newHo.id; + } + } + const { error } = await supabase.from("collections").insert({ case_id: caseId, homeowner_id: homeownerId, @@ -1143,36 +1221,48 @@ function AddCollectionDialog({ onCreated(); }; + const hoOptions = available.map((h) => ({ + value: `ho:${h.id}`, + label: `${h.last_name}, ${h.first_name}${h.unit_number ? ` — Unit ${h.unit_number}` : ""}`, + keywords: `${h.first_name} ${h.last_name} ${h.unit_number ?? ""}`, + group: "HOA homeowners", + })); + const ctOptions = contactOptions.map(({ contact, first, last }) => ({ + value: `ct:${contact.id}`, + label: `${last || contact.name}${first ? `, ${first}` : ""} — Case contact`, + keywords: `${first} ${last} ${contact.name} ${contact.email ?? ""}`, + group: "Case contacts (homeowner)", + })); + const allOptions = [...hoOptions, ...ctOptions]; + return ( Add homeowner collection - Attach a homeowner from this HOA to this matter. Each homeowner can - only have one collection per case. + Attach a homeowner to this matter. You can pick from this HOA's + homeowners or any case contact tagged as a homeowner. Each + homeowner can only have one collection per case.
- {available.length === 0 ? ( + {allOptions.length === 0 ? (

- {homeowners.length === 0 - ? "No homeowners yet on this HOA. Use 'New homeowner' first." - : "All homeowners on this HOA already have a collection on this matter."} + {homeowners.length === 0 && contactHomeowners.length === 0 + ? "No homeowners yet. Use 'New homeowner' or link a homeowner contact to this case." + : "All available homeowners already have a collection on this matter."}

) : ( { - const label = `${h.last_name}, ${h.first_name}${h.unit_number ? ` — Unit ${h.unit_number}` : ""}`; - return { value: h.id, label, keywords: `${h.first_name} ${h.last_name} ${h.unit_number ?? ""}` }; - })} + options={allOptions} /> )}