ThreadCount Community edition

Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
This commit is contained in:
ThreadCount
2026-09-13 08:54:35 +10:00
commit 344b1701dd
505 changed files with 56231 additions and 0 deletions
@@ -0,0 +1,57 @@
"use client";
import { useState } from "react";
/* The one thing on the console that cannot be undone. Owner only (the server checks too), the
* facility's exact name typed, and a live code from the operator's own second factor. */
export default function Danger({ facilityId, name, owner, totpEnabled }: { facilityId: string; name: string; owner: boolean; totpEnabled: boolean }) {
const [open, setOpen] = useState(false);
const [confirm, setConfirm] = useState("");
const [code, setCode] = useState("");
const [err, setErr] = useState("");
const [busy, setBusy] = useState(false);
async function submit(e: React.FormEvent) {
e.preventDefault();
setBusy(true); setErr("");
try {
const r = await fetch("/api/ops/controls", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "facility.delete", facilityId, confirm, code }) });
const j = await r.json().catch(() => ({}));
if (!r.ok) { setErr(j.error || "That didnt work."); return; }
window.location.assign("/ops/facilities?deleted=1");
} catch {
setErr("No connection — check the network and try again.");
} finally {
setBusy(false);
}
}
const label: React.CSSProperties = { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--color-neutral-600)", marginBottom: 4 };
if (!owner) return <div className="tc-panel-body" style={{ fontSize: 13, color: "var(--color-neutral-700)" }}>Only an owner can delete a facility.</div>;
if (!totpEnabled) return <div className="tc-panel-body" style={{ fontSize: 13, color: "var(--color-neutral-700)" }}>Enrol a second factor under <a href="/ops/security">Your sign-in</a> before deleting anything.</div>;
if (!open) {
return (
<div className="tc-panel-body" style={{ fontSize: 13, display: "flex", alignItems: "center", gap: 12 }}>
<div style={{ flex: 1, color: "var(--color-neutral-700)" }}>Deletes every record, image and account belonging to this facility. There is no undo beyond the database dumps on the box.</div>
<button type="button" className="btn" onClick={() => setOpen(true)}>Delete facility</button>
</div>
);
}
return (
<form onSubmit={submit} className="tc-panel-body" style={{ fontSize: 13, display: "grid", gap: 10 }}>
<div>
<label htmlFor="danger-confirm" style={label}>Type the facility name exactly</label>
<input id="danger-confirm" className="input" value={confirm} onChange={(e) => setConfirm(e.target.value)} placeholder={name} autoComplete="off" autoFocus />
</div>
<div>
<label htmlFor="danger-code" style={label}>Code from your authenticator</label>
<input id="danger-code" className="input" inputMode="numeric" autoComplete="one-time-code" maxLength={7} value={code} onChange={(e) => setCode(e.target.value)} style={{ maxWidth: 160 }} />
</div>
{err && <div role="alert" style={{ border: "2px solid var(--color-accent)", padding: "8px 12px", fontWeight: 600, color: "var(--color-accent-700)" }}>{err}</div>}
<div style={{ display: "flex", gap: 8 }}>
<button type="submit" className="btn btn-primary" disabled={busy || confirm.trim() !== name || code.replace(/\s+/g, "").length < 6}>{busy ? "Deleting…" : "Delete this facility"}</button>
<button type="button" className="btn" onClick={() => { setOpen(false); setConfirm(""); setCode(""); setErr(""); }} disabled={busy}>Cancel</button>
</div>
</form>
);
}