@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,257 @@
1
+ import { useMemo } from 'react';
2
+ import dayjs from 'dayjs';
3
+ import useSWR from 'swr';
4
+ import { fhirBaseUrl, openmrsFetch, parseDate } from '@openmrs/esm-framework';
5
+ import { JSON_MERGE_PATH_MIME_TYPE, OPENMRS_FHIR_EXT_REQUEST_FULFILLER_STATUS } from '../constants';
6
+ import {
7
+ type AllergyIntoleranceResponse,
8
+ type EncounterResponse,
9
+ type MedicationRequest,
10
+ type MedicationRequestResponse,
11
+ type PrescriptionsTableRow,
12
+ type MedicationDispense,
13
+ type Encounter,
14
+ type MedicationRequestFulfillerStatus,
15
+ type MedicationRequestBundle,
16
+ type SimpleLocation,
17
+ } from '../types';
18
+ import {
19
+ getPrescriptionDetailsEndpoint,
20
+ getMedicationDisplay,
21
+ getMedicationReferenceOrCodeableConcept,
22
+ getPrescriptionTableEndpoint,
23
+ sortMedicationDispensesByWhenHandedOver,
24
+ computePrescriptionStatusMessageCode,
25
+ getAssociatedMedicationDispenses,
26
+ } from '../utils';
27
+
28
+ export function usePrescriptionsTable(
29
+ loadData: boolean,
30
+ customPrescriptionsTableEndpoint: string = '',
31
+ status: string = '',
32
+ pageSize: number = 10,
33
+ pageOffset: number = 0,
34
+ patientSearchTerm: string = '',
35
+ locations: SimpleLocation[] = [],
36
+ medicationRequestExpirationPeriodInDays: number,
37
+ refreshInterval: number,
38
+ ) {
39
+ const { data, error } = useSWR<{ data: EncounterResponse }, Error>(
40
+ loadData
41
+ ? getPrescriptionTableEndpoint(
42
+ customPrescriptionsTableEndpoint,
43
+ status,
44
+ pageOffset,
45
+ pageSize,
46
+ dayjs(new Date()).startOf('day').subtract(medicationRequestExpirationPeriodInDays, 'day').toISOString(),
47
+ patientSearchTerm,
48
+ locations?.map((location) => location.id).join(','),
49
+ )
50
+ : null,
51
+ openmrsFetch,
52
+ { refreshInterval: refreshInterval },
53
+ );
54
+
55
+ let prescriptionsTableRows: PrescriptionsTableRow[];
56
+ if (data) {
57
+ const entries = data?.data.entry;
58
+ if (entries) {
59
+ const encounters = entries
60
+ .filter((entry) => entry?.resource?.resourceType == 'Encounter')
61
+ .map((entry) => entry.resource as Encounter);
62
+ const medicationRequests = entries
63
+ .filter((entry) => entry?.resource?.resourceType == 'MedicationRequest')
64
+ .map((entry) => entry.resource as MedicationRequest);
65
+ const medicationDispenses = entries
66
+ .filter((entry) => entry?.resource?.resourceType == 'MedicationDispense')
67
+ .map((entry) => entry.resource as MedicationDispense)
68
+ .sort(sortMedicationDispensesByWhenHandedOver);
69
+ prescriptionsTableRows = encounters.map((encounter) => {
70
+ const medicationRequestsForEncounter = medicationRequests.filter(
71
+ (medicationRequest) => medicationRequest.encounter.reference == 'Encounter/' + encounter.id,
72
+ );
73
+
74
+ const medicationRequestReferences = medicationRequestsForEncounter.map(
75
+ (medicationRequest) => 'MedicationRequest/' + medicationRequest.id,
76
+ );
77
+ const medicationDispensesForMedicationRequests = medicationDispenses.filter((medicationDispense) =>
78
+ medicationRequestReferences.includes(medicationDispense.authorizingPrescription[0]?.reference),
79
+ );
80
+ return buildPrescriptionsTableRow(
81
+ encounter,
82
+ medicationRequestsForEncounter,
83
+ medicationDispensesForMedicationRequests,
84
+ medicationRequestExpirationPeriodInDays,
85
+ );
86
+ });
87
+ prescriptionsTableRows.sort((a, b) => (a.created < b.created ? 1 : -1));
88
+ } else {
89
+ prescriptionsTableRows = [];
90
+ }
91
+ }
92
+
93
+ return {
94
+ prescriptionsTableRows,
95
+ error: error,
96
+ isLoading: !prescriptionsTableRows && !error,
97
+ totalOrders: data?.data.total,
98
+ };
99
+ }
100
+
101
+ function buildPrescriptionsTableRow(
102
+ encounter: Encounter,
103
+ medicationRequests: Array<MedicationRequest>,
104
+ medicationDispense: Array<MedicationDispense>,
105
+ medicationRequestExpirationPeriodInDays: number,
106
+ ): PrescriptionsTableRow {
107
+ return {
108
+ id: encounter?.id,
109
+ created: encounter?.period?.start,
110
+ patient: {
111
+ name: encounter?.subject?.display,
112
+ uuid: encounter?.subject?.reference?.split('/')[1],
113
+ },
114
+ drugs: [
115
+ ...new Set(
116
+ medicationRequests
117
+ .map((medicationRequest) => getMedicationDisplay(getMedicationReferenceOrCodeableConcept(medicationRequest)))
118
+ .sort((a, b) => {
119
+ return a.localeCompare(b);
120
+ }),
121
+ ),
122
+ ].join('; '),
123
+ lastDispenser:
124
+ medicationDispense && medicationDispense[0]?.performer && medicationDispense[0]?.performer[0]?.actor.display,
125
+ prescriber: [...new Set(medicationRequests.map((o) => o.requester.display))].join(', '),
126
+ status: computePrescriptionStatusMessageCode(medicationRequests, medicationRequestExpirationPeriodInDays),
127
+ location: encounter?.location ? encounter?.location[0]?.location.display : null,
128
+ };
129
+ }
130
+
131
+ export function usePrescriptionDetails(encounterUuid: string, refreshInterval = null) {
132
+ const { data, ...rest } = useSWR<{ data: MedicationRequestResponse }, Error>(
133
+ getPrescriptionDetailsEndpoint(encounterUuid),
134
+ openmrsFetch,
135
+ { refreshInterval: refreshInterval },
136
+ );
137
+
138
+ const { medicationRequestBundles, prescriptionDate } = useMemo(() => {
139
+ if (data) {
140
+ return medicationRequestResponseToPrescriptionDetails(data.data.entry);
141
+ } else {
142
+ return { medicationRequestBundles: [], prescriptionDate: null };
143
+ }
144
+ }, [data]);
145
+
146
+ return {
147
+ medicationRequestBundles,
148
+ prescriptionDate,
149
+ ...rest,
150
+ };
151
+ }
152
+
153
+ /**
154
+ * fetches prescription details of a given encounter directly via openmrsFetch (instead of useSWR)
155
+ * @param encounterUuid
156
+ * @returns
157
+ */
158
+ export async function getPrescriptionDetails(encounterUuid: string) {
159
+ const result = await openmrsFetch<MedicationRequestResponse>(getPrescriptionDetailsEndpoint(encounterUuid));
160
+ const {
161
+ data: { entry },
162
+ } = result;
163
+ return medicationRequestResponseToPrescriptionDetails(entry);
164
+ }
165
+
166
+ function medicationRequestResponseToPrescriptionDetails(
167
+ results: { resource: MedicationRequest | MedicationDispense }[],
168
+ ) {
169
+ const medicationRequestBundles: Array<MedicationRequestBundle> = [];
170
+ let prescriptionDate: Date;
171
+
172
+ const encounter = results
173
+ ?.filter((entry) => entry?.resource?.resourceType == 'Encounter')
174
+ .map((entry) => entry.resource as Encounter);
175
+
176
+ if (encounter) {
177
+ // by definition of the request (search by encounter) there should be one and only one encounter
178
+ prescriptionDate = parseDate(encounter[0]?.period.start);
179
+
180
+ const medicationRequests = results
181
+ ?.filter((entry) => entry?.resource?.resourceType == 'MedicationRequest')
182
+ .map((entry) => entry.resource as MedicationRequest);
183
+
184
+ const medicationDispenses = results
185
+ ?.filter((entry) => entry?.resource?.resourceType == 'MedicationDispense')
186
+ .map((entry) => entry.resource as MedicationDispense)
187
+ .sort(sortMedicationDispensesByWhenHandedOver);
188
+
189
+ medicationRequests.every((medicationRequest) =>
190
+ medicationRequestBundles.push({
191
+ request: medicationRequest,
192
+ dispenses: getAssociatedMedicationDispenses(medicationRequest, medicationDispenses).sort(
193
+ sortMedicationDispensesByWhenHandedOver,
194
+ ),
195
+ }),
196
+ );
197
+ }
198
+
199
+ return { medicationRequestBundles, prescriptionDate };
200
+ }
201
+
202
+ export function usePatientAllergies(patientUuid: string, refreshInterval) {
203
+ const { data, error, isLoading } = useSWR<{ data: AllergyIntoleranceResponse }, Error>(
204
+ `${fhirBaseUrl}/AllergyIntolerance?patient=${patientUuid}`,
205
+ openmrsFetch,
206
+ { refreshInterval: refreshInterval },
207
+ );
208
+
209
+ const allergies = data?.data.entry?.map((allergy) => allergy.resource) ?? [];
210
+
211
+ return {
212
+ allergies,
213
+ totalAllergies: data?.data.total,
214
+ error,
215
+ isLoading,
216
+ };
217
+ }
218
+
219
+ // supports passing just the uuid/code or the entire reference, ie either: "MedicationReference/123-abc" or "123-abc"
220
+ export function useMedicationRequest(reference: string, refreshInterval) {
221
+ reference = reference
222
+ ? reference.startsWith('MedicationRequest')
223
+ ? reference
224
+ : `MedicationRequest/${reference}`
225
+ : null;
226
+
227
+ const { data } = useSWR<{ data: MedicationRequest }, Error>(
228
+ reference ? `${fhirBaseUrl}/${reference}` : null,
229
+ openmrsFetch,
230
+ { refreshInterval: refreshInterval },
231
+ );
232
+ return {
233
+ medicationRequest: data ? data.data : null,
234
+ };
235
+ }
236
+
237
+ export function updateMedicationRequestFulfillerStatus(
238
+ medicationRequestUuid: string,
239
+ fulfillerStatus: MedicationRequestFulfillerStatus,
240
+ ) {
241
+ const url = `${fhirBaseUrl}/MedicationRequest/${medicationRequestUuid}`;
242
+
243
+ return openmrsFetch(url, {
244
+ method: 'PATCH',
245
+ headers: {
246
+ 'Content-Type': JSON_MERGE_PATH_MIME_TYPE,
247
+ },
248
+ body: {
249
+ extension: [
250
+ {
251
+ url: OPENMRS_FHIR_EXT_REQUEST_FULFILLER_STATUS,
252
+ valueCode: fulfillerStatus,
253
+ },
254
+ ],
255
+ },
256
+ });
257
+ }
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { usePatientAge } from './patient.resources';
4
+
5
+ type PatientInfoCellProps = {
6
+ patient: {
7
+ name: string;
8
+ uuid: string;
9
+ };
10
+ };
11
+
12
+ const PatientInfoCell: React.FC<PatientInfoCellProps> = ({ patient: { name: display, uuid } }) => {
13
+ const { error, isLoading, age } = usePatientAge(uuid);
14
+ const { t } = useTranslation();
15
+ const ageLabel = t('age', 'Age');
16
+ function concatAgePatientDisplay(input: string, age: number): string | null {
17
+ const attrIndex = input.lastIndexOf(')');
18
+ if (attrIndex !== -1) return input.slice(0, attrIndex) + `, ${ageLabel}: ${age}` + input.slice(attrIndex);
19
+ else return `${input} ${ageLabel}: ${age}`;
20
+ }
21
+ if (isLoading || error) return <>{display}</>;
22
+ const displayWithAge = concatAgePatientDisplay(display, age);
23
+ return <>{displayWithAge}</>;
24
+ };
25
+
26
+ export default PatientInfoCell;
@@ -0,0 +1,14 @@
1
+ import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+
4
+ export const usePatientAge = (patienUuid: string) => {
5
+ const customRep = 'custom:(age)';
6
+ const url = `${restBaseUrl}/person/${patienUuid}?v=${customRep}`;
7
+ const { data, error, isLoading, mutate } = useSWR<FetchResponse<{ age: number }>>(url, openmrsFetch);
8
+ return {
9
+ age: data?.data?.age,
10
+ error,
11
+ isLoading,
12
+ mutate,
13
+ };
14
+ };
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Calendar, Location } from '@carbon/react/icons';
4
+ import { useConfig, useSession, formatDate } from '@openmrs/esm-framework';
5
+ import PharmacyIllustration from './pharmacy-illustration.component';
6
+ import { type PharmacyConfig } from '../config-schema';
7
+ import styles from './pharmacy-header.scss';
8
+
9
+ export const PharmacyHeader: React.FC = () => {
10
+ const { t } = useTranslation();
11
+ const userSession = useSession();
12
+ const config = useConfig<PharmacyConfig>();
13
+ const userLocation = userSession?.sessionLocation?.display;
14
+
15
+ return (
16
+ <div className={styles.header}>
17
+ <div className={styles.leftJustifiedItems}>
18
+ <PharmacyIllustration />
19
+ <div className={styles.pageLabels}>
20
+ <p>{t('appName', config.appName)}</p>
21
+ <p className={styles.pageName}>{t('home', 'Home')}</p>
22
+ </div>
23
+ </div>
24
+ <div className={styles.rightJustifiedItems}>
25
+ <div className={styles.dateAndLocation}>
26
+ <Location size={16} />
27
+ <span className={styles.value}>{userLocation}</span>
28
+ <span className={styles.middot}>&middot;</span>
29
+ <Calendar size={16} />
30
+ <span className={styles.value}>{formatDate(new Date(), { noToday: true })}</span>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ );
35
+ };
@@ -0,0 +1,55 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .header {
6
+ @include type.type-style('body-compact-02');
7
+ color: $text-02;
8
+ height: layout.$spacing-12;
9
+ background-color: $ui-02;
10
+ display: flex;
11
+ justify-content: space-between;
12
+ }
13
+
14
+ .leftJustifiedItems {
15
+ display: flex;
16
+ flex-direction: row;
17
+ align-items: center;
18
+ margin-left: layout.$spacing-05;
19
+ }
20
+
21
+ .rightJustifiedItems {
22
+ @include type.type-style('body-compact-02');
23
+ color: $text-02;
24
+ padding-top: layout.$spacing-05;
25
+ }
26
+
27
+ .pageName {
28
+ @include type.type-style('heading-04');
29
+ }
30
+
31
+ .pageLabels {
32
+ margin-left: layout.$spacing-05;
33
+ p:first-of-type {
34
+ margin-bottom: layout.$spacing-02;
35
+ }
36
+ }
37
+
38
+ .dateAndLocation {
39
+ display: flex;
40
+ justify-content: flex-end;
41
+ align-items: center;
42
+ margin-right: layout.$spacing-05;
43
+ }
44
+
45
+ .value {
46
+ margin-left: layout.$spacing-02;
47
+ }
48
+
49
+ .middot {
50
+ margin: 0 layout.$spacing-03;
51
+ }
52
+
53
+ .view {
54
+ @include type.type-style('label-01');
55
+ }
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+
3
+ const PharmacyIllustration: React.FC = () => {
4
+ return (
5
+ <svg width="72" height="72" viewBox="0 0 62 58" xmlns="http://www.w3.org/2000/svg">
6
+ <defs>
7
+ <path id="whv9q6f4ta" d="M0 0h62v30.508H0z" />
8
+ <path id="hp71ojgcnc" d="M0 0h24.603v26.571H0z" />
9
+ </defs>
10
+ <g fill="none">
11
+ <g opacity=".193" transform="matrix(-1 0 0 1 62 26.571)">
12
+ <path
13
+ d="M62 0c0 16.85-13.916 30.508-31.037 30.508S0 16.849 0 0h62z"
14
+ fill="var(--brand-03)"
15
+ mask="url(#rt0c8mo6eb)"
16
+ />
17
+ </g>
18
+ <g opacity=".396" transform="matrix(-1 0 0 1 60.032 0)">
19
+ <path
20
+ d="m24.603 26.571-13.35-23.51C9.592.141 5.884-.869 3.011.817.138 2.503-.855 6.272.804 9.193l9.874 17.378"
21
+ fill="var(--brand-03)"
22
+ mask="url(#hgianthqwd)"
23
+ />
24
+ </g>
25
+ </g>
26
+ </svg>
27
+ );
28
+ };
29
+
30
+ export default PharmacyIllustration;
@@ -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
+ locations={[]}
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;
@@ -0,0 +1,26 @@
1
+ @use '@carbon/layout';
2
+ @use '~@openmrs/esm-styleguide/src/vars' as *;
3
+
4
+ .searchTabPanel {
5
+ display: flex;
6
+ flex-direction: column;
7
+ margin: layout.$spacing-05 layout.$spacing-04;
8
+ gap: layout.$spacing-05;
9
+ }
10
+
11
+ .searchForPatientPlaceholder {
12
+ display: flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+ text-align: center;
16
+ min-height: 400px;
17
+ flex: 1;
18
+ border: solid 1px $grey-2;
19
+ }
20
+
21
+ .searchBar {
22
+ input {
23
+ background-color: $ui-02;
24
+ }
25
+ display: flex;
26
+ }
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import { Layer } from '@carbon/react';
3
+ import PrescriptionPrintAction from '../print-prescription/prescription-print-action.component';
4
+ import styles from './prescription-actions.scss';
5
+
6
+ type PrescriptionsActionsFooterProps = {
7
+ encounterUuid: string;
8
+ patientUuid: string;
9
+ };
10
+
11
+ const PrescriptionsActionsFooter: React.FC<PrescriptionsActionsFooterProps> = ({ encounterUuid, patientUuid }) => {
12
+ return (
13
+ <Layer className={styles.actionsContainer}>
14
+ <div className={styles.actionCluster}>
15
+ {/* Left buttons */}
16
+ <PrescriptionPrintAction encounterUuid={encounterUuid} patientUuid={patientUuid} />
17
+ </div>
18
+
19
+ <div className={styles.actionCluster}>{/* Right buttons */}</div>
20
+ </Layer>
21
+ );
22
+ };
23
+
24
+ export default PrescriptionsActionsFooter;
@@ -0,0 +1,14 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+
4
+ .actionsContainer {
5
+ flex-direction: row;
6
+ display: flex;
7
+ justify-content: space-between;
8
+ }
9
+
10
+ .actionCluster {
11
+ gap: layout.$spacing-03;
12
+ display: flex;
13
+ flex-direction: row;
14
+ }