import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react"; import { onViewerChange, viewerEpoch } from "../../../../utils/writeThroughHold"; import type { ServiceRequest } from "fhir/r4"; import { AlertCircle, FlaskConical, Loader2, Plus } from "lucide-react"; import { Alert, AlertDescription } from "../../../../components/ui/alert"; import { Button } from "../../../../components/ui/button"; import { Card, CardContent } from "../../../../components/ui/card"; import { EmptyState } from "../../../../components/ui/empty-state"; import { useAuthServiceContext, usePatientServiceContext } from "../../../../services"; import { useReferralWorkbenchService } from "../../../../services/ReferralWorkbenchService"; import { formatFhirHumanName } from "../../../../utils/fhirHumanName"; import { isDiagnosticOrderState, type DiagnosticOrderState, } from "../../../../utils/diagnosticOrderQueue"; import { useMarkDiagnosticOrderReviewed } from "../../../../hooks/useMarkDiagnosticOrderReviewed.ts"; import { useWithdrawDiagnosticOrder } from "../../../../hooks/useWithdrawDiagnosticOrder.ts"; import ArchiveConfirmModal from "../../../../components/ArchiveConfirmModal/ArchiveConfirmModal"; import type { ArchiveReason } from "../../../../utils/archivedResource"; import { withArchiveDetail } from "../../../../utils/archivedResource"; import { relayServerFailure } from "../../../../utils/fetchFailureMessage"; import { withReview } from "../../../../utils/diagnosticOrderReview"; import PlaceDiagnosticOrderDialog from "./PlaceDiagnosticOrderDialog"; import { DiagnosticOrderRow } from "./DiagnosticOrderRow"; import { groupByPanel } from "../../../../utils/diagnosticPanel"; import { withoutMirroredOrders } from "../../../../utils/resultLineage"; import { useOrderLabReports } from "../../../../hooks/useOrderLabReports.ts"; import { isSaveRetired, isSaveTimedOut, startSaveOperation, type SaveOperation } from "../../../../utils/saveOperation"; /** * THE PRACTITIONER'S SIDE OF A DIAGNOSTIC ORDER, on the patient it is about. * * THE GAP THIS CLOSES, and the file next door records making the same mistake: the whole execution * half was built -- the queue, the state machine, the result path, the permission -- and no screen * anywhere placed one. A queue nothing can put anything into. * * WHY THE CHART AND NOT THE ENCOUNTER, for the reason the referral screen traced: a practitioner * orders from a visit AND between visits, and only one surface covers both. The encounter screen has * no ordering area at all. Care Provision is where the other request-family resources already live, * which is the shelf a diagnostic order belongs on. * * The encounter still travels when there is one -- the dialog reads it from context, so an order * placed during a visit is bound to that visit without anybody being asked which. * * AND IT IS A PRACTITIONER SURFACE BY RULING. POST /R4/ServiceRequest allows [PRACTITIONERS] and * carries no permission section: ordering is a clinical judgement. The staff queue deliberately has * no authoring affordance and must not grow one. */ const STATE_EXTENSION_URL = "https://akashichealthrecords.com/fhir/StructureDefinition/diagnostic-order-state"; const LABORATORY_CODE = "108252007"; const IMAGING_CODE = "363679005"; const kindOf = (order: ServiceRequest): "laboratory" | "imaging" | undefined => { for (const category of order.category ?? []) { for (const coding of category.coding ?? []) { if (coding.system !== "http://snomed.info/sct") continue; if (coding.code === LABORATORY_CODE) return "laboratory"; if (coding.code === IMAGING_CODE) return "imaging"; } } return undefined; }; const stateOf = (order: ServiceRequest): DiagnosticOrderState | undefined => { for (const extension of order.extension ?? []) { if (extension.url !== STATE_EXTENSION_URL) continue; if (isDiagnosticOrderState(extension.valueString)) return extension.valueString; } return undefined; }; /** * The same order with a new state stamped on it. * * Replaces rather than appends: two state extensions on one resource is a record that disagrees with * itself, and `stateOf` above returns whichever it meets first. */ const withState = (order: ServiceRequest, state: DiagnosticOrderState): ServiceRequest => ({ ...order, extension: [ ...(order.extension ?? []).filter((extension) => extension.url !== STATE_EXTENSION_URL), { url: STATE_EXTENSION_URL, valueString: state }, ], }); const readOpenedOrder = (): string | undefined => new URLSearchParams(window.location.hash.split("?")[1] ?? "").get("order") ?? undefined; const DiagnosticOrders = () => { const { patientState } = usePatientServiceContext(); const { currentUser, wpOrganization } = useAuthServiceContext(); const callerPractitionerId = currentUser?.fhir?.resourceType === "Practitioner" ? currentUser.fhir.id : undefined; // The same subject-scoped search the referral screen uses; it returns every ServiceRequest for the // patient and each screen filters to its own kind. const { listPatientReferrals } = useReferralWorkbenchService(); const [orders, setOrders] = useState(null); const [error, setError] = useState(null); const [placing, setPlacing] = useState(false); // Which order is one click from being acknowledged. Reviewing cannot be undone -- the machine only // moves forward -- so it takes a deliberate second press rather than a confirm dialog, which is the // lighter way to make an irreversible act deliberate. const [confirming, setConfirming] = useState(); const [reviewing, setReviewing] = useState(); const [reviewError, setReviewError] = useState(); // The laboratory's own report on any of these orders that has one, so the clinician acknowledging a // result can read it against the page it was typed from. const labReports = useOrderLabReports(orders, patientState?.id); const markReviewed = useMarkDiagnosticOrderReviewed(); const withdrawOrder = useWithdrawDiagnosticOrder(); // Keep the selected order id with the dialog state. const [withdrawing, setWithdrawing] = useState(); const [withdrawSubmitting, setWithdrawSubmitting] = useState(false); const [withdrawError, setWithdrawError] = useState(); const patientId = patientState?.id; // The order a "Result to review" link opened, when it is one of this patient's. // Read off the hash, where a HashRouter keeps the query, so the page renders with or without a router. const [openedOrderId, setOpenedOrderId] = useState(readOpenedOrder); useEffect(() => { const follow = () => setOpenedOrderId(readOpenedOrder()); window.addEventListener("hashchange", follow); return () => window.removeEventListener("hashchange", follow); }, []); // The patient and viewer the list on screen belongs to: an answer read for another is never shown. const shownFor = useRef(undefined); const belongs = (asked: string, epoch: number) => shownFor.current === asked && viewerEpoch() === epoch; // The viewer, practice and patient this view is open for. const epoch = useSyncExternalStore(onViewerChange, viewerEpoch); const owner = `${epoch}|${patientId ?? ""}`; const currentOwner = useRef(owner); currentOwner.current = owner; // Each opening of the placement dialog is its own, numbered as it opens: remounted then, never while open, since // an open dialog torn down mid-flight leaves the page inert. A reopened dialog is never the old one. const placementCount = useRef(0); const [placement, setPlacement] = useState(() => ({ key: owner + "|0", owner })); const currentPlacement = useRef(placement.key); currentPlacement.current = placement.key; const placementOwns = () => currentPlacement.current === placement.key && currentOwner.current === placement.owner; const inPlacement = (fn: (...args: A) => void) => (...args: A) => { if (placementOwns()) fn(...args); }; // Every review and withdrawal in flight, so each is retired, with its request, when the view moves on. const live = useRef(new Set()); const own = (asked: string) => { const operation = startSaveOperation(() => shownFor.current === asked); live.current.add(operation); return operation; }; const patientName = formatFhirHumanName(patientState?.name, { emptyText: "" }) || undefined; const fetchOrders = useCallback(async (id: string) => { const bundle = await listPatientReferrals(id); return withoutMirroredOrders((bundle?.entry ?? []) .map((entry) => entry.resource) .filter((resource): resource is ServiceRequest => resource?.resourceType === "ServiceRequest") // THE CATEGORY IS THE DISCRIMINATOR, the same one the server branches on. Listing referrals // here would tell a practitioner they had ordered a test they had not. .filter((resource) => kindOf(resource) !== undefined), wpOrganization?.current?.id); }, [listPatientReferrals, wpOrganization]); const load = useCallback(async () => { if (!patientId) return; setError(null); const asked = patientId; const epoch = viewerEpoch(); try { const found = await fetchOrders(asked); if (belongs(asked, epoch)) setOrders(found); } catch { if (!belongs(asked, epoch)) return; // Stated rather than shown as an empty list. "No orders" and "we could not ask" look identical // on screen and mean opposite things to somebody deciding whether to order. setError("We could not load this patient's orders just now."); setOrders(null); } }, [patientId, fetchOrders]); // After a placement nobody confirmed. Merged rather than replaced, because the search can lag a // write and replacing would drop an order placed moments ago that it does not know about yet. const refreshKeepingShown = useCallback(async () => { if (!patientId) return; const asked = patientId; const epoch = viewerEpoch(); try { const found = await fetchOrders(asked); if (!belongs(asked, epoch)) return; setOrders((prev) => [ ...(prev ?? []).filter((shown) => !found.some((order) => order.id === shown.id)), ...found, ]); } catch { // The dialog already says the order may exist; a failed second look leaves the list alone. } }, [patientId, fetchOrders]); const review = async (orderId: string) => { if (!patientId) return; const operation = own(patientId); setReviewing(orderId); setReviewError(undefined); try { const response = await operation.step(() => markReviewed(orderId, operation.transport), true); if (!operation.belongs()) return; // STAMPED FROM WHAT THE SERVER RETURNED, and not re-read. The list search is eventually // consistent and can answer without a change just written -- the reason a placed order used to // stay invisible until the page was refreshed. setOrders((prev) => (prev ?? []).map((order) => { if (order.id !== orderId) return order; const advanced = withState(order, response.state ?? "reviewed"); return response.review ? withReview(advanced, response.review) : advanced; }), ); setConfirming(undefined); } catch (failure: unknown) { if (isSaveRetired(failure) || !operation.belongs()) return; // Said out loud rather than left as a button that did nothing. An unacknowledged result that // LOOKS acknowledged is the failure this whole state exists to prevent. setReviewError(isSaveTimedOut(failure) ? failure.message : relayServerFailure(failure, "That result could not be marked reviewed. Nothing changed.")); } finally { live.current.delete(operation); if (operation.belongs()) setReviewing(undefined); } }; const withdraw = async (order: ServiceRequest, reason: ArchiveReason, note?: string) => { if (!order.id || !patientId) return; const orderId = order.id; const operation = own(patientId); setWithdrawSubmitting(true); setWithdrawError(undefined); try { const response = await operation.step(() => withdrawOrder(orderId, reason, note, operation.transport), true); if (!operation.belongs()) return; // Apply the returned state immediately; list search is eventually consistent. // Mirror the server's archive detail so every withdrawal reason is visible before the next search. setOrders((prev) => (prev ?? []).map((candidate) => candidate.id === order.id ? { ...withArchiveDetail(candidate, reason, note), status: (response.status as ServiceRequest["status"]) ?? "revoked", } : candidate, ), ); setWithdrawing(undefined); } catch (failure: unknown) { if (isSaveRetired(failure) || !operation.belongs()) return; // Preserve the server's reason when withdrawal is refused. setWithdrawError(isSaveTimedOut(failure) ? failure.message : relayServerFailure(failure, "That order could not be withdrawn. Nothing changed.")); } finally { live.current.delete(operation); if (operation.belongs()) setWithdrawSubmitting(false); } }; // Another patient or viewer starts from nothing: no order, result or pending review of the last one stays up. const clearShown = useCallback(() => { live.current.forEach((operation) => operation.retire()); live.current.clear(); setOrders(null); setError(null); setConfirming(undefined); setReviewError(undefined); setReviewing(undefined); setPlacing(false); setWithdrawing(undefined); setWithdrawSubmitting(false); setWithdrawError(undefined); }, []); useEffect(() => { shownFor.current = patientId; clearShown(); }, [patientId, clearShown]); const reload = useRef(load); reload.current = load; useEffect(() => onViewerChange(() => { clearShown(); void reload.current(); }), [clearShown]); useEffect(() => { void load(); }, [load]); useEffect(() => () => live.current.forEach((operation) => operation.retire()), []); const openedMissing = Boolean(openedOrderId && orders && !orders.some((order) => order.id === openedOrderId)); return (

Tests and imaging

Tests and studies ordered for this patient, and where each one got to.

{error && ( {error} )} {reviewError && ( {reviewError} )} {orders === null && !error && (

Loading orders…

)} {orders?.length === 0 && ( /* Says what WILL happen rather than asserting nothing is wrong. A practitioner seeing this has never ordered one for this patient. */ )} {openedMissing && ( The result you opened is not among this patient's orders. It may have been withdrawn, or belong to someone else. )} {groupByPanel(orders ?? []).map((group) => ( {/* A PANEL IS NAMED OVER ITS COMPONENTS, because that is what the clinician ordered. The components keep their own states and their own controls below it: the tests in a panel are resulted, rejected and repeated individually, so one state over the group would be a summary nobody could act on. */} {group.panel && (

{group.panel.name ?? "Ordered together"}

{`${group.orders.length} tests, ordered in one act`}

)} {group.orders.map((order) => ( void review(orderId)} onWithdraw={(chosen) => { setWithdrawError(undefined); setWithdrawing(chosen); }} /> ))}
))} { if (!open && !withdrawSubmitting) setWithdrawing(undefined); }} resourceLabel={withdrawing?.code?.text ?? "this order"} title={`Withdraw ${withdrawing?.code?.text ?? "this order"}?`} description="It stays on this patient's list marked Withdrawn and comes off the practice's lab or imaging worklist." confirmLabel="Withdraw" submittingLabel="Withdrawing…" cancelLabel="Keep the order" submitting={withdrawSubmitting} error={withdrawError} onConfirm={(reason, note) => { if (withdrawing) void withdraw(withdrawing, reason, note); }} /> {patientId && ( setOrders((prev) => [...created, ...(prev ?? [])]))} onUnconfirmed={inPlacement(() => void refreshKeepingShown())} /> )}
); }; export default DiagnosticOrders;