"use client"; /* 2A — Kit check. Twice a year, reconcile the record with reality, item by item. * * The copy does most of the work here. "Nothing here is chargeable" and "the linen room uses these * answers to set par levels, not to chase people" are not reassurance for its own sake — a check * that felt like an audit would be answered with whatever number keeps someone 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. */ import { useState } from "react"; import { MBar, MBody, MError, 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({ dueBy, lastConfirmed, rows }: { 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 (done) { return ( <>
Thanks
That’s your record confirmed.

Your answers have gone to the linen room, and they’ll square anything that didn’t match. Nothing is charged, and you don’t need to do anything else.

); } return ( <> {dueBy ? fmtDate(dueBy).split(" ").slice(1).join(" ") : ""}} />
Due by {fmtDate(dueBy)}
Have you still got everything on your record?
{/* Nobody keeps their uniform at work. It goes home, it gets washed, and on any given day a good part of it is on the line or in a bag in the boot. The question that used to be asked here — whether this matched what was in your locker — could only be answered by somebody standing in front of a locker they do not have, so it either got answered wrongly or not at all. Counting from memory, wherever the garments are, is the honest ask. */}

Count everything you still have, wherever it is — what’s in the wash and on the line counts too.{" "} {lastConfirmed ? `Last confirmed ${fmtDate(lastConfirmed)}. ` : ""}Nothing here is chargeable.

setErr("")} />
{rows.length} item{rows.length === 1 ? "" : "s"} on your record
{rows.length === 0 && (
Nothing on your record to check.
)} {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} on record
{/* "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, r.onRecord === 1 ? "Got it" : `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; the record still says what it said, and only the linen room can change it. Telling somebody their record has already been corrected, when it hasn't, is how they stop believing the next thing this screen says. */}

{r.onRecord - (a as number) === 1 ? "One" : `${r.onRecord - (a as number)}`} unaccounted for. The linen room will square your record — ask for a replacement separately if you need one.

)}
); })}

Anything you can’t account for is noted for the linen room to look at — nothing is charged, and nothing changes on your record until they do. They use these answers to set par levels, not to chase people.

setDone(true)} /> ); }