@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,74 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import styles from './clear-queue-entries-dialog.scss';
4
+ import { Button, ButtonSkeleton, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
5
+ import { showNotification, showToast } from '@openmrs/esm-framework';
6
+ import { batchClearQueueEntries } from './clear-queue-entries-dialog.resource';
7
+ import { MappedVisitQueueEntry, useVisitQueueEntries } from '../active-visits/active-visits-table.resource';
8
+
9
+ interface ClearQueueEntriesDialogProps {
10
+ visitQueueEntries: Array<MappedVisitQueueEntry>;
11
+ closeModal: () => void;
12
+ }
13
+
14
+ const ClearQueueEntriesDialog: React.FC<ClearQueueEntriesDialogProps> = ({ visitQueueEntries, closeModal }) => {
15
+ const { t } = useTranslation();
16
+ const { mutate } = useVisitQueueEntries('', '');
17
+ const [isSubmitting, setIsSubmitting] = useState(false);
18
+
19
+ const handleClearQueueBatchRequest = useCallback(() => {
20
+ setIsSubmitting(true);
21
+ batchClearQueueEntries(visitQueueEntries).then(
22
+ (response) => {
23
+ closeModal();
24
+ showToast({
25
+ critical: true,
26
+ title: t('clearQueue', 'Clear queue'),
27
+ kind: 'success',
28
+ description: t('queuesClearedSuccessfully', 'Queues cleared successfully'),
29
+ });
30
+ mutate();
31
+ },
32
+ (error) => {
33
+ showNotification({
34
+ title: t('errorClearingQueues', 'Error clearing queues'),
35
+ kind: 'error',
36
+ critical: true,
37
+ description: error?.message,
38
+ });
39
+ },
40
+ );
41
+ }, [closeModal, mutate, t, visitQueueEntries]);
42
+
43
+ return (
44
+ <div>
45
+ <ModalHeader
46
+ closeModal={closeModal}
47
+ label={t('serviceQueue', 'Service queue')}
48
+ title={t('clearAllQueueEntries', 'Clear all queue entries?')}
49
+ />
50
+ <ModalBody>
51
+ <p className={styles.subHeading} id="subHeading">
52
+ {t(
53
+ 'clearAllQueueEntriesWarningMessage',
54
+ 'Clearing all queue entries will remove all the patients from the queues and will not allow you to fill any other encounter forms for the patients',
55
+ )}
56
+ </p>
57
+ </ModalBody>
58
+ <ModalFooter>
59
+ <Button kind="secondary" onClick={closeModal}>
60
+ {t('cancel', 'Cancel')}
61
+ </Button>
62
+ {isSubmitting === true ? (
63
+ <ButtonSkeleton />
64
+ ) : (
65
+ <Button kind="danger" onClick={handleClearQueueBatchRequest}>
66
+ {t('clearQueue', 'Clear queue')}
67
+ </Button>
68
+ )}
69
+ </ModalFooter>
70
+ </div>
71
+ );
72
+ };
73
+
74
+ export default ClearQueueEntriesDialog;
@@ -0,0 +1,46 @@
1
+ import { openmrsFetch, parseDate, toDateObjectStrict, toOmrsIsoString } from '@openmrs/esm-framework';
2
+ import { endPatientStatus, MappedVisitQueueEntry } from '../active-visits/active-visits-table.resource';
3
+
4
+ export async function batchClearQueueEntries(queueEntries: Array<MappedVisitQueueEntry>) {
5
+ const abortController = new AbortController();
6
+ // number of concurrent requests in one batch
7
+ const batchSize = 10;
8
+ // request counter
9
+ let curReq = 0;
10
+ // as long as there are items in the list continue to form batches
11
+ while (curReq < queueEntries.length) {
12
+ const end = queueEntries.length < curReq + batchSize ? queueEntries.length : curReq + batchSize;
13
+ const concurrentReq = new Array(batchSize);
14
+ const endedAt = toDateObjectStrict(toOmrsIsoString(new Date()));
15
+ for (let index = curReq; index < end; index++) {
16
+ await Promise.all([
17
+ endPatientStatus(queueEntries[index]?.queueUuid, queueEntries[index]?.queueEntryUuid, endedAt),
18
+ ]);
19
+
20
+ concurrentReq.push(
21
+ openmrsFetch(`/ws/rest/v1/visit/${queueEntries[index].visitUuid}`, {
22
+ method: 'POST',
23
+ headers: {
24
+ 'Content-Type': 'application/json',
25
+ },
26
+ signal: abortController.signal,
27
+ body: {
28
+ location: queueEntries[index]?.visitLocation,
29
+ startDatetime: parseDate(queueEntries[index]?.visitStartDateTime),
30
+ visitType: queueEntries[index]?.visitTypeUuid,
31
+ stopDatetime: new Date(),
32
+ },
33
+ }),
34
+ );
35
+ curReq++;
36
+ }
37
+ // wait until all promises are done or one promise is rejected
38
+ return await Promise.all(concurrentReq)
39
+ .then((res) => {
40
+ return res;
41
+ })
42
+ .catch((error) => {
43
+ return error;
44
+ });
45
+ }
46
+ }
@@ -0,0 +1,15 @@
1
+ @use '@carbon/styles/scss/type';
2
+ @use '@carbon/styles/scss/spacing';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+ @import '~carbon-components/src/globals/scss/mixins';
5
+
6
+ #subHeading {
7
+ @include carbon--type-style('productive-heading-01');
8
+ color: $ui-05;
9
+ }
10
+
11
+ .clearBtn {
12
+ margin-left: spacing.$spacing-05;
13
+ height: spacing.$spacing-09;
14
+ border: 0.063rem solid #a2191f;
15
+ }
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import ClearQueueEntriesDialog from './clear-queue-entries-dialog.component';
4
+ import { batchClearQueueEntries } from './clear-queue-entries-dialog.resource';
5
+
6
+ const mockBatchClearQueueEntries = batchClearQueueEntries as jest.Mock;
7
+
8
+ jest.mock('@openmrs/esm-framework', () => ({
9
+ ...jest.requireActual('@openmrs/esm-framework'),
10
+ showToast: jest.fn(),
11
+ }));
12
+
13
+ jest.mock('./clear-queue-entries-dialog.resource');
14
+
15
+ jest.mock('../active-visits/active-visits-table.resource', () => ({
16
+ useVisitQueueEntries: () => ({
17
+ mutate: jest.fn(),
18
+ }),
19
+ }));
20
+
21
+ describe('ClearQueueEntriesDialog Component', () => {
22
+ const visitQueueEntriesMock = [];
23
+
24
+ it('renders the component with warning message', () => {
25
+ render(<ClearQueueEntriesDialog visitQueueEntries={visitQueueEntriesMock} closeModal={() => {}} />);
26
+
27
+ expect(screen.getByRole('heading', { name: 'Service queue' })).toBeInTheDocument();
28
+ expect(screen.getByText('Clear all queue entries?')).toBeInTheDocument();
29
+ expect(screen.getByText('Cancel')).toBeInTheDocument();
30
+ expect(screen.getByText('Clear queue')).toBeInTheDocument();
31
+ });
32
+
33
+ it('should close modal when clicked on cancel', async () => {
34
+ const closeModalMock = jest.fn();
35
+ mockBatchClearQueueEntries.mockImplementationOnce(() => Promise.resolve());
36
+ render(<ClearQueueEntriesDialog visitQueueEntries={visitQueueEntriesMock} closeModal={closeModalMock} />);
37
+
38
+ fireEvent.click(screen.getByText('Cancel'));
39
+
40
+ expect(closeModalMock).toHaveBeenCalled();
41
+ });
42
+ });
@@ -0,0 +1,36 @@
1
+ import React, { useCallback } from 'react';
2
+ import { showModal } from '@openmrs/esm-framework';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { MappedVisitQueueEntry } from '../active-visits/active-visits-table.resource';
5
+ import { Button } from '@carbon/react';
6
+ import { TrashCan } from '@carbon/react/icons';
7
+ import styles from './clear-queue-entries-dialog.scss';
8
+
9
+ interface ClearQueueEntriesProps {
10
+ visitQueueEntries: Array<MappedVisitQueueEntry>;
11
+ }
12
+
13
+ const ClearQueueEntries: React.FC<ClearQueueEntriesProps> = ({ visitQueueEntries }) => {
14
+ const { t } = useTranslation();
15
+
16
+ const launchClearAllQueueEntriesModal = useCallback(() => {
17
+ const dispose = showModal('clear-all-queue-entries', {
18
+ closeModal: () => dispose(),
19
+ visitQueueEntries,
20
+ });
21
+ }, [visitQueueEntries]);
22
+
23
+ return (
24
+ <Button
25
+ size="sm"
26
+ kind="danger--tertiary"
27
+ className={styles.clearBtn}
28
+ renderIcon={(props) => <TrashCan size={16} {...props} />}
29
+ onClick={launchClearAllQueueEntriesModal}
30
+ iconDescription={t('clearQueue', 'Clear queue')}>
31
+ {t('clearQueue', 'Clear queue')}
32
+ </Button>
33
+ );
34
+ };
35
+
36
+ export default ClearQueueEntries;
@@ -0,0 +1,177 @@
1
+ import { Type } from '@openmrs/esm-framework';
2
+ import vitalsConfigSchema, { VitalsConfigObject } from './current-visit/visit-details/vitals-config-schema';
3
+ import biometricsConfigSchema, { BiometricsConfigObject } from './current-visit/visit-details/biometrics-config-schema';
4
+
5
+ export const configSchema = {
6
+ concepts: {
7
+ priorityConceptSetUuid: {
8
+ _type: Type.ConceptUuid,
9
+ _default: '78063dec-b6d8-40c1-9483-dd4d3c3ca434',
10
+ },
11
+ defaultPriorityConceptUuid: {
12
+ _type: Type.ConceptUuid,
13
+ _description: 'The UUID of the default priority for the queues eg Not urgent.',
14
+ _default: 'f4620bfa-3625-4883-bd3f-84c2cce14470',
15
+ },
16
+ emergencyPriorityConceptUuid: {
17
+ _type: Type.ConceptUuid,
18
+ _description: 'The UUID of the priority with the highest sort weight for the queues eg Emergency.',
19
+ _default: '04f6f7e0-e3cb-4e13-a133-4479f759574e',
20
+ },
21
+ serviceConceptSetUuid: {
22
+ _type: Type.ConceptUuid,
23
+ _default: 'a8f3f64a-11d5-4a09-b0fb-c8118fa349f3',
24
+ },
25
+ statusConceptSetUuid: {
26
+ _type: Type.ConceptUuid,
27
+ _default: 'd60ffa60-fca6-4c60-aea9-a79469ae65c7',
28
+ },
29
+ defaultStatusConceptUuid: {
30
+ _type: Type.ConceptUuid,
31
+ _description: 'The UUID of the default status for the queues eg Waiting.',
32
+ _default: '51ae5e4d-b72b-4912-bf31-a17efb690aeb',
33
+ },
34
+ defaultTransitionStatus: {
35
+ _type: Type.ConceptUuid,
36
+ _description: 'The UUID of the default status for attending a service in the queues eg In Service.',
37
+ _default: 'ca7494ae-437f-4fd0-8aae-b88b9a2ba47d',
38
+ },
39
+ visitQueueNumberAttributeUuid: {
40
+ _type: Type.ConceptUuid,
41
+ _description: 'The UUID of the visit attribute that contains the visit queue number.',
42
+ _default: 'c61ce16f-272a-41e7-9924-4c555d0932c5',
43
+ },
44
+ systolicBloodPressureUuid: {
45
+ _type: Type.ConceptUuid,
46
+ _default: '5085AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
47
+ },
48
+ diastolicBloodPressureUuid: {
49
+ _type: Type.ConceptUuid,
50
+ _default: '5086AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
51
+ },
52
+ pulseUuid: {
53
+ _type: Type.ConceptUuid,
54
+ _default: '5087AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
55
+ },
56
+ temperatureUuid: {
57
+ _type: Type.ConceptUuid,
58
+ _default: '5088AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
59
+ },
60
+ oxygenSaturationUuid: {
61
+ _type: Type.ConceptUuid,
62
+ _default: '5092AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
63
+ },
64
+ heightUuid: {
65
+ _type: Type.ConceptUuid,
66
+ _default: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
67
+ },
68
+ weightUuid: {
69
+ _type: Type.ConceptUuid,
70
+ _default: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
71
+ },
72
+ respiratoryRateUuid: {
73
+ _type: Type.ConceptUuid,
74
+ _default: '5242AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
75
+ },
76
+ generalPatientNoteUuid: {
77
+ _type: Type.ConceptUuid,
78
+ _default: '165095AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
79
+ },
80
+ midUpperArmCircumferenceUuid: {
81
+ _type: Type.ConceptUuid,
82
+ _default: '1343AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
83
+ },
84
+ historicalObsConceptUuid: {
85
+ _type: Type.Array,
86
+ _description: 'The Uuids of the obs that are displayed on the previous visit modal',
87
+ _default: ['161643AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'],
88
+ },
89
+ },
90
+ contactAttributeType: {
91
+ _type: Type.UUID,
92
+ _description:
93
+ 'The Uuids of person attribute-type that captures contact information `e.g Next of kin contact details`',
94
+ _default: [],
95
+ },
96
+ vitals: vitalsConfigSchema,
97
+ biometrics: biometricsConfigSchema,
98
+ showQueueTableTab: {
99
+ _type: Type.Boolean,
100
+ _default: false,
101
+ _description: 'Disable outpatient table tabs',
102
+ },
103
+ appointmentStatuses: {
104
+ _type: Type.Array,
105
+ _description: 'Configurable appointment status (status of appointments)',
106
+ _default: ['Requested', 'Scheduled', 'CheckedIn', 'Completed', 'Cancelled', 'Missed'],
107
+ },
108
+ defaultIdentifierTypes: {
109
+ _type: Type.Array,
110
+ _element: {
111
+ _type: Type.String,
112
+ },
113
+ _description: 'The identifier types to be display on all patient search result page',
114
+ _default: ['05ee9cf4-7242-4a17-b4d4-00f707265c8a', 'f85081e2-b4be-4e48-b3a4-7994b69bb101'],
115
+ },
116
+ showRecommendedVisitTypeTab: {
117
+ _type: Type.Boolean,
118
+ _description: 'Whether start visit form should display recommended visit type tab. Requires `visitTypeResourceUrl`',
119
+ _default: false,
120
+ },
121
+ customPatientChartUrl: {
122
+ _type: Type.String,
123
+ _default: '',
124
+ _description: 'Custom URL to load patient chart',
125
+ },
126
+ customPatientIdUrl: {
127
+ _type: Type.String,
128
+ _default: '',
129
+ _description: 'Custom URL to fetch patient with id',
130
+ },
131
+ defaultFacilityUrl: {
132
+ _type: Type.String,
133
+ _default: '/ws/rest/v1/kenyaemr/default-facility',
134
+ _description: 'Custom URL to load default facility if it is not in the session',
135
+ },
136
+ customPatientChartText: {
137
+ _type: Type.String,
138
+ _default: '',
139
+ _description: 'Custom label for patient chart button',
140
+ },
141
+ };
142
+
143
+ export interface ConfigObject {
144
+ concepts: {
145
+ priorityConceptSetUuid: string;
146
+ defaultPriorityConceptUuid: string;
147
+ serviceConceptSetUuid: string;
148
+ statusConceptSetUuid: string;
149
+ defaultStatusConceptUuid: string;
150
+ systolicBloodPressureUuid: string;
151
+ diastolicBloodPressureUuid: string;
152
+ pulseUuid: string;
153
+ temperatureUuid: string;
154
+ oxygenSaturationUuid: string;
155
+ heightUuid: string;
156
+ weightUuid: string;
157
+ respiratoryRateUuid: string;
158
+ midUpperArmCircumferenceUuid: string;
159
+ emergencyPriorityConceptUuid: string;
160
+ historicalObsConceptUuid: Array<string>;
161
+ };
162
+ contactAttributeType: Array<string>;
163
+ vitals: VitalsConfigObject;
164
+ biometrics: BiometricsConfigObject;
165
+ showQueueTableTab: boolean;
166
+ appointmentStatuses: Array<string>;
167
+ defaultIdentifierTypes: Array<string>;
168
+ showRecommendedVisitTypeTab: boolean;
169
+ customPatientChartUrl: string;
170
+ customPatientIdUrl: string;
171
+ defaultFacilityUrl: string;
172
+ customPatientChartText: string;
173
+ }
174
+
175
+ export interface OutpatientConfig {
176
+ visitTypeResourceUrl: string;
177
+ }
@@ -0,0 +1,7 @@
1
+ import dayjs from 'dayjs';
2
+ export const spaRoot = window['getOpenmrsSpaBase'];
3
+ export const basePath = '/outpatient';
4
+ export const spaBasePath = `${window.spaBase}/home`;
5
+ export const omrsDateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZZ';
6
+ export const startOfDay = dayjs(new Date().setUTCHours(0, 0, 0, 0)).format(omrsDateFormat);
7
+ export const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -0,0 +1,35 @@
1
+ import React, { useMemo } from 'react';
2
+ import { ConfigurableLink } from '@openmrs/esm-framework';
3
+ import { BrowserRouter, useLocation } from 'react-router-dom';
4
+
5
+ export interface DashboardLinkConfig {
6
+ name: string;
7
+ title: string;
8
+ }
9
+
10
+ function DashboardExtension({ dashboardLinkConfig }: { dashboardLinkConfig: DashboardLinkConfig }) {
11
+ const { name, title } = dashboardLinkConfig;
12
+ const location = useLocation();
13
+ const spaBasePath = `${window.spaBase}/home`;
14
+
15
+ const navLink = useMemo(() => {
16
+ const pathArray = location.pathname.split('/home');
17
+ const lastElement = pathArray[pathArray.length - 1];
18
+ return decodeURIComponent(lastElement);
19
+ }, [location.pathname]);
20
+
21
+ return (
22
+ <ConfigurableLink
23
+ to={`${spaBasePath}/${name}`}
24
+ className={`cds--side-nav__link ${navLink.match(name) && 'active-left-nav-link'}`}>
25
+ {title}
26
+ </ConfigurableLink>
27
+ );
28
+ }
29
+
30
+ export const createDashboardLink = (dashboardLinkConfig: DashboardLinkConfig) => () =>
31
+ (
32
+ <BrowserRouter>
33
+ <DashboardExtension dashboardLinkConfig={dashboardLinkConfig} />
34
+ </BrowserRouter>
35
+ );
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Tag, DataTableSkeleton } from '@carbon/react';
4
+ import CurrentVisitDetails from './visit-details/current-visit-details.component';
5
+ import { useVisit } from './current-visit.resource';
6
+ import styles from './current-visit.scss';
7
+
8
+ interface CurrentVisitProps {
9
+ patientUuid: string;
10
+ visitUuid: string;
11
+ }
12
+
13
+ const CurrentVisit: React.FC<CurrentVisitProps> = ({ patientUuid, visitUuid }) => {
14
+ const { t } = useTranslation();
15
+ const { visit, isError, isLoading } = useVisit(visitUuid);
16
+
17
+ if (isLoading) {
18
+ return <DataTableSkeleton role="progressbar" />;
19
+ }
20
+ if (visit) {
21
+ return (
22
+ <div className={styles.wrapper}>
23
+ <div>
24
+ <p className={styles.heading}>{visit?.visitType?.display}</p>
25
+ <div className={styles.subHeading}>
26
+ {t('scheduledToday', 'Scheduled for today')} <Tag type="blue"> {t('onTime', 'On time')}</Tag>
27
+ </div>
28
+ </div>
29
+
30
+ <div className={styles.visitContainer}>
31
+ <CurrentVisitDetails encounters={visit.encounters} patientUuid={patientUuid} />
32
+ </div>
33
+ </div>
34
+ );
35
+ }
36
+ };
37
+
38
+ export default CurrentVisit;
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import CurrentVisit from './current-visit-summary.component';
4
+ import { useVisit } from './current-visit.resource';
5
+ import { mockPastVisit } from '../../__mocks__/visits.mock';
6
+
7
+ const useVisitMock = useVisit as jest.Mock;
8
+
9
+ jest.mock('./current-visit.resource', () => ({
10
+ useVisit: jest.fn(() => ({
11
+ visit: {
12
+ visitType: { display: 'Visit Type' },
13
+ encounters: [],
14
+ },
15
+ isError: false,
16
+ isLoading: false,
17
+ })),
18
+ }));
19
+
20
+ const patientUuid = mockPastVisit.data.results[0].patient.uuid;
21
+ const visitUuid = mockPastVisit.data.results[0].uuid;
22
+
23
+ describe('CurrentVisit', () => {
24
+ it('renders visit details correctly', async () => {
25
+ render(<CurrentVisit patientUuid={patientUuid} visitUuid={visitUuid} />);
26
+
27
+ await waitFor(() => {
28
+ expect(screen.queryByRole('progressbar')).toBeNull();
29
+ expect(screen.getByText('Visit Type')).toBeInTheDocument();
30
+ expect(screen.getByText('Scheduled for today')).toBeInTheDocument();
31
+ expect(screen.getByText('On time')).toBeInTheDocument();
32
+ });
33
+ });
34
+ it('should render skeleton when loading', async () => {
35
+ useVisitMock.mockImplementationOnce(() => ({
36
+ visit: undefined,
37
+ isError: false,
38
+ isLoading: true,
39
+ }));
40
+
41
+ render(<CurrentVisit patientUuid={patientUuid} visitUuid={visitUuid} />);
42
+
43
+ await waitFor(() => {
44
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
45
+ });
46
+ });
47
+ });
@@ -0,0 +1,84 @@
1
+ import useSWR from 'swr';
2
+ import { openmrsFetch, Visit } from '@openmrs/esm-framework';
3
+ import { ConceptMetadata } from './hooks/useVitalsConceptMetadata';
4
+ import { ObsMetaInfo } from '../types/index';
5
+
6
+ export function useVisit(visitUuid: string) {
7
+ const customRepresentation =
8
+ 'custom:(uuid,encounters:(uuid,encounterDatetime,' +
9
+ 'orders:(uuid,dateActivated,' +
10
+ 'drug:(uuid,name,strength),doseUnits:(uuid,display),' +
11
+ 'dose,route:(uuid,display),frequency:(uuid,display),' +
12
+ 'duration,durationUnits:(uuid,display),numRefills,' +
13
+ 'orderType:(uuid,display),orderer:(uuid,person:(uuid,display))),' +
14
+ 'obs:(uuid,concept:(uuid,display,conceptClass:(uuid,display)),' +
15
+ 'display,groupMembers:(uuid,concept:(uuid,display),' +
16
+ 'value:(uuid,display)),value),encounterType:(uuid,display),' +
17
+ 'encounterProviders:(uuid,display,encounterRole:(uuid,display),' +
18
+ 'provider:(uuid,person:(uuid,display)))),visitType:(uuid,name,display),startDatetime';
19
+
20
+ const apiUrl = `/ws/rest/v1/visit/${visitUuid}?v=${customRepresentation}`;
21
+
22
+ const { data, error, isLoading, isValidating } = useSWR<{ data: Visit }, Error>(
23
+ visitUuid ? apiUrl : null,
24
+ openmrsFetch,
25
+ );
26
+
27
+ return {
28
+ visit: data ? data.data : null,
29
+ isError: error,
30
+ isLoading,
31
+ isValidating,
32
+ };
33
+ }
34
+
35
+ export function calculateBMI(weight: number, height: number): number {
36
+ if (!weight || !height) {
37
+ return;
38
+ }
39
+
40
+ if (weight > 0 && height > 0) {
41
+ return Number((weight / (height / 100) ** 2).toFixed(1));
42
+ }
43
+ }
44
+
45
+ export function assessValue(value: number, range: ObsMetaInfo): ObservationInterpretation {
46
+ if (range?.hiCritical && value >= range.hiCritical) {
47
+ return 'critically_high';
48
+ }
49
+
50
+ if (range?.hiAbsolute && value >= range.hiAbsolute) {
51
+ return 'critically_high';
52
+ }
53
+
54
+ if (range?.hiNormal && value > range.hiNormal) {
55
+ return 'high';
56
+ }
57
+
58
+ if (range?.lowCritical && value <= range.lowCritical) {
59
+ return 'critically_low';
60
+ }
61
+
62
+ if (range?.lowAbsolute && value <= range.lowAbsolute) {
63
+ return 'critically_low';
64
+ }
65
+
66
+ if (range?.lowNormal && value < range.lowNormal) {
67
+ return 'low';
68
+ }
69
+
70
+ return 'normal';
71
+ }
72
+
73
+ export type ObservationInterpretation = 'critically_low' | 'critically_high' | 'high' | 'low' | 'normal';
74
+
75
+ export function getReferenceRangesForConcept(
76
+ conceptUuid: string,
77
+ conceptMetadata: Array<ConceptMetadata>,
78
+ ): ConceptMetadata {
79
+ if (!conceptUuid || !conceptMetadata?.length) {
80
+ return null;
81
+ }
82
+
83
+ return conceptMetadata?.find((metadata) => metadata.uuid === conceptUuid);
84
+ }
@@ -0,0 +1,34 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .visitContainer {
6
+ background-color: $ui-background;
7
+ display: grid;
8
+ grid-template-columns: max-content auto;
9
+ }
10
+
11
+ .bodyLong01 {
12
+ @include type.type-style('body-01');
13
+ }
14
+
15
+ .heading {
16
+ @include type.type-style('heading-compact-02');
17
+ }
18
+
19
+ .subHeading {
20
+ @include type.type-style('label-01');
21
+ color: $text-02;
22
+ }
23
+
24
+ .structuredListRow {
25
+ border-bottom: none;
26
+ }
27
+
28
+ .structuredList {
29
+ margin-bottom: 0;
30
+ }
31
+
32
+ .wrapper {
33
+ margin: spacing.$spacing-03;
34
+ }