import React, { useState, useEffect } from 'react'; import { Card, CardHeader, CardBody, CardDivider } from '../ui/wp-card'; import { Elevation } from '../ui/elevation'; import { InputControl } from '../ui/input-control'; import { DismissibleAlert } from '../ui/dismissible-alert'; import { Heading } from '../ui/heading'; import { Spacer } from '../ui/spacer'; import { TabPanel } from '../ui/tab-panel'; import { Disabled } from '../ui/disabled'; import { useFormContext, Controller, useFieldArray } from 'react-hook-form'; import { IValueSet, useAuthServiceContext, useValueSetServiceContext } from '../../services'; import { FhirAnnotation, FhirCode, FhirCodeableConcept, FhirDosage, FhirDuration, FhirPeriod, FhirQuantity, FhirReference, FhirReferenceCollection, FhirValueSetSearch } from '../../components'; import { useFhirCarePlans, useFhirCareTeams, useFhirConditions, useFhirDetectedIssues, useFhirDevices, useFhirEncounters, useFhirImmunizationRecommendations, useFhirMedicationRequestBroadcast, useFhirMedicationRequests, useFhirObservations, useFhirPractitioners, useFhirRelatedPersons } from '../../hooks'; import { FhirResource, Reference } from 'fhir/r4'; import { prescriberOptions } from '../../utils/prescribers'; import { useSeatStanding } from '../../utils/seatStanding'; // - WordPress's ToggleControl, swapped for the ui/* adapter. Same props, same JSX; // only `__nextHasNoMarginBottom` is dropped, which means nothing outside WordPress. import { ToggleControl } from '../ui/toggle-control'; interface FhirMedicationRequestProps { elevated?: boolean; disabled?: boolean; enableInlineActions?: boolean; } export const FhirMedicationRequest: React.FC = ({ elevated = true, disabled = false, enableInlineActions = true }) => { const { control, watch } = useFormContext(); const { userRole, USER_ROLES } = useAuthServiceContext(); const { fetchValueSet, valueSetUrls } = useValueSetServiceContext(); const [loading, setLoading] = useState(false); // value sets const [medicationrequestStatus, setMedicationrequestStatus] = useState([]); const [medicationrequestStatusReason, setMedicationrequestStatusReason] = useState([]); const [medicationrequestIntent, setMedicationrequestIntent] = useState([]); const [medicationrequestCategory, setMedicationrequestCategory] = useState([]); const [requestPriority, setRequestPriority] = useState([]); const [medicationrequestCourseOfTherapy, setMedicationrequestCourseOfTherapy] = useState([]); const [actSubstanceAdminSubstitutionCode, setActSubstanceAdminSubstitutionCode] = useState([]); const [substanceAdminSubstitutionReason, setSubstanceAdminSubstitutionReason] = useState([]); const isPatient = [USER_ROLES.PATIENT, USER_ROLES.RELATED_PERSON].includes(userRole as string); const spacerMargin = '24px'; useFhirMedicationRequestBroadcast(); const { medicationRequestsList } = useFhirMedicationRequests(); const { conditionsList } = useFhirConditions(); const { observationsList } = useFhirObservations(); const { carePlansList } = useFhirCarePlans(); const { immunizationRecommendationsList } = useFhirImmunizationRecommendations(); const { careTeamsList } = useFhirCareTeams(); const { devicesList } = useFhirDevices(); const { relatedPersonsList } = useFhirRelatedPersons(); const { detectedIssuesList } = useFhirDetectedIssues(); const { encountersList } = useFhirEncounters(); const { practitionersList } = useFhirPractitioners(); const medicationType = watch('medicationType'); const reasonReferenceFieldArray = useFieldArray({ control, name: 'reasonReference' }); const basedOnFieldArray = useFieldArray({ control, name: 'basedOn' }); const performerFieldArray = useFieldArray({ control, name: 'performer' }); const requesterFieldArray = useFieldArray({ control, name: 'requester' }); // WHO MAY BE NAMED AS HAVING ORDERED THIS. Watched rather than read once, because whoever is // already on the record has to stay in the list - see utils/prescribers for why a name that // vanishes from a signed order is the worse failure. const requesterValue = watch('requester'); const namedRequesters = ((requesterValue ?? []) as { reference?: Reference }[]) .map((entry) => entry?.reference); const seatStanding = useSeatStanding(); const prescribers = prescriberOptions(practitionersList, namedRequesters, seatStanding); useEffect(() => { const loadValueSets = async () => { setLoading(true); try { const [ medicationrequestStatusValueSet, medicationrequestStatusReasonValueSet, medicationrequestIntentValueSet, medicationrequestCategoryValueSet, requestPriorityValueSet, medicationrequestCourseOfTherapyValueSet, actSubstanceAdminSubstitutionCodeValueSet, substanceAdminSubstitutionReasonValueSet ] = await Promise.all([ fetchValueSet(valueSetUrls.medicationrequestStatus), fetchValueSet(valueSetUrls.medicationrequestStatusReason), fetchValueSet(valueSetUrls.medicationrequestIntent), fetchValueSet(valueSetUrls.medicationrequestCategory), fetchValueSet(valueSetUrls.requestPriority), fetchValueSet(valueSetUrls.medicationrequestCourseOfTherapy), fetchValueSet(valueSetUrls.actSubstanceAdminSubstitutionCode), fetchValueSet(valueSetUrls.substanceAdminSubstitutionReason) ]); setMedicationrequestStatus(medicationrequestStatusValueSet); setMedicationrequestStatusReason(medicationrequestStatusReasonValueSet); setMedicationrequestIntent(medicationrequestIntentValueSet); setMedicationrequestCategory(medicationrequestCategoryValueSet); setRequestPriority(requestPriorityValueSet); setMedicationrequestCourseOfTherapy(medicationrequestCourseOfTherapyValueSet); setActSubstanceAdminSubstitutionCode(actSubstanceAdminSubstitutionCodeValueSet); setSubstanceAdminSubstitutionReason(substanceAdminSubstitutionReasonValueSet); } catch (err) { console.error('Error loading value sets:', err); } finally { setLoading(false); } }; loadValueSets(); }, [fetchValueSet, valueSetUrls]); return ( {elevated && } Details {/* medicationCodeableConcept */} {medicationType === 'codeableConcept' && ( <> )} {medicationType === 'reference' && ( <> {/* medicationReference */} )} {/* priorPrescription */} {/* status */} {/* statusReason */} {/* intent */} {/* category */} {/* priority */} {/* courseOfTherapyType */} {/* authoredOn */} ( <> {fieldState.error && ( Authored on date is required )} )}/> {/* requester */} { switch (referenceType) { case 'Practitioner': return prescribers; case 'RelatedPerson': return relatedPersonsList; case 'Device': return devicesList; default: return []; } }} required={true} disabled={disabled} readonly={isPatient} multiple={false} enableInlineActions={enableInlineActions}/> {/* A NARROWED LIST CAN BE AN EMPTY ONE, and an empty required picker with no explanation is a dead end - the reader sees a blank dropdown and no way to learn that the practice simply has nobody who may prescribe. Said here rather than inside the picker because the reason is specific to this question. */} {!isPatient && prescribers.length === 0 && ( Nobody at this practice currently holds a clinician seat, so there is no one to record as having ordered this. Give a practitioner a clinician seat on their Workspaces tab first. )} {/* doNotPerform */} ( )}/> {/* reported */} ( )}/> {/* detectedIssue */} {/* encounter */} {(tab) => { // reason if (tab.name === 'reason') { return (
{/* reasonCode */} {/* reasonReference */} { switch (referenceType) { case 'Condition': return conditionsList; case 'Observation': return observationsList; default: return []; } }} required={false} disabled={disabled} readonly={isPatient} enableInlineActions={enableInlineActions}/>
); } // substitution if (tab.name === 'substitution') { return (
{/* substitution.allowedBoolean */} ( )}/> {watch(`substitution.allowedBoolean`) && ( <> {/* substitution.allowedCodeableConcept */} {/* substitution.reason */} )}
); } // dosage if (tab.name === 'dosage') { return (
{/* dosage */} {/* A prescription with no sig is not a prescription, it is a note naming a drug. Required here and not in the shared form, because a medication statement records what a patient says they take and a dispense record follows somebody else's order. */}
); } // dispense if (tab.name === 'dispense') { return (
{/* dispenseRequest.quantity */} {/* dispenseRequest.expectedSupplyDuration */} {/* dispenseInterval */} {/* numberOfRepeatsAllowed */} {/* An empty box used to coerce to 0, so "no refills" and "nobody said" were the same stored value and a pharmacist could not tell them apart. Empty now stays undefined - which is what the service already writes against - and the field is required, so zero is stated rather than inferred. */} ( field.onChange( value === '' || value === undefined ? undefined : parseInt(value))}/> )}/> {/* validityPeriod */} {/* initialFill.quantity */} {/* initialFill.duration */}
); } // basedOn if (tab.name === 'basedOn') { return (
{/* basedOn */} { switch (referenceType) { case 'CarePlan': return carePlansList; case 'MedicationRequest': return medicationRequestsList; case 'ImmunizationRecommendation': return immunizationRecommendationsList; default: return []; } }} required={false} disabled={disabled} readonly={isPatient} enableInlineActions={enableInlineActions}/>
); } // performer if (tab.name === 'performer') { return (
{/* performerType */} {/* performer */} { switch (referenceType) { case 'Practitioner': return practitionersList.map(p => p.practitioner); case 'Device': return devicesList; case 'RelatedPerson': return relatedPersonsList; case 'CareTeam': return careTeamsList; default: return []; } }} required={false} disabled={disabled} readonly={isPatient} multiple={false} enableInlineActions={enableInlineActions}/>
); } // notes if (tab.name === 'notes') { return (
{/* note */}
); } }}
); }; export default FhirMedicationRequest;