@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,100 @@
1
+ import { useMemo } from 'react';
2
+ import { fhirBaseUrl, formatDate, parseDate, useFhirFetchAll } from '@openmrs/esm-framework';
3
+
4
+ export interface Resource {
5
+ resourceType: string;
6
+ id: string;
7
+ meta: ResourceMeta;
8
+ clinicalStatus: ClinicalStatus;
9
+ code: Code;
10
+ subject: Subject;
11
+ onsetDateTime: string;
12
+ recordedDate: string;
13
+ recorder: Recorder;
14
+ }
15
+
16
+ export interface ResourceMeta {
17
+ versionId: string;
18
+ lastUpdated: string;
19
+ tag: Array<ResourceMetaTag>;
20
+ }
21
+
22
+ export interface ResourceMetaTag {
23
+ system: string;
24
+ code: string;
25
+ display: string;
26
+ }
27
+
28
+ export interface ClinicalStatus {
29
+ coding: Array<Coding>;
30
+ }
31
+
32
+ export interface Coding {
33
+ system: string;
34
+ code: string;
35
+ }
36
+
37
+ export interface Code {
38
+ coding: Array<ConditionCoding>;
39
+ text: string;
40
+ }
41
+
42
+ export interface ConditionCoding {
43
+ code: string;
44
+ display?: string;
45
+ system?: string;
46
+ }
47
+
48
+ export interface Subject {
49
+ reference: string;
50
+ type: string;
51
+ display: string;
52
+ }
53
+
54
+ export interface Recorder {
55
+ reference: string;
56
+ type: string;
57
+ display: string;
58
+ }
59
+
60
+ export interface Condition {
61
+ id: string;
62
+ status?: 'active' | 'inactive';
63
+ display: string;
64
+ patient: string;
65
+ onsetDateTime: string | null;
66
+ recordedDate: string;
67
+ recorder: string;
68
+ }
69
+
70
+ export const usePatientConditions = (patientUuid: string) => {
71
+ const url = `${fhirBaseUrl}/Condition?patient=${patientUuid}&_summary=data&clinical-status=active`;
72
+ const { data, isLoading, mutate, error } = useFhirFetchAll<Resource>(url);
73
+
74
+ const conditions = useMemo(() => {
75
+ return data?.reduce<Array<Condition>>((prev, entry) => {
76
+ if (entry?.resourceType === 'Condition') {
77
+ const condition: Condition = {
78
+ id: entry.id,
79
+ display: entry?.code?.text ?? entry?.code?.coding?.[0]?.display,
80
+ patient: entry?.subject?.display,
81
+ recordedDate: entry?.recordedDate,
82
+ recorder: entry?.recorder?.display,
83
+ status: entry?.clinicalStatus?.coding[0]?.code as 'active' | 'inactive',
84
+ onsetDateTime: entry.onsetDateTime ? formatDate(parseDate(entry.onsetDateTime)) : null,
85
+ };
86
+ prev.push(condition);
87
+ }
88
+ return prev;
89
+ }, []);
90
+ }, [data]);
91
+
92
+ return {
93
+ conditions,
94
+ isLoading,
95
+ error,
96
+ mutate,
97
+ };
98
+ };
99
+
100
+ export const pageSizesOptions = [3, 5, 10, 20, 50, 100];
@@ -0,0 +1,26 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .conditionContainer {
6
+ background-color: colors.$white-0;
7
+ margin-bottom: layout.$spacing-05;
8
+ }
9
+
10
+ .table {
11
+ table-layout: fixed;
12
+ width: 100%;
13
+
14
+ td {
15
+ padding-left: 2rem !important;
16
+ }
17
+ }
18
+
19
+ .conditionColumn {
20
+ width: 70%;
21
+ }
22
+
23
+ .onsetDateColumn {
24
+ width: 30%;
25
+ text-align: left;
26
+ }
@@ -0,0 +1,200 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { usePagination } from '@openmrs/esm-framework';
4
+ import { usePatientConditions } from './conditions.resource';
5
+ import PatientConditions from './conditions.component';
6
+
7
+ jest.mock('./conditions.resource', () => ({
8
+ usePatientConditions: jest.fn(),
9
+ pageSizesOptions: [3, 5, 10],
10
+ }));
11
+
12
+ const mockUsePatientConditions = jest.mocked(usePatientConditions);
13
+ const mockUsePagination = jest.mocked(usePagination);
14
+
15
+ const mockPatientUuid = 'test-patient-uuid';
16
+
17
+ describe('PatientConditions', () => {
18
+ beforeEach(() => {
19
+ mockUsePagination.mockReturnValue({
20
+ results: [],
21
+ currentPage: 1,
22
+ goTo: jest.fn(),
23
+ goToNext: jest.fn(),
24
+ goToPrevious: jest.fn(),
25
+ paginated: false,
26
+ showNextButton: false,
27
+ showPreviousButton: false,
28
+ totalPages: 1,
29
+ });
30
+ });
31
+
32
+ it('renders loading skeleton while fetching conditions', () => {
33
+ mockUsePatientConditions.mockReturnValue({
34
+ conditions: [],
35
+ error: undefined,
36
+ isLoading: true,
37
+ mutate: jest.fn(),
38
+ });
39
+
40
+ render(<PatientConditions patientUuid={mockPatientUuid} />);
41
+
42
+ // DataTableSkeleton renders a table with skeleton rows
43
+ expect(screen.getByRole('table')).toBeInTheDocument();
44
+ });
45
+
46
+ it('renders error state when fetching fails', () => {
47
+ const errorMessage = 'Failed to fetch conditions';
48
+ mockUsePatientConditions.mockReturnValue({
49
+ conditions: [],
50
+ error: new Error(errorMessage),
51
+ isLoading: false,
52
+ mutate: jest.fn(),
53
+ });
54
+
55
+ render(<PatientConditions patientUuid={mockPatientUuid} />);
56
+
57
+ expect(screen.getByText(/error/i)).toBeInTheDocument();
58
+ });
59
+
60
+ it('renders empty state when no conditions exist', () => {
61
+ mockUsePatientConditions.mockReturnValue({
62
+ conditions: [],
63
+ error: undefined,
64
+ isLoading: false,
65
+ mutate: jest.fn(),
66
+ });
67
+
68
+ render(<PatientConditions patientUuid={mockPatientUuid} />);
69
+
70
+ // Verify EmptyCard is rendered with correct header title
71
+ expect(screen.getByTestId('empty-card')).toBeInTheDocument();
72
+ expect(screen.getByRole('heading', { level: 4, name: 'Active conditions' })).toBeInTheDocument();
73
+ });
74
+
75
+ it('renders conditions table with data', () => {
76
+ const conditions = [
77
+ {
78
+ id: 'condition-1',
79
+ display: 'Hypertension',
80
+ onsetDateTime: '15-Jan-2024',
81
+ patient: 'Test Patient',
82
+ recordedDate: '2024-01-15',
83
+ recorder: 'Dr. Smith',
84
+ status: 'active' as const,
85
+ },
86
+ {
87
+ id: 'condition-2',
88
+ display: 'Diabetes',
89
+ onsetDateTime: '10-Dec-2023',
90
+ patient: 'Test Patient',
91
+ recordedDate: '2023-12-10',
92
+ recorder: 'Dr. Jones',
93
+ status: 'active' as const,
94
+ },
95
+ ];
96
+
97
+ mockUsePatientConditions.mockReturnValue({
98
+ conditions,
99
+ error: undefined,
100
+ isLoading: false,
101
+ mutate: jest.fn(),
102
+ });
103
+
104
+ mockUsePagination.mockReturnValue({
105
+ results: conditions,
106
+ currentPage: 1,
107
+ goTo: jest.fn(),
108
+ goToNext: jest.fn(),
109
+ goToPrevious: jest.fn(),
110
+ paginated: false,
111
+ showNextButton: false,
112
+ showPreviousButton: false,
113
+ totalPages: 1,
114
+ });
115
+
116
+ render(<PatientConditions patientUuid={mockPatientUuid} />);
117
+
118
+ expect(screen.getByText('Active conditions')).toBeInTheDocument();
119
+ expect(screen.getByText('Hypertension')).toBeInTheDocument();
120
+ expect(screen.getByText('Diabetes')).toBeInTheDocument();
121
+ expect(screen.getByText('15-Jan-2024')).toBeInTheDocument();
122
+ expect(screen.getByText('10-Dec-2023')).toBeInTheDocument();
123
+ });
124
+
125
+ it('displays double dash for missing onset date', () => {
126
+ const conditions = [
127
+ {
128
+ id: 'condition-1',
129
+ display: 'Headache',
130
+ onsetDateTime: null,
131
+ patient: 'Test Patient',
132
+ recordedDate: '2024-01-15',
133
+ recorder: 'Dr. Smith',
134
+ status: 'active' as const,
135
+ },
136
+ ];
137
+
138
+ mockUsePatientConditions.mockReturnValue({
139
+ conditions,
140
+ error: undefined,
141
+ isLoading: false,
142
+ mutate: jest.fn(),
143
+ });
144
+
145
+ mockUsePagination.mockReturnValue({
146
+ results: conditions,
147
+ currentPage: 1,
148
+ goTo: jest.fn(),
149
+ goToNext: jest.fn(),
150
+ goToPrevious: jest.fn(),
151
+ paginated: false,
152
+ showNextButton: false,
153
+ showPreviousButton: false,
154
+ totalPages: 1,
155
+ });
156
+
157
+ render(<PatientConditions patientUuid={mockPatientUuid} />);
158
+
159
+ expect(screen.getByText('Headache')).toBeInTheDocument();
160
+ expect(screen.getByText('--')).toBeInTheDocument();
161
+ });
162
+
163
+ it('renders table headers correctly', () => {
164
+ const conditions = [
165
+ {
166
+ id: 'condition-1',
167
+ display: 'Test Condition',
168
+ onsetDateTime: '01-Jan-2024',
169
+ patient: 'Test Patient',
170
+ recordedDate: '2024-01-01',
171
+ recorder: 'Dr. Test',
172
+ status: 'active' as const,
173
+ },
174
+ ];
175
+
176
+ mockUsePatientConditions.mockReturnValue({
177
+ conditions,
178
+ error: undefined,
179
+ isLoading: false,
180
+ mutate: jest.fn(),
181
+ });
182
+
183
+ mockUsePagination.mockReturnValue({
184
+ results: conditions,
185
+ currentPage: 1,
186
+ goTo: jest.fn(),
187
+ goToNext: jest.fn(),
188
+ goToPrevious: jest.fn(),
189
+ paginated: false,
190
+ showNextButton: false,
191
+ showPreviousButton: false,
192
+ totalPages: 1,
193
+ });
194
+
195
+ render(<PatientConditions patientUuid={mockPatientUuid} />);
196
+
197
+ expect(screen.getByText('Condition')).toBeInTheDocument();
198
+ expect(screen.getByText('Onset date')).toBeInTheDocument();
199
+ });
200
+ });
@@ -0,0 +1,192 @@
1
+ import { Type, validators } from '@openmrs/esm-framework';
2
+ import { type CustomTab } from './types';
3
+
4
+ export const configSchema = {
5
+ appName: {
6
+ _type: Type.String,
7
+ _default: 'Pharmacy',
8
+ },
9
+ actionButtons: {
10
+ pauseButton: {
11
+ enabled: {
12
+ _type: Type.Boolean,
13
+ _description: 'Enabled/Disable including a Pause button in the button action bar',
14
+ _default: true,
15
+ },
16
+ },
17
+ closeButton: {
18
+ enabled: {
19
+ _type: Type.Boolean,
20
+ _description: 'Enabled/Disable including a Close button in the button action bar',
21
+ _default: true,
22
+ },
23
+ },
24
+ },
25
+ dispenseBehavior: {
26
+ allowModifyingPrescription: {
27
+ _type: Type.Boolean,
28
+ _description:
29
+ 'Enable/Disable editing the prescription. If Disabled, Quantity will be he only editable field on prescription form. Note that this means that quantity units will need to be mandatory and set correctly on the prescription.',
30
+ _default: true,
31
+ },
32
+ restrictTotalQuantityDispensed: {
33
+ _type: Type.Boolean,
34
+ _description:
35
+ 'Enable/Disable restricting dispensing quantity greater than total quantity ordered. Marks prescription as complete when total quantity dispensed. If true, allowModifyingPrescription *must* be false, as this functionality relies solely on numeric quantity and assumes no change in formulation, dosage, unit, etc',
36
+ _default: false,
37
+ },
38
+ },
39
+ dispenserProviderRoles: {
40
+ _type: Type.Array,
41
+ _description:
42
+ 'Array of provider roles uuids. If specified, only providers with these roles will be listed in the "Dispensed By" dropdown. Note that this simply restricts the providers that can be recorded as Dispensers, it does not limit who can create dispense events.',
43
+ _default: [],
44
+ },
45
+ medicationRequestExpirationPeriodInDays: {
46
+ _type: Type.Number,
47
+ _description: 'Medication Requests older that this will be considered expired',
48
+ _default: 90,
49
+ },
50
+ locationBehavior: {
51
+ locationColumn: {
52
+ enabled: {
53
+ _type: Type.Boolean,
54
+ _description:
55
+ 'Enabled/Disable including a Location column in the main prescriptions table showing ordering location',
56
+ _default: false,
57
+ },
58
+ },
59
+ locationFilter: {
60
+ enabled: {
61
+ _type: Type.Boolean,
62
+ _description: 'Enable/Disable Location filter on main prescriptions page',
63
+ _default: false,
64
+ },
65
+ tag: {
66
+ _type: Type.String,
67
+ _description: 'Name of the location tag to use when fetching locations to populate filter',
68
+ _default: 'Login Location',
69
+ },
70
+ associatedPharmacyLocationAttribute: {
71
+ _type: Type.String,
72
+ _description: 'Name of the attribute used to associate locations with a pharmacy location',
73
+ _default: 'Associated Pharmacy Location',
74
+ },
75
+ },
76
+ },
77
+ refreshInterval: {
78
+ _type: Type.Number,
79
+ _description: 'The interval, in milliseconds, to query the backend for new/changed data',
80
+ _default: 60000,
81
+ },
82
+ valueSets: {
83
+ reasonForPause: {
84
+ uuid: {
85
+ _type: Type.UUID,
86
+ _description:
87
+ "UUID for the Value Set of valid answers to the 'Reason for Pause' question. Defaults to CIEL value set: https://app.openconceptlab.org/#/orgs/CIEL/sources/CIEL/concepts/168099/",
88
+ _default: '2462a9d7-61fb-4bf5-9359-aedecb8d03cb',
89
+ },
90
+ },
91
+ reasonForClose: {
92
+ uuid: {
93
+ _type: Type.UUID,
94
+ _description:
95
+ "UUID for the Value Set of valid answers to the 'Reason for Close' question. Defaults to CIEL value set: https://app.openconceptlab.org/#/orgs/CIEL/sources/CIEL/concepts/168099/",
96
+ _default: '2462a9d7-61fb-4bf5-9359-aedecb8d03cb',
97
+ },
98
+ },
99
+ substitutionReason: {
100
+ uuid: {
101
+ _type: Type.UUID,
102
+ _description:
103
+ "UUID for the Value Set of valid answers to the 'Reason for Substitution' question. Defaults to CIEL value set: https://app.openconceptlab.org/#/orgs/CIEL/sources/CIEL/concepts/167862/",
104
+ _default: '2de6e1be-f2dd-4ba0-9516-8a611aa2af9b',
105
+ },
106
+ },
107
+ substitutionType: {
108
+ uuid: {
109
+ _type: Type.UUID,
110
+ _description:
111
+ "UUID for the Value Set of valid answers to the 'Type of Substitution' question. Defaults to CIEL value set: https://app.openconceptlab.org/#/orgs/CIEL/sources/CIEL/concepts/167859/",
112
+ _default: 'b9c5bca0-d026-4245-a4d2-e4c0a8999082',
113
+ },
114
+ },
115
+ },
116
+ enableStockDispense: {
117
+ _type: Type.Boolean,
118
+ _description:
119
+ 'Enable or disable stock deduction during the dispensing process. Requires the stock management module to be installed and configured.',
120
+ _default: false,
121
+ },
122
+ validateBatch: {
123
+ _type: Type.Boolean,
124
+ _description:
125
+ 'Enable or disable stock item batch number validation. Requires the stock management module to be installed and configured.',
126
+ _default: true,
127
+ },
128
+ leftNavMode: {
129
+ _type: Type.String,
130
+ _description: 'Sets the left nav mode for the dispensing app.',
131
+ _validators: [validators.oneOf(['normal', 'collapsed', 'hidden'])],
132
+ _default: 'collapsed',
133
+ },
134
+ completeOrderWithThisDispense: {
135
+ _type: Type.Boolean,
136
+ _description: 'Enable or disable the "Complete order with this dispense" checkbox on the dispense form.',
137
+ _default: false,
138
+ },
139
+ customTabs: {
140
+ _type: Type.Array,
141
+ _description: 'Custom tabs to be added to the prescription panel. See README for more details.',
142
+ _default: [],
143
+ },
144
+ };
145
+
146
+ export interface PharmacyConfig {
147
+ appName: string;
148
+ actionButtons: {
149
+ pauseButton: {
150
+ enabled: boolean;
151
+ };
152
+ closeButton: {
153
+ enabled: boolean;
154
+ };
155
+ };
156
+ refreshInterval: number;
157
+ dispenseBehavior: {
158
+ allowModifyingPrescription: boolean;
159
+ restrictTotalQuantityDispensed: boolean;
160
+ };
161
+ dispenserProviderRoles: [];
162
+ medicationRequestExpirationPeriodInDays: number;
163
+ locationBehavior: {
164
+ locationColumn: {
165
+ enabled: boolean;
166
+ };
167
+ locationFilter: {
168
+ enabled: boolean;
169
+ tag: string;
170
+ associatedPharmacyLocationAttribute: string;
171
+ };
172
+ };
173
+ valueSets: {
174
+ reasonForPause: {
175
+ uuid: string;
176
+ };
177
+ reasonForClose: {
178
+ uuid: string;
179
+ };
180
+ substitutionReason: {
181
+ uuid: string;
182
+ };
183
+ substitutionType: {
184
+ uuid: string;
185
+ };
186
+ };
187
+ enableStockDispense: boolean;
188
+ completeOrderWithThisDispense: boolean;
189
+ validateBatch: boolean;
190
+ leftNavMode: 'normal' | 'collapsed' | 'hidden';
191
+ customTabs: Array<CustomTab>;
192
+ }
@@ -0,0 +1,22 @@
1
+ export const spaRoot = window['getOpenmrsSpaBase'];
2
+ export const basePath = '/dispensing';
3
+ export const spaBasePath = `${window.spaBase}${basePath}`;
4
+
5
+ // defined in FHIR 2 module
6
+ export const OPENMRS_FHIR_PREFIX = 'http://fhir.openmrs.org';
7
+ export const OPENMRS_FHIR_EXT_PREFIX = OPENMRS_FHIR_PREFIX + '/ext';
8
+ export const OPENMRS_FHIR_EXT_MEDICINE = OPENMRS_FHIR_EXT_PREFIX + '/medicine';
9
+ export const OPENMRS_FHIR_EXT_DISPENSE_RECORDED = OPENMRS_FHIR_EXT_PREFIX + '/medicationdispense/recorded';
10
+ export const OPENMRS_FHIR_EXT_REQUEST_FULFILLER_STATUS = OPENMRS_FHIR_EXT_PREFIX + '/medicationrequest/fulfillerstatus';
11
+
12
+ export const PRIVILEGE_CREATE_DISPENSE = 'Task: dispensing.create.dispense';
13
+ export const PRIVILEGE_CREATE_DISPENSE_MODIFY_DETAILS = 'Task: dispensing.create.dispense.allowSubstitutions';
14
+ export const PRIVILEGE_EDIT_DISPENSE = 'Task: dispensing.edit.dispense';
15
+ export const PRIVILEGE_DELETE_DISPENSE = 'Task: dispensing.delete.dispense';
16
+ export const PRIVILEGE_DELETE_DISPENSE_THIS_PROVIDER_ONLY = 'Task: dispensing.delete.dispense.ifCreator';
17
+
18
+ export const JSON_MERGE_PATH_MIME_TYPE = 'application/merge-patch+json';
19
+
20
+ export const PRESCRIPTIONS_TABLE_ENDPOINT = 'Encounter?_query=encountersWithMedicationRequests';
21
+
22
+ export const PRESCRIPTION_DETAILS_ENDPOINT = 'MedicationRequest';
@@ -0,0 +1,36 @@
1
+ import React, { useMemo } from 'react';
2
+ import classNames from 'classnames';
3
+ import { BrowserRouter } from 'react-router-dom';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { ConfigurableLink } from '@openmrs/esm-framework';
6
+
7
+ const DispensingDashboardLink = () => {
8
+ return (
9
+ <BrowserRouter>
10
+ <DashboardExtension />
11
+ </BrowserRouter>
12
+ );
13
+ };
14
+
15
+ export default DispensingDashboardLink;
16
+
17
+ function DashboardExtension() {
18
+ const { t } = useTranslation();
19
+ const spaBasePath = `${window.spaBase}/home`;
20
+
21
+ const navLink = useMemo(() => {
22
+ const pathArray = location.pathname.split('/home');
23
+ const lastElement = pathArray[pathArray.length - 1];
24
+ return decodeURIComponent(lastElement);
25
+ }, []);
26
+
27
+ return (
28
+ <ConfigurableLink
29
+ className={classNames('cds--side-nav__link', {
30
+ 'active-left-nav-link': navLink.match('dispensing'),
31
+ })}
32
+ to={`${spaBasePath}/dispensing`}>
33
+ {t('dispensing', 'Dispensing')}
34
+ </ConfigurableLink>
35
+ );
36
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { InlineNotification } from '@carbon/react';
4
+ import { useConfig } from '@openmrs/esm-framework';
5
+ import { type PharmacyConfig } from '../config-schema';
6
+ import { PharmacyHeader } from '../pharmacy-header/pharmacy-header.component';
7
+ import PrescriptionTabLists from '../prescriptions/prescription-tab-lists.component';
8
+ import FillPrescriptionButton from '../fill-prescription/fill-prescription-button.component';
9
+
10
+ export default function DispensingDashboard() {
11
+ const config = useConfig<PharmacyConfig>();
12
+ const { t } = useTranslation();
13
+
14
+ if (config.dispenseBehavior.restrictTotalQuantityDispensed && config.dispenseBehavior.allowModifyingPrescription) {
15
+ return (
16
+ <div className={`omrs-main-content`}>
17
+ <InlineNotification
18
+ title={t('dispensingAppError', 'Dispensing App Error')}
19
+ subtitle={t(
20
+ 'dispensingAppMisconfigurationMessage',
21
+ "Please contact your system administration: Misconfiguration - 'restrictTotalQuantityDispensed' cannot be enabled if 'allowModifyingPrescription' is enabled.",
22
+ )}
23
+ />
24
+ </div>
25
+ );
26
+ } else {
27
+ return (
28
+ <div className={`omrs-main-content`}>
29
+ <PharmacyHeader />
30
+ {/* <DispensingTiles /> */}
31
+ <FillPrescriptionButton />
32
+ <PrescriptionTabLists />
33
+ </div>
34
+ );
35
+ }
36
+ }
@@ -0,0 +1,2 @@
1
+ declare module '*.css';
2
+ declare module '*.scss';