@kenyaemr/esm-morgue-app 5.4.2-pre.2283 → 5.4.2-pre.2288
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 +19 -19
- package/dist/109.js +2 -0
- package/dist/109.js.map +1 -0
- package/dist/201.js +1 -0
- package/dist/201.js.map +1 -0
- package/dist/293.js +1 -0
- package/dist/293.js.map +1 -0
- package/dist/300.js +1 -1
- package/dist/347.js +1 -2
- package/dist/347.js.map +1 -1
- package/dist/373.js +2 -0
- package/dist/373.js.map +1 -0
- package/dist/38.js +1 -0
- package/dist/38.js.map +1 -0
- package/dist/389.js +1 -0
- package/dist/389.js.map +1 -0
- package/dist/398.js +1 -0
- package/dist/398.js.map +1 -0
- package/dist/4.js +2 -0
- package/dist/4.js.map +1 -0
- package/dist/410.js +1 -0
- package/dist/410.js.map +1 -0
- package/dist/420.js +2 -0
- package/dist/420.js.map +1 -0
- package/dist/467.js +1 -0
- package/dist/467.js.map +1 -0
- package/dist/632.js +1 -0
- package/dist/632.js.map +1 -0
- package/dist/798.js +1 -0
- package/dist/798.js.map +1 -0
- package/dist/811.js +1 -0
- package/dist/811.js.map +1 -0
- package/dist/824.js +1 -0
- package/dist/824.js.map +1 -0
- package/dist/827.js +1 -0
- package/dist/827.js.map +1 -0
- package/dist/842.js +2 -0
- package/dist/842.js.LICENSE.txt +5 -0
- package/dist/842.js.map +1 -0
- package/dist/918.js +1 -1
- package/dist/918.js.map +1 -1
- package/dist/kenyaemr-esm-morgue-app.js +1 -1
- package/dist/kenyaemr-esm-morgue-app.js.buildmanifest.json +218 -291
- package/dist/kenyaemr-esm-morgue-app.js.map +1 -1
- package/dist/main.js +2 -1
- package/dist/main.js.LICENSE.txt +15 -0
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/bed/bed.component.tsx +164 -0
- package/src/bed/bed.scss +192 -0
- package/src/bed/divider/divider.component.tsx +18 -0
- package/src/bed/empty-bed.component.tsx +47 -0
- package/src/bed-layout/admitted/admitted-bed-layout.component.tsx +189 -0
- package/src/bed-layout/awaiting/awaiting-bed-layout.component.tsx +86 -0
- package/src/bed-layout/bed-layout.resource.ts +72 -0
- package/src/bed-layout/bed-layout.scss +55 -0
- package/src/bed-layout/discharged/discharged-bed-layout.component.tsx +109 -0
- package/src/bed-layout/discharged/discharged-bed-layout.resource.ts +99 -0
- package/src/bed-linelist-view/admitted/admitted-bed-linelist-view.component.tsx +420 -0
- package/src/bed-linelist-view/awaiting/awaiting-bed-linelist-view.component.tsx +224 -0
- package/src/bed-linelist-view/bed-linelist-view.scss +5 -0
- package/src/bed-linelist-view/discharged/discharged-bed-line-view.component.tsx +256 -0
- package/src/config-schema.ts +41 -9
- package/src/constants.ts +57 -0
- package/src/deceased-patient-header/deceased-patient-header.component.tsx +31 -0
- package/src/deceased-patient-header/deceased-patient-header.scss +50 -0
- package/src/{component → deceased-patient-header}/deceasedInfo/deceased-info.component.tsx +1 -1
- package/src/deceased-patient-header/deceasedInfo/deceased-info.resource.ts +11 -0
- package/src/extension/actionButton.component.tsx +5 -59
- package/src/extension/deceasedInfoBanner.component.tsx +5 -9
- package/src/{hook/useAdmitPatient.ts → forms/admit-deceased-person-workspace/admit-deceased-person.resource.ts} +177 -46
- package/src/forms/admit-deceased-person-workspace/admit-deceased-person.scss +143 -0
- package/src/forms/admit-deceased-person-workspace/admit-deceased-person.workspace.tsx +648 -0
- package/src/{hook/usePersonAttributes.ts → forms/discharge-deceased-person-workspace/discharge-body.resource.ts} +1 -1
- package/src/forms/discharge-deceased-person-workspace/discharge-body.scss +56 -0
- package/src/forms/discharge-deceased-person-workspace/discharge-body.workspace.tsx +362 -0
- package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.resource.ts +18 -0
- package/src/{workspaces/patientAdditionalInfoForm.scss → forms/dispose-deceased-person-workspace/dispose-deceased-person.scss} +46 -66
- package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.workspace.tsx +401 -0
- package/src/forms/form-entry-workspace/form-entry-workspace.workspace.tsx +62 -0
- package/src/forms/swap-compartment-workspace/swap-unit.scss +144 -0
- package/src/forms/swap-compartment-workspace/swap-unit.workspace.tsx +280 -0
- package/src/header/header.component.tsx +41 -0
- package/src/header/header.scss +58 -0
- package/src/home/home.component.tsx +87 -0
- package/src/home/home.resource.ts +261 -0
- package/src/home/home.scss +5 -0
- package/src/index.ts +18 -12
- package/src/metrics/metrics-card.component.tsx +31 -0
- package/src/metrics/metrics-card.scss +51 -0
- package/src/root.component.tsx +7 -3
- package/src/routes.json +25 -1
- package/src/schemas/index.ts +66 -0
- package/src/summary/summary.component.tsx +42 -0
- package/src/summary/summary.scss +10 -0
- package/src/switcher/content-switcher.component.tsx +220 -0
- package/src/switcher/content-switcher.scss +30 -0
- package/src/types/index.ts +336 -359
- package/src/utils/utils.ts +20 -2
- package/src/view-details/main/main.component.tsx +34 -0
- package/src/view-details/main/main.scss +45 -0
- package/src/view-details/panels/attachement.component.tsx +21 -0
- package/src/view-details/panels/autopsy.component.tsx +215 -0
- package/src/view-details/panels/billing-history.component.tsx +13 -0
- package/src/view-details/panels/observations/observation.component.tsx +57 -0
- package/src/view-details/panels/observations/observation.scss +24 -0
- package/src/view-details/panels/panels.scss +46 -0
- package/src/view-details/view-details.component.tsx +65 -0
- package/src/view-details/view-details.resource.ts +65 -0
- package/src/view-details/views-details.scss +82 -0
- package/translations/en.json +74 -21
- package/tsconfig.json +1 -1
- package/dist/113.js +0 -1
- package/dist/113.js.map +0 -1
- package/dist/160.js +0 -1
- package/dist/160.js.map +0 -1
- package/dist/299.js +0 -1
- package/dist/299.js.map +0 -1
- package/dist/433.js +0 -2
- package/dist/433.js.map +0 -1
- package/dist/441.js +0 -1
- package/dist/441.js.map +0 -1
- package/dist/496.js +0 -1
- package/dist/496.js.map +0 -1
- package/dist/511.js +0 -1
- package/dist/511.js.map +0 -1
- package/dist/603.js +0 -1
- package/dist/603.js.map +0 -1
- package/dist/610.js +0 -1
- package/dist/610.js.map +0 -1
- package/dist/612.js +0 -1
- package/dist/612.js.map +0 -1
- package/dist/656.js +0 -2
- package/dist/656.js.map +0 -1
- package/dist/752.js +0 -1
- package/dist/752.js.map +0 -1
- package/dist/754.js +0 -1
- package/dist/754.js.map +0 -1
- package/dist/781.js +0 -1
- package/dist/781.js.map +0 -1
- package/dist/801.js +0 -2
- package/dist/801.js.map +0 -1
- package/dist/817.js +0 -1
- package/dist/817.js.map +0 -1
- package/dist/877.js +0 -1
- package/dist/877.js.map +0 -1
- package/dist/924.js +0 -1
- package/dist/924.js.map +0 -1
- package/src/autosuggest/autosuggest.component.tsx +0 -162
- package/src/autosuggest/autosuggest.scss +0 -61
- package/src/autosuggest/patient-search-info.component.tsx +0 -75
- package/src/autosuggest/patient-search-info.scss +0 -62
- package/src/autosuggest/search-empty-state.component.tsx +0 -21
- package/src/autosuggest/search-empty-state.scss +0 -18
- package/src/card/avail-compartment.compartment.tsx +0 -94
- package/src/card/compartment-view.compartment.tsx +0 -62
- package/src/card/compartment.scss +0 -128
- package/src/card/compartmentSharing.component.tsx +0 -21
- package/src/card/compartmentSharing.scss +0 -24
- package/src/card/empty-compartment.component.tsx +0 -28
- package/src/card/empty-compartment.scss +0 -61
- package/src/component/main.component.tsx +0 -17
- package/src/component/next-of-kin-details/nextOfKinDetails.component.tsx +0 -50
- package/src/component/next-of-kin-details/nextOfKinDetails.scss +0 -37
- package/src/header/admitted-queue-header.component.tsx +0 -30
- package/src/header/admitted-queue-header.scss +0 -32
- package/src/header/morgue-header.component.tsx +0 -38
- package/src/header/morgue-header.scss +0 -95
- package/src/header/morgue-illustration.component.tsx +0 -13
- package/src/hook/useDeceasedPatients.ts +0 -12
- package/src/hook/useDischargedPatient.ts +0 -55
- package/src/hook/useMorgue.resource.ts +0 -163
- package/src/hook/useMortuaryAdmissionLocation.ts +0 -64
- package/src/tables/admitted-queue.component.tsx +0 -54
- package/src/tables/admitted-queue.scss +0 -62
- package/src/tables/discharge-queue.component.tsx +0 -87
- package/src/tables/generic-table.component.tsx +0 -140
- package/src/tables/generic-table.scss +0 -37
- package/src/tabs/tabs.component.tsx +0 -82
- package/src/tabs/tabs.scss +0 -15
- package/src/workspaces/admit-body.scss +0 -46
- package/src/workspaces/admit-body.workspace.tsx +0 -79
- package/src/workspaces/discharge-body.scss +0 -67
- package/src/workspaces/discharge-body.workspace.tsx +0 -329
- package/src/workspaces/patientAdditionalInfoForm.workspace.tsx +0 -562
- package/src/workspaces/swap-unit.scss +0 -46
- package/src/workspaces/swap-unit.workspace.tsx +0 -168
- /package/dist/{347.js.LICENSE.txt → 109.js.LICENSE.txt} +0 -0
- /package/dist/{656.js.LICENSE.txt → 373.js.LICENSE.txt} +0 -0
- /package/dist/{801.js.LICENSE.txt → 4.js.LICENSE.txt} +0 -0
- /package/dist/{433.js.LICENSE.txt → 420.js.LICENSE.txt} +0 -0
- /package/src/{component → deceased-patient-header}/deceasedInfo/deceased-info.scss +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FetchResponse,
|
|
3
|
+
openmrsFetch,
|
|
4
|
+
restBaseUrl,
|
|
5
|
+
fhirBaseUrl,
|
|
6
|
+
useConfig,
|
|
7
|
+
useFhirPagination,
|
|
8
|
+
} from '@openmrs/esm-framework';
|
|
9
|
+
import useSWR from 'swr';
|
|
10
|
+
import {
|
|
11
|
+
MappedVisitQueueEntry,
|
|
12
|
+
MortuaryPatient,
|
|
13
|
+
UseVisitQueueEntries,
|
|
14
|
+
VisitQueueEntry,
|
|
15
|
+
MortuaryLocationResponse,
|
|
16
|
+
Entry,
|
|
17
|
+
} from '../types';
|
|
18
|
+
import React, { useMemo, useEffect, useState } from 'react';
|
|
19
|
+
import { ConfigObject } from '../config-schema';
|
|
20
|
+
|
|
21
|
+
interface MortuaryApiResponse {
|
|
22
|
+
results: MortuaryPatient[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const useAwaitingQueuePatients = (admissionLocation?: MortuaryLocationResponse) => {
|
|
26
|
+
const { morgueDischargeEncounterTypeUuid } = useConfig<ConfigObject>();
|
|
27
|
+
const [currPageSize, setCurrPageSize] = useState(10);
|
|
28
|
+
|
|
29
|
+
const customRepresentation =
|
|
30
|
+
'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)))';
|
|
31
|
+
const url = `${restBaseUrl}/morgue/patient?v=${customRepresentation}&dead=true`;
|
|
32
|
+
const { isLoading, error, data, mutate } = useSWR<FetchResponse<MortuaryApiResponse>>(url, openmrsFetch);
|
|
33
|
+
|
|
34
|
+
const locationUuid = admissionLocation?.ward?.uuid;
|
|
35
|
+
const dischargeUrl =
|
|
36
|
+
locationUuid && morgueDischargeEncounterTypeUuid
|
|
37
|
+
? `${fhirBaseUrl}/Encounter?_summary=data&type=${morgueDischargeEncounterTypeUuid}&location=${locationUuid}`
|
|
38
|
+
: null;
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
data: dischargeData,
|
|
42
|
+
isLoading: dischargeLoading,
|
|
43
|
+
error: dischargeError,
|
|
44
|
+
mutate: mutateDischarge,
|
|
45
|
+
} = useFhirPagination<Entry>(dischargeUrl, currPageSize);
|
|
46
|
+
|
|
47
|
+
const dischargedPatientUuids = useMemo(() => {
|
|
48
|
+
if (!dischargeData || !Array.isArray(dischargeData)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const uuids = dischargeData
|
|
53
|
+
.map((entry: Entry) => {
|
|
54
|
+
const reference = entry?.subject?.reference;
|
|
55
|
+
if (reference && reference.startsWith('Patient/')) {
|
|
56
|
+
return reference.split('/')[1];
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
})
|
|
60
|
+
.filter((uuid: string | undefined) => uuid);
|
|
61
|
+
|
|
62
|
+
return [...new Set(uuids)];
|
|
63
|
+
}, [dischargeData]);
|
|
64
|
+
|
|
65
|
+
const admittedPatientUuids = useMemo(() => {
|
|
66
|
+
if (!admissionLocation?.bedLayouts) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return admissionLocation.bedLayouts
|
|
71
|
+
.flatMap((bed) => bed.patients?.map((patient) => patient.uuid))
|
|
72
|
+
.filter(Boolean) as string[];
|
|
73
|
+
}, [admissionLocation]);
|
|
74
|
+
|
|
75
|
+
const filteredAwaitingPatients = useMemo(() => {
|
|
76
|
+
if (!data?.data?.results) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return data.data.results.filter((patient) => {
|
|
81
|
+
const patientUuid = patient?.person?.person?.uuid;
|
|
82
|
+
if (!patientUuid) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (dischargedPatientUuids.includes(patientUuid)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (admittedPatientUuids.includes(patientUuid)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return true;
|
|
95
|
+
});
|
|
96
|
+
}, [data, dischargedPatientUuids, admittedPatientUuids]);
|
|
97
|
+
|
|
98
|
+
const admittedPatients = useMemo(() => {
|
|
99
|
+
if (!admissionLocation?.bedLayouts) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return admissionLocation.bedLayouts.flatMap((bed) => bed.patients || []).filter(Boolean);
|
|
104
|
+
}, [admissionLocation]);
|
|
105
|
+
|
|
106
|
+
const { dischargedPatients, dischargedPatientsCount } = useMemo(() => {
|
|
107
|
+
if (!dischargeData || !Array.isArray(dischargeData) || dischargeLoading) {
|
|
108
|
+
return { dischargedPatients: [], dischargedPatientsCount: 0 };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const discharged = dischargeData.filter((entry) => entry?.subject?.reference);
|
|
112
|
+
return {
|
|
113
|
+
dischargedPatients: discharged,
|
|
114
|
+
dischargedPatientsCount: discharged.length,
|
|
115
|
+
};
|
|
116
|
+
}, [dischargeData, dischargeLoading]);
|
|
117
|
+
|
|
118
|
+
const mutateAll = React.useCallback(() => {
|
|
119
|
+
mutate();
|
|
120
|
+
if (mutateDischarge) {
|
|
121
|
+
mutateDischarge();
|
|
122
|
+
}
|
|
123
|
+
}, [mutate, mutateDischarge]);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
awaitingQueueDeceasedPatients: filteredAwaitingPatients,
|
|
127
|
+
admittedPatients,
|
|
128
|
+
dischargedPatients,
|
|
129
|
+
dischargedPatientsCount,
|
|
130
|
+
isLoadingAwaitingQueuePatients: isLoading,
|
|
131
|
+
isLoadingDischarge: dischargeLoading,
|
|
132
|
+
isLoadingAll: isLoading || dischargeLoading,
|
|
133
|
+
errorFetchingAwaitingQueuePatients: error || dischargeError,
|
|
134
|
+
mutateAwaitingQueuePatients: mutateAll,
|
|
135
|
+
mutateAll,
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Custom hook specifically for awaiting patients (patients without active visits or beds)
|
|
141
|
+
* This hook is now simplified since the filtering is done upstream
|
|
142
|
+
* @param patients - Array of mortuary patients (already filtered by useAwaitingQueuePatients)
|
|
143
|
+
* @returns The same array of patients (already filtered upstream)
|
|
144
|
+
*/
|
|
145
|
+
export const useAwaitingPatients = (patients: MortuaryPatient[]) => {
|
|
146
|
+
// Since useAwaitingQueuePatients already filters out discharged and admitted patients,
|
|
147
|
+
// we can return the patients as-is
|
|
148
|
+
return patients || [];
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Fetches visit data for multiple patients
|
|
153
|
+
* @param patientUuids - Array of patient UUIDs
|
|
154
|
+
* @returns Promise that resolves to a record of visit statuses
|
|
155
|
+
*/
|
|
156
|
+
const fetchPatientVisits = async (patientUuids: string[]) => {
|
|
157
|
+
const visitPromises = patientUuids.map(async (uuid) => {
|
|
158
|
+
try {
|
|
159
|
+
const response = await openmrsFetch(`${restBaseUrl}/visit?patient=${uuid}&includeInactive=false`);
|
|
160
|
+
return {
|
|
161
|
+
uuid,
|
|
162
|
+
hasActiveVisit: response.data?.results?.length > 0,
|
|
163
|
+
};
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error(`Error fetching visit for patient ${uuid}:`, error);
|
|
166
|
+
return { uuid, hasActiveVisit: false };
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const visits = await Promise.all(visitPromises);
|
|
171
|
+
return visits.reduce((acc, { uuid, hasActiveVisit }) => {
|
|
172
|
+
acc[uuid] = hasActiveVisit;
|
|
173
|
+
return acc;
|
|
174
|
+
}, {} as Record<string, boolean>);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Custom hook to filter mortuary patients based on their visit status
|
|
179
|
+
* @param patients - Array of mortuary patients
|
|
180
|
+
* @returns Object containing filtered patients for different statuses
|
|
181
|
+
*/
|
|
182
|
+
export const useFilteredPatients = (patients: MortuaryPatient[]) => {
|
|
183
|
+
const [visitData, setVisitData] = useState<Record<string, boolean>>({});
|
|
184
|
+
const patientUuids = useMemo(() => {
|
|
185
|
+
return patients.map((patient) => patient?.person?.person?.uuid).filter(Boolean) as string[];
|
|
186
|
+
}, [patients]);
|
|
187
|
+
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
if (patientUuids.length > 0) {
|
|
190
|
+
fetchPatientVisits(patientUuids).then(setVisitData);
|
|
191
|
+
}
|
|
192
|
+
}, [patientUuids]);
|
|
193
|
+
|
|
194
|
+
return useMemo(() => {
|
|
195
|
+
if (!patients || patients.length === 0) {
|
|
196
|
+
return {
|
|
197
|
+
awaitingAdmission: [],
|
|
198
|
+
admitted: [],
|
|
199
|
+
all: [],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const awaitingAdmission: MortuaryPatient[] = [];
|
|
204
|
+
const admitted: MortuaryPatient[] = [];
|
|
205
|
+
|
|
206
|
+
patients.forEach((patient) => {
|
|
207
|
+
const patientUuid = patient?.person?.person?.uuid;
|
|
208
|
+
if (!patientUuid) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (visitData[patientUuid]) {
|
|
213
|
+
admitted.push(patient);
|
|
214
|
+
} else {
|
|
215
|
+
awaitingAdmission.push(patient);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
awaitingAdmission,
|
|
221
|
+
};
|
|
222
|
+
}, [patients, visitData]);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export function useVisitQueueEntry(patientUuid: string, visitUuid: string): UseVisitQueueEntries {
|
|
226
|
+
const apiUrl = `${restBaseUrl}/visit-queue-entry?v=full&patient=${patientUuid}`;
|
|
227
|
+
const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: { results: Array<VisitQueueEntry> } }, Error>(
|
|
228
|
+
apiUrl,
|
|
229
|
+
openmrsFetch,
|
|
230
|
+
);
|
|
231
|
+
const mapVisitQueueEntryProperties = (visitQueueEntry: VisitQueueEntry): MappedVisitQueueEntry => ({
|
|
232
|
+
id: visitQueueEntry.uuid,
|
|
233
|
+
name: visitQueueEntry.queueEntry.queue.display,
|
|
234
|
+
patientUuid: visitQueueEntry.queueEntry.patient.uuid,
|
|
235
|
+
priority:
|
|
236
|
+
visitQueueEntry.queueEntry.priority.display === 'Urgent'
|
|
237
|
+
? 'Priority'
|
|
238
|
+
: visitQueueEntry.queueEntry.priority.display,
|
|
239
|
+
priorityUuid: visitQueueEntry.queueEntry.priority.uuid,
|
|
240
|
+
service: visitQueueEntry.queueEntry.queue?.display,
|
|
241
|
+
status: visitQueueEntry.queueEntry.status.display,
|
|
242
|
+
statusUuid: visitQueueEntry.queueEntry.status.uuid,
|
|
243
|
+
visitUuid: visitQueueEntry.visit?.uuid,
|
|
244
|
+
visitType: visitQueueEntry.visit?.visitType?.display,
|
|
245
|
+
queue: visitQueueEntry.queueEntry.queue,
|
|
246
|
+
queueEntryUuid: visitQueueEntry.queueEntry.uuid,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const mappedVisitQueueEntry =
|
|
250
|
+
data?.data?.results
|
|
251
|
+
?.map(mapVisitQueueEntryProperties)
|
|
252
|
+
.filter((visitQueueEntry) => visitUuid !== undefined && visitUuid === visitQueueEntry.visitUuid)
|
|
253
|
+
.shift() ?? null;
|
|
254
|
+
return {
|
|
255
|
+
queueEntry: mappedVisitQueueEntry,
|
|
256
|
+
isLoading,
|
|
257
|
+
error: error,
|
|
258
|
+
isValidating,
|
|
259
|
+
mutate,
|
|
260
|
+
};
|
|
261
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
} from '@openmrs/esm-framework';
|
|
8
8
|
import { configSchema } from './config-schema';
|
|
9
9
|
import { createLeftPanelLink } from './left-panel/morgue-left-panel-link.component';
|
|
10
|
+
import FormEntryWorkspace from './forms/form-entry-workspace/form-entry-workspace.workspace';
|
|
11
|
+
import DisposeForm from './forms/dispose-deceased-person-workspace/dispose-deceased-person.workspace';
|
|
10
12
|
const moduleName = '@kenyaemr/esm-morgue-app';
|
|
11
13
|
|
|
12
14
|
const options = {
|
|
@@ -27,11 +29,6 @@ export function startupApp() {
|
|
|
27
29
|
parent: `${window.spaBase}/home`,
|
|
28
30
|
},
|
|
29
31
|
]);
|
|
30
|
-
registerFeatureFlag(
|
|
31
|
-
'mortuaryFeatureFlag',
|
|
32
|
-
'Mortuary App Service',
|
|
33
|
-
'Mortuary feature flag, this enables and disables the mortuary app feature',
|
|
34
|
-
);
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
export const root = getAsyncLifecycle(() => import('./root.component'), options);
|
|
@@ -44,12 +41,21 @@ export const morgueDashboardLink = getSyncLifecycle(
|
|
|
44
41
|
options,
|
|
45
42
|
);
|
|
46
43
|
|
|
47
|
-
export const patientAdditionalInfoForm = getAsyncLifecycle(
|
|
48
|
-
() => import('./workspaces/patientAdditionalInfoForm.workspace'),
|
|
49
|
-
options,
|
|
50
|
-
);
|
|
51
|
-
export const dischargeBodyForm = getAsyncLifecycle(() => import('./workspaces/discharge-body.workspace'), options);
|
|
52
|
-
export const admitBodyForm = getAsyncLifecycle(() => import('./workspaces/admit-body.workspace'), options);
|
|
53
|
-
export const swapForm = getAsyncLifecycle(() => import('./workspaces/swap-unit.workspace'), options);
|
|
54
44
|
export const actionBarButtons = getAsyncLifecycle(() => import('./extension/actionButton.component'), options);
|
|
55
45
|
export const bannerInfo = getAsyncLifecycle(() => import('./extension/deceasedInfoBanner.component'), options);
|
|
46
|
+
export const admitDeceasedPersonForm = getAsyncLifecycle(
|
|
47
|
+
() => import('./forms/admit-deceased-person-workspace/admit-deceased-person.workspace'),
|
|
48
|
+
options,
|
|
49
|
+
);
|
|
50
|
+
export const swapForm = getAsyncLifecycle(
|
|
51
|
+
() => import('./forms/swap-compartment-workspace/swap-unit.workspace'),
|
|
52
|
+
options,
|
|
53
|
+
);
|
|
54
|
+
export const dischargeBodyForm = getAsyncLifecycle(
|
|
55
|
+
() => import('./forms/discharge-deceased-person-workspace/discharge-body.workspace'),
|
|
56
|
+
options,
|
|
57
|
+
);
|
|
58
|
+
export const mortuaryFormEntry = getSyncLifecycle(FormEntryWorkspace, options);
|
|
59
|
+
export const disposeDeceasedPersonForm = getSyncLifecycle(DisposeForm, options);
|
|
60
|
+
export const mortuaryChartView = getAsyncLifecycle(() => import('./view-details/main/main.component'), options);
|
|
61
|
+
export const mortuarySummary = getAsyncLifecycle(() => import('./summary/summary.component'), options);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Layer, Tile } from '@carbon/react';
|
|
3
|
+
import styles from './metrics-card.scss';
|
|
4
|
+
|
|
5
|
+
interface MetricsCardProps {
|
|
6
|
+
label: string;
|
|
7
|
+
value: number | string | React.ReactNode;
|
|
8
|
+
headerLabel: string;
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const MetricsCard: React.FC<MetricsCardProps> = ({ children, headerLabel, label, value }) => {
|
|
13
|
+
return (
|
|
14
|
+
<Layer className={`${children && styles.cardWithChildren} ${styles.container}`}>
|
|
15
|
+
<Tile className={styles.tileContainer}>
|
|
16
|
+
<div className={styles.tileHeader}>
|
|
17
|
+
<div className={styles.headerLabelContainer}>
|
|
18
|
+
<label className={styles.headerLabel}>{headerLabel}</label>
|
|
19
|
+
</div>
|
|
20
|
+
{children}
|
|
21
|
+
</div>
|
|
22
|
+
<div>
|
|
23
|
+
<label className={styles.totalsLabel}>{label}</label>
|
|
24
|
+
<p className={styles.totalsValue}>{value}</p>
|
|
25
|
+
</div>
|
|
26
|
+
</Tile>
|
|
27
|
+
</Layer>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default MetricsCard;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
@use '@carbon/colors';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/type';
|
|
4
|
+
|
|
5
|
+
.container {
|
|
6
|
+
flex-grow: 1;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.tileContainer {
|
|
10
|
+
border: 1px solid colors.$gray-20;
|
|
11
|
+
height: 7.875rem;
|
|
12
|
+
padding: layout.$spacing-05;
|
|
13
|
+
margin: layout.$spacing-03 layout.$spacing-03;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.tileHeader {
|
|
17
|
+
display: flex;
|
|
18
|
+
justify-content: space-between;
|
|
19
|
+
align-items: baseline;
|
|
20
|
+
margin-bottom: layout.$spacing-03;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.headerLabel {
|
|
24
|
+
@include type.type-style('heading-compact-01');
|
|
25
|
+
color: colors.$gray-70;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.totalsLabel {
|
|
29
|
+
@include type.type-style('label-01');
|
|
30
|
+
color: colors.$gray-70;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.totalsValue {
|
|
34
|
+
@include type.type-style('heading-04');
|
|
35
|
+
color: colors.$gray-100;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.headerLabelContainer {
|
|
39
|
+
margin-bottom: layout.$spacing-05;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.link {
|
|
43
|
+
text-decoration: none;
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
color: colors.$blue-60;
|
|
47
|
+
|
|
48
|
+
svg {
|
|
49
|
+
margin-left: layout.$spacing-03;
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/root.component.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
|
3
3
|
import { UserHasAccess } from '@openmrs/esm-framework';
|
|
4
|
-
import MainComponent from './
|
|
5
|
-
import
|
|
4
|
+
import MainComponent from './home/home.component';
|
|
5
|
+
import DeceasedDetailsView from './view-details/main/main.component';
|
|
6
6
|
|
|
7
7
|
const Root: React.FC = () => {
|
|
8
8
|
const baseName = window.getOpenmrsSpaBase() + 'home/morgue';
|
|
@@ -12,7 +12,11 @@ const Root: React.FC = () => {
|
|
|
12
12
|
<UserHasAccess privilege="o3 : View Mortuary Dashboard">
|
|
13
13
|
<Routes>
|
|
14
14
|
<Route path="/" element={<MainComponent />} />
|
|
15
|
-
<Route
|
|
15
|
+
<Route
|
|
16
|
+
path="/patient/:patientUuid/compartment/:bedNumber/:bedId/mortuary-chart"
|
|
17
|
+
element={<DeceasedDetailsView />}
|
|
18
|
+
/>
|
|
19
|
+
<Route path="/patient/:patientUuid/mortuary-chart" element={<DeceasedDetailsView />} />
|
|
16
20
|
</Routes>
|
|
17
21
|
</UserHasAccess>
|
|
18
22
|
</BrowserRouter>
|
package/src/routes.json
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"component": "morgueDashboardLink",
|
|
10
10
|
"name": "morgue-dashboard-link",
|
|
11
11
|
"slot": "homepage-dashboard-slot",
|
|
12
|
-
"featureFlag": "mortuaryFeatureFlag",
|
|
13
12
|
"meta": {
|
|
14
13
|
"name": "morgue",
|
|
15
14
|
"title": "morgue",
|
|
@@ -47,6 +46,11 @@
|
|
|
47
46
|
"fullWidth": false
|
|
48
47
|
}
|
|
49
48
|
},
|
|
49
|
+
{
|
|
50
|
+
"name": "mortuary-summary-info",
|
|
51
|
+
"component": "mortuarySummary",
|
|
52
|
+
"slot": "mortuary-summary-info-slot"
|
|
53
|
+
},
|
|
50
54
|
{
|
|
51
55
|
"component": "root",
|
|
52
56
|
"name": "morgue-dashboard-root",
|
|
@@ -82,6 +86,26 @@
|
|
|
82
86
|
"component": "swapForm",
|
|
83
87
|
"title": "Swap form",
|
|
84
88
|
"type": "other-form"
|
|
89
|
+
},{
|
|
90
|
+
"name": "admit-deceased-person-form",
|
|
91
|
+
"component": "admitDeceasedPersonForm",
|
|
92
|
+
"title": "Admit Deceased Person",
|
|
93
|
+
"type": "other-form"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "mortuary-form-entry",
|
|
97
|
+
"component": "mortuaryFormEntry",
|
|
98
|
+
"title": "Mortuary Form Entry",
|
|
99
|
+
"type": "form",
|
|
100
|
+
"width": "extra-wide",
|
|
101
|
+
"canMaximize": true,
|
|
102
|
+
"canHide": true
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "dispose-deceased-person-form",
|
|
106
|
+
"component": "disposeDeceasedPersonForm",
|
|
107
|
+
"title": "Dispose Deceased Person",
|
|
108
|
+
"type": "other-form"
|
|
85
109
|
}
|
|
86
110
|
],
|
|
87
111
|
"pages": [
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const deceasedPatientAdmitSchema = z.object({
|
|
4
|
+
dateOfAdmission: z
|
|
5
|
+
.date({ coerce: true })
|
|
6
|
+
.refine((date) => !!date, 'Date of admission is required')
|
|
7
|
+
.refine((date) => date <= new Date(), 'Date of admission cannot be in the future'),
|
|
8
|
+
timeOfDeath: z.string().nonempty('Time of death is required'),
|
|
9
|
+
period: z
|
|
10
|
+
.string()
|
|
11
|
+
.nonempty('AM/PM is required')
|
|
12
|
+
.regex(/^(AM|PM)$/i, 'Invalid period'),
|
|
13
|
+
tagNumber: z.string().nonempty('Tag number is required'),
|
|
14
|
+
obNumber: z.string().optional(),
|
|
15
|
+
policeName: z.string().optional(),
|
|
16
|
+
policeIDNo: z.string().optional(),
|
|
17
|
+
dischargeArea: z.string().optional(),
|
|
18
|
+
visitType: z.string().uuid('invalid visit type'),
|
|
19
|
+
availableCompartment: z
|
|
20
|
+
.union([z.number(), z.string()])
|
|
21
|
+
.refine((val) => {
|
|
22
|
+
if (typeof val === 'string') {
|
|
23
|
+
return val.length > 0 && !isNaN(Number(val)) && Number(val) > 0;
|
|
24
|
+
}
|
|
25
|
+
return typeof val === 'number' && !isNaN(val) && val > 0;
|
|
26
|
+
}, 'Please select a valid compartment')
|
|
27
|
+
.transform((val) => (typeof val === 'string' ? Number(val) : val)),
|
|
28
|
+
services: z.array(z.string().uuid('invalid service')).nonempty('Must select one service'),
|
|
29
|
+
paymentMethod: z.string().uuid('invalid payment method'),
|
|
30
|
+
insuranceScheme: z.string().optional(),
|
|
31
|
+
policyNumber: z.string().optional(),
|
|
32
|
+
});
|
|
33
|
+
export const dischargeSchema = z.object({
|
|
34
|
+
dateOfDischarge: z.date({ coerce: true }).refine((date) => !!date, 'Date of discharge is required'),
|
|
35
|
+
timeOfDischarge: z.string().nonempty('Time of discharge is required'),
|
|
36
|
+
period: z
|
|
37
|
+
.string()
|
|
38
|
+
.nonempty('AM/PM is required')
|
|
39
|
+
.regex(/^(AM|PM)$/i, 'Invalid period'),
|
|
40
|
+
burialPermitNumber: z.string().nonempty('Burial Permit Number is required'),
|
|
41
|
+
nextOfKinNames: z.string().nonempty('Next of kin names is required'),
|
|
42
|
+
relationshipType: z.string().nonempty('Next of kin relationship is required'),
|
|
43
|
+
nextOfKinAddress: z.string().nonempty('Next of kin address is required'),
|
|
44
|
+
nextOfKinContact: z
|
|
45
|
+
.string()
|
|
46
|
+
.regex(/^\d{10}$/, 'Phone number must be exactly 10 digits')
|
|
47
|
+
.nonempty('Next of kin phone number is required'),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export const disposeSchema = z.object({
|
|
51
|
+
dateOfDischarge: z.date({ coerce: true }).refine((date) => !!date, 'Date of discharge is required'),
|
|
52
|
+
timeOfDischarge: z.string().nonempty('Time of discharge is required'),
|
|
53
|
+
period: z
|
|
54
|
+
.string()
|
|
55
|
+
.nonempty('AM/PM is required')
|
|
56
|
+
.regex(/^(AM|PM)$/i, 'Invalid period'),
|
|
57
|
+
serialNumber: z.string().nonempty('Serial Number is required'),
|
|
58
|
+
courtOrderCaseNumber: z.string().nonempty('Court Order Case Number is required'),
|
|
59
|
+
nextOfKinNames: z.string().nonempty('Next of kin names is required'),
|
|
60
|
+
relationshipType: z.string().nonempty('Next of kin relationship is required'),
|
|
61
|
+
nextOfKinAddress: z.string().nonempty('Next of kin address is required'),
|
|
62
|
+
nextOfKinContact: z
|
|
63
|
+
.string()
|
|
64
|
+
.regex(/^\d{10}$/, 'Phone number must be exactly 10 digits')
|
|
65
|
+
.nonempty('Next of kin phone number is required'),
|
|
66
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DataTableSkeleton, ContentSwitcher, Switch, InlineLoading } from '@carbon/react';
|
|
3
|
+
import { ArrowRight } from '@carbon/react/icons';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { ConfigurableLink, ErrorState } from '@openmrs/esm-framework';
|
|
6
|
+
import styles from './summary.scss';
|
|
7
|
+
import MetricsCard from '../metrics/metrics-card.component';
|
|
8
|
+
|
|
9
|
+
interface SummaryProps {
|
|
10
|
+
awaitingQueueCount: number;
|
|
11
|
+
admittedCount: number;
|
|
12
|
+
dischargedCount: number;
|
|
13
|
+
isLoading?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const Summary: React.FC<SummaryProps> = ({ awaitingQueueCount, admittedCount, dischargedCount, isLoading = false }) => {
|
|
17
|
+
const { t } = useTranslation();
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<div className={styles.cardContainer}>
|
|
22
|
+
<MetricsCard
|
|
23
|
+
headerLabel={t('awaitingAdmissionHeader', 'Awaiting Admission')}
|
|
24
|
+
label={t('totalCount', 'total')}
|
|
25
|
+
value={isLoading ? <InlineLoading /> : awaitingQueueCount.toString()}
|
|
26
|
+
/>
|
|
27
|
+
<MetricsCard
|
|
28
|
+
headerLabel={t('admittedHeader', 'Admitted')}
|
|
29
|
+
label={t('totalCount', 'total')}
|
|
30
|
+
value={admittedCount.toString()}
|
|
31
|
+
/>
|
|
32
|
+
<MetricsCard
|
|
33
|
+
headerLabel={t('dischargedHeader', 'Discharged')}
|
|
34
|
+
label={t('totalCount', 'total')}
|
|
35
|
+
value={dischargedCount.toString()}
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default Summary;
|