import React, { useEffect, useState, useMemo } from 'react'; import dayjs from 'dayjs'; import { useTranslation } from 'react-i18next'; import { ComboBox, Dropdown, NumberInput, Stack, TextArea, Toggle } from '@carbon/react'; import { OpenmrsDatePicker, useConfig, useSession, userHasAccess, ResponsiveWrapper } from '@openmrs/esm-framework'; import { getConceptCodingUuid, getMedicationReferenceOrCodeableConcept, getOpenMRSMedicineDrugName } from '../utils'; import { useMedicationCodeableConcept, useMedicationFormulations } from '../medication/medication.resource'; import { useMedicationRequest, usePrescriptionDetails } from '../medication-request/medication-request.resource'; import { blankSubstitution, useOrderConfig, useProviders, useSubstitutionReasonValueSet, useSubstitutionTypeValueSet, } from '../medication-dispense/medication-dispense.resource'; import { PRIVILEGE_CREATE_DISPENSE_MODIFY_DETAILS } from '../constants'; import { type Medication, type MedicationDispense } from '../types'; import { type PharmacyConfig } from '../config-schema'; import MedicationCard from '../components/medication-card.component'; import styles from '../components/medication-dispense-review.scss'; interface MedicationDispenseReviewProps { medicationDispense: MedicationDispense; updateMedicationDispense(updates: Partial); isFreeTextDosage: boolean; setIsFreeTextDosage: (isFreeTextDosage: boolean) => void; quantityRemaining: number; quantityDispensed: number; } const MedicationDispenseReview: React.FC = ({ medicationDispense, updateMedicationDispense, isFreeTextDosage, setIsFreeTextDosage, quantityRemaining, quantityDispensed, }) => { const { t } = useTranslation(); const config = useConfig(); const session = useSession(); const [isEditingFormulation, setIsEditingFormulation] = useState(false); // type of substitution question const [substitutionTypes, setSubstitutionTypes] = useState([]); // reason for substitution question const [substitutionReasons, setSubstitutionReasons] = useState([]); //quantity prescribed const [quantityPrescribed] = useState(medicationDispense.quantity.value); const [userCanModify, setUserCanModify] = useState(false); const allowEditing = config.dispenseBehavior.allowModifyingPrescription; const { orderConfigObject } = useOrderConfig(); const { substitutionTypeValueSet } = useSubstitutionTypeValueSet(config.valueSets.substitutionType.uuid); const { substitutionReasonValueSet } = useSubstitutionReasonValueSet(config.valueSets.substitutionReason.uuid); const providers = useProviders(config.dispenserProviderRoles); // get the medication request associated with this dispense event // (we fetch this so that we can use it below to determine if the formulation dispensed varies from what was // ordered; it's slightly inefficient/awkward to fetch it from the server here because we *have* fetched it earlier, // it just seems cleaner to fetch it here rather than to make sure we pass it down through various components; with // SWR handling caching, we may want to consider pulling more down into this) const { medicationRequest, isLoading: isMedicationRequestLoading } = useMedicationRequest( medicationDispense.authorizingPrescription ? medicationDispense.authorizingPrescription[0].reference : null, config.refreshInterval, ); // we fetch this just to get the prescription date const { prescriptionDate } = usePrescriptionDetails(medicationRequest ? medicationRequest.encounter.reference : null); const { drugRoutes, drugDosingUnits, drugDispensingUnits, orderFrequencies } = useMemo(() => { if (orderConfigObject) { // Dosing Unit eg Tablets const drugRoutes = orderConfigObject.drugRoutes.map((x) => ({ id: x.uuid, text: x.display })); // Dispensing Unit eg Tablets const drugDosingUnits = orderConfigObject.drugDosingUnits.map((x) => ({ id: x.uuid, text: x.display })); // Route eg Oral const drugDispensingUnits = orderConfigObject.drugDispensingUnits.map((x) => ({ id: x.uuid, text: x.display })); // Frequency eg Twice daily const orderFrequencies = orderConfigObject.orderFrequencies.map((x) => ({ id: x.uuid, text: x.display })); return { drugRoutes, drugDosingUnits, drugDispensingUnits, orderFrequencies }; } else { return { drugRoutes: [], drugDosingUnits: [], drugDispensingUnits: [], orderFrequencies: [] }; } }, [orderConfigObject]); useEffect(() => { const substitutionTypeOptions = []; if (substitutionTypeValueSet?.compose?.include) { const uuidValueSet = substitutionTypeValueSet.compose.include.find((include) => !include.system); if (uuidValueSet) { uuidValueSet.concept?.forEach((concept) => substitutionTypeOptions.push({ id: concept.code, text: concept.display, }), ); } substitutionTypeOptions.sort((a, b) => a.text.localeCompare(b.text)); } setSubstitutionTypes(substitutionTypeOptions); }, [substitutionTypeValueSet]); useEffect(() => { const substitutionReasonOptions = []; if (substitutionReasonValueSet?.compose?.include) { const uuidValueSet = substitutionReasonValueSet.compose.include.find((include) => !include.system); if (uuidValueSet) { uuidValueSet.concept?.forEach((concept) => substitutionReasonOptions.push({ id: concept.code, text: concept.display, }), ); } substitutionReasonOptions.sort((a, b) => a.text.localeCompare(b.text)); } setSubstitutionReasons(substitutionReasonOptions); }, [substitutionReasonValueSet]); // if this an order for a drug by concept, but not a particular formulation, we already have access to concept uuid const existingMedicationCodeableConceptUuid = getConceptCodingUuid( getMedicationReferenceOrCodeableConcept(medicationDispense)?.medicationCodeableConcept?.coding, ); // otherwise we need to fetch the medication details to get the codeable concept // (note that React Hooks should not be called conditionally, so we *always* call this hook, but it will return null if existingMedicationCodeableConcept is defined const { medicationCodeableConceptUuid } = useMedicationCodeableConcept( existingMedicationCodeableConceptUuid, getMedicationReferenceOrCodeableConcept(medicationDispense).medicationReference?.reference, ); // get the formulations const { medicationFormulations } = useMedicationFormulations( existingMedicationCodeableConceptUuid ? existingMedicationCodeableConceptUuid : medicationCodeableConceptUuid ? medicationCodeableConceptUuid : null, ); // check to see if the current dispense would be a substitution, and update accordingly const isSubstitution = medicationRequest?.medicationReference?.reference && medicationDispense?.medicationReference?.reference && medicationRequest.medicationReference.reference != medicationDispense.medicationReference.reference; const { substitution } = medicationDispense; useEffect(() => { if (isMedicationRequestLoading) { return; } if (isSubstitution) { // make sure that the value substitution.wasSubstituted exists and is truthy if (!substitution.wasSubstituted) { const newSubstitution = { ...substitution, wasSubstituted: true }; updateMedicationDispense({ substitution: newSubstitution }); } } else { if (substitution != blankSubstitution) { updateMedicationDispense({ substitution: blankSubstitution }); } } }, [isMedicationRequestLoading, isSubstitution, substitution, updateMedicationDispense]); useEffect(() => { setUserCanModify(session?.user && userHasAccess(PRIVILEGE_CREATE_DISPENSE_MODIFY_DETAILS, session.user)); }, [session]); const providersPlusCurrentUser = useMemo(() => { const currentUserInProviderList = providers.some((provider) => provider.uuid === session?.currentProvider?.uuid); return currentUserInProviderList ? providers : [ ...providers, { uuid: session.currentProvider.uuid, person: { display: session?.user?.person?.display ?? '', }, }, ]; }, [providers, session]); const initialDispenser = useMemo(() => { return medicationDispense?.performer?.[0]?.actor?.reference ? providersPlusCurrentUser?.find( (provider) => provider.uuid === medicationDispense.performer[0].actor.reference.split('/')[1], ) : undefined; }, [medicationDispense?.performer, providersPlusCurrentUser]); useEffect(() => { if (initialDispenser?.uuid) { updateMedicationDispense({ performer: [ { actor: { reference: `Practitioner/${initialDispenser.uuid}`, }, }, ], }); } }, [initialDispenser, updateMedicationDispense]); return (
{!isEditingFormulation ? ( setIsEditingFormulation(true) : null} /> ) : ( getOpenMRSMedicineDrugName(item)} initialSelectedItem={{ ...medicationFormulations?.find( (formulation) => formulation.id === medicationDispense.medicationReference?.reference.split('/')[1], ), }} titleText={t('medicationFormulation', 'Medication Formulation')} label={t('medicationFormulation', 'Medication Formulation')} onChange={({ selectedItem }) => { const typedItem = selectedItem as Medication; if ('Medication/' + typedItem.id !== medicationDispense.medicationReference.reference) { updateMedicationDispense({ medicationCodeableConcept: undefined, medicationReference: { reference: 'Medication/' + typedItem?.id, display: getOpenMRSMedicineDrugName(typedItem), }, quantity: { ...medicationDispense.quantity, value: 0, }, ...(isFreeTextDosage ? {} : { dosageInstruction: [ { ...medicationDispense.dosageInstruction[0], doseAndRate: [ { ...medicationDispense.dosageInstruction[0].doseAndRate[0], doseQuantity: { ...medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity, value: 0, }, }, ], }, ], }), }); } setIsEditingFormulation(false); }} /> )}
{isSubstitution && (
item?.text} initialSelectedItem={{ id: medicationDispense.substitution.type?.coding[0]?.code, text: medicationDispense.substitution.type?.text, }} onChange={({ selectedItem }) => { updateMedicationDispense({ substitution: { ...medicationDispense.substitution, type: { coding: [ { code: selectedItem?.id, }, ], }, }, }); }} />
)} {isSubstitution && (
item?.text} initialSelectedItem={{ id: medicationDispense.substitution.reason[0]?.coding[0]?.code, text: medicationDispense.substitution.reason[0]?.text, }} onChange={({ selectedItem }) => { updateMedicationDispense({ substitution: { ...medicationDispense.substitution, reason: [ { coding: [ { code: selectedItem?.id, }, ], }, ], }, }); }} />
)} {config.dispenseBehavior.restrictTotalQuantityDispensed ? (

{t('quantityPrescribed', 'Quantity Prescribed')}: {quantityPrescribed}

{t('quantityDispensed', 'Quantity Dispensed')}: {quantityDispensed}

{t('quantityRemaining', 'Quantity Remaining to Dispense')}: {quantityRemaining}

) : null}
{t('quantity', 'Quantity')}
} min={0} max={config.dispenseBehavior.restrictTotalQuantityDispensed ? quantityRemaining : undefined} onChange={(event, state) => { updateMedicationDispense({ quantity: { ...medicationDispense.quantity, value: state.value ? parseFloat(state.value.toString()) : 0, }, }); }} /> item?.text} initialSelectedItem={{ id: medicationDispense.quantity.code, text: medicationDispense.quantity.unit, }} onChange={({ selectedItem }) => { updateMedicationDispense({ // note that we specifically recreate doseQuantity to overwrite any unit or system properties that may have been set quantity: { value: medicationDispense.quantity.value, code: selectedItem?.id, }, }); }} required />
{ setIsFreeTextDosage(value); // clear out the dose, route and frequency if changed to free text dosage if (value) { updateMedicationDispense({ dosageInstruction: [ { ...medicationDispense.dosageInstruction[0], doseAndRate: [ { doseQuantity: { value: null, code: null, }, }, ], route: { coding: [ { code: null, }, ], }, timing: { ...medicationDispense.dosageInstruction[0].timing, code: { coding: [ { code: null, }, ], }, }, text: '', }, ], }); } else { // clear out the text field if changed from free text to coded dosage updateMedicationDispense({ dosageInstruction: [ { ...medicationDispense.dosageInstruction[0], text: '', }, ], }); } }} />
{!isFreeTextDosage && ( <>
{ updateMedicationDispense({ dosageInstruction: [ { ...medicationDispense.dosageInstruction[0], doseAndRate: [ { ...medicationDispense.dosageInstruction[0].doseAndRate[0], doseQuantity: { ...medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity, value: state.value ? parseFloat(state.value.toString()) : 0, }, }, ], }, ], }); }} /> item?.text} initialSelectedItem={{ id: medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity?.code, text: medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity?.unit, }} onChange={({ selectedItem }) => { updateMedicationDispense({ dosageInstruction: [ { ...medicationDispense.dosageInstruction[0], doseAndRate: [ { doseQuantity: { // note that we specifically recreate doseQuantity to overwrite any unit or system properties that may have been set value: medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity?.value, code: selectedItem?.id, }, }, ], }, ], }); }} required />
item?.text} onChange={({ selectedItem }) => { updateMedicationDispense({ dosageInstruction: [ { ...medicationDispense.dosageInstruction[0], route: { coding: [ { code: selectedItem?.id, }, ], }, }, ], }); }} required /> item?.text} onChange={({ selectedItem }) => { updateMedicationDispense({ dosageInstruction: [ { ...medicationDispense.dosageInstruction[0], timing: { ...medicationDispense.dosageInstruction[0].timing, code: { coding: [ { code: selectedItem?.id, }, ], }, }, }, ], }); }} required /> )}