"use client"; /* Home — today. Four figures, what’s just happened, and a way into a count. */ import Link from "next/link"; import { useMemo } from "react"; import { useDerived, useSnap } from "@/lib/client"; import { countsAsIssued, daysBetween, label, longLabel, onhand, touched, reorderAt, staffName, variantName } from "@/lib/compute"; import { INK, IconRight, MBody, MNav, MRow, MRule, MSection, MTopBrand } from "@/components/m"; function Stat({ n, l, hot }: { n: string; l: string; hot?: boolean }) { return (
{n}
{l}
); } export default function MHome() { const { s } = useSnap(); const { L, byId, staffById, variants } = useDerived(); const d = useMemo(() => { const today = s.today; let issued = 0, returned = 0; for (const i of s.issues) { if (i.date === today) issued += i.qty; if (i.returned?.date === today) returned += i.qty; } const low = variants.filter((v) => touched(s, L, v.key) && onhand(s, L, v.key) <= reorderAt(s, v.key)); const lastCount = s.stocktakes.find((t) => t.mode !== "preloved"); const since = lastCount ? daysBetween(lastCount.date, today) : null; // Recent activity, newest first. One line per person per day per kind — four garments handed // to the same nurse in one go is one thing that happened, not four. const grouped: Record = {}; for (const i of s.issues.slice(-120)) { const add = (kind: "Issued" | "Returned", at: string) => { const k = `${i.staffId}|${kind}|${at}`; (grouped[k] ||= { staffId: i.staffId, kind, at, qty: 0 }).qty += i.qty; }; add("Issued", i.date); if (i.returned) add("Returned", i.returned.date); } const recent = Object.values(grouped) .sort((a, b) => (a.at < b.at ? 1 : a.at > b.at ? -1 : 0)) .slice(0, 3) .map((g) => ({ title: `${staffName(staffById[g.staffId], "Staff")} — ${g.qty} item${g.qty === 1 ? "" : "s"}`, sub: `${g.kind} · ${g.at === today ? "today" : g.at}`, mark: "ink" as const, href: `/m/person/${g.staffId}`, at: g.at, })) as { title: string; sub: string; mark: "ink" | "accent"; href?: string; at: string }[]; // A line AT its reorder level is in `low` on purpose (reorder now, not once it's short), but // "Below par · 3 of 3" reads as a contradiction on the phone, so name the two states apart. for (const v of low.slice(0, 2)) { const oh = onhand(s, L, v.key), par = reorderAt(s, v.key); recent.push({ title: `${variantName(byId[v.itemId], v.size)}`, sub: `${oh < par ? "Below par" : "At par"} · ${oh} of ${par}`, mark: "accent", href: `/m/stock`, at: "" }); } return { issued, returned, low: low.length, since, recent }; }, [s, L, byId, staffById, variants]); const fac = [s.settings.facility, s.settings.location].filter(Boolean).join(" · "); const dateLine = new Date(+s.today.slice(0, 4), +s.today.slice(5, 7) - 1, +s.today.slice(8, 10)) .toLocaleDateString("en-AU", { weekday: "long", day: "numeric", month: "long" }); return ( <>
{dateLine}

Today

0} /> 30} />
{d.recent.length === 0 ?
Nothing has moved yet today.
: d.recent.map((r, i) => ( {r.sub}} /> ))}
Start a count Issue to someone Deliveries, pickups, rounds and more
); }