Added 3-column pleading footer

X-Lovable-Edit-ID: edt-5a17f98a-2560-4dde-acba-0c0fe2ea5c06
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-17 02:03:48 +00:00
co-authored by renee-png
2 changed files with 88 additions and 1 deletions
+53 -1
View File
@@ -13,6 +13,7 @@ import {
BorderStyle,
Footer,
LevelFormat,
PageNumber,
} from "docx";
import pkg from "file-saver";
const { saveAs } = pkg;
@@ -33,6 +34,9 @@ export interface PleadingInput {
bodyHtml?: string; // rich HTML body
body?: string; // legacy plain text fallback
footnotes?: PleadingFootnote[];
footerLeft?: string;
footerCenter?: string;
footerRight?: string; // user text prepended to "Page X of Y"
}
const FONT = "Bookman Old Style";
@@ -179,6 +183,54 @@ function htmlToParagraphs(html: string): Paragraph[] {
return out;
}
function footerCell(width: number, align: (typeof AlignmentType)[keyof typeof AlignmentType], children: TextRun[]) {
return new TableCell({
width: { size: width, type: WidthType.DXA },
borders: noBorder,
margins: { top: 40, bottom: 40, left: 60, right: 60 },
children: [
new Paragraph({
alignment: align,
children: children.length ? children : [run("")],
}),
],
});
}
function buildFooterTable(left?: string, center?: string, right?: string): Table {
const total = 9360;
const col = Math.floor(total / 3);
const cols = [col, col, total - 2 * col];
const leftRuns: TextRun[] = left ? [run(left, { size: 20 })] : [];
const centerRuns: TextRun[] = center ? [run(center, { size: 20 })] : [];
// Right column: optional user text + always "Page X of Y"
const rightRuns: TextRun[] = [];
if (right && right.trim()) {
rightRuns.push(run(`${right} `, { size: 20 }));
}
rightRuns.push(run("Page ", { size: 20 }));
rightRuns.push(new TextRun({ children: [PageNumber.CURRENT], font: FONT, size: 20 }));
rightRuns.push(run(" of ", { size: 20 }));
rightRuns.push(new TextRun({ children: [PageNumber.TOTAL_PAGES], font: FONT, size: 20 }));
return new Table({
width: { size: total, type: WidthType.DXA },
columnWidths: cols,
borders: noBorder,
rows: [
new TableRow({
children: [
footerCell(cols[0], AlignmentType.LEFT, leftRuns),
footerCell(cols[1], AlignmentType.CENTER, centerRuns),
footerCell(cols[2], AlignmentType.RIGHT, rightRuns),
],
}),
],
});
}
export function buildPleadingDoc(input: PleadingInput): Document {
const headerLine1 = `IN THE ${input.courtType} COURT OF THE ${input.circuit} JUDICIAL CIRCUIT,`;
const headerLine2 = `IN AND FOR ${input.county.toUpperCase()} COUNTY, FLORIDA`;
@@ -297,7 +349,7 @@ export function buildPleadingDoc(input: PleadingInput): Document {
},
footers: {
default: new Footer({
children: [new Paragraph({ alignment: AlignmentType.CENTER, children: [run("")] })],
children: [buildFooterTable(input.footerLeft, input.footerCenter, input.footerRight)],
}),
},
children: [
+35
View File
@@ -35,6 +35,9 @@ function PleadingNewPage() {
const [title, setTitle] = useState("");
const [bodyHtml, setBodyHtml] = useState("<p></p>");
const [footnotes, setFootnotes] = useState<PleadingFootnote[]>([]);
const [footerLeft, setFooterLeft] = useState("");
const [footerCenter, setFooterCenter] = useState("");
const [footerRight, setFooterRight] = useState("");
const [docName, setDocName] = useState("Pleading");
const [saving, setSaving] = useState(false);
@@ -53,6 +56,7 @@ function PleadingNewPage() {
courtType, circuit, county,
plaintiffs, defendants, caseNumber,
title, bodyHtml, footnotes,
footerLeft, footerCenter, footerRight,
};
const headerLine1 = `IN THE ${courtType} COURT OF THE ${circuit} JUDICIAL CIRCUIT,`;
@@ -279,6 +283,37 @@ function PleadingNewPage() {
)}
</CardContent>
</Card>
{/* Footer */}
<Card className="mt-6">
<CardContent className="p-5 space-y-3">
<div>
<Label className="text-base">Page footer</Label>
<p className="text-xs text-muted-foreground mt-0.5">
Three columns. The right column always ends with <code className="px-1 rounded bg-muted">Page X of Y</code>.
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div className="space-y-1.5">
<Label className="text-xs">Left</Label>
<Input value={footerLeft} onChange={(e) => setFooterLeft(e.target.value)} placeholder="e.g. Smith v. Jones" />
</div>
<div className="space-y-1.5">
<Label className="text-xs">Center</Label>
<Input value={footerCenter} onChange={(e) => setFooterCenter(e.target.value)} placeholder="e.g. Case No. 2025-CA-001234" />
</div>
<div className="space-y-1.5">
<Label className="text-xs">Right (prefix)</Label>
<Input value={footerRight} onChange={(e) => setFooterRight(e.target.value)} placeholder="(optional text before Page X of Y)" />
</div>
</div>
<div className="border rounded-md p-3 bg-muted/20 grid grid-cols-3 text-xs" style={{ fontFamily: '"Bookman Old Style", Georgia, serif' }}>
<div className="text-left truncate">{footerLeft || <span className="text-muted-foreground italic">left</span>}</div>
<div className="text-center truncate">{footerCenter || <span className="text-muted-foreground italic">center</span>}</div>
<div className="text-right truncate">{footerRight ? `${footerRight} ` : ""}Page 1 of N</div>
</div>
</CardContent>
</Card>
</PageContainer>
</ProtectedLayout>
);