// Admin: departments, their stations and the AD groups each one needs (phase 9). import { useState, type FormEvent } from "react"; import { admin9Api, type AdminDepartment, type DeptAdded, type DeptGroup } from "../../api/admin"; import { errorText } from "../../lib/format"; import { useLoad } from "../../lib/useLoad"; import { Notice, Problem, type Msg } from "./common"; const RX_CODE = /^[0-9A-Z]{2,4}$/; const RX_SLUG = /^[A-Za-z][A-Za-z0-9]{1,30}$/; const SHOP_LEVELS = ["Worker", "Lead", "Supervisor"] as const; function OuPill({ g }: { g: DeptGroup }) { if (g.in_ou === true) return In AD; if (g.in_ou === false) return Missing in AD; return Not checked; } type StationDraft = { id?: number; name: string; active: boolean }; function DeptEditor({ d, onSaved, onClose }: { d: AdminDepartment; onSaved: () => void; onClose: () => void }) { const [name, setName] = useState(d.name); const [active, setActive] = useState(d.active); const [stations, setStations] = useState( [...d.stations].sort((a, b) => a.sort - b.sort).map((s) => ({ id: s.id, name: s.name, active: s.active })), ); const [busy, setBusy] = useState(false); const [msg, setMsg] = useState(null); const setStation = (i: number, patch: Partial) => setStations((cur) => cur.map((s, j) => (j === i ? { ...s, ...patch } : s))); const save = async (e: FormEvent) => { e.preventDefault(); const names = stations.map((s) => s.name.trim()); if (!name.trim()) { setMsg({ ok: false, text: "Give the department a name." }); return; } if (names.some((n) => !n)) { setMsg({ ok: false, text: "Every station needs a name." }); return; } if (new Set(names.map((n) => n.toLowerCase())).size !== names.length) { setMsg({ ok: false, text: "Two stations have the same name." }); return; } setBusy(true); setMsg(null); try { await admin9Api.patchDepartment(d.code, { name: name.trim(), active, stations: stations.map((s, i) => ({ ...(s.id !== undefined ? { id: s.id } : {}), name: s.name.trim(), sort: i + 1, active: s.active })), }); setMsg({ ok: true, text: "Department saved." }); onSaved(); } catch (err) { setMsg({ ok: false, text: errorText(err) }); } finally { setBusy(false); } }; return (

Edit {d.code} {d.name}

Stations

Stations are never deleted, because station limits point at them. Clear Active to hide one.
{stations.map((s, i) => (
{" "}
))}
); } function swap(list: T[], a: number, b: number): T[] { const out = [...list]; const x = out[a]; const y = out[b]; if (x === undefined || y === undefined) return list; out[a] = y; out[b] = x; return out; } function AddDepartment({ onAdded }: { onAdded: (r: DeptAdded) => void }) { const [code, setCode] = useState(""); const [slug, setSlug] = useState(""); const [name, setName] = useState(""); const [kind, setKind] = useState<"shop" | "support">("shop"); const [weld, setWeld] = useState(false); const [sup, setSup] = useState(true); const [stations, setStations] = useState(""); const [busy, setBusy] = useState(false); const [msg, setMsg] = useState(null); const preview = kind === "shop" && RX_CODE.test(code) && RX_SLUG.test(slug) ? SHOP_LEVELS.map((l) => `PS-${code}-${slug}-${l}`) : []; const submit = async (e: FormEvent) => { e.preventDefault(); if (!RX_CODE.test(code)) { setMsg({ ok: false, text: "The code is 2 to 4 capital letters or digits, like 15." }); return; } if (!RX_SLUG.test(slug)) { setMsg({ ok: false, text: "The short name starts with a letter and has only letters and digits, like Paint." }); return; } if (!name.trim()) { setMsg({ ok: false, text: "Give the department a name." }); return; } const list = stations .split(/\r?\n/) .map((s) => s.trim()) .filter(Boolean); setBusy(true); setMsg(null); try { const r = await admin9Api.addDepartment({ code, slug, name: name.trim(), kind, weld, supervisor_group: sup, stations: list }); setCode(""); setSlug(""); setName(""); setStations(""); onAdded(r); } catch (err) { setMsg({ ok: false, text: errorText(err) }); } finally { setBusy(false); } }; return (

Add department