import { useState } from "react"; import { AlertCircle, ChevronLeft } from "lucide-react"; import { Alert, AlertDescription } from "../../../components/ui/alert.tsx"; import { Button } from "../../../components/ui/button.tsx"; import { Input } from "../../../components/ui/input.tsx"; import { priorityWord } from "../../../components/ui/row-meta.tsx"; import type { DiagnosticOrderQueueRow, ImagingSchedule } from "../../../hooks/useDiagnosticOrderQueue.ts"; import { relayServerFailure } from "../../../utils/fetchFailureMessage"; import { SEP, bookedAt } from "./ImagingWorklist.tsx"; import { Fact } from "./QueueOrderDialogs.tsx"; import { bodyPartWords, modalityWords } from "./orderableRadiology.ts"; import { useScheduleDetails } from "./scheduleDetails.ts"; import ArchiveConfirmModal from "../../../components/ArchiveConfirmModal/ArchiveConfirmModal"; import type { ArchiveReason } from "../../../utils/archivedResource"; import "./ImagingWorklist.css"; /** * Scheduling a study on its own page, as imaging.html draws it. Only a confirmed save returns to the * worklist with the row moved; a refusal stays here with the reason. No device connection exists yet, * so upload is the only image path named. The department may cancel a study it has not yet performed. */ type ScheduleProps = { row?: DiagnosticOrderQueueRow; onBack: () => void; onSchedule: (orderId: string, schedule: ImagingSchedule) => Promise; onCancel?: (orderId: string, reason: ArchiveReason, note?: string) => Promise; }; // The study's details and the rooms are read only once a study is being booked, not whenever the worklist is on screen. const ScheduleStudyPage = (props: ScheduleProps) => props.row?.orderId ? : null; const ScheduleStudyForm = ({ row, onBack, onSchedule, onCancel }: ScheduleProps & { row: DiagnosticOrderQueueRow }) => { const [cancelOpen, setCancelOpen] = useState(false); const [cancelling, setCancelling] = useState(false); const [cancelError, setCancelError] = useState(); const [at, setAt] = useState(""); const [busy, setBusy] = useState(false); const [failure, setFailure] = useState(); const orderId = row.orderId as string; // One read for this order: the modality, body part and side from its own code, and the practice's rooms. const details = useScheduleDetails(orderId); const orderable = details.terminology; const roomsLoading = details.rooms.status === "loading"; const roomsError = details.rooms.status === "failed" ? details.rooms.reason : undefined; const rooms = details.rooms.status === "read" || details.rooms.status === "incomplete" ? details.rooms.list : []; // A room chosen belongs to the read it was chosen from; another viewer, order or reading starts unchosen. const [choice, setChoice] = useState<{ scope: string; roomId: string }>(); const roomId = choice?.scope === details.scope && rooms.some((each) => each.id === choice.roomId) ? choice.roomId : ""; const unread = details.terminology.status === "failed" || details.rooms.status === "failed"; const modality = orderable.status === "read" ? orderable.parts.dicomModality : row.modality; const fromOrder = (words: (parts: Parameters[0]) => string | undefined) => orderable.status === "loading" ? "Reading the order's code..." : orderable.status === "failed" ? `Could not be read from the order: ${orderable.reason}` : orderable.status === "read" ? words(orderable.parts) ?? "Not given by the order" : "Not given by the order"; const submit = async () => { if (busy) return; setBusy(true); setFailure(undefined); try { await onSchedule(orderId, { ...(at ? { at: new Date(at).toISOString() } : {}), ...(roomId ? { roomId } : {}), ...(modality ? { modality } : {}), }); onBack(); } catch (reason) { setFailure(relayServerFailure(reason, "Could not schedule this study. It may have moved on already.")); } finally { setBusy(false); } }; const cancel = async (reason: ArchiveReason, note?: string) => { if (!onCancel) return; setCancelling(true); setCancelError(undefined); try { await onCancel(orderId, reason, note); setCancelOpen(false); onBack(); } catch (refusal) { setCancelError(relayServerFailure(refusal, "Could not cancel this study. It may have moved on already.")); } finally { setCancelling(false); } }; const detail = [ row.orderedBy, row.authoredOn ? "ordered " + bookedAt(row.authoredOn) : undefined, priorityWord(row.priority), ].filter(Boolean).join(SEP); return (

Schedule a study

{[row.test, row.patientDisplay, detail].filter(Boolean).join(SEP)}

The study

{row.accession ?? "Issued when scheduled"} {orderable.status === "none" && row.modality ? row.modality : fromOrder(modalityWords)} {fromOrder(bodyPartWords)}
{roomsError ? (

The practice's rooms could not be read: {roomsError}

) : details.rooms.status === "incomplete" ? (

Not every room could be read, so this list may be missing some.

) : !roomsLoading && rooms.length === 0 ? (

An administrator adds rooms under Organization, Locations.

) : null}
setAt(event.target.value)} />
Assigned when performed

How the images will reach us

Always available
{unread ? (
) : null} {failure ? ( {failure} ) : null}
{onCancel ? ( ) : null}
{onCancel ? ( { if (!open && !cancelling) setCancelOpen(false); }} resourceLabel={row.test ?? "this study"} title={`Cancel ${row.test ?? "this study"}?`} description="It stays on the patient's record marked Withdrawn and comes off the imaging worklist." confirmLabel="Cancel the study" submittingLabel="Cancelling..." cancelLabel="Keep the study" submitting={cancelling} error={cancelError} onConfirm={(reason, note) => void cancel(reason, note)} /> ) : null}
); }; export default ScheduleStudyPage;