Files
threadcount-community/app/ops/(console)/controls/Controls.tsx
T
ThreadCount 1bc2de655a ThreadCount Community edition
Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
2026-09-13 08:45:19 +10:00

62 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
);
}