@openmrs/esm-service-queues-app 5.0.1-pre.2087

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 (254) hide show
  1. package/.turbo/turbo-build.log +40 -0
  2. package/LICENSE.md +401 -0
  3. package/README.md +23 -0
  4. package/__mocks__/active-visits.mock.ts +97 -0
  5. package/__mocks__/appointments-data.mock.ts +283 -0
  6. package/__mocks__/search.mock.ts +35 -0
  7. package/__mocks__/visits.mock.ts +42 -0
  8. package/dist/121.js +1 -0
  9. package/dist/121.js.map +1 -0
  10. package/dist/130.js +2 -0
  11. package/dist/130.js.LICENSE.txt +5 -0
  12. package/dist/130.js.map +1 -0
  13. package/dist/176.js +1 -0
  14. package/dist/176.js.map +1 -0
  15. package/dist/227.js +1 -0
  16. package/dist/227.js.map +1 -0
  17. package/dist/242.js +1 -0
  18. package/dist/242.js.map +1 -0
  19. package/dist/252.js +1 -0
  20. package/dist/252.js.map +1 -0
  21. package/dist/286.js +1 -0
  22. package/dist/286.js.map +1 -0
  23. package/dist/308.js +1 -0
  24. package/dist/308.js.map +1 -0
  25. package/dist/319.js +1 -0
  26. package/dist/328.js +1 -0
  27. package/dist/328.js.map +1 -0
  28. package/dist/330.js +1 -0
  29. package/dist/330.js.map +1 -0
  30. package/dist/364.js +1 -0
  31. package/dist/364.js.map +1 -0
  32. package/dist/425.js +1 -0
  33. package/dist/425.js.map +1 -0
  34. package/dist/457.js +1 -0
  35. package/dist/457.js.map +1 -0
  36. package/dist/469.js +1 -0
  37. package/dist/469.js.map +1 -0
  38. package/dist/521.js +1 -0
  39. package/dist/521.js.map +1 -0
  40. package/dist/53.js +1 -0
  41. package/dist/53.js.map +1 -0
  42. package/dist/574.js +1 -0
  43. package/dist/591.js +2 -0
  44. package/dist/591.js.LICENSE.txt +32 -0
  45. package/dist/591.js.map +1 -0
  46. package/dist/610.js +1 -0
  47. package/dist/610.js.map +1 -0
  48. package/dist/611.js +1 -0
  49. package/dist/611.js.map +1 -0
  50. package/dist/616.js +1 -0
  51. package/dist/616.js.map +1 -0
  52. package/dist/644.js +1 -0
  53. package/dist/644.js.map +1 -0
  54. package/dist/670.js +2 -0
  55. package/dist/670.js.LICENSE.txt +57 -0
  56. package/dist/670.js.map +1 -0
  57. package/dist/733.js +1 -0
  58. package/dist/733.js.map +1 -0
  59. package/dist/757.js +1 -0
  60. package/dist/764.js +1 -0
  61. package/dist/764.js.map +1 -0
  62. package/dist/766.js +1 -0
  63. package/dist/766.js.map +1 -0
  64. package/dist/784.js +2 -0
  65. package/dist/784.js.LICENSE.txt +9 -0
  66. package/dist/784.js.map +1 -0
  67. package/dist/788.js +1 -0
  68. package/dist/807.js +1 -0
  69. package/dist/833.js +1 -0
  70. package/dist/86.js +1 -0
  71. package/dist/86.js.map +1 -0
  72. package/dist/981.js +1 -0
  73. package/dist/981.js.map +1 -0
  74. package/dist/main.js +1 -0
  75. package/dist/main.js.map +1 -0
  76. package/dist/openmrs-esm-service-queues-app.js +1 -0
  77. package/dist/openmrs-esm-service-queues-app.js.buildmanifest.json +930 -0
  78. package/dist/openmrs-esm-service-queues-app.js.map +1 -0
  79. package/dist/routes.json +1 -0
  80. package/jest.config.js +3 -0
  81. package/package.json +53 -0
  82. package/src/active-visits/active-visits-tab.component.tsx +71 -0
  83. package/src/active-visits/active-visits-tab.test.tsx +19 -0
  84. package/src/active-visits/active-visits-table.component.tsx +543 -0
  85. package/src/active-visits/active-visits-table.resource.ts +443 -0
  86. package/src/active-visits/active-visits-table.scss +271 -0
  87. package/src/active-visits/active-visits-table.test.tsx +131 -0
  88. package/src/active-visits/change-status-dialog.component.tsx +233 -0
  89. package/src/active-visits/change-status-dialog.scss +32 -0
  90. package/src/active-visits/change-status-dialog.test.tsx +107 -0
  91. package/src/add-patient-toqueue/add-patient-toqueue-dialog.component.tsx +218 -0
  92. package/src/add-patient-toqueue/add-patient-toqueue-dialog.scss +32 -0
  93. package/src/add-provider-queue-room/add-provider-queue-room.component.tsx +236 -0
  94. package/src/add-provider-queue-room/add-provider-queue-room.resource.tsx +76 -0
  95. package/src/add-provider-queue-room/add-provider-queue-room.scss +17 -0
  96. package/src/add-provider-queue-room/add-provider-queue-room.test.tsx +98 -0
  97. package/src/clear-queue-entries-dialog/clear-queue-entries-dialog.component.tsx +74 -0
  98. package/src/clear-queue-entries-dialog/clear-queue-entries-dialog.resource.tsx +46 -0
  99. package/src/clear-queue-entries-dialog/clear-queue-entries-dialog.scss +15 -0
  100. package/src/clear-queue-entries-dialog/clear-queue-entries-dialog.test.tsx +42 -0
  101. package/src/clear-queue-entries-dialog/clear-queue-entries.component.tsx +36 -0
  102. package/src/config-schema.ts +177 -0
  103. package/src/constants.ts +7 -0
  104. package/src/createDashboardLink.tsx +35 -0
  105. package/src/current-visit/current-visit-summary.component.tsx +38 -0
  106. package/src/current-visit/current-visit-summary.test.tsx +47 -0
  107. package/src/current-visit/current-visit.resource.ts +84 -0
  108. package/src/current-visit/current-visit.scss +34 -0
  109. package/src/current-visit/hooks/useVitalsConceptMetadata.tsx +101 -0
  110. package/src/current-visit/visit-details/biometrics-config-schema.ts +14 -0
  111. package/src/current-visit/visit-details/current-visit-details.component.tsx +96 -0
  112. package/src/current-visit/visit-details/triage-note.component.tsx +53 -0
  113. package/src/current-visit/visit-details/triage-note.scss +89 -0
  114. package/src/current-visit/visit-details/vitals-config-schema.ts +17 -0
  115. package/src/current-visit/visit-details/vitals.component.tsx +164 -0
  116. package/src/dashboard.meta.ts +5 -0
  117. package/src/declarations.d.ts +2 -0
  118. package/src/helpers/functions.ts +67 -0
  119. package/src/helpers/helpers.ts +170 -0
  120. package/src/helpers/time-helpers.ts +15 -0
  121. package/src/home.component.tsx +29 -0
  122. package/src/home.test.tsx +58 -0
  123. package/src/index.ts +100 -0
  124. package/src/overlay.component.tsx +46 -0
  125. package/src/overlay.scss +96 -0
  126. package/src/overlay.test.tsx +49 -0
  127. package/src/past-visit/past-visit-details/encounter-list.component.tsx +51 -0
  128. package/src/past-visit/past-visit-details/medications-list.component.tsx +95 -0
  129. package/src/past-visit/past-visit-details/notes-list.component.tsx +40 -0
  130. package/src/past-visit/past-visit-details/past-visit-summary.component.tsx +172 -0
  131. package/src/past-visit/past-visit-details/past-visit-summary.scss +58 -0
  132. package/src/past-visit/past-visit.component.tsx +37 -0
  133. package/src/past-visit/past-visit.resource.ts +83 -0
  134. package/src/past-visit/past-visit.scss +126 -0
  135. package/src/past-visit/past-visit.test.tsx +41 -0
  136. package/src/patient-info/appointment-details.component.tsx +98 -0
  137. package/src/patient-info/appointment-details.scss +34 -0
  138. package/src/patient-info/appointment-details.test.tsx +36 -0
  139. package/src/patient-info/appointments.resource.tsx +43 -0
  140. package/src/patient-info/contact-details.component.tsx +71 -0
  141. package/src/patient-info/contact-details.scss +31 -0
  142. package/src/patient-info/contact-details.test.tsx +94 -0
  143. package/src/patient-info/hooks/usePatientAttributes.tsx +42 -0
  144. package/src/patient-info/patient-info.component.tsx +78 -0
  145. package/src/patient-info/patient-info.scss +60 -0
  146. package/src/patient-info/patient-info.test.tsx +45 -0
  147. package/src/patient-queue-header/patient-queue-header.component.tsx +64 -0
  148. package/src/patient-queue-header/patient-queue-header.scss +88 -0
  149. package/src/patient-queue-header/patient-queue-illustration.component.tsx +22 -0
  150. package/src/patient-queue-metrics/clinic-metrics.component.tsx +93 -0
  151. package/src/patient-queue-metrics/clinic-metrics.resource.tsx +51 -0
  152. package/src/patient-queue-metrics/clinic-metrics.scss +11 -0
  153. package/src/patient-queue-metrics/clinic-metrics.test.tsx +87 -0
  154. package/src/patient-queue-metrics/metrics-card.component.tsx +64 -0
  155. package/src/patient-queue-metrics/metrics-card.scss +80 -0
  156. package/src/patient-queue-metrics/metrics-header.component.tsx +74 -0
  157. package/src/patient-queue-metrics/metrics-header.scss +22 -0
  158. package/src/patient-queue-metrics/queue-metrics.resource.ts +54 -0
  159. package/src/patient-search/advanced-search.component.tsx +190 -0
  160. package/src/patient-search/advanced-search.scss +154 -0
  161. package/src/patient-search/advanced-search.test.tsx +29 -0
  162. package/src/patient-search/basic-search.component.tsx +112 -0
  163. package/src/patient-search/basic-search.scss +139 -0
  164. package/src/patient-search/basic-search.test.tsx +23 -0
  165. package/src/patient-search/empty-data-illustration.component.tsx +41 -0
  166. package/src/patient-search/hooks/useActivePatientEnrollment.tsx +29 -0
  167. package/src/patient-search/hooks/useDefaultLocation.tsx +14 -0
  168. package/src/patient-search/hooks/usePatients.tsx +25 -0
  169. package/src/patient-search/hooks/useQueueLocations.tsx +20 -0
  170. package/src/patient-search/hooks/useRecommendedVisitTypes.tsx +36 -0
  171. package/src/patient-search/hooks/useScheduledVisits.tsx +50 -0
  172. package/src/patient-search/patient-scheduled-visits.component.tsx +350 -0
  173. package/src/patient-search/patient-scheduled-visits.scss +131 -0
  174. package/src/patient-search/patient-scheduled-visits.test.tsx +55 -0
  175. package/src/patient-search/patient-search.component.tsx +64 -0
  176. package/src/patient-search/search-illustration.component.tsx +27 -0
  177. package/src/patient-search/search-results.component.tsx +75 -0
  178. package/src/patient-search/search-results.scss +80 -0
  179. package/src/patient-search/search-results.test.tsx +77 -0
  180. package/src/patient-search/search.resource.ts +10 -0
  181. package/src/patient-search/visit-form/base-visit-type.component.tsx +82 -0
  182. package/src/patient-search/visit-form/base-visit-type.scss +100 -0
  183. package/src/patient-search/visit-form/base-visit-type.test.tsx +41 -0
  184. package/src/patient-search/visit-form/queue.resource.ts +73 -0
  185. package/src/patient-search/visit-form/recommended-visit-type.component.tsx +38 -0
  186. package/src/patient-search/visit-form/visit-form.component.tsx +366 -0
  187. package/src/patient-search/visit-form/visit-form.scss +96 -0
  188. package/src/patient-search/visit-form-queue-fields/visit-form-queue-fields.component.tsx +160 -0
  189. package/src/patient-search/visit-form-queue-fields/visit-form-queue-fields.scss +21 -0
  190. package/src/patient-search/visit-form-queue-fields/visit-form-queue-fields.test.tsx +67 -0
  191. package/src/queue-entry-table-components/actions-menu.component.tsx +51 -0
  192. package/src/queue-entry-table-components/actions-menu.scss +3 -0
  193. package/src/queue-entry-table-components/edit-entry.component.tsx +28 -0
  194. package/src/queue-entry-table-components/edit-entry.scss +14 -0
  195. package/src/queue-entry-table-components/open-chart.component.tsx +33 -0
  196. package/src/queue-entry-table-components/open-chart.resource.tsx +14 -0
  197. package/src/queue-entry-table-components/open-chart.scss +14 -0
  198. package/src/queue-entry-table-components/status-icon.component.tsx +17 -0
  199. package/src/queue-entry-table-components/transition-entry.component.tsx +53 -0
  200. package/src/queue-entry-table-components/transition-entry.scss +22 -0
  201. package/src/queue-patient-linelists/queue-linelist-base-table.component.tsx +230 -0
  202. package/src/queue-patient-linelists/queue-linelist-base-table.scss +110 -0
  203. package/src/queue-patient-linelists/queue-linelist-base-table.test.tsx +118 -0
  204. package/src/queue-patient-linelists/queue-linelist-filter.component.tsx +172 -0
  205. package/src/queue-patient-linelists/queue-linelist-filter.scss +63 -0
  206. package/src/queue-patient-linelists/queue-linelist-filter.test.tsx +76 -0
  207. package/src/queue-patient-linelists/queue-linelist.component.tsx +29 -0
  208. package/src/queue-patient-linelists/queue-linelist.resource.tsx +84 -0
  209. package/src/queue-patient-linelists/queue-linelist.test.tsx +22 -0
  210. package/src/queue-patient-linelists/queue-services-table.component.tsx +63 -0
  211. package/src/queue-patient-linelists/scheduled-appointments-table.component.tsx +300 -0
  212. package/src/queue-patient-linelists/scheduled-appointments-table.test.tsx +42 -0
  213. package/src/queue-rooms/queue-room-form.component.tsx +168 -0
  214. package/src/queue-rooms/queue-room-form.test.tsx +65 -0
  215. package/src/queue-rooms/queue-room.resource.tsx +20 -0
  216. package/src/queue-rooms/queue-room.scss +58 -0
  217. package/src/queue-screen/queue-screen.component.tsx +47 -0
  218. package/src/queue-screen/queue-screen.scss +39 -0
  219. package/src/queue-screen/queue-screen.test.tsx +47 -0
  220. package/src/queue-screen/useActiveTickets.tsx +13 -0
  221. package/src/queue-services/queue-service-form.component.tsx +183 -0
  222. package/src/queue-services/queue-service-form.test.tsx +56 -0
  223. package/src/queue-services/queue-service.resource.tsx +38 -0
  224. package/src/queue-services/queue-service.scss +54 -0
  225. package/src/remove-queue-entry-dialog/remove-queue-entry.component.tsx +95 -0
  226. package/src/remove-queue-entry-dialog/remove-queue-entry.resource.tsx +93 -0
  227. package/src/remove-queue-entry-dialog/remove-queue-entry.scss +8 -0
  228. package/src/remove-queue-entry-dialog/remove-queue-entry.test.tsx +44 -0
  229. package/src/root.component.tsx +28 -0
  230. package/src/root.scss +15 -0
  231. package/src/root.test.tsx +28 -0
  232. package/src/routes.json +91 -0
  233. package/src/side-menu/nav-group/createNavGroup.tsx +17 -0
  234. package/src/side-menu/nav-group/nav-group.component.tsx +24 -0
  235. package/src/side-menu/nav-group/nav-group.test.tsx +32 -0
  236. package/src/side-menu/nav-group/nav-group.ts +10 -0
  237. package/src/side-menu/side-menu.component.tsx +6 -0
  238. package/src/side-menu/side-menu.test.tsx +17 -0
  239. package/src/transition-queue-entry/transition-queue-entry-dialog.component.tsx +174 -0
  240. package/src/transition-queue-entry/transition-queue-entry-dialog.scss +12 -0
  241. package/src/transition-queue-entry/transition-queue-entry-dialog.test.tsx +95 -0
  242. package/src/transition-queue-entry/transition-queue-entry.resource.ts +16 -0
  243. package/src/types/index.ts +434 -0
  244. package/src/visits-missing-inqueue/visits-missing-inqueue.component.tsx +298 -0
  245. package/src/visits-missing-inqueue/visits-missing-inqueue.resource.ts +93 -0
  246. package/src/visits-missing-inqueue/visits-missing-inqueue.scss +108 -0
  247. package/translations/am.json +258 -0
  248. package/translations/en.json +258 -0
  249. package/translations/es.json +258 -0
  250. package/translations/fr.json +258 -0
  251. package/translations/he.json +262 -0
  252. package/translations/km.json +258 -0
  253. package/tsconfig.json +5 -0
  254. package/webpack.config.js +1 -0
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import QueueServiceForm from './queue-service-form.component';
4
+
5
+ jest.mock('@openmrs/esm-framework', () => ({
6
+ useLayoutType: () => 'desktop',
7
+ showNotification: jest.fn(),
8
+ showToast: jest.fn(),
9
+ }));
10
+
11
+ jest.mock('./queue-service.resource', () => ({
12
+ useServiceConcepts: () => ({
13
+ queueConcepts: [
14
+ { uuid: '6f017eb0-b035-4acd-b284-da45f5067502', display: 'Concept 1' },
15
+ { uuid: '5f017eb0-b035-4acd-b284-da45f5067502', display: 'Concept 2' },
16
+ ],
17
+ }),
18
+ saveQueue: jest.fn(() => Promise.resolve({ status: 201 })),
19
+ }));
20
+
21
+ jest.mock('../patient-search/hooks/useQueueLocations', () => ({
22
+ useQueueLocations: () => ({
23
+ queueLocations: [
24
+ { id: '34567eb0-b035-4acd-b284-da45f5067502', name: 'Location 1' },
25
+ { id: '12wi7eb0-b035-4acd-b284-da45f5067502', name: 'Location 2' },
26
+ ],
27
+ }),
28
+ }));
29
+
30
+ describe('QueueServiceForm', () => {
31
+ it('should display required error messages when form is submitted with missing fields', async () => {
32
+ render(<QueueServiceForm toggleSearchType={() => {}} closePanel={() => {}} />);
33
+
34
+ const submitButton = screen.getByText('Save');
35
+
36
+ fireEvent.click(submitButton);
37
+
38
+ expect(screen.getByText('Missing queue name')).toBeInTheDocument();
39
+ });
40
+
41
+ it('should submit the form when all fields are filled', async () => {
42
+ render(<QueueServiceForm toggleSearchType={() => {}} closePanel={() => {}} />);
43
+
44
+ const queueNameInput = screen.getByLabelText('Queue name');
45
+ const serviceSelect = screen.getByLabelText('Select a service type');
46
+ const locationSelect = screen.getByLabelText('Select a location');
47
+
48
+ fireEvent.change(queueNameInput, { target: { value: 'Test Queue' } });
49
+ fireEvent.change(serviceSelect, { target: { value: '6f017eb0-b035-4acd-b284-da45f5067502' } });
50
+ fireEvent.change(locationSelect, { target: { value: '34567eb0-b035-4acd-b284-da45f5067502' } });
51
+
52
+ expect(queueNameInput).toHaveValue('Test Queue');
53
+ expect(serviceSelect).toHaveValue('6f017eb0-b035-4acd-b284-da45f5067502');
54
+ expect(locationSelect).toHaveValue('34567eb0-b035-4acd-b284-da45f5067502');
55
+ });
56
+ });
@@ -0,0 +1,38 @@
1
+ import useSWR from 'swr';
2
+ import useSWRImmutable from 'swr/immutable';
3
+ import { FetchResponse, openmrsFetch, useConfig } from '@openmrs/esm-framework';
4
+
5
+ export function useServiceConcepts() {
6
+ const config = useConfig();
7
+ const {
8
+ concepts: { serviceConceptSetUuid },
9
+ } = config;
10
+
11
+ const apiUrl = `/ws/rest/v1/concept/${serviceConceptSetUuid}`;
12
+ const { data, error, isLoading } = useSWRImmutable<FetchResponse>(apiUrl, openmrsFetch);
13
+
14
+ return {
15
+ queueConcepts: data ? data?.data?.setMembers : [],
16
+ isLoading,
17
+ };
18
+ }
19
+
20
+ export function saveQueue(queueName: string, queueConcept: string, queueDescription: string, queueLocation: string) {
21
+ const abortController = new AbortController();
22
+
23
+ return openmrsFetch(`/ws/rest/v1/queue`, {
24
+ method: 'POST',
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ },
28
+ signal: abortController.signal,
29
+ body: {
30
+ name: queueName,
31
+ description: queueDescription,
32
+ service: { uuid: queueConcept },
33
+ location: {
34
+ uuid: queueLocation,
35
+ },
36
+ },
37
+ });
38
+ }
@@ -0,0 +1,54 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .heading {
6
+ @include type.type-style('productive-heading-01');
7
+ margin: spacing.$spacing-05 0 spacing.$spacing-05;
8
+ }
9
+
10
+ .warningContainer {
11
+ background-color: $carbon--red-50;
12
+ padding: spacing.$spacing-04;
13
+ margin: spacing.$spacing-03 0 spacing.$spacing-03;
14
+ display: flex;
15
+ justify-content: space-between;
16
+ .warning {
17
+ @include type.type-style('heading-compact-01');
18
+ color: $ui-05;
19
+ }
20
+ }
21
+
22
+ .form {
23
+ display: flex;
24
+ flex-direction: column;
25
+ justify-content: space-between;
26
+ height: calc(100vh - 6rem);
27
+ }
28
+
29
+ .grid {
30
+ margin: 0 spacing.$spacing-05;
31
+ padding: 0rem;
32
+ }
33
+
34
+ .input {
35
+ margin-top: spacing.$spacing-04;
36
+ }
37
+
38
+ .button {
39
+ height: spacing.$spacing-10;
40
+ display: flex;
41
+ align-content: flex-start;
42
+ align-items: baseline;
43
+ min-width: 50%;
44
+ }
45
+
46
+ .tablet {
47
+ padding: spacing.$spacing-06 spacing.$spacing-05;
48
+ background-color: $ui-02;
49
+ }
50
+
51
+ .desktop {
52
+ padding: 0rem;
53
+ margin-top: spacing.$spacing-05;
54
+ }
@@ -0,0 +1,95 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
4
+ import {
5
+ parseDate,
6
+ showNotification,
7
+ showToast,
8
+ toDateObjectStrict,
9
+ toOmrsIsoString,
10
+ useVisit,
11
+ } from '@openmrs/esm-framework';
12
+ import { MappedQueueEntry } from '../types';
13
+ import { startOfDay } from '../constants';
14
+ import { useCheckedInAppointments, voidQueueEntry } from './remove-queue-entry.resource';
15
+ import { useVisitQueueEntries } from '../active-visits/active-visits-table.resource';
16
+ import styles from './remove-queue-entry.scss';
17
+
18
+ interface RemoveQueueEntryDialogProps {
19
+ queueEntry: MappedQueueEntry;
20
+ closeModal: () => void;
21
+ }
22
+
23
+ const RemoveQueueEntryDialog: React.FC<RemoveQueueEntryDialogProps> = ({ queueEntry, closeModal }) => {
24
+ const { t } = useTranslation();
25
+ const { currentVisit } = useVisit(queueEntry.patientUuid);
26
+ const { mutate } = useVisitQueueEntries('', '');
27
+ const abortController = new AbortController();
28
+
29
+ const { data: appointments } = useCheckedInAppointments(queueEntry.patientUuid, startOfDay);
30
+
31
+ const removeQueueEntry = () => {
32
+ const endCurrentVisitPayload = {
33
+ location: currentVisit?.location?.uuid,
34
+ startDatetime: parseDate(currentVisit?.startDatetime),
35
+ visitType: currentVisit?.visitType?.uuid,
36
+ stopDatetime: new Date(),
37
+ };
38
+
39
+ const endedAt = toDateObjectStrict(toOmrsIsoString(new Date()));
40
+
41
+ voidQueueEntry(
42
+ queueEntry.queueUuid,
43
+ queueEntry.queueEntryUuid,
44
+ endedAt,
45
+ endCurrentVisitPayload,
46
+ queueEntry.visitUuid,
47
+ appointments,
48
+ ).then((response) => {
49
+ closeModal();
50
+ mutate();
51
+ showToast({
52
+ critical: true,
53
+ kind: 'success',
54
+ description: t('queueEntryRemovedSuccessfully', `Queue entry removed successfully`),
55
+ title: t('queueEntryRemoved', 'Queue entry removed'),
56
+ });
57
+ (error) => {
58
+ showNotification({
59
+ title: t('removeQueueEntryError', 'Error removing queue entry'),
60
+ kind: 'error',
61
+ critical: true,
62
+ description: error?.message,
63
+ });
64
+ };
65
+ });
66
+ };
67
+
68
+ return (
69
+ <div>
70
+ <ModalHeader
71
+ closeModal={closeModal}
72
+ label={t('serviceQueue', 'Service queue')}
73
+ title={t('removeFromQueueAndEndVisit', 'Remove patient from queue and end active visit?')}
74
+ />
75
+ <ModalBody>
76
+ <p className={styles.subHeading} id="subHeading">
77
+ {t(
78
+ 'endVisitWarningMessage',
79
+ 'Ending this visit will remove this patient from the queue and will not allow you to fill another encounter form for this patient',
80
+ )}
81
+ </p>
82
+ </ModalBody>
83
+ <ModalFooter>
84
+ <Button kind="secondary" onClick={closeModal}>
85
+ {t('cancel', 'Cancel')}
86
+ </Button>
87
+ <Button kind="danger" onClick={removeQueueEntry}>
88
+ {t('endVisit', 'End Visit')}
89
+ </Button>
90
+ </ModalFooter>
91
+ </div>
92
+ );
93
+ };
94
+
95
+ export default RemoveQueueEntryDialog;
@@ -0,0 +1,93 @@
1
+ import { openmrsFetch, updateVisit } from '@openmrs/esm-framework';
2
+ import dayjs from 'dayjs';
3
+ import { endPatientStatus } from '../active-visits/active-visits-table.resource';
4
+ import { AppointmentsFetchResponse, 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 voidQueueEntry(
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 = `/ws/rest/v1/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 = `/ws/rest/v1/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,8 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @import '~@openmrs/esm-styleguide/src/vars';
3
+ @import '~carbon-components/src/globals/scss/mixins';
4
+
5
+ #subHeading {
6
+ @include carbon--type-style('productive-heading-01');
7
+ color: $ui-05;
8
+ }
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import RemoveQueueEntryDialog from './remove-queue-entry.component';
4
+
5
+ jest.mock('@openmrs/esm-framework', () => ({
6
+ ...jest.requireActual('@openmrs/esm-framework'),
7
+ voidQueueEntry: jest.fn(),
8
+ showNotification: jest.fn(),
9
+ useConfig: jest.fn(() => ({
10
+ concepts: {
11
+ visitQueueNumberAttributeUuid: 'c61ce16f-272a-41e7-9924-4c555d0932c5',
12
+ },
13
+ })),
14
+ }));
15
+
16
+ describe('RemoveQueueEntryDialog', () => {
17
+ const queueEntry = {
18
+ queueUuid: 'fa1e98f1-f002-4174-9e55-34d60951e710',
19
+ queueEntryUuid: '712289ab-32c0-430f-87b6-d9c1e4e4686e',
20
+ visitUuid: 'c90386ff-ae85-45cc-8a01-25852099c5ae',
21
+ patientUuid: 'cc75ad73-c24b-499c-8db9-a7ef4fc0b36d',
22
+ };
23
+
24
+ it('renders dialog content', () => {
25
+ const closeModal = jest.fn();
26
+ render(<RemoveQueueEntryDialog queueEntry={queueEntry} closeModal={closeModal} />);
27
+
28
+ expect(screen.getByText('Service queue')).toBeInTheDocument();
29
+ expect(screen.getByText('Remove patient from queue and end active visit?')).toBeInTheDocument();
30
+ expect(screen.getByText(/Ending this visit will remove this patient/)).toBeInTheDocument();
31
+
32
+ expect(screen.getByText('Cancel')).toBeInTheDocument();
33
+ expect(screen.getByText('End Visit')).toBeInTheDocument();
34
+ });
35
+
36
+ it('calls closeModal when Cancel button is clicked', () => {
37
+ const closeModal = jest.fn();
38
+ render(<RemoveQueueEntryDialog queueEntry={queueEntry} closeModal={closeModal} />);
39
+
40
+ fireEvent.click(screen.getByText('Cancel'));
41
+
42
+ expect(closeModal).toHaveBeenCalledTimes(1);
43
+ });
44
+ });
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { SWRConfig } from 'swr';
3
+ import { BrowserRouter, Route, Routes } from 'react-router-dom';
4
+ import { spaBasePath } from './constants';
5
+ import AppointmentsTable from './queue-patient-linelists/scheduled-appointments-table.component';
6
+ import ServicesTable from './queue-patient-linelists/queue-services-table.component';
7
+
8
+ const swrConfiguration = {
9
+ // Maximum number of retries when the backend returns an error
10
+ errorRetryCount: 3,
11
+ };
12
+
13
+ const Root: React.FC = () => {
14
+ return (
15
+ <main>
16
+ <SWRConfig value={swrConfiguration}>
17
+ <BrowserRouter basename={`${spaBasePath}/service-queues`}>
18
+ <Routes>
19
+ <Route path="/appointments-list/:value/" element={<AppointmentsTable />} />
20
+ <Route path="/queue-list/:value/" element={<ServicesTable />} />
21
+ </Routes>
22
+ </BrowserRouter>
23
+ </SWRConfig>
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,28 @@
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 AppointmentsTable component for "/appointments-list/:value/" route', () => {
15
+ window.history.pushState({}, 'Appointments List', '/spa/home/service-queues/appointments-list/some-value/');
16
+ render(<Root />);
17
+
18
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
19
+ });
20
+
21
+ it('renders ServicesTable component for "/queue-list/:value/" route', () => {
22
+ window.history.pushState({}, 'Queue List', '/spa/home/service-queues/queue-list/some-value/');
23
+ render(<Root />);
24
+
25
+ const servicesTable = screen.getByText('A list of clients waiting for queue-list');
26
+ expect(servicesTable).toBeInTheDocument();
27
+ });
28
+ });
@@ -0,0 +1,91 @@
1
+ {
2
+ "$schema": "https://json.openmrs.org/routes.schema.json",
3
+ "backendDependencies": {
4
+ "webservices.rest": "^2.2.0"
5
+ },
6
+ "pages": [
7
+ {
8
+ "component": "appointmentsList",
9
+ "routeRegex": "^appointments-list",
10
+ "online": true,
11
+ "offline": true
12
+ },
13
+ {
14
+ "component": "queueList",
15
+ "routeRegex": "^queue-list",
16
+ "online": true,
17
+ "offline": true
18
+ }
19
+ ],
20
+ "extensions": [
21
+ {
22
+ "name": "outpatient-side-nav-ext",
23
+ "component": "outpatientSideNav",
24
+ "slot": "outpatient-sidebar-slot",
25
+ "online": true,
26
+ "offline": true
27
+ },
28
+ {
29
+ "name": "service-queues-dashboard-link",
30
+ "component": "serviceQueuesDashboardLink",
31
+ "slot": "homepage-dashboard-slot",
32
+ "meta": {
33
+ "name": "service-queues",
34
+ "slot": "service-queues-dashboard-slot",
35
+ "title": "Service queues"
36
+ },
37
+ "online": true,
38
+ "offline": true
39
+ },
40
+ {
41
+ "name": "home-dashboard",
42
+ "component": "homeDashboard",
43
+ "slot": "service-queues-dashboard-slot",
44
+ "online": true,
45
+ "offline": true
46
+ },
47
+ {
48
+ "name": "edit-queue-entry-status-modal",
49
+ "component": "editQueueEntryStatusModal"
50
+ },
51
+ {
52
+ "name": "patient-info-banner-slot",
53
+ "component": "patientInfoBannerSlot"
54
+ },
55
+ {
56
+ "name": "add-patient-to-queue",
57
+ "component": "addPatientToQueue",
58
+ "slot": "add-patient-to-queue-slot"
59
+ },
60
+ {
61
+ "name": "remove-queue-entry",
62
+ "component": "removeQueueEntry"
63
+ },
64
+ {
65
+ "name": "clear-all-queue-entries",
66
+ "component": "clearAllQueueEntries"
67
+ },
68
+ {
69
+ "name": "add-visit-to-queue-modal",
70
+ "component": "addVisitToQueueModal"
71
+ },
72
+ {
73
+ "name": "transition-queue-entry-status-modal",
74
+ "component": "transitionQueueEntryStatusModal"
75
+ },
76
+ {
77
+ "name": "previous-visit-summary-widget",
78
+ "component": "previousVisitSummaryWidget",
79
+ "slot": "previous-visit-summary-slot"
80
+ },
81
+ {
82
+ "name": "add-provider-to-room-modal",
83
+ "component": "addProviderToRoomModal"
84
+ },
85
+ {
86
+ "name": "add-queue-entry-widget",
87
+ "component": "addQueueEntryWidget",
88
+ "slot": "add-queue-entry-slot"
89
+ }
90
+ ]
91
+ }
@@ -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;
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import NavGroupExtension from './nav-group.component';
4
+ import { registerNavGroup } from './nav-group';
5
+
6
+ jest.mock('./nav-group', () => ({
7
+ registerNavGroup: jest.fn(),
8
+ }));
9
+
10
+ describe('NavGroupExtension', () => {
11
+ const props = {
12
+ title: 'Test Group',
13
+ slotName: 'testSlot',
14
+ renderIcon: () => <div>Test Icon</div>,
15
+ };
16
+
17
+ it('registers the navigation group on mount', () => {
18
+ render(<NavGroupExtension {...props} />);
19
+
20
+ expect(registerNavGroup).toHaveBeenCalledWith(props.slotName);
21
+ });
22
+
23
+ it('renders navigation group with provided icon and title', () => {
24
+ render(<NavGroupExtension {...props} />);
25
+
26
+ const icon = screen.getByText('Test Icon');
27
+ expect(icon).toBeInTheDocument();
28
+
29
+ const title = screen.getByText(props.title);
30
+ expect(title).toBeInTheDocument();
31
+ });
32
+ });
@@ -0,0 +1,10 @@
1
+ import { createGlobalStore, createUseStore } from '@openmrs/esm-framework';
2
+
3
+ const navGroupStore = createGlobalStore('outpatient-nav-groups', { navGroups: [] });
4
+
5
+ export function registerNavGroup(slotName: string) {
6
+ const store = navGroupStore.getState();
7
+ navGroupStore.setState({ navGroups: [slotName, ...store.navGroups] });
8
+ }
9
+
10
+ export const useNavGroups = createUseStore(navGroupStore);
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import { LeftNavMenu } from '@openmrs/esm-framework';
3
+
4
+ const SideMenu = () => <LeftNavMenu />;
5
+
6
+ export default SideMenu;
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import SideMenu from './side-menu.component';
4
+
5
+ jest.mock('@openmrs/esm-framework', () => ({
6
+ LeftNavMenu: () => <div data-testid="left-nav-menu">Mocked LeftNavMenu</div>,
7
+ }));
8
+ jest.mock('@openmrs/esm-framework');
9
+
10
+ describe('SideMenu', () => {
11
+ it('renders the LeftNavMenu', () => {
12
+ render(<SideMenu />);
13
+
14
+ const leftNavMenu = screen.getByTestId('left-nav-menu');
15
+ expect(leftNavMenu).toBeInTheDocument();
16
+ });
17
+ });