@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.
@@ -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
- type DefaultWorkspaceProps,
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 extends DefaultWorkspaceProps {
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
- patientListDetails,
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
- <div data-tutorial-target="patient-list-form" className={styles.container}>
164
- {/* data-tutorial-target attribute is essential for joyride in onboarding app ! */}
165
- <div className={styles.content}>
166
- <h4 className={styles.header}>{t('configureList', 'Configure your patient list using the fields below')}</h4>
167
- <div>
168
- <Layer level={responsiveLevel}>
169
- <TextInput
170
- id={`${id}-input`}
171
- invalid={!!validationErrors.name}
172
- invalidText={validationErrors.name}
173
- labelText={t('newPatientListNameLabel', 'List name')}
174
- name="name"
175
- onChange={handleChange}
176
- placeholder={t('listNamePlaceholder', 'e.g. Potential research participants')}
177
- value={cohortDetails?.name}
178
- />
179
- </Layer>
180
- </div>
181
- <div className={styles.input}>
182
- <Layer level={responsiveLevel}>
183
- <Dropdown
184
- id="cohortType"
185
- items={listCohortTypes}
186
- itemToString={(item) => (item ? item.display : '')}
187
- label={t('chooseCohortType', 'Choose cohort type')}
188
- onChange={({ selectedItem }) => {
189
- setCohortDetails((prev) => ({
190
- ...prev,
191
- cohortType: selectedItem?.uuid || '',
192
- }));
193
- }}
194
- selectedItem={listCohortTypes.find((item) => item.uuid === cohortDetails.cohortType) || null}
195
- titleText={t('selectCohortType', 'Select cohort type')}
196
- type="default"
197
- />
198
- </Layer>
199
- </div>
200
- <div className={styles.input}>
201
- <Layer level={responsiveLevel}>
202
- <TextArea
203
- enableCounter
204
- id={`${id}-textarea`}
205
- labelText={t('newPatientListDescriptionLabel', 'Describe the purpose of this list in a few words')}
206
- maxCount={255}
207
- name="description"
208
- onChange={handleChange}
209
- placeholder={t(
210
- 'listDescriptionPlaceholder',
211
- 'e.g. Patients with diagnosed asthma who may be willing to be a part of a university research study',
212
- )}
213
- value={cohortDetails?.description}
214
- />
215
- </Layer>
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
- <ButtonSet className={styles.buttonSet}>
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
 
@@ -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, launchWorkspace } from '@openmrs/esm-framework';
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
- launchWorkspace('patient-list-form-workspace', {
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
- "workspaces": [
46
+ "workspaces2": [
47
47
  {
48
48
  "name": "patient-list-form-workspace",
49
49
  "component": "patientListFormWorkspace",
50
- "title": "patientListFormHeader",
51
- "type": "patient-lists"
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
  }