Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-18 16:18:47 +00:00
co-authored by renee-png
parent 3456ba6ad1
commit a692b9ec7d
+73 -24
View File
@@ -134,6 +134,47 @@ export async function fetchCustomFieldDefs(): Promise<CustomFieldVar[]> {
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";
export function applyCase(value: string, modifier?: string | null): string {
if (!value || !modifier) return value ?? "";
const m = modifier.toLowerCase().trim();
switch (m) {
case "upper":
return value.toUpperCase();
case "lower":
return value.toLowerCase();
case "title":
// Capitalize each word; preserve internal punctuation/whitespace.
return value.toLowerCase().replace(/\b([a-z\u00C0-\u024F])/g, (c) => c.toUpperCase());
case "sentence":
case "capitalize": {
// Lowercase the whole string, then capitalize the first letter of each sentence.
const lower = value.toLowerCase();
return lower.replace(/(^\s*[a-z\u00C0-\u024F])|([.!?]\s+[a-z\u00C0-\u024F])/g, (s) =>
s.toUpperCase(),
);
}
default:
return value;
}
}
// Replace any occurrence of {{name}} or {{name|modifier}} using the provided lookup.
function replaceWithModifier(
body: string,
pattern: RegExp,
resolver: (name: string) => string | undefined,
): string {
return body.replace(pattern, (_match, rawName: string, _pipe: string | undefined, mod: string | undefined) => {
const value = resolver(rawName.trim());
if (value == null) return "";
return applyCase(value, mod);
});
}
export function applyVariables(
body: string,
ctx: {
@@ -145,32 +186,40 @@ export function applyVariables(
},
): string {
const today = format(new Date(), "MMMM d, yyyy");
const replacements: Record<string, string> = {
"{{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 ?? "",
const systemValues: Record<string, string> = {
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);
}
// Custom case-field variables like {{custom.mortgage_holder}}
if (ctx.customValues) {
for (const [k, v] of Object.entries(ctx.customValues)) {
out = out.split(`{{custom.${k}}}`).join(v ?? "");
}
}
// Per-form fillable fields like {{field.tenant_name}}
if (ctx.fieldValues) {
for (const [k, v] of Object.entries(ctx.fieldValues)) {
out = out.split(`{{field.${k}}}`).join(v ?? "");
}
}
// System variables: {{name}} or {{name|modifier}}
out = replaceWithModifier(
out,
/\{\{\s*([a-zA-Z][a-zA-Z0-9_]*)\s*(\|\s*([a-zA-Z]+)\s*)?\}\}/g,
(name) => systemValues[name],
);
// Custom case-field variables: {{custom.key}} or {{custom.key|modifier}}
out = replaceWithModifier(
out,
/\{\{\s*custom\.([a-zA-Z0-9_]+)\s*(\|\s*([a-zA-Z]+)\s*)?\}\}/g,
(name) => ctx.customValues?.[name],
);
// Per-form fillable fields: {{field.key}} or {{field.key|modifier}}
out = replaceWithModifier(
out,
/\{\{\s*field\.([a-zA-Z0-9_]+)\s*(\|\s*([a-zA-Z]+)\s*)?\}\}/g,
(name) => ctx.fieldValues?.[name],
);
return out;
}