@kenyaemr/esm-ward-app 8.5.1-pre.52 → 8.5.1-pre.65
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 +30 -20
- package/dist/1498.js +1 -1
- package/dist/1498.js.LICENSE.txt +65 -0
- package/dist/1498.js.map +1 -1
- package/dist/1899.js +1 -1
- package/dist/1899.js.map +1 -1
- package/dist/1917.js +1 -1
- package/dist/1917.js.map +1 -1
- package/dist/2557.js +1 -1
- package/dist/2557.js.map +1 -1
- package/dist/2932.js +1 -1
- package/dist/3103.js +1 -0
- package/dist/3103.js.map +1 -0
- package/dist/3104.js +1 -0
- package/dist/3365.js +1 -1
- package/dist/3737.js +1 -1
- package/dist/4224.js +1 -2
- package/dist/4224.js.map +1 -1
- package/dist/4300.js +1 -1
- package/dist/6009.js +1 -1
- package/dist/6009.js.map +1 -1
- package/dist/6871.js +1 -2
- package/dist/6871.js.map +1 -1
- package/dist/717.js +1 -0
- package/dist/717.js.map +1 -0
- package/dist/7179.js +1 -2
- package/dist/7179.js.map +1 -1
- package/dist/723.js +1 -1
- package/dist/723.js.map +1 -0
- package/dist/7449.js +1 -1
- package/dist/7449.js.map +1 -1
- package/dist/7495.js +2 -0
- package/dist/7495.js.map +1 -0
- package/dist/7524.js +1 -1
- package/dist/7524.js.map +1 -1
- package/dist/7661.js +1 -1
- package/dist/7661.js.map +1 -1
- package/dist/7893.js +1 -0
- package/dist/7893.js.map +1 -0
- package/dist/8130.js +1 -0
- package/dist/8130.js.map +1 -0
- package/dist/8205.js +1 -1
- package/dist/8308.js +1 -1
- package/dist/8308.js.map +1 -1
- package/dist/8411.js +1 -0
- package/dist/8411.js.map +1 -0
- package/dist/8501.js +1 -2
- package/dist/8501.js.map +1 -1
- package/dist/9045.js +1 -1
- package/dist/9045.js.map +1 -1
- package/dist/9830.js +1 -0
- package/dist/9830.js.map +1 -0
- package/dist/9876.js +1 -2
- package/dist/9876.js.map +1 -1
- package/dist/{1879.js → 9954.js} +1 -1
- package/dist/9954.js.map +1 -0
- package/dist/kenyaemr-esm-ward-app.js +1 -1
- package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +251 -326
- package/dist/kenyaemr-esm-ward-app.js.map +1 -1
- package/dist/main.js +2 -1
- package/dist/main.js.LICENSE.txt +114 -0
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +61 -0
- package/src/hooks/useSummaryMetrics.ts +1 -1
- package/src/in-patient/admission-request.component.tsx +114 -0
- package/src/in-patient/admission-request.test.tsx +472 -0
- package/src/in-patient/encounter-observations/encounter-observations.component.tsx +71 -0
- package/src/in-patient/encounter-observations/index.ts +3 -0
- package/src/in-patient/encounter-observations/styles.scss +22 -0
- package/src/in-patient/encounter-observations/visit.resource.tsx +161 -0
- package/src/in-patient/in-patient-table/in-patient-table.component.tsx +155 -0
- package/src/in-patient/in-patient-table/in-patient-table.scss +37 -0
- package/src/in-patient/in-patient.component.tsx +32 -0
- package/src/in-patient/in-patient.meta.tsx +10 -0
- package/src/in-patient/in-patient.resource.tsx +56 -0
- package/src/in-patient/inpatient-detail-view.component.tsx +146 -0
- package/src/in-patient/inpatient-forms.component.tsx +75 -0
- package/src/in-patient/inpatient.scss +23 -0
- package/src/index.ts +9 -0
- package/src/routes.json +20 -0
- package/src/ward-view/linelist-wards/LineListTable.tsx +2 -2
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +17 -8
- package/src/ward-workspace/admit-patient-form-workspace/patient-admission.resources.ts +2 -6
- package/src/ward.resource.ts +10 -2
- package/translations/en.json +25 -0
- package/dist/1879.js.map +0 -1
- package/dist/1919.js +0 -1
- package/dist/2123.js +0 -1
- package/dist/2123.js.map +0 -1
- package/dist/237.js +0 -2
- package/dist/237.js.LICENSE.txt +0 -54
- package/dist/237.js.map +0 -1
- package/dist/2898.js +0 -2
- package/dist/2898.js.map +0 -1
- package/dist/2953.js +0 -2
- package/dist/2953.js.LICENSE.txt +0 -9
- package/dist/2953.js.map +0 -1
- package/dist/4191.js +0 -2
- package/dist/4191.js.LICENSE.txt +0 -9
- package/dist/4191.js.map +0 -1
- package/dist/4224.js.LICENSE.txt +0 -9
- package/dist/4300.js.map +0 -1
- package/dist/465.js +0 -1
- package/dist/465.js.map +0 -1
- package/dist/681.js +0 -2
- package/dist/681.js.LICENSE.txt +0 -9
- package/dist/681.js.map +0 -1
- package/dist/6871.js.LICENSE.txt +0 -9
- package/dist/7179.js.LICENSE.txt +0 -9
- package/dist/8501.js.LICENSE.txt +0 -5
- package/dist/8622.js +0 -1
- package/dist/8622.js.map +0 -1
- package/dist/9876.js.LICENSE.txt +0 -9
- /package/dist/{2898.js.LICENSE.txt → 7495.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { OpenmrsResource, Privilege } from '@openmrs/esm-framework';
|
|
2
|
+
|
|
3
|
+
export interface MappedEncounter {
|
|
4
|
+
id: string;
|
|
5
|
+
datetime: string;
|
|
6
|
+
encounterType: string;
|
|
7
|
+
editPrivilege: string;
|
|
8
|
+
form: OpenmrsResource;
|
|
9
|
+
obs: Array<Observation>;
|
|
10
|
+
provider: string;
|
|
11
|
+
visitUuid: string;
|
|
12
|
+
visitType: string;
|
|
13
|
+
visitTypeUuid?: string;
|
|
14
|
+
visitStartDatetime?: string;
|
|
15
|
+
visitStopDatetime?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Encounter {
|
|
19
|
+
uuid: string;
|
|
20
|
+
diagnoses: Array<Diagnosis>;
|
|
21
|
+
encounterDatetime: string;
|
|
22
|
+
encounterProviders: Array<EncounterProvider>;
|
|
23
|
+
encounterType: {
|
|
24
|
+
uuid: string;
|
|
25
|
+
display: string;
|
|
26
|
+
viewPrivilege: Privilege;
|
|
27
|
+
editPrivilege: Privilege;
|
|
28
|
+
};
|
|
29
|
+
obs: Array<Observation>;
|
|
30
|
+
orders: Array<Order>;
|
|
31
|
+
form: OpenmrsResource;
|
|
32
|
+
patient: OpenmrsResource;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface EncounterProvider {
|
|
36
|
+
uuid: string;
|
|
37
|
+
display: string;
|
|
38
|
+
encounterRole: {
|
|
39
|
+
uuid: string;
|
|
40
|
+
display: string;
|
|
41
|
+
};
|
|
42
|
+
provider: {
|
|
43
|
+
uuid: string;
|
|
44
|
+
person: {
|
|
45
|
+
uuid: string;
|
|
46
|
+
display: string;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface Observation {
|
|
52
|
+
uuid: string;
|
|
53
|
+
concept: {
|
|
54
|
+
uuid: string;
|
|
55
|
+
display: string;
|
|
56
|
+
conceptClass: {
|
|
57
|
+
uuid: string;
|
|
58
|
+
display: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
display: string;
|
|
62
|
+
groupMembers: null | Array<{
|
|
63
|
+
uuid: string;
|
|
64
|
+
concept: {
|
|
65
|
+
uuid: string;
|
|
66
|
+
display: string;
|
|
67
|
+
};
|
|
68
|
+
value: {
|
|
69
|
+
uuid: string;
|
|
70
|
+
display: string;
|
|
71
|
+
};
|
|
72
|
+
display: string;
|
|
73
|
+
}>;
|
|
74
|
+
value: any;
|
|
75
|
+
obsDatetime?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface Order {
|
|
79
|
+
uuid: string;
|
|
80
|
+
dateActivated: string;
|
|
81
|
+
dateStopped?: Date | null;
|
|
82
|
+
dose: number;
|
|
83
|
+
dosingInstructions: string | null;
|
|
84
|
+
dosingType?: 'org.openmrs.FreeTextDosingInstructions' | 'org.openmrs.SimpleDosingInstructions';
|
|
85
|
+
doseUnits: {
|
|
86
|
+
uuid: string;
|
|
87
|
+
display: string;
|
|
88
|
+
};
|
|
89
|
+
drug: {
|
|
90
|
+
uuid: string;
|
|
91
|
+
name: string;
|
|
92
|
+
strength: string;
|
|
93
|
+
display: string;
|
|
94
|
+
};
|
|
95
|
+
duration: number;
|
|
96
|
+
durationUnits: {
|
|
97
|
+
uuid: string;
|
|
98
|
+
display: string;
|
|
99
|
+
};
|
|
100
|
+
frequency: {
|
|
101
|
+
uuid: string;
|
|
102
|
+
display: string;
|
|
103
|
+
};
|
|
104
|
+
numRefills: number;
|
|
105
|
+
orderNumber: string;
|
|
106
|
+
orderReason: string | null;
|
|
107
|
+
orderReasonNonCoded: string | null;
|
|
108
|
+
orderer: {
|
|
109
|
+
uuid: string;
|
|
110
|
+
person: {
|
|
111
|
+
uuid: string;
|
|
112
|
+
display: string;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
orderType: {
|
|
116
|
+
uuid: string;
|
|
117
|
+
display: string;
|
|
118
|
+
};
|
|
119
|
+
route: {
|
|
120
|
+
uuid: string;
|
|
121
|
+
display: string;
|
|
122
|
+
};
|
|
123
|
+
quantity: number;
|
|
124
|
+
quantityUnits: OpenmrsResource;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface Note {
|
|
128
|
+
concept: OpenmrsResource;
|
|
129
|
+
note: string;
|
|
130
|
+
provider: {
|
|
131
|
+
name: string;
|
|
132
|
+
role: string;
|
|
133
|
+
};
|
|
134
|
+
time: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface OrderItem {
|
|
138
|
+
order: Order;
|
|
139
|
+
provider: {
|
|
140
|
+
name: string;
|
|
141
|
+
role: string;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface Diagnosis {
|
|
146
|
+
certainty: string;
|
|
147
|
+
display: string;
|
|
148
|
+
encounter: OpenmrsResource;
|
|
149
|
+
links: Array<any>;
|
|
150
|
+
patient: OpenmrsResource;
|
|
151
|
+
rank: number;
|
|
152
|
+
resourceVersion: string;
|
|
153
|
+
uuid: string;
|
|
154
|
+
voided: boolean;
|
|
155
|
+
diagnosis: {
|
|
156
|
+
coded: {
|
|
157
|
+
display: string;
|
|
158
|
+
links: Array<any>;
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
DataTable,
|
|
4
|
+
TableContainer,
|
|
5
|
+
Table,
|
|
6
|
+
TableHead,
|
|
7
|
+
TableRow,
|
|
8
|
+
TableExpandHeader,
|
|
9
|
+
TableHeader,
|
|
10
|
+
TableBody,
|
|
11
|
+
TableExpandRow,
|
|
12
|
+
TableExpandedRow,
|
|
13
|
+
TableCell,
|
|
14
|
+
Button,
|
|
15
|
+
} from '@carbon/react';
|
|
16
|
+
import { Edit } from '@carbon/react/icons';
|
|
17
|
+
import { type Encounter } from '../encounter-observations/visit.resource';
|
|
18
|
+
import { useTranslation } from 'react-i18next';
|
|
19
|
+
import { formatDatetime, launchWorkspace, usePagination, type Visit } from '@openmrs/esm-framework';
|
|
20
|
+
import EncounterObservations from '../encounter-observations';
|
|
21
|
+
import { EmptyState, PatientChartPagination } from '@openmrs/esm-patient-common-lib';
|
|
22
|
+
import styles from './in-patient-table.scss';
|
|
23
|
+
import { mutate } from 'swr';
|
|
24
|
+
|
|
25
|
+
type InPatientTableProps = {
|
|
26
|
+
tableRows: Array<Encounter>;
|
|
27
|
+
currentVisit: Visit;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const InPatientTable: React.FC<InPatientTableProps> = ({ tableRows }) => {
|
|
31
|
+
const { t } = useTranslation();
|
|
32
|
+
const headers = [
|
|
33
|
+
{ key: 'dateTime', header: t('dateDate', 'Date & time') },
|
|
34
|
+
{ key: 'formName', header: t('formName', 'Form Name') },
|
|
35
|
+
{ key: 'provider', header: t('provider', 'Provider') },
|
|
36
|
+
{ key: 'encounterType', header: t('encounterType', 'Encounter Type') },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const { results, goTo, currentPage } = usePagination(tableRows, 10);
|
|
40
|
+
|
|
41
|
+
const paginatedRows = useMemo(() => {
|
|
42
|
+
return results.map((row) => ({
|
|
43
|
+
id: row.uuid,
|
|
44
|
+
dateTime: formatDatetime(new Date(row.encounterDatetime), {
|
|
45
|
+
mode: 'standard',
|
|
46
|
+
}),
|
|
47
|
+
formName: row.form.display,
|
|
48
|
+
provider: row.encounterProviders[0]?.provider?.person?.display,
|
|
49
|
+
encounterType: row.encounterType.display,
|
|
50
|
+
}));
|
|
51
|
+
}, [results]);
|
|
52
|
+
|
|
53
|
+
const onEncounterEdit = (encounter: Encounter) => {
|
|
54
|
+
launchWorkspace('patient-form-entry-workspace', {
|
|
55
|
+
workspaceTitle: encounter.form.display,
|
|
56
|
+
mutateForm: () => {
|
|
57
|
+
mutate((key) => typeof key === 'string' && key.startsWith(`/ws/rest/v1/encounter`), undefined, {
|
|
58
|
+
revalidate: true,
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
formInfo: {
|
|
62
|
+
encounterUuid: encounter.uuid,
|
|
63
|
+
formUuid: encounter?.form?.uuid,
|
|
64
|
+
additionalProps: {},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (results.length === 0) {
|
|
70
|
+
return (
|
|
71
|
+
<EmptyState displayText={t('noEncounters', 'No encounters found')} headerTitle={t('encounters', 'Encounters')} />
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<>
|
|
77
|
+
<DataTable size="sm" useZebraStyles rows={paginatedRows} headers={headers}>
|
|
78
|
+
{({
|
|
79
|
+
rows,
|
|
80
|
+
headers,
|
|
81
|
+
getHeaderProps,
|
|
82
|
+
getRowProps,
|
|
83
|
+
|
|
84
|
+
getTableProps,
|
|
85
|
+
getTableContainerProps,
|
|
86
|
+
}) => (
|
|
87
|
+
<TableContainer
|
|
88
|
+
title={t('encounters', 'Encounters')}
|
|
89
|
+
description={t('encountersDescription', 'List of encounters during the current visit')}
|
|
90
|
+
{...getTableContainerProps()}>
|
|
91
|
+
<Table {...getTableProps()} aria-label="sample table">
|
|
92
|
+
<TableHead>
|
|
93
|
+
<TableRow>
|
|
94
|
+
<TableExpandHeader aria-label="expand row" />
|
|
95
|
+
{headers.map((header, i) => (
|
|
96
|
+
<TableHeader
|
|
97
|
+
key={i}
|
|
98
|
+
{...getHeaderProps({
|
|
99
|
+
header,
|
|
100
|
+
})}>
|
|
101
|
+
{header.header}
|
|
102
|
+
</TableHeader>
|
|
103
|
+
))}
|
|
104
|
+
</TableRow>
|
|
105
|
+
</TableHead>
|
|
106
|
+
<TableBody>
|
|
107
|
+
{rows.map((row, index) => (
|
|
108
|
+
<React.Fragment key={row.id}>
|
|
109
|
+
<TableExpandRow
|
|
110
|
+
{...getRowProps({
|
|
111
|
+
row,
|
|
112
|
+
})}>
|
|
113
|
+
{row.cells.map((cell) => (
|
|
114
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
115
|
+
))}
|
|
116
|
+
</TableExpandRow>
|
|
117
|
+
{row.isExpanded ? (
|
|
118
|
+
<TableExpandedRow className={styles.expandedRow} colSpan={headers.length + 2}>
|
|
119
|
+
<>
|
|
120
|
+
<EncounterObservations observations={results[index].obs} />
|
|
121
|
+
|
|
122
|
+
<>
|
|
123
|
+
{results[index]?.form?.uuid && (
|
|
124
|
+
<Button
|
|
125
|
+
kind="ghost"
|
|
126
|
+
onClick={() => onEncounterEdit(results[index])}
|
|
127
|
+
renderIcon={(props) => <Edit size={16} {...props} />}>
|
|
128
|
+
{t('editThisEncounter', 'Edit this encounter')}
|
|
129
|
+
</Button>
|
|
130
|
+
)}
|
|
131
|
+
</>
|
|
132
|
+
</>
|
|
133
|
+
</TableExpandedRow>
|
|
134
|
+
) : (
|
|
135
|
+
<TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
|
|
136
|
+
)}
|
|
137
|
+
</React.Fragment>
|
|
138
|
+
))}
|
|
139
|
+
</TableBody>
|
|
140
|
+
</Table>
|
|
141
|
+
</TableContainer>
|
|
142
|
+
)}
|
|
143
|
+
</DataTable>
|
|
144
|
+
<PatientChartPagination
|
|
145
|
+
currentItems={results.length}
|
|
146
|
+
totalItems={tableRows?.length}
|
|
147
|
+
pageNumber={currentPage}
|
|
148
|
+
pageSize={10}
|
|
149
|
+
onPageNumberChange={(page) => goTo(page)}
|
|
150
|
+
/>
|
|
151
|
+
</>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export default InPatientTable;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
4
|
+
|
|
5
|
+
.tableContainer {
|
|
6
|
+
padding: 0;
|
|
7
|
+
|
|
8
|
+
:global(.cds--data-table-header) {
|
|
9
|
+
padding: 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
:global(.cds--table-toolbar) {
|
|
13
|
+
position: relative;
|
|
14
|
+
overflow: visible;
|
|
15
|
+
top: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&:global(.cds--data-table-container) {
|
|
19
|
+
background: none;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.expandedRow {
|
|
24
|
+
padding-inline-start: layout.$spacing-05;
|
|
25
|
+
|
|
26
|
+
> td {
|
|
27
|
+
padding: inherit;
|
|
28
|
+
|
|
29
|
+
> div {
|
|
30
|
+
max-height: max-content;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
> div {
|
|
35
|
+
background-color: $ui-02;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@carbon/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import AdmissionRequest from './admission-request.component';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import InpatientDetailView from './inpatient-detail-view.component';
|
|
6
|
+
|
|
7
|
+
type InPatientProps = {
|
|
8
|
+
patientUuid: string;
|
|
9
|
+
patient: fhir.Patient;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const InPatient: React.FC<InPatientProps> = ({ patientUuid }) => {
|
|
13
|
+
const { t } = useTranslation();
|
|
14
|
+
return (
|
|
15
|
+
<Tabs>
|
|
16
|
+
<TabList contained>
|
|
17
|
+
<Tab>{t('admissionRequests', 'Admission Requests')}</Tab>
|
|
18
|
+
<Tab>{t('inpatientDetails', 'Inpatient Detail')}</Tab>
|
|
19
|
+
</TabList>
|
|
20
|
+
<TabPanels>
|
|
21
|
+
<TabPanel>
|
|
22
|
+
<AdmissionRequest patientUuid={patientUuid} />;
|
|
23
|
+
</TabPanel>
|
|
24
|
+
<TabPanel>
|
|
25
|
+
<InpatientDetailView patientUuid={patientUuid} />
|
|
26
|
+
</TabPanel>
|
|
27
|
+
</TabPanels>
|
|
28
|
+
</Tabs>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default InPatient;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const inPatientMeta = {
|
|
2
|
+
slot: 'patient-chart-in-patient-dashboard-slot',
|
|
3
|
+
path: 'in-patient',
|
|
4
|
+
title: 'In Patient',
|
|
5
|
+
moduleName: '@kenyaemr/esm-bed-management-app',
|
|
6
|
+
name: 'In Patient',
|
|
7
|
+
columns: 1,
|
|
8
|
+
config: {},
|
|
9
|
+
icon: 'omrs-icon-hospital-bed',
|
|
10
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type Encounter, openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import type { InpatientRequest as AdmissionRequest } from '../types';
|
|
4
|
+
import type { WardConfigObject } from '../config-schema';
|
|
5
|
+
|
|
6
|
+
export const usePatientEncounters = (patientUuid: string) => {
|
|
7
|
+
const { inPatientForms } = useConfig<WardConfigObject>();
|
|
8
|
+
const { data, isLoading, error, mutate } = useSWR<{
|
|
9
|
+
data: { results: Array<Encounter> };
|
|
10
|
+
}>(
|
|
11
|
+
`${restBaseUrl}/encounter?patient=${patientUuid}&v=custom:(uuid,display,encounterDatetime,obs:full,form:(uuid,display),encounterType:(uuid,display),encounterProviders:(uuid,display,encounterRole:(uuid,display),provider:(uuid,person:(uuid,display))),orders:(uuid,display),diagnoses:(uuid,display)`,
|
|
12
|
+
openmrsFetch,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const encounters =
|
|
16
|
+
data?.data?.['results']?.filter((encounter) =>
|
|
17
|
+
inPatientForms?.find((form) => form.uuid === encounter?.form?.uuid),
|
|
18
|
+
) ?? [];
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
encounters: encounters,
|
|
22
|
+
isLoading,
|
|
23
|
+
error,
|
|
24
|
+
mutate,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const defaultRep =
|
|
29
|
+
'custom:(' +
|
|
30
|
+
'dispositionLocation,' +
|
|
31
|
+
'dispositionType,' +
|
|
32
|
+
'disposition,' +
|
|
33
|
+
'dispositionEncounter:full,' +
|
|
34
|
+
'patient:(uuid,identifiers,voided,' +
|
|
35
|
+
'person:(uuid,display,gender,age,birthdate,birthtime,preferredName,preferredAddress,dead,deathDate)),' +
|
|
36
|
+
'dispositionObsGroup,' +
|
|
37
|
+
'visit)';
|
|
38
|
+
|
|
39
|
+
export const useAdmissionRequest = (patientUuid: string) => {
|
|
40
|
+
const patientUuids = [patientUuid];
|
|
41
|
+
const searchParams = new URLSearchParams();
|
|
42
|
+
searchParams.set('dispositionType', 'ADMIT');
|
|
43
|
+
searchParams.set('patients', patientUuids.join(','));
|
|
44
|
+
searchParams.set('v', defaultRep);
|
|
45
|
+
|
|
46
|
+
const url = `${restBaseUrl}/emrapi/inpatient/request?${searchParams.toString()}`;
|
|
47
|
+
|
|
48
|
+
const { data, isLoading, error, mutate } = useSWR<{ data: { results: Array<AdmissionRequest> } }>(url, openmrsFetch);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
admissionRequest: data?.data?.results ?? [],
|
|
52
|
+
isLoading: isLoading,
|
|
53
|
+
error: error,
|
|
54
|
+
mutate: mutate,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { DataTableSkeleton, InlineLoading, Layer, Tile } from '@carbon/react';
|
|
2
|
+
import {
|
|
3
|
+
ErrorState,
|
|
4
|
+
formatDatetime,
|
|
5
|
+
parseDate,
|
|
6
|
+
useConfig,
|
|
7
|
+
useEmrConfiguration,
|
|
8
|
+
usePatient,
|
|
9
|
+
useVisit,
|
|
10
|
+
type EmrApiConfigurationResponse,
|
|
11
|
+
} from '@openmrs/esm-framework';
|
|
12
|
+
import { CardHeader, EmptyDataIllustration } from '@openmrs/esm-patient-common-lib/src';
|
|
13
|
+
import dayjs from 'dayjs';
|
|
14
|
+
import React, { useMemo, type FC } from 'react';
|
|
15
|
+
import { useTranslation } from 'react-i18next';
|
|
16
|
+
import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
|
|
17
|
+
import InpatientForms from './inpatient-forms.component';
|
|
18
|
+
import styles from './inpatient.scss';
|
|
19
|
+
import { type WardConfigObject } from '../config-schema';
|
|
20
|
+
|
|
21
|
+
type InpatientDetailViewProps = {
|
|
22
|
+
patientUuid: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const InpatientDetailView: FC<InpatientDetailViewProps> = ({ patientUuid }) => {
|
|
26
|
+
const { isLoading: isLoadingPatient, patient, error } = usePatient(patientUuid);
|
|
27
|
+
const { isLoadingEmrConfiguration, emrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
|
|
28
|
+
const { isLoading: isLoadingActiveVisit, error: currVisistError, currentVisit } = useVisit(patientUuid);
|
|
29
|
+
const { inPatientVisitTypeUuid } = useConfig<WardConfigObject>();
|
|
30
|
+
const { t } = useTranslation();
|
|
31
|
+
if (isLoadingActiveVisit || isLoadingEmrConfiguration || isLoadingPatient) {
|
|
32
|
+
return <DataTableSkeleton />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (error || errorFetchingEmrConfiguration || currVisistError) {
|
|
36
|
+
return (
|
|
37
|
+
<ErrorState
|
|
38
|
+
error={error ?? errorFetchingEmrConfiguration ?? currVisistError}
|
|
39
|
+
headerTitle={t('inpatientdetails', 'Inpatient Details')}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!currentVisit || currentVisit?.visitType?.uuid !== inPatientVisitTypeUuid) {
|
|
45
|
+
return (
|
|
46
|
+
<Layer>
|
|
47
|
+
<CardHeader title={t('inpatientdetails', 'Inpatient Details')}>
|
|
48
|
+
<></>
|
|
49
|
+
</CardHeader>
|
|
50
|
+
<Tile className={styles.patientNotAdmitted}>
|
|
51
|
+
<EmptyDataIllustration />
|
|
52
|
+
<p>{t('noActiveVisit', 'This Patient Not currently admitted to ward')}</p>
|
|
53
|
+
</Tile>
|
|
54
|
+
</Layer>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div>
|
|
60
|
+
<PatientAdmitted patientUuid={patientUuid} patient={patient} emrConfiguration={emrConfiguration} />
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default InpatientDetailView;
|
|
66
|
+
|
|
67
|
+
const PatientAdmitted: FC<{
|
|
68
|
+
patientUuid: string;
|
|
69
|
+
patient: fhir.Patient;
|
|
70
|
+
emrConfiguration: EmrApiConfigurationResponse;
|
|
71
|
+
}> = ({ emrConfiguration, patient, patientUuid }) => {
|
|
72
|
+
const { currentVisit } = useVisit(patientUuid);
|
|
73
|
+
const { t } = useTranslation();
|
|
74
|
+
|
|
75
|
+
const { isPatientAdmitted, dateOfAdmission, dayasInWard, ward } = useMemo(() => {
|
|
76
|
+
const hasAdmissionEncounter = currentVisit.encounters.find(
|
|
77
|
+
(encounter) => encounter.encounterType.uuid === emrConfiguration?.admissionEncounterType?.uuid,
|
|
78
|
+
);
|
|
79
|
+
const hasDischargeEncounter = currentVisit.encounters.find(
|
|
80
|
+
(encounter) => encounter.encounterType.uuid === emrConfiguration?.exitFromInpatientEncounterType?.uuid,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const dateOfAdmission = hasAdmissionEncounter?.encounterDatetime
|
|
84
|
+
? parseDate(hasAdmissionEncounter?.encounterDatetime)
|
|
85
|
+
: null;
|
|
86
|
+
|
|
87
|
+
const today = dayjs().startOf('day');
|
|
88
|
+
const dayasInWard = dateOfAdmission ? Math.abs(today.diff(dateOfAdmission, 'days')) : 0;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
isPatientAdmitted: hasAdmissionEncounter && !hasDischargeEncounter,
|
|
92
|
+
dateOfAdmission,
|
|
93
|
+
dayasInWard,
|
|
94
|
+
ward: hasAdmissionEncounter?.location,
|
|
95
|
+
};
|
|
96
|
+
}, [emrConfiguration, currentVisit]);
|
|
97
|
+
const { isLoading, admissionLocation, error } = useAdmissionLocation(undefined, ward?.uuid);
|
|
98
|
+
const bedLayout = useMemo(() => {
|
|
99
|
+
return admissionLocation?.bedLayouts?.find((layout) => layout.patients?.some((pat) => pat?.uuid === patientUuid));
|
|
100
|
+
}, [admissionLocation, patientUuid]);
|
|
101
|
+
|
|
102
|
+
if (!isPatientAdmitted) {
|
|
103
|
+
return (
|
|
104
|
+
<Layer>
|
|
105
|
+
<CardHeader title={t('admissionDetails', 'Admission Details')}>
|
|
106
|
+
<></>
|
|
107
|
+
</CardHeader>
|
|
108
|
+
<Tile className={styles.patientNotAdmitted}>
|
|
109
|
+
<EmptyDataIllustration />
|
|
110
|
+
<p>{t('patientNotAdmitted', 'This Patient Not currently admitted to ward')}</p>;
|
|
111
|
+
</Tile>
|
|
112
|
+
</Layer>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return (
|
|
116
|
+
<Layer>
|
|
117
|
+
<CardHeader title={t('admissionDetail', 'Admission Details')}>
|
|
118
|
+
<InpatientForms patientUuid={patientUuid} patient={patient} emrConfiguration={emrConfiguration} />
|
|
119
|
+
</CardHeader>
|
|
120
|
+
<div className={styles.detailsContainer}>
|
|
121
|
+
<Tile>
|
|
122
|
+
<strong>{t('dateOfAdmission', 'Date of Admission')}</strong>
|
|
123
|
+
<p>{formatDatetime(dateOfAdmission)}</p>
|
|
124
|
+
</Tile>
|
|
125
|
+
<Tile>
|
|
126
|
+
<strong>{t('daysInWard', 'Days in ward')}</strong>
|
|
127
|
+
<p>{dayasInWard}</p>
|
|
128
|
+
</Tile>
|
|
129
|
+
<Tile>
|
|
130
|
+
<strong>{t('ward', 'Ward')}</strong>
|
|
131
|
+
<p>{ward?.display}</p>
|
|
132
|
+
</Tile>
|
|
133
|
+
<Tile>
|
|
134
|
+
{isLoading ? (
|
|
135
|
+
<InlineLoading />
|
|
136
|
+
) : (
|
|
137
|
+
<>
|
|
138
|
+
<strong>{t('bed', 'Bed')}</strong>
|
|
139
|
+
<p>{bedLayout?.bedNumber}</p>
|
|
140
|
+
</>
|
|
141
|
+
)}
|
|
142
|
+
</Tile>
|
|
143
|
+
</div>
|
|
144
|
+
</Layer>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ComboButton, Dropdown, MenuItem } from '@carbon/react';
|
|
2
|
+
import React, { type FC, useMemo } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import {
|
|
5
|
+
type EmrApiConfigurationResponse,
|
|
6
|
+
evaluateAsBoolean,
|
|
7
|
+
launchWorkspace,
|
|
8
|
+
useConfig,
|
|
9
|
+
useVisit,
|
|
10
|
+
} from '@openmrs/esm-framework';
|
|
11
|
+
import { launchStartVisitPrompt } from '@openmrs/esm-patient-common-lib';
|
|
12
|
+
import dayjs from 'dayjs';
|
|
13
|
+
import { type WardConfigObject } from '../config-schema';
|
|
14
|
+
type InpatientFormsProps = {
|
|
15
|
+
patientUuid: string;
|
|
16
|
+
patient: fhir.Patient;
|
|
17
|
+
emrConfiguration: EmrApiConfigurationResponse;
|
|
18
|
+
};
|
|
19
|
+
const InpatientForms: FC<InpatientFormsProps> = ({ patientUuid, patient, emrConfiguration }) => {
|
|
20
|
+
const { t } = useTranslation();
|
|
21
|
+
const { inPatientForms } = useConfig<WardConfigObject>();
|
|
22
|
+
const { currentVisit } = useVisit(patientUuid);
|
|
23
|
+
const filteredForms = inPatientForms.filter((form) => {
|
|
24
|
+
if (!form.hideExpression) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
const age = dayjs().diff(dayjs(patient.birthDate), 'year');
|
|
28
|
+
const ageInDays = dayjs().diff(dayjs(patient.birthDate), 'day');
|
|
29
|
+
const ageInMonths = dayjs().diff(dayjs(patient.birthDate), 'month');
|
|
30
|
+
const gender = patient.gender;
|
|
31
|
+
const hide = form.hideExpression
|
|
32
|
+
? evaluateAsBoolean(form.hideExpression, { age, gender, ageInDays, ageInMonths })
|
|
33
|
+
: false;
|
|
34
|
+
return hide;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const isPatientAdmitted = useMemo(() => {
|
|
38
|
+
const hasAdmissionEncounter = currentVisit.encounters.some(
|
|
39
|
+
(encounter) => encounter.encounterType.uuid === emrConfiguration?.admissionEncounterType?.uuid,
|
|
40
|
+
);
|
|
41
|
+
const hasDischargeEncounter = currentVisit.encounters.some(
|
|
42
|
+
(encounter) => encounter.encounterType.uuid === emrConfiguration?.exitFromInpatientEncounterType?.uuid,
|
|
43
|
+
);
|
|
44
|
+
return hasAdmissionEncounter && !hasDischargeEncounter;
|
|
45
|
+
}, [emrConfiguration, currentVisit]);
|
|
46
|
+
|
|
47
|
+
const handleLaunchForm = (form: { label: string; uuid: string }) => {
|
|
48
|
+
if (!currentVisit) {
|
|
49
|
+
return launchStartVisitPrompt();
|
|
50
|
+
}
|
|
51
|
+
const filledFormEncounter = currentVisit?.encounters?.find((en) => en?.form?.uuid === form.uuid);
|
|
52
|
+
launchWorkspace('patient-form-entry-workspace', {
|
|
53
|
+
workspaceTitle: form.label,
|
|
54
|
+
mutateForm: () => {},
|
|
55
|
+
formInfo: {
|
|
56
|
+
encounterUuid: filledFormEncounter?.uuid ?? '',
|
|
57
|
+
formUuid: form.uuid,
|
|
58
|
+
additionalProps: {},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (!isPatientAdmitted) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return (
|
|
67
|
+
<ComboButton size="sm" label={t('inPatientForms', 'In-Patient Forms')}>
|
|
68
|
+
{filteredForms.map((form) => (
|
|
69
|
+
<MenuItem key={form.uuid} onClick={() => handleLaunchForm(form)} label={form.label} />
|
|
70
|
+
))}
|
|
71
|
+
</ComboButton>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default InpatientForms;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.patientNotAdmitted {
|
|
6
|
+
margin-bottom: layout.$spacing-05;
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: layout.$spacing-05;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.layoutContainer {
|
|
14
|
+
border: solid 1px colors.$gray-40;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.detailsContainer {
|
|
18
|
+
display: grid;
|
|
19
|
+
|
|
20
|
+
grid-template-columns: 1fr 1fr;
|
|
21
|
+
|
|
22
|
+
gap: layout.$spacing-05;
|
|
23
|
+
}
|