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:
@@ -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,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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user