mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Unify financial report styling with branded cover page
Extract the general Report Generator's branded cover (cover image/band, logo, title, prepared-for/by) into shared src/lib/reportCover.ts. Financial reports now open with the same cover: platform AccountingReportsPage via new renderReportPdfWithCover(), and the Zoho/Board financial reports (zohoFinancialReportPdf generators). ReportGeneratorPage refactored to use the shared module (removes duplicated cover code). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,9 +18,10 @@ import { money, fmtDate } from "./lib/format";
|
||||
import jsPDF from "jspdf";
|
||||
import autoTable from "jspdf-autotable";
|
||||
import {
|
||||
renderReportPdf, fmtAmount,
|
||||
renderReportPdfWithCover, fmtAmount,
|
||||
type StructuredReport, type StructuredRow,
|
||||
} from "./lib/reportPdf";
|
||||
import { drawReportCoverPage, type ReportCoverData } from "@/lib/reportCover";
|
||||
import { calcNetIncome, isRetainedEarnings, isCurrentYearEarnings, isSystemEquityAccount } from "./lib/earnings";
|
||||
import {
|
||||
computePnL, computeMargins, toMinor, fromMinor, PnlValidationError,
|
||||
@@ -334,14 +335,27 @@ export default function AccountingReportsPage() {
|
||||
const hasOwnExport = active === "trial-balance" || active === "general-ledger";
|
||||
const anyExportable = !!(structured || flat || exportFlat);
|
||||
|
||||
const doExportPDF = () => {
|
||||
const doExportPDF = async () => {
|
||||
const fileBase = `${activeMeta.name.replace(/\s+/g, "-").toLowerCase()}-${from}-to-${to}`;
|
||||
const src = flat ?? exportFlat;
|
||||
const cover: ReportCoverData = {
|
||||
title: activeMeta.name,
|
||||
date: rangeLabel,
|
||||
companyName: associationName ?? "Company",
|
||||
preparedBy: "Avria Community Management, LLC",
|
||||
};
|
||||
if (structured) {
|
||||
const doc = renderReportPdf(structured, { companyName: associationName ?? "Company", appName: APP_NAME, rangeLabel, currency: cur, showCodes, showCompare, showZero });
|
||||
const doc = await renderReportPdfWithCover(
|
||||
structured,
|
||||
{ companyName: associationName ?? "Company", appName: APP_NAME, rangeLabel, currency: cur, showCodes, showCompare, showZero },
|
||||
cover,
|
||||
);
|
||||
doc.save(`${fileBase}.pdf`);
|
||||
} else if (src) {
|
||||
const doc = new jsPDF({ orientation: src.columns.length > 6 ? "landscape" : "portrait" });
|
||||
// Shared branded cover page, then the tabular report on the next page.
|
||||
await drawReportCoverPage(doc, doc.internal.pageSize.getWidth(), doc.internal.pageSize.getHeight(), cover);
|
||||
doc.addPage();
|
||||
doc.setFont("helvetica", "bold"); doc.setFontSize(14); doc.setTextColor(33, 37, 41);
|
||||
doc.text(src.title, 14, 16);
|
||||
doc.setFont("helvetica", "bold"); doc.setFontSize(9);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import jsPDF from "jspdf";
|
||||
import { drawReportCoverPage, type ReportCoverData } from "@/lib/reportCover";
|
||||
|
||||
export type RowKind = "section" | "group" | "sub" | "total" | "grand" | "spacer";
|
||||
|
||||
@@ -47,8 +48,9 @@ export function fmtAmount(n: number | undefined | null): string {
|
||||
return n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
export function renderReportPdf(report: StructuredReport, opts: RenderOpts): jsPDF {
|
||||
const doc = new jsPDF({ unit: "pt", format: "letter" });
|
||||
/** Draw the report body onto `doc`, starting on its current page. */
|
||||
function buildReport(doc: jsPDF, report: StructuredReport, opts: RenderOpts): jsPDF {
|
||||
const firstPage = doc.getNumberOfPages();
|
||||
const W = doc.internal.pageSize.getWidth();
|
||||
const H = doc.internal.pageSize.getHeight();
|
||||
const ML = 40, MR = 40;
|
||||
@@ -114,11 +116,12 @@ export function renderReportPdf(report: StructuredReport, opts: RenderOpts): jsP
|
||||
const drawFooter = () => {
|
||||
const total = doc.getNumberOfPages();
|
||||
const created = new Date().toLocaleDateString("en-US");
|
||||
for (let p = 1; p <= total; p++) {
|
||||
// Footer only on content pages (skip any preceding cover page).
|
||||
for (let p = firstPage; p <= total; p++) {
|
||||
doc.setPage(p);
|
||||
doc.setFont("helvetica", "normal"); doc.setFontSize(8); doc.setTextColor(...MUTED);
|
||||
doc.text(`Created on ${created}`, ML, H - 28);
|
||||
doc.text(`Page ${p}`, contentR, H - 28, { align: "right" });
|
||||
doc.text(`Page ${p - firstPage + 1}`, contentR, H - 28, { align: "right" });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -233,3 +236,24 @@ export function renderReportPdf(report: StructuredReport, opts: RenderOpts): jsP
|
||||
drawFooter();
|
||||
return doc;
|
||||
}
|
||||
|
||||
/** Render a financial report PDF (no cover page). */
|
||||
export function renderReportPdf(report: StructuredReport, opts: RenderOpts): jsPDF {
|
||||
const doc = new jsPDF({ unit: "pt", format: "letter" });
|
||||
return buildReport(doc, report, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a financial report PDF that opens with the shared branded cover page
|
||||
* (same look as the general Report Generator), followed by the report body.
|
||||
*/
|
||||
export async function renderReportPdfWithCover(
|
||||
report: StructuredReport,
|
||||
opts: RenderOpts,
|
||||
cover: ReportCoverData,
|
||||
): Promise<jsPDF> {
|
||||
const doc = new jsPDF({ unit: "pt", format: "letter" });
|
||||
await drawReportCoverPage(doc, doc.internal.pageSize.getWidth(), doc.internal.pageSize.getHeight(), cover);
|
||||
doc.addPage();
|
||||
return buildReport(doc, report, opts);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user