import React, { useState, useEffect, useRef } from "react"; import { Check } from "lucide-react"; import { Attachment, Patient } from "fhir/r4"; import { FhirCodeableConceptDisplay } from "../../utils/fhirCodeableConcept"; import { FhirHumanNameDisplay } from "../../utils/fhirHumanName"; import { FHIRAddressDisplay } from "../../utils/fhirAddress"; import { FHIRContactPointDisplay } from "../../utils/fhirContactPoint"; import { FhirIdentifierDisplay } from "../../utils/fhirIdentifier"; import { FhirReferenceDisplay } from "../../utils/fhirReference"; import { FhirPeriodDisplay } from "../../utils/fhirPeriod"; import { formatDate } from "../../utils/dateUtils"; import { ProfilePhoto } from "../index.ts"; import { renderField } from "../../utils/renderField"; import PatientInfo from "./PatientInfo.tsx"; import Contacts, { addressLabel } from "./Contacts.tsx"; import EmergencyContacts from "./EmergencyContacts.tsx"; import { usePatientServiceContext } from "../../services/PatientService.tsx"; import { useAuthServiceContext } from "../../services/AuthService.tsx"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog"; import { Card, CardHeader } from "../ui/card"; import { Button as UIButton } from "../ui/button"; import { FieldHistoryProvider, FieldHistoryScope } from "../FieldHistory/FieldHistoryProvider"; import { PROFILE_HISTORY, profileAddressHistory } from "../../utils/fieldHistory/profileHistoryFields"; import { ShowFieldHistoryToggle } from "../FieldHistory/ShowFieldHistoryToggle"; import { RecordEditedMarker } from "../FieldHistory/RecordEditedMarker"; import { FieldGroup, Section } from "../FieldHistory/FieldLayout"; import { PatientRecordOrigin } from "../PatientProfileCard/PatientRecordOrigin"; import type { SaveOperation } from "../../utils/saveOperation"; import { newerOf } from "../../utils/saveOverCurrent"; import { olderThan } from "../../utils/versionOrder"; interface FhirPatientDisplayProps { patient: Patient; isOpen?: boolean; onClose?: () => void; title?: string; asModal?: boolean; isLoading?: boolean; showEditButton?: boolean; showProfileBlocks?: boolean; contentOnly?: boolean; /** The profile sections without Edit controls or the photo card. */ readOnly?: boolean; onPatientUpdated?: (updatedPatient: Patient) => void; } export const FhirPatientDisplay: React.FC = ({ patient, isOpen = true, onClose, title = "Patient Details", asModal = true, isLoading, showEditButton = false, onPatientUpdated, showProfileBlocks = true, contentOnly = false, readOnly = false, }) => { const { updatePatient } = usePatientServiceContext(); const { currentUser, setCurrentUser, actingAs } = useAuthServiceContext(); const [currentPatient, setCurrentPatient] = useState(patient); const [submitting, setSubmitting] = useState(false); const [photo, setPhoto] = useState( patient?.photo?.[0], ); // THE NEWEST VERSION OF THIS PATIENT KNOWN HERE: handed in, saved by a section, or learned of after a refusal. Two // sections' saves can answer out of order (A's v4 held while B reads v4 and saves v5), and an older answer is shown // and handed on nowhere. const newest = useRef(patient); const isNewest = (record: Patient) => newest.current?.id !== record.id || !olderThan(record, newest.current); useEffect(() => { if (isNewest(patient)) newest.current = patient; setCurrentPatient((shown) => (shown?.id === patient?.id ? newerOf(patient, shown) : patient)); }, [patient]); const renderActiveValue = () => { if (currentPatient?.active === true) { return "Yes"; } if (currentPatient?.active === false) { return "No"; } return "-"; }; const renderDeceasedValue = () => { if (currentPatient?.deceasedBoolean === true) { if (currentPatient?.deceasedDateTime) { return `Yes (${formatDate(currentPatient.deceasedDateTime)})`; } return "Yes"; } if (currentPatient?.deceasedBoolean === false) { return "No"; } if (currentPatient?.deceasedDateTime) { return `Yes (${formatDate(currentPatient.deceasedDateTime)})`; } return "-"; }; const renderMultipleBirthValue = () => { if (currentPatient?.multipleBirthBoolean === true) { return "Yes"; } if (currentPatient?.multipleBirthBoolean === false) { return "No"; } if (currentPatient?.multipleBirthInteger) { return `Yes (${currentPatient.multipleBirthInteger})`; } return "-"; }; const renderLanguagesValue = () => { if ( !currentPatient?.communication || currentPatient.communication.length === 0 ) { return "-"; } return ( communication.language, )} multiple={true} /> ); }; const renderEmergencyContactName = () => { if ( !currentPatient?.contact || currentPatient.contact.length === 0 || !currentPatient.contact[0].name ) { return "-"; } return ; }; const renderEmergencyContactRelationship = () => { if ( !currentPatient?.contact || currentPatient.contact.length === 0 || !currentPatient.contact[0].relationship ) { return "-"; } return ( ); }; const renderEmergencyContactPhone = () => { if (!currentPatient?.contact || currentPatient.contact.length === 0) { return "-"; } return ( ); }; const renderEmergencyContactEmail = () => { if (!currentPatient?.contact || currentPatient.contact.length === 0) { return "-"; } return ( ); }; const renderEmergencyContactAddress = () => { if (!currentPatient?.contact || currentPatient.contact.length === 0) { return "-"; } return ; }; const renderEmergencyContactGender = () => { if (!currentPatient?.contact || currentPatient.contact.length === 0) { return "-"; } return currentPatient.contact[0].gender || "-"; }; const renderEmergencyContactOrganization = () => { if ( !currentPatient?.contact || currentPatient.contact.length === 0 || !currentPatient.contact[0].organization ) { return "-"; } return ( ); }; const renderEmergencyContactPeriod = () => { if ( !currentPatient?.contact || currentPatient.contact.length === 0 || !currentPatient.contact[0].period ) { return "-"; } return ; }; // `basedOn`: the version the section built this on; the write is conditional on it. Its answer is installed // (here, in the signed-in user, and by the page) only while the save that sent it still belongs to what is on // screen: an answer for one patient or viewer never lands on another. const handleUpdatePatient = async (updatedPatient: Patient, basedOn: string, operation: SaveOperation) => { // The request itself belongs to the save: it is not sent, and its answer is not held, once the save has moved on. // No page-wide busy flag is set here: each section owns its own saving state, ended by its save's deadline, so a // request that never answers cannot leave every other section without its Edit control. const result = await updatePatient(updatedPatient, basedOn, operation.transport); operation.admit(); if (!isNewest(result)) return result; newest.current = result; setCurrentPatient((shown) => (shown?.id === result.id ? newerOf(result, shown) : shown)); // When editing the logged-in user's OWN profile, sync the shared auth state from the PUT response so the // Profile display and the derived "Complete your profile" system task (both keyed off currentUser.fhir) update // immediately - without a refresh, which would hit HealthLake's lagging search index. Checked against the // signed-in user as they are when the answer lands, so a receptionist/practitioner editing a different patient // never overwrites their own currentUser. setCurrentUser((signedIn) => (signedIn?.fhir?.id && signedIn.fhir.id === result.id && !olderThan(result, signedIn.fhir) ? { ...signedIn, fhir: result } : signedIn)); onPatientUpdated?.(result); return result; }; // A newer record a section learned of after a refused save is shown here too, never an older one or another patient's. const handleSeen = (seen: Patient) => { if (isNewest(seen)) newest.current = seen; setCurrentPatient((shown) => (shown?.id === seen.id ? newerOf(seen, shown) : shown)); }; // Fax, pager and the like have no history row of their own; they stay listed together. const otherContacts = (currentPatient?.telecom ?? []).filter( (point) => point.system !== "phone" && point.system !== "email", ); const renderContent = () => ( <>

{currentPatient?.resourceType}

{showEditButton && ( Save Changes )}
{renderField( "Name", , )} {renderField( "Date of Birth", formatDate(currentPatient?.birthDate), "birthDate", )} {renderField("Gender", currentPatient?.gender, "gender")} {renderField("Active", renderActiveValue(), "active")} {renderField("Deceased", renderDeceasedValue())} {renderField("Multiple Birth", renderMultipleBirthValue())} {renderField( "Marital Status", // The component's own empty text is "Qualifications not provided", written for a practitioner's // qualifications. An element is never falsy, so renderField's "-" never showed here either. , "maritalStatus.coding[0].display", )} {renderField("Languages", renderLanguagesValue())} {/* Rows split the way the desk's How to reach you does, so each carries the history it keeps. */} {(currentPatient?.address?.length ?? 0) === 0 ? renderField("Address", , profileAddressHistory(0)) : currentPatient?.address?.map((address, index) => ( {renderField( addressLabel(address, index), , profileAddressHistory(index), )} ))} {renderField( "Phone", , PROFILE_HISTORY.phone, )} {renderField( "Email", , PROFILE_HISTORY.email, )} {otherContacts.length > 0 && renderField("Contact", )} {renderField( "Identifiers", , )}
{renderField("Emergency Contact Name", renderEmergencyContactName())} {renderField("Relationship", renderEmergencyContactRelationship())} {renderField("Emergency Contact Phone", renderEmergencyContactPhone())} {renderField("Emergency Contact Email", renderEmergencyContactEmail())} {renderField( "Emergency Contact Address", renderEmergencyContactAddress(), )} {renderField("Gender", renderEmergencyContactGender())} {renderField("Organization", renderEmergencyContactOrganization())} {renderField("Period", renderEmergencyContactPeriod())}
{renderField( "General Practitioner", , )}
{renderField( "Managing Organization", , )}
); if (contentOnly) { return renderContent(); } if (!asModal) { return ( <>

Profile

{/* WHERE THIS RECORD CAME FROM, on the screen that holds what it says. A patient the front desk registered has never been shown the name, date of birth and phone number somebody typed for them, and nothing anywhere asks them to look. The provenance the chart already states to staff is the same fact; this says it to the person it is about, and on a staff-created record asks them to check it. */} {patient.id && ( )}
{showProfileBlocks && ( // One column on a phone in the deck order; from a tablet up, You on the left and the two contact // sections on the right.
)} ); } if (!isOpen) return null; return ( { if (!next) onClose?.(); }} >
{title}
{renderContent()}
); }; export default FhirPatientDisplay;