@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,179 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ Column,
5
+ Form,
6
+ Layer,
7
+ Stack,
8
+ TextInput,
9
+ Select,
10
+ SelectItem,
11
+ ButtonSet,
12
+ Button,
13
+ InlineNotification,
14
+ } from '@carbon/react';
15
+ import { mutate } from 'swr';
16
+ import { type DefaultWorkspaceProps, restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
17
+ import { saveQueue, useServiceConcepts } from './queue-service.resource';
18
+ import { useQueueLocations } from '../patient-search/hooks/useQueueLocations';
19
+ import styles from './queue-service-form.scss';
20
+
21
+ const QueueServiceForm: React.FC<DefaultWorkspaceProps> = ({ closeWorkspace }) => {
22
+ const { t } = useTranslation();
23
+ const { queueConcepts } = useServiceConcepts();
24
+ const [queueName, setQueueName] = useState('');
25
+ const [queueConcept, setQueueConcept] = useState('');
26
+ const [isMissingName, setMissingName] = useState(false);
27
+ const [isMissingQueue, setMissingQueue] = useState(false);
28
+ const [isMissingLocation, setMissingLocation] = useState(false);
29
+ const [userLocation, setUserLocation] = useState('');
30
+ const { queueLocations } = useQueueLocations();
31
+
32
+ const createQueue = useCallback(
33
+ (event) => {
34
+ event.preventDefault();
35
+
36
+ if (!queueName) {
37
+ setMissingName(true);
38
+ return;
39
+ }
40
+ if (!queueConcept) {
41
+ setMissingQueue(true);
42
+ return;
43
+ }
44
+ if (!userLocation) {
45
+ setMissingLocation(true);
46
+ return;
47
+ }
48
+
49
+ setMissingName(false);
50
+ setMissingQueue(false);
51
+ setMissingLocation(false);
52
+
53
+ saveQueue(queueName, queueConcept, queueName, userLocation).then(
54
+ ({ status }) => {
55
+ if (status === 201) {
56
+ showSnackbar({
57
+ title: t('addQueue', 'Add queue'),
58
+ kind: 'success',
59
+ subtitle: t('queueAddedSuccessfully', 'Queue added successfully'),
60
+ });
61
+ closeWorkspace();
62
+ mutate(`${restBaseUrl}/queue?${userLocation}`);
63
+ mutate(`${restBaseUrl}/queue?location=${userLocation}`);
64
+ }
65
+ },
66
+ (error) => {
67
+ showSnackbar({
68
+ title: t('errorAddingQueue', 'Error adding queue'),
69
+ kind: 'error',
70
+ isLowContrast: false,
71
+ subtitle: error?.message,
72
+ });
73
+ },
74
+ );
75
+ },
76
+ [queueName, queueConcept, userLocation, t, closeWorkspace],
77
+ );
78
+
79
+ return (
80
+ <Form onSubmit={createQueue} className={styles.form}>
81
+ <Stack gap={4} className={styles.grid}>
82
+ <Column>
83
+ <Layer className={styles.input}>
84
+ <TextInput
85
+ id="queueName"
86
+ invalidText="Required"
87
+ labelText={t('queueName', 'Queue name')}
88
+ onChange={(event) => setQueueName(event.target.value)}
89
+ value={queueName}
90
+ />
91
+ {isMissingName && (
92
+ <section>
93
+ <InlineNotification
94
+ style={{ margin: '0', minWidth: '100%' }}
95
+ kind="error"
96
+ lowContrast={true}
97
+ title={t('missingQueueName', 'Missing queue name')}
98
+ subtitle={t('addQueueName', 'Please add a queue name')}
99
+ />
100
+ </section>
101
+ )}
102
+ </Layer>
103
+ </Column>
104
+
105
+ <Column>
106
+ <Layer className={styles.input}>
107
+ <Select
108
+ labelText={t('selectServiceType', 'Select a service type')}
109
+ id="queueConcept"
110
+ invalidText="Required"
111
+ value={queueConcept}
112
+ onChange={(event) => setQueueConcept(event.target.value)}>
113
+ {!queueConcept && <SelectItem text={t('selectServiceType', 'Select a service type')} />}
114
+ {queueConcepts.length === 0 && <SelectItem text={t('noServicesAvailable', 'No services available')} />}
115
+ {queueConcepts?.length > 0 &&
116
+ queueConcepts.map((concept) => (
117
+ <SelectItem key={concept.uuid} text={concept.display} value={concept.uuid}>
118
+ {concept.display}
119
+ </SelectItem>
120
+ ))}
121
+ </Select>
122
+ {isMissingQueue && (
123
+ <section>
124
+ <InlineNotification
125
+ style={{ margin: '0', minWidth: '100%' }}
126
+ kind="error"
127
+ lowContrast={true}
128
+ title={t('missingService', 'Missing service')}
129
+ subtitle={t('selectServiceType', 'Select a service type')}
130
+ />
131
+ </section>
132
+ )}
133
+ </Layer>
134
+ </Column>
135
+
136
+ <Column>
137
+ <Layer className={styles.input}>
138
+ <Select
139
+ labelText={t('selectALocation', 'Select a location')}
140
+ id="location"
141
+ invalidText="Required"
142
+ value={userLocation}
143
+ onChange={(event) => setUserLocation(event.target.value)}>
144
+ {!userLocation && <SelectItem text={t('selectALocation', 'Select a location')} />}
145
+ {queueLocations.length === 0 && <SelectItem text={t('noLocationsAvailable', 'No locations available')} />}
146
+ {queueLocations?.length > 0 &&
147
+ queueLocations.map((location) => (
148
+ <SelectItem key={location.id} text={location.name} value={location.id}>
149
+ {location.name}
150
+ </SelectItem>
151
+ ))}
152
+ </Select>
153
+ {isMissingLocation && (
154
+ <section>
155
+ <InlineNotification
156
+ style={{ margin: '0', minWidth: '100%' }}
157
+ kind="error"
158
+ lowContrast={true}
159
+ title={t('missingLocation', 'Missing location')}
160
+ subtitle={t('pleaseSelectLocation', 'Please select a location')}
161
+ />
162
+ </section>
163
+ )}
164
+ </Layer>
165
+ </Column>
166
+ </Stack>
167
+ <ButtonSet className={styles.buttonSet}>
168
+ <Button className={styles.button} kind="secondary" onClick={closeWorkspace}>
169
+ {t('cancel', 'Cancel')}
170
+ </Button>
171
+ <Button className={styles.button} kind="primary" type="submit">
172
+ {t('save', 'Save')}
173
+ </Button>
174
+ </ButtonSet>
175
+ </Form>
176
+ );
177
+ };
178
+
179
+ export default QueueServiceForm;
@@ -0,0 +1,33 @@
1
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import { useSystemSetting } from '../hooks/useSystemSetting';
3
+ import { useConcept } from '../hooks/useConcept';
4
+
5
+ export function useServiceConcepts() {
6
+ const { systemSetting: serviceConceptSetting } = useSystemSetting('queue.serviceConceptSetName');
7
+ const { concept: serviceConceptSet, error, isLoading } = useConcept(serviceConceptSetting?.value);
8
+ return {
9
+ queueConcepts: serviceConceptSet?.setMembers?.sort((c1, c2) => c1.display.localeCompare(c2.display)) || [],
10
+ error,
11
+ isLoading,
12
+ };
13
+ }
14
+
15
+ export function saveQueue(queueName: string, queueConcept: string, queueDescription: string, queueLocation: string) {
16
+ const abortController = new AbortController();
17
+
18
+ return openmrsFetch(`${restBaseUrl}/queue`, {
19
+ method: 'POST',
20
+ headers: {
21
+ 'Content-Type': 'application/json',
22
+ },
23
+ signal: abortController.signal,
24
+ body: {
25
+ name: queueName,
26
+ description: queueDescription,
27
+ service: { uuid: queueConcept },
28
+ location: {
29
+ uuid: queueLocation,
30
+ },
31
+ },
32
+ });
33
+ }
@@ -0,0 +1,135 @@
1
+ import { showToast, translateFrom, useConfig } from '@openmrs/esm-framework';
2
+ import { useMemo } from 'react';
3
+ import { useTranslation, type TFunction } from 'react-i18next';
4
+ import {
5
+ builtInColumns,
6
+ defaultColumnConfig,
7
+ defaultQueueTable,
8
+ type ColumnDefinition,
9
+ type ConfigObject,
10
+ } from '../../config-schema';
11
+ import { type QueueTableColumn } from '../../types';
12
+ import { queueTableComingFromColumn } from './queue-table-coming-from-cell.component';
13
+ import { queueTableExtensionColumn } from './queue-table-extension-cell.component';
14
+ import { queueTableNameColumn } from './queue-table-name-cell.component';
15
+ import { queueTablePatientAgeColumn } from './queue-table-patient-age-cell.component';
16
+ import { queueTablePatientIdentifierColumn } from './queue-table-patient-identifier-cell.component';
17
+ import { queueTablePriorityColumn } from './queue-table-priority-cell.component';
18
+ import { queueTableQueueNameColumn } from './queue-table-queue-name-cell.component';
19
+ import { queueTableStatusColumn } from './queue-table-status-cell.component';
20
+ import { queueTableVisitAttributeQueueNumberColumn } from './queue-table-visit-attribute-queue-number-cell.component';
21
+ import { queueTableVisitStartTimeColumn } from './queue-table-visit-start-time-cell.component';
22
+ import { queueTableWaitTimeColumn } from './queue-table-wait-time-cell.component';
23
+ import { queueTableActionColumn } from './queue-table-action-cell.component';
24
+
25
+ // returns the columns to display for a queue table of a particular queue + status.
26
+ // For a table displaying all entries of a particular queue, the status param should be null
27
+ // For a table displaying all entries from all queues, both params should be null
28
+ export function useColumns(queue: string, status: string): QueueTableColumn[] {
29
+ const { t } = useTranslation();
30
+ const config = useConfig<ConfigObject>();
31
+ const { queueTables, visitQueueNumberAttributeUuid } = config;
32
+ const { columnDefinitions } = queueTables;
33
+ const tableDefinitions = [...queueTables.tableDefinitions, defaultQueueTable];
34
+ const globalColumnConfig = {
35
+ ...defaultColumnConfig,
36
+ visitQueueNumberAttributeUuid,
37
+ };
38
+
39
+ const columnsMap = useMemo(() => {
40
+ const map = new Map<string, QueueTableColumn>();
41
+ for (const column of builtInColumns) {
42
+ map.set(column, getColumnFromDefinition(t, { id: column, config: globalColumnConfig }));
43
+ }
44
+ for (const columnDef of columnDefinitions) {
45
+ if (columnDef.columnType == 'queue-number' || columnDef.id == 'queue-number') {
46
+ columnDef.config.visitQueueNumberAttributeUuid =
47
+ columnDef.config.visitQueueNumberAttributeUuid ?? visitQueueNumberAttributeUuid;
48
+ }
49
+ map.set(columnDef.id, getColumnFromDefinition(t, columnDef));
50
+ }
51
+ return map;
52
+ }, [columnDefinitions, t]);
53
+
54
+ const tableDefinition = useMemo(
55
+ () =>
56
+ tableDefinitions.find((tableDef) => {
57
+ const appliedTo = tableDef.appliedTo;
58
+
59
+ return (
60
+ appliedTo == null ||
61
+ appliedTo.some(
62
+ (criteria) =>
63
+ (criteria.queue == null || criteria.queue == queue) &&
64
+ (criteria.status == null || criteria.status == status),
65
+ )
66
+ );
67
+ }),
68
+ [tableDefinitions, queue, status],
69
+ );
70
+
71
+ const columns = tableDefinition?.columns?.map((columnId) => {
72
+ const column = columnsMap.get(columnId);
73
+ if (!column) {
74
+ showToast({
75
+ title: t('invalidColumnConfig', 'Invalid column config'),
76
+ kind: 'warning',
77
+ description: 'Unknown column id: ' + columnId,
78
+ });
79
+ }
80
+ return column;
81
+ });
82
+ return columns;
83
+ }
84
+
85
+ function getColumnFromDefinition(t: TFunction, columnDef: ColumnDefinition): QueueTableColumn {
86
+ const { id, header, headerI18nModule, columnType } = columnDef;
87
+
88
+ const translatedHeader = header ? translateFrom(headerI18nModule ?? '@openmrs/esm-service-queues-app', header) : null;
89
+
90
+ switch (columnType ?? id) {
91
+ case 'patient-name': {
92
+ return queueTableNameColumn(id, translatedHeader ?? t('name', 'Name'));
93
+ }
94
+ case 'patient-identifier': {
95
+ return queueTablePatientIdentifierColumn(id, translatedHeader ?? t('patientId', 'Patient Id'), columnDef.config);
96
+ }
97
+ case 'queue-number': {
98
+ return queueTableVisitAttributeQueueNumberColumn(
99
+ id,
100
+ translatedHeader ?? t('queueNumber', 'Queue Number'),
101
+ columnDef.config,
102
+ );
103
+ }
104
+ case 'patient-age': {
105
+ return queueTablePatientAgeColumn(id, translatedHeader ?? t('age', 'Age'));
106
+ }
107
+ case 'priority': {
108
+ return queueTablePriorityColumn(id, translatedHeader ?? t('priority', 'Priority'), columnDef.config);
109
+ }
110
+ case 'status': {
111
+ return queueTableStatusColumn(id, translatedHeader ?? t('status', 'Status'), columnDef.config);
112
+ }
113
+ case 'coming-from': {
114
+ return queueTableComingFromColumn(id, translatedHeader ?? t('comingFrom', 'Coming from'));
115
+ }
116
+ case 'queue': {
117
+ return queueTableQueueNameColumn(id, translatedHeader ?? t('queue', 'Queue'));
118
+ }
119
+ case 'wait-time': {
120
+ return queueTableWaitTimeColumn(id, translatedHeader ?? t('waitTime', 'Wait time'));
121
+ }
122
+ case 'visit-start-time': {
123
+ return queueTableVisitStartTimeColumn(id, translatedHeader ?? t('visitStartTime', 'Visit start time'));
124
+ }
125
+ case 'actions': {
126
+ return queueTableActionColumn(id, translatedHeader ?? t('actions', 'Actions'));
127
+ }
128
+ case 'extension': {
129
+ return queueTableExtensionColumn(id, translatedHeader);
130
+ }
131
+ default: {
132
+ throw new Error('Unknown column type from configuration: ' + columnType);
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,88 @@
1
+ import React from 'react';
2
+ import { Button, OverflowMenu, OverflowMenuItem } from '@carbon/react';
3
+ import { showModal } from '@openmrs/esm-framework';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';
6
+ import styles from './queue-table-action-cell.scss';
7
+
8
+ export function QueueTableActionCell({ queueEntry }: QueueTableCellComponentProps) {
9
+ const { t } = useTranslation();
10
+
11
+ return (
12
+ <div className={styles.actionCellContainer}>
13
+ <Button
14
+ kind="ghost"
15
+ aria-label={t('actions', 'Actions')}
16
+ onClick={() => {
17
+ const dispose = showModal('transition-queue-entry-modal', {
18
+ closeModal: () => dispose(),
19
+ queueEntry,
20
+ });
21
+ }}>
22
+ {t('transition', 'Transition')}
23
+ </Button>
24
+ <OverflowMenu aria-label="Actions menu" size="sm" flipped>
25
+ <OverflowMenuItem
26
+ className={styles.menuItem}
27
+ aria-label={t('edit', 'Edit')}
28
+ hasDivider
29
+ onClick={() => {
30
+ const dispose = showModal('edit-queue-entry-modal', {
31
+ closeModal: () => dispose(),
32
+ queueEntry,
33
+ });
34
+ }}
35
+ itemText={t('edit', 'Edit')}
36
+ />
37
+ <OverflowMenuItem
38
+ className={styles.menuItem}
39
+ aria-label={t('removePatient', 'Remove patient')}
40
+ hasDivider
41
+ onClick={() => {
42
+ const dispose = showModal('end-queue-entry-modal', {
43
+ closeModal: () => dispose(),
44
+ queueEntry,
45
+ });
46
+ }}
47
+ itemText={t('removePatient', 'Remove patient')}
48
+ />
49
+ {queueEntry.previousQueueEntry == null ? (
50
+ <OverflowMenuItem
51
+ className={styles.menuItem}
52
+ aria-label={t('delete', 'Delete')}
53
+ hasDivider
54
+ isDelete
55
+ onClick={() => {
56
+ const dispose = showModal('void-queue-entry-modal', {
57
+ closeModal: () => dispose(),
58
+ queueEntry,
59
+ });
60
+ }}
61
+ itemText={t('delete', 'Delete')}
62
+ />
63
+ ) : (
64
+ <OverflowMenuItem
65
+ className={styles.menuItem}
66
+ aria-label={t('undoTransition', 'Undo transition')}
67
+ hasDivider
68
+ isDelete
69
+ onClick={() => {
70
+ const dispose = showModal('undo-transition-queue-entry-modal', {
71
+ closeModal: () => dispose(),
72
+ queueEntry,
73
+ });
74
+ }}
75
+ itemText={t('undoTransition', 'Undo transition')}
76
+ />
77
+ )}
78
+ </OverflowMenu>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ export const queueTableActionColumn: QueueTableColumnFunction = (key, header) => ({
84
+ key,
85
+ header,
86
+ CellComponent: QueueTableActionCell,
87
+ getFilterableValue: null,
88
+ });
@@ -0,0 +1,7 @@
1
+ .actionCellContainer {
2
+ white-space: nowrap;
3
+ }
4
+
5
+ .menuItem {
6
+ max-width: none;
7
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';
3
+
4
+ export const QueueTableComingFromCell = ({ queueEntry }: QueueTableCellComponentProps) => {
5
+ return <>{queueEntry.queueComingFrom?.display}</>;
6
+ };
7
+
8
+ export const queueTableComingFromColumn: QueueTableColumnFunction = (key, header) => ({
9
+ key,
10
+ header,
11
+ CellComponent: QueueTableComingFromCell,
12
+ getFilterableValue: (queueEntry) => queueEntry.queueComingFrom?.display,
13
+ });
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { ExtensionSlot } from '@openmrs/esm-framework';
3
+ import { type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';
4
+
5
+ export const queueTableExtensionColumn: QueueTableColumnFunction = (key, header) => {
6
+ const QueueTableExtensionCell = ({ queueEntry }: QueueTableCellComponentProps) => {
7
+ return <ExtensionSlot name={`queue-table-${key}-slot`} state={{ queueEntry }} />;
8
+ };
9
+
10
+ return {
11
+ key,
12
+ header,
13
+ CellComponent: QueueTableExtensionCell,
14
+ getFilterableValue: null,
15
+ };
16
+ };
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { ConfigurableLink, useConfig } from '@openmrs/esm-framework';
3
+ import { type ConfigObject } from '../../config-schema';
4
+ import { type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';
5
+
6
+ export const QueueTableNameCell = ({ queueEntry }: QueueTableCellComponentProps) => {
7
+ const { customPatientChartUrl } = useConfig<ConfigObject>();
8
+ return (
9
+ <ConfigurableLink to={customPatientChartUrl} templateParams={{ patientUuid: queueEntry.patient.uuid }}>
10
+ {queueEntry.patient.person.display}
11
+ </ConfigurableLink>
12
+ );
13
+ };
14
+
15
+ export const queueTableNameColumn: QueueTableColumnFunction = (key, header) => ({
16
+ key,
17
+ header,
18
+ CellComponent: QueueTableNameCell,
19
+ getFilterableValue: (queueEntry) => queueEntry.patient.person.display,
20
+ });
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import dayjs from 'dayjs';
3
+ import { type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';
4
+
5
+ export const QueueTablePatientAgeCell = ({ queueEntry }: QueueTableCellComponentProps) => {
6
+ const birthdate = dayjs(queueEntry.patient.person.birthdate);
7
+ const todaydate = dayjs();
8
+ const age = todaydate.diff(birthdate, 'years');
9
+
10
+ return <span>{age}</span>;
11
+ };
12
+
13
+ export const queueTablePatientAgeColumn: QueueTableColumnFunction = (key, header) => ({
14
+ key,
15
+ header,
16
+ CellComponent: QueueTablePatientAgeCell,
17
+ getFilterableValue: null,
18
+ });
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { type PatientIdentifierColumnConfig } from '../../config-schema';
3
+ import { type QueueEntry, type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';
4
+
5
+ export const queueTablePatientIdentifierColumn: QueueTableColumnFunction = (
6
+ key,
7
+ header,
8
+ config: PatientIdentifierColumnConfig,
9
+ ) => {
10
+ const { identifierTypeUuid } = config;
11
+
12
+ const getPatientIdentifier = (queueEntry: QueueEntry) =>
13
+ queueEntry.patient.identifiers.find((i) => i.identifierType?.uuid == identifierTypeUuid)?.identifier;
14
+
15
+ const QueueTablePatientIdentifierCell = ({ queueEntry }: QueueTableCellComponentProps) => {
16
+ return <span>{getPatientIdentifier(queueEntry)}</span>;
17
+ };
18
+
19
+ return {
20
+ key,
21
+ header,
22
+ CellComponent: QueueTablePatientIdentifierCell,
23
+ getFilterableValue: getPatientIdentifier,
24
+ };
25
+ };
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import QueuePriority from '../../queue-entry-table-components/queue-priority.component';
3
+ import { type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';
4
+ import { type PriorityColumnConfig } from '../../config-schema';
5
+
6
+ export const queueTablePriorityColumn: QueueTableColumnFunction = (key, header, config: PriorityColumnConfig) => {
7
+ const QueueTablePriorityCell = ({ queueEntry }: QueueTableCellComponentProps) => {
8
+ return (
9
+ <QueuePriority
10
+ priority={queueEntry.priority}
11
+ priorityComment={queueEntry.priorityComment}
12
+ priorityConfigs={config?.priorityConfigs}
13
+ />
14
+ );
15
+ };
16
+
17
+ return {
18
+ key,
19
+ header,
20
+ CellComponent: QueueTablePriorityCell,
21
+ getFilterableValue: (queueEntry) => queueEntry.priority.display,
22
+ };
23
+ };
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import { type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';
3
+
4
+ // reprevents a column showing which queue a queue entry belongs to
5
+ export const QueueTableQueueNameCell = ({ queueEntry }: QueueTableCellComponentProps) => {
6
+ return <>{queueEntry.queue.display}</>;
7
+ };
8
+
9
+ export const queueTableQueueNameColumn: QueueTableColumnFunction = (key, header) => ({
10
+ key,
11
+ header,
12
+ CellComponent: QueueTableQueueNameCell,
13
+ getFilterableValue: (queueEntry) => queueEntry.queue.display,
14
+ });
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import QueueStatus from '../../queue-entry-table-components/queue-status.component';
3
+ import { type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';
4
+ import { type StatusColumnConfig } from '../../config-schema';
5
+
6
+ export const queueTableStatusColumn: QueueTableColumnFunction = (key, header, config: StatusColumnConfig) => {
7
+ const QueueTableStatusCell = ({ queueEntry }: QueueTableCellComponentProps) => {
8
+ // Do not pass queue into status, as we do not want to render it
9
+ return <QueueStatus status={queueEntry.status} statusConfigs={config?.statusConfigs} />;
10
+ };
11
+
12
+ return {
13
+ key,
14
+ header,
15
+ CellComponent: QueueTableStatusCell,
16
+ getFilterableValue: (queueEntry) => queueEntry.status.display,
17
+ };
18
+ };
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import { type VisitAttributeQueueNumberColumnConfig } from '../../config-schema';
3
+ import { type QueueTableColumnFunction, type QueueEntry, type QueueTableCellComponentProps } from '../../types';
4
+ import { InlineNotification } from '@carbon/react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ export const queueTableVisitAttributeQueueNumberColumn: QueueTableColumnFunction = (
8
+ key,
9
+ header,
10
+ { visitQueueNumberAttributeUuid }: VisitAttributeQueueNumberColumnConfig,
11
+ ) => {
12
+ function getVisitQueueNumber(queueEntry: QueueEntry) {
13
+ return queueEntry.visit?.attributes?.find((e) => e?.attributeType?.uuid === visitQueueNumberAttributeUuid)?.value;
14
+ }
15
+
16
+ const QueueTableVisitAttributeQueueNumberCell = ({ queueEntry }: QueueTableCellComponentProps) => {
17
+ const { t } = useTranslation();
18
+ return (
19
+ <>
20
+ {visitQueueNumberAttributeUuid ? (
21
+ <span>{getVisitQueueNumber(queueEntry)}</span>
22
+ ) : (
23
+ <InlineNotification lowContrast hideCloseButton>
24
+ {t('visitQueueNumberAttributeUuid not configured', 'visitQueueNumberAttributeUuid not configured')}
25
+ </InlineNotification>
26
+ )}
27
+ </>
28
+ );
29
+ };
30
+
31
+ return {
32
+ key,
33
+ header,
34
+ CellComponent: QueueTableVisitAttributeQueueNumberCell,
35
+ getFilterableValue: getVisitQueueNumber,
36
+ };
37
+ };
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import { type QueueTableColumnFunction, type QueueTableCellComponentProps, type QueueEntry } from '../../types';
3
+ import { formatDatetime } from '@openmrs/esm-framework';
4
+
5
+ export const queueTableVisitStartTimeColumn: QueueTableColumnFunction = (key, header) => {
6
+ function getVisitStartTime(queueEntry: QueueEntry) {
7
+ return formatDatetime(new Date(queueEntry.visit?.startDatetime));
8
+ }
9
+
10
+ const QueueTableVisitStartTimeCell = ({ queueEntry }: QueueTableCellComponentProps) => {
11
+ return <span>{getVisitStartTime(queueEntry)}</span>;
12
+ };
13
+
14
+ return {
15
+ key,
16
+ header,
17
+ CellComponent: QueueTableVisitStartTimeCell,
18
+ getFilterableValue: getVisitStartTime,
19
+ };
20
+ };