From 39829b7e1b825453bd8fc6741f823ee87644f48b Mon Sep 17 00:00:00 2001 From: renee-png Date: Mon, 1 Jun 2026 22:17:04 -0400 Subject: [PATCH] Accounting statement matches main account-statement; fix payments cut off - Homeowner "Export/Email Statement" now uses the same generateLedgerStatement layout as the main-app account statement (account holder, amounts-due breakdown, categorized columns incl. Pay (AR), no cover page) instead of the branded-cover table. - Ledger view: default the "To" date to the latest entry when it's beyond today, so a payment dated when an invoice was marked paid (updated_at) is no longer filtered out of the rows / Total Paid. Co-Authored-By: Claude Opus 4.8 --- .../AccountingCustomerDetailPage.tsx | 80 ++++++++----------- 1 file changed, 33 insertions(+), 47 deletions(-) diff --git a/src/pages/accounting/AccountingCustomerDetailPage.tsx b/src/pages/accounting/AccountingCustomerDetailPage.tsx index 2ab54b7..63ad449 100644 --- a/src/pages/accounting/AccountingCustomerDetailPage.tsx +++ b/src/pages/accounting/AccountingCustomerDetailPage.tsx @@ -14,9 +14,7 @@ import { Badge } from "@/components/ui/badge"; import { ArrowLeft, Download, Mail, FileText, Pencil, Check, X } from "lucide-react"; import { toast } from "sonner"; import { money, fmtDate } from "./lib/format"; -import jsPDF from "jspdf"; -import autoTable from "jspdf-autotable"; -import { drawReportCoverPage } from "@/lib/reportCover"; +import { generateLedgerStatement } from "@/components/unit-profile/UnitLedgerStatementPDF"; function today() { return new Date().toLocaleDateString("en-CA", { timeZone: "America/New_York" }); @@ -47,6 +45,7 @@ export default function AccountingCustomerDetailPage() { const [from, setFrom] = useState(""); const [fromTouched, setFromTouched] = useState(false); const [to, setTo] = useState(today()); + const [toTouched, setToTouched] = useState(false); const [drawer, setDrawer] = useState(null); const [editing, setEditing] = useState(false); const [editForm, setEditForm] = useState(null); @@ -147,6 +146,14 @@ export default function AccountingCustomerDetailPage() { if (!fromTouched && !from && allRows.length) setFrom(allRows[0].date); }, [allRows, fromTouched, from]); + // Extend "To" to the latest entry if it's beyond today, so recently-dated + // payments (e.g. an invoice marked paid today) aren't filtered out. + useEffect(() => { + if (toTouched || !allRows.length) return; + const latest = allRows[allRows.length - 1].date; + if (latest > to) setTo(latest); + }, [allRows, toTouched, to]); + const openingBalance = useMemo( () => allRows.filter((r) => r.date < from).reduce((s, r) => s + r.debit - r.credit, 0), [allRows, from] @@ -242,54 +249,33 @@ export default function AccountingCustomerDetailPage() { qc.invalidateQueries({ queryKey: ["customers", cid] }); }; - const exportStatement = async () => { + const exportStatement = () => { if (!homeowner) return; - const doc = new jsPDF({ unit: "pt", format: "letter" }); - // Shared branded cover page (matches all other report exports), then detail. - await drawReportCoverPage(doc, doc.internal.pageSize.getWidth(), doc.internal.pageSize.getHeight(), { - title: "Homeowner Statement", - date: `${fmtDate(from)} – ${fmtDate(to)}`, - companyName: associationName ?? "Association", - preparedBy: "Avria Community Management, LLC", + if (!allRows.length) { toast.error("No ledger activity to export"); return; } + // Use the same account-statement layout as the main-app owner ledger + // (account holder, amounts-due breakdown, categorized columns, no cover). + const entries = allRows.map((r) => ({ + date: r.date, + debit: r.debit, + credit: r.credit, + transaction_type: r.credit > 0 ? "payment" : (r.description || ""), + description: r.description, + })); + generateLedgerStatement({ + unitData: { + unit_number: homeowner.unit_number, + address: homeowner.property_address, + account_number: homeowner.account_number, + }, + owners: [{ is_primary: true, first_name: homeowner.name, last_name: "" }], + entries, + associationName: associationName ?? "Association", }); - doc.addPage(); - doc.setFontSize(16); - doc.text(associationName ?? "Association", 40, 50); - doc.setFontSize(14); - doc.text("Homeowner Statement", 40, 100); - doc.setFontSize(10); - doc.text(homeowner.name, 40, 118); - if (homeowner.property_address) doc.text(`Property: ${homeowner.property_address}${homeowner.unit_number ? `, ${homeowner.unit_number}` : ""}`, 40, 132); - if (homeowner.lot_number) doc.text(`Lot: ${homeowner.lot_number}`, 40, 146); - doc.text(`Period: ${fmtDate(from)} – ${fmtDate(to)}`, 40, homeowner.lot_number ? 162 : 146); - - const startY = homeowner.lot_number ? 182 : 166; - autoTable(doc, { - startY, - head: [["Date", "Type", "Ref #", "Description", "Charges", "Payments", "Balance"]], - body: [ - [fmtDate(from), "", "", "Opening Balance", "", "", money(openingBalance, cur)], - ...withRunning.map((r) => [ - fmtDate(r.date), - r.type, - r.ref, - r.description, - r.debit ? money(r.debit, cur) : "", - r.credit ? money(r.credit, cur) : "", - money(r.running, cur), - ]), - ], - foot: [["", "", "", "Current Balance Due", "", "", money(currentBalance, cur)]], - styles: { fontSize: 9 }, - headStyles: { fillColor: [240, 240, 240], textColor: 20 }, - footStyles: { fillColor: [240, 240, 240], textColor: 20, fontStyle: "bold" }, - }); - doc.save(`statement-${homeowner.name.replace(/\s+/g, "_")}.pdf`); }; - const emailStatement = async () => { + const emailStatement = () => { if (!homeowner) return; - await exportStatement(); + exportStatement(); const subject = encodeURIComponent(`Statement from ${associationName ?? "us"}`); const body = encodeURIComponent( `Hello ${homeowner.name},\n\nPlease find attached your homeowner statement for the period ${fmtDate(from)} – ${fmtDate(to)}.\nCurrent balance due: ${money(currentBalance, cur)}.\n\nThank you.` @@ -494,7 +480,7 @@ export default function AccountingCustomerDetailPage() {
{ setFromTouched(true); setFrom(e.target.value); }} className="w-40" />
-
setTo(e.target.value)} className="w-40" />
+
{ setToTouched(true); setTo(e.target.value); }} className="w-40" />