Added contact field support

X-Lovable-Edit-ID: edt-b71210e8-f29d-4599-9bf7-b7f868b39a1f
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-18 02:15:44 +00:00
co-authored by renee-png
4 changed files with 96 additions and 6 deletions
+8 -5
View File
@@ -630,19 +630,22 @@ export function CustomFormBuilder() {
? AlignmentType.JUSTIFIED
: AlignmentType.LEFT;
const indentTwips = Math.round((seg.indent || 0) * 15); // ~px → twips (1px≈15twip)
const lines = applyVariables(seg.text, ctx).split("\n");
children.push(
new DocxParagraph({
alignment: align,
indent: indentTwips ? { left: indentTwips } : undefined,
children: [
new TextRun({
text: applyVariables(seg.text, ctx),
children: lines.flatMap((line, idx) => {
const run = new TextRun({
text: line,
font: fontFamily,
size: fontSize * 2,
italics: seg.caption,
bold: !!seg.heading && seg.heading > 0,
}),
],
break: idx > 0 ? 1 : undefined,
});
return [run];
}),
}),
);
}
@@ -33,6 +33,7 @@ const TYPE_LABEL: Record<FormFieldType, string> = {
county: "Florida County",
client: "Client / Association",
homeowner: "Homeowner",
contact: "Contact (court, counsel, etc.)",
};
function slug(s: string): string {
@@ -12,8 +12,11 @@ import {
import {
fetchClients,
fetchHomeowners,
fetchContacts,
contactMailingLines,
type ClientLite,
type HomeownerLite,
type ContactLite,
type FormFieldDef,
} from "@/lib/forms-shared";
import { FL_COUNTIES } from "@/lib/florida";
@@ -29,14 +32,20 @@ export function FormFieldsFillPanel({
}) {
const [clients, setClients] = useState<ClientLite[]>([]);
const [homeownersByClient, setHomeownersByClient] = useState<Record<string, HomeownerLite[]>>({});
const [contacts, setContacts] = useState<ContactLite[]>([]);
// Track which client a homeowner field is filtered by (per-field-key)
const [hoClientByField, setHoClientByField] = useState<Record<string, string>>({});
// Track which contact id is selected per contact-field-key
const [contactIdByField, setContactIdByField] = useState<Record<string, string>>({});
useEffect(() => {
if (fields.some((f) => f.type === "client" || f.type === "homeowner")) {
fetchClients().then(setClients);
}
if (fields.some((f) => f.type === "contact")) {
fetchContacts().then(setContacts);
}
}, [fields]);
const set = (key: string, v: string) => onChange({ ...values, [key]: v });
@@ -215,6 +224,43 @@ export function FormFieldsFillPanel({
</div>
);
}
if (f.type === "contact") {
const selectedId = contactIdByField[f.key] ?? "";
const selected = contacts.find((c) => c.id === selectedId);
return (
<div key={f.key} className="space-y-1">
{labelEl}
<Select
value={selectedId}
onValueChange={(val) => {
setContactIdByField((p) => ({ ...p, [f.key]: val }));
const c = contacts.find((x) => x.id === val);
set(f.key, contactMailingLines(c).join("\n"));
}}
>
<SelectTrigger className="h-9">
<SelectValue placeholder="Select contact (court, counsel, vendor…)" />
</SelectTrigger>
<SelectContent>
{contacts.map((c) => {
const sub = [c.contact_type, c.company].filter(Boolean).join(" • ");
return (
<SelectItem key={c.id} value={c.id}>
{c.name}
{sub ? ` — ${sub}` : ""}
</SelectItem>
);
})}
</SelectContent>
</Select>
{selected && (
<pre className="text-[11px] text-muted-foreground whitespace-pre-wrap font-sans border rounded p-2 bg-muted/30">
{contactMailingLines(selected).join("\n")}
</pre>
)}
</div>
);
}
// text (default)
return (
<div key={f.key}>
+41 -1
View File
@@ -26,6 +26,45 @@ export interface HomeownerLite {
opening_balance: number;
}
export interface ContactLite {
id: string;
name: string;
contact_type: string;
company: string | null;
title: string | null;
address_line1: string | null;
address_line2: string | null;
city: string | null;
state: string | null;
postal_code: string | null;
email: string | null;
phone: string | null;
}
export async function fetchContacts(): Promise<ContactLite[]> {
const { data } = await supabase
.from("contacts")
.select(
"id,name,contact_type,company,title,address_line1,address_line2,city,state,postal_code,email,phone",
)
.is("archived_at", null)
.order("name");
return (data ?? []) as ContactLite[];
}
export function contactMailingLines(c: ContactLite | null | undefined): string[] {
if (!c) return [];
const lines: string[] = [];
if (c.name) lines.push(c.name);
if (c.title) lines.push(c.title);
if (c.company) lines.push(c.company);
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 async function fetchClients(): Promise<ClientLite[]> {
const { data } = await supabase
.from("clients")
@@ -144,7 +183,8 @@ export type FormFieldType =
| "dropdown"
| "county"
| "client"
| "homeowner";
| "homeowner"
| "contact";
export interface FormFieldDef {
key: string;