@openmrs/esm-fast-data-entry-app 1.4.2-pre.624 → 1.4.2-pre.628
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/dist/4300.js +1 -1
- package/dist/6466.js +1 -1
- package/dist/6466.js.map +1 -1
- package/dist/792.js +1 -1
- package/dist/792.js.map +1 -1
- package/dist/8091.js +1 -1
- package/dist/8091.js.map +1 -1
- package/dist/9814.js.map +1 -1
- package/dist/9823.js +1 -1
- package/dist/9823.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-fast-data-entry-app.js +1 -1
- package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +20 -20
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/add-group-modal/AddGroupModal.test.tsx +198 -0
- package/src/add-group-modal/AddGroupModal.tsx +22 -4
- package/src/config-schema.ts +6 -0
- package/src/context/GroupFormWorkflowContext.tsx +14 -0
- package/src/form-entry-workflow/patient-search-header/PatientSearchHeader.test.tsx +121 -0
- package/src/form-entry-workflow/patient-search-header/PatientSearchHeader.tsx +28 -5
- package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +33 -8
- package/src/hooks/location-tag.resource.ts +1 -14
- package/src/hooks/useStartVisit.ts +3 -4
- package/src/types.ts +14 -0
- package/translations/en.json +3 -0
|
@@ -12,6 +12,20 @@ export interface GroupType {
|
|
|
12
12
|
id: string;
|
|
13
13
|
name: string;
|
|
14
14
|
members: Array<Type.Object>;
|
|
15
|
+
location?: {
|
|
16
|
+
uuid: string;
|
|
17
|
+
display?: string;
|
|
18
|
+
};
|
|
19
|
+
cohortMembers?: Array<{
|
|
20
|
+
patient: {
|
|
21
|
+
uuid?: string;
|
|
22
|
+
person?: {
|
|
23
|
+
names?: Array<{
|
|
24
|
+
display?: string;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}>;
|
|
15
29
|
}
|
|
16
30
|
|
|
17
31
|
export interface MetaType {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent, cleanup, waitFor } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import PatientSearchHeader from './PatientSearchHeader';
|
|
5
|
+
import FormWorkflowContext from '../../context/FormWorkflowContext';
|
|
6
|
+
import { showSnackbar, useConfig, useSession, type ConfigSchema, type Session } from '@openmrs/esm-framework';
|
|
7
|
+
import { useHsuIdIdentifier } from '../../hooks/location-tag.resource';
|
|
8
|
+
|
|
9
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
10
|
+
ExtensionSlot: ({ state }) => (
|
|
11
|
+
<button data-testid="mock-search-select" onClick={() => state.selectPatientAction('patient-123')}>
|
|
12
|
+
Select Patient
|
|
13
|
+
</button>
|
|
14
|
+
),
|
|
15
|
+
interpolateUrl: jest.fn((url) => url),
|
|
16
|
+
navigate: jest.fn(),
|
|
17
|
+
showSnackbar: jest.fn(),
|
|
18
|
+
useConfig: jest.fn(),
|
|
19
|
+
useSession: jest.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock('react-i18next', () => ({
|
|
23
|
+
useTranslation: () => ({
|
|
24
|
+
t: (key: string, defaultValue: string, interpolation: { hsuLocation?: string; sessionLocation?: string }) => {
|
|
25
|
+
if (interpolation?.hsuLocation) {
|
|
26
|
+
return `Error: Patient at ${interpolation.hsuLocation} cannot be added to session at ${interpolation.sessionLocation}`;
|
|
27
|
+
}
|
|
28
|
+
return defaultValue || key;
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
jest.mock('../../hooks/location-tag.resource', () => ({
|
|
34
|
+
useHsuIdIdentifier: jest.fn(),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
jest.mock('react-router-dom', () => ({
|
|
38
|
+
Link: ({ children }) => <div>{children}</div>,
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
const mockShowSnackbar = showSnackbar as jest.MockedFunction<typeof showSnackbar>;
|
|
42
|
+
const mockUseConfig = useConfig as jest.MockedFunction<typeof useConfig>;
|
|
43
|
+
const mockUseSession = useSession as jest.MockedFunction<typeof useSession>;
|
|
44
|
+
const mockUseHsuIdIdentifier = useHsuIdIdentifier as jest.MockedFunction<typeof useHsuIdIdentifier>;
|
|
45
|
+
|
|
46
|
+
describe('PatientSearchHeader - Enforcement Feature', () => {
|
|
47
|
+
const mockContext = {
|
|
48
|
+
addPatient: jest.fn(),
|
|
49
|
+
workflowState: 'NEW_PATIENT',
|
|
50
|
+
activeFormUuid: 'form-123',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const sessionLocation = { uuid: 'loc-session', display: 'General Hospital' };
|
|
54
|
+
const mismatchedHsuLocation = {
|
|
55
|
+
location: { uuid: 'loc-other', display: 'Remote Clinic' },
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
mockUseSession.mockReturnValue({ sessionLocation } as Session);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
jest.clearAllMocks();
|
|
64
|
+
cleanup();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('triggers an error Snackbar when enforcePatientListLocationMatch is enabled and locations mismatch', async () => {
|
|
68
|
+
mockUseConfig.mockReturnValue({
|
|
69
|
+
enforcePatientListLocationMatch: true,
|
|
70
|
+
patientLocationMismatchCheck: false,
|
|
71
|
+
} as ConfigSchema);
|
|
72
|
+
|
|
73
|
+
mockUseHsuIdIdentifier.mockReturnValue({
|
|
74
|
+
hsuIdentifier: mismatchedHsuLocation,
|
|
75
|
+
} as unknown as ReturnType<typeof useHsuIdIdentifier>);
|
|
76
|
+
|
|
77
|
+
render(
|
|
78
|
+
<FormWorkflowContext.Provider value={mockContext as never}>
|
|
79
|
+
<PatientSearchHeader />
|
|
80
|
+
</FormWorkflowContext.Provider>,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const searchBar = screen.getByTestId('mock-search-select');
|
|
84
|
+
fireEvent.click(searchBar);
|
|
85
|
+
|
|
86
|
+
await waitFor(() => {
|
|
87
|
+
expect(mockShowSnackbar).toHaveBeenCalledWith(
|
|
88
|
+
expect.objectContaining({
|
|
89
|
+
kind: 'error',
|
|
90
|
+
title: 'Location Mismatch',
|
|
91
|
+
subtitle: expect.stringContaining('Remote Clinic'),
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(mockContext.addPatient).not.toHaveBeenCalled();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('does NOT trigger snackbar and adds patient if locations match even if enforcement is on', async () => {
|
|
100
|
+
mockUseConfig.mockReturnValue({
|
|
101
|
+
enforcePatientListLocationMatch: true,
|
|
102
|
+
} as ConfigSchema);
|
|
103
|
+
|
|
104
|
+
mockUseHsuIdIdentifier.mockReturnValue({
|
|
105
|
+
hsuIdentifier: { location: { uuid: 'loc-session', display: 'General Hospital' } },
|
|
106
|
+
} as unknown as ReturnType<typeof useHsuIdIdentifier>);
|
|
107
|
+
|
|
108
|
+
render(
|
|
109
|
+
<FormWorkflowContext.Provider value={mockContext as never}>
|
|
110
|
+
<PatientSearchHeader />
|
|
111
|
+
</FormWorkflowContext.Provider>,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
fireEvent.click(screen.getByTestId('mock-search-select'));
|
|
115
|
+
|
|
116
|
+
await waitFor(() => {
|
|
117
|
+
expect(mockContext.addPatient).toHaveBeenCalledWith('patient-123');
|
|
118
|
+
expect(mockShowSnackbar).not.toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Add, Close } from '@carbon/react/icons';
|
|
2
|
-
import { ExtensionSlot, interpolateUrl, navigate, useConfig, useSession } from '@openmrs/esm-framework';
|
|
2
|
+
import { ExtensionSlot, interpolateUrl, navigate, useConfig, useSession, showSnackbar } from '@openmrs/esm-framework';
|
|
3
3
|
import { Button } from '@carbon/react';
|
|
4
4
|
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
|
5
5
|
import { Link } from 'react-router-dom';
|
|
@@ -16,6 +16,7 @@ const PatientSearchHeader = () => {
|
|
|
16
16
|
const { sessionLocation } = useSession();
|
|
17
17
|
const config = useConfig();
|
|
18
18
|
const { addPatient, workflowState, activeFormUuid } = useContext(FormWorkflowContext);
|
|
19
|
+
const { t } = useTranslation();
|
|
19
20
|
|
|
20
21
|
const onPatientMismatchedLocationModalConfirm = useCallback(() => {
|
|
21
22
|
addPatient(selectedPatientUuid);
|
|
@@ -34,15 +35,37 @@ const PatientSearchHeader = () => {
|
|
|
34
35
|
useEffect(() => {
|
|
35
36
|
if (!selectedPatientUuid || !hsuIdentifier) return;
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
const locationMismatch = sessionLocation.uuid !== hsuIdentifier.location.uuid;
|
|
39
|
+
|
|
40
|
+
if (config.enforcePatientListLocationMatch && locationMismatch) {
|
|
41
|
+
showSnackbar({
|
|
42
|
+
kind: 'error',
|
|
43
|
+
title: t('locationMismatch', 'Location Mismatch'),
|
|
44
|
+
subtitle: t(
|
|
45
|
+
'patientLocationMismatchEnforced',
|
|
46
|
+
'Cannot add patient from {{hsuLocation}} to a session at {{sessionLocation}}',
|
|
47
|
+
{
|
|
48
|
+
hsuLocation: hsuIdentifier.location?.display,
|
|
49
|
+
sessionLocation: sessionLocation?.display,
|
|
50
|
+
},
|
|
51
|
+
),
|
|
52
|
+
});
|
|
53
|
+
setSelectedPatientUuid(null);
|
|
54
|
+
} else if (config.patientLocationMismatchCheck && locationMismatch) {
|
|
38
55
|
setPatientLocationMismatchModalOpen(true);
|
|
39
56
|
} else {
|
|
40
57
|
addPatient(selectedPatientUuid);
|
|
41
58
|
setSelectedPatientUuid(null);
|
|
42
59
|
}
|
|
43
|
-
}, [
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
}, [
|
|
61
|
+
selectedPatientUuid,
|
|
62
|
+
sessionLocation,
|
|
63
|
+
hsuIdentifier,
|
|
64
|
+
addPatient,
|
|
65
|
+
config.patientLocationMismatchCheck,
|
|
66
|
+
config.enforcePatientListLocationMatch,
|
|
67
|
+
t,
|
|
68
|
+
]);
|
|
46
69
|
|
|
47
70
|
if (workflowState !== 'NEW_PATIENT') return null;
|
|
48
71
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Close, Add } from '@carbon/react/icons';
|
|
2
2
|
import { Button } from '@carbon/react';
|
|
3
3
|
import React, { useCallback, useContext, useState } from 'react';
|
|
4
|
+
import { useConfig, useSession, showSnackbar } from '@openmrs/esm-framework';
|
|
4
5
|
import GroupFormWorkflowContext from '../../context/GroupFormWorkflowContext';
|
|
5
6
|
import styles from './styles.scss';
|
|
6
7
|
import { useTranslation } from 'react-i18next';
|
|
@@ -9,16 +10,40 @@ import AddGroupModal from '../../add-group-modal/AddGroupModal';
|
|
|
9
10
|
|
|
10
11
|
const GroupSearchHeader = () => {
|
|
11
12
|
const { t } = useTranslation();
|
|
13
|
+
const config = useConfig();
|
|
14
|
+
const { sessionLocation } = useSession();
|
|
12
15
|
const { activeGroupUuid, setGroup, destroySession } = useContext(GroupFormWorkflowContext);
|
|
13
16
|
const [isOpen, setOpen] = useState(false);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
|
|
18
|
+
const handleSelectGroup = useCallback(
|
|
19
|
+
(group) => {
|
|
20
|
+
if (config.enforcePatientListLocationMatch && group.location && sessionLocation.uuid !== group.location.uuid) {
|
|
21
|
+
showSnackbar({
|
|
22
|
+
kind: 'error',
|
|
23
|
+
title: t('locationMismatch', 'Location Mismatch'),
|
|
24
|
+
subtitle: t(
|
|
25
|
+
'groupLocationMismatchEnforced',
|
|
26
|
+
'Cannot select group from {{groupLocation}} for a session at {{sessionLocation}}',
|
|
27
|
+
{
|
|
28
|
+
groupLocation: group.location?.display,
|
|
29
|
+
sessionLocation: sessionLocation?.display,
|
|
30
|
+
},
|
|
31
|
+
),
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (group.cohortMembers) {
|
|
37
|
+
group.cohortMembers.sort((a, b) => {
|
|
38
|
+
const aName = a?.patient?.person?.names?.[0]?.display;
|
|
39
|
+
const bName = b?.patient?.person?.names?.[0]?.display;
|
|
40
|
+
return aName.localeCompare(bName, undefined, { sensitivity: 'base' });
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
setGroup(group);
|
|
44
|
+
},
|
|
45
|
+
[config.enforcePatientListLocationMatch, sessionLocation, setGroup, t],
|
|
46
|
+
);
|
|
22
47
|
|
|
23
48
|
const handleCancel = useCallback(() => {
|
|
24
49
|
setOpen(false);
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
import useSWR from 'swr';
|
|
2
2
|
import { openmrsFetch } from '@openmrs/esm-framework';
|
|
3
|
-
|
|
4
|
-
export interface Identifier {
|
|
5
|
-
uuid: string;
|
|
6
|
-
identifier: string;
|
|
7
|
-
display: string;
|
|
8
|
-
identifierType: {
|
|
9
|
-
uuid: string;
|
|
10
|
-
display: string;
|
|
11
|
-
};
|
|
12
|
-
location: {
|
|
13
|
-
uuid: string;
|
|
14
|
-
display: string;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
3
|
+
import { type Identifier } from '../types';
|
|
17
4
|
|
|
18
5
|
export function useHsuIdIdentifier(patientUuid: string) {
|
|
19
6
|
const hsuIdType = '05a29f94-c0ed-11e2-94be-8c13b969e334';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useState } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import { showNotification,
|
|
3
|
+
import { showNotification, showSnackbar, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
4
4
|
|
|
5
5
|
const useStartVisit = ({ showSuccessNotification = true, showErrorNotification = true }) => {
|
|
6
6
|
const { t } = useTranslation();
|
|
@@ -14,10 +14,9 @@ const useStartVisit = ({ showSuccessNotification = true, showErrorNotification =
|
|
|
14
14
|
setError(false);
|
|
15
15
|
setSuccess(result);
|
|
16
16
|
if (showSuccessNotification) {
|
|
17
|
-
|
|
18
|
-
critical: true,
|
|
17
|
+
showSnackbar({
|
|
19
18
|
kind: 'success',
|
|
20
|
-
|
|
19
|
+
subtitle: t('visitStartedSuccessfully', `${result?.data?.visitType?.display} started successfully`),
|
|
21
20
|
title: t('visitStarted', 'Visit started'),
|
|
22
21
|
});
|
|
23
22
|
}
|
package/src/types.ts
CHANGED
|
@@ -23,3 +23,17 @@ export interface SpecificQuestionConfig {
|
|
|
23
23
|
defaultAnswer?: string;
|
|
24
24
|
disabled?: boolean;
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
export interface Identifier {
|
|
28
|
+
uuid: string;
|
|
29
|
+
identifier: string;
|
|
30
|
+
display: string;
|
|
31
|
+
identifierType: {
|
|
32
|
+
uuid: string;
|
|
33
|
+
display: string;
|
|
34
|
+
};
|
|
35
|
+
location: {
|
|
36
|
+
uuid: string;
|
|
37
|
+
display: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
package/translations/en.json
CHANGED
|
@@ -27,8 +27,10 @@
|
|
|
27
27
|
"formsAppMenuLink": "Fast Data Entry",
|
|
28
28
|
"formsFilled": "Forms filled",
|
|
29
29
|
"goToForm": "Go To Form",
|
|
30
|
+
"groupLocationMismatchEnforced": "Cannot select group from {{groupLocation}} for a session at {{sessionLocation}}",
|
|
30
31
|
"groupNameError": "Please enter a group name.",
|
|
31
32
|
"identifier": "Identifier",
|
|
33
|
+
"locationMismatch": "Location Mismatch",
|
|
32
34
|
"markAbsentPatients": "The patients in this group. Patients that are not present in the session should be marked as absent.",
|
|
33
35
|
"members": "members",
|
|
34
36
|
"name": "Name",
|
|
@@ -44,6 +46,7 @@
|
|
|
44
46
|
"orLabelName": "OR label name",
|
|
45
47
|
"patientIsPresent": "Patient is present",
|
|
46
48
|
"patientLocationMismatch": "The selected HSU location ({{hsuLocation}}) does not match the current session location ({{sessionLocation}}). Are you sure you want to proceed?",
|
|
49
|
+
"patientLocationMismatchEnforced": "Cannot add patient from {{hsuLocation}} to a session at {{sessionLocation}}",
|
|
47
50
|
"patientsInGroup": "Patients in group",
|
|
48
51
|
"postError": "POST Error",
|
|
49
52
|
"practitionerName": "Practitioner Name",
|