Files
ThreadCount 0910bc32c1 ThreadCount Community edition
Uniform stock management for healthcare linen rooms: the coordinator app, the phone counter and the staff app, for your own server. Built from 38e16eb on 2026-09-15. Licensed under the Functional Source License (FSL-1.1-ALv2).
2026-09-16 07:57:54 +10:00

72 lines
3.5 KiB
TypeScript

"use client";
import { useState } from "react";
import { ErrorLine } from "@/components/ui";
import { printAccessSlip } from "@/components/dialogs";
import { SLIP_DAYS, daysBetween, facilityDate, facilityToday, slipLive, type StaffRec } from "@/lib/compute";
import type { Act, Mutate } from "./shared";
/* Staff app activation. The code comes back once, in the op's response, and is never in the snapshot. */
export default function SelfService({ st, act, mutate, isAdmin, facility, tz }: { st: StaffRec; act: Act; mutate: Mutate; isAdmin: boolean; facility: string; tz: string }) {
const [code, setCode] = useState<string | null>(null);
const [err, setErr] = useState("");
const [busy, setBusy] = useState(false);
const today = facilityToday(tz);
const live = slipLive(st.selfCodeAt, today, tz);
const printed = facilityDate(st.selfCodeAt ?? "", tz);
const age = printed ? daysBetween(printed, today) : null;
const left = age === null ? null : SLIP_DAYS - age;
const when = age === 0 ? "today" : age === 1 ? "yesterday" : `${age} days ago`;
if (code) {
return (
<div className="tc-people-confirm">
<div className="tc-lbl">Code for {st.first}</div>
<div className="tc-people-code">{code}</div>
<div className="tc-people-hint">Shown once. Print or copy it now.</div>
<div style={{ display: "flex", gap: 10, marginTop: 10, flexWrap: "wrap" }}>
<button type="button" className="btn btn-primary" onClick={() => printAccessSlip({ settings: { facility } }, st, code)}>Print the slip</button>
<button type="button" className="btn btn-secondary" onClick={() => navigator.clipboard?.writeText(code).catch(() => {})}>Copy</button>
<button type="button" className="btn btn-ghost" onClick={() => setCode(null)}>Done</button>
</div>
</div>
);
}
return (
<div style={{ marginTop: 12 }}>
<div style={{ fontSize: 13 }}>
{st.selfEmail
? <>Signed up as <b>{st.selfEmail}</b>.</>
: st.selfCode
? age === null
? <>Code outstanding but <b>won&apos;t work</b> (no print date).</>
: live
? <>Code printed {when}, unused; expires {left === 1 ? "tomorrow" : `in ${left} days`}.</>
: <>Code printed {when} has <b>expired</b>.</>
: <>No staff-app login yet.</>}
</div>
<ErrorLine msg={err} />
{isAdmin && (
<div style={{ display: "flex", gap: 14, marginTop: 10, flexWrap: "wrap", alignItems: "center" }}>
{!st.selfEmail && (
<button type="button" className="btn btn-secondary" disabled={busy} onClick={async () => {
setBusy(true); setErr("");
const r = await mutate<{ code: string }>("staff.selfCode", { id: st.id });
setBusy(false);
if (!r.ok) { setErr(r.error); return; }
setCode(r.result.code);
}}>{busy ? "Generating…" : st.selfCode ? "New code" : "Generate a code"}</button>
)}
{st.selfCode && !st.selfEmail && <button type="button" className="btn btn-ghost tc-people-ghost" onClick={() => act("staff.selfClear", { id: st.id })}>Cancel the code</button>}
{st.selfEmail && (
<button type="button" className="btn btn-ghost tc-people-ghost" onClick={() => {
if (confirm(`Remove ${st.first}'s access? They'll be signed out and need a new code to get back in.`)) act("staff.selfUnlink", { id: st.id });
}}>Remove access</button>
)}
</div>
)}
</div>
);
}