"use client"; import Link from "next/link"; import { useState } from "react"; import { useDerived, useSnap } from "@/lib/client"; import { Empty, LiveRegion } from "@/components/ui"; import { Panel, Tag } from "@/components/portal"; import { HandInDialog, ReturnDialog, printCreditSlip, printHandInReceipt } from "@/components/dialogs"; import { viewPhoto } from "@/lib/photo"; import { statusText } from "@/lib/staffreq"; import { approvalDeparture, facilityDate, fmtDate, issueCost, label, money, statusTag, type ApprovalRec, type IssueRec, type StaffRec } from "@/lib/compute"; import type { RequestRow } from "@/components/requests/RequestList"; import SignedToggle from "./SignedToggle"; import type { RequestsHook } from "./RequestsTab"; import type { Act } from "./shared"; /** A request whose approver is the person it is for, in the words of where it has got to. */ const selfTag = (status: string) => (status === "awaiting" ? "Theirs to approve" : status === "declined" ? "Declined by themselves" : "Self-approved"); type FormRow = { kind: "approval"; on: string; a: ApprovalRec } | { kind: "request"; on: string; r: RequestRow }; export default function HistoryTab({ st, act, setErr, req }: { st: StaffRec; act: Act; setErr: (e: string) => void; req: RequestsHook }) { const { s, isAdmin } = useSnap(); const { byId } = useDerived(); const [ret, setRet] = useState(null); const [handin, setHandin] = useState(false); const [hiMsg, setHiMsg] = useState(""); const [alt, setAlt] = useState({ garment: "", desc: "" }); const issues = s.issues.filter((i) => i.staffId === st.id).sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : b.createdAt.localeCompare(a.createdAt))); const handins = s.handins.filter((h) => h.staffId === st.id); const alterations = s.alterations.filter((a) => a.staffId === st.id); const orders = s.orders.filter((o) => o.staffId === st.id); const approvals = s.approvals.filter((a) => a.staffId === st.id).reverse().sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : 0)); const openForm = (qs: string) => window.open(`/print/order-form?${qs}`, "_blank", "noopener"); /* Approvals come off the snapshot; requests off /api/requests?staff=. Each row is labelled, because a signed approval and an unapproved ask weigh differently in an audit. */ const forms: FormRow[] = [ ...approvals.map((a) => ({ kind: "approval" as const, on: a.date, a })), ...(req.data?.requests ?? []).filter((r) => r.staffId === st.id).map((r) => ({ kind: "request" as const, on: facilityDate(r.createdAt, s.settings.timezone), r })), ].sort((x, y) => (x.on < y.on ? 1 : x.on > y.on ? -1 : 0)); return (
{issues.length === 0 ?
Nothing issued yet.
: (
{issues.map((i) => { const it = byId[i.itemId]; const size = it?.sizes[i.si] ?? ""; return ( ); })}
DateGarmentSizeQtyValueStatusSignedAction
{fmtDate(i.date)} {label(it)} {size} {i.qty} {money(i.qty * issueCost(i, byId))} {i.returned ? i.returned.cond.replace("Returned - ", "Returned – ") : i.preloved ? "Pre-loved" : i.direct ? "Collected" : "Issued"} {i.handedIn && Handed in} {i.override && Override} {i.returned?.photoId && } {!i.returned && !i.handedIn && }
)}
setHandin(true)}>Record hand-in}> {handins.length === 0 ?
No hand-ins on file.
: (
{handins.map((h) => { const good = h.lines.filter((l) => l.cond === "Good").reduce((t, l) => t + l.qty, 0); const rag = h.lines.filter((l) => l.cond === "Rag").reduce((t, l) => t + l.qty, 0); return (
{fmtDate(h.date)} {[good ? `${good} good` : "", rag ? `${rag} rag` : ""].filter(Boolean).join(" · ") || "–"} · received by {h.by} {h.credit && Credited}
); })}
)}
setAlt({ ...alt, garment: e.target.value })} /> setAlt({ ...alt, desc: e.target.value })} />
{alterations.length === 0 ?
No alterations recorded.
: (
{alterations.map((a) => (
{fmtDate(a.date)} {a.garment} {a.desc || "–"} {a.status} {a.status !== "Returned to staff" && }
))}
)}
{req.data?.moreRequests &&
Only the latest {req.data.requestLimit} requests are listed.
} {!req.data && !req.error &&
Loading requests…
} {forms.length === 0 && (req.data || req.error) &&
No order form printed or recorded yet.
}
{forms.map((x) => x.kind === "approval" ? ( printCreditSlip(s, st, x.a)} /> ) : (
{fmtDate(x.on)} Request {x.r.code} · {x.r.garments} {x.r.garments === 1 ? "garment" : "garments"}{x.r.reason ? ` · ${x.r.reason}` : ""} · {statusText(x.r).label} {x.r.managerId === st.id && {selfTag(x.r.status)}}
))}
{orders.length === 0 ?
No orders placed for them.
: (
{orders.map((o) => (
{o.code} {fmtDate(o.date)} {o.lines.reduce((t, l) => t + l.qty, 0)} items · {o.supplier} {o.status}
))}
)}
{ret && setRet(null)} />} {handin && setHandin(false)} onDone={(m) => { setErr(""); setHiMsg(m); }} />}
); } function ApprovalRow({ a, st, today, isAdmin, act, openForm, credit }: { a: ApprovalRec; st: StaffRec; today: string; isAdmin: boolean; act: Act; openForm: (qs: string) => void; credit: () => void; }) { const rem = a.sets - a.used; const recorded = a.notes.trim(); const over = recorded ? null : approvalDeparture(a); return (
{fmtDate(a.date)} Approval {a.sets} {a.sets === 1 ? "set" : "sets"} approved by {a.by}{a.fte ? <> · FTE {a.fte} : ""} {a.byStaffId === st.id && Self-approved} {a.date > today && Dated ahead of today} 0 ? "low" : "quiet"}>{rem > 0 ? `${rem} of ${a.sets} left` : "Fully collected"} {a.photoId && } {isAdmin && }
{(recorded || over) &&
{recorded || `${over} No reason was written down.`}
}
); }