@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,96 @@
1
+ import React, { type ReactNode } from 'react';
2
+ import classNames from 'classnames';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Tile } from '@carbon/react';
5
+ import { isDesktop, useLayoutType } from '@openmrs/esm-framework';
6
+ import { type MedicationDispense, type MedicationRequest, type Quantity } from '../types';
7
+ import {
8
+ calculateIsFreeTextDosage,
9
+ getDosageInstruction,
10
+ getMedicationDisplay,
11
+ getMedicationReferenceOrCodeableConcept,
12
+ getQuantity,
13
+ getRefillsAllowed,
14
+ } from '../utils';
15
+ import styles from './medication-event.scss';
16
+
17
+ /**
18
+ * Renders a prescription request of a prescript event (ex: ordered, dispensed)
19
+ */
20
+ const MedicationEvent: React.FC<{
21
+ medicationEvent: MedicationRequest | MedicationDispense;
22
+ status?: ReactNode;
23
+ children?: ReactNode;
24
+ isDispenseEvent?: boolean;
25
+ }> = ({ medicationEvent, status = null, children, isDispenseEvent }) => {
26
+ const { t } = useTranslation();
27
+ const dosageInstruction = getDosageInstruction(medicationEvent.dosageInstruction);
28
+ const isFreeTextDosage = calculateIsFreeTextDosage(dosageInstruction);
29
+ const quantity: Quantity = getQuantity(medicationEvent);
30
+ const refillsAllowed: number = getRefillsAllowed(medicationEvent);
31
+ const isTablet = !isDesktop(useLayoutType());
32
+
33
+ return (
34
+ <Tile
35
+ className={classNames({
36
+ [styles.medicationEventTile]: true,
37
+ [styles.dispenseEvent]: isDispenseEvent,
38
+ [styles.isTablet]: isTablet,
39
+ })}>
40
+ <div>
41
+ <p className={styles.medicationName}>
42
+ {status}
43
+ {status && ' '}
44
+ <strong>{getMedicationDisplay(getMedicationReferenceOrCodeableConcept(medicationEvent))}</strong>
45
+ </p>
46
+
47
+ {!isFreeTextDosage && (
48
+ <p className={styles.bodyLong01}>
49
+ <span className={styles.label01}>{t('dose', 'Dose').toUpperCase()}</span>{' '}
50
+ <span className={styles.dosage}>
51
+ {dosageInstruction?.doseAndRate &&
52
+ dosageInstruction.doseAndRate.map((doseAndRate, index) => {
53
+ return (
54
+ <span key={index}>
55
+ {doseAndRate?.doseQuantity?.value} {doseAndRate?.doseQuantity?.unit}
56
+ </span>
57
+ );
58
+ })}
59
+ </span>
60
+ {dosageInstruction?.route?.text && <> &mdash; {dosageInstruction.route.text}</>}
61
+ {dosageInstruction?.timing?.code?.text && <> &mdash; {dosageInstruction.timing.code.text}</>}
62
+ {dosageInstruction?.timing?.repeat?.duration && (
63
+ <>
64
+ {' '}
65
+ for {dosageInstruction.timing.repeat.duration} {dosageInstruction.timing.repeat.durationUnit}
66
+ </>
67
+ )}
68
+ </p>
69
+ )}
70
+
71
+ {quantity && (
72
+ <p className={styles.bodyLong01}>
73
+ <span className={styles.label01}>{t('quantity', 'Quantity').toUpperCase()}</span>{' '}
74
+ <span className={styles.quantity}>
75
+ {quantity.value} {quantity.unit}
76
+ </span>
77
+ </p>
78
+ )}
79
+
80
+ {(refillsAllowed || refillsAllowed === 0) && (
81
+ <p className={styles.bodyLong01}>
82
+ <span className={styles.label01}>{t('refills', 'Refills').toUpperCase()}</span>{' '}
83
+ <span className={styles.refills}>{refillsAllowed}</span>
84
+ </p>
85
+ )}
86
+ {dosageInstruction?.text && <p className={styles.bodyLong01}>{dosageInstruction.text}</p>}
87
+ {dosageInstruction?.additionalInstruction?.length > 0 && (
88
+ <p className={styles.bodyLong01}>{dosageInstruction?.additionalInstruction[0].text}</p>
89
+ )}
90
+ </div>
91
+ {children}
92
+ </Tile>
93
+ );
94
+ };
95
+
96
+ export default MedicationEvent;
@@ -0,0 +1,44 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .medicationEventTile {
6
+ display: flex;
7
+ justify-content: space-between;
8
+ width: 100%;
9
+ margin: layout.$spacing-01 0 layout.$spacing-03;
10
+ padding: layout.$spacing-04 layout.$spacing-05 layout.$spacing-04 layout.$spacing-03;
11
+ background-color: #fff;
12
+ border-left: layout.$spacing-03 solid var(--brand-03);
13
+ color: $text-02;
14
+ margin-bottom: layout.$spacing-05 !important;
15
+
16
+ &.dispenseEvent {
17
+ border-left: layout.$spacing-03 solid red;
18
+ }
19
+
20
+ &.isTablet {
21
+ flex-direction: column;
22
+ gap: layout.$spacing-05;
23
+ }
24
+ }
25
+
26
+ .medicationName {
27
+ font-size: layout.$spacing-05 !important;
28
+ }
29
+
30
+ .bodyLong01 {
31
+ @include type.type-style('body-01');
32
+ font-size: 13px !important;
33
+ }
34
+
35
+ .label01 {
36
+ @include type.type-style('label-01');
37
+ }
38
+
39
+ .dosage,
40
+ .quantity,
41
+ .refills {
42
+ color: $text-02;
43
+ font-weight: bold;
44
+ }
@@ -0,0 +1,212 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { useConfig } from '@openmrs/esm-framework';
4
+ import { type MedicationRequest, MedicationRequestStatus } from '../types';
5
+ import MedicationEvent from './medication-event.component';
6
+
7
+ const mockUseConfig = jest.mocked(useConfig);
8
+
9
+ const baseMedicationRequest: MedicationRequest = {
10
+ resourceType: 'MedicationRequest',
11
+ id: 'test-medication-request-id',
12
+ meta: { lastUpdated: '2023-01-24T19:02:04.000-05:00' },
13
+ status: MedicationRequestStatus.active,
14
+ intent: 'order',
15
+ priority: 'routine',
16
+ medicationReference: {
17
+ reference: 'Medication/test-medication-id',
18
+ type: 'Medication',
19
+ display: 'Paracetamol 500mg tablet',
20
+ },
21
+ subject: {
22
+ reference: 'Patient/test-patient-id',
23
+ type: 'Patient',
24
+ display: 'Test Patient',
25
+ },
26
+ encounter: {
27
+ reference: 'Encounter/test-encounter-id',
28
+ type: 'Encounter',
29
+ },
30
+ requester: {
31
+ reference: 'Practitioner/test-practitioner-id',
32
+ type: 'Practitioner',
33
+ identifier: { value: 'TEST' },
34
+ display: 'Test Doctor',
35
+ },
36
+ dosageInstruction: [
37
+ {
38
+ text: 'Take one tablet twice daily',
39
+ timing: {
40
+ repeat: {
41
+ duration: 7,
42
+ durationUnit: 'd',
43
+ },
44
+ code: {
45
+ coding: [{ code: 'BID', display: 'Twice daily' }],
46
+ text: 'Twice daily',
47
+ },
48
+ },
49
+ route: {
50
+ coding: [{ code: 'PO', display: 'Oral' }],
51
+ text: 'Oral',
52
+ },
53
+ doseAndRate: [
54
+ {
55
+ doseQuantity: {
56
+ value: 1,
57
+ unit: 'tablet',
58
+ code: 'tablet',
59
+ },
60
+ },
61
+ ],
62
+ asNeededBoolean: false,
63
+ },
64
+ ],
65
+ dispenseRequest: {
66
+ validityPeriod: { start: '2023-01-24T19:02:04.000-05:00' },
67
+ numberOfRepeatsAllowed: 2,
68
+ quantity: {
69
+ value: 14,
70
+ unit: 'tablet',
71
+ code: 'tablet',
72
+ },
73
+ },
74
+ };
75
+
76
+ describe('MedicationEvent', () => {
77
+ beforeEach(() => {
78
+ mockUseConfig.mockReturnValue({
79
+ dispenseBehavior: {
80
+ allowModifyingPrescription: false,
81
+ restrictTotalQuantityDispensed: false,
82
+ },
83
+ });
84
+ });
85
+
86
+ it('renders medication name', () => {
87
+ render(<MedicationEvent medicationEvent={baseMedicationRequest} />);
88
+
89
+ expect(screen.getByText('Paracetamol 500mg tablet')).toBeInTheDocument();
90
+ });
91
+
92
+ it('renders status tag when provided', () => {
93
+ render(
94
+ <MedicationEvent medicationEvent={baseMedicationRequest} status={<span data-testid="status-tag">Active</span>} />,
95
+ );
96
+
97
+ expect(screen.getByTestId('status-tag')).toBeInTheDocument();
98
+ expect(screen.getByText('Active')).toBeInTheDocument();
99
+ });
100
+
101
+ it('renders dose information for structured dosage', () => {
102
+ render(<MedicationEvent medicationEvent={baseMedicationRequest} />);
103
+
104
+ expect(screen.getByText('DOSE')).toBeInTheDocument();
105
+ // Check for dose value and unit in the dosage section
106
+ expect(screen.getByText(/1 tablet/)).toBeInTheDocument();
107
+ });
108
+
109
+ it('renders route when available', () => {
110
+ render(<MedicationEvent medicationEvent={baseMedicationRequest} />);
111
+
112
+ expect(screen.getByText(/Oral/)).toBeInTheDocument();
113
+ });
114
+
115
+ it('renders timing when available', () => {
116
+ render(<MedicationEvent medicationEvent={baseMedicationRequest} />);
117
+
118
+ expect(screen.getByText(/Twice daily/)).toBeInTheDocument();
119
+ });
120
+
121
+ it('renders duration when available', () => {
122
+ render(<MedicationEvent medicationEvent={baseMedicationRequest} />);
123
+
124
+ expect(screen.getByText(/for 7 d/)).toBeInTheDocument();
125
+ });
126
+
127
+ it('renders quantity when available', () => {
128
+ render(<MedicationEvent medicationEvent={baseMedicationRequest} />);
129
+
130
+ expect(screen.getByText('QUANTITY')).toBeInTheDocument();
131
+ expect(screen.getByText(/14/)).toBeInTheDocument();
132
+ });
133
+
134
+ it('renders refills when available', () => {
135
+ render(<MedicationEvent medicationEvent={baseMedicationRequest} />);
136
+
137
+ expect(screen.getByText('REFILLS')).toBeInTheDocument();
138
+ expect(screen.getByText('2')).toBeInTheDocument();
139
+ });
140
+
141
+ it('renders free text dosage instructions', () => {
142
+ const freeTextMedicationRequest: MedicationRequest = {
143
+ ...baseMedicationRequest,
144
+ dosageInstruction: [
145
+ {
146
+ text: 'Take as directed by physician',
147
+ } as any,
148
+ ],
149
+ };
150
+
151
+ render(<MedicationEvent medicationEvent={freeTextMedicationRequest} />);
152
+
153
+ expect(screen.getByText('Take as directed by physician')).toBeInTheDocument();
154
+ });
155
+
156
+ it('does not render route dash separator when route is missing', () => {
157
+ const noRouteMedicationRequest: MedicationRequest = {
158
+ ...baseMedicationRequest,
159
+ dosageInstruction: [
160
+ {
161
+ doseAndRate: [
162
+ {
163
+ doseQuantity: {
164
+ value: 1,
165
+ unit: 'tablet',
166
+ code: 'tablet',
167
+ },
168
+ },
169
+ ],
170
+ timing: {
171
+ code: {
172
+ coding: [{ code: 'BID', display: 'Twice daily' }],
173
+ text: 'Twice daily',
174
+ },
175
+ },
176
+ asNeededBoolean: false,
177
+ route: {
178
+ coding: [],
179
+ },
180
+ } as any,
181
+ ],
182
+ };
183
+
184
+ render(<MedicationEvent medicationEvent={noRouteMedicationRequest} />);
185
+
186
+ // Should show dose and timing but not have orphaned dashes
187
+ expect(screen.getByText('DOSE')).toBeInTheDocument();
188
+ expect(screen.getByText(/Twice daily/)).toBeInTheDocument();
189
+ });
190
+
191
+ it('handles null dosageInstruction gracefully', () => {
192
+ const noDosageMedicationRequest: MedicationRequest = {
193
+ ...baseMedicationRequest,
194
+ dosageInstruction: null,
195
+ };
196
+
197
+ render(<MedicationEvent medicationEvent={noDosageMedicationRequest} />);
198
+
199
+ // Should still render medication name without crashing
200
+ expect(screen.getByText('Paracetamol 500mg tablet')).toBeInTheDocument();
201
+ });
202
+
203
+ it('renders children when provided', () => {
204
+ render(
205
+ <MedicationEvent medicationEvent={baseMedicationRequest}>
206
+ <button>Action Button</button>
207
+ </MedicationEvent>,
208
+ );
209
+
210
+ expect(screen.getByRole('button', { name: 'Action Button' })).toBeInTheDocument();
211
+ });
212
+ });
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import { Button } from '@carbon/react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { launchWorkspace2, type Session } from '@openmrs/esm-framework';
5
+ import { initiateMedicationDispenseBody } from '../../medication-dispense/medication-dispense.resource';
6
+ import { type Provider, type MedicationRequestBundle } from '../../types';
7
+
8
+ type CloseActionButtonProps = {
9
+ patientUuid: string;
10
+ encounterUuid: string;
11
+ medicationRequestBundle: MedicationRequestBundle;
12
+ session: Session;
13
+ providers: Array<Provider>;
14
+ closeable: boolean;
15
+ disabled: boolean;
16
+ };
17
+
18
+ const CloseActionButton: React.FC<CloseActionButtonProps> = ({
19
+ patientUuid,
20
+ encounterUuid,
21
+ medicationRequestBundle,
22
+ session,
23
+ providers,
24
+ closeable,
25
+ disabled,
26
+ }) => {
27
+ const { t } = useTranslation();
28
+
29
+ const closeDispenseFormProps = {
30
+ patientUuid,
31
+ encounterUuid,
32
+ medicationDispense: initiateMedicationDispenseBody(medicationRequestBundle.request, session, providers, false),
33
+ mode: 'enter',
34
+ };
35
+
36
+ const handleLaunchWorkspace = () => {
37
+ launchWorkspace2('close-dispense-workspace', closeDispenseFormProps);
38
+ };
39
+
40
+ if (!closeable) {
41
+ return null;
42
+ }
43
+ return (
44
+ <Button kind="danger" onClick={handleLaunchWorkspace} disabled={disabled}>
45
+ {t('close', 'Close')}
46
+ </Button>
47
+ );
48
+ };
49
+
50
+ export default CloseActionButton;
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+ import { Button } from '@carbon/react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { launchWorkspace2, type Session } from '@openmrs/esm-framework';
5
+ import { initiateMedicationDispenseBody } from '../../medication-dispense/medication-dispense.resource';
6
+ import { type Provider, type MedicationRequestBundle } from '../../types';
7
+
8
+ type DispenseActionButtonProps = {
9
+ patientUuid: string;
10
+ encounterUuid: string;
11
+ medicationRequestBundle: MedicationRequestBundle;
12
+ session: Session;
13
+ providers: Array<Provider>;
14
+ dispensable: boolean;
15
+ quantityRemaining: number;
16
+ quantityDispensed: number;
17
+ disabled: boolean;
18
+ };
19
+
20
+ const DispenseActionButton: React.FC<DispenseActionButtonProps> = ({
21
+ patientUuid,
22
+ encounterUuid,
23
+ medicationRequestBundle,
24
+ session,
25
+ providers,
26
+ dispensable,
27
+ quantityRemaining,
28
+ quantityDispensed,
29
+ disabled,
30
+ }) => {
31
+ const { t } = useTranslation();
32
+ const dispenseWorkspaceProps = {
33
+ patientUuid,
34
+ encounterUuid,
35
+ medicationDispense: initiateMedicationDispenseBody(medicationRequestBundle.request, session, providers, true),
36
+ medicationRequestBundle,
37
+ quantityRemaining,
38
+ quantityDispensed,
39
+ mode: 'enter',
40
+ };
41
+
42
+ const handleLaunchWorkspace = () => {
43
+ launchWorkspace2('dispense-workspace', dispenseWorkspaceProps);
44
+ };
45
+
46
+ if (!dispensable) {
47
+ return null;
48
+ }
49
+
50
+ return (
51
+ <Button kind="primary" onClick={handleLaunchWorkspace} disabled={disabled}>
52
+ {t('dispense', 'Dispense')}
53
+ </Button>
54
+ );
55
+ };
56
+
57
+ export default DispenseActionButton;
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import { Button } from '@carbon/react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { launchWorkspace2, type Session } from '@openmrs/esm-framework';
5
+ import { initiateMedicationDispenseBody } from '../../medication-dispense/medication-dispense.resource';
6
+ import { type Provider, type MedicationRequestBundle } from '../../types';
7
+
8
+ type PauseActionButtonProps = {
9
+ patientUuid: string;
10
+ encounterUuid: string;
11
+ medicationRequestBundle: MedicationRequestBundle;
12
+ session: Session;
13
+ providers: Array<Provider>;
14
+ pauseable: boolean;
15
+ disabled: boolean;
16
+ };
17
+
18
+ const PauseActionButton: React.FC<PauseActionButtonProps> = ({
19
+ patientUuid,
20
+ encounterUuid,
21
+ medicationRequestBundle,
22
+ session,
23
+ providers,
24
+ pauseable,
25
+ disabled,
26
+ }) => {
27
+ const { t } = useTranslation();
28
+ const pauseWorkspaceProps = {
29
+ patientUuid,
30
+ encounterUuid,
31
+ medicationDispense: initiateMedicationDispenseBody(medicationRequestBundle.request, session, providers, false),
32
+ mode: 'enter',
33
+ };
34
+
35
+ const handleLaunchWorkspace = () => {
36
+ launchWorkspace2('pause-dispense-workspace', pauseWorkspaceProps);
37
+ };
38
+
39
+ if (!pauseable) {
40
+ return null;
41
+ }
42
+ return (
43
+ <Button kind="secondary" onClick={handleLaunchWorkspace} disabled={disabled}>
44
+ {t('pause', 'Pause')}
45
+ </Button>
46
+ );
47
+ };
48
+
49
+ export default PauseActionButton;
@@ -0,0 +1,118 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import {
3
+ DataTable,
4
+ DataTableSkeleton,
5
+ Layer,
6
+ Pagination,
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableHead,
11
+ TableHeader,
12
+ TableRow,
13
+ } from '@carbon/react';
14
+ import { useTranslation } from 'react-i18next';
15
+ import { CardHeader, EmptyCard, ErrorState, usePagination } from '@openmrs/esm-framework';
16
+ import { pageSizesOptions, usePatientConditions } from './conditions.resource';
17
+ import styles from './conditions.scss';
18
+
19
+ type PatientConditionsProps = {
20
+ patientUuid: string;
21
+ };
22
+
23
+ const PatientConditions: React.FC<PatientConditionsProps> = ({ patientUuid }) => {
24
+ const { t } = useTranslation();
25
+ const { conditions, error, isLoading } = usePatientConditions(patientUuid);
26
+ const [pageSize, setPageSize] = useState(3);
27
+ const { results, currentPage, goTo } = usePagination(conditions, pageSize);
28
+ const headers = useMemo(
29
+ () => [
30
+ { header: t('conditions', 'Condition'), key: 'display' },
31
+ { header: t('onsetDate', 'Onset date'), key: 'onsetDateTime' },
32
+ ],
33
+ [t],
34
+ );
35
+
36
+ const getColumnClass = (key: string) => {
37
+ switch (key) {
38
+ case 'display':
39
+ return styles.conditionColumn;
40
+ case 'onsetDateTime':
41
+ return styles.onsetDateColumn;
42
+ default:
43
+ return '';
44
+ }
45
+ };
46
+
47
+ const title = t('activeConditions', 'Active conditions');
48
+ const tableRows = useMemo(
49
+ () =>
50
+ results.map((row) => ({
51
+ ...row,
52
+ onsetDateTime: row.onsetDateTime || '--',
53
+ })),
54
+ [results],
55
+ );
56
+
57
+ if (isLoading) {
58
+ return <DataTableSkeleton />;
59
+ }
60
+
61
+ if (error) {
62
+ return <ErrorState headerTitle={title} error={error} />;
63
+ }
64
+
65
+ if (!conditions?.length) {
66
+ return (
67
+ <Layer className={styles.conditionContainer}>
68
+ <EmptyCard headerTitle={title} displayText={t('activeConditionsEmpty', 'active conditions')} />
69
+ </Layer>
70
+ );
71
+ }
72
+
73
+ return (
74
+ <Layer className={styles.conditionContainer}>
75
+ <CardHeader title={title}>
76
+ <React.Fragment />
77
+ </CardHeader>
78
+ <DataTable useZebraStyles rows={tableRows} headers={headers}>
79
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
80
+ <Table {...getTableProps()} className={styles.table}>
81
+ <TableHead>
82
+ <TableRow>
83
+ {headers.map((header) => (
84
+ <TableHeader {...getHeaderProps({ header })} key={header.key} className={getColumnClass(header.key)}>
85
+ {header.header}
86
+ </TableHeader>
87
+ ))}
88
+ </TableRow>
89
+ </TableHead>
90
+ <TableBody>
91
+ {rows.map((row) => (
92
+ <TableRow {...getRowProps({ row })} key={row.id}>
93
+ {row.cells.map((cell) => (
94
+ <TableCell key={cell.id} className={getColumnClass(cell.info.header)}>
95
+ {cell.value}
96
+ </TableCell>
97
+ ))}
98
+ </TableRow>
99
+ ))}
100
+ </TableBody>
101
+ </Table>
102
+ )}
103
+ </DataTable>
104
+ <Pagination
105
+ page={currentPage}
106
+ pageSize={pageSize}
107
+ pageSizes={pageSizesOptions}
108
+ totalItems={conditions.length}
109
+ onChange={({ page, pageSize }) => {
110
+ goTo(page);
111
+ setPageSize(pageSize);
112
+ }}
113
+ />
114
+ </Layer>
115
+ );
116
+ };
117
+
118
+ export default PatientConditions;