@openmrs/esm-patient-list-management-app 9.2.1-pre.7290 → 9.2.1-pre.7303
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 +5 -5
- package/dist/1992.js +1 -1
- package/dist/1992.js.map +1 -1
- package/dist/7760.js +1 -1
- package/dist/7760.js.map +1 -1
- package/dist/openmrs-esm-patient-list-management-app.js.buildmanifest.json +6 -6
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/header/header.component.tsx +2 -3
- package/src/header/header.test.tsx +45 -0
- package/src/list-details/list-details.component.tsx +3 -4
- package/src/list-details/list-details.test.tsx +12 -5
- package/src/patient-list-form/patient-list-form.workspace.test.tsx +212 -0
- package/src/patient-list-form/patient-list-form.workspace.tsx +100 -74
- package/src/root.component.tsx +2 -4
- package/src/routes.json +14 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, type SyntheticEvent, useEffect, useId, useState } from 'react';
|
|
1
|
+
import React, { useCallback, type SyntheticEvent, useEffect, useId, useMemo, useState } from 'react';
|
|
2
2
|
import { Button, ButtonSet, Dropdown, Layer, TextArea, TextInput } from '@carbon/react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { type TFunction } from 'i18next';
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
showSnackbar,
|
|
10
10
|
useLayoutType,
|
|
11
11
|
useSession,
|
|
12
|
-
|
|
12
|
+
Workspace2,
|
|
13
|
+
type Workspace2DefinitionProps,
|
|
13
14
|
} from '@openmrs/esm-framework';
|
|
14
15
|
import type { NewCohortData, NewCohortDataPayload, OpenmrsCohort } from '../api/types';
|
|
15
16
|
import {
|
|
@@ -36,16 +37,18 @@ const createCohortSchema = (t: TFunction) => {
|
|
|
36
37
|
|
|
37
38
|
type CohortFormData = z.infer<ReturnType<typeof createCohortSchema>>;
|
|
38
39
|
|
|
39
|
-
export interface PatientListFormWorkspaceProps
|
|
40
|
+
export interface PatientListFormWorkspaceProps {
|
|
41
|
+
/** Existing patient list to edit. If not provided, creates a new list. */
|
|
40
42
|
patientListDetails?: OpenmrsCohort;
|
|
43
|
+
/** Callback triggered after successful create/edit operation */
|
|
41
44
|
onSuccess?: () => void;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
const PatientListFormWorkspace: React.FC<PatientListFormWorkspaceProps
|
|
45
|
-
|
|
46
|
-
onSuccess = () => {},
|
|
47
|
+
const PatientListFormWorkspace: React.FC<Workspace2DefinitionProps<PatientListFormWorkspaceProps>> = ({
|
|
48
|
+
workspaceProps,
|
|
47
49
|
closeWorkspace,
|
|
48
50
|
}) => {
|
|
51
|
+
const { patientListDetails, onSuccess = () => {} } = workspaceProps ?? {};
|
|
49
52
|
const id = useId();
|
|
50
53
|
const isTablet = useLayoutType() === 'tablet';
|
|
51
54
|
const responsiveLevel = isTablet ? 1 : 0;
|
|
@@ -63,6 +66,19 @@ const PatientListFormWorkspace: React.FC<PatientListFormWorkspaceProps> = ({
|
|
|
63
66
|
});
|
|
64
67
|
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});
|
|
65
68
|
|
|
69
|
+
const { initialValues, isDirty } = useMemo(() => {
|
|
70
|
+
const initial = {
|
|
71
|
+
name: patientListDetails?.name || '',
|
|
72
|
+
description: patientListDetails?.description || '',
|
|
73
|
+
cohortType: patientListDetails?.cohortType?.uuid || '',
|
|
74
|
+
};
|
|
75
|
+
const dirty =
|
|
76
|
+
cohortDetails.name !== initial.name ||
|
|
77
|
+
cohortDetails.description !== initial.description ||
|
|
78
|
+
cohortDetails.cohortType !== initial.cohortType;
|
|
79
|
+
return { initialValues: initial, isDirty: dirty };
|
|
80
|
+
}, [cohortDetails, patientListDetails]);
|
|
81
|
+
|
|
66
82
|
const validateForm = useCallback(
|
|
67
83
|
(data: CohortFormData) => {
|
|
68
84
|
try {
|
|
@@ -129,7 +145,7 @@ const PatientListFormWorkspace: React.FC<PatientListFormWorkspaceProps> = ({
|
|
|
129
145
|
});
|
|
130
146
|
onSuccess();
|
|
131
147
|
setIsSubmitting(false);
|
|
132
|
-
closeWorkspace();
|
|
148
|
+
closeWorkspace({ discardUnsavedChanges: true });
|
|
133
149
|
})
|
|
134
150
|
.catch(onError);
|
|
135
151
|
} else {
|
|
@@ -146,7 +162,7 @@ const PatientListFormWorkspace: React.FC<PatientListFormWorkspaceProps> = ({
|
|
|
146
162
|
});
|
|
147
163
|
onSuccess();
|
|
148
164
|
setIsSubmitting(false);
|
|
149
|
-
closeWorkspace();
|
|
165
|
+
closeWorkspace({ discardUnsavedChanges: true });
|
|
150
166
|
})
|
|
151
167
|
.catch(onError);
|
|
152
168
|
}
|
|
@@ -159,75 +175,85 @@ const PatientListFormWorkspace: React.FC<PatientListFormWorkspaceProps> = ({
|
|
|
159
175
|
}));
|
|
160
176
|
}, []);
|
|
161
177
|
|
|
178
|
+
const workspaceTitle = patientListDetails
|
|
179
|
+
? t('editPatientListHeader', 'Edit patient list')
|
|
180
|
+
: t('newPatientListHeader', 'New patient list');
|
|
181
|
+
|
|
182
|
+
const handleCancel = useCallback(() => {
|
|
183
|
+
closeWorkspace();
|
|
184
|
+
}, [closeWorkspace]);
|
|
185
|
+
|
|
162
186
|
return (
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
<
|
|
169
|
-
<
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
<
|
|
183
|
-
<
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
<
|
|
202
|
-
<
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
187
|
+
<Workspace2 title={workspaceTitle} hasUnsavedChanges={isDirty}>
|
|
188
|
+
<div data-tutorial-target="patient-list-form" className={styles.container}>
|
|
189
|
+
{/* data-tutorial-target attribute is essential for joyride in onboarding app ! */}
|
|
190
|
+
<div className={styles.content}>
|
|
191
|
+
<h4 className={styles.header}>{t('configureList', 'Configure your patient list using the fields below')}</h4>
|
|
192
|
+
<div>
|
|
193
|
+
<Layer level={responsiveLevel}>
|
|
194
|
+
<TextInput
|
|
195
|
+
id={`${id}-input`}
|
|
196
|
+
invalid={!!validationErrors.name}
|
|
197
|
+
invalidText={validationErrors.name}
|
|
198
|
+
labelText={t('newPatientListNameLabel', 'List name')}
|
|
199
|
+
name="name"
|
|
200
|
+
onChange={handleChange}
|
|
201
|
+
placeholder={t('listNamePlaceholder', 'e.g. Potential research participants')}
|
|
202
|
+
value={cohortDetails?.name}
|
|
203
|
+
/>
|
|
204
|
+
</Layer>
|
|
205
|
+
</div>
|
|
206
|
+
<div className={styles.input}>
|
|
207
|
+
<Layer level={responsiveLevel}>
|
|
208
|
+
<Dropdown
|
|
209
|
+
id="cohortType"
|
|
210
|
+
items={listCohortTypes}
|
|
211
|
+
itemToString={(item) => (item ? item.display : '')}
|
|
212
|
+
label={t('chooseCohortType', 'Choose cohort type')}
|
|
213
|
+
onChange={({ selectedItem }) => {
|
|
214
|
+
setCohortDetails((prev) => ({
|
|
215
|
+
...prev,
|
|
216
|
+
cohortType: selectedItem?.uuid || '',
|
|
217
|
+
}));
|
|
218
|
+
}}
|
|
219
|
+
selectedItem={listCohortTypes.find((item) => item.uuid === cohortDetails.cohortType) || null}
|
|
220
|
+
titleText={t('selectCohortType', 'Select cohort type')}
|
|
221
|
+
type="default"
|
|
222
|
+
/>
|
|
223
|
+
</Layer>
|
|
224
|
+
</div>
|
|
225
|
+
<div className={styles.input}>
|
|
226
|
+
<Layer level={responsiveLevel}>
|
|
227
|
+
<TextArea
|
|
228
|
+
enableCounter
|
|
229
|
+
id={`${id}-textarea`}
|
|
230
|
+
labelText={t('newPatientListDescriptionLabel', 'Describe the purpose of this list in a few words')}
|
|
231
|
+
maxCount={255}
|
|
232
|
+
name="description"
|
|
233
|
+
onChange={handleChange}
|
|
234
|
+
placeholder={t(
|
|
235
|
+
'listDescriptionPlaceholder',
|
|
236
|
+
'e.g. Patients with diagnosed asthma who may be willing to be a part of a university research study',
|
|
237
|
+
)}
|
|
238
|
+
value={cohortDetails?.description}
|
|
239
|
+
/>
|
|
240
|
+
</Layer>
|
|
241
|
+
</div>
|
|
216
242
|
</div>
|
|
243
|
+
<ButtonSet className={styles.buttonSet}>
|
|
244
|
+
<Button className={styles.button} onClick={handleCancel} kind="secondary" size="xl">
|
|
245
|
+
{getCoreTranslation('cancel')}
|
|
246
|
+
</Button>
|
|
247
|
+
<Button className={styles.button} onClick={handleSubmit} size="xl" disabled={isSubmitting}>
|
|
248
|
+
{isSubmitting
|
|
249
|
+
? t('submitting', 'Submitting')
|
|
250
|
+
: patientListDetails
|
|
251
|
+
? t('editList', 'Edit list')
|
|
252
|
+
: t('createList', 'Create list')}
|
|
253
|
+
</Button>
|
|
254
|
+
</ButtonSet>
|
|
217
255
|
</div>
|
|
218
|
-
|
|
219
|
-
<Button className={styles.button} onClick={closeWorkspace} kind="secondary" size="xl">
|
|
220
|
-
{getCoreTranslation('cancel')}
|
|
221
|
-
</Button>
|
|
222
|
-
<Button className={styles.button} onClick={handleSubmit} size="xl" disabled={isSubmitting}>
|
|
223
|
-
{isSubmitting
|
|
224
|
-
? t('submitting', 'Submitting')
|
|
225
|
-
: patientListDetails
|
|
226
|
-
? t('editList', 'Edit list')
|
|
227
|
-
: t('createList', 'Create list')}
|
|
228
|
-
</Button>
|
|
229
|
-
</ButtonSet>
|
|
230
|
-
</div>
|
|
256
|
+
</Workspace2>
|
|
231
257
|
);
|
|
232
258
|
};
|
|
233
259
|
|
package/src/root.component.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
2
|
import { BrowserRouter, Route, Routes, useSearchParams } from 'react-router-dom';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { WorkspaceContainer,
|
|
4
|
+
import { WorkspaceContainer, launchWorkspace2 } from '@openmrs/esm-framework';
|
|
5
5
|
import ListDetails from './list-details/list-details.component';
|
|
6
6
|
import ListsDashboard from './lists-dashboard/lists-dashboard.component';
|
|
7
7
|
|
|
@@ -15,9 +15,7 @@ const AutoLaunchPatientListWorkspace: React.FC = () => {
|
|
|
15
15
|
if (shouldOpenCreate && !hasOpenedRef.current) {
|
|
16
16
|
hasOpenedRef.current = true;
|
|
17
17
|
const rafId = requestAnimationFrame(() => {
|
|
18
|
-
|
|
19
|
-
workspaceTitle: t('newPatientListHeader', 'New patient list'),
|
|
20
|
-
});
|
|
18
|
+
launchWorkspace2('patient-list-form-workspace');
|
|
21
19
|
setSearchParams({}, { replace: true });
|
|
22
20
|
});
|
|
23
21
|
return () => cancelAnimationFrame(rafId);
|
package/src/routes.json
CHANGED
|
@@ -43,12 +43,23 @@
|
|
|
43
43
|
"component": "deletePatientListModal"
|
|
44
44
|
}
|
|
45
45
|
],
|
|
46
|
-
"
|
|
46
|
+
"workspaces2": [
|
|
47
47
|
{
|
|
48
48
|
"name": "patient-list-form-workspace",
|
|
49
49
|
"component": "patientListFormWorkspace",
|
|
50
|
-
"
|
|
51
|
-
|
|
50
|
+
"window": "patient-list-form-window"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"workspaceWindows2": [
|
|
54
|
+
{
|
|
55
|
+
"name": "patient-list-form-window",
|
|
56
|
+
"group": "patient-list-form-workspace-group"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"workspaceGroups2": [
|
|
60
|
+
{
|
|
61
|
+
"name": "patient-list-form-workspace-group",
|
|
62
|
+
"overlay": false
|
|
52
63
|
}
|
|
53
64
|
]
|
|
54
65
|
}
|