diff --git a/src/components/forms/form-pickers.tsx b/src/components/forms/form-pickers.tsx new file mode 100644 index 0000000..d0048d5 --- /dev/null +++ b/src/components/forms/form-pickers.tsx @@ -0,0 +1,96 @@ +import { useEffect, useState } from "react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Label } from "@/components/ui/label"; +import { + fetchClients, + fetchHomeowners, + type ClientLite, + type HomeownerLite, +} from "@/lib/forms-shared"; + +export function ClientHomeownerPicker({ + clientId, + homeownerId, + onClientChange, + onHomeownerChange, + showHomeowner = true, +}: { + clientId: string; + homeownerId: string; + onClientChange: (id: string, client: ClientLite | null) => void; + onHomeownerChange: (id: string, homeowner: HomeownerLite | null) => void; + showHomeowner?: boolean; +}) { + const [clients, setClients] = useState([]); + const [homeowners, setHomeowners] = useState([]); + + useEffect(() => { + fetchClients().then(setClients); + }, []); + + useEffect(() => { + if (!clientId) { + setHomeowners([]); + return; + } + fetchHomeowners(clientId).then(setHomeowners); + }, [clientId]); + + return ( +
+
+ + +
+ {showHomeowner && ( +
+ + +
+ )} +
+ ); +} diff --git a/src/lib/forms-shared.ts b/src/lib/forms-shared.ts new file mode 100644 index 0000000..b3c2d44 --- /dev/null +++ b/src/lib/forms-shared.ts @@ -0,0 +1,157 @@ +import { supabase } from "@/integrations/supabase/client"; +import { format } from "date-fns"; + +export interface ClientLite { + id: string; + name: string; + address_line1: string | null; + address_line2: string | null; + city: string | null; + state: string | null; + postal_code: string | null; + primary_contact_email: string | null; + primary_contact_phone: string | null; + annual_interest_rate: number | null; +} + +export interface HomeownerLite { + id: string; + client_id: string; + first_name: string; + last_name: string; + unit_number: string | null; + address: string | null; + email: string | null; + phone: string | null; + opening_balance: number; +} + +export async function fetchClients(): Promise { + const { data } = await supabase + .from("clients") + .select( + "id,name,address_line1,address_line2,city,state,postal_code,primary_contact_email,primary_contact_phone,annual_interest_rate", + ) + .order("name"); + return (data ?? []) as ClientLite[]; +} + +export async function fetchHomeowners(clientId: string): Promise { + const { data } = await supabase + .from("homeowners") + .select("id,client_id,first_name,last_name,unit_number,address,email,phone,opening_balance") + .eq("client_id", clientId) + .order("last_name"); + return (data ?? []) as HomeownerLite[]; +} + +export function ownerFullName(h: HomeownerLite | null | undefined): string { + if (!h) return ""; + return `${h.first_name} ${h.last_name}`.trim(); +} + +export function clientAddressLines(c: ClientLite | null | undefined): string[] { + if (!c) return []; + const lines: string[] = []; + if (c.address_line1) lines.push(c.address_line1); + if (c.address_line2) lines.push(c.address_line2); + const cityLine = [c.city, c.state, c.postal_code].filter(Boolean).join(", "); + if (cityLine) lines.push(cityLine); + return lines; +} + +export function ownerMailingLines(h: HomeownerLite | null | undefined): string[] { + if (!h) return []; + const lines: string[] = [ownerFullName(h)]; + if (h.address) lines.push(h.address); + return lines; +} + +export const SYSTEM_VARIABLES = [ + { key: "{{clientName}}", description: "Client / Association name" }, + { key: "{{ownerName}}", description: "Homeowner full name" }, + { key: "{{propertyAddress}}", description: "Homeowner property address" }, + { key: "{{unitNumber}}", description: "Homeowner unit number" }, + { key: "{{accountNumber}}", description: "Account / unit identifier" }, + { key: "{{balance}}", description: "Current balance" }, + { key: "{{currentDate}}", description: "Today's date" }, + { key: "{{firmName}}", description: "Your firm name" }, +] as const; + +export function applyVariables( + body: string, + ctx: { client?: ClientLite | null; homeowner?: HomeownerLite | null; firmName?: string }, +): string { + const today = format(new Date(), "MMMM d, yyyy"); + const replacements: Record = { + "{{clientName}}": ctx.client?.name ?? "", + "{{ownerName}}": ownerFullName(ctx.homeowner), + "{{propertyAddress}}": ctx.homeowner?.address ?? "", + "{{unitNumber}}": ctx.homeowner?.unit_number ?? "", + "{{accountNumber}}": ctx.homeowner?.unit_number ?? "", + "{{balance}}": (ctx.homeowner?.opening_balance ?? 0).toFixed(2), + "{{currentDate}}": today, + "{{firmName}}": ctx.firmName ?? "", + }; + let out = body; + for (const [k, v] of Object.entries(replacements)) { + out = out.split(k).join(v); + } + return out; +} + +export function fmtCurrency(n: number | string | null | undefined): string { + const v = typeof n === "string" ? parseFloat(n) : (n ?? 0); + return (Number.isFinite(v) ? v : 0).toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); +} + +export function fmtDateLong(d: Date | string | null | undefined): string { + if (!d) return ""; + const date = typeof d === "string" ? new Date(d.includes("T") ? d : d + "T12:00:00") : d; + return format(date, "MMMM d, yyyy"); +} + +export interface FirmInfo { + company_name: string | null; + address_line1: string | null; + address_line2: string | null; + city: string | null; + state: string | null; + postal_code: string | null; + contact_email: string | null; + contact_phone: string | null; + website: string | null; +} + +export async function fetchFirm(): Promise { + const { data } = await supabase.from("firm_settings").select("*").maybeSingle(); + return (data as FirmInfo) ?? null; +} + +export async function savePdfToDocuments(opts: { + blob: Blob; + caseId: string; + name: string; + folder?: string; +}) { + const { blob, caseId, name, folder = "Forms & Letters" } = opts; + const path = `${caseId}/${Date.now()}-${name}`; + const { error: upErr } = await supabase.storage + .from("case-documents") + .upload(path, blob, { contentType: "application/pdf", upsert: false }); + if (upErr) throw upErr; + const { data: u } = await supabase.auth.getUser(); + const { error: insErr } = await supabase.from("documents").insert({ + case_id: caseId, + folder, + name, + storage_path: path, + mime_type: "application/pdf", + size_bytes: blob.size, + uploaded_by: u.user?.id ?? null, + }); + if (insErr) throw insErr; +}