@openmrs/esm-patient-forms-app 11.3.1-patch.9310 → 11.3.1-patch.9508
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 -22
- package/dist/1123.js +2 -0
- package/dist/1123.js.LICENSE.txt +9 -0
- package/dist/1123.js.map +1 -0
- package/dist/2773.js +1 -0
- package/dist/2773.js.map +1 -0
- package/dist/4055.js +1 -1
- package/dist/4078.js +1 -1
- package/dist/4078.js.map +1 -1
- package/dist/439.js +1 -0
- package/dist/5670.js +1 -1
- package/dist/5696.js +1 -0
- package/dist/5696.js.map +1 -0
- package/dist/6199.js +2 -0
- package/dist/6199.js.map +1 -0
- package/dist/6589.js +1 -0
- package/dist/7003.js +1 -1
- package/dist/7003.js.map +1 -1
- package/dist/7007.js +1 -1
- package/dist/7007.js.map +1 -1
- package/dist/707.js +1 -1
- package/dist/707.js.map +1 -1
- package/dist/7906.js +2 -0
- package/dist/{5792.js.LICENSE.txt → 7906.js.LICENSE.txt} +0 -10
- package/dist/7906.js.map +1 -0
- package/dist/8371.js +1 -0
- package/dist/8803.js +1 -0
- package/dist/8803.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-forms-app.js +1 -1
- package/dist/openmrs-esm-patient-forms-app.js.buildmanifest.json +205 -109
- package/dist/openmrs-esm-patient-forms-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +2 -2
- package/src/clinical-form-action-button.component.tsx +40 -13
- package/src/clinical-form-action-button.test.tsx +22 -23
- package/src/config-schema.ts +1 -1
- package/src/forms/form-entry.test.tsx +28 -14
- package/src/forms/form-entry.workspace.tsx +99 -19
- package/src/forms/forms-dashboard.component.tsx +58 -13
- package/src/forms/forms-dashboard.test.tsx +15 -3
- package/src/forms/forms-dashboard.workspace.tsx +10 -33
- package/src/forms/forms-list.component.tsx +41 -20
- package/src/forms/forms-list.test.tsx +2 -2
- package/src/forms/forms-table.component.tsx +1 -1
- package/src/hooks/use-forms.ts +4 -3
- package/src/htmlformentry/html-form-entry.workspace.tsx +54 -0
- package/src/index.ts +9 -1
- package/src/offline-forms/use-offline-form-encounters.ts +2 -2
- package/src/offline.ts +7 -5
- package/src/routes.json +44 -13
- package/translations/cs.json +22 -0
- package/translations/fr.json +2 -2
- package/translations/sq.json +22 -0
- package/translations/zh_TW.json +22 -0
- package/dist/2621.js +0 -2
- package/dist/2621.js.map +0 -1
- package/dist/4341.js +0 -1
- package/dist/4341.js.map +0 -1
- package/dist/5792.js +0 -2
- package/dist/5792.js.map +0 -1
- package/dist/9649.js +0 -1
- package/dist/9649.js.map +0 -1
- package/src/forms/exported-form-entry.workspace.tsx +0 -32
- package/src/forms/exported-forms-dashboard.workspace.tsx +0 -45
- package/src/forms/form-entry.component.tsx +0 -138
- package/src/forms/form-entry.resources.ts +0 -37
- /package/dist/{2621.js.LICENSE.txt → 6199.js.LICENSE.txt} +0 -0
|
@@ -1,27 +1,54 @@
|
|
|
1
1
|
import React, { type ComponentProps } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { ActionMenuButton, DocumentIcon, launchWorkspace, useWorkspaces } from '@openmrs/esm-framework';
|
|
4
|
+
import {
|
|
5
|
+
clinicalFormsWorkspace,
|
|
6
|
+
formEntryWorkspace,
|
|
7
|
+
htmlFormEntryWorkspace,
|
|
8
|
+
useLaunchWorkspaceRequiringVisit,
|
|
9
|
+
} from '@openmrs/esm-patient-common-lib';
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* This button uses the patient chart store and MUST only be used
|
|
8
13
|
* within the patient chart
|
|
9
14
|
*/
|
|
10
|
-
const ClinicalFormActionButton: React.FC<
|
|
11
|
-
groupProps: { patientUuid },
|
|
12
|
-
}) => {
|
|
15
|
+
const ClinicalFormActionButton: React.FC<{ patientUuid: string }> = ({ patientUuid }) => {
|
|
13
16
|
const { t } = useTranslation();
|
|
14
|
-
const
|
|
17
|
+
const { workspaces } = useWorkspaces();
|
|
18
|
+
const launchFormsWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, clinicalFormsWorkspace);
|
|
19
|
+
|
|
20
|
+
const formEntryWorkspaces = workspaces.filter((w) => w.name === formEntryWorkspace);
|
|
21
|
+
const recentlyOpenedForm = formEntryWorkspaces[0];
|
|
22
|
+
|
|
23
|
+
const htmlFormEntryWorkspaces = workspaces.filter((w) => w.name === htmlFormEntryWorkspace);
|
|
24
|
+
const recentlyOpenedHtmlForm = htmlFormEntryWorkspaces[0];
|
|
25
|
+
|
|
26
|
+
const isFormOpen = formEntryWorkspaces?.length >= 1;
|
|
27
|
+
const isHtmlFormOpen = htmlFormEntryWorkspaces?.length >= 1;
|
|
28
|
+
|
|
29
|
+
const launchPatientWorkspaceCb = () => {
|
|
30
|
+
if (isFormOpen) {
|
|
31
|
+
launchWorkspace(formEntryWorkspace, {
|
|
32
|
+
workspaceTitle: recentlyOpenedForm?.additionalProps?.['workspaceTitle'],
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// we aren't currently supporting keeping HTML Form workspaces open, but just in case
|
|
36
|
+
else if (isHtmlFormOpen) {
|
|
37
|
+
launchWorkspace(htmlFormEntryWorkspace, {
|
|
38
|
+
workspaceTitle: recentlyOpenedHtmlForm?.additionalProps?.['workspaceTitle'],
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
launchFormsWorkspace();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
15
44
|
|
|
16
45
|
return (
|
|
17
|
-
<
|
|
18
|
-
|
|
46
|
+
<ActionMenuButton
|
|
47
|
+
getIcon={(props: ComponentProps<typeof DocumentIcon>) => <DocumentIcon {...props} />}
|
|
19
48
|
label={t('clinicalForms', 'Clinical forms')}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}}
|
|
24
|
-
onBeforeWorkspaceLaunch={startVisitIfNeeded}
|
|
49
|
+
iconDescription={t('clinicalForms', 'Clinical forms')}
|
|
50
|
+
handler={launchPatientWorkspaceCb}
|
|
51
|
+
type={'clinical-form'}
|
|
25
52
|
/>
|
|
26
53
|
);
|
|
27
54
|
};
|
|
@@ -1,47 +1,46 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import {
|
|
3
|
+
import { ActionMenuButton, useLayoutType, useWorkspaces } from '@openmrs/esm-framework';
|
|
4
4
|
import ClinicalFormActionButton from './clinical-form-action-button.component';
|
|
5
|
-
import { mockPatient } from 'tools';
|
|
6
5
|
|
|
7
|
-
const mockActionMenuButton = jest.mocked(ActionMenuButton2);
|
|
8
6
|
const mockUseLayoutType = jest.mocked(useLayoutType);
|
|
7
|
+
const mockUseWorkspaces = useWorkspaces as jest.Mock;
|
|
8
|
+
const mockActionMenuButton = jest.mocked(ActionMenuButton);
|
|
9
9
|
|
|
10
|
-
mockActionMenuButton.mockImplementation(({ label, tagContent }) => (
|
|
11
|
-
<button>
|
|
10
|
+
mockActionMenuButton.mockImplementation(({ handler, label, tagContent }) => (
|
|
11
|
+
<button onClick={handler}>
|
|
12
12
|
{tagContent} {label}
|
|
13
13
|
</button>
|
|
14
14
|
));
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
mockUseWorkspaces.mockImplementation(() => ({
|
|
17
|
+
active: true,
|
|
18
|
+
windowState: 'normal',
|
|
19
|
+
workspaces: [
|
|
20
|
+
{
|
|
21
|
+
canHide: false,
|
|
22
|
+
name: 'clinical-forms-workspace',
|
|
23
|
+
title: 'Clinical forms',
|
|
24
|
+
preferredWindowSize: 'normal',
|
|
25
|
+
type: 'form',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
workspaceWindowState: 'normal',
|
|
29
|
+
prompt: null,
|
|
30
|
+
}));
|
|
24
31
|
|
|
25
32
|
describe('<ClinicalFormActionButton>', () => {
|
|
26
33
|
test('should display clinical form action button on tablet view', () => {
|
|
27
34
|
mockUseLayoutType.mockReturnValue('tablet');
|
|
28
35
|
|
|
29
|
-
render(
|
|
30
|
-
<ClinicalFormActionButton
|
|
31
|
-
groupProps={{ patientUuid: mockPatient.id, patient: mockPatient, visitContext: null, mutateVisitContext: null }}
|
|
32
|
-
/>,
|
|
33
|
-
);
|
|
36
|
+
render(<ClinicalFormActionButton patientUuid={'some-uuid'} />);
|
|
34
37
|
expect(screen.getByRole('button', { name: /Clinical forms/i })).toBeInTheDocument();
|
|
35
38
|
});
|
|
36
39
|
|
|
37
40
|
test('should display clinical form action button on desktop view', () => {
|
|
38
41
|
mockUseLayoutType.mockReturnValue('small-desktop');
|
|
39
42
|
|
|
40
|
-
render(
|
|
41
|
-
<ClinicalFormActionButton
|
|
42
|
-
groupProps={{ patientUuid: mockPatient.id, patient: mockPatient, visitContext: null, mutateVisitContext: null }}
|
|
43
|
-
/>,
|
|
44
|
-
);
|
|
43
|
+
render(<ClinicalFormActionButton patientUuid={'some-uuid'} />);
|
|
45
44
|
const clinicalActionButton = screen.getByRole('button', { name: /Form/i });
|
|
46
45
|
expect(clinicalActionButton).toBeInTheDocument();
|
|
47
46
|
});
|
package/src/config-schema.ts
CHANGED
|
@@ -123,7 +123,7 @@ export interface FormsSection {
|
|
|
123
123
|
forms: Array<string>;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
export interface
|
|
126
|
+
export interface ConfigObject {
|
|
127
127
|
htmlFormEntryForms: Array<HtmlFormEntryForm>;
|
|
128
128
|
formSections: Array<FormsSection>;
|
|
129
129
|
customFormsUrl: string;
|
|
@@ -1,25 +1,39 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import { BehaviorSubject } from 'rxjs';
|
|
4
|
-
import { ExtensionSlot, useConnectivity } from '@openmrs/esm-framework';
|
|
4
|
+
import { ExtensionSlot, useConnectivity, useFeatureFlag } from '@openmrs/esm-framework';
|
|
5
5
|
import { mockPatient } from 'tools';
|
|
6
|
-
import FormEntry
|
|
6
|
+
import FormEntry from './form-entry.workspace';
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
resources: [],
|
|
8
|
+
const mockCurrentVisit = {
|
|
9
|
+
uuid: '17f512b4-d264-4113-a6fe-160cb38cb46e',
|
|
10
|
+
encounters: [],
|
|
11
|
+
patient: { uuid: '8673ee4f-e2ab-4077-ba55-4980f408773e' },
|
|
12
|
+
visitType: {
|
|
13
|
+
uuid: '7b0f5697-27e3-40c4-8bae-f4049abfb4ed',
|
|
14
|
+
display: 'Facility Visit',
|
|
16
15
|
},
|
|
17
|
-
|
|
16
|
+
attributes: [],
|
|
17
|
+
startDatetime: '2021-03-16T08:16:00.000+0000',
|
|
18
|
+
stopDatetime: null,
|
|
19
|
+
location: {
|
|
20
|
+
uuid: '6351fcf4-e311-4a19-90f9-35667d99a8af',
|
|
21
|
+
name: 'Registration Desk',
|
|
22
|
+
display: 'Registration Desk',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const testProps = {
|
|
27
|
+
closeWorkspace: jest.fn(),
|
|
28
|
+
closeWorkspaceWithSavedChanges: jest.fn(),
|
|
29
|
+
promptBeforeClosing: jest.fn(),
|
|
18
30
|
patientUuid: mockPatient.id,
|
|
19
31
|
patient: mockPatient,
|
|
20
|
-
|
|
32
|
+
formInfo: { formUuid: 'some-form-uuid' },
|
|
33
|
+
mutateForm: jest.fn(),
|
|
34
|
+
setTitle: jest.fn(),
|
|
35
|
+
visitContext: mockCurrentVisit,
|
|
21
36
|
mutateVisitContext: null,
|
|
22
|
-
closeWorkspace: jest.fn(),
|
|
23
37
|
};
|
|
24
38
|
|
|
25
39
|
const mockFormEntrySub = jest.fn();
|
|
@@ -35,7 +49,7 @@ describe('FormEntry', () => {
|
|
|
35
49
|
);
|
|
36
50
|
mockExtensionSlot.mockImplementation((ext) => ext.name as any);
|
|
37
51
|
|
|
38
|
-
render(<FormEntry {...
|
|
52
|
+
render(<FormEntry {...testProps} />);
|
|
39
53
|
|
|
40
54
|
await screen.findByText(/form-widget-slot/);
|
|
41
55
|
expect(screen.getByText(/form-widget-slot/)).toBeInTheDocument();
|
|
@@ -1,28 +1,108 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { useSWRConfig } from 'swr';
|
|
3
|
+
import { ExtensionSlot, useConnectivity } from '@openmrs/esm-framework';
|
|
4
|
+
import {
|
|
5
|
+
clinicalFormsWorkspace,
|
|
6
|
+
invalidateVisitAndEncounterData,
|
|
7
|
+
type DefaultPatientWorkspaceProps,
|
|
8
|
+
type FormEntryProps,
|
|
9
|
+
} from '@openmrs/esm-patient-common-lib';
|
|
4
10
|
|
|
5
|
-
interface
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
interface FormEntryComponentProps extends DefaultPatientWorkspaceProps {
|
|
12
|
+
mutateForm: () => void;
|
|
13
|
+
formInfo: FormEntryProps;
|
|
14
|
+
clinicalFormsWorkspaceName?: string;
|
|
8
15
|
}
|
|
9
16
|
|
|
10
|
-
const
|
|
17
|
+
const FormEntry: React.FC<FormEntryComponentProps> = ({
|
|
18
|
+
patientUuid,
|
|
19
|
+
patient,
|
|
20
|
+
clinicalFormsWorkspaceName = clinicalFormsWorkspace,
|
|
11
21
|
closeWorkspace,
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
closeWorkspaceWithSavedChanges,
|
|
23
|
+
promptBeforeClosing,
|
|
24
|
+
mutateForm,
|
|
25
|
+
formInfo,
|
|
26
|
+
visitContext,
|
|
27
|
+
mutateVisitContext,
|
|
14
28
|
}) => {
|
|
29
|
+
const { encounterUuid, formUuid, visitStartDatetime, visitStopDatetime, visitTypeUuid, visitUuid, additionalProps } =
|
|
30
|
+
formInfo || {};
|
|
31
|
+
const [showForm, setShowForm] = useState(true);
|
|
32
|
+
const isOnline = useConnectivity();
|
|
33
|
+
const { mutate: globalMutate } = useSWRConfig();
|
|
34
|
+
|
|
35
|
+
const state = useMemo(
|
|
36
|
+
() => ({
|
|
37
|
+
view: 'form',
|
|
38
|
+
formUuid: formUuid ?? null,
|
|
39
|
+
visitUuid: visitUuid ?? visitContext?.uuid ?? null,
|
|
40
|
+
visitTypeUuid: visitTypeUuid ?? visitContext?.visitType?.uuid ?? null,
|
|
41
|
+
visitStartDatetime: visitStartDatetime ?? visitContext?.startDatetime ?? null,
|
|
42
|
+
visitStopDatetime: visitStopDatetime ?? visitContext?.stopDatetime ?? null,
|
|
43
|
+
isOffline: !isOnline,
|
|
44
|
+
patientUuid: patientUuid ?? null,
|
|
45
|
+
patient,
|
|
46
|
+
encounterUuid: encounterUuid ?? null,
|
|
47
|
+
closeWorkspace: () => {
|
|
48
|
+
typeof mutateForm === 'function' && mutateForm();
|
|
49
|
+
closeWorkspace();
|
|
50
|
+
},
|
|
51
|
+
closeWorkspaceWithSavedChanges: () => {
|
|
52
|
+
typeof mutateForm === 'function' && mutateForm();
|
|
53
|
+
// Update current visit data for critical components
|
|
54
|
+
mutateVisitContext?.();
|
|
55
|
+
|
|
56
|
+
// Also invalidate visit history and encounter tables since form submission may create/update encounters
|
|
57
|
+
invalidateVisitAndEncounterData(globalMutate, patientUuid);
|
|
58
|
+
|
|
59
|
+
closeWorkspaceWithSavedChanges();
|
|
60
|
+
},
|
|
61
|
+
promptBeforeClosing,
|
|
62
|
+
additionalProps,
|
|
63
|
+
clinicalFormsWorkspaceName,
|
|
64
|
+
}),
|
|
65
|
+
[
|
|
66
|
+
additionalProps,
|
|
67
|
+
clinicalFormsWorkspaceName,
|
|
68
|
+
closeWorkspace,
|
|
69
|
+
closeWorkspaceWithSavedChanges,
|
|
70
|
+
visitContext?.startDatetime,
|
|
71
|
+
visitContext?.stopDatetime,
|
|
72
|
+
visitContext?.uuid,
|
|
73
|
+
visitContext?.visitType?.uuid,
|
|
74
|
+
encounterUuid,
|
|
75
|
+
formUuid,
|
|
76
|
+
globalMutate,
|
|
77
|
+
isOnline,
|
|
78
|
+
mutateVisitContext,
|
|
79
|
+
mutateForm,
|
|
80
|
+
patient,
|
|
81
|
+
patientUuid,
|
|
82
|
+
promptBeforeClosing,
|
|
83
|
+
visitStartDatetime,
|
|
84
|
+
visitStopDatetime,
|
|
85
|
+
visitTypeUuid,
|
|
86
|
+
visitUuid,
|
|
87
|
+
],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// FIXME: This logic triggers a reload of the form when the formUuid changes. It's a workaround for the fact that the form doesn't reload when the formUuid changes.
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (state.formUuid) {
|
|
93
|
+
setShowForm(false);
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
setShowForm(true);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}, [state]);
|
|
99
|
+
|
|
15
100
|
return (
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
patientUuid={patientUuid}
|
|
21
|
-
visitContext={visitContext}
|
|
22
|
-
mutateVisitContext={mutateVisitContext}
|
|
23
|
-
closeWorkspace={closeWorkspace}
|
|
24
|
-
/>
|
|
101
|
+
<div>
|
|
102
|
+
<ExtensionSlot name="visit-context-header-slot" state={{ patientUuid }} />
|
|
103
|
+
{showForm && formInfo && patientUuid && patient && <ExtensionSlot name="form-widget-slot" state={state} />}
|
|
104
|
+
</div>
|
|
25
105
|
);
|
|
26
106
|
};
|
|
27
107
|
|
|
28
|
-
export default
|
|
108
|
+
export default FormEntry;
|
|
@@ -1,28 +1,73 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
1
|
+
import React, { useCallback, useMemo } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { Tile } from '@carbon/react';
|
|
4
|
-
import { ResponsiveWrapper, useConfig, useConnectivity
|
|
5
|
-
import {
|
|
6
|
-
|
|
4
|
+
import { ResponsiveWrapper, useConfig, useConnectivity } from '@openmrs/esm-framework';
|
|
5
|
+
import {
|
|
6
|
+
type DefaultPatientWorkspaceProps,
|
|
7
|
+
EmptyDataIllustration,
|
|
8
|
+
type Form,
|
|
9
|
+
launchFormEntryOrHtmlForms,
|
|
10
|
+
mapFormsToHtmlFormEntryForms,
|
|
11
|
+
} from '@openmrs/esm-patient-common-lib';
|
|
12
|
+
import type { ConfigObject } from '../config-schema';
|
|
7
13
|
import { useForms } from '../hooks/use-forms';
|
|
8
14
|
import FormsList from './forms-list.component';
|
|
9
15
|
import styles from './forms-dashboard.scss';
|
|
10
16
|
|
|
11
|
-
interface
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
interface FormsDashboardProps extends DefaultPatientWorkspaceProps {
|
|
18
|
+
clinicalFormsWorkspaceName?: string;
|
|
19
|
+
formEntryWorkspaceName?: string;
|
|
20
|
+
htmlFormEntryWorkspaceName?: string;
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
const FormsDashboard: React.FC<
|
|
23
|
+
const FormsDashboard: React.FC<FormsDashboardProps> = ({
|
|
24
|
+
patientUuid,
|
|
25
|
+
clinicalFormsWorkspaceName,
|
|
26
|
+
formEntryWorkspaceName,
|
|
27
|
+
htmlFormEntryWorkspaceName,
|
|
28
|
+
visitContext,
|
|
29
|
+
}) => {
|
|
18
30
|
const { t } = useTranslation();
|
|
19
|
-
const config = useConfig<
|
|
31
|
+
const config = useConfig<ConfigObject>();
|
|
20
32
|
const isOnline = useConnectivity();
|
|
21
33
|
const {
|
|
22
34
|
data: forms,
|
|
35
|
+
allForms,
|
|
23
36
|
error,
|
|
24
37
|
mutateForms,
|
|
25
|
-
} = useForms(
|
|
38
|
+
} = useForms(patientUuid, visitContext?.uuid, undefined, undefined, !isOnline, config.orderBy);
|
|
39
|
+
|
|
40
|
+
const htmlFormEntryForms = useMemo(() => {
|
|
41
|
+
return mapFormsToHtmlFormEntryForms(allForms, config.htmlFormEntryForms);
|
|
42
|
+
}, [config.htmlFormEntryForms, allForms]);
|
|
43
|
+
|
|
44
|
+
const handleFormOpen = useCallback(
|
|
45
|
+
(form: Form, encounterUuid: string) => {
|
|
46
|
+
launchFormEntryOrHtmlForms(
|
|
47
|
+
htmlFormEntryForms,
|
|
48
|
+
patientUuid,
|
|
49
|
+
form,
|
|
50
|
+
visitContext?.uuid,
|
|
51
|
+
encounterUuid,
|
|
52
|
+
visitContext?.visitType.uuid,
|
|
53
|
+
visitContext?.startDatetime,
|
|
54
|
+
visitContext?.stopDatetime,
|
|
55
|
+
mutateForms,
|
|
56
|
+
clinicalFormsWorkspaceName,
|
|
57
|
+
formEntryWorkspaceName,
|
|
58
|
+
htmlFormEntryWorkspaceName,
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
[
|
|
62
|
+
visitContext,
|
|
63
|
+
htmlFormEntryForms,
|
|
64
|
+
mutateForms,
|
|
65
|
+
patientUuid,
|
|
66
|
+
clinicalFormsWorkspaceName,
|
|
67
|
+
formEntryWorkspaceName,
|
|
68
|
+
htmlFormEntryWorkspaceName,
|
|
69
|
+
],
|
|
70
|
+
);
|
|
26
71
|
|
|
27
72
|
const sections = useMemo(() => {
|
|
28
73
|
return config.formSections?.map((formSection) => ({
|
|
@@ -47,14 +92,14 @@ const FormsDashboard: React.FC<FormsDashbaordProps> = ({ handleFormOpen, patient
|
|
|
47
92
|
return (
|
|
48
93
|
<div className={styles.container}>
|
|
49
94
|
{sections.length === 0 ? (
|
|
50
|
-
<FormsList
|
|
95
|
+
<FormsList completedForms={forms} error={error} handleFormOpen={handleFormOpen} />
|
|
51
96
|
) : (
|
|
52
97
|
sections.map((section) => {
|
|
53
98
|
return (
|
|
54
99
|
<FormsList
|
|
55
100
|
key={`form-section-${section.name}`}
|
|
56
101
|
sectionName={section.name}
|
|
57
|
-
|
|
102
|
+
completedForms={section.availableForms}
|
|
58
103
|
error={error}
|
|
59
104
|
handleFormOpen={handleFormOpen}
|
|
60
105
|
/>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
|
|
4
|
-
import { configSchema, type
|
|
4
|
+
import { configSchema, type ConfigObject } from '../config-schema';
|
|
5
|
+
import { mockCurrentVisit } from '__mocks__';
|
|
5
6
|
import FormsDashboard from './forms-dashboard.component';
|
|
6
7
|
import { mockPatient } from 'tools';
|
|
7
8
|
|
|
8
|
-
const mockUseConfig = jest.mocked(useConfig<
|
|
9
|
+
const mockUseConfig = jest.mocked(useConfig<ConfigObject>);
|
|
9
10
|
|
|
10
11
|
jest.mock('../hooks/use-forms', () => ({
|
|
11
12
|
useForms: jest.fn().mockReturnValueOnce({
|
|
@@ -20,7 +21,18 @@ mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), ht
|
|
|
20
21
|
|
|
21
22
|
describe('FormsDashboard', () => {
|
|
22
23
|
test('renders an empty state if there are no forms persisted on the server', async () => {
|
|
23
|
-
render(
|
|
24
|
+
render(
|
|
25
|
+
<FormsDashboard
|
|
26
|
+
promptBeforeClosing={jest.fn()}
|
|
27
|
+
closeWorkspace={jest.fn()}
|
|
28
|
+
closeWorkspaceWithSavedChanges={jest.fn()}
|
|
29
|
+
patientUuid={mockPatient.id}
|
|
30
|
+
patient={mockPatient}
|
|
31
|
+
setTitle={jest.fn()}
|
|
32
|
+
visitContext={mockCurrentVisit}
|
|
33
|
+
mutateVisitContext={null}
|
|
34
|
+
/>,
|
|
35
|
+
);
|
|
24
36
|
|
|
25
37
|
expect(screen.getByText(/there are no forms to display/i)).toBeInTheDocument();
|
|
26
38
|
});
|
|
@@ -1,39 +1,16 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { useTranslation } from 'react-i18next';
|
|
1
|
+
import React from 'react';
|
|
3
2
|
import FormsDashboard from './forms-dashboard.component';
|
|
4
3
|
import styles from './forms-dashboard-workspace.scss';
|
|
5
|
-
import { type
|
|
6
|
-
import { ExtensionSlot
|
|
4
|
+
import { type DefaultPatientWorkspaceProps } from '@openmrs/esm-patient-common-lib';
|
|
5
|
+
import { ExtensionSlot } from '@openmrs/esm-framework';
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* either the form-entry workspace or the html-form-entry workspace.
|
|
11
|
-
*
|
|
12
|
-
* This workspace should only be used within the patient chart
|
|
13
|
-
*/
|
|
14
|
-
const FormsDashboardWorkspace: React.FC<PatientWorkspace2DefinitionProps<{}, {}>> = ({
|
|
15
|
-
launchChildWorkspace,
|
|
16
|
-
groupProps: { patient, patientUuid, visitContext },
|
|
17
|
-
}) => {
|
|
18
|
-
const { t } = useTranslation();
|
|
19
|
-
const handleFormOpen = useCallback(
|
|
20
|
-
(form: Form, encounterUuid: string) => {
|
|
21
|
-
launchChildWorkspace('patient-form-entry-workspace', {
|
|
22
|
-
form,
|
|
23
|
-
encounterUuid,
|
|
24
|
-
});
|
|
25
|
-
},
|
|
26
|
-
[launchChildWorkspace],
|
|
27
|
-
);
|
|
7
|
+
export default function FormsWorkspace(props: DefaultPatientWorkspaceProps) {
|
|
8
|
+
const { patientUuid } = props;
|
|
28
9
|
|
|
29
10
|
return (
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
</div>
|
|
35
|
-
</Workspace2>
|
|
11
|
+
<div className={styles.container}>
|
|
12
|
+
<ExtensionSlot name="visit-context-header-slot" state={{ patientUuid }} />
|
|
13
|
+
<FormsDashboard {...props} />
|
|
14
|
+
</div>
|
|
36
15
|
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export default FormsDashboardWorkspace;
|
|
16
|
+
}
|
|
@@ -9,7 +9,7 @@ import FormsTable from './forms-table.component';
|
|
|
9
9
|
import styles from './forms-list.scss';
|
|
10
10
|
|
|
11
11
|
export type FormsListProps = {
|
|
12
|
-
|
|
12
|
+
completedForms?: Array<CompletedFormInfo>;
|
|
13
13
|
error?: any;
|
|
14
14
|
sectionName?: string;
|
|
15
15
|
handleFormOpen: (form: Form, encounterUuid: string) => void;
|
|
@@ -20,23 +20,32 @@ export type FormsListProps = {
|
|
|
20
20
|
* t('forms', 'Forms')
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
const FormsList: React.FC<FormsListProps> = ({
|
|
23
|
+
const FormsList: React.FC<FormsListProps> = ({ completedForms, error, sectionName = 'forms', handleFormOpen }) => {
|
|
24
24
|
const { t } = useTranslation();
|
|
25
25
|
const [searchTerm, setSearchTerm] = useState('');
|
|
26
26
|
const isTablet = useLayoutType() === 'tablet';
|
|
27
|
+
const [locale, setLocale] = useState(window.i18next.language ?? navigator.language);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (window.i18next?.on) {
|
|
31
|
+
const languageChanged = (lng: string) => setLocale(lng);
|
|
32
|
+
window.i18next.on('languageChanged', languageChanged);
|
|
33
|
+
return () => window.i18next.off('languageChanged', languageChanged);
|
|
34
|
+
}
|
|
35
|
+
}, []);
|
|
27
36
|
|
|
28
37
|
const handleSearch = useMemo(() => debounce((searchTerm) => setSearchTerm(searchTerm), 300), []);
|
|
29
38
|
|
|
30
39
|
const filteredForms = useMemo(() => {
|
|
31
40
|
if (!searchTerm) {
|
|
32
|
-
return
|
|
41
|
+
return completedForms;
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
return fuzzy
|
|
36
|
-
.filter(searchTerm,
|
|
45
|
+
.filter(searchTerm, completedForms, { extract: (formInfo) => formInfo.form.display ?? formInfo.form.name })
|
|
37
46
|
.sort((r1, r2) => r1.score - r2.score)
|
|
38
47
|
.map((result) => result.original);
|
|
39
|
-
}, [
|
|
48
|
+
}, [completedForms, searchTerm]);
|
|
40
49
|
|
|
41
50
|
const tableHeaders = useMemo(() => {
|
|
42
51
|
return [
|
|
@@ -66,30 +75,42 @@ const FormsList: React.FC<FormsListProps> = ({ forms, error, sectionName, handle
|
|
|
66
75
|
[filteredForms],
|
|
67
76
|
);
|
|
68
77
|
|
|
69
|
-
if (!
|
|
78
|
+
if (!completedForms && !error) {
|
|
70
79
|
return <DataTableSkeleton role="progressbar" />;
|
|
71
80
|
}
|
|
72
81
|
|
|
73
|
-
if (
|
|
82
|
+
if (completedForms?.length === 0) {
|
|
74
83
|
return <></>;
|
|
75
84
|
}
|
|
76
85
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
if (sectionName === 'forms') {
|
|
87
|
+
return (
|
|
88
|
+
<ResponsiveWrapper>
|
|
89
|
+
<FormsTable
|
|
90
|
+
tableHeaders={tableHeaders}
|
|
91
|
+
tableRows={tableRows}
|
|
92
|
+
isTablet={isTablet}
|
|
93
|
+
handleSearch={handleSearch}
|
|
94
|
+
handleFormOpen={handleFormOpen}
|
|
95
|
+
/>
|
|
96
|
+
</ResponsiveWrapper>
|
|
97
|
+
);
|
|
98
|
+
} else {
|
|
99
|
+
return (
|
|
100
|
+
<ResponsiveWrapper>
|
|
80
101
|
<div className={isTablet ? styles.tabletHeading : styles.desktopHeading}>
|
|
81
102
|
<h4>{t(sectionName)}</h4>
|
|
82
103
|
</div>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
<FormsTable
|
|
105
|
+
tableHeaders={tableHeaders}
|
|
106
|
+
tableRows={tableRows}
|
|
107
|
+
isTablet={isTablet}
|
|
108
|
+
handleSearch={handleSearch}
|
|
109
|
+
handleFormOpen={handleFormOpen}
|
|
110
|
+
/>
|
|
111
|
+
</ResponsiveWrapper>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
93
114
|
};
|
|
94
115
|
|
|
95
116
|
export default FormsList;
|
|
@@ -7,7 +7,7 @@ import FormsList, { type FormsListProps } from './forms-list.component';
|
|
|
7
7
|
jest.mock('lodash-es/debounce', () => jest.fn((fn) => fn));
|
|
8
8
|
|
|
9
9
|
const defaultProps: FormsListProps & { reset: () => void } = {
|
|
10
|
-
|
|
10
|
+
completedForms: [],
|
|
11
11
|
handleFormOpen: jest.fn(),
|
|
12
12
|
reset() {
|
|
13
13
|
this.completedForms = [];
|
|
@@ -25,7 +25,7 @@ beforeEach(async () => {
|
|
|
25
25
|
it('renders a list of forms fetched from the server', async () => {
|
|
26
26
|
const user = userEvent.setup();
|
|
27
27
|
|
|
28
|
-
renderFormsList({
|
|
28
|
+
renderFormsList({ completedForms: forms.map((form) => ({ form, associatedEncounters: [] })) });
|
|
29
29
|
|
|
30
30
|
const searchbox = screen.getByRole('searchbox');
|
|
31
31
|
expect(searchbox).toBeInTheDocument();
|
|
@@ -71,7 +71,7 @@ const FormsTable = ({ tableHeaders, tableRows, isTablet, handleSearch, handleFor
|
|
|
71
71
|
<Link
|
|
72
72
|
style={{ cursor: 'pointer' }}
|
|
73
73
|
onClick={() => {
|
|
74
|
-
handleFormOpen(tableRows[i].form,
|
|
74
|
+
handleFormOpen(tableRows[i].form, '');
|
|
75
75
|
}}
|
|
76
76
|
role="presentation"
|
|
77
77
|
className={styles.formName}
|