@kenyaemr/esm-service-queues-app 8.1.1-pre.129 → 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 +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
@@ -1,337 +0,0 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import dayjs from 'dayjs';
3
- import {
4
- Button,
5
- ButtonSet,
6
- ContentSwitcher,
7
- DatePicker,
8
- DatePickerInput,
9
- Form,
10
- FormGroup,
11
- InlineNotification,
12
- RadioButton,
13
- RadioButtonGroup,
14
- Row,
15
- Select,
16
- SelectItem,
17
- Stack,
18
- Switch,
19
- TimePicker,
20
- TimePickerSelect,
21
- } from '@carbon/react';
22
- import { useTranslation } from 'react-i18next';
23
- import {
24
- ExtensionSlot,
25
- ResponsiveWrapper,
26
- saveVisit,
27
- showSnackbar,
28
- toDateObjectStrict,
29
- toOmrsIsoString,
30
- useConfig,
31
- useLayoutType,
32
- useLocations,
33
- useSession,
34
- } from '@openmrs/esm-framework';
35
- import { RecommendedVisitTypeSelector, VisitTypeSelector } from './visit-type-selector.component';
36
- import { postQueueEntry } from '../../active-visits/active-visits-table.resource';
37
- import { type amPm, convertTime12to24 } from '../../helpers/time-helpers';
38
- import { useActivePatientEnrollment } from '../hooks/useActivePatientEnrollment';
39
- import { type NewVisitPayload, type PatientProgram } from '../../types';
40
- import styles from './visit-form.scss';
41
- import { useDefaultLoginLocation } from '../hooks/useDefaultLocation';
42
- import isEmpty from 'lodash-es/isEmpty';
43
- import { useMutateQueueEntries } from '../../hooks/useQueueEntries';
44
- import { type ConfigObject } from '../../config-schema';
45
- import { datePickerFormat, datePickerPlaceHolder } from '../../constants';
46
- import VisitFormQueueFields from '../visit-form-queue-fields/visit-form-queue-fields.component';
47
-
48
- interface VisitFormProps {
49
- patientUuid: string;
50
- closeWorkspace: () => void;
51
- }
52
-
53
- const VisitForm: React.FC<VisitFormProps> = ({ patientUuid, closeWorkspace }) => {
54
- const { t } = useTranslation();
55
- const isTablet = useLayoutType() === 'tablet';
56
- const locations = useLocations();
57
- const sessionUser = useSession();
58
- const { defaultFacility, isLoading: loadingDefaultFacility } = useDefaultLoginLocation();
59
-
60
- const config = useConfig<ConfigObject>();
61
- const [contentSwitcherIndex, setContentSwitcherIndex] = useState(config.showRecommendedVisitTypeTab ? 0 : 1);
62
- const [isMissingVisitType, setIsMissingVisitType] = useState(false);
63
- const [isSubmitting, setIsSubmitting] = useState(false);
64
- const [timeFormat, setTimeFormat] = useState<amPm>(new Date().getHours() >= 12 ? 'PM' : 'AM');
65
- const [visitDate, setVisitDate] = useState(new Date());
66
- const [visitTime, setVisitTime] = useState(dayjs(new Date()).format('hh:mm'));
67
- const state = useMemo(() => ({ patientUuid }), [patientUuid]);
68
- const [ignoreChanges, setIgnoreChanges] = useState(true);
69
- const { activePatientEnrollment } = useActivePatientEnrollment(patientUuid);
70
- const [enrollment, setEnrollment] = useState<PatientProgram>(activePatientEnrollment[0]);
71
- const { mutateQueueEntries } = useMutateQueueEntries();
72
- const visitQueueNumberAttributeUuid = config.visitQueueNumberAttributeUuid;
73
- const [selectedLocation, setSelectedLocation] = useState('');
74
- const [visitType, setVisitType] = useState('');
75
- const [{ service, priority, status, sortWeight, queueLocation }, setVisitFormFields] = useState({
76
- service: null,
77
- priority: null,
78
- status: null,
79
- sortWeight: null,
80
- queueLocation: null,
81
- });
82
-
83
- useEffect(() => {
84
- if (locations?.length && sessionUser) {
85
- setSelectedLocation(sessionUser?.sessionLocation?.uuid);
86
- } else if (!loadingDefaultFacility && defaultFacility) {
87
- setSelectedLocation(defaultFacility?.uuid);
88
- }
89
- }, [locations, sessionUser, loadingDefaultFacility]);
90
-
91
- const handleSubmit = useCallback(
92
- (event) => {
93
- event.preventDefault();
94
-
95
- if (!visitType) {
96
- setIsMissingVisitType(true);
97
- return;
98
- }
99
-
100
- setIsSubmitting(true);
101
-
102
- const [hours, minutes] = convertTime12to24(visitTime, timeFormat);
103
-
104
- const payload: NewVisitPayload = {
105
- patient: patientUuid,
106
- startDatetime: toDateObjectStrict(
107
- toOmrsIsoString(
108
- new Date(dayjs(visitDate).year(), dayjs(visitDate).month(), dayjs(visitDate).date(), hours, minutes),
109
- ),
110
- ),
111
- visitType: visitType,
112
- location: selectedLocation,
113
- attributes: [],
114
- };
115
-
116
- const abortController = new AbortController();
117
-
118
- saveVisit(payload, abortController).then((response) => {
119
- // add new queue entry if visit created successfully
120
- postQueueEntry(
121
- response.data.uuid,
122
- service,
123
- patientUuid,
124
- priority,
125
- status,
126
- sortWeight,
127
- queueLocation,
128
- visitQueueNumberAttributeUuid,
129
- )
130
- .then(() => {
131
- showSnackbar({
132
- kind: 'success',
133
- isLowContrast: true,
134
- title: t('startAVisit', 'Start a visit'),
135
- subtitle: t(
136
- 'startVisitQueueSuccessfully',
137
- 'Patient has been added to active visits list and queue.',
138
- `${hours} : ${minutes}`,
139
- ),
140
- });
141
- closeWorkspace();
142
- setIsSubmitting(false);
143
- mutateQueueEntries();
144
- })
145
- .catch((error) => {
146
- showSnackbar({
147
- title: t('queueEntryError', 'Error adding patient to the queue'),
148
- kind: 'error',
149
- subtitle: error?.message,
150
- });
151
- setIsSubmitting(false);
152
- })
153
- .catch((error) => {
154
- showSnackbar({
155
- title: t('startVisitError', 'Error starting visit'),
156
- kind: 'error',
157
- subtitle: error?.message,
158
- });
159
- setIsSubmitting(false);
160
- });
161
- });
162
- },
163
- [
164
- closeWorkspace,
165
- mutateQueueEntries,
166
- patientUuid,
167
- selectedLocation,
168
- t,
169
- timeFormat,
170
- visitDate,
171
- visitQueueNumberAttributeUuid,
172
- visitTime,
173
- visitType,
174
- ],
175
- );
176
-
177
- const handleOnChange = useCallback(() => {
178
- setIgnoreChanges((prevState) => !prevState);
179
- }, []);
180
-
181
- return (
182
- <Form className={styles.form} onChange={handleOnChange} onSubmit={handleSubmit}>
183
- <div>
184
- {isTablet && (
185
- <Row className={styles.headerGridRow}>
186
- <ExtensionSlot name="visit-form-header-slot" className={styles.dataGridRow} state={state} />
187
- </Row>
188
- )}
189
- <Stack gap={8} className={styles.container}>
190
- <section className={styles.section}>
191
- <div className={styles.sectionTitle}>{t('dateAndTimeOfVisit', 'Date and time of visit')}</div>
192
- <div className={styles.dateTimeSection}>
193
- <DatePicker
194
- dateFormat={datePickerFormat}
195
- datePickerType="single"
196
- id="visitDate"
197
- style={{ paddingBottom: '1rem' }}
198
- maxDate={new Date().toISOString()}
199
- onChange={([date]) => setVisitDate(date)}
200
- value={visitDate}>
201
- <DatePickerInput
202
- id="visitStartDateInput"
203
- labelText={t('date', 'Date')}
204
- placeholder={datePickerPlaceHolder}
205
- style={{ width: '100%' }}
206
- />
207
- </DatePicker>
208
- <ResponsiveWrapper>
209
- <TimePicker
210
- id="visitStartTime"
211
- labelText={t('time', 'Time')}
212
- onChange={(event) => setVisitTime(event.target.value as amPm)}
213
- pattern="^(1[0-2]|0?[1-9]):([0-5]?[0-9])$"
214
- style={{ marginLeft: '0.125rem', flex: 'none' }}
215
- value={visitTime}>
216
- <TimePickerSelect
217
- id="visitStartTimeSelect"
218
- onChange={(event) => setTimeFormat(event.target.value as amPm)}
219
- value={timeFormat}
220
- labelText={t('time', 'Time')}
221
- aria-label={t('time', 'Time')}>
222
- <SelectItem value="AM" text="AM" />
223
- <SelectItem value="PM" text="PM" />
224
- </TimePickerSelect>
225
- </TimePicker>
226
- </ResponsiveWrapper>
227
- </div>
228
- </section>
229
-
230
- <section className={styles.section}>
231
- <div className={styles.sectionTitle}>{t('facility', 'Facility')}</div>
232
- <Select
233
- labelText={t('selectFacility', 'Select a facility')}
234
- id="location"
235
- invalidText="Required"
236
- value={selectedLocation}
237
- defaultSelected={selectedLocation}
238
- onChange={(event) => setSelectedLocation(event.target.value)}>
239
- {!selectedLocation ? <SelectItem text={t('selectOption', 'Select an option')} value="" /> : null}
240
- {!isEmpty(defaultFacility) ? (
241
- <SelectItem key={defaultFacility?.uuid} text={defaultFacility?.display} value={defaultFacility?.uuid}>
242
- {defaultFacility?.display}
243
- </SelectItem>
244
- ) : locations?.length > 0 ? (
245
- locations.map((location) => (
246
- <SelectItem key={location.uuid} text={location.display} value={location.uuid}>
247
- {location.display}
248
- </SelectItem>
249
- ))
250
- ) : null}
251
- </Select>
252
- </section>
253
-
254
- {config.showRecommendedVisitTypeTab && (
255
- <section>
256
- <div className={styles.sectionTitle}>{t('program', 'Program')}</div>
257
- <FormGroup legendText={t('selectProgramType', 'Select program type')}>
258
- <RadioButtonGroup
259
- defaultSelected={enrollment?.program?.uuid}
260
- orientation="vertical"
261
- onChange={(uuid) =>
262
- setEnrollment(activePatientEnrollment.find(({ program }) => program.uuid === uuid))
263
- }
264
- name="program-type-radio-group"
265
- valueSelected="default-selected">
266
- {activePatientEnrollment.map(({ uuid, display, program }) => (
267
- <RadioButton
268
- key={uuid}
269
- className={styles.radioButton}
270
- id={uuid}
271
- labelText={display}
272
- value={program.uuid}
273
- />
274
- ))}
275
- </RadioButtonGroup>
276
- </FormGroup>
277
- </section>
278
- )}
279
- <section>
280
- <div className={styles.sectionTitle}>{t('visitType', 'Visit Type')}</div>
281
- {config.showRecommendedVisitTypeTab && (
282
- <ContentSwitcher
283
- selectedIndex={contentSwitcherIndex}
284
- className={styles.contentSwitcher}
285
- onChange={({ index }) => setContentSwitcherIndex(index)}>
286
- <Switch name="recommended" text={t('recommended', 'Recommended')} />
287
- <Switch name="all" text={t('all', 'All')} />
288
- </ContentSwitcher>
289
- )}
290
- {config.showRecommendedVisitTypeTab && contentSwitcherIndex === 0 && (
291
- <RecommendedVisitTypeSelector
292
- onChange={(visitType) => {
293
- setVisitType(visitType);
294
- setIsMissingVisitType(false);
295
- }}
296
- patientUuid={patientUuid}
297
- patientProgram={enrollment}
298
- locationUuid={selectedLocation}
299
- />
300
- )}
301
- {(!config.showRecommendedVisitTypeTab || contentSwitcherIndex === 1) && (
302
- <VisitTypeSelector
303
- onChange={(visitType) => {
304
- setVisitType(visitType);
305
- setIsMissingVisitType(false);
306
- }}
307
- />
308
- )}
309
- </section>
310
- {isMissingVisitType && (
311
- <section>
312
- <InlineNotification
313
- style={{ margin: '0', minWidth: '100%' }}
314
- kind="error"
315
- lowContrast={true}
316
- title={t('missingVisitType', 'Missing visit type')}
317
- subtitle={t('selectVisitType', 'Please select a Visit Type')}
318
- />
319
- </section>
320
- )}
321
-
322
- <VisitFormQueueFields setFormFields={setVisitFormFields} />
323
- </Stack>
324
- </div>
325
- <ButtonSet className={isTablet ? styles.tabletButtons : styles.desktopButtons}>
326
- <Button className={styles.button} kind="secondary" onClick={closeWorkspace}>
327
- {t('discard', 'Discard')}
328
- </Button>
329
- <Button className={styles.button} disabled={isSubmitting} kind="primary" type="submit">
330
- {t('startVisit', 'Start visit')}
331
- </Button>
332
- </ButtonSet>
333
- </Form>
334
- );
335
- };
336
-
337
- export default VisitForm;
@@ -1,153 +0,0 @@
1
- import React, { useState, useMemo, useEffect } from 'react';
2
- import classNames from 'classnames';
3
- import isEmpty from 'lodash-es/isEmpty';
4
- import { useTranslation } from 'react-i18next';
5
- import {
6
- InlineNotification,
7
- Layer,
8
- RadioButton,
9
- RadioButtonGroup,
10
- Search,
11
- StructuredListSkeleton,
12
- Tile,
13
- } from '@carbon/react';
14
- import { ResponsiveWrapper, useDebounce, useLayoutType, useVisitTypes, type VisitType } from '@openmrs/esm-framework';
15
- import EmptyDataIllustration from '../empty-data-illustration.component';
16
- import styles from './visit-type-selector.scss';
17
- import { useRecommendedVisitTypes } from '../hooks/useRecommendedVisitTypes';
18
- import { type PatientProgram } from '../../types';
19
-
20
- export interface VisitTypeSelectorProps {
21
- onChange: (event) => void;
22
- }
23
-
24
- export const VisitTypeSelector: React.FC<VisitTypeSelectorProps> = ({ onChange }) => {
25
- const allVisitTypes = useVisitTypes();
26
-
27
- return (
28
- <div>
29
- {allVisitTypes.length == 0 ? (
30
- <StructuredListSkeleton />
31
- ) : (
32
- <VisitTypeSelectorPresentation visitTypes={allVisitTypes} onChange={onChange} />
33
- )}
34
- </div>
35
- );
36
- };
37
-
38
- export interface RecommendedVisitTypeSelectorProps {
39
- onChange: (event) => void;
40
- patientUuid: string;
41
- patientProgram: PatientProgram;
42
- locationUuid: string;
43
- }
44
-
45
- /** Recommended visits are specfic to a patient, patient program, and location. */
46
- export const RecommendedVisitTypeSelector: React.FC<RecommendedVisitTypeSelectorProps> = ({
47
- onChange,
48
- patientUuid,
49
- patientProgram,
50
- locationUuid,
51
- }) => {
52
- const { t } = useTranslation();
53
- const { recommendedVisitTypes, error, isLoading } = useRecommendedVisitTypes(
54
- patientUuid,
55
- patientProgram?.uuid,
56
- patientProgram?.program?.uuid,
57
- locationUuid,
58
- );
59
-
60
- return (
61
- <div style={{ marginTop: '0.625rem' }}>
62
- {isLoading ? (
63
- <StructuredListSkeleton />
64
- ) : (
65
- <VisitTypeSelectorPresentation onChange={onChange} visitTypes={recommendedVisitTypes} />
66
- )}
67
- {error && (
68
- <InlineNotification
69
- kind="error"
70
- title={t('failedToLoadRecommendedVisitTypes', 'Failed to load recommended visit types')}></InlineNotification>
71
- )}
72
- </div>
73
- );
74
- };
75
-
76
- interface VisitTypeSelectorPresentationProps {
77
- onChange: (event) => void;
78
- visitTypes: VisitType[];
79
- }
80
-
81
- const MAX_RESULTS = 5;
82
-
83
- const VisitTypeSelectorPresentation: React.FC<VisitTypeSelectorPresentationProps> = ({ visitTypes, onChange }) => {
84
- const { t } = useTranslation();
85
- const isTablet = useLayoutType() === 'tablet';
86
- const [searchTerm, setSearchTerm] = useState<string>('');
87
- const debouncedSearchTerm = useDebounce(searchTerm);
88
- const [selectedVisitType, setSelectedVisitType] = useState<string>();
89
-
90
- const results = useMemo(() => {
91
- if (!isEmpty(debouncedSearchTerm)) {
92
- return visitTypes.filter(
93
- (visitType) => visitType.display.toLowerCase().search(debouncedSearchTerm.toLowerCase()) !== -1,
94
- );
95
- } else {
96
- return visitTypes;
97
- }
98
- }, [debouncedSearchTerm, visitTypes]);
99
-
100
- const truncatedResults = results.slice(0, MAX_RESULTS);
101
-
102
- useEffect(() => {
103
- if (results.length > 0) {
104
- onChange(results[0].uuid);
105
- setSelectedVisitType(results[0].uuid);
106
- }
107
- }, [results]);
108
-
109
- return (
110
- <div
111
- className={classNames(styles.visitTypeOverviewWrapper, {
112
- [styles.tablet]: isTablet,
113
- [styles.desktop]: !isTablet,
114
- })}>
115
- {truncatedResults.length < visitTypes.length ? (
116
- <ResponsiveWrapper>
117
- <Search
118
- onChange={(event) => setSearchTerm(event.target.value)}
119
- placeholder={t('searchForAVisitType', 'Search for a visit type')}
120
- labelText=""
121
- />
122
- </ResponsiveWrapper>
123
- ) : null}
124
- {truncatedResults.length ? (
125
- <RadioButtonGroup
126
- className={styles.radioButtonGroup}
127
- defaultSelected={results[0].uuid}
128
- orientation="vertical"
129
- onChange={(visitType) => {
130
- setSelectedVisitType(visitType);
131
- onChange(visitType);
132
- }}
133
- name="radio-button-group"
134
- valueSelected={selectedVisitType}>
135
- {truncatedResults.map(({ uuid, display, name }) => (
136
- <RadioButton key={uuid} className={styles.radioButton} id={name} labelText={display} value={uuid} />
137
- ))}
138
- {/* TODO: This is supposed to paginate. Right now it just shows the user a truncated list
139
- with no indication that the list is truncated. */}
140
- </RadioButtonGroup>
141
- ) : (
142
- <Layer>
143
- <Tile className={styles.tile}>
144
- <EmptyDataIllustration />
145
- <p className={styles.content}>
146
- {t('noVisitTypesMatchingSearch', 'There are no visit types matching this search text')}
147
- </p>
148
- </Tile>
149
- </Layer>
150
- )}
151
- </div>
152
- );
153
- };
@@ -1,100 +0,0 @@
1
- @use '@carbon/layout';
2
- @use '@carbon/type';
3
- @use '@openmrs/esm-styleguide/src/vars' as *;
4
-
5
- .visitTypeOverviewWrapper {
6
- margin: layout.$spacing-05 0;
7
- border: 0.0625rem solid $grey-2;
8
- }
9
-
10
- .tablet {
11
- background-color: $ui-02;
12
- }
13
-
14
- .desktop {
15
- background-color: $ui-01;
16
-
17
- .paginationContainer div {
18
- background-color: $ui-01;
19
- }
20
- }
21
-
22
- .visitTypeOverviewWrapper div:nth-child(3) > div:nth-child(2) {
23
- position: relative;
24
- }
25
-
26
- .visitTypeOverviewWrapper div:nth-child(3) span * {
27
- display: none;
28
- }
29
-
30
- .radioButtonGroup {
31
- display: flex;
32
- flex-direction: column;
33
- align-items: flex-start;
34
- margin-top: layout.$spacing-03;
35
- min-height: layout.$spacing-10;
36
- width: 100%;
37
- @include type.type-style('body-compact-01');
38
- }
39
-
40
- .radioButton {
41
- padding: layout.$spacing-02 layout.$spacing-05;
42
- margin: layout.$spacing-03 0;
43
- }
44
-
45
- .content {
46
- @include type.type-style('heading-compact-01');
47
- color: $text-02;
48
- margin-top: layout.$spacing-05;
49
- margin-bottom: layout.$spacing-03;
50
- }
51
-
52
- .desktopHeading {
53
- h4 {
54
- @include type.type-style('heading-compact-02');
55
- color: $text-02;
56
- }
57
- }
58
-
59
- .tabletHeading {
60
- h4 {
61
- @include type.type-style('heading-03');
62
- color: $text-02;
63
- }
64
- }
65
-
66
- .desktopHeading,
67
- .tabletHeading {
68
- text-align: left;
69
- text-transform: capitalize;
70
- margin-bottom: layout.$spacing-05;
71
-
72
- h4:after {
73
- content: '';
74
- display: block;
75
- width: layout.$spacing-07;
76
- padding-top: 0.188rem;
77
- border-bottom: 0.375rem solid var(--brand-03);
78
- }
79
- }
80
-
81
- .heading:after {
82
- content: '';
83
- display: block;
84
- width: layout.$spacing-07;
85
- padding-top: 0.188rem;
86
- border-bottom: 0.375rem solid var(--brand-03);
87
- }
88
-
89
- .tile {
90
- text-align: center;
91
- border: 1px solid $ui-03;
92
- }
93
-
94
- // Overriding styles for RTL support
95
- html[dir='rtl'] {
96
- .desktopHeading,
97
- .tabletHeading {
98
- text-align: right;
99
- }
100
- }
@@ -1,84 +0,0 @@
1
- /* eslint-disable testing-library/no-node-access */
2
- import React from 'react';
3
- import userEvent from '@testing-library/user-event';
4
- import { render, screen } from '@testing-library/react';
5
- import { mockVisitTypes } from '__mocks__';
6
- import { useVisitTypes } from '@openmrs/esm-framework';
7
- import { VisitTypeSelector } from './visit-type-selector.component';
8
-
9
- const mockUseVisitTypes = jest.mocked(useVisitTypes);
10
-
11
- describe('VisitTypeSelector', () => {
12
- beforeEach(() => {
13
- mockUseVisitTypes.mockReturnValue(mockVisitTypes);
14
- });
15
-
16
- it('renders visit types with no search bar if there are 5 or fewer', () => {
17
- const fewVisitTypes = mockVisitTypes.slice(0, 3);
18
- mockUseVisitTypes.mockReturnValue(fewVisitTypes);
19
- render(<VisitTypeSelector onChange={() => {}} />);
20
-
21
- expect(screen.queryByRole('searchbox')).not.toBeInTheDocument();
22
-
23
- fewVisitTypes.forEach((visitType) => {
24
- const radioButton = screen.getByLabelText(visitType.display);
25
- expect(radioButton).toBeInTheDocument();
26
- });
27
- });
28
-
29
- it('renders the first 5 visit types with a search bar if there are more than 5', () => {
30
- render(<VisitTypeSelector onChange={() => {}} />);
31
-
32
- expect(screen.getByRole('searchbox')).toBeInTheDocument();
33
-
34
- mockVisitTypes.slice(0, 5).forEach((visitType) => {
35
- const radioButton = screen.getByLabelText(visitType.display);
36
- expect(radioButton).toBeInTheDocument();
37
- });
38
- });
39
-
40
- it('filters by search input and calls onChange', async () => {
41
- const user = userEvent.setup();
42
-
43
- const mockOnChange = jest.fn();
44
- render(<VisitTypeSelector onChange={mockOnChange} />);
45
-
46
- const searchInput = screen.getByRole('searchbox').closest('input');
47
- await user.type(searchInput, 'hiv');
48
-
49
- expect(searchInput.value).toBe('hiv');
50
- expect(screen.getByLabelText('HIV Return Visit')).toBeInTheDocument();
51
- expect(screen.getByLabelText('HIV Initial Visit')).toBeInTheDocument();
52
- expect(screen.queryByLabelText('Outpatient Visit')).not.toBeInTheDocument();
53
- expect(screen.queryByLabelText('Diabetes Clinic Visit')).not.toBeInTheDocument();
54
-
55
- expect(mockOnChange).toHaveBeenLastCalledWith(
56
- mockVisitTypes.filter((vt) => vt.display == 'HIV Return Visit')[0].uuid,
57
- );
58
- });
59
-
60
- it('calls onChange when a visit type is selected', async () => {
61
- const user = userEvent.setup();
62
-
63
- const mockOnChange = jest.fn();
64
- render(<VisitTypeSelector onChange={mockOnChange} />);
65
-
66
- const radioButton = screen.getByLabelText(mockVisitTypes[1].display).closest('input');
67
- await user.click(radioButton);
68
- expect(radioButton).toBeChecked();
69
- expect(mockOnChange).toHaveBeenLastCalledWith(mockVisitTypes[1].uuid);
70
- });
71
-
72
- it('allows changing the search input if no results are returned from a search', async () => {
73
- const user = userEvent.setup();
74
-
75
- render(<VisitTypeSelector onChange={() => {}} />);
76
-
77
- const searchInput: HTMLInputElement = screen.getByRole('searchbox');
78
- await user.type(searchInput, 'asdfasdf');
79
-
80
- const searchInputAfter: HTMLInputElement = screen.getByRole('searchbox');
81
- expect(searchInputAfter).toBeInTheDocument();
82
- expect(screen.getByText(/no visit types/i)).toBeInTheDocument();
83
- });
84
- });