"use client"; /* 2A — Kit check. Every few months, reconcile the record with reality, item by item. * * "Nothing here is chargeable" is not reassurance for its own sake — a check that felt like an * audit would be answered with whatever number keeps somebody out of trouble, and the resulting * data would be worse than no data. * * Writing off and re-requesting stay separate acts. Saying a garment is missing does not quietly * order another one: that would turn an honest answer into a request somebody's manager has to * decline, which is exactly how you teach a ward to stop answering honestly. * * Between rounds this is a STATE, not a refusal. A refusal explains nothing by design, and "no kit * check is open" is not a secret — it is the answer somebody who tapped a notification from last * autumn needs, with the one way onward under it. */ import { useState } from "react"; import { MBar, MBody, MEmpty, MError, MONO, MRule, MTop } from "@/components/m"; import { ACCENT_300, DIVIDER, GROUND, INK, N300, N600, N700 } from "@/components/staffui"; import { useStaff } from "@/lib/staffclient"; import { fmtDate } from "@/lib/compute"; type Row = { itemId: string; item: string; size: string; si: number; onRecord: number; answered: number | null }; export default function KitCheckScreen({ closed, dueBy = "", lastConfirmed = "", rows = [] }: { /** No cycle open. The screen still exists; it just has one thing to say. */ closed?: boolean; dueBy?: string; lastConfirmed?: string; rows?: Row[]; }) { const { mutate, busy } = useStaff(); const [answers, setAnswers] = useState>( Object.fromEntries(rows.filter((r) => r.answered !== null).map((r) => [`${r.itemId}:${r.si}`, r.answered as number])), ); const [expanded, setExpanded] = useState>({}); const [err, setErr] = useState(""); const [done, setDone] = useState(false); const answeredCount = rows.filter((r) => answers[`${r.itemId}:${r.si}`] !== undefined).length; async function answer(r: Row, confirmed: number) { const k = `${r.itemId}:${r.si}`; setAnswers((a) => ({ ...a, [k]: confirmed })); setErr(""); const res = await mutate("kit.answer", { itemId: r.itemId, si: r.si, onRecord: r.onRecord, confirmed }); if (!res.ok) { setErr(res.error); setAnswers((a) => { const n = { ...a }; delete n[k]; return n; }); } } // aria-pressed, because the only other thing separating the chosen answer from the unchosen one // is an ink fill: nothing a screen reader can hear, and nothing at all in high contrast. const pick = (on: boolean, label: string, onClick: () => void): React.ReactNode => ( ); if (closed) { return ( <>
); } if (done) { return ( <>
Thanks
That’s your record confirmed.
The linen room will square anything short. Nothing is charged.
); } return ( <> {/* The ink header carries the question and the one rule that decides how honestly it gets answered. Nobody keeps their uniform at work — it is at home, in the wash, in a bag in the boot — so the ask is to count what they still have, wherever it is. */}
Have you still got everything on your record?
Count everything you still have, wherever it is. Nothing here is chargeable. {lastConfirmed ? ` Last confirmed ${fmtDate(lastConfirmed)}.` : ""}
setErr("")} /> {rows.length === 0 ? (
) : (
{rows.map((r) => { const k = `${r.itemId}:${r.si}`; const a = answers[k]; const short = a !== undefined && a < r.onRecord; const open = expanded[k]; return (
{r.item} — {r.size} ×{r.onRecord}
{/* "All 3 here" was an answer only somebody standing in front of the garments could give, and nobody is: they are at home, half of them in the wash. */} {pick(a === r.onRecord, `Got all ${r.onRecord}`, () => { setExpanded((e) => ({ ...e, [k]: false })); void answer(r, r.onRecord); })} {pick(short || !!open, short ? (a === 0 ? "None left" : `Only ${a}`) : "Fewer", () => setExpanded((e) => ({ ...e, [k]: !e[k] })))}
{open && (
{Array.from({ length: r.onRecord }, (_, i) => i).map((n) => ( ))}
)} {short && ( /* This used to say the missing ones "come off your record". They don't — the answer is written down and that is all it does; only the linen room can change the record. Telling somebody it has already been corrected, when it hasn't, is how they stop believing the next thing this screen says. */
{r.onRecord - (a as number)} unaccounted for. The linen room will square your record.
)}
); })}
)}
{rows.length === 0 ? ( ) : ( setDone(true)} /> )} ); }