import { useState, useEffect, useCallback } from 'react'; import { Item, ItemGroup } from "../../../../components/ui/item-group"; import { useNavigate } from 'react-router-dom'; import { Icon } from "../../../../components/ui/icon"; import { DismissibleAlert } from '../../../../components/ui/dismissible-alert'; import { Flex, FlexItem, FlexBlock } from '../../../../components/ui/flex'; import { Text } from '../../../../components/ui/text'; import { Spacer } from '../../../../components/ui/spacer'; // `image` still feeds WordPress's . `calendar` was imported and never used - dropped. import { Plus, ImageIcon as image } from 'lucide-react'; import { Media } from 'fhir/r4'; import { useMediaServiceContext, usePatientServiceContext } from '../../../../services'; import { AppSpinner } from '../../../../components'; import { formatFhirCodeableConcept } from '../../../../utils/fhirCodeableConcept.tsx'; import { formatDate } from '../../../../utils/dateUtils.ts'; import { patientCacheGuard } from '../../../../utils/cacheOwner'; import { SourcePractice } from '../../../../components/ui/source-practice'; import { RecordPracticeNote, recordPracticeId, useRecordPracticeView, useRetireOnPracticeChange } from '../../../../contexts/RecordPracticeContext'; const MediaList = () => { const navigate = useNavigate(); const { patientState } = usePatientServiceContext(); const { fetchMediaListWithPolling, shouldReload, setShouldReload, mediaBundle: cachedMediaBundle, mediaList: cachedMediaList, cachedForPatientId, setUnsaved, setMediaState } = useMediaServiceContext(); const [reading, setLoading] = useState(false); const [failed, setFailed] = useState(false); // The cache outlives a change of patient: show only the patient asked about. const guard = patientCacheGuard(cachedForPatientId, patientState?.id); const mediaBundle = guard.bundle(cachedMediaBundle, null); const mediaList = guard.list(cachedMediaList); const loading = guard.loading(reading, failed); // The record's practice context: its own media under a named practice; the empty state speaks for the whole record. const practiceView = useRecordPracticeView(mediaList, recordPracticeId); // A media item opens on its own page, so nothing on this list is left open to close. useRetireOnPracticeChange(() => undefined); const loadData = useCallback(async () => { if (!patientState) { return; } setLoading(true); setFailed(false); try { await fetchMediaListWithPolling(patientState); } catch (error) { console.error('Failed to load media items:', error); setFailed(true); } finally { setLoading(false); if (shouldReload) { setShouldReload(false); } } }, [patientState, shouldReload, fetchMediaListWithPolling, setShouldReload]); useEffect(() => { if (!mediaBundle || shouldReload) { loadData(); } setUnsaved(false); }, [loadData, mediaBundle, shouldReload, setUnsaved]); const viewMedia = (media: Media) => { setMediaState(media); navigate(media.id as string, { relative: 'route' }); }; const addMedia = () => { setMediaState(null); navigate('new', { relative: 'route' }); }; const getStatusDisplay = (media: Media): { text: string, color?: string } => { if (!media) return { text: 'Unknown' }; switch (media.status) { case 'preparation': return { text: 'Preparation', color: '#e39700' }; case 'in-progress': return { text: 'In Progress', color: '#e39700' }; case 'not-done': return { text: 'Not Done', color: '#cc1818' }; case 'on-hold': return { text: 'On Hold', color: '#e39700' }; case 'stopped': return { text: 'Stopped', color: '#cc1818' }; case 'completed': return { text: 'Completed', color: '#00a32a' }; case 'entered-in-error': return { text: 'Error', color: '#cc1818' }; case 'unknown': return { text: 'Unknown', color: '#757575' }; default: return { text: media.status.charAt(0).toUpperCase() + media.status.slice(1).replace(/-/g, ' ') }; } }; const getMediaTypeDisplay = (media: Media): string => { if (media.type) { return formatFhirCodeableConcept(media.type); } if (media.modality) { return formatFhirCodeableConcept(media.modality); } return 'Media'; }; const getMediaDimensions = (media: Media): string => { if (media.width && media.height) { return `${media.width} × ${media.height}`; } return ''; }; const getMediaContentType = (media: Media): string => { if (media.content?.contentType) { const mainType = media.content.contentType.split('/')[0]; return mainType.charAt(0).toUpperCase() + mainType.slice(1); } return ''; }; return (
{/*Media*/} {/**/} {!loading && (!mediaBundle?.entry || mediaList.length === 0) && ( No media items found. )} <> {loading ? ( ) : ( <> {practiceView.shown.length !== 0 && ( {practiceView.shown.map((media: Media) => { const mediaType = getMediaTypeDisplay(media); const { text: statusText, color: statusColor } = getStatusDisplay(media); const dimensions = getMediaDimensions(media); const contentType = getMediaContentType(media); const createdDate = media.createdDateTime ? formatDate(media.createdDateTime) : ''; const issuedDate = media.issued ? formatDate(media.issued) : ''; const displayDate = createdDate || issuedDate; return ( viewMedia(media)}> {mediaType} {media.bodySite && ( {` ${formatFhirCodeableConcept(media.bodySite)}`} )} {displayDate && ( {` [${displayDate}]`} )} {contentType && ( {contentType} )} {dimensions && ( {dimensions} )} {statusText} ); })} )} )}
); }; export default MediaList;