"use client"; /* 1H โ€” Shelf check. Kill the "have you got any size 12s?" message before it is sent. * * Words, never counts. Wards see In stock / Low / None on shelf; the number stays in the linen * room. That is partly the product rule and partly honesty โ€” the count here is as fresh as the * last stocktake of that garment, which is why each row says when it was counted rather than * implying a live figure. */ import Link from "next/link"; import { useMemo, useState } from "react"; import { MBody, MRule, MTop, inputStyle } from "@/components/m"; import { INK, N600, N700, StockTag } from "@/components/staffui"; import StaffNav from "@/components/staffnav"; import { fmtDate } from "@/lib/compute"; type Size = { size: string; si: number; word: string; countedOn: string }; type Item = { id: string; item: string; type: string; gender: string; sizes: Size[]; recorded: string }; export default function ShelfScreen({ items }: { items: Item[] }) { const [q, setQ] = useState(""); const shown = useMemo(() => { const needle = q.trim().toLowerCase(); if (!needle) return items; return items.filter((i) => i.item.toLowerCase().includes(needle) || i.type.toLowerCase().includes(needle) || i.sizes.some((s) => String(s.size).toLowerCase() === needle)); }, [items, q]); return ( <>
{/* Named, not just placeheld: the placeholder is gone as soon as anyone types, and a box that then announces itself as "edit, blank" is a box nobody can come back to. */} setQ(e.target.value)} placeholder="Search garments or a size" aria-label="Search garments or a size" autoComplete="off" style={{ ...inputStyle, width: "100%" }} />
{shown.length === 0 && (
Nothing matches “{q}”.
)} {shown.map((it) => (
{it.item}{it.gender && it.gender !== "Unisex" ? ` ยท ${it.gender}` : ""}
{it.sizes.map((s) => { const row = ( <> {s.size} {it.recorded && String(it.recorded) === String(s.size) && ( your size )} ); const style: React.CSSProperties = { display: "flex", gap: 12, alignItems: "center", padding: "14px 16px", background: "#fff", borderTop: "1px solid var(--color-divider)", borderRight: 0, borderBottom: 0, width: "100%", borderLeft: s.word === "none" ? "6px solid var(--color-accent)" : "6px solid transparent", font: "inherit", color: INK, borderRadius: 0, textDecoration: "none", }; // A size that isn't there is the one row worth tapping: it leads to the waitlist // rather than a dead end, which is the whole point of 2B. return s.word === "none" ? ( {row} ) : (
{row}
); })}
))}

Availability comes from the last linen-room stocktake {shown[0]?.sizes.find((s) => s.countedOn) ? ` โ€” most recently ${fmtDate(shown.flatMap((i) => i.sizes).map((s) => s.countedOn).filter(Boolean).sort().reverse()[0] || "")}` : ""}. Wards see words, not counts.

); }