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:45:19 +10:00
commit 1bc2de655a
505 changed files with 56223 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
"use client";
import { useState } from "react";
/* The switches and the demo reset. Each control posts one action and reloads, so what the page
* shows is always what the server holds — the row, with the environment's override on top. */
type Sw = { signupsDisabled: boolean; demoDisabled: boolean; plansLive: boolean; signupsByEnv: boolean; demoByEnv: boolean; plansByEnv: boolean; updatedAt: string | null };
export default function Controls({ sw, owner, demoAgoMin }: { sw: Sw; owner: boolean; demoAgoMin: number | null }) {
const [err, setErr] = useState("");
const [busy, setBusy] = useState("");
async function act(action: string, extra: Record<string, unknown> = {}) {
setBusy(action + (extra.key || "")); setErr("");
try {
const r = await fetch("/api/ops/controls", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action, ...extra }) });
const j = await r.json().catch(() => ({}));
if (!r.ok) { setErr(j.error || "That didnt work."); return; }
window.location.reload();
} catch {
setErr("No connection — check the network and try again.");
} finally {
setBusy("");
}
}
const row = (label: string, state: string, locked: boolean, button: React.ReactNode) => (
<div style={{ display: "flex", alignItems: "center", gap: 12, padding: "12px 0", borderBottom: "1px solid var(--color-divider)" }}>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontWeight: 700 }}>{label}</div>
<div style={{ fontSize: 12.5, color: "var(--color-neutral-700)" }}>{state}{locked ? " · locked by the environment on the box" : ""}</div>
</div>
{button}
</div>
);
return (
<div className="tc-panel-body" style={{ fontSize: 13 }}>
{err && <div role="alert" style={{ border: "2px solid var(--color-accent)", padding: "8px 12px", fontSize: 13, fontWeight: 600, color: "var(--color-accent-700)", marginBottom: 8 }}>{err}</div>}
{row("Sign-ups", sw.signupsDisabled || sw.signupsByEnv ? "Closed — the create-account form is hidden and the endpoint refuses" : "Open", sw.signupsByEnv,
<button type="button" className="btn" disabled={!owner || sw.signupsByEnv || !!busy} onClick={() => act("switch", { key: "signupsDisabled", value: !sw.signupsDisabled })}>
{sw.signupsDisabled ? "Open sign-ups" : "Close sign-ups"}
</button>)}
{row("Demo", sw.demoDisabled || sw.demoByEnv ? "Out of service — /demo says so and the entry endpoint answers 404" : `In service${demoAgoMin === null ? "" : ` · rebuilt ${demoAgoMin} min ago`}`, sw.demoByEnv,
<button type="button" className="btn" disabled={!owner || sw.demoByEnv || !!busy} onClick={() => act("switch", { key: "demoDisabled", value: !sw.demoDisabled })}>
{sw.demoDisabled ? "Put demo in service" : "Take demo out of service"}
</button>)}
{row("Plans", sw.plansLive || sw.plansByEnv ? "Live — new sign-ups start on Hosted Small and Settings shows the Plan tab" : "Off — every new facility is grandfathered: free, with everything, for good", sw.plansByEnv,
<button type="button" className="btn" disabled={!owner || sw.plansByEnv || !!busy} onClick={() => { if (sw.plansLive || window.confirm("Turn plans on? Every facility created from now on is capped at 60 staff records until it buys, and the sixty days' notice to existing rooms must already have run.")) void act("switch", { key: "plansLive", value: !sw.plansLive }); }}>
{sw.plansLive ? "Turn plans off" : "Turn plans on"}
</button>)}
<div style={{ display: "flex", alignItems: "center", gap: 12, padding: "12px 0" }}>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontWeight: 700 }}>Reset the demo now</div>
<div style={{ fontSize: 12.5, color: "var(--color-neutral-700)" }}>Drops and rebuilds the shared facility, as the twenty-minute clock does. Visitors mid-session see fresh data.</div>
</div>
<button type="button" className="btn" disabled={!!busy} onClick={() => { if (window.confirm("Rebuild the demo facility now?")) void act("demo.reset"); }}>{busy === "demo.reset" ? "Rebuilding…" : "Reset demo"}</button>
</div>
{!owner && <div style={{ fontSize: 12, color: "var(--color-neutral-700)", marginTop: 6 }}>Switches are an owners to flip.</div>}
</div>
);
}
+44
View File
@@ -0,0 +1,44 @@
import { requireOperator } from "@/lib/ops/session";
import { overview } from "@/lib/ops/projections";
import { switches } from "@/lib/switches";
import Controls from "./Controls";
/* Controls: the two platform switches and the demo reset. Reads the switch row through the
* product's own reader so what is shown is exactly what the sign-up route and the demo see —
* the row, with the environment's override on top. */
export const dynamic = "force-dynamic";
export default async function OpsControls() {
const op = await requireOperator();
const [sw, o] = await Promise.all([switches(), overview()]);
const fmt = (d: Date) => d.toLocaleString("en-AU", { timeZone: "Australia/Brisbane", dateStyle: "medium", timeStyle: "short" });
return (
<>
<div className="page-head">
<div>
<div className="sec" style={{ border: 0, padding: 0 }}>Controls</div>
<h1 className="h1">Switches</h1>
<div style={{ fontSize: 13, color: "var(--color-neutral-700)", marginTop: 4 }}>
{sw.row.updatedAt ? `Last changed ${fmt(sw.row.updatedAt)}` : "Never changed from here"} · every change is written to the trail
</div>
</div>
</div>
<div className="tc-panel" style={{ marginTop: 20, maxWidth: 720 }}>
<div className="tc-panel-head"><span>Platform</span><span className="tc-panel-aside">{o.facilities} facilities · {o.live} live</span></div>
<Controls
sw={{ signupsDisabled: sw.row.signupsDisabled, demoDisabled: sw.row.demoDisabled, plansLive: sw.row.plansLive, signupsByEnv: sw.signupsByEnv, demoByEnv: sw.demoByEnv, plansByEnv: sw.plansByEnv, updatedAt: sw.row.updatedAt?.toISOString() ?? null }}
owner={op.role === "OWNER"}
demoAgoMin={o.demoRebuiltAgoMin}
/>
</div>
<div className="tc-panel" style={{ marginTop: 24, maxWidth: 720 }}>
<div className="tc-panel-head"><span>Deleting a facility</span></div>
<div className="tc-panel-body" style={{ fontSize: 13, color: "var(--color-neutral-700)" }}>
Lives on the facilitys own page, at the bottom. Owner, the facilitys exact name typed, and a live code from your second factor. It cannot be undone.
</div>
</div>
</>
);
}