@kenyaemr/esm-morgue-app 5.4.2-pre.2344 → 5.4.2-pre.2349
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 +21 -26
- package/dist/162.js +2 -0
- package/dist/162.js.map +1 -0
- package/dist/197.js +1 -1
- package/dist/221.js +1 -1
- package/dist/221.js.map +1 -1
- package/dist/293.js +1 -1
- package/dist/294.js +1 -1
- package/dist/300.js +1 -1
- package/dist/351.js +1 -0
- package/dist/351.js.map +1 -0
- package/dist/38.js +1 -1
- package/dist/38.js.map +1 -1
- package/dist/404.js +1 -0
- package/dist/404.js.map +1 -0
- package/dist/441.js +1 -0
- package/dist/441.js.map +1 -0
- package/dist/467.js +1 -1
- package/dist/467.js.map +1 -1
- package/dist/500.js +1 -0
- package/dist/500.js.map +1 -0
- package/dist/570.js +1 -0
- package/dist/570.js.map +1 -0
- package/dist/608.js +2 -0
- package/dist/608.js.map +1 -0
- package/dist/611.js +2 -0
- package/dist/611.js.map +1 -0
- package/dist/632.js +2 -1
- package/dist/632.js.map +1 -1
- package/dist/653.js +1 -1
- package/dist/653.js.map +1 -1
- package/dist/657.js +1 -0
- package/dist/657.js.map +1 -0
- package/dist/805.js +1 -1
- package/dist/805.js.map +1 -1
- package/dist/814.js +2 -0
- package/dist/814.js.LICENSE.txt +5 -0
- package/dist/814.js.map +1 -0
- package/dist/824.js +1 -1
- package/dist/824.js.map +1 -1
- package/dist/845.js +1 -0
- package/dist/845.js.map +1 -0
- package/dist/888.js +1 -0
- package/dist/888.js.map +1 -0
- package/dist/899.js +1 -0
- package/dist/899.js.map +1 -0
- package/dist/918.js +1 -1
- package/dist/918.js.map +1 -1
- package/dist/990.js +1 -0
- package/dist/990.js.map +1 -0
- package/dist/kenyaemr-esm-morgue-app.js +1 -1
- package/dist/kenyaemr-esm-morgue-app.js.buildmanifest.json +258 -184
- package/dist/kenyaemr-esm-morgue-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +0 -6
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/bed/bed.component.tsx +63 -134
- package/src/bed/components/deceased-patient-card-header.component.tsx +73 -0
- package/src/bed/components/deceased-patient-info.component.tsx +47 -0
- package/src/bed/components/deceased-patient-status-footer.component.tsx +43 -0
- package/src/bed-layout/admitted/admitted-bed-layout.component.tsx +175 -96
- package/src/bed-layout/awaiting/awaiting-bed-layout.component.tsx +103 -36
- package/src/bed-layout/bed-layout.scss +4 -0
- package/src/bed-layout/discharged/discharged-bed-layout.component.tsx +131 -73
- package/src/bed-linelist-view/admitted/admitted-bed-linelist-view.component.tsx +182 -134
- package/src/bed-linelist-view/awaiting/awaiting-bed-linelist-view.component.tsx +115 -71
- package/src/bed-linelist-view/discharged/discharged-bed-line-view.component.tsx +181 -109
- package/src/config-schema.ts +140 -4
- package/src/context/deceased-person-context.tsx +33 -0
- package/src/extension/actionButton.component.tsx +1 -1
- package/src/forms/admit-deceased-person-workspace/admit-deceased-person.resource.ts +84 -166
- package/src/forms/admit-deceased-person-workspace/admit-deceased-person.scss +14 -0
- package/src/forms/admit-deceased-person-workspace/admit-deceased-person.workspace.tsx +504 -334
- package/src/forms/discharge-deceased-person-workspace/discharge-body.resource.ts +0 -1
- package/src/forms/discharge-deceased-person-workspace/discharge-body.scss +15 -0
- package/src/forms/discharge-deceased-person-workspace/discharge-body.workspace.tsx +303 -244
- package/src/helpers/expression-helper.ts +122 -0
- package/src/home/home.component.tsx +23 -4
- package/src/index.ts +0 -2
- package/src/metrics/metrics-card.component.tsx +2 -2
- package/src/routes.json +0 -6
- package/src/schemas/index.ts +243 -51
- package/src/summary/summary.component.tsx +16 -9
- package/src/switcher/content-switcher.component.tsx +61 -35
- package/src/switcher/content-switcher.scss +13 -0
- package/src/types/index.ts +43 -1
- package/translations/am.json +16 -6
- package/translations/en.json +16 -6
- package/translations/sw.json +16 -6
- package/dist/347.js +0 -1
- package/dist/347.js.map +0 -1
- package/dist/373.js +0 -2
- package/dist/373.js.map +0 -1
- package/dist/398.js +0 -1
- package/dist/398.js.map +0 -1
- package/dist/410.js +0 -1
- package/dist/410.js.map +0 -1
- package/dist/429.js +0 -2
- package/dist/429.js.map +0 -1
- package/dist/579.js +0 -2
- package/dist/579.js.map +0 -1
- package/dist/619.js +0 -1
- package/dist/619.js.map +0 -1
- package/dist/633.js +0 -1
- package/dist/633.js.map +0 -1
- package/dist/712.js +0 -1
- package/dist/712.js.map +0 -1
- package/dist/713.js +0 -1
- package/dist/713.js.map +0 -1
- package/dist/723.js +0 -1
- package/dist/723.js.map +0 -1
- package/dist/989.js +0 -2
- package/dist/989.js.map +0 -1
- package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.resource.ts +0 -18
- package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.scss +0 -84
- package/src/forms/dispose-deceased-person-workspace/dispose-deceased-person.workspace.tsx +0 -505
- /package/dist/{989.js.LICENSE.txt → 162.js.LICENSE.txt} +0 -0
- /package/dist/{373.js.LICENSE.txt → 608.js.LICENSE.txt} +0 -0
- /package/dist/{429.js.LICENSE.txt → 611.js.LICENSE.txt} +0 -0
- /package/dist/{579.js.LICENSE.txt → 632.js.LICENSE.txt} +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { EnhancedPatient, MortuaryPatient, Patient } from '../types';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Converts a string to uppercase.
|
|
3
5
|
* @param {string} str - The string to convert.
|
|
@@ -9,3 +11,123 @@ export const toUpperCase = (str) => {
|
|
|
9
11
|
} // Handle empty or undefined input
|
|
10
12
|
return str.toUpperCase();
|
|
11
13
|
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Transforms a MortuaryPatient object into an EnhancedPatient.
|
|
17
|
+
* @param {MortuaryPatient} mortuaryPatient - The MortuaryPatient object to transform.
|
|
18
|
+
* @return {EnhancedPatient} - The transformed EnhancedPatient.
|
|
19
|
+
*/
|
|
20
|
+
export const transformMortuaryPatient = (mortuaryPatient: MortuaryPatient): EnhancedPatient => ({
|
|
21
|
+
uuid: mortuaryPatient.person?.person?.uuid || mortuaryPatient.person?.uuid,
|
|
22
|
+
person: {
|
|
23
|
+
display: mortuaryPatient.person?.person?.display || mortuaryPatient.person?.display,
|
|
24
|
+
gender: mortuaryPatient.person?.person?.gender || mortuaryPatient.person?.gender,
|
|
25
|
+
age: mortuaryPatient.person?.person?.age || mortuaryPatient.person?.age,
|
|
26
|
+
deathDate: mortuaryPatient.person?.person?.deathDate || mortuaryPatient.person?.deathDate,
|
|
27
|
+
causeOfDeath: mortuaryPatient.person?.person?.causeOfDeath || mortuaryPatient.person?.causeOfDeath,
|
|
28
|
+
},
|
|
29
|
+
originalMortuaryPatient: mortuaryPatient,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Transforms a Patient object into an EnhancedPatient with bed information.
|
|
34
|
+
* @param {Patient} patient - The Patient object to transform.
|
|
35
|
+
* @param {Object} bedInfo - The bed information associated with the patient.
|
|
36
|
+
* @param {string} bedInfo.bedNumber - The bed number assigned to the patient.
|
|
37
|
+
* @param {number} bedInfo.bedId - The unique identifier for the bed.
|
|
38
|
+
* @param {string} [bedInfo.bedType] - The type of bed assigned, if available.
|
|
39
|
+
* @return {EnhancedPatient} - The transformed EnhancedPatient with bed details.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
export const transformAdmittedPatient = (
|
|
43
|
+
patient: Patient,
|
|
44
|
+
bedInfo: { bedNumber: string; bedId: number; bedType?: string },
|
|
45
|
+
): EnhancedPatient => ({
|
|
46
|
+
uuid: patient.uuid,
|
|
47
|
+
person: {
|
|
48
|
+
display: patient.person?.display,
|
|
49
|
+
gender: patient.person?.gender,
|
|
50
|
+
age: patient.person?.age,
|
|
51
|
+
deathDate: patient.person?.deathDate,
|
|
52
|
+
causeOfDeath: patient.person?.causeOfDeath,
|
|
53
|
+
},
|
|
54
|
+
bedInfo,
|
|
55
|
+
originalPatient: patient,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Transforms a Patient object into an EnhancedPatient with a discharged status and optional encounter date.
|
|
60
|
+
* @param {Patient} patient - The Patient object to transform.
|
|
61
|
+
* @param {string} [encounterDate] - The date of the encounter for the patient's discharge.
|
|
62
|
+
* @return {EnhancedPatient} - The transformed EnhancedPatient with a discharged status.
|
|
63
|
+
*/
|
|
64
|
+
export const transformDischargedPatient = (patient: Patient, encounterDate?: string): EnhancedPatient => ({
|
|
65
|
+
uuid: patient.uuid,
|
|
66
|
+
person: {
|
|
67
|
+
display: patient.person?.display,
|
|
68
|
+
gender: patient.person?.gender,
|
|
69
|
+
age: patient.person?.age,
|
|
70
|
+
deathDate: patient.person?.deathDate,
|
|
71
|
+
causeOfDeath: patient.person?.causeOfDeath,
|
|
72
|
+
},
|
|
73
|
+
isDischarged: true,
|
|
74
|
+
encounterDate,
|
|
75
|
+
originalPatient: patient,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Retrieves the original MortuaryPatient or Patient object from an EnhancedPatient,
|
|
80
|
+
* if present. If neither is available, returns null.
|
|
81
|
+
* @param {EnhancedPatient} enhancedPatient - The EnhancedPatient object to extract the original patient from.
|
|
82
|
+
* @return {MortuaryPatient | Patient | null} - The original MortuaryPatient or Patient if available, or null.
|
|
83
|
+
*/
|
|
84
|
+
export const getOriginalPatient = (enhancedPatient: EnhancedPatient): MortuaryPatient | Patient | null => {
|
|
85
|
+
if (enhancedPatient.originalMortuaryPatient) {
|
|
86
|
+
return enhancedPatient.originalMortuaryPatient;
|
|
87
|
+
}
|
|
88
|
+
if (enhancedPatient.originalPatient) {
|
|
89
|
+
return enhancedPatient.originalPatient;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Extracts the original Patient object from an EnhancedPatient, if available.
|
|
96
|
+
* @param {EnhancedPatient} enhancedPatient - The EnhancedPatient object to extract the original patient from.
|
|
97
|
+
* @return {Patient | null} - The original Patient if available, or null.
|
|
98
|
+
* If the EnhancedPatient has an originalPatient property, that will be returned directly.
|
|
99
|
+
* If the EnhancedPatient has an originalMortuaryPatient property with a patient property, that will be returned.
|
|
100
|
+
* If neither of the above conditions are met, a partial Patient object will be created from the EnhancedPatient data.
|
|
101
|
+
* Note that this partial Patient object will not have the full set of properties a real Patient object would have.
|
|
102
|
+
*/
|
|
103
|
+
export const extractPatientFromEnhanced = (enhancedPatient: EnhancedPatient): Patient | null => {
|
|
104
|
+
if (enhancedPatient.originalPatient) {
|
|
105
|
+
return enhancedPatient.originalPatient;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (enhancedPatient.originalMortuaryPatient?.patient) {
|
|
109
|
+
return enhancedPatient.originalMortuaryPatient.patient;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
uuid: enhancedPatient.uuid,
|
|
113
|
+
display: enhancedPatient.person.display,
|
|
114
|
+
identifiers: [],
|
|
115
|
+
person: {
|
|
116
|
+
uuid: enhancedPatient.uuid,
|
|
117
|
+
display: enhancedPatient.person.display,
|
|
118
|
+
gender: enhancedPatient.person.gender,
|
|
119
|
+
age: enhancedPatient.person.age,
|
|
120
|
+
birthdate: '',
|
|
121
|
+
birthdateEstimated: false,
|
|
122
|
+
dead: true,
|
|
123
|
+
deathDate: enhancedPatient.person.deathDate,
|
|
124
|
+
causeOfDeath: enhancedPatient.person.causeOfDeath || { uuid: '', display: '' },
|
|
125
|
+
preferredAddress: { uuid: '', display: '' },
|
|
126
|
+
attributes: [],
|
|
127
|
+
voided: false,
|
|
128
|
+
birthtime: null,
|
|
129
|
+
deathdateEstimated: false,
|
|
130
|
+
identifiers: [],
|
|
131
|
+
},
|
|
132
|
+
} as Patient;
|
|
133
|
+
};
|
|
@@ -6,10 +6,10 @@ import Summary from '../summary/summary.component';
|
|
|
6
6
|
import CustomContentSwitcher from '../switcher/content-switcher.component';
|
|
7
7
|
import { useAwaitingPatients, useAwaitingQueuePatients } from './home.resource';
|
|
8
8
|
import { useLocation, useMortuaryAdmissionLocation } from '../bed-layout/bed-layout.resource';
|
|
9
|
+
import { DataTableSkeleton } from '@carbon/react';
|
|
9
10
|
|
|
10
11
|
const HomeViewComponent: React.FC = () => {
|
|
11
12
|
const { t } = useTranslation();
|
|
12
|
-
|
|
13
13
|
const [selectedLocation, setSelectedLocation] = React.useState<string>('');
|
|
14
14
|
|
|
15
15
|
const { locations, isLoading: isLoadingLocation, error: locationError } = useLocation();
|
|
@@ -34,6 +34,24 @@ const HomeViewComponent: React.FC = () => {
|
|
|
34
34
|
mutateAll,
|
|
35
35
|
} = useAwaitingQueuePatients(admissionLocation);
|
|
36
36
|
|
|
37
|
+
const isInitialDataLoading = React.useMemo(() => {
|
|
38
|
+
return (
|
|
39
|
+
isLoadingLocation ||
|
|
40
|
+
isLoadingAdmission ||
|
|
41
|
+
isLoadingAwaitingQueuePatients ||
|
|
42
|
+
isLoadingDischarge ||
|
|
43
|
+
(!selectedLocation && locationItems?.length === 0)
|
|
44
|
+
);
|
|
45
|
+
}, [isLoadingLocation, isLoadingAdmission, isLoadingAwaitingQueuePatients, isLoadingDischarge, selectedLocation]);
|
|
46
|
+
|
|
47
|
+
const [hasInitialDataLoaded, setHasInitialDataLoaded] = React.useState(false);
|
|
48
|
+
|
|
49
|
+
React.useEffect(() => {
|
|
50
|
+
if (!isInitialDataLoading && selectedLocation) {
|
|
51
|
+
setHasInitialDataLoaded(true);
|
|
52
|
+
}
|
|
53
|
+
}, [isInitialDataLoading, selectedLocation]);
|
|
54
|
+
|
|
37
55
|
const mutateAllData = React.useCallback(() => {
|
|
38
56
|
mutateAdmissionLocation();
|
|
39
57
|
mutateAll();
|
|
@@ -48,7 +66,7 @@ const HomeViewComponent: React.FC = () => {
|
|
|
48
66
|
}, [locations]);
|
|
49
67
|
|
|
50
68
|
React.useEffect(() => {
|
|
51
|
-
if (locationItems
|
|
69
|
+
if (locationItems?.length === 1 && !selectedLocation) {
|
|
52
70
|
setSelectedLocation(locationItems[0].id);
|
|
53
71
|
}
|
|
54
72
|
}, [locationItems, selectedLocation]);
|
|
@@ -58,6 +76,7 @@ const HomeViewComponent: React.FC = () => {
|
|
|
58
76
|
const handleLocationChange = React.useCallback((data: { selectedItem: { id: string; text: string } }) => {
|
|
59
77
|
if (data.selectedItem) {
|
|
60
78
|
setSelectedLocation(data.selectedItem.id);
|
|
79
|
+
setHasInitialDataLoaded(false);
|
|
61
80
|
}
|
|
62
81
|
}, []);
|
|
63
82
|
|
|
@@ -68,11 +87,11 @@ const HomeViewComponent: React.FC = () => {
|
|
|
68
87
|
awaitingQueueCount={trulyAwaitingPatients.length}
|
|
69
88
|
admittedCount={admittedPatients.length}
|
|
70
89
|
dischargedCount={dischargedPatientsCount}
|
|
71
|
-
isLoading={
|
|
90
|
+
isLoading={isInitialDataLoading && !hasInitialDataLoaded}
|
|
72
91
|
/>
|
|
73
92
|
<CustomContentSwitcher
|
|
74
93
|
awaitingQueueDeceasedPatients={trulyAwaitingPatients}
|
|
75
|
-
isLoading={
|
|
94
|
+
isLoading={isInitialDataLoading && !hasInitialDataLoaded}
|
|
76
95
|
locationItems={locationItems}
|
|
77
96
|
selectedLocation={selectedLocation}
|
|
78
97
|
admissionLocation={admissionLocation}
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
import { configSchema } from './config-schema';
|
|
9
9
|
import { createLeftPanelLink } from './left-panel/morgue-left-panel-link.component';
|
|
10
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';
|
|
12
11
|
import PrintPostMortemOverflowMenuItem from './extension/overflow-menu-item-postmortem/print-postmorterm-report.component';
|
|
13
12
|
const moduleName = '@kenyaemr/esm-morgue-app';
|
|
14
13
|
|
|
@@ -57,7 +56,6 @@ export const dischargeBodyForm = getAsyncLifecycle(
|
|
|
57
56
|
options,
|
|
58
57
|
);
|
|
59
58
|
export const mortuaryFormEntry = getSyncLifecycle(FormEntryWorkspace, options);
|
|
60
|
-
export const disposeDeceasedPersonForm = getSyncLifecycle(DisposeForm, options);
|
|
61
59
|
export const mortuaryChartView = getAsyncLifecycle(() => import('./view-details/main/main.component'), options);
|
|
62
60
|
export const printConfirmationModal = getAsyncLifecycle(
|
|
63
61
|
() => import('./modals/mortuary-gate-pass/print-preview-confirmation.modal'),
|
|
@@ -3,9 +3,9 @@ import { Layer, Tile } from '@carbon/react';
|
|
|
3
3
|
import styles from './metrics-card.scss';
|
|
4
4
|
|
|
5
5
|
interface MetricsCardProps {
|
|
6
|
-
label: string;
|
|
6
|
+
label: string | React.ReactNode;
|
|
7
7
|
value: number | string | React.ReactNode;
|
|
8
|
-
headerLabel: string;
|
|
8
|
+
headerLabel: string | React.ReactNode;
|
|
9
9
|
children?: React.ReactNode;
|
|
10
10
|
}
|
|
11
11
|
|
package/src/routes.json
CHANGED
|
@@ -87,12 +87,6 @@
|
|
|
87
87
|
"width": "extra-wide",
|
|
88
88
|
"canMaximize": true,
|
|
89
89
|
"canHide": true
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
"name": "dispose-deceased-person-form",
|
|
93
|
-
"component": "disposeDeceasedPersonForm",
|
|
94
|
-
"title": "Dispose Deceased Person",
|
|
95
|
-
"type": "other-form"
|
|
96
90
|
}
|
|
97
91
|
],
|
|
98
92
|
"modals": [
|
package/src/schemas/index.ts
CHANGED
|
@@ -1,66 +1,258 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { ConfigObject } from '../config-schema';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
.
|
|
6
|
-
.
|
|
7
|
-
.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
4
|
+
const getConceptCodes = (config: ConfigObject) => ({
|
|
5
|
+
hospitalDeathCodes: [
|
|
6
|
+
config.locationOfDeathTypesList.find((item) => item.label === 'InPatient')?.concept,
|
|
7
|
+
config.locationOfDeathTypesList.find((item) => item.label === 'Outpatient')?.concept,
|
|
8
|
+
config.locationOfDeathTypesList.find((item) => item.label === 'Dead on arrival')?.concept,
|
|
9
|
+
].filter(Boolean) as string[],
|
|
10
|
+
homeDeathCode: config.locationOfDeathTypesList.find((item) => item.label === 'Home')?.concept,
|
|
11
|
+
policeCaseCode: config.locationOfDeathTypesList.find((item) => item.label === 'Unknown (Police case)')?.concept,
|
|
12
|
+
yesConfirmationCode: config.deathConfirmationTypes.find((type) => type.label === 'Yes')?.concept,
|
|
13
|
+
noConfirmationCode: config.deathConfirmationTypes.find((type) => type.label === 'No')?.concept,
|
|
14
|
+
bodyEmbalmmentCode: config.deadBodyPreservationTypeUuid.find((type) => type.label === 'Body embalment')?.concept,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const createDeceasedPatientAdmitSchema = (config?: ConfigObject) => {
|
|
18
|
+
const baseSchema = z.object({
|
|
19
|
+
dateOfAdmission: z
|
|
20
|
+
.date({ coerce: true })
|
|
21
|
+
.refine((date) => !!date, 'Date of admission is required')
|
|
22
|
+
.refine((date) => date <= new Date(), 'Date of admission cannot be in the future'),
|
|
23
|
+
placeOfDeath: z.string().min(1, 'Place of death is required'),
|
|
24
|
+
tagNumber: z.string().min(1, 'Tag number is required'),
|
|
25
|
+
visitType: z.string().uuid('Invalid visit type'),
|
|
26
|
+
availableCompartment: z
|
|
27
|
+
.union([z.number(), z.string()])
|
|
28
|
+
.refine((val) => {
|
|
29
|
+
if (typeof val === 'string') {
|
|
30
|
+
return val.length > 0 && !isNaN(Number(val)) && Number(val) > 0;
|
|
31
|
+
}
|
|
32
|
+
return typeof val === 'number' && !isNaN(val) && val > 0;
|
|
33
|
+
}, 'Please select a valid compartment')
|
|
34
|
+
.transform((val) => (typeof val === 'string' ? Number(val) : val)),
|
|
35
|
+
paymentMethod: z.string().uuid('Invalid payment method'),
|
|
36
|
+
services: z.array(z.string().uuid('Invalid service')).min(1, 'Must select at least one service'),
|
|
37
|
+
|
|
38
|
+
deathConfirmed: z.string().min(1, 'Death confirmation is required'),
|
|
39
|
+
autopsyPermission: z.string().min(1, 'Autopsy permission is required'),
|
|
40
|
+
|
|
41
|
+
dateOfDeath: z.date({ coerce: true }).optional(),
|
|
42
|
+
timeOfDeath: z.string().optional(),
|
|
43
|
+
period: z.string().optional(),
|
|
44
|
+
deathNotificationNumber: z.string().optional(),
|
|
45
|
+
attendingClinician: z.string().optional(),
|
|
46
|
+
doctorsRemarks: z.string().optional(),
|
|
47
|
+
causeOfDeath: z.string().optional(),
|
|
48
|
+
deadBodyPreservation: z.string().optional(),
|
|
49
|
+
bodyEmbalmentType: z.string().optional(),
|
|
50
|
+
obNumber: z.string().optional(),
|
|
51
|
+
policeName: z.string().optional(),
|
|
52
|
+
policeIDNo: z.string().optional(),
|
|
53
|
+
insuranceScheme: z.string().optional(),
|
|
54
|
+
policyNumber: z.string().optional(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!config) {
|
|
58
|
+
return baseSchema;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return baseSchema.superRefine((data, ctx) => {
|
|
62
|
+
const conceptCodes = getConceptCodes(config);
|
|
63
|
+
|
|
64
|
+
const isHospitalDeath = conceptCodes.hospitalDeathCodes.includes(data.placeOfDeath);
|
|
65
|
+
const isHomeDeath = data.placeOfDeath === conceptCodes.homeDeathCode;
|
|
66
|
+
const isPoliceCaseDeath = data.placeOfDeath === conceptCodes.policeCaseCode;
|
|
67
|
+
|
|
68
|
+
if (data.visitType !== config.morgueVisitTypeUuid) {
|
|
69
|
+
ctx.addIssue({
|
|
70
|
+
code: z.ZodIssueCode.custom,
|
|
71
|
+
message: 'Invalid visit type for mortuary',
|
|
72
|
+
path: ['visitType'],
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const validConfirmationCodes = [conceptCodes.yesConfirmationCode, conceptCodes.noConfirmationCode].filter(Boolean);
|
|
77
|
+
if (validConfirmationCodes.length > 0 && !validConfirmationCodes.includes(data.deathConfirmed)) {
|
|
78
|
+
ctx.addIssue({
|
|
79
|
+
code: z.ZodIssueCode.custom,
|
|
80
|
+
message: 'Invalid death confirmation value',
|
|
81
|
+
path: ['deathConfirmed'],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (validConfirmationCodes.length > 0 && !validConfirmationCodes.includes(data.autopsyPermission)) {
|
|
86
|
+
ctx.addIssue({
|
|
87
|
+
code: z.ZodIssueCode.custom,
|
|
88
|
+
message: 'Invalid autopsy permission value',
|
|
89
|
+
path: ['autopsyPermission'],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!isHomeDeath && data.placeOfDeath !== '') {
|
|
94
|
+
if (!data.dateOfDeath) {
|
|
95
|
+
ctx.addIssue({
|
|
96
|
+
code: z.ZodIssueCode.custom,
|
|
97
|
+
message: 'Date of death is required for non-home deaths',
|
|
98
|
+
path: ['dateOfDeath'],
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (!data.timeOfDeath || data.timeOfDeath.trim() === '') {
|
|
102
|
+
ctx.addIssue({
|
|
103
|
+
code: z.ZodIssueCode.custom,
|
|
104
|
+
message: 'Time of death is required for non-home deaths',
|
|
105
|
+
path: ['timeOfDeath'],
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (!data.period || data.period.trim() === '') {
|
|
109
|
+
ctx.addIssue({
|
|
110
|
+
code: z.ZodIssueCode.custom,
|
|
111
|
+
message: 'AM/PM is required for non-home deaths',
|
|
112
|
+
path: ['period'],
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (isHospitalDeath) {
|
|
118
|
+
if (!data.attendingClinician || data.attendingClinician.trim() === '') {
|
|
119
|
+
ctx.addIssue({
|
|
120
|
+
code: z.ZodIssueCode.custom,
|
|
121
|
+
message: 'Attending clinician is required for hospital deaths',
|
|
122
|
+
path: ['attendingClinician'],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (!data.doctorsRemarks || data.doctorsRemarks.trim() === '') {
|
|
126
|
+
ctx.addIssue({
|
|
127
|
+
code: z.ZodIssueCode.custom,
|
|
128
|
+
message: "Doctor's remarks are required for hospital deaths",
|
|
129
|
+
path: ['doctorsRemarks'],
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (isPoliceCaseDeath) {
|
|
135
|
+
if (!data.policeName || data.policeName.trim() === '') {
|
|
136
|
+
ctx.addIssue({
|
|
137
|
+
code: z.ZodIssueCode.custom,
|
|
138
|
+
message: 'Police name is required for police cases',
|
|
139
|
+
path: ['policeName'],
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (!data.policeIDNo || data.policeIDNo.trim() === '') {
|
|
143
|
+
ctx.addIssue({
|
|
144
|
+
code: z.ZodIssueCode.custom,
|
|
145
|
+
message: 'Police ID number is required for police cases',
|
|
146
|
+
path: ['policeIDNo'],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (!data.obNumber || data.obNumber.trim() === '') {
|
|
150
|
+
ctx.addIssue({
|
|
151
|
+
code: z.ZodIssueCode.custom,
|
|
152
|
+
message: 'OB number is required for police cases',
|
|
153
|
+
path: ['obNumber'],
|
|
154
|
+
});
|
|
24
155
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (data.deadBodyPreservation) {
|
|
159
|
+
const validPreservationTypes = config.deadBodyPreservationTypeUuid.map((type) => type.concept);
|
|
160
|
+
if (!validPreservationTypes.includes(data.deadBodyPreservation)) {
|
|
161
|
+
ctx.addIssue({
|
|
162
|
+
code: z.ZodIssueCode.custom,
|
|
163
|
+
message: 'Invalid body preservation type',
|
|
164
|
+
path: ['deadBodyPreservation'],
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (data.deadBodyPreservation === conceptCodes.bodyEmbalmmentCode) {
|
|
169
|
+
if (!data.bodyEmbalmentType || data.bodyEmbalmentType.trim() === '') {
|
|
170
|
+
ctx.addIssue({
|
|
171
|
+
code: z.ZodIssueCode.custom,
|
|
172
|
+
message: 'Body embalment type is required when body embalment is selected',
|
|
173
|
+
path: ['bodyEmbalmentType'],
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
const validEmbalmmentTypes = config.bodyEmbalmmentTypesUuid.map((type) => type.concept);
|
|
177
|
+
if (!validEmbalmmentTypes.includes(data.bodyEmbalmentType)) {
|
|
178
|
+
ctx.addIssue({
|
|
179
|
+
code: z.ZodIssueCode.custom,
|
|
180
|
+
message: 'Invalid body embalment type',
|
|
181
|
+
path: ['bodyEmbalmentType'],
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (data.paymentMethod === config.insurancepaymentModeUuid) {
|
|
189
|
+
if (!data.insuranceScheme || data.insuranceScheme.trim() === '') {
|
|
190
|
+
ctx.addIssue({
|
|
191
|
+
code: z.ZodIssueCode.custom,
|
|
192
|
+
message: 'Insurance scheme is required when insurance payment method is selected',
|
|
193
|
+
path: ['insuranceScheme'],
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (!data.policyNumber || data.policyNumber.trim() === '') {
|
|
197
|
+
ctx.addIssue({
|
|
198
|
+
code: z.ZodIssueCode.custom,
|
|
199
|
+
message: 'Policy number is required when insurance is selected',
|
|
200
|
+
path: ['policyNumber'],
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export const deceasedPatientAdmitSchema = createDeceasedPatientAdmitSchema();
|
|
208
|
+
|
|
209
|
+
const baseDischargeSchema = z.object({
|
|
210
|
+
burialPermitNumber: z.string().min(1, 'Burial Permit Number is required'),
|
|
211
|
+
nextOfKinNames: z.string().min(1, 'Next of kin names is required'),
|
|
212
|
+
relationshipType: z.string().min(1, 'Next of kin relationship is required'),
|
|
213
|
+
nextOfKinNationalId: z.string().min(1, 'Next of kin national ID is required'),
|
|
214
|
+
nextOfKinContact: z
|
|
215
|
+
.string()
|
|
216
|
+
.regex(/^\d{10}$/, 'Phone number must be exactly 10 digits')
|
|
217
|
+
.min(1, 'Next of kin phone number is required'),
|
|
32
218
|
});
|
|
33
|
-
|
|
219
|
+
|
|
220
|
+
const dischargeSchema = baseDischargeSchema.extend({
|
|
221
|
+
dischargeType: z.literal('discharge'),
|
|
34
222
|
dateOfDischarge: z.date({ coerce: true }).refine((date) => !!date, 'Date of discharge is required'),
|
|
35
|
-
timeOfDischarge: z.string().
|
|
223
|
+
timeOfDischarge: z.string().min(1, 'Time of discharge is required'),
|
|
36
224
|
period: z
|
|
37
225
|
.string()
|
|
38
|
-
.
|
|
226
|
+
.min(1, 'AM/PM is required')
|
|
39
227
|
.regex(/^(AM|PM)$/i, 'Invalid period'),
|
|
40
|
-
|
|
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'),
|
|
228
|
+
dischargeArea: z.string().min(1, 'Discharge Area is required'),
|
|
48
229
|
});
|
|
49
230
|
|
|
50
|
-
|
|
231
|
+
const transferSchema = baseDischargeSchema.extend({
|
|
232
|
+
dischargeType: z.literal('transfer'),
|
|
51
233
|
dateOfDischarge: z.date({ coerce: true }).refine((date) => !!date, 'Date of discharge is required'),
|
|
52
|
-
timeOfDischarge: z.string().
|
|
234
|
+
timeOfDischarge: z.string().min(1, 'Time of discharge is required'),
|
|
53
235
|
period: z
|
|
54
236
|
.string()
|
|
55
|
-
.
|
|
237
|
+
.min(1, 'AM/PM is required')
|
|
56
238
|
.regex(/^(AM|PM)$/i, 'Invalid period'),
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
239
|
+
dischargeArea: z.string().min(1, 'Discharge Area is required'),
|
|
240
|
+
receivingArea: z.string().min(1, 'Receiving mortuary is required'),
|
|
241
|
+
reasonForTransfer: z.string().min(1, 'Reason for transfer is required'),
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const disposeSchema = baseDischargeSchema.extend({
|
|
245
|
+
dischargeType: z.literal('dispose'),
|
|
246
|
+
dischargeArea: z.string().min(1, 'Discharge Area is required'),
|
|
247
|
+
serialNumber: z.string().min(1, 'Serial Number is required'),
|
|
248
|
+
courtOrderCaseNumber: z.string().min(1, 'Court Order Case Number is required'),
|
|
66
249
|
});
|
|
250
|
+
|
|
251
|
+
export const dischargeFormSchema = z.discriminatedUnion('dischargeType', [
|
|
252
|
+
dischargeSchema,
|
|
253
|
+
transferSchema,
|
|
254
|
+
disposeSchema,
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
export type DischargeFormValues = z.infer<typeof dischargeFormSchema>;
|
|
258
|
+
export type DischargeType = 'discharge' | 'transfer' | 'dispose';
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DataTableSkeleton,
|
|
4
|
+
ContentSwitcher,
|
|
5
|
+
Switch,
|
|
6
|
+
InlineLoading,
|
|
7
|
+
SkeletonText,
|
|
8
|
+
RadioButtonSkeleton,
|
|
9
|
+
} from '@carbon/react';
|
|
3
10
|
import { ArrowRight } from '@carbon/react/icons';
|
|
4
11
|
import { useTranslation } from 'react-i18next';
|
|
5
12
|
import { ConfigurableLink, ErrorState } from '@openmrs/esm-framework';
|
|
@@ -20,19 +27,19 @@ const Summary: React.FC<SummaryProps> = ({ awaitingQueueCount, admittedCount, di
|
|
|
20
27
|
<>
|
|
21
28
|
<div className={styles.cardContainer}>
|
|
22
29
|
<MetricsCard
|
|
23
|
-
headerLabel={t('awaitingAdmissionHeader', 'Awaiting Admission')}
|
|
24
|
-
label={t('totalCount', 'total')}
|
|
30
|
+
headerLabel={isLoading ? <RadioButtonSkeleton /> : t('awaitingAdmissionHeader', 'Awaiting Admission')}
|
|
31
|
+
label={isLoading ? <SkeletonText /> : t('totalCount', 'total')}
|
|
25
32
|
value={isLoading ? <InlineLoading /> : awaitingQueueCount.toString()}
|
|
26
33
|
/>
|
|
27
34
|
<MetricsCard
|
|
28
|
-
headerLabel={t('admittedHeader', 'Admitted')}
|
|
29
|
-
label={t('totalCount', 'total')}
|
|
30
|
-
value={admittedCount.toString()}
|
|
35
|
+
headerLabel={isLoading ? <RadioButtonSkeleton /> : t('admittedHeader', 'Admitted')}
|
|
36
|
+
label={isLoading ? <SkeletonText /> : t('totalCount', 'total')}
|
|
37
|
+
value={isLoading ? <InlineLoading /> : admittedCount.toString()}
|
|
31
38
|
/>
|
|
32
39
|
<MetricsCard
|
|
33
|
-
headerLabel={t('dischargedHeader', 'Discharged')}
|
|
34
|
-
label={t('totalCount', 'total')}
|
|
35
|
-
value={dischargedCount.toString()}
|
|
40
|
+
headerLabel={isLoading ? <RadioButtonSkeleton /> : t('dischargedHeader', 'Discharged')}
|
|
41
|
+
label={isLoading ? <SkeletonText /> : t('totalCount', 'total')}
|
|
42
|
+
value={isLoading ? <InlineLoading /> : dischargedCount.toString()}
|
|
36
43
|
/>
|
|
37
44
|
</div>
|
|
38
45
|
</>
|