import React, { useEffect, useState } from 'react'; import { TextareaControl } from "../ui/textarea-control"; import { CheckboxControl } from "../ui/checkbox-control"; import { IValueSet, useValueSetServiceContext } from '../../services'; import { Dosage } from 'fhir/r4'; import { Controller, useFieldArray, useFormContext } from 'react-hook-form'; import { Card, CardBody, CardHeader } from '../ui/wp-card'; import { InputControl } from '../ui/input-control'; import { Divider } from '../ui/divider'; import { DismissibleAlert } from '../ui/dismissible-alert'; import { Heading } from '../ui/heading'; import { Flex, FlexItem } from '../ui/flex'; import { Spacer } from '../ui/spacer'; import { PlusCircle, Trash2 } from 'lucide-react'; import { FhirCodeableConcept, FhirQuantity, FhirRange, FhirRatio, FhirTiming, TypeSelector } from '../index.ts'; import FhirValueSetSearch from '../FhirValueSetSearch/FhirValueSetSearch.tsx'; interface FhirDosageProps { context: string; disabled?: boolean; label?: string; readonly?: boolean; /** * Demand the free-text instructions. Set by a PRESCRIPTION, where a sig is the whole point of the * order, and left off by a medication statement or a dispense record, where the sig belongs to the * order rather than to what is being recorded. FHIR marks nothing on Dosage mandatory, so this is * ours to decide per embedding site rather than once for the shared form. */ required?: boolean; } const FhirDosage = ({ context, disabled = false, label = 'Dosage Instructions', readonly = false, required = false }: FhirDosageProps) => { const { fetchValueSet, valueSetUrls } = useValueSetServiceContext(); const [additionalInstructionCodes, setAdditionalInstructionCodes] = useState([]); const [administrationMethodCodes, setAdministrationMethodCodes] = useState([]); const [doseRateType, setDoseRateType] = useState([]); const spacerMargin = '24px'; const { watch, control, setValue, getValues } = useFormContext(); const dosageFieldArray = useFieldArray({ control, name: context }); useEffect(() => { const loadValueSets = async () => { const [ additionalInstructionCodesValueSet, administrationMethodCodesValueSet, doseRateTypeValueSet ] = await Promise.all([ fetchValueSet(valueSetUrls.additionalInstructionCodes), fetchValueSet(valueSetUrls.administrationMethodCodes), fetchValueSet(valueSetUrls.doseRateType) ]); setAdditionalInstructionCodes(additionalInstructionCodesValueSet); setAdministrationMethodCodes(administrationMethodCodesValueSet); setDoseRateType(doseRateTypeValueSet); }; loadValueSets(); }, [fetchValueSet, valueSetUrls]); const canAddDosage = () => { return true; }; const addDosage = () => { dosageFieldArray.append({ sequence: dosageFieldArray.fields.length + 1, text: '', patientInstruction: '', additionalInstruction: [], asNeeded: false, asNeededFor: null, site: null, route: null, method: null, doseType: 'quantity', doseAndRate: [], maxDosePerPeriod: { numerator: { value: '', unit: '', system: '', code: '' }, denominator: { value: '', unit: '', system: '', code: '' } }, maxDosePerAdministration: { value: '', unit: '', system: '', code: '' }, maxDosePerLifetime: { value: '', unit: '', system: '', code: '' }, timingType: 'code', timing: { code: null } }); }; const removeDosage = (index: number) => { dosageFieldArray.remove(index); }; /* * A REQUIRED FIELD INSIDE AN EMPTY ARRAY IS NOT A REQUIREMENT. The dosage list starts empty, so the * instructions control never mounts and its rule never runs - a prescription would refuse nothing and * still save with no sig at all. When the sig is required the first row has to exist, and it cannot be * removed back down to none. */ useEffect(() => { if (required && dosageFieldArray.fields.length === 0) addDosage(); }, [required, dosageFieldArray.fields.length]); const addDoseAndRate = (index: number) => { const path: string = `${context}.${index}.doseAndRate`; const doseAndRate = getValues(path) ?? []; setValue(path, [...doseAndRate, { type: null, doseQuantity: { value: '', unit: '', system: '', code: '' } }]); }; const removeDoseAndRate = (index: number, doseAndRateIndex: number) => { const path: string = `${context}.${index}.doseAndRate`; const doseAndRate = getValues(path) ?? []; doseAndRate.splice(doseAndRateIndex, 1); setValue(path, doseAndRate); }; return ( <> {dosageFieldArray.fields.length === 0 && ( <> {label} {!readonly && ( )} No dosage instructions added yet. )} {(dosageFieldArray.fields as any[]).map((dosage: Dosage, index: number) => { const doseAndRate = watch(`${context}.${index}.doseAndRate`); return (

Dosage {index + 1}

{/* The last row stays when the sig is required, so the field that refuses cannot be removed out from under the refusal. */} {!readonly && !(required && dosageFieldArray.fields.length === 1) && ( )}
{/* sequence */} ( )}/> {/* text */} ( )}/> {/* additionalInstruction */} {/* patientInstruction */} ( )}/> {/* timing */} {/* asNeeded and asNeededFor */} As Needed ( )}/> {watch(`${context}.${index}.asNeededBoolean`) && ( <> )} {/* site */} Administration Details {/* route */} {/* method */} {/* doseAndRate */} Dose and Rate {doseAndRate.length === 0 && ( {!readonly && ( )} )} {(doseAndRate as any[]).map((doseRate, doseRateIndex) => ( Dose and Rate {doseRateIndex + 1} {/* doseAndRate[].type */} {/* doseType */} {/* doseRange */} {watch(`${context}.${index}.doseAndRate.${doseRateIndex}.doseType`) === 'range' && ( <> )} {/* doseQuantity */} {watch(`${context}.${index}.doseAndRate.${doseRateIndex}.doseType`) === 'quantity' && ( <> )} {/* rateType */} {/* rateQuantity */} {watch(`${context}.${index}.doseAndRate.${doseRateIndex}.rateType`) === 'quantity' && ( <> )} {/* rateRange */} {watch(`${context}.${index}.doseAndRate.${doseRateIndex}.rateType`) === 'range' && ( <> )} {/* rateRatio */} {watch(`${context}.${index}.doseAndRate.${doseRateIndex}.rateType`) === 'ratio' && ( <> )} ))} {!readonly && doseAndRate.length > 0 && ( )} {/*Maximum Dose Constraints*/} {/**/} {/**/} {/* maxDosePerPeriod */} {/* maxDosePerAdministration */} {/* maxDosePerLifetime */}
); })} {!readonly && dosageFieldArray.fields.length > 0 && ( )} ); }; export default FhirDosage;