@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,362 @@
1
+ import {
2
+ Button,
3
+ Checkbox,
4
+ ContentSwitcher,
5
+ DatePicker,
6
+ DatePickerInput,
7
+ InlineNotification,
8
+ ModalBody,
9
+ ModalFooter,
10
+ ModalHeader,
11
+ RadioButton,
12
+ RadioButtonGroup,
13
+ Select,
14
+ SelectItem,
15
+ Stack,
16
+ Switch,
17
+ TextArea,
18
+ TimePicker,
19
+ TimePickerSelect,
20
+ } from '@carbon/react';
21
+ import { showSnackbar, type FetchResponse } from '@openmrs/esm-framework';
22
+ import dayjs from 'dayjs';
23
+ import React, { useMemo, useState } from 'react';
24
+ import { useTranslation } from 'react-i18next';
25
+ import { datePickerFormat, datePickerPlaceHolder, time12HourFormatRegexPattern } from '../../constants';
26
+ import { convertTime12to24, type amPm } from '../../helpers/time-helpers';
27
+ import { useMutateQueueEntries } from '../../hooks/useQueueEntries';
28
+ import { useQueues } from '../../hooks/useQueues';
29
+ import { type QueueEntry } from '../../types';
30
+ import styles from './queue-entry-actons-modal.scss';
31
+
32
+ interface QueueEntryActionModalProps {
33
+ queueEntry: QueueEntry;
34
+ closeModal: () => void;
35
+ modalParams: ModalParams;
36
+ }
37
+
38
+ interface FormState {
39
+ selectedQueue: string;
40
+ selectedPriority: string;
41
+ selectedStatus: string;
42
+ prioritycomment: string;
43
+ modifyDefaultTransitionDateTime: boolean;
44
+ transitionDate: Date;
45
+ transitionTime: string;
46
+ transitionTimeFormat: amPm;
47
+ }
48
+
49
+ interface ModalParams {
50
+ modalTitle: string;
51
+ modalInstruction: string;
52
+ submitButtonText: string;
53
+ submitSuccessTitle: string;
54
+ submitSuccessText: string;
55
+ submitFailureTitle: string;
56
+ submitAction: (queueEntry: QueueEntry, formState: FormState) => Promise<FetchResponse<any>>;
57
+ disableSubmit: (queueEntry, formState) => boolean;
58
+ isTransition: boolean; // is transition or edit?
59
+ }
60
+
61
+ // Modal for performing a queue entry action that requires additional form fields / inputs from user
62
+ // Used by EditQueueEntryModal and TransitionQueueEntryModal
63
+ export const QueueEntryActionModal: React.FC<QueueEntryActionModalProps> = ({
64
+ queueEntry,
65
+ closeModal,
66
+ modalParams,
67
+ }) => {
68
+ const { t } = useTranslation();
69
+ const { mutateQueueEntries } = useMutateQueueEntries();
70
+ const {
71
+ modalTitle,
72
+ modalInstruction,
73
+ submitButtonText,
74
+ submitSuccessTitle,
75
+ submitSuccessText,
76
+ submitFailureTitle,
77
+ submitAction,
78
+ disableSubmit,
79
+ isTransition,
80
+ } = modalParams;
81
+
82
+ const initialTransitionDate = isTransition ? new Date() : new Date(queueEntry.startedAt);
83
+ const [formState, setFormState] = useState<FormState>({
84
+ selectedQueue: queueEntry.queue.uuid,
85
+ selectedPriority: queueEntry.priority.uuid,
86
+ selectedStatus: queueEntry.status.uuid,
87
+ prioritycomment: queueEntry.priorityComment ?? '',
88
+ modifyDefaultTransitionDateTime: false,
89
+ transitionDate: initialTransitionDate,
90
+ transitionTime: dayjs(initialTransitionDate).format('hh:mm'),
91
+ transitionTimeFormat: dayjs(initialTransitionDate).hour() < 12 ? 'AM' : 'PM',
92
+ });
93
+ const { queues } = useQueues();
94
+ const [isSubmitting, setIsSubmitting] = useState(false);
95
+
96
+ const selectedQueue = queues.find((q) => q.uuid == formState.selectedQueue);
97
+
98
+ const statuses = selectedQueue?.allowedStatuses;
99
+ const hasNoStatusesConfigured = selectedQueue && statuses.length == 0;
100
+ const priorities = selectedQueue?.allowedPriorities;
101
+ const hasNoPrioritiesConfigured = selectedQueue && priorities.length == 0;
102
+
103
+ const setSelectedQueueUuid = (selectedQueueUuid: string) => {
104
+ const newSelectedQueue = queues.find((q) => q.uuid == selectedQueueUuid);
105
+ const { allowedStatuses, allowedPriorities } = newSelectedQueue;
106
+ const newQueueHasCurrentStatus = allowedStatuses.find((s) => s.uuid == formState.selectedStatus);
107
+ const newQueueHasCurrentPriority = allowedPriorities.find((s) => s.uuid == formState.selectedPriority);
108
+ setFormState({
109
+ ...formState,
110
+ selectedQueue: selectedQueueUuid,
111
+ selectedStatus: newQueueHasCurrentStatus ? formState.selectedStatus : allowedStatuses[0]?.uuid,
112
+ selectedPriority: newQueueHasCurrentPriority ? formState.selectedPriority : allowedPriorities[0]?.uuid,
113
+ });
114
+ };
115
+
116
+ const setSelectedPriorityUuid = (selectedPriorityUuid: string) => {
117
+ setFormState({ ...formState, selectedPriority: selectedPriorityUuid });
118
+ };
119
+
120
+ const setSelectedStatusUuid = (selectedStatusUuid: string) => {
121
+ setFormState({ ...formState, selectedStatus: selectedStatusUuid });
122
+ };
123
+
124
+ const setPriorityComment = (prioritycomment: string) => {
125
+ setFormState({ ...formState, prioritycomment });
126
+ };
127
+
128
+ const setTransitionDate = (transitionDate: Date) => {
129
+ setFormState({ ...formState, transitionDate });
130
+ };
131
+
132
+ const setTransitionTime = (transitionTime: string) => {
133
+ setFormState({ ...formState, transitionTime });
134
+ };
135
+
136
+ const setTransitionTimeFormat = (transitionTimeFormat: amPm) => {
137
+ setFormState({ ...formState, transitionTimeFormat });
138
+ };
139
+
140
+ const setModifyDefaultTransitionDateTime = (modifyDefaultTransitionDateTime) => {
141
+ setFormState({ ...formState, modifyDefaultTransitionDateTime });
142
+ };
143
+
144
+ const submitForm = (e) => {
145
+ e.preventDefault();
146
+ setIsSubmitting(true);
147
+
148
+ submitAction(queueEntry, formState)
149
+ .then(({ status }) => {
150
+ if (status === 200) {
151
+ showSnackbar({
152
+ isLowContrast: true,
153
+ title: submitSuccessTitle,
154
+ kind: 'success',
155
+ subtitle: submitSuccessText,
156
+ });
157
+ mutateQueueEntries();
158
+ closeModal();
159
+ } else {
160
+ throw { message: t('unexpectedServerResponse', 'Unexpected Server Response') };
161
+ }
162
+ })
163
+ .catch((error) => {
164
+ showSnackbar({
165
+ title: submitFailureTitle,
166
+ kind: 'error',
167
+ subtitle: error?.message,
168
+ });
169
+ })
170
+ .finally(() => {
171
+ setIsSubmitting(false);
172
+ });
173
+ };
174
+
175
+ const isTimeInvalid = useMemo(() => {
176
+ const now = new Date();
177
+ const startAtDate = new Date(formState.transitionDate);
178
+ const [hour, minute] = convertTime12to24(formState.transitionTime, formState.transitionTimeFormat);
179
+ startAtDate.setHours(hour, minute, 0, 0);
180
+ return startAtDate > now;
181
+ }, [formState.transitionDate, formState.transitionTime, formState.transitionTimeFormat]);
182
+
183
+ const selectedPriorityIndex = priorities.findIndex((p) => p.uuid == formState.selectedPriority);
184
+
185
+ return (
186
+ <>
187
+ <ModalHeader closeModal={closeModal} title={modalTitle} />
188
+ <ModalBody>
189
+ <div className={styles.queueEntryActionModalBody}>
190
+ <Stack gap={4}>
191
+ <h5>{queueEntry.display}</h5>
192
+ <p>{modalInstruction}</p>
193
+ <section className={styles.section}>
194
+ <div className={styles.sectionTitle}>{t('serviceQueue', 'Service queue')}</div>
195
+ <Select
196
+ labelText={t('selectQueue', 'Select a queue')}
197
+ id="queue"
198
+ invalidText="Required"
199
+ value={formState.selectedQueue}
200
+ onChange={(event) => setSelectedQueueUuid(event.target.value)}>
201
+ {queues?.map(({ uuid, display, location }) => (
202
+ <SelectItem
203
+ key={uuid}
204
+ text={
205
+ uuid == queueEntry.queue.uuid
206
+ ? t('currentValueFormatted', '{{value}} (Current)', {
207
+ value: `${display} - ${location?.display}`,
208
+ interpolation: { escapeValue: false },
209
+ })
210
+ : `${display} - ${location?.display}`
211
+ }
212
+ value={uuid}
213
+ />
214
+ ))}
215
+ </Select>
216
+ </section>
217
+
218
+ <section>
219
+ <div className={styles.sectionTitle}>{t('queueStatus', 'Queue status')}</div>
220
+ {hasNoStatusesConfigured ? (
221
+ <InlineNotification
222
+ kind={'error'}
223
+ lowContrast
224
+ subtitle={t('configureStatus', 'Please configure status to continue.')}
225
+ title={t('noStatusConfigured', 'No status configured')}
226
+ />
227
+ ) : (
228
+ <RadioButtonGroup
229
+ name="status"
230
+ valueSelected={formState.selectedStatus}
231
+ onChange={(uuid) => {
232
+ setSelectedStatusUuid(uuid);
233
+ }}>
234
+ {statuses?.map(({ uuid, display }) => (
235
+ <RadioButton
236
+ key={uuid}
237
+ name={display}
238
+ labelText={
239
+ uuid == queueEntry.status.uuid
240
+ ? t('currentValueFormatted', '{{value}} (Current)', {
241
+ value: display,
242
+ interpolation: { escapeValue: false },
243
+ })
244
+ : display
245
+ }
246
+ value={uuid}
247
+ />
248
+ ))}
249
+ </RadioButtonGroup>
250
+ )}
251
+ </section>
252
+
253
+ <section className={styles.section}>
254
+ <div className={styles.sectionTitle}>{t('queuePriority', 'Queue priority')}</div>
255
+ {hasNoPrioritiesConfigured ? (
256
+ <InlineNotification
257
+ className={styles.inlineNotification}
258
+ kind={'error'}
259
+ lowContrast
260
+ subtitle={t('configurePriorities', 'Please configure priorities to continue.')}
261
+ title={t('noPrioritiesConfigured', 'No priorities configured')}
262
+ />
263
+ ) : (
264
+ <ContentSwitcher
265
+ size="sm"
266
+ selectedIndex={selectedPriorityIndex}
267
+ onChange={(event) => {
268
+ setSelectedPriorityUuid(event.name as string);
269
+ }}>
270
+ {priorities?.map(({ uuid, display }) => (
271
+ <Switch
272
+ role="radio"
273
+ name={uuid}
274
+ text={
275
+ uuid == queueEntry.priority.uuid
276
+ ? t('currentValueFormatted', '{{value}} (Current)', {
277
+ value: display,
278
+ interpolation: { escapeValue: false },
279
+ })
280
+ : display
281
+ }
282
+ key={uuid}
283
+ value={uuid}
284
+ />
285
+ ))}
286
+ </ContentSwitcher>
287
+ )}
288
+ </section>
289
+
290
+ <section className={styles.section}>
291
+ <div className={styles.sectionTitle}>{t('priorityComment', 'Priority comment')}</div>
292
+ <TextArea
293
+ labelText=""
294
+ value={formState.prioritycomment}
295
+ onChange={(e) => setPriorityComment(e.target.value)}
296
+ placeholder={t('enterCommentHere', 'Enter comment here')}
297
+ />
298
+ </section>
299
+
300
+ <section className={styles.section}>
301
+ <div className={styles.sectionTitle}>{t('timeOfTransition', 'Time of transition')}</div>
302
+ <Checkbox
303
+ labelText={t('modifyDefaultValue', 'Modify default value')}
304
+ id={'modifyTransitionTime'}
305
+ checked={formState.modifyDefaultTransitionDateTime}
306
+ onChange={(_, { checked }) => {
307
+ setModifyDefaultTransitionDateTime(checked);
308
+ }}
309
+ />
310
+ <div className={styles.dateTimeFields}>
311
+ <DatePicker
312
+ datePickerType="single"
313
+ dateFormat={datePickerFormat}
314
+ value={formState.transitionDate}
315
+ maxDate={new Date().setHours(23, 59, 59, 59)}
316
+ onChange={([date]) => {
317
+ setTransitionDate(date);
318
+ }}>
319
+ <DatePickerInput
320
+ id="datePickerInput"
321
+ labelText={t('date', 'Date')}
322
+ placeholder={datePickerPlaceHolder}
323
+ disabled={!formState.modifyDefaultTransitionDateTime}
324
+ />
325
+ </DatePicker>
326
+
327
+ <TimePicker
328
+ labelText={t('time', 'Time')}
329
+ onChange={(event) => setTransitionTime(event.target.value)}
330
+ pattern={time12HourFormatRegexPattern}
331
+ value={formState.transitionTime}
332
+ invalid={isTimeInvalid}
333
+ invalidText={t('timeCannotBeInFuture', 'Time cannot be in the future')}
334
+ disabled={!formState.modifyDefaultTransitionDateTime}>
335
+ <TimePickerSelect
336
+ id="visitStartTimeSelect"
337
+ onChange={(event) => setTransitionTimeFormat(event.target.value as amPm)}
338
+ value={formState.transitionTimeFormat}
339
+ labelText={t('time', 'Time')}
340
+ aria-label={t('time', 'Time')}>
341
+ <SelectItem value="AM" text="AM" />
342
+ <SelectItem value="PM" text="PM" />
343
+ </TimePickerSelect>
344
+ </TimePicker>
345
+ </div>
346
+ </section>
347
+ </Stack>
348
+ </div>
349
+ </ModalBody>
350
+ <ModalFooter>
351
+ <Button kind="secondary" onClick={closeModal}>
352
+ {t('cancel', 'Cancel')}
353
+ </Button>
354
+ <Button disabled={isSubmitting || disableSubmit(queueEntry, formState)} onClick={submitForm}>
355
+ {submitButtonText}
356
+ </Button>
357
+ </ModalFooter>
358
+ </>
359
+ );
360
+ };
361
+
362
+ export default QueueEntryActionModal;
@@ -0,0 +1,152 @@
1
+ import { openmrsFetch, showSnackbar } from '@openmrs/esm-framework';
2
+ import { screen } from '@testing-library/react';
3
+ import { mockQueueEntryBrian, mockQueueSurgery, mockStatusInService, mockQueues } from '__mocks__';
4
+ import React from 'react';
5
+ import { renderWithSwr } from 'tools';
6
+ import TransitionQueueEntryModal from './transition-queue-entry-modal.component';
7
+ import userEvent from '@testing-library/user-event';
8
+ import EditQueueEntryModal from './edit-queue-entry-modal.component';
9
+
10
+ const mockedOpenmrsFetch = openmrsFetch as jest.Mock;
11
+
12
+ jest.mock('../../hooks/useQueues', () => {
13
+ return {
14
+ useQueues: jest.fn().mockReturnValue({
15
+ queues: mockQueues,
16
+ }),
17
+ };
18
+ });
19
+
20
+ describe('TransitionQueueEntryModal: ', () => {
21
+ const queueEntry = mockQueueEntryBrian;
22
+ const { queue } = queueEntry;
23
+ const { allowedStatuses, allowedPriorities } = queue;
24
+
25
+ const nextQueue = mockQueueSurgery;
26
+
27
+ it('renders the dialog with the right status and priority options', () => {
28
+ renderWithSwr(<TransitionQueueEntryModal queueEntry={queueEntry} closeModal={() => {}} />);
29
+ expect(screen.getByText(queueEntry.patient.display)).toBeInTheDocument();
30
+
31
+ for (const status of allowedStatuses) {
32
+ const expectedStatusDisplay =
33
+ queueEntry.status.uuid == status.uuid ? `${status.display} (Current)` : status.display;
34
+ expect(screen.getByRole('radio', { name: expectedStatusDisplay })).toBeInTheDocument();
35
+ }
36
+
37
+ for (const pri of allowedPriorities) {
38
+ const expectedPriorityDisplay = queueEntry.priority.uuid == pri.uuid ? `${pri.display} (Current)` : pri.display;
39
+ expect(screen.getByRole('radio', { name: expectedPriorityDisplay })).toBeInTheDocument();
40
+ }
41
+ });
42
+
43
+ it('has a cancel button that closes the modal', async () => {
44
+ const closeModal = jest.fn();
45
+ const user = userEvent.setup();
46
+
47
+ renderWithSwr(<TransitionQueueEntryModal queueEntry={queueEntry} closeModal={closeModal} />);
48
+ const cancelButton = screen.getByText('Cancel');
49
+ await user.click(cancelButton);
50
+ expect(closeModal).toHaveBeenCalled();
51
+ });
52
+
53
+ it('has a disabled submit button when selected queue and status is same as before', () => {
54
+ renderWithSwr(<TransitionQueueEntryModal queueEntry={queueEntry} closeModal={() => {}} />);
55
+ const submitButton = screen.getByRole('button', { name: /Transition patient/ });
56
+ expect(submitButton).toBeDisabled();
57
+ });
58
+
59
+ it('has an working submit button when selected queue and status is different from before', async () => {
60
+ mockedOpenmrsFetch.mockResolvedValue({
61
+ status: 200,
62
+ });
63
+ const user = userEvent.setup();
64
+ renderWithSwr(<TransitionQueueEntryModal queueEntry={queueEntry} closeModal={() => {}} />);
65
+
66
+ // change queue
67
+ const queueDropdown = screen.getByRole('combobox', { name: /Select a queue/ });
68
+ await queueDropdown.click();
69
+ const queueSelection = screen.getByRole('option', {
70
+ name: `${nextQueue.display} - ${nextQueue.location?.display}`,
71
+ });
72
+ await user.selectOptions(queueDropdown, queueSelection);
73
+
74
+ // change status
75
+ const inServiceRadioButton = screen.getByText(mockStatusInService.display);
76
+ await inServiceRadioButton.click();
77
+
78
+ const submitButton = screen.getByRole('button', { name: /Transition patient/ });
79
+ expect(submitButton).not.toBeDisabled();
80
+ await submitButton.click();
81
+
82
+ expect(mockedOpenmrsFetch).toHaveBeenCalled();
83
+ expect(showSnackbar).toHaveBeenCalled();
84
+ });
85
+ });
86
+
87
+ describe('EditQueueEntryModal: ', () => {
88
+ const queueEntry = mockQueueEntryBrian;
89
+ const { queue } = queueEntry;
90
+ const { allowedStatuses, allowedPriorities } = queue;
91
+
92
+ const nextQueue = mockQueueSurgery;
93
+
94
+ it('renders the dialog with the right status and priority options', () => {
95
+ renderWithSwr(<EditQueueEntryModal queueEntry={queueEntry} closeModal={() => {}} />);
96
+ expect(screen.getByText(queueEntry.patient.display)).toBeInTheDocument();
97
+
98
+ for (const status of allowedStatuses) {
99
+ const expectedStatusDisplay =
100
+ queueEntry.status.uuid == status.uuid ? `${status.display} (Current)` : status.display;
101
+ expect(screen.getByRole('radio', { name: expectedStatusDisplay })).toBeInTheDocument();
102
+ }
103
+
104
+ for (const pri of allowedPriorities) {
105
+ const expectedPriorityDisplay = queueEntry.priority.uuid == pri.uuid ? `${pri.display} (Current)` : pri.display;
106
+ expect(screen.getByRole('radio', { name: expectedPriorityDisplay })).toBeInTheDocument();
107
+ }
108
+ });
109
+
110
+ it('has a cancel button that closes the modal', async () => {
111
+ const closeModal = jest.fn();
112
+ const user = userEvent.setup();
113
+
114
+ renderWithSwr(<EditQueueEntryModal queueEntry={queueEntry} closeModal={closeModal} />);
115
+ const cancelButton = screen.getByText('Cancel');
116
+ await user.click(cancelButton);
117
+ expect(closeModal).toHaveBeenCalled();
118
+ });
119
+
120
+ it('has a enabled submit button when selected queue and status is same as before', () => {
121
+ renderWithSwr(<EditQueueEntryModal queueEntry={queueEntry} closeModal={() => {}} />);
122
+ const submitButton = screen.getByRole('button', { name: /Edit queue entry/ });
123
+ expect(submitButton).toBeEnabled();
124
+ });
125
+
126
+ it('has an working submit button when selected queue and status is different from before', async () => {
127
+ mockedOpenmrsFetch.mockResolvedValue({
128
+ status: 200,
129
+ });
130
+ const user = userEvent.setup();
131
+ renderWithSwr(<EditQueueEntryModal queueEntry={queueEntry} closeModal={() => {}} />);
132
+
133
+ // change queue
134
+ const queueDropdown = screen.getByRole('combobox', { name: /Select a queue/ });
135
+ await queueDropdown.click();
136
+ const queueSelection = screen.getByRole('option', {
137
+ name: `${nextQueue.display} - ${nextQueue.location?.display}`,
138
+ });
139
+ await user.selectOptions(queueDropdown, queueSelection);
140
+
141
+ // change status
142
+ const inServiceRadioButton = screen.getByText(mockStatusInService.display);
143
+ await inServiceRadioButton.click();
144
+
145
+ const submitButton = screen.getByRole('button', { name: /Edit queue entry/ });
146
+ expect(submitButton).not.toBeDisabled();
147
+ await submitButton.click();
148
+
149
+ expect(mockedOpenmrsFetch).toHaveBeenCalled();
150
+ expect(showSnackbar).toHaveBeenCalled();
151
+ });
152
+ });
@@ -0,0 +1,83 @@
1
+ import { openmrsFetch, restBaseUrl, type Location } from '@openmrs/esm-framework';
2
+ import { type Concept, type QueueEntry } from '../../types';
3
+
4
+ // see QueueEntryTransition.java in openmrs-module-queue
5
+ interface TransitionQueueEntryParams {
6
+ queueEntryToTransition: string;
7
+ transitionDate?: string;
8
+ newQueue?: string;
9
+ newStatus?: string;
10
+ newPriority?: string;
11
+ newPriorityComment?: string;
12
+ }
13
+
14
+ /**
15
+ * A transition is defined as an action that ends a current queue entry and immediately starts a new one
16
+ * with (slightly) different values. For now, this could be used to transition the queue entry's status,
17
+ * priority or queue. This allows us to keep a history of queue entries through a patient's visit.
18
+ * Note that there are some use cases (like RDE or data correction) where a transition is NOT appropriate.
19
+ * @param params
20
+ * @param abortController
21
+ * @returns
22
+ */
23
+ export function transitionQueueEntry(params: TransitionQueueEntryParams, abortController?: AbortController) {
24
+ return openmrsFetch(`${restBaseUrl}/queue-entry/transition`, {
25
+ method: 'POST',
26
+ headers: {
27
+ 'Content-Type': 'application/json',
28
+ },
29
+ signal: abortController?.signal,
30
+ body: params,
31
+ });
32
+ }
33
+
34
+ // see QueueEntryResource.java#getUpdatableProperties() in openmrs-module-queue
35
+ interface UpdateQueueEntryParams {
36
+ status?: Concept;
37
+ priority?: Concept;
38
+ priorityComment?: string;
39
+ sortWeight?: number;
40
+ startedAt?: string;
41
+ endedAt?: string;
42
+ loationWaitingFor?: Location;
43
+ providerWaitingFor?: Location;
44
+ }
45
+
46
+ export function updateQueueEntry(
47
+ queueEntryUuid: string,
48
+ params: UpdateQueueEntryParams,
49
+ abortController?: AbortController,
50
+ ) {
51
+ return openmrsFetch(`${restBaseUrl}/queue-entry/${queueEntryUuid}`, {
52
+ method: 'POST',
53
+ headers: {
54
+ 'Content-Type': 'application/json',
55
+ },
56
+ signal: abortController?.signal,
57
+ body: params,
58
+ });
59
+ }
60
+ interface UndoTransitionParams {
61
+ queueEntry: string;
62
+ }
63
+
64
+ export function undoTransition(params: UndoTransitionParams, abortController?: AbortController) {
65
+ return openmrsFetch(`${restBaseUrl}/queue-entry/transition`, {
66
+ method: 'DELETE',
67
+ headers: {
68
+ 'Content-Type': 'application/json',
69
+ },
70
+ signal: abortController?.signal,
71
+ body: params,
72
+ });
73
+ }
74
+
75
+ export function voidQueueEntry(queueEntryUuid: string, abortController?: AbortController) {
76
+ return openmrsFetch(`${restBaseUrl}/queue-entry/${queueEntryUuid}`, {
77
+ method: 'DELETE',
78
+ headers: {
79
+ 'Content-Type': 'application/json',
80
+ },
81
+ signal: abortController?.signal,
82
+ });
83
+ }
@@ -0,0 +1,36 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .radioButtonGroup {
6
+ display: flex;
7
+ flex-direction: column;
8
+ align-items: flex-start;
9
+ margin-top: spacing.$spacing-03;
10
+ min-height: spacing.$spacing-10;
11
+ width: 100%;
12
+ @include type.type-style('body-compact-01');
13
+ }
14
+
15
+ .radioButton {
16
+ padding: spacing.$spacing-02 spacing.$spacing-02;
17
+ margin: spacing.$spacing-03 0;
18
+ }
19
+
20
+ section {
21
+ margin: spacing.$spacing-03;
22
+ }
23
+
24
+ .sectionTitle {
25
+ @include type.type-style('heading-compact-02');
26
+ color: $text-02;
27
+ margin-bottom: spacing.$spacing-04;
28
+ }
29
+
30
+ .queueEntryActionModalBody {
31
+ padding-bottom: spacing.$spacing-05;
32
+ }
33
+
34
+ .dateTimeFields {
35
+ display: flex;
36
+ }