"use client"; /* Places & cost centres: a link to the locations editor (now in Stock) and the departments editor. */ import { useState } from "react"; import Link from "next/link"; import { useSnap } from "@/lib/client"; import { Field } from "@/components/ui"; import { Panel } from "@/components/portal"; import { csvOf, type DeptRec } from "@/lib/compute"; import { downloadCsv } from "@/lib/print"; import { Msg, SectionHead, useSaver } from "./common"; export default function PlacesSection() { const { s, isAdmin, mutate } = useSnap(); const { msg, say, draft, setDraft, draftNow, forgetDraft, debounced, schedule } = useSaver(); const [nd, setNd] = useState({ name: "", cc: "" }); const nLocs = s.locations.filter((l) => !l.archived).length; const deptStaff: Record = {}; for (const st of s.staff) deptStaff[st.dept] = (deptStaff[st.dept] || 0) + 1; /* The row's cost centre and name as typed, so one box saving the row never undoes the other. */ const deptCc = (d: DeptRec, from = draft) => from["dept:" + d.id] ?? d.cc; const deptName = (d: DeptRec) => draft["deptname:" + d.id] ?? d.name; function deptNameRefusal(d: DeptRec, name: string) { if (!name) return `A ward needs a name — ${d.name} hasn’t been changed.`; const clash = s.depts.find((o) => o.id !== d.id && o.name.trim().toLowerCase() === name.toLowerCase()); return clash ? `${clash.name} is already on the list.` : ""; } const deptSaveName = (d: DeptRec) => { const n = deptName(d).trim(); return deptNameRefusal(d, n) ? d.name : n; }; /* dept.save carries the old name forward, so staff and orders filed under it move with it. The check waits for the typing to stop; half a name is not a refusal. */ function renameDept(d: DeptRec, v: string) { const k = "deptname:" + d.id; setDraft((x) => ({ ...x, [k]: v })); schedule(k, async () => { const name = v.trim(); const no = deptNameRefusal(d, name); if (no) { forgetDraft(k, v); say("depts", no); return; } if (name === d.name) return; const r = await mutate("dept.save", { id: d.id, name, cc: deptCc(d, draftNow.current).trim() }); if (!r.ok) { forgetDraft(k, v); say("depts", r.error); return; } say("depts", `Renamed to ${name}; its staff and orders moved with it.`); }, 600); } // The name the register holds (not a rename still settling) and the cost centre as typed. function exportDepts() { downloadCsv(`threadcount-departments-${s.today}.csv`, csvOf(["dept", "cc", "staff"], s.depts.map((d) => [d.name, deptCc(d).trim(), deptStaff[d.name] || 0]))); } return ( <> {nLocs} location{nLocs === 1 ? "" : "s"}}>
Rooms, shelves and bays are kept in Stock. Open Stock › Locations
Export CSV}> Departments & cost centres {s.depts.length > 0 && (
{s.depts.map((d) => { const n = deptStaff[d.name] || 0; return ( ); })}
Department / wardCost centreStaffRemove
renameDept(d, e.target.value)} disabled={!isAdmin} /> debounced("dept:" + d.id, e.target.value, "dept.save", { id: d.id, name: deptSaveName(d), cc: e.target.value.trim() }, "depts")} /> {n} {isAdmin && !n && }
)} {s.depts.length === 0 &&
No departments yet.
} {isAdmin && (
{(c) => setNd({ ...nd, name: e.target.value })} placeholder="e.g. Ward 5C" />} {(c) => setNd({ ...nd, cc: e.target.value })} placeholder="e.g. RGH-5090" />}
)} ); }