import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useForm, FormProvider } from 'react-hook-form'; import type { Device } from 'fhir/r4'; import { Check, FileText } from 'lucide-react'; import { DismissibleAlert } from '../../../../../components/ui/dismissible-alert'; import { AppHeader, AppSpinner, ProfileLayoutContainer, ScrollableTab, HistoryNavigator, FhirDevice } from '../../../../../components'; import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel } from '../../../../../components/ui/alert-dialog'; import { useDeviceServiceContext, usePatientServiceContext } from '../../../../../services'; import { useFhirDeviceTargetedBroadcast } from '../../../../../hooks'; import { useEditBaseline } from '../../../../../hooks/useEditBaseline'; import { useProxyCannotAddToRecord } from '../../../../../hooks/useProxyCannotAddToRecord'; import { describeProxyCannotAddToRecord } from '../../../../../utils/proxyFormAuthority'; import { describeWriteFailure } from '../../../../../utils/fetchFailureMessage'; import { isSaveRetired, outcomeUnknown, useSaveScope } from '../../../../../utils/saveOperation'; import { reloadBaseline, saveOverBaseline, staleReasonOf } from '../../../../../utils/saveOverBaseline'; import { asRecordChanged } from '../../../../../utils/recordChanged'; import { isArchived, type ArchiveReason } from '../../../../../utils/archivedResource'; import { StaleRecordNotice } from '../../../../../components/StaleRecordNotice/StaleRecordNotice'; const emptyEquipment = { status: '', deviceName: [], manufacturer: '', modelNumber: '', serialNumber: '', lotNumber: '', expirationDate: '', udiCarrier: [], identifier: [], }; const archiveReasons: { value: ArchiveReason; label: string }[] = [ { value: 'duplicate', label: 'Duplicate' }, { value: 'no-longer-applies', label: 'No longer applies' }, { value: 'other', label: 'Other' }, ]; const EditDevicePage: React.FC = () => { const { deviceId } = useParams<{ deviceId: string }>(); const navigate = useNavigate(); const proxyCannotAdd = useProxyCannotAddToRecord(); const { patientState } = usePatientServiceContext(); const patientId = patientState?.id; const { fetchDevice, transformToFhirDevice, transformFromFhirDevice, createDevice, updateDevice, archiveDevice, devicesList, cachedForPatientId } = useDeviceServiceContext(); const methods = useForm({ defaultValues: emptyEquipment }); const { handleSubmit, reset, getValues } = methods; const formOf = useCallback((record: Device) => transformFromFhirDevice(record) as Record, [transformFromFhirDevice]); const read = useCallback(() => getValues() as Record, [getValues]); const write = useCallback((values: Record) => reset(values as never), [reset]); const baseline = useEditBaseline>({ formOf, read, write }); const { open: openBaseline, close: closeBaseline, arrive } = baseline; const [loading, setLoading] = useState(false); const [submitting, setSubmitting] = useState(false); const [reloading, setReloading] = useState(false); const [success, setSuccess] = useState(); const [error, setError] = useState(); const [loadedKey, setLoadedKey] = useState(); const [createHeld, setCreateHeld] = useState(false); const [archiveTarget, setArchiveTarget] = useState(); const [archiveReason, setArchiveReason] = useState(); const [archiving, setArchiving] = useState(false); const [archiveError, setArchiveError] = useState(); const filledKey = useRef(); const saving = useRef(false); const archiveSaving = useRef(false); const isNew = deviceId === 'new'; const pageKey = patientId && deviceId ? `${patientId}/${deviceId}` : undefined; const scope = useSaveScope(pageKey, () => { closeBaseline(); reset(emptyEquipment); filledKey.current = undefined; setLoadedKey(undefined); saving.current = false; archiveSaving.current = false; setSubmitting(false); setLoading(false); setReloading(false); setCreateHeld(false); setSuccess(undefined); setError(undefined); setArchiveTarget(undefined); setArchiveReason(undefined); setArchiveError(undefined); setArchiving(false); }); const current = loadedKey === pageKey ? baseline.opening : undefined; const bound = Boolean(current && current.id === deviceId && current.patient?.reference === `Patient/${patientId}`); const archived = Boolean(current && isArchived(current)); const canEdit = Boolean(patientId && deviceId && !proxyCannotAdd && !loading && !submitting && !archiving && !baseline.stale && !createHeld && (isNew || (bound && !archived))); const archiveBound = Boolean(archiveTarget && pageKey && archiveTarget.id === deviceId && archiveTarget.patient?.reference === `Patient/${patientId}` && !proxyCannotAdd); const archiveScope = useSaveScope(archiveBound ? `${pageKey}/archive/${archiveTarget?.meta?.versionId}` : undefined); useEffect(() => { if (!pageKey || !patientId || !deviceId) return; if (isNew) { if (filledKey.current === pageKey) return; filledKey.current = pageKey; closeBaseline(); reset(emptyEquipment); setLoadedKey(pageKey); return; } const cached = cachedForPatientId === patientId ? devicesList.find(record => record.resourceType === 'Device' && record.id === deviceId && record.patient?.reference === `Patient/${patientId}`) : undefined; if (filledKey.current === pageKey) { if (cached) arrive(cached); return; } const operation = scope.start(); let live = true; const install = (record: Device) => { operation.admit(); if (!live || record.resourceType !== 'Device' || record.id !== deviceId || record.patient?.reference !== `Patient/${patientId}`) throw new Error('The equipment record does not belong to this page.'); filledKey.current = pageKey; openBaseline(record); setLoadedKey(pageKey); setLoading(false); }; if (cached) { install(cached); operation.retire(); } else { setLoading(true); void operation.step(() => fetchDevice(deviceId, operation.transport)).then(install).catch(readError => { if (live && operation.belongs() && !isSaveRetired(readError)) setError('Could not load this equipment record. Try again.'); }).finally(() => { if (live && operation.belongs()) setLoading(false); operation.retire(); }); } return () => { live = false; operation.retire(); }; }, [pageKey, patientId, deviceId, isNew, devicesList, cachedForPatientId, fetchDevice, arrive, openBaseline, closeBaseline, reset, scope]); useFhirDeviceTargetedBroadcast({ device: bound ? current : undefined, deviceId, onUpdate: incoming => { if (incoming.id === deviceId && incoming.patient?.reference === `Patient/${patientId}`) arrive(incoming); }, onDelete: () => navigate('..', { relative: 'path' }), }); const onSubmit = async (data: unknown) => { if (!canEdit || !patientState || saving.current) return; if (!isNew && !baseline.basedOn) { baseline.refuse('outOfDate'); return; } const operation = scope.start(); saving.current = true; setSubmitting(true); setSuccess(undefined); setError(undefined); try { if (isNew) { await operation.step(() => createDevice(transformToFhirDevice(data, patientState), operation.transport), true); operation.admit(); setCreateHeld(true); navigate('..', { relative: 'path' }); } else { const outcome = await saveOverBaseline({ baseline, operation, write: (basedOn, transport) => updateDevice({ ...transformToFhirDevice(data, patientState), id: current?.id, meta: { versionId: basedOn }, }, transport).catch(failure => { throw asRecordChanged(failure); }) }); if ('retired' in outcome) return; if ('refused' in outcome) { if (!outcome.stale) setError(outcome.refused); return; } operation.admit(); setSuccess('Equipment updated.'); } } catch (failure) { if (isSaveRetired(failure) || !operation.belongs()) return; if (isNew && outcomeUnknown(failure)) { setCreateHeld(true); setError('Whether it was created could not be confirmed. Check the list before trying again.'); } else setError(describeWriteFailure(failure, 'Failed to save equipment')); } finally { if (operation.belongs()) { saving.current = false; setSubmitting(false); } operation.retire(); } }; const reload = async () => { if (!bound || !current?.id) return; const id = current.id; const operation = scope.start(); setReloading(true); setError(undefined); try { await reloadBaseline({ baseline, operation, readNow: async transport => { const record = await fetchDevice(id, transport); if (record.id !== id || record.patient?.reference !== `Patient/${patientId}`) throw new Error('The equipment record does not belong to this page.'); return record; } }); } catch (failure) { if (!isSaveRetired(failure) && operation.belongs()) setError('The equipment record could not be reloaded.'); } finally { if (operation.belongs()) setReloading(false); operation.retire(); } }; const closeArchive = useCallback(() => { archiveScope.renew(); setArchiveTarget(undefined); setArchiveReason(undefined); setArchiveError(undefined); setArchiving(false); archiveSaving.current = false; }, [archiveScope]); useEffect(() => { if (proxyCannotAdd) closeArchive(); }, [proxyCannotAdd, closeArchive]); const openArchive = () => { if (!canEdit || !bound || !current?.meta?.versionId) return; // The command retains this displayed edition even if the surrounding list refreshes. archiveScope.renew(); setArchiveTarget({ resourceType: 'Device', id: current.id, patient: { ...current.patient }, meta: { versionId: current.meta.versionId } }); setArchiveReason(undefined); setArchiveError(undefined); }; const confirmArchive = async () => { if (!archiveBound || !archiveTarget || !archiveReason || !canEdit || archiveSaving.current) return; const operation = archiveScope.start(); const token = baseline.beginSave(); archiveSaving.current = true; setArchiving(true); setArchiveError(undefined); try { const saved = await operation.step(() => archiveDevice(archiveTarget, archiveReason, undefined, operation.transport), true); operation.admit(); baseline.endSave(token, saved); operation.admit(); navigate('..', { relative: 'path' }); } catch (failure) { baseline.endSave(token); if (isSaveRetired(failure) || !operation.belongs()) return; const reason = staleReasonOf(asRecordChanged(failure)); if (reason) baseline.refuse(reason); setArchiveError(describeWriteFailure(failure, 'The equipment archive could not be confirmed')); } finally { if (operation.belongs()) { archiveSaving.current = false; setArchiving(false); } operation.retire(); } }; if (loading || (!isNew && pageKey && !current && !error)) return ; return resourceType="Device" resourceId={deviceId ?? ''}>{showHistory => (
{!isNew && } {!proxyCannotAdd && } {!isNew && !proxyCannotAdd && } {proxyCannotAdd &&

{describeProxyCannotAddToRecord(undefined)}

} {archived &&

This equipment record is archived. Its history is available.

} {success && setSuccess(undefined)}>{success}} {error && setError(undefined)}>{error}} void reload()} reloading={reloading} /> { if (!open) closeArchive(); }}> Archive equipment? This files the saved record away and retains its history. Unsaved edits are not saved. Choose a reason.
{archiveReasons.map(reason => )}
{archiveError &&

{archiveError}

} {baseline.stale &&

Close this dialog and reload the changed record before archiving.

} Keep it active
)}; }; export default EditDevicePage;