@kenyaemr/esm-morgue-app 5.3.8-pre.1567 → 5.3.8-pre.1570
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 +20 -20
- package/dist/300.js +1 -1
- package/dist/38.js +1 -0
- package/dist/38.js.map +1 -0
- package/dist/553.js +1 -0
- package/dist/553.js.map +1 -0
- package/dist/570.js +1 -0
- package/dist/570.js.map +1 -0
- package/dist/632.js +1 -1
- package/dist/632.js.map +1 -1
- package/dist/687.js +2 -0
- package/dist/687.js.map +1 -0
- package/dist/759.js +1 -1
- package/dist/759.js.map +1 -1
- package/dist/864.js +1 -1
- package/dist/926.js +1 -1
- package/dist/926.js.map +1 -1
- package/dist/942.js +1 -1
- package/dist/942.js.map +1 -1
- package/dist/kenyaemr-esm-morgue-app.js +1 -1
- package/dist/kenyaemr-esm-morgue-app.js.buildmanifest.json +129 -108
- package/dist/kenyaemr-esm-morgue-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/card/compartment-view.compartment.tsx +1 -1
- package/src/component/main.component.tsx +7 -0
- package/src/config-schema.ts +7 -1
- package/src/empty-state/{empty-search-deceased.component.tsx → empty-morgue-admission.component.tsx} +3 -3
- package/src/header/morgue-header.component.tsx +11 -15
- package/src/header/morgue-header.scss +34 -40
- package/src/hook/useMorgue.resource.ts +107 -19
- package/src/tables/admitted-queue.component.tsx +11 -4
- package/src/tables/discharge-queue.component.tsx +73 -0
- package/src/tables/generic-table.component.tsx +1 -1
- package/src/tables/waiting-queue.component.tsx +10 -12
- package/src/tabs/tabs.component.tsx +44 -26
- package/src/tabs/tabs.scss +1 -2
- package/src/types/index.ts +120 -0
- package/src/utils/utils.ts +23 -0
- package/src/workspaces/discharge-body.scss +13 -0
- package/src/workspaces/discharge-body.workspace.tsx +224 -24
- package/translations/en.json +4 -5
- package/dist/268.js +0 -2
- package/dist/268.js.map +0 -1
- package/dist/340.js +0 -1
- package/dist/340.js.map +0 -1
- package/dist/701.js +0 -1
- package/dist/701.js.map +0 -1
- package/dist/809.js +0 -1
- package/dist/809.js.map +0 -1
- /package/dist/{268.js.LICENSE.txt → 687.js.LICENSE.txt} +0 -0
- /package/src/empty-state/{empty-deceased.scss → empty-morgue-admission.scss} +0 -0
|
@@ -2,9 +2,16 @@ import React from 'react';
|
|
|
2
2
|
import { MorgueHeader } from '../header/morgue-header.component';
|
|
3
3
|
import { MorgueTabs } from '../tabs/tabs.component';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { useDeceasedPatient } from '../hook/useMorgue.resource';
|
|
5
6
|
|
|
6
7
|
const MainComponent: React.FC = () => {
|
|
7
8
|
const { t } = useTranslation();
|
|
9
|
+
const { data: deceasedPatients, isLoading } = useDeceasedPatient();
|
|
10
|
+
|
|
11
|
+
const awaitingCount = isLoading ? null : deceasedPatients?.filter((p) => p.status === 'awaiting').length || 0;
|
|
12
|
+
const admittedCount = isLoading ? null : deceasedPatients?.filter((p) => p.status === 'admitted').length || 0;
|
|
13
|
+
const dischargedCount = isLoading ? null : deceasedPatients?.filter((p) => p.status === 'discharged').length || 0;
|
|
14
|
+
|
|
8
15
|
return (
|
|
9
16
|
<div className={`omrs-main-content`}>
|
|
10
17
|
<MorgueHeader title={t('mortuary', 'Mortuary')} />
|
package/src/config-schema.ts
CHANGED
|
@@ -46,7 +46,7 @@ export const configSchema = {
|
|
|
46
46
|
morgueDischargeEncounterTypeUuid: {
|
|
47
47
|
type: Type.String,
|
|
48
48
|
_description: 'Encounter type for morgue discharge',
|
|
49
|
-
_default: '
|
|
49
|
+
_default: 'd618f40b-b5a3-4f17-81c8-2f04e2aad58e',
|
|
50
50
|
},
|
|
51
51
|
visitPaymentMethodAttributeUuid: {
|
|
52
52
|
_type: Type.String,
|
|
@@ -83,6 +83,11 @@ export const configSchema = {
|
|
|
83
83
|
_description: 'UUID for discharge area concept',
|
|
84
84
|
_default: 'a458fd00-199e-4d08-ad5c-1be322a6ccbe',
|
|
85
85
|
},
|
|
86
|
+
adminUuid: {
|
|
87
|
+
_type: Type.String,
|
|
88
|
+
_description: 'UUID for admin user',
|
|
89
|
+
_default: 'e02c40e5-04e7-11e5-ae3c-a0b3cc4f922f',
|
|
90
|
+
},
|
|
86
91
|
};
|
|
87
92
|
|
|
88
93
|
export interface BillingConfig {
|
|
@@ -127,4 +132,5 @@ export type ConfigObject = {
|
|
|
127
132
|
policeIDNumber: string;
|
|
128
133
|
dischargeAreaUuid: string;
|
|
129
134
|
morgueDischargeEncounterTypeUuid: string;
|
|
135
|
+
adminUuid: string;
|
|
130
136
|
};
|
package/src/empty-state/{empty-search-deceased.component.tsx → empty-morgue-admission.component.tsx}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import styles from './empty-
|
|
3
|
+
import styles from './empty-morgue-admission.scss';
|
|
4
4
|
import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib';
|
|
5
5
|
import { DocumentUnknown, IbmWatsonKnowledgeStudio } from '@carbon/react/icons';
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ interface EmptyDeceasedSearchProps {
|
|
|
9
9
|
subTitle: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const EmptyMorgueAdmission: React.FC<EmptyDeceasedSearchProps> = ({ title, subTitle }) => {
|
|
13
13
|
const { t } = useTranslation();
|
|
14
14
|
|
|
15
15
|
return (
|
|
@@ -21,4 +21,4 @@ const EmptyDeceasedSearch: React.FC<EmptyDeceasedSearchProps> = ({ title, subTit
|
|
|
21
21
|
);
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
export default
|
|
24
|
+
export default EmptyMorgueAdmission;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import { Calendar, Location } from '@carbon/react/icons';
|
|
3
|
+
import { Calendar, Location, UserFollow } from '@carbon/react/icons';
|
|
4
4
|
import { useSession, formatDate } from '@openmrs/esm-framework';
|
|
5
5
|
import styles from './morgue-header.scss';
|
|
6
6
|
import MorgueIllustration from './morgue-illustration.component';
|
|
7
|
+
import { InlineLoading } from '@carbon/react';
|
|
7
8
|
|
|
8
9
|
interface MorgueHeaderProps {
|
|
9
10
|
title: string;
|
|
@@ -11,8 +12,8 @@ interface MorgueHeaderProps {
|
|
|
11
12
|
|
|
12
13
|
export const MorgueHeader: React.FC<MorgueHeaderProps> = ({ title }) => {
|
|
13
14
|
const { t } = useTranslation();
|
|
14
|
-
const
|
|
15
|
-
const
|
|
15
|
+
const session = useSession();
|
|
16
|
+
const location = session?.sessionLocation?.display;
|
|
16
17
|
|
|
17
18
|
return (
|
|
18
19
|
<div className={styles.header}>
|
|
@@ -23,18 +24,13 @@ export const MorgueHeader: React.FC<MorgueHeaderProps> = ({ title }) => {
|
|
|
23
24
|
<p>{t('mortuaryManagement', 'Mortuary management')}</p>
|
|
24
25
|
</div>
|
|
25
26
|
</div>
|
|
26
|
-
<div className={styles
|
|
27
|
-
<div className={styles
|
|
28
|
-
<
|
|
29
|
-
<span className={styles.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<span className={styles.
|
|
33
|
-
<span className={styles.metricValue}>0</span>
|
|
34
|
-
</div>
|
|
35
|
-
<div className={styles.wrapMetrics}>
|
|
36
|
-
<span className={styles.metricLabel}>{t('discharges', 'Discharges')}</span>
|
|
37
|
-
<span className={styles.metricValue}>0</span>
|
|
27
|
+
<div className={styles['right-justified-items']}>
|
|
28
|
+
<div className={styles['date-and-location']}>
|
|
29
|
+
<Location size={16} />
|
|
30
|
+
<span className={styles.value}>{location}</span>
|
|
31
|
+
<span className={styles.middot}>·</span>
|
|
32
|
+
<Calendar size={16} />
|
|
33
|
+
<span className={styles.value}>{formatDate(new Date(), { mode: 'standard' })}</span>
|
|
38
34
|
</div>
|
|
39
35
|
</div>
|
|
40
36
|
</div>
|
|
@@ -43,59 +43,53 @@ svg.iconOverrides {
|
|
|
43
43
|
fill: var(--brand-03);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
.
|
|
46
|
+
.left-justified-items {
|
|
47
47
|
display: flex;
|
|
48
48
|
flex-direction: row;
|
|
49
|
-
justify-content: flex-end;
|
|
50
49
|
align-items: center;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
padding: layout.$spacing-04 layout.$spacing-05 0 0;
|
|
54
|
-
width: auto;
|
|
55
|
-
height: auto;
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
align-items: center;
|
|
56
52
|
}
|
|
57
53
|
|
|
58
|
-
.
|
|
59
|
-
|
|
60
|
-
height: auto;
|
|
61
|
-
flex-grow: 1;
|
|
54
|
+
.right-justified-items {
|
|
55
|
+
@include type.type-style('body-compact-02');
|
|
62
56
|
display: flex;
|
|
63
57
|
flex-direction: column;
|
|
64
|
-
justify-content:
|
|
65
|
-
align-items: flex-start;
|
|
66
|
-
gap: layout.$spacing-03;
|
|
67
|
-
padding: 0;
|
|
68
|
-
min-width: layout.$spacing-12;
|
|
58
|
+
justify-content: space-between;
|
|
69
59
|
}
|
|
70
60
|
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
font-size: layout.$spacing-04;
|
|
74
|
-
line-height: 1.33;
|
|
75
|
-
letter-spacing: 0.32px;
|
|
76
|
-
text-align: left;
|
|
77
|
-
white-space: nowrap;
|
|
78
|
-
overflow: hidden;
|
|
79
|
-
text-overflow: ellipsis;
|
|
61
|
+
.page-name {
|
|
62
|
+
@include type.type-style('heading-04');
|
|
80
63
|
}
|
|
81
64
|
|
|
82
|
-
.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
white-space: nowrap;
|
|
89
|
-
overflow: hidden;
|
|
90
|
-
text-overflow: ellipsis;
|
|
65
|
+
.page-labels {
|
|
66
|
+
margin: layout.$spacing-05;
|
|
67
|
+
|
|
68
|
+
p:first-of-type {
|
|
69
|
+
margin-bottom: layout.$spacing-02;
|
|
70
|
+
}
|
|
91
71
|
}
|
|
92
72
|
|
|
93
|
-
.
|
|
73
|
+
.date-and-location {
|
|
94
74
|
display: flex;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
75
|
+
justify-content: flex-end;
|
|
76
|
+
align-items: center;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.userContainer {
|
|
80
|
+
display: flex;
|
|
81
|
+
justify-content: flex-end;
|
|
98
82
|
gap: layout.$spacing-05;
|
|
99
|
-
|
|
100
|
-
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.value {
|
|
86
|
+
margin-left: layout.$spacing-02;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.middot {
|
|
90
|
+
margin: 0 layout.$spacing-03;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.view {
|
|
94
|
+
@include type.type-style('label-01');
|
|
101
95
|
}
|
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FetchResponse,
|
|
3
3
|
openmrsFetch,
|
|
4
|
+
openmrsObservableFetch,
|
|
4
5
|
OpenmrsResource,
|
|
5
6
|
restBaseUrl,
|
|
7
|
+
toDateObjectStrict,
|
|
8
|
+
toOmrsIsoString,
|
|
6
9
|
useConfig,
|
|
7
10
|
useOpenmrsPagination,
|
|
8
11
|
} from '@openmrs/esm-framework';
|
|
9
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
DeceasedPatientResponse,
|
|
14
|
+
PaymentMethod,
|
|
15
|
+
VisitTypeResponse,
|
|
16
|
+
Location,
|
|
17
|
+
Patient,
|
|
18
|
+
Visit,
|
|
19
|
+
UseVisitQueueEntries,
|
|
20
|
+
VisitQueueEntry,
|
|
21
|
+
MappedVisitQueueEntry,
|
|
22
|
+
UpdateVisitPayload,
|
|
23
|
+
PaginatedResponse,
|
|
24
|
+
} from '../types';
|
|
10
25
|
import useSWR from 'swr';
|
|
11
26
|
import { BillingConfig, ConfigObject } from '../config-schema';
|
|
12
27
|
import { useEffect, useState } from 'react';
|
|
13
28
|
import useSWRImmutable from 'swr/immutable';
|
|
14
29
|
import { makeUrlUrl } from '../utils/utils';
|
|
30
|
+
import type { Observable } from 'rxjs';
|
|
15
31
|
|
|
16
32
|
const getMorguePatientStatus = async (
|
|
17
33
|
patientUuid: string,
|
|
@@ -19,7 +35,7 @@ const getMorguePatientStatus = async (
|
|
|
19
35
|
morgueDischargeEncounter: string,
|
|
20
36
|
): Promise<'discharged' | 'admitted' | 'awaiting'> => {
|
|
21
37
|
const customRepresentation = 'custom:(visitType:(uuid),startDatetime,stopDatetime,encounters:(encounterType:(uuid)))';
|
|
22
|
-
const url = `${restBaseUrl}/visit?v=${customRepresentation}&
|
|
38
|
+
const url = `${restBaseUrl}/visit?v=${customRepresentation}&patient=${patientUuid}&limit=1`;
|
|
23
39
|
const response = await openmrsFetch<{
|
|
24
40
|
results: Array<{
|
|
25
41
|
visitType: { uuid: string };
|
|
@@ -38,6 +54,7 @@ const getMorguePatientStatus = async (
|
|
|
38
54
|
);
|
|
39
55
|
const isMorgueVisit = visit?.visitType?.uuid === morgueVisitTypeUuid;
|
|
40
56
|
const isVisitActive = !(typeof visit?.stopDatetime === 'string');
|
|
57
|
+
|
|
41
58
|
if (!isMorgueVisit) {
|
|
42
59
|
return 'awaiting';
|
|
43
60
|
}
|
|
@@ -46,28 +63,33 @@ const getMorguePatientStatus = async (
|
|
|
46
63
|
}
|
|
47
64
|
return 'admitted';
|
|
48
65
|
};
|
|
49
|
-
export const useDeceasedPatient = () => {
|
|
66
|
+
export const useDeceasedPatient = (searchTerm?: string) => {
|
|
50
67
|
const { morgueVisitTypeUuid, morgueDischargeEncounterTypeUuid } = useConfig<ConfigObject>();
|
|
51
68
|
const [deceasedPatient, setDeceasedPatient] = useState([]);
|
|
52
69
|
const [isLoadingStatus, setIsLoadingStatus] = useState(false);
|
|
53
70
|
const [statusError, setStatusError] = useState();
|
|
54
71
|
const customRepresentation =
|
|
55
72
|
'custom:(uuid,display,identifiers:(identifier,uuid,preferred,location:(uuid,name)),person:(uuid,display,gender,birthdate,dead,age,deathDate,causeOfDeath:(uuid,display),preferredAddress:(uuid,stateProvince,countyDistrict,address4)))';
|
|
56
|
-
const
|
|
73
|
+
const uril = makeUrlUrl(`${restBaseUrl}/morgue/patient?v=${customRepresentation}&dead=true&q=${searchTerm}`);
|
|
74
|
+
const pageSize = 10;
|
|
75
|
+
const {
|
|
76
|
+
data: paginatedData,
|
|
77
|
+
error: paginatedError,
|
|
78
|
+
isLoading: isPaginatedLoading,
|
|
79
|
+
} = useOpenmrsPagination<PaginatedResponse>(uril, pageSize);
|
|
57
80
|
|
|
58
|
-
const { data, error, isLoading } = useSWRImmutable<{ data: DeceasedPatientResponse }>(url, openmrsFetch);
|
|
59
81
|
useEffect(() => {
|
|
60
|
-
if (
|
|
82
|
+
if (paginatedData?.length) {
|
|
61
83
|
(async () => {
|
|
62
84
|
try {
|
|
63
85
|
setIsLoadingStatus(true);
|
|
64
86
|
const status = await Promise.all(
|
|
65
|
-
|
|
87
|
+
paginatedData?.map((data) => {
|
|
66
88
|
return getMorguePatientStatus(data?.uuid, morgueVisitTypeUuid, morgueDischargeEncounterTypeUuid);
|
|
67
89
|
}),
|
|
68
90
|
);
|
|
69
91
|
|
|
70
|
-
setDeceasedPatient(
|
|
92
|
+
setDeceasedPatient(paginatedData?.map((patient, index) => ({ ...patient, status: status[index] })));
|
|
71
93
|
} catch (error) {
|
|
72
94
|
setStatusError(error);
|
|
73
95
|
} finally {
|
|
@@ -75,18 +97,13 @@ export const useDeceasedPatient = () => {
|
|
|
75
97
|
}
|
|
76
98
|
})();
|
|
77
99
|
}
|
|
78
|
-
}, [morgueVisitTypeUuid, morgueDischargeEncounterTypeUuid,
|
|
100
|
+
}, [morgueVisitTypeUuid, morgueDischargeEncounterTypeUuid, paginatedData]);
|
|
79
101
|
|
|
80
|
-
return {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
'custom:(visitType:(uuid,name,display),uuid,encounters:(uuid,diagnoses:(uuid,display,rank,diagnosis),form:(uuid,display),encounterDatetime,orders:full,obs:(uuid,concept:(uuid,display,conceptClass:(uuid,display)),display,groupMembers:(uuid,concept:(uuid,display),value:(uuid,display),display),value,obsDatetime),encounterType:(uuid,display,viewPrivilege,editPrivilege),encounterProviders:(uuid,display,encounterRole:(uuid,display),provider:(uuid,person:(uuid,display)))),visitType:(uuid,name,display),startDatetime,stopDatetime,patient,attributes:(attributeType:ref,display,uuid,value)';
|
|
86
|
-
const url = makeUrlUrl(`${restBaseUrl}/encounter?patient=${patientUuid}&v=full`);
|
|
87
|
-
|
|
88
|
-
const paginatedEncounter = useOpenmrsPagination(url, 10);
|
|
89
|
-
return paginatedEncounter;
|
|
102
|
+
return {
|
|
103
|
+
data: deceasedPatient,
|
|
104
|
+
error: paginatedError ?? statusError,
|
|
105
|
+
isLoading: isPaginatedLoading || isLoadingStatus,
|
|
106
|
+
};
|
|
90
107
|
};
|
|
91
108
|
|
|
92
109
|
export const useVisitType = () => {
|
|
@@ -177,3 +194,74 @@ const usePerson = (uuid: string) => {
|
|
|
177
194
|
};
|
|
178
195
|
|
|
179
196
|
export default usePerson;
|
|
197
|
+
|
|
198
|
+
export function useVisitQueueEntry(patientUuid, visitUuid): UseVisitQueueEntries {
|
|
199
|
+
const apiUrl = `${restBaseUrl}/visit-queue-entry?v=full&patient=${patientUuid}`;
|
|
200
|
+
const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: { results: Array<VisitQueueEntry> } }, Error>(
|
|
201
|
+
apiUrl,
|
|
202
|
+
openmrsFetch,
|
|
203
|
+
);
|
|
204
|
+
const mapVisitQueueEntryProperties = (visitQueueEntry: VisitQueueEntry): MappedVisitQueueEntry => ({
|
|
205
|
+
id: visitQueueEntry.uuid,
|
|
206
|
+
name: visitQueueEntry.queueEntry.queue.display,
|
|
207
|
+
patientUuid: visitQueueEntry.queueEntry.patient.uuid,
|
|
208
|
+
priority:
|
|
209
|
+
visitQueueEntry.queueEntry.priority.display === 'Urgent'
|
|
210
|
+
? 'Priority'
|
|
211
|
+
: visitQueueEntry.queueEntry.priority.display,
|
|
212
|
+
priorityUuid: visitQueueEntry.queueEntry.priority.uuid,
|
|
213
|
+
service: visitQueueEntry.queueEntry.queue?.display,
|
|
214
|
+
status: visitQueueEntry.queueEntry.status.display,
|
|
215
|
+
statusUuid: visitQueueEntry.queueEntry.status.uuid,
|
|
216
|
+
visitUuid: visitQueueEntry.visit?.uuid,
|
|
217
|
+
visitType: visitQueueEntry.visit?.visitType?.display,
|
|
218
|
+
queue: visitQueueEntry.queueEntry.queue,
|
|
219
|
+
queueEntryUuid: visitQueueEntry.queueEntry.uuid,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const mappedVisitQueueEntry =
|
|
223
|
+
data?.data?.results
|
|
224
|
+
?.map(mapVisitQueueEntryProperties)
|
|
225
|
+
.filter((visitQueueEntry) => visitUuid !== undefined && visitUuid === visitQueueEntry.visitUuid)
|
|
226
|
+
.shift() ?? null;
|
|
227
|
+
return {
|
|
228
|
+
queueEntry: mappedVisitQueueEntry,
|
|
229
|
+
isLoading,
|
|
230
|
+
error: error,
|
|
231
|
+
isValidating,
|
|
232
|
+
mutate,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function removeQueuedPatient(
|
|
237
|
+
queueUuid: string,
|
|
238
|
+
queueEntryUuid: string,
|
|
239
|
+
abortController: AbortController,
|
|
240
|
+
endedAt?: Date,
|
|
241
|
+
) {
|
|
242
|
+
return openmrsFetch(`${restBaseUrl}/queue/${queueUuid}/entry/${queueEntryUuid}`, {
|
|
243
|
+
method: 'POST',
|
|
244
|
+
headers: {
|
|
245
|
+
'Content-Type': 'application/json',
|
|
246
|
+
},
|
|
247
|
+
body: {
|
|
248
|
+
endedAt: toDateObjectStrict(toOmrsIsoString(endedAt) ?? toOmrsIsoString(new Date())),
|
|
249
|
+
},
|
|
250
|
+
signal: abortController.signal,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function updateVisit(
|
|
255
|
+
uuid: string,
|
|
256
|
+
payload: UpdateVisitPayload,
|
|
257
|
+
abortController: AbortController,
|
|
258
|
+
): Observable<any> {
|
|
259
|
+
return openmrsObservableFetch(`${restBaseUrl}/visit/${uuid}`, {
|
|
260
|
+
signal: abortController.signal,
|
|
261
|
+
method: 'POST',
|
|
262
|
+
headers: {
|
|
263
|
+
'Content-type': 'application/json',
|
|
264
|
+
},
|
|
265
|
+
body: payload,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
@@ -6,6 +6,7 @@ import CompartmentView from '../card/compartment-view.compartment';
|
|
|
6
6
|
import { useDeceasedPatient } from '../hook/useMorgue.resource';
|
|
7
7
|
import { InlineLoading } from '@carbon/react';
|
|
8
8
|
import { CardHeader, ErrorState } from '@openmrs/esm-patient-common-lib';
|
|
9
|
+
import EmptyMorgueAdmission from '../empty-state/empty-morgue-admission.component';
|
|
9
10
|
|
|
10
11
|
export const AdmittedQueue: React.FC = () => {
|
|
11
12
|
const { data: deceasedPatients, error, isLoading } = useDeceasedPatient();
|
|
@@ -17,7 +18,6 @@ export const AdmittedQueue: React.FC = () => {
|
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
const admittedPatients = deceasedPatients?.filter((patient) => patient.status === 'admitted') || [];
|
|
20
|
-
|
|
21
21
|
if (isLoading) {
|
|
22
22
|
return (
|
|
23
23
|
<InlineLoading
|
|
@@ -32,14 +32,21 @@ export const AdmittedQueue: React.FC = () => {
|
|
|
32
32
|
return <ErrorState error={error} headerTitle={t('allocation', 'Allocation')} />;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
if (admittedPatients?.length === 0) {
|
|
36
|
+
return (
|
|
37
|
+
<EmptyMorgueAdmission
|
|
38
|
+
title={t('allocations', 'Allocation')}
|
|
39
|
+
subTitle={t('noAdmittedBodies', 'There are no admitted bodies')}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
return (
|
|
36
45
|
<div className={styles.layoutWrapper}>
|
|
37
46
|
<CardHeader title={t('allocation', 'Allocation')} children={''} />
|
|
38
47
|
<DeceasedFilter onSearchChange={handleSearchChange} />
|
|
39
48
|
<div className={styles.patientCardContainer}>
|
|
40
|
-
<
|
|
41
|
-
<CompartmentView patientVisit={{ results: admittedPatients }} searchQuery={searchQuery} />
|
|
42
|
-
</div>
|
|
49
|
+
<CompartmentView patientVisit={{ results: admittedPatients }} searchQuery={searchQuery} />
|
|
43
50
|
</div>
|
|
44
51
|
</div>
|
|
45
52
|
);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import GenericTable from './generic-table.component';
|
|
3
|
+
import { ConfigurableLink, ErrorState, formatDate, launchWorkspace } from '@openmrs/esm-framework';
|
|
4
|
+
import { toUpperCase } from '../helpers/expression-helper';
|
|
5
|
+
import { Tag, Button, DataTableSkeleton, OverflowMenu, OverflowMenuItem } from '@carbon/react';
|
|
6
|
+
import styles from './generic-table.scss';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
import { useDeceasedPatient } from '../hook/useMorgue.resource';
|
|
9
|
+
import { formatDateTime } from '../utils/utils';
|
|
10
|
+
|
|
11
|
+
interface DischargedProps {
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
deceasedPatients: any;
|
|
14
|
+
error: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const DischargedBodies: React.FC<DischargedProps> = ({ isLoading, deceasedPatients, error }) => {
|
|
18
|
+
const { t } = useTranslation();
|
|
19
|
+
const dischargedInLine = t('dischargeBodies', 'Discharged bodies');
|
|
20
|
+
const patientChartUrl = '${openmrsSpaBase}/patient/${patientUuid}/chart/deceased-panel';
|
|
21
|
+
|
|
22
|
+
const genericTableHeader = [
|
|
23
|
+
{ header: 'Patient Name', key: 'name' },
|
|
24
|
+
{ header: 'Gender', key: 'gender' },
|
|
25
|
+
{ header: 'Identifier', key: 'identifier' },
|
|
26
|
+
{ header: 'Age', key: 'age' },
|
|
27
|
+
{ header: 'Date of Death', key: 'deathDate' },
|
|
28
|
+
{ header: 'Cause of Death', key: 'causeOfDeath' },
|
|
29
|
+
{ header: 'Status', key: 'status' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
if (isLoading) {
|
|
33
|
+
return (
|
|
34
|
+
<div className={styles.table}>
|
|
35
|
+
<DataTableSkeleton
|
|
36
|
+
headers={genericTableHeader}
|
|
37
|
+
aria-label="discharged-datatable"
|
|
38
|
+
showToolbar={false}
|
|
39
|
+
showHeader={false}
|
|
40
|
+
rowCount={10}
|
|
41
|
+
zebra
|
|
42
|
+
columnCount={7}
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (error) {
|
|
49
|
+
return <ErrorState error={error} headerTitle={t('dischargeBodies', 'Discharged bodies')} />;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const dischargedDeceased = deceasedPatients?.filter((patient) => patient?.status === 'discharged') || [];
|
|
53
|
+
|
|
54
|
+
const rows = dischargedDeceased.map((patient) => ({
|
|
55
|
+
id: patient.uuid,
|
|
56
|
+
name: (
|
|
57
|
+
<ConfigurableLink
|
|
58
|
+
style={{ textDecoration: 'none', maxWidth: '50%' }}
|
|
59
|
+
to={patientChartUrl}
|
|
60
|
+
templateParams={{ patientUuid: patient?.person?.uuid }}>
|
|
61
|
+
{patient.person.display?.toUpperCase()}
|
|
62
|
+
</ConfigurableLink>
|
|
63
|
+
),
|
|
64
|
+
gender: patient.person.gender,
|
|
65
|
+
age: patient?.person?.age,
|
|
66
|
+
identifier: patient?.identifiers[0]?.identifier,
|
|
67
|
+
deathDate: new Date(patient.person.deathDate).toLocaleString(),
|
|
68
|
+
causeOfDeath: patient.person.causeOfDeath?.display,
|
|
69
|
+
status: <Tag type="magenta">{patient.status}</Tag>,
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
return <GenericTable rows={rows} headers={genericTableHeader} title={dischargedInLine} />;
|
|
73
|
+
};
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import { CardHeader, PatientChartPagination } from '@openmrs/esm-patient-common-lib';
|
|
16
16
|
import { useLayoutType, usePagination } from '@openmrs/esm-framework';
|
|
17
17
|
import styles from './generic-table.scss';
|
|
18
|
-
import EmptyDeceasedSearch from '../empty-state/empty-
|
|
18
|
+
import EmptyDeceasedSearch from '../empty-state/empty-morgue-admission.component';
|
|
19
19
|
|
|
20
20
|
interface GenericTableProps {
|
|
21
21
|
rows: any[];
|
|
@@ -6,9 +6,15 @@ import { Tag, Button, DataTableSkeleton, OverflowMenu, OverflowMenuItem } from '
|
|
|
6
6
|
import styles from './generic-table.scss';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
import { useDeceasedPatient } from '../hook/useMorgue.resource';
|
|
9
|
+
import { formatDateTime } from '../utils/utils';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
interface WaitingQueueProps {
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
deceasedPatients: any;
|
|
14
|
+
error: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const WaitingQueue: React.FC<WaitingQueueProps> = ({ isLoading, deceasedPatients, error }) => {
|
|
12
18
|
const { t } = useTranslation();
|
|
13
19
|
const waitingInLine = t('waitingInLine', 'Waiting In Line');
|
|
14
20
|
|
|
@@ -46,11 +52,11 @@ export const WaitingQueue: React.FC = () => {
|
|
|
46
52
|
|
|
47
53
|
const rows = awaitingPatients.map((patient) => ({
|
|
48
54
|
id: patient.uuid,
|
|
49
|
-
name:
|
|
55
|
+
name: patient.person.display?.toUpperCase(),
|
|
50
56
|
gender: patient.person.gender,
|
|
51
57
|
age: patient?.person?.age,
|
|
52
58
|
identifier: patient?.identifiers[0]?.identifier,
|
|
53
|
-
deathDate:
|
|
59
|
+
deathDate: patient.person.deathDate ? new Date(patient.person.deathDate).toLocaleString() : t('nullDate', '--'),
|
|
54
60
|
causeOfDeath: patient.person.causeOfDeath?.display,
|
|
55
61
|
status: <Tag type="magenta">{patient.status}</Tag>,
|
|
56
62
|
}));
|
|
@@ -62,17 +68,9 @@ export const WaitingQueue: React.FC = () => {
|
|
|
62
68
|
});
|
|
63
69
|
};
|
|
64
70
|
|
|
65
|
-
const handleDischargeForm = (patientUuid: string) => {
|
|
66
|
-
launchWorkspace('discharge-body-form', {
|
|
67
|
-
workspaceTitle: t('dischargeForm', 'Discharge form'),
|
|
68
|
-
patientUuid,
|
|
69
|
-
});
|
|
70
|
-
};
|
|
71
|
-
|
|
72
71
|
const actionColumn = (row) => (
|
|
73
72
|
<OverflowMenu size="sm" flipped>
|
|
74
73
|
<OverflowMenuItem itemText={t('admit', 'Admit')} onClick={() => handleAdmissionForm(row.id)} />
|
|
75
|
-
<OverflowMenuItem isDelete itemText={t('release', 'Release')} onClick={() => handleDischargeForm(row.id)} />
|
|
76
74
|
</OverflowMenu>
|
|
77
75
|
);
|
|
78
76
|
|
|
@@ -1,42 +1,60 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { Tabs, Tab, TabList, TabPanel, TabPanels,
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Tabs, Tab, TabList, TabPanel, TabPanels, InlineLoading, Layer } from '@carbon/react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import styles from './tabs.scss';
|
|
5
5
|
import { WaitingQueue } from '../tables/waiting-queue.component';
|
|
6
6
|
import { AdmittedQueue } from '../tables/admitted-queue.component';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { useDeceasedPatient } from '../hook/useMorgue.resource';
|
|
8
|
+
import { DischargedBodies } from '../tables/discharge-queue.component';
|
|
9
9
|
|
|
10
10
|
export const MorgueTabs: React.FC = () => {
|
|
11
11
|
const { t } = useTranslation();
|
|
12
|
+
const { data: deceasedPatients, error, isLoading } = useDeceasedPatient();
|
|
13
|
+
|
|
14
|
+
const awaitingCount = deceasedPatients?.filter((p) => p.status === 'awaiting').length || 0;
|
|
15
|
+
const admittedCount = deceasedPatients?.filter((p) => p.status === 'admitted').length || 0;
|
|
16
|
+
const dischargedCount = deceasedPatients?.filter((p) => p.status === 'discharged').length || 0;
|
|
17
|
+
|
|
18
|
+
const getTabLabel = (baseLabel: string, count: number | null) => (
|
|
19
|
+
<span className={styles.tabLabel}>
|
|
20
|
+
{baseLabel} {isLoading ? '' : `(${count})`}
|
|
21
|
+
</span>
|
|
22
|
+
);
|
|
12
23
|
|
|
13
24
|
const tabPanels = [
|
|
14
|
-
{
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
{
|
|
26
|
+
name: getTabLabel(t('waitQueue', 'Waiting queue'), awaitingCount),
|
|
27
|
+
component: <WaitingQueue isLoading={isLoading} deceasedPatients={deceasedPatients} error={error} />,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: getTabLabel(t('admitted', 'Admitted'), admittedCount),
|
|
31
|
+
component: <AdmittedQueue />,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: getTabLabel(t('discharged', 'Discharged'), dischargedCount),
|
|
35
|
+
component: <DischargedBodies isLoading={isLoading} deceasedPatients={deceasedPatients} error={error} />,
|
|
36
|
+
},
|
|
17
37
|
];
|
|
18
38
|
|
|
19
39
|
return (
|
|
20
|
-
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
<TabList aria-label="Content Switcher as Tabs" contained>
|
|
25
|
-
{tabPanels.map((tab, index) => (
|
|
26
|
-
<Tab key={index}>{tab.name}</Tab>
|
|
27
|
-
))}
|
|
28
|
-
</TabList>
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
<TabPanels>
|
|
40
|
+
<div className={styles.referralsList} data-testid="referralsList-list">
|
|
41
|
+
<Tabs selected={0} role="navigation">
|
|
42
|
+
<div className={styles.tabsContainer}>
|
|
43
|
+
<TabList aria-label="Content Switcher as Tabs" contained>
|
|
32
44
|
{tabPanels.map((tab, index) => (
|
|
33
|
-
<
|
|
34
|
-
<Layer>{tab.component}</Layer>
|
|
35
|
-
</TabPanel>
|
|
45
|
+
<Tab key={index}>{tab.name}</Tab>
|
|
36
46
|
))}
|
|
37
|
-
</
|
|
38
|
-
</
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
</TabList>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<TabPanels>
|
|
51
|
+
{tabPanels.map((tab, index) => (
|
|
52
|
+
<TabPanel key={index}>
|
|
53
|
+
<Layer>{tab.component}</Layer>
|
|
54
|
+
</TabPanel>
|
|
55
|
+
))}
|
|
56
|
+
</TabPanels>
|
|
57
|
+
</Tabs>
|
|
58
|
+
</div>
|
|
41
59
|
);
|
|
42
60
|
};
|