import React, { useEffect, useMemo } from 'react'; import type { Encounter, Patient } from 'fhir/r4'; import { MapPin } from 'lucide-react'; import PresenceDot from '../../../components/PresenceDot/PresenceDot'; import { Card } from '../../../components/ui/card'; import { usePatientSnapshot } from '../../../hooks/usePatientSnapshot'; import { useAnchorView } from '../../../hooks/anchorView/useAnchorView'; import { SnapshotChart } from '../../../components/PatientSnapshot/SnapshotChart'; import { availableActions, NOT_CHECKED_IN_NOTE, VIDEO_STARTS_NOTE, type ActionDescriptor, type QueueItem } from './encounterLifecycle'; import { deriveSinceLastVisit } from './sinceLastVisit'; import { SinceLastVisit } from './SinceLastVisitCard'; import { useEncounterActions } from './useEncounterActions'; import { useReopenReason } from './askReopenReason'; import { BTN_CLASS, ageSex, ModalityBadge, VisitStatus } from './queueItemDisplay'; import { FinishedVisitReference } from './EncounterChartPanel'; import { LiveFinishConfirmDialog } from './LiveFinishConfirmDialog'; const STARTED = new Set(['in-progress', 'finished', 'cancelled', 'entered-in-error']); interface Props { item: QueueItem; patient?: Patient; onChanged?: () => void; onEnter: (encounter: Encounter, item: QueueItem) => void; } export const EncounterFocusPanel: React.FC = ({ item, patient, onChanged, onEnter }) => { // The original view is what this panel shows before Start, for a visit already on the record and not yet begun. const viewScope = useMemo(() => (item.encounter?.id && !STARTED.has(item.encounter.status) ? { encounterId: item.encounter.id } : undefined), [item.encounter?.id, item.encounter?.status]); const { view, viewId } = useAnchorView(patient?.id, viewScope); const actions = useEncounterActions({ onChanged, onEnter, view }); // Read the record again whenever the visit itself changes (finish, reopen, on-leave), or the snapshot // goes on showing the visit as it stood when the panel opened. const asOf = useMemo(() => new Date().toISOString(), [item.patientId, item.status, item.encounter?.meta?.versionId]); // The visit as this page last wrote it, which the record's history can trail by a minute or more. const knownVisit = useMemo(() => (item.encounter ? [item.encounter] : undefined), [item.encounter]); const read = useMemo(() => (view ? { read: () => view.readOptions(), key: viewId } : undefined), [view, viewId]); const { results, running } = usePatientSnapshot(patient, patient ? asOf : undefined, knownVisit, read); const since = useMemo(() => deriveSinceLastVisit(results), [results]); // Before Start this panel draws every row of each ready chart type, and the since-last-visit card from those rows; // the view cites only those its receipts prove. useEffect(() => { view?.rendered([ ...results.filter((result) => result.status === 'ready').flatMap((result) => result.items.map((shown) => shown.resource)), ...since.groups.flatMap((group) => group.items.map((shown) => shown.resource)), ]); }, [view, results, since]); // The reason the reopen carries. `ask` opens the dialog; the dialog itself is rendered below, so the // queue and the encounter workspace ask the same question in the same words. const { ask: askForReopenReason, dialog: reopenReasonDialog } = useReopenReason( (reason) => void actions.reopen(item, reason), ); // Why a visit that has not begun offers no Start: a video visit waits for both, any other for check-in. const video = actions.isVideoVisit(item); const waitingNote = video ? (['scheduled', 'arrived', 'triaged'].includes(item.status) ? VIDEO_STARTS_NOTE : undefined) : (item.status === 'scheduled' ? NOT_CHECKED_IN_NOTE : undefined); const run = (a: ActionDescriptor) => { switch (a.kind) { case 'start': return actions.start(item); case 'prepare-video': return actions.prepareVideo(item); case 'resume': return actions.resume(item); case 'set-onleave': return actions.setOnLeave(item); case 'bring-back': return actions.bringBackIn(item); case 'finish': return actions.finish(item); // The SAME dialog the encounter workspace uses. This button carries the identical label and is // the one reached from the list, so it is the one a clinician meets first - and it used to reopen // a finished visit silently, with no reason and nothing in the record to say a reopen had // happened. The dialog requires a reason; dismissing it writes nothing. case 'reopen': return askForReopenReason(); case 'open-chart': case 'preview': return actions.openChart(item); } }; const busy = actions.busyKey === item.key; return ( {reopenReasonDialog} {/* header */}
{item.room && Room {item.room}}

{item.patientName ?? patient?.name?.[0]?.text ?? 'Unknown patient'}

{ageSex(patient)}

Reason {item.reason ?? 'No reason given'}

{/* action bar */}
{availableActions(item.status, 'provider', false, true, video).map((a) => ( ))} {waitingNote &&

{waitingNote}

}
{/* body */}
{item.status === 'finished' /* A FINISHED VISIT KEEPS ITS REFERENCE (deck "Finished"; Codex P06 ruling item 2): the conditions, their drill-down and the way into the record stay, from this panel's own snapshot. */ ? : <>

Chart snapshot

{/* 🏷️ Card 644 — level 4: "Chart snapshot" above is an h3. */}
}
); };