@openmrs/esm-dispensing-app 1.9.1 → 1.9.2-pre.1002

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 (179) hide show
  1. package/.eslintrc +8 -0
  2. package/.turbo/cache/f95995cd573a4ae0-meta.json +1 -0
  3. package/.turbo/cache/f95995cd573a4ae0.tar.zst +0 -0
  4. package/.turbo/turbo-build.log +38 -0
  5. package/README.md +14 -4
  6. package/dist/1119.js +1 -1
  7. package/dist/1197.js +1 -1
  8. package/dist/1856.js +1 -0
  9. package/dist/1856.js.map +1 -0
  10. package/dist/2146.js +1 -1
  11. package/dist/2690.js +1 -1
  12. package/dist/2898.js +1 -1
  13. package/dist/2898.js.map +1 -1
  14. package/dist/3099.js +1 -1
  15. package/dist/3584.js +1 -1
  16. package/dist/4055.js +1 -1
  17. package/dist/4099.js +1 -1
  18. package/dist/4099.js.map +1 -1
  19. package/dist/4132.js +1 -1
  20. package/dist/4300.js +1 -1
  21. package/dist/4335.js +1 -1
  22. package/dist/445.js +2 -0
  23. package/dist/445.js.map +1 -0
  24. package/dist/4618.js +1 -1
  25. package/dist/4652.js +1 -1
  26. package/dist/4944.js +1 -1
  27. package/dist/4999.js +2 -0
  28. package/dist/4999.js.map +1 -0
  29. package/dist/5173.js +1 -1
  30. package/dist/5241.js +1 -1
  31. package/dist/5379.js +1 -0
  32. package/dist/5379.js.map +1 -0
  33. package/dist/5442.js +1 -1
  34. package/dist/561.js +1 -0
  35. package/dist/561.js.map +1 -0
  36. package/dist/5661.js +1 -1
  37. package/dist/5897.js +1 -1
  38. package/dist/5897.js.map +1 -1
  39. package/dist/6022.js +1 -1
  40. package/dist/609.js +1 -1
  41. package/dist/609.js.map +1 -1
  42. package/dist/6468.js +1 -1
  43. package/dist/6540.js +1 -1
  44. package/dist/6540.js.map +1 -1
  45. package/dist/6679.js +1 -1
  46. package/dist/67.js +1 -1
  47. package/dist/67.js.map +1 -1
  48. package/dist/6840.js +1 -1
  49. package/dist/6841.js +1 -1
  50. package/dist/6841.js.map +1 -1
  51. package/dist/6859.js +1 -1
  52. package/dist/7097.js +1 -1
  53. package/dist/7159.js +1 -1
  54. package/dist/723.js +1 -1
  55. package/dist/7255.js +1 -0
  56. package/dist/7255.js.map +1 -0
  57. package/dist/7617.js +1 -1
  58. package/dist/795.js +1 -1
  59. package/dist/8163.js +1 -1
  60. package/dist/8349.js +1 -1
  61. package/dist/8600.js +1 -1
  62. package/dist/8600.js.map +1 -1
  63. package/dist/8618.js +1 -1
  64. package/dist/890.js +1 -1
  65. package/dist/9214.js +1 -1
  66. package/dist/9538.js +1 -1
  67. package/dist/9569.js +1 -1
  68. package/dist/961.js +1 -1
  69. package/dist/961.js.map +1 -1
  70. package/dist/963.js +1 -1
  71. package/dist/963.js.map +1 -1
  72. package/dist/986.js +1 -1
  73. package/dist/9879.js +1 -1
  74. package/dist/9895.js +1 -1
  75. package/dist/9900.js +1 -1
  76. package/dist/9913.js +1 -1
  77. package/dist/main.js +1 -1
  78. package/dist/main.js.map +1 -1
  79. package/dist/openmrs-esm-dispensing-app.js +1 -1
  80. package/dist/openmrs-esm-dispensing-app.js.buildmanifest.json +302 -323
  81. package/dist/openmrs-esm-dispensing-app.js.map +1 -1
  82. package/dist/routes.json +1 -1
  83. package/e2e/commands/drug-order-operations.ts +2 -3
  84. package/e2e/commands/encounter-operations.ts +4 -8
  85. package/e2e/commands/index.ts +3 -3
  86. package/e2e/commands/patient-operations.ts +1 -1
  87. package/e2e/commands/provider-operations.ts +2 -5
  88. package/e2e/commands/visit-operations.ts +1 -1
  89. package/e2e/fixtures/api.ts +1 -1
  90. package/e2e/specs/close-prescription.spec.ts +5 -0
  91. package/e2e/specs/dispense-medication.spec.ts +4 -0
  92. package/e2e/specs/pause-prescription.spec.ts +5 -0
  93. package/e2e/support/github/Dockerfile +1 -1
  94. package/e2e/support/github/run-e2e-docker-env.sh +23 -29
  95. package/package.json +12 -11
  96. package/src/components/action-buttons.component.tsx +8 -3
  97. package/src/components/action-buttons.test.tsx +3 -0
  98. package/src/components/prescription-actions/close-action-button.component.tsx +3 -1
  99. package/src/components/prescription-actions/dispense-action-button.component.tsx +3 -1
  100. package/src/components/prescription-actions/pause-action-button.component.tsx +3 -1
  101. package/src/dashboard/dispensing-dashboard.component.tsx +3 -0
  102. package/src/fill-prescription/fill-prescription-button.comonent.tsx +43 -0
  103. package/src/fill-prescription/fill-prescription-button.scss +8 -0
  104. package/src/forms/close-dispense-form.workspace.tsx +2 -1
  105. package/src/forms/dispense-form.workspace.tsx +16 -16
  106. package/src/forms/forms.scss +15 -0
  107. package/src/forms/medication-dispense-review.component.tsx +34 -87
  108. package/src/forms/medication-dispense-review.test.tsx +1 -1
  109. package/src/forms/pause-dispense-form.workspace.tsx +2 -1
  110. package/src/history/history-and-comments.component.tsx +2 -0
  111. package/src/medication-dispense/medication-dispense.resource.tsx +11 -9
  112. package/src/medication-request/medication-request.resource.test.tsx +7 -3
  113. package/src/medication-request/medication-request.resource.tsx +47 -44
  114. package/src/prescriptions/patient-search-tab-panel.component.tsx +58 -0
  115. package/src/prescriptions/patient-search-tab-panel.scss +26 -0
  116. package/src/prescriptions/prescription-details.component.tsx +3 -1
  117. package/src/prescriptions/prescription-tab-lists.component.tsx +18 -84
  118. package/src/prescriptions/prescription-tab-panel.component.tsx +45 -148
  119. package/src/prescriptions/prescriptions-table.component.tsx +164 -0
  120. package/src/routes.json +2 -2
  121. package/src/types.ts +10 -1
  122. package/src/utils.ts +31 -4
  123. package/tools/setup-tests.ts +7 -0
  124. package/translations/am.json +3 -0
  125. package/translations/ar.json +3 -0
  126. package/translations/ar_SY.json +3 -0
  127. package/translations/bn.json +3 -0
  128. package/translations/de.json +3 -0
  129. package/translations/en.json +3 -0
  130. package/translations/en_US.json +3 -0
  131. package/translations/es.json +3 -0
  132. package/translations/es_MX.json +3 -0
  133. package/translations/fr.json +3 -0
  134. package/translations/he.json +3 -0
  135. package/translations/hi.json +3 -0
  136. package/translations/hi_IN.json +3 -0
  137. package/translations/id.json +3 -0
  138. package/translations/it.json +28 -25
  139. package/translations/ka.json +3 -0
  140. package/translations/km.json +3 -0
  141. package/translations/ku.json +3 -0
  142. package/translations/ky.json +3 -0
  143. package/translations/lg.json +3 -0
  144. package/translations/ne.json +3 -0
  145. package/translations/pl.json +3 -0
  146. package/translations/pt.json +3 -0
  147. package/translations/pt_BR.json +3 -0
  148. package/translations/qu.json +3 -0
  149. package/translations/ro_RO.json +3 -0
  150. package/translations/ru_RU.json +3 -0
  151. package/translations/si.json +3 -0
  152. package/translations/sw.json +3 -0
  153. package/translations/sw_KE.json +3 -0
  154. package/translations/tr.json +3 -0
  155. package/translations/tr_TR.json +3 -0
  156. package/translations/uk.json +3 -0
  157. package/translations/uz.json +3 -0
  158. package/translations/uz@Latn.json +3 -0
  159. package/translations/uz_UZ.json +3 -0
  160. package/translations/vi.json +3 -0
  161. package/translations/zh.json +3 -0
  162. package/translations/zh_CN.json +28 -25
  163. package/dist/1033.js +0 -1
  164. package/dist/1033.js.map +0 -1
  165. package/dist/3414.js +0 -1
  166. package/dist/3414.js.map +0 -1
  167. package/dist/6411.js +0 -1
  168. package/dist/6411.js.map +0 -1
  169. package/dist/6741.js +0 -2
  170. package/dist/6741.js.map +0 -1
  171. package/dist/7268.js +0 -2
  172. package/dist/7268.js.map +0 -1
  173. package/dist/7556.js +0 -1
  174. package/dist/7556.js.map +0 -1
  175. package/dist/7935.js +0 -1
  176. package/dist/7935.js.map +0 -1
  177. package/e2e/commands/medication-request-operation.ts +0 -97
  178. /package/dist/{7268.js.LICENSE.txt → 445.js.LICENSE.txt} +0 -0
  179. /package/dist/{6741.js.LICENSE.txt → 4999.js.LICENSE.txt} +0 -0
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useMemo, useState } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { Button, Checkbox, Form, FormLabel, InlineLoading } from '@carbon/react';
4
4
  import {
@@ -20,6 +20,7 @@ import {
20
20
  computeNewFulfillerStatusAfterDispenseEvent,
21
21
  getFulfillerStatus,
22
22
  getUuidFromReference,
23
+ markEncounterAsStale,
23
24
  revalidate,
24
25
  } from '../utils';
25
26
  import { type PharmacyConfig } from '../config-schema';
@@ -61,9 +62,6 @@ const DispenseForm: React.FC<DispenseFormProps> = ({
61
62
  // Keep track of medication dispense payload
62
63
  const [medicationDispensePayload, setMedicationDispensePayload] = useState<MedicationDispense>();
63
64
 
64
- // whether or not the form is valid and ready to submit
65
- const [isValid, setIsValid] = useState(false);
66
-
67
65
  // to prevent duplicate submits
68
66
  const [isSubmitting, setIsSubmitting] = useState(false);
69
67
 
@@ -74,6 +72,7 @@ const DispenseForm: React.FC<DispenseFormProps> = ({
74
72
  if (!isSubmitting) {
75
73
  setIsSubmitting(true);
76
74
  const abortController = new AbortController();
75
+ markEncounterAsStale(encounterUuid);
77
76
  saveMedicationDispense(medicationDispensePayload, MedicationDispenseStatus.completed, abortController)
78
77
  .then((response) => {
79
78
  if (response.ok) {
@@ -173,8 +172,16 @@ const DispenseForm: React.FC<DispenseFormProps> = ({
173
172
  }
174
173
  };
175
174
 
176
- const checkIsValid = () => {
177
- if (
175
+ const updateMedicationDispense = useCallback((medicationDispenseUpdate: Partial<MedicationDispense>) => {
176
+ setMedicationDispensePayload((prevState) => ({
177
+ ...prevState,
178
+ ...medicationDispenseUpdate,
179
+ }));
180
+ }, []);
181
+
182
+ // whether or not the form is valid and ready to submit
183
+ const isValid = useMemo(() => {
184
+ return (
178
185
  medicationDispensePayload &&
179
186
  medicationDispensePayload.performer &&
180
187
  medicationDispensePayload.performer[0]?.actor.reference &&
@@ -188,19 +195,12 @@ const DispenseForm: React.FC<DispenseFormProps> = ({
188
195
  (!medicationDispensePayload.substitution.wasSubstituted ||
189
196
  (medicationDispensePayload.substitution.reason[0]?.coding[0].code &&
190
197
  medicationDispensePayload.substitution.type?.coding[0].code))
191
- ) {
192
- setIsValid(true);
193
- } else {
194
- setIsValid(false);
195
- }
196
- };
198
+ );
199
+ }, [medicationDispensePayload, quantityRemaining]);
197
200
 
198
201
  // initialize the internal dispense payload with the dispenses passed in as props
199
202
  useEffect(() => setMedicationDispensePayload(medicationDispense), [medicationDispense]);
200
203
 
201
- // check is valid on any changes
202
- useEffect(checkIsValid, [medicationDispensePayload, quantityRemaining, inventoryItem]);
203
-
204
204
  const isButtonDisabled = (config.enableStockDispense ? !inventoryItem : false) || !isValid || isSubmitting;
205
205
 
206
206
  const bannerState = useMemo(() => {
@@ -238,7 +238,7 @@ const DispenseForm: React.FC<DispenseFormProps> = ({
238
238
  <div>
239
239
  <MedicationDispenseReview
240
240
  medicationDispense={medicationDispensePayload}
241
- updateMedicationDispense={setMedicationDispensePayload}
241
+ updateMedicationDispense={updateMedicationDispense}
242
242
  quantityRemaining={quantityRemaining}
243
243
  quantityDispensed={quantityDispensed}
244
244
  />
@@ -152,3 +152,18 @@
152
152
  .reviewContainer {
153
153
  margin: 0.8rem 0 0 0 !important;
154
154
  }
155
+
156
+ // copied from .sectionHeader of drug-order-form.scss
157
+ .prescriptionFormSectionHeader {
158
+ @include type.type-style('heading-01');
159
+ margin-bottom: layout.$spacing-05;
160
+ }
161
+
162
+ // copied from .field of drug-order-form.scss
163
+ .prescriptionFormField {
164
+ margin-bottom: layout.$spacing-05;
165
+
166
+ :global(.cds--label) {
167
+ vertical-align: middle;
168
+ }
169
+ }
@@ -7,6 +7,7 @@ import { getConceptCodingUuid, getMedicationReferenceOrCodeableConcept, getOpenM
7
7
  import { useMedicationCodeableConcept, useMedicationFormulations } from '../medication/medication.resource';
8
8
  import { useMedicationRequest, usePrescriptionDetails } from '../medication-request/medication-request.resource';
9
9
  import {
10
+ blankSubstitution,
10
11
  useOrderConfig,
11
12
  useProviders,
12
13
  useSubstitutionReasonValueSet,
@@ -20,7 +21,7 @@ import styles from '../components/medication-dispense-review.scss';
20
21
 
21
22
  interface MedicationDispenseReviewProps {
22
23
  medicationDispense: MedicationDispense;
23
- updateMedicationDispense: Function;
24
+ updateMedicationDispense(updates: Partial<MedicationDispense>);
24
25
  quantityRemaining: number;
25
26
  quantityDispensed: number;
26
27
  }
@@ -35,15 +36,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
35
36
  const config = useConfig<PharmacyConfig>();
36
37
  const session = useSession();
37
38
  const [isEditingFormulation, setIsEditingFormulation] = useState(false);
38
- const [isSubstitution, setIsSubstitution] = useState(false);
39
- // Dosing Unit eg Tablets
40
- const [drugDosingUnits, setDrugDosingUnits] = useState([]);
41
- // Dispensing Unit eg Tablets
42
- const [drugDispensingUnits, setDrugDispensingUnits] = useState([]);
43
- // Route eg Oral
44
- const [drugRoutes, setDrugRoutes] = useState([]);
45
- // Frequency eg Twice daily
46
- const [orderFrequencies, setOrderFrequencies] = useState([]);
47
39
  // type of substitution question
48
40
  const [substitutionTypes, setSubstitutionTypes] = useState([]);
49
41
  // reason for substitution question
@@ -72,41 +64,21 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
72
64
  // we fetch this just to get the prescription date
73
65
  const { prescriptionDate } = usePrescriptionDetails(medicationRequest ? medicationRequest.encounter.reference : null);
74
66
 
75
- useEffect(() => {
67
+ const { drugRoutes, drugDosingUnits, drugDispensingUnits, orderFrequencies } = useMemo(() => {
76
68
  if (orderConfigObject) {
77
- // sync drug route options order config
78
- const availableRoutes = drugRoutes.map((x) => x.id);
79
- const otherRouteOptions = [];
80
- orderConfigObject.drugRoutes.forEach(
81
- (x) => availableRoutes.includes(x.uuid) || otherRouteOptions.push({ id: x.uuid, text: x.display }),
82
- );
83
- setDrugRoutes([...drugRoutes, ...otherRouteOptions]);
84
-
85
- // sync dosage.unit options with what's defined in the order config
86
- const availableDosingUnits = drugDosingUnits.map((x) => x.id);
87
- const otherDosingUnits = [];
88
- orderConfigObject.drugDosingUnits.forEach(
89
- (x) => availableDosingUnits.includes(x.uuid) || otherDosingUnits.push({ id: x.uuid, text: x.display }),
90
- );
91
- setDrugDosingUnits([...drugDosingUnits, ...otherDosingUnits]);
92
-
93
- // sync dispensing unit options with what's defined in the order config
94
- const availableDispensingUnits = drugDispensingUnits.map((x) => x.id);
95
- const otherDispensingUnits = [];
96
- orderConfigObject.drugDispensingUnits.forEach(
97
- (x) => availableDispensingUnits.includes(x.uuid) || otherDispensingUnits.push({ id: x.uuid, text: x.display }),
98
- );
99
- setDrugDispensingUnits([...drugDispensingUnits, ...otherDispensingUnits]);
100
-
101
- // sync order frequency options with order config
102
- const availableFrequencies = orderFrequencies.map((x) => x.id);
103
- const otherFrequencyOptions = [];
104
- orderConfigObject.orderFrequencies.forEach(
105
- (x) => availableFrequencies.includes(x.uuid) || otherFrequencyOptions.push({ id: x.uuid, text: x.display }),
106
- );
107
- setOrderFrequencies([...orderFrequencies, ...otherFrequencyOptions]);
69
+ // Dosing Unit eg Tablets
70
+ const drugRoutes = orderConfigObject.drugRoutes.map((x) => ({ id: x.uuid, text: x.display }));
71
+ // Dispensing Unit eg Tablets
72
+ const drugDosingUnits = orderConfigObject.drugDosingUnits.map((x) => ({ id: x.uuid, text: x.display }));
73
+ // Route eg Oral
74
+ const drugDispensingUnits = orderConfigObject.drugDispensingUnits.map((x) => ({ id: x.uuid, text: x.display }));
75
+ // Frequency eg Twice daily
76
+ const orderFrequencies = orderConfigObject.orderFrequencies.map((x) => ({ id: x.uuid, text: x.display }));
77
+ return { drugRoutes, drugDosingUnits, drugDispensingUnits, orderFrequencies };
78
+ } else {
79
+ return { drugRoutes: [], drugDosingUnits: [], drugDispensingUnits: [], orderFrequencies: [] };
108
80
  }
109
- }, [drugDispensingUnits, drugDosingUnits, drugRoutes, orderConfigObject, orderFrequencies]);
81
+ }, [orderConfigObject]);
110
82
 
111
83
  useEffect(() => {
112
84
  const substitutionTypeOptions = [];
@@ -164,36 +136,25 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
164
136
  );
165
137
 
166
138
  // check to see if the current dispense would be a substitution, and update accordingly
139
+ const isSubstitution =
140
+ medicationRequest?.medicationReference?.reference &&
141
+ medicationDispense?.medicationReference?.reference &&
142
+ medicationRequest.medicationReference.reference != medicationDispense.medicationReference.reference;
143
+
144
+ const { substitution } = medicationDispense;
167
145
  useEffect(() => {
168
- if (
169
- medicationRequest?.medicationReference?.reference &&
170
- medicationDispense?.medicationReference?.reference &&
171
- medicationRequest.medicationReference.reference != medicationDispense.medicationReference.reference
172
- ) {
173
- setIsSubstitution(true);
174
- updateMedicationDispense({
175
- ...medicationDispense,
176
- substitution: {
177
- ...medicationDispense.substitution,
178
- wasSubstituted: true,
179
- },
180
- });
146
+ if (isSubstitution) {
147
+ // make sure that the value substitution.wasSubstituted exists and is truthy
148
+ if (!substitution.wasSubstituted) {
149
+ const newSubstitution = { ...substitution, wasSubstituted: true };
150
+ updateMedicationDispense({ substitution: newSubstitution });
151
+ }
181
152
  } else {
182
- setIsSubstitution(false);
183
- updateMedicationDispense({
184
- ...medicationDispense,
185
- substitution: {
186
- wasSubstituted: false,
187
- reason: [
188
- {
189
- coding: [{ code: null }],
190
- },
191
- ],
192
- type: { coding: [{ code: null }] },
193
- },
194
- });
153
+ if (substitution != blankSubstitution) {
154
+ updateMedicationDispense({ substitution: blankSubstitution });
155
+ }
195
156
  }
196
- }, [medicationDispense, medicationRequest, updateMedicationDispense]);
157
+ }, [isSubstitution, substitution, updateMedicationDispense]);
197
158
 
198
159
  useEffect(() => {
199
160
  setUserCanModify(session?.user && userHasAccess(PRIVILEGE_CREATE_DISPENSE_MODIFY_DETAILS, session.user));
@@ -211,11 +172,9 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
211
172
  }
212
173
  : undefined;
213
174
  }, [medicationDispense?.performer, providers, session?.currentProvider?.uuid, session?.user?.person?.display]);
214
-
215
175
  useEffect(() => {
216
176
  if (initialDispenser?.uuid) {
217
177
  updateMedicationDispense({
218
- ...medicationDispense,
219
178
  performer: [
220
179
  {
221
180
  actor: {
@@ -225,7 +184,7 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
225
184
  ],
226
185
  });
227
186
  }
228
- }, [initialDispenser, medicationDispense, updateMedicationDispense]);
187
+ }, [initialDispenser, updateMedicationDispense]);
229
188
 
230
189
  return (
231
190
  <div className={styles.medicationDispenseReviewContainer}>
@@ -251,7 +210,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
251
210
  onChange={({ selectedItem }) => {
252
211
  const typedItem = selectedItem as Medication;
253
212
  updateMedicationDispense({
254
- ...medicationDispense,
255
213
  medicationCodeableConcept: undefined,
256
214
  medicationReference: {
257
215
  reference: 'Medication/' + typedItem?.id,
@@ -279,7 +237,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
279
237
  }}
280
238
  onChange={({ selectedItem }) => {
281
239
  updateMedicationDispense({
282
- ...medicationDispense,
283
240
  substitution: {
284
241
  ...medicationDispense.substitution,
285
242
  type: {
@@ -312,7 +269,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
312
269
  }}
313
270
  onChange={({ selectedItem }) => {
314
271
  updateMedicationDispense({
315
- ...medicationDispense,
316
272
  substitution: {
317
273
  ...medicationDispense.substitution,
318
274
  reason: [
@@ -362,10 +318,9 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
362
318
  max={config.dispenseBehavior.restrictTotalQuantityDispensed ? quantityRemaining : undefined}
363
319
  onChange={(event, state) => {
364
320
  updateMedicationDispense({
365
- ...medicationDispense,
366
321
  quantity: {
367
322
  ...medicationDispense.quantity,
368
- value: state.value ? parseFloat(state.value.toString()) : '',
323
+ value: state.value ? parseFloat(state.value.toString()) : 0,
369
324
  },
370
325
  });
371
326
  }}
@@ -383,7 +338,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
383
338
  }}
384
339
  onChange={({ selectedItem }) => {
385
340
  updateMedicationDispense({
386
- ...medicationDispense,
387
341
  // note that we specifically recreate doseQuantity to overwrite any unit or system properties that may have been set
388
342
  quantity: {
389
343
  value: medicationDispense.quantity.value,
@@ -408,7 +362,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
408
362
  value={medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity.value}
409
363
  onChange={(event, state) => {
410
364
  updateMedicationDispense({
411
- ...medicationDispense,
412
365
  dosageInstruction: [
413
366
  {
414
367
  ...medicationDispense.dosageInstruction[0],
@@ -417,7 +370,7 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
417
370
  ...medicationDispense.dosageInstruction[0].doseAndRate[0],
418
371
  doseQuantity: {
419
372
  ...medicationDispense.dosageInstruction[0].doseAndRate[0].doseQuantity,
420
- value: state.value ? parseFloat(state.value.toString()) : '',
373
+ value: state.value ? parseFloat(state.value.toString()) : 0,
421
374
  },
422
375
  },
423
376
  ],
@@ -440,7 +393,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
440
393
  }}
441
394
  onChange={({ selectedItem }) => {
442
395
  updateMedicationDispense({
443
- ...medicationDispense,
444
396
  dosageInstruction: [
445
397
  {
446
398
  ...medicationDispense.dosageInstruction[0],
@@ -474,7 +426,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
474
426
  itemToString={(item) => item?.text}
475
427
  onChange={({ selectedItem }) => {
476
428
  updateMedicationDispense({
477
- ...medicationDispense,
478
429
  dosageInstruction: [
479
430
  {
480
431
  ...medicationDispense.dosageInstruction[0],
@@ -507,7 +458,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
507
458
  itemToString={(item) => item?.text}
508
459
  onChange={({ selectedItem }) => {
509
460
  updateMedicationDispense({
510
- ...medicationDispense,
511
461
  dosageInstruction: [
512
462
  {
513
463
  ...medicationDispense.dosageInstruction[0],
@@ -535,7 +485,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
535
485
  maxLength={65535}
536
486
  onChange={(e) => {
537
487
  updateMedicationDispense({
538
- ...medicationDispense,
539
488
  dosageInstruction: [
540
489
  {
541
490
  ...medicationDispense.dosageInstruction[0],
@@ -555,7 +504,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
555
504
  const currentDate = medicationDispense.whenHandedOver ? dayjs(medicationDispense.whenHandedOver) : null;
556
505
  const selectedDate = dayjs(input);
557
506
  updateMedicationDispense({
558
- ...medicationDispense,
559
507
  whenHandedOver: currentDate?.isSame(selectedDate, 'day')
560
508
  ? currentDate.toISOString()
561
509
  : selectedDate.toISOString(), // to preserve any time component, only update if the day actually changes
@@ -570,7 +518,6 @@ const MedicationDispenseReview: React.FC<MedicationDispenseReviewProps> = ({
570
518
  initialSelectedItem={initialDispenser}
571
519
  onChange={({ selectedItem }) => {
572
520
  updateMedicationDispense({
573
- ...medicationDispense,
574
521
  performer: [
575
522
  {
576
523
  actor: {
@@ -126,7 +126,7 @@ describe('Medication Dispense Review Component tests', () => {
126
126
  },
127
127
  };
128
128
 
129
- const mockUpdate: Function = jest.fn();
129
+ const mockUpdate = jest.fn();
130
130
  render(
131
131
  <MedicationDispenseReview
132
132
  medicationDispense={medicationDispense}
@@ -13,7 +13,7 @@ import {
13
13
  } from '@openmrs/esm-framework';
14
14
  import { saveMedicationDispense, useReasonForPauseValueSet } from '../medication-dispense/medication-dispense.resource';
15
15
  import { updateMedicationRequestFulfillerStatus } from '../medication-request/medication-request.resource';
16
- import { getUuidFromReference, revalidate } from '../utils';
16
+ import { getUuidFromReference, markEncounterAsStale, revalidate } from '../utils';
17
17
  import { type MedicationDispense, MedicationDispenseStatus, MedicationRequestFulfillerStatus } from '../types';
18
18
  import { type PharmacyConfig } from '../config-schema';
19
19
  import styles from './forms.scss';
@@ -71,6 +71,7 @@ const PauseDispenseForm: React.FC<PauseDispenseFormProps> = ({
71
71
  if (!isSubmitting) {
72
72
  setIsSubmitting(true);
73
73
  const abortController = new AbortController();
74
+ markEncounterAsStale(encounterUuid);
74
75
  saveMedicationDispense(medicationDispensePayload, MedicationDispenseStatus.on_hold, abortController)
75
76
  .then((response) => {
76
77
  // only update request status when added a new dispense event, not updating
@@ -34,6 +34,7 @@ import {
34
34
  revalidate,
35
35
  sortMedicationDispensesByWhenHandedOver,
36
36
  computeTotalQuantityDispensed,
37
+ markEncounterAsStale,
37
38
  } from '../utils';
38
39
  import { type PharmacyConfig } from '../config-schema';
39
40
 
@@ -222,6 +223,7 @@ const HistoryAndComments: React.FC<{
222
223
  config.dispenseBehavior.restrictTotalQuantityDispensed,
223
224
  );
224
225
 
226
+ markEncounterAsStale(encounterUuid);
225
227
  deleteMedicationDispense(medicationDispense.id)
226
228
  .then(() => {
227
229
  showSnackbar({
@@ -166,16 +166,18 @@ export function initiateMedicationDispenseBody(
166
166
  ],
167
167
  },
168
168
  ],
169
- substitution: {
170
- wasSubstituted: false,
171
- reason: [
172
- {
173
- coding: [{ code: null }],
174
- },
175
- ],
176
- type: { coding: [{ code: null }] },
177
- },
169
+ substitution: blankSubstitution,
178
170
  };
179
171
  }
180
172
  return medicationDispense;
181
173
  }
174
+
175
+ export const blankSubstitution = {
176
+ wasSubstituted: false,
177
+ reason: [
178
+ {
179
+ coding: [{ code: null }],
180
+ },
181
+ ],
182
+ type: { coding: [{ code: null }] },
183
+ };
@@ -13,12 +13,16 @@ import { JSON_MERGE_PATH_MIME_TYPE, OPENMRS_FHIR_EXT_REQUEST_FULFILLER_STATUS }
13
13
 
14
14
  jest.mocked(openmrsFetch);
15
15
  jest.mock('swr');
16
+ jest.mock('react', () => ({
17
+ ...jest.requireActual('react'),
18
+ useMemo: jest.fn((fn) => fn()),
19
+ }));
16
20
 
17
21
  describe('Medication Request Resource Test', () => {
18
22
  test('usePrescriptionsTable should call active endpoint and proper date based on expiration period if status parameter is active', () => {
19
23
  // @ts-ignore
20
24
  useSWR.mockImplementation(() => ({ data: { data: 'mockedReturnData' } }));
21
- usePrescriptionsTable(5, 5, 'bob', null, 'ACTIVE', 10, 10000);
25
+ usePrescriptionsTable(true, 5, 5, 'bob', null, 'ACTIVE', 10, 10000);
22
26
  expect(useSWR).toHaveBeenCalledWith(
23
27
  `/ws/fhir2/R4/Encounter?_query=encountersWithMedicationRequests&_getpagesoffset=5&_count=5&date=ge${dayjs()
24
28
  .startOf('day')
@@ -32,7 +36,7 @@ describe('Medication Request Resource Test', () => {
32
36
  test('usePrescriptionsTable should call all endpoint if status parameter is not active', () => {
33
37
  // @ts-ignore
34
38
  useSWR.mockImplementation(() => ({ data: { data: 'mockedReturnData' } }));
35
- usePrescriptionsTable(5, 5, 'bob', null, null, 10, 10000);
39
+ usePrescriptionsTable(true, 5, 5, 'bob', null, null, 10, 10000);
36
40
  expect(useSWR).toHaveBeenCalledWith(
37
41
  `/ws/fhir2/R4/Encounter?_query=encountersWithMedicationRequests&_getpagesoffset=5&_count=5&patientSearchTerm=bob`,
38
42
  openmrsFetch,
@@ -587,7 +591,7 @@ describe('Medication Request Resource Test', () => {
587
591
 
588
592
  // @ts-ignore
589
593
  useSWR.mockImplementation(() => ({ data: { data: queryResultsBundle } }));
590
- const { prescriptionsTableRows, totalOrders } = usePrescriptionsTable(2, 0, 'bob', 'ACTIVE', null, 90, 10000);
594
+ const { prescriptionsTableRows, totalOrders } = usePrescriptionsTable(true, 2, 0, 'bob', 'ACTIVE', null, 90, 10000);
591
595
  expect(totalOrders).toBe(26);
592
596
  expect(prescriptionsTableRows.length).toBe(2);
593
597
  expect(prescriptionsTableRows[0].id).toBe('7aee7123-9e50-4f72-a636-895d77a63e98');
@@ -23,8 +23,10 @@ import {
23
23
  } from '../utils';
24
24
  import dayjs from 'dayjs';
25
25
  import { JSON_MERGE_PATH_MIME_TYPE, OPENMRS_FHIR_EXT_REQUEST_FULFILLER_STATUS } from '../constants';
26
+ import { useMemo } from 'react';
26
27
 
27
28
  export function usePrescriptionsTable(
29
+ loadData: boolean,
28
30
  pageSize: number = 10,
29
31
  pageOffset: number = 0,
30
32
  patientSearchTerm: string = '',
@@ -34,15 +36,17 @@ export function usePrescriptionsTable(
34
36
  refreshInterval: number,
35
37
  ) {
36
38
  const { data, error } = useSWR<{ data: EncounterResponse }, Error>(
37
- status === 'ACTIVE'
38
- ? getPrescriptionTableActiveMedicationRequestsEndpoint(
39
- pageOffset,
40
- pageSize,
41
- dayjs(new Date()).startOf('day').subtract(medicationRequestExpirationPeriodInDays, 'day').toISOString(),
42
- patientSearchTerm,
43
- location,
44
- )
45
- : getPrescriptionTableAllMedicationRequestsEndpoint(pageOffset, pageSize, patientSearchTerm, location),
39
+ loadData
40
+ ? status === 'ACTIVE'
41
+ ? getPrescriptionTableActiveMedicationRequestsEndpoint(
42
+ pageOffset,
43
+ pageSize,
44
+ dayjs(new Date()).startOf('day').subtract(medicationRequestExpirationPeriodInDays, 'day').toISOString(),
45
+ patientSearchTerm,
46
+ location,
47
+ )
48
+ : getPrescriptionTableAllMedicationRequestsEndpoint(pageOffset, pageSize, patientSearchTerm, location)
49
+ : null,
46
50
  openmrsFetch,
47
51
  { refreshInterval: refreshInterval },
48
52
  );
@@ -124,54 +128,53 @@ function buildPrescriptionsTableRow(
124
128
  }
125
129
 
126
130
  export function usePrescriptionDetails(encounterUuid: string, refreshInterval = null) {
127
- const medicationRequestBundles: Array<MedicationRequestBundle> = [];
128
- let prescriptionDate: Date;
129
- let isLoading = true;
130
-
131
- const { data, error } = useSWR<{ data: MedicationRequestResponse }, Error>(
131
+ const { data, ...rest } = useSWR<{ data: MedicationRequestResponse }, Error>(
132
132
  getPrescriptionDetailsEndpoint(encounterUuid),
133
133
  openmrsFetch,
134
134
  { refreshInterval: refreshInterval },
135
135
  );
136
136
 
137
- if (data) {
138
- const results = data?.data.entry;
139
-
140
- const encounter = results
141
- ?.filter((entry) => entry?.resource?.resourceType == 'Encounter')
142
- .map((entry) => entry.resource as Encounter);
143
-
144
- if (encounter) {
145
- // by definition of the request (search by encounter) there should be one and only one encounter
146
- prescriptionDate = parseDate(encounter[0]?.period.start);
137
+ const { medicationRequestBundles, prescriptionDate } = useMemo(() => {
138
+ const medicationRequestBundles: Array<MedicationRequestBundle> = [];
139
+ let prescriptionDate: Date;
140
+ if (data) {
141
+ const results = data?.data.entry;
147
142
 
148
- const medicationRequests = results
149
- ?.filter((entry) => entry?.resource?.resourceType == 'MedicationRequest')
150
- .map((entry) => entry.resource as MedicationRequest);
151
-
152
- const medicationDispenses = results
153
- ?.filter((entry) => entry?.resource?.resourceType == 'MedicationDispense')
154
- .map((entry) => entry.resource as MedicationDispense)
155
- .sort(sortMedicationDispensesByWhenHandedOver);
143
+ const encounter = results
144
+ ?.filter((entry) => entry?.resource?.resourceType == 'Encounter')
145
+ .map((entry) => entry.resource as Encounter);
156
146
 
157
- medicationRequests.every((medicationRequest) =>
158
- medicationRequestBundles.push({
159
- request: medicationRequest,
160
- dispenses: getAssociatedMedicationDispenses(medicationRequest, medicationDispenses).sort(
161
- sortMedicationDispensesByWhenHandedOver,
162
- ),
163
- }),
164
- );
147
+ if (encounter) {
148
+ // by definition of the request (search by encounter) there should be one and only one encounter
149
+ prescriptionDate = parseDate(encounter[0]?.period.start);
150
+
151
+ const medicationRequests = results
152
+ ?.filter((entry) => entry?.resource?.resourceType == 'MedicationRequest')
153
+ .map((entry) => entry.resource as MedicationRequest);
154
+
155
+ const medicationDispenses = results
156
+ ?.filter((entry) => entry?.resource?.resourceType == 'MedicationDispense')
157
+ .map((entry) => entry.resource as MedicationDispense)
158
+ .sort(sortMedicationDispensesByWhenHandedOver);
159
+
160
+ medicationRequests.every((medicationRequest) =>
161
+ medicationRequestBundles.push({
162
+ request: medicationRequest,
163
+ dispenses: getAssociatedMedicationDispenses(medicationRequest, medicationDispenses).sort(
164
+ sortMedicationDispensesByWhenHandedOver,
165
+ ),
166
+ }),
167
+ );
168
+ }
165
169
  }
166
- }
167
170
 
168
- isLoading = (!medicationRequestBundles || medicationRequestBundles.length == 0) && !error;
171
+ return { medicationRequestBundles, prescriptionDate };
172
+ }, [data]);
169
173
 
170
174
  return {
171
175
  medicationRequestBundles,
172
176
  prescriptionDate,
173
- error,
174
- isLoading,
177
+ ...rest,
175
178
  };
176
179
  }
177
180
 
@@ -0,0 +1,58 @@
1
+ import React, { useState } from 'react';
2
+ import { Button, Search, TabPanel } from '@carbon/react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { PatientSearchPictogram } from '@openmrs/esm-framework';
5
+ import PrescriptionsTable from './prescriptions-table.component';
6
+ import styles from './patient-search-tab-panel.scss';
7
+
8
+ const PatientSearchTabPanel: React.FC = () => {
9
+ const { t } = useTranslation();
10
+ const [searchTerm, setSearchTerm] = useState('');
11
+ const [submittedSearchTerm, setSubmittedSearchTerm] = useState('');
12
+
13
+ return (
14
+ <TabPanel>
15
+ <div className={styles.searchTabPanel}>
16
+ <form
17
+ className={styles.searchBar}
18
+ onSubmit={(e) => {
19
+ e.preventDefault();
20
+ setSubmittedSearchTerm(searchTerm);
21
+ }}>
22
+ <Search
23
+ closeButtonLabelText={t('clearSearchInput', 'Clear search input')}
24
+ defaultValue={searchTerm}
25
+ placeholder={t('searchForPatient', 'Search for a patient by name or identifier number')}
26
+ labelText={t('searchForPatient', 'Search for a patient by name or identifier number')}
27
+ onChange={(e) => {
28
+ setSearchTerm(e.target.value);
29
+ }}
30
+ onClear={() => setSubmittedSearchTerm('')}
31
+ size="lg"
32
+ />
33
+ <Button kind="secondary" type="submit">
34
+ {t('search', 'Search')}
35
+ </Button>
36
+ </form>
37
+ {submittedSearchTerm ? (
38
+ <PrescriptionsTable
39
+ loadData={true}
40
+ status={'ACTIVE'}
41
+ debouncedSearchTerm={submittedSearchTerm}
42
+ location={''}
43
+ />
44
+ ) : (
45
+ <div className={styles.searchForPatientPlaceholder}>
46
+ <div>
47
+ <PatientSearchPictogram />
48
+ <h5>{t('searchForPatientHeader', 'Search for a patient')}</h5>
49
+ <div>{t('searchForPatient', 'Search for a patient by name or identifier number')}</div>
50
+ </div>
51
+ </div>
52
+ )}
53
+ </div>
54
+ </TabPanel>
55
+ );
56
+ };
57
+
58
+ export default PatientSearchTabPanel;