@kenyaemr/esm-express-workflow-app 5.4.3 → 5.4.4-pre.12
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/.turbo/turbo-build.log +4 -4
- package/dist/971.js +1 -0
- package/dist/971.js.map +1 -0
- package/dist/kenyaemr-esm-express-workflow-app.js +3 -3
- package/dist/kenyaemr-esm-express-workflow-app.js.buildmanifest.json +29 -29
- package/dist/kenyaemr-esm-express-workflow-app.js.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +3 -2
- package/src/components/consultation/consultation.component.tsx +41 -177
- package/src/components/consultation/consultation.resource.ts +17 -7
- package/src/components/consultation/consultation.utils.tsx +222 -0
- package/src/components/mch/mch.consultation.component.tsx +27 -15
- package/src/components/mch/mch.triage.component.tsx +42 -33
- package/src/components/triage/triage.component.tsx +35 -18
- package/src/components/triage/triage.resource.ts +56 -50
- package/src/config-schema.ts +6 -0
- package/src/hooks/useServiceQueues.tsx +93 -25
- package/src/shared/patient-chart/patient-chart.resources.ts +8 -12
- package/src/shared/patient-chart/patient-summary-dashboard/patient-summary-dashboard.component.tsx +1 -2
- package/src/shared/queue/queue-entry/queue-entry-table.component.tsx +73 -81
- package/src/shared/queue/queue-summary-cards.component.tsx +32 -0
- package/src/shared/queue/queue-tab.component.tsx +35 -40
- package/src/shared/queue/queue-tab.scss +1 -0
- package/src/types/index.ts +16 -2
- package/dist/24.js +0 -1
- package/dist/24.js.map +0 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
1
2
|
import { IconButton, InlineLoading } from '@carbon/react';
|
|
2
3
|
import { Renew } from '@carbon/react/icons';
|
|
3
4
|
import { useConfig } from '@openmrs/esm-framework';
|
|
4
5
|
import { EmptyState } from '@openmrs/esm-patient-common-lib';
|
|
5
|
-
import React, { useCallback, useMemo, useState } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
|
+
|
|
7
8
|
import { ExpressWorkflowConfig } from '../../config-schema';
|
|
8
9
|
import { useQueues } from '../../hooks/useServiceQueues';
|
|
9
10
|
import QueueTab from '../../shared/queue/queue-tab.component';
|
|
11
|
+
import QueueSummaryCards, { QueueSummaryCard } from '../../shared/queue/queue-summary-cards.component';
|
|
10
12
|
import { Queue, QueueFilter } from '../../types';
|
|
11
13
|
import {
|
|
12
14
|
useConsultationQueueMetrics,
|
|
@@ -17,7 +19,7 @@ import styles from '../consultation/consultation.scss';
|
|
|
17
19
|
|
|
18
20
|
const MCHConsultation: React.FC = () => {
|
|
19
21
|
const { t } = useTranslation();
|
|
20
|
-
const { queues, isLoading
|
|
22
|
+
const { queues, isLoading: isLoadingQueues } = useQueues();
|
|
21
23
|
const [currQueue, setCurrQueue] = useState<Queue>();
|
|
22
24
|
const [filters, setFilters] = useState<Array<QueueFilter>>([]);
|
|
23
25
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
@@ -26,7 +28,12 @@ const MCHConsultation: React.FC = () => {
|
|
|
26
28
|
priorities: { emergencyPriorityConceptUuid, urgentPriorityConceptUuid, notUrgentPriorityConceptUuid },
|
|
27
29
|
queueServiceConceptUuids,
|
|
28
30
|
} = useConfig<ExpressWorkflowConfig>();
|
|
29
|
-
const {
|
|
31
|
+
const {
|
|
32
|
+
data: totalVisits,
|
|
33
|
+
isLoading: isLoadingTotalVisits,
|
|
34
|
+
isValidating: isValidatingTotalVisits,
|
|
35
|
+
} = useTotalVisits();
|
|
36
|
+
|
|
30
37
|
const consultationQueues = queues.filter(
|
|
31
38
|
(queue) =>
|
|
32
39
|
queue.service.uuid === queueServiceConceptUuids.consultationService &&
|
|
@@ -38,21 +45,21 @@ const MCHConsultation: React.FC = () => {
|
|
|
38
45
|
const {
|
|
39
46
|
waitingEntries,
|
|
40
47
|
isLoading: isLoadingQueueMetrics,
|
|
41
|
-
error: waitingError,
|
|
42
48
|
emergencyEntries,
|
|
43
49
|
urgentEntries,
|
|
44
50
|
notUrgentEntries,
|
|
51
|
+
isValidating: isValidatingQueueMetrics,
|
|
45
52
|
} = useConsultationQueueMetrics(activeQueue);
|
|
46
53
|
const {
|
|
47
54
|
awaitingCount,
|
|
48
55
|
completedCount,
|
|
49
|
-
totalCount,
|
|
50
56
|
lab,
|
|
51
57
|
radiology,
|
|
52
58
|
procedures,
|
|
53
59
|
isLoading: isLoadingInvestigations,
|
|
54
60
|
refresh: refreshInvestigations,
|
|
55
61
|
investigationCategorizedEntries,
|
|
62
|
+
isValidating: isValidatingInvestigations,
|
|
56
63
|
} = useInvestigationStats(activeQueue);
|
|
57
64
|
// Single refresh handler for all investigations
|
|
58
65
|
const handleRefresh = useCallback(async () => {
|
|
@@ -66,7 +73,7 @@ const MCHConsultation: React.FC = () => {
|
|
|
66
73
|
}
|
|
67
74
|
}, [refreshInvestigations]);
|
|
68
75
|
|
|
69
|
-
const cards = useMemo(
|
|
76
|
+
const cards: Array<QueueSummaryCard> = useMemo(
|
|
70
77
|
() => [
|
|
71
78
|
{
|
|
72
79
|
title: t('awaitingConsultation', 'Awaiting consultation'),
|
|
@@ -258,7 +265,10 @@ const MCHConsultation: React.FC = () => {
|
|
|
258
265
|
],
|
|
259
266
|
);
|
|
260
267
|
|
|
261
|
-
|
|
268
|
+
const isLoading = isLoadingQueues || isLoadingTotalVisits || isLoadingInvestigations || isLoadingQueueMetrics;
|
|
269
|
+
const isValidating = isValidatingTotalVisits || isValidatingInvestigations || isValidatingQueueMetrics;
|
|
270
|
+
|
|
271
|
+
if (isLoading && !isValidating) {
|
|
262
272
|
return <InlineLoading description={t('loadingQueues', 'Loading queues...')} />;
|
|
263
273
|
}
|
|
264
274
|
|
|
@@ -271,14 +281,16 @@ const MCHConsultation: React.FC = () => {
|
|
|
271
281
|
);
|
|
272
282
|
}
|
|
273
283
|
return (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
284
|
+
<>
|
|
285
|
+
<QueueSummaryCards cards={cards} />
|
|
286
|
+
<QueueTab
|
|
287
|
+
queues={consultationQueues}
|
|
288
|
+
navigatePath="mch"
|
|
289
|
+
usePatientChart
|
|
290
|
+
filters={filters}
|
|
291
|
+
onFiltersChanged={setFilters}
|
|
292
|
+
/>
|
|
293
|
+
</>
|
|
282
294
|
);
|
|
283
295
|
};
|
|
284
296
|
|
|
@@ -5,13 +5,14 @@ import { ErrorState, useConfig } from '@openmrs/esm-framework';
|
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { useQueues } from '../../hooks/useServiceQueues';
|
|
7
7
|
import QueueTab from '../../shared/queue/queue-tab.component';
|
|
8
|
+
import QueueSummaryCards, { QueueSummaryCard } from '../../shared/queue/queue-summary-cards.component';
|
|
8
9
|
import { Queue, QueueFilter } from '../../types';
|
|
9
10
|
import { useTriageQueuesMetrics } from '../triage/triage.resource';
|
|
10
11
|
import { ExpressWorkflowConfig } from '../../config-schema';
|
|
11
12
|
|
|
12
13
|
const MCHTriage: React.FC = () => {
|
|
13
14
|
const { t } = useTranslation();
|
|
14
|
-
const { queues, isLoading, error } = useQueues();
|
|
15
|
+
const { queues, isLoading: isLoadingQueues, error } = useQueues();
|
|
15
16
|
const [currQueue, setCurrQueue] = useState<Queue>();
|
|
16
17
|
const [filters, setFilters] = useState<Array<QueueFilter>>([]);
|
|
17
18
|
const {
|
|
@@ -31,11 +32,15 @@ const MCHTriage: React.FC = () => {
|
|
|
31
32
|
const {
|
|
32
33
|
error: metricsError,
|
|
33
34
|
isLoading: isLoadingMetrics,
|
|
34
|
-
|
|
35
|
+
attendedToEntries,
|
|
35
36
|
waitingEntries,
|
|
37
|
+
isValidating: isValidatingMetrics,
|
|
36
38
|
} = useTriageQueuesMetrics(activeQueue);
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
const isLoading = isLoadingQueues || isLoadingMetrics;
|
|
41
|
+
const isValidating = isValidatingMetrics;
|
|
42
|
+
|
|
43
|
+
if (isLoading && !isValidating) {
|
|
39
44
|
return <InlineLoading description={t('loadingQueues', 'Loading queues...')} />;
|
|
40
45
|
}
|
|
41
46
|
|
|
@@ -43,37 +48,41 @@ const MCHTriage: React.FC = () => {
|
|
|
43
48
|
return <ErrorState error={error ?? metricsError} headerTitle={t('errorLoadingQueues', 'Error loading queues')} />;
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
const cards: Array<QueueSummaryCard> = [
|
|
52
|
+
{
|
|
53
|
+
title: t('patientsAwaiting', 'Patient awaiting'),
|
|
54
|
+
value: waitingEntries?.length?.toString(),
|
|
55
|
+
onClick: () => {
|
|
56
|
+
setFilters((prevFilters) => [
|
|
57
|
+
...prevFilters.filter((f) => f.key !== 'status'),
|
|
58
|
+
{ key: 'status', value: waitingStatus, label: t('waiting', 'Waiting') },
|
|
59
|
+
]);
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
title: t('patientAttended', 'Patient attended to'),
|
|
64
|
+
value: attendedToEntries?.length?.toString(),
|
|
65
|
+
onClick: () => {
|
|
66
|
+
setFilters((prevFilters) => [
|
|
67
|
+
...prevFilters.filter((f) => f.key !== 'status'),
|
|
68
|
+
{ key: 'status', value: `${finishedStatus},${inServiceStatus}`, label: t('attendedTo', 'Attended to') },
|
|
69
|
+
]);
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
|
|
46
74
|
return (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
]);
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
title: t('patientAttended', 'Patient attended to'),
|
|
63
|
-
value: attendedtoEntries?.length?.toString(),
|
|
64
|
-
onClick: () => {
|
|
65
|
-
setFilters((prevFilters) => [
|
|
66
|
-
...prevFilters.filter((f) => f.key !== 'status'),
|
|
67
|
-
{ key: 'status', value: `${finishedStatus},${inServiceStatus}`, label: t('attendedTo', 'Attended to') },
|
|
68
|
-
]);
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
]}
|
|
72
|
-
onTabChanged={setCurrQueue}
|
|
73
|
-
usePatientChart
|
|
74
|
-
filters={filters}
|
|
75
|
-
onFiltersChanged={setFilters}
|
|
76
|
-
/>
|
|
75
|
+
<>
|
|
76
|
+
<QueueSummaryCards cards={cards} />
|
|
77
|
+
<QueueTab
|
|
78
|
+
queues={triageQueues}
|
|
79
|
+
navigatePath="mch"
|
|
80
|
+
onTabChanged={setCurrQueue}
|
|
81
|
+
usePatientChart
|
|
82
|
+
filters={filters}
|
|
83
|
+
onFiltersChanged={setFilters}
|
|
84
|
+
/>
|
|
85
|
+
</>
|
|
77
86
|
);
|
|
78
87
|
};
|
|
79
88
|
|
|
@@ -13,6 +13,7 @@ import { InlineLoading } from '@carbon/react';
|
|
|
13
13
|
|
|
14
14
|
import { useQueues } from '../../hooks/useServiceQueues';
|
|
15
15
|
import QueueTab from '../../shared/queue/queue-tab.component';
|
|
16
|
+
import QueueSummaryCards, { QueueSummaryCard } from '../../shared/queue/queue-summary-cards.component';
|
|
16
17
|
import styles from './triage.scss';
|
|
17
18
|
import { Queue, QueueFilter } from '../../types';
|
|
18
19
|
import { useTriageQueuesMetrics } from './triage.resource';
|
|
@@ -38,13 +39,19 @@ export default Triage;
|
|
|
38
39
|
|
|
39
40
|
const TriageQueueTab: React.FC = () => {
|
|
40
41
|
const { t } = useTranslation();
|
|
41
|
-
const [currQueue, setCurrQueue] = useState<Queue>();
|
|
42
|
-
const { queues, isLoading, error } = useQueues();
|
|
43
|
-
const [filters, setFilters] = useState<Array<QueueFilter>>([]);
|
|
44
42
|
const {
|
|
45
43
|
queueStatusConceptUuids: { finishedStatus, waitingStatus, inServiceStatus },
|
|
46
44
|
queueServiceConceptUuids,
|
|
47
45
|
} = useConfig<ExpressWorkflowConfig>();
|
|
46
|
+
const [currQueue, setCurrQueue] = useState<Queue>();
|
|
47
|
+
const [filters, setFilters] = useState<Array<QueueFilter>>([]);
|
|
48
|
+
|
|
49
|
+
const {
|
|
50
|
+
queues,
|
|
51
|
+
isLoading: isLoadingQueues,
|
|
52
|
+
error: errorLoadingQueues,
|
|
53
|
+
isValidating: isValidatingQueues,
|
|
54
|
+
} = useQueues();
|
|
48
55
|
|
|
49
56
|
const triageQueues = queues
|
|
50
57
|
.filter(
|
|
@@ -60,17 +67,25 @@ const TriageQueueTab: React.FC = () => {
|
|
|
60
67
|
error: metricsError,
|
|
61
68
|
isLoading: isLoadingMetrics,
|
|
62
69
|
waitingEntries,
|
|
63
|
-
|
|
70
|
+
attendedToEntries,
|
|
71
|
+
isValidating: isValidatingMetrics,
|
|
64
72
|
} = useTriageQueuesMetrics(activeQueue);
|
|
65
73
|
|
|
66
|
-
|
|
74
|
+
const isLoading = (isLoadingQueues || isLoadingMetrics) && !isValidatingQueues && !isValidatingMetrics;
|
|
75
|
+
|
|
76
|
+
if (isLoading) {
|
|
67
77
|
return <InlineLoading description={t('loadingQueues', 'Loading queues...')} />;
|
|
68
78
|
}
|
|
69
79
|
|
|
70
|
-
if (metricsError) {
|
|
71
|
-
return
|
|
80
|
+
if (metricsError || errorLoadingQueues) {
|
|
81
|
+
return (
|
|
82
|
+
<ErrorState
|
|
83
|
+
error={metricsError ?? errorLoadingQueues}
|
|
84
|
+
headerTitle={t('errorLoadingQueues', 'Error loading queues')}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
72
87
|
}
|
|
73
|
-
const cards = [
|
|
88
|
+
const cards: Array<QueueSummaryCard> = [
|
|
74
89
|
{
|
|
75
90
|
title: t('clientsPatientsWaiting', 'Clients/Patients waiting'),
|
|
76
91
|
value: waitingEntries.length.toString(),
|
|
@@ -83,7 +98,7 @@ const TriageQueueTab: React.FC = () => {
|
|
|
83
98
|
},
|
|
84
99
|
{
|
|
85
100
|
title: t('clientsPatientsAttendedTo', 'Clients/Patients attended to'),
|
|
86
|
-
value:
|
|
101
|
+
value: attendedToEntries.length.toString(),
|
|
87
102
|
onClick: () => {
|
|
88
103
|
setFilters((prevFilters) => [
|
|
89
104
|
...prevFilters.filter((f) => f.key !== 'status'),
|
|
@@ -94,14 +109,16 @@ const TriageQueueTab: React.FC = () => {
|
|
|
94
109
|
];
|
|
95
110
|
|
|
96
111
|
return (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
<>
|
|
113
|
+
<QueueSummaryCards cards={cards} />
|
|
114
|
+
<QueueTab
|
|
115
|
+
queues={triageQueues}
|
|
116
|
+
navigatePath="triage"
|
|
117
|
+
usePatientChart
|
|
118
|
+
onTabChanged={setCurrQueue}
|
|
119
|
+
filters={filters}
|
|
120
|
+
onFiltersChanged={setFilters}
|
|
121
|
+
/>
|
|
122
|
+
</>
|
|
106
123
|
);
|
|
107
124
|
};
|
|
@@ -1,62 +1,68 @@
|
|
|
1
|
-
import { useConfig } from '@openmrs/esm-framework';
|
|
2
1
|
import { useMemo } from 'react';
|
|
3
|
-
import
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
4
|
+
|
|
5
|
+
import { type ExpressWorkflowConfig } from '../../config-schema';
|
|
4
6
|
import { useQueueEntries } from '../../hooks/useServiceQueues';
|
|
5
|
-
import { Queue } from '../../types';
|
|
7
|
+
import { type Queue } from '../../types';
|
|
6
8
|
|
|
7
9
|
export const useTriageQueuesMetrics = (queue?: Queue) => {
|
|
8
|
-
const {
|
|
9
|
-
|
|
10
|
-
queueEntries: waitingEntries,
|
|
11
|
-
isLoading: isLoadingWaiting,
|
|
12
|
-
error: waitingError,
|
|
13
|
-
} = useQueueEntries({
|
|
14
|
-
service: [queueServiceConceptUuids.triageService],
|
|
15
|
-
statuses: [queueStatusConceptUuids.waitingStatus],
|
|
16
|
-
location: queue?.location?.uuid ? [queue.location.uuid] : undefined,
|
|
17
|
-
});
|
|
18
|
-
const {
|
|
19
|
-
queueEntries: inServiceEntries,
|
|
20
|
-
isLoading: isLoadingInService,
|
|
21
|
-
error: inServiceError,
|
|
22
|
-
} = useQueueEntries({
|
|
23
|
-
service: [queueServiceConceptUuids.triageService],
|
|
24
|
-
statuses: [queueStatusConceptUuids.inServiceStatus],
|
|
25
|
-
location: queue?.location?.uuid ? [queue.location.uuid] : undefined,
|
|
26
|
-
});
|
|
10
|
+
const { queueStatusConceptUuids } = useConfig<ExpressWorkflowConfig>();
|
|
11
|
+
|
|
27
12
|
const {
|
|
28
|
-
queueEntries:
|
|
29
|
-
isLoading
|
|
30
|
-
error
|
|
13
|
+
queueEntries: allTriageEntries,
|
|
14
|
+
isLoading,
|
|
15
|
+
error,
|
|
16
|
+
isValidating: isValidatingQueueEntries,
|
|
31
17
|
} = useQueueEntries({
|
|
32
|
-
service: [queueServiceConceptUuids.triageService],
|
|
33
|
-
statuses: [queueStatusConceptUuids.finishedStatus],
|
|
34
|
-
location: queue?.location?.uuid ? [queue.location.uuid] : undefined,
|
|
35
|
-
});
|
|
36
|
-
const { queueEntries: attendedtoEntries } = useQueueEntries({
|
|
37
|
-
service: [queueServiceConceptUuids.triageService],
|
|
38
|
-
statuses: [queueStatusConceptUuids.inServiceStatus, queueStatusConceptUuids.finishedStatus],
|
|
39
18
|
location: queue?.location?.uuid ? [queue.location.uuid] : undefined,
|
|
19
|
+
startedOnOrAfter: dayjs().subtract(24, 'hour').format('YYYY-MM-DD HH:mm:ss'),
|
|
40
20
|
});
|
|
41
21
|
|
|
22
|
+
const queueUuid = queue?.uuid;
|
|
23
|
+
|
|
24
|
+
const waitingEntries = useMemo(
|
|
25
|
+
() =>
|
|
26
|
+
allTriageEntries.filter(
|
|
27
|
+
(entry) => entry?.queue?.uuid === queueUuid && entry?.status?.uuid === queueStatusConceptUuids.waitingStatus,
|
|
28
|
+
),
|
|
29
|
+
[allTriageEntries, queueUuid, queueStatusConceptUuids.waitingStatus],
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const inServiceEntries = useMemo(
|
|
33
|
+
() =>
|
|
34
|
+
allTriageEntries.filter(
|
|
35
|
+
(entry) => entry?.queue?.uuid === queueUuid && entry?.status?.uuid === queueStatusConceptUuids.inServiceStatus,
|
|
36
|
+
),
|
|
37
|
+
[allTriageEntries, queueUuid, queueStatusConceptUuids.inServiceStatus],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const finishedEntries = useMemo(
|
|
41
|
+
() =>
|
|
42
|
+
allTriageEntries.filter(
|
|
43
|
+
(entry) => entry?.queue?.uuid === queueUuid && entry?.status?.uuid === queueStatusConceptUuids.finishedStatus,
|
|
44
|
+
),
|
|
45
|
+
[allTriageEntries, queueUuid, queueStatusConceptUuids.finishedStatus],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const attendedToEntries = useMemo(
|
|
49
|
+
() =>
|
|
50
|
+
allTriageEntries.filter(
|
|
51
|
+
(entry) =>
|
|
52
|
+
entry?.queue?.uuid === queueUuid &&
|
|
53
|
+
(entry?.status?.uuid === queueStatusConceptUuids.inServiceStatus ||
|
|
54
|
+
entry?.status?.uuid === queueStatusConceptUuids.finishedStatus),
|
|
55
|
+
),
|
|
56
|
+
[allTriageEntries, queueUuid, queueStatusConceptUuids.inServiceStatus, queueStatusConceptUuids.finishedStatus],
|
|
57
|
+
);
|
|
58
|
+
|
|
42
59
|
return {
|
|
43
|
-
isLoading
|
|
44
|
-
waitingEntries
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
[inServiceEntries, queue],
|
|
51
|
-
),
|
|
52
|
-
finishedEntries: useMemo(
|
|
53
|
-
() => finishedEntries.filter((entry) => entry?.queue?.uuid === queue?.uuid),
|
|
54
|
-
[finishedEntries, queue],
|
|
55
|
-
),
|
|
56
|
-
attendedtoEntries: useMemo(
|
|
57
|
-
() => attendedtoEntries.filter((entry) => entry?.queue?.uuid === queue?.uuid),
|
|
58
|
-
[attendedtoEntries, queue],
|
|
59
|
-
),
|
|
60
|
-
error: finishedError ?? inServiceError ?? waitingError,
|
|
60
|
+
isLoading,
|
|
61
|
+
waitingEntries,
|
|
62
|
+
inServiceEntries,
|
|
63
|
+
finishedEntries,
|
|
64
|
+
attendedToEntries,
|
|
65
|
+
error,
|
|
66
|
+
isValidating: isValidatingQueueEntries,
|
|
61
67
|
};
|
|
62
68
|
};
|
package/src/config-schema.ts
CHANGED
|
@@ -176,6 +176,11 @@ export const configSchema = {
|
|
|
176
176
|
_description: 'The UUID of the outpatient visit type.',
|
|
177
177
|
_default: '3371a4d4-f66f-4454-a86d-92c7b3da990c',
|
|
178
178
|
},
|
|
179
|
+
inPatientVisitTypeUuid: {
|
|
180
|
+
_type: Type.String,
|
|
181
|
+
_description: 'The UUID of the in-patient visit type.',
|
|
182
|
+
_default: 'a73e2ac6-263b-47fc-99fc-e0f2c09fc914',
|
|
183
|
+
},
|
|
179
184
|
};
|
|
180
185
|
|
|
181
186
|
export type ExpressWorkflowConfig = {
|
|
@@ -231,4 +236,5 @@ export type ExpressWorkflowConfig = {
|
|
|
231
236
|
labourWard: string;
|
|
232
237
|
};
|
|
233
238
|
outpatientVisitTypeUuid: string;
|
|
239
|
+
inPatientVisitTypeUuid: string;
|
|
234
240
|
};
|
|
@@ -1,68 +1,136 @@
|
|
|
1
|
-
import { openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework';
|
|
2
|
-
import dayjs from 'dayjs';
|
|
3
1
|
import { useMemo } from 'react';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
2
|
+
import useSWRImmutable from 'swr/immutable';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import { openmrsFetch, restBaseUrl, useConfig, useOpenmrsPagination } from '@openmrs/esm-framework';
|
|
5
|
+
|
|
6
|
+
import { type ExpressWorkflowConfig } from '../config-schema';
|
|
7
|
+
import { QueueEntriesPagination, type Queue, type QueueEntry, type QueueEntryFilters } from '../types/index';
|
|
8
|
+
|
|
9
|
+
const QUEUE_LIST_REP =
|
|
10
|
+
'custom:(uuid,display,name,location:(uuid,display),service:(uuid,display),queueRooms:(uuid,display))';
|
|
11
|
+
|
|
12
|
+
const SWR_QUEUE_OPTIONS = {
|
|
13
|
+
dedupingInterval: 5000,
|
|
14
|
+
revalidateOnFocus: false,
|
|
15
|
+
} as const;
|
|
7
16
|
|
|
8
17
|
export const useQueues = () => {
|
|
9
|
-
const
|
|
10
|
-
|
|
18
|
+
const queueListKey = `/ws/rest/v1/queue?v=${encodeURIComponent(QUEUE_LIST_REP)}`;
|
|
19
|
+
const { data, isLoading, error, isValidating } = useSWRImmutable<{ data: { results: Array<Queue> } }>(
|
|
20
|
+
queueListKey,
|
|
11
21
|
openmrsFetch,
|
|
22
|
+
SWR_QUEUE_OPTIONS,
|
|
12
23
|
);
|
|
13
24
|
|
|
14
25
|
return {
|
|
15
26
|
queues: data?.data?.results || [],
|
|
16
27
|
isLoading,
|
|
17
28
|
error,
|
|
29
|
+
isValidating,
|
|
18
30
|
};
|
|
19
31
|
};
|
|
20
32
|
|
|
21
|
-
export const useQueueEntries = (filters?: QueueEntryFilters) => {
|
|
33
|
+
export const useQueueEntries = (filters?: QueueEntryFilters, defaultPageSize: number = 100) => {
|
|
22
34
|
const { outpatientVisitTypeUuid } = useConfig<ExpressWorkflowConfig>();
|
|
23
35
|
const repString =
|
|
24
|
-
'custom:(uuid,
|
|
36
|
+
'custom:(uuid,queue:(uuid,display,name,location:(uuid,display)),status:(uuid,display),patient:(uuid,person:(uuid,display),identifiers),visit:(uuid,visitType:(uuid),attributes:(uuid,value,attributeType:(uuid))),priority:(uuid,display),priorityComment,startedAt,previousQueueEntry:(uuid,queue:(uuid,display)))';
|
|
25
37
|
|
|
26
|
-
const
|
|
38
|
+
const queryString = useMemo(() => {
|
|
27
39
|
const params = new URLSearchParams();
|
|
28
40
|
params.append('v', repString);
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
const merged = {
|
|
43
|
+
...filters,
|
|
44
|
+
status: filters?.statuses ?? [],
|
|
45
|
+
statuses: undefined,
|
|
46
|
+
startedOnOrAfter: filters?.startedOnOrAfter ?? undefined,
|
|
47
|
+
startedOnOrBefore: filters?.startedOnOrBefore ?? undefined,
|
|
48
|
+
};
|
|
49
|
+
if (merged) {
|
|
50
|
+
Object.entries(merged).forEach(([key, value]) => {
|
|
51
|
+
if (key === 'statuses' || value === undefined || value === null) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
[...value].sort((a, b) => String(a).localeCompare(String(b))).forEach((v) => params.append(key, v));
|
|
56
|
+
} else {
|
|
57
|
+
params.append(key, String(value));
|
|
38
58
|
}
|
|
39
59
|
});
|
|
40
60
|
}
|
|
41
61
|
|
|
42
|
-
if (
|
|
62
|
+
if (filters?.isEnded == null) {
|
|
43
63
|
params.append('isEnded', 'false');
|
|
44
64
|
}
|
|
45
65
|
|
|
46
66
|
return params.toString();
|
|
47
|
-
}
|
|
67
|
+
}, [
|
|
68
|
+
filters?.location?.join(','),
|
|
69
|
+
filters?.service?.join(','),
|
|
70
|
+
[...(filters?.statuses ?? [])].sort((a, b) => String(a).localeCompare(String(b))).join(','),
|
|
71
|
+
filters?.priorities?.join(','),
|
|
72
|
+
filters?.patient,
|
|
73
|
+
filters?.visit,
|
|
74
|
+
filters?.hasVisit,
|
|
75
|
+
filters?.isEnded,
|
|
76
|
+
filters?.locationsWaitingFor?.join(','),
|
|
77
|
+
filters?.providersWaitingFor?.join(','),
|
|
78
|
+
filters?.queuesComingFrom?.join(','),
|
|
79
|
+
]);
|
|
48
80
|
|
|
49
|
-
const
|
|
50
|
-
|
|
81
|
+
const url = queryString ? `/ws/rest/v1/queue-entry?${queryString}` : '/ws/rest/v1/queue-entry';
|
|
82
|
+
|
|
83
|
+
const {
|
|
84
|
+
data,
|
|
85
|
+
isLoading,
|
|
86
|
+
isValidating,
|
|
87
|
+
error,
|
|
88
|
+
totalPages,
|
|
89
|
+
totalCount,
|
|
90
|
+
currentPage,
|
|
91
|
+
currentPageSize,
|
|
92
|
+
paginated,
|
|
93
|
+
showNextButton,
|
|
94
|
+
showPreviousButton,
|
|
95
|
+
goTo,
|
|
96
|
+
goToNext,
|
|
97
|
+
goToPrevious,
|
|
98
|
+
} = useOpenmrsPagination<QueueEntry>(url, defaultPageSize, {
|
|
99
|
+
swrConfig: {
|
|
100
|
+
dedupingInterval: 2000,
|
|
101
|
+
revalidateOnFocus: false,
|
|
102
|
+
keepPreviousData: true,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
51
105
|
|
|
52
|
-
const { data, isLoading, error } = useSWR<{ data: { results: Array<QueueEntry> } }>(url, openmrsFetch);
|
|
53
106
|
const queueEntries = useMemo(() => {
|
|
54
|
-
return (data
|
|
107
|
+
return (data ?? [])?.filter((entry) => {
|
|
55
108
|
return (
|
|
56
109
|
dayjs(entry.startedAt).isAfter(dayjs().subtract(24, 'hour')) &&
|
|
57
110
|
entry?.visit?.visitType?.uuid === outpatientVisitTypeUuid
|
|
58
111
|
);
|
|
59
112
|
});
|
|
60
|
-
}, [data
|
|
113
|
+
}, [data, outpatientVisitTypeUuid]);
|
|
114
|
+
|
|
115
|
+
const pagination: QueueEntriesPagination = {
|
|
116
|
+
totalPages,
|
|
117
|
+
totalCount,
|
|
118
|
+
currentPage,
|
|
119
|
+
currentPageSize,
|
|
120
|
+
paginated,
|
|
121
|
+
showNextButton,
|
|
122
|
+
showPreviousButton,
|
|
123
|
+
goTo,
|
|
124
|
+
goToNext,
|
|
125
|
+
goToPrevious,
|
|
126
|
+
};
|
|
61
127
|
|
|
62
128
|
return {
|
|
63
129
|
queueEntries,
|
|
64
130
|
isLoading,
|
|
131
|
+
isValidating,
|
|
65
132
|
error,
|
|
133
|
+
pagination,
|
|
66
134
|
};
|
|
67
135
|
};
|
|
68
136
|
export function serveQueueEntry(servicePointName: string, ticketNumber: string, status: string, locationUuid?: string) {
|
|
@@ -3,17 +3,15 @@ import { useTranslation } from 'react-i18next';
|
|
|
3
3
|
import {
|
|
4
4
|
AssignedExtension,
|
|
5
5
|
FetchResponse,
|
|
6
|
-
type Location,
|
|
7
6
|
openmrsFetch,
|
|
8
7
|
restBaseUrl,
|
|
9
8
|
useAssignedExtensions,
|
|
10
9
|
useConfig,
|
|
11
10
|
useEmrConfiguration,
|
|
12
|
-
usePatient,
|
|
13
11
|
useVisit,
|
|
14
12
|
} from '@openmrs/esm-framework';
|
|
15
13
|
import { ExpressWorkflowConfig } from '../../config-schema';
|
|
16
|
-
import
|
|
14
|
+
import useSWRImmutable from 'swr/immutable';
|
|
17
15
|
|
|
18
16
|
export const usePatientChartTabs = (navigationPath: string, patientUuid: string, patient?: fhir.Patient) => {
|
|
19
17
|
const { t } = useTranslation();
|
|
@@ -99,11 +97,10 @@ export const usePatientChartTabs = (navigationPath: string, patientUuid: string,
|
|
|
99
97
|
* }
|
|
100
98
|
*/
|
|
101
99
|
export const useCurrentPatientAdmissionEncounter = (patientUuid: string) => {
|
|
100
|
+
// TODO: use visit context store instead of useVisit. This is happening on every mount of the patient chart dashboard. Which is not efficient.
|
|
102
101
|
const { currentVisit, error: visitError, isLoading: isLoadingVisit, mutate: mutateVisit } = useVisit(patientUuid);
|
|
103
102
|
const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
|
|
104
|
-
const { inPatientVisitTypeUuid } = useConfig<
|
|
105
|
-
externalModuleName: '@kenyaemr/esm-ward-app',
|
|
106
|
-
});
|
|
103
|
+
const { inPatientVisitTypeUuid } = useConfig<ExpressWorkflowConfig>();
|
|
107
104
|
// Admission or Tranfer encounter depending on wether patient was transfered or admitted directly
|
|
108
105
|
const latestAdmisionEncounter = useMemo(() => {
|
|
109
106
|
return currentVisit?.encounters?.find(
|
|
@@ -138,8 +135,7 @@ export const useCurrentPatientAdmissionEncounter = (patientUuid: string) => {
|
|
|
138
135
|
* Show partography component when patient is a female patient, admitted to labour ward
|
|
139
136
|
* @param patientUuid string
|
|
140
137
|
*/
|
|
141
|
-
export const useShowPatography = (
|
|
142
|
-
const { patient, isLoading: isLoadingPatient, error: patientError } = usePatient(patientUuid);
|
|
138
|
+
export const useShowPatography = (patient: fhir.Patient) => {
|
|
143
139
|
const isFemale = patient?.gender?.toLowerCase() === 'female';
|
|
144
140
|
|
|
145
141
|
const {
|
|
@@ -147,7 +143,7 @@ export const useShowPatography = (patientUuid: string) => {
|
|
|
147
143
|
error: admissionError,
|
|
148
144
|
isLoading: isloadingAdmission,
|
|
149
145
|
isPatientAdmitted,
|
|
150
|
-
} = useCurrentPatientAdmissionEncounter(
|
|
146
|
+
} = useCurrentPatientAdmissionEncounter(patient.id);
|
|
151
147
|
const {
|
|
152
148
|
error: tagsError,
|
|
153
149
|
isLoading: isloadingTags,
|
|
@@ -160,8 +156,8 @@ export const useShowPatography = (patientUuid: string) => {
|
|
|
160
156
|
);
|
|
161
157
|
|
|
162
158
|
return {
|
|
163
|
-
isLoading:
|
|
164
|
-
error:
|
|
159
|
+
isLoading: isloadingAdmission || isloadingTags,
|
|
160
|
+
error: admissionError ?? tagsError,
|
|
165
161
|
showPartography: isFemale && isPatientAdmitted && admissionLocationIsLabourWard,
|
|
166
162
|
};
|
|
167
163
|
};
|
|
@@ -171,7 +167,7 @@ type Tag = { uuid: string; display: string; name: string; description: string };
|
|
|
171
167
|
const useAdmissionLocationTags = (locationUuid?: string) => {
|
|
172
168
|
const rep = 'custom:(tags:(uuid,display,name,description))';
|
|
173
169
|
const url = `${restBaseUrl}/location/${locationUuid}?v=${rep}`;
|
|
174
|
-
const { data, error, isLoading, mutate } =
|
|
170
|
+
const { data, error, isLoading, mutate } = useSWRImmutable<FetchResponse<{ tags: Array<Tag> }>>(
|
|
175
171
|
locationUuid ? url : null,
|
|
176
172
|
openmrsFetch,
|
|
177
173
|
);
|