"use client"; /* Stocktake — choose what you're counting. One row per location that actually holds garments, plus everything not yet placed, so nothing on the shelf is uncountable. */ import { useMemo } from "react"; import { useDerived, useSnap } from "@/lib/client"; import { UNPLACED, bcBound, daysBetween, locSubtree, locTree, onhand, touched } from "@/lib/compute"; import { INK, MBody, MEmpty, MNav, MNote, MRow, MRule, MSection, MTop } from "@/components/m"; /* "Last counted 0 days ago" and "1 days ago" are how a shelf counted this morning used to read. */ function lastCounted(last: string | undefined, today: string): string { if (!last) return "Never counted"; const n = daysBetween(last, today); if (n <= 0) return "Counted today"; if (n === 1) return "Counted yesterday"; return `Last counted ${n} days ago`; } export default function MCountStart() { const { s } = useSnap(); const { L, variants } = useDerived(); const rows = useMemo(() => { const lastAt: Record = {}; for (const t of s.stocktakes) if (t.mode !== "preloved" && t.locationId && !lastAt[t.locationId]) lastAt[t.locationId] = t.date; const out = locTree(s).map(({ loc, depth }) => { const sub = locSubtree(s, loc.id); // Placed on the shelf is enough to make a shelf countable. A location holding only sizes // that have never been stocked is exactly the shelf someone needs to count in. const mine = variants.filter((v) => sub.has(s.placed[v.key] || "")); return { id: loc.id, name: loc.name, kind: loc.kind, depth, lines: mine.length, units: mine.reduce((t, v) => t + onhand(s, L, v.key), 0), last: lastAt[loc.id] as string | undefined }; }).filter((r) => r.lines > 0); // Only the unplaced bucket needs a test at all — without one it would list the whole // catalogue. A bound barcode counts as much as stock history does: somebody stood at the // counter with the garment in hand and scanned its label onto that size, which says the size // physically exists even when no stock figure does. The counting and variance screens filter // the unplaced bucket with exactly this expression and all three have to agree — a room whose // unplaced sizes are all barcode-bound and never yet stocked otherwise gets no "Not on a shelf // yet" row here, and the one screen that could count them in is unreachable from the menu. const loose = variants.filter((v) => !s.placed[v.key] && (touched(s, L, v.key) || !!bcBound(s, v.item, v.si))); if (loose.length) out.push({ id: UNPLACED, name: "Not on a shelf yet", kind: "", depth: 0, lines: loose.length, units: loose.reduce((t, v) => t + onhand(s, L, v.key), 0), last: undefined }); return out; }, [s, L, variants]); return ( <>

Where are you counting?

Scan every garment on the shelf. Each scan adds one to that line, and the expected figure stays on screen the whole way.

{rows.length === 0 ? ( ) : ( <> {rows.map((r) => ( {r.name}} sub={{lastCounted(r.last, s.today)}} right={{r.lines} · {r.units}} /> ))} )} A count stays open until you commit it, so you can put the phone down halfway along a shelf and pick it up again.
); }