"use client"; /* Reprint a label. Short by design: it exists because a garment nobody can scan silently vanishes from every count. Only sizes with a real supplier barcode can be reprinted — ThreadCount's internal fallback code appears nowhere on a garment, so printing it would help nobody. */ import { useEffect, useMemo, useState } from "react"; import { useDerived, useSnap } from "@/lib/client"; import { bcBound, label, variantName } from "@/lib/compute"; import { isNative } from "@/lib/nativescan"; import MScan from "@/components/MScan"; import { INK, IconScan, MBar, MBody, MEmpty, MError, MNote, MRow, MRule, MSection, MStepper, MTop, inputStyle } from "@/components/m"; const REASONS = ["Worn off in the laundry", "Torn", "Never labelled", "Other"]; export default function MLabel() { const { s } = useSnap(); const { byId, variants } = useDerived(); const [q, setQ] = useState(""); const [pick, setPick] = useState(null); const [reason, setReason] = useState(""); const [copies, setCopies] = useState(6); const [scan, setScan] = useState(false); const [err, setErr] = useState(""); /* The Android shell cannot print. Its WebView opens no second window, so the label sheet would replace the app, and window.print() doesn't exist there — the button looked like it worked and stranded the person on a page with nothing to do. Read after mount: the server render doesn't know which shell it is being sent to. */ const [inApp, setInApp] = useState(false); useEffect(() => { setInApp(isNative()); }, []); const rows = useMemo(() => { const needle = q.trim().toLowerCase(); return variants .map((v) => ({ ...v, code: bcBound(s, v.item, v.si), name: `${variantName(byId[v.itemId], v.size)}` })) .filter((r) => r.code) .filter((r) => !needle || `${r.name} ${r.code} ${r.item.sku}`.toLowerCase().includes(needle)); }, [s, variants, byId, q]); const chosen = rows.find((r) => r.key === pick); const printable = chosen && chosen.code; return ( <> setErr("")} /> {!chosen ? ( <>

Barcode gone

A garment nobody can scan drops out of every count. Find it by code or description and print a fresh label.

setQ(e.target.value)} placeholder="Code or description" autoFocus aria-label="Find a garment" style={{ ...inputStyle, flex: 1 }} />
{rows.length === 0 ? : rows.slice(0, 40).map((r) => ( setPick(r.key)} mark="ink" title={r.name} sub={`${r.code}${r.item.sku ? ` · ${r.item.sku}` : ""}`} /> ))} ) : ( <>
{chosen.name}
{chosen.code}
{REASONS.map((r) => { const on = reason === r; return ( ); })}
Six to an A4 sheet.
The label carries the same barcode the supplier printed, so it scans identically to the ones still on the shelf. {inApp && ( Printing is a desktop job — the app can’t open a label sheet. Open ThreadCount at threadcount.tech, find {chosen.name} under {chosen.code}, and print it from there. )} )}
{chosen && ( { if (!printable) { setErr("That size has no supplier barcode bound to it."); return; } const url = `/print/labels?code=${encodeURIComponent(chosen.code)}&copies=${copies}&reason=${encodeURIComponent(reason)}`; window.open(url, "_blank", "noopener"); }} /> )} {scan && { const k = s.barcodes[raw.trim()]; if (k) { setPick(k); setQ(""); } else setErr(`${raw.trim()} isn’t a garment ThreadCount knows.`); setScan(false); }} onClose={() => setScan(false)} />} ); }