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
+66
View File
@@ -0,0 +1,66 @@
import { requireOperator } from "@/lib/ops/session";
import { facilities } from "@/lib/ops/projections";
/* Every facility: counts, dates, state. No customer records are reachable from this screen, and
* no contact appears on it at all — a reveal is a separate, trailed act on the facility's own page. */
export const dynamic = "force-dynamic";
const PILL: Record<string, { text: string; color: string }> = {
live: { text: "Live", color: "#2f6b4f" },
dormant: { text: "Dormant", color: "var(--color-neutral-600)" },
unconfigured: { text: "Never set up", color: "var(--color-neutral-600)" },
demo: { text: "Demo", color: "var(--color-neutral-600)" },
};
function Pill({ state }: { state: string }) {
const p = PILL[state] || PILL.dormant;
return <span style={{ display: "inline-block", fontSize: 11, fontWeight: 700, letterSpacing: "0.05em", textTransform: "uppercase", padding: "2px 7px", border: `1.5px solid ${p.color}`, color: p.color }}>{p.text}</span>;
}
export default async function OpsFacilities() {
await requireOperator();
const rows = await facilities();
const real = rows.filter((f) => !f.isDemo);
const demo = rows.filter((f) => f.isDemo);
const fmt = (d: Date) => d.toLocaleDateString("en-AU", { day: "numeric", month: "short" });
const n = (x: number) => x.toLocaleString();
const th = (t: string, right = false) => <th style={{ textAlign: right ? "right" : "left", fontSize: 11, fontWeight: 700, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--color-neutral-600)", padding: "9px 16px", borderBottom: "2px solid var(--color-text)", whiteSpace: "nowrap" }}>{t}</th>;
const td = (c: React.ReactNode, right = false) => <td style={{ textAlign: right ? "right" : "left", padding: "9px 16px", borderBottom: "1px solid var(--color-divider)", whiteSpace: "nowrap", fontVariantNumeric: "tabular-nums" }}>{c}</td>;
return (
<>
<div className="page-head">
<div>
<div className="sec" style={{ border: 0, padding: 0 }}>Facilities</div>
<h1 className="h1">Every facility</h1>
<div style={{ fontSize: 13, color: "var(--color-neutral-700)", marginTop: 4 }}>Counts and dates only. No customer records are reachable from this screen.</div>
</div>
</div>
<div className="tc-panel" style={{ marginTop: 20 }}>
<div className="tc-panel-head"><span>{real.length} facilities</span><span className="tc-panel-aside">{demo.length ? "demo shown last" : "no demo facility"}</span></div>
<div style={{ overflowX: "auto" }}>
<table style={{ borderCollapse: "collapse", width: "100%", fontSize: 13 }}>
<thead><tr>{th("Facility")}{th("State")}{th("Plan")}{th("Created")}{th("Users", true)}{th("Staff", true)}{th("Items", true)}{th("Issues", true)}{th("Requests", true)}{th("rev", true)}{th("Backup")}</tr></thead>
<tbody>
{[...real, ...demo].map((f) => (
<tr key={f.id}>
{td(<a href={`/ops/facilities/${f.id}`} style={{ fontWeight: 700 }}>{f.name}</a>)}
{td(<Pill state={f.state} />)}
{td(f.isDemo ? "—" : (
<span style={{ color: f.planView.readOnly ? "var(--color-accent-700)" : undefined, fontWeight: f.planView.readOnly ? 700 : undefined }}>
{f.planView.grandfathered ? `${f.planView.label} · grandfathered` : `${f.planView.label} · ${f.planView.state.replace("_", "-")}`}
</span>
))}
{td(f.isDemo ? "—" : fmt(f.createdAt))}
{td(n(f.users), true)}{td(n(f.staff), true)}{td(n(f.items), true)}{td(n(f.issues), true)}{td(n(f.requests), true)}{td(f.isDemo ? "—" : n(f.rev), true)}
{td(f.isDemo ? "—" : f.backupAgeDays === null ? <span style={{ color: "#8a6218", fontWeight: 700 }}>never</span> : f.backupAgeDays > 7 ? <span style={{ color: "#8a6218", fontWeight: 700 }}>{f.backupAgeDays}d</span> : `${f.backupAgeDays}d`)}
</tr>
))}
{rows.length === 0 && <tr>{td("No facilities yet.")}</tr>}
</tbody>
</table>
</div>
</div>
</>
);
}