@kenyaemr/esm-ward-app 8.1.1-pre.114 → 8.1.1-pre.118
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 +18 -17
- package/dist/109.js +1 -0
- package/dist/109.js.map +1 -0
- package/dist/125.js +1 -0
- package/dist/125.js.map +1 -0
- package/dist/126.js +1 -0
- package/dist/126.js.map +1 -0
- package/dist/130.js +1 -1
- 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/161.js +2 -0
- package/dist/161.js.map +1 -0
- package/dist/269.js +1 -1
- package/dist/269.js.map +1 -1
- package/dist/466.js +1 -1
- package/dist/466.js.map +1 -1
- package/dist/500.js +1 -0
- package/dist/500.js.map +1 -0
- package/dist/53.js +1 -0
- package/dist/53.js.map +1 -0
- package/dist/557.js +1 -0
- package/dist/557.js.map +1 -0
- package/dist/559.js +1 -0
- package/dist/559.js.map +1 -0
- package/dist/574.js +1 -1
- package/dist/577.js +1 -1
- package/dist/577.js.map +1 -1
- package/dist/659.js +1 -1
- package/dist/659.js.map +1 -1
- package/dist/701.js +1 -0
- package/dist/701.js.map +1 -0
- package/dist/749.js +1 -1
- package/dist/749.js.map +1 -1
- package/dist/908.js +1 -0
- package/dist/908.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 +294 -74
- package/dist/kenyaemr-esm-ward-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/mock.tsx +54 -0
- package/package.json +1 -1
- 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 +2 -1
- package/src/beds/empty-bed.scss +0 -4
- package/src/beds/occupied-bed.scss +1 -0
- package/src/config-schema-mother-child-row.ts +26 -0
- package/src/config-schema-pending-items-extension.ts +29 -0
- package/src/config-schema.ts +12 -14
- package/src/hooks/useAdmissionLocation.ts +22 -4
- 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 +2 -6
- package/src/hooks/usePatientPendingOrders.ts +16 -0
- package/src/hooks/useWardPatientGrouping.ts +25 -0
- package/src/index.ts +50 -3
- package/src/location-selector/location-selector.component.tsx +18 -21
- package/src/routes.json +43 -0
- package/src/types/index.ts +34 -0
- package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +7 -2
- package/src/ward-patient-card/card-rows/mother-child-row.extension.tsx +110 -0
- package/src/ward-patient-card/card-rows/mother-child-row.scss +22 -0
- package/src/ward-patient-card/card-rows/pending-items-car-row.extension.tsx +50 -0
- package/src/ward-patient-card/row-elements/ward-pateint-skeleton-text.tsx +9 -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 +54 -36
- package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +2 -3
- 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 +36 -32
- package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +15 -9
- 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/ward-patient-card-element.component.tsx +4 -0
- package/src/ward-patient-card/ward-patient-card.component.tsx +21 -14
- package/src/ward-patient-card/ward-patient-card.scss +61 -8
- package/src/ward-patient-card/ward-patient-resource.ts +15 -0
- package/src/ward-view/ward-view.component.tsx +124 -132
- package/src/ward-view/ward-view.resource.ts +121 -1
- package/src/ward-view/ward-view.scss +16 -6
- package/src/ward-view/ward-view.test.tsx +27 -42
- package/src/ward-view-header/admission-requests-bar.component.tsx +8 -7
- package/src/ward-view-header/admission-requests-bar.test.tsx +8 -21
- 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 +77 -0
- package/src/ward-view-header/ward-metrics.scss +8 -0
- package/src/ward-view-header/ward-metrics.test.tsx +91 -0
- package/src/ward-view-header/ward-view-header.component.tsx +3 -0
- package/src/ward-view-header/ward-view-header.scss +0 -1
- package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +11 -3
- package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +4 -5
- package/src/ward-workspace/admission-request-card/admission-request-card.scss +8 -4
- package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +8 -3
- package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +2 -0
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +29 -61
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +37 -21
- package/src/ward-workspace/patient-banner/patient-banner.component.tsx +3 -3
- 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 +7 -5
- package/src/ward-workspace/patient-discharge/patient-discharge.scss +41 -0
- package/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx +120 -0
- package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +40 -30
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +29 -22
- 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 +2 -2
- 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 +6 -0
- package/translations/en.json +18 -1
- package/dist/346.js +0 -1
- package/dist/346.js.map +0 -1
- package/dist/76.js +0 -1
- package/dist/76.js.map +0 -1
- package/dist/803.js +0 -1
- package/dist/803.js.map +0 -1
- package/dist/958.js +0 -2
- package/dist/958.js.map +0 -1
- package/dist/960.js +0 -1
- package/dist/960.js.map +0 -1
- /package/dist/{958.js.LICENSE.txt → 161.js.LICENSE.txt} +0 -0
- /package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.style.scss +0 -0
|
@@ -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/styles/scss/spacing';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@import '~@openmrs/esm-styleguide/src/vars';
|
|
4
|
+
|
|
5
|
+
.metric {
|
|
6
|
+
margin-left: spacing.$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: 15px;
|
|
24
|
+
width: 15px;
|
|
25
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styles from './ward-metrics.scss';
|
|
3
|
+
import { useBeds } from '../hooks/useBeds';
|
|
4
|
+
import { showNotification, useAppContext, useFeatureFlag } from '@openmrs/esm-framework';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import {
|
|
7
|
+
getWardMetricNameTranslation,
|
|
8
|
+
getWardMetrics,
|
|
9
|
+
getWardMetricValueTranslation,
|
|
10
|
+
} from '../ward-view/ward-view.resource';
|
|
11
|
+
import WardMetric from './ward-metric.component';
|
|
12
|
+
import type { WardPatientGroupDetails } from '../types';
|
|
13
|
+
import useWardLocation from '../hooks/useWardLocation';
|
|
14
|
+
|
|
15
|
+
const wardMetrics = [{ name: 'patients' }, { name: 'freeBeds' }, { name: 'capacity' }];
|
|
16
|
+
|
|
17
|
+
const WardMetrics = () => {
|
|
18
|
+
const { location } = useWardLocation();
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
|
|
21
|
+
const wardPatientGroup = useAppContext<WardPatientGroupDetails>('ward-patients-group');
|
|
22
|
+
const { admissionLocationResponse, inpatientAdmissionResponse, inpatientRequestResponse, bedLayouts } =
|
|
23
|
+
wardPatientGroup || {};
|
|
24
|
+
const { isLoading, error } = admissionLocationResponse ?? {};
|
|
25
|
+
const isDataLoading =
|
|
26
|
+
admissionLocationResponse?.isLoading ||
|
|
27
|
+
inpatientAdmissionResponse?.isLoading ||
|
|
28
|
+
inpatientRequestResponse?.isLoading;
|
|
29
|
+
if (!wardPatientGroup) return <></>;
|
|
30
|
+
|
|
31
|
+
if (error) {
|
|
32
|
+
showNotification({
|
|
33
|
+
kind: 'error',
|
|
34
|
+
title: t('errorLoadingBedDetails', 'Error loading bed details'),
|
|
35
|
+
description: error.message,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const wardMetricValues = getWardMetrics(bedLayouts, wardPatientGroup);
|
|
40
|
+
return (
|
|
41
|
+
<div className={styles.metricsContainer}>
|
|
42
|
+
{isBedManagementModuleInstalled ? (
|
|
43
|
+
wardMetrics.map((wardMetric) => {
|
|
44
|
+
return (
|
|
45
|
+
<WardMetric
|
|
46
|
+
metricName={getWardMetricNameTranslation(wardMetric.name, t)}
|
|
47
|
+
metricValue={getWardMetricValueTranslation(wardMetric.name, t, wardMetricValues[wardMetric.name])}
|
|
48
|
+
isLoading={!!isLoading || !!isDataLoading}
|
|
49
|
+
key={wardMetric.name}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
})
|
|
53
|
+
) : (
|
|
54
|
+
<WardMetric
|
|
55
|
+
metricName={getWardMetricNameTranslation('patients', t)}
|
|
56
|
+
metricValue={'--'}
|
|
57
|
+
isLoading={false}
|
|
58
|
+
key={'patients'}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
{isBedManagementModuleInstalled && (
|
|
62
|
+
<WardMetric
|
|
63
|
+
metricName={getWardMetricNameTranslation('pendingOut', t)}
|
|
64
|
+
metricValue={
|
|
65
|
+
error
|
|
66
|
+
? '--'
|
|
67
|
+
: getWardMetricValueTranslation('pendingOut', t, wardPatientGroup?.wardPatientPendingCount?.toString())
|
|
68
|
+
}
|
|
69
|
+
isLoading={!!isDataLoading}
|
|
70
|
+
key="pending"
|
|
71
|
+
/>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default WardMetrics;
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
|
|
16
|
+
const wardMetrics = [
|
|
17
|
+
{ name: 'patients', key: 'patients', defaultTranslation: 'Patients' },
|
|
18
|
+
{ name: 'freeBeds', key: 'freeBeds', defaultTranslation: 'Free beds' },
|
|
19
|
+
{ name: 'capacity', key: 'capacity', defaultTranslation: 'Capacity' },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
jest.mock('react-router-dom', () => ({
|
|
23
|
+
...jest.requireActual('react-router-dom'),
|
|
24
|
+
useParams: jest.fn().mockReturnValue({}),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
jest.mock('../hooks/useWardLocation', () =>
|
|
28
|
+
jest.fn().mockReturnValue({
|
|
29
|
+
location: { uuid: 'abcd', display: 'mock location' },
|
|
30
|
+
isLoadingLocation: false,
|
|
31
|
+
errorFetchingLocation: null,
|
|
32
|
+
invalidLocation: false,
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
jest.mock('../hooks/useBeds', () => ({
|
|
37
|
+
useBeds: jest.fn(),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
jest.mock('../hooks/useAdmissionLocation', () => ({
|
|
41
|
+
useAdmissionLocation: jest.fn(),
|
|
42
|
+
}));
|
|
43
|
+
jest.mock('../hooks/useInpatientAdmission', () => ({
|
|
44
|
+
useInpatientAdmission: jest.fn(),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
jest.mock('../hooks/useInpatientRequest', () => ({
|
|
48
|
+
useInpatientRequest: jest.fn(),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
const mockUseWardLocation = jest.mocked(useWardLocation);
|
|
52
|
+
|
|
53
|
+
const mockAdmissionLocationResponse = jest.mocked(useAdmissionLocation).mockReturnValue({
|
|
54
|
+
error: undefined,
|
|
55
|
+
mutate: jest.fn(),
|
|
56
|
+
isValidating: false,
|
|
57
|
+
isLoading: false,
|
|
58
|
+
admissionLocation: mockAdmissionLocation,
|
|
59
|
+
});
|
|
60
|
+
const mockInpatientAdmissionResponse = jest.mocked(useInpatientAdmission).mockReturnValue({
|
|
61
|
+
error: undefined,
|
|
62
|
+
mutate: jest.fn(),
|
|
63
|
+
isValidating: false,
|
|
64
|
+
isLoading: false,
|
|
65
|
+
inpatientAdmissions: mockInpatientAdmissions,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const inpatientAdmissionsUuidMap = getInpatientAdmissionsUuidMap(mockInpatientAdmissions);
|
|
69
|
+
const mockWardPatientGroupDetails = {
|
|
70
|
+
admissionLocationResponse: mockAdmissionLocationResponse(),
|
|
71
|
+
inpatientAdmissionResponse: mockInpatientAdmissionResponse(),
|
|
72
|
+
...createAndGetWardPatientGrouping(mockInpatientAdmissions, mockAdmissionLocation, mockInpatientRequest),
|
|
73
|
+
};
|
|
74
|
+
jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails);
|
|
75
|
+
describe('Ward Metrics', () => {
|
|
76
|
+
it('Should display metrics of in the ward ', () => {
|
|
77
|
+
mockUseWardLocation.mockReturnValueOnce({
|
|
78
|
+
location: null,
|
|
79
|
+
isLoadingLocation: false,
|
|
80
|
+
errorFetchingLocation: null,
|
|
81
|
+
invalidLocation: true,
|
|
82
|
+
});
|
|
83
|
+
const { bedLayouts } = mockWardPatientGroupDetails;
|
|
84
|
+
const bedMetrics = getWardMetrics(bedLayouts, mockWardPatientGroupDetails);
|
|
85
|
+
renderWithSwr(<WardMetrics />);
|
|
86
|
+
for (let [key, value] of Object.entries(bedMetrics)) {
|
|
87
|
+
const fieldName = wardMetrics.find((metric) => metric.name == key)?.defaultTranslation;
|
|
88
|
+
expect(screen.getByText(fieldName!)).toBeInTheDocument();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -2,14 +2,17 @@ import React 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
7
|
interface WardViewHeaderProps {}
|
|
7
8
|
|
|
8
9
|
const WardViewHeader: React.FC<WardViewHeaderProps> = () => {
|
|
9
10
|
const { location } = useWardLocation();
|
|
11
|
+
|
|
10
12
|
return (
|
|
11
13
|
<div className={styles.wardViewHeader}>
|
|
12
14
|
<h4>{location?.display}</h4>
|
|
15
|
+
<WardMetrics />
|
|
13
16
|
<AdmissionRequestsBar />
|
|
14
17
|
</div>
|
|
15
18
|
);
|
package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx
CHANGED
|
@@ -2,11 +2,12 @@ import { Button } from '@carbon/react';
|
|
|
2
2
|
import { ArrowRightIcon, launchWorkspace, useLayoutType } from '@openmrs/esm-framework';
|
|
3
3
|
import React, { useCallback } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import type { WardPatientCard } from '../../types';
|
|
5
|
+
import type { WardPatientWorkspaceProps, WardPatientCard } from '../../types';
|
|
6
6
|
import type { AdmitPatientFormWorkspaceProps } from '../admit-patient-form-workspace/types';
|
|
7
7
|
import styles from './admission-request-card.scss';
|
|
8
8
|
|
|
9
|
-
const AdmissionRequestCardActions: WardPatientCard = (
|
|
9
|
+
const AdmissionRequestCardActions: WardPatientCard = (wardPatient) => {
|
|
10
|
+
const { patient, inpatientRequest } = wardPatient;
|
|
10
11
|
const { dispositionType } = inpatientRequest;
|
|
11
12
|
const { t } = useTranslation();
|
|
12
13
|
const responsiveSize = useLayoutType() === 'tablet' ? 'lg' : 'md';
|
|
@@ -14,9 +15,16 @@ const AdmissionRequestCardActions: WardPatientCard = ({ patient, inpatientReques
|
|
|
14
15
|
() => launchWorkspace<AdmitPatientFormWorkspaceProps>('admit-patient-form-workspace', { patient, dispositionType }),
|
|
15
16
|
[],
|
|
16
17
|
);
|
|
18
|
+
|
|
19
|
+
const launchPatientTransferForm = useCallback(() => {
|
|
20
|
+
launchWorkspace<WardPatientWorkspaceProps>('patient-transfer-request-workspace', {
|
|
21
|
+
wardPatient,
|
|
22
|
+
});
|
|
23
|
+
}, [wardPatient]);
|
|
24
|
+
|
|
17
25
|
return (
|
|
18
26
|
<div className={styles.admissionRequestActionBar}>
|
|
19
|
-
<Button kind="ghost" size={responsiveSize}>
|
|
27
|
+
<Button kind="ghost" size={responsiveSize} onClick={launchPatientTransferForm}>
|
|
20
28
|
{t('transferElsewhere', 'Transfer elsewhere')}
|
|
21
29
|
</Button>
|
|
22
30
|
<Button kind="ghost" renderIcon={ArrowRightIcon} size={responsiveSize} onClick={launchPatientAdmissionForm}>
|
package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { ExtensionSlot, formatDatetime, getLocale } from '@openmrs/esm-framework';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import WardPatientWorkspaceBanner from '../patient-banner/patient-banner.component';
|
|
5
|
-
import styles from './admission-request-card.scss';
|
|
6
|
-
import type WardPatientCard from '../../ward-patient-card/ward-patient-card.component';
|
|
7
4
|
import { useCurrentWardCardConfig } from '../../hooks/useCurrentWardCardConfig';
|
|
8
|
-
import { WardPatientCardElement } from '../../ward-patient-card/ward-patient-card-element.component';
|
|
9
5
|
import WardPatientName from '../../ward-patient-card/row-elements/ward-patient-name';
|
|
6
|
+
import { WardPatientCardElement } from '../../ward-patient-card/ward-patient-card-element.component';
|
|
7
|
+
import type WardPatientCard from '../../ward-patient-card/ward-patient-card.component';
|
|
8
|
+
import styles from './admission-request-card.scss';
|
|
10
9
|
|
|
11
10
|
const AdmissionRequestCardHeader: WardPatientCard = (wardPatient) => {
|
|
12
11
|
const { inpatientRequest } = wardPatient;
|
|
@@ -42,7 +41,7 @@ const AdmissionRequestCardHeader: WardPatientCard = (wardPatient) => {
|
|
|
42
41
|
<ExtensionSlot
|
|
43
42
|
name={rowsExtensionSlotName}
|
|
44
43
|
state={extensionSlotState}
|
|
45
|
-
className={styles.
|
|
44
|
+
className={styles.admissionRequestCardExtensionSlot}
|
|
46
45
|
/>
|
|
47
46
|
</div>
|
|
48
47
|
);
|
|
@@ -25,10 +25,14 @@
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
.admissionRequestCardExtensionSlot {
|
|
29
|
+
display: none;
|
|
30
|
+
|
|
31
|
+
&:has(div:not(:empty)) {
|
|
32
|
+
display: block;
|
|
33
|
+
margin: layout.$spacing-03 0;
|
|
34
|
+
background-color: white;
|
|
35
|
+
}
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
.admissionEncounterDetails {
|
package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx
CHANGED
|
@@ -23,12 +23,15 @@ mockUseWardLocation.mockReturnValue({
|
|
|
23
23
|
invalidLocation: false,
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
const mockInpatientRequestResponse = {
|
|
26
|
+
const mockInpatientRequestResponse: ReturnType<typeof useInpatientRequest> = {
|
|
27
27
|
error: undefined,
|
|
28
28
|
mutate: jest.fn(),
|
|
29
29
|
isValidating: false,
|
|
30
30
|
isLoading: false,
|
|
31
|
-
inpatientRequests:
|
|
31
|
+
inpatientRequests: mockInpatientRequest,
|
|
32
|
+
totalCount: 1,
|
|
33
|
+
hasMore: false,
|
|
34
|
+
loadMore: jest.fn(),
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
jest.mocked(useInpatientRequest).mockReturnValue(mockInpatientRequestResponse);
|
|
@@ -43,6 +46,8 @@ const workspaceProps = {
|
|
|
43
46
|
describe('Admission Requests Workspace', () => {
|
|
44
47
|
it('should render a admission request card', () => {
|
|
45
48
|
renderWithSwr(<AdmissionRequestsWorkspace {...workspaceProps} />);
|
|
46
|
-
expect(
|
|
49
|
+
expect(
|
|
50
|
+
screen.getByText(mockInpatientRequest[0].patient.person?.preferredName?.display as string),
|
|
51
|
+
).toBeInTheDocument();
|
|
47
52
|
});
|
|
48
53
|
});
|
|
@@ -9,6 +9,8 @@ import { type InpatientRequest } from '../../types';
|
|
|
9
9
|
|
|
10
10
|
interface AdmissionRequestsWorkspaceProps {}
|
|
11
11
|
const AdmissionRequestsWorkspace: React.FC<AdmissionRequestsWorkspaceProps> = () => {
|
|
12
|
+
// note: useAppContext() does not work here for some reason, so we call `useInpatientRequest`
|
|
13
|
+
// directly. See: https://openmrs.atlassian.net/browse/O3-4020
|
|
12
14
|
const {
|
|
13
15
|
inpatientRequests,
|
|
14
16
|
isLoading: isLoadingInpatientRequests,
|
|
@@ -1,21 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { openmrsFetch, showSnackbar, useAppContext, useFeatureFlag, useSession } from '@openmrs/esm-framework';
|
|
2
2
|
import { screen } from '@testing-library/react';
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { mockAdmissionLocation, mockLocationInpatientWard, mockPatientAlice } from '../../../../../__mocks__';
|
|
4
6
|
import { renderWithSwr } from '../../../../../tools';
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
mockAdmissionLocation,
|
|
8
|
-
mockInpatientRequest,
|
|
9
|
-
mockLocationInpatientWard,
|
|
10
|
-
mockPatientAlice,
|
|
11
|
-
} from '../../../../../__mocks__';
|
|
12
|
-
import type { DispositionType } from '../../types';
|
|
13
|
-
import type { AdmitPatientFormWorkspaceProps } from './types';
|
|
7
|
+
import { mockWardPatientGroupDetails } from '../../../mock';
|
|
14
8
|
import { useAdmissionLocation } from '../../hooks/useAdmissionLocation';
|
|
15
|
-
import { openmrsFetch, provide, showSnackbar, useFeatureFlag, useSession } from '@openmrs/esm-framework';
|
|
16
9
|
import useEmrConfiguration from '../../hooks/useEmrConfiguration';
|
|
17
|
-
import useWardLocation from '../../hooks/useWardLocation';
|
|
18
10
|
import { useInpatientRequest } from '../../hooks/useInpatientRequest';
|
|
11
|
+
import useWardLocation from '../../hooks/useWardLocation';
|
|
12
|
+
import type { DispositionType } from '../../types';
|
|
13
|
+
import AdmitPatientFormWorkspace from './admit-patient-form.workspace';
|
|
14
|
+
import type { AdmitPatientFormWorkspaceProps } from './types';
|
|
19
15
|
|
|
20
16
|
jest.mock('../../hooks/useAdmissionLocation', () => ({
|
|
21
17
|
useAdmissionLocation: jest.fn(),
|
|
@@ -29,15 +25,23 @@ jest.mock('../../hooks/useInpatientRequest', () => ({
|
|
|
29
25
|
useInpatientRequest: jest.fn(),
|
|
30
26
|
}));
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
jest.mock('../../hooks/useWardPatientGrouping', () => ({
|
|
29
|
+
useWardPatientGrouping: jest.fn(),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
jest.mock('../../hooks/useInpatientAdmission', () => ({
|
|
33
|
+
useInpatientAdmission: jest.fn(),
|
|
34
|
+
}));
|
|
35
|
+
|
|
33
36
|
const mockedUseEmrConfiguration = jest.mocked(useEmrConfiguration);
|
|
34
37
|
const mockedUseWardLocation = jest.mocked(useWardLocation);
|
|
35
38
|
const mockedOpenmrsFetch = jest.mocked(openmrsFetch);
|
|
36
|
-
const mockedUseAdmissionLocation = jest.mocked(useAdmissionLocation);
|
|
37
39
|
const mockedUseFeatureFlag = jest.mocked(useFeatureFlag);
|
|
38
40
|
const mockedShowSnackbar = jest.mocked(showSnackbar);
|
|
39
41
|
const mockedUseSession = jest.mocked(useSession);
|
|
40
42
|
|
|
43
|
+
jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails());
|
|
44
|
+
|
|
41
45
|
const mockWorkspaceProps: AdmitPatientFormWorkspaceProps = {
|
|
42
46
|
patient: mockPatientAlice,
|
|
43
47
|
closeWorkspace: jest.fn(),
|
|
@@ -45,24 +49,19 @@ const mockWorkspaceProps: AdmitPatientFormWorkspaceProps = {
|
|
|
45
49
|
promptBeforeClosing: jest.fn(),
|
|
46
50
|
setTitle: jest.fn(),
|
|
47
51
|
dispositionType: 'ADMIT',
|
|
52
|
+
setCancelTitle: jest.fn(),
|
|
53
|
+
setCancelMessage: jest.fn(),
|
|
54
|
+
setCancelConfirmText: jest.fn(),
|
|
48
55
|
};
|
|
49
56
|
|
|
50
57
|
function renderAdmissionForm(dispositionType: DispositionType = 'ADMIT') {
|
|
51
58
|
renderWithSwr(<AdmitPatientFormWorkspace {...{ ...mockWorkspaceProps, dispositionType }} />);
|
|
52
59
|
}
|
|
53
60
|
|
|
54
|
-
const mockedMutateInpatientRequest = jest.fn();
|
|
55
|
-
|
|
56
61
|
describe('Testing AdmitPatientForm', () => {
|
|
57
62
|
beforeEach(() => {
|
|
58
63
|
jest.clearAllMocks();
|
|
59
|
-
|
|
60
|
-
isLoading: false,
|
|
61
|
-
isValidating: false,
|
|
62
|
-
admissionLocation: mockAdmissionLocation,
|
|
63
|
-
mutate: jest.fn(),
|
|
64
|
-
error: undefined,
|
|
65
|
-
});
|
|
64
|
+
|
|
66
65
|
mockedUseSession.mockReturnValue({
|
|
67
66
|
currentProvider: {
|
|
68
67
|
uuid: 'current-provider-uuid',
|
|
@@ -91,13 +90,6 @@ describe('Testing AdmitPatientForm', () => {
|
|
|
91
90
|
},
|
|
92
91
|
mutateEmrConfiguration: jest.fn(),
|
|
93
92
|
});
|
|
94
|
-
mockedUseInpatientRequest.mockReturnValue({
|
|
95
|
-
mutate: mockedMutateInpatientRequest,
|
|
96
|
-
error: undefined,
|
|
97
|
-
inpatientRequests: [mockInpatientRequest],
|
|
98
|
-
isLoading: false,
|
|
99
|
-
isValidating: false,
|
|
100
|
-
});
|
|
101
93
|
mockedUseWardLocation.mockReturnValue({
|
|
102
94
|
location: mockLocationInpatientWard,
|
|
103
95
|
invalidLocation: false,
|
|
@@ -157,30 +149,14 @@ describe('Testing AdmitPatientForm', () => {
|
|
|
157
149
|
|
|
158
150
|
it('should render admit patient form if bed management module is present, but no beds are configured', () => {
|
|
159
151
|
mockedUseFeatureFlag.mockReturnValue(true);
|
|
160
|
-
|
|
161
|
-
isLoading: false,
|
|
162
|
-
isValidating: false,
|
|
163
|
-
admissionLocation: {
|
|
164
|
-
...mockAdmissionLocation,
|
|
165
|
-
totalBeds: 0,
|
|
166
|
-
bedLayouts: [],
|
|
167
|
-
},
|
|
168
|
-
mutate: jest.fn(),
|
|
169
|
-
error: null,
|
|
170
|
-
});
|
|
152
|
+
const replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []);
|
|
171
153
|
renderAdmissionForm();
|
|
172
154
|
expect(screen.getByText('Select a bed')).toBeInTheDocument();
|
|
173
|
-
expect(screen.getByText(
|
|
155
|
+
expect(screen.getByText(/No beds configured/i)).toBeInTheDocument();
|
|
156
|
+
replacedProperty.restore();
|
|
174
157
|
});
|
|
175
158
|
|
|
176
159
|
it('should submit the form, create encounter and submit bed', async () => {
|
|
177
|
-
mockedUseAdmissionLocation.mockReturnValueOnce({
|
|
178
|
-
isLoading: false,
|
|
179
|
-
isValidating: false,
|
|
180
|
-
admissionLocation: mockAdmissionLocation,
|
|
181
|
-
mutate: jest.fn(),
|
|
182
|
-
error: null,
|
|
183
|
-
});
|
|
184
160
|
// @ts-ignore - we only need these two keys for now
|
|
185
161
|
mockedOpenmrsFetch.mockResolvedValue({
|
|
186
162
|
ok: true,
|
|
@@ -290,17 +266,6 @@ describe('Testing AdmitPatientForm', () => {
|
|
|
290
266
|
});
|
|
291
267
|
|
|
292
268
|
it('should admit patient if no beds are configured', async () => {
|
|
293
|
-
mockedUseAdmissionLocation.mockReturnValueOnce({
|
|
294
|
-
isLoading: false,
|
|
295
|
-
isValidating: false,
|
|
296
|
-
admissionLocation: {
|
|
297
|
-
...mockAdmissionLocation,
|
|
298
|
-
totalBeds: 0,
|
|
299
|
-
bedLayouts: [],
|
|
300
|
-
},
|
|
301
|
-
mutate: jest.fn(),
|
|
302
|
-
error: null,
|
|
303
|
-
});
|
|
304
269
|
// @ts-ignore - we only need these two keys for now
|
|
305
270
|
mockedOpenmrsFetch.mockResolvedValue({
|
|
306
271
|
ok: true,
|
|
@@ -313,7 +278,7 @@ describe('Testing AdmitPatientForm', () => {
|
|
|
313
278
|
const admitButton = screen.getByRole('button', { name: 'Admit' });
|
|
314
279
|
expect(admitButton).toBeEnabled();
|
|
315
280
|
await user.click(admitButton);
|
|
316
|
-
expect(mockedOpenmrsFetch).toHaveBeenCalledTimes(
|
|
281
|
+
expect(mockedOpenmrsFetch).toHaveBeenCalledTimes(2);
|
|
317
282
|
expect(mockedOpenmrsFetch).toHaveBeenCalledWith('/ws/rest/v1/encounter', {
|
|
318
283
|
method: 'POST',
|
|
319
284
|
headers: {
|
|
@@ -332,6 +297,9 @@ describe('Testing AdmitPatientForm', () => {
|
|
|
332
297
|
],
|
|
333
298
|
},
|
|
334
299
|
});
|
|
300
|
+
expect(mockedOpenmrsFetch).toHaveBeenCalledWith(`/ws/rest/v1/beds/1?patientUuid=${mockPatientAlice.uuid}`, {
|
|
301
|
+
method: 'DELETE',
|
|
302
|
+
});
|
|
335
303
|
expect(mockedShowSnackbar).toHaveBeenCalledWith({
|
|
336
304
|
kind: 'success',
|
|
337
305
|
subtitle: 'Patient admitted successfully to Inpatient Ward',
|
|
@@ -1,19 +1,16 @@
|
|
|
1
|
+
import { Button, ButtonSet, Column, Dropdown, DropdownSkeleton, Form, InlineNotification, Row } from '@carbon/react';
|
|
2
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
3
|
+
import { showSnackbar, useAppContext, useFeatureFlag, useSession } from '@openmrs/esm-framework';
|
|
1
4
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { z } from 'zod';
|
|
3
5
|
import { Controller, useForm } from 'react-hook-form';
|
|
4
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
5
6
|
import { useTranslation } from 'react-i18next';
|
|
6
|
-
import {
|
|
7
|
-
import { showSnackbar, useFeatureFlag, useSession } from '@openmrs/esm-framework';
|
|
8
|
-
import { filterBeds } from '../../ward-view/ward-view.resource';
|
|
9
|
-
import type { BedLayout, DispositionType } from '../../types';
|
|
10
|
-
import { assignPatientToBed, createEncounter } from '../../ward.resource';
|
|
11
|
-
import { useAdmissionLocation } from '../../hooks/useAdmissionLocation';
|
|
12
|
-
import { useInpatientRequest } from '../../hooks/useInpatientRequest';
|
|
7
|
+
import { z } from 'zod';
|
|
13
8
|
import useEmrConfiguration from '../../hooks/useEmrConfiguration';
|
|
14
9
|
import useWardLocation from '../../hooks/useWardLocation';
|
|
15
|
-
import type {
|
|
10
|
+
import type { BedLayout, WardPatientGroupDetails } from '../../types';
|
|
11
|
+
import { assignPatientToBed, createEncounter, removePatientFromBed } from '../../ward.resource';
|
|
16
12
|
import styles from './admit-patient-form.scss';
|
|
13
|
+
import type { AdmitPatientFormWorkspaceProps } from './types';
|
|
17
14
|
|
|
18
15
|
const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
|
|
19
16
|
patient,
|
|
@@ -26,11 +23,13 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
|
|
|
26
23
|
const { location } = useWardLocation();
|
|
27
24
|
const { currentProvider } = useSession();
|
|
28
25
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
29
|
-
const { mutate: mutateInpatientRequest } = useInpatientRequest();
|
|
30
26
|
const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
|
|
31
27
|
const [showErrorNotifications, setShowErrorNotifications] = useState(false);
|
|
32
|
-
const
|
|
33
|
-
const
|
|
28
|
+
const wardPatientGrouping = useAppContext<WardPatientGroupDetails>('ward-patients-group');
|
|
29
|
+
const { isLoading, mutate: mutateAdmissionLocation } = wardPatientGrouping?.admissionLocationResponse ?? {};
|
|
30
|
+
const { mutate: mutateInpatientRequest } = wardPatientGrouping?.inpatientRequestResponse ?? {};
|
|
31
|
+
const { mutate: mutateInpatientAdmission } = wardPatientGrouping?.inpatientAdmissionResponse ?? {};
|
|
32
|
+
const beds = isLoading ? [] : wardPatientGrouping?.bedLayouts ?? [];
|
|
34
33
|
const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
|
|
35
34
|
const getBedRepresentation = useCallback((bedLayout: BedLayout) => {
|
|
36
35
|
const bedNumber = bedLayout.bedNumber;
|
|
@@ -92,8 +91,15 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
|
|
|
92
91
|
if (response.ok) {
|
|
93
92
|
if (bedSelected) {
|
|
94
93
|
return assignPatientToBed(values.bedId, patient.uuid, response.data.uuid);
|
|
94
|
+
} else {
|
|
95
|
+
const bed = wardPatientGrouping.bedLayouts.find((bedLayout) =>
|
|
96
|
+
bedLayout.patients.some((p) => p.uuid == patient.uuid),
|
|
97
|
+
);
|
|
98
|
+
if (bed) {
|
|
99
|
+
return removePatientFromBed(bed.bedId, patient.uuid);
|
|
100
|
+
}
|
|
101
|
+
return response;
|
|
95
102
|
}
|
|
96
|
-
return response;
|
|
97
103
|
}
|
|
98
104
|
},
|
|
99
105
|
(err: Error) => {
|
|
@@ -134,9 +140,6 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
|
|
|
134
140
|
}),
|
|
135
141
|
});
|
|
136
142
|
}
|
|
137
|
-
mutateAdmissionLocation();
|
|
138
|
-
mutateInpatientRequest();
|
|
139
|
-
closeWorkspaceWithSavedChanges();
|
|
140
143
|
}
|
|
141
144
|
},
|
|
142
145
|
() => {
|
|
@@ -148,16 +151,28 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
|
|
|
148
151
|
'Patient admitted successfully but fail to assign bed to patient',
|
|
149
152
|
),
|
|
150
153
|
});
|
|
151
|
-
mutateAdmissionLocation();
|
|
152
|
-
mutateInpatientRequest();
|
|
153
|
-
closeWorkspaceWithSavedChanges();
|
|
154
154
|
},
|
|
155
155
|
)
|
|
156
156
|
.finally(() => {
|
|
157
157
|
setIsSubmitting(false);
|
|
158
|
+
mutateAdmissionLocation();
|
|
159
|
+
mutateInpatientRequest();
|
|
160
|
+
mutateInpatientAdmission();
|
|
161
|
+
closeWorkspaceWithSavedChanges();
|
|
158
162
|
});
|
|
159
163
|
},
|
|
160
|
-
[
|
|
164
|
+
[
|
|
165
|
+
beds,
|
|
166
|
+
patient,
|
|
167
|
+
emrConfiguration,
|
|
168
|
+
location,
|
|
169
|
+
closeWorkspaceWithSavedChanges,
|
|
170
|
+
dispositionType,
|
|
171
|
+
currentProvider,
|
|
172
|
+
mutateAdmissionLocation,
|
|
173
|
+
mutateInpatientRequest,
|
|
174
|
+
mutateInpatientAdmission,
|
|
175
|
+
],
|
|
161
176
|
);
|
|
162
177
|
|
|
163
178
|
const onError = useCallback((values) => {
|
|
@@ -165,6 +180,7 @@ const AdmitPatientFormWorkspace: React.FC<AdmitPatientFormWorkspaceProps> = ({
|
|
|
165
180
|
setIsSubmitting(false);
|
|
166
181
|
}, []);
|
|
167
182
|
|
|
183
|
+
if (!wardPatientGrouping) return <></>;
|
|
168
184
|
return (
|
|
169
185
|
<Form control={control} className={styles.form} onSubmit={handleSubmit(onSubmit, onError)}>
|
|
170
186
|
<div className={styles.formContent}>
|
|
@@ -8,10 +8,10 @@ import WardPatientName from '../../ward-patient-card/row-elements/ward-patient-n
|
|
|
8
8
|
|
|
9
9
|
const WardPatientWorkspaceBanner = (wardPatient: WardPatient) => {
|
|
10
10
|
const { headerRowElements } = useCurrentWardCardConfig();
|
|
11
|
-
const { patient, bed
|
|
11
|
+
const { patient, bed } = wardPatient;
|
|
12
12
|
|
|
13
|
-
if (!
|
|
14
|
-
console.warn('Patient details
|
|
13
|
+
if (!patient) {
|
|
14
|
+
console.warn('Patient details were not received by the ward workspace');
|
|
15
15
|
return null;
|
|
16
16
|
}
|
|
17
17
|
|