import React, { useState, useEffect, useCallback } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { DismissibleAlert } from '../../../../../components/ui/dismissible-alert'; import { Spacer } from '../../../../../components/ui/spacer'; import { useForm, FormProvider } from 'react-hook-form'; import { AdverseEvent } from 'fhir/r4'; import { Check, FileText } from 'lucide-react'; import { useAdverseEventServiceContext, usePatientServiceContext, useAuthServiceContext } from '../../../../../services'; import { AppHeader, AppSpinner, ProfileLayoutContainer, ScrollableTab, FhirAdverseEvent, HistoryNavigator } from '../../../../../components'; import { useFhirAdverseEventTargetedBroadcast, useActiveEncounterReference } from '../../../../../hooks'; import { useArchive } from '../../../../../hooks/useArchive'; import ArchiveConfirmModal from '../../../../../components/ArchiveConfirmModal/ArchiveConfirmModal'; import { describeWriteFailure } from '../../../../../utils/fetchFailureMessage'; import { patientCacheGuard } from '../../../../../utils/cacheOwner'; const EditAdverseEventPage: React.FC = () => { const { adverseEventId } = useParams<{ adverseEventId: string }>(); const navigate = useNavigate(); const { currentUser } = useAuthServiceContext(); const { patientState, encounterState } = usePatientServiceContext(); const { cachedForPatientId: adverseEventsOwner, fetchAdverseEvent, transformToFhirAdverseEvent, transformFromFhirAdverseEvent, createAdverseEvent, updateAdverseEvent, archiveAdverseEvent, adverseEventsList: rawAdverseEventsList, } = useAdverseEventServiceContext(); // The cache outlives a change of patient: read only the patient on screen. const adverseEventsGuard = patientCacheGuard(adverseEventsOwner, patientState?.id); const adverseEventsList = adverseEventsGuard.list(rawAdverseEventsList); const [loading, setLoading] = useState(false); const [submitting, setSubmitting] = useState(false); const [success, setSuccess] = useState(); const [error, setError] = useState(); const [adverseEvent, setAdverseEvent] = useState(); const isNew = adverseEventId === 'new'; const spacerMargin = '24px'; // THE FIELD AGREES WITH THE SAVE NOW. The service already applied the active encounter whether or // not this field was touched, so the record filed against the visit correctly while the control // read "Select...". A clinician reading that is told their record is unattached when it is not, and // the natural correction is to open a list of every past visit and pick one - which is how a wrong // visit gets attached to a clinical fact. // // The same hook the eight clinical modals use, so there is one rule rather than a second copy. It // returns undefined outside a visit, which is what keeps the plain chart route opening empty. const activeEncounter = useActiveEncounterReference(); const methods = useForm({ defaultValues: { identifier: [], actuality: '', category: [], event: null, subject: null, encounter: activeEncounter ?? null, date: '', detected: '', recordedDate: '', resultingCondition: [], location: null, seriousness: null, severity: null, outcome: null, recorder: null, contributor: [], suspectEntity: [], subjectMedicalHistory: [], referenceDocument: [], study: [], } }); const { handleSubmit, reset } = methods; const init = useCallback((adverseEvent: AdverseEvent) => { reset(transformFromFhirAdverseEvent(adverseEvent)); setAdverseEvent(adverseEvent); }, [transformFromFhirAdverseEvent]); useFhirAdverseEventTargetedBroadcast({ adverseEvent, adverseEventId, onUpdate: init, onDelete: () => navigate('..', { relative: 'path' }) }); const archive = useArchive((reason, note) => archiveAdverseEvent(adverseEvent!, reason, note)); useEffect(() => { const loadAdverseEvent = async () => { if (isNew || adverseEvent) return; const foundAdverseEvent = adverseEventsList?.find(ae => ae.id === adverseEventId); if (foundAdverseEvent) { init(foundAdverseEvent); return; } setLoading(true); try { const fetchedAdverseEvent = await fetchAdverseEvent(adverseEventId as string); init(fetchedAdverseEvent); } catch (err) { console.error('Error loading adverse event:', err); setError('Could not load adverse event. Try again.'); } finally { setLoading(false); } }; loadAdverseEvent(); }, [adverseEventId, fetchAdverseEvent, transformFromFhirAdverseEvent]); const onSubmit = async (data: any) => { if (!patientState) { setError('Patient information is missing. Please try again.'); return; } setSubmitting(true); setSuccess(undefined); setError(undefined); try { if (isNew) { await createAdverseEvent(transformToFhirAdverseEvent(data, patientState, encounterState, currentUser?.fhir)); navigate('..', { relative: 'path' }); } else { await updateAdverseEvent({ id: adverseEvent?.id, ...transformToFhirAdverseEvent(data) }); } setSuccess(`Adverse event ${isNew ? 'created' : 'updated'} successfully.`); setTimeout(() => setSuccess(undefined), 3_000); } catch (err: any) { console.error(`Error ${isNew ? 'creating' : 'updating'} adverse event:`, err); setError(describeWriteFailure(err, `Failed to ${isNew ? 'create' : 'update'} adverse event`)); } finally { setSubmitting(false); } }; if (loading) { return ( ); } return ( resourceType="AdverseEvent" resourceId={adverseEventId || ''}> {(showHistory) => (
{!isNew && ( )} {!isNew && ( )} {success && ( <> setSuccess(undefined)}> {success} )} {error && ( <> setError(undefined)}>
{error}
)} { if (!o) archive.close(); }} resourceLabel="Adverse Event" submitting={archive.submitting} error={archive.error} onConfirm={async (reason, note) => { if (await archive.submit(reason, note)) { navigate('..', { relative: 'path' }); } }} />
)} ); }; export default EditAdverseEventPage;