import { useState } from "react"; import type { Location, Organization } from "fhir/r4"; import { ToggleControl } from "../../../../components/ui/toggle-control"; import { useAuthServiceContext } from "../../../../services/AuthService"; import { useOrganizationServiceContext } from "../../../../services/OrganizationService"; import { environment } from "../../../../environments/environment"; import { relayServerFailure } from "../../../../utils/fetchFailureMessage"; import { usesPhysicalRooming, withPhysicalRooming } from "../../../../utils/practiceRooming"; import { isSaveRetired, useSaveScope } from "../../../../utils/saveOperation"; import { holdSettingsWrite, nextSettingsAttempt, recordSettingsAttempt, settingsWriteHolder, useUnresolvedSettingsWrite, watchSettingsWrite, } from "../../../../utils/unresolvedSettingsWrite"; type Props = { organization: Organization; locations: Location[]; canManage: boolean }; // A held answer, and the version it was written over: "on|7". const heldAnswer = (rooming: boolean, version?: string) => `${rooming ? "on" : "off"}|${version ?? ""}`; const readHeld = (detail?: string) => { const [answer, version] = (detail ?? "").split("|"); return { rooming: answer === "on", version: version || undefined }; }; // Whether this practice rooms patients. Unset follows its rooms; turning it off keeps every room. // // A save belongs to the practice being edited, the version it was read at, the viewer (AuthService's key names the // practice too) and the right to manage, and to this opening. Every write carries If-Match on that version, which is // what makes an unanswered one settleable: once the practice's record has moved past it, that write has either // landed or can never land; while it has not, saving the same answer again on that version shuts it out. export const PracticeRoomingSwitch = ({ organization, locations, canManage }: Props) => { const { setWpOrganization } = useAuthServiceContext(); const { updateOrganization, fetchOrganizationAsCaller } = useOrganizationServiceContext(); const [saving, setSaving] = useState(false); const [notice, setNotice] = useState(); const [stale, setStale] = useState(false); // A check found the earlier version still standing: only saving the same answer again settles it. const [stillEarlier, setStillEarlier] = useState(false); const owner = `${organization.id ?? ""}|${organization.meta?.versionId ?? ""}|${canManage ? "may" : "refused"}`; const scope = useSaveScope(owner, () => { setSaving(false); setNotice(undefined); setStale(false); setStillEarlier(false); }); const holder = settingsWriteHolder("practice-rooming"); const unresolved = useUnresolvedSettingsWrite(holder); const held = unresolved?.state === "unconfirmed"; if (!canManage) return null; const save = async (rooming: boolean) => { const operation = scope.start(); const heldFor = holder; const startedWith = unresolved; const attempt = nextSettingsAttempt(startedWith); const version = organization.meta?.versionId; const write = watchSettingsWrite((onSend) => updateOrganization(withPhysicalRooming(organization, rooming), { ...operation.transport, onSend })); setSaving(true); setNotice(undefined); setStale(false); try { const saved = await operation.step(write.run, true); // Confirmed, and on the version every earlier unanswered write was based on: none of them can land after it. holdSettingsWrite(heldFor, undefined); setStillEarlier(false); setWpOrganization(saved); } catch (err) { // A version refusal is an answer: nothing was written. const refusedAsStale = (err as Error | null)?.name === "StaleOrganizationError"; const unknown = write.unconfirmed() && !refusedAsStale; const hold = unknown ? { kind: "rooming", state: "unconfirmed" as const, detail: heldAnswer(rooming, version) } : undefined; recordSettingsAttempt(heldFor, attempt, startedWith, hold); if (isSaveRetired(err) || !operation.belongs() || unknown) return; setStale(refusedAsStale); // Refused, or never sent: either way nothing was written. Only an answer has words of its own to relay. const notSaved = "This was not saved. Nothing has changed; try again."; const answered = typeof (err as { status?: unknown } | null)?.status === "number"; setNotice(refusedAsStale ? (err as Error).message : answered ? relayServerFailure(err, notSaved) : notSaved); } finally { if (operation.belongs()) setSaving(false); operation.retire(); } }; // The practice's own record, read for this opening and installed: the current answer, and whether an unanswered // write is settled. A record still on the version that write was based on is not proof either way. const check = async () => { if (!organization.id) return; const operation = scope.start(); const heldFor = holder; const startedWith = unresolved; const based = startedWith ? readHeld(startedWith.detail).version : undefined; setSaving(true); setNotice(undefined); try { const record = await operation.step(() => fetchOrganizationAsCaller( organization.id as string, environment.wpData?.installationId, organization.id, operation.transport)); // Only this practice's own complete record, with a version a later save can be written over, settles anything or // replaces the record on screen; any other answer is a failed check that leaves both alone. if (record?.resourceType !== "Organization" || record.id !== organization.id || !record.meta?.versionId) { setNotice("What is saved could not be read just now. Try again in a minute."); return; } const moved = Boolean(based) && Boolean(record.meta?.versionId) && record.meta?.versionId !== based; // Admitted for this opening, where nothing else can be saved while the write is held, so the hold is still the // one this read was made for; a read from an earlier opening is retired before it gets here. if (startedWith && moved) holdSettingsWrite(heldFor, undefined); setStale(false); setStillEarlier(Boolean(startedWith) && !moved); setWpOrganization(record); } catch (err) { if (isSaveRetired(err) || !operation.belongs()) return; setNotice(relayServerFailure(err, "What is saved could not be read just now. Try again in a minute.")); } finally { if (operation.belongs()) setSaving(false); operation.retire(); } }; const heldRooming = held ? readHeld(unresolved?.detail).rooming : undefined; return (
void save(checked)} disabled={saving || held} /> {held && (

{stillEarlier ? `This practice still shows the earlier answer, and turning rooming ${heldRooming ? "on" : "off"} may yet arrive. Save it again to settle it.` : `We could not confirm whether rooming was turned ${heldRooming ? "on" : "off"}.`}

{stillEarlier ? ( ) : ( )}
)} {notice && (

{notice}

)} {stale && !held && ( )}
); }; export default PracticeRoomingSwitch;