import { availableRecordEmpty, useChartRecordStanding } from '../../../../hooks/useRecordAccessStanding'; import { useProxyCannotAddToRecord } from '../../../../hooks/useProxyCannotAddToRecord'; 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'; // `info` is KEPT: it feeds the WordPress icon component on each row as well as the Help // button. Dropping it because "its Button was converted" would break the row icon. import { Pencil, Plus, Info as info } from 'lucide-react'; import { Condition } from 'fhir/r4'; import { AppSpinner, FhirConditionDisplay, ScrollableTab } from '../../../../components'; import { ResourceListStatus } from '../../../../components/ui/resource-list-status'; import { useFhirConditionBroadcast, useFhirConditions, useFhirConditionTargetedBroadcast, useRequestRecordCorrection } from '../../../../hooks'; import { isConditionRecordedByCareTeam, resolveConditionAuthorReference } from '../../../../utils/messaging/requestCorrection'; import { formatFhirCodeableConcept } from '../../../../utils/fhirCodeableConcept.tsx'; import { useAuthServiceContext, useConditionServiceContext } from '../../../../services'; import { mayEditRecord } from '../../../../utils/recordEditability'; import { useSeatStanding } from '../../../../utils/seatStanding'; import { SourcePractice } from '../../../../components/ui/source-practice'; import { RecordPracticeNote, recordPracticeId, useRecordPracticeView, useRetireOnPracticeChange } from '../../../../contexts/RecordPracticeContext'; const Conditions = () => { const { standing: recordStanding } = useChartRecordStanding(); const navigate = useNavigate(); const { userRole, USER_ROLES, practitionerRoles } = useAuthServiceContext(); const seatStanding = useSeatStanding(); const { getConditionDisplay } = useConditionServiceContext(); const { loading, error, clearError, conditionsList, attempted, freshness: recordFreshness } = useFhirConditions(); const [selectedCondition, setSelectedCondition] = useState(null); const [showDisplayModal, setShowDisplayModal] = useState(false); // The record's practice context: its own conditions under a named practice; the empty state speaks for the whole record. const practiceView = useRecordPracticeView(conditionsList, recordPracticeId); useRetireOnPracticeChange(() => { setShowDisplayModal(false); setSelectedCondition(null); }); useFhirConditionBroadcast(); useFhirConditionTargetedBroadcast({ condition: selectedCondition || undefined, isOpen: showDisplayModal, onUpdate: (updatedCondition) => { setSelectedCondition(updatedCondition); }, onDelete: () => { setShowDisplayModal(false); setSelectedCondition(null); } }); const viewCondition = (condition: Condition) => { setSelectedCondition(condition); setShowDisplayModal(true); }; const editCondition = (event: React.MouseEvent, condition: Condition) => { event.stopPropagation(); navigate(condition.id as string, { relative: 'route' }); }; const addCondition = () => { navigate('new', { relative: 'route' }); }; const roleAllowsEdit = () => { return userRole && [USER_ROLES.PATIENT, USER_ROLES.RELATED_PERSON, USER_ROLES.PRACTITIONER].includes(userRole as string); }; /* * AND THE ROLE IS NOT THE RULE THE SERVER APPLIES. * * Writing a Condition 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: 'Condition', viewerIsStaff: Boolean(userRole) && ![USER_ROLES.PATIENT, USER_ROLES.RELATED_PERSON].includes(userRole as string), practitionerRoles, }); const proxyCannotAdd = useProxyCannotAddToRecord(); const isAuthorizedToEdit = () => roleAllowsEdit() && seat.mayEdit && !proxyCannotAdd; /* * A CONDITION THE CARE TEAM RECORDED IS NOT THE PATIENT'S TO EDIT, and the server refuses the edit: * "You can only edit health concerns you added. This record is managed by your care team." The row knows * who recorded it, so the patient's side is not offered the edit. A row with no recorder keeps Edit - the * server decides. */ const viewerIsPatientSide = [USER_ROLES.PATIENT, USER_ROLES.RELATED_PERSON].includes(userRole as string); const recordedByCareTeam = isConditionRecordedByCareTeam; const mayEditRow = (condition: Condition) => isAuthorizedToEdit() && !(viewerIsPatientSide && recordedByCareTeam(condition)); const { requestCorrection, error: correctionError, recovery: correctionRecovery } = useRequestRecordCorrection( selectedCondition ? resolveConditionAuthorReference(selectedCondition) : undefined); const mayRequestCorrection = Boolean(selectedCondition) && viewerIsPatientSide && recordedByCareTeam(selectedCondition as Condition); const handleRequestCorrection = async (note: string) => { if (!selectedCondition) return { ok: false }; const label = formatFhirCodeableConcept(selectedCondition.code) || 'this condition'; return requestCorrection(resolveConditionAuthorReference(selectedCondition), label, note); }; const getConditionStatusInfo = (condition: Condition) => { const clinicalStatusCode = condition.clinicalStatus?.coding?.[0]?.code ?? ''; const isActive = clinicalStatusCode === 'active'; const isResolved = clinicalStatusCode === 'resolved' || clinicalStatusCode === 'remission'; return { isActive, isResolved }; }; const getConditionDateInfo = (condition: Condition): string => { if (condition.onsetDateTime) { return `Onset: ${new Date(condition.onsetDateTime).toLocaleDateString()}`; } else if (condition.recordedDate) { return `Recorded: ${new Date(condition.recordedDate).toLocaleDateString()}`; } return ''; }; 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((condition: Condition) => { const displayTitle = getConditionDisplay(condition); const statusInfo = getConditionStatusInfo(condition); const dateInfo = getConditionDateInfo(condition); return ( viewCondition(condition)}> {displayTitle} {dateInfo && ( {dateInfo} )} {mayEditRow(condition) && ( )} ); })} )} )} {selectedCondition && ( { setShowDisplayModal(false); setSelectedCondition(null); }} onRequestCorrection={mayRequestCorrection ? handleRequestCorrection : undefined} correctionError={correctionError} correctionRecovery={correctionRecovery} /> )}
); }; export default Conditions;