import { useEffect, useMemo, useRef, useState } from "react"; import type { UIEvent } from "react"; import { Appointment, Task } from "fhir/r4"; import { formatPreferredAvailability } from "../../../utils/appointmentAvailabilityPreference"; import { CircleAlert, CircleCheck, CircleX, ClipboardList, Clock3, Eye, FileText, LoaderCircle, Pencil, TriangleAlert, } from "lucide-react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "../../../components/ui/dialog.tsx"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "../../../components/ui/select.tsx"; import { Tabs, TabsContent, TabsList, TabsTrigger, } from "../../../components/ui/tabs.tsx"; import { VisitInvitationPanel } from "../../../components/VisitInvitation/VisitInvitationPanel.tsx"; import RecordAuthorizationDialog from "../../../components/RecordAuthorizationDialog/RecordAuthorizationDialog.tsx"; import { ORG_AGREEMENT_ORDER } from "../../../components/OrgAgreements/orgAgreements"; import { AKASHIC_TASK_STATUS_REASON_SYSTEM, getAkashicTaskKind, getCustomTaskDueDisplay, getCustomTaskDueMode, isCustomTask, PATIENT_UNABLE_TO_COMPLETE_CODE, } from "../../../services/TaskService.tsx"; import type { PatientReadinessSummary } from "../../../services/PatientReadinessService.tsx"; import { getAppointmentLocationName } from "../../../utils/appointmentLocation.ts"; import { resolveBookingTimeZone, viewerZoneLabelFor } from "../../../utils/practiceTimezone.ts"; import { useAuthServiceContext } from "../../../services"; import { useFhirLocations, useFhirPractitioners } from "../../../hooks"; import { appointmentPatientId, resolveAppointmentPatientLabel, resolveAppointmentPractitionerLabel, type PatientNameResolution, } from "../../../utils/appointmentParticipants.ts"; import { arrivalDayUnavailableReason, careUnavailableReason, getAppointmentArrivalTimeliness, getReceptionistAppointmentStatus, getReceptionistAppointmentStatusLabel, noShowUnavailableReason, RECEPTIONIST_APPOINTMENT_STATUS_OPTIONS, } from "./receptionistAppointmentStatus.ts"; import type { ReceptionistAppointmentStatus } from "./receptionistAppointmentStatus.ts"; import { appointmentSubjectLine } from "../../../utils/appointmentSubject"; import { useTreatmentAgreements } from "./useTreatmentAgreements.ts"; import { customTaskTitle } from "../../../utils/appointmentTasks.ts"; interface ReceptionistAppointmentDetailsDialogProps { open: boolean; appointment?: Appointment; /** * HANDED DOWN FROM THE BOARD, not resolved again here. done-when 4: a row that names its * patient must not open a dialog that does not - and the only way to guarantee that is for both to * read ONE resolution. A second hook here would also re-read a patient the row already fetched. */ patientNames: PatientNameResolution; tasks: Task[]; tasksLoading: boolean; taskError?: string; readiness?: PatientReadinessSummary; readinessLoading: boolean; readinessError?: string; onRetryReadiness?: () => void; patientAppointments: Appointment[]; patientAppointmentsLoading: boolean; patientAppointmentsError?: string; statusUpdating: boolean; statusError?: string; /** The status asked for and not saved; named in the failure and offered again by Retry. */ requestedStatus?: ReceptionistAppointmentStatus; onRetryStatus?: () => void; /** A write that may have landed, with the confirming read failed: no status is claimed. */ statusUnknown?: boolean; onCheckStatus?: () => void; /** The server's refusal of the last status change, in its own words. Nothing retries it. */ statusRefusal?: string; /** The visit changed since it was read here: nothing is chosen until it is loaded as it is now. */ statusStale?: boolean; onReloadStatus?: () => void; /** What the last saved status change left unfinished on the schedule; the change itself was saved. */ statusNote?: string[]; highlightStatus?: boolean; onOpenChange: (open: boolean) => void; onEdit: () => void; onStatusChange: (status: ReceptionistAppointmentStatus) => void; onViewProfile?: () => void; /** Leaves a definitely failed change and returns to the visit's details. */ onDismissStatusError?: () => void; /** Present only while a finished visit waits for checkout. Payment and a follow-up are optional. */ checkout?: { /** Absent for someone who cannot open invoices; the button would only lead to a refusal. */ onInvoices?: () => void; onScheduleFollowUp: () => void; onDone: () => void; saving: boolean; error?: string; /** What the saved checkout left unfinished; the checkout itself was saved. */ note?: string[]; }; /** What a checkout saved from these details left unfinished, once the visit no longer waits for checkout. */ checkoutSavedNote?: string[]; /** * Present for a cancelled visit: saves it again exactly as it is, which closes its visit records the cancellation left * open. Its outcome is said here; a failure also says what it did to the visit's record. */ cancelledResave?: { onResave: () => void; saving: boolean; note?: string[]; error?: string; visitRecordLine?: string; /** Unconfirmed or changed since: nothing more is saved from these details until the visit is loaded as it is now. */ standing?: "unconfirmed" | "changed"; onReload?: () => void; }; } type TaskDisplayStatus = "complete" | "incomplete" | "unable" | "cancelled"; type AppointmentHistoryTab = "upcoming" | "past"; type AppointmentTimelineStatus = | "on-time" | "late" | "cancelled" | "missed" | "scheduled" | "pending"; type AppointmentTimelineIndicator = { status: AppointmentTimelineStatus; label: string; }; const APPOINTMENT_HISTORY_PAGE_SIZE = 5; const ACTIVE_APPOINTMENT_STATUSES = new Set([ "proposed", "pending", "booked", "arrived", "checked-in", "waitlist", ]); // WHAT THE VISIT IS FOR, from the one shared helper - see utils/appointmentSubject. The second of three // local copies of the same idea, all of which showed the routine default in the place the service belongs. const getAppointmentTypeDisplay = (appointment: Appointment) => appointmentSubjectLine(appointment); const getAppointmentStatusDisplay = (status?: Appointment["status"]) => { switch (status) { case "booked": return "Booked"; case "proposed": return "Proposed"; case "pending": return "Pending"; case "arrived": return "Arrived"; case "fulfilled": return "Completed"; case "cancelled": return "Cancelled"; case "noshow": return "No-show"; case "entered-in-error": return "Entered in error"; case "checked-in": return "Checked in"; case "waitlist": return "Waitlist"; default: return "Unknown"; } }; // "Aug 24 · 10:00–11:00 AM", as the deck writes the visit: the meridiem once when both ends share it. const formatAppointmentWhen = (appointment: Appointment, zoneLabel?: string | null) => { if (!appointment.start) return "Date not available"; const start = new Date(appointment.start); const day = start.toLocaleDateString("en-US", { month: "short", day: "numeric" }); const clock = (at: Date) => at.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true }); const suffix = zoneLabel ? ` ${zoneLabel}` : ""; if (!appointment.end) return `${day} · ${clock(start)}${suffix}`; const [from, fromMeridiem] = clock(start).split(" "); const until = clock(new Date(appointment.end)); const range = until.endsWith(fromMeridiem) ? `${from}–${until}` : `${clock(start)}–${until}`; return `${day} · ${range}${suffix}`; }; const formatAppointmentTime = (value?: string) => { if (!value) return undefined; return new Date(value).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true, }); }; /** * `zoneLabel` is supplied ONLY when this browser's clock differs from the practice's. * * An appointment's `start` is an absolute instant, so this renders it in the VIEWER's timezone - * correct, and indistinguishable from the practice's own clock unless it says so. A remote receptionist * reading "7:00 AM" for a 9:00 AM Central appointment has no way to notice. Appended once, at the end of * the range, rather than on both ends: "7:00 AM - 8:00 AM PDT" reads as one span, which is what it is. */ const formatAppointmentTimeRange = (appointment: Appointment, zoneLabel?: string | null) => { const start = formatAppointmentTime(appointment.start); const end = formatAppointmentTime(appointment.end); const suffix = zoneLabel ? ` ${zoneLabel}` : ""; if (start && end) return `${start} – ${end}${suffix}`; const single = start || end; return single ? `${single}${suffix}` : "Time not available"; }; const formatAppointmentListDate = (value?: string) => { if (!value) return "Date not available"; return new Date(value).toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric", year: "numeric", }); }; const getAppointmentStartTimestamp = (appointment: Appointment) => { if (!appointment.start) return undefined; const timestamp = new Date(appointment.start).getTime(); return Number.isNaN(timestamp) ? undefined : timestamp; }; const getAppointmentParticipantReference = ( appointment: Appointment, resourceTypes: string[], ) => appointment.participant?.find((participant) => resourceTypes.some((resourceType) => participant.actor?.reference?.startsWith(`${resourceType}/`), ), )?.actor?.reference; const isSameAppointment = (candidate: Appointment, selected: Appointment) => { if (candidate.id && selected.id) { return candidate.id === selected.id; } return ( candidate.start === selected.start && candidate.end === selected.end && getAppointmentParticipantReference(candidate, ["Practitioner", "PractitionerRole"]) === getAppointmentParticipantReference(selected, ["Practitioner", "PractitionerRole"]) ); }; const getAppointmentTimelineIndicator = ( appointment: Appointment, now = Date.now(), ): AppointmentTimelineIndicator => { if (appointment.status === "cancelled") { return { status: "cancelled", label: "Cancelled" }; } if (appointment.status === "noshow") { return { status: "missed", label: "Missed" }; } if ( appointment.status === "fulfilled" || appointment.status === "arrived" || appointment.status === "checked-in" ) { return getAppointmentArrivalTimeliness(appointment) === "late" ? { status: "late", label: "Late" } : { status: "on-time", label: "On time" }; } const startTimestamp = getAppointmentStartTimestamp(appointment); if ( startTimestamp !== undefined && startTimestamp < now && ACTIVE_APPOINTMENT_STATUSES.has(appointment.status) ) { return { status: "late", label: "Late" }; } if (appointment.status === "pending") { return { status: "pending", label: "Pending" }; } if (appointment.status === "proposed") { return { status: "pending", label: "Proposed" }; } if (appointment.status === "waitlist") { return { status: "pending", label: "Waitlisted" }; } if (appointment.status === "booked") { return { status: "scheduled", label: "Booked" }; } return { status: "pending", label: getAppointmentStatusDisplay(appointment.status) }; }; const AppointmentTimelineStatusIcon = ({ status, }: { status: AppointmentTimelineStatus; }) => { if (status === "scheduled") { return ; } if (status === "late" || status === "pending") { return ; } if (status === "cancelled" || status === "missed") { return ; } return ; }; const isUnableToCompleteTask = (task: Task) => isCustomTask(task) && task.status === "failed" && (task.statusReason?.coding?.some( (coding) => coding.system === AKASHIC_TASK_STATUS_REASON_SYSTEM && coding.code === PATIENT_UNABLE_TO_COMPLETE_CODE, ) || Boolean(task.statusReason?.text?.trim())); const getTaskDisplayStatus = (task: Task): TaskDisplayStatus => { if (task.status === "completed") return "complete"; if (task.status === "cancelled") return "cancelled"; if (isUnableToCompleteTask(task)) return "unable"; return "incomplete"; }; const getTaskStatusLabel = (status: TaskDisplayStatus) => { if (status === "complete") return "Complete"; if (status === "unable") return "Unable to complete"; if (status === "cancelled") return "Cancelled"; return "Incomplete"; }; const getTaskTitle = (task: Task) => { const kind = getAkashicTaskKind(task); // A custom task's title is its name; its description is the instructions, shown under it. const title = customTaskTitle(task); if (title) return title; if (kind === "questionnaire") { return ( task.focus?.display?.trim() || task.description?.replace(/^Complete questionnaire:\s*/i, "").trim() || "Questionnaire" ); } return task.description?.trim() || task.code?.text?.trim() || "Assigned task"; }; const getTaskTypeLabel = (task: Task) => { const kind = getAkashicTaskKind(task); if (kind === "questionnaire") return "Questionnaire"; if (kind === "custom") return "Custom task"; return "Task"; }; const getTaskDueDisplay = (task: Task) => { if (!isCustomTask(task)) { return "Due for this appointment"; } const dueMode = getCustomTaskDueMode(task); if (dueMode === "this-appointment") { return "Due for this appointment"; } if (dueMode === "next-appointment") { return "Due for this appointment"; } if (dueMode === "specific-appointment") { return "Assigned to this specific appointment"; } return getCustomTaskDueDisplay(task); }; const TaskStatusIcon = ({ status }: { status: TaskDisplayStatus }) => { if (status === "complete") return ; if (status === "unable" || status === "cancelled") return ; return ; }; export default function ReceptionistAppointmentDetailsDialog({ open, appointment, patientNames, tasks, tasksLoading, taskError, readiness, readinessLoading, readinessError, onRetryReadiness, patientAppointments, patientAppointmentsLoading, patientAppointmentsError, statusUpdating, statusError, requestedStatus, onRetryStatus, statusUnknown = false, onCheckStatus, statusRefusal, statusStale = false, onReloadStatus, statusNote, highlightStatus = false, onOpenChange, onEdit, onStatusChange, onViewProfile, onDismissStatusError, checkout, checkoutSavedNote, cancelledResave, }: ReceptionistAppointmentDetailsDialogProps) { // For deciding whether these times need a timezone label - see zoneLabelFor below. const { wpOrganization } = useAuthServiceContext(); const { locationsList } = useFhirLocations(); // The practitioner half needs no fetch - the reference resolves against records this page already // loads. The PATIENT half is a PROP, not a hook here; see the note on it. const { practitionersList } = useFhirPractitioners(); /** * Whose clock is this appointment's time on, if not the viewer's own? * * RESOLVED PER APPOINTMENT, not once for the dialog. The patient-history list below shows OTHER * appointments, which in a multi-site org can be at a DIFFERENT site on a different clock - one label * computed from the appointment in focus would then be wrong for exactly the rows a receptionist is * scanning to spot a conflict. * Returns null when the clocks match, which is the ordinary case and renders no suffix at all. */ const zoneLabelFor = (appt: Appointment | undefined): string | null => { if (!appt?.start) return null; const siteId = appt.participant ?.map((p) => p.actor?.reference) .find((ref) => ref?.startsWith("Location/")) ?.split("/")[1]; return viewerZoneLabelFor( new Date(appt.start), resolveBookingTimeZone({ site: siteId ? locationsList.find((loc) => loc.id === siteId) : undefined, organization: wpOrganization.current ?? undefined, }), ); }; const [activeAppointmentTab, setActiveAppointmentTab] = useState("upcoming"); const [visibleUpcomingCount, setVisibleUpcomingCount] = useState( APPOINTMENT_HISTORY_PAGE_SIZE, ); const [visiblePastCount, setVisiblePastCount] = useState( APPOINTMENT_HISTORY_PAGE_SIZE, ); const paginationLockRef = useRef(false); const statusFieldRef = useRef(null); const appointmentIdentity = appointment?.id || `${appointment?.start || "no-start"}:${appointment?.end || "no-end"}`; useEffect(() => { if (!open) return; setActiveAppointmentTab("upcoming"); setVisibleUpcomingCount(APPOINTMENT_HISTORY_PAGE_SIZE); setVisiblePastCount(APPOINTMENT_HISTORY_PAGE_SIZE); paginationLockRef.current = false; }, [appointmentIdentity, open]); useEffect(() => { if (!open || !highlightStatus) return; const animationFrame = window.requestAnimationFrame(() => { statusFieldRef.current?.scrollIntoView({ behavior: "smooth", block: "center", }); statusFieldRef.current ?.querySelector('[data-slot="select-trigger"]') ?.focus(); }); return () => window.cancelAnimationFrame(animationFrame); }, [appointmentIdentity, highlightStatus, open]); const { upcomingAppointments, pastAppointments } = useMemo(() => { if (!appointment) { return { upcomingAppointments: [] as Appointment[], pastAppointments: [] as Appointment[], }; } const now = Date.now(); const otherAppointments = patientAppointments.filter((patientAppointment) => { const startTimestamp = getAppointmentStartTimestamp(patientAppointment); return ( startTimestamp !== undefined && patientAppointment.status !== "entered-in-error" && !isSameAppointment(patientAppointment, appointment) ); }); return { upcomingAppointments: otherAppointments .filter((patientAppointment) => (getAppointmentStartTimestamp(patientAppointment) as number) >= now, ) .sort( (left, right) => (getAppointmentStartTimestamp(left) as number) - (getAppointmentStartTimestamp(right) as number), ), pastAppointments: otherAppointments .filter((patientAppointment) => (getAppointmentStartTimestamp(patientAppointment) as number) < now, ) .sort( (left, right) => (getAppointmentStartTimestamp(right) as number) - (getAppointmentStartTimestamp(left) as number), ), }; }, [appointment, patientAppointments]); const activeAppointments = activeAppointmentTab === "upcoming" ? upcomingAppointments : pastAppointments; const activeVisibleCount = activeAppointmentTab === "upcoming" ? visibleUpcomingCount : visiblePastCount; const hasMoreActiveAppointments = activeVisibleCount < activeAppointments.length; useEffect(() => { paginationLockRef.current = false; }, [activeAppointmentTab, visiblePastCount, visibleUpcomingCount]); const handleDialogBodyScroll = (event: UIEvent) => { if (!hasMoreActiveAppointments || paginationLockRef.current) { return; } const scrollElement = event.currentTarget; const distanceFromBottom = scrollElement.scrollHeight - scrollElement.scrollTop - scrollElement.clientHeight; if (distanceFromBottom > 64) { return; } paginationLockRef.current = true; if (activeAppointmentTab === "upcoming") { setVisibleUpcomingCount((currentCount) => Math.min( currentCount + APPOINTMENT_HISTORY_PAGE_SIZE, upcomingAppointments.length, ), ); return; } setVisiblePastCount((currentCount) => Math.min( currentCount + APPOINTMENT_HISTORY_PAGE_SIZE, pastAppointments.length, ), ); }; const { agreements, recheck: recheckAgreements } = useTreatmentAgreements( appointment ? appointmentPatientId(appointment) : undefined, wpOrganization.current?.id, open, ); // A refusal means the agreements this screen last read are out of date. useEffect(() => { if (statusRefusal) void recheckAgreements(); }, [statusRefusal, recheckAgreements]); if (!appointment) return null; const patient = resolveAppointmentPatientLabel(appointment, patientNames); const preferredAvailability = formatPreferredAvailability(appointment); // A time-window request is not placed yet: no practitioner or time has been chosen, by design. const awaitingPlacement = !appointment.start && Boolean(preferredAvailability); const NOT_CHOSEN = "Not chosen yet"; const practitioner = resolveAppointmentPractitionerLabel( appointment, practitionersList, awaitingPlacement ? NOT_CHOSEN : "Unknown practitioner", ); const appointmentType = getAppointmentTypeDisplay(appointment); // follow-on. Deliberately given NO organization fallback - see the render site below. const appointmentLocation = getAppointmentLocationName(appointment, undefined, locationsList); const viewerZoneLabel = zoneLabelFor(appointment); const visitSiteId = appointment.participant ?.map((p) => p.actor?.reference) .find((ref) => ref?.startsWith("Location/")) ?.split("/")[1]; // The visit's own clock decides its day, as the server does. const visitTimeZone = resolveBookingTimeZone({ site: visitSiteId ? locationsList.find((loc) => loc.id === visitSiteId) : undefined, organization: wpOrganization.current ?? undefined, }); const reasonText = appointment.reasonCode?.[0]?.text?.trim(); // A time-window request has no slot - it can't be confirmed in place; route it to the edit form so // the receptionist can place a practitioner + time before it becomes booked. const hasSlot = Boolean( appointment.slot?.some((s) => s.reference?.startsWith("Slot/")), ); const receptionistStatus = getReceptionistAppointmentStatus(appointment); const lastSavedLabel = getReceptionistAppointmentStatusLabel(appointment); const completedTaskCount = tasks.filter((task) => task.status === "completed").length; const otherAppointmentCount = upcomingAppointments.length + pastAppointments.length; const firstName = patient.split(/\s+/)[0] || patient; const requestedLabel = requestedStatus ? RECEPTIONIST_APPOINTMENT_STATUS_OPTIONS.find((option) => option.value === requestedStatus)?.label ?? requestedStatus : undefined; const requestedIsCheckIn = requestedStatus === "arrived-on-time" || requestedStatus === "arrived-late"; // The overlay focuses on the one change in flight, or on the one that definitely failed, as drawn. const view: "saving" | "failed" | "details" = statusUpdating && requestedStatus && !statusUnknown ? "saving" : statusError && requestedStatus && !statusUnknown && !statusRefusal && !statusStale ? "failed" : "details"; const statusSelect = (value: string, disabled: boolean, label: string) => ( ); const alert = (title: string, message: string, action?: { label: string; onClick: () => void }) => (
); const confirmAction = (appointment.status === "proposed" || appointment.status === "pending") ? hasSlot ? ( ) : ( ) : null; const resaveAction = appointment.status === "cancelled" && cancelledResave ? ( ) : null; const renderAppointmentList = ( appointmentList: Appointment[], visibleCount: number, emptyMessage: string, ) => { if (patientAppointmentsLoading && patientAppointments.length === 0) { return (
Loading patient appointments…
); } if (patientAppointmentsError) { return (
{patientAppointmentsError}
); } if (appointmentList.length === 0) { return

{emptyMessage}

; } const visibleAppointments = appointmentList.slice(0, visibleCount); const hasMoreAppointments = visibleAppointments.length < appointmentList.length; const nextPageSize = Math.min( APPOINTMENT_HISTORY_PAGE_SIZE, appointmentList.length - visibleAppointments.length, ); return ( <>
{visibleAppointments.map((patientAppointment, index) => { const timelineIndicator = getAppointmentTimelineIndicator(patientAppointment); const appointmentPractitioner = resolveAppointmentPractitionerLabel( patientAppointment, practitionersList, "Unknown practitioner", ); return (
{formatAppointmentListDate(patientAppointment.start)} {formatAppointmentTimeRange(patientAppointment, zoneLabelFor(patientAppointment))}
{getAppointmentTypeDisplay(patientAppointment)} {appointmentPractitioner}
{timelineIndicator.label}
); })}
Showing {visibleAppointments.length} of {appointmentList.length} {hasMoreAppointments && ( Scroll down to load {nextPageSize} more. )}
); }; const statusDetails = (
Appointment status {statusSelect(statusUnknown ? "" : receptionistStatus, statusUpdating || statusUnknown || statusStale, "Appointment status")} {statusUnknown ? ( alert( "Could not confirm the change", "We could not confirm whether this change was saved. Nothing will be sent again until it is checked.", onCheckStatus ? { label: "Check again", onClick: onCheckStatus } : undefined, ) ) : statusStale ? ( alert( `Could not change ${firstName}'s appointment`, statusRefusal ?? statusError ?? "Someone else changed this appointment, so nothing was saved.", onReloadStatus ? { label: "Load it as it is now", onClick: onReloadStatus } : undefined, ) ) : statusRefusal ? ( alert(`Could not change ${firstName}'s appointment`, `${statusRefusal} It is still ${lastSavedLabel}.`) ) : statusError ? ( alert(`Could not change ${firstName}'s appointment`, `${statusError} It is still ${lastSavedLabel}.`) ) : statusNote && statusNote.length > 0 ? (
{statusNote.map((line) =>

{line}

)}
) : (

Changes save when selected. The board updates after the save succeeds.

)}
); const detailRows = [ { label: "Appointment type", value: appointmentType }, { label: "Reason", value: reasonText }, { label: "Prefers", value: awaitingPlacement ? undefined : preferredAvailability }, { label: "Description", value: appointment.description }, { label: "Patient instructions", value: appointment.patientInstruction }, { label: "Comment", value: appointment.comment }, ].filter((row): row is { label: string; value: string } => Boolean(row.value)); return ( Appointment Details {patient} · {appointmentType}
Patient
{patient}
{awaitingPlacement && (
Requested
{preferredAvailability}
)}
Practitioner
{practitioner}
Appointment
{awaitingPlacement ? (
{NOT_CHOSEN}
) : (
{formatAppointmentWhen(appointment, viewerZoneLabel)}
)}
{/* No organization fallback: on a staff surface a missing Location is left absent rather than named as the practice, which would call a video visit an in-person one. */} {appointmentLocation && (
Location
{appointmentLocation}
)}
{view === "saving" && (
Appointment status {statusSelect(requestedStatus ?? receptionistStatus, true, "Appointment status")}

Updating…

Last saved status: {lastSavedLabel}. {requestedIsCheckIn ? ` ${firstName} has not moved to Checked in yet.` : ""}

)} {view === "failed" && ( <> {alert( requestedIsCheckIn ? `Could not check in ${firstName}` : `Could not change ${firstName}'s appointment to ${requestedLabel}`, `The appointment is still ${lastSavedLabel}. The selection and patient context are retained.`, onRetryStatus ? { label: requestedIsCheckIn ? "Retry check-in" : "Retry", onClick: onRetryStatus } : undefined, )}
Requested status {statusSelect(requestedStatus as string, false, "Requested status")}

Last saved status: {lastSavedLabel}

)} {view === "details" && ( <> {statusDetails} {checkout && (

Checkout

The visit is finished.{" "} {checkout.onInvoices ? "Take a payment or book a follow-up if needed, then mark checkout done." : "Book a follow-up if needed, then mark checkout done."}

{checkout.note && checkout.note.length > 0 && (
{checkout.note.map((line) =>

{line}

)}
)} {checkout.error && (

{checkout.error}

)}
{checkout.onInvoices && ( )}
)} {appointment.status === "cancelled" && cancelledResave && (cancelledResave.error || cancelledResave.note?.length || cancelledResave.visitRecordLine) && (

Visit records

{cancelledResave.error ? alert( "Could not save the visit again", cancelledResave.error, cancelledResave.standing && cancelledResave.onReload && !cancelledResave.saving ? { label: "Load it as it is now", onClick: cancelledResave.onReload } : undefined, ) : cancelledResave.note && cancelledResave.note.length > 0 && (
{cancelledResave.note.map((line) =>

{line}

)}
)} {cancelledResave.visitRecordLine && (

{cancelledResave.visitRecordLine}

)}
)} {!checkout && checkoutSavedNote && checkoutSavedNote.length > 0 && (

Checkout

{checkoutSavedNote.map((line) =>

{line}

)}
)} {appointmentPatientId(appointment) && (

Agreements

{/* Three documents, one row each, the same bundle the patient meets. An outstanding one draws the capture panel; an answered one draws its settled row. */} {ORG_AGREEMENT_ORDER.map((instrument) => ( void recheckAgreements()} /> ))}
)}

Patient Profile & History

{readinessLoading && !readiness ? (

Checking patient setup…

) : readinessError ? ( alert( "Unable to check patient readiness", readinessError, onRetryReadiness ? { label: "Retry", onClick: onRetryReadiness } : undefined, ) ) : readiness ? ( <>

{readiness.completed ? "Setup complete" : `${readiness.completedCount} of ${readiness.totalCount} setup areas complete`}

{!readiness.completed && (

Still needed: {readiness.incompleteLabels.join(", ")}.

)} ) : (

Patient profile and history status is unavailable.

)}

Assigned tasks {tasks.length > 0 && ( {completedTaskCount} of {tasks.length} complete )}

{tasksLoading && tasks.length === 0 ? (

Loading appointment tasks…

) : taskError ? (

{taskError}

) : tasks.length === 0 ? (

No outstanding tasks for this visit.

) : (
{tasks.map((task, index) => { const displayStatus = getTaskDisplayStatus(task); const taskType = getTaskTypeLabel(task); return (
{taskType}
{getTaskTitle(task)}
{customTaskTitle(task) && task.description?.trim() && (

{task.description.trim()}

)}
{getTaskStatusLabel(displayStatus)}
{/* A voided task is no longer due; the server says why (cancelling the visit writes "Appointment cancelled"). */} {displayStatus === "cancelled" ? task.statusReason?.text?.trim() || "No longer needed" : getTaskDueDisplay(task)}
{displayStatus === "unable" && (
Patient explanation

{task.statusReason?.text?.trim() || "No explanation was provided."}

)}
); })}
)}
More appointment details {detailRows.map((row) => (
{row.label} {row.value}
))} {/* The collaboration team hangs off a saved appointment id. */} {appointment.id && (
)}

Other appointments {!patientAppointmentsLoading && !patientAppointmentsError && ( {otherAppointmentCount} total )}

{ if (value === "upcoming" || value === "past") { setActiveAppointmentTab(value); paginationLockRef.current = false; } }} className="ahr-receptionist-appointment-history-tabs" > Upcoming {upcomingAppointments.length} Past {pastAppointments.length} {renderAppointmentList(upcomingAppointments, visibleUpcomingCount, "No other upcoming appointments are scheduled.")} {renderAppointmentList(pastAppointments, visiblePastCount, "No past appointments were found.")}
)}
{view === "saving" ? (

Saving appointment status…

) : view === "failed" && onDismissStatusError ? ( ) : view === "failed" ? null : ( <> {onViewProfile && ( )} {confirmAction} {resaveAction} )}
); }