import React, { useCallback } from 'react'; import { usePatientServiceContext, useAuthServiceContext } from '../../../../../services'; import PatientProfileCard from '../../../../../components/PatientProfileCard/PatientProfileCard'; import RecordAuthorizationDialog from '../../../../../components/RecordAuthorizationDialog/RecordAuthorizationDialog'; import { ORG_AGREEMENT_ORDER } from '../../../../../components/OrgAgreements/orgAgreements'; import { useAgreementReread } from '../../../../../hooks/useAgreementReread'; import { formatFhirHumanName } from '../../../../../utils/fhirHumanName.tsx'; import { Patient } from 'fhir/r4'; // The routed patient-profile section for staff (practitioner + receptionist) at /patients/:id/profile. Repointed // from the heavy FhirPatient form (EditPatientProfile) to the shared PatientProfileCard so both roles get the same // safe, click-to-edit demographics view (guardrail-1 full fetch->merge->PUT) as the receptionist workspace modal. // The patient's CLINICAL chart lives in sibling routes and is untouched. const PractitionerEditProfilePage: React.FC = () => { const { patientState, setPatientState, fetchPatient } = usePatientServiceContext(); const { userRole, USER_ROLES } = useAuthServiceContext(); // THE CHART HAS TO BE TOLD, because the server changed the patient and only the dialog knows. // // Recording an authorization writes Patient.generalPractitioner server-side, as a consequence of the // attestation. The dialog refreshes its own status row and is correct - but the banner in the chart // header and the Care & identity row are both derived from the Patient this page is holding, so they // kept saying the record was unreadable after it had been opened. Measured in a browser: the status // said Authorized while the banner still said to go and authorize. // // forceReload IS LOAD-BEARING. fetchPatient serves from patientList unless forced, so without it this // returns the same stale copy and the refresh is inert while looking correct. // // It does NOT declareExpectedPatient, and that is the protection rather than an omission: this is an // UPDATE to the patient already on screen, not a subject change, so it rides the expectation the // opening surface set. Declaring here would re-derive the expectation from the response it exists to // check. See PatientService's note on who may declare. const refreshAfterAuthorization = useCallback(async () => { const id = patientState?.id; if (!id) return; try { setPatientState(await fetchPatient(id, false, undefined, true)); } catch (err) { // The write succeeded - only the refresh failed. Leaving the stale banner is the same defect // this fixes, but claiming success would be worse, so it stays quiet and the next navigation // picks up the truth. console.error('Failed to refresh the patient after recording authorization:', err); } }, [patientState?.id, fetchPatient, setPatientState]); // THE SECTION THE CHART'S OWN BANNER SENDS PEOPLE TO. A patient with no care relationship opens as a // complete-looking chart whose every clinical list then refuses, and the header says to fix it here. // Until now there was nothing here to fix it with: the general practitioner row is read-only, and the // only staff attest controls were inside an UPCOMING appointment's sheet and in the receptionist's // Quick View workspace. So a patient with no appointment could only be authorized by a receptionist, // and a practitioner - the one person actually reading the banner, because their lists are the ones // refusing - had no route at all. // // The same component the receptionist workspace already stacks above this card, mounted the same way. // It is not a second answer to the question: it fetches its own status, so the two surfaces cannot // drift into disagreeing. // // KEYED ON THE VIEWER, not on the route. renderPatientRoutes() is shared with the patient's own // section, so a patient or related person can reach this path - and this is a STAFF attestation, // recording that somebody agreed in person. If the viewer and the grantor were the same human it // would be a self-authorization with a witness field naming the person it authorizes. const viewerIsPatient = [USER_ROLES.PATIENT, USER_ROLES.RELATED_PERSON].includes(userRole as string); // The patient may answer from their own portal while this is open; the rows read again while it is seen. const agreementReread = useAgreementReread(!viewerIsPatient && !!patientState?.id); if (!patientState) return null; const patient = patientState as Patient; // The same fallback the receptionist workspace uses, so the two surfaces cannot name one person two // ways. `formatFhirHumanName` already handles a record with no name at all. const patientName = formatFhirHumanName(patient.name, { emptyText: 'Patient' }); return ( <> {!viewerIsPatient && patient.id && (