fix(reports): colour the comparison chart per item in yearly mode too
The monthly/weekly comparison already drew one colour per item (series = items). Yearly mode instead coloured by financial year (series = FYs, items on the x-axis), so multiple cost centres / accounting codes in the same yearly graph shared colours. Unify all three granularities to series = items: the x-axis is months / weeks / FYs and each item keeps its own distinct colour (yearly becomes grouped bars per item rather than per year). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
47ac2c7813
commit
cb0e754238
3 changed files with 22 additions and 40 deletions
|
|
@ -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 `<SelectCheckbox>` links, then `cmp=1` compares just the selected set). Weekly focuses one FY month and buckets by week-of-month (W1–W5). The shared `<ReportsToolbar>` (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 `<SelectCheckbox>` links, then `cmp=1` compares just the selected set). Weekly focuses one FY month and buckets by week-of-month (W1–W5). The shared `<ReportsToolbar>` (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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string, string | number>[];
|
||||
let series: Series[];
|
||||
if (yearly) {
|
||||
chartData = shown.map((r) => {
|
||||
const row: Record<string, string | number> = { 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<string, string | number> = { 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<string, string | number>[] = chartLabels.map((lab, i) => {
|
||||
const row: Record<string, string | number> = { 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({
|
|||
</p>
|
||||
<span className="text-xs text-neutral-400">{periodLabel}</span>
|
||||
</div>
|
||||
<ComparisonChart kind={yearly ? "bars" : "lines"} data={chartData} xKey={yearly ? "name" : "x"} series={series} />
|
||||
<ComparisonChart kind={yearly ? "bars" : "lines"} data={chartData} xKey="x" series={series} />
|
||||
</div>
|
||||
|
||||
<div className="overflow-hidden rounded-lg border border-neutral-200 bg-white">
|
||||
|
|
|
|||
|
|
@ -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<string, string | number>[];
|
||||
let series: Series[];
|
||||
if (yearly) {
|
||||
chartData = shown.map((r) => {
|
||||
const row: Record<string, string | number> = { 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<string, string | number> = { 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<string, string | number>[] = chartLabels.map((lab, i) => {
|
||||
const row: Record<string, string | number> = { 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({
|
|||
</p>
|
||||
<span className="text-xs text-neutral-400">{periodLabel}</span>
|
||||
</div>
|
||||
<ComparisonChart kind={yearly ? "bars" : "lines"} data={chartData} xKey={yearly ? "name" : "x"} series={series} />
|
||||
<ComparisonChart kind={yearly ? "bars" : "lines"} data={chartData} xKey="x" series={series} />
|
||||
</div>
|
||||
|
||||
<div className="overflow-hidden rounded-lg border border-neutral-200 bg-white">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue