@kenyaemr/esm-service-queues-app 8.1.1-pre.124 → 8.1.2-pre.152

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 (256) hide show
  1. package/.turbo/turbo-build.log +26 -26
  2. package/dist/1006.js +1 -0
  3. package/dist/1006.js.map +1 -0
  4. package/dist/1060.js +1 -0
  5. package/dist/1060.js.map +1 -0
  6. package/dist/130.js +1 -1
  7. package/dist/130.js.map +1 -1
  8. package/dist/1325.js +1 -0
  9. package/dist/1325.js.map +1 -0
  10. package/dist/1644.js +1 -0
  11. package/dist/1727.js +1 -0
  12. package/dist/1727.js.map +1 -0
  13. package/dist/1800.js +1 -0
  14. package/dist/1800.js.map +1 -0
  15. package/dist/236.js +1 -1
  16. package/dist/2757.js +1 -0
  17. package/dist/{282.js → 2760.js} +1 -1
  18. package/dist/2760.js.map +1 -0
  19. package/dist/2784.js +2 -0
  20. package/dist/2784.js.map +1 -0
  21. package/dist/3199.js +1 -0
  22. package/dist/3199.js.map +1 -0
  23. package/dist/3372.js +2 -0
  24. package/dist/3372.js.map +1 -0
  25. package/dist/3574.js +1 -0
  26. package/dist/3604.js +1 -0
  27. package/dist/3604.js.map +1 -0
  28. package/dist/3652.js +1 -0
  29. package/dist/3760.js +1 -0
  30. package/dist/3760.js.map +1 -0
  31. package/dist/3818.js +1 -0
  32. package/dist/3818.js.map +1 -0
  33. package/dist/3828.js +1 -0
  34. package/dist/3828.js.map +1 -0
  35. package/dist/4272.js +1 -0
  36. package/dist/4378.js +1 -0
  37. package/dist/443.js +1 -0
  38. package/dist/443.js.map +1 -0
  39. package/dist/4460.js +1 -0
  40. package/dist/4705.js +1 -0
  41. package/dist/4911.js +1 -0
  42. package/dist/4911.js.map +1 -0
  43. package/dist/5236.js +1 -0
  44. package/dist/5236.js.map +1 -0
  45. package/dist/5240.js +1 -0
  46. package/dist/5282.js +1 -0
  47. package/dist/5282.js.map +1 -0
  48. package/dist/5336.js +1 -0
  49. package/dist/539.js +1 -0
  50. package/dist/5673.js +1 -0
  51. package/dist/5711.js +1 -0
  52. package/dist/5737.js +1 -0
  53. package/dist/5833.js +1 -0
  54. package/dist/6566.js +1 -0
  55. package/dist/6578.js +2 -0
  56. package/dist/{660.js.LICENSE.txt → 6578.js.LICENSE.txt} +5 -0
  57. package/dist/6578.js.map +1 -0
  58. package/dist/6591.js +2 -0
  59. package/dist/6591.js.map +1 -0
  60. package/dist/6670.js +1 -0
  61. package/dist/6670.js.map +1 -0
  62. package/dist/6727.js +1 -0
  63. package/dist/744.js +1 -0
  64. package/dist/752.js +1 -1
  65. package/dist/752.js.map +1 -1
  66. package/dist/7807.js +1 -0
  67. package/dist/8271.js +1 -0
  68. package/dist/8319.js +1 -0
  69. package/dist/8788.js +1 -0
  70. package/dist/899.js +1 -0
  71. package/dist/9261.js +1 -0
  72. package/dist/9392.js +1 -0
  73. package/dist/9392.js.map +1 -0
  74. package/dist/9993.js +1 -0
  75. package/dist/9993.js.map +1 -0
  76. package/dist/kenyaemr-esm-service-queues-app.js +1 -1
  77. package/dist/kenyaemr-esm-service-queues-app.js.buildmanifest.json +541 -234
  78. package/dist/kenyaemr-esm-service-queues-app.js.map +1 -1
  79. package/dist/main.js +1 -1
  80. package/dist/main.js.LICENSE.txt +5 -0
  81. package/dist/main.js.map +1 -1
  82. package/dist/routes.json +1 -1
  83. package/package-lock.json +5892 -0
  84. package/package.json +8 -5
  85. package/src/active-visits/active-visits-table.resource.ts +0 -62
  86. package/src/active-visits/change-status-dialog.component.tsx +10 -3
  87. package/src/active-visits/change-status-dialog.scss +29 -1
  88. package/src/active-visits/change-status-dialog.test.tsx +11 -3
  89. package/src/add-provider-queue-room/add-provider-queue-room.component.tsx +53 -54
  90. package/src/add-provider-queue-room/add-provider-queue-room.scss +28 -0
  91. package/src/add-provider-queue-room/add-provider-queue-room.test.tsx +1 -1
  92. package/src/clear-queue-entries-dialog/clear-queue-entries-dialog.component.tsx +1 -0
  93. package/src/clear-queue-entries-dialog/clear-queue-entries-dialog.scss +29 -0
  94. package/src/clear-queue-entries-dialog/clear-queue-entries-dialog.test.tsx +4 -1
  95. package/src/config-schema.ts +28 -8
  96. package/src/create-queue-entry/create-queue-entry.workspace.tsx +99 -0
  97. package/src/create-queue-entry/existing-visit-form/existing-visit-form.component.tsx +73 -0
  98. package/src/{patient-search/visit-form-queue-fields/visit-form-queue-fields.component.tsx → create-queue-entry/queue-fields/queue-fields.component.tsx} +76 -50
  99. package/src/create-queue-entry/queue-fields/queue-fields.resource.ts +63 -0
  100. package/src/{patient-search/visit-form-queue-fields/visit-form-queue-fields.scss → create-queue-entry/queue-fields/queue-fields.scss} +0 -4
  101. package/src/{patient-search/visit-form-queue-fields/visit-form-queue-fields.test.tsx → create-queue-entry/queue-fields/queue-fields.test.tsx} +37 -17
  102. package/src/create-queue-entry/queue-fields/visit-form-queue-fields.extension.tsx +31 -0
  103. package/src/hooks/useQueueEntries.ts +13 -12
  104. package/src/index.ts +8 -14
  105. package/src/patient-info/patient-info.component.tsx +10 -8
  106. package/src/patient-queue-header/patient-queue-header.component.tsx +18 -13
  107. package/src/patient-queue-metrics/metrics-header.component.tsx +4 -4
  108. package/src/queue-entry-table-components/queue-priority.component.tsx +16 -11
  109. package/src/queue-entry-table-components/queue-priority.scss +5 -0
  110. package/src/queue-entry-table-components/transition-entry.component.tsx +1 -1
  111. package/src/queue-patient-linelists/queue-linelist-base-table.component.tsx +1 -1
  112. package/src/queue-patient-linelists/queue-linelist-filter.workspace.tsx +13 -12
  113. package/src/queue-patient-linelists/scheduled-appointments-table.component.tsx +1 -1
  114. package/src/queue-rooms/queue-room-form.test.tsx +2 -2
  115. package/src/queue-rooms/queue-room-form.workspace.tsx +1 -1
  116. package/src/queue-screen/queue-screen.test.tsx +11 -4
  117. package/src/queue-services/queue-service-form.test.tsx +76 -16
  118. package/src/queue-services/queue-service-form.workspace.tsx +131 -131
  119. package/src/queue-services/queue-service.resource.ts +7 -2
  120. package/src/queue-table/cells/columns.resource.ts +10 -7
  121. package/src/queue-table/cells/queue-table-visit-attribute-queue-number-cell.component.tsx +1 -0
  122. package/src/queue-table/default-queue-table.component.tsx +13 -13
  123. package/src/queue-table/default-queue-table.test.tsx +3 -3
  124. package/src/queue-table/queue-entry-actions/queue-entry-actions-modal.scss +28 -0
  125. package/src/queue-table/queue-entry-actions/queue-entry-actions.modal.tsx +8 -2
  126. package/src/queue-table/queue-entry-actions/queue-entry-confirm-action.modal.tsx +2 -1
  127. package/src/queue-table/queue-entry-actions/queue-entry-confirm-action.scss +29 -0
  128. package/src/queue-table/queue-table.component.tsx +1 -1
  129. package/src/queue-table/queue-table.scss +12 -2
  130. package/src/queue-table/queue-table.test.tsx +10 -1
  131. package/src/remove-queue-entry-dialog/remove-queue-entry.component.tsx +16 -4
  132. package/src/remove-queue-entry-dialog/remove-queue-entry.scss +29 -0
  133. package/src/routes.json +4 -8
  134. package/src/transition-latest-queue-entry/transition-latest-queue-entry.resource.ts +2 -10
  135. package/src/transition-queue-entry/transition-queue-entry-dialog.component.tsx +41 -32
  136. package/src/transition-queue-entry/transition-queue-entry-dialog.scss +28 -0
  137. package/src/types/index.ts +0 -8
  138. package/src/views/queue-tables-for-all-statuses.component.tsx +12 -15
  139. package/translations/am.json +5 -78
  140. package/translations/ar.json +5 -78
  141. package/translations/de.json +235 -0
  142. package/translations/en.json +9 -86
  143. package/translations/es.json +3 -76
  144. package/translations/fr.json +5 -78
  145. package/translations/he.json +5 -78
  146. package/translations/hi.json +235 -0
  147. package/translations/hi_IN.json +235 -0
  148. package/translations/id.json +235 -0
  149. package/translations/it.json +235 -0
  150. package/translations/km.json +5 -78
  151. package/translations/ne.json +235 -0
  152. package/translations/pt.json +235 -0
  153. package/translations/pt_BR.json +235 -0
  154. package/translations/qu.json +235 -0
  155. package/translations/si.json +235 -0
  156. package/translations/sw.json +235 -0
  157. package/translations/sw_KE.json +235 -0
  158. package/translations/tr.json +235 -0
  159. package/translations/tr_TR.json +235 -0
  160. package/translations/uk.json +235 -0
  161. package/translations/vi.json +235 -0
  162. package/translations/zh.json +6 -79
  163. package/translations/zh_CN.json +5 -78
  164. package/dist/169.js +0 -1
  165. package/dist/169.js.map +0 -1
  166. package/dist/199.js +0 -1
  167. package/dist/199.js.map +0 -1
  168. package/dist/236.js.map +0 -1
  169. package/dist/271.js +0 -1
  170. package/dist/282.js.map +0 -1
  171. package/dist/319.js +0 -1
  172. package/dist/325.js +0 -1
  173. package/dist/325.js.map +0 -1
  174. package/dist/366.js +0 -1
  175. package/dist/366.js.map +0 -1
  176. package/dist/372.js +0 -2
  177. package/dist/372.js.map +0 -1
  178. package/dist/392.js +0 -1
  179. package/dist/392.js.map +0 -1
  180. package/dist/460.js +0 -1
  181. package/dist/501.js +0 -1
  182. package/dist/501.js.map +0 -1
  183. package/dist/574.js +0 -1
  184. package/dist/591.js +0 -2
  185. package/dist/591.js.map +0 -1
  186. package/dist/6.js +0 -1
  187. package/dist/6.js.map +0 -1
  188. package/dist/60.js +0 -1
  189. package/dist/60.js.map +0 -1
  190. package/dist/604.js +0 -1
  191. package/dist/604.js.map +0 -1
  192. package/dist/644.js +0 -1
  193. package/dist/660.js +0 -2
  194. package/dist/660.js.map +0 -1
  195. package/dist/670.js +0 -1
  196. package/dist/670.js.map +0 -1
  197. package/dist/727.js +0 -1
  198. package/dist/727.js.map +0 -1
  199. package/dist/748.js +0 -1
  200. package/dist/748.js.map +0 -1
  201. package/dist/757.js +0 -1
  202. package/dist/760.js +0 -1
  203. package/dist/760.js.map +0 -1
  204. package/dist/784.js +0 -2
  205. package/dist/784.js.map +0 -1
  206. package/dist/788.js +0 -1
  207. package/dist/800.js +0 -1
  208. package/dist/800.js.map +0 -1
  209. package/dist/807.js +0 -1
  210. package/dist/818.js +0 -1
  211. package/dist/818.js.map +0 -1
  212. package/dist/828.js +0 -1
  213. package/dist/828.js.map +0 -1
  214. package/dist/833.js +0 -1
  215. package/dist/911.js +0 -1
  216. package/dist/911.js.map +0 -1
  217. package/dist/940.js +0 -1
  218. package/dist/940.js.map +0 -1
  219. package/src/add-patient-toqueue/add-patient-toqueue-dialog.component.tsx +0 -228
  220. package/src/add-patient-toqueue/add-patient-toqueue-dialog.scss +0 -32
  221. package/src/patient-search/advanced-search.component.tsx +0 -191
  222. package/src/patient-search/advanced-search.scss +0 -154
  223. package/src/patient-search/advanced-search.test.tsx +0 -26
  224. package/src/patient-search/basic-search.component.tsx +0 -112
  225. package/src/patient-search/basic-search.scss +0 -139
  226. package/src/patient-search/basic-search.test.tsx +0 -18
  227. package/src/patient-search/empty-data-illustration.component.tsx +0 -41
  228. package/src/patient-search/hooks/useActivePatientEnrollment.tsx +0 -29
  229. package/src/patient-search/hooks/useDefaultLocation.ts +0 -14
  230. package/src/patient-search/hooks/usePatients.tsx +0 -25
  231. package/src/patient-search/hooks/useRecommendedVisitTypes.tsx +0 -35
  232. package/src/patient-search/hooks/useScheduledVisits.ts +0 -52
  233. package/src/patient-search/patient-scheduled-visits.component.tsx +0 -315
  234. package/src/patient-search/patient-scheduled-visits.scss +0 -131
  235. package/src/patient-search/patient-scheduled-visits.test.tsx +0 -39
  236. package/src/patient-search/patient-search.workspace.tsx +0 -135
  237. package/src/patient-search/search-illustration.component.tsx +0 -27
  238. package/src/patient-search/search-results.component.tsx +0 -75
  239. package/src/patient-search/search-results.scss +0 -80
  240. package/src/patient-search/search-results.test.tsx +0 -69
  241. package/src/patient-search/search.resource.ts +0 -10
  242. package/src/patient-search/visit-form/existing-visit-form.component.tsx +0 -112
  243. package/src/patient-search/visit-form/queue.resource.ts +0 -64
  244. package/src/patient-search/visit-form/visit-form.component.tsx +0 -337
  245. package/src/patient-search/visit-form/visit-type-selector.component.tsx +0 -153
  246. package/src/patient-search/visit-form/visit-type-selector.scss +0 -100
  247. package/src/patient-search/visit-form/visit-type-selector.test.tsx +0 -84
  248. package/src/visits-missing-inqueue/visits-missing-inqueue.component.tsx +0 -277
  249. package/src/visits-missing-inqueue/visits-missing-inqueue.resource.ts +0 -93
  250. package/src/visits-missing-inqueue/visits-missing-inqueue.scss +0 -111
  251. /package/dist/{784.js.LICENSE.txt → 2784.js.LICENSE.txt} +0 -0
  252. /package/dist/{372.js.LICENSE.txt → 3372.js.LICENSE.txt} +0 -0
  253. /package/dist/{591.js.LICENSE.txt → 6591.js.LICENSE.txt} +0 -0
  254. /package/src/{patient-search/patient-search.scss → create-queue-entry/create-queue-entry.scss} +0 -0
  255. /package/src/{patient-search/visit-form/visit-form.scss → create-queue-entry/existing-visit-form/existing-visit-form.scss} +0 -0
  256. /package/src/{patient-search → create-queue-entry}/hooks/useQueueLocations.tsx +0 -0
@@ -1,166 +1,162 @@
1
- import React, { useCallback, useState } from 'react';
1
+ import React from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
+ import type { TFunction } from 'react-i18next';
4
+ import { useForm, Controller } from 'react-hook-form';
5
+ import { z } from 'zod';
6
+ import { zodResolver } from '@hookform/resolvers/zod';
3
7
  import {
8
+ Button,
9
+ ButtonSet,
4
10
  Column,
5
11
  Form,
12
+ InlineLoading,
6
13
  Layer,
7
- Stack,
8
- TextInput,
9
14
  Select,
10
15
  SelectItem,
11
- ButtonSet,
12
- Button,
13
- InlineNotification,
16
+ Stack,
17
+ TextInput,
14
18
  } from '@carbon/react';
15
19
  import { mutate } from 'swr';
16
20
  import { type DefaultWorkspaceProps, restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
17
21
  import { saveQueue, useServiceConcepts } from './queue-service.resource';
18
- import { useQueueLocations } from '../patient-search/hooks/useQueueLocations';
22
+ import { useQueueLocations } from '../create-queue-entry/hooks/useQueueLocations';
19
23
  import styles from './queue-service-form.scss';
20
24
 
25
+ const createQueueServiceSchema = (t: TFunction) =>
26
+ z.object({
27
+ queueName: z
28
+ .string({
29
+ required_error: t('queueNameRequired', 'Queue name is required'),
30
+ })
31
+ .trim()
32
+ .min(1, t('queueNameRequired', 'Queue name is required')),
33
+ queueServiceType: z
34
+ .string({
35
+ required_error: t('queueConceptRequired', 'Queue concept is required'),
36
+ })
37
+ .trim()
38
+ .min(1, t('queueConceptRequired', 'Queue concept is required')),
39
+ userLocation: z
40
+ .string({
41
+ required_error: t('queueLocationRequired', 'Queue location is required'),
42
+ })
43
+ .trim()
44
+ .min(1, t('queueLocationRequired', 'Queue location is required')),
45
+ });
46
+
47
+ type QueueServiceFormData = z.infer<ReturnType<typeof createQueueServiceSchema>>;
48
+
21
49
  const QueueServiceForm: React.FC<DefaultWorkspaceProps> = ({ closeWorkspace }) => {
22
50
  const { t } = useTranslation();
23
51
  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
52
  const { queueLocations } = useQueueLocations();
31
53
 
32
- const createQueue = useCallback(
33
- (event) => {
34
- event.preventDefault();
54
+ const QueueServiceSchema = createQueueServiceSchema(t);
55
+
56
+ const {
57
+ control,
58
+ handleSubmit,
59
+ formState: { errors, isSubmitting },
60
+ } = useForm<QueueServiceFormData>({
61
+ resolver: zodResolver(QueueServiceSchema),
62
+ defaultValues: {
63
+ queueName: '',
64
+ queueServiceType: '',
65
+ userLocation: '',
66
+ },
67
+ });
35
68
 
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
- }
69
+ const createQueue = async (data: QueueServiceFormData) => {
70
+ try {
71
+ await saveQueue(data.queueName, data.queueServiceType, '', data.userLocation);
48
72
 
49
- setMissingName(false);
50
- setMissingQueue(false);
51
- setMissingLocation(false);
73
+ showSnackbar({
74
+ title: t('queueServiceCreated', 'Queue service created'),
75
+ kind: 'success',
76
+ subtitle: t('queueServiceCreatedSuccessfully', 'Queue service created successfully'),
77
+ });
52
78
 
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
- );
79
+ closeWorkspace();
80
+ await Promise.all([
81
+ mutate(`${restBaseUrl}/queue?${data.userLocation}`),
82
+ mutate(`${restBaseUrl}/queue?location=${data.userLocation}`),
83
+ ]);
84
+ } catch (error) {
85
+ showSnackbar({
86
+ title: t('errorCreatingQueueService', 'Error creating queue service'),
87
+ kind: 'error',
88
+ isLowContrast: false,
89
+ subtitle: error?.responseBody?.message || error?.message,
90
+ });
91
+ }
92
+ };
78
93
 
79
94
  return (
80
- <Form onSubmit={createQueue} className={styles.form}>
81
- <Stack gap={4} className={styles.grid}>
95
+ <Form onSubmit={handleSubmit(createQueue)} className={styles.form}>
96
+ <Stack gap={5} className={styles.grid}>
82
97
  <Column>
83
98
  <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
+ <Controller
100
+ name="queueName"
101
+ control={control}
102
+ render={({ field }) => (
103
+ <TextInput
104
+ {...field}
105
+ id="queueName"
106
+ invalidText={errors.queueName?.message}
107
+ invalid={!!errors.queueName}
108
+ labelText={t('queueName', 'Queue name')}
99
109
  />
100
- </section>
101
- )}
110
+ )}
111
+ />
102
112
  </Layer>
103
113
  </Column>
104
-
105
114
  <Column>
106
115
  <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
- )}
116
+ <Controller
117
+ name="queueServiceType"
118
+ control={control}
119
+ render={({ field }) => (
120
+ <Select
121
+ {...field}
122
+ labelText={t('selectServiceType', 'Select a service type')}
123
+ id="queueServiceType"
124
+ invalid={!!errors?.queueServiceType}
125
+ invalidText={errors?.queueServiceType?.message}>
126
+ <SelectItem text={t('selectServiceType', 'Select a service type')} value="" />
127
+ {queueConcepts?.length > 0 &&
128
+ queueConcepts.map((concept) => (
129
+ <SelectItem key={concept.uuid} text={concept.display} value={concept.uuid}>
130
+ {concept.display}
131
+ </SelectItem>
132
+ ))}
133
+ </Select>
134
+ )}
135
+ />
133
136
  </Layer>
134
137
  </Column>
135
-
136
138
  <Column>
137
139
  <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
- )}
140
+ <Controller
141
+ name="userLocation"
142
+ control={control}
143
+ render={({ field }) => (
144
+ <Select
145
+ {...field}
146
+ id="location"
147
+ invalid={!!errors?.userLocation}
148
+ invalidText={errors?.userLocation?.message}
149
+ labelText={t('selectALocation', 'Select a location')}>
150
+ <SelectItem text={t('selectALocation', 'Select a location')} value="" />
151
+ {queueLocations?.length > 0 &&
152
+ queueLocations.map((location) => (
153
+ <SelectItem key={location.id} text={location.name} value={location.id}>
154
+ {location.name}
155
+ </SelectItem>
156
+ ))}
157
+ </Select>
158
+ )}
159
+ />
164
160
  </Layer>
165
161
  </Column>
166
162
  </Stack>
@@ -168,8 +164,12 @@ const QueueServiceForm: React.FC<DefaultWorkspaceProps> = ({ closeWorkspace }) =
168
164
  <Button className={styles.button} kind="secondary" onClick={closeWorkspace}>
169
165
  {t('cancel', 'Cancel')}
170
166
  </Button>
171
- <Button className={styles.button} kind="primary" type="submit">
172
- {t('save', 'Save')}
167
+ <Button className={styles.button} disabled={isSubmitting} kind="primary" type="submit">
168
+ {isSubmitting ? (
169
+ <InlineLoading description={t('saving', 'Saving') + '...'} />
170
+ ) : (
171
+ <span>{t('save', 'Save')}</span>
172
+ )}
173
173
  </Button>
174
174
  </ButtonSet>
175
175
  </Form>
@@ -12,7 +12,12 @@ export function useServiceConcepts() {
12
12
  };
13
13
  }
14
14
 
15
- export function saveQueue(queueName: string, queueConcept: string, queueDescription: string, queueLocation: string) {
15
+ export function saveQueue(
16
+ queueName: string,
17
+ queueServiceType: string,
18
+ queueDescription?: string,
19
+ queueLocation?: string,
20
+ ) {
16
21
  const abortController = new AbortController();
17
22
 
18
23
  return openmrsFetch(`${restBaseUrl}/queue`, {
@@ -24,7 +29,7 @@ export function saveQueue(queueName: string, queueConcept: string, queueDescript
24
29
  body: {
25
30
  name: queueName,
26
31
  description: queueDescription,
27
- service: { uuid: queueConcept },
32
+ service: { uuid: queueServiceType },
28
33
  location: {
29
34
  uuid: queueLocation,
30
35
  },
@@ -1,5 +1,5 @@
1
- import { showToast, translateFrom, useConfig } from '@openmrs/esm-framework';
2
1
  import { useMemo } from 'react';
2
+ import { showToast, useConfig } from '@openmrs/esm-framework';
3
3
  import { useTranslation, type TFunction } from 'react-i18next';
4
4
  import {
5
5
  builtInColumns,
@@ -30,11 +30,14 @@ export function useColumns(queue: string, status: string): QueueTableColumn[] {
30
30
  const config = useConfig<ConfigObject>();
31
31
  const { queueTables, visitQueueNumberAttributeUuid } = config;
32
32
  const { columnDefinitions } = queueTables;
33
- const tableDefinitions = [...queueTables.tableDefinitions, defaultQueueTable];
34
- const globalColumnConfig = {
35
- ...defaultColumnConfig,
36
- visitQueueNumberAttributeUuid,
37
- };
33
+ const tableDefinitions = useMemo(() => [...queueTables.tableDefinitions, defaultQueueTable], [queueTables]);
34
+ const globalColumnConfig = useMemo(
35
+ () => ({
36
+ ...defaultColumnConfig,
37
+ visitQueueNumberAttributeUuid,
38
+ }),
39
+ [visitQueueNumberAttributeUuid],
40
+ );
38
41
 
39
42
  const columnsMap = useMemo(() => {
40
43
  const map = new Map<string, QueueTableColumn>();
@@ -49,7 +52,7 @@ export function useColumns(queue: string, status: string): QueueTableColumn[] {
49
52
  map.set(columnDef.id, getColumnFromDefinition(t, columnDef));
50
53
  }
51
54
  return map;
52
- }, [columnDefinitions, t]);
55
+ }, [columnDefinitions, globalColumnConfig, t, visitQueueNumberAttributeUuid]);
53
56
 
54
57
  const tableDefinition = useMemo(
55
58
  () =>
@@ -15,6 +15,7 @@ export const queueTableVisitAttributeQueueNumberColumn: QueueTableColumnFunction
15
15
 
16
16
  const QueueTableVisitAttributeQueueNumberCell = ({ queueEntry }: QueueTableCellComponentProps) => {
17
17
  const { t } = useTranslation();
18
+
18
19
  return (
19
20
  <>
20
21
  {visitQueueNumberAttributeUuid ? (
@@ -27,13 +27,15 @@ import QueueTableExpandedRow from './queue-table-expanded-row.component';
27
27
  import QueueTable from './queue-table.component';
28
28
  import styles from './queue-table.scss';
29
29
 
30
- const serviceQueuesPatientSearchWorkspace = 'service-queues-patient-search';
30
+ const serviceQueuesPatientSearchWorkspace = 'create-queue-entry-workspace';
31
31
 
32
32
  /*
33
33
  Component with default values / sub-components passed into the more generic QueueTable.
34
34
  This is used in the main dashboard of the queues app. (Currently behind a feature flag)
35
35
  */
36
36
  function DefaultQueueTable() {
37
+ const { t } = useTranslation();
38
+ const layout = useLayoutType();
37
39
  const selectedService = useSelectedService();
38
40
  const currentLocationUuid = useSelectedQueueLocationUuid();
39
41
  const selectedQueueStatus = useSelectedQueueStatus();
@@ -48,8 +50,6 @@ function DefaultQueueTable() {
48
50
  );
49
51
  const { queueEntries, isLoading, error, isValidating } = useQueueEntries(searchCriteria);
50
52
 
51
- const { t } = useTranslation();
52
-
53
53
  useEffect(() => {
54
54
  if (error?.message) {
55
55
  showSnackbar({
@@ -58,11 +58,10 @@ function DefaultQueueTable() {
58
58
  subtitle: error?.message,
59
59
  });
60
60
  }
61
- }, [error?.message]);
62
- const layout = useLayoutType();
61
+ }, [error?.message, t]);
63
62
 
64
63
  const [isPatientSearchOpen, setIsPatientSearchOpen] = useState(false);
65
- const [patientSearchQuery, setPatientSearchQuery] = useState<string>('');
64
+ const [patientSearchQuery, setPatientSearchQuery] = useState('');
66
65
 
67
66
  const handleBackToSearchList = useCallback(() => {
68
67
  setIsPatientSearchOpen(true);
@@ -88,7 +87,7 @@ function DefaultQueueTable() {
88
87
  return columnSearchTerm?.includes(searchTermLowercase);
89
88
  });
90
89
  });
91
- }, [queueEntries, searchTerm]);
90
+ }, [columns, queueEntries, searchTerm]);
92
91
 
93
92
  return (
94
93
  <div className={styles.defaultQueueTable}>
@@ -164,6 +163,7 @@ function QueueDropdownFilter() {
164
163
 
165
164
  const handleServiceChange = useCallback(({ selectedItem }) => {
166
165
  updateSelectedService(selectedItem.uuid, selectedItem?.display);
166
+ mutateQueueEntries.mutateQueueEntries();
167
167
  }, []);
168
168
 
169
169
  return (
@@ -171,13 +171,13 @@ function QueueDropdownFilter() {
171
171
  <div className={styles.filterContainer}>
172
172
  <Dropdown
173
173
  id="serviceFilter"
174
- titleText={t('filterByService', 'Filter by service:')}
175
- label={selectedService?.serviceDisplay ?? t('all', 'All')}
176
- type="inline"
177
174
  items={[{ display: `${t('all', 'All')}` }, ...(services ?? [])]}
178
175
  itemToString={(item) => (item ? item.display : '')}
176
+ label={selectedService?.serviceDisplay ?? t('all', 'All')}
179
177
  onChange={handleServiceChange}
180
178
  size={isDesktop(layout) ? 'sm' : 'lg'}
179
+ titleText={t('filterByService', 'Filter by service:')}
180
+ type="inline"
181
181
  />
182
182
  </div>
183
183
  </>
@@ -198,13 +198,13 @@ function StatusDropdownFilter() {
198
198
  <div className={styles.filterContainer}>
199
199
  <Dropdown
200
200
  id="statusFilter"
201
- titleText={t('filterByStatus', 'Filter by status:')}
202
- label={queueStatus?.statusDisplay ?? t('all', 'All')}
203
- type="inline"
204
201
  items={[{ display: `${t('all', 'All')}` }, ...(statuses ?? [])]}
205
202
  itemToString={(item) => (item ? item.display : '')}
203
+ label={queueStatus?.statusDisplay ?? t('all', 'All')}
206
204
  onChange={handleServiceChange}
207
205
  size={isDesktop(layout) ? 'sm' : 'lg'}
206
+ titleText={t('filterByStatus', 'Filter by status:')}
207
+ type="inline"
208
208
  />
209
209
  </div>
210
210
  </>
@@ -13,7 +13,7 @@ import {
13
13
  import { renderWithSwr } from 'tools';
14
14
  import { useQueueEntries } from '../hooks/useQueueEntries';
15
15
  import { useQueueRooms } from '../add-provider-queue-room/add-provider-queue-room.resource';
16
- import { useQueueLocations } from '../patient-search/hooks/useQueueLocations';
16
+ import { useQueueLocations } from '../create-queue-entry/hooks/useQueueLocations';
17
17
  import { type ConfigObject, configSchema } from '../config-schema';
18
18
  import DefaultQueueTable from '../queue-table/default-queue-table.component';
19
19
 
@@ -29,8 +29,8 @@ jest.mock('../hooks/useQueues', () => {
29
29
  };
30
30
  });
31
31
 
32
- jest.mock('../patient-search/hooks/useQueueLocations', () => ({
33
- ...jest.requireActual('../patient-search/hooks/useQueueLocations'),
32
+ jest.mock('../create-queue-entry/hooks/useQueueLocations', () => ({
33
+ ...jest.requireActual('../create-queue-entry/hooks/useQueueLocations'),
34
34
  useQueueLocations: jest.fn(),
35
35
  }));
36
36
 
@@ -34,3 +34,31 @@ section {
34
34
  .dateTimeFields {
35
35
  display: flex;
36
36
  }
37
+
38
+ .modalHeader {
39
+ :global {
40
+ .cds--modal-close-button {
41
+ position: absolute;
42
+ inset-block-start: 0;
43
+ inset-inline-end: 0;
44
+ margin: 0;
45
+ margin-top: calc(-1 * #{layout.$spacing-05});
46
+ }
47
+
48
+ .cds--modal-close {
49
+ background-color: rgba(0, 0, 0, 0);
50
+
51
+ &:hover {
52
+ background-color: var(--cds-layer-hover);
53
+ }
54
+ }
55
+
56
+ .cds--popover--left > .cds--popover > .cds--popover-content {
57
+ transform: translate(-4rem, 0.85rem);
58
+ }
59
+
60
+ .cds--popover--left > .cds--popover > .cds--popover-caret {
61
+ transform: translate(-3.75rem, 1.25rem);
62
+ }
63
+ }
64
+ }
@@ -198,13 +198,19 @@ export const QueueEntryActionModal: React.FC<QueueEntryActionModalProps> = ({
198
198
  );
199
199
  }
200
200
  return null;
201
- }, [formState.transitionDate, formState.transitionTime, formState.transitionTimeFormat, t]);
201
+ }, [
202
+ formState.transitionDate,
203
+ formState.transitionTime,
204
+ formState.transitionTimeFormat,
205
+ queueEntry.previousQueueEntry?.startedAt,
206
+ t,
207
+ ]);
202
208
 
203
209
  const selectedPriorityIndex = priorities?.findIndex((p) => p.uuid == formState.selectedPriority);
204
210
 
205
211
  return (
206
212
  <>
207
- <ModalHeader closeModal={closeModal} title={modalTitle} />
213
+ <ModalHeader className={styles.modalHeader} closeModal={closeModal} title={modalTitle} />
208
214
  <ModalBody>
209
215
  <div className={styles.queueEntryActionModalBody}>
210
216
  <Stack gap={4}>
@@ -4,6 +4,7 @@ import { type QueueEntry } from '../../types';
4
4
  import { Button, ModalHeader, ModalBody, ModalFooter, Stack } from '@carbon/react';
5
5
  import { type FetchResponse, showSnackbar } from '@openmrs/esm-framework';
6
6
  import { useMutateQueueEntries } from '../../hooks/useQueueEntries';
7
+ import styles from './queue-entry-confirm-action.scss';
7
8
 
8
9
  interface QueueEntryUndoActionsModalProps {
9
10
  queueEntry: QueueEntry;
@@ -75,7 +76,7 @@ export const QueueEntryConfirmActionModal: React.FC<QueueEntryUndoActionsModalPr
75
76
 
76
77
  return (
77
78
  <>
78
- <ModalHeader closeModal={closeModal} title={modalTitle} />
79
+ <ModalHeader className={styles.modalHeader} closeModal={closeModal} title={modalTitle} />
79
80
  <ModalBody>
80
81
  <Stack gap={4}>
81
82
  <h5>{queueEntry.display}</h5>
@@ -0,0 +1,29 @@
1
+ @use '@carbon/layout';
2
+
3
+ .modalHeader {
4
+ :global {
5
+ .cds--modal-close-button {
6
+ position: absolute;
7
+ inset-block-start: 0;
8
+ inset-inline-end: 0;
9
+ margin: 0;
10
+ margin-top: calc(-1 * #{layout.$spacing-05});
11
+ }
12
+
13
+ .cds--modal-close {
14
+ background-color: rgba(0, 0, 0, 0);
15
+
16
+ &:hover {
17
+ background-color: var(--cds-layer-hover);
18
+ }
19
+ }
20
+
21
+ .cds--popover--left > .cds--popover > .cds--popover-content {
22
+ transform: translate(-4rem, 0.85rem);
23
+ }
24
+
25
+ .cds--popover--left > .cds--popover > .cds--popover-caret {
26
+ transform: translate(-3.75rem, 1.25rem);
27
+ }
28
+ }
29
+ }
@@ -74,7 +74,7 @@ function QueueTable({
74
74
 
75
75
  useEffect(() => {
76
76
  goTo(1);
77
- }, [queueEntries]);
77
+ }, [goTo, queueEntries]);
78
78
 
79
79
  const rowsData =
80
80
  paginatedQueueEntries?.map((queueEntry) => {
@@ -192,13 +192,12 @@ html[dir='rtl'] {
192
192
  }
193
193
 
194
194
  .tileContainer {
195
- background-color: $ui-02;
196
195
  border-top: 1px solid $ui-03;
197
196
  padding: layout.$spacing-07 0;
198
- // margin: 0 layout.$spacing-05;
199
197
  }
200
198
 
201
199
  .tile {
200
+ background-color: $ui-01;
202
201
  margin: auto;
203
202
  width: fit-content;
204
203
  }
@@ -209,3 +208,14 @@ html[dir='rtl'] {
209
208
  justify-content: center;
210
209
  align-items: center;
211
210
  }
211
+
212
+ .content {
213
+ @include type.type-style('heading-compact-02');
214
+ color: $text-02;
215
+ margin-bottom: layout.$spacing-03;
216
+ }
217
+
218
+ .helper {
219
+ @include type.type-style('body-compact-01');
220
+ color: $text-02;
221
+ }
@@ -3,7 +3,7 @@ import React from 'react';
3
3
  import { getDefaultsFromConfigSchema, useConfig, useSession } from '@openmrs/esm-framework';
4
4
  import { screen, within } from '@testing-library/react';
5
5
  import { type ConfigObject, configSchema } from '../config-schema';
6
- import { mockPriorityNonUrgent, mockQueueEntries, mockSession } from '__mocks__';
6
+ import { mockPriorityNonUrgent, mockPriorityUrgent, mockQueueEntries, mockSession } from '__mocks__';
7
7
  import { renderWithSwr } from 'tools';
8
8
  import QueueTable from './queue-table.component';
9
9
 
@@ -176,6 +176,11 @@ describe('QueueTable', () => {
176
176
  color: 'blue',
177
177
  style: 'bold',
178
178
  },
179
+ {
180
+ conceptUuid: mockPriorityUrgent.uuid,
181
+ color: 'orange',
182
+ style: null,
183
+ },
179
184
  ],
180
185
  statusConfigs: [],
181
186
  visitQueueNumberAttributeUuid: 'queue-number-visit-attr-uuid',
@@ -200,6 +205,10 @@ describe('QueueTable', () => {
200
205
  const firstRow = rows[1];
201
206
  const cells = within(firstRow).getAllByRole('cell');
202
207
  expect(cells[1].childNodes[0]).toHaveClass('bold');
208
+
209
+ const secondRow = rows[2];
210
+ const secondCells = within(secondRow).getAllByRole('cell');
211
+ expect(secondCells[1].childNodes[0]).toHaveClass('orange');
203
212
  });
204
213
 
205
214
  it('uses the visitQueueNumberAttributeUuid defined at the top level', () => {