@kenyaemr/esm-ward-app 8.0.1-pre.99 → 8.0.3-pre.131
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +20 -24
- package/dist/109.js +1 -0
- package/dist/109.js.map +1 -0
- package/dist/124.js +1 -0
- package/dist/124.js.map +1 -0
- package/dist/125.js +1 -0
- package/dist/125.js.map +1 -0
- package/dist/130.js +1 -1
- package/dist/130.js.LICENSE.txt +2 -0
- package/dist/130.js.map +1 -1
- package/dist/146.js +1 -0
- package/dist/146.js.map +1 -0
- package/dist/15.js +1 -0
- package/dist/15.js.map +1 -0
- package/dist/153.js +1 -0
- package/dist/153.js.map +1 -0
- package/dist/303.js +2 -1
- package/dist/303.js.map +1 -1
- package/dist/325.js +1 -0
- package/dist/325.js.map +1 -0
- package/dist/372.js +2 -0
- package/dist/372.js.map +1 -0
- package/dist/471.js +1 -0
- package/dist/471.js.map +1 -0
- package/dist/481.js +1 -0
- package/dist/481.js.map +1 -0
- package/dist/53.js +1 -0
- package/dist/53.js.map +1 -0
- package/dist/{960.js → 559.js} +1 -1
- package/dist/559.js.map +1 -0
- package/dist/574.js +1 -1
- package/dist/576.js +1 -0
- package/dist/576.js.map +1 -0
- package/dist/577.js +1 -1
- package/dist/577.js.map +1 -1
- package/dist/{255.js → 649.js} +2 -2
- package/dist/649.js.LICENSE.txt +9 -0
- package/dist/649.js.map +1 -0
- package/dist/{659.js → 662.js} +1 -1
- package/dist/662.js.map +1 -0
- package/dist/920.js +1 -0
- package/dist/920.js.map +1 -0
- package/dist/921.js +1 -0
- package/dist/921.js.map +1 -0
- package/dist/922.js +1 -0
- package/dist/922.js.map +1 -0
- package/dist/969.js +1 -0
- package/dist/969.js.map +1 -0
- package/dist/kenyaemr-esm-ward-app.js +1 -1
- package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +304 -128
- package/dist/kenyaemr-esm-ward-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +0 -10
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/mock.tsx +62 -0
- package/package-lock.json +5001 -0
- package/package.json +2 -2
- package/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx +37 -0
- package/src/action-menu-buttons/discharge-workspace-siderail.component.tsx +20 -0
- package/src/beds/empty-bed-skeleton.tsx +4 -3
- package/src/beds/empty-bed.component.tsx +3 -3
- package/src/beds/ward-bed.component.tsx +41 -0
- package/src/beds/ward-bed.scss +45 -0
- package/src/beds/{occupied-bed.test.tsx → ward-bed.test.tsx} +42 -20
- package/src/config-schema.ts +203 -84
- package/src/constant.ts +1 -1
- package/src/hooks/useAdmissionLocation.ts +22 -4
- package/src/hooks/useAssignedBedByPatient.ts +9 -0
- package/src/hooks/useBeds.ts +3 -4
- package/src/hooks/useConcept.ts +3 -4
- package/src/hooks/useEmrConfiguration.ts +5 -0
- package/src/hooks/useInpatientAdmission.ts +9 -14
- package/src/hooks/useInpatientRequest.ts +4 -15
- package/src/hooks/useLocations.ts +8 -51
- package/src/hooks/useMotherAndChildren.ts +46 -0
- package/src/hooks/useObs.ts +3 -7
- package/src/hooks/usePatientPendingOrders.ts +16 -0
- package/src/hooks/useWardPatientGrouping.ts +32 -0
- package/src/index.ts +45 -17
- package/src/location-selector/location-selector.component.tsx +18 -21
- package/src/root.component.tsx +3 -0
- package/src/routes.json +41 -3
- package/src/types/index.ts +50 -1
- package/src/ward-patient-card/card-rows/admission-request-note-row.component.tsx +38 -0
- package/src/ward-patient-card/card-rows/coded-obs-tags-row.component.tsx +108 -0
- package/src/ward-patient-card/card-rows/mother-child-row.component.tsx +84 -0
- package/src/ward-patient-card/card-rows/mother-child-row.scss +22 -0
- package/src/ward-patient-card/card-rows/pending-items-row.component.tsx +54 -0
- package/src/ward-patient-card/row-elements/ward-patient-age.tsx +1 -1
- package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +62 -39
- package/src/ward-patient-card/row-elements/ward-patient-header-address.tsx +5 -5
- package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +18 -41
- package/src/ward-patient-card/row-elements/ward-patient-location.tsx +19 -0
- package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +38 -34
- package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +26 -13
- package/src/ward-patient-card/row-elements/ward-patient-pending-order.component.tsx +45 -0
- package/src/ward-patient-card/row-elements/ward-patient-pending-transfer.tsx +38 -0
- package/src/ward-patient-card/row-elements/ward-patient-responsive-tooltip.tsx +32 -0
- package/src/ward-patient-card/row-elements/ward-patient-skeleton-text.tsx +9 -0
- package/src/ward-patient-card/ward-patient-card.component.tsx +14 -45
- package/src/ward-patient-card/ward-patient-card.scss +68 -9
- package/src/ward-view/default-ward/default-ward-beds.component.tsx +42 -0
- package/src/ward-view/default-ward/default-ward-patient-card-header.component.tsx +32 -0
- package/src/ward-view/default-ward/default-ward-patient-card.component.tsx +31 -0
- package/src/ward-view/default-ward/default-ward-pending-patients.component.tsx +52 -0
- package/src/ward-view/default-ward/default-ward-unassigned-patients.component.tsx +32 -0
- package/src/ward-view/default-ward/default-ward-view.component.tsx +31 -0
- package/src/ward-view/materal-ward/maternal-ward-beds.component.tsx +65 -0
- package/src/ward-view/materal-ward/maternal-ward-patient-card-header.component.tsx +30 -0
- package/src/ward-view/materal-ward/maternal-ward-patient-card.component.tsx +93 -0
- package/src/{beds/occupied-bed.scss → ward-view/materal-ward/maternal-ward-patient-card.scss} +4 -9
- package/src/ward-view/materal-ward/maternal-ward-patient-card.test.tsx +58 -0
- package/src/ward-view/materal-ward/maternal-ward-pending-patients.component.tsx +48 -0
- package/src/ward-view/materal-ward/maternal-ward-unassigned-patients.component.tsx +33 -0
- package/src/ward-view/materal-ward/maternal-ward-view.component.tsx +38 -0
- package/src/ward-view/materal-ward/maternal-ward-view.resource.ts +89 -0
- package/src/ward-view/ward-view.component.tsx +15 -163
- package/src/ward-view/ward-view.resource.ts +193 -1
- package/src/ward-view/ward-view.scss +17 -6
- package/src/ward-view/ward-view.test.tsx +43 -48
- package/src/ward-view/ward.component.tsx +106 -0
- package/src/ward-view-header/admission-requests-bar.component.tsx +14 -9
- package/src/ward-view-header/admission-requests-bar.test.tsx +11 -23
- package/src/ward-view-header/admission-requests.scss +1 -1
- package/src/ward-view-header/ward-metric.component.tsx +24 -0
- package/src/ward-view-header/ward-metric.scss +25 -0
- package/src/ward-view-header/ward-metrics.component.tsx +78 -0
- package/src/ward-view-header/ward-metrics.scss +7 -0
- package/src/ward-view-header/ward-metrics.test.tsx +37 -0
- package/src/ward-view-header/ward-view-header.component.tsx +9 -4
- package/src/ward-view-header/ward-view-header.scss +0 -1
- package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +70 -6
- package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +10 -23
- package/src/ward-workspace/admission-request-card/admission-request-card.component.tsx +9 -3
- package/src/ward-workspace/admission-request-card/admission-request-card.scss +13 -4
- package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +55 -33
- package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +30 -37
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +98 -203
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +116 -180
- package/src/ward-workspace/bed-selector.component.tsx +119 -0
- package/src/ward-workspace/bed-selector.scss +15 -0
- package/src/ward-workspace/patient-banner/patient-banner.component.tsx +7 -14
- package/src/ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace.tsx +23 -0
- package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient-action-button.extension.tsx +2 -1
- package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.workspace.tsx +18 -9
- package/src/ward-workspace/patient-discharge/patient-discharge.scss +41 -0
- package/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx +113 -0
- package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +68 -79
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +24 -24
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.scss +12 -2
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace.tsx +12 -8
- package/src/ward-workspace/patient-transfer-request-workspace/patient-transfer-request.workspace.tsx +11 -0
- package/src/ward-workspace/ward-patient-notes/form/notes-form.component.tsx +1 -1
- package/src/ward-workspace/ward-patient-notes/form/notes-form.test.tsx +1 -1
- package/src/ward-workspace/ward-patient-notes/history/notes-container.component.tsx +2 -2
- package/src/ward-workspace/ward-patient-notes/notes.resource.ts +5 -7
- package/src/ward-workspace/ward-patient-notes/notes.workspace.tsx +1 -1
- package/src/ward-workspace/ward-patient-notes/types.ts +0 -4
- package/src/ward.resource.ts +38 -2
- package/translations/en.json +31 -7
- package/dist/152.js +0 -1
- package/dist/152.js.map +0 -1
- package/dist/255.js.map +0 -1
- package/dist/269.js +0 -1
- package/dist/269.js.map +0 -1
- package/dist/346.js +0 -1
- package/dist/346.js.map +0 -1
- package/dist/466.js +0 -1
- package/dist/466.js.map +0 -1
- package/dist/659.js.map +0 -1
- package/dist/729.js +0 -1
- package/dist/729.js.map +0 -1
- package/dist/749.js +0 -1
- package/dist/749.js.map +0 -1
- package/dist/76.js +0 -1
- package/dist/76.js.map +0 -1
- package/dist/793.js +0 -2
- package/dist/793.js.map +0 -1
- package/dist/803.js +0 -1
- package/dist/803.js.map +0 -1
- package/dist/960.js.map +0 -1
- package/src/beds/empty-bed.scss +0 -28
- package/src/beds/occupied-bed.component.tsx +0 -35
- package/src/beds/unassigned-patient.component.tsx +0 -20
- package/src/beds/unassigned-patient.scss +0 -6
- package/src/config-schema-admission-request-note.ts +0 -9
- package/src/config-schema-extension-colored-obs-tags.ts +0 -91
- package/src/hooks/useCurrentWardCardConfig.ts +0 -32
- package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +0 -27
- package/src/ward-patient-card/card-rows/colored-obs-tags-card-row.extension.tsx +0 -13
- package/src/ward-patient-card/ward-patient-card-element.component.tsx +0 -65
- package/src/ward-view/ward-bed.component.tsx +0 -14
- /package/dist/{793.js.LICENSE.txt → 303.js.LICENSE.txt} +0 -0
- /package/dist/{255.js.LICENSE.txt → 372.js.LICENSE.txt} +0 -0
- /package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.style.scss +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
@use '@carbon/layout';
|
|
2
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
2
3
|
|
|
3
4
|
.wardView {
|
|
4
5
|
background-color: #e4e4e4;
|
|
5
|
-
position: absolute;
|
|
6
|
-
top: var(--omrs-topnav-height);
|
|
7
|
-
bottom: 0;
|
|
8
|
-
left: 0;
|
|
9
|
-
right: 0;
|
|
10
6
|
display: flex;
|
|
11
7
|
flex-direction: column;
|
|
12
8
|
padding: 0 layout.$spacing-05;
|
|
9
|
+
|
|
10
|
+
&.verticalTiling {
|
|
11
|
+
height: calc(100vh - var(--omrs-topnav-height));
|
|
12
|
+
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
.wardViewMain {
|
|
@@ -17,5 +17,16 @@
|
|
|
17
17
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
18
18
|
display: grid;
|
|
19
19
|
gap: layout.$spacing-05;
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
&.verticalTiling {
|
|
22
|
+
display: block;
|
|
23
|
+
column-width: 280px;
|
|
24
|
+
overflow-x: auto;
|
|
25
|
+
height: calc(100% - 4rem);
|
|
26
|
+
|
|
27
|
+
> div {
|
|
28
|
+
break-inside: avoid;
|
|
29
|
+
margin-bottom: 10px;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
21
32
|
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
type ConfigSchema,
|
|
3
|
+
getDefaultsFromConfigSchema,
|
|
4
|
+
useAppContext,
|
|
5
|
+
useConfig,
|
|
6
|
+
useFeatureFlag,
|
|
7
|
+
} from '@openmrs/esm-framework';
|
|
2
8
|
import { screen } from '@testing-library/react';
|
|
3
|
-
import
|
|
9
|
+
import React from 'react';
|
|
4
10
|
import { useParams } from 'react-router-dom';
|
|
5
|
-
import { mockAdmissionLocation, mockInpatientAdmissions } from '__mocks__';
|
|
6
11
|
import { renderWithSwr } from 'tools';
|
|
12
|
+
import { mockWardPatientGroupDetails, mockWardViewContext } from '../../mock';
|
|
7
13
|
import { configSchema } from '../config-schema';
|
|
8
|
-
import {
|
|
9
|
-
import { useInpatientAdmission } from '../hooks/useInpatientAdmission';
|
|
14
|
+
import { useObs } from '../hooks/useObs';
|
|
10
15
|
import useWardLocation from '../hooks/useWardLocation';
|
|
16
|
+
import { type WardViewContext } from '../types';
|
|
17
|
+
import DefaultWardView from './default-ward/default-ward-view.component';
|
|
11
18
|
import WardView from './ward-view.component';
|
|
12
19
|
|
|
13
20
|
jest.mocked(useConfig).mockReturnValue({
|
|
@@ -24,6 +31,9 @@ jest.mock('../hooks/useWardLocation', () =>
|
|
|
24
31
|
invalidLocation: false,
|
|
25
32
|
}),
|
|
26
33
|
);
|
|
34
|
+
jest.mock('../hooks/useObs', () => ({
|
|
35
|
+
useObs: jest.fn(),
|
|
36
|
+
}));
|
|
27
37
|
|
|
28
38
|
const mockUseWardLocation = jest.mocked(useWardLocation);
|
|
29
39
|
|
|
@@ -33,57 +43,48 @@ jest.mock('react-router-dom', () => ({
|
|
|
33
43
|
}));
|
|
34
44
|
const mockUseParams = useParams as jest.Mock;
|
|
35
45
|
|
|
36
|
-
jest.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
useInpatientAdmission: jest.fn(),
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
jest.mocked(useAdmissionLocation).mockReturnValue({
|
|
44
|
-
error: undefined,
|
|
45
|
-
mutate: jest.fn(),
|
|
46
|
-
isValidating: false,
|
|
47
|
-
isLoading: false,
|
|
48
|
-
admissionLocation: mockAdmissionLocation,
|
|
46
|
+
jest.mocked(useAppContext<WardViewContext>).mockReturnValue(mockWardViewContext);
|
|
47
|
+
//@ts-ignore
|
|
48
|
+
jest.mocked(useObs).mockReturnValue({
|
|
49
|
+
data: [],
|
|
49
50
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
isValidating: false,
|
|
54
|
-
isLoading: false,
|
|
55
|
-
inpatientAdmissions: mockInpatientAdmissions,
|
|
51
|
+
|
|
52
|
+
const intersectionObserverMock = () => ({
|
|
53
|
+
observe: () => null,
|
|
56
54
|
});
|
|
55
|
+
window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock);
|
|
57
56
|
|
|
58
57
|
describe('WardView', () => {
|
|
58
|
+
let replacedProperty: jest.ReplaceProperty<any> | null = null;
|
|
59
|
+
|
|
59
60
|
it('renders the session location when no location provided in URL', () => {
|
|
60
|
-
renderWithSwr(<
|
|
61
|
+
renderWithSwr(<DefaultWardView />);
|
|
61
62
|
const header = screen.getByRole('heading', { name: 'mock location' });
|
|
62
63
|
expect(header).toBeInTheDocument();
|
|
63
64
|
});
|
|
64
65
|
|
|
65
66
|
it('renders the location provided in URL', () => {
|
|
66
67
|
mockUseParams.mockReturnValueOnce({ locationUuid: 'abcd' });
|
|
67
|
-
renderWithSwr(<
|
|
68
|
+
renderWithSwr(<DefaultWardView />);
|
|
68
69
|
const header = screen.getByRole('heading', { name: 'mock location' });
|
|
69
70
|
expect(header).toBeInTheDocument();
|
|
70
71
|
});
|
|
71
72
|
|
|
72
73
|
it('renders the correct number of occupied and empty beds', async () => {
|
|
73
|
-
renderWithSwr(<
|
|
74
|
+
renderWithSwr(<DefaultWardView />);
|
|
74
75
|
const emptyBedCards = await screen.findAllByText(/empty bed/i);
|
|
75
76
|
expect(emptyBedCards).toHaveLength(3);
|
|
76
77
|
});
|
|
77
78
|
|
|
78
79
|
it('renders admitted patient without bed', async () => {
|
|
79
|
-
renderWithSwr(<
|
|
80
|
+
renderWithSwr(<DefaultWardView />);
|
|
80
81
|
const admittedPatientWithoutBed = screen.queryByText('Brian Johnson');
|
|
81
82
|
expect(admittedPatientWithoutBed).toBeInTheDocument();
|
|
82
83
|
});
|
|
83
84
|
|
|
84
85
|
it('renders all admitted patients even if bed management module not installed', async () => {
|
|
85
86
|
mockUseFeatureFlag.mockReturnValueOnce(false);
|
|
86
|
-
renderWithSwr(<
|
|
87
|
+
renderWithSwr(<DefaultWardView />);
|
|
87
88
|
const admittedPatientWithoutBed = screen.queryByText('Brian Johnson');
|
|
88
89
|
expect(admittedPatientWithoutBed).toBeInTheDocument();
|
|
89
90
|
});
|
|
@@ -103,35 +104,29 @@ describe('WardView', () => {
|
|
|
103
104
|
expect(invalidText).toBeInTheDocument();
|
|
104
105
|
});
|
|
105
106
|
|
|
106
|
-
it('
|
|
107
|
+
it('should render warning if backend module installed and no beds configured', () => {
|
|
107
108
|
// override the default response so that no beds are returned
|
|
108
|
-
jest.
|
|
109
|
-
error: undefined,
|
|
110
|
-
mutate: jest.fn(),
|
|
111
|
-
isValidating: false,
|
|
112
|
-
isLoading: false,
|
|
113
|
-
admissionLocation: { ...mockAdmissionLocation, bedLayouts: [] },
|
|
114
|
-
});
|
|
115
|
-
mockUseFeatureFlag.mockReturnValueOnce(true);
|
|
109
|
+
replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []);
|
|
116
110
|
|
|
117
|
-
|
|
111
|
+
mockUseFeatureFlag.mockReturnValue(true);
|
|
112
|
+
|
|
113
|
+
renderWithSwr(<DefaultWardView />);
|
|
118
114
|
const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location');
|
|
119
115
|
expect(noBedsConfiguredForThisLocation).toBeInTheDocument();
|
|
120
116
|
});
|
|
121
117
|
|
|
122
|
-
it('
|
|
118
|
+
it('should not render warning if backend module installed and no beds configured', () => {
|
|
123
119
|
// override the default response so that no beds are returned
|
|
124
|
-
jest.
|
|
125
|
-
|
|
126
|
-
mutate: jest.fn(),
|
|
127
|
-
isValidating: false,
|
|
128
|
-
isLoading: false,
|
|
129
|
-
admissionLocation: { ...mockAdmissionLocation, bedLayouts: [] },
|
|
130
|
-
});
|
|
131
|
-
mockUseFeatureFlag.mockReturnValueOnce(false);
|
|
120
|
+
replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []);
|
|
121
|
+
mockUseFeatureFlag.mockReturnValue(false);
|
|
132
122
|
|
|
133
123
|
renderWithSwr(<WardView />);
|
|
134
124
|
const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location');
|
|
135
125
|
expect(noBedsConfiguredForThisLocation).not.toBeInTheDocument();
|
|
136
126
|
});
|
|
127
|
+
|
|
128
|
+
afterEach(() => {
|
|
129
|
+
replacedProperty?.restore();
|
|
130
|
+
replacedProperty = null;
|
|
131
|
+
});
|
|
137
132
|
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { InlineNotification } from '@carbon/react';
|
|
2
|
+
import { useAppContext, useFeatureFlag } from '@openmrs/esm-framework';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import React, { useEffect, useRef, type ReactNode } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import EmptyBedSkeleton from '../beds/empty-bed-skeleton';
|
|
7
|
+
import useWardLocation from '../hooks/useWardLocation';
|
|
8
|
+
import { type WardViewContext } from '../types';
|
|
9
|
+
import styles from './ward-view.scss';
|
|
10
|
+
|
|
11
|
+
const Ward = ({ wardBeds, wardUnassignedPatients }: { wardBeds: ReactNode; wardUnassignedPatients: ReactNode }) => {
|
|
12
|
+
const { location } = useWardLocation();
|
|
13
|
+
const { t } = useTranslation();
|
|
14
|
+
const isVertical = useFeatureFlag('ward-view-vertical-tiling');
|
|
15
|
+
|
|
16
|
+
const {wardPatientGroupDetails} = useAppContext<WardViewContext>('ward-view-context') ?? {};
|
|
17
|
+
const { bedLayouts } = wardPatientGroupDetails ?? {};
|
|
18
|
+
const { isLoading: isLoadingAdmissionLocation, error: errorLoadingAdmissionLocation } =
|
|
19
|
+
wardPatientGroupDetails?.admissionLocationResponse ?? {};
|
|
20
|
+
const {
|
|
21
|
+
isLoading: isLoadingInpatientAdmissions,
|
|
22
|
+
error: errorLoadingInpatientAdmissions,
|
|
23
|
+
hasMore: hasMoreInpatientAdmissions,
|
|
24
|
+
loadMore: loadMoreInpatientAdmissions,
|
|
25
|
+
} = wardPatientGroupDetails?.inpatientAdmissionResponse ?? {};
|
|
26
|
+
const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
|
|
27
|
+
|
|
28
|
+
const scrollToLoadMoreTrigger = useRef<HTMLDivElement>(null);
|
|
29
|
+
useEffect(
|
|
30
|
+
function scrollToLoadMore() {
|
|
31
|
+
const observer = new IntersectionObserver(
|
|
32
|
+
(entries) => {
|
|
33
|
+
entries.forEach((entry) => {
|
|
34
|
+
if (entry.isIntersecting) {
|
|
35
|
+
if (hasMoreInpatientAdmissions && !errorLoadingInpatientAdmissions && !isLoadingInpatientAdmissions) {
|
|
36
|
+
loadMoreInpatientAdmissions();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
{ threshold: 1 },
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (scrollToLoadMoreTrigger.current) {
|
|
45
|
+
observer.observe(scrollToLoadMoreTrigger.current);
|
|
46
|
+
}
|
|
47
|
+
return () => {
|
|
48
|
+
if (scrollToLoadMoreTrigger.current) {
|
|
49
|
+
observer.unobserve(scrollToLoadMoreTrigger.current);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
[scrollToLoadMoreTrigger, hasMoreInpatientAdmissions, errorLoadingInpatientAdmissions, loadMoreInpatientAdmissions],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (!wardPatientGroupDetails) return <></>;
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className={classNames(styles.wardViewMain, { [styles.verticalTiling]: isVertical })}>
|
|
60
|
+
{wardBeds}
|
|
61
|
+
{bedLayouts?.length == 0 && isBedManagementModuleInstalled && (
|
|
62
|
+
<InlineNotification
|
|
63
|
+
kind="warning"
|
|
64
|
+
lowContrast={true}
|
|
65
|
+
title={t('noBedsConfigured', 'No beds configured for this location')}
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
{wardUnassignedPatients}
|
|
69
|
+
{(isLoadingAdmissionLocation || isLoadingInpatientAdmissions) && <EmptyBeds />}
|
|
70
|
+
{errorLoadingAdmissionLocation && (
|
|
71
|
+
<InlineNotification
|
|
72
|
+
kind="error"
|
|
73
|
+
lowContrast={true}
|
|
74
|
+
title={t('errorLoadingWardLocation', 'Error loading ward location')}
|
|
75
|
+
subtitle={
|
|
76
|
+
errorLoadingAdmissionLocation?.message ??
|
|
77
|
+
t('invalidWardLocation', 'Invalid ward location: {{location}}', { location: location.display })
|
|
78
|
+
}
|
|
79
|
+
/>
|
|
80
|
+
)}
|
|
81
|
+
{errorLoadingInpatientAdmissions && (
|
|
82
|
+
<InlineNotification
|
|
83
|
+
kind="error"
|
|
84
|
+
lowContrast={true}
|
|
85
|
+
title={t('errorLoadingPatients', 'Error loading admitted patients')}
|
|
86
|
+
subtitle={errorLoadingInpatientAdmissions?.message}
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
<div ref={scrollToLoadMoreTrigger}></div>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const EmptyBeds = () => {
|
|
95
|
+
return (
|
|
96
|
+
<>
|
|
97
|
+
{Array(20)
|
|
98
|
+
.fill(0)
|
|
99
|
+
.map((_, i) => (
|
|
100
|
+
<EmptyBedSkeleton key={i} />
|
|
101
|
+
))}
|
|
102
|
+
</>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export default Ward;
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Movement } from '@carbon/react/icons';
|
|
3
1
|
import { Button, InlineNotification } from '@carbon/react';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { Movement } from '@carbon/react/icons';
|
|
3
|
+
import { ArrowRightIcon, isDesktop, launchWorkspace, useAppContext, useLayoutType } from '@openmrs/esm-framework';
|
|
4
|
+
import React, { type ReactNode } from 'react';
|
|
6
5
|
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { type WardViewContext } from '../types';
|
|
7
7
|
import styles from './admission-requests.scss';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
interface AdmissionRequestsBarProps {
|
|
10
|
+
wardPendingPatients: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const AdmissionRequestsBar: React.FC<AdmissionRequestsBarProps> = ({ wardPendingPatients }) => {
|
|
14
|
+
const {wardPatientGroupDetails} = useAppContext<WardViewContext>('ward-view-context') ?? {};
|
|
15
|
+
const { inpatientRequests, isLoading, error } = wardPatientGroupDetails?.inpatientRequestResponse ?? {};
|
|
11
16
|
const { t } = useTranslation();
|
|
12
17
|
const layout = useLayoutType();
|
|
13
18
|
|
|
@@ -20,7 +25,7 @@ const AdmissionRequestsBar = () => {
|
|
|
20
25
|
return (
|
|
21
26
|
<InlineNotification
|
|
22
27
|
kind="error"
|
|
23
|
-
title={t('errorLoadingPatientAdmissionRequests', 'Error
|
|
28
|
+
title={t('errorLoadingPatientAdmissionRequests', 'Error loading patient admission requests')}
|
|
24
29
|
/>
|
|
25
30
|
);
|
|
26
31
|
}
|
|
@@ -29,12 +34,12 @@ const AdmissionRequestsBar = () => {
|
|
|
29
34
|
<div className={styles.admissionRequestsContainer}>
|
|
30
35
|
<Movement className={styles.movementIcon} size="24" />
|
|
31
36
|
<span className={styles.content}>
|
|
32
|
-
{t('admissionRequestsCount', '{{count}} admission request
|
|
37
|
+
{t('admissionRequestsCount', '{{count}} admission request', {
|
|
33
38
|
count: inpatientRequests.length,
|
|
34
39
|
})}
|
|
35
40
|
</span>
|
|
36
41
|
<Button
|
|
37
|
-
onClick={() => launchWorkspace('admission-requests-workspace')}
|
|
42
|
+
onClick={() => launchWorkspace('admission-requests-workspace', { wardPendingPatients })}
|
|
38
43
|
renderIcon={ArrowRightIcon}
|
|
39
44
|
kind="ghost"
|
|
40
45
|
size={isDesktop(layout) ? 'sm' : 'lg'}>
|
|
@@ -1,38 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
import userEvent from '@testing-library/user-event';
|
|
1
|
+
import { launchWorkspace, useAppContext } from '@openmrs/esm-framework';
|
|
3
2
|
import { screen } from '@testing-library/react';
|
|
4
|
-
import
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import React from 'react';
|
|
5
5
|
import { renderWithSwr } from 'tools';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { mockWardViewContext } from '../../mock';
|
|
7
|
+
import { type WardViewContext } from '../types';
|
|
8
8
|
import AdmissionRequestsBar from './admission-requests-bar.component';
|
|
9
9
|
|
|
10
|
-
jest.
|
|
11
|
-
useInpatientRequest: jest.fn(),
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
const mockInpatientRequestResponse = {
|
|
15
|
-
error: undefined,
|
|
16
|
-
mutate: jest.fn(),
|
|
17
|
-
isValidating: false,
|
|
18
|
-
isLoading: false,
|
|
19
|
-
inpatientRequests: [mockInpatientRequest],
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
jest.mocked(useInpatientRequest).mockReturnValue(mockInpatientRequestResponse);
|
|
10
|
+
jest.mocked(useAppContext<WardViewContext>).mockReturnValue(mockWardViewContext);
|
|
23
11
|
|
|
24
12
|
describe('Admission Requests Button', () => {
|
|
25
|
-
it('
|
|
13
|
+
it('should launch workspace when clicked on manage button', async () => {
|
|
26
14
|
const user = userEvent.setup();
|
|
27
|
-
renderWithSwr(<AdmissionRequestsBar />);
|
|
15
|
+
renderWithSwr(<AdmissionRequestsBar wardPendingPatients={[]} />);
|
|
28
16
|
|
|
29
17
|
await user.click(screen.getByRole('button', { name: /manage/i }));
|
|
30
18
|
expect(launchWorkspace).toHaveBeenCalled();
|
|
31
19
|
});
|
|
32
20
|
|
|
33
|
-
it('
|
|
34
|
-
renderWithSwr(<AdmissionRequestsBar />);
|
|
21
|
+
it('should have one admission request', () => {
|
|
22
|
+
renderWithSwr(<AdmissionRequestsBar wardPendingPatients={[<div>Dummy Patient</div>]} />);
|
|
35
23
|
|
|
36
|
-
expect(screen.getByText('1 admission request
|
|
24
|
+
expect(screen.getByText('1 admission request')).toBeInTheDocument();
|
|
37
25
|
});
|
|
38
26
|
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styles from './ward-metric.scss';
|
|
3
|
+
import { SkeletonPlaceholder } from '@carbon/react';
|
|
4
|
+
|
|
5
|
+
interface WardMetricProps {
|
|
6
|
+
metricName: string;
|
|
7
|
+
metricValue: string;
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
}
|
|
10
|
+
const WardMetric: React.FC<WardMetricProps> = ({ metricName, metricValue, isLoading }) => {
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className={styles.metric}>
|
|
14
|
+
<span className={styles.metricName}>{metricName}</span>
|
|
15
|
+
{isLoading ? (
|
|
16
|
+
<SkeletonPlaceholder className={styles.skeleton} />
|
|
17
|
+
) : (
|
|
18
|
+
<span className={styles.metricValue}>{metricValue}</span>
|
|
19
|
+
)}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default WardMetric;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
4
|
+
|
|
5
|
+
.metric {
|
|
6
|
+
margin-left: layout.$spacing-05;
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: end;
|
|
9
|
+
gap: 5px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.metricName {
|
|
13
|
+
@include type.type-style('helper-text-01');
|
|
14
|
+
color: $color-gray-70;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.metricValue {
|
|
18
|
+
@include type.type-style('heading-03');
|
|
19
|
+
line-height: revert;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.skeleton {
|
|
23
|
+
height: layout.$spacing-05;
|
|
24
|
+
width: layout.$spacing-05;
|
|
25
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { showNotification, useAppContext, useFeatureFlag } from '@openmrs/esm-framework';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import type { WardViewContext } from '../types';
|
|
5
|
+
import {
|
|
6
|
+
getWardMetricNameTranslation,
|
|
7
|
+
getWardMetrics,
|
|
8
|
+
getWardMetricValueTranslation,
|
|
9
|
+
} from '../ward-view/ward-view.resource';
|
|
10
|
+
import WardMetric from './ward-metric.component';
|
|
11
|
+
import styles from './ward-metrics.scss';
|
|
12
|
+
|
|
13
|
+
const wardMetrics = [{ name: 'patients' }, { name: 'freeBeds' }, { name: 'capacity' }];
|
|
14
|
+
|
|
15
|
+
const WardMetrics = () => {
|
|
16
|
+
const { t } = useTranslation();
|
|
17
|
+
const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
|
|
18
|
+
const { wardPatientGroupDetails } = useAppContext<WardViewContext>('ward-view-context') ?? {};
|
|
19
|
+
const { admissionLocationResponse, inpatientAdmissionResponse, inpatientRequestResponse, bedLayouts } =
|
|
20
|
+
wardPatientGroupDetails || {};
|
|
21
|
+
const { isLoading, error } = admissionLocationResponse ?? {};
|
|
22
|
+
const isDataLoading =
|
|
23
|
+
admissionLocationResponse?.isLoading ||
|
|
24
|
+
inpatientAdmissionResponse?.isLoading ||
|
|
25
|
+
inpatientRequestResponse?.isLoading;
|
|
26
|
+
if (!wardPatientGroupDetails) return <></>;
|
|
27
|
+
|
|
28
|
+
if (error) {
|
|
29
|
+
showNotification({
|
|
30
|
+
kind: 'error',
|
|
31
|
+
title: t('errorLoadingBedDetails', 'Error loading bed details'),
|
|
32
|
+
description: error.message,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const wardMetricValues = getWardMetrics(bedLayouts, wardPatientGroupDetails);
|
|
37
|
+
return (
|
|
38
|
+
<div className={styles.metricsContainer}>
|
|
39
|
+
{isBedManagementModuleInstalled ? (
|
|
40
|
+
wardMetrics.map((wardMetric) => {
|
|
41
|
+
return (
|
|
42
|
+
<WardMetric
|
|
43
|
+
metricName={getWardMetricNameTranslation(wardMetric.name, t)}
|
|
44
|
+
metricValue={getWardMetricValueTranslation(wardMetric.name, t, wardMetricValues[wardMetric.name])}
|
|
45
|
+
isLoading={!!isLoading || !!isDataLoading}
|
|
46
|
+
key={wardMetric.name}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
})
|
|
50
|
+
) : (
|
|
51
|
+
<WardMetric
|
|
52
|
+
metricName={getWardMetricNameTranslation('patients', t)}
|
|
53
|
+
metricValue={'--'}
|
|
54
|
+
isLoading={false}
|
|
55
|
+
key={'patients'}
|
|
56
|
+
/>
|
|
57
|
+
)}
|
|
58
|
+
{isBedManagementModuleInstalled && (
|
|
59
|
+
<WardMetric
|
|
60
|
+
metricName={getWardMetricNameTranslation('pendingOut', t)}
|
|
61
|
+
metricValue={
|
|
62
|
+
error
|
|
63
|
+
? '--'
|
|
64
|
+
: getWardMetricValueTranslation(
|
|
65
|
+
'pendingOut',
|
|
66
|
+
t,
|
|
67
|
+
wardPatientGroupDetails?.wardPatientPendingCount?.toString(),
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
isLoading={!!isDataLoading}
|
|
71
|
+
key="pending"
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default WardMetrics;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import WardMetrics from './ward-metrics.component';
|
|
3
|
+
import { renderWithSwr } from '../../../../tools/test-utils';
|
|
4
|
+
import {
|
|
5
|
+
createAndGetWardPatientGrouping,
|
|
6
|
+
getInpatientAdmissionsUuidMap,
|
|
7
|
+
getWardMetrics,
|
|
8
|
+
} from '../ward-view/ward-view.resource';
|
|
9
|
+
import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
|
|
10
|
+
import { mockAdmissionLocation, mockInpatientAdmissions, mockInpatientRequest } from '__mocks__';
|
|
11
|
+
import { useInpatientAdmission } from '../hooks/useInpatientAdmission';
|
|
12
|
+
import useWardLocation from '../hooks/useWardLocation';
|
|
13
|
+
import { screen } from '@testing-library/react';
|
|
14
|
+
import { useAppContext } from '@openmrs/esm-framework';
|
|
15
|
+
import { type WardViewContext } from '../types';
|
|
16
|
+
import { mockWardViewContext } from '../../mock';
|
|
17
|
+
|
|
18
|
+
const wardMetrics = [
|
|
19
|
+
{ name: 'patients', key: 'patients', defaultTranslation: 'Patients' },
|
|
20
|
+
{ name: 'freeBeds', key: 'freeBeds', defaultTranslation: 'Free beds' },
|
|
21
|
+
{ name: 'capacity', key: 'capacity', defaultTranslation: 'Capacity' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
jest.mocked(useAppContext<WardViewContext>).mockReturnValue(mockWardViewContext);
|
|
25
|
+
|
|
26
|
+
describe('Ward Metrics', () => {
|
|
27
|
+
it('Should display metrics of in the ward ', () => {
|
|
28
|
+
const mockWardPatientGroupDetails = mockWardViewContext.wardPatientGroupDetails;
|
|
29
|
+
const { bedLayouts } = mockWardPatientGroupDetails;
|
|
30
|
+
const bedMetrics = getWardMetrics(bedLayouts, mockWardPatientGroupDetails);
|
|
31
|
+
renderWithSwr(<WardMetrics />);
|
|
32
|
+
for (let [key, value] of Object.entries(bedMetrics)) {
|
|
33
|
+
const fieldName = wardMetrics.find((metric) => metric.name == key)?.defaultTranslation;
|
|
34
|
+
expect(screen.getByText(fieldName!)).toBeInTheDocument();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
2
|
import styles from './ward-view-header.scss';
|
|
3
3
|
import AdmissionRequestsBar from './admission-requests-bar.component';
|
|
4
4
|
import useWardLocation from '../hooks/useWardLocation';
|
|
5
|
+
import WardMetrics from './ward-metrics.component';
|
|
5
6
|
|
|
6
|
-
interface WardViewHeaderProps {
|
|
7
|
+
interface WardViewHeaderProps {
|
|
8
|
+
wardPendingPatients: ReactNode;
|
|
9
|
+
}
|
|
7
10
|
|
|
8
|
-
const WardViewHeader: React.FC<WardViewHeaderProps> = () => {
|
|
11
|
+
const WardViewHeader: React.FC<WardViewHeaderProps> = ({ wardPendingPatients }) => {
|
|
9
12
|
const { location } = useWardLocation();
|
|
13
|
+
|
|
10
14
|
return (
|
|
11
15
|
<div className={styles.wardViewHeader}>
|
|
12
16
|
<h4>{location?.display}</h4>
|
|
13
|
-
<
|
|
17
|
+
<WardMetrics />
|
|
18
|
+
<AdmissionRequestsBar {...{ wardPendingPatients }} />
|
|
14
19
|
</div>
|
|
15
20
|
);
|
|
16
21
|
};
|