"use client"; import { useMemo, useState } from "react"; import { Dialog } from "@/components/ui"; import { Bar, Figures, Panel } from "@/components/portal"; import { csvEsc, csvOf, fmtDate, money, monthLabel, prevMonth } from "@/lib/compute"; import { downloadCsv } from "@/lib/print"; import { HeadActions, PanelEmpty, TableWrap, TOTAL, plural } from "./bits"; import type { ReportData } from "./useReportData"; export const JOURNAL_ID = "tc-rep-journal"; export default function SpendTab({ d, onMonth }: { d: ReportData; onMonth: (m: string) => void }) { const { R, month, mLbl, csv, print, jnTotItems, jnTot } = d; // Only the keys are held: the lines are recounted from the snapshot on every render, so the // dialog still agrees with the row that opened it if stock moves while it is open. const [drill, setDrill] = useState<{ cc: string; dept: string; keys: string[] } | null>(null); const drillRows = useMemo(() => (drill ? drill.keys.flatMap((k) => R.ccLines[k] || []).sort((a, b) => a.date.localeCompare(b.date) || a.who.localeCompare(b.who) || a.item.localeCompare(b.item)) : []), [drill, R]); const drillQty = drillRows.reduce((t, r) => t + r.qty, 0), drillAmt = drillRows.reduce((t, r) => t + r.amt, 0); const csvDrill = () => drill && downloadCsv(`threadcount-cost-centre-${drill.cc.replace(/[^A-Za-z0-9]+/g, "-").toLowerCase()}-${month}.csv`, `Issues behind cost centre,${csvEsc(drill.cc)},${month}\n\n` + csvOf(["Date", "Staff", "Item", "Size", "Qty", "Unit cost", "Value"], [...drillRows.map((r) => [r.date, r.who, r.item, r.size, r.qty, r.unit.toFixed(2), r.amt.toFixed(2)] as (string | number)[]), ["TOTAL", "", "", "", drillQty, "", drillAmt.toFixed(2)]])); const open = (cc: string, dept: string, keys: string[]) => setDrill({ cc: cc === "—" ? "UNALLOCATED" : cc, dept, keys }); const drillBtn = (cc: string, dept: string, keys: string[], items: number, amt: number) => { const name = cc === "—" ? "UNALLOCATED" : cc; return ( ); }; const pm = prevMonth(month); const pct = R.totPrev > 0 ? Math.round(((R.totAmt - R.totPrev) / R.totPrev) * 100) : null; const pctText = pct === null ? "—" : (pct > 0 ? "+" : pct < 0 ? "−" : "") + Math.abs(pct) + "%"; const ccShown = R.ccRows.filter((r) => r.items > 0 || r.amt > 0); const ccMax = Math.max(0, ...ccShown.map((r) => r.amt)); const monthOnly = monthLabel(month, { month: "long" }); return (
vs {monthLabel(pm, { month: "long" })} {pctText} }, { value: R.totItems + R.plQty, label: "Garments issued", note: <>{R.plQty} of them pre-loved }, { value: money(R.ordSpend), label: "Ordered from suppliers", note: <>{R.ordCount} order{R.ordCount === 1 ? "" : "s"} }, ]} />
}> {ccShown.length === 0 ? No issues recorded in {mLbl}. : ( Cost centreWardShare of the largestItemsValue {ccShown.map((r) => ( open(r.cc, r.dept, [r.key])} style={{ cursor: "pointer" }}> {drillBtn(r.cc, r.dept, [r.key], r.items, r.amt)} {r.dept} {r.items} {money(r.amt)} ))} TOTAL{R.totItems}{money(R.totAmt)} )} {R.groupRows.length === 0 ? Nothing issued in {mLbl}. : ( GroupPeopleValue {R.groupRows.map((g) => {g.g}{g.people}{money(g.amt)})} )}
}> {R.staffRows.length === 0 ? Nothing issued in {mLbl}. : ( StaffCost centreItemsValue {R.staffRows.map((r, i) => {r.who}{r.cc || "—"}{r.items}{money(r.amt)})} )}
{R.trend.map((b) => ( ))}
GL {R.glAcct}} onCsv={csv.journal} csvText="Export journal CSV" csvSecondary onPrint={print.journal} />} foot={R.jnUnallocated ? : undefined}> {R.jnRows.length === 0 ? No issues to journal in {mLbl}. : ( Cost centreDepartmentGL accountDescriptionItemsDebit {R.jnRows.map((r) => {drillBtn(r.cc, r.dept, r.keys, r.items, r.debit)}{r.dept}{r.gl}{r.desc}{r.items}{money(r.debit)})} TOTAL{jnTotItems}{money(jnTot)} )} }> MonthItems issuedIssued valueOrders placed {R.fyRows.map((m) => {m.label}{m.items}{money(m.issued)}{money(m.orders)})} FY TOTAL{R.fyTot.items}{money(R.fyTot.issued)}{money(R.fyTot.orders)} {drill && ( setDrill(null)} sub={`${drill.dept} · ${plural(drillQty, "item")} · ${money(drillAmt)}`} foot={<> {drillRows.length > 0 && } }> {drillRows.length === 0 ? Nothing was issued against this cost centre in {mLbl}. : (
DateStaffItemSizeQtyUnit costValue {drillRows.map((r, i) => {fmtDate(r.date)}{r.who}{r.item}{r.size}{r.qty}{money(r.unit)}{money(r.amt)})} TOTAL{drillQty}{money(drillAmt)}
)}
)}
); }