import { useState } from 'react'; import { Item, ItemGroup } from "../../../../components/ui/item-group"; import { useNavigate } from 'react-router-dom'; import { Icon } from "../../../../components/ui/icon"; import { Flex, FlexItem, FlexBlock } from '../../../../components/ui/flex'; import { Text } from '../../../../components/ui/text'; import { Spacer } from '../../../../components/ui/spacer'; // `calendar` and `check` still feed WordPress icon components on each row - a separate sweep. import { Pencil, Plus, Calendar as calendar, Check as check } from 'lucide-react'; import { ImmunizationRecommendation, ImmunizationRecommendationRecommendation } from 'fhir/r4'; import { AppSpinner, FhirImmunizationRecommendationDisplay, ScrollableTab } from '../../../../components'; import { ResourceListStatus } from '../../../../components/ui/resource-list-status'; import { formatFhirCodeableConcept } from '../../../../utils/fhirCodeableConcept.tsx'; import { formatDate } from '../../../../utils/dateUtils.ts'; import { useFhirImmunizationRecommendationBroadcast, useFhirImmunizationRecommendations, useFhirImmunizationRecommendationTargetedBroadcast } from '../../../../hooks'; import { useAuthServiceContext, useImmunizationRecommendationServiceContext } from '../../../../services'; import { mayEditRecord } from '../../../../utils/recordEditability'; import { useSeatStanding } from '../../../../utils/seatStanding'; import '../medicationRecords.css'; import { SourcePractice } from '../../../../components/ui/source-practice'; import { RecordPracticeNote, recordPracticeId, useRecordPracticeView, useRetireOnPracticeChange } from '../../../../contexts/RecordPracticeContext'; const ImmunizationRecommendations = () => { const navigate = useNavigate(); const { userRole, USER_ROLES, practitionerRoles } = useAuthServiceContext(); const seatStanding = useSeatStanding(); const { getImmunizationRecommendationDisplay } = useImmunizationRecommendationServiceContext(); const { loading, error, clearError, immunizationRecommendationsList, attempted } = useFhirImmunizationRecommendations(); const [selectedImmunizationRecommendation, setSelectedImmunizationRecommendation] = useState(null); const [showDisplayModal, setShowDisplayModal] = useState(false); // The record's practice context: its own recommendations under a named practice; the empty state speaks for the whole record. const practiceView = useRecordPracticeView(immunizationRecommendationsList, recordPracticeId); useRetireOnPracticeChange(() => { setShowDisplayModal(false); setSelectedImmunizationRecommendation(null); }); useFhirImmunizationRecommendationBroadcast(); useFhirImmunizationRecommendationTargetedBroadcast({ immunizationRecommendation: selectedImmunizationRecommendation || undefined, isOpen: showDisplayModal, onUpdate: (updatedImmunizationRecommendation) => { setSelectedImmunizationRecommendation(updatedImmunizationRecommendation); }, onDelete: () => { setShowDisplayModal(false); setSelectedImmunizationRecommendation(null); } }); const viewImmunizationRecommendation = (immunizationRecommendation: ImmunizationRecommendation) => { setSelectedImmunizationRecommendation(immunizationRecommendation); setShowDisplayModal(true); }; const editImmunizationRecommendation = (event: React.MouseEvent, immunizationRecommendation: ImmunizationRecommendation) => { event.stopPropagation(); navigate(immunizationRecommendation.id as string, { relative: 'path' }); }; const addImmunizationRecommendation = () => { navigate('new', { relative: 'path' }); }; const roleAllowsEdit = () => { return userRole && [USER_ROLES.PRACTITIONER].includes(userRole as string); }; /* * AND THE ROLE IS NOT THE RULE THE SERVER APPLIES. * * Writing a ImmunizationRecommendation needs a paid clinician seat, resolved per request by `callerMayWrite`. The role * check above passes for an unseated practitioner - a medical assistant, a nurse rooming patients, * somebody whose seat was turned off this morning - so the page offered Add and Edit, and the save * came back refused. A refusal that arrives after the typing has already spent the work. */ const seat = mayEditRecord({ seatStanding, resourceType: 'ImmunizationRecommendation', viewerIsStaff: Boolean(userRole) && ![USER_ROLES.PATIENT, USER_ROLES.RELATED_PERSON].includes(userRole as string), practitionerRoles, }); const isAuthorizedToEdit = () => roleAllowsEdit() && seat.mayEdit; const getForecastStatusDisplay = (recommendation: ImmunizationRecommendationRecommendation): { text: string, color?: string } => { if (!recommendation || !recommendation.forecastStatus) return { text: '' }; const status = formatFhirCodeableConcept(recommendation.forecastStatus); if (status.toLowerCase().includes('due')) { return { text: status, color: '#e39700' }; } else if (status.toLowerCase().includes('overdue')) { return { text: status, color: '#cc1818' }; } else if (status.toLowerCase().includes('complete')) { return { text: status, color: '#00a32a' }; } else { return { text: status }; } }; const getNextDoseDate = (recommendation: ImmunizationRecommendationRecommendation): string => { if (!recommendation || !recommendation.dateCriterion || recommendation.dateCriterion.length === 0) { return ''; } const dueDateCriterion = recommendation.dateCriterion.find(criterion => criterion.code && formatFhirCodeableConcept(criterion.code).toLowerCase().includes('due')); if (dueDateCriterion && dueDateCriterion.value) { return formatDate(dueDateCriterion.value); } return recommendation.dateCriterion[0].value ? formatDate(recommendation.dateCriterion[0].value) : ''; }; return (
{/* SAID, NOT WITHHELD. A control that simply disappears reads as a broken screen or as a permission the reader has lost, and both send them to an administrator to check permissions that are already correct. A seat is nameable, and a practice can enable one in a minute. */} {roleAllowsEdit() && !seat.mayEdit && (

{seat.reason}

)} {isAuthorizedToEdit() && ( )}
<> {loading ? ( ) : ( {practiceView.shown.length !== 0 && ( {practiceView.shown.map((immunizationRecommendation: ImmunizationRecommendation) => { const displayTitle = getImmunizationRecommendationDisplay(immunizationRecommendation); const firstRecommendation = immunizationRecommendation.recommendation && immunizationRecommendation.recommendation.length > 0 ? immunizationRecommendation.recommendation[0] : null; const { text: forecastStatusText, color: forecastStatusColor } = firstRecommendation ? getForecastStatusDisplay(firstRecommendation) : { text: '' }; const nextDoseDate = firstRecommendation ? getNextDoseDate(firstRecommendation) : ''; const recommendationDate = immunizationRecommendation.date ? formatDate(immunizationRecommendation.date) : ''; const recommendationCount = immunizationRecommendation.recommendation ? immunizationRecommendation.recommendation.length : 0; return ( viewImmunizationRecommendation(immunizationRecommendation)}> {/* Wraps: on a narrow row Edit moves below the whole title rather than being squeezed past the card's edge. */} {displayTitle} {recommendationCount > 1 && ( {recommendationCount} recommendations )} {(nextDoseDate || recommendationDate) && ( {nextDoseDate ? `Next dose: ${nextDoseDate}` : recommendationDate ? `Recommended: ${recommendationDate}` : ''} )} {isAuthorizedToEdit() && ( )} ); })} )} )} {selectedImmunizationRecommendation && ( { setShowDisplayModal(false); setSelectedImmunizationRecommendation(null); }} /> )}
); }; export default ImmunizationRecommendations;