Added client custom vars support
X-Lovable-Edit-ID: edt-2fe21d13-e759-4a99-8f55-5a3c405faf6c Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
@@ -59,8 +59,10 @@ import {
|
||||
applyVariables,
|
||||
fetchFirm,
|
||||
fetchCustomFieldDefs,
|
||||
fetchClientCustomFieldDefs,
|
||||
fetchAccessibleCases,
|
||||
fetchCaseCustomValues,
|
||||
fetchClientCustomValues,
|
||||
savePdfToDocuments,
|
||||
SYSTEM_VARIABLES,
|
||||
type ClientLite,
|
||||
@@ -259,6 +261,8 @@ export function CustomFormBuilder() {
|
||||
const [firm, setFirm] = useState<FirmInfo | null>(null);
|
||||
const [search, setSearch] = useState("");
|
||||
const [customDefs, setCustomDefs] = useState<CustomFieldVar[]>([]);
|
||||
const [clientCustomDefs, setClientCustomDefs] = useState<CustomFieldVar[]>([]);
|
||||
const [clientCustomValues, setClientCustomValues] = useState<Record<string, string>>({});
|
||||
const [fontFamily, setFontFamily] = useState("Bookman Old Style");
|
||||
const [fontSize, setFontSize] = useState(12);
|
||||
const [lineHeight, setLineHeight] = useState(1.5);
|
||||
@@ -314,9 +318,19 @@ export function CustomFormBuilder() {
|
||||
useEffect(() => {
|
||||
fetchFirm().then(setFirm);
|
||||
fetchCustomFieldDefs().then(setCustomDefs);
|
||||
fetchClientCustomFieldDefs().then(setClientCustomDefs);
|
||||
loadTemplates();
|
||||
}, []);
|
||||
|
||||
// Load client custom field values whenever the selected client changes.
|
||||
useEffect(() => {
|
||||
if (!client?.id) {
|
||||
setClientCustomValues({});
|
||||
return;
|
||||
}
|
||||
fetchClientCustomValues(client.id).then(setClientCustomValues);
|
||||
}, [client?.id]);
|
||||
|
||||
const loadTemplates = async () => {
|
||||
const { data } = await supabase
|
||||
.from("custom_form_templates")
|
||||
@@ -662,11 +676,17 @@ export function CustomFormBuilder() {
|
||||
if (selectedCaseId) {
|
||||
customValues = await fetchCaseCustomValues(selectedCaseId);
|
||||
}
|
||||
// Always re-fetch the latest client custom values at render time so edits
|
||||
// made in another tab show up without needing to reselect the client.
|
||||
const freshClientCustomValues = client?.id
|
||||
? await fetchClientCustomValues(client.id)
|
||||
: {};
|
||||
return {
|
||||
client,
|
||||
homeowner,
|
||||
firmName: firm?.company_name ?? "",
|
||||
customValues,
|
||||
clientCustomValues: freshClientCustomValues,
|
||||
fieldValues,
|
||||
};
|
||||
};
|
||||
@@ -1172,6 +1192,11 @@ export function CustomFormBuilder() {
|
||||
description: d.label + (d.description ? ` — ${d.description}` : ""),
|
||||
kind: "custom" as const,
|
||||
})),
|
||||
...clientCustomDefs.map((d) => ({
|
||||
key: `{{client.custom.${d.key}}}`,
|
||||
description: `Client: ${d.label}` + (d.description ? ` — ${d.description}` : ""),
|
||||
kind: "client-custom" as const,
|
||||
})),
|
||||
...formFields.map((f) => ({
|
||||
key: `{{field.${f.key}}}`,
|
||||
description: `${f.label} (${f.type})`,
|
||||
@@ -1183,7 +1208,7 @@ export function CustomFormBuilder() {
|
||||
v.key.toLowerCase().includes(search.toLowerCase()) ||
|
||||
v.description.toLowerCase().includes(search.toLowerCase()),
|
||||
);
|
||||
}, [customDefs, formFields, search]);
|
||||
}, [customDefs, clientCustomDefs, formFields, search]);
|
||||
|
||||
const filteredCases = useMemo(() => {
|
||||
const s = caseSearch.toLowerCase();
|
||||
|
||||
+34
-3
@@ -134,6 +134,15 @@ export async function fetchCustomFieldDefs(): Promise<CustomFieldVar[]> {
|
||||
return (data ?? []) as CustomFieldVar[];
|
||||
}
|
||||
|
||||
export async function fetchClientCustomFieldDefs(): Promise<CustomFieldVar[]> {
|
||||
const { data } = await supabase
|
||||
.from("custom_client_fields")
|
||||
.select("key,label,description")
|
||||
.eq("active", true)
|
||||
.order("sort_order");
|
||||
return (data ?? []) as CustomFieldVar[];
|
||||
}
|
||||
|
||||
// Case modifiers usable in templates as {{var|modifier}}.
|
||||
// Supported: upper, lower, title, sentence, capitalize (alias for sentence).
|
||||
export type CaseModifier = "upper" | "lower" | "title" | "sentence" | "capitalize";
|
||||
@@ -189,6 +198,7 @@ export function applyVariables(
|
||||
homeowner?: HomeownerLite | null;
|
||||
firmName?: string;
|
||||
customValues?: Record<string, string>;
|
||||
clientCustomValues?: Record<string, string>;
|
||||
fieldValues?: Record<string, string>;
|
||||
},
|
||||
): string {
|
||||
@@ -206,11 +216,12 @@ export function applyVariables(
|
||||
|
||||
let out = body;
|
||||
|
||||
// System variables: {{name}} or {{name|modifier}}
|
||||
// Client custom-field variables: {{client.custom.key}} or {{client.custom.key|modifier}}
|
||||
// Must run BEFORE the generic {{custom.key}} pattern so the prefix isn't swallowed.
|
||||
out = replaceWithModifier(
|
||||
out,
|
||||
/\{\{\s*([a-zA-Z][a-zA-Z0-9_]*)\s*(\|\s*([a-zA-Z]+)\s*)?\}\}/g,
|
||||
(name) => systemValues[name],
|
||||
/\{\{\s*client\.custom\.([a-zA-Z0-9_]+)\s*(\|\s*([a-zA-Z]+)\s*)?\}\}/g,
|
||||
(name) => ctx.clientCustomValues?.[name],
|
||||
);
|
||||
|
||||
// Custom case-field variables: {{custom.key}} or {{custom.key|modifier}}
|
||||
@@ -227,6 +238,13 @@ export function applyVariables(
|
||||
(name) => ctx.fieldValues?.[name],
|
||||
);
|
||||
|
||||
// System variables: {{name}} or {{name|modifier}} — run LAST so dotted vars above win.
|
||||
out = replaceWithModifier(
|
||||
out,
|
||||
/\{\{\s*([a-zA-Z][a-zA-Z0-9_]*)\s*(\|\s*([a-zA-Z]+)\s*)?\}\}/g,
|
||||
(name) => systemValues[name],
|
||||
);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -337,3 +355,16 @@ export async function fetchCaseCustomValues(caseId: string): Promise<Record<stri
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
export async function fetchClientCustomValues(clientId: string): Promise<Record<string, string>> {
|
||||
const { data } = await supabase
|
||||
.from("client_field_values")
|
||||
.select("value, field:custom_client_fields(key)")
|
||||
.eq("client_id", clientId);
|
||||
const map: Record<string, string> = {};
|
||||
(data ?? []).forEach((row: any) => {
|
||||
const k = row.field?.key;
|
||||
if (k) map[k] = row.value ?? "";
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user