@kenyaemr/esm-service-queues-app 7.0.2-pre.65

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 (289) hide show
  1. package/.turbo/turbo-build.log +40 -0
  2. package/README.md +20 -0
  3. package/dist/130.js +2 -0
  4. package/dist/130.js.LICENSE.txt +3 -0
  5. package/dist/130.js.map +1 -0
  6. package/dist/152.js +1 -0
  7. package/dist/152.js.map +1 -0
  8. package/dist/169.js +1 -0
  9. package/dist/169.js.map +1 -0
  10. package/dist/185.js +1 -0
  11. package/dist/185.js.map +1 -0
  12. package/dist/233.js +1 -0
  13. package/dist/233.js.map +1 -0
  14. package/dist/237.js +1 -0
  15. package/dist/237.js.map +1 -0
  16. package/dist/255.js +2 -0
  17. package/dist/255.js.LICENSE.txt +9 -0
  18. package/dist/255.js.map +1 -0
  19. package/dist/271.js +1 -0
  20. package/dist/276.js +1 -0
  21. package/dist/276.js.map +1 -0
  22. package/dist/303.js +1 -0
  23. package/dist/303.js.map +1 -0
  24. package/dist/319.js +1 -0
  25. package/dist/401.js +1 -0
  26. package/dist/401.js.map +1 -0
  27. package/dist/430.js +2 -0
  28. package/dist/430.js.LICENSE.txt +50 -0
  29. package/dist/430.js.map +1 -0
  30. package/dist/460.js +1 -0
  31. package/dist/501.js +1 -0
  32. package/dist/501.js.map +1 -0
  33. package/dist/574.js +1 -0
  34. package/dist/591.js +2 -0
  35. package/dist/591.js.LICENSE.txt +32 -0
  36. package/dist/591.js.map +1 -0
  37. package/dist/644.js +1 -0
  38. package/dist/647.js +1 -0
  39. package/dist/647.js.map +1 -0
  40. package/dist/650.js +1 -0
  41. package/dist/650.js.map +1 -0
  42. package/dist/669.js +1 -0
  43. package/dist/669.js.map +1 -0
  44. package/dist/696.js +1 -0
  45. package/dist/696.js.map +1 -0
  46. package/dist/703.js +1 -0
  47. package/dist/703.js.map +1 -0
  48. package/dist/729.js +1 -0
  49. package/dist/729.js.map +1 -0
  50. package/dist/738.js +1 -0
  51. package/dist/738.js.map +1 -0
  52. package/dist/757.js +1 -0
  53. package/dist/764.js +1 -0
  54. package/dist/764.js.map +1 -0
  55. package/dist/784.js +2 -0
  56. package/dist/784.js.LICENSE.txt +9 -0
  57. package/dist/784.js.map +1 -0
  58. package/dist/788.js +1 -0
  59. package/dist/806.js +1 -0
  60. package/dist/806.js.map +1 -0
  61. package/dist/807.js +1 -0
  62. package/dist/833.js +1 -0
  63. package/dist/877.js +1 -0
  64. package/dist/877.js.map +1 -0
  65. package/dist/917.js +1 -0
  66. package/dist/917.js.map +1 -0
  67. package/dist/940.js +1 -0
  68. package/dist/940.js.map +1 -0
  69. package/dist/981.js +1 -0
  70. package/dist/981.js.map +1 -0
  71. package/dist/kenyaemr-esm-service-queues-app.js +1 -0
  72. package/dist/kenyaemr-esm-service-queues-app.js.buildmanifest.json +965 -0
  73. package/dist/kenyaemr-esm-service-queues-app.js.map +1 -0
  74. package/dist/main.js +2 -0
  75. package/dist/main.js.LICENSE.txt +60 -0
  76. package/dist/main.js.map +1 -0
  77. package/dist/routes.json +1 -0
  78. package/jest.config.js +3 -0
  79. package/package.json +54 -0
  80. package/src/active-visits/active-visits-row-actions.component.tsx +25 -0
  81. package/src/active-visits/active-visits-row-actions.scss +4 -0
  82. package/src/active-visits/active-visits-table.resource.ts +273 -0
  83. package/src/active-visits/change-status-dialog.component.tsx +272 -0
  84. package/src/active-visits/change-status-dialog.scss +47 -0
  85. package/src/active-visits/change-status-dialog.test.tsx +154 -0
  86. package/src/add-patient-toqueue/add-patient-toqueue-dialog.component.tsx +228 -0
  87. package/src/add-patient-toqueue/add-patient-toqueue-dialog.scss +32 -0
  88. package/src/add-provider-queue-room/add-provider-queue-room.component.tsx +238 -0
  89. package/src/add-provider-queue-room/add-provider-queue-room.resource.ts +76 -0
  90. package/src/add-provider-queue-room/add-provider-queue-room.scss +17 -0
  91. package/src/add-provider-queue-room/add-provider-queue-room.test.tsx +105 -0
  92. package/src/clear-queue-entries-dialog/clear-queue-entries-dialog.component.tsx +76 -0
  93. package/src/clear-queue-entries-dialog/clear-queue-entries-dialog.resource.ts +7 -0
  94. package/src/clear-queue-entries-dialog/clear-queue-entries-dialog.scss +8 -0
  95. package/src/clear-queue-entries-dialog/clear-queue-entries-dialog.test.tsx +43 -0
  96. package/src/clear-queue-entries-dialog/clear-queue-entries.component.tsx +43 -0
  97. package/src/config-schema.ts +465 -0
  98. package/src/constants.ts +10 -0
  99. package/src/createDashboardLink.component.tsx +39 -0
  100. package/src/current-visit/current-visit-summary.component.tsx +38 -0
  101. package/src/current-visit/current-visit-summary.test.tsx +43 -0
  102. package/src/current-visit/current-visit.resource.ts +84 -0
  103. package/src/current-visit/current-visit.scss +34 -0
  104. package/src/current-visit/hooks/useVitalsConceptMetadata.tsx +101 -0
  105. package/src/current-visit/visit-details/biometrics-config-schema.ts +14 -0
  106. package/src/current-visit/visit-details/current-visit-details.component.tsx +96 -0
  107. package/src/current-visit/visit-details/triage-note.component.tsx +53 -0
  108. package/src/current-visit/visit-details/triage-note.scss +89 -0
  109. package/src/current-visit/visit-details/vitals-config-schema.ts +17 -0
  110. package/src/current-visit/visit-details/vitals.component.tsx +165 -0
  111. package/src/dashboard.meta.ts +5 -0
  112. package/src/declarations.d.ts +4 -0
  113. package/src/helpers/functions.ts +28 -0
  114. package/src/helpers/helpers.ts +177 -0
  115. package/src/helpers/time-helpers.ts +15 -0
  116. package/src/home.component.tsx +16 -0
  117. package/src/home.test.tsx +48 -0
  118. package/src/hooks/useConcept.ts +13 -0
  119. package/src/hooks/useQueue.ts +10 -0
  120. package/src/hooks/useQueueEntries.ts +187 -0
  121. package/src/hooks/useQueues.ts +22 -0
  122. package/src/hooks/useSystemSetting.ts +18 -0
  123. package/src/index.ts +172 -0
  124. package/src/past-visit/past-visit-details/encounter-list.component.tsx +54 -0
  125. package/src/past-visit/past-visit-details/medications-list.component.tsx +98 -0
  126. package/src/past-visit/past-visit-details/notes-list.component.tsx +41 -0
  127. package/src/past-visit/past-visit-details/past-visit-summary.component.tsx +181 -0
  128. package/src/past-visit/past-visit-details/past-visit-summary.scss +58 -0
  129. package/src/past-visit/past-visit.component.tsx +37 -0
  130. package/src/past-visit/past-visit.resource.ts +83 -0
  131. package/src/past-visit/past-visit.scss +126 -0
  132. package/src/past-visit/past-visit.test.tsx +41 -0
  133. package/src/patient-info/appointment-details.component.tsx +98 -0
  134. package/src/patient-info/appointment-details.scss +34 -0
  135. package/src/patient-info/appointment-details.test.tsx +36 -0
  136. package/src/patient-info/appointments.resource.ts +43 -0
  137. package/src/patient-info/hooks/usePatientAttributes.tsx +42 -0
  138. package/src/patient-info/patient-info.component.tsx +82 -0
  139. package/src/patient-info/patient-info.scss +60 -0
  140. package/src/patient-info/patient-info.test.tsx +43 -0
  141. package/src/patient-queue-header/patient-queue-header.component.tsx +99 -0
  142. package/src/patient-queue-header/patient-queue-header.scss +90 -0
  143. package/src/patient-queue-header/patient-queue-illustration.component.tsx +22 -0
  144. package/src/patient-queue-metrics/clinic-metrics.component.tsx +98 -0
  145. package/src/patient-queue-metrics/clinic-metrics.resource.ts +58 -0
  146. package/src/patient-queue-metrics/clinic-metrics.scss +11 -0
  147. package/src/patient-queue-metrics/clinic-metrics.test.tsx +76 -0
  148. package/src/patient-queue-metrics/metrics-card.component.tsx +68 -0
  149. package/src/patient-queue-metrics/metrics-card.scss +80 -0
  150. package/src/patient-queue-metrics/metrics-header.component.tsx +61 -0
  151. package/src/patient-queue-metrics/metrics-header.scss +26 -0
  152. package/src/patient-queue-metrics/queue-metrics.resource.ts +42 -0
  153. package/src/patient-search/advanced-search.component.tsx +191 -0
  154. package/src/patient-search/advanced-search.scss +154 -0
  155. package/src/patient-search/advanced-search.test.tsx +29 -0
  156. package/src/patient-search/basic-search.component.tsx +112 -0
  157. package/src/patient-search/basic-search.scss +139 -0
  158. package/src/patient-search/basic-search.test.tsx +23 -0
  159. package/src/patient-search/empty-data-illustration.component.tsx +41 -0
  160. package/src/patient-search/hooks/useActivePatientEnrollment.tsx +29 -0
  161. package/src/patient-search/hooks/useDefaultLocation.ts +14 -0
  162. package/src/patient-search/hooks/usePatients.tsx +25 -0
  163. package/src/patient-search/hooks/useQueueLocations.tsx +23 -0
  164. package/src/patient-search/hooks/useRecommendedVisitTypes.tsx +35 -0
  165. package/src/patient-search/hooks/useScheduledVisits.ts +52 -0
  166. package/src/patient-search/patient-scheduled-visits.component.tsx +324 -0
  167. package/src/patient-search/patient-scheduled-visits.scss +131 -0
  168. package/src/patient-search/patient-scheduled-visits.test.tsx +44 -0
  169. package/src/patient-search/patient-search.scss +43 -0
  170. package/src/patient-search/patient-search.workspace.tsx +135 -0
  171. package/src/patient-search/search-illustration.component.tsx +27 -0
  172. package/src/patient-search/search-results.component.tsx +75 -0
  173. package/src/patient-search/search-results.scss +80 -0
  174. package/src/patient-search/search-results.test.tsx +77 -0
  175. package/src/patient-search/search.resource.ts +10 -0
  176. package/src/patient-search/visit-form/existing-visit-form.component.tsx +112 -0
  177. package/src/patient-search/visit-form/queue.resource.ts +64 -0
  178. package/src/patient-search/visit-form/visit-form.component.tsx +344 -0
  179. package/src/patient-search/visit-form/visit-form.scss +73 -0
  180. package/src/patient-search/visit-form/visit-type-selector.component.tsx +155 -0
  181. package/src/patient-search/visit-form/visit-type-selector.scss +100 -0
  182. package/src/patient-search/visit-form/visit-type-selector.test.tsx +83 -0
  183. package/src/patient-search/visit-form-queue-fields/visit-form-queue-fields.component.tsx +178 -0
  184. package/src/patient-search/visit-form-queue-fields/visit-form-queue-fields.scss +19 -0
  185. package/src/patient-search/visit-form-queue-fields/visit-form-queue-fields.test.tsx +63 -0
  186. package/src/queue-entry-table-components/edit-entry.scss +14 -0
  187. package/src/queue-entry-table-components/queue-duration.component.tsx +41 -0
  188. package/src/queue-entry-table-components/queue-priority.component.tsx +38 -0
  189. package/src/queue-entry-table-components/queue-priority.scss +12 -0
  190. package/src/queue-entry-table-components/queue-status.component.tsx +39 -0
  191. package/src/queue-entry-table-components/transition-entry.component.tsx +55 -0
  192. package/src/queue-entry-table-components/transition-entry.scss +22 -0
  193. package/src/queue-patient-linelists/queue-linelist-base-table.component.tsx +241 -0
  194. package/src/queue-patient-linelists/queue-linelist-base-table.scss +110 -0
  195. package/src/queue-patient-linelists/queue-linelist-base-table.test.tsx +93 -0
  196. package/src/queue-patient-linelists/queue-linelist-filter.scss +63 -0
  197. package/src/queue-patient-linelists/queue-linelist-filter.test.tsx +94 -0
  198. package/src/queue-patient-linelists/queue-linelist-filter.workspace.tsx +185 -0
  199. package/src/queue-patient-linelists/queue-linelist.resource.ts +84 -0
  200. package/src/queue-patient-linelists/queue-services-table.component.tsx +63 -0
  201. package/src/queue-patient-linelists/scheduled-appointments-table.component.tsx +305 -0
  202. package/src/queue-patient-linelists/scheduled-appointments-table.test.tsx +41 -0
  203. package/src/queue-rooms/queue-room-form.scss +56 -0
  204. package/src/queue-rooms/queue-room-form.test.tsx +80 -0
  205. package/src/queue-rooms/queue-room-form.workspace.tsx +169 -0
  206. package/src/queue-rooms/queue-room.resource.ts +20 -0
  207. package/src/queue-screen/queue-screen.component.tsx +47 -0
  208. package/src/queue-screen/queue-screen.scss +39 -0
  209. package/src/queue-screen/queue-screen.test.tsx +51 -0
  210. package/src/queue-screen/useActiveTickets.tsx +13 -0
  211. package/src/queue-services/queue-service-form.scss +61 -0
  212. package/src/queue-services/queue-service-form.test.tsx +60 -0
  213. package/src/queue-services/queue-service-form.workspace.tsx +179 -0
  214. package/src/queue-services/queue-service.resource.ts +33 -0
  215. package/src/queue-table/cells/columns.resource.ts +135 -0
  216. package/src/queue-table/cells/queue-table-action-cell.component.tsx +88 -0
  217. package/src/queue-table/cells/queue-table-action-cell.scss +7 -0
  218. package/src/queue-table/cells/queue-table-coming-from-cell.component.tsx +13 -0
  219. package/src/queue-table/cells/queue-table-extension-cell.component.tsx +16 -0
  220. package/src/queue-table/cells/queue-table-name-cell.component.tsx +20 -0
  221. package/src/queue-table/cells/queue-table-patient-age-cell.component.tsx +18 -0
  222. package/src/queue-table/cells/queue-table-patient-identifier-cell.component.tsx +25 -0
  223. package/src/queue-table/cells/queue-table-priority-cell.component.tsx +23 -0
  224. package/src/queue-table/cells/queue-table-queue-name-cell.component.tsx +14 -0
  225. package/src/queue-table/cells/queue-table-status-cell.component.tsx +18 -0
  226. package/src/queue-table/cells/queue-table-visit-attribute-queue-number-cell.component.tsx +37 -0
  227. package/src/queue-table/cells/queue-table-visit-start-time-cell.component.tsx +20 -0
  228. package/src/queue-table/cells/queue-table-wait-time-cell.component.tsx +17 -0
  229. package/src/queue-table/default-queue-table.component.tsx +174 -0
  230. package/src/queue-table/default-queue-table.test.tsx +131 -0
  231. package/src/queue-table/queue-entry-actions/edit-queue-entry-modal.component.tsx +52 -0
  232. package/src/queue-table/queue-entry-actions/end-queue-entry-modal.component.tsx +39 -0
  233. package/src/queue-table/queue-entry-actions/queue-entry-actions-modal.component.tsx +362 -0
  234. package/src/queue-table/queue-entry-actions/queue-entry-actions-modal.test.tsx +152 -0
  235. package/src/queue-table/queue-entry-actions/queue-entry-actions.resource.ts +83 -0
  236. package/src/queue-table/queue-entry-actions/queue-entry-actons-modal.scss +36 -0
  237. package/src/queue-table/queue-entry-actions/queue-entry-confirm-action-modal.component.tsx +97 -0
  238. package/src/queue-table/queue-entry-actions/queue-entry-confirm-action-modal.test.tsx +106 -0
  239. package/src/queue-table/queue-entry-actions/queue-entry-undo-actions-modal.test.tsx +76 -0
  240. package/src/queue-table/queue-entry-actions/transition-queue-entry-modal.component.tsx +51 -0
  241. package/src/queue-table/queue-entry-actions/undo-transition-queue-entry-modal.component.tsx +58 -0
  242. package/src/queue-table/queue-entry-actions/void-queue-entry-modal.component.tsx +34 -0
  243. package/src/queue-table/queue-table-by-status-menu.component.tsx +42 -0
  244. package/src/queue-table/queue-table-by-status-menu.scss +11 -0
  245. package/src/queue-table/queue-table-by-status-skeleton.component.tsx +32 -0
  246. package/src/queue-table/queue-table-by-status.component.tsx +96 -0
  247. package/src/queue-table/queue-table-expanded-row.component.tsx +29 -0
  248. package/src/queue-table/queue-table-metrics-card.component.tsx +50 -0
  249. package/src/queue-table/queue-table-metrics-card.scss +48 -0
  250. package/src/queue-table/queue-table-metrics.component.tsx +30 -0
  251. package/src/queue-table/queue-table-metrics.scss +11 -0
  252. package/src/queue-table/queue-table.component.tsx +179 -0
  253. package/src/queue-table/queue-table.scss +192 -0
  254. package/src/queue-table/queue-table.test.tsx +210 -0
  255. package/src/remove-queue-entry-dialog/remove-queue-entry.component.tsx +87 -0
  256. package/src/remove-queue-entry-dialog/remove-queue-entry.resource.ts +93 -0
  257. package/src/remove-queue-entry-dialog/remove-queue-entry.scss +7 -0
  258. package/src/remove-queue-entry-dialog/remove-queue-entry.test.tsx +45 -0
  259. package/src/root.component.tsx +28 -0
  260. package/src/root.scss +15 -0
  261. package/src/root.test.tsx +24 -0
  262. package/src/routes.json +133 -0
  263. package/src/side-menu/nav-group/createNavGroup.tsx +17 -0
  264. package/src/side-menu/nav-group/nav-group.component.tsx +24 -0
  265. package/src/side-menu/nav-group/nav-group.test.tsx +32 -0
  266. package/src/side-menu/nav-group/nav-group.ts +10 -0
  267. package/src/side-menu/side-menu.component.tsx +6 -0
  268. package/src/side-menu/side-menu.test.tsx +17 -0
  269. package/src/transition-queue-entry/transition-queue-entry-dialog.component.tsx +134 -0
  270. package/src/transition-queue-entry/transition-queue-entry-dialog.scss +12 -0
  271. package/src/transition-queue-entry/transition-queue-entry-dialog.test.tsx +102 -0
  272. package/src/transition-queue-entry/transition-queue-entry.resource.ts +16 -0
  273. package/src/types/index.ts +494 -0
  274. package/src/views/queue-table-by-status-view.component.tsx +25 -0
  275. package/src/views/queue-tables-for-all-statuses.component.tsx +150 -0
  276. package/src/visits-missing-inqueue/visits-missing-inqueue.component.tsx +277 -0
  277. package/src/visits-missing-inqueue/visits-missing-inqueue.resource.ts +93 -0
  278. package/src/visits-missing-inqueue/visits-missing-inqueue.scss +108 -0
  279. package/translations/am.json +295 -0
  280. package/translations/ar.json +295 -0
  281. package/translations/en.json +305 -0
  282. package/translations/es.json +295 -0
  283. package/translations/fr.json +295 -0
  284. package/translations/he.json +295 -0
  285. package/translations/km.json +295 -0
  286. package/translations/zh.json +295 -0
  287. package/translations/zh_CN.json +295 -0
  288. package/tsconfig.json +5 -0
  289. package/webpack.config.js +1 -0
@@ -0,0 +1,210 @@
1
+ import React from 'react';
2
+ import { defineConfigSchema, getDefaultsFromConfigSchema, useConfig, useSession } from '@openmrs/esm-framework';
3
+ import { screen, within } from '@testing-library/react';
4
+ import { mockPriorityNonUrgent, mockQueueEntries, mockSession } from '__mocks__';
5
+ import { renderWithSwr } from 'tools';
6
+ import QueueTable from './queue-table.component';
7
+ import { type ConfigObject, configSchema } from '../config-schema';
8
+
9
+ const mockUseSession = jest.mocked(useSession);
10
+ const mockUseConfig = jest.mocked(useConfig);
11
+ const configDefaults = getDefaultsFromConfigSchema<ConfigObject>(configSchema);
12
+
13
+ const configWithCustomColumns = {
14
+ queueTables: {
15
+ columnDefinitions: [
16
+ {
17
+ id: 'emr-id',
18
+ columnType: 'patient-identifier',
19
+ config: {
20
+ identifierType: '8d793bee-c2cc-11de-8d13-0010c6dffd0f',
21
+ },
22
+ header: 'EMR ID',
23
+ },
24
+ {
25
+ id: 'in-service-time',
26
+ columnType: 'wait-time',
27
+ header: 'Time in service',
28
+ },
29
+ {
30
+ id: 'check-in-date',
31
+ columnType: 'visit-start-time',
32
+ header: 'Check-in date',
33
+ },
34
+ ],
35
+ tableDefinitions: [
36
+ {
37
+ columns: ['patient-name', 'emr-id', 'patient-age', 'check-in-date', 'wait-time', 'actions'],
38
+ appliedTo: [
39
+ {
40
+ queue: 'triage-queue-uuid',
41
+ status: 'waiting-status-uuid',
42
+ },
43
+ ],
44
+ },
45
+ {
46
+ columns: ['patient-name', 'emr-id', 'patient-age', 'in-service-time', 'actions'],
47
+ appliedTo: [
48
+ {
49
+ queue: 'triage-queue-uuid',
50
+ status: 'in-service-status-uuid',
51
+ },
52
+ ],
53
+ },
54
+ ],
55
+ },
56
+ };
57
+
58
+ describe('QueueTable', () => {
59
+ beforeEach(() => {
60
+ mockUseSession.mockReturnValue(mockSession.data);
61
+ mockUseConfig.mockReturnValue(configDefaults);
62
+ });
63
+
64
+ it('renders an empty table with default columns when there are no queue entries', () => {
65
+ renderWithSwr(<QueueTable queueEntries={[]} statusUuid={null} queueUuid={null} />);
66
+
67
+ const rows = screen.queryAllByRole('row');
68
+ expect(rows).toHaveLength(1); // should only have the header row
69
+
70
+ const headerRow = rows[0];
71
+ const expectedHeaders = [/name/i, /coming from/i, /priority/i, /status/i, /queue/i, /wait time/i, /actions/i];
72
+ const headers = within(headerRow).getAllByRole('columnheader');
73
+ for (let i = 0; i < headers.length; i++) {
74
+ expect(headers[i]).toHaveTextContent(expectedHeaders[i]);
75
+ }
76
+ });
77
+
78
+ it('renders queue entries with default columns', () => {
79
+ renderWithSwr(<QueueTable queueEntries={mockQueueEntries} statusUuid={null} queueUuid={null} />);
80
+
81
+ for (const entry of mockQueueEntries) {
82
+ const patientName = entry.patient.display;
83
+ const row = screen.getByText(patientName).closest('tr');
84
+
85
+ expect(within(row).getByText(entry.status.display)).toBeInTheDocument();
86
+ expect(within(row).getByText(entry.priority.display)).toBeInTheDocument();
87
+ }
88
+ });
89
+
90
+ it('allows modifying the columns of the table applied to all queues and statuses', () => {
91
+ mockUseConfig.mockReturnValue({
92
+ ...configDefaults,
93
+ queueTables: {
94
+ ...configDefaults.queueTables,
95
+ tableDefinitions: [
96
+ {
97
+ columns: ['patient-name', 'status', 'wait-time', 'actions'],
98
+ appliedTo: [{ queue: null, status: null }],
99
+ },
100
+ ],
101
+ },
102
+ });
103
+
104
+ renderWithSwr(<QueueTable queueEntries={mockQueueEntries} statusUuid={'foo'} queueUuid={'bar'} />);
105
+
106
+ const rows = screen.queryAllByRole('row');
107
+ const headerRow = rows[0];
108
+ const expectedHeaders = [/name/i, /status/i, /wait time/i, /actions/i];
109
+ const headers = within(headerRow).getAllByRole('columnheader');
110
+ for (let i = 0; i < headers.length; i++) {
111
+ expect(headers[i]).toHaveTextContent(expectedHeaders[i]);
112
+ }
113
+ });
114
+
115
+ it('will use the correct default table even when not provided explicitly in the config', () => {
116
+ mockUseConfig.mockReturnValue({
117
+ ...configDefaults,
118
+ ...configWithCustomColumns,
119
+ });
120
+
121
+ renderWithSwr(<QueueTable queueEntries={mockQueueEntries} statusUuid={'foo'} queueUuid={'bar'} />);
122
+
123
+ const rows = screen.queryAllByRole('row');
124
+ const headerRow = rows[0];
125
+ const expectedHeaders = [/name/i, /coming from/i, /priority/i, /status/i, /queue/i, /wait time/i, /actions/i];
126
+ const headers = within(headerRow).getAllByRole('columnheader');
127
+ for (let i = 0; i < headers.length; i++) {
128
+ expect(headers[i]).toHaveTextContent(expectedHeaders[i]);
129
+ }
130
+ });
131
+
132
+ it('allows modifying the columns of tables by queue and status, including with custom column definitions', () => {
133
+ mockUseConfig.mockReturnValue({
134
+ ...configDefaults,
135
+ ...configWithCustomColumns,
136
+ });
137
+
138
+ renderWithSwr(
139
+ <QueueTable
140
+ queueEntries={mockQueueEntries}
141
+ statusUuid={'in-service-status-uuid'}
142
+ queueUuid={'triage-queue-uuid'}
143
+ />,
144
+ );
145
+
146
+ const rows = screen.queryAllByRole('row');
147
+ const headerRow = rows[0];
148
+ const expectedHeaders = [/name/i, /EMR ID/i, /age/i, /time in service/i, /actions/i];
149
+ const headers = within(headerRow).getAllByRole('columnheader');
150
+ for (let i = 0; i < headers.length; i++) {
151
+ expect(headers[i]).toHaveTextContent(expectedHeaders[i]);
152
+ }
153
+ });
154
+
155
+ it('supports custom styles for priority tags at column level', () => {
156
+ mockUseConfig.mockReturnValue({
157
+ ...configDefaults,
158
+ queueTables: {
159
+ columnDefinitions: [
160
+ {
161
+ id: 'priority',
162
+ config: {
163
+ priorityConfigs: [
164
+ {
165
+ conceptUuid: mockPriorityNonUrgent.uuid,
166
+ color: 'blue',
167
+ style: 'bold',
168
+ },
169
+ ],
170
+ },
171
+ },
172
+ ],
173
+ tableDefinitions: [
174
+ {
175
+ columns: ['patient-name', 'priority'],
176
+ },
177
+ ],
178
+ },
179
+ });
180
+
181
+ renderWithSwr(<QueueTable queueEntries={mockQueueEntries} statusUuid={null} queueUuid={'triage-queue-uuid'} />);
182
+
183
+ const rows = screen.queryAllByRole('row');
184
+ const firstRow = rows[1];
185
+ const cells = within(firstRow).getAllByRole('cell');
186
+ expect(cells[1].childNodes[0]).toHaveClass('bold');
187
+ });
188
+
189
+ it('uses the visitQueueNumberAttributeUuid defined at the top level', () => {
190
+ mockUseConfig.mockReturnValue({
191
+ ...configDefaults,
192
+ visitQueueNumberAttributeUuid: 'queue-number-visit-attr-uuid',
193
+ queueTables: {
194
+ ...configDefaults.queueTables,
195
+ tableDefinitions: [
196
+ {
197
+ columns: ['patient-name', 'queue-number'],
198
+ },
199
+ ],
200
+ },
201
+ });
202
+
203
+ renderWithSwr(<QueueTable queueEntries={mockQueueEntries} statusUuid={null} queueUuid={'triage-queue-uuid'} />);
204
+
205
+ const rows = screen.queryAllByRole('row');
206
+ const aliceRow = rows[2];
207
+ const cells = within(aliceRow).getAllByRole('cell');
208
+ expect(cells[1].childNodes[0]).toHaveTextContent('42');
209
+ });
210
+ });
@@ -0,0 +1,87 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
4
+ import { parseDate, showSnackbar, useVisit } from '@openmrs/esm-framework';
5
+ import { type MappedQueueEntry } from '../types';
6
+ import { startOfDay } from '../constants';
7
+ import { useCheckedInAppointments, endQueueEntry } from './remove-queue-entry.resource';
8
+ import styles from './remove-queue-entry.scss';
9
+ import { useMutateQueueEntries } from '../hooks/useQueueEntries';
10
+
11
+ interface RemoveQueueEntryDialogProps {
12
+ queueEntry: MappedQueueEntry;
13
+ closeModal: () => void;
14
+ }
15
+
16
+ const RemoveQueueEntryDialog: React.FC<RemoveQueueEntryDialogProps> = ({ queueEntry, closeModal }) => {
17
+ const { t } = useTranslation();
18
+ const { currentVisit } = useVisit(queueEntry.patientUuid);
19
+ const { mutateQueueEntries } = useMutateQueueEntries();
20
+
21
+ const { data: appointments } = useCheckedInAppointments(queueEntry.patientUuid, startOfDay);
22
+
23
+ const removeQueueEntry = () => {
24
+ const endCurrentVisitPayload = {
25
+ location: currentVisit?.location?.uuid,
26
+ startDatetime: parseDate(currentVisit?.startDatetime),
27
+ visitType: currentVisit?.visitType?.uuid,
28
+ stopDatetime: new Date(),
29
+ };
30
+
31
+ const endedAt = new Date();
32
+
33
+ endQueueEntry(
34
+ queueEntry.queue.uuid,
35
+ queueEntry.queueEntryUuid,
36
+ endedAt,
37
+ endCurrentVisitPayload,
38
+ queueEntry.visitUuid,
39
+ appointments,
40
+ ).then((response) => {
41
+ closeModal();
42
+ mutateQueueEntries();
43
+ showSnackbar({
44
+ isLowContrast: true,
45
+ kind: 'success',
46
+ subtitle: t('queueEntryRemovedSuccessfully', `Queue entry removed successfully`),
47
+ title: t('queueEntryRemoved', 'Queue entry removed'),
48
+ });
49
+ (error) => {
50
+ showSnackbar({
51
+ title: t('removeQueueEntryError', 'Error removing queue entry'),
52
+ kind: 'error',
53
+ isLowContrast: false,
54
+ subtitle: error?.message,
55
+ });
56
+ };
57
+ });
58
+ };
59
+
60
+ return (
61
+ <div>
62
+ <ModalHeader
63
+ closeModal={closeModal}
64
+ label={t('serviceQueue', 'Service queue')}
65
+ title={t('removeFromQueueAndEndVisit', 'Remove patient from queue and end active visit?')}
66
+ />
67
+ <ModalBody>
68
+ <p className={styles.subHeading} id="subHeading">
69
+ {t(
70
+ 'endVisitWarningMessage',
71
+ 'Ending this visit will remove this patient from the queue and will not allow you to fill another encounter form for this patient',
72
+ )}
73
+ </p>
74
+ </ModalBody>
75
+ <ModalFooter>
76
+ <Button kind="secondary" onClick={closeModal}>
77
+ {t('cancel', 'Cancel')}
78
+ </Button>
79
+ <Button kind="danger" onClick={removeQueueEntry}>
80
+ {t('endVisit', 'End visit')}
81
+ </Button>
82
+ </ModalFooter>
83
+ </div>
84
+ );
85
+ };
86
+
87
+ export default RemoveQueueEntryDialog;
@@ -0,0 +1,93 @@
1
+ import { openmrsFetch, restBaseUrl, updateVisit } from '@openmrs/esm-framework';
2
+ import dayjs from 'dayjs';
3
+ import { endPatientStatus } from '../active-visits/active-visits-table.resource';
4
+ import { type AppointmentsFetchResponse, type EndVisitPayload } from '../types';
5
+ import useSWR from 'swr';
6
+ import { omrsDateFormat, timeZone } from '../constants';
7
+ import { first } from 'rxjs/operators';
8
+
9
+ const statusChangeTime = dayjs(new Date()).format(omrsDateFormat);
10
+
11
+ export async function endQueueEntry(
12
+ queueUuid: string,
13
+
14
+ queueEntryUuid: string,
15
+ endedAt: Date,
16
+ endCurrentVisitPayload: EndVisitPayload,
17
+ visitUuid: string,
18
+ appointments: any,
19
+ ) {
20
+ const abortController = new AbortController();
21
+
22
+ if (endCurrentVisitPayload) {
23
+ if (appointments?.length) {
24
+ appointments.forEach(async (appointment) => {
25
+ await Promise.all([changeAppointmentStatus('Completed', appointment.uuid)]);
26
+ });
27
+ }
28
+
29
+ await Promise.all([endPatientStatus(queueUuid, queueEntryUuid, endedAt)]);
30
+
31
+ return updateVisit(visitUuid, endCurrentVisitPayload, abortController)
32
+ .pipe(first())
33
+ .subscribe(
34
+ (response) => {
35
+ return response.status;
36
+ },
37
+ (error) => {
38
+ return error;
39
+ },
40
+ );
41
+ } else {
42
+ return await Promise.all([endPatientStatus(queueUuid, queueEntryUuid, endedAt)])
43
+ .then((res) => {
44
+ return res;
45
+ })
46
+ .catch((error) => {
47
+ return error;
48
+ });
49
+ }
50
+ }
51
+
52
+ export function useCheckedInAppointments(patientUuid: string, startDate: string) {
53
+ const abortController = new AbortController();
54
+
55
+ const appointmentsSearchUrl = `${restBaseUrl}/appointments/search`;
56
+ const fetcher = () =>
57
+ openmrsFetch(appointmentsSearchUrl, {
58
+ method: 'POST',
59
+ signal: abortController.signal,
60
+ headers: {
61
+ 'Content-Type': 'application/json',
62
+ },
63
+ body: {
64
+ patientUuid: patientUuid,
65
+ startDate: startDate,
66
+ },
67
+ });
68
+
69
+ const { data, error, isLoading, isValidating } = useSWR<AppointmentsFetchResponse, Error>(
70
+ appointmentsSearchUrl,
71
+ fetcher,
72
+ );
73
+
74
+ const appointments = data?.data?.length
75
+ ? data.data.filter((appointment) => appointment.status === 'CheckedIn')
76
+ : null;
77
+
78
+ return {
79
+ data: data ? appointments : null,
80
+ isError: error,
81
+ isLoading,
82
+ isValidating,
83
+ };
84
+ }
85
+
86
+ export async function changeAppointmentStatus(toStatus: string, appointmentUuid: string) {
87
+ const url = `${restBaseUrl}/appointments/${appointmentUuid}/status-change`;
88
+ return openmrsFetch(url, {
89
+ body: { toStatus, onDate: statusChangeTime, timeZone: timeZone },
90
+ method: 'POST',
91
+ headers: { 'Content-Type': 'application/json' },
92
+ });
93
+ }
@@ -0,0 +1,7 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @import '~@openmrs/esm-styleguide/src/vars';
3
+
4
+ #subHeading {
5
+ @include type.type-style('heading-compact-01');
6
+ color: $ui-05;
7
+ }
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen } from '@testing-library/react';
4
+ import RemoveQueueEntryDialog from './remove-queue-entry.component';
5
+
6
+ jest.mock('@openmrs/esm-framework', () => ({
7
+ ...jest.requireActual('@openmrs/esm-framework'),
8
+ voidQueueEntry: jest.fn(),
9
+ showNotification: jest.fn(),
10
+ useConfig: jest.fn(() => ({
11
+ visitQueueNumberAttributeUuid: 'c61ce16f-272a-41e7-9924-4c555d0932c5',
12
+ })),
13
+ }));
14
+
15
+ describe('RemoveQueueEntryDialog', () => {
16
+ const queueEntry = {
17
+ queueUuid: 'fa1e98f1-f002-4174-9e55-34d60951e710',
18
+ queueEntryUuid: '712289ab-32c0-430f-87b6-d9c1e4e4686e',
19
+ visitUuid: 'c90386ff-ae85-45cc-8a01-25852099c5ae',
20
+ patientUuid: 'cc75ad73-c24b-499c-8db9-a7ef4fc0b36d',
21
+ };
22
+
23
+ it('renders dialog content', () => {
24
+ const closeModal = jest.fn();
25
+ render(<RemoveQueueEntryDialog queueEntry={queueEntry} closeModal={closeModal} />);
26
+
27
+ expect(screen.getByText('Service queue')).toBeInTheDocument();
28
+ expect(screen.getByText('Remove patient from queue and end active visit?')).toBeInTheDocument();
29
+ expect(screen.getByText(/Ending this visit will remove this patient/)).toBeInTheDocument();
30
+
31
+ expect(screen.getByText('Cancel')).toBeInTheDocument();
32
+ expect(screen.getByText('End visit')).toBeInTheDocument();
33
+ });
34
+
35
+ it('calls closeModal when Cancel button is clicked', async () => {
36
+ const user = userEvent.setup();
37
+ const closeModal = jest.fn();
38
+
39
+ render(<RemoveQueueEntryDialog queueEntry={queueEntry} closeModal={closeModal} />);
40
+
41
+ await user.click(screen.getByText('Cancel'));
42
+
43
+ expect(closeModal).toHaveBeenCalledTimes(1);
44
+ });
45
+ });
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { BrowserRouter, Route, Routes } from 'react-router-dom';
3
+ import AppointmentsTable from './queue-patient-linelists/scheduled-appointments-table.component';
4
+ import Home from './home.component';
5
+ import ServicesTable from './queue-patient-linelists/queue-services-table.component';
6
+ import QueueScreen from './queue-screen/queue-screen.component';
7
+ import QueueTableByStatusView from './views/queue-table-by-status-view.component';
8
+
9
+ const Root: React.FC = () => {
10
+ const serviceQueuesBasename = window.getOpenmrsSpaBase() + 'home/service-queues';
11
+
12
+ return (
13
+ <main>
14
+ <BrowserRouter basename={serviceQueuesBasename}>
15
+ <Routes>
16
+ <Route path="/" element={<Home />} />
17
+ <Route path="/queue-table-by-status/:queueUuid" element={<QueueTableByStatusView />} />
18
+ <Route path="/queue-table-by-status/:queueUuid/:statusUuid" element={<QueueTableByStatusView />} />
19
+ <Route path="/screen" element={<QueueScreen />} />
20
+ <Route path="/appointments-list/:value/" element={<AppointmentsTable />} />
21
+ <Route path="/queue-list/:service/:serviceUuid/:locationUuid" element={<ServicesTable />} />
22
+ </Routes>
23
+ </BrowserRouter>
24
+ </main>
25
+ );
26
+ };
27
+
28
+ export default Root;
package/src/root.scss ADDED
@@ -0,0 +1,15 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .productiveHeading01 {
6
+ @include type.type-style('heading-compact-01');
7
+ }
8
+
9
+ .productiveHeading02 {
10
+ @include type.type-style('heading-compact-02');
11
+ }
12
+
13
+ .bodyLong01 {
14
+ @include type.type-style('body-01');
15
+ }
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import Root from './root.component';
4
+
5
+ describe('Root Component', () => {
6
+ // Mock the pushState function and store the original implementation
7
+ const originalPushState = window.history.pushState;
8
+
9
+ // Restore the original pushState after all tests
10
+ afterAll(() => {
11
+ window.history.pushState = originalPushState;
12
+ });
13
+
14
+ it('renders ServicesTable component for "/service-queues/queue-list/:service/:serviceUuid/:locationUuid', () => {
15
+ window.history.pushState(
16
+ {},
17
+ 'Queue List',
18
+ '/openmrs/spa/home/service-queues/queue-list/:service/:serviceUuid/:locationUuid',
19
+ );
20
+ render(<Root />);
21
+
22
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
23
+ });
24
+ });
@@ -0,0 +1,133 @@
1
+ {
2
+ "$schema": "https://json.openmrs.org/routes.schema.json",
3
+ "backendDependencies": {
4
+ "webservices.rest": "^2.2.0",
5
+ "queue": "^2.4.0-0"
6
+ },
7
+ "extensions": [
8
+ {
9
+ "name": "outpatient-side-nav-ext",
10
+ "component": "outpatientSideNav",
11
+ "slot": "outpatient-sidebar-slot",
12
+ "online": true,
13
+ "offline": true
14
+ },
15
+ {
16
+ "name": "service-queues-dashboard-link",
17
+ "component": "serviceQueuesDashboardLink",
18
+ "slot": "homepage-dashboard-slot",
19
+ "meta": {
20
+ "name": "service-queues",
21
+ "slot": "service-queues-dashboard-slot",
22
+ "title": "Service queues"
23
+ },
24
+ "online": true,
25
+ "offline": true
26
+ },
27
+ {
28
+ "name": "queue-table-by-status-menu-dashboard-link",
29
+ "component": "queueTableByStatusMenu",
30
+ "meta": {
31
+ "name": "service-queues",
32
+ "slot": "service-queues-dashboard-slot",
33
+ "title": "Service queues"
34
+ },
35
+ "online": true,
36
+ "offline": true
37
+ },
38
+ {
39
+ "component": "root",
40
+ "name": "service-queues-dashboard",
41
+ "slot": "service-queues-dashboard-slot"
42
+ },
43
+ {
44
+ "name": "edit-queue-entry-status-modal",
45
+ "component": "editQueueEntryStatusModal"
46
+ },
47
+ {
48
+ "name": "patient-info-banner-slot",
49
+ "component": "patientInfoBannerSlot"
50
+ },
51
+ {
52
+ "name": "remove-queue-entry",
53
+ "component": "removeQueueEntry"
54
+ },
55
+ {
56
+ "name": "clear-all-queue-entries",
57
+ "component": "clearAllQueueEntries"
58
+ },
59
+ {
60
+ "name": "add-visit-to-queue-modal",
61
+ "component": "addVisitToQueueModal"
62
+ },
63
+ {
64
+ "name": "transition-queue-entry-status-modal",
65
+ "component": "transitionQueueEntryStatusModal"
66
+ },
67
+ {
68
+ "name": "previous-visit-summary-widget",
69
+ "component": "pastVisitSummary",
70
+ "slot": "previous-visit-summary-slot"
71
+ },
72
+ {
73
+ "name": "add-provider-to-room-modal",
74
+ "component": "addProviderToRoomModal"
75
+ },
76
+ {
77
+ "name": "transition-queue-entry-modal",
78
+ "component": "transitionQueueEntryModal"
79
+ },
80
+ {
81
+ "name": "edit-queue-entry-modal",
82
+ "component": "editQueueEntryModal"
83
+ },
84
+ {
85
+ "name": "undo-transition-queue-entry-modal",
86
+ "component": "undoTransitionQueueEntryModal"
87
+ },
88
+ {
89
+ "name": "void-queue-entry-modal",
90
+ "component": "voidQueueEntryModal"
91
+ },
92
+ {
93
+ "name": "end-queue-entry-modal",
94
+ "component": "endQueueEntryModal"
95
+ },
96
+ {
97
+ "name": "active-visits-row-actions",
98
+ "component": "activeVisitsRowActions",
99
+ "slot": "queue-table-serve-patient-slot"
100
+ },
101
+ {
102
+ "name": "visit-form-queue-fields",
103
+ "component": "visitFormQueueFields",
104
+ "slot":"visit-form-queue-slot"
105
+ }
106
+ ],
107
+ "workspaces": [
108
+ {
109
+ "name": "service-queues-service-form",
110
+ "title": "addNewQueueService",
111
+ "component": "addNewQueueServiceWorkspace",
112
+ "type": "service-queues"
113
+ },
114
+ {
115
+ "name": "service-queues-room-form",
116
+ "title": "addNewQueueServiceRoom",
117
+ "component": "addNewQueueServiceRoomWorkspace",
118
+ "type": "service-queues"
119
+ },
120
+ {
121
+ "name": "service-queues-linelist-filter",
122
+ "title": "filter",
123
+ "component": "queueLinelistFilterWorkspace",
124
+ "type": "service-queues"
125
+ },
126
+ {
127
+ "name": "service-queues-patient-search",
128
+ "title": "searchPatient",
129
+ "component": "patientSearchWorkspace",
130
+ "type": "service-queues"
131
+ }
132
+ ]
133
+ }
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import NavGroupExtension from './nav-group.component';
3
+
4
+ export const createNavGroup = ({
5
+ title,
6
+ slot,
7
+ renderIcon,
8
+ }: {
9
+ title: string;
10
+ slot: string;
11
+ renderIcon?: React.ComponentType<any>;
12
+ }) => {
13
+ const NavGroup = () => {
14
+ return <NavGroupExtension title={title} slotName={slot} renderIcon={renderIcon} />;
15
+ };
16
+ return NavGroup;
17
+ };
@@ -0,0 +1,24 @@
1
+ import React, { useEffect } from 'react';
2
+ import { ExtensionSlot } from '@openmrs/esm-framework';
3
+ import { SideNavMenu } from '@carbon/react';
4
+ import { registerNavGroup } from './nav-group';
5
+
6
+ export interface NavGroupExtensionProps {
7
+ title: string;
8
+ slotName: string;
9
+ renderIcon?: React.ComponentType<any>;
10
+ }
11
+
12
+ const NavGroupExtension = ({ title, slotName, renderIcon }: NavGroupExtensionProps) => {
13
+ useEffect(() => {
14
+ registerNavGroup(slotName);
15
+ }, [slotName]);
16
+
17
+ return (
18
+ <SideNavMenu renderIcon={renderIcon} title={title}>
19
+ <ExtensionSlot name={slotName} />
20
+ </SideNavMenu>
21
+ );
22
+ };
23
+
24
+ export default NavGroupExtension;