@ampath/esm-dispensing-app 1.10.0-next.1

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.
Files changed (277) hide show
  1. package/.editorconfig +12 -0
  2. package/.eslintignore +2 -0
  3. package/.eslintrc +80 -0
  4. package/.husky/pre-commit +7 -0
  5. package/.husky/pre-push +6 -0
  6. package/.prettierignore +12 -0
  7. package/.tx/config +11 -0
  8. package/.yarn/versions/1c40b9b6.yml +0 -0
  9. package/.yarn/versions/ff162597.yml +0 -0
  10. package/LICENSE +401 -0
  11. package/README.md +124 -0
  12. package/__mocks__/react-i18next.js +51 -0
  13. package/dist/1043.js +1 -0
  14. package/dist/1043.js.map +1 -0
  15. package/dist/1119.js +1 -0
  16. package/dist/1197.js +1 -0
  17. package/dist/2146.js +1 -0
  18. package/dist/2177.js +2 -0
  19. package/dist/2177.js.LICENSE.txt +9 -0
  20. package/dist/2177.js.map +1 -0
  21. package/dist/2690.js +1 -0
  22. package/dist/2890.js +2 -0
  23. package/dist/2890.js.LICENSE.txt +14 -0
  24. package/dist/2890.js.map +1 -0
  25. package/dist/2898.js +1 -0
  26. package/dist/2898.js.map +1 -0
  27. package/dist/3041.js +1 -0
  28. package/dist/3041.js.map +1 -0
  29. package/dist/3099.js +1 -0
  30. package/dist/3184.js +2 -0
  31. package/dist/3184.js.LICENSE.txt +14 -0
  32. package/dist/3184.js.map +1 -0
  33. package/dist/3568.js +1 -0
  34. package/dist/3568.js.map +1 -0
  35. package/dist/3584.js +1 -0
  36. package/dist/4055.js +1 -0
  37. package/dist/4099.js +1 -0
  38. package/dist/4099.js.map +1 -0
  39. package/dist/4132.js +1 -0
  40. package/dist/4225.js +1 -0
  41. package/dist/4225.js.map +1 -0
  42. package/dist/4300.js +1 -0
  43. package/dist/4335.js +1 -0
  44. package/dist/4353.js +1 -0
  45. package/dist/4353.js.map +1 -0
  46. package/dist/439.js +1 -0
  47. package/dist/4618.js +1 -0
  48. package/dist/4652.js +1 -0
  49. package/dist/4944.js +1 -0
  50. package/dist/5173.js +1 -0
  51. package/dist/5241.js +1 -0
  52. package/dist/5422.js +1 -0
  53. package/dist/5422.js.map +1 -0
  54. package/dist/5442.js +1 -0
  55. package/dist/5661.js +1 -0
  56. package/dist/5897.js +1 -0
  57. package/dist/5897.js.map +1 -0
  58. package/dist/6022.js +1 -0
  59. package/dist/609.js +1 -0
  60. package/dist/609.js.map +1 -0
  61. package/dist/6468.js +1 -0
  62. package/dist/6540.js +2 -0
  63. package/dist/6540.js.LICENSE.txt +9 -0
  64. package/dist/6540.js.map +1 -0
  65. package/dist/6589.js +1 -0
  66. package/dist/6606.js +1 -0
  67. package/dist/6606.js.map +1 -0
  68. package/dist/6679.js +1 -0
  69. package/dist/6825.js +1 -0
  70. package/dist/6825.js.map +1 -0
  71. package/dist/6840.js +1 -0
  72. package/dist/6841.js +1 -0
  73. package/dist/6841.js.map +1 -0
  74. package/dist/6859.js +1 -0
  75. package/dist/7097.js +1 -0
  76. package/dist/7159.js +1 -0
  77. package/dist/723.js +1 -0
  78. package/dist/7240.js +1 -0
  79. package/dist/7240.js.map +1 -0
  80. package/dist/7255.js +1 -0
  81. package/dist/7255.js.map +1 -0
  82. package/dist/7617.js +1 -0
  83. package/dist/795.js +1 -0
  84. package/dist/8163.js +1 -0
  85. package/dist/8349.js +1 -0
  86. package/dist/8371.js +1 -0
  87. package/dist/8569.js +2 -0
  88. package/dist/8569.js.LICENSE.txt +41 -0
  89. package/dist/8569.js.map +1 -0
  90. package/dist/8600.js +1 -0
  91. package/dist/8600.js.map +1 -0
  92. package/dist/8618.js +1 -0
  93. package/dist/8885.js +1 -0
  94. package/dist/8885.js.map +1 -0
  95. package/dist/890.js +1 -0
  96. package/dist/9214.js +1 -0
  97. package/dist/9538.js +1 -0
  98. package/dist/9569.js +1 -0
  99. package/dist/961.js +2 -0
  100. package/dist/961.js.LICENSE.txt +19 -0
  101. package/dist/961.js.map +1 -0
  102. package/dist/963.js +1 -0
  103. package/dist/963.js.map +1 -0
  104. package/dist/986.js +1 -0
  105. package/dist/9879.js +1 -0
  106. package/dist/9895.js +1 -0
  107. package/dist/9900.js +1 -0
  108. package/dist/9913.js +1 -0
  109. package/dist/main.js +2 -0
  110. package/dist/main.js.LICENSE.txt +51 -0
  111. package/dist/main.js.map +1 -0
  112. package/dist/openmrs-esm-dispensing-app.js +1 -0
  113. package/dist/openmrs-esm-dispensing-app.js.buildmanifest.json +1645 -0
  114. package/dist/openmrs-esm-dispensing-app.js.map +1 -0
  115. package/dist/routes.json +1 -0
  116. package/e2e/README.md +119 -0
  117. package/e2e/commands/drug-order-operations.ts +43 -0
  118. package/e2e/commands/encounter-operations.ts +60 -0
  119. package/e2e/commands/index.ts +5 -0
  120. package/e2e/commands/patient-operations.ts +109 -0
  121. package/e2e/commands/provider-operations.ts +9 -0
  122. package/e2e/commands/types/index.ts +157 -0
  123. package/e2e/commands/visit-operations.ts +38 -0
  124. package/e2e/core/global-setup.ts +32 -0
  125. package/e2e/core/index.ts +1 -0
  126. package/e2e/core/test.ts +31 -0
  127. package/e2e/fixtures/api.ts +27 -0
  128. package/e2e/fixtures/index.ts +1 -0
  129. package/e2e/pages/dispensing-page.ts +9 -0
  130. package/e2e/pages/index.ts +1 -0
  131. package/e2e/specs/active-prescriptions.spec.ts +72 -0
  132. package/e2e/specs/close-prescription.spec.ts +71 -0
  133. package/e2e/specs/dispense-medication.spec.ts +71 -0
  134. package/e2e/specs/pause-prescription.spec.ts +72 -0
  135. package/e2e/support/github/Dockerfile +34 -0
  136. package/e2e/support/github/docker-compose.yml +24 -0
  137. package/e2e/support/github/run-e2e-docker-env.sh +42 -0
  138. package/e2e/types/index.ts +157 -0
  139. package/example.env +7 -0
  140. package/jest.config.js +24 -0
  141. package/package.json +110 -0
  142. package/playwright.config.ts +36 -0
  143. package/prettier.config.js +8 -0
  144. package/src/components/action-buttons.component.tsx +87 -0
  145. package/src/components/action-buttons.scss +16 -0
  146. package/src/components/action-buttons.test.tsx +217 -0
  147. package/src/components/medication-card.component.tsx +37 -0
  148. package/src/components/medication-card.scss +20 -0
  149. package/src/components/medication-card.test.tsx +36 -0
  150. package/src/components/medication-dispense-review.scss +108 -0
  151. package/src/components/medication-event.component.tsx +96 -0
  152. package/src/components/medication-event.scss +44 -0
  153. package/src/components/medication-event.test.tsx +212 -0
  154. package/src/components/prescription-actions/close-action-button.component.tsx +50 -0
  155. package/src/components/prescription-actions/dispense-action-button.component.tsx +57 -0
  156. package/src/components/prescription-actions/pause-action-button.component.tsx +49 -0
  157. package/src/conditions/conditions.component.tsx +118 -0
  158. package/src/conditions/conditions.resource.ts +100 -0
  159. package/src/conditions/conditions.scss +26 -0
  160. package/src/conditions/conditions.test.tsx +200 -0
  161. package/src/config-schema.ts +192 -0
  162. package/src/constants.ts +22 -0
  163. package/src/dashboard/dispensing-dashboard-link.component.tsx +36 -0
  164. package/src/dashboard/dispensing-dashboard.component.tsx +36 -0
  165. package/src/declarations.d.ts +2 -0
  166. package/src/diagnoses/diagnoses.component.tsx +111 -0
  167. package/src/diagnoses/diagnoses.resource.ts +30 -0
  168. package/src/diagnoses/diagnoses.scss +31 -0
  169. package/src/dispensing-link.component.tsx +9 -0
  170. package/src/dispensing-tiles/dispensing-tile.component.tsx +42 -0
  171. package/src/dispensing-tiles/dispensing-tile.scss +43 -0
  172. package/src/dispensing-tiles/dispensing-tiles.component.tsx +39 -0
  173. package/src/dispensing-tiles/dispensing-tiles.resource.tsx +30 -0
  174. package/src/dispensing-tiles/dispensing-tiles.scss +11 -0
  175. package/src/dispensing.component.tsx +31 -0
  176. package/src/dispensing.scss +5 -0
  177. package/src/dispensing.test.tsx +9 -0
  178. package/src/fill-prescription/fill-prescription-button.component.tsx +103 -0
  179. package/src/fill-prescription/fill-prescription-button.scss +8 -0
  180. package/src/fill-prescription/on-prescription-filled.modal.tsx +140 -0
  181. package/src/fill-prescription/on-prescription-filled.scss +7 -0
  182. package/src/forms/close-dispense-form.workspace.tsx +194 -0
  183. package/src/forms/dispense-form.workspace.test.tsx +334 -0
  184. package/src/forms/dispense-form.workspace.tsx +324 -0
  185. package/src/forms/forms.scss +152 -0
  186. package/src/forms/medication-dispense-review.component.tsx +649 -0
  187. package/src/forms/medication-dispense-review.test.tsx +158 -0
  188. package/src/forms/pause-dispense-form.workspace.tsx +196 -0
  189. package/src/forms/stock-dispense/stock-dispense.component.tsx +126 -0
  190. package/src/forms/stock-dispense/stock.resource.tsx +67 -0
  191. package/src/history/delete-confirm.modal.tsx +35 -0
  192. package/src/history/history-and-comments.component.tsx +338 -0
  193. package/src/history/history-and-comments.scss +54 -0
  194. package/src/index.ts +57 -0
  195. package/src/location/location.resource.test.tsx +108 -0
  196. package/src/location/location.resource.tsx +32 -0
  197. package/src/medication/medication.resource.test.tsx +156 -0
  198. package/src/medication/medication.resource.tsx +45 -0
  199. package/src/medication-dispense/medication-dispense.resource.test.tsx +243 -0
  200. package/src/medication-dispense/medication-dispense.resource.tsx +178 -0
  201. package/src/medication-request/medication-request.resource.test.tsx +1333 -0
  202. package/src/medication-request/medication-request.resource.tsx +257 -0
  203. package/src/patient/patient-info-cell.component.tsx +26 -0
  204. package/src/patient/patient.resources.ts +14 -0
  205. package/src/pharmacy-header/pharmacy-header.component.tsx +35 -0
  206. package/src/pharmacy-header/pharmacy-header.scss +55 -0
  207. package/src/pharmacy-header/pharmacy-illustration.component.tsx +30 -0
  208. package/src/prescriptions/patient-search-tab-panel.component.tsx +58 -0
  209. package/src/prescriptions/patient-search-tab-panel.scss +26 -0
  210. package/src/prescriptions/prescription-actions.component.tsx +24 -0
  211. package/src/prescriptions/prescription-actions.scss +14 -0
  212. package/src/prescriptions/prescription-details.component.tsx +152 -0
  213. package/src/prescriptions/prescription-details.scss +87 -0
  214. package/src/prescriptions/prescription-details.test.tsx +267 -0
  215. package/src/prescriptions/prescription-expanded.component.tsx +58 -0
  216. package/src/prescriptions/prescription-expanded.scss +56 -0
  217. package/src/prescriptions/prescription-tab-lists.component.tsx +70 -0
  218. package/src/prescriptions/prescription-tab-panel.component.tsx +83 -0
  219. package/src/prescriptions/prescriptions-table.component.tsx +189 -0
  220. package/src/prescriptions/prescriptions.scss +152 -0
  221. package/src/print-prescription/prescription-print-action.component.tsx +30 -0
  222. package/src/print-prescription/prescription-print-preview.modal.tsx +92 -0
  223. package/src/print-prescription/prescription-printout.component.tsx +154 -0
  224. package/src/print-prescription/print-prescription.scss +75 -0
  225. package/src/print-prescription/printable-prescriptions.component.tsx +57 -0
  226. package/src/routes.json +137 -0
  227. package/src/types.ts +530 -0
  228. package/src/utils.test.ts +2947 -0
  229. package/src/utils.ts +637 -0
  230. package/tools/i18next-parser.config.js +89 -0
  231. package/tools/setup-tests.ts +8 -0
  232. package/tools/update-openmrs-deps.mjs +42 -0
  233. package/translations/am.json +133 -0
  234. package/translations/ar.json +133 -0
  235. package/translations/ar_SY.json +133 -0
  236. package/translations/bn.json +133 -0
  237. package/translations/cs.json +133 -0
  238. package/translations/de.json +133 -0
  239. package/translations/en.json +133 -0
  240. package/translations/en_US.json +133 -0
  241. package/translations/es.json +133 -0
  242. package/translations/es_MX.json +133 -0
  243. package/translations/fr.json +133 -0
  244. package/translations/he.json +133 -0
  245. package/translations/hi.json +133 -0
  246. package/translations/hi_IN.json +133 -0
  247. package/translations/id.json +133 -0
  248. package/translations/it.json +133 -0
  249. package/translations/ka.json +133 -0
  250. package/translations/km.json +133 -0
  251. package/translations/ku.json +133 -0
  252. package/translations/ky.json +133 -0
  253. package/translations/lg.json +133 -0
  254. package/translations/ne.json +133 -0
  255. package/translations/pl.json +133 -0
  256. package/translations/pt.json +133 -0
  257. package/translations/pt_BR.json +133 -0
  258. package/translations/qu.json +133 -0
  259. package/translations/ro_RO.json +133 -0
  260. package/translations/ru_RU.json +133 -0
  261. package/translations/si.json +133 -0
  262. package/translations/sq.json +133 -0
  263. package/translations/sw.json +133 -0
  264. package/translations/sw_KE.json +133 -0
  265. package/translations/tr.json +133 -0
  266. package/translations/tr_TR.json +133 -0
  267. package/translations/uk.json +133 -0
  268. package/translations/uz.json +133 -0
  269. package/translations/uz@Latn.json +133 -0
  270. package/translations/uz_UZ.json +133 -0
  271. package/translations/vi.json +133 -0
  272. package/translations/zh.json +133 -0
  273. package/translations/zh_CN.json +133 -0
  274. package/translations/zh_TW.json +133 -0
  275. package/tsconfig.json +23 -0
  276. package/turbo.json +41 -0
  277. package/webpack.config.js +1 -0
@@ -0,0 +1,649 @@
1
+ import React, { useEffect, useState, useMemo } from 'react';
2
+ import dayjs from 'dayjs';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { ComboBox, Dropdown, NumberInput, Stack, TextArea, Toggle } from '@carbon/react';
5
+ import { OpenmrsDatePicker, useConfig, useSession, userHasAccess, ResponsiveWrapper } from '@openmrs/esm-framework';
6
+ import { getConceptCodingUuid, getMedicationReferenceOrCodeableConcept, getOpenMRSMedicineDrugName } from '../utils';
7
+ import { useMedicationCodeableConcept, useMedicationFormulations } from '../medication/medication.resource';
8
+ import { useMedicationRequest, usePrescriptionDetails } from '../medication-request/medication-request.resource';
9
+ import {
10
+ blankSubstitution,
11
+ useOrderConfig,
12
+ useProviders,
13
+ useSubstitutionReasonValueSet,
14
+ useSubstitutionTypeValueSet,
15
+ } from '../medication-dispense/medication-dispense.resource';
16
+ import { PRIVILEGE_CREATE_DISPENSE_MODIFY_DETAILS } from '../constants';
17
+ import { type Medication, type MedicationDispense } from '../types';
18
+ import { type PharmacyConfig } from '../config-schema';
19
+ import MedicationCard from '../components/medication-card.component';
20
+ import styles from '../components/medication-dispense-review.scss';
21
+
22
+ interface MedicationDispenseReviewProps {
23
+ medicationDispense: MedicationDispense;
24
+ updateMedicationDispense(updates: Partial<MedicationDispense>);
25
+ isFreeTextDosage: boolean;
26
+ setIsFreeTextDosage: (isFreeTextDosage: boolean) => void;
27
+ quantityRemaining: number;
28
+ quantityDispensed: number;
29
+ }
30
+
31
+ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
32
+ medicationDispense,
33
+ updateMedicationDispense,
34
+ isFreeTextDosage,
35
+ setIsFreeTextDosage,
36
+ quantityRemaining,
37
+ quantityDispensed,
38
+ }) => {
39
+ const { t } = useTranslation();
40
+ const config = useConfig<PharmacyConfig>();
41
+ const session = useSession();
42
+ const [isEditingFormulation, setIsEditingFormulation] = useState(false);
43
+ // type of substitution question
44
+ const [substitutionTypes, setSubstitutionTypes] = useState([]);
45
+ // reason for substitution question
46
+ const [substitutionReasons, setSubstitutionReasons] = useState([]);
47
+ //quantity prescribed
48
+ const [quantityPrescribed] = useState(medicationDispense.quantity.value);
49
+ const [userCanModify, setUserCanModify] = useState(false);
50
+
51
+ const allowEditing = config.dispenseBehavior.allowModifyingPrescription;
52
+
53
+ const { orderConfigObject } = useOrderConfig();
54
+ const { substitutionTypeValueSet } = useSubstitutionTypeValueSet(config.valueSets.substitutionType.uuid);
55
+ const { substitutionReasonValueSet } = useSubstitutionReasonValueSet(config.valueSets.substitutionReason.uuid);
56
+ const providers = useProviders(config.dispenserProviderRoles);
57
+
58
+ // get the medication request associated with this dispense event
59
+ // (we fetch this so that we can use it below to determine if the formulation dispensed varies from what was
60
+ // ordered; it's slightly inefficient/awkward to fetch it from the server here because we *have* fetched it earlier,
61
+ // it just seems cleaner to fetch it here rather than to make sure we pass it down through various components; with
62
+ // SWR handling caching, we may want to consider pulling more down into this)
63
+ const { medicationRequest } = useMedicationRequest(
64
+ medicationDispense.authorizingPrescription ? medicationDispense.authorizingPrescription[0].reference : null,
65
+ config.refreshInterval,
66
+ );
67
+
68
+ // we fetch this just to get the prescription date
69
+ const { prescriptionDate } = usePrescriptionDetails(medicationRequest ? medicationRequest.encounter.reference : null);
70
+
71
+ const { drugRoutes, drugDosingUnits, drugDispensingUnits, orderFrequencies } = useMemo(() => {
72
+ if (orderConfigObject) {
73
+ // Dosing Unit eg Tablets
74
+ const drugRoutes = orderConfigObject.drugRoutes.map((x) => ({ id: x.uuid, text: x.display }));
75
+ // Dispensing Unit eg Tablets
76
+ const drugDosingUnits = orderConfigObject.drugDosingUnits.map((x) => ({ id: x.uuid, text: x.display }));
77
+ // Route eg Oral
78
+ const drugDispensingUnits = orderConfigObject.drugDispensingUnits.map((x) => ({ id: x.uuid, text: x.display }));
79
+ // Frequency eg Twice daily
80
+ const orderFrequencies = orderConfigObject.orderFrequencies.map((x) => ({ id: x.uuid, text: x.display }));
81
+ return { drugRoutes, drugDosingUnits, drugDispensingUnits, orderFrequencies };
82
+ } else {
83
+ return { drugRoutes: [], drugDosingUnits: [], drugDispensingUnits: [], orderFrequencies: [] };
84
+ }
85
+ }, [orderConfigObject]);
86
+
87
+ useEffect(() => {
88
+ const substitutionTypeOptions = [];
89
+
90
+ if (substitutionTypeValueSet?.compose?.include) {
91
+ const uuidValueSet = substitutionTypeValueSet.compose.include.find((include) => !include.system);
92
+ if (uuidValueSet) {
93
+ uuidValueSet.concept?.forEach((concept) =>
94
+ substitutionTypeOptions.push({
95
+ id: concept.code,
96
+ text: concept.display,
97
+ }),
98
+ );
99
+ }
100
+ substitutionTypeOptions.sort((a, b) => a.text.localeCompare(b.text));
101
+ }
102
+ setSubstitutionTypes(substitutionTypeOptions);
103
+ }, [substitutionTypeValueSet]);
104
+
105
+ useEffect(() => {
106
+ const substitutionReasonOptions = [];
107
+
108
+ if (substitutionReasonValueSet?.compose?.include) {
109
+ const uuidValueSet = substitutionReasonValueSet.compose.include.find((include) => !include.system);
110
+ if (uuidValueSet) {
111
+ uuidValueSet.concept?.forEach((concept) =>
112
+ substitutionReasonOptions.push({
113
+ id: concept.code,
114
+ text: concept.display,
115
+ }),
116
+ );
117
+ }
118
+ substitutionReasonOptions.sort((a, b) => a.text.localeCompare(b.text));
119
+ }
120
+ setSubstitutionReasons(substitutionReasonOptions);
121
+ }, [substitutionReasonValueSet]);
122
+
123
+ // if this an order for a drug by concept, but not a particular formulation, we already have access to concept uuid
124
+ const existingMedicationCodeableConceptUuid = getConceptCodingUuid(
125
+ getMedicationReferenceOrCodeableConcept(medicationDispense)?.medicationCodeableConcept?.coding,
126
+ );
127
+ // otherwise we need to fetch the medication details to get the codeable concept
128
+ // (note that React Hooks should not be called conditionally, so we *always* call this hook, but it will return null if existingMedicationCodeableConcept is defined
129
+ const { medicationCodeableConceptUuid } = useMedicationCodeableConcept(
130
+ existingMedicationCodeableConceptUuid,
131
+ getMedicationReferenceOrCodeableConcept(medicationDispense).medicationReference?.reference,
132
+ );
133
+ // get the formulations
134
+ const { medicationFormulations } = useMedicationFormulations(
135
+ existingMedicationCodeableConceptUuid
136
+ ? existingMedicationCodeableConceptUuid
137
+ : medicationCodeableConceptUuid
138
+ ? medicationCodeableConceptUuid
139
+ : null,
140
+ );
141
+
142
+ // check to see if the current dispense would be a substitution, and update accordingly
143
+ const isSubstitution =
144
+ medicationRequest?.medicationReference?.reference &&
145
+ medicationDispense?.medicationReference?.reference &&
146
+ medicationRequest.medicationReference.reference != medicationDispense.medicationReference.reference;
147
+
148
+ const { substitution } = medicationDispense;
149
+ useEffect(() => {
150
+ if (isSubstitution) {
151
+ // make sure that the value substitution.wasSubstituted exists and is truthy
152
+ if (!substitution.wasSubstituted) {
153
+ const newSubstitution = { ...substitution, wasSubstituted: true };
154
+ updateMedicationDispense({ substitution: newSubstitution });
155
+ }
156
+ } else {
157
+ if (substitution != blankSubstitution) {
158
+ updateMedicationDispense({ substitution: blankSubstitution });
159
+ }
160
+ }
161
+ }, [isSubstitution, substitution, updateMedicationDispense]);
162
+
163
+ useEffect(() => {
164
+ setUserCanModify(session?.user && userHasAccess(PRIVILEGE_CREATE_DISPENSE_MODIFY_DETAILS, session.user));
165
+ }, [session]);
166
+
167
+ const providersPlusCurrentUser = useMemo(() => {
168
+ const currentUserInProviderList = providers.some((provider) => provider.uuid === session?.currentProvider?.uuid);
169
+ return currentUserInProviderList
170
+ ? providers
171
+ : [
172
+ ...providers,
173
+ {
174
+ uuid: session.currentProvider.uuid,
175
+ person: {
176
+ display: session?.user?.person?.display ?? '',
177
+ },
178
+ },
179
+ ];
180
+ }, [providers, session]);
181
+
182
+ const initialDispenser = useMemo(() => {
183
+ return medicationDispense?.performer?.[0]?.actor?.reference
184
+ ? providersPlusCurrentUser?.find(
185
+ (provider) => provider.uuid === medicationDispense.performer[0].actor.reference.split('/')[1],
186
+ )
187
+ : undefined;
188
+ }, [medicationDispense?.performer, providersPlusCurrentUser]);
189
+ useEffect(() => {
190
+ if (initialDispenser?.uuid) {
191
+ updateMedicationDispense({
192
+ performer: [
193
+ {
194
+ actor: {
195
+ reference: `Practitioner/${initialDispenser.uuid}`,
196
+ },
197
+ },
198
+ ],
199
+ });
200
+ }
201
+ }, [initialDispenser, updateMedicationDispense]);
202
+
203
+ return (
204
+ <div className={styles.medicationDispenseReviewContainer}>
205
+ <Stack gap={5}>
206
+ <div className={styles.medicationCardContainer}>
207
+ {!isEditingFormulation ? (
208
+ <MedicationCard
209
+ medication={getMedicationReferenceOrCodeableConcept(medicationDispense)}
210
+ editAction={userCanModify && allowEditing ? () => setIsEditingFormulation(true) : null}
211
+ />
212
+ ) : (
213
+ <ResponsiveWrapper>
214
+ <Dropdown
215
+ id="medicationFormulation"
216
+ items={medicationFormulations}
217
+ itemToString={(item: Medication) => getOpenMRSMedicineDrugName(item)}
218
+ initialSelectedItem={{
219
+ ...medicationFormulations?.find(
220
+ (formulation) => formulation.id === medicationDispense.medicationReference?.reference.split('/')[1],
221
+ ),
222
+ }}
223
+ titleText={t('medicationFormulation', 'Medication Formulation')}
224
+ label={t('medicationFormulation', 'Medication Formulation')}
225
+ onChange={({ selectedItem }) => {
226
+ const typedItem = selectedItem as Medication;
227
+ if ('Medication/' + typedItem.id !== medicationDispense.medicationReference.reference) {
228
+ updateMedicationDispense({
229
+ medicationCodeableConcept: undefined,
230
+ medicationReference: {
231
+ reference: 'Medication/' + typedItem?.id,
232
+ display: getOpenMRSMedicineDrugName(typedItem),
233
+ },
234
+ quantity: {
235
+ ...medicationDispense.quantity,
236
+ value: 0,
237
+ },
238
+ ...(isFreeTextDosage
239
+ ? {}
240
+ : {
241
+ dosageInstruction: [
242
+ {
243
+ ...medicationDispense.dosageInstruction[0],
244
+ doseAndRate: [
245
+ {
246
+ ...medicationDispense.dosageInstruction[0].doseAndRate[0],
247
+ doseQuantity: {
248
+ ...medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity,
249
+ value: 0,
250
+ },
251
+ },
252
+ ],
253
+ },
254
+ ],
255
+ }),
256
+ });
257
+ }
258
+ setIsEditingFormulation(false);
259
+ }}
260
+ />
261
+ </ResponsiveWrapper>
262
+ )}
263
+ </div>
264
+
265
+ {isSubstitution && (
266
+ <div className={styles.dispenseDetailsContainer}>
267
+ <ResponsiveWrapper>
268
+ <ComboBox
269
+ className={styles.substitutionType}
270
+ id="substitutionType"
271
+ items={substitutionTypes}
272
+ titleText={t('substitutionType', 'Type of substitution')}
273
+ itemToString={(item) => item?.text}
274
+ initialSelectedItem={{
275
+ id: medicationDispense.substitution.type?.coding[0]?.code,
276
+ text: medicationDispense.substitution.type?.text,
277
+ }}
278
+ onChange={({ selectedItem }) => {
279
+ updateMedicationDispense({
280
+ substitution: {
281
+ ...medicationDispense.substitution,
282
+ type: {
283
+ coding: [
284
+ {
285
+ code: selectedItem?.id,
286
+ },
287
+ ],
288
+ },
289
+ },
290
+ });
291
+ }}
292
+ />
293
+ </ResponsiveWrapper>
294
+ </div>
295
+ )}
296
+
297
+ {isSubstitution && (
298
+ <div className={styles.dispenseDetailsContainer}>
299
+ <ResponsiveWrapper>
300
+ <ComboBox
301
+ className={styles.substitutionReason}
302
+ id="substitutionReason"
303
+ items={substitutionReasons}
304
+ titleText={t('substitutionReason', 'Reason for substitution')}
305
+ itemToString={(item) => item?.text}
306
+ initialSelectedItem={{
307
+ id: medicationDispense.substitution.reason[0]?.coding[0]?.code,
308
+ text: medicationDispense.substitution.reason[0]?.text,
309
+ }}
310
+ onChange={({ selectedItem }) => {
311
+ updateMedicationDispense({
312
+ substitution: {
313
+ ...medicationDispense.substitution,
314
+ reason: [
315
+ {
316
+ coding: [
317
+ {
318
+ code: selectedItem?.id,
319
+ },
320
+ ],
321
+ },
322
+ ],
323
+ },
324
+ });
325
+ }}
326
+ />
327
+ </ResponsiveWrapper>
328
+ </div>
329
+ )}
330
+ {config.dispenseBehavior.restrictTotalQuantityDispensed ? (
331
+ <ResponsiveWrapper>
332
+ <div>
333
+ <p className={styles.quantitySummary}>
334
+ {t('quantityPrescribed', 'Quantity Prescribed')}: {quantityPrescribed}
335
+ </p>
336
+ <p className={styles.quantitySummary}>
337
+ {t('quantityDispensed', 'Quantity Dispensed')}: {quantityDispensed}
338
+ </p>
339
+ <p className={styles.quantitySummary}>
340
+ {t('quantityRemaining', 'Quantity Remaining to Dispense')}: {quantityRemaining}
341
+ </p>
342
+ </div>
343
+ </ResponsiveWrapper>
344
+ ) : null}
345
+
346
+ <div className={styles.dispenseDetailsContainer}>
347
+ <NumberInput
348
+ allowEmpty={true}
349
+ value={medicationDispense.quantity.value}
350
+ disabled={!userCanModify}
351
+ hideSteppers={true}
352
+ id="quantity"
353
+ invalidText={t('numberIsNotValid', 'Number is not valid')}
354
+ label={
355
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>{t('quantity', 'Quantity')}</div>
356
+ }
357
+ min={0}
358
+ max={config.dispenseBehavior.restrictTotalQuantityDispensed ? quantityRemaining : undefined}
359
+ onChange={(event, state) => {
360
+ updateMedicationDispense({
361
+ quantity: {
362
+ ...medicationDispense.quantity,
363
+ value: state.value ? parseFloat(state.value.toString()) : 0,
364
+ },
365
+ });
366
+ }}
367
+ />
368
+ <ResponsiveWrapper>
369
+ <ComboBox
370
+ id="quantityUnits"
371
+ disabled={!userCanModify || !allowEditing}
372
+ items={drugDispensingUnits}
373
+ titleText={t('drugDispensingUnit', 'Dispensing unit')}
374
+ itemToString={(item) => item?.text}
375
+ initialSelectedItem={{
376
+ id: medicationDispense.quantity.code,
377
+ text: medicationDispense.quantity.unit,
378
+ }}
379
+ onChange={({ selectedItem }) => {
380
+ updateMedicationDispense({
381
+ // note that we specifically recreate doseQuantity to overwrite any unit or system properties that may have been set
382
+ quantity: {
383
+ value: medicationDispense.quantity.value,
384
+ code: selectedItem?.id,
385
+ },
386
+ });
387
+ }}
388
+ required
389
+ />
390
+ </ResponsiveWrapper>
391
+ </div>
392
+
393
+ <div className={styles.dispenseDetailsContainer}>
394
+ <ResponsiveWrapper>
395
+ <Toggle
396
+ toggled={isFreeTextDosage}
397
+ disabled={!userCanModify || !allowEditing}
398
+ id="isFreeTextToggled"
399
+ size={'sm'}
400
+ labelText={t('freeTextDosage', 'Free text dosage')}
401
+ onToggle={(value) => {
402
+ setIsFreeTextDosage(value);
403
+ // clear out the dose, route and frequency if changed to free text dosage
404
+ if (value) {
405
+ updateMedicationDispense({
406
+ dosageInstruction: [
407
+ {
408
+ ...medicationDispense.dosageInstruction[0],
409
+ doseAndRate: [
410
+ {
411
+ doseQuantity: {
412
+ value: null,
413
+ code: null,
414
+ },
415
+ },
416
+ ],
417
+ route: {
418
+ coding: [
419
+ {
420
+ code: null,
421
+ },
422
+ ],
423
+ },
424
+ timing: {
425
+ ...medicationDispense.dosageInstruction[0].timing,
426
+ code: {
427
+ coding: [
428
+ {
429
+ code: null,
430
+ },
431
+ ],
432
+ },
433
+ },
434
+ text: '',
435
+ },
436
+ ],
437
+ });
438
+ } else {
439
+ // clear out the text field if changed from free text to coded dosage
440
+ updateMedicationDispense({
441
+ dosageInstruction: [
442
+ {
443
+ ...medicationDispense.dosageInstruction[0],
444
+ text: '',
445
+ },
446
+ ],
447
+ });
448
+ }
449
+ }}
450
+ />
451
+ </ResponsiveWrapper>
452
+ </div>
453
+
454
+ {!isFreeTextDosage && (
455
+ <>
456
+ <div className={styles.dispenseDetailsContainer}>
457
+ <NumberInput
458
+ allowEmpty={false}
459
+ disabled={!userCanModify || !allowEditing}
460
+ hideSteppers={true}
461
+ id="dosingQuantity"
462
+ invalidText={t('numberIsNotValid', 'Number is not valid')}
463
+ min={0}
464
+ label={t('dose', 'Dose')}
465
+ value={medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity.value}
466
+ onChange={(event, state) => {
467
+ updateMedicationDispense({
468
+ dosageInstruction: [
469
+ {
470
+ ...medicationDispense.dosageInstruction[0],
471
+ doseAndRate: [
472
+ {
473
+ ...medicationDispense.dosageInstruction[0].doseAndRate[0],
474
+ doseQuantity: {
475
+ ...medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity,
476
+ value: state.value ? parseFloat(state.value.toString()) : 0,
477
+ },
478
+ },
479
+ ],
480
+ },
481
+ ],
482
+ });
483
+ }}
484
+ />
485
+ <ResponsiveWrapper>
486
+ <ComboBox
487
+ id="dosingUnits"
488
+ disabled={!userCanModify || !allowEditing}
489
+ items={drugDosingUnits}
490
+ titleText={t('doseUnit', 'Dose unit')}
491
+ itemToString={(item) => item?.text}
492
+ initialSelectedItem={{
493
+ id: medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity?.code,
494
+ text: medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity?.unit,
495
+ }}
496
+ onChange={({ selectedItem }) => {
497
+ updateMedicationDispense({
498
+ dosageInstruction: [
499
+ {
500
+ ...medicationDispense.dosageInstruction[0],
501
+ doseAndRate: [
502
+ {
503
+ doseQuantity: {
504
+ // note that we specifically recreate doseQuantity to overwrite any unit or system properties that may have been set
505
+ value: medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity?.value,
506
+ code: selectedItem?.id,
507
+ },
508
+ },
509
+ ],
510
+ },
511
+ ],
512
+ });
513
+ }}
514
+ required
515
+ />
516
+ </ResponsiveWrapper>
517
+ </div>
518
+ <ResponsiveWrapper>
519
+ <ComboBox
520
+ id="editRoute"
521
+ disabled={!userCanModify || !allowEditing}
522
+ items={drugRoutes}
523
+ initialSelectedItem={{
524
+ id: medicationDispense.dosageInstruction[0].route?.coding[0]?.code,
525
+ text: medicationDispense.dosageInstruction[0].route?.text,
526
+ }}
527
+ titleText={t('route', 'Route')}
528
+ itemToString={(item) => item?.text}
529
+ onChange={({ selectedItem }) => {
530
+ updateMedicationDispense({
531
+ dosageInstruction: [
532
+ {
533
+ ...medicationDispense.dosageInstruction[0],
534
+ route: {
535
+ coding: [
536
+ {
537
+ code: selectedItem?.id,
538
+ },
539
+ ],
540
+ },
541
+ },
542
+ ],
543
+ });
544
+ }}
545
+ required
546
+ />
547
+ </ResponsiveWrapper>
548
+ <ResponsiveWrapper>
549
+ <ComboBox
550
+ id="frequency"
551
+ disabled={!userCanModify || !allowEditing}
552
+ items={orderFrequencies}
553
+ initialSelectedItem={{
554
+ id: medicationDispense.dosageInstruction[0].timing?.code?.coding[0]?.code,
555
+ text: medicationDispense.dosageInstruction[0].timing?.code?.text,
556
+ }}
557
+ titleText={t('frequency', 'Frequency')}
558
+ itemToString={(item) => item?.text}
559
+ onChange={({ selectedItem }) => {
560
+ updateMedicationDispense({
561
+ dosageInstruction: [
562
+ {
563
+ ...medicationDispense.dosageInstruction[0],
564
+ timing: {
565
+ ...medicationDispense.dosageInstruction[0].timing,
566
+ code: {
567
+ coding: [
568
+ {
569
+ code: selectedItem?.id,
570
+ },
571
+ ],
572
+ },
573
+ },
574
+ },
575
+ ],
576
+ });
577
+ }}
578
+ required
579
+ />
580
+ </ResponsiveWrapper>
581
+ </>
582
+ )}
583
+
584
+ <TextArea
585
+ labelText={
586
+ isFreeTextDosage
587
+ ? t('freeTextDosage', 'Free text dosage')
588
+ : t('patientInstructions', 'Patient instructions')
589
+ }
590
+ value={medicationDispense.dosageInstruction[0].text}
591
+ maxLength={65535}
592
+ onChange={(e) => {
593
+ updateMedicationDispense({
594
+ dosageInstruction: [
595
+ {
596
+ ...medicationDispense.dosageInstruction[0],
597
+ text: e.target.value,
598
+ },
599
+ ],
600
+ });
601
+ }}
602
+ />
603
+
604
+ <OpenmrsDatePicker
605
+ id="dispenseDate"
606
+ labelText={t('dispenseDate', 'Date of Dispense')}
607
+ minDate={prescriptionDate ? dayjs(prescriptionDate).startOf('day').toDate() : null}
608
+ maxDate={dayjs().toDate()}
609
+ onChange={(input) => {
610
+ const currentDate = medicationDispense.whenHandedOver ? dayjs(medicationDispense.whenHandedOver) : null;
611
+ const selectedDate = dayjs(input);
612
+ updateMedicationDispense({
613
+ whenHandedOver: currentDate?.isSame(selectedDate, 'day')
614
+ ? currentDate.toISOString()
615
+ : selectedDate.toISOString(), // to preserve any time component, only update if the day actually changes
616
+ });
617
+ }}
618
+ value={dayjs(medicationDispense.whenHandedOver).toDate()}></OpenmrsDatePicker>
619
+
620
+ {providersPlusCurrentUser && (
621
+ <ResponsiveWrapper>
622
+ <ComboBox
623
+ id="dispenser"
624
+ initialSelectedItem={initialDispenser}
625
+ onChange={({ selectedItem }) => {
626
+ const dispenserUuid = selectedItem ? selectedItem.uuid : session.currentProvider.uuid;
627
+ updateMedicationDispense({
628
+ performer: [
629
+ {
630
+ actor: {
631
+ reference: `Practitioner/${dispenserUuid}`,
632
+ },
633
+ },
634
+ ],
635
+ });
636
+ }}
637
+ items={providersPlusCurrentUser}
638
+ itemToString={(item) => item?.person?.display}
639
+ required
640
+ titleText={t('dispensedBy', 'Dispensed by')}
641
+ />
642
+ </ResponsiveWrapper>
643
+ )}
644
+ </Stack>
645
+ </div>
646
+ );
647
+ };
648
+
649
+ export default MedicationDispenseReview;