Reports: drop Income Statement, add Monthly columns view to P&L

The Income Statement was a duplicate of the P&L, so removed it from the report
menu. The P&L now has a 'Monthly columns' toggle that renders the same
multi-period (by month/quarter/year) breakdown the income statement provided —
relabeled 'Profit & Loss'. Default P&L view is unchanged (single period).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 18:13:56 -04:00
parent 5714543533
commit ac454e8f5e
+12 -8
View File
@@ -53,7 +53,6 @@ const FINANCIAL: ReportId[] = ["pnl", "balance-sheet", "cash-flow", "movement-of
const GROUPS = [ const GROUPS = [
{ name: "Business Overview", reports: [ { name: "Business Overview", reports: [
{ id: "pnl" as ReportId, name: "Profit & Loss" }, { id: "pnl" as ReportId, name: "Profit & Loss" },
{ id: "income-statement" as ReportId, name: "Income Statement" },
{ id: "balance-sheet" as ReportId, name: "Balance Sheet" }, { id: "balance-sheet" as ReportId, name: "Balance Sheet" },
{ id: "cash-flow" as ReportId, name: "Cash Flow Statement" }, { id: "cash-flow" as ReportId, name: "Cash Flow Statement" },
{ id: "movement-of-equity" as ReportId, name: "Movement of Equity" }, { id: "movement-of-equity" as ReportId, name: "Movement of Equity" },
@@ -329,6 +328,7 @@ export default function AccountingReportsPage({ association }: { association?: {
// Toggles // Toggles
const [showCodes, setShowCodes] = useState(false); const [showCodes, setShowCodes] = useState(false);
const [showZero, setShowZero] = useState(false); const [showZero, setShowZero] = useState(false);
const [pnlMonthView, setPnlMonthView] = useState(false);
const { data: companyMeta } = useQuery({ const { data: companyMeta } = useQuery({
queryKey: ["company-fy", cid], queryKey: ["company-fy", cid],
@@ -469,7 +469,8 @@ export default function AccountingReportsPage({ association }: { association?: {
}, [active, arOpen, data, flat, structured, cur, activeMeta.name]); }, [active, arOpen, data, flat, structured, cur, activeMeta.name]);
// Reports whose export is handled internally (own PDF/CSV buttons inside the component) // Reports whose export is handled internally (own PDF/CSV buttons inside the component)
const hasOwnExport = active === "trial-balance" || active === "general-ledger" || active === "budget-vs-actuals" || active === "income-statement" const hasOwnExport = active === "trial-balance" || active === "general-ledger" || active === "budget-vs-actuals"
|| (active === "pnl" && pnlMonthView)
|| active === "ar-aging-property" || active === "prepaid-homeowners" || active === "cash-disbursement"; || active === "ar-aging-property" || active === "prepaid-homeowners" || active === "cash-disbursement";
const anyExportable = !!(structured || flat || exportFlat); const anyExportable = !!(structured || flat || exportFlat);
@@ -646,6 +647,9 @@ export default function AccountingReportsPage({ association }: { association?: {
<div className="flex flex-wrap gap-4 border-t pt-3"> <div className="flex flex-wrap gap-4 border-t pt-3">
<Toggle id="t-codes" checked={showCodes} onChange={setShowCodes} label="Account codes" /> <Toggle id="t-codes" checked={showCodes} onChange={setShowCodes} label="Account codes" />
<Toggle id="t-zero" checked={showZero} onChange={setShowZero} label="Zero-balance accounts" /> <Toggle id="t-zero" checked={showZero} onChange={setShowZero} label="Zero-balance accounts" />
{active === "pnl" && (
<Toggle id="t-pnl-month" checked={pnlMonthView} onChange={setPnlMonthView} label="Monthly columns" />
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@@ -653,7 +657,7 @@ export default function AccountingReportsPage({ association }: { association?: {
{active === "budget-vs-actuals" && ( {active === "budget-vs-actuals" && (
<BudgetVsActuals companyId={cid} from={from} to={to} currency={cur} companyName={associationName ?? "Company"} rangeLabel={rangeLabel} logoUrl={logoUrl} /> <BudgetVsActuals companyId={cid} from={from} to={to} currency={cur} companyName={associationName ?? "Company"} rangeLabel={rangeLabel} logoUrl={logoUrl} />
)} )}
{active === "income-statement" && ( {active === "pnl" && pnlMonthView && (
<IncomeStatementReport companyId={cid} companyName={associationName ?? "Company"} from={from} to={to} currency={cur} logoUrl={logoUrl} /> <IncomeStatementReport companyId={cid} companyName={associationName ?? "Company"} from={from} to={to} currency={cur} logoUrl={logoUrl} />
)} )}
{active === "trial-balance" && ( {active === "trial-balance" && (
@@ -679,7 +683,7 @@ export default function AccountingReportsPage({ association }: { association?: {
<ReconciliationReport d={data} currency={cur} /> <ReconciliationReport d={data} currency={cur} />
</ReportSheet> </ReportSheet>
)} )}
{isFinancial && ( {isFinancial && !(active === "pnl" && pnlMonthView) && (
!data ? ( !data ? (
<Card><CardContent className="p-6"><div className="py-8 text-center text-sm text-muted-foreground">Loading</div></CardContent></Card> <Card><CardContent className="p-6"><div className="py-8 text-center text-sm text-muted-foreground">Loading</div></CardContent></Card>
) : structured ? ( ) : structured ? (
@@ -927,7 +931,7 @@ function IncomeStatementReport({ companyId, companyName, from, to, currency, log
const doc = new jsPDF({ unit: "pt", format: "letter", orientation: "landscape" }); const doc = new jsPDF({ unit: "pt", format: "letter", orientation: "landscape" });
const logo = await loadBrandedLogo(logoUrl); const logo = await loadBrandedLogo(logoUrl);
const startY = drawBrandedHeader(doc, { const startY = drawBrandedHeader(doc, {
logo, title: "Income Statement", subtitle, logo, title: "Profit & Loss", subtitle,
metaLines: [{ label: "Properties:", value: companyName }], metaLines: [{ label: "Properties:", value: companyName }],
}); });
const head = [["Account", ...periods.map((p) => p.label), "Total"]]; const head = [["Account", ...periods.map((p) => p.label), "Total"]];
@@ -973,7 +977,7 @@ function IncomeStatementReport({ companyId, companyName, from, to, currency, log
}, },
}); });
drawBrandedFooter(doc); drawBrandedFooter(doc);
doc.save(`income-statement-${gran}-${from}-to-${to}.pdf`); doc.save(`profit-loss-${gran}-${from}-to-${to}.pdf`);
}; };
const exportCsv = () => { const exportCsv = () => {
@@ -997,7 +1001,7 @@ function IncomeStatementReport({ companyId, companyName, from, to, currency, log
const blob = new Blob([lines.join("\n")], { type: "text/csv" }); const blob = new Blob([lines.join("\n")], { type: "text/csv" });
const a = document.createElement("a"); const a = document.createElement("a");
a.href = URL.createObjectURL(blob); a.href = URL.createObjectURL(blob);
a.download = `income-statement-${gran}-${from}-to-${to}.csv`; a.download = `profit-loss-${gran}-${from}-to-${to}.csv`;
a.click(); a.click();
URL.revokeObjectURL(a.href); URL.revokeObjectURL(a.href);
}; };
@@ -1034,7 +1038,7 @@ function IncomeStatementReport({ companyId, companyName, from, to, currency, log
) : !hasRows ? ( ) : !hasRows ? (
<Card><CardContent className="p-8 text-center text-sm text-muted-foreground">No income or expense activity in this range.</CardContent></Card> <Card><CardContent className="p-8 text-center text-sm text-muted-foreground">No income or expense activity in this range.</CardContent></Card>
) : ( ) : (
<ReportSheet title="Income Statement" subtitle={subtitle} companyName={companyName} logoUrl={logoUrl}> <ReportSheet title="Profit & Loss" subtitle={subtitle} companyName={companyName} logoUrl={logoUrl}>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>