import { useProxyCannotAddToRecord } from '../../../../hooks/useProxyCannotAddToRecord'; import { useState, useEffect } from "react"; import { DeviceUseStatement } from "fhir/r4"; import { useForm, FormProvider, Controller, useFieldArray, } from "react-hook-form"; import { FhirDeviceUseStatementDisplay, FhirValueSetSearch } from "../../../../components"; import { useFhirDeviceUseStatementBroadcast, useFhirDeviceUseStatements, useFhirDeviceUseStatementTargetedBroadcast, useFhirDevices, useFhirConditions, useFhirObservations, useFhirProcedures, } from "../../../../hooks"; import { useAuthServiceContext, useDeviceUseStatementServiceContext, usePatientServiceContext, useValueSetServiceContext, IValueSet, } from "../../../../services"; import { Calendar, ChevronRight, Cpu, Edit, Plus, Radio, Trash, Trash2, AlertCircle, } from "lucide-react"; import { Button } from "../../../../components/ui/button.tsx"; import { Skeleton } from "../../../../components/ui/skeleton.tsx"; import { formatDateForDisplay } from "../../../../lib/utils.ts"; import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from "../../../../components/ui/sheet.tsx"; import { useArchive } from "../../../../hooks/useArchive"; import ArchiveConfirmModal from "../../../../components/ArchiveConfirmModal/ArchiveConfirmModal"; import { Label } from "../../../../components/ui/label.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "../../../../components/ui/select.tsx"; import { Input } from "../../../../components/ui/input.tsx"; import { Textarea } from "../../../../components/ui/textarea.tsx"; import { RadioGroup, RadioGroupItem, } from "../../../../components/ui/radio-group.tsx"; import { Tabs, TabsContent, TabsList, TabsTrigger, } from "../../../../components/ui/tabs.tsx"; import { Alert, AlertDescription } from "../../../../components/ui/alert.tsx"; import { describeWriteFailure } from '../../../../utils/fetchFailureMessage'; import { SourcePractice } from '../../../../components/ui/source-practice'; import { RecordPracticeNote, recordPracticeId, useRecordPracticeView, useRetireOnPracticeChange } from '../../../../contexts/RecordPracticeContext'; const DeviceUseStatements = () => { const { userRole, USER_ROLES } = useAuthServiceContext(); // The nav row that opens this page says one thing and the page said another. A clinician reading // the same page through the chart is a different audience and keeps their own wording. const isPatientOrProxy = [USER_ROLES.PATIENT, USER_ROLES.RELATED_PERSON].includes(userRole as string); const { patientState } = usePatientServiceContext(); const { loading, error, deviceUseStatementsList } = useFhirDeviceUseStatements(); const { createDeviceUseStatement, updateDeviceUseStatement, archiveDeviceUseStatement, transformFromFhirDeviceUseStatement, } = useDeviceUseStatementServiceContext(); const { fetchValueSet, valueSetUrls } = useValueSetServiceContext(); const [selectedDeviceUseStatement, setSelectedDeviceUseStatement] = useState(null); const [showDisplayModal, setShowDisplayModal] = useState(false); // The record's practice context: its own device statements under a named practice; the empty state speaks for the whole record. const practiceView = useRecordPracticeView(deviceUseStatementsList, recordPracticeId); useRetireOnPracticeChange(() => { setShowDisplayModal(false); setSelectedDeviceUseStatement(null); }); const [openSheet, setOpenSheet] = useState(false); const [isEditMode, setIsEditMode] = useState(false); const [editingId, setEditingId] = useState(null); const [isSaving, setIsSaving] = useState(false); const [saveError, setSaveError] = useState(null); const [deviceToDelete, setDeviceToDelete] = useState(null); const archive = useArchive((reason, note) => deviceToDelete ? archiveDeviceUseStatement(deviceToDelete, reason, note) : Promise.resolve() ); const [loadingValueSets, setLoadingValueSets] = useState(false); const [deviceStatementStatus, setDeviceStatementStatus] = useState([]); const { devicesList } = useFhirDevices(); const { conditionsList } = useFhirConditions(); const { observationsList } = useFhirObservations(); const { proceduresList } = useFhirProcedures(); const methods = useForm({ defaultValues: { status: "", device: null, bodySite: null, timingType: "dateTime", timingDateTime: "", timingPeriod: { start: "", end: "" }, timingTiming: null, reasonCode: [], reasonReference: [], note: [], derivedFrom: [], recordedOn: new Date().toISOString().slice(0, 16), }, }); const { control, handleSubmit, watch, reset, setValue, formState: { errors }, } = methods; const timingType = watch("timingType"); const reasonReferenceFieldArray = useFieldArray({ control, name: "reasonReference" }); const derivedFromFieldArray = useFieldArray({ control, name: "derivedFrom" }); const noteFieldArray = useFieldArray({ control, name: "note" }); useEffect(() => { const loadValueSets = async () => { setLoadingValueSets(true); try { const [deviceStatementStatusValueSet] = await Promise.all([ fetchValueSet(valueSetUrls.deviceStatementStatus), ]); setDeviceStatementStatus(deviceStatementStatusValueSet); } catch (err) { console.error("Error loading value sets:", err); } finally { setLoadingValueSets(false); } }; loadValueSets(); }, [fetchValueSet, valueSetUrls]); useFhirDeviceUseStatementBroadcast(); useFhirDeviceUseStatementTargetedBroadcast({ deviceUseStatement: selectedDeviceUseStatement || undefined, isOpen: showDisplayModal, onUpdate: (updatedDeviceUseStatement) => { setSelectedDeviceUseStatement(updatedDeviceUseStatement); }, onDelete: () => { setShowDisplayModal(false); setSelectedDeviceUseStatement(null); }, }); const viewDeviceUseStatement = (deviceUseStatement: DeviceUseStatement) => { setSelectedDeviceUseStatement(deviceUseStatement); setShowDisplayModal(true); }; const editDeviceUseStatement = ( event: React.MouseEvent, deviceUseStatement: DeviceUseStatement, ) => { event.stopPropagation(); setIsEditMode(true); setEditingId(deviceUseStatement.id!); loadDeviceUseStatementForEdit(deviceUseStatement); }; const addDeviceUseStatement = () => { if (!patientState?.id) { setSaveError("Cannot create device use statement: No patient selected"); return; } setIsEditMode(false); setEditingId(null); reset({ status: "", device: null, bodySite: null, timingType: "dateTime", timingDateTime: new Date().toISOString().slice(0, 16), timingPeriod: { start: "", end: "" }, timingTiming: null, reasonCode: [], reasonReference: [], note: [], derivedFrom: [], recordedOn: new Date().toISOString().slice(0, 16), }); setSaveError(null); setOpenSheet(true); }; const loadDeviceUseStatementForEdit = (deviceUseStatement: DeviceUseStatement) => { const formValues = transformFromFhirDeviceUseStatement(deviceUseStatement); reset(formValues); setOpenSheet(true); }; const confirmArchive = (deviceUseStatement: DeviceUseStatement) => { setDeviceToDelete(deviceUseStatement); archive.open(); }; const handleAddReasonReference = () => { reasonReferenceFieldArray.append({ reference: { reference: "", display: "" }, referenceType: "Condition", }); }; const handleRemoveReasonReference = (index: number) => { reasonReferenceFieldArray.remove(index); }; const handleAddDerivedFrom = () => { derivedFromFieldArray.append({ reference: { reference: "", display: "" }, referenceType: "Procedure", }); }; const handleRemoveDerivedFrom = (index: number) => { derivedFromFieldArray.remove(index); }; const handleAddNote = () => { noteFieldArray.append({ text: "", time: new Date().toISOString() }); }; const handleRemoveNote = (index: number) => { noteFieldArray.remove(index); }; const formatDateForFHIR = (dateString: string): string => { if (!dateString) return new Date().toISOString(); try { if (dateString.endsWith("Z") || /[+-]\d{2}:\d{2}$/.test(dateString)) { return dateString; } let normalized = dateString; if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(normalized)) { normalized = `${normalized}:00`; } const date = new Date(normalized); if (isNaN(date.getTime())) { console.warn("Invalid date:", dateString); return new Date().toISOString(); } return date.toISOString(); } catch (error) { console.error("Error formatting date:", error); return new Date().toISOString(); } }; // Update the onSubmit function in DeviceUseStatements.tsx (around line 250-300) const onSubmit = async (data: any) => { if (!patientState?.id) { setSaveError("Patient ID is not available. Please ensure a patient is selected."); return; } setIsSaving(true); setSaveError(null); try { // Format dates for FHIR if (data.recordedOn) { data.recordedOn = formatDateForFHIR(data.recordedOn); } if (data.timingDateTime) { data.timingDateTime = formatDateForFHIR(data.timingDateTime); } if (data.timingPeriod?.start) { data.timingPeriod.start = formatDateForFHIR(data.timingPeriod.start); } if (data.timingPeriod?.end) { data.timingPeriod.end = formatDateForFHIR(data.timingPeriod.end); } // Transform the data to proper FHIR format const deviceUseStatementData: any = { resourceType: "DeviceUseStatement", ...(isEditMode && editingId ? { id: editingId } : {}), status: data.status, subject: { reference: `Patient/${patientState.id}` }, device: data.device, }; // Handle bodySite - FhirValueSetSearch writes { code, display, system } (IValueSet) if (data.bodySite?.code) { deviceUseStatementData.bodySite = { coding: [{ code: data.bodySite.code, display: data.bodySite.display, ...(data.bodySite.system && { system: data.bodySite.system }) }], text: data.bodySite.display }; } // Handle recordedOn if (data.recordedOn) { deviceUseStatementData.recordedOn = data.recordedOn; } // Handle timing if (data.timingType === "dateTime" && data.timingDateTime) { deviceUseStatementData.timingDateTime = data.timingDateTime; } else if (data.timingType === "period" && data.timingPeriod) { if (data.timingPeriod.start || data.timingPeriod.end) { deviceUseStatementData.timingPeriod = {}; if (data.timingPeriod.start) { deviceUseStatementData.timingPeriod.start = data.timingPeriod.start; } if (data.timingPeriod.end) { deviceUseStatementData.timingPeriod.end = data.timingPeriod.end; } } } else if (data.timingType === "timing" && data.timingTiming) { deviceUseStatementData.timingTiming = data.timingTiming; } // Handle reasonCode - FhirValueSetSearch writes [{ code, display, system }] (IValueSet[]) if (data.reasonCode && data.reasonCode.length > 0) { const mappedReasonCode = data.reasonCode .filter((rc: any) => rc?.code) .map((rc: any) => ({ coding: [{ code: rc.code, display: rc.display, ...(rc.system && { system: rc.system }) }], text: rc.display })); if (mappedReasonCode.length > 0) { deviceUseStatementData.reasonCode = mappedReasonCode; } } // Handle reasonReference if (data.reasonReference && data.reasonReference.length > 0) { const validReasonReferences = data.reasonReference .filter((ref: any) => ref?.reference?.reference) .map((ref: any) => ({ reference: ref.reference.reference, display: ref.reference.display || "", type: ref.referenceType, })); if (validReasonReferences.length > 0) { deviceUseStatementData.reasonReference = validReasonReferences; } } // Handle derivedFrom if (data.derivedFrom && data.derivedFrom.length > 0) { const validDerivedFrom = data.derivedFrom .filter((ref: any) => ref?.reference?.reference) .map((ref: any) => ({ reference: ref.reference.reference, display: ref.reference.display || "", type: ref.referenceType, })); if (validDerivedFrom.length > 0) { deviceUseStatementData.derivedFrom = validDerivedFrom; } } // Handle notes if (data.note && data.note.length > 0) { const validNotes = data.note.filter((note: any) => note.text && note.text.trim() !== ""); if (validNotes.length > 0) { deviceUseStatementData.note = validNotes.map((note: any) => ({ text: note.text, time: note.time || new Date().toISOString(), })); } } // Clean up undefined/null values const cleanObservation = JSON.parse(JSON.stringify(deviceUseStatementData)); Object.keys(cleanObservation).forEach((key) => { if (cleanObservation[key] === undefined || cleanObservation[key] === null || (Array.isArray(cleanObservation[key]) && cleanObservation[key].length === 0)) { delete cleanObservation[key]; } }); if (isEditMode && editingId) { await updateDeviceUseStatement(cleanObservation); } else { await createDeviceUseStatement(cleanObservation); } reset(); setOpenSheet(false); setIsEditMode(false); setEditingId(null); } catch (err: any) { console.error("Error saving device use statement:", err); setSaveError(describeWriteFailure(err, 'Failed to save device use statement')); } finally { setIsSaving(false); } }; const proxyCannotAdd = useProxyCannotAddToRecord(); const isAuthorizedToEdit = () => { return ( !proxyCannotAdd && userRole && [ USER_ROLES.PATIENT, USER_ROLES.RELATED_PERSON, USER_ROLES.PRACTITIONER, ].includes(userRole as string) ); }; return (

{isPatientOrProxy ? "Devices you use" : "Devices"}

View and manage medical devices, implants, and associated traceability information.

{isAuthorizedToEdit() && ( )}
{/* Add/Edit Sheet */} {isEditMode ? "Edit Device Use Statement" : "New Device Use Statement"}
{saveError && ( {saveError} )}
( )} /> {errors.device && (

{errors.device.message as string}

)}
( )} /> {errors.status && (

{errors.status.message as string}

)}
( )} />

When was the device used?

Timing information type

(
)} />
{timingType === "dateTime" && (
( )} />
)} {timingType === "period" && (
( )} />
( )} />
)}
Reason Supporting Information Notes
{reasonReferenceFieldArray.fields.length === 0 && ( No reason references added yet. Click "Add Reference" to add conditions or observations. )} {reasonReferenceFieldArray.fields.map((field, index) => (

{index + 1}

Reference Type

( )} />
{ const referenceType = watch(`reasonReference.${index}.referenceType`); const list = referenceType === "Condition" ? conditionsList : observationsList; return ( ); }} />
))}

Supporting Information

{derivedFromFieldArray.fields.length === 0 && ( No supporting information added yet. Click "Add Reference" to add procedures. )} {derivedFromFieldArray.fields.map((field, index) => (

{index + 1}

Supporting Information

( )} />
( )} />
))}
{noteFieldArray.fields.length === 0 && ( No notes added yet. Click "Add Notes" to add comments. )} {noteFieldArray.fields.map((field, index) => (

{index + 1}

(