-
Notifications
You must be signed in to change notification settings - Fork 250
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
(feat) O3-4430: Optional Indication field in the drug order form #2228
base: main
Are you sure you want to change the base?
Changes from 2 commits
ec5c9fb
420cc76
7f287a7
0bb3cc7
43d50ad
4505be4
7ed27d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -60,115 +60,125 @@ export interface DrugOrderFormProps { | |||||||||
promptBeforeClosing: (testFcn: () => boolean) => void; | ||||||||||
} | ||||||||||
|
||||||||||
const createMedicationOrderFormSchema = (requireOutpatientQuantity: boolean, t: TFunction) => { | ||||||||||
const comboSchema = { | ||||||||||
default: z.boolean().optional(), | ||||||||||
value: z.string(), | ||||||||||
valueCoded: z.string(), | ||||||||||
}; | ||||||||||
function useCreateMedicationOrderFormSchema() { | ||||||||||
const { t } = useTranslation(); | ||||||||||
const { requireOutpatientQuantity } = useRequireOutpatientQuantity(); | ||||||||||
const { isIndicationFieldOptional } = useConfig<ConfigObject>(); | ||||||||||
|
||||||||||
const baseSchemaFields = { | ||||||||||
freeTextDosage: z.string().refine((value) => !!value, { | ||||||||||
message: t('freeDosageErrorMessage', 'Add free dosage note'), | ||||||||||
}), | ||||||||||
dosage: z.number({ | ||||||||||
invalid_type_error: t('dosageRequiredErrorMessage', 'Dosage is required'), | ||||||||||
}), | ||||||||||
unit: z.object( | ||||||||||
{ ...comboSchema }, | ||||||||||
{ | ||||||||||
invalid_type_error: t('selectUnitErrorMessage', 'Dose unit is required'), | ||||||||||
}, | ||||||||||
), | ||||||||||
route: z.object( | ||||||||||
{ ...comboSchema }, | ||||||||||
{ | ||||||||||
invalid_type_error: t('selectRouteErrorMessage', 'Route is required'), | ||||||||||
}, | ||||||||||
), | ||||||||||
patientInstructions: z.string().nullable(), | ||||||||||
asNeeded: z.boolean(), | ||||||||||
asNeededCondition: z.string().nullable(), | ||||||||||
duration: z.number().nullable(), | ||||||||||
durationUnit: z.object({ ...comboSchema }).nullable(), | ||||||||||
indication: z.string().refine((value) => value !== '', { | ||||||||||
message: t('indicationErrorMessage', 'Indication is required'), | ||||||||||
}), | ||||||||||
startDate: z.date(), | ||||||||||
frequency: z.object( | ||||||||||
{ ...comboSchema }, | ||||||||||
{ | ||||||||||
invalid_type_error: t('selectFrequencyErrorMessage', 'Frequency is required'), | ||||||||||
}, | ||||||||||
), | ||||||||||
}; | ||||||||||
const schema = useMemo(() => { | ||||||||||
const comboSchema = { | ||||||||||
default: z.boolean().optional(), | ||||||||||
value: z.string(), | ||||||||||
valueCoded: z.string(), | ||||||||||
}; | ||||||||||
|
||||||||||
const outpatientDrugOrderFields = { | ||||||||||
pillsDispensed: z | ||||||||||
.number() | ||||||||||
.nullable() | ||||||||||
.refine( | ||||||||||
(value) => { | ||||||||||
if (requireOutpatientQuantity && (typeof value !== 'number' || value < 1)) { | ||||||||||
return false; | ||||||||||
} | ||||||||||
return true; | ||||||||||
}, | ||||||||||
const baseSchemaFields = { | ||||||||||
freeTextDosage: z.string().refine((value) => !!value, { | ||||||||||
message: t('freeDosageErrorMessage', 'Add free dosage note'), | ||||||||||
}), | ||||||||||
dosage: z.number({ | ||||||||||
invalid_type_error: t('dosageRequiredErrorMessage', 'Dosage is required'), | ||||||||||
}), | ||||||||||
unit: z.object( | ||||||||||
{ ...comboSchema }, | ||||||||||
{ | ||||||||||
message: t('pillDispensedErrorMessage', 'Quantity to dispense is required'), | ||||||||||
invalid_type_error: t('selectUnitErrorMessage', 'Dose unit is required'), | ||||||||||
}, | ||||||||||
), | ||||||||||
quantityUnits: z | ||||||||||
.object(comboSchema) | ||||||||||
.nullable() | ||||||||||
.refine( | ||||||||||
(value) => { | ||||||||||
if (requireOutpatientQuantity && !value) { | ||||||||||
return false; | ||||||||||
} | ||||||||||
return true; | ||||||||||
}, | ||||||||||
route: z.object( | ||||||||||
{ ...comboSchema }, | ||||||||||
{ | ||||||||||
message: t('selectQuantityUnitsErrorMessage', 'Quantity unit is required'), | ||||||||||
invalid_type_error: t('selectRouteErrorMessage', 'Route is required'), | ||||||||||
}, | ||||||||||
), | ||||||||||
numRefills: z | ||||||||||
.number() | ||||||||||
.nullable() | ||||||||||
.refine( | ||||||||||
(value) => { | ||||||||||
if (requireOutpatientQuantity && (typeof value !== 'number' || value < 0)) { | ||||||||||
return false; | ||||||||||
} | ||||||||||
return true; | ||||||||||
}, | ||||||||||
patientInstructions: z.string().nullable(), | ||||||||||
asNeeded: z.boolean(), | ||||||||||
asNeededCondition: z.string().nullable(), | ||||||||||
duration: z.number().nullable(), | ||||||||||
durationUnit: z.object({ ...comboSchema }).nullable(), | ||||||||||
indication: !isIndicationFieldOptional | ||||||||||
? z.string().refine((value) => value !== '', { | ||||||||||
message: t('indicationErrorMessage', 'Indication is required'), | ||||||||||
}) | ||||||||||
: z.string().nullish(), | ||||||||||
startDate: z.date(), | ||||||||||
frequency: z.object( | ||||||||||
{ ...comboSchema }, | ||||||||||
{ | ||||||||||
message: t('numRefillsErrorMessage', 'Number of refills is required'), | ||||||||||
invalid_type_error: t('selectFrequencyErrorMessage', 'Frequency is required'), | ||||||||||
}, | ||||||||||
), | ||||||||||
}; | ||||||||||
|
||||||||||
const nonFreeTextDosageSchema = z.object({ | ||||||||||
...baseSchemaFields, | ||||||||||
...outpatientDrugOrderFields, | ||||||||||
isFreeTextDosage: z.literal(false), | ||||||||||
freeTextDosage: z.string().optional(), | ||||||||||
}); | ||||||||||
}; | ||||||||||
|
||||||||||
const freeTextDosageSchema = z.object({ | ||||||||||
...baseSchemaFields, | ||||||||||
...outpatientDrugOrderFields, | ||||||||||
isFreeTextDosage: z.literal(true), | ||||||||||
dosage: z.number().nullable(), | ||||||||||
unit: z.object(comboSchema).nullable(), | ||||||||||
route: z.object(comboSchema).nullable(), | ||||||||||
frequency: z.object(comboSchema).nullable(), | ||||||||||
}); | ||||||||||
const outpatientDrugOrderFields = { | ||||||||||
pillsDispensed: z | ||||||||||
.number() | ||||||||||
.nullable() | ||||||||||
.refine( | ||||||||||
(value) => { | ||||||||||
if (requireOutpatientQuantity && (typeof value !== 'number' || value < 1)) { | ||||||||||
return false; | ||||||||||
} | ||||||||||
return true; | ||||||||||
}, | ||||||||||
{ | ||||||||||
message: t('pillDispensedErrorMessage', 'Quantity to dispense is required'), | ||||||||||
}, | ||||||||||
), | ||||||||||
quantityUnits: z | ||||||||||
.object(comboSchema) | ||||||||||
.nullable() | ||||||||||
.refine( | ||||||||||
(value) => { | ||||||||||
if (requireOutpatientQuantity && !value) { | ||||||||||
return false; | ||||||||||
} | ||||||||||
return true; | ||||||||||
}, | ||||||||||
{ | ||||||||||
message: t('selectQuantityUnitsErrorMessage', 'Quantity unit is required'), | ||||||||||
}, | ||||||||||
), | ||||||||||
numRefills: z | ||||||||||
.number() | ||||||||||
.nullable() | ||||||||||
.refine( | ||||||||||
(value) => { | ||||||||||
if (requireOutpatientQuantity && (typeof value !== 'number' || value < 0)) { | ||||||||||
return false; | ||||||||||
} | ||||||||||
return true; | ||||||||||
}, | ||||||||||
{ | ||||||||||
message: t('numRefillsErrorMessage', 'Number of refills is required'), | ||||||||||
}, | ||||||||||
), | ||||||||||
}; | ||||||||||
|
||||||||||
return z.discriminatedUnion('isFreeTextDosage', [nonFreeTextDosageSchema, freeTextDosageSchema]); | ||||||||||
}; | ||||||||||
const nonFreeTextDosageSchema = z.object({ | ||||||||||
...baseSchemaFields, | ||||||||||
...outpatientDrugOrderFields, | ||||||||||
isFreeTextDosage: z.literal(false), | ||||||||||
freeTextDosage: z.string().optional(), | ||||||||||
}); | ||||||||||
|
||||||||||
const freeTextDosageSchema = z.object({ | ||||||||||
...baseSchemaFields, | ||||||||||
...outpatientDrugOrderFields, | ||||||||||
isFreeTextDosage: z.literal(true), | ||||||||||
dosage: z.number().nullable(), | ||||||||||
unit: z.object(comboSchema).nullable(), | ||||||||||
route: z.object(comboSchema).nullable(), | ||||||||||
frequency: z.object(comboSchema).nullable(), | ||||||||||
}); | ||||||||||
|
||||||||||
return z.discriminatedUnion('isFreeTextDosage', [nonFreeTextDosageSchema, freeTextDosageSchema]); | ||||||||||
}, [isIndicationFieldOptional, requireOutpatientQuantity, t]); | ||||||||||
|
||||||||||
return schema; | ||||||||||
} | ||||||||||
|
||||||||||
type MedicationOrderFormData = z.infer<ReturnType<typeof createMedicationOrderFormSchema>>; | ||||||||||
type MedicationOrderFormData = z.infer<ReturnType<typeof useCreateMedicationOrderFormSchema>>; | ||||||||||
|
||||||||||
function MedicationInfoHeader({ | ||||||||||
orderBasketItem, | ||||||||||
|
@@ -221,18 +231,14 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt | |||||||||
const config = useConfig<ConfigObject>(); | ||||||||||
const isTablet = useLayoutType() === 'tablet'; | ||||||||||
const { orderConfigObject, error: errorFetchingOrderConfig } = useOrderConfig(); | ||||||||||
const { requireOutpatientQuantity } = useRequireOutpatientQuantity(); | ||||||||||
|
||||||||||
const defaultStartDate = useMemo(() => { | ||||||||||
if (typeof initialOrderBasketItem?.startDate === 'string') parseDate(initialOrderBasketItem?.startDate); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Tangential to your changes, but this logic has a bug - the result of the So IMO, that line should change to: startDate: medication.dateActivated, Unless there's a specific reason for doing otherwise, @ibacher. We can address this in a separate PR. |
||||||||||
|
||||||||||
return initialOrderBasketItem?.startDate as Date; | ||||||||||
}, [initialOrderBasketItem?.startDate]); | ||||||||||
|
||||||||||
const medicationOrderFormSchema = useMemo( | ||||||||||
() => createMedicationOrderFormSchema(requireOutpatientQuantity, t), | ||||||||||
[requireOutpatientQuantity, t], | ||||||||||
); | ||||||||||
const medicationOrderFormSchema = useCreateMedicationOrderFormSchema(); | ||||||||||
|
||||||||||
const { | ||||||||||
control, | ||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -36,6 +36,11 @@ export const configSchema = { | |||||
'Number of milliseconds to delay the search operation in the drug search input by after the user starts typing. The useDebounce hook delays the search by 300ms by default', | ||||||
_default: 300, | ||||||
}, | ||||||
isIndicationFieldOptional: { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 for Positive phrasing is easier to grok (implies "we require this by default") as opposed to the double negative The validation logic reads better as well: // Current validation logic
if (!isIndicationFieldOptional) { /* require indication */ }
// With requireIndication
if (requireIndication) { /* require indication */ } |
||||||
_type: Type.Boolean, | ||||||
_description: 'Whether to make the indication field optional in the drug order form', | ||||||
vasharma05 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
_default: false, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
}, | ||||||
}; | ||||||
|
||||||
export interface ConfigObject { | ||||||
|
@@ -47,4 +52,5 @@ export interface ConfigObject { | |||||
showPrintButton: boolean; | ||||||
maxDispenseDurationInDays: number; | ||||||
debounceDelayInMs: number; | ||||||
isIndicationFieldOptional: boolean; | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,12 +46,10 @@ const MedicationRecord: React.FC<MedicationRecordProps> = ({ medication }) => { | |
{medication.dosingInstructions && <span> — {medication.dosingInstructions.toLocaleLowerCase()}</span>} | ||
</p> | ||
<p className={styles.bodyLong01}> | ||
{medication.orderReasonNonCoded ? ( | ||
<span> | ||
<span className={styles.label01}>{t('indication', 'Indication').toUpperCase()}</span>{' '} | ||
{medication.orderReasonNonCoded} | ||
</span> | ||
) : null} | ||
<span> | ||
<span className={styles.label01}>{t('indication', 'Indication').toUpperCase()}</span>{' '} | ||
{medication.orderReasonNonCoded ?? t('none', 'None')} | ||
</span> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually think the previous implementation here is better. Can we reverse this please? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually the |
||
{medication.quantity ? ( | ||
<span> | ||
<span className={styles.label01}> — {t('quantity', 'Quantity').toUpperCase()}</span>{' '} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -207,8 +207,9 @@ const OrderDetailsTable: React.FC<OrderDetailsProps> = ({ patientUuid, showAddBu | |
dosage: | ||
order.type === 'drugorder' ? ( | ||
<div className={styles.singleLineText}>{`${t('indication', 'Indication').toUpperCase()} | ||
${order.orderReasonNonCoded} ${'-'} ${t('quantity', 'Quantity').toUpperCase()} ${order.quantity} ${order | ||
?.quantityUnits?.display} `}</div> | ||
${order.orderReasonNonCoded ?? t('none', 'None')} ${'-'} ${t('quantity', 'Quantity').toUpperCase()} ${ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "No indication provided" |
||
order.quantity | ||
} ${order?.quantityUnits?.display} `}</div> | ||
) : ( | ||
'--' | ||
), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, be careful when placing text in front of users indicating the absence of data.