import { useState } from 'react'; import { CustomSelectControl } from "../ui/custom-select-control"; import { FormTokenField } from "../ui/form-token-field"; import type { CustomSelectOption } from "../ui/custom-select-control"; import type { TokenItem } from "../ui/form-token-field"; import { ExternalLink, Plus } from 'lucide-react'; import { DismissibleAlert } from '../ui/dismissible-alert'; import { Flex, FlexItem, FlexBlock } from '../ui/flex'; import { SHEET_BODY_TYPES } from '../../utils/sheetBodyDisplays'; import { Sheet, SheetContent } from '../ui/sheet'; import { Controller, useFormContext } from 'react-hook-form'; import { AdverseEvent, AllergyIntolerance, CarePlan, CareTeam, ClinicalImpression, Condition, DetectedIssue, Device, DeviceRequest, DeviceUseStatement, DocumentReference, Encounter, EpisodeOfCare, FamilyMemberHistory, FhirResource, Goal, HealthcareService, Immunization, ImmunizationEvaluation, ImmunizationRecommendation, Location, MedicationAdministration, MedicationDispense, MedicationRequest, MedicationStatement, NutritionOrder, Observation, Practitioner, Procedure, QuestionnaireResponse, Reference, RelatedPerson, Resource, RiskAssessment, Task, VisionPrescription } from 'fhir/r4'; import { useAdverseEventServiceContext, useAllergyIntoleranceServiceContext, useAuthServiceContext, useCarePlanServiceContext, useCareTeamServiceContext, useClinicalImpressionServiceContext, useConditionServiceContext, useDetectedIssueServiceContext, useDeviceRequestServiceContext, useDeviceServiceContext, useDeviceUseStatementServiceContext, useDocumentReferenceServiceContext, useEncounterServiceContext, useEpisodeOfCareServiceContext, useFamilyMemberHistoryServiceContext, useFhirServiceContext, useGoalServiceContext, useHealthcareServiceServiceContext, useImmunizationEvaluationServiceContext, useImmunizationRecommendationServiceContext, useImmunizationServiceContext, useLocationServiceContext, useMedicationAdministrationServiceContext, useMedicationDispenseServiceContext, useMedicationRequestServiceContext, useMedicationStatementServiceContext, useNutritionOrderServiceContext, useObservationServiceContext, usePractitionerRoleServiceContext, useProcedureServiceContext, useQuestionnaireResponseServiceContext, useRelatedPersonServiceContext, useRiskAssessmentServiceContext, useTaskServiceContext, useVisionPrescriptionServiceContext } from '../../services'; import { FhirAdverseEventDisplay, FhirAllergyIntoleranceDisplay, FhirCarePlanDisplay, FhirCareTeamDisplay, FhirClinicalImpressionDisplay, FhirConditionDisplay, FhirDetectedIssueDisplay, FhirDeviceDisplay, FhirDeviceRequestDisplay, FhirDeviceUseStatementDisplay, FhirDocumentReferenceDisplay, FhirEncounterDisplay, FhirEpisodeOfCareDisplay, FhirFamilyMemberHistoryDisplay, FhirGoalDisplay, FhirHealthcareServiceDisplay, FhirImmunizationDisplay, FhirImmunizationEvaluationDisplay, FhirImmunizationRecommendationDisplay, FhirLocationDisplay, FhirMedicationAdministrationDisplay, FhirMedicationDispenseDisplay, FhirMedicationRequestDisplay, FhirMedicationStatementDisplay, FhirNutritionOrderDisplay, FhirObservationDisplay, FhirProcedureDisplay, FhirQuestionnaireResponseDisplay, FhirRelatedPersonDisplay, FhirRiskAssessmentDisplay, FhirTaskDisplay, FhirVisionPrescriptionDisplay, FhirPractitionerDisplay } from '../../components'; import { AdverseEventModal, AllergyIntoleranceModal, CarePlanModal, CareTeamModal, ClinicalImpressionModal, ConditionModal, DetectedIssueModal, DeviceModal, DeviceRequestModal, DeviceUseStatementModal, DocumentReferenceModal, EncounterModal, EpisodeOfCareModal, FamilyMemberHistoryModal, GoalModal, HealthcareServiceModal, ImmunizationEvaluationModal, ImmunizationModal, ImmunizationRecommendationModal, LocationModal, MedicationAdministrationModal, MedicationDispenseModal, MedicationRequestModal, MedicationStatementModal, NutritionOrderModal, ObservationModal, ProcedureModal, RelatedPersonModal, RiskAssessmentModal, TaskModal, VisionPrescriptionModal } from '../../modals'; import { formatFhirHumanName } from '../../utils/fhirHumanName.tsx'; import { getNestedError } from '../../utils/formUtils.ts'; interface FhirReferenceProps { label: string; context: string; referenceType: string; referenceList?: FhirResource[]; placeholder?: string; disabled?: boolean; required?: boolean; multiple?: boolean; enableInlineActions?: boolean; } const FhirReference = ({ label, context, referenceType, referenceList = [], placeholder = 'Select a reference', disabled = false, required = false, multiple = false, enableInlineActions = true }: FhirReferenceProps) => { const { control, formState: { errors } } = useFormContext(); const { currentUser, userRole, USER_ROLES } = useAuthServiceContext(); /** * A REFERENCE FIELD THAT WILL REFUSE A SAVE NOW SAYS SO BEFORE THE PRESS. * * `required` has always reached the Controller below and blocked the submit, and nothing ever put a * marker beside the label - so six live call sites (clinical impression, device use statement, * encounter, episode of care, immunization evaluation, procedure) could stop a save on a field that * looked optional. Both controls here take `label` as a STRING, which is why this is a string rather * than the asterisk element the shadcn fields render. * * Keyed off the same `required` that does the enforcing, so nothing optional gains one. */ const markedLabel = required && label ? `${label} *` : label; const { createReferenceObject, extractReferenceId } = useFhirServiceContext(); const { getPractitionerRoleDisplay } = usePractitionerRoleServiceContext(); const { getLocationDisplay } = useLocationServiceContext(); const { getHealthcareServiceDisplay } = useHealthcareServiceServiceContext(); const { getRelatedPersonDisplay } = useRelatedPersonServiceContext(); const { getAllergyIntoleranceDisplay } = useAllergyIntoleranceServiceContext(); const { getFamilyMemberHistoryDisplay } = useFamilyMemberHistoryServiceContext(); const { getConditionDisplay } = useConditionServiceContext(); const { getProcedureDisplay } = useProcedureServiceContext(); const { getAdverseEventDisplay } = useAdverseEventServiceContext(); const { getClinicalImpressionDisplay } = useClinicalImpressionServiceContext(); const { getDetectedIssueDisplay } = useDetectedIssueServiceContext(); const { getObservationDisplay } = useObservationServiceContext(); const { getMedicationStatementDisplay } = useMedicationStatementServiceContext(); const { getMedicationRequestDisplay } = useMedicationRequestServiceContext(); const { getMedicationAdministrationDisplay } = useMedicationAdministrationServiceContext(); const { getMedicationDispenseDisplay } = useMedicationDispenseServiceContext(); const { getImmunizationDisplay } = useImmunizationServiceContext(); const { getImmunizationEvaluationDisplay } = useImmunizationEvaluationServiceContext(); const { getImmunizationRecommendationDisplay } = useImmunizationRecommendationServiceContext(); const { getDeviceDisplay } = useDeviceServiceContext(); const { getDeviceUseStatementDisplay } = useDeviceUseStatementServiceContext(); const { getDeviceRequestDisplay } = useDeviceRequestServiceContext(); const { getCarePlanDisplay } = useCarePlanServiceContext(); const { getCareTeamDisplay } = useCareTeamServiceContext(); const { getGoalDisplay } = useGoalServiceContext(); const { getNutritionOrderDisplay } = useNutritionOrderServiceContext(); const { getRiskAssessmentDisplay } = useRiskAssessmentServiceContext(); const { getVisionPrescriptionDisplay } = useVisionPrescriptionServiceContext(); const { getEncounterDisplay } = useEncounterServiceContext(); const { getEpisodeOfCareDisplay } = useEpisodeOfCareServiceContext(); const { getDocumentReferenceDisplay } = useDocumentReferenceServiceContext(); const { getQuestionnaireResponseDisplay } = useQuestionnaireResponseServiceContext(); const { getTaskDisplay } = useTaskServiceContext(); const [selectedType, setSelectedType] = useState(); const [selectedItem, setSelectedItem] = useState(); const [showModal, setShowModal] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false); const getDisplayName = (resource: FhirResource): string => { if (!resource) return ''; switch (resource.resourceType) { case 'Patient': case 'Practitioner': return formatFhirHumanName(resource.name) ?? `${resource.resourceType} ${resource.id}`; case 'RelatedPerson': return getRelatedPersonDisplay(resource); case 'Organization': return resource.name ?? `Organization ${resource.id}`; case 'Location': return getLocationDisplay(resource); case 'HealthcareService': return getHealthcareServiceDisplay(resource); case 'Questionnaire': return resource.title ?? `Questionnaire ${resource.id}`; case 'PractitionerRole': // format: "Dr. Name - Specialty at Organization" return getPractitionerRoleDisplay(resource); case 'FamilyMemberHistory': return getFamilyMemberHistoryDisplay(resource); case 'AllergyIntolerance': return getAllergyIntoleranceDisplay(resource); case 'Condition': return getConditionDisplay(resource); case 'Procedure': return getProcedureDisplay(resource); case 'AdverseEvent': return getAdverseEventDisplay(resource); case 'ClinicalImpression': return getClinicalImpressionDisplay(resource); case 'DetectedIssue': return getDetectedIssueDisplay(resource); case 'Observation': return getObservationDisplay(resource); case 'MedicationStatement': return getMedicationStatementDisplay(resource); case 'MedicationRequest': return getMedicationRequestDisplay(resource); case 'MedicationAdministration': return getMedicationAdministrationDisplay(resource); case 'MedicationDispense': return getMedicationDispenseDisplay(resource); case 'Immunization': return getImmunizationDisplay(resource); case 'ImmunizationEvaluation': return getImmunizationEvaluationDisplay(resource); case 'ImmunizationRecommendation': return getImmunizationRecommendationDisplay(resource); case 'Device': return getDeviceDisplay(resource); case 'DeviceUseStatement': return getDeviceUseStatementDisplay(resource); case 'DeviceRequest': return getDeviceRequestDisplay(resource); case 'CarePlan': return getCarePlanDisplay(resource); case 'CareTeam': return getCareTeamDisplay(resource); case 'Goal': return getGoalDisplay(resource); case 'NutritionOrder': return getNutritionOrderDisplay(resource); case 'RiskAssessment': return getRiskAssessmentDisplay(resource); case 'VisionPrescription': return getVisionPrescriptionDisplay(resource); case 'Encounter': return getEncounterDisplay(resource); case 'EpisodeOfCare': return getEpisodeOfCareDisplay(resource); case 'Task': return getTaskDisplay(resource); case 'QuestionnaireResponse': return getQuestionnaireResponseDisplay(resource); case 'DocumentReference': return getDocumentReferenceDisplay(resource); default: return `${resource.resourceType} ${resource.id}`; } }; const getReferenceDisplayName = (reference: Reference): string => { if (!reference) return ''; if (reference.display) return reference.display; if (reference.reference) { const [resourceType, id] = reference.reference.split('/'); const resource = referenceList.find(r => r.id === id && r.resourceType === resourceType); if (resource) { return getDisplayName(resource); } return reference.reference; } return ''; }; const findResourceById = (id: string): FhirResource | undefined => { return referenceList.find(resource => resource.id === id); }; const create = () => { setShowCreateModal(true); }; const view = (customSelectValue: CustomSelectOption | undefined) => { if (customSelectValue?.key) { const resource = findResourceById(customSelectValue.key); if (resource) { setSelectedType(resource.resourceType); setSelectedItem(resource); setShowModal(true); } } }; const closeModal = () => { setShowModal(false); setSelectedType(undefined); setSelectedItem(undefined); }; const canCreateReference = () => { if ([USER_ROLES.PATIENT, USER_ROLES.RELATED_PERSON].includes(userRole as string)) { return ['FamilyMemberHistory', 'AllergyIntolerance', 'MedicationStatement', 'Condition', 'Observation'].includes(referenceType); } else { return !['Location', 'Questionnaire', 'QuestionnaireResponse', 'Practitioner'].includes(referenceType); } }; return ( <> { if (multiple) { const tokenValues: (string | TokenItem)[] = Array.isArray(field.value) ? field.value.map((ref: Reference) => { const displayName = getReferenceDisplayName(ref); return displayName || ''; }) : []; const suggestions = referenceList.map(resource => getDisplayName(resource)); return ( { const references = tokens.map(token => { const tokenStr = typeof token === 'string' ? token : token.value; const resource = referenceList.find(r => getDisplayName(r) === tokenStr); if (resource) { return createReferenceObject(resource.resourceType, resource.id as string, getDisplayName(resource)); } return null; }).filter(Boolean) as Reference[]; field.onChange(references); }} suggestions={suggestions} disabled={disabled} placeholder={placeholder} messages={{ added: 'Reference added.', removed: 'Reference removed.', remove: 'Remove reference', __experimentalInvalid: 'Invalid reference.' }}/> ); } else { let customSelectValue: CustomSelectOption | undefined; if (field.value) { const referenceId = extractReferenceId(field.value); const resource = findResourceById(referenceId); if (resource) { customSelectValue = { key: resource.id as string, name: getDisplayName(resource) }; } else if (field.value.display) { customSelectValue = { key: referenceId || '', name: field.value.display }; } } else { customSelectValue = { key: '', name: '' }; } return ( ({ key: resource.id as string, name: getDisplayName(resource) })) ]} onChange={(newValue: { selectedItem: CustomSelectOption }) => { const resource = findResourceById(newValue.selectedItem.key); if (resource) { const reference = createReferenceObject(resource.resourceType, resource.id as string, getDisplayName(resource)); field.onChange(reference); } }} placeholder={placeholder} disabled={disabled}/> {enableInlineActions && ( <> {customSelectValue?.key && !['Questionnaire'].includes(referenceType) && ( )} {referenceType && canCreateReference() && ( )} )} ); } }}/> {getNestedError(errors, context) && ( {label} is required )} {/* LocationModal */} setShowCreateModal(false)}/> {/* HealthcareServiceModal */} setShowCreateModal(false)}/> {/* RelatedPersonModal */} setShowCreateModal(false)}/> {/* FamilyMemberHistoryModal */} setShowCreateModal(false)}/> {/* AllergyIntoleranceModal */} {showCreateModal && referenceType === 'AllergyIntolerance' && ( setShowCreateModal(false)}/> )} {/* ConditionModal */} {showCreateModal && referenceType === 'Condition' && ( setShowCreateModal(false)}/> )} {/* ProcedureModal */} {showCreateModal && referenceType === 'Procedure' && ( setShowCreateModal(false)}/> )} {/* AdverseEventModal */} {showCreateModal && referenceType === 'AdverseEvent' && ( setShowCreateModal(false)}/> )} {/* ClinicalImpressionModal */} setShowCreateModal(false)}/> {/* DetectedIssueModal */} setShowCreateModal(false)}/> {/* ObservationModal */} setShowCreateModal(false)}/> {/* MedicationStatementModal */} setShowCreateModal(false)}/> {/* MedicationRequestModal */} {showCreateModal && referenceType === 'MedicationRequest' && ( setShowCreateModal(false)}/> )} {/* MedicationAdministrationModal */} setShowCreateModal(false)}/> {/* MedicationDispenseModal */} setShowCreateModal(false)}/> {/* ImmunizationModal */} setShowCreateModal(false)}/> {/* ImmunizationEvaluationModal */} setShowCreateModal(false)}/> {/* ImmunizationRecommendationModal */} setShowCreateModal(false)}/> {/* DeviceModal */} setShowCreateModal(false)}/> {/* DeviceUseStatementModal */} setShowCreateModal(false)}/> {/* DeviceRequestModal */} {showCreateModal && referenceType === 'DeviceRequest' && ( setShowCreateModal(false)}/> )} {/* CarePlanModal */} {showCreateModal && referenceType === 'CarePlan' && ( setShowCreateModal(false)}/> )} {/* CareTeamModal */} setShowCreateModal(false)}/> {/* GoalModal */} setShowCreateModal(false)}/> {/* NutritionOrderModal */} setShowCreateModal(false)}/> {/* RiskAssessmentModal */} {showCreateModal && referenceType === 'RiskAssessment' && ( setShowCreateModal(false)}/> )} {/* VisionPrescriptionModal */} setShowCreateModal(false)}/> {/* EncounterModal */} setShowCreateModal(false)}/> {/* EpisodeOfCareModal */} setShowCreateModal(false)}/> {/* TaskModal */} {showCreateModal && referenceType === 'Task' && ( setShowCreateModal(false)}/> )} {/* DocumentReferenceModal */} setShowCreateModal(false)}/> {/* FhirLocationDisplay */} {/* FhirHealthcareServiceDisplay */} {/* FhirPractitionerDisplay */} {/* FhirRelatedPersonDisplay - mounted only when the selection IS one, because this display asks the identity endpoint for whatever id it is handed. */} {selectedType === 'RelatedPerson' && ( )} {/* FhirFamilyMemberHistoryDisplay */} {/* FhirAllergyIntoleranceDisplay */} {/* FhirConditionDisplay */} {/* FhirProcedureDisplay */} {/* FhirAdverseEventDisplay */} {/* FhirClinicalImpressionDisplay */} {/* FhirDetectedIssueDisplay */} {/* These displays draw only a sheet's contents, so the sheet is ours to open. */} { if (!open) closeModal(); }} > {/* FhirObservationDisplay */} {/* FhirMedicationStatementDisplay */} {/* FhirMedicationRequestDisplay */} {/* FhirDeviceDisplay */} {/* FhirDeviceUseStatementDisplay */} {/* FhirDeviceRequestDisplay */} {/* FhirCarePlanDisplay */} {/* FhirCareTeamDisplay */} {/* FhirGoalDisplay */} {/* FhirNutritionOrderDisplay */} {/* FhirRiskAssessmentDisplay */} {/* FhirVisionPrescriptionDisplay */} {/* FhirTaskDisplay */} {/* FhirMedicationAdministrationDisplay */} {/* FhirMedicationDispenseDisplay */} {/* FhirImmunizationDisplay */} {/* FhirImmunizationEvaluationDisplay */} {/* FhirImmunizationRecommendationDisplay */} {/* FhirEncounterDisplay */} {/* FhirEpisodeOfCareDisplay */} {/* FhirQuestionnaireResponseDisplay */} {/* FhirDocumentReferenceDisplay */} ); }; export default FhirReference;