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:
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user