@openmrs/esm-patient-immunizations-app 11.3.1-pre.9452 → 11.3.1-pre.9455

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.
@@ -14,8 +14,9 @@ import {
14
14
  useConfig,
15
15
  useLayoutType,
16
16
  useSession,
17
+ Workspace2,
17
18
  } from '@openmrs/esm-framework';
18
- import { type DefaultPatientWorkspaceProps } from '@openmrs/esm-patient-common-lib';
19
+ import { type PatientWorkspace2DefinitionProps } from '@openmrs/esm-patient-common-lib';
19
20
  import { DoseInput } from './components/dose-input.component';
20
21
  import { immunizationFormSub } from './utils';
21
22
  import { mapToFHIRImmunizationResource } from './immunization-mapper';
@@ -26,13 +27,9 @@ import { useImmunizations } from '../hooks/useImmunizations';
26
27
  import { useImmunizationsConceptSet } from '../hooks/useImmunizationsConceptSet';
27
28
  import styles from './immunizations-form.scss';
28
29
 
29
- const ImmunizationsForm: React.FC<DefaultPatientWorkspaceProps> = ({
30
- patient,
31
- patientUuid,
30
+ const ImmunizationsForm: React.FC<PatientWorkspace2DefinitionProps<{}, {}>> = ({
32
31
  closeWorkspace,
33
- closeWorkspaceWithSavedChanges,
34
- promptBeforeClosing,
35
- visitContext,
32
+ groupProps: { patientUuid, patient, visitContext },
36
33
  }) => {
37
34
  const config = useConfig<ImmunizationConfigObject>();
38
35
  const currentUser = useSession();
@@ -45,7 +42,6 @@ const ImmunizationsForm: React.FC<DefaultPatientWorkspaceProps> = ({
45
42
  immunizationObsUuid: string;
46
43
  visitUuid?: string;
47
44
  }>();
48
- const now = useMemo(() => new Date(), []);
49
45
 
50
46
  const immunizationFormSchema = useMemo(() => {
51
47
  return z.object({
@@ -59,7 +55,7 @@ const ImmunizationsForm: React.FC<DefaultPatientWorkspaceProps> = ({
59
55
  (date) => {
60
56
  // Normalize both dates to start of day in local timezone
61
57
  const inputDate = dayjs(date).startOf('day');
62
- const today = dayjs(now).startOf('day');
58
+ const today = dayjs().startOf('day');
63
59
  return inputDate.isSame(today) || inputDate.isBefore(today);
64
60
  },
65
61
  {
@@ -74,7 +70,7 @@ const ImmunizationsForm: React.FC<DefaultPatientWorkspaceProps> = ({
74
70
  lotNumber: z.string().nullable().optional(),
75
71
  manufacturer: z.string().nullable().optional(),
76
72
  });
77
- }, [patient.birthDate, t, now]);
73
+ }, [patient.birthDate, t]);
78
74
 
79
75
  type ImmunizationFormInputData = z.infer<typeof immunizationFormSchema>;
80
76
  const formProps = useForm<ImmunizationFormInputData>({
@@ -82,7 +78,7 @@ const ImmunizationsForm: React.FC<DefaultPatientWorkspaceProps> = ({
82
78
  resolver: zodResolver(immunizationFormSchema),
83
79
  defaultValues: {
84
80
  vaccineUuid: '',
85
- vaccinationDate: dayjs(now).startOf('day').toDate(),
81
+ vaccinationDate: dayjs().startOf('day').toDate(),
86
82
  doseNumber: 1,
87
83
  nextDoseDate: null,
88
84
  note: '',
@@ -100,17 +96,12 @@ const ImmunizationsForm: React.FC<DefaultPatientWorkspaceProps> = ({
100
96
  watch,
101
97
  } = formProps;
102
98
  const vaccinationDate = watch('vaccinationDate');
103
-
104
- useEffect(() => {
105
- promptBeforeClosing(() => isDirty);
106
- }, [isDirty, promptBeforeClosing]);
107
-
108
99
  const vaccineUuid = watch('vaccineUuid');
109
100
 
110
101
  useEffect(() => {
111
102
  const sub = immunizationFormSub.subscribe((props) => {
112
103
  if (props) {
113
- const vaccinationDateOrNow = props.vaccinationDate ? parseDate(props.vaccinationDate) : now;
104
+ const vaccinationDateOrNow = props.vaccinationDate ? parseDate(props.vaccinationDate) : new Date();
114
105
  reset({
115
106
  vaccineUuid: props.vaccineUuid,
116
107
  vaccinationDate: vaccinationDateOrNow,
@@ -129,7 +120,7 @@ const ImmunizationsForm: React.FC<DefaultPatientWorkspaceProps> = ({
129
120
  sub.unsubscribe();
130
121
  immunizationFormSub.next(null);
131
122
  };
132
- }, [reset, now]);
123
+ }, [reset]);
133
124
 
134
125
  const onSubmit = useCallback(
135
126
  async (data: ImmunizationFormInputData) => {
@@ -170,7 +161,7 @@ const ImmunizationsForm: React.FC<DefaultPatientWorkspaceProps> = ({
170
161
  immunizationToEditMeta?.immunizationObsUuid,
171
162
  abortController,
172
163
  );
173
- closeWorkspaceWithSavedChanges();
164
+ closeWorkspace({ discardUnsavedChanges: true });
174
165
  mutate();
175
166
  showSnackbar({
176
167
  kind: 'success',
@@ -193,157 +184,161 @@ const ImmunizationsForm: React.FC<DefaultPatientWorkspaceProps> = ({
193
184
  visitContext?.uuid,
194
185
  immunizationToEditMeta,
195
186
  immunizationsConceptSet,
196
- closeWorkspaceWithSavedChanges,
187
+ closeWorkspace,
197
188
  t,
198
189
  mutate,
199
190
  ],
200
191
  );
201
192
  return (
202
- <FormProvider {...formProps}>
203
- <Form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
204
- <Stack gap={5} className={styles.container}>
205
- <ResponsiveWrapper>
206
- <Controller
207
- name="vaccinationDate"
208
- control={control}
209
- render={({ field, fieldState }) => (
210
- <OpenmrsDatePicker
211
- {...field}
212
- className={styles.datePicker}
213
- id="vaccinationDate"
214
- invalid={Boolean(fieldState?.error?.message)}
215
- invalidText={fieldState?.error?.message}
216
- labelText={t('vaccinationDate', 'Vaccination date')}
217
- maxDate={now}
218
- />
219
- )}
220
- />
221
- </ResponsiveWrapper>
222
- <ResponsiveWrapper>
223
- <Controller
224
- name="vaccineUuid"
225
- control={control}
226
- render={({ field: { onChange, value } }) => (
227
- <Dropdown
228
- disabled={!!immunizationToEditMeta}
229
- id="immunization"
230
- invalid={!!errors?.vaccineUuid}
231
- invalidText={errors?.vaccineUuid?.message}
232
- itemToString={(item) =>
233
- immunizationsConceptSet?.answers.find((candidate) => candidate.uuid == item)?.display
234
- }
235
- items={immunizationsConceptSet?.answers?.map((item) => item.uuid) || []}
236
- label={t('selectImmunization', 'Select immunization')}
237
- onChange={(val) => onChange(val.selectedItem)}
238
- selectedItem={value}
239
- titleText={t('immunization', 'Immunization')}
240
- />
241
- )}
242
- />
243
- </ResponsiveWrapper>
244
- {vaccineUuid && (
193
+ <Workspace2 title={t('immunizationWorkspaceTitle', 'Immunization')} hasUnsavedChanges={isDirty}>
194
+ <FormProvider {...formProps}>
195
+ <Form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
196
+ <Stack gap={5} className={styles.container}>
245
197
  <ResponsiveWrapper>
246
- <DoseInput vaccine={vaccineUuid} sequences={config.sequenceDefinitions} control={control} />
198
+ <Controller
199
+ name="vaccinationDate"
200
+ control={control}
201
+ render={({ field, fieldState }) => (
202
+ <OpenmrsDatePicker
203
+ {...field}
204
+ className={styles.datePicker}
205
+ id="vaccinationDate"
206
+ invalid={Boolean(fieldState?.error?.message)}
207
+ invalidText={fieldState?.error?.message}
208
+ labelText={t('vaccinationDate', 'Vaccination date')}
209
+ maxDate={new Date()}
210
+ />
211
+ )}
212
+ />
247
213
  </ResponsiveWrapper>
248
- )}
249
- <div className={styles.vaccineBatchHeading}>{t('vaccineBatchInformation', 'Vaccine Batch Information')}</div>
250
- <ResponsiveWrapper>
251
- <Controller
252
- name="manufacturer"
253
- control={control}
254
- render={({ field: { onChange, value } }) => (
255
- <TextInput
256
- id="manufacturer"
257
- labelText={t('manufacturer', 'Manufacturer')}
258
- onChange={(evt) => onChange(evt.target.value)}
259
- type="text"
260
- value={value}
261
- />
262
- )}
263
- />
264
- </ResponsiveWrapper>
265
- <ResponsiveWrapper>
266
- <Controller
267
- name="lotNumber"
268
- control={control}
269
- render={({ field: { onChange, value } }) => (
270
- <TextInput
271
- id="lotNumber"
272
- labelText={t('lotNumber', 'Lot Number')}
273
- onChange={(evt) => onChange(evt.target.value)}
274
- type="text"
275
- value={value}
276
- />
277
- )}
278
- />
279
- </ResponsiveWrapper>
280
- <ResponsiveWrapper>
281
- <Controller
282
- name="expirationDate"
283
- control={control}
284
- render={({ field, fieldState }) => (
285
- <OpenmrsDatePicker
286
- {...field}
287
- className={styles.datePicker}
288
- id="vaccinationExpiration"
289
- invalid={Boolean(fieldState?.error?.message)}
290
- invalidText={fieldState?.error?.message}
291
- labelText={t('expirationDate', 'Expiration date')}
292
- minDate={vaccinationDate}
293
- />
294
- )}
295
- />
296
- </ResponsiveWrapper>
297
- <ResponsiveWrapper>
298
- <Controller
299
- name="note"
300
- control={control}
301
- render={({ field: { onChange, value } }) => (
302
- <TextArea
303
- enableCounter
304
- id="note"
305
- invalidText={errors?.note?.message}
306
- labelText={t('note', 'Note')}
307
- maxCount={255}
308
- onChange={(evt) => onChange(evt.target.value)}
309
- placeholder={t('immunizationNotePlaceholder', 'For example: mild redness at injection site')}
310
- value={value}
311
- />
312
- )}
313
- />
314
- </ResponsiveWrapper>
315
- <ResponsiveWrapper>
316
- <Controller
317
- name="nextDoseDate"
318
- control={control}
319
- render={({ field, fieldState }) => (
320
- <OpenmrsDatePicker
321
- {...field}
322
- className={styles.datePicker}
323
- id="nextDoseDate"
324
- invalid={Boolean(fieldState?.error?.message)}
325
- invalidText={fieldState?.error?.message}
326
- labelText={t('nextDoseDate', 'Next dose date')}
327
- minDate={vaccinationDate}
328
- />
329
- )}
330
- />
331
- </ResponsiveWrapper>
332
- </Stack>
333
- <ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
334
- <Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
335
- {getCoreTranslation('cancel')}
336
- </Button>
337
- <Button className={styles.button} kind="primary" disabled={isSubmitting} type="submit">
338
- {isSubmitting ? (
339
- <InlineLoading className={styles.spinner} description={t('saving', 'Saving') + '...'} />
340
- ) : (
341
- <span>{getCoreTranslation('save')}</span>
214
+ <ResponsiveWrapper>
215
+ <Controller
216
+ name="vaccineUuid"
217
+ control={control}
218
+ render={({ field: { onChange, value } }) => (
219
+ <Dropdown
220
+ disabled={!!immunizationToEditMeta}
221
+ id="immunization"
222
+ invalid={!!errors?.vaccineUuid}
223
+ invalidText={errors?.vaccineUuid?.message}
224
+ itemToString={(item) =>
225
+ immunizationsConceptSet?.answers.find((candidate) => candidate.uuid == item)?.display
226
+ }
227
+ items={immunizationsConceptSet?.answers?.map((item) => item.uuid) || []}
228
+ label={t('selectImmunization', 'Select immunization')}
229
+ onChange={(val) => onChange(val.selectedItem)}
230
+ selectedItem={value}
231
+ titleText={t('immunization', 'Immunization')}
232
+ />
233
+ )}
234
+ />
235
+ </ResponsiveWrapper>
236
+ {vaccineUuid && (
237
+ <ResponsiveWrapper>
238
+ <DoseInput vaccine={vaccineUuid} sequences={config.sequenceDefinitions} control={control} />
239
+ </ResponsiveWrapper>
342
240
  )}
343
- </Button>
344
- </ButtonSet>
345
- </Form>
346
- </FormProvider>
241
+ <div className={styles.vaccineBatchHeading}>
242
+ {t('vaccineBatchInformation', 'Vaccine Batch Information')}
243
+ </div>
244
+ <ResponsiveWrapper>
245
+ <Controller
246
+ name="manufacturer"
247
+ control={control}
248
+ render={({ field: { onChange, value } }) => (
249
+ <TextInput
250
+ id="manufacturer"
251
+ labelText={t('manufacturer', 'Manufacturer')}
252
+ onChange={(evt) => onChange(evt.target.value)}
253
+ type="text"
254
+ value={value}
255
+ />
256
+ )}
257
+ />
258
+ </ResponsiveWrapper>
259
+ <ResponsiveWrapper>
260
+ <Controller
261
+ name="lotNumber"
262
+ control={control}
263
+ render={({ field: { onChange, value } }) => (
264
+ <TextInput
265
+ id="lotNumber"
266
+ labelText={t('lotNumber', 'Lot Number')}
267
+ onChange={(evt) => onChange(evt.target.value)}
268
+ type="text"
269
+ value={value}
270
+ />
271
+ )}
272
+ />
273
+ </ResponsiveWrapper>
274
+ <ResponsiveWrapper>
275
+ <Controller
276
+ name="expirationDate"
277
+ control={control}
278
+ render={({ field, fieldState }) => (
279
+ <OpenmrsDatePicker
280
+ {...field}
281
+ className={styles.datePicker}
282
+ id="vaccinationExpiration"
283
+ invalid={Boolean(fieldState?.error?.message)}
284
+ invalidText={fieldState?.error?.message}
285
+ labelText={t('expirationDate', 'Expiration date')}
286
+ minDate={vaccinationDate}
287
+ />
288
+ )}
289
+ />
290
+ </ResponsiveWrapper>
291
+ <ResponsiveWrapper>
292
+ <Controller
293
+ name="note"
294
+ control={control}
295
+ render={({ field: { onChange, value } }) => (
296
+ <TextArea
297
+ enableCounter
298
+ id="note"
299
+ invalidText={errors?.note?.message}
300
+ labelText={t('note', 'Note')}
301
+ maxCount={255}
302
+ onChange={(evt) => onChange(evt.target.value)}
303
+ placeholder={t('immunizationNotePlaceholder', 'For example: mild redness at injection site')}
304
+ value={value}
305
+ />
306
+ )}
307
+ />
308
+ </ResponsiveWrapper>
309
+ <ResponsiveWrapper>
310
+ <Controller
311
+ name="nextDoseDate"
312
+ control={control}
313
+ render={({ field, fieldState }) => (
314
+ <OpenmrsDatePicker
315
+ {...field}
316
+ className={styles.datePicker}
317
+ id="nextDoseDate"
318
+ invalid={Boolean(fieldState?.error?.message)}
319
+ invalidText={fieldState?.error?.message}
320
+ labelText={t('nextDoseDate', 'Next dose date')}
321
+ minDate={vaccinationDate}
322
+ />
323
+ )}
324
+ />
325
+ </ResponsiveWrapper>
326
+ </Stack>
327
+ <ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
328
+ <Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
329
+ {getCoreTranslation('cancel')}
330
+ </Button>
331
+ <Button className={styles.button} kind="primary" disabled={isSubmitting} type="submit">
332
+ {isSubmitting ? (
333
+ <InlineLoading className={styles.spinner} description={t('saving', 'Saving') + '...'} />
334
+ ) : (
335
+ <span>{getCoreTranslation('save')}</span>
336
+ )}
337
+ </Button>
338
+ </ButtonSet>
339
+ </Form>
340
+ </FormProvider>
341
+ </Workspace2>
347
342
  );
348
343
  };
349
344
 
@@ -14,7 +14,7 @@ import {
14
14
  TableHeader,
15
15
  TableRow,
16
16
  } from '@carbon/react';
17
- import { AddIcon, formatDate, launchWorkspace, parseDate, usePagination } from '@openmrs/esm-framework';
17
+ import { AddIcon, formatDate, launchWorkspace2, parseDate, usePagination } from '@openmrs/esm-framework';
18
18
  import { CardHeader, EmptyState, ErrorState, PatientChartPagination } from '@openmrs/esm-patient-common-lib';
19
19
  import { useImmunizations } from '../hooks/useImmunizations';
20
20
  import styles from './immunizations-overview.scss';
@@ -36,7 +36,7 @@ const ImmunizationsOverview: React.FC<ImmunizationsOverviewProps> = ({ patient,
36
36
  const { data: immunizations, error, isLoading, isValidating } = useImmunizations(patientUuid);
37
37
  const { results: paginatedImmunizations, goTo, currentPage } = usePagination(immunizations ?? [], immunizationsCount);
38
38
 
39
- const launchImmunizationsForm = React.useCallback(() => launchWorkspace('immunization-form-workspace'), []);
39
+ const launchImmunizationsForm = React.useCallback(() => launchWorkspace2('immunization-form-workspace'), []);
40
40
 
41
41
  const tableHeaders = [
42
42
  {
package/src/routes.json CHANGED
@@ -37,17 +37,23 @@
37
37
  }
38
38
  ],
39
39
  "pages": [],
40
- "workspaces": [
40
+ "modals": [
41
+ {
42
+ "name": "immunization-delete-confirmation-modal",
43
+ "component": "deleteImmunizationConfirmationModal"
44
+ }
45
+ ],
46
+ "workspaces2": [
41
47
  {
42
48
  "name": "immunization-form-workspace",
43
- "title": "immunizationWorkspaceTitle",
44
- "component": "immunizationFormWorkspace"
49
+ "component": "immunizationFormWorkspace",
50
+ "window": "immunization-form-window"
45
51
  }
46
52
  ],
47
- "modals": [
53
+ "workspaceWindows2": [
48
54
  {
49
- "name": "immunization-delete-confirmation-modal",
50
- "component": "deleteImmunizationConfirmationModal"
55
+ "name": "immunization-form-window",
56
+ "group": "patient-chart"
51
57
  }
52
58
  ]
53
59
  }