Files
threadcount-community/app/m/(app)/stock/page.tsx
T
ThreadCount 1bc2de655a ThreadCount Community edition
Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
2026-09-13 08:45:19 +10:00

68 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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 isnt 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 />
</>
);
}