@kenyaemr/esm-service-queues-app 8.1.1-pre.129 → 8.1.2-pre.154

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 +2 -2
  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 +12 -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
@@ -0,0 +1,99 @@
1
+ import { Button, DataTableSkeleton } from '@carbon/react';
2
+ import {
3
+ ArrowLeftIcon,
4
+ ErrorState,
5
+ ExtensionSlot,
6
+ getPatientName,
7
+ PatientBannerContactDetails,
8
+ PatientBannerPatientInfo,
9
+ PatientBannerToggleContactDetailsButton,
10
+ PatientPhoto,
11
+ usePatient,
12
+ useVisit,
13
+ type DefaultWorkspaceProps,
14
+ } from '@openmrs/esm-framework';
15
+ import React, { useState } from 'react';
16
+ import { useTranslation } from 'react-i18next';
17
+ import styles from './create-queue-entry.scss';
18
+ import ExistingVisitFormComponent from './existing-visit-form/existing-visit-form.component';
19
+
20
+ interface PatientSearchProps extends DefaultWorkspaceProps {
21
+ selectedPatientUuid: string;
22
+ currentServiceQueueUuid?: string;
23
+ handleBackToSearchList?: () => void;
24
+ }
25
+
26
+ export const AddPatientToQueueContext = React.createContext({
27
+ currentServiceQueueUuid: '',
28
+ });
29
+
30
+ /**
31
+ *
32
+ * This is the component that appears when clicking on a search result in the "Add patient to queue" workspace,
33
+ */
34
+ const CreateQueueEntryWorkspace: React.FC<PatientSearchProps> = ({
35
+ closeWorkspace,
36
+ promptBeforeClosing,
37
+ selectedPatientUuid,
38
+ currentServiceQueueUuid,
39
+ handleBackToSearchList,
40
+ }) => {
41
+ const { t } = useTranslation();
42
+ const { patient } = usePatient(selectedPatientUuid);
43
+ const { activeVisit, isLoading, error } = useVisit(selectedPatientUuid);
44
+
45
+ const [showContactDetails, setContactDetails] = useState(false);
46
+
47
+ const patientName = patient && getPatientName(patient);
48
+
49
+ return patient ? (
50
+ <div className={styles.patientSearchContainer}>
51
+ <AddPatientToQueueContext.Provider value={{ currentServiceQueueUuid }}>
52
+ <div className={styles.patientBannerContainer}>
53
+ <div className={styles.patientBanner}>
54
+ <div className={styles.patientPhoto}>
55
+ <PatientPhoto patientUuid={patient.id} patientName={patientName} />
56
+ </div>
57
+ <PatientBannerPatientInfo patient={patient} />
58
+ <PatientBannerToggleContactDetailsButton
59
+ showContactDetails={showContactDetails}
60
+ toggleContactDetails={() => setContactDetails(!showContactDetails)}
61
+ />
62
+ </div>
63
+ {showContactDetails ? (
64
+ <PatientBannerContactDetails patientId={patient.id} deceased={patient.deceasedBoolean} />
65
+ ) : null}
66
+ </div>
67
+ <div className={styles.backButton}>
68
+ <Button
69
+ kind="ghost"
70
+ renderIcon={(props) => <ArrowLeftIcon size={24} {...props} />}
71
+ iconDescription={t('backToSearchResults', 'Back to search results')}
72
+ size="sm"
73
+ onClick={() => handleBackToSearchList?.()}>
74
+ <span>{t('backToSearchResults', 'Back to search results')}</span>
75
+ </Button>
76
+ </div>
77
+ {isLoading ? (
78
+ <DataTableSkeleton role="progressbar" />
79
+ ) : error ? (
80
+ <ErrorState headerTitle={t('errorFetchingVisit', 'Error fetching patient visit')} error={error} />
81
+ ) : activeVisit ? (
82
+ <ExistingVisitFormComponent visit={activeVisit} closeWorkspace={closeWorkspace} />
83
+ ) : (
84
+ <ExtensionSlot
85
+ name="start-visit-workspace-form-slot"
86
+ state={{
87
+ patientUuid: selectedPatientUuid,
88
+ closeWorkspace,
89
+ promptBeforeClosing,
90
+ openedFrom: 'service-queues-add-patient',
91
+ }}
92
+ />
93
+ )}
94
+ </AddPatientToQueueContext.Provider>
95
+ </div>
96
+ ) : null;
97
+ };
98
+
99
+ export default CreateQueueEntryWorkspace;
@@ -0,0 +1,73 @@
1
+ import { Button, ButtonSet, Form, Row } from '@carbon/react';
2
+ import { ExtensionSlot, useLayoutType, type Visit } from '@openmrs/esm-framework';
3
+ import classNames from 'classnames';
4
+ import React, { useCallback, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { useMutateQueueEntries } from '../../hooks/useQueueEntries';
7
+ import QueueFields from '../queue-fields/queue-fields.component';
8
+ import styles from './existing-visit-form.scss';
9
+
10
+ interface ExistingVisitFormProps {
11
+ closeWorkspace: () => void;
12
+ visit: Visit;
13
+ }
14
+
15
+ /**
16
+ * This is the form that appears when clicking on a search result in the "Add patient to queue" workspace,
17
+ * when the patient already has an active visit.
18
+ */
19
+ const ExistingVisitForm: React.FC<ExistingVisitFormProps> = ({ visit, closeWorkspace }) => {
20
+ const { t } = useTranslation();
21
+ const isTablet = useLayoutType() === 'tablet';
22
+ const [isSubmitting, setIsSubmitting] = useState(false);
23
+
24
+ const { mutateQueueEntries } = useMutateQueueEntries();
25
+ const [callback, setCallback] = useState<{
26
+ submitQueueEntry: (visit: Visit) => Promise<any>;
27
+ }>(null);
28
+
29
+ const handleSubmit = useCallback(
30
+ (event) => {
31
+ event.preventDefault();
32
+ setIsSubmitting(true);
33
+
34
+ callback
35
+ ?.submitQueueEntry?.(visit)
36
+ ?.then(() => {
37
+ closeWorkspace();
38
+ mutateQueueEntries();
39
+ })
40
+ ?.finally(() => {
41
+ setIsSubmitting(false);
42
+ });
43
+ },
44
+ [closeWorkspace, callback, visit, mutateQueueEntries],
45
+ );
46
+
47
+ return visit ? (
48
+ <>
49
+ {isTablet && (
50
+ <Row className={styles.headerGridRow}>
51
+ <ExtensionSlot
52
+ name="visit-form-header-slot"
53
+ className={styles.dataGridRow}
54
+ state={{ patientUuid: visit.patient.uuid }}
55
+ />
56
+ </Row>
57
+ )}
58
+ <Form className={classNames(styles.form, styles.container)} onSubmit={handleSubmit}>
59
+ <QueueFields setOnSubmit={(onSubmit) => setCallback({ submitQueueEntry: onSubmit })} />
60
+ <ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
61
+ <Button className={styles.button} kind="secondary" onClick={closeWorkspace}>
62
+ {t('discard', 'Discard')}
63
+ </Button>
64
+ <Button className={styles.button} disabled={isSubmitting} kind="primary" type="submit">
65
+ {t('addPatientToQueue', 'Add patient to queue')}
66
+ </Button>
67
+ </ButtonSet>
68
+ </Form>
69
+ </>
70
+ ) : null;
71
+ };
72
+
73
+ export default ExistingVisitForm;
@@ -1,6 +1,3 @@
1
- import React, { useContext, useEffect, useState } from 'react';
2
- import { useTranslation } from 'react-i18next';
3
- import classNames from 'classnames';
4
1
  import {
5
2
  InlineNotification,
6
3
  RadioButton,
@@ -9,67 +6,106 @@ import {
9
6
  Select,
10
7
  SelectItem,
11
8
  SelectSkeleton,
12
- TextInput,
13
9
  } from '@carbon/react';
14
- import { useConfig, ResponsiveWrapper, useSession } from '@openmrs/esm-framework';
10
+ import { ResponsiveWrapper, showSnackbar, useConfig, useSession, type Visit } from '@openmrs/esm-framework';
11
+ import React, { useCallback, useContext, useEffect, useState } from 'react';
12
+ import { useTranslation } from 'react-i18next';
13
+ import { type ConfigObject } from '../../config-schema';
14
+ import { useMutateQueueEntries } from '../../hooks/useQueueEntries';
15
15
  import { useQueues } from '../../hooks/useQueues';
16
+ import { AddPatientToQueueContext } from '../create-queue-entry.workspace';
16
17
  import { useQueueLocations } from '../hooks/useQueueLocations';
17
- import { type ConfigObject } from '../../config-schema';
18
- import { AddPatientToQueueContext } from '../patient-search.workspace';
19
- import styles from './visit-form-queue-fields.scss';
18
+ import { postQueueEntry } from './queue-fields.resource';
19
+ import styles from './queue-fields.scss';
20
20
 
21
- export interface VisitFormQueueFieldsProps {
22
- setFormFields: (fields: {
23
- queueLocation: string;
24
- service: string;
25
- status: string;
26
- priority: string;
27
- sortWeight: number;
28
- }) => void;
21
+ export interface QueueFieldsProps {
22
+ setOnSubmit(onSubmit: (visit: Visit) => Promise<any>);
29
23
  }
30
24
 
31
- const VisitFormQueueFields: React.FC<VisitFormQueueFieldsProps> = (props) => {
32
- const { setFormFields } = props;
25
+ /**
26
+ * This component contains form fields for starting a patient's queue entry.
27
+ */
28
+ const QueueFields: React.FC<QueueFieldsProps> = ({ setOnSubmit }) => {
33
29
  const { t } = useTranslation();
34
30
  const { queueLocations, isLoading: isLoadingQueueLocations } = useQueueLocations();
35
31
  const { sessionLocation } = useSession();
36
- const config = useConfig<ConfigObject>();
32
+ const {
33
+ visitQueueNumberAttributeUuid,
34
+ concepts: { defaultStatusConceptUuid, defaultPriorityConceptUuid, emergencyPriorityConceptUuid },
35
+ } = useConfig<ConfigObject>();
37
36
  const [selectedQueueLocation, setSelectedQueueLocation] = useState(queueLocations[0]?.id);
38
37
  const { queues, isLoading: isLoadingQueues } = useQueues(selectedQueueLocation);
39
- const defaultStatus = config.concepts.defaultStatusConceptUuid;
40
38
  const [selectedService, setSelectedService] = useState('');
41
39
  const { currentServiceQueueUuid } = useContext(AddPatientToQueueContext);
42
- const [priority, setPriority] = useState(config.concepts.defaultPriorityConceptUuid);
40
+ const [priority, setPriority] = useState(defaultPriorityConceptUuid);
43
41
  const priorities = queues.find((q) => q.uuid === selectedService)?.allowedPriorities ?? [];
44
- const [sortWeight, setSortWeight] = useState(0);
42
+ const { mutateQueueEntries } = useMutateQueueEntries();
43
+ const memoMutateQueueEntries = useCallback(mutateQueueEntries, [mutateQueueEntries]);
44
+
45
+ const sortWeight = priority === emergencyPriorityConceptUuid ? 1 : 0;
46
+
47
+ const onSubmit = useCallback(
48
+ (visit: Visit) => {
49
+ if (selectedQueueLocation && selectedService && priority) {
50
+ return postQueueEntry(
51
+ visit.uuid,
52
+ selectedService,
53
+ visit.patient.uuid,
54
+ priority,
55
+ defaultStatusConceptUuid,
56
+ sortWeight,
57
+ selectedQueueLocation,
58
+ visitQueueNumberAttributeUuid,
59
+ )
60
+ .then(() => {
61
+ showSnackbar({
62
+ kind: 'success',
63
+ isLowContrast: true,
64
+ title: t('addedPatientToQueue', 'Added patient to queue'),
65
+ subtitle: t('queueEntryAddedSuccessfully', 'Queue entry added successfully'),
66
+ });
67
+ memoMutateQueueEntries();
68
+ })
69
+ .catch((error) => {
70
+ showSnackbar({
71
+ title: t('queueEntryError', 'Error adding patient to the queue'),
72
+ kind: 'error',
73
+ isLowContrast: false,
74
+ subtitle: error?.message,
75
+ });
76
+ throw error;
77
+ });
78
+ } else {
79
+ return Promise.resolve();
80
+ }
81
+ },
82
+ [
83
+ selectedQueueLocation,
84
+ selectedService,
85
+ priority,
86
+ sortWeight,
87
+ defaultStatusConceptUuid,
88
+ visitQueueNumberAttributeUuid,
89
+ memoMutateQueueEntries,
90
+ t,
91
+ ],
92
+ );
45
93
 
46
94
  useEffect(() => {
47
- if (priority === config.concepts.emergencyPriorityConceptUuid) {
48
- setSortWeight(1);
49
- }
50
- }, [priority]);
95
+ setOnSubmit?.(onSubmit);
96
+ }, [onSubmit, setOnSubmit]);
51
97
 
52
98
  useEffect(() => {
53
99
  if (currentServiceQueueUuid) {
54
100
  setSelectedService(currentServiceQueueUuid);
55
101
  }
56
- }, [queues]);
102
+ }, [currentServiceQueueUuid, queues]);
57
103
 
58
104
  useEffect(() => {
59
105
  if (queueLocations.map((l) => l.id).includes(sessionLocation.uuid)) {
60
106
  setSelectedQueueLocation(sessionLocation.uuid);
61
107
  }
62
- }, [queueLocations]);
63
-
64
- useEffect(() => {
65
- setFormFields({
66
- queueLocation: selectedQueueLocation,
67
- service: selectedService,
68
- status: defaultStatus,
69
- priority,
70
- sortWeight,
71
- });
72
- }, [selectedQueueLocation, selectedService, defaultStatus, priority, sortWeight]);
108
+ }, [queueLocations, sessionLocation.uuid]);
73
109
 
74
110
  return (
75
111
  <div>
@@ -159,7 +195,7 @@ const VisitFormQueueFields: React.FC<VisitFormQueueFieldsProps> = (props) => {
159
195
  className={styles.radioButtonWrapper}
160
196
  name="priority"
161
197
  id="priority"
162
- defaultSelected={config.concepts.defaultPriorityConceptUuid}
198
+ defaultSelected={defaultPriorityConceptUuid}
163
199
  onChange={(uuid) => setPriority(uuid)}>
164
200
  {priorities.map(({ uuid, display }) => (
165
201
  <RadioButton key={uuid} labelText={display} value={uuid} />
@@ -168,18 +204,8 @@ const VisitFormQueueFields: React.FC<VisitFormQueueFieldsProps> = (props) => {
168
204
  ) : null}
169
205
  </section>
170
206
  ) : null}
171
-
172
- <section className={classNames(styles.section, styles.sectionHidden)}>
173
- <TextInput
174
- type="number"
175
- id="sortWeight"
176
- name="sortWeight"
177
- labelText={t('sortWeight', 'Sort weight')}
178
- value={sortWeight}
179
- />
180
- </section>
181
207
  </div>
182
208
  );
183
209
  };
184
210
 
185
- export default VisitFormQueueFields;
211
+ export default QueueFields;
@@ -0,0 +1,63 @@
1
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+
3
+ export async function generateVisitQueueNumber(
4
+ location: string,
5
+ visitUuid: string,
6
+ queueUuid: string,
7
+ visitQueueNumberAttributeUuid: string,
8
+ ) {
9
+ const abortController = new AbortController();
10
+
11
+ await openmrsFetch(
12
+ `${restBaseUrl}/queue-entry-number?location=${location}&queue=${queueUuid}&visit=${visitUuid}&visitAttributeType=${visitQueueNumberAttributeUuid}`,
13
+ {
14
+ method: 'GET',
15
+ headers: {
16
+ 'Content-Type': 'application/json',
17
+ },
18
+ signal: abortController.signal,
19
+ },
20
+ );
21
+ }
22
+
23
+ export async function postQueueEntry(
24
+ visitUuid: string,
25
+ queueUuid: string,
26
+ patientUuid: string,
27
+ priority: string,
28
+ status: string,
29
+ sortWeight: number,
30
+ locationUuid: string,
31
+ visitQueueNumberAttributeUuid: string,
32
+ ) {
33
+ const abortController = new AbortController();
34
+
35
+ await Promise.all([generateVisitQueueNumber(locationUuid, visitUuid, queueUuid, visitQueueNumberAttributeUuid)]);
36
+
37
+ return openmrsFetch(`${restBaseUrl}/visit-queue-entry`, {
38
+ method: 'POST',
39
+ headers: {
40
+ 'Content-Type': 'application/json',
41
+ },
42
+ signal: abortController.signal,
43
+ body: {
44
+ visit: { uuid: visitUuid },
45
+ queueEntry: {
46
+ status: {
47
+ uuid: status,
48
+ },
49
+ priority: {
50
+ uuid: priority,
51
+ },
52
+ queue: {
53
+ uuid: queueUuid,
54
+ },
55
+ patient: {
56
+ uuid: patientUuid,
57
+ },
58
+ startedAt: new Date(),
59
+ sortWeight: sortWeight,
60
+ },
61
+ },
62
+ });
63
+ }
@@ -11,7 +11,3 @@
11
11
  .section {
12
12
  margin: layout.$spacing-07 layout.$spacing-05 0;
13
13
  }
14
-
15
- .sectionHidden {
16
- display: none;
17
- }
@@ -2,10 +2,18 @@
2
2
  import React from 'react';
3
3
  import userEvent from '@testing-library/user-event';
4
4
  import { render, screen } from '@testing-library/react';
5
- import { getDefaultsFromConfigSchema, useConfig, useLayoutType, useSession } from '@openmrs/esm-framework';
5
+ import {
6
+ type FetchResponse,
7
+ getDefaultsFromConfigSchema,
8
+ useConfig,
9
+ useLayoutType,
10
+ useSession,
11
+ type Visit,
12
+ } from '@openmrs/esm-framework';
6
13
  import { configSchema, type ConfigObject } from '../../config-schema';
7
- import { mockSession } from '__mocks__';
8
- import VisitFormQueueFields from './visit-form-queue-fields.component';
14
+ import { mockSession, mockVisitAlice } from '__mocks__';
15
+ import QueueFields from './queue-fields.component';
16
+ import { postQueueEntry } from './queue-fields.resource';
9
17
 
10
18
  const mockUseConfig = jest.mocked(useConfig<ConfigObject>);
11
19
  const mockUseLayoutType = jest.mocked(useLayoutType);
@@ -32,7 +40,14 @@ jest.mock('../../hooks/useQueues', () => {
32
40
  };
33
41
  });
34
42
 
35
- describe('VisitFormQueueFields', () => {
43
+ jest.mock('./queue-fields.resource', () => {
44
+ return {
45
+ postQueueEntry: jest.fn(),
46
+ };
47
+ });
48
+ const mockPostQueueEntry = jest.mocked(postQueueEntry).mockResolvedValue({} as FetchResponse);
49
+
50
+ describe('QueueFields', () => {
36
51
  beforeEach(() => {
37
52
  mockUseLayoutType.mockReturnValue('small-desktop');
38
53
  mockUseSession.mockReturnValue(mockSession.data);
@@ -43,27 +58,32 @@ describe('VisitFormQueueFields', () => {
43
58
 
44
59
  it('renders the form fields and returns the set values', async () => {
45
60
  const user = userEvent.setup();
46
- const setFormFields = jest.fn();
47
- render(<VisitFormQueueFields setFormFields={setFormFields} />);
61
+ let onSubmit: (visit: Visit) => Promise<any> = null;
62
+ const setOnSubmit = (callback) => {
63
+ onSubmit = callback;
64
+ };
65
+ render(<QueueFields setOnSubmit={setOnSubmit} />);
48
66
 
49
67
  expect(screen.getByLabelText('Select a queue location')).toBeInTheDocument();
50
68
  expect(screen.getByLabelText('Select a service')).toBeInTheDocument();
51
- expect(screen.getByLabelText('Sort weight')).toBeInTheDocument();
52
69
 
70
+ const queueUuid = 'e2ec9cf0-ec38-4d2b-af6c-59c82fa30b90';
53
71
  const serviceSelect = screen.getByLabelText('Select a service').closest('select');
54
- await user.selectOptions(serviceSelect, 'e2ec9cf0-ec38-4d2b-af6c-59c82fa30b90');
72
+ await user.selectOptions(serviceSelect, queueUuid);
55
73
 
56
74
  expect(screen.getByText('Priority')).toBeInTheDocument();
57
75
  expect(screen.getByText('High')).toBeInTheDocument();
58
76
 
59
- expect(setFormFields).toHaveBeenCalledWith({
60
- queueLocation: '1',
61
- service: 'e2ec9cf0-ec38-4d2b-af6c-59c82fa30b90',
62
- // The status and priority are the defaults that come from the config schema.
63
- // They are selected even though they are not 'allowed' for the queue.
64
- status: '51ae5e4d-b72b-4912-bf31-a17efb690aeb',
65
- priority: 'f4620bfa-3625-4883-bd3f-84c2cce14470',
66
- sortWeight: 0,
67
- });
77
+ await onSubmit(mockVisitAlice);
78
+ expect(mockPostQueueEntry).toHaveBeenCalledWith(
79
+ mockVisitAlice.uuid,
80
+ queueUuid, // queueUuid
81
+ mockVisitAlice.patient.uuid,
82
+ 'f4620bfa-3625-4883-bd3f-84c2cce14470', // priority
83
+ '51ae5e4d-b72b-4912-bf31-a17efb690aeb', // status
84
+ 0, // sortWeight
85
+ '1', // locationUuid
86
+ null, // visitQueueNumberAttributeUuid
87
+ );
68
88
  });
69
89
  });
@@ -0,0 +1,31 @@
1
+ import { type Visit } from '@openmrs/esm-framework';
2
+ import React from 'react';
3
+ import QueueFields from './queue-fields.component';
4
+
5
+ interface VisitFormCallbacks {
6
+ onVisitCreatedOrUpdated: (visit: Visit) => Promise<any>;
7
+ }
8
+ // See VisitFormExtensionState in esm-patient-chart-app
9
+ export interface VisitFormQueueFieldsProps {
10
+ setVisitFormCallbacks: (callbacks: VisitFormCallbacks) => void;
11
+ visitFormOpenedFrom: string;
12
+ patientChartConfig?: {
13
+ showServiceQueueFields: boolean;
14
+ };
15
+ patientUuid: string;
16
+ }
17
+
18
+ /**
19
+ * This extension contains form fields for starting a patient's queue entry.
20
+ * It is used slotted into the patient-chart's start visit form
21
+ */
22
+ const VisitFormQueueFields: React.FC<VisitFormQueueFieldsProps> = (props) => {
23
+ const { setVisitFormCallbacks, visitFormOpenedFrom, patientChartConfig } = props;
24
+ if (patientChartConfig.showServiceQueueFields || visitFormOpenedFrom == 'service-queues-add-patient') {
25
+ return <QueueFields setOnSubmit={(onSubmit) => setVisitFormCallbacks({ onVisitCreatedOrUpdated: onSubmit })} />;
26
+ } else {
27
+ return <></>;
28
+ }
29
+ };
30
+
31
+ export default VisitFormQueueFields;
@@ -1,9 +1,9 @@
1
- import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
- import { type QueueEntry, type QueueEntrySearchCriteria } from '../types';
3
- import useSWR from 'swr';
4
1
  import { useCallback, useEffect, useMemo, useState } from 'react';
5
- import { useSWRConfig } from 'swr/_internal';
6
2
  import isEqual from 'lodash-es/isEqual';
3
+ import useSWR from 'swr';
4
+ import { useSWRConfig } from 'swr/_internal';
5
+ import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
6
+ import { type QueueEntry, type QueueEntrySearchCriteria } from '../types';
7
7
 
8
8
  type QueueEntryResponse = FetchResponse<{
9
9
  results: Array<QueueEntry>;
@@ -82,14 +82,15 @@ export function useQueueEntries(searchCriteria?: QueueEntrySearchCriteria, rep:
82
82
  // for it yet. Next it returns stale data again, this time with `isValidating` set to true. At this
83
83
  // point we say we are no longer waiting for mutate. Finally, it returns fresh data with `isValidating`
84
84
  // again set to false. We may then update the data array and move on to the next page.
85
- const [data, setData] = useState<Array<Array<QueueEntry>>>([]);
86
- const [totalCount, setTotalCount] = useState<number>();
87
- const [currentPage, setCurrentPage] = useState<number>(0);
88
- const [currentSearchCriteria, setCurrentSearchCriteria] = useState(searchCriteria);
89
- const [currentRep, setCurrentRep] = useState(rep);
90
- const [pageUrl, setPageUrl] = useState<string>(getInitialUrl(currentRep, currentSearchCriteria));
91
- const [error, setError] = useState<Error>();
92
85
  const { mutateQueueEntries } = useMutateQueueEntries();
86
+
87
+ const [currentPage, setCurrentPage] = useState(0);
88
+ const [currentRep, setCurrentRep] = useState(rep);
89
+ const [currentSearchCriteria, setCurrentSearchCriteria] = useState(searchCriteria);
90
+ const [data, setData] = useState<Array<Array<QueueEntry>>>([]);
91
+ const [error, setError] = useState<Error | undefined>(undefined);
92
+ const [pageUrl, setPageUrl] = useState(getInitialUrl(currentRep, currentSearchCriteria));
93
+ const [totalCount, setTotalCount] = useState(0);
93
94
  const [waitingForMutate, setWaitingForMutate] = useState(false);
94
95
 
95
96
  const refetchAllData = useCallback(
@@ -114,7 +115,7 @@ export function useQueueEntries(searchCriteria?: QueueEntrySearchCriteria, rep:
114
115
  }
115
116
  refetchAllData(rep, searchCriteria);
116
117
  }
117
- }, [searchCriteria, currentSearchCriteria, setCurrentSearchCriteria, currentRep, rep]);
118
+ }, [currentRep, currentSearchCriteria, refetchAllData, rep, searchCriteria, setCurrentSearchCriteria]);
118
119
 
119
120
  const { data: pageData, isValidating, error: pageError } = useSWR<QueueEntryResponse, Error>(pageUrl, openmrsFetch);
120
121
 
package/src/index.ts CHANGED
@@ -10,7 +10,7 @@ import outpatientSideNavComponent from './side-menu/side-menu.component';
10
10
  import homeDashboardComponent from './home.component';
11
11
  import patientInfoBannerSlotComponent from './patient-info/patient-info.component';
12
12
  import pastVisitSummaryComponent from './past-visit/past-visit.component';
13
- import VisitFormQueueFields from './patient-search/visit-form-queue-fields/visit-form-queue-fields.component';
13
+ import VisitFormQueueFields from './create-queue-entry/queue-fields/visit-form-queue-fields.extension';
14
14
 
15
15
  export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
16
16
 
@@ -64,14 +64,6 @@ export const clearAllQueueEntries = getAsyncLifecycle(
64
64
  },
65
65
  );
66
66
 
67
- export const addVisitToQueueModal = getAsyncLifecycle(
68
- () => import('./add-patient-toqueue/add-patient-toqueue-dialog.component'),
69
- {
70
- featureName: 'add visit to queue',
71
- moduleName,
72
- },
73
- );
74
-
75
67
  export const transitionQueueEntryStatusModal = getAsyncLifecycle(
76
68
  () => import('./transition-queue-entry/transition-queue-entry-dialog.component'),
77
69
  {
@@ -157,11 +149,13 @@ export const addNewQueueServiceRoomWorkspace = getAsyncLifecycle(
157
149
 
158
150
  export const visitFormQueueFields = getSyncLifecycle(VisitFormQueueFields, options);
159
151
 
160
- // t('searchPatient', 'Search Patient')
161
- export const patientSearchWorkspace = getAsyncLifecycle(() => import('./patient-search/patient-search.workspace'), {
162
- featureName: 'service-queues-patient-search',
163
- moduleName,
164
- });
152
+ export const createQueueEntryWorkspace = getAsyncLifecycle(
153
+ () => import('./create-queue-entry/create-queue-entry.workspace'),
154
+ {
155
+ featureName: 'create-queue-entry-workspace',
156
+ moduleName,
157
+ },
158
+ );
165
159
 
166
160
  export const activeVisitsRowActions = getAsyncLifecycle(
167
161
  () => import('./active-visits/active-visits-row-actions.component'),
@@ -1,18 +1,18 @@
1
+ import React, { useState } from 'react';
2
+ import classNames from 'classnames';
3
+ import { useTranslation } from 'react-i18next';
1
4
  import { ClickableTile } from '@carbon/react';
2
5
  import { Edit } from '@carbon/react/icons';
3
6
  import {
4
- ConfigurableLink,
5
- PatientBannerContactDetails,
6
- PatientBannerToggleContactDetailsButton,
7
- PatientPhoto,
8
7
  age,
8
+ ConfigurableLink,
9
9
  formatDate,
10
10
  getPatientName,
11
11
  parseDate,
12
+ PatientBannerContactDetails,
13
+ PatientBannerToggleContactDetailsButton,
14
+ PatientPhoto,
12
15
  } from '@openmrs/esm-framework';
13
- import classNames from 'classnames';
14
- import React, { useState } from 'react';
15
- import { useTranslation } from 'react-i18next';
16
16
  import AppointmentDetails from './appointment-details.component';
17
17
  import styles from './patient-info.scss';
18
18
 
@@ -23,8 +23,8 @@ interface PatientInfoProps {
23
23
 
24
24
  const PatientInfo: React.FC<PatientInfoProps> = ({ patient, handlePatientInfoClick }) => {
25
25
  const { t } = useTranslation();
26
- const [showContactDetails, setShowContactDetails] = useState<boolean>(false);
27
26
  const patientName = getPatientName(patient);
27
+ const [showContactDetails, setShowContactDetails] = useState(false);
28
28
 
29
29
  const toggleShowMore = (e: React.MouseEvent) => {
30
30
  e.stopPropagation();
@@ -32,6 +32,8 @@ const PatientInfo: React.FC<PatientInfoProps> = ({ patient, handlePatientInfoCli
32
32
  };
33
33
 
34
34
  return (
35
+ // TODO: Fix this warning: validateDOMNesting(...): <a> cannot appear as a descendant of <a>.
36
+ // This is because the ConfigurableLink is inside a ClickableTile.
35
37
  <ClickableTile className={styles.container} onClick={handlePatientInfoClick}>
36
38
  <div
37
39
  className={classNames({