@kenyaemr/esm-care-panel-app 5.4.2-pre.2203 → 5.4.2-pre.2223
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 -192
- package/dist/144.js +2 -0
- package/dist/144.js.map +1 -0
- package/dist/174.js +1 -0
- package/dist/174.js.map +1 -0
- package/dist/216.js +2 -0
- package/dist/216.js.map +1 -0
- package/dist/300.js +1 -0
- package/dist/41.js +2 -0
- package/dist/41.js.map +1 -0
- package/dist/470.js +1 -0
- package/dist/470.js.map +1 -0
- package/dist/474.js +2 -0
- package/dist/474.js.map +1 -0
- package/dist/495.js +1 -0
- package/dist/{372.js.map → 495.js.map} +1 -1
- package/dist/537.js +1 -0
- package/dist/537.js.map +1 -0
- package/dist/58.js +1 -0
- package/dist/58.js.map +1 -0
- package/dist/613.js +2 -0
- package/dist/613.js.map +1 -0
- package/dist/684.js +1 -0
- package/dist/684.js.map +1 -0
- package/dist/837.js +2 -0
- package/dist/837.js.LICENSE.txt +29 -0
- package/dist/837.js.map +1 -0
- package/dist/838.js +1 -0
- package/dist/838.js.map +1 -0
- package/dist/858.js +2 -0
- package/dist/858.js.map +1 -0
- package/dist/876.js +1 -0
- package/dist/876.js.map +1 -0
- package/dist/89.js +1 -0
- package/dist/89.js.map +1 -0
- package/dist/907.js +1 -0
- package/dist/907.js.map +1 -0
- package/dist/913.js +2 -0
- package/dist/{591.js.map → 913.js.map} +1 -1
- package/dist/kenyaemr-esm-care-panel-app.js +1 -1
- package/dist/kenyaemr-esm-care-panel-app.js.buildmanifest.json +289 -97
- package/dist/kenyaemr-esm-care-panel-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/package.json +4 -1
- package/src/care-panel/care-panel.component.test.tsx +3 -3
- package/src/care-panel/care-panel.component.tsx +24 -33
- package/src/care-panel/care-panel.scss +3 -2
- package/src/care-panel-dashboard/care-panel-dashboard.component.tsx +4 -11
- package/src/care-panel-dashboard/care-panel-dashboard.scss +1 -1
- package/src/care-programs/care-programs.component.tsx +3 -9
- package/src/care-programs/care-programs.test.tsx +5 -5
- package/src/declarations.d.ts +0 -13
- package/src/index.ts +9 -1
- package/src/patient-discharge/discharge-workspace-siderail.component.tsx +23 -0
- package/src/patient-discharge/patient-discharge.resource.tsx +123 -0
- package/src/patient-discharge/patient-discharge.workspace.test.tsx +160 -0
- package/src/patient-discharge/patient-discharge.workspace.tsx +93 -0
- package/src/patient-summary/patient-summary.component.tsx +1 -1
- package/src/patient-summary/patient-summary.scss +1 -1
- package/src/program-enrollment/program-enrollment.component.tsx +15 -14
- package/src/program-enrollment/program-enrollment.scss +3 -3
- package/src/program-summary/program-summary.component.tsx +1 -1
- package/src/program-summary/program-summary.scss +1 -1
- package/src/regimen/regimen-history.component.tsx +1 -1
- package/src/regimen/regimen-history.scss +4 -4
- package/src/regimen-editor/regimen-form.component.tsx +6 -6
- package/src/regimen-editor/standard-regimen.scss +2 -2
- package/src/routes.json +15 -0
- package/src/types/index.ts +32 -0
- package/translations/en.json +1 -0
- package/dist/130.js +0 -2
- package/dist/130.js.LICENSE.txt +0 -7
- package/dist/130.js.map +0 -1
- package/dist/2.js +0 -1
- package/dist/2.js.map +0 -1
- package/dist/292.js +0 -2
- package/dist/292.js.map +0 -1
- package/dist/316.js +0 -2
- package/dist/316.js.map +0 -1
- package/dist/346.js +0 -1
- package/dist/346.js.map +0 -1
- package/dist/372.js +0 -1
- package/dist/373.js +0 -2
- package/dist/373.js.map +0 -1
- package/dist/446.js +0 -2
- package/dist/446.js.map +0 -1
- package/dist/574.js +0 -1
- package/dist/589.js +0 -1
- package/dist/589.js.map +0 -1
- package/dist/591.js +0 -2
- package/dist/698.js +0 -2
- package/dist/698.js.map +0 -1
- package/dist/784.js +0 -2
- package/dist/784.js.map +0 -1
- /package/dist/{316.js.LICENSE.txt → 144.js.LICENSE.txt} +0 -0
- /package/dist/{446.js.LICENSE.txt → 216.js.LICENSE.txt} +0 -0
- /package/dist/{292.js.LICENSE.txt → 41.js.LICENSE.txt} +0 -0
- /package/dist/{784.js.LICENSE.txt → 474.js.LICENSE.txt} +0 -0
- /package/dist/{373.js.LICENSE.txt → 613.js.LICENSE.txt} +0 -0
- /package/dist/{698.js.LICENSE.txt → 858.js.LICENSE.txt} +0 -0
- /package/dist/{591.js.LICENSE.txt → 913.js.LICENSE.txt} +0 -0
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[],"extensions":[{"name":"care-panel-patient-summary","component":"carePanelPatientSummary","slot":"patient-chart-care-panel-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"care-panel-summary-dashboard-link","component":"carePanelSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":3,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-care-panel-dashboard-slot","layoutMode":"anchored","path":"Care panel"}},{"name":"delete-regimen-confirmation-dialog","component":"deleteRegimenConfirmationDialog"},{"name":"hiv-patient-visit-summary-dashboard-link","component":"hivPatientSummaryDashboardLink","slot":"hiv-care-and-treatment-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-hiv-patient-summary-slot","path":"HIV Patient Summary","layoutMode":"anchored"}},{"name":"hiv-patient-visit-summary","slot":"patient-chart-hiv-patient-summary-slot","component":"hivPatientSummary","order":3,"online":true,"offline":false},{"name":"dispensing-patient-vitals","component":"dispensingPaentientVitals","slot":"dispensing-condition-and-diagnoses","order":1,"online":true,"offline":true}],"workspaces":[{"name":"patient-regimen-workspace","title":"Patient Regimen","component":"regimenFormWorkspace","type":"form","canMaximize":true,"canHide":true,"width":"wider"}],"version":"5.4.2-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[],"extensions":[{"name":"care-panel-patient-summary","component":"carePanelPatientSummary","slot":"patient-chart-care-panel-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"care-panel-summary-dashboard-link","component":"carePanelSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":3,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-care-panel-dashboard-slot","layoutMode":"anchored","path":"Care panel"}},{"name":"delete-regimen-confirmation-dialog","component":"deleteRegimenConfirmationDialog"},{"name":"hiv-patient-visit-summary-dashboard-link","component":"hivPatientSummaryDashboardLink","slot":"hiv-care-and-treatment-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-hiv-patient-summary-slot","path":"HIV Patient Summary","layoutMode":"anchored"}},{"name":"hiv-patient-visit-summary","slot":"patient-chart-hiv-patient-summary-slot","component":"hivPatientSummary","order":3,"online":true,"offline":false},{"name":"dispensing-patient-vitals","component":"dispensingPaentientVitals","slot":"dispensing-condition-and-diagnoses","order":1,"online":true,"offline":true},{"name":"patient-discharge-side-rail-icon","component":"patientDischargeSideRailIcon","slot":"action-menu-ward-patient-items-slot"}],"workspaces":[{"name":"patient-regimen-workspace","title":"Patient Regimen","component":"regimenFormWorkspace","type":"form","canMaximize":true,"canHide":true,"width":"wider"},{"name":"patient-care-discharge-workspace","title":"Patient Discharge","component":"patientDischargeWorkspace","type":"workspace","canMaximize":true,"canHide":true,"width":"extra-wide","groups":["ward-patient"]}],"version":"5.4.2-pre.2223"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenyaemr/esm-care-panel-app",
|
|
3
|
-
"version": "5.4.2-pre.
|
|
3
|
+
"version": "5.4.2-pre.2223",
|
|
4
4
|
"description": "Patient care panels microfrontend for the OpenMRS SPA",
|
|
5
5
|
"browser": "dist/kenyaemr-esm-care-panel-app.js",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -48,5 +48,8 @@
|
|
|
48
48
|
"react-router-dom": "6.x",
|
|
49
49
|
"swr": "2.x"
|
|
50
50
|
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"webpack": "^5.99.9"
|
|
53
|
+
},
|
|
51
54
|
"stableVersion": "5.4.1"
|
|
52
55
|
}
|
|
@@ -29,7 +29,7 @@ describe('CarePanel Component', () => {
|
|
|
29
29
|
isLoading: false,
|
|
30
30
|
isError: false,
|
|
31
31
|
});
|
|
32
|
-
render(<CarePanel patientUuid={mockPatientUuid} formEntrySub={jest.fn()}
|
|
32
|
+
render(<CarePanel patientUuid={mockPatientUuid} formEntrySub={jest.fn()} />);
|
|
33
33
|
expect(screen.getByText('Care Panel')).toBeInTheDocument();
|
|
34
34
|
});
|
|
35
35
|
|
|
@@ -40,7 +40,7 @@ describe('CarePanel Component', () => {
|
|
|
40
40
|
isError: false,
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
render(<CarePanel patientUuid={mockPatientUuid} formEntrySub={jest.fn()}
|
|
43
|
+
render(<CarePanel patientUuid={mockPatientUuid} formEntrySub={jest.fn()} />);
|
|
44
44
|
|
|
45
45
|
expect(screen.getByTestId('mocked-structured-list-skeleton')).toBeInTheDocument();
|
|
46
46
|
expect(screen.queryByTestId('mocked-program-summary')).not.toBeInTheDocument();
|
|
@@ -54,7 +54,7 @@ describe('CarePanel Component', () => {
|
|
|
54
54
|
isError: true,
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
render(<CarePanel patientUuid={mockPatientUuid} formEntrySub={jest.fn()}
|
|
57
|
+
render(<CarePanel patientUuid={mockPatientUuid} formEntrySub={jest.fn()} />);
|
|
58
58
|
|
|
59
59
|
expect(screen.getByText('Error loading program enrollments')).toBeInTheDocument();
|
|
60
60
|
expect(screen.queryByTestId('mocked-structured-list-skeleton')).not.toBeInTheDocument();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useMemo, useState } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import { StructuredListSkeleton, ContentSwitcher, Switch } from '@carbon/react';
|
|
3
|
+
import { StructuredListSkeleton, ContentSwitcher, Switch, type SwitchEventHandlersParams } from '@carbon/react';
|
|
4
4
|
import styles from './care-panel.scss';
|
|
5
5
|
import { useEnrollmentHistory } from '../hooks/useEnrollmentHistory';
|
|
6
6
|
import ProgramSummary from '../program-summary/program-summary.component';
|
|
@@ -11,23 +11,16 @@ import first from 'lodash/first';
|
|
|
11
11
|
import sortBy from 'lodash/sortBy';
|
|
12
12
|
import { ErrorState } from '@openmrs/esm-framework';
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
type CarePanelProps = {
|
|
15
15
|
patientUuid: string;
|
|
16
16
|
formEntrySub: any;
|
|
17
|
-
launchPatientWorkspace: Function;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type SwitcherItem = {
|
|
21
|
-
index: number;
|
|
22
|
-
name?: string;
|
|
23
|
-
text?: string;
|
|
24
17
|
};
|
|
25
18
|
|
|
26
|
-
const CarePanel: React.FC<CarePanelProps> = ({ patientUuid, formEntrySub
|
|
19
|
+
const CarePanel: React.FC<CarePanelProps> = ({ patientUuid, formEntrySub }) => {
|
|
27
20
|
const { t } = useTranslation();
|
|
28
21
|
const { isLoading, error, enrollments, isValidating } = useEnrollmentHistory(patientUuid);
|
|
29
22
|
const switcherHeaders = sortBy(Object.keys(enrollments || {}));
|
|
30
|
-
const [switchItem, setSwitcherItem] = useState<
|
|
23
|
+
const [switchItem, setSwitcherItem] = useState<SwitchEventHandlersParams>({ index: 0 });
|
|
31
24
|
const patientEnrollments = useMemo(
|
|
32
25
|
() => (isLoading ? [] : enrollments[switchItem?.name || first(switcherHeaders)]),
|
|
33
26
|
[enrollments, isLoading, switchItem?.name, switcherHeaders],
|
|
@@ -36,7 +29,7 @@ const CarePanel: React.FC<CarePanelProps> = ({ patientUuid, formEntrySub, launch
|
|
|
36
29
|
if (isLoading) {
|
|
37
30
|
return (
|
|
38
31
|
<div className={styles.widgetCard}>
|
|
39
|
-
<StructuredListSkeleton
|
|
32
|
+
<StructuredListSkeleton />
|
|
40
33
|
</div>
|
|
41
34
|
);
|
|
42
35
|
}
|
|
@@ -54,29 +47,27 @@ const CarePanel: React.FC<CarePanelProps> = ({ patientUuid, formEntrySub, launch
|
|
|
54
47
|
}
|
|
55
48
|
|
|
56
49
|
return (
|
|
57
|
-
|
|
58
|
-
<
|
|
59
|
-
<
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
</ContentSwitcher>
|
|
66
|
-
</div>
|
|
67
|
-
</CardHeader>
|
|
68
|
-
<div style={{ width: '100%', minHeight: '20rem' }}>
|
|
69
|
-
<ProgramSummary patientUuid={patientUuid} programName={switcherHeaders[switchItem?.index]} />
|
|
70
|
-
<RegimenHistory patientUuid={patientUuid} category={switcherHeaders[switchItem?.index]} />
|
|
71
|
-
<ProgramEnrollment
|
|
72
|
-
patientUuid={patientUuid}
|
|
73
|
-
programName={switcherHeaders[switchItem?.index]}
|
|
74
|
-
enrollments={patientEnrollments}
|
|
75
|
-
formEntrySub={formEntrySub}
|
|
76
|
-
/>
|
|
50
|
+
<div className={styles.widgetCard}>
|
|
51
|
+
<CardHeader title={t('carePanel', 'Care Panel')}>
|
|
52
|
+
<div className={styles.contextSwitcherContainer}>
|
|
53
|
+
<ContentSwitcher size="md" selectedIndex={switchItem?.index} onChange={(params) => setSwitcherItem(params)}>
|
|
54
|
+
{switcherHeaders?.map((enrollment) => (
|
|
55
|
+
<Switch key={enrollment} name={enrollment} text={enrollment} />
|
|
56
|
+
))}
|
|
57
|
+
</ContentSwitcher>
|
|
77
58
|
</div>
|
|
59
|
+
</CardHeader>
|
|
60
|
+
<div style={{ width: '100%', minHeight: '20rem' }}>
|
|
61
|
+
<ProgramSummary patientUuid={patientUuid} programName={switcherHeaders[switchItem?.index]} />
|
|
62
|
+
<RegimenHistory patientUuid={patientUuid} category={switcherHeaders[switchItem?.index]} />
|
|
63
|
+
<ProgramEnrollment
|
|
64
|
+
patientUuid={patientUuid}
|
|
65
|
+
programName={switcherHeaders[switchItem?.index]}
|
|
66
|
+
enrollments={patientEnrollments}
|
|
67
|
+
formEntrySub={formEntrySub}
|
|
68
|
+
/>
|
|
78
69
|
</div>
|
|
79
|
-
|
|
70
|
+
</div>
|
|
80
71
|
);
|
|
81
72
|
};
|
|
82
73
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
@use '@carbon/styles/scss/type';
|
|
2
2
|
@use '@carbon/styles/scss/spacing';
|
|
3
|
-
@
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
@use '~@openmrs/esm-styleguide/src/vars' as *;
|
|
4
5
|
|
|
5
6
|
.bodyShort01 {
|
|
6
7
|
@include type.type-style('body-compact-01');
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
.widgetCard {
|
|
10
|
-
background-color:
|
|
11
|
+
background-color: colors.$white;
|
|
11
12
|
border: 1px solid $ui-03;
|
|
12
13
|
position: relative;
|
|
13
14
|
}
|
|
@@ -7,14 +7,11 @@ import CarePrograms from '../care-programs/care-programs.component';
|
|
|
7
7
|
|
|
8
8
|
import CarePanelMachineLearning from '../machine-learning/machine-learning.component';
|
|
9
9
|
import styles from './care-panel-dashboard.scss';
|
|
10
|
+
import { DefaultWorkspaceProps, launchWorkspace } from '@openmrs/esm-framework/src';
|
|
10
11
|
|
|
11
|
-
type CarePanelDashboardProps = { patientUuid: string; formEntrySub: any
|
|
12
|
+
type CarePanelDashboardProps = { patientUuid: string; formEntrySub: any } & DefaultWorkspaceProps;
|
|
12
13
|
|
|
13
|
-
const CarePanelDashboard: React.FC<CarePanelDashboardProps> = ({
|
|
14
|
-
formEntrySub,
|
|
15
|
-
patientUuid,
|
|
16
|
-
launchPatientWorkspace,
|
|
17
|
-
}) => {
|
|
14
|
+
const CarePanelDashboard: React.FC<CarePanelDashboardProps> = ({ formEntrySub, patientUuid }) => {
|
|
18
15
|
const { t } = useTranslation();
|
|
19
16
|
return (
|
|
20
17
|
<Layer className={styles.container}>
|
|
@@ -32,11 +29,7 @@ const CarePanelDashboard: React.FC<CarePanelDashboardProps> = ({
|
|
|
32
29
|
</TabList>
|
|
33
30
|
<TabPanels>
|
|
34
31
|
<TabPanel>
|
|
35
|
-
<CarePanel
|
|
36
|
-
patientUuid={patientUuid}
|
|
37
|
-
formEntrySub={formEntrySub}
|
|
38
|
-
launchPatientWorkspace={launchPatientWorkspace}
|
|
39
|
-
/>
|
|
32
|
+
<CarePanel patientUuid={patientUuid} formEntrySub={formEntrySub} />
|
|
40
33
|
</TabPanel>
|
|
41
34
|
<TabPanel>
|
|
42
35
|
<CarePrograms patientUuid={patientUuid} />
|
|
@@ -14,16 +14,10 @@ import {
|
|
|
14
14
|
DataTableSkeleton,
|
|
15
15
|
} from '@carbon/react';
|
|
16
16
|
import { Close, DocumentAdd } from '@carbon/react/icons';
|
|
17
|
-
import {
|
|
18
|
-
CardHeader,
|
|
19
|
-
EmptyState,
|
|
20
|
-
launchStartVisitPrompt,
|
|
21
|
-
ErrorState,
|
|
22
|
-
launchPatientWorkspace,
|
|
23
|
-
} from '@openmrs/esm-patient-common-lib';
|
|
17
|
+
import { CardHeader, EmptyState, launchStartVisitPrompt, ErrorState } from '@openmrs/esm-patient-common-lib';
|
|
24
18
|
import { useTranslation } from 'react-i18next';
|
|
25
19
|
import { PatientCarePrograms, useCarePrograms } from '../hooks/useCarePrograms';
|
|
26
|
-
import { formatDate, restBaseUrl, useLayoutType, useVisit } from '@openmrs/esm-framework';
|
|
20
|
+
import { formatDate, launchWorkspace, restBaseUrl, useLayoutType, useVisit } from '@openmrs/esm-framework';
|
|
27
21
|
import capitalize from 'lodash/capitalize';
|
|
28
22
|
import { mutate } from 'swr';
|
|
29
23
|
|
|
@@ -64,7 +58,7 @@ const CarePrograms: React.FC<CareProgramsProps> = ({ patientUuid }) => {
|
|
|
64
58
|
: `${careProgram.display} Enrollment form`;
|
|
65
59
|
|
|
66
60
|
currentVisit
|
|
67
|
-
?
|
|
61
|
+
? launchWorkspace('patient-form-entry-workspace', {
|
|
68
62
|
workspaceTitle: workspaceTitle,
|
|
69
63
|
mutateForm: () => {
|
|
70
64
|
handleMutations();
|
|
@@ -2,8 +2,8 @@ import React from 'react';
|
|
|
2
2
|
import { screen, render } from '@testing-library/react';
|
|
3
3
|
import CarePrograms from './care-programs.component';
|
|
4
4
|
import * as careProgramsHook from '../hooks/useCarePrograms';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { launchStartVisitPrompt } from '@openmrs/esm-patient-common-lib';
|
|
6
|
+
import { launchWorkspace, useVisit } from '@openmrs/esm-framework';
|
|
7
7
|
import { PatientCarePrograms } from '../hooks/useCarePrograms';
|
|
8
8
|
import userEvent from '@testing-library/user-event';
|
|
9
9
|
|
|
@@ -17,6 +17,7 @@ const testProps = {
|
|
|
17
17
|
error: null,
|
|
18
18
|
carePrograms: [],
|
|
19
19
|
mutate: jest.fn(),
|
|
20
|
+
mutateEligiblePrograms: jest.fn(),
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
const mockAPIResponse: Array<PatientCarePrograms> = [
|
|
@@ -45,7 +46,6 @@ const mockAPIResponse: Array<PatientCarePrograms> = [
|
|
|
45
46
|
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
46
47
|
...jest.requireActual('@openmrs/esm-patient-common-lib'),
|
|
47
48
|
launchStartVisitPrompt: jest.fn(),
|
|
48
|
-
launchPatientWorkspace: jest.fn(),
|
|
49
49
|
}));
|
|
50
50
|
|
|
51
51
|
describe('CarePrograms', () => {
|
|
@@ -95,7 +95,7 @@ describe('CarePrograms', () => {
|
|
|
95
95
|
const enrollButton = screen.getByRole('button', { name: /Enroll/ });
|
|
96
96
|
const discontinueButton = screen.getByRole('button', { name: /Discontinue/ });
|
|
97
97
|
await user.click(enrollButton);
|
|
98
|
-
expect(
|
|
98
|
+
expect(launchWorkspace).toHaveBeenCalledWith('patient-form-entry-workspace', {
|
|
99
99
|
formInfo: {
|
|
100
100
|
additionalProps: { enrollmenrDetails: expect.anything() },
|
|
101
101
|
encounterUuid: '',
|
|
@@ -106,7 +106,7 @@ describe('CarePrograms', () => {
|
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
await user.click(discontinueButton);
|
|
109
|
-
expect(
|
|
109
|
+
expect(launchWorkspace).toHaveBeenCalledWith('patient-form-entry-workspace', {
|
|
110
110
|
formInfo: {
|
|
111
111
|
additionalProps: {
|
|
112
112
|
enrollmenrDetails: {
|
package/src/declarations.d.ts
CHANGED
|
@@ -1,15 +1,2 @@
|
|
|
1
1
|
declare module '*.css';
|
|
2
2
|
declare module '*.scss';
|
|
3
|
-
declare module '@carbon/react';
|
|
4
|
-
declare module 'react-aria' {
|
|
5
|
-
export const I18nProvider: (...args: any) => JSX.Element;
|
|
6
|
-
export { DateValue } from '@react-types/datepicker';
|
|
7
|
-
export const mergeProps: any;
|
|
8
|
-
export const useLocale: any;
|
|
9
|
-
export const useDateField: any;
|
|
10
|
-
export const useDatePicker: any;
|
|
11
|
-
export const useDateSegment: any;
|
|
12
|
-
export const useFocusRing: any;
|
|
13
|
-
export const useHover: any;
|
|
14
|
-
export const useObjectRef: any;
|
|
15
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineConfigSchema, getSyncLifecycle, registerBreadcrumbs } from '@openmrs/esm-framework';
|
|
1
|
+
import { defineConfigSchema, getAsyncLifecycle, getSyncLifecycle, registerBreadcrumbs } from '@openmrs/esm-framework';
|
|
2
2
|
import { configSchema } from './config-schema';
|
|
3
3
|
import { dashboardMeta, hivPatientSummaryDashboardMeta } from './dashboard.meta';
|
|
4
4
|
import { createDashboardLink } from '@openmrs/esm-patient-common-lib';
|
|
@@ -9,6 +9,8 @@ import regimenFormComponent from './regimen-editor/regimen-form.component';
|
|
|
9
9
|
import CarePanelDashboard from './care-panel-dashboard/care-panel-dashboard.component';
|
|
10
10
|
import PatientSummary from './patient-summary/patient-summary.component';
|
|
11
11
|
import DispensingPatientVitals from './dispensing-patient-details/patient-vitals.component';
|
|
12
|
+
import PatientDischargeSideRailIcon from './patient-discharge/discharge-workspace-siderail.component';
|
|
13
|
+
import PatientDischargeWorkspace from './patient-discharge/patient-discharge.workspace';
|
|
12
14
|
|
|
13
15
|
const moduleName = '@kenyaemr/esm-care-panel-app';
|
|
14
16
|
|
|
@@ -48,3 +50,9 @@ export const hivPatientSummary = getSyncLifecycle(PatientSummary, options);
|
|
|
48
50
|
export const regimenFormWorkspace = getSyncLifecycle(regimenFormComponent, options);
|
|
49
51
|
|
|
50
52
|
export const dispensingPaentientVitals = getSyncLifecycle(DispensingPatientVitals, options);
|
|
53
|
+
|
|
54
|
+
export const patientDischargeSideRailIcon = getSyncLifecycle(PatientDischargeSideRailIcon, options);
|
|
55
|
+
export const patientDischargeWorkspace = getAsyncLifecycle(
|
|
56
|
+
() => import('./patient-discharge/patient-discharge.workspace'),
|
|
57
|
+
options,
|
|
58
|
+
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ActionMenuButton, launchWorkspace } from '@openmrs/esm-framework';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { Exit } from '@carbon/react/icons';
|
|
5
|
+
|
|
6
|
+
const DischargeIcon = (props) => <Exit {...props} />;
|
|
7
|
+
|
|
8
|
+
export default function PatientDischargeSideRailIcon() {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
const handler = () => {
|
|
11
|
+
launchWorkspace('patient-care-discharge-workspace');
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<ActionMenuButton
|
|
16
|
+
getIcon={DischargeIcon}
|
|
17
|
+
label={t('discharge', 'Discharge')}
|
|
18
|
+
iconDescription={t('discharge', 'Discharge')}
|
|
19
|
+
handler={handler}
|
|
20
|
+
type="ward-patient-discharge"
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Encounter,
|
|
3
|
+
openmrsFetch,
|
|
4
|
+
OpenmrsResource,
|
|
5
|
+
restBaseUrl,
|
|
6
|
+
showSnackbar,
|
|
7
|
+
useAppContext,
|
|
8
|
+
useSession,
|
|
9
|
+
Visit,
|
|
10
|
+
} from '@openmrs/esm-framework';
|
|
11
|
+
import { WardPatient } from '../types';
|
|
12
|
+
import { useTranslation } from 'react-i18next';
|
|
13
|
+
|
|
14
|
+
export function removePatientFromBed(bedId: number, patientUuid: string) {
|
|
15
|
+
return openmrsFetch(`${restBaseUrl}/beds/${bedId}?patientUuid=${patientUuid}`, {
|
|
16
|
+
method: 'DELETE',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const createDischargeEncounterPayload = (
|
|
21
|
+
patientUuid: string,
|
|
22
|
+
encounterType: OpenmrsResource,
|
|
23
|
+
location: OpenmrsResource,
|
|
24
|
+
currentProvider: OpenmrsResource,
|
|
25
|
+
visitUuid: string,
|
|
26
|
+
clinicianEncounterRole: OpenmrsResource,
|
|
27
|
+
) => {
|
|
28
|
+
const encounterPayload = {
|
|
29
|
+
patient: patientUuid,
|
|
30
|
+
encounterType,
|
|
31
|
+
location: location?.uuid,
|
|
32
|
+
encounterProviders: [
|
|
33
|
+
{
|
|
34
|
+
provider: currentProvider?.uuid,
|
|
35
|
+
encounterRole: clinicianEncounterRole?.uuid,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
obs: [],
|
|
39
|
+
visit: visitUuid,
|
|
40
|
+
};
|
|
41
|
+
return encounterPayload;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const createDischargeEncounter = (encounterPayload: any) => {
|
|
45
|
+
return openmrsFetch(`${restBaseUrl}/encounter`, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
body: encounterPayload,
|
|
48
|
+
headers: { 'Content-Type': 'application/json' },
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Custom hook for handling patient discharge operations
|
|
54
|
+
*
|
|
55
|
+
* This hook provides functionality to discharge a patient by:
|
|
56
|
+
* 1. Creating a discharge encounter
|
|
57
|
+
* 2. Removing the patient from their bed (if applicable)
|
|
58
|
+
* 3. Updating the ward patient group details
|
|
59
|
+
*
|
|
60
|
+
* @returns {Object} An object containing the handleDischarge function
|
|
61
|
+
* @property {Function} handleDischarge - Function to handle the patient discharge process
|
|
62
|
+
*/
|
|
63
|
+
export const usePatientDischarge = () => {
|
|
64
|
+
const { t } = useTranslation();
|
|
65
|
+
const { wardPatientGroupDetails } =
|
|
66
|
+
useAppContext<{ wardPatientGroupDetails: { mutate: () => void } }>('ward-view-context') ?? {};
|
|
67
|
+
const session = useSession();
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Handles the patient discharge process
|
|
71
|
+
*
|
|
72
|
+
* @param {Encounter} encounter - The current encounter
|
|
73
|
+
* @param {WardPatient} wardPatient - The ward patient information
|
|
74
|
+
* @param {Record<string, unknown>} emrConfiguration - EMR configuration containing encounter types and roles
|
|
75
|
+
* @param {Visit} visit - The current visit
|
|
76
|
+
* @returns {Promise<void>} A promise that resolves when the discharge process is complete
|
|
77
|
+
*/
|
|
78
|
+
const handleDischarge = async (
|
|
79
|
+
encounter: Encounter,
|
|
80
|
+
wardPatient: WardPatient,
|
|
81
|
+
emrConfiguration: Record<string, unknown>,
|
|
82
|
+
visit: Visit,
|
|
83
|
+
) => {
|
|
84
|
+
try {
|
|
85
|
+
const encounterPayload = createDischargeEncounterPayload(
|
|
86
|
+
wardPatient.patient.uuid,
|
|
87
|
+
emrConfiguration.exitFromInpatientEncounterType as OpenmrsResource,
|
|
88
|
+
session?.sessionLocation as OpenmrsResource,
|
|
89
|
+
session?.currentProvider as OpenmrsResource,
|
|
90
|
+
visit.uuid,
|
|
91
|
+
emrConfiguration.clinicianEncounterRole as OpenmrsResource,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const dischargeResponse = await createDischargeEncounter(encounterPayload);
|
|
95
|
+
|
|
96
|
+
if (!dischargeResponse?.ok) {
|
|
97
|
+
throw new Error('Failed to create discharge encounter');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (wardPatient?.bed?.id) {
|
|
101
|
+
const bedRemovalResponse = await removePatientFromBed(wardPatient.bed.id, wardPatient?.patient?.uuid);
|
|
102
|
+
if (!bedRemovalResponse?.ok) {
|
|
103
|
+
throw new Error('Failed to remove patient from bed');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
showSnackbar({
|
|
108
|
+
title: t('patientWasDischarged', 'Patient was discharged'),
|
|
109
|
+
kind: 'success',
|
|
110
|
+
});
|
|
111
|
+
} catch (err) {
|
|
112
|
+
showSnackbar({
|
|
113
|
+
title: t('errorDischargingPatient', 'Error discharging patient'),
|
|
114
|
+
subtitle: err instanceof Error ? err.message : 'Unknown error occurred',
|
|
115
|
+
kind: 'error',
|
|
116
|
+
});
|
|
117
|
+
} finally {
|
|
118
|
+
wardPatientGroupDetails?.mutate();
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return { handleDischarge };
|
|
123
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { PatientDischargeWorkspace } from './patient-discharge.workspace';
|
|
4
|
+
import { useVisitOrOfflineVisit } from '@openmrs/esm-patient-common-lib';
|
|
5
|
+
import { usePatient, useEmrConfiguration } from '@openmrs/esm-framework';
|
|
6
|
+
import { usePatientDischarge } from './patient-discharge.resource';
|
|
7
|
+
|
|
8
|
+
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
9
|
+
useVisitOrOfflineVisit: jest.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
13
|
+
usePatient: jest.fn(),
|
|
14
|
+
useEmrConfiguration: jest.fn(),
|
|
15
|
+
ExtensionSlot: jest.fn().mockImplementation(({ name }) => <div data-testid={name} />),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock('./patient-discharge.resource', () => ({
|
|
19
|
+
usePatientDischarge: jest.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock('react-i18next', () => ({
|
|
23
|
+
useTranslation: () => ({
|
|
24
|
+
t: (key: string) => key,
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
describe('PatientDischargeWorkspace', () => {
|
|
29
|
+
const mockProps = {
|
|
30
|
+
patientUuid: 'test-patient-uuid',
|
|
31
|
+
wardPatient: {
|
|
32
|
+
patient: {
|
|
33
|
+
uuid: 'test-patient-uuid',
|
|
34
|
+
display: 'Test Patient',
|
|
35
|
+
identifiers: [],
|
|
36
|
+
person: {
|
|
37
|
+
uuid: 'test-person-uuid',
|
|
38
|
+
display: 'Test Person',
|
|
39
|
+
gender: 'M',
|
|
40
|
+
birthdate: '1990-01-01',
|
|
41
|
+
dead: false,
|
|
42
|
+
age: 33,
|
|
43
|
+
deathDate: null,
|
|
44
|
+
causeOfDeath: null,
|
|
45
|
+
preferredAddress: null,
|
|
46
|
+
attributes: [],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
visit: {
|
|
50
|
+
uuid: 'test-visit-uuid',
|
|
51
|
+
display: 'Test Visit',
|
|
52
|
+
encounters: [],
|
|
53
|
+
visitType: { uuid: 'test-visit-type-uuid', display: 'Test Visit Type' },
|
|
54
|
+
startDatetime: '2024-01-01',
|
|
55
|
+
stopDatetime: null,
|
|
56
|
+
},
|
|
57
|
+
bed: {
|
|
58
|
+
uuid: 'test-bed-uuid',
|
|
59
|
+
id: 1,
|
|
60
|
+
bedNumber: 'BED-001',
|
|
61
|
+
bedType: { uuid: 'test-bed-type-uuid', display: 'Standard' },
|
|
62
|
+
status: { uuid: 'test-status-uuid', display: 'OCCUPIED' },
|
|
63
|
+
location: { uuid: 'test-location-uuid', display: 'Test Location' },
|
|
64
|
+
row: 1,
|
|
65
|
+
column: 1,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
closeWorkspace: jest.fn(),
|
|
69
|
+
closeWorkspaceWithSavedChanges: jest.fn(),
|
|
70
|
+
promptBeforeClosing: jest.fn(),
|
|
71
|
+
setTitle: jest.fn(),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const mockPatient = {
|
|
75
|
+
uuid: 'test-patient-uuid',
|
|
76
|
+
display: 'Test Patient',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const mockVisit = {
|
|
80
|
+
uuid: 'test-visit-uuid',
|
|
81
|
+
visitType: { uuid: 'test-visit-type-uuid' },
|
|
82
|
+
encounters: [{ uuid: 'test-encounter-uuid' }],
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const mockEmrConfiguration = {
|
|
86
|
+
visitTypes: [],
|
|
87
|
+
visitTypeConfig: {},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
jest.clearAllMocks();
|
|
92
|
+
(useVisitOrOfflineVisit as jest.Mock).mockReturnValue({
|
|
93
|
+
isLoading: false,
|
|
94
|
+
currentVisit: mockVisit,
|
|
95
|
+
error: null,
|
|
96
|
+
});
|
|
97
|
+
(usePatient as jest.Mock).mockReturnValue({
|
|
98
|
+
patient: mockPatient,
|
|
99
|
+
isLoading: false,
|
|
100
|
+
error: null,
|
|
101
|
+
});
|
|
102
|
+
(useEmrConfiguration as jest.Mock).mockReturnValue({
|
|
103
|
+
emrConfiguration: mockEmrConfiguration,
|
|
104
|
+
isLoadingEmrConfiguration: false,
|
|
105
|
+
errorFetchingEmrConfiguration: null,
|
|
106
|
+
});
|
|
107
|
+
(usePatientDischarge as jest.Mock).mockReturnValue({
|
|
108
|
+
handleDischarge: jest.fn(),
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('renders loading state when data is being fetched', () => {
|
|
113
|
+
(useVisitOrOfflineVisit as jest.Mock).mockReturnValue({
|
|
114
|
+
isLoading: true,
|
|
115
|
+
currentVisit: null,
|
|
116
|
+
error: null,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
render(<PatientDischargeWorkspace {...mockProps} />);
|
|
120
|
+
const loadingSpinner = screen.getAllByText(/loading/i);
|
|
121
|
+
expect(loadingSpinner).toHaveLength(2);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('renders error state when there is an error', () => {
|
|
125
|
+
(useVisitOrOfflineVisit as jest.Mock).mockReturnValue({
|
|
126
|
+
isLoading: false,
|
|
127
|
+
currentVisit: null,
|
|
128
|
+
error: new Error('Test error'),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
render(<PatientDischargeWorkspace {...mockProps} />);
|
|
132
|
+
expect(screen.getByText('error')).toBeInTheDocument();
|
|
133
|
+
expect(screen.getByText(/errorLoadingPatientWorkspace/)).toBeInTheDocument();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('renders the component with all required slots when data is loaded', () => {
|
|
137
|
+
render(<PatientDischargeWorkspace {...mockProps} />);
|
|
138
|
+
|
|
139
|
+
expect(screen.getByTestId('visit-context-header-slot')).toBeInTheDocument();
|
|
140
|
+
expect(screen.getByTestId('form-widget-slot')).toBeInTheDocument();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('passes correct state to form-widget-slot', () => {
|
|
144
|
+
render(<PatientDischargeWorkspace {...mockProps} />);
|
|
145
|
+
|
|
146
|
+
const formWidgetSlot = screen.getByTestId('form-widget-slot');
|
|
147
|
+
expect(formWidgetSlot).toBeInTheDocument();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('handles missing visit data gracefully', () => {
|
|
151
|
+
(useVisitOrOfflineVisit as jest.Mock).mockReturnValue({
|
|
152
|
+
isLoading: false,
|
|
153
|
+
currentVisit: null,
|
|
154
|
+
error: null,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
render(<PatientDischargeWorkspace {...mockProps} />);
|
|
158
|
+
expect(screen.getByTestId('visit-context-header-slot')).toBeInTheDocument();
|
|
159
|
+
});
|
|
160
|
+
});
|