import { auth } from "@/auth"; import { redirect } from "next/navigation"; import Link from "next/link"; import type { Metadata } from "next"; import { ChevronRight } from "lucide-react"; import { hasPermission } from "@/lib/permissions"; import { formatCurrency, formatCompactINR } from "@/lib/utils"; import { getReportDataset, costCentreRows, costCentreWeekly, applyScope, parseScope, parseGranularity, resolveFy, resolveMonth, parseSel, toggleSel, fyLabel, FY_MONTHS, WEEK_LABELS, SCOPE_LABELS, type CostCentreSpend, } from "@/lib/reports"; import { ReportsToolbar } from "@/components/reports/reports-toolbar"; import { ComparisonChart, Sparkline, type Series } from "@/components/reports/charts"; import { SERIES_COLORS } from "@/lib/report-colors"; import { Kpi, KpiStrip } from "@/components/reports/kpi"; import { ReportBreadcrumb, ReportTitle, SelectCheckbox, CompareBar } from "@/components/reports/report-header"; export const metadata: Metadata = { title: "Cost Centres — Reports" }; const sum = (a: number[]) => a.reduce((x, y) => x + y, 0); export default async function CostCentresReport({ searchParams, }: { searchParams: Promise<{ fy?: string; gran?: string; scope?: string; month?: string; sel?: string; cmp?: string }>; }) { const session = await auth(); if (!session?.user) return null; if (!hasPermission(session.user.role, "view_analytics")) redirect("/dashboard"); const sp = await searchParams; const ds = await getReportDataset(); const gran = parseGranularity(sp.gran); const scope = parseScope(sp.scope); const fy = resolveFy(ds, sp.fy); const yearly = gran === "yearly"; const weekly = gran === "weekly"; const month = resolveMonth(ds, fy, sp.month); const sel = parseSel(sp.sel); const cmp = sp.cmp === "1" && sel.length > 0; const ranked = costCentreRows(ds, fy); const rankOf = (r: CostCentreSpend) => (yearly ? sum(r.fyTotals) : weekly ? r.months[month] : r.total); ranked.sort((a, b) => rankOf(b) - rankOf(a)); const shown = cmp ? ranked.filter((r) => sel.includes(r.id)) : applyScope(ranked, scope); const grand = shown.reduce((s, r) => s + rankOf(r), 0); const top = shown[0]; const sparkOf = (r: CostCentreSpend) => (yearly ? r.fyTotals : weekly ? costCentreWeekly(ds, r.id, fy, month) : r.months); const nf = ds.fys.length; const curT = nf >= 1 ? shown.reduce((s, r) => s + r.fyTotals[nf - 1], 0) : 0; 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 — 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]; 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); // Query-string helpers (preserve current filters). const baseParams: Record = { fy: String(fy), gran: gran === "monthly" ? undefined : gran, scope: scope === "top5" ? undefined : scope, month: weekly ? String(month) : undefined, }; const qs = (extra: Record) => { const p = new URLSearchParams(); for (const [k, v] of Object.entries({ ...baseParams, ...extra })) if (v) p.set(k, v); const s = p.toString(); return s ? `?${s}` : ""; }; const selHref = (id: string) => { const next = toggleSel(sel, id); return `/reports/cost-centres${qs({ sel: next.join(",") || undefined, cmp: cmp && next.length ? "1" : undefined })}`; }; const detailHref = (id: string) => `/reports/cost-centres/${id}${qs({ scope: undefined })}`; const exportHref = `/api/reports/spend?dim=cost-centre&fy=${fy}&gran=${gran}&scope=${scope}${cmp ? `&sel=${sel.join(",")}` : ""}`; return (
({ value: i, label: monthLabel(i) }))} exportHref={exportHref} /> {cmp ? ( ← Back to browse ) : ( sel.length > 0 && )} {grand === 0 ? (
No approved spend recorded for {periodLabel} yet.
) : ( <> = 0 ? "+" : ""}${yoy.toFixed(1)}%`} sub="vs prior FY" delta={yoy} />

{yearly ? "Spend by cost centre — year over year" : weekly ? "Weekly spend by cost centre" : "Monthly spend by cost centre"}

{periodLabel}
{shown.map((r) => { const value = rankOf(r); const pct = grand ? (value / grand) * 100 : 0; return ( ); })}
Cost Centre Trend Total Spend % of Shown POs
{r.name} {r.code}
{formatCurrency(value)}
{pct.toFixed(0)}%
{r.poCount}
)}
); }