1bc2de655a
Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
62 lines
4.3 KiB
TypeScript
62 lines
4.3 KiB
TypeScript
"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 didn’t 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 owner’s to flip.</div>}
|
||
</div>
|
||
);
|
||
}
|