ThreadCount Community edition
Uniform stock management for healthcare linen rooms: the coordinator app, the phone counter and the staff app, for your own server. Built from 5470a36 on 2026-09-13. Licensed under the Functional Source License (FSL-1.1-ALv2).
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
"use client";
|
||||
/* Stock — on hand against par, worst first, with the three things you do about it underneath. */
|
||||
import Link from "next/link";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useDerived, useSnap } from "@/lib/client";
|
||||
import { bcBound, label, locMap, locTrail, onhand, touched, reorderAt, variantName } from "@/lib/compute";
|
||||
import { INK, IconRight, MBody, MEmpty, MNav, MRow, MRule, MSection, MTop, inputStyle } from "@/components/m";
|
||||
|
||||
export default function MStock() {
|
||||
const { s } = useSnap();
|
||||
const { L, byId, variants } = useDerived();
|
||||
const [q, setQ] = useState("");
|
||||
const locs = useMemo(() => locMap(s), [s]);
|
||||
|
||||
const rows = useMemo(() => {
|
||||
const needle = q.trim().toLowerCase();
|
||||
return variants
|
||||
.filter((v) => touched(s, L, v.key))
|
||||
.map((v) => {
|
||||
const oh = onhand(s, L, v.key), par = reorderAt(s, v.key);
|
||||
// The bound supplier code, not bcFor()'s generated stand-in: this line is read against a
|
||||
// label on a garment, and a number printed on nothing is worse than saying there isn't one.
|
||||
return { ...v, oh, par, low: oh <= par, code: bcBound(s, v.item, v.si), where: locTrail(locs, s.placed[v.key], 0), name: `${variantName(byId[v.itemId], v.size)}` };
|
||||
})
|
||||
.filter((r) => !needle || `${r.name} ${r.code} ${r.where}`.toLowerCase().includes(needle))
|
||||
// Short lines first, then furthest below par — the shelf you have to do something about.
|
||||
.sort((a, b) => Number(b.low) - Number(a.low) || (a.oh - a.par) - (b.oh - b.par) || a.name.localeCompare(b.name));
|
||||
}, [s, L, variants, byId, q, locs]);
|
||||
|
||||
const low = rows.filter((r) => r.low).length;
|
||||
const link: React.CSSProperties = { display: "flex", alignItems: "center", gap: 12, minHeight: 64, padding: "0 20px", border: "2px solid " + INK, color: INK, textDecoration: "none", fontFamily: "var(--font-heading)", fontWeight: 800, fontSize: 14, letterSpacing: "0.08em", textTransform: "uppercase" };
|
||||
|
||||
return (
|
||||
<>
|
||||
<MTop title="Stock" right={low ? `${low} below par` : `${rows.length} line${rows.length === 1 ? "" : "s"}`} />
|
||||
<MRule />
|
||||
<MBody>
|
||||
<div style={{ padding: 16, borderBottom: "2px solid " + INK }}>
|
||||
<input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Garment, code or shelf" aria-label="Filter stock" style={inputStyle} />
|
||||
</div>
|
||||
<MSection label="Line" right="On hand / par" />
|
||||
{rows.length === 0
|
||||
? <MEmpty title="Nothing in stock yet" sub="This list is what has moved. Add a garment to the catalogue and scan some in, and it appears here." />
|
||||
: rows.slice(0, 200).map((r) => (
|
||||
<MRow key={r.key} mark={r.low ? "accent" : "ink"} attention={r.low}
|
||||
title={r.name}
|
||||
sub={[r.code || "No barcode bound", `par ${r.par}`, r.where].filter(Boolean).join(" · ")}
|
||||
right={
|
||||
<span style={{ fontFamily: "var(--font-heading)", fontWeight: 800, fontSize: 21, fontVariantNumeric: "tabular-nums", color: r.low ? "var(--color-accent-700)" : INK }}>
|
||||
{r.oh}<span style={{ color: "var(--color-neutral-700)", fontSize: 17 }}>/{r.par}</span>
|
||||
</span>
|
||||
} />
|
||||
))}
|
||||
|
||||
<div style={{ padding: 16, display: "grid", gap: 12 }}>
|
||||
<Link href="/m/reorder" style={link}><span style={{ flex: 1 }}>{low ? `Reorder ${low} line${low === 1 ? "" : "s"}` : "Reorder draft"}</span><IconRight /></Link>
|
||||
<Link href="/m/variance" style={link}><span style={{ flex: 1 }}>Variance over time</span><IconRight /></Link>
|
||||
<Link href="/m/label" style={link}><span style={{ flex: 1 }}>Reprint a label</span><IconRight /></Link>
|
||||
{/* Stock only lists variants with history, so a garment added five minutes ago isn’t here
|
||||
yet. The catalogue is where it actually lives. */}
|
||||
<Link href="/m/catalogue" style={link}><span style={{ flex: 1 }}>Catalogue</span><IconRight /></Link>
|
||||
</div>
|
||||
</MBody>
|
||||
<MNav />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user