Implements the wiki "Reports Mockup" as a Reports → Purchasing sidebar section, wired to real approved-PO spend. Two report families, each index → drill/detail: - Cost Centres (/reports/cost-centres) — spend compared across vessels; row opens a cost-centre report with a Top-accounting-codes breakdown re-pivotable by tier (Heading/Sub/Leaf) + Top-N. - Accounting Codes (/reports/accounting-codes) — drills the Account tree (headings → sub → leaves) via ?parent=; a leaf opens its report broken down by cost centre (or, for a non-leaf, by sub-account). Shared: a pinned filter toolbar (Granularity Monthly/Yearly, Financial Year, Show Top5/Top10/Bottom5/All) whose values live in the URL query so the server component re-renders — no client fetching. KPI tiles, recharts comparison/trend/ breakdown charts, per-row trend sparklines, and CSV export (/api/reports/spend). - lib/reports.ts: the pure, unit-tested aggregation core. Spend = a PO once it reaches POST_APPROVAL_STATUSES, dated by approvedAt, valued at totalAmount (the dashboard's basis); Indian Apr–Mar FY; each PO's leaf accountId rolled up to parents. One query in getReportDataset(), everything else pure. - Sidebar: new collapsible "Reports" section with a "Purchasing" subheading (subgroup support added to the Section model). Gated by view_analytics (Manager/SuperUser/Auditor/Admin); export by the same. Deferred (documented): synthetic Weekly granularity, the "Add to graph" custom multi-select, and line-item-level account allocation (v1 uses the PO-level account). Sites are not cost centres — only vessels. Tests: 11 unit cases for the aggregation core + 3 sidebar cases for the Reports section. Full unit suite 303 green; tsc clean. Smoke-tested all routes end to end against seed data (index/drill/detail/export 200; non-analytics role 307/403). Wiki: "Reports Mockup" marked implemented; "Pages and Navigation" lists the new routes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
28 lines
938 B
TypeScript
28 lines
938 B
TypeScript
import { cn } from "@/lib/utils";
|
||
|
||
// Presentational KPI tile (server component — no interactivity). `delta` colours
|
||
// the sub-line green/red for positive/negative changes (e.g. YoY).
|
||
export function Kpi({
|
||
label,
|
||
value,
|
||
sub,
|
||
delta,
|
||
}: {
|
||
label: string;
|
||
value: string;
|
||
sub?: string;
|
||
delta?: number;
|
||
}) {
|
||
const subColor = delta === undefined ? "text-neutral-400" : delta >= 0 ? "text-green-600" : "text-red-600";
|
||
return (
|
||
<div className="rounded-lg border border-neutral-200 bg-white p-4">
|
||
<p className="text-xs font-medium uppercase tracking-wider text-neutral-400">{label}</p>
|
||
<p className="mt-1.5 text-xl font-semibold text-neutral-900">{value}</p>
|
||
<p className={cn("mt-0.5 text-xs", subColor)}>{sub ?? " "}</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function KpiStrip({ children }: { children: React.ReactNode }) {
|
||
return <div className="mb-6 grid grid-cols-2 gap-4 sm:grid-cols-4">{children}</div>;
|
||
}
|