@openmrs/esm-patient-allergies-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.
@@ -23,8 +23,14 @@ import {
23
23
  import { z } from 'zod';
24
24
  import { zodResolver } from '@hookform/resolvers/zod';
25
25
  import { Controller, useForm, useWatch } from 'react-hook-form';
26
- import { ExtensionSlot, showSnackbar, useConfig, useLayoutType, ResponsiveWrapper } from '@openmrs/esm-framework';
27
- import { type DefaultPatientWorkspaceProps } from '@openmrs/esm-patient-common-lib';
26
+ import {
27
+ ExtensionSlot,
28
+ showSnackbar,
29
+ useConfig,
30
+ useLayoutType,
31
+ ResponsiveWrapper,
32
+ Workspace2,
33
+ } from '@openmrs/esm-framework';
28
34
  import {
29
35
  type Allergen,
30
36
  type NewAllergy,
@@ -37,6 +43,7 @@ import { useAllergies } from '../allergy-intolerance.resource';
37
43
  import { type AllergiesConfigObject } from '../../config-schema';
38
44
  import { ALLERGEN_TYPES, type Allergy } from '../../types';
39
45
  import styles from './allergy-form.scss';
46
+ import { type PatientWorkspace2DefinitionProps } from '@openmrs/esm-patient-common-lib';
40
47
 
41
48
  interface AllergyFormData {
42
49
  allergen: Allergen;
@@ -47,7 +54,7 @@ interface AllergyFormData {
47
54
  severityOfWorstReaction: string;
48
55
  }
49
56
 
50
- interface AllergyFormProps extends DefaultPatientWorkspaceProps {
57
+ export interface AllergyFormWorkspaceProps {
51
58
  allergy?: Allergy;
52
59
  formContext: 'creating' | 'editing';
53
60
  }
@@ -111,14 +118,11 @@ const allergyFormSchema = (t: TFunction, otherConceptUuid: string) =>
111
118
  }
112
119
  });
113
120
 
114
- function AllergyForm({
121
+ function AllergyFormWorkspace({
115
122
  closeWorkspace,
116
- patient,
117
- patientUuid,
118
- allergy,
119
- formContext,
120
- promptBeforeClosing,
121
- }: AllergyFormProps) {
123
+ groupProps: { patient, patientUuid },
124
+ workspaceProps: { allergy, formContext },
125
+ }: PatientWorkspace2DefinitionProps<AllergyFormWorkspaceProps, {}>) {
122
126
  const { allergens } = useAllergens();
123
127
  const { allergicReactions, isLoading: isLoadingReactions } = useAllergicReactions();
124
128
  const { concepts } = useConfig<AllergiesConfigObject>();
@@ -285,10 +289,6 @@ function AllergyForm({
285
289
  }
286
290
  }, [allergy, formContext, getAllergyFormDefaultValues, inEditMode, isLoadingReactions, setValue]);
287
291
 
288
- useEffect(() => {
289
- promptBeforeClosing(() => isDirty);
290
- }, [promptBeforeClosing, isDirty]);
291
-
292
292
  const selectedAllergen = useWatch({
293
293
  control,
294
294
  name: 'allergen',
@@ -345,7 +345,7 @@ function AllergyForm({
345
345
 
346
346
  const handleSuccess = () => {
347
347
  mutate();
348
- closeWorkspace({ ignoreChanges: true });
348
+ closeWorkspace({ discardUnsavedChanges: true });
349
349
  showSnackbar({
350
350
  isLowContrast: true,
351
351
  kind: 'success',
@@ -399,200 +399,210 @@ function AllergyForm({
399
399
  );
400
400
 
401
401
  return (
402
- <Form className={styles.formContainer} onSubmit={handleSubmit(onSubmit)}>
403
- {isTablet ? (
404
- <Row className={styles.header}>
405
- <ExtensionSlot className={styles.content} name="patient-details-header-slot" state={extensionSlotState} />
406
- </Row>
407
- ) : null}
408
- <div className={styles.form}>
409
- {isLoadingReactions ? (
410
- <div className={styles.loaderContainer}>
411
- <InlineLoading className={styles.loading} description={`${t('loading', 'Loading')} ...`} />
412
- </div>
413
- ) : (
414
- <Stack gap={5} className={styles.formContent}>
415
- {selectedAllergen?.uuid === otherConceptUuid && (
416
- <InlineNotification
417
- hideCloseButton
418
- kind="warning"
419
- lowContrast
420
- style={{ minWidth: '100%' }}
421
- subtitle={t(
422
- 'nonCodedAllergenWarningDescription',
423
- "Adding a custom allergen may impact system-wide allergy notifications. It's recommended to choose from the provided list for accurate alerts. Custom entries may not trigger notifications in all relevant contexts.",
424
- )}
425
- title={t('nonCodedAllergenWarningTitle', 'Warning: Custom Allergen Entry')}
426
- />
427
- )}
428
- <ResponsiveWrapper>
429
- <FormGroup legendText="">
430
- <Controller
431
- name="allergen"
432
- control={control}
433
- render={({ field: { onChange, value } }) => (
434
- <ComboBox
435
- id="allergen"
436
- invalid={!!errors.allergen}
437
- invalidText={errors.allergen?.message}
438
- itemToString={(item: unknown) => (item as Allergen)?.display}
439
- items={allergenItems as unknown as Array<Record<string, unknown>>}
440
- onChange={(props: { selectedItem?: unknown }) => {
441
- if (typeof props?.selectedItem !== 'undefined') {
442
- onChange(props.selectedItem as Allergen);
443
- }
444
- }}
445
- readOnly={inEditMode}
446
- placeholder={t('selectAllergen', 'Select the allergen')}
447
- selectedItem={value as unknown as Allergen}
448
- titleText={t('allergen', 'Allergen')}
449
- />
402
+ <Workspace2
403
+ title={allergy ? t('editAllergy', 'Edit an allergy') : t('recordNewAllergy', 'Record a new allergy')}
404
+ hasUnsavedChanges={isDirty}
405
+ >
406
+ <Form className={styles.formContainer} onSubmit={handleSubmit(onSubmit)}>
407
+ {isTablet ? (
408
+ <Row className={styles.header}>
409
+ <ExtensionSlot className={styles.content} name="patient-details-header-slot" state={extensionSlotState} />
410
+ </Row>
411
+ ) : null}
412
+ <div className={styles.form}>
413
+ {isLoadingReactions ? (
414
+ <div className={styles.loaderContainer}>
415
+ <InlineLoading className={styles.loading} description={`${t('loading', 'Loading')} ...`} />
416
+ </div>
417
+ ) : (
418
+ <Stack gap={5} className={styles.formContent}>
419
+ {selectedAllergen?.uuid === otherConceptUuid && (
420
+ <InlineNotification
421
+ hideCloseButton
422
+ kind="warning"
423
+ lowContrast
424
+ style={{ minWidth: '100%' }}
425
+ subtitle={t(
426
+ 'nonCodedAllergenWarningDescription',
427
+ "Adding a custom allergen may impact system-wide allergy notifications. It's recommended to choose from the provided list for accurate alerts. Custom entries may not trigger notifications in all relevant contexts.",
450
428
  )}
429
+ title={t('nonCodedAllergenWarningTitle', 'Warning: Custom Allergen Entry')}
451
430
  />
452
- </FormGroup>
453
- </ResponsiveWrapper>
454
- {selectedAllergen?.uuid === otherConceptUuid && (
431
+ )}
455
432
  <ResponsiveWrapper>
456
- <Controller
457
- name="nonCodedAllergen"
458
- control={control}
459
- render={({ field: { onBlur, onChange, value } }) => (
460
- <TextInput
461
- id="nonCodedAllergen"
462
- invalid={!!errors.nonCodedAllergen}
463
- invalidText={errors.nonCodedAllergen?.message}
464
- labelText={t('otherNonCodedAllergen', 'Other non-coded allergen')}
465
- onBlur={onBlur}
466
- onChange={onChange}
467
- placeholder={t('typeAllergenName', 'Please type in the name of the allergen')}
468
- readOnly={inEditMode}
469
- value={value}
470
- />
471
- )}
472
- />
473
- </ResponsiveWrapper>
474
- )}
475
- <>
476
- <div className={classNames({ [styles.checkboxContainer]: isTablet })}>
477
- <FormGroup legendText="" data-testid="allergic-reactions-container">
478
- {isLoadingReactions ? (
479
- <>
480
- {Array.from({ length: 10 }).map((_, index) => (
481
- <CheckboxSkeleton key={`checkbox-skeleton-${index}`} />
482
- ))}
483
- </>
484
- ) : (
485
- <Controller
486
- name="allergicReactions"
487
- control={control}
488
- render={({ field: { onChange, value } }) => (
489
- <CheckboxGroup
490
- invalid={!!errors.allergicReactions}
491
- invalidText={errors.allergicReactions?.message}
492
- legendText={t('selectReactions', 'Select the reactions')}
493
- >
494
- {allergicReactionsItems.map(({ id, labelText }) => (
495
- <Checkbox
496
- checked={Array.isArray(value) && value.includes(id)}
497
- className={styles.checkbox}
498
- id={id}
499
- key={id}
500
- labelText={labelText}
501
- onChange={(_, { checked, id }) => {
502
- const currentValue = Array.isArray(value) ? value : [];
503
- onChange(checked ? [...currentValue, id] : currentValue.filter((item) => item !== id));
504
- }}
505
- />
506
- ))}
507
- </CheckboxGroup>
508
- )}
509
- />
510
- )}
433
+ <FormGroup legendText="">
434
+ <Controller
435
+ name="allergen"
436
+ control={control}
437
+ render={({ field: { onChange, value } }) => (
438
+ <ComboBox
439
+ id="allergen"
440
+ invalid={!!errors.allergen}
441
+ invalidText={errors.allergen?.message}
442
+ itemToString={(item: unknown) => (item as Allergen)?.display}
443
+ items={allergenItems as unknown as Array<Record<string, unknown>>}
444
+ onChange={(props: { selectedItem?: unknown }) => {
445
+ if (typeof props?.selectedItem !== 'undefined') {
446
+ onChange(props.selectedItem as Allergen);
447
+ }
448
+ }}
449
+ readOnly={inEditMode}
450
+ placeholder={t('selectAllergen', 'Select the allergen')}
451
+ selectedItem={value as unknown as Allergen}
452
+ titleText={t('allergen', 'Allergen')}
453
+ />
454
+ )}
455
+ />
511
456
  </FormGroup>
512
- </div>
513
- {selectedAllergicReactions?.includes(otherConceptUuid) ? (
457
+ </ResponsiveWrapper>
458
+ {selectedAllergen?.uuid === otherConceptUuid && (
514
459
  <ResponsiveWrapper>
515
460
  <Controller
516
- name="nonCodedAllergicReaction"
461
+ name="nonCodedAllergen"
517
462
  control={control}
518
463
  render={({ field: { onBlur, onChange, value } }) => (
519
464
  <TextInput
520
- id="nonCodedAllergicReaction"
521
- invalid={!!errors.nonCodedAllergicReaction}
522
- invalidText={errors.nonCodedAllergicReaction?.message}
523
- labelText={t('otherNonCodedAllergicReaction', 'Other non-coded allergic reaction')}
465
+ id="nonCodedAllergen"
466
+ invalid={!!errors.nonCodedAllergen}
467
+ invalidText={errors.nonCodedAllergen?.message}
468
+ labelText={t('otherNonCodedAllergen', 'Other non-coded allergen')}
524
469
  onBlur={onBlur}
525
470
  onChange={onChange}
526
- placeholder={t('typeAllergicReactionName', 'Please type in the name of the allergic reaction')}
471
+ placeholder={t('typeAllergenName', 'Please type in the name of the allergen')}
472
+ readOnly={inEditMode}
527
473
  value={value}
528
474
  />
529
475
  )}
530
476
  />
531
477
  </ResponsiveWrapper>
532
- ) : null}
533
- </>
534
- <FormGroup legendText={t('severityOfWorstReaction', 'Severity of worst reaction')}>
535
- <Controller
536
- name="severityOfWorstReaction"
537
- control={control}
538
- render={({ field: { onBlur, onChange, value } }) => (
539
- <RadioButtonGroup
540
- name="severity-of-worst-reaction"
541
- invalid={!!errors.severityOfWorstReaction}
542
- invalidText={errors.severityOfWorstReaction?.message}
543
- onBlur={onBlur}
544
- onChange={(event) => onChange(event.toString())}
545
- valueSelected={value}
546
- >
547
- {severityLevels.map(({ key, display, uuid }) => (
548
- <RadioButton id={key} key={key} labelText={display} value={uuid} />
549
- ))}
550
- </RadioButtonGroup>
551
- )}
552
- />
553
- </FormGroup>
554
-
555
- <ResponsiveWrapper>
556
- <Controller
557
- name="comment"
558
- control={control}
559
- render={({ field: { onBlur, onChange, value } }) => (
560
- <TextArea
561
- id="comments"
562
- labelText={t('comments', 'Comments')}
563
- onChange={onChange}
564
- placeholder={t('typeAdditionalComments', 'Type any additional comments here')}
565
- onBlur={onBlur}
566
- value={value}
567
- rows={4}
568
- />
569
- )}
570
- />
571
- </ResponsiveWrapper>
572
- </Stack>
573
- )}
574
- <ButtonSet
575
- className={classNames(styles.actionButtons, isTablet ? styles.tabletButtons : styles.desktopButtons)}
576
- >
577
- <Button className={styles.button} onClick={() => closeWorkspace()} kind="secondary">
578
- {t('discard', 'Discard')}
579
- </Button>
580
- <Button
581
- className={styles.button}
582
- disabled={isSubmitting || (inEditMode && isLoadingReactions)}
583
- kind="primary"
584
- type="submit"
478
+ )}
479
+ <>
480
+ <div className={classNames({ [styles.checkboxContainer]: isTablet })}>
481
+ <FormGroup legendText="" data-testid="allergic-reactions-container">
482
+ {isLoadingReactions ? (
483
+ <>
484
+ {Array.from({ length: 10 }).map((_, index) => (
485
+ <CheckboxSkeleton key={`checkbox-skeleton-${index}`} />
486
+ ))}
487
+ </>
488
+ ) : (
489
+ <Controller
490
+ name="allergicReactions"
491
+ control={control}
492
+ render={({ field: { onChange, value } }) => (
493
+ <CheckboxGroup
494
+ invalid={!!errors.allergicReactions}
495
+ invalidText={errors.allergicReactions?.message}
496
+ legendText={t('selectReactions', 'Select the reactions')}
497
+ >
498
+ {allergicReactionsItems.map(({ id, labelText }) => (
499
+ <Checkbox
500
+ checked={Array.isArray(value) && value.includes(id)}
501
+ className={styles.checkbox}
502
+ id={id}
503
+ key={id}
504
+ labelText={labelText}
505
+ onChange={(_, { checked, id }) => {
506
+ const currentValue = Array.isArray(value) ? value : [];
507
+ onChange(
508
+ checked ? [...currentValue, id] : currentValue.filter((item) => item !== id),
509
+ );
510
+ }}
511
+ />
512
+ ))}
513
+ </CheckboxGroup>
514
+ )}
515
+ />
516
+ )}
517
+ </FormGroup>
518
+ </div>
519
+ {selectedAllergicReactions?.includes(otherConceptUuid) ? (
520
+ <ResponsiveWrapper>
521
+ <Controller
522
+ name="nonCodedAllergicReaction"
523
+ control={control}
524
+ render={({ field: { onBlur, onChange, value } }) => (
525
+ <TextInput
526
+ id="nonCodedAllergicReaction"
527
+ invalid={!!errors.nonCodedAllergicReaction}
528
+ invalidText={errors.nonCodedAllergicReaction?.message}
529
+ labelText={t('otherNonCodedAllergicReaction', 'Other non-coded allergic reaction')}
530
+ onBlur={onBlur}
531
+ onChange={onChange}
532
+ placeholder={t(
533
+ 'typeAllergicReactionName',
534
+ 'Please type in the name of the allergic reaction',
535
+ )}
536
+ value={value}
537
+ />
538
+ )}
539
+ />
540
+ </ResponsiveWrapper>
541
+ ) : null}
542
+ </>
543
+ <FormGroup legendText={t('severityOfWorstReaction', 'Severity of worst reaction')}>
544
+ <Controller
545
+ name="severityOfWorstReaction"
546
+ control={control}
547
+ render={({ field: { onBlur, onChange, value } }) => (
548
+ <RadioButtonGroup
549
+ name="severity-of-worst-reaction"
550
+ invalid={!!errors.severityOfWorstReaction}
551
+ invalidText={errors.severityOfWorstReaction?.message}
552
+ onBlur={onBlur}
553
+ onChange={(event) => onChange(event.toString())}
554
+ valueSelected={value}
555
+ >
556
+ {severityLevels.map(({ key, display, uuid }) => (
557
+ <RadioButton id={key} key={key} labelText={display} value={uuid} />
558
+ ))}
559
+ </RadioButtonGroup>
560
+ )}
561
+ />
562
+ </FormGroup>
563
+
564
+ <ResponsiveWrapper>
565
+ <Controller
566
+ name="comment"
567
+ control={control}
568
+ render={({ field: { onBlur, onChange, value } }) => (
569
+ <TextArea
570
+ id="comments"
571
+ labelText={t('comments', 'Comments')}
572
+ onChange={onChange}
573
+ placeholder={t('typeAdditionalComments', 'Type any additional comments here')}
574
+ onBlur={onBlur}
575
+ value={value}
576
+ rows={4}
577
+ />
578
+ )}
579
+ />
580
+ </ResponsiveWrapper>
581
+ </Stack>
582
+ )}
583
+ <ButtonSet
584
+ className={classNames(styles.actionButtons, isTablet ? styles.tabletButtons : styles.desktopButtons)}
585
585
  >
586
- {isSubmitting ? (
587
- <InlineLoading description={t('saving', 'Saving') + '...'} />
588
- ) : (
589
- <span>{t('saveAndClose', 'Save and close')}</span>
590
- )}
591
- </Button>
592
- </ButtonSet>
593
- </div>
594
- </Form>
586
+ <Button className={styles.button} onClick={() => closeWorkspace()} kind="secondary">
587
+ {t('discard', 'Discard')}
588
+ </Button>
589
+ <Button
590
+ className={styles.button}
591
+ disabled={isSubmitting || (inEditMode && isLoadingReactions)}
592
+ kind="primary"
593
+ type="submit"
594
+ >
595
+ {isSubmitting ? (
596
+ <InlineLoading description={t('saving', 'Saving') + '...'} />
597
+ ) : (
598
+ <span>{t('saveAndClose', 'Save and close')}</span>
599
+ )}
600
+ </Button>
601
+ </ButtonSet>
602
+ </div>
603
+ </Form>
604
+ </Workspace2>
595
605
  );
596
606
  }
597
607
 
598
- export default AllergyForm;
608
+ export default AllergyFormWorkspace;
@@ -13,7 +13,7 @@ import {
13
13
  TableRow,
14
14
  } from '@carbon/react';
15
15
  import { useTranslation } from 'react-i18next';
16
- import { AddIcon, launchWorkspace, useLayoutType, usePagination } from '@openmrs/esm-framework';
16
+ import { AddIcon, launchWorkspace2, useLayoutType, usePagination } from '@openmrs/esm-framework';
17
17
  import { CardHeader, EmptyState, ErrorState, PatientChartPagination } from '@openmrs/esm-patient-common-lib';
18
18
  import { allergiesCount, patientAllergiesFormWorkspace } from '../constants';
19
19
  import { useAllergies } from './allergy-intolerance.resource';
@@ -57,7 +57,7 @@ const AllergiesOverview: React.FC<AllergiesOverviewProps> = ({ patient }) => {
57
57
  }));
58
58
  }, [paginatedAllergies]);
59
59
 
60
- const launchAllergiesForm = useCallback(() => launchWorkspace(patientAllergiesFormWorkspace), []);
60
+ const launchAllergiesForm = useCallback(() => launchWorkspace2(patientAllergiesFormWorkspace), []);
61
61
 
62
62
  if (isLoading) {
63
63
  return <DataTableSkeleton role="progressbar" compact={isDesktop} zebra />;
package/src/index.ts CHANGED
@@ -51,7 +51,6 @@ export const allergiesDashboardLink = getSyncLifecycle(
51
51
  options,
52
52
  );
53
53
 
54
- // t('recordNewAllergy', "Record a new allergy")
55
54
  export const allergyFormWorkspace = getAsyncLifecycle(
56
55
  () => import('./allergies/allergies-form/allergy-form.workspace'),
57
56
  options,
package/src/routes.json CHANGED
@@ -44,12 +44,17 @@
44
44
  "component": "deleteAllergyModal"
45
45
  }
46
46
  ],
47
- "workspaces": [
47
+ "workspaces2": [
48
48
  {
49
49
  "name": "patient-allergy-form-workspace",
50
- "title": "recordNewAllergy",
51
50
  "component": "allergyFormWorkspace",
52
- "type": "form"
51
+ "window": "patient-allergy-form-window"
52
+ }
53
+ ],
54
+ "workspaceWindows2": [
55
+ {
56
+ "name": "patient-allergy-form-window",
57
+ "group": "patient-chart"
53
58
  }
54
59
  ]
55
60
  }