diff --git a/src/lib/docx-pleading.ts b/src/lib/docx-pleading.ts index 30f4c6b..484f737 100644 --- a/src/lib/docx-pleading.ts +++ b/src/lib/docx-pleading.ts @@ -36,6 +36,15 @@ export interface PleadingSignature { imageType?: "png" | "jpg" | "jpeg"; } +export interface ServiceContact { + name: string; + role?: string; + company?: string; + email?: string; + phone?: string; + address?: string; // multi-line +} + export interface PleadingInput { courtType: "CIRCUIT" | "COUNTY"; circuit: string; // e.g. "ELEVENTH" @@ -51,6 +60,8 @@ export interface PleadingInput { footerCenter?: string; footerRight?: string; // user text prepended to "Page X of Y" signature?: PleadingSignature; + serviceList?: ServiceContact[]; + serviceListTitle?: string; } const FONT = "Bookman Old Style"; @@ -375,6 +386,32 @@ export function buildPleadingDoc(input: PleadingInput): Document { }); } + // Service list (optional) — rendered on a fresh page at end + const serviceList = (input.serviceList || []).filter((c) => c.name.trim().length > 0); + if (serviceList.length > 0) { + const slTitle = (input.serviceListTitle || "SERVICE LIST").toUpperCase(); + bodyParagraphs.push( + new Paragraph({ + pageBreakBefore: true, + alignment: AlignmentType.CENTER, + children: [run(slTitle, { bold: true })], + }), + ); + bodyParagraphs.push(emptyP()); + serviceList.forEach((c, idx) => { + bodyParagraphs.push(p(c.name, { bold: true })); + if (c.role || c.company) { + bodyParagraphs.push(p([c.role, c.company].filter(Boolean).join(", "))); + } + if (c.address) { + c.address.split("\n").forEach((line) => bodyParagraphs.push(p(line))); + } + if (c.phone) bodyParagraphs.push(p(`Tel: ${c.phone}`)); + if (c.email) bodyParagraphs.push(p(`Email: ${c.email}`)); + if (idx < serviceList.length - 1) bodyParagraphs.push(emptyP()); + }); + } + return new Document({ styles: { default: { document: { run: { font: FONT, size: SIZE } } }, diff --git a/src/lib/pdf-pleading.ts b/src/lib/pdf-pleading.ts index 6243f1e..7804c85 100644 --- a/src/lib/pdf-pleading.ts +++ b/src/lib/pdf-pleading.ts @@ -328,6 +328,34 @@ export async function downloadPleadingPdf(input: PleadingInput, filename: string } } + // Service list — fresh page at end + const serviceList = (input.serviceList || []).filter((c) => c.name.trim().length > 0); + if (serviceList.length > 0) { + pdf.addPage(); + y = MARGIN; + const slTitle = (input.serviceListTitle || "SERVICE LIST").toUpperCase(); + setFont(pdf, { bold: true }); + pdf.text(slTitle, PAGE_W / 2, y, { align: "center" }); + y += LINE_H * 2; + setFont(pdf, {}); + serviceList.forEach((c, idx) => { + const block: string[] = []; + block.push(c.name); + const sub = [c.role, c.company].filter(Boolean).join(", "); + if (sub) block.push(sub); + if (c.address) c.address.split("\n").forEach((l) => block.push(l)); + if (c.phone) block.push(`Tel: ${c.phone}`); + if (c.email) block.push(`Email: ${c.email}`); + block.forEach((line, i) => { + ensureSpace(LINE_H); + setFont(pdf, { bold: i === 0 }); + pdf.text(line, MARGIN, y); + y += LINE_H; + }); + if (idx < serviceList.length - 1) y += LINE_H * 0.5; + }); + } + const total = pdf.getNumberOfPages(); for (let i = 1; i <= total; i++) { pdf.setPage(i); diff --git a/src/routes/documents.pleading.new.tsx b/src/routes/documents.pleading.new.tsx index 87f933b..a8dc10c 100644 --- a/src/routes/documents.pleading.new.tsx +++ b/src/routes/documents.pleading.new.tsx @@ -13,13 +13,14 @@ import { import { Switch } from "@/components/ui/switch"; import { FL_CIRCUITS } from "@/lib/florida"; import { Packer } from "docx"; -import { buildPleadingDoc, downloadPleading, type PleadingFootnote, type PleadingSignature } from "@/lib/docx-pleading"; +import { buildPleadingDoc, downloadPleading, type PleadingFootnote, type PleadingSignature, type ServiceContact } from "@/lib/docx-pleading"; import { downloadPleadingPdf } from "@/lib/pdf-pleading"; import { RichTextEditor } from "@/components/documents/rich-text-editor"; +import { ContactPickerPopover } from "@/components/clients/contact-picker-popover"; import { supabase } from "@/integrations/supabase/client"; import { useAuth } from "@/lib/auth"; import { toast } from "sonner"; -import { Download, FileText, Save, Loader2, Plus, Trash2, PenLine } from "lucide-react"; +import { Download, FileText, Save, Loader2, Plus, Trash2, PenLine, Users } from "lucide-react"; export const Route = createFileRoute("/documents/pleading/new")({ component: PleadingNewPage, @@ -46,6 +47,8 @@ function PleadingNewPage() { const [footerRight, setFooterRight] = useState(""); const [docName, setDocName] = useState("Pleading"); const [saving, setSaving] = useState(false); + const [serviceList, setServiceList] = useState([]); + const [serviceListTitle, setServiceListTitle] = useState("SERVICE LIST"); // Attorney signature (loaded from profile) const [includeSignature, setIncludeSignature] = useState(true); @@ -124,6 +127,8 @@ function PleadingNewPage() { if (typeof p.footerLeft === "string") setFooterLeft(p.footerLeft); if (typeof p.footerCenter === "string") setFooterCenter(p.footerCenter); if (typeof p.footerRight === "string") setFooterRight(p.footerRight); + if (Array.isArray(p.serviceList)) setServiceList(p.serviceList); + if (typeof p.serviceListTitle === "string") setServiceListTitle(p.serviceListTitle); setDocName(`${data.name} (copy)`); toast.success("Loaded saved pleading"); })(); @@ -146,6 +151,8 @@ function PleadingNewPage() { title, bodyHtml, footnotes, footerLeft, footerCenter, footerRight, signature: buildSignature(), + serviceList, + serviceListTitle, }; const headerLine1 = `IN THE ${courtType} COURT OF THE ${circuit} JUDICIAL CIRCUIT,`; @@ -437,7 +444,141 @@ function PleadingNewPage() { - {/* Footer */} + {/* Service list (optional) */} + + +
+
+ +

+ Optional. If you add any contacts, a dedicated page is appended to the pleading listing each one. +

+
+ { + setServiceList((prev) => [ + ...prev, + { + name: c.name, + role: c.title || undefined, + company: c.company || undefined, + email: c.email || undefined, + phone: c.phone || undefined, + address: "", + }, + ]); + }} + /> +
+ +
+ + setServiceListTitle(e.target.value)} + placeholder="SERVICE LIST" + /> +
+ + {serviceList.length === 0 ? ( +
+ No contacts added. Use “Add from contacts” to include a service list page. +
+ ) : ( +
+ {serviceList.map((c, idx) => ( +
+
+
+ + setServiceList((prev) => + prev.map((x, i) => (i === idx ? { ...x, name: e.target.value } : x)), + ) + } + /> + + setServiceList((prev) => + prev.map((x, i) => (i === idx ? { ...x, role: e.target.value } : x)), + ) + } + /> + + setServiceList((prev) => + prev.map((x, i) => (i === idx ? { ...x, company: e.target.value } : x)), + ) + } + /> + + setServiceList((prev) => + prev.map((x, i) => (i === idx ? { ...x, email: e.target.value } : x)), + ) + } + /> + + setServiceList((prev) => + prev.map((x, i) => (i === idx ? { ...x, phone: e.target.value } : x)), + ) + } + /> +