Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
559f9450a0
commit
bae323c7df
@@ -16,19 +16,11 @@ import {
|
||||
type HomeownerLite,
|
||||
type FirmInfo,
|
||||
} from "@/lib/forms-shared";
|
||||
import { fillTokens, loadFormTemplate, type FormTemplateConfig } from "@/lib/form-templates";
|
||||
import { supabase } from "@/integrations/supabase/client";
|
||||
import { jsPDF } from "jspdf";
|
||||
import { toast } from "sonner";
|
||||
|
||||
const DEFAULT_BODY = `Dear {{ownerName}},
|
||||
|
||||
This letter is to inform you that…
|
||||
|
||||
If you have any questions, please contact our office.
|
||||
|
||||
Sincerely,
|
||||
{{firmName}}`;
|
||||
|
||||
async function loadLogoDataUrl(path: string | null | undefined): Promise<string | null> {
|
||||
if (!path) return null;
|
||||
const { data: signed } = await supabase.storage.from("firm-logos").createSignedUrl(path, 60);
|
||||
@@ -55,8 +47,9 @@ export function LetterGenerator() {
|
||||
const [logoDataUrl, setLogoDataUrl] = useState<string | null>(null);
|
||||
const [attorneyName, setAttorneyName] = useState("");
|
||||
const [subject, setSubject] = useState("");
|
||||
const [body, setBody] = useState(DEFAULT_BODY);
|
||||
const [body, setBody] = useState("");
|
||||
const [date, setDate] = useState(new Date().toISOString().slice(0, 10));
|
||||
const [template, setTemplate] = useState<FormTemplateConfig | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchFirm().then(async (f) => {
|
||||
@@ -64,6 +57,11 @@ export function LetterGenerator() {
|
||||
const url = await loadLogoDataUrl((f as any)?.logo_storage_path);
|
||||
setLogoDataUrl(url);
|
||||
});
|
||||
loadFormTemplate("letter").then((t) => {
|
||||
setTemplate(t);
|
||||
// Seed the editable body with the template default on first load.
|
||||
setBody((prev) => prev || t.body || "");
|
||||
});
|
||||
supabase.auth.getUser().then(async ({ data }) => {
|
||||
const uid = data.user?.id;
|
||||
if (!uid) return;
|
||||
@@ -80,12 +78,19 @@ export function LetterGenerator() {
|
||||
}, []);
|
||||
|
||||
const handleExport = () => {
|
||||
if (!template) return;
|
||||
const ctx = { client, homeowner, firmName: firm?.company_name ?? "" };
|
||||
const renderedBody = applyVariables(body, ctx);
|
||||
const renderedSubject = applyVariables(subject, ctx);
|
||||
|
||||
// Use the template's font / size / margin choices for the body.
|
||||
const bodyFont = (template.font ?? "times") as "helvetica" | "times" | "courier";
|
||||
const bodyFontSize = template.fontSizePt ?? 11;
|
||||
const margin = template.marginPt ?? 60;
|
||||
const lh = template.lineHeightPt ?? 14;
|
||||
|
||||
const doc = new jsPDF({ unit: "pt", format: "letter" });
|
||||
const pageW = doc.internal.pageSize.getWidth();
|
||||
const margin = 60;
|
||||
const maxW = pageW - margin * 2;
|
||||
const centerX = pageW / 2;
|
||||
|
||||
@@ -103,13 +108,13 @@ export function LetterGenerator() {
|
||||
}
|
||||
}
|
||||
|
||||
// Firm name (left) + Attorney name italic (right) on same line
|
||||
// Firm name (left) + Attorney name italic (right)
|
||||
const firmName = (firm?.company_name ?? "").toUpperCase();
|
||||
doc.setFont("times", "bold");
|
||||
doc.setFont(bodyFont, "bold");
|
||||
doc.setFontSize(12);
|
||||
if (firmName) doc.text(firmName, margin, y);
|
||||
if (attorneyName) {
|
||||
doc.setFont("times", "italic");
|
||||
doc.setFont(bodyFont, "italic");
|
||||
doc.setFontSize(11);
|
||||
doc.text(attorneyName, pageW - margin, y, { align: "right" });
|
||||
}
|
||||
@@ -126,47 +131,46 @@ export function LetterGenerator() {
|
||||
if (addr) contactParts.push(addr);
|
||||
if (firm?.contact_phone) contactParts.push(firm.contact_phone);
|
||||
if (firm?.contact_email) contactParts.push(firm.contact_email);
|
||||
doc.setFont("times", "normal");
|
||||
doc.setFont(bodyFont, "normal");
|
||||
doc.setFontSize(10);
|
||||
if (contactParts.length) {
|
||||
doc.text(contactParts.join(" \u00B7 "), centerX, y, { align: "center" });
|
||||
y += 8;
|
||||
}
|
||||
|
||||
// Horizontal rule
|
||||
doc.setLineWidth(0.75);
|
||||
doc.line(margin, y, pageW - margin, y);
|
||||
y += 28;
|
||||
|
||||
// Centered bold date
|
||||
doc.setFont("times", "bold");
|
||||
doc.setFont(bodyFont, "bold");
|
||||
doc.setFontSize(11);
|
||||
doc.text(fmtDateLong(date), centerX, y, { align: "center" });
|
||||
y += 28;
|
||||
|
||||
// Recipient block
|
||||
doc.setFont("times", "normal");
|
||||
doc.setFontSize(11);
|
||||
doc.setFont(bodyFont, "normal");
|
||||
doc.setFontSize(bodyFontSize);
|
||||
ownerMailingLines(homeowner).forEach((l) => {
|
||||
doc.text(l, margin, y);
|
||||
y += 13;
|
||||
y += lh - 1;
|
||||
});
|
||||
if (!homeowner && client) {
|
||||
doc.text(client.name, margin, y);
|
||||
y += 13;
|
||||
y += lh - 1;
|
||||
clientAddressLines(client).forEach((l) => {
|
||||
doc.text(l, margin, y);
|
||||
y += 13;
|
||||
y += lh - 1;
|
||||
});
|
||||
}
|
||||
|
||||
y += 18;
|
||||
if (renderedSubject) {
|
||||
doc.setFont("times", "bold");
|
||||
doc.setFont(bodyFont, "bold");
|
||||
const subjLines = doc.splitTextToSize(`Re: ${renderedSubject}`, maxW);
|
||||
doc.text(subjLines, margin, y);
|
||||
y += subjLines.length * 13 + 14;
|
||||
doc.setFont("times", "normal");
|
||||
y += subjLines.length * lh + 14;
|
||||
doc.setFont(bodyFont, "normal");
|
||||
}
|
||||
|
||||
const bodyLines = doc.splitTextToSize(renderedBody, maxW);
|
||||
@@ -176,7 +180,25 @@ export function LetterGenerator() {
|
||||
y = 60;
|
||||
}
|
||||
doc.text(line, margin, y);
|
||||
y += 14;
|
||||
y += lh;
|
||||
}
|
||||
|
||||
// Optional signature block from the template (admins can edit it).
|
||||
if (template.signature) {
|
||||
y += 18;
|
||||
const sig = fillTokens(template.signature, {
|
||||
"firm.name": firm?.company_name ?? "",
|
||||
"owner.name": homeowner ? `${homeowner.first_name} ${homeowner.last_name}`.trim() : "",
|
||||
"client.name": client?.name ?? "",
|
||||
});
|
||||
sig.split("\n").forEach((line) => {
|
||||
if (y > 740) {
|
||||
doc.addPage();
|
||||
y = 60;
|
||||
}
|
||||
doc.text(line, margin, y);
|
||||
y += lh;
|
||||
});
|
||||
}
|
||||
|
||||
doc.save(`Letter_${date}.pdf`);
|
||||
@@ -228,11 +250,13 @@ export function LetterGenerator() {
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Variables: <code>{"{{ownerName}}"}</code>, <code>{"{{clientName}}"}</code>,{" "}
|
||||
<code>{"{{propertyAddress}}"}</code>, <code>{"{{balance}}"}</code>,{" "}
|
||||
<code>{"{{currentDate}}"}</code>, <code>{"{{firmName}}"}</code>
|
||||
<code>{"{{currentDate}}"}</code>, <code>{"{{firmName}}"}</code>. The default body,
|
||||
signature, fonts and margins are managed in{" "}
|
||||
<strong>Settings → Form templates</strong>.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={handleExport}>
|
||||
<Button onClick={handleExport} disabled={!template}>
|
||||
<FileDown className="h-4 w-4 mr-2" />
|
||||
Export PDF
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user