Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(fix) O3-2807: Quantity Units should be required when a quantity to dispense is specified #1636

Merged
merged 19 commits into from
Feb 13, 2024
Merged
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
844de36
(fix) dispensing units in drug-order-form default to dosage units wit…
mccarthyaaron Feb 3, 2024
772d757
changes in dosage unit only trigger changes in the dispensing unit wh…
mccarthyaaron Feb 3, 2024
7d91e9e
the dispensing units default to the dosing units when the dispensing …
mccarthyaaron Feb 3, 2024
08ea98b
Quantity units should be same as dosing units by default
vasharma05 Feb 6, 2024
46413eb
Fixed the Form Provider path
vasharma05 Feb 6, 2024
61b35d9
Removed Form Provider
vasharma05 Feb 6, 2024
cdeb02a
Quantity unit is required field
vasharma05 Feb 6, 2024
c4eeb10
Better error handling for quantity units
vasharma05 Feb 6, 2024
068fcb3
Merge branch 'openmrs:main' into drug-order-form
mccarthyaaron Feb 8, 2024
12a1aac
if the default value of the quantity units is not specified, then it …
mccarthyaaron Feb 8, 2024
cb9b02c
if the quantity unit is not specified, manually setting the dose unit…
mccarthyaaron Feb 8, 2024
b4e9648
quantity unit is required only when the quantity to dispense is speci…
mccarthyaaron Feb 8, 2024
282d82d
Update packages/esm-patient-medications-app/src/add-drug-order/drug-o…
vasharma05 Feb 9, 2024
fb1adb5
Final changes
vasharma05 Feb 9, 2024
f380d44
Merge branch 'main' of https://www.github.com/openmrs/openmrs-esm-pat…
vasharma05 Feb 9, 2024
c087559
removed the methods variable
mccarthyaaron Feb 9, 2024
624173c
Replaced watch() with getValues() where necessary
mccarthyaaron Feb 9, 2024
0315e57
Merge branch 'main' of https://www.github.com/openmrs/openmrs-esm-pat…
vasharma05 Feb 13, 2024
936a825
Quantity untis should be translated
vasharma05 Feb 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
import { Add, ArrowLeft, Subtract } from '@carbon/react/icons';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { Controller, useController, useForm } from 'react-hook-form';
import { type Control, Controller, useController, useForm } from 'react-hook-form';
import { age, formatDate, parseDate, useConfig, useLayoutType, usePatient } from '@openmrs/esm-framework';
import { useOrderConfig } from '../api/order-config';
import { type ConfigObject } from '../config-schema';
Expand Down Expand Up @@ -68,21 +68,34 @@ const schemaFields = {
frequency: z.object({ ...comboSchema }, { invalid_type_error: 'Please select a frequency' }),
};

const medicationOrderFormSchema = z.discriminatedUnion('isFreeTextDosage', [
z.object({
...schemaFields,
isFreeTextDosage: z.literal(false),
freeTextDosage: z.string().optional(),
}),
z.object({
...schemaFields,
isFreeTextDosage: z.literal(true),
dosage: z.number().nullable(),
unit: z.object({ ...comboSchema }).nullable(),
route: z.object({ ...comboSchema }).nullable(),
frequency: z.object({ ...comboSchema }).nullable(),
}),
]);
const medicationOrderFormSchema = z
.discriminatedUnion('isFreeTextDosage', [
z.object({
...schemaFields,
isFreeTextDosage: z.literal(false),
freeTextDosage: z.string().optional(),
}),
z.object({
...schemaFields,
isFreeTextDosage: z.literal(true),
dosage: z.number().nullable(),
unit: z.object({ ...comboSchema }).nullable(),
route: z.object({ ...comboSchema }).nullable(),
frequency: z.object({ ...comboSchema }).nullable(),
}),
])
.refine(
(formValues) => {
if (formValues.pillsDispensed > 0) {
return Boolean(formValues.quantityUnits);
}
return true;
},
{
message: 'Please select Quantity unit',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
message: 'Please select Quantity unit',
message: 'Please select a quantity unit',

@vasharma05, could you guide @mccarthyaaron on how to make these error messages translatable? We should probably add an example to our Coding Conventions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be eager to learn how to do that. Thanks

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mccarthyaaron!
You can refer #1652.
Thanks!

path: ['quantityUnits'],
},
);

type MedicationOrderFormData = z.infer<typeof medicationOrderFormSchema>;

Expand Down Expand Up @@ -143,7 +156,14 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel }: Drug
return initialOrderBasketItem?.startDate as Date;
}, [initialOrderBasketItem?.startDate]);

const { handleSubmit, control, watch, setValue } = useForm<MedicationOrderFormData>({
const {
handleSubmit,
control,
watch,
getValues,
setValue,
formState: { errors },
} = useForm<MedicationOrderFormData>({
mode: 'all',
resolver: zodResolver(medicationOrderFormSchema),
defaultValues: {
Expand All @@ -166,6 +186,15 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel }: Drug
},
});

const handleUnitAfterChange = useCallback(
(newValue: MedicationOrderFormData['unit'], prevValue: MedicationOrderFormData['unit']) => {
if (prevValue?.valueCoded === getValues('quantityUnits')?.valueCoded) {
setValue('quantityUnits', newValue, { shouldValidate: true });
}
},
[setValue],
);

const routeValue = watch('route')?.value;
const unitValue = watch('unit')?.value;
const dosage = watch('dosage');
Expand Down Expand Up @@ -277,7 +306,6 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel }: Drug
</span>
</div>
)}

<Form className={styles.orderForm} onSubmit={handleSubmit(handleFormSubmission)} id="drugOrderForm">
<div>
{errorFetchingOrderConfig && (
Expand Down Expand Up @@ -368,12 +396,14 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel }: Drug
control={control}
name="unit"
type="comboBox"
getValues={getValues}
size={isTablet ? 'lg' : 'md'}
id="dosingUnits"
items={drugDosingUnits}
placeholder={t('editDosageUnitsPlaceholder', 'Unit')}
titleText={t('editDosageUnitsTitle', 'Dose unit')}
itemToString={(item) => item?.value}
handleAfterChange={handleUnitAfterChange}
/>
</InputWrapper>
</Column>
Expand Down Expand Up @@ -675,31 +705,58 @@ const CustomNumberInput = ({ setValue, control, name, labelText, ...inputProps }
);
};

const ControlledFieldInput = ({ name, control, type, ...restProps }) => {
interface ControlledFieldInputProps {
name: keyof MedicationOrderFormData;
type: 'toggle' | 'checkbox' | 'number' | 'textArea' | 'textInput' | 'comboBox';
handleAfterChange?: (
newValue: MedicationOrderFormData[keyof MedicationOrderFormData],
prevValue: MedicationOrderFormData[keyof MedicationOrderFormData],
) => void;
control: Control<MedicationOrderFormData>;
[x: string]: any;
Copy link
Member

@denniskigen denniskigen Feb 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a better type here than any? any completely bails out of the type system.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Properly typing this requires a discriminated union, since the values of restProps depends on the type. Something like:

type ControlledFieldInputProps = BaseControlledFieldInputProps & (
  ToggleFieldProps | CheckboxFieldProps | NumberFieldProps | TextAreaProps | TextInputProps | ComboBoxProps
)

interface BaseControlledFieldInputProps {
  name: keyof MedicationOrderFormData;
	handleAfterChange?: (
	   newValue: MedicationOrderFormData[keyof MedicationOrderFormData],
	   prevValue: MedicationOrderFormData[keyof MedicationOrderFormData],
	 ) => void;
   control: Control<MedicationOrderFormData>;
}

interface ToggleFieldProps extends ToggleProps /* part of Carbon */ {
  type: 'toggle',
}

// repeat for other types

And that should give us something fully typed.

}

const ControlledFieldInput = ({
name,
type,
control,
getValues,
handleAfterChange,
...restProps
}: ControlledFieldInputProps) => {
const {
field: { onBlur, onChange, value, ref },
fieldState,
} = useController<MedicationOrderFormData>({ name: name, control });

const handleChange = useCallback(
(newValue: MedicationOrderFormData[keyof MedicationOrderFormData]) => {
const prevValue = getValues?.(name);
onChange(newValue);
handleAfterChange?.(newValue, prevValue);
},
[getValues, onChange, handleAfterChange],
);

const component = useMemo(() => {
if (type === 'toggle')
return (
<Toggle
toggled={value}
onChange={() => {} /* Required by the typings, but we don't need it. */}
onToggle={(value) => onChange(value)}
onToggle={(value) => handleChange(value)}
{...restProps}
/>
);

if (type === 'checkbox')
return <Checkbox checked={value} onChange={(e, { checked, id }) => onChange(checked)} {...restProps} />;
return <Checkbox checked={value} onChange={(e, { checked, id }) => handleChange(checked)} {...restProps} />;

if (type === 'number')
return (
<NumberInput
value={!!value ? value : 0}
onChange={(e, { value }) => onChange(parseFloat(value))}
onChange={(e, { value }) => handleChange(parseFloat(value))}
className={fieldState?.error?.message && styles.fieldError}
onBlur={onBlur}
ref={ref}
Expand All @@ -711,7 +768,7 @@ const ControlledFieldInput = ({ name, control, type, ...restProps }) => {
return (
<TextArea
value={value}
onChange={(e) => onChange(e.target.value)}
onChange={(e) => handleChange(e.target.value)}
onBlur={onBlur}
ref={ref}
className={fieldState?.error?.message && styles.fieldError}
Expand All @@ -723,7 +780,7 @@ const ControlledFieldInput = ({ name, control, type, ...restProps }) => {
return (
<TextInput
value={value}
onChange={(e) => onChange(e.target.value)}
onChange={(e) => handleChange(e.target.value)}
ref={ref}
onBlur={onBlur}
className={fieldState?.error?.message && styles.fieldError}
Expand All @@ -735,7 +792,7 @@ const ControlledFieldInput = ({ name, control, type, ...restProps }) => {
return (
<ComboBox
selectedItem={value}
onChange={({ selectedItem }) => onChange(selectedItem)}
onChange={({ selectedItem }) => handleChange(selectedItem)}
onBlur={onBlur}
ref={ref}
className={fieldState?.error?.message && styles.fieldError}
Expand Down
Loading