@openmrs/esm-patient-medications-app 11.3.1-patch.9064 → 11.3.1-patch.9508
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +22 -27
- package/dist/1022.js +1 -0
- package/dist/1022.js.map +1 -0
- package/dist/1119.js +1 -1
- package/dist/1197.js +1 -1
- package/dist/2146.js +1 -1
- package/dist/2690.js +1 -1
- package/dist/3099.js +1 -1
- package/dist/3338.js +2 -0
- package/dist/3338.js.map +1 -0
- package/dist/3584.js +1 -1
- package/dist/4055.js +1 -1
- package/dist/4132.js +1 -1
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/439.js +1 -0
- package/dist/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4944.js +1 -1
- package/dist/4953.js +1 -0
- package/dist/4953.js.map +1 -0
- package/dist/5134.js +2 -0
- package/dist/5134.js.map +1 -0
- package/dist/5173.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/5442.js +1 -1
- package/dist/5661.js +1 -1
- package/dist/5670.js +1 -1
- package/dist/5670.js.map +1 -1
- package/dist/6022.js +1 -1
- package/dist/6336.js +1 -0
- package/dist/6336.js.map +1 -0
- package/dist/6468.js +1 -1
- package/dist/6589.js +1 -0
- package/dist/6679.js +1 -1
- package/dist/6840.js +1 -1
- package/dist/6859.js +1 -1
- package/dist/7097.js +1 -1
- package/dist/7151.js +1 -0
- package/dist/7151.js.map +1 -0
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7617.js +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8349.js +1 -1
- package/dist/8371.js +1 -0
- package/dist/8437.js +1 -0
- package/dist/8437.js.map +1 -0
- package/dist/8618.js +1 -1
- package/dist/8803.js +1 -1
- package/dist/8803.js.map +1 -1
- package/dist/8812.js +1 -1
- package/dist/8812.js.map +1 -1
- package/dist/890.js +1 -1
- package/dist/9214.js +1 -1
- package/dist/9538.js +1 -1
- package/dist/9569.js +1 -1
- package/dist/986.js +1 -1
- package/dist/9879.js +1 -1
- package/dist/9895.js +1 -1
- package/dist/9900.js +1 -1
- package/dist/9913.js +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-medications-app.js +1 -1
- package/dist/openmrs-esm-patient-medications-app.js.buildmanifest.json +353 -259
- package/dist/openmrs-esm-patient-medications-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +4 -4
- package/src/active-medications/active-medications.component.tsx +1 -1
- package/src/active-medications/active-medications.test.tsx +1 -6
- package/src/add-drug-order/add-drug-order.scss +6 -0
- package/src/add-drug-order/add-drug-order.test.tsx +7 -4
- package/src/add-drug-order/add-drug-order.workspace.tsx +42 -15
- package/src/add-drug-order/drug-order-form.component.tsx +208 -222
- package/src/add-drug-order/drug-order-form.resource.ts +213 -0
- package/src/add-drug-order/drug-order-form.scss +11 -7
- package/src/add-drug-order/drug-search/drug-search-combobox.component.tsx +54 -0
- package/src/add-drug-order/drug-search/drug-search-combobox.test.tsx +56 -0
- package/src/add-drug-order/drug-search/drug-search.component.tsx +3 -1
- package/src/add-drug-order/drug-search/drug-search.resource.tsx +73 -17
- package/src/add-drug-order/drug-search/order-basket-search-results.component.tsx +16 -5
- package/src/add-drug-order/fill-prescription-form.workspace.tsx +111 -0
- package/src/api/api.ts +18 -1
- package/src/components/medications-details-table.component.tsx +3 -3
- package/src/config-schema.ts +15 -2
- package/src/drug-order-basket-panel/drug-order-basket-panel.extension.tsx +22 -5
- package/src/drug-order-basket-panel/drug-order-basket-panel.scss +27 -12
- package/src/drug-order-basket-panel/drug-order-basket-panel.test.tsx +11 -4
- package/src/index.ts +9 -1
- package/src/medications-summary/medications-summary.component.tsx +4 -2
- package/src/past-medications/past-medications.component.tsx +44 -0
- package/src/past-medications/past-medications.scss +7 -0
- package/src/past-medications/past-medications.test.tsx +85 -0
- package/src/routes.json +43 -1
- package/translations/am.json +24 -4
- package/translations/ar.json +24 -4
- package/translations/ar_SY.json +24 -4
- package/translations/bn.json +24 -4
- package/translations/cs.json +115 -0
- package/translations/de.json +24 -4
- package/translations/en.json +22 -4
- package/translations/en_US.json +24 -4
- package/translations/es.json +37 -17
- package/translations/es_MX.json +24 -4
- package/translations/fr.json +30 -10
- package/translations/he.json +24 -4
- package/translations/hi.json +24 -4
- package/translations/hi_IN.json +24 -4
- package/translations/id.json +33 -13
- package/translations/it.json +33 -13
- package/translations/ka.json +31 -11
- package/translations/km.json +24 -4
- package/translations/ku.json +24 -4
- package/translations/ky.json +24 -4
- package/translations/lg.json +24 -4
- package/translations/ne.json +24 -4
- package/translations/pl.json +24 -4
- package/translations/pt.json +33 -13
- package/translations/pt_BR.json +33 -13
- package/translations/qu.json +24 -4
- package/translations/ro_RO.json +33 -13
- package/translations/ru_RU.json +24 -4
- package/translations/si.json +24 -4
- package/translations/sq.json +115 -0
- package/translations/sw.json +24 -4
- package/translations/sw_KE.json +24 -4
- package/translations/tr.json +24 -4
- package/translations/tr_TR.json +24 -4
- package/translations/uk.json +24 -4
- package/translations/uz.json +24 -4
- package/translations/uz@Latn.json +24 -4
- package/translations/uz_UZ.json +24 -4
- package/translations/vi.json +24 -4
- package/translations/zh.json +25 -5
- package/translations/zh_CN.json +33 -13
- package/translations/zh_TW.json +115 -0
- package/dist/2537.js +0 -1
- package/dist/2537.js.map +0 -1
- package/dist/4918.js +0 -1
- package/dist/4918.js.map +0 -1
- package/dist/5837.js +0 -2
- package/dist/5837.js.map +0 -1
- package/dist/6783.js +0 -2
- package/dist/6783.js.map +0 -1
- package/dist/9365.js +0 -1
- package/dist/9365.js.map +0 -1
- package/dist/9647.js +0 -1
- package/dist/9647.js.map +0 -1
- /package/dist/{5837.js.LICENSE.txt → 3338.js.LICENSE.txt} +0 -0
- /package/dist/{6783.js.LICENSE.txt → 5134.js.LICENSE.txt} +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React, { type ChangeEvent, type ComponentProps, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import { useTranslation } from 'react-i18next';
|
|
3
2
|
import classNames from 'classnames';
|
|
4
3
|
import {
|
|
5
4
|
Button,
|
|
@@ -7,36 +6,38 @@ import {
|
|
|
7
6
|
Checkbox,
|
|
8
7
|
Column,
|
|
9
8
|
ComboBox,
|
|
10
|
-
IconButton,
|
|
11
9
|
Form,
|
|
12
10
|
FormGroup,
|
|
13
11
|
FormLabel,
|
|
14
12
|
Grid,
|
|
13
|
+
IconButton,
|
|
15
14
|
InlineNotification,
|
|
16
15
|
Layer,
|
|
17
16
|
NumberInput,
|
|
17
|
+
Stack,
|
|
18
18
|
TextArea,
|
|
19
19
|
TextInput,
|
|
20
20
|
Toggle,
|
|
21
21
|
} from '@carbon/react';
|
|
22
22
|
import { Subtract } from '@carbon/react/icons';
|
|
23
23
|
import { capitalize } from 'lodash-es';
|
|
24
|
+
import { useTranslation } from 'react-i18next';
|
|
25
|
+
import { type Control, Controller, type FieldErrors, useController } from 'react-hook-form';
|
|
24
26
|
import {
|
|
25
27
|
AddIcon,
|
|
26
28
|
age,
|
|
27
|
-
ArrowLeftIcon,
|
|
28
29
|
ExtensionSlot,
|
|
29
30
|
formatDate,
|
|
30
31
|
getPatientName,
|
|
31
32
|
OpenmrsDatePicker,
|
|
32
33
|
parseDate,
|
|
34
|
+
showSnackbar,
|
|
33
35
|
useConfig,
|
|
36
|
+
useFeatureFlag,
|
|
34
37
|
useLayoutType,
|
|
38
|
+
useSession,
|
|
35
39
|
} from '@openmrs/esm-framework';
|
|
36
|
-
import { type
|
|
37
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
38
|
-
import { usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
39
|
-
import { z } from 'zod';
|
|
40
|
+
import { type Drug } from '@openmrs/esm-patient-common-lib';
|
|
40
41
|
import { useOrderConfig } from '../api/order-config';
|
|
41
42
|
import { type ConfigObject } from '../config-schema';
|
|
42
43
|
import type {
|
|
@@ -48,143 +49,40 @@ import type {
|
|
|
48
49
|
MedicationRoute,
|
|
49
50
|
QuantityUnit,
|
|
50
51
|
} from '../types';
|
|
51
|
-
import {
|
|
52
|
+
import { type Provider, useActivePatientOrders, useProviders } from '../api';
|
|
53
|
+
import {
|
|
54
|
+
drugOrderBasketItemToFormValue,
|
|
55
|
+
type MedicationOrderFormData,
|
|
56
|
+
useDrugOrderForm,
|
|
57
|
+
} from './drug-order-form.resource';
|
|
58
|
+
import DrugSearchComboBox from './drug-search/drug-search-combobox.component';
|
|
52
59
|
import styles from './drug-order-form.scss';
|
|
53
60
|
|
|
54
61
|
export interface DrugOrderFormProps {
|
|
62
|
+
patientUuid: string;
|
|
55
63
|
initialOrderBasketItem: DrugOrderBasketItem;
|
|
56
|
-
|
|
64
|
+
patient: fhir.Patient;
|
|
65
|
+
onSave: (finalizedOrder: DrugOrderBasketItem) => Promise<void>;
|
|
66
|
+
saveButtonText: string;
|
|
57
67
|
onCancel: () => void;
|
|
58
68
|
promptBeforeClosing: (testFcn: () => boolean) => void;
|
|
59
|
-
}
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
default: z.boolean().optional(),
|
|
69
|
-
value: z.string(),
|
|
70
|
-
valueCoded: z.string(),
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const baseSchemaFields = {
|
|
74
|
-
freeTextDosage: z.string().refine((value) => !!value, {
|
|
75
|
-
message: t('freeDosageErrorMessage', 'Add free dosage note'),
|
|
76
|
-
}),
|
|
77
|
-
dosage: z.number({
|
|
78
|
-
invalid_type_error: t('dosageRequiredErrorMessage', 'Dosage is required'),
|
|
79
|
-
}),
|
|
80
|
-
unit: z.object(
|
|
81
|
-
{ ...comboSchema },
|
|
82
|
-
{
|
|
83
|
-
invalid_type_error: t('selectUnitErrorMessage', 'Dose unit is required'),
|
|
84
|
-
},
|
|
85
|
-
),
|
|
86
|
-
route: z.object(
|
|
87
|
-
{ ...comboSchema },
|
|
88
|
-
{
|
|
89
|
-
invalid_type_error: t('selectRouteErrorMessage', 'Route is required'),
|
|
90
|
-
},
|
|
91
|
-
),
|
|
92
|
-
patientInstructions: z.string().nullable(),
|
|
93
|
-
asNeeded: z.boolean(),
|
|
94
|
-
asNeededCondition: z.string().nullable(),
|
|
95
|
-
duration: z.number().nullable(),
|
|
96
|
-
durationUnit: z.object({ ...comboSchema }).nullable(),
|
|
97
|
-
indication: requireIndication
|
|
98
|
-
? z.string().refine((value) => value !== '', {
|
|
99
|
-
message: t('indicationErrorMessage', 'Indication is required'),
|
|
100
|
-
})
|
|
101
|
-
: z.string().nullish(),
|
|
102
|
-
startDate: z.date(),
|
|
103
|
-
frequency: z.object(
|
|
104
|
-
{ ...comboSchema },
|
|
105
|
-
{
|
|
106
|
-
invalid_type_error: t('selectFrequencyErrorMessage', 'Frequency is required'),
|
|
107
|
-
},
|
|
108
|
-
),
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const outpatientDrugOrderFields = {
|
|
112
|
-
pillsDispensed: z
|
|
113
|
-
.number()
|
|
114
|
-
.nullable()
|
|
115
|
-
.refine(
|
|
116
|
-
(value) => {
|
|
117
|
-
if (requireOutpatientQuantity && (typeof value !== 'number' || value < 1)) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
return true;
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
message: t('pillDispensedErrorMessage', 'Quantity to dispense is required'),
|
|
124
|
-
},
|
|
125
|
-
),
|
|
126
|
-
quantityUnits: z
|
|
127
|
-
.object(comboSchema)
|
|
128
|
-
.nullable()
|
|
129
|
-
.refine(
|
|
130
|
-
(value) => {
|
|
131
|
-
if (requireOutpatientQuantity && !value) {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
return true;
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
message: t('selectQuantityUnitsErrorMessage', 'Quantity unit is required'),
|
|
138
|
-
},
|
|
139
|
-
),
|
|
140
|
-
numRefills: z
|
|
141
|
-
.number()
|
|
142
|
-
.nullable()
|
|
143
|
-
.refine(
|
|
144
|
-
(value) => {
|
|
145
|
-
if (requireOutpatientQuantity && (typeof value !== 'number' || value < 0)) {
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
return true;
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
message: t('numRefillsErrorMessage', 'Number of refills is required'),
|
|
152
|
-
},
|
|
153
|
-
),
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const nonFreeTextDosageSchema = z.object({
|
|
157
|
-
...baseSchemaFields,
|
|
158
|
-
...outpatientDrugOrderFields,
|
|
159
|
-
isFreeTextDosage: z.literal(false),
|
|
160
|
-
freeTextDosage: z.string().optional(),
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
const freeTextDosageSchema = z.object({
|
|
164
|
-
...baseSchemaFields,
|
|
165
|
-
...outpatientDrugOrderFields,
|
|
166
|
-
isFreeTextDosage: z.literal(true),
|
|
167
|
-
dosage: z.number().nullable(),
|
|
168
|
-
unit: z.object(comboSchema).nullable(),
|
|
169
|
-
route: z.object(comboSchema).nullable(),
|
|
170
|
-
frequency: z.object(comboSchema).nullable(),
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
return z.discriminatedUnion('isFreeTextDosage', [nonFreeTextDosageSchema, freeTextDosageSchema]);
|
|
174
|
-
}, [requireIndication, requireOutpatientQuantity, t]);
|
|
175
|
-
|
|
176
|
-
return schema;
|
|
70
|
+
/**
|
|
71
|
+
* If true, allows user to select the prescribing provider when ordering medications from the order basket.
|
|
72
|
+
* (Otherwise, the prescribing provider always defaults to the current user.)
|
|
73
|
+
* The `prescriberProviderRoles` config must be set for this to work properly.
|
|
74
|
+
*/
|
|
75
|
+
allowSelectingPrescribingClinician: boolean;
|
|
76
|
+
allowSelectingDrug: boolean;
|
|
177
77
|
}
|
|
178
78
|
|
|
179
|
-
type MedicationOrderFormData = z.infer<ReturnType<typeof useCreateMedicationOrderFormSchema>>;
|
|
180
|
-
|
|
181
79
|
function MedicationInfoHeader({
|
|
182
|
-
|
|
80
|
+
drug,
|
|
183
81
|
routeValue,
|
|
184
82
|
unitValue,
|
|
185
83
|
dosage,
|
|
186
84
|
}: {
|
|
187
|
-
|
|
85
|
+
drug: Drug;
|
|
188
86
|
routeValue: string;
|
|
189
87
|
unitValue: string;
|
|
190
88
|
dosage: number | null;
|
|
@@ -194,11 +92,11 @@ function MedicationInfoHeader({
|
|
|
194
92
|
return (
|
|
195
93
|
<div className={styles.medicationInfo} id="medicationInfo">
|
|
196
94
|
<strong className={styles.productiveHeading02}>
|
|
197
|
-
{
|
|
95
|
+
{drug?.display} {drug?.strength && `(${drug?.strength})`}
|
|
198
96
|
</strong>{' '}
|
|
199
97
|
<span className={styles.bodyLong01}>
|
|
200
98
|
{routeValue && <>— {routeValue}</>}{' '}
|
|
201
|
-
{
|
|
99
|
+
{drug?.dosageForm?.display && <>— {drug?.dosageForm?.display}</>}{' '}
|
|
202
100
|
</span>
|
|
203
101
|
{dosage && unitValue ? (
|
|
204
102
|
<>
|
|
@@ -210,7 +108,7 @@ function MedicationInfoHeader({
|
|
|
210
108
|
</strong>
|
|
211
109
|
</>
|
|
212
110
|
) : null}{' '}
|
|
213
|
-
<ExtensionSlot name="medication-info-slot" state={{ order:
|
|
111
|
+
<ExtensionSlot name="medication-info-slot" state={{ order: { drug } as DrugOrderBasketItem }} />
|
|
214
112
|
</div>
|
|
215
113
|
);
|
|
216
114
|
}
|
|
@@ -224,54 +122,67 @@ function InputWrapper({ children }) {
|
|
|
224
122
|
);
|
|
225
123
|
}
|
|
226
124
|
|
|
227
|
-
export function DrugOrderForm({
|
|
125
|
+
export function DrugOrderForm({
|
|
126
|
+
initialOrderBasketItem,
|
|
127
|
+
patient,
|
|
128
|
+
onSave,
|
|
129
|
+
saveButtonText,
|
|
130
|
+
onCancel,
|
|
131
|
+
promptBeforeClosing,
|
|
132
|
+
allowSelectingPrescribingClinician,
|
|
133
|
+
allowSelectingDrug,
|
|
134
|
+
}: DrugOrderFormProps) {
|
|
228
135
|
const { t } = useTranslation();
|
|
229
|
-
const
|
|
136
|
+
const { daysDurationUnit, prescriberProviderRoles } = useConfig<ConfigObject>();
|
|
230
137
|
const isTablet = useLayoutType() === 'tablet';
|
|
231
138
|
const { orderConfigObject, error: errorFetchingOrderConfig } = useOrderConfig();
|
|
232
139
|
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return initialOrderBasketItem?.startDate as Date;
|
|
237
|
-
}, [initialOrderBasketItem?.startDate]);
|
|
238
|
-
|
|
239
|
-
const medicationOrderFormSchema = useCreateMedicationOrderFormSchema();
|
|
140
|
+
const isProviderManagementModuleInstalled = useFeatureFlag('providermanagement-module');
|
|
141
|
+
const allowAndSupportSelectingPrescribingClinician =
|
|
142
|
+
isProviderManagementModuleInstalled && allowSelectingPrescribingClinician;
|
|
240
143
|
|
|
144
|
+
const {
|
|
145
|
+
data: providers,
|
|
146
|
+
isLoading: isLoadingProviders,
|
|
147
|
+
error: errorLoadingProviders,
|
|
148
|
+
} = useProviders(allowAndSupportSelectingPrescribingClinician ? prescriberProviderRoles : null);
|
|
149
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
150
|
+
|
|
151
|
+
const { currentProvider } = useSession();
|
|
152
|
+
const defaultPrescribingProvider = allowAndSupportSelectingPrescribingClinician
|
|
153
|
+
? providers?.find((p) => p.uuid === currentProvider.uuid)
|
|
154
|
+
: currentProvider;
|
|
155
|
+
const drugOrderForm = useDrugOrderForm(initialOrderBasketItem, defaultPrescribingProvider?.uuid);
|
|
241
156
|
const {
|
|
242
157
|
control,
|
|
243
158
|
formState: { isDirty },
|
|
244
159
|
getValues,
|
|
160
|
+
reset,
|
|
245
161
|
handleSubmit,
|
|
246
162
|
setValue,
|
|
247
163
|
watch,
|
|
248
|
-
} =
|
|
249
|
-
mode: 'all',
|
|
250
|
-
resolver: zodResolver(medicationOrderFormSchema),
|
|
251
|
-
defaultValues: {
|
|
252
|
-
isFreeTextDosage: initialOrderBasketItem?.isFreeTextDosage,
|
|
253
|
-
freeTextDosage: initialOrderBasketItem?.freeTextDosage,
|
|
254
|
-
dosage: initialOrderBasketItem?.dosage ?? null,
|
|
255
|
-
unit: initialOrderBasketItem?.unit,
|
|
256
|
-
route: initialOrderBasketItem?.route,
|
|
257
|
-
patientInstructions: initialOrderBasketItem?.patientInstructions,
|
|
258
|
-
asNeeded: initialOrderBasketItem?.asNeeded,
|
|
259
|
-
asNeededCondition: initialOrderBasketItem?.asNeededCondition,
|
|
260
|
-
duration: initialOrderBasketItem?.duration,
|
|
261
|
-
durationUnit: initialOrderBasketItem?.durationUnit,
|
|
262
|
-
pillsDispensed: initialOrderBasketItem?.pillsDispensed ?? null,
|
|
263
|
-
quantityUnits: initialOrderBasketItem?.quantityUnits,
|
|
264
|
-
numRefills: initialOrderBasketItem?.numRefills ?? null,
|
|
265
|
-
indication: initialOrderBasketItem?.indication,
|
|
266
|
-
frequency: initialOrderBasketItem?.frequency,
|
|
267
|
-
startDate: defaultStartDate,
|
|
268
|
-
},
|
|
269
|
-
});
|
|
164
|
+
} = drugOrderForm;
|
|
270
165
|
|
|
271
166
|
useEffect(() => {
|
|
272
167
|
promptBeforeClosing(() => isDirty);
|
|
273
168
|
}, [isDirty, promptBeforeClosing]);
|
|
274
169
|
|
|
170
|
+
// reset the dosage information if set to free text dosage
|
|
171
|
+
const handleIsFreeTextDosageAfterChange = useCallback(
|
|
172
|
+
(newValue: MedicationOrderFormData['isFreeTextDosage']) => {
|
|
173
|
+
if (newValue) {
|
|
174
|
+
setValue('dosage', null, { shouldValidate: true });
|
|
175
|
+
setValue('unit', null, { shouldValidate: true });
|
|
176
|
+
setValue('route', null, { shouldValidate: true });
|
|
177
|
+
setValue('frequency', null, { shouldValidate: true });
|
|
178
|
+
setValue('patientInstructions', null, { shouldValidate: true });
|
|
179
|
+
} else {
|
|
180
|
+
setValue('freeTextDosage', null, { shouldValidate: true });
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
[setValue],
|
|
184
|
+
);
|
|
185
|
+
|
|
275
186
|
const handleUnitAfterChange = useCallback(
|
|
276
187
|
(newValue: MedicationOrderFormData['unit'], prevValue: MedicationOrderFormData['unit']) => {
|
|
277
188
|
if (prevValue?.valueCoded === getValues('quantityUnits')?.valueCoded) {
|
|
@@ -281,13 +192,18 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt
|
|
|
281
192
|
[setValue, getValues],
|
|
282
193
|
);
|
|
283
194
|
|
|
195
|
+
const drug = watch('drug') as Drug;
|
|
284
196
|
const routeValue = watch('route')?.value;
|
|
285
197
|
const unitValue = watch('unit')?.value;
|
|
286
198
|
const dosage = watch('dosage');
|
|
199
|
+
const startDate = watch('startDate');
|
|
287
200
|
|
|
288
|
-
const handleFormSubmission = (data: MedicationOrderFormData) => {
|
|
201
|
+
const handleFormSubmission = async (data: MedicationOrderFormData) => {
|
|
289
202
|
const newBasketItems = {
|
|
290
203
|
...initialOrderBasketItem,
|
|
204
|
+
drug: data.drug,
|
|
205
|
+
orderer: data.orderer.uuid,
|
|
206
|
+
careSetting: data.careSetting,
|
|
291
207
|
isFreeTextDosage: data.isFreeTextDosage,
|
|
292
208
|
freeTextDosage: data.freeTextDosage,
|
|
293
209
|
dosage: data.dosage,
|
|
@@ -304,8 +220,23 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt
|
|
|
304
220
|
indication: data.indication,
|
|
305
221
|
frequency: data.frequency,
|
|
306
222
|
startDate: data.startDate,
|
|
307
|
-
};
|
|
308
|
-
|
|
223
|
+
} as DrugOrderBasketItem;
|
|
224
|
+
setIsSaving(true);
|
|
225
|
+
await onSave(newBasketItems);
|
|
226
|
+
setIsSaving(false);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const handleFormSubmissionError = (errors: FieldErrors<MedicationOrderFormData>) => {
|
|
230
|
+
if (errors) {
|
|
231
|
+
console.error('Error in drug order form', errors);
|
|
232
|
+
showSnackbar({
|
|
233
|
+
title: t('drugOrderValidationFailed', 'Validation failed'),
|
|
234
|
+
subtitle: t('drugOrderValidationFailedDescription', 'Please check the form for errors and try again.'),
|
|
235
|
+
kind: 'error',
|
|
236
|
+
timeoutInMs: 5000,
|
|
237
|
+
isLowContrast: true,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
309
240
|
};
|
|
310
241
|
|
|
311
242
|
const drugDosingUnits: Array<DosingUnit> = useMemo(
|
|
@@ -336,11 +267,11 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt
|
|
|
336
267
|
() =>
|
|
337
268
|
orderConfigObject?.durationUnits ?? [
|
|
338
269
|
{
|
|
339
|
-
valueCoded:
|
|
340
|
-
value:
|
|
270
|
+
valueCoded: daysDurationUnit?.uuid,
|
|
271
|
+
value: daysDurationUnit?.display,
|
|
341
272
|
},
|
|
342
273
|
],
|
|
343
|
-
[orderConfigObject,
|
|
274
|
+
[orderConfigObject, daysDurationUnit],
|
|
344
275
|
);
|
|
345
276
|
|
|
346
277
|
const orderFrequencies: Array<MedicationFrequency> = useMemo(() => {
|
|
@@ -351,6 +282,10 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt
|
|
|
351
282
|
return menu?.item?.value?.toLowerCase().includes(menu?.inputValue?.toLowerCase());
|
|
352
283
|
}, []);
|
|
353
284
|
|
|
285
|
+
const filterItemsByProviderName = useCallback((menu) => {
|
|
286
|
+
return menu?.item?.person?.display?.toLowerCase().includes(menu?.inputValue?.toLowerCase());
|
|
287
|
+
}, []);
|
|
288
|
+
|
|
354
289
|
const filterItemsBySynonymNames = useCallback((menu) => {
|
|
355
290
|
if (menu?.inputValue?.length) {
|
|
356
291
|
return menu.item?.names?.some((abbr: string) => abbr.toLowerCase().includes(menu.inputValue.toLowerCase()));
|
|
@@ -359,7 +294,6 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt
|
|
|
359
294
|
}, []);
|
|
360
295
|
|
|
361
296
|
const [showStickyMedicationHeader, setShowMedicationHeader] = useState(false);
|
|
362
|
-
const { patient } = usePatientChartStore();
|
|
363
297
|
const patientName = patient ? getPatientName(patient) : '';
|
|
364
298
|
|
|
365
299
|
const observer = useRef(null);
|
|
@@ -384,30 +318,38 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt
|
|
|
384
318
|
},
|
|
385
319
|
[setShowMedicationHeader],
|
|
386
320
|
);
|
|
321
|
+
const {
|
|
322
|
+
fieldState: { error: drugFieldError },
|
|
323
|
+
} = useController<MedicationOrderFormData>({ name: 'drug', control });
|
|
324
|
+
|
|
325
|
+
// TODO: use the backend instead of this to determine whether the drug formulation can be ordered
|
|
326
|
+
// See: https://openmrs.atlassian.net/browse/RESTWS-1003
|
|
327
|
+
const { data: activeOrders } = useActivePatientOrders(patient.id);
|
|
328
|
+
const drugAlreadyPrescribedForNewOrder = useMemo(
|
|
329
|
+
() => initialOrderBasketItem.action == 'NEW' && activeOrders?.some((order) => order?.drug?.uuid === drug?.uuid),
|
|
330
|
+
[activeOrders, drug, initialOrderBasketItem.action],
|
|
331
|
+
);
|
|
387
332
|
|
|
388
333
|
return (
|
|
389
334
|
<div className={styles.container}>
|
|
390
335
|
{showStickyMedicationHeader && (
|
|
391
336
|
<div className={styles.stickyMedicationInfo}>
|
|
392
|
-
<MedicationInfoHeader
|
|
393
|
-
dosage={dosage}
|
|
394
|
-
orderBasketItem={initialOrderBasketItem}
|
|
395
|
-
routeValue={routeValue}
|
|
396
|
-
unitValue={unitValue}
|
|
397
|
-
/>
|
|
337
|
+
<MedicationInfoHeader dosage={dosage} drug={drug} routeValue={routeValue} unitValue={unitValue} />
|
|
398
338
|
</div>
|
|
399
339
|
)}
|
|
400
|
-
{
|
|
401
|
-
<
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
340
|
+
<div className={styles.patientHeader}>
|
|
341
|
+
<span className={styles.bodyShort02}>{patientName}</span>
|
|
342
|
+
<span className={classNames(styles.text02, styles.bodyShort01)}>
|
|
343
|
+
{capitalize(patient?.gender)} · {age(patient?.birthDate)} ·{' '}
|
|
344
|
+
<span>{formatDate(parseDate(patient?.birthDate), { mode: 'wide', time: false })}</span>
|
|
345
|
+
</span>
|
|
346
|
+
</div>
|
|
347
|
+
<ExtensionSlot name="allergy-list-pills-slot" state={{ patientUuid: patient?.id }} />
|
|
348
|
+
<Form
|
|
349
|
+
className={styles.orderForm}
|
|
350
|
+
onSubmit={handleSubmit(handleFormSubmission, handleFormSubmissionError)}
|
|
351
|
+
id="drugOrderForm"
|
|
352
|
+
>
|
|
411
353
|
<div>
|
|
412
354
|
{errorFetchingOrderConfig && (
|
|
413
355
|
<InlineNotification
|
|
@@ -418,36 +360,73 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt
|
|
|
418
360
|
subtitle={t('tryReopeningTheForm', 'Please try launching the form again')}
|
|
419
361
|
/>
|
|
420
362
|
)}
|
|
421
|
-
{!isTablet && (
|
|
422
|
-
<div>
|
|
423
|
-
<div className={styles.backButton}>
|
|
424
|
-
<Button
|
|
425
|
-
kind="ghost"
|
|
426
|
-
renderIcon={(props: ComponentProps<typeof ArrowLeftIcon>) => <ArrowLeftIcon size={24} {...props} />}
|
|
427
|
-
iconDescription="Return to order basket"
|
|
428
|
-
size="sm"
|
|
429
|
-
onClick={onCancel}
|
|
430
|
-
>
|
|
431
|
-
<span>{t('backToOrderBasket', 'Back to order basket')}</span>
|
|
432
|
-
</Button>
|
|
433
|
-
</div>
|
|
434
|
-
<ExtensionSlot name="allergy-list-pills-slot" state={{ patientUuid: patient?.id }} />
|
|
435
|
-
</div>
|
|
436
|
-
)}
|
|
437
|
-
|
|
438
363
|
<h1 className={styles.orderFormHeading}>{t('orderForm', 'Order Form')}</h1>
|
|
364
|
+
{(allowSelectingDrug || allowSelectingPrescribingClinician) && (
|
|
365
|
+
<section className={styles.formSection}>
|
|
366
|
+
<h3 className={styles.sectionHeader}>{t('prescriptionInfo', 'Prescription info')}</h3>
|
|
367
|
+
<Stack gap={5}>
|
|
368
|
+
{allowSelectingDrug && (
|
|
369
|
+
<InputWrapper>
|
|
370
|
+
<DrugSearchComboBox
|
|
371
|
+
setSelectedDrugItem={(item) => {
|
|
372
|
+
// when selecting a new drug, it can have its own order template that populates many fields
|
|
373
|
+
// we should just reset the entire form
|
|
374
|
+
reset(drugOrderBasketItemToFormValue(item, startDate, currentProvider.uuid));
|
|
375
|
+
}}
|
|
376
|
+
/>
|
|
377
|
+
{drugAlreadyPrescribedForNewOrder && (
|
|
378
|
+
<FormLabel className={styles.errorLabel}>
|
|
379
|
+
{t('activePrescriptionExists', 'Active prescription exists for this drug')}
|
|
380
|
+
</FormLabel>
|
|
381
|
+
)}
|
|
382
|
+
<FormLabel className={styles.errorLabel}>{drugFieldError?.message}</FormLabel>
|
|
383
|
+
</InputWrapper>
|
|
384
|
+
)}
|
|
385
|
+
{allowAndSupportSelectingPrescribingClinician &&
|
|
386
|
+
!isLoadingProviders &&
|
|
387
|
+
(providers?.length > 0 ? (
|
|
388
|
+
<ControlledFieldInput
|
|
389
|
+
control={control}
|
|
390
|
+
name="orderer"
|
|
391
|
+
type="comboBox"
|
|
392
|
+
getValues={getValues}
|
|
393
|
+
id="orderer"
|
|
394
|
+
shouldFilterItem={filterItemsByProviderName}
|
|
395
|
+
placeholder={t('prescribingClinician', 'Prescribing Clinician')}
|
|
396
|
+
titleText={t('prescribingClinician', 'Prescribing Clinician')}
|
|
397
|
+
items={providers}
|
|
398
|
+
itemToString={(item: Provider) => item?.person?.display}
|
|
399
|
+
/>
|
|
400
|
+
) : errorLoadingProviders ? (
|
|
401
|
+
<InlineNotification
|
|
402
|
+
kind="warning"
|
|
403
|
+
lowContrast
|
|
404
|
+
className={styles.inlineNotification}
|
|
405
|
+
title={t('errorLoadingClinicians', 'Error loading clinicians')}
|
|
406
|
+
subtitle={t('tryReopeningTheForm', 'Please try launching the form again')}
|
|
407
|
+
/>
|
|
408
|
+
) : (
|
|
409
|
+
<InlineNotification
|
|
410
|
+
kind="warning"
|
|
411
|
+
lowContrast
|
|
412
|
+
className={styles.inlineNotification}
|
|
413
|
+
title={t('noCliniciansFound', 'No clinicians found')}
|
|
414
|
+
subtitle={t(
|
|
415
|
+
'noCliniciansFoundDescription',
|
|
416
|
+
'Cannot select prescribing clinician because no clinicians with appropriate roles are found. Check configuration.',
|
|
417
|
+
)}
|
|
418
|
+
/>
|
|
419
|
+
))}
|
|
420
|
+
</Stack>
|
|
421
|
+
</section>
|
|
422
|
+
)}
|
|
439
423
|
<div ref={medicationInfoHeaderRef}>
|
|
440
|
-
<MedicationInfoHeader
|
|
441
|
-
dosage={dosage}
|
|
442
|
-
orderBasketItem={initialOrderBasketItem}
|
|
443
|
-
routeValue={routeValue}
|
|
444
|
-
unitValue={unitValue}
|
|
445
|
-
/>
|
|
424
|
+
<MedicationInfoHeader dosage={dosage} drug={drug} routeValue={routeValue} unitValue={unitValue} />
|
|
446
425
|
</div>
|
|
447
426
|
<section className={styles.formSection}>
|
|
448
427
|
<Grid className={styles.gridRow}>
|
|
449
428
|
<Column lg={12} md={6} sm={4}>
|
|
450
|
-
<h3 className={styles.sectionHeader}>{t('dosageInstructions', '
|
|
429
|
+
<h3 className={styles.sectionHeader}>{t('dosageInstructions', 'Dosage instructions')}</h3>
|
|
451
430
|
</Column>
|
|
452
431
|
<Column className={styles.freeTextDosageToggle} lg={4} md={2} sm={4}>
|
|
453
432
|
<ControlledFieldInput
|
|
@@ -458,6 +437,7 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt
|
|
|
458
437
|
id="freeTextDosageToggle"
|
|
459
438
|
aria-label={t('freeTextDosage', 'Free text dosage')}
|
|
460
439
|
labelText={t('freeTextDosage', 'Free text dosage')}
|
|
440
|
+
handleAfterChange={handleIsFreeTextDosageAfterChange}
|
|
461
441
|
/>
|
|
462
442
|
</Column>
|
|
463
443
|
</Grid>
|
|
@@ -599,7 +579,7 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt
|
|
|
599
579
|
)}
|
|
600
580
|
</section>
|
|
601
581
|
<section className={styles.formSection}>
|
|
602
|
-
<h3 className={styles.sectionHeader}>{t('prescriptionDuration', '
|
|
582
|
+
<h3 className={styles.sectionHeader}>{t('prescriptionDuration', 'Prescription duration')}</h3>
|
|
603
583
|
<Grid className={styles.gridRow}>
|
|
604
584
|
{/* TODO: This input does nothing */}
|
|
605
585
|
<Column lg={16} md={4} sm={4}>
|
|
@@ -665,7 +645,7 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt
|
|
|
665
645
|
</Grid>
|
|
666
646
|
</section>
|
|
667
647
|
<section className={styles.formSection}>
|
|
668
|
-
<h3 className={styles.sectionHeader}>{t('dispensingInformation', '
|
|
648
|
+
<h3 className={styles.sectionHeader}>{t('dispensingInformation', 'Dispensing instructions')}</h3>
|
|
669
649
|
<Grid className={styles.gridRow}>
|
|
670
650
|
<Column lg={8} md={3} sm={4}>
|
|
671
651
|
<InputWrapper>
|
|
@@ -739,9 +719,7 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt
|
|
|
739
719
|
</section>
|
|
740
720
|
</div>
|
|
741
721
|
|
|
742
|
-
<ButtonSet
|
|
743
|
-
className={classNames(styles.buttonSet, isTablet ? styles.tabletButtonSet : styles.desktopButtonSet)}
|
|
744
|
-
>
|
|
722
|
+
<ButtonSet className={styles.buttonSet}>
|
|
745
723
|
<Button className={styles.button} kind="secondary" onClick={onCancel} size="xl">
|
|
746
724
|
{t('discard', 'Discard')}
|
|
747
725
|
</Button>
|
|
@@ -750,9 +728,9 @@ export function DrugOrderForm({ initialOrderBasketItem, onSave, onCancel, prompt
|
|
|
750
728
|
kind="primary"
|
|
751
729
|
type="submit"
|
|
752
730
|
size="xl"
|
|
753
|
-
disabled={!!errorFetchingOrderConfig}
|
|
731
|
+
disabled={!!errorFetchingOrderConfig || isSaving || drugAlreadyPrescribedForNewOrder}
|
|
754
732
|
>
|
|
755
|
-
{
|
|
733
|
+
{saveButtonText}
|
|
756
734
|
</Button>
|
|
757
735
|
</ButtonSet>
|
|
758
736
|
</Form>
|
|
@@ -795,7 +773,9 @@ const CustomNumberInput = ({ setValue, control, name, labelText, isTablet, ...in
|
|
|
795
773
|
|
|
796
774
|
return (
|
|
797
775
|
<div className={styles.customElement}>
|
|
798
|
-
<span className="cds--label"
|
|
776
|
+
<span className="cds--label" id={`${name}-label`}>
|
|
777
|
+
{labelText}
|
|
778
|
+
</span>
|
|
799
779
|
<div className={styles.customNumberInput}>
|
|
800
780
|
<IconButton onClick={decrement} label={t('decrement', 'Decrement')} size={responsiveSize}>
|
|
801
781
|
<Subtract size={16} />
|
|
@@ -809,6 +789,7 @@ const CustomNumberInput = ({ setValue, control, name, labelText, isTablet, ...in
|
|
|
809
789
|
size={responsiveSize}
|
|
810
790
|
id={name}
|
|
811
791
|
labelText=""
|
|
792
|
+
aria-labelledby={`${name}-label`}
|
|
812
793
|
{...inputProps}
|
|
813
794
|
/>
|
|
814
795
|
<IconButton onClick={increment} label={t('increment', 'Increment')} size={responsiveSize}>
|
|
@@ -847,12 +828,12 @@ const ControlledFieldInput = ({
|
|
|
847
828
|
handleAfterChange,
|
|
848
829
|
...restProps
|
|
849
830
|
}: ControlledFieldInputProps) => {
|
|
831
|
+
const { t } = useTranslation();
|
|
850
832
|
const {
|
|
851
833
|
field: { onBlur, onChange, value, ref },
|
|
852
834
|
fieldState: { error },
|
|
853
835
|
} = useController<MedicationOrderFormData>({ name, control });
|
|
854
836
|
const isTablet = useLayoutType() === 'tablet';
|
|
855
|
-
const responsiveSize = isTablet ? 'md' : 'sm';
|
|
856
837
|
|
|
857
838
|
const fieldErrorStyles = classNames({
|
|
858
839
|
[styles.fieldError]: error?.message,
|
|
@@ -876,6 +857,8 @@ const ControlledFieldInput = ({
|
|
|
876
857
|
ref={ref}
|
|
877
858
|
// @ts-ignore
|
|
878
859
|
size={isTablet ? 'md' : 'sm'}
|
|
860
|
+
labelA={t('on', 'On')}
|
|
861
|
+
labelB={t('off', 'Off')}
|
|
879
862
|
{...restProps}
|
|
880
863
|
/>
|
|
881
864
|
);
|
|
@@ -953,13 +936,14 @@ const ControlledFieldInput = ({
|
|
|
953
936
|
ref={ref}
|
|
954
937
|
size={isTablet ? 'md' : 'sm'}
|
|
955
938
|
selectedItem={value}
|
|
939
|
+
initialSelectedItem={value}
|
|
956
940
|
{...comboBoxProps}
|
|
957
941
|
/>
|
|
958
942
|
);
|
|
959
943
|
}
|
|
960
944
|
|
|
961
945
|
return null;
|
|
962
|
-
}, [type, value, restProps, handleChange, fieldErrorStyles, onBlur, ref, isTablet]);
|
|
946
|
+
}, [type, value, restProps, handleChange, fieldErrorStyles, onBlur, ref, isTablet, t]);
|
|
963
947
|
|
|
964
948
|
return (
|
|
965
949
|
<>
|
|
@@ -968,3 +952,5 @@ const ControlledFieldInput = ({
|
|
|
968
952
|
</>
|
|
969
953
|
);
|
|
970
954
|
};
|
|
955
|
+
|
|
956
|
+
export default DrugOrderForm;
|