@kenyaemr/esm-ward-app 8.5.1-pre.50 → 8.5.1-pre.61
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 +55 -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 +129 -0
- package/src/in-patient/inpatient-forms.component.tsx +74 -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 +6 -6
- 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,129 @@
|
|
|
1
|
+
import { DataTableSkeleton, InlineLoading, Layer, Tile } from '@carbon/react';
|
|
2
|
+
import {
|
|
3
|
+
ErrorState,
|
|
4
|
+
formatDatetime,
|
|
5
|
+
parseDate,
|
|
6
|
+
useEmrConfiguration,
|
|
7
|
+
usePatient,
|
|
8
|
+
useVisit,
|
|
9
|
+
type EmrApiConfigurationResponse,
|
|
10
|
+
} from '@openmrs/esm-framework';
|
|
11
|
+
import { CardHeader, EmptyDataIllustration } from '@openmrs/esm-patient-common-lib/src';
|
|
12
|
+
import dayjs from 'dayjs';
|
|
13
|
+
import React, { useMemo, type FC } from 'react';
|
|
14
|
+
import { useTranslation } from 'react-i18next';
|
|
15
|
+
import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
|
|
16
|
+
import InpatientForms from './inpatient-forms.component';
|
|
17
|
+
import styles from './inpatient.scss';
|
|
18
|
+
|
|
19
|
+
type InpatientDetailViewProps = {
|
|
20
|
+
patientUuid: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const InpatientDetailView: FC<InpatientDetailViewProps> = ({ patientUuid }) => {
|
|
24
|
+
const { isLoading: isLoadingPatient, patient, error } = usePatient(patientUuid);
|
|
25
|
+
const { isLoadingEmrConfiguration, emrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
|
|
26
|
+
const { isLoading: isLoadingActiveVisit, error: currVisistError } = useVisit(patientUuid);
|
|
27
|
+
const { t } = useTranslation();
|
|
28
|
+
if (isLoadingActiveVisit || isLoadingEmrConfiguration || isLoadingPatient) {
|
|
29
|
+
return <DataTableSkeleton />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (error || errorFetchingEmrConfiguration || currVisistError) {
|
|
33
|
+
return (
|
|
34
|
+
<ErrorState
|
|
35
|
+
error={error ?? errorFetchingEmrConfiguration ?? currVisistError}
|
|
36
|
+
headerTitle={t('inpatientdetails', 'Inpatient Details')}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div>
|
|
43
|
+
<PatientAdmitted patientUuid={patientUuid} patient={patient} emrConfiguration={emrConfiguration} />
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default InpatientDetailView;
|
|
49
|
+
|
|
50
|
+
const PatientAdmitted: FC<{
|
|
51
|
+
patientUuid: string;
|
|
52
|
+
patient: fhir.Patient;
|
|
53
|
+
emrConfiguration: EmrApiConfigurationResponse;
|
|
54
|
+
}> = ({ emrConfiguration, patient, patientUuid }) => {
|
|
55
|
+
const { currentVisit } = useVisit(patientUuid);
|
|
56
|
+
const { t } = useTranslation();
|
|
57
|
+
|
|
58
|
+
const { isPatientAdmitted, dateOfAdmission, dayasInWard, ward } = useMemo(() => {
|
|
59
|
+
const hasAdmissionEncounter = currentVisit.encounters.find(
|
|
60
|
+
(encounter) => encounter.encounterType.uuid === emrConfiguration?.admissionEncounterType?.uuid,
|
|
61
|
+
);
|
|
62
|
+
const hasDischargeEncounter = currentVisit.encounters.find(
|
|
63
|
+
(encounter) => encounter.encounterType.uuid === emrConfiguration?.exitFromInpatientEncounterType?.uuid,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const dateOfAdmission = hasAdmissionEncounter?.encounterDatetime
|
|
67
|
+
? parseDate(hasAdmissionEncounter?.encounterDatetime)
|
|
68
|
+
: null;
|
|
69
|
+
|
|
70
|
+
const today = dayjs().startOf('day');
|
|
71
|
+
const dayasInWard = dateOfAdmission ? Math.abs(today.diff(dateOfAdmission, 'days')) : 0;
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
isPatientAdmitted: hasAdmissionEncounter && !hasDischargeEncounter,
|
|
75
|
+
dateOfAdmission,
|
|
76
|
+
dayasInWard,
|
|
77
|
+
ward: hasAdmissionEncounter?.location,
|
|
78
|
+
};
|
|
79
|
+
}, [emrConfiguration, currentVisit]);
|
|
80
|
+
const { isLoading, admissionLocation, error } = useAdmissionLocation(undefined, ward?.uuid);
|
|
81
|
+
const bedLayout = useMemo(() => {
|
|
82
|
+
return admissionLocation?.bedLayouts?.find((layout) => layout.patients?.some((pat) => pat?.uuid === patientUuid));
|
|
83
|
+
}, [admissionLocation, patientUuid]);
|
|
84
|
+
|
|
85
|
+
if (!isPatientAdmitted) {
|
|
86
|
+
return (
|
|
87
|
+
<Layer>
|
|
88
|
+
<CardHeader title={t('admissionDetails', 'Admission Details')}>
|
|
89
|
+
<></>
|
|
90
|
+
</CardHeader>
|
|
91
|
+
<Tile className={styles.patientNotAdmitted}>
|
|
92
|
+
<EmptyDataIllustration />
|
|
93
|
+
<p>{t('patientNotAdmitted', 'This Patient Not currently admitted to ward')}</p>;
|
|
94
|
+
</Tile>
|
|
95
|
+
</Layer>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return (
|
|
99
|
+
<Layer>
|
|
100
|
+
<CardHeader title={t('admissionDetail', 'Admission Details')}>
|
|
101
|
+
<InpatientForms patientUuid={patientUuid} patient={patient} emrConfiguration={emrConfiguration} />
|
|
102
|
+
</CardHeader>
|
|
103
|
+
<div className={styles.detailsContainer}>
|
|
104
|
+
<Tile>
|
|
105
|
+
<strong>{t('dateOfAdmission', 'Date of Admission')}</strong>
|
|
106
|
+
<p>{formatDatetime(dateOfAdmission)}</p>
|
|
107
|
+
</Tile>
|
|
108
|
+
<Tile>
|
|
109
|
+
<strong>{t('daysInWard', 'Days in ward')}</strong>
|
|
110
|
+
<p>{dayasInWard}</p>
|
|
111
|
+
</Tile>
|
|
112
|
+
<Tile>
|
|
113
|
+
<strong>{t('ward', 'Ward')}</strong>
|
|
114
|
+
<p>{ward?.display}</p>
|
|
115
|
+
</Tile>
|
|
116
|
+
<Tile>
|
|
117
|
+
{isLoading ? (
|
|
118
|
+
<InlineLoading />
|
|
119
|
+
) : (
|
|
120
|
+
<>
|
|
121
|
+
<strong>{t('bed', 'Bed')}</strong>
|
|
122
|
+
<p>{bedLayout?.bedNumber}</p>
|
|
123
|
+
</>
|
|
124
|
+
)}
|
|
125
|
+
</Tile>
|
|
126
|
+
</div>
|
|
127
|
+
</Layer>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
launchWorkspace('patient-form-entry-workspace', {
|
|
52
|
+
workspaceTitle: form.label,
|
|
53
|
+
mutateForm: () => {},
|
|
54
|
+
formInfo: {
|
|
55
|
+
encounterUuid: '',
|
|
56
|
+
formUuid: form.uuid,
|
|
57
|
+
additionalProps: {},
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (!isPatientAdmitted) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return (
|
|
66
|
+
<ComboButton size="sm" label={t('inPatientForms', 'In-Patient Forms')}>
|
|
67
|
+
{filteredForms.map((form) => (
|
|
68
|
+
<MenuItem key={form.uuid} onClick={() => handleLaunchForm(form)} label={form.label} />
|
|
69
|
+
))}
|
|
70
|
+
</ComboButton>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
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
|
+
}
|