@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,83 @@
1
+ import React from 'react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { VisitTypeSelector } from './visit-type-selector.component';
4
+ import { render, screen } from '@testing-library/react';
5
+ import { mockPatient, mockVisitTypes } from '__mocks__';
6
+ import { useVisitTypes } from '@openmrs/esm-framework';
7
+
8
+ const mockUseVisitTypes = jest.mocked(useVisitTypes);
9
+
10
+ describe('VisitTypeSelector', () => {
11
+ beforeEach(() => {
12
+ mockUseVisitTypes.mockReturnValue(mockVisitTypes);
13
+ });
14
+
15
+ it('renders visit types with no search bar if there are 5 or fewer', () => {
16
+ const fewVisitTypes = mockVisitTypes.slice(0, 3);
17
+ mockUseVisitTypes.mockReturnValue(fewVisitTypes);
18
+ render(<VisitTypeSelector onChange={() => {}} />);
19
+
20
+ expect(screen.queryByRole('searchbox')).not.toBeInTheDocument();
21
+
22
+ fewVisitTypes.forEach((visitType) => {
23
+ const radioButton = screen.getByLabelText(visitType.display);
24
+ expect(radioButton).toBeInTheDocument();
25
+ });
26
+ });
27
+
28
+ it('renders the first 5 visit types with a search bar if there are more than 5', () => {
29
+ render(<VisitTypeSelector onChange={() => {}} />);
30
+
31
+ expect(screen.queryByRole('searchbox')).toBeInTheDocument();
32
+
33
+ mockVisitTypes.slice(0, 5).forEach((visitType) => {
34
+ const radioButton = screen.getByLabelText(visitType.display);
35
+ expect(radioButton).toBeInTheDocument();
36
+ });
37
+ });
38
+
39
+ it('filters by search input and calls onChange', async () => {
40
+ const user = userEvent.setup();
41
+
42
+ const mockOnChange = jest.fn();
43
+ render(<VisitTypeSelector onChange={mockOnChange} />);
44
+
45
+ const searchInput = screen.getByRole('searchbox').closest('input');
46
+ await user.type(searchInput, 'hiv');
47
+
48
+ expect(searchInput.value).toBe('hiv');
49
+ expect(screen.getByLabelText('HIV Return Visit')).toBeInTheDocument();
50
+ expect(screen.getByLabelText('HIV Initial Visit')).toBeInTheDocument();
51
+ expect(screen.queryByLabelText('Outpatient Visit')).not.toBeInTheDocument();
52
+ expect(screen.queryByLabelText('Diabetes Clinic Visit')).not.toBeInTheDocument();
53
+
54
+ expect(mockOnChange).toHaveBeenLastCalledWith(
55
+ mockVisitTypes.filter((vt) => vt.display == 'HIV Return Visit')[0].uuid,
56
+ );
57
+ });
58
+
59
+ it('calls onChange when a visit type is selected', async () => {
60
+ const user = userEvent.setup();
61
+
62
+ const mockOnChange = jest.fn();
63
+ render(<VisitTypeSelector onChange={mockOnChange} />);
64
+
65
+ const radioButton = screen.getByLabelText(mockVisitTypes[1].display).closest('input');
66
+ await user.click(radioButton);
67
+ expect(radioButton).toBeChecked();
68
+ expect(mockOnChange).toHaveBeenLastCalledWith(mockVisitTypes[1].uuid);
69
+ });
70
+
71
+ it('allows changing the search input if no results are returned from a search', async () => {
72
+ const user = userEvent.setup();
73
+
74
+ render(<VisitTypeSelector onChange={() => {}} />);
75
+
76
+ const searchInput: HTMLInputElement = screen.getByRole('searchbox');
77
+ await user.type(searchInput, 'asdfasdf');
78
+
79
+ const searchInputAfter: HTMLInputElement = screen.getByRole('searchbox');
80
+ expect(searchInputAfter).toBeInTheDocument();
81
+ expect(screen.getByText(/no visit types/i)).toBeInTheDocument();
82
+ });
83
+ });
@@ -0,0 +1,178 @@
1
+ import React, { useContext, useEffect, useRef, useState } from 'react';
2
+ import classNames from 'classnames';
3
+ import { InlineNotification, Select, SelectItem, RadioButtonGroup, RadioButton, TextInput } from '@carbon/react';
4
+ import { useQueueLocations } from '../hooks/useQueueLocations';
5
+ import styles from './visit-form-queue-fields.scss';
6
+ import { useConfig, ResponsiveWrapper, useSession } from '@openmrs/esm-framework';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { useQueues } from '../../hooks/useQueues';
9
+ import { type ConfigObject } from '../../config-schema';
10
+ import { SelectSkeleton } from '@carbon/react';
11
+ import { RadioButtonSkeleton } from '@carbon/react';
12
+ import { AddPatientToQueueContext } from '../patient-search.workspace';
13
+
14
+ export interface VisitFormQueueFieldsProps {
15
+ setFormFields: (fields: {
16
+ queueLocation: string;
17
+ service: string;
18
+ status: string;
19
+ priority: string;
20
+ sortWeight: number;
21
+ }) => void;
22
+ }
23
+
24
+ const VisitFormQueueFields: React.FC<VisitFormQueueFieldsProps> = (props) => {
25
+ const { setFormFields } = props;
26
+ const { t } = useTranslation();
27
+ const { queueLocations, isLoading: isLoadingQueueLocations } = useQueueLocations();
28
+ const { sessionLocation } = useSession();
29
+ const config = useConfig<ConfigObject>();
30
+ const [selectedQueueLocation, setSelectedQueueLocation] = useState(queueLocations[0]?.id);
31
+ const { queues, isLoading: isLoadingQueues } = useQueues(selectedQueueLocation);
32
+ const defaultStatus = config.concepts.defaultStatusConceptUuid;
33
+ const [selectedService, setSelectedService] = useState('');
34
+ const { currentServiceQueueUuid } = useContext(AddPatientToQueueContext);
35
+ const [priority, setPriority] = useState(config.concepts.defaultPriorityConceptUuid);
36
+ const priorities = queues.find((q) => q.uuid === selectedService)?.allowedPriorities ?? [];
37
+ const [sortWeight, setSortWeight] = useState(0);
38
+
39
+ useEffect(() => {
40
+ if (priority === config.concepts.emergencyPriorityConceptUuid) {
41
+ setSortWeight(1);
42
+ }
43
+ }, [priority]);
44
+
45
+ useEffect(() => {
46
+ if (currentServiceQueueUuid) {
47
+ setSelectedService(currentServiceQueueUuid);
48
+ }
49
+ }, [queues]);
50
+
51
+ useEffect(() => {
52
+ if (queueLocations.map((l) => l.id).includes(sessionLocation.uuid)) {
53
+ setSelectedQueueLocation(sessionLocation.uuid);
54
+ }
55
+ }, [queueLocations]);
56
+
57
+ useEffect(() => {
58
+ setFormFields({
59
+ queueLocation: selectedQueueLocation,
60
+ service: selectedService,
61
+ status: defaultStatus,
62
+ priority,
63
+ sortWeight,
64
+ });
65
+ }, [selectedQueueLocation, selectedService, defaultStatus, priority, sortWeight]);
66
+
67
+ return (
68
+ <div>
69
+ <section className={styles.section}>
70
+ <div className={styles.sectionTitle}>{t('queueLocation', 'Queue location')}</div>
71
+ <ResponsiveWrapper>
72
+ {isLoadingQueueLocations ? (
73
+ <SelectSkeleton />
74
+ ) : (
75
+ <Select
76
+ labelText={t('selectQueueLocation', 'Select a queue location')}
77
+ id="queueLocation"
78
+ name="queueLocation"
79
+ invalidText="Required"
80
+ value={selectedQueueLocation}
81
+ onChange={(event) => setSelectedQueueLocation(event.target.value)}>
82
+ {!selectedQueueLocation ? (
83
+ <SelectItem text={t('selectQueueLocation', 'Select a queue location')} value="" />
84
+ ) : null}
85
+ {queueLocations?.length > 0 &&
86
+ queueLocations.map((location) => (
87
+ <SelectItem key={location.id} text={location.name} value={location.id}>
88
+ {location.name}
89
+ </SelectItem>
90
+ ))}
91
+ </Select>
92
+ )}
93
+ </ResponsiveWrapper>
94
+ </section>
95
+
96
+ <section className={styles.section}>
97
+ <div className={styles.sectionTitle}>{t('service', 'Service')}</div>
98
+ {isLoadingQueues ? (
99
+ <SelectSkeleton />
100
+ ) : !queues?.length ? (
101
+ <InlineNotification
102
+ className={styles.inlineNotification}
103
+ kind={'error'}
104
+ lowContrast
105
+ subtitle={t('configureServices', 'Please configure services to continue.')}
106
+ title={t('noServicesConfigured', 'No services configured')}
107
+ />
108
+ ) : (
109
+ <Select
110
+ labelText={t('selectService', 'Select a service')}
111
+ id="service"
112
+ name="service"
113
+ invalidText="Required"
114
+ value={selectedService}
115
+ onChange={(event) => setSelectedService(event.target.value)}>
116
+ {!selectedService ? <SelectItem text={t('selectQueueService', 'Select a queue service')} value="" /> : null}
117
+ {queues?.length > 0 &&
118
+ queues.map((service) => (
119
+ <SelectItem key={service.uuid} text={service.name} value={service.uuid}>
120
+ {service.name}
121
+ </SelectItem>
122
+ ))}
123
+ </Select>
124
+ )}
125
+ </section>
126
+
127
+ {/* Status section of the form would go here; historical version of this code can be found at
128
+ https://github.com/openmrs/openmrs-esm-patient-management/blame/6c31e5ff2579fc89c2fd0d12c13510a1f2e913e0/packages/esm-service-queues-app/src/patient-search/visit-form-queue-fields/visit-form-queue-fields.component.tsx#L115 */}
129
+
130
+ {selectedService ? (
131
+ <section className={styles.section}>
132
+ <div className={styles.sectionTitle}>{t('priority', 'Priority')}</div>
133
+ {isLoadingQueues ? (
134
+ <RadioButtonGroup>
135
+ <RadioButtonSkeleton />
136
+ <RadioButtonSkeleton />
137
+ <RadioButtonSkeleton />
138
+ </RadioButtonGroup>
139
+ ) : !priorities?.length ? (
140
+ <InlineNotification
141
+ className={styles.inlineNotification}
142
+ kind={'error'}
143
+ lowContrast
144
+ title={t('noPrioritiesForServiceTitle', 'No priorities available')}>
145
+ {t(
146
+ 'noPrioritiesForService',
147
+ 'The selected service does not have any allowed priorities. This is an error in configuration. Please contact your system administrator.',
148
+ )}
149
+ </InlineNotification>
150
+ ) : priorities.length ? (
151
+ <RadioButtonGroup
152
+ className={styles.radioButtonWrapper}
153
+ name="priority"
154
+ id="priority"
155
+ defaultSelected={config.concepts.defaultPriorityConceptUuid}
156
+ onChange={(uuid) => setPriority(uuid)}>
157
+ {priorities.map(({ uuid, display }) => (
158
+ <RadioButton key={uuid} labelText={display} value={uuid} />
159
+ ))}
160
+ </RadioButtonGroup>
161
+ ) : null}
162
+ </section>
163
+ ) : null}
164
+
165
+ <section className={classNames(styles.section, styles.sectionHidden)}>
166
+ <TextInput
167
+ type="number"
168
+ id="sortWeight"
169
+ name="sortWeight"
170
+ labelText={t('sortWeight', 'Sort weight')}
171
+ value={sortWeight}
172
+ />
173
+ </section>
174
+ </div>
175
+ );
176
+ };
177
+
178
+ export default VisitFormQueueFields;
@@ -0,0 +1,19 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/styles/scss/spacing';
4
+
5
+ @import '~@openmrs/esm-styleguide/src/vars';
6
+
7
+ .sectionTitle {
8
+ @include type.type-style('heading-compact-02');
9
+ color: $text-02;
10
+ margin-bottom: layout.$spacing-03;
11
+ }
12
+
13
+ .section {
14
+ margin: layout.$spacing-07 layout.$spacing-05 0;
15
+ }
16
+
17
+ .sectionHidden {
18
+ display: none;
19
+ }
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen } from '@testing-library/react';
4
+ import VisitFormQueueFields from './visit-form-queue-fields.component';
5
+ import { defineConfigSchema, useLayoutType, useSession } from '@openmrs/esm-framework';
6
+ import { configSchema } from '../../config-schema';
7
+
8
+ defineConfigSchema('@openmrs/esm-service-queues-app', configSchema);
9
+
10
+ const mockUseLayoutType = useLayoutType as jest.Mock;
11
+ mockUseLayoutType.mockReturnValue('small-desktop');
12
+
13
+ const mockUseSession = useSession as jest.Mock;
14
+ mockUseSession.mockReturnValue({ sessionLocation: { uuid: '1' } });
15
+
16
+ jest.mock('../hooks/useQueueLocations', () => ({
17
+ useQueueLocations: jest.fn(() => ({
18
+ queueLocations: [{ id: '1', name: 'Location 1' }],
19
+ })),
20
+ }));
21
+
22
+ jest.mock('../../hooks/useQueues', () => {
23
+ return {
24
+ useQueues: jest.fn().mockReturnValue({
25
+ queues: [
26
+ {
27
+ uuid: 'e2ec9cf0-ec38-4d2b-af6c-59c82fa30b90',
28
+ name: 'Service 1',
29
+ allowedPriorities: [{ uuid: '197852c7-5fd4-4b33-89cc-7bae6848c65a', display: 'High' }],
30
+ allowedStatuses: [{ uuid: '176052c7-5fd4-4b33-89cc-7bae6848c65a', display: 'In Progress' }],
31
+ },
32
+ ],
33
+ }),
34
+ };
35
+ });
36
+
37
+ describe('VisitFormQueueFields', () => {
38
+ it('renders the form fields and returns the set values', async () => {
39
+ const user = userEvent.setup();
40
+ const setFormFields = jest.fn();
41
+ render(<VisitFormQueueFields setFormFields={setFormFields} />);
42
+
43
+ expect(screen.getByLabelText('Select a queue location')).toBeInTheDocument();
44
+ expect(screen.getByLabelText('Select a service')).toBeInTheDocument();
45
+ expect(screen.getByLabelText('Sort weight')).toBeInTheDocument();
46
+
47
+ const serviceSelect = screen.getByLabelText('Select a service').closest('select');
48
+ await user.selectOptions(serviceSelect, 'e2ec9cf0-ec38-4d2b-af6c-59c82fa30b90');
49
+
50
+ expect(screen.getByText('Priority')).toBeInTheDocument();
51
+ expect(screen.getByText('High')).toBeInTheDocument();
52
+
53
+ expect(setFormFields).toHaveBeenCalledWith({
54
+ queueLocation: '1',
55
+ service: 'e2ec9cf0-ec38-4d2b-af6c-59c82fa30b90',
56
+ // The status and priority are the defaults that come from the config schema.
57
+ // They are selected even though they are not 'allowed' for the queue.
58
+ status: '51ae5e4d-b72b-4912-bf31-a17efb690aeb',
59
+ priority: 'f4620bfa-3625-4883-bd3f-84c2cce14470',
60
+ sortWeight: 0,
61
+ });
62
+ });
63
+ });
@@ -0,0 +1,14 @@
1
+ @import '~@openmrs/esm-styleguide/src/vars';
2
+
3
+ .editIcon {
4
+ background-color: transparent;
5
+
6
+ & svg {
7
+ fill: var(--omrs-color-brand-orange);
8
+ color: var(--omrs-color-brand-orange);
9
+ }
10
+ }
11
+
12
+ .editIcon:hover {
13
+ background-color: transparent;
14
+ }
@@ -0,0 +1,41 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import dayjs from 'dayjs';
4
+
5
+ interface QueueDurationProps {
6
+ startedAt: Date;
7
+ endedAt?: Date;
8
+ }
9
+
10
+ const QueueDuration: React.FC<QueueDurationProps> = ({ startedAt, endedAt }) => {
11
+ return <DurationString startedAt={startedAt} endedAt={endedAt} />;
12
+ };
13
+
14
+ function DurationString({ startedAt, endedAt }: { startedAt: Date; endedAt: Date }) {
15
+ const { t } = useTranslation();
16
+
17
+ const endedTime = endedAt ? dayjs(endedAt) : dayjs();
18
+ const [currentTime, setCurrentTime] = useState(dayjs());
19
+
20
+ useEffect(() => {
21
+ const handle = setInterval(() => setCurrentTime(dayjs()), 60000);
22
+ return () => clearInterval(handle);
23
+ }, []);
24
+
25
+ const totalMinutes = dayjs(endedTime ?? currentTime).diff(startedAt, 'minutes');
26
+ const hours = Math.trunc(totalMinutes / 60);
27
+ const minutes = Math.trunc(totalMinutes % 60);
28
+
29
+ return (
30
+ <span>
31
+ {Math.abs(hours) > 0
32
+ ? t('hourAndMinuteFormatted', '{{hours}} hour(s) and {{minutes}} minute(s)', {
33
+ hours,
34
+ minutes: Math.abs(minutes),
35
+ })
36
+ : t('minuteFormatted', '{{minutes}} minute(s)', { minutes })}
37
+ </span>
38
+ );
39
+ }
40
+
41
+ export default QueueDuration;
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { DefinitionTooltip, Tag } from '@carbon/react';
3
+ import styles from './queue-priority.scss';
4
+ import { type PriorityConfig } from '../config-schema';
5
+ import { type Concept } from '../types';
6
+ import classNames from 'classnames';
7
+
8
+ interface QueuePriorityProps {
9
+ priority: Concept;
10
+ priorityComment?: string;
11
+ priorityConfigs: PriorityConfig[];
12
+ }
13
+
14
+ const QueuePriority: React.FC<QueuePriorityProps> = ({ priority, priorityComment, priorityConfigs }) => {
15
+ const priorityConfig = priorityConfigs.find((c) => c.conceptUuid === priority.uuid);
16
+ return (
17
+ <>
18
+ {priorityComment ? (
19
+ <DefinitionTooltip className={styles.tooltip} align="bottom-left" definition={priorityComment}>
20
+ <Tag
21
+ role="tooltip"
22
+ className={classNames(styles.tag, priorityConfig?.style === 'bold' && styles.bold)}
23
+ type={priorityConfig?.color}>
24
+ {priority.display}
25
+ </Tag>
26
+ </DefinitionTooltip>
27
+ ) : (
28
+ <Tag
29
+ className={classNames(styles.tag, priorityConfig?.style === 'bold' && styles.bold)}
30
+ type={priorityConfig?.color}>
31
+ {priority.display}
32
+ </Tag>
33
+ )}
34
+ </>
35
+ );
36
+ };
37
+
38
+ export default QueuePriority;
@@ -0,0 +1,12 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @use '@carbon/styles/scss/spacing';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .tag {
6
+ margin: 0.25rem 0;
7
+ white-space: nowrap;
8
+ }
9
+
10
+ .bold {
11
+ font-weight: bold;
12
+ }
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { Group, InProgress } from '@carbon/react/icons';
3
+ import styles from '../queue-table/queue-table.scss';
4
+ import { type Concept, type Queue } from '../types';
5
+ import { type StatusConfig } from '../config-schema';
6
+
7
+ interface QueueStatusProps {
8
+ status: Concept;
9
+ queue?: Queue;
10
+ statusConfigs: StatusConfig[];
11
+ }
12
+
13
+ const QueueStatus: React.FC<QueueStatusProps> = ({ status, queue, statusConfigs }) => {
14
+ const statusConfig = statusConfigs?.find((c) => c.conceptUuid === status.uuid);
15
+ return (
16
+ <span className={styles.statusContainer}>
17
+ <StatusIcon statusConfig={statusConfig} />
18
+ <span>
19
+ {status.display}
20
+ {queue ? ' - ' + queue.display : ''}
21
+ </span>
22
+ </span>
23
+ );
24
+ };
25
+
26
+ interface StatusIconProps {
27
+ statusConfig?: StatusConfig;
28
+ }
29
+
30
+ const StatusIcon: React.FC<StatusIconProps> = ({ statusConfig }) => {
31
+ return (
32
+ <span>
33
+ {statusConfig?.iconComponent === 'InProgress' && <InProgress size={16} />}
34
+ {statusConfig?.iconComponent === 'Group' && <Group size={16} />}
35
+ </span>
36
+ );
37
+ };
38
+
39
+ export default QueueStatus;
@@ -0,0 +1,55 @@
1
+ import React, { useCallback } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import classNames from 'classnames';
4
+ import { mutate } from 'swr';
5
+ import { Button } from '@carbon/react';
6
+ import { Notification } from '@carbon/react/icons';
7
+ import { restBaseUrl, showModal, showNotification } from '@openmrs/esm-framework';
8
+ import { type MappedVisitQueueEntry, serveQueueEntry } from '../active-visits/active-visits-table.resource';
9
+ import styles from './transition-entry.scss';
10
+
11
+ interface TransitionMenuProps {
12
+ queueEntry: MappedVisitQueueEntry;
13
+ }
14
+
15
+ const TransitionMenu: React.FC<TransitionMenuProps> = ({ queueEntry }) => {
16
+ const { t } = useTranslation();
17
+
18
+ const launchTransitionPriorityModal = useCallback(() => {
19
+ serveQueueEntry(queueEntry?.queue.name, queueEntry?.visitQueueNumber, 'calling').then(
20
+ ({ status }) => {
21
+ if (status === 200) {
22
+ mutate(`${restBaseUrl}/queueutil/assignticket`);
23
+ }
24
+ },
25
+ (error) => {
26
+ showNotification({
27
+ title: t('errorPostingToScreen', 'Error posting to screen'),
28
+ kind: 'error',
29
+ critical: true,
30
+ description: error?.message,
31
+ });
32
+ },
33
+ );
34
+ const dispose = showModal('transition-queue-entry-status-modal', {
35
+ closeModal: () => dispose(),
36
+ queueEntry,
37
+ });
38
+ }, [queueEntry]);
39
+
40
+ return (
41
+ <Button
42
+ renderIcon={(props) => <Notification size={16} {...props} />}
43
+ className={classNames(styles.callBtn, {
44
+ [styles.requeueIcon]: queueEntry?.priorityComment === 'Requeued',
45
+ [styles.normalIcon]: queueEntry?.priorityComment !== 'Requeued',
46
+ })}
47
+ onClick={launchTransitionPriorityModal}
48
+ iconDescription={t('call', 'Call')}
49
+ hasIconOnly
50
+ tooltipAlignment="end"
51
+ />
52
+ );
53
+ };
54
+
55
+ export default TransitionMenu;
@@ -0,0 +1,22 @@
1
+ @import '~@openmrs/esm-styleguide/src/vars';
2
+
3
+ .callBtn:hover {
4
+ background-color: transparent;
5
+ }
6
+
7
+ .requeueIcon {
8
+ background-color: transparent;
9
+
10
+ & svg {
11
+ fill: $danger;
12
+ color: $danger;
13
+ }
14
+ }
15
+
16
+ .normalIcon {
17
+ background-color: transparent;
18
+
19
+ & svg {
20
+ fill: var(--omrs-color-success);
21
+ }
22
+ }