diff --git a/App/CLAUDE.md b/App/CLAUDE.md index 5df2eb6..66480ac 100644 --- a/App/CLAUDE.md +++ b/App/CLAUDE.md @@ -154,7 +154,7 @@ Spend analytics under a **Reports** sidebar section (with a **"Purchasing"** sub **Spend definition** (`lib/reports.ts`, the pure/unit-tested core): a PO counts once it reaches `POST_APPROVAL_STATUSES`, dated by `approvedAt`, valued at the full `totalAmount` — the same basis as the dashboard tiles. FY is the Indian **Apr–Mar** year. `getReportDataset()` does one query pass; everything else is pure functions over it. **`allocatePoSpend()`** splits each PO across the accounting codes its **line items** carry (line `accountId`, falling back to the PO-level account), **proportionally** so the per-PO rows always sum back to `totalAmount` — so multi-account POs are attributed correctly in the accounting-code report. `poCount` is **distinct POs** (a multi-account PO yields several rows). Account spend rolls leaf descendants up via `buildAccountIndex().leavesUnder`. -**Filters** live in the **URL query** so the server component re-renders — no client fetching: `gran` (**weekly** / monthly / yearly), `fy`, `month` (weekly), `scope` (Top/Bottom-N), `parent` (accounting drill), `tier` / `break` / `topn` (detail breakdowns), and `sel` + `cmp` (the **custom "Add to graph"** multi-select — tick rows via the `` links, then `cmp=1` compares just the selected set). Weekly focuses one FY month and buckets by week-of-month (W1–W5). The shared `` (client) writes the params; charts are **recharts** (`components/reports/charts.tsx`); KPIs/tables/breadcrumbs are server-rendered. Export → `/api/reports/spend?dim=…` (CSV mirroring the on-screen view, incl. the custom selection). +**Filters** live in the **URL query** so the server component re-renders — no client fetching: `gran` (**weekly** / monthly / yearly), `fy`, `month` (weekly), `scope` (Top/Bottom-N), `parent` (accounting drill), `tier` / `break` / `topn` (detail breakdowns), and `sel` + `cmp` (the **custom "Add to graph"** multi-select — tick rows via the `` links, then `cmp=1` compares just the selected set). Weekly focuses one FY month and buckets by week-of-month (W1–W5). The shared `` (client) writes the params; charts are **recharts** (`components/reports/charts.tsx`) — the comparison chart plots **one colour-coded series per item** (cost centre / accounting code) in every granularity, including the yearly grouped-bars view (x-axis = FYs, a coloured bar per item — not one colour per year); KPIs/tables/breadcrumbs are server-rendered. Export → `/api/reports/spend?dim=…` (CSV mirroring the on-screen view, incl. the custom selection). Sites are **not** cost centres (only vessels are). diff --git a/App/app/(portal)/reports/accounting-codes/page.tsx b/App/app/(portal)/reports/accounting-codes/page.tsx index cf5a9c8..c48f9ec 100644 --- a/App/app/(portal)/reports/accounting-codes/page.tsx +++ b/App/app/(portal)/reports/accounting-codes/page.tsx @@ -79,25 +79,16 @@ export default async function AccountingCodesReport({ const prevT = nf >= 2 ? shown.reduce((s, r) => s + r.fyTotals[nf - 2], 0) : 0; const yoy = prevT ? ((curT - prevT) / prevT) * 100 : 0; + // One distinct colour per accounting code (series) in every granularity; the + // x-axis is months / weeks / financial years. const colored = (i: number) => SERIES_COLORS[i % SERIES_COLORS.length]; - let chartData: Record[]; - let series: Series[]; - if (yearly) { - chartData = shown.map((r) => { - const row: Record = { name: r.node.code }; - ds.fys.forEach((y, i) => (row[fyLabel(y)] = r.fyTotals[i])); - return row; - }); - series = ds.fys.map((y, i) => ({ key: fyLabel(y), color: colored(i) })); - } else { - const labels = weekly ? [...WEEK_LABELS] : [...FY_MONTHS]; - chartData = labels.map((lab, i) => { - const row: Record = { x: lab }; - shown.forEach((r) => (row[r.node.code] = sparkOf(r)[i])); - return row; - }); - series = shown.map((r, i) => ({ key: r.node.code, color: colored(i) })); - } + const chartLabels = yearly ? ds.fys.map(fyLabel) : weekly ? [...WEEK_LABELS] : [...FY_MONTHS]; + const chartData: Record[] = chartLabels.map((lab, i) => { + const row: Record = { x: lab }; + shown.forEach((r) => (row[r.node.code] = sparkOf(r)[i])); + return row; + }); + const series: Series[] = shown.map((r, i) => ({ key: r.node.code, color: colored(i) })); const monthLabel = (i: number) => `${FY_MONTHS[i]} '${String((fy + (i >= 9 ? 1 : 0)) % 100).padStart(2, "0")}`; const periodLabel = yearly ? ds.fys.map(fyLabel).join(" · ") : weekly ? `${monthLabel(month)} · ${fyLabel(fy)}` : fyLabel(fy); @@ -190,7 +181,7 @@ export default async function AccountingCodesReport({

{periodLabel} - +
diff --git a/App/app/(portal)/reports/cost-centres/page.tsx b/App/app/(portal)/reports/cost-centres/page.tsx index acccab0..98ea42f 100644 --- a/App/app/(portal)/reports/cost-centres/page.tsx +++ b/App/app/(portal)/reports/cost-centres/page.tsx @@ -64,26 +64,17 @@ export default async function CostCentresReport({ const prevT = nf >= 2 ? shown.reduce((s, r) => s + r.fyTotals[nf - 2], 0) : 0; const yoy = prevT ? ((curT - prevT) / prevT) * 100 : 0; - // Chart data. + // Chart data — one distinct colour per item (series) in every granularity; the + // x-axis is months / weeks / financial years. (Yearly is grouped bars per item, + // not per FY, so each cost centre keeps its own colour.) const colored = (i: number) => SERIES_COLORS[i % SERIES_COLORS.length]; - let chartData: Record[]; - let series: Series[]; - if (yearly) { - chartData = shown.map((r) => { - const row: Record = { name: r.name }; - ds.fys.forEach((y, i) => (row[fyLabel(y)] = r.fyTotals[i])); - return row; - }); - series = ds.fys.map((y, i) => ({ key: fyLabel(y), color: colored(i) })); - } else { - const labels = weekly ? [...WEEK_LABELS] : [...FY_MONTHS]; - chartData = labels.map((lab, i) => { - const row: Record = { x: lab }; - shown.forEach((r) => (row[r.name] = sparkOf(r)[i])); - return row; - }); - series = shown.map((r, i) => ({ key: r.name, color: colored(i) })); - } + const chartLabels = yearly ? ds.fys.map(fyLabel) : weekly ? [...WEEK_LABELS] : [...FY_MONTHS]; + const chartData: Record[] = chartLabels.map((lab, i) => { + const row: Record = { x: lab }; + shown.forEach((r) => (row[r.name] = sparkOf(r)[i])); + return row; + }); + const series: Series[] = shown.map((r, i) => ({ key: r.name, color: colored(i) })); const monthLabel = (i: number) => `${FY_MONTHS[i]} '${String((fy + (i >= 9 ? 1 : 0)) % 100).padStart(2, "0")}`; const periodLabel = yearly ? ds.fys.map(fyLabel).join(" · ") : weekly ? `${monthLabel(month)} · ${fyLabel(fy)}` : fyLabel(fy); @@ -158,7 +149,7 @@ export default async function CostCentresReport({

{periodLabel}
- +