@digihmis/esm-service-queue-app 1.0.0
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.
- package/README.md +4 -0
- package/dist/117.js +1 -0
- package/dist/117.js.map +1 -0
- package/dist/132.js +1 -0
- package/dist/132.js.map +1 -0
- package/dist/152.js +1 -0
- package/dist/152.js.map +1 -0
- package/dist/157.js +1 -0
- package/dist/157.js.map +1 -0
- package/dist/209.js +1 -0
- package/dist/209.js.map +1 -0
- package/dist/217.js +1 -0
- package/dist/217.js.map +1 -0
- package/dist/317.js +1 -0
- package/dist/317.js.map +1 -0
- package/dist/349.js +1 -0
- package/dist/349.js.map +1 -0
- package/dist/371.js +1 -0
- package/dist/371.js.map +1 -0
- package/dist/378.js +1 -0
- package/dist/378.js.map +1 -0
- package/dist/390.js +1 -0
- package/dist/390.js.map +1 -0
- package/dist/393.js +12 -0
- package/dist/393.js.map +1 -0
- package/dist/442.js +1 -0
- package/dist/442.js.map +1 -0
- package/dist/45.js +1 -0
- package/dist/45.js.map +1 -0
- package/dist/466.js +1 -0
- package/dist/466.js.map +1 -0
- package/dist/50.js +1 -0
- package/dist/50.js.map +1 -0
- package/dist/508.js +1 -0
- package/dist/508.js.map +1 -0
- package/dist/61.js +1 -0
- package/dist/61.js.map +1 -0
- package/dist/689.js +1 -0
- package/dist/689.js.map +1 -0
- package/dist/712.js +1 -0
- package/dist/712.js.map +1 -0
- package/dist/716.js +1 -0
- package/dist/716.js.map +1 -0
- package/dist/720.js +1 -0
- package/dist/720.js.map +1 -0
- package/dist/731.js +15 -0
- package/dist/731.js.map +1 -0
- package/dist/742.js +1 -0
- package/dist/742.js.map +1 -0
- package/dist/771.js +1 -0
- package/dist/771.js.map +1 -0
- package/dist/806.js +1 -0
- package/dist/806.js.map +1 -0
- package/dist/912.js +1 -0
- package/dist/912.js.map +1 -0
- package/dist/913.js +1 -0
- package/dist/913.js.map +1 -0
- package/dist/940.js +1 -0
- package/dist/940.js.map +1 -0
- package/dist/989.js +1 -0
- package/dist/989.js.map +1 -0
- package/dist/digihmis-esm-service-queue-app.js +6 -0
- package/dist/digihmis-esm-service-queue-app.js.buildmanifest.json +873 -0
- package/dist/digihmis-esm-service-queue-app.js.map +1 -0
- package/dist/main.js +6 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/jest.config.js +26 -0
- package/package.json +51 -0
- package/rspack.config.js +1 -0
- package/src/config-schema.ts +181 -0
- package/src/dashboard.meta.ts +5 -0
- package/src/declarations.d.ts +6 -0
- package/src/index.ts +22 -0
- package/src/move-patient-new-room/component/queue-priority.component.tsx +44 -0
- package/src/move-patient-new-room/component/queue-priority.scss +16 -0
- package/src/move-patient-new-room/move-patient-new-room.extension.tsx +375 -0
- package/src/move-patient-new-room/move-patient-new-room.resource.ts +164 -0
- package/src/move-patient-new-room/move-patient-new-room.scss +50 -0
- package/src/move-patient-new-room/type/index.ts +156 -0
- package/src/routes.json +22 -0
- package/src/service-queue-checkin-form/queue-fields.component.tsx +316 -0
- package/src/service-queue-checkin-form/service-queue-checkin-form.extension.tsx +27 -0
- package/src/service-queue-checkin-form/service-queue-checkin-form.resource.ts +99 -0
- package/src/service-queue-checkin-form/service-queue-checkin-form.scss +18 -0
- package/src/setup-tests.ts +1 -0
- package/src/type/index.ts +10 -0
- package/translations/am.json +14 -0
- package/translations/ar.json +14 -0
- package/translations/en.json +14 -0
- package/translations/es.json +14 -0
- package/translations/fr.json +14 -0
- package/translations/he.json +14 -0
- package/translations/km.json +14 -0
- package/translations/pt.json +14 -0
- package/translations/sw.json +14 -0
- package/translations/vi.json +14 -0
- package/translations/zh.json +14 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { type OpenmrsResource } from '@openmrs/esm-framework';
|
|
2
|
+
|
|
3
|
+
export interface Concept extends OpenmrsResource {
|
|
4
|
+
setMembers?: Array<Concept>;
|
|
5
|
+
answers?: Array<ConceptAnswer>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ConceptAnswer {
|
|
9
|
+
uuid: string;
|
|
10
|
+
display: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ConceptAnswers {
|
|
14
|
+
uuid: string;
|
|
15
|
+
display: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ConceptResponse {
|
|
19
|
+
uuid: string;
|
|
20
|
+
display: string;
|
|
21
|
+
answers: Array<ConceptAnswers>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface QueueEntry {
|
|
25
|
+
uuid: string;
|
|
26
|
+
queue: {
|
|
27
|
+
uuid: string;
|
|
28
|
+
display: string;
|
|
29
|
+
};
|
|
30
|
+
queueRoom: {
|
|
31
|
+
uuid: string;
|
|
32
|
+
display: string;
|
|
33
|
+
};
|
|
34
|
+
status: {
|
|
35
|
+
uuid: string;
|
|
36
|
+
display: string;
|
|
37
|
+
};
|
|
38
|
+
priority: {
|
|
39
|
+
uuid: string;
|
|
40
|
+
display: string;
|
|
41
|
+
};
|
|
42
|
+
patient: {
|
|
43
|
+
uuid: string;
|
|
44
|
+
display: string;
|
|
45
|
+
person?: {
|
|
46
|
+
age: number;
|
|
47
|
+
gender: string;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
visit?: {
|
|
51
|
+
uuid: string;
|
|
52
|
+
display?: string;
|
|
53
|
+
};
|
|
54
|
+
startedAt: string;
|
|
55
|
+
endedAt?: string;
|
|
56
|
+
sortWeight?: number;
|
|
57
|
+
priorityComment?: string;
|
|
58
|
+
queueComingFrom?: {
|
|
59
|
+
uuid: string;
|
|
60
|
+
display: string;
|
|
61
|
+
};
|
|
62
|
+
queueRoomComingFrom?: {
|
|
63
|
+
uuid: string;
|
|
64
|
+
display: string;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface Queue {
|
|
69
|
+
uuid: string;
|
|
70
|
+
display: string;
|
|
71
|
+
name: string;
|
|
72
|
+
description: string;
|
|
73
|
+
location: {
|
|
74
|
+
uuid: string;
|
|
75
|
+
display: string;
|
|
76
|
+
};
|
|
77
|
+
service: {
|
|
78
|
+
uuid: string;
|
|
79
|
+
display: string;
|
|
80
|
+
};
|
|
81
|
+
allowedPriorities: Array<{
|
|
82
|
+
uuid: string;
|
|
83
|
+
display: string;
|
|
84
|
+
}>;
|
|
85
|
+
allowedStatuses: Array<{
|
|
86
|
+
uuid: string;
|
|
87
|
+
display: string;
|
|
88
|
+
}>;
|
|
89
|
+
queueRooms: Array<QueueRoom>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface QueueRoom {
|
|
93
|
+
uuid: string;
|
|
94
|
+
display: string;
|
|
95
|
+
name: string;
|
|
96
|
+
description: string;
|
|
97
|
+
queue?: {
|
|
98
|
+
uuid: string;
|
|
99
|
+
display: string;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface Patient {
|
|
104
|
+
uuid: string;
|
|
105
|
+
display: string;
|
|
106
|
+
person: {
|
|
107
|
+
uuid: string;
|
|
108
|
+
display: string;
|
|
109
|
+
gender: string;
|
|
110
|
+
age: number;
|
|
111
|
+
birthdate: string;
|
|
112
|
+
dead: boolean;
|
|
113
|
+
deathDate?: string;
|
|
114
|
+
causeOfDeath?: {
|
|
115
|
+
uuid: string;
|
|
116
|
+
display: string;
|
|
117
|
+
};
|
|
118
|
+
identifiers: Array<{
|
|
119
|
+
uuid: string;
|
|
120
|
+
identifier: string;
|
|
121
|
+
identifierType: {
|
|
122
|
+
uuid: string;
|
|
123
|
+
display: string;
|
|
124
|
+
};
|
|
125
|
+
preferred: boolean;
|
|
126
|
+
}>;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface QueueEntryPayload {
|
|
131
|
+
patientUuid: string;
|
|
132
|
+
queueUuid: string;
|
|
133
|
+
queueRoomUuid?: string;
|
|
134
|
+
priorityUuid: string;
|
|
135
|
+
statusUuid: string;
|
|
136
|
+
sortWeight: number;
|
|
137
|
+
queueComingFrom?: string;
|
|
138
|
+
queueRoomComingFrom?: string;
|
|
139
|
+
visitUuid?: string;
|
|
140
|
+
startedAt?: string;
|
|
141
|
+
priorityComment?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface QueueEntryRequestBody {
|
|
145
|
+
patient: string;
|
|
146
|
+
queue: string;
|
|
147
|
+
status: string;
|
|
148
|
+
priority: string;
|
|
149
|
+
sortWeight: number;
|
|
150
|
+
startedAt: string;
|
|
151
|
+
queueRoom?: string;
|
|
152
|
+
queueComingFrom?: string;
|
|
153
|
+
queueRoomComingFrom?: string;
|
|
154
|
+
visit?: string;
|
|
155
|
+
priorityComment?: string;
|
|
156
|
+
}
|
package/src/routes.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.openmrs.org/routes.schema.json",
|
|
3
|
+
"backendDependencies": {
|
|
4
|
+
"fhir2": ">=1.2",
|
|
5
|
+
"webservices.rest": "^2.24.0"
|
|
6
|
+
},
|
|
7
|
+
"extensions": [
|
|
8
|
+
{
|
|
9
|
+
"name": "service-queue-check-in-form",
|
|
10
|
+
"slot": "visit-form-bottom-slot",
|
|
11
|
+
"component": "serviceQueueCheckIn",
|
|
12
|
+
"order": 1
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"modals": [
|
|
16
|
+
{
|
|
17
|
+
"name": "move-patient-to-new-room-modal",
|
|
18
|
+
"component": "moveToNextRoomModal"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { FormGroup, InlineNotification, Select, SelectItem, SelectSkeleton, Stack } from '@carbon/react';
|
|
3
|
+
import { Controller, useForm } from 'react-hook-form';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { type TFunction } from 'i18next';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
8
|
+
import { ResponsiveWrapper, showSnackbar, useConfig, useSession, Visit } from '@openmrs/esm-framework';
|
|
9
|
+
import styles from './service-queue-checkin-form.scss';
|
|
10
|
+
import { postQueueEntry, useMutateQueueEntries, useQueue } from './service-queue-checkin-form.resource';
|
|
11
|
+
import { ServiceQueueConfig } from '../config-schema';
|
|
12
|
+
|
|
13
|
+
type QueueFieldsProps = {
|
|
14
|
+
setOnSubmit(onSubmit: (visit: Visit) => Promise<void>): void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const createQueueRoomSchema = (t: TFunction) =>
|
|
18
|
+
z.object({
|
|
19
|
+
roomToVisit: z.string().trim().min(1, t('roomToVisitRequired', 'Room to visit is required')),
|
|
20
|
+
roomType: z.string().trim().min(1, t('roomTypeRequired', 'Room type is required')),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const QueueFields: React.FC<QueueFieldsProps> = React.memo(({ setOnSubmit }) => {
|
|
24
|
+
const { t } = useTranslation();
|
|
25
|
+
const { queues, isLoading } = useQueue();
|
|
26
|
+
const { sessionLocation } = useSession();
|
|
27
|
+
const {
|
|
28
|
+
visitQueueNumberAttributeUuid,
|
|
29
|
+
concepts: { defaultStatusConceptUuid, defaultPriorityConceptUuid, emergencyPriorityConceptUuid },
|
|
30
|
+
defualtQueueRoom: { triageQueueUuid, outPatienttriageUuid },
|
|
31
|
+
} = useConfig<ServiceQueueConfig>();
|
|
32
|
+
const schema = useMemo(() => createQueueRoomSchema(t), [t]);
|
|
33
|
+
const [priority, setPriority] = useState(defaultPriorityConceptUuid);
|
|
34
|
+
|
|
35
|
+
const { mutateQueueEntries } = useMutateQueueEntries();
|
|
36
|
+
const memoMutateQueueEntries = useCallback(mutateQueueEntries, [mutateQueueEntries]);
|
|
37
|
+
|
|
38
|
+
const sortWeight = priority === emergencyPriorityConceptUuid ? 1 : 0;
|
|
39
|
+
|
|
40
|
+
const [extraVisitInfo, setExtraVisitInfo] = useState<{
|
|
41
|
+
roomToVisit: string;
|
|
42
|
+
roomToVisitName: string;
|
|
43
|
+
roomType: string;
|
|
44
|
+
roomTypeName: string;
|
|
45
|
+
} | null>(null);
|
|
46
|
+
|
|
47
|
+
const {
|
|
48
|
+
control,
|
|
49
|
+
formState: { errors, touchedFields, isSubmitted },
|
|
50
|
+
setValue,
|
|
51
|
+
watch,
|
|
52
|
+
getValues,
|
|
53
|
+
clearErrors,
|
|
54
|
+
} = useForm({
|
|
55
|
+
defaultValues: {
|
|
56
|
+
roomToVisit: triageQueueUuid,
|
|
57
|
+
roomType: outPatienttriageUuid,
|
|
58
|
+
},
|
|
59
|
+
mode: 'onChange',
|
|
60
|
+
resolver: zodResolver(schema),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const roomToVisit = watch('roomToVisit');
|
|
64
|
+
const roomType = watch('roomType');
|
|
65
|
+
|
|
66
|
+
const queueResults = useMemo(() => queues || [], [queues]);
|
|
67
|
+
|
|
68
|
+
const memoizedQueueLocations = useMemo(
|
|
69
|
+
() => queueResults.map((q) => ({ uuid: q.uuid, name: q.display })),
|
|
70
|
+
[queueResults],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const memoizedQueueRooms = useMemo(() => {
|
|
74
|
+
const selectedQueue = queueResults.find((q) => q.uuid === roomToVisit);
|
|
75
|
+
return selectedQueue?.queueRooms?.map((room) => ({ uuid: room.uuid, name: room.display })) || [];
|
|
76
|
+
}, [queueResults, roomToVisit]);
|
|
77
|
+
|
|
78
|
+
const isDataLoaded = !isLoading;
|
|
79
|
+
|
|
80
|
+
const selectedQueueLocation = useMemo(
|
|
81
|
+
() => queueResults.find((q) => q.uuid === roomToVisit),
|
|
82
|
+
[queueResults, roomToVisit],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const selectedRoom = useMemo(
|
|
86
|
+
() => selectedQueueLocation?.queueRooms?.find((room) => room.uuid === roomType),
|
|
87
|
+
[selectedQueueLocation, roomType],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (roomToVisit && roomType && selectedQueueLocation && selectedRoom) {
|
|
92
|
+
setExtraVisitInfo({
|
|
93
|
+
roomToVisit: roomToVisit,
|
|
94
|
+
roomToVisitName: selectedQueueLocation.display,
|
|
95
|
+
roomType: roomType,
|
|
96
|
+
roomTypeName: selectedRoom.display,
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
setExtraVisitInfo(null);
|
|
100
|
+
}
|
|
101
|
+
}, [roomToVisit, roomType, selectedQueueLocation, selectedRoom]);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const currentRoomToVisit = getValues('roomToVisit');
|
|
105
|
+
const defaultRoomExists = queueResults.some((q) => q.uuid === triageQueueUuid);
|
|
106
|
+
|
|
107
|
+
if (
|
|
108
|
+
!defaultRoomExists &&
|
|
109
|
+
sessionLocation?.uuid &&
|
|
110
|
+
!touchedFields.roomToVisit &&
|
|
111
|
+
memoizedQueueLocations.length > 0
|
|
112
|
+
) {
|
|
113
|
+
const locationExists = memoizedQueueLocations.some((loc) => loc.uuid === sessionLocation.uuid);
|
|
114
|
+
if (locationExists && currentRoomToVisit !== sessionLocation.uuid) {
|
|
115
|
+
setValue('roomToVisit', sessionLocation.uuid, { shouldValidate: isDataLoaded });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}, [
|
|
119
|
+
sessionLocation?.uuid,
|
|
120
|
+
memoizedQueueLocations,
|
|
121
|
+
touchedFields.roomToVisit,
|
|
122
|
+
setValue,
|
|
123
|
+
isDataLoaded,
|
|
124
|
+
getValues,
|
|
125
|
+
queueResults,
|
|
126
|
+
triageQueueUuid,
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
const onSubmit = useCallback(
|
|
130
|
+
(visit: Visit) => {
|
|
131
|
+
const currentRoomType = getValues('roomType');
|
|
132
|
+
const currentRoomToVisit = getValues('roomToVisit');
|
|
133
|
+
|
|
134
|
+
if (!currentRoomType || !currentRoomToVisit) {
|
|
135
|
+
showSnackbar({
|
|
136
|
+
title: t('queueEntryError', 'Error adding patient to the queue'),
|
|
137
|
+
kind: 'error',
|
|
138
|
+
isLowContrast: false,
|
|
139
|
+
subtitle: t('selectRoomAndType', 'Please select both room to visit and room type'),
|
|
140
|
+
});
|
|
141
|
+
return Promise.reject(new Error('Room to visit and room type are required'));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const selectedQueue = queueResults.find((q) => q.uuid === currentRoomToVisit);
|
|
145
|
+
const selectedQueueRoom = selectedQueue?.queueRooms?.find((room) => room.uuid === currentRoomType);
|
|
146
|
+
|
|
147
|
+
const queueUuid = selectedQueue?.uuid;
|
|
148
|
+
|
|
149
|
+
if (!queueUuid) {
|
|
150
|
+
showSnackbar({
|
|
151
|
+
title: t('queueEntryError', 'Error adding patient to the queue'),
|
|
152
|
+
kind: 'error',
|
|
153
|
+
isLowContrast: false,
|
|
154
|
+
subtitle: t('noQueueAssociated', 'No queue associated with selected room'),
|
|
155
|
+
});
|
|
156
|
+
return Promise.reject(new Error('No queue associated with selected room'));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return postQueueEntry(
|
|
160
|
+
visit.uuid,
|
|
161
|
+
queueUuid,
|
|
162
|
+
visit.patient.uuid,
|
|
163
|
+
priority,
|
|
164
|
+
defaultStatusConceptUuid,
|
|
165
|
+
sortWeight,
|
|
166
|
+
sessionLocation.uuid,
|
|
167
|
+
visitQueueNumberAttributeUuid,
|
|
168
|
+
currentRoomType,
|
|
169
|
+
)
|
|
170
|
+
.then(() => {
|
|
171
|
+
showSnackbar({
|
|
172
|
+
kind: 'success',
|
|
173
|
+
isLowContrast: true,
|
|
174
|
+
title: t('addedPatientToQueue', 'Added patient to {{queueRoom}}', {
|
|
175
|
+
queueRoom: selectedQueueRoom?.display,
|
|
176
|
+
}),
|
|
177
|
+
subtitle: t('queueEntryAddedSuccessfully', 'Queue entry added successfully'),
|
|
178
|
+
});
|
|
179
|
+
memoMutateQueueEntries();
|
|
180
|
+
})
|
|
181
|
+
.catch((error) => {
|
|
182
|
+
showSnackbar({
|
|
183
|
+
title: t('queueEntryError', 'Error adding patient to the queue'),
|
|
184
|
+
kind: 'error',
|
|
185
|
+
isLowContrast: false,
|
|
186
|
+
subtitle: error?.message,
|
|
187
|
+
});
|
|
188
|
+
throw error;
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
[
|
|
192
|
+
getValues,
|
|
193
|
+
queueResults,
|
|
194
|
+
priority,
|
|
195
|
+
defaultStatusConceptUuid,
|
|
196
|
+
sortWeight,
|
|
197
|
+
sessionLocation?.uuid,
|
|
198
|
+
visitQueueNumberAttributeUuid,
|
|
199
|
+
memoMutateQueueEntries,
|
|
200
|
+
t,
|
|
201
|
+
],
|
|
202
|
+
);
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
setOnSubmit(onSubmit);
|
|
205
|
+
}, [onSubmit, setOnSubmit]);
|
|
206
|
+
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (isDataLoaded && roomToVisit && memoizedQueueRooms.length > 0) {
|
|
209
|
+
const defaultRoomType = outPatienttriageUuid;
|
|
210
|
+
const currentRoomType = getValues('roomType');
|
|
211
|
+
|
|
212
|
+
if (currentRoomType === defaultRoomType) {
|
|
213
|
+
const isRoomTypeValid = memoizedQueueRooms.some((room) => room.uuid === defaultRoomType);
|
|
214
|
+
if (isRoomTypeValid) {
|
|
215
|
+
setValue('roomType', defaultRoomType, { shouldValidate: true });
|
|
216
|
+
} else {
|
|
217
|
+
setValue('roomType', '', { shouldValidate: false });
|
|
218
|
+
}
|
|
219
|
+
} else if (currentRoomType && currentRoomType !== '') {
|
|
220
|
+
const isRoomTypeValid = memoizedQueueRooms.some((room) => room.uuid === currentRoomType);
|
|
221
|
+
if (!isRoomTypeValid) {
|
|
222
|
+
setValue('roomType', '', { shouldValidate: isDataLoaded });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}, [roomToVisit, memoizedQueueRooms, isDataLoaded, setValue, getValues, outPatienttriageUuid]);
|
|
227
|
+
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (!roomToVisit && roomToVisit !== triageQueueUuid) {
|
|
230
|
+
setValue('roomType', '', { shouldValidate: false });
|
|
231
|
+
clearErrors('roomType');
|
|
232
|
+
}
|
|
233
|
+
}, [roomToVisit, setValue, clearErrors, triageQueueUuid]);
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<div>
|
|
237
|
+
<h1 className={styles.sectionTitle}>{t('roomToVisit', 'Room to Visit')}</h1>
|
|
238
|
+
<Stack>
|
|
239
|
+
<ResponsiveWrapper>
|
|
240
|
+
<FormGroup>
|
|
241
|
+
<Controller
|
|
242
|
+
name="roomToVisit"
|
|
243
|
+
control={control}
|
|
244
|
+
render={({ field }) =>
|
|
245
|
+
isLoading ? (
|
|
246
|
+
<SelectSkeleton />
|
|
247
|
+
) : (
|
|
248
|
+
<Select
|
|
249
|
+
{...field}
|
|
250
|
+
labelText=""
|
|
251
|
+
id="roomToVisit"
|
|
252
|
+
invalid={!!errors.roomToVisit && (touchedFields.roomToVisit || isSubmitted)}
|
|
253
|
+
invalidText={errors.roomToVisit?.message}
|
|
254
|
+
onChange={(event) => {
|
|
255
|
+
field.onChange(event.target.value);
|
|
256
|
+
if (event.target.value !== roomToVisit) {
|
|
257
|
+
setValue('roomType', '', { shouldValidate: false });
|
|
258
|
+
clearErrors('roomType');
|
|
259
|
+
}
|
|
260
|
+
}}>
|
|
261
|
+
<SelectItem text={t('selectRoomVisit', 'Select a room to visit')} value="" />
|
|
262
|
+
{memoizedQueueLocations?.map((location) => (
|
|
263
|
+
<SelectItem key={location.uuid} text={location.name} value={location.uuid}>
|
|
264
|
+
{location.name}
|
|
265
|
+
</SelectItem>
|
|
266
|
+
))}
|
|
267
|
+
</Select>
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
/>
|
|
271
|
+
</FormGroup>
|
|
272
|
+
</ResponsiveWrapper>
|
|
273
|
+
|
|
274
|
+
{roomToVisit && (
|
|
275
|
+
<FormGroup>
|
|
276
|
+
<Controller
|
|
277
|
+
name="roomType"
|
|
278
|
+
control={control}
|
|
279
|
+
render={({ field }) =>
|
|
280
|
+
isLoading ? (
|
|
281
|
+
<SelectSkeleton />
|
|
282
|
+
) : !memoizedQueueRooms?.length ? (
|
|
283
|
+
<InlineNotification
|
|
284
|
+
kind="error"
|
|
285
|
+
lowContrast
|
|
286
|
+
subtitle={t('configureRooms', 'Please configure rooms to continue.')}
|
|
287
|
+
title={t('noRoomsConfigured', 'No rooms configured')}
|
|
288
|
+
/>
|
|
289
|
+
) : (
|
|
290
|
+
<Select
|
|
291
|
+
{...field}
|
|
292
|
+
labelText=""
|
|
293
|
+
id="roomType"
|
|
294
|
+
invalid={!!errors.roomType && (touchedFields.roomType || isSubmitted)}
|
|
295
|
+
invalidText={errors.roomType?.message}
|
|
296
|
+
onChange={(event) => {
|
|
297
|
+
field.onChange(event.target.value);
|
|
298
|
+
}}>
|
|
299
|
+
<SelectItem text={t('selectRoomType', 'Select a room type')} value="" />
|
|
300
|
+
{memoizedQueueRooms?.map((room) => (
|
|
301
|
+
<SelectItem key={room.uuid} text={room.name} value={room.uuid}>
|
|
302
|
+
{room.name}
|
|
303
|
+
</SelectItem>
|
|
304
|
+
))}
|
|
305
|
+
</Select>
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
/>
|
|
309
|
+
</FormGroup>
|
|
310
|
+
)}
|
|
311
|
+
</Stack>
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
export default QueueFields;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useConfig, Visit } from '@openmrs/esm-framework';
|
|
3
|
+
import QueueFields from './queue-fields.component';
|
|
4
|
+
import { ServiceQueueConfig } from '../config-schema';
|
|
5
|
+
|
|
6
|
+
interface VisitFormCallbacks {
|
|
7
|
+
onVisitCreatedOrUpdated: (visit: Visit) => Promise<any>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface VisitFormQueueFieldsProps {
|
|
11
|
+
setVisitFormCallbacks: (callbacks: VisitFormCallbacks) => void;
|
|
12
|
+
visitFormOpenedFrom: string;
|
|
13
|
+
patientUuid: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ServiceQueueCheckIn: React.FC<VisitFormQueueFieldsProps> = (props) => {
|
|
17
|
+
const config = useConfig<ServiceQueueConfig>();
|
|
18
|
+
const { setVisitFormCallbacks, patientUuid, visitFormOpenedFrom } = props;
|
|
19
|
+
|
|
20
|
+
if (!config.showServiceQueueFields) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return <QueueFields setOnSubmit={(onSubmit) => setVisitFormCallbacks({ onVisitCreatedOrUpdated: onSubmit })} />;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default ServiceQueueCheckIn;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { QueueResponse } from '../type';
|
|
4
|
+
import { useSWRConfig } from 'swr/_internal';
|
|
5
|
+
|
|
6
|
+
export const useQueue = () => {
|
|
7
|
+
const customRepresentation = 'custom:(uuid,display,queueRooms:(uuid,display))';
|
|
8
|
+
const apiUrl = `${restBaseUrl}/queue?v=${customRepresentation}`;
|
|
9
|
+
|
|
10
|
+
const { data, ...rest } = useSWR<FetchResponse<QueueResponse>>(apiUrl, openmrsFetch);
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
queues: data?.data?.results ?? [],
|
|
14
|
+
...rest,
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export async function generateVisitQueueNumber(
|
|
19
|
+
location: string,
|
|
20
|
+
visitUuid: string,
|
|
21
|
+
queueUuid: string,
|
|
22
|
+
visitQueueNumberAttributeUuid: string,
|
|
23
|
+
) {
|
|
24
|
+
const abortController = new AbortController();
|
|
25
|
+
|
|
26
|
+
await openmrsFetch(
|
|
27
|
+
`${restBaseUrl}/queue-entry-number?location=${location}&queue=${queueUuid}&visit=${visitUuid}&visitAttributeType=${visitQueueNumberAttributeUuid}`,
|
|
28
|
+
{
|
|
29
|
+
method: 'GET',
|
|
30
|
+
headers: {
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
},
|
|
33
|
+
signal: abortController.signal,
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function postQueueEntry(
|
|
39
|
+
visitUuid: string,
|
|
40
|
+
queueUuid: string,
|
|
41
|
+
patientUuid: string,
|
|
42
|
+
priority: string,
|
|
43
|
+
status: string,
|
|
44
|
+
sortWeight: number,
|
|
45
|
+
locationUuid: string,
|
|
46
|
+
visitQueueNumberAttributeUuid: string,
|
|
47
|
+
queueRoomUuid: string,
|
|
48
|
+
) {
|
|
49
|
+
const abortController = new AbortController();
|
|
50
|
+
|
|
51
|
+
await Promise.all([generateVisitQueueNumber(locationUuid, visitUuid, queueUuid, visitQueueNumberAttributeUuid)]);
|
|
52
|
+
|
|
53
|
+
return openmrsFetch(`${restBaseUrl}/visit-queue-entry`, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: {
|
|
56
|
+
'Content-Type': 'application/json',
|
|
57
|
+
},
|
|
58
|
+
signal: abortController.signal,
|
|
59
|
+
body: {
|
|
60
|
+
visit: { uuid: visitUuid },
|
|
61
|
+
queueEntry: {
|
|
62
|
+
status: {
|
|
63
|
+
uuid: status,
|
|
64
|
+
},
|
|
65
|
+
priority: {
|
|
66
|
+
uuid: priority,
|
|
67
|
+
},
|
|
68
|
+
queue: {
|
|
69
|
+
uuid: queueUuid,
|
|
70
|
+
},
|
|
71
|
+
queueRoom: {
|
|
72
|
+
uuid: queueRoomUuid,
|
|
73
|
+
},
|
|
74
|
+
patient: {
|
|
75
|
+
uuid: patientUuid,
|
|
76
|
+
},
|
|
77
|
+
startedAt: new Date(),
|
|
78
|
+
sortWeight: sortWeight,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function useMutateQueueEntries() {
|
|
85
|
+
const { mutate } = useSWRConfig();
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
mutateQueueEntries: () => {
|
|
89
|
+
return mutate((key) => {
|
|
90
|
+
return (
|
|
91
|
+
typeof key === 'string' &&
|
|
92
|
+
(key.includes(`${restBaseUrl}/queue-entry`) || key.includes(`${restBaseUrl}/visit-queue-entry`))
|
|
93
|
+
);
|
|
94
|
+
}).then(() => {
|
|
95
|
+
window.dispatchEvent(new CustomEvent('queue-entry-updated'));
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.sectionTitle {
|
|
6
|
+
@include type.type-style('heading-compact-02');
|
|
7
|
+
color: colors.$gray-70;
|
|
8
|
+
margin: 0 0 layout.$spacing-03 0;
|
|
9
|
+
|
|
10
|
+
:global(.omrs-breakpoint-lt-tablet) & {
|
|
11
|
+
@include type.type-style('heading-compact-01');
|
|
12
|
+
margin: 0 0 layout.$spacing-05 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
:global(.omrs-breakpoint-lt-desktop) & {
|
|
16
|
+
margin: 0 0 layout.$spacing-04 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"addedPatientToQueue": "Added patient to {{queueRoom}}",
|
|
3
|
+
"configureRooms": "Please configure rooms to continue.",
|
|
4
|
+
"noQueueAssociated": "No queue associated with selected room",
|
|
5
|
+
"noRoomsConfigured": "No rooms configured",
|
|
6
|
+
"queueEntryAddedSuccessfully": "Queue entry added successfully",
|
|
7
|
+
"queueEntryError": "Error adding patient to the queue",
|
|
8
|
+
"roomToVisit": "Room to Visit",
|
|
9
|
+
"roomToVisitRequired": "Room to visit is required",
|
|
10
|
+
"roomTypeRequired": "Room type is required",
|
|
11
|
+
"selectRoomAndType": "Please select both room to visit and room type",
|
|
12
|
+
"selectRoomType": "Select a room type",
|
|
13
|
+
"selectRoomVisit": "Select a room to visit"
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"addedPatientToQueue": "Added patient to {{queueRoom}}",
|
|
3
|
+
"configureRooms": "Please configure rooms to continue.",
|
|
4
|
+
"noQueueAssociated": "No queue associated with selected room",
|
|
5
|
+
"noRoomsConfigured": "No rooms configured",
|
|
6
|
+
"queueEntryAddedSuccessfully": "Queue entry added successfully",
|
|
7
|
+
"queueEntryError": "Error adding patient to the queue",
|
|
8
|
+
"roomToVisit": "Room to Visit",
|
|
9
|
+
"roomToVisitRequired": "Room to visit is required",
|
|
10
|
+
"roomTypeRequired": "Room type is required",
|
|
11
|
+
"selectRoomAndType": "Please select both room to visit and room type",
|
|
12
|
+
"selectRoomType": "Select a room type",
|
|
13
|
+
"selectRoomVisit": "Select a room to visit"
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"addedPatientToQueue": "Added patient to {{queueRoom}}",
|
|
3
|
+
"configureRooms": "Please configure rooms to continue.",
|
|
4
|
+
"noQueueAssociated": "No queue associated with selected room",
|
|
5
|
+
"noRoomsConfigured": "No rooms configured",
|
|
6
|
+
"queueEntryAddedSuccessfully": "Queue entry added successfully",
|
|
7
|
+
"queueEntryError": "Error adding patient to the queue",
|
|
8
|
+
"roomToVisit": "Room to Visit",
|
|
9
|
+
"roomToVisitRequired": "Room to visit is required",
|
|
10
|
+
"roomTypeRequired": "Room type is required",
|
|
11
|
+
"selectRoomAndType": "Please select both room to visit and room type",
|
|
12
|
+
"selectRoomType": "Select a room type",
|
|
13
|
+
"selectRoomVisit": "Select a room to visit"
|
|
14
|
+
}
|