"use client"; import Link from "next/link"; import { useEffect, useRef, useState } from "react"; import { useDerived, useSnap } from "@/lib/client"; import { Empty, Field } from "@/components/ui"; import { Panel, QueueRow, Tag } from "@/components/portal"; import { PhotoButton, ReturnDialog } from "@/components/dialogs"; import { viewPhoto } from "@/lib/photo"; import { PICKUP_LATE_DAYS } from "@/lib/portalcounts"; import { CASUAL_ALLOWED, FTE_CASUAL, allowanceRouteOf, approvalDeparture, capCheck, daysBetween, fmtDate, initialGarments, initialSets, initialUsed, label, openApprovals, setsForFte, type ApprovalRec, type IssueRec, type StaffRec } from "@/lib/compute"; import SignedToggle from "./SignedToggle"; import { dayMonth, fullName, objectPronoun, type Act } from "./shared"; const ROUTE_LABEL = { fte: "FTE table", kit: "Starting kit", approval: "Manager approval" } as const; export default function UniformTab({ st, act, setErr }: { st: StaffRec; act: Act; setErr: (e: string) => void }) { const { s, isAdmin } = useSnap(); const { byId } = useDerived(); const [ret, setRet] = useState(null); const held = capCheck(s, st); const holding = s.issues.filter((i) => i.staffId === st.id && !i.returned && !i.handedIn) .sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : b.createdAt.localeCompare(a.createdAt))); const garments = holding.reduce((t, i) => t + i.qty, 0); const pickups = s.pickups.filter((p) => p.staffId === st.id && !p.pickedUp) .map((p) => ({ p, days: p.received ? daysBetween(p.received, s.today) : 0 })) .sort((a, b) => b.days - a.days); return (
{holding.length === 0 ?
Holding nothing.
: (
{holding.map((i) => { const it = byId[i.itemId]; const size = it?.sizes[i.si] ?? ""; return ( ); })}
GarmentSizeQtyIssuedSourceAction
{label(it)} {size} {i.qty} {fmtDate(i.date)} {i.preloved ? "Pre-loved" : i.direct ? "Collected" : "Shelf"} · {i.override && Override}
)}
{pickups.length > 0 && ( {pickups.map(({ p, days }) => ( = PICKUP_LATE_DAYS} title={p.lines.map((l, n) => {n > 0 && ", "}{label(byId[l.itemId])} · {l.size} ×{l.qty})} meta={<>{p.orderCode}{p.received ? ` · arrived ${dayMonth(p.received)}` : ""}{p.contacted ? " · called" : ""}} actions={<> {!p.contacted && } } /> ))} )}
{ret && setRet(null)} />}
); } function ApprovalPanel({ st, act, setErr }: { st: StaffRec; act: Act; setErr: (e: string) => void }) { const { s } = useSnap(); const [open, setOpen] = useState(false); const [ap, setAp] = useState({ sets: "", fte: "", date: s.today, note: "" }); const [photo, setPhoto] = useState(null); const noGroup = !(st.group || "").trim(); const route = allowanceRouteOf(s, st); const fte = st.fte || ""; const casual = fte.trim().toLowerCase() === FTE_CASUAL.toLowerCase(); const proposed = setsForFte(fte); const kitSets = initialSets(s, st), kitOf = initialGarments(s, st), kitUsed = initialUsed(s, st.id); const all: ApprovalRec[] = s.approvals.filter((a) => a.staffId === st.id) .sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : 0)); const openAps = openApprovals(s, st.id); const latestOpen = openAps[openAps.length - 1]; const used = openAps.reduce((t, a) => t + a.used, 0); const left = openAps.reduce((t, a) => t + a.sets - a.used, 0); const formPhoto = (latestOpen ?? all[0])?.photoId ?? null; /* The form is recorded as signed by the manager on the register: one box, one signer. */ const mgr = st.managerId ? s.staff.find((x) => x.id === st.managerId) : undefined; const apMgr = mgr && !mgr.inactive ? mgr : undefined; const apBy = apMgr ? fullName(apMgr) : ""; const apFte = ap.fte.trim() || fte; const apProposed = setsForFte(apFte); const apOver = approvalDeparture({ sets: parseInt(ap.sets, 10) || 0, fte: apFte, by: apBy || "the manager" }); const apFuture = ap.date > s.today; const invalid = !apMgr || !(parseInt(ap.sets, 10) > 0) || apFuture; async function record() { if (!apMgr) return; const ok = await act("approval.add", { staffId: st.id, by: apBy, byStaffId: apMgr.id, sets: parseInt(ap.sets, 10), fte: ap.fte, date: ap.date, notes: ap.note.trim(), photoId: photo }); if (ok) { setAp({ sets: "", fte: "", date: s.today, note: "" }); setPhoto(null); setOpen(false); } } return (
{route === "fte" && !noGroup && (
Proposed by FTE {fte && {fte}} {!fte ? "no FTE recorded" : casual ? `${CASUAL_ALLOWED} sets` : proposed !== null ? `${proposed} sets` : "–"}
)} {route === "kit" && !noGroup && (
Starting kit {kitSets ?? 0} sets · {kitUsed} of {kitOf ?? 0} issued
)}
Signed by the manager {latestOpen ? `${latestOpen.sets} sets · ${dayMonth(latestOpen.date)}` : "nothing signed"}
Drawn {used} · {left} left
{formPhoto && }
{open && (
{(c) => setAp({ ...ap, sets: e.target.value.replace(/[^0-9]/g, "") })} />} {(c) => setAp({ ...ap, fte: e.target.value })} />} {(c) => setAp({ ...ap, date: e.target.value })} />}
{(c) =>