@openmrs/esm-patient-notes-app 11.3.0 → 11.3.1-patch.9310

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.
Files changed (48) hide show
  1. package/.turbo/turbo-build.log +22 -19
  2. package/dist/2499.js +2 -0
  3. package/dist/2499.js.map +1 -0
  4. package/dist/4400.js +1 -0
  5. package/dist/4400.js.map +1 -0
  6. package/dist/5670.js +1 -0
  7. package/dist/5670.js.map +1 -0
  8. package/dist/6336.js +1 -0
  9. package/dist/6336.js.map +1 -0
  10. package/dist/6554.js +2 -0
  11. package/dist/6554.js.map +1 -0
  12. package/dist/8051.js +1 -1
  13. package/dist/8051.js.map +1 -1
  14. package/dist/9444.js +1 -0
  15. package/dist/9444.js.map +1 -0
  16. package/dist/9538.js +1 -1
  17. package/dist/main.js +1 -1
  18. package/dist/main.js.map +1 -1
  19. package/dist/openmrs-esm-patient-notes-app.js +1 -1
  20. package/dist/openmrs-esm-patient-notes-app.js.buildmanifest.json +168 -168
  21. package/dist/openmrs-esm-patient-notes-app.js.map +1 -1
  22. package/dist/routes.json +1 -1
  23. package/package.json +5 -4
  24. package/src/dashboard.meta.ts +3 -1
  25. package/src/index.ts +0 -1
  26. package/src/notes/notes-overview.extension.tsx +57 -3
  27. package/src/notes/visit-notes-form.test.tsx +32 -13
  28. package/src/notes/visit-notes-form.workspace.tsx +238 -241
  29. package/src/routes.json +11 -11
  30. package/src/visit-note-action-button.extension.tsx +15 -9
  31. package/src/visit-note-action-button.test.tsx +13 -30
  32. package/translations/it.json +1 -1
  33. package/dist/1433.js +0 -1
  34. package/dist/1433.js.map +0 -1
  35. package/dist/2356.js +0 -1
  36. package/dist/2356.js.map +0 -1
  37. package/dist/3691.js +0 -2
  38. package/dist/3691.js.map +0 -1
  39. package/dist/521.js +0 -2
  40. package/dist/521.js.map +0 -1
  41. package/dist/5639.js +0 -1
  42. package/dist/5639.js.map +0 -1
  43. package/dist/717.js +0 -1
  44. package/dist/717.js.map +0 -1
  45. package/src/notes/notes-main.component.tsx +0 -62
  46. package/src/notes/notes-main.test.tsx +0 -101
  47. /package/dist/{521.js.LICENSE.txt → 2499.js.LICENSE.txt} +0 -0
  48. /package/dist/{3691.js.LICENSE.txt → 6554.js.LICENSE.txt} +0 -0
@@ -2,7 +2,8 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
2
  import classnames from 'classnames';
3
3
  import dayjs from 'dayjs';
4
4
  import { debounce } from 'lodash-es';
5
- import { useTranslation, type TFunction } from 'react-i18next';
5
+ import { useTranslation } from 'react-i18next';
6
+ import type { TFunction } from 'i18next';
6
7
  import { useSWRConfig } from 'swr';
7
8
  import { z } from 'zod';
8
9
  import { zodResolver } from '@hookform/resolvers/zod';
@@ -36,13 +37,14 @@ import {
36
37
  useConfig,
37
38
  useLayoutType,
38
39
  useSession,
40
+ Workspace2,
39
41
  type Encounter,
40
42
  type UploadedFile,
41
43
  } from '@openmrs/esm-framework';
42
44
  import {
43
45
  invalidateVisitAndEncounterData,
46
+ type PatientWorkspace2DefinitionProps,
44
47
  useAllowedFileExtensions,
45
- type DefaultPatientWorkspaceProps,
46
48
  } from '@openmrs/esm-patient-common-lib';
47
49
  import type { ConfigObject } from '../config-schema';
48
50
  import type { Concept, Diagnosis, DiagnosisPayload, VisitNotePayload } from '../types';
@@ -91,18 +93,15 @@ const createSchema = (t: TFunction) => {
91
93
  });
92
94
  };
93
95
 
94
- interface VisitNotesFormProps extends DefaultPatientWorkspaceProps {
96
+ export interface VisitNotesFormProps {
95
97
  encounter?: Encounter;
96
98
  formContext: 'creating' | 'editing';
97
99
  }
98
100
 
99
- const VisitNotesForm: React.FC<VisitNotesFormProps> = ({
101
+ const VisitNotesForm: React.FC<PatientWorkspace2DefinitionProps<VisitNotesFormProps, {}>> = ({
100
102
  closeWorkspace,
101
- closeWorkspaceWithSavedChanges,
102
- patientUuid,
103
- promptBeforeClosing,
104
- encounter,
105
- formContext = 'creating',
103
+ workspaceProps: { formContext, encounter },
104
+ groupProps: { patientUuid },
106
105
  }) => {
107
106
  const isEditing: boolean = Boolean(formContext === 'editing' && encounter?.id);
108
107
  const searchTimeoutInMs = 500;
@@ -167,10 +166,6 @@ const VisitNotesForm: React.FC<VisitNotesFormProps> = ({
167
166
  },
168
167
  });
169
168
 
170
- useEffect(() => {
171
- promptBeforeClosing(() => isDirty);
172
- }, [isDirty, promptBeforeClosing]);
173
-
174
169
  useEffect(() => {
175
170
  if (encounter?.diagnoses?.length) {
176
171
  try {
@@ -456,7 +451,7 @@ const VisitNotesForm: React.FC<VisitNotesFormProps> = ({
456
451
  mutateAttachments();
457
452
  }
458
453
 
459
- closeWorkspaceWithSavedChanges();
454
+ closeWorkspace({ discardUnsavedChanges: true });
460
455
 
461
456
  showSnackbar({
462
457
  isLowContrast: true,
@@ -478,7 +473,7 @@ const VisitNotesForm: React.FC<VisitNotesFormProps> = ({
478
473
  },
479
474
  [
480
475
  clinicianEncounterRole,
481
- closeWorkspaceWithSavedChanges,
476
+ closeWorkspace,
482
477
  combinedDiagnoses,
483
478
  encounter?.diagnoses,
484
479
  encounter?.id,
@@ -501,240 +496,242 @@ const VisitNotesForm: React.FC<VisitNotesFormProps> = ({
501
496
  const onError = (errors) => console.error(errors);
502
497
 
503
498
  return (
504
- <Form className={styles.form} onSubmit={handleSubmit(onSubmit, onError)}>
505
- <ExtensionSlot name="visit-context-header-slot" state={{ patientUuid }} />
506
-
507
- {isTablet && (
508
- <Row className={styles.headerGridRow}>
509
- <ExtensionSlot name="visit-form-header-slot" className={styles.dataGridRow} state={memoizedState} />
510
- </Row>
511
- )}
499
+ <Workspace2 title={t('visitNoteWorkspaceTitle', 'Visit note')} hasUnsavedChanges={isDirty}>
500
+ <Form className={styles.form} onSubmit={handleSubmit(onSubmit, onError)}>
501
+ <ExtensionSlot name="visit-context-header-slot" state={{ patientUuid }} />
512
502
 
513
- <div className={styles.formContainer}>
514
- <Stack gap={2}>
515
- {isTablet ? <h2 className={styles.heading}>{t('addVisitNote', 'Add a visit note')}</h2> : null}
516
- <Row className={styles.row}>
517
- <Column sm={1}>
518
- <span className={styles.columnLabel}>{t('date', 'Date')}</span>
519
- </Column>
520
- <Column sm={3}>
521
- <Controller
522
- name="noteDate"
523
- control={control}
524
- render={({ field, fieldState }) => (
525
- <ResponsiveWrapper>
526
- <OpenmrsDatePicker
527
- {...field}
528
- data-testid="visitDateTimePicker"
529
- id="visitDateTimePicker"
530
- invalid={Boolean(fieldState?.error?.message)}
531
- invalidText={fieldState?.error?.message}
532
- isDisabled={isEditing}
533
- labelText={t('visitDate', 'Visit date')}
534
- maxDate={new Date()}
535
- />
536
- </ResponsiveWrapper>
537
- )}
538
- />
539
- </Column>
503
+ {isTablet && (
504
+ <Row className={styles.headerGridRow}>
505
+ <ExtensionSlot name="visit-form-header-slot" className={styles.dataGridRow} state={memoizedState} />
540
506
  </Row>
541
- <div className={styles.diagnosesText}>
542
- {selectedPrimaryDiagnoses && selectedPrimaryDiagnoses.length ? (
543
- <>
544
- {selectedPrimaryDiagnoses.map((diagnosis, index) => (
545
- <Tag
546
- className={styles.tag}
547
- filter
548
- key={index}
549
- onClose={() => handleRemoveDiagnosis(diagnosis, 'primaryInputSearch')}
550
- type="red"
551
- >
552
- {diagnosis.display}
553
- </Tag>
554
- ))}
555
- </>
556
- ) : null}
557
- {selectedSecondaryDiagnoses && selectedSecondaryDiagnoses.length ? (
558
- <>
559
- {selectedSecondaryDiagnoses.map((diagnosis, index) => (
560
- <Tag
561
- className={styles.tag}
562
- filter
563
- key={index}
564
- onClose={() => handleRemoveDiagnosis(diagnosis, 'secondaryInputSearch')}
565
- type="blue"
566
- >
567
- {diagnosis.display}
568
- </Tag>
569
- ))}
570
- </>
571
- ) : null}
572
- {selectedPrimaryDiagnoses &&
573
- !selectedPrimaryDiagnoses.length &&
574
- selectedSecondaryDiagnoses &&
575
- !selectedSecondaryDiagnoses.length && (
576
- <span>{t('emptyDiagnosisText', 'No diagnosis selected — Enter a diagnosis below')}</span>
577
- )}
578
- </div>
579
- <Row className={styles.row}>
580
- <Column sm={1}>
581
- <span className={styles.columnLabel}>{t('primaryDiagnosis', 'Primary diagnosis')}</span>
582
- </Column>
583
- <Column sm={3}>
584
- <FormGroup legendText={t('searchForPrimaryDiagnosis', 'Search for a primary diagnosis')}>
585
- <DiagnosisSearch
586
- name="primaryDiagnosisSearch"
507
+ )}
508
+
509
+ <div className={styles.formContainer}>
510
+ <Stack gap={2}>
511
+ {isTablet ? <h2 className={styles.heading}>{t('addVisitNote', 'Add a visit note')}</h2> : null}
512
+ <Row className={styles.row}>
513
+ <Column sm={1}>
514
+ <span className={styles.columnLabel}>{t('date', 'Date')}</span>
515
+ </Column>
516
+ <Column sm={3}>
517
+ <Controller
518
+ name="noteDate"
587
519
  control={control}
588
- labelText={t('enterPrimaryDiagnoses', 'Enter Primary diagnoses')}
589
- placeholder={t('primaryDiagnosisInputPlaceholder', 'Choose a primary diagnosis')}
590
- handleSearch={handleSearch}
591
- error={errors?.primaryDiagnosisSearch}
592
- setIsSearching={setIsSearching}
520
+ render={({ field, fieldState }) => (
521
+ <ResponsiveWrapper>
522
+ <OpenmrsDatePicker
523
+ {...field}
524
+ data-testid="visitDateTimePicker"
525
+ id="visitDateTimePicker"
526
+ invalid={Boolean(fieldState?.error?.message)}
527
+ invalidText={fieldState?.error?.message}
528
+ isDisabled={isEditing}
529
+ labelText={t('visitDate', 'Visit date')}
530
+ maxDate={new Date()}
531
+ />
532
+ </ResponsiveWrapper>
533
+ )}
593
534
  />
594
- {error ? (
595
- <InlineNotification
596
- className={styles.errorNotification}
597
- lowContrast
598
- title={t('error', 'Error')}
599
- subtitle={t('errorFetchingConcepts', 'There was a problem fetching concepts') + '.'}
600
- onClose={() => setError(null)}
535
+ </Column>
536
+ </Row>
537
+ <div className={styles.diagnosesText}>
538
+ {selectedPrimaryDiagnoses && selectedPrimaryDiagnoses.length ? (
539
+ <>
540
+ {selectedPrimaryDiagnoses.map((diagnosis, index) => (
541
+ <Tag
542
+ className={styles.tag}
543
+ filter
544
+ key={index}
545
+ onClose={() => handleRemoveDiagnosis(diagnosis, 'primaryInputSearch')}
546
+ type="red"
547
+ >
548
+ {diagnosis.display}
549
+ </Tag>
550
+ ))}
551
+ </>
552
+ ) : null}
553
+ {selectedSecondaryDiagnoses && selectedSecondaryDiagnoses.length ? (
554
+ <>
555
+ {selectedSecondaryDiagnoses.map((diagnosis, index) => (
556
+ <Tag
557
+ className={styles.tag}
558
+ filter
559
+ key={index}
560
+ onClose={() => handleRemoveDiagnosis(diagnosis, 'secondaryInputSearch')}
561
+ type="blue"
562
+ >
563
+ {diagnosis.display}
564
+ </Tag>
565
+ ))}
566
+ </>
567
+ ) : null}
568
+ {selectedPrimaryDiagnoses &&
569
+ !selectedPrimaryDiagnoses.length &&
570
+ selectedSecondaryDiagnoses &&
571
+ !selectedSecondaryDiagnoses.length && (
572
+ <span>{t('emptyDiagnosisText', 'No diagnosis selected — Enter a diagnosis below')}</span>
573
+ )}
574
+ </div>
575
+ <Row className={styles.row}>
576
+ <Column sm={1}>
577
+ <span className={styles.columnLabel}>{t('primaryDiagnosis', 'Primary diagnosis')}</span>
578
+ </Column>
579
+ <Column sm={3}>
580
+ <FormGroup legendText={t('searchForPrimaryDiagnosis', 'Search for a primary diagnosis')}>
581
+ <DiagnosisSearch
582
+ name="primaryDiagnosisSearch"
583
+ control={control}
584
+ labelText={t('enterPrimaryDiagnoses', 'Enter Primary diagnoses')}
585
+ placeholder={t('primaryDiagnosisInputPlaceholder', 'Choose a primary diagnosis')}
586
+ handleSearch={handleSearch}
587
+ error={errors?.primaryDiagnosisSearch}
588
+ setIsSearching={setIsSearching}
601
589
  />
602
- ) : null}
603
- <DiagnosesDisplay
604
- fieldName={'primaryDiagnosisSearch'}
605
- isDiagnosisNotSelected={isDiagnosisNotSelected}
606
- isLoading={isLoadingPrimaryDiagnoses}
607
- isSearching={isSearching}
608
- onAddDiagnosis={handleAddDiagnosis}
609
- searchResults={searchPrimaryResults}
610
- t={t}
611
- value={watch('primaryDiagnosisSearch')}
612
- />
613
- </FormGroup>
614
- </Column>
615
- </Row>
616
- <Row className={styles.row}>
617
- <Column sm={1}>
618
- <span className={styles.columnLabel}>{t('secondaryDiagnosis', 'Secondary diagnosis')}</span>
619
- </Column>
620
- <Column sm={3}>
621
- <FormGroup legendText={t('searchForSecondaryDiagnosis', 'Search for a secondary diagnosis')}>
622
- <DiagnosisSearch
623
- name="secondaryDiagnosisSearch"
624
- control={control}
625
- labelText={t('enterSecondaryDiagnoses', 'Enter Secondary diagnoses')}
626
- placeholder={t('secondaryDiagnosisInputPlaceholder', 'Choose a secondary diagnosis')}
627
- handleSearch={handleSearch}
628
- setIsSearching={setIsSearching}
629
- />
630
- {error ? (
631
- <InlineNotification
632
- className={styles.errorNotification}
633
- lowContrast
634
- title={t('error', 'Error')}
635
- subtitle={t('errorFetchingConcepts', 'There was a problem fetching concepts') + '.'}
636
- onClose={() => setError(null)}
590
+ {error ? (
591
+ <InlineNotification
592
+ className={styles.errorNotification}
593
+ lowContrast
594
+ title={t('error', 'Error')}
595
+ subtitle={t('errorFetchingConcepts', 'There was a problem fetching concepts') + '.'}
596
+ onClose={() => setError(null)}
597
+ />
598
+ ) : null}
599
+ <DiagnosesDisplay
600
+ fieldName={'primaryDiagnosisSearch'}
601
+ isDiagnosisNotSelected={isDiagnosisNotSelected}
602
+ isLoading={isLoadingPrimaryDiagnoses}
603
+ isSearching={isSearching}
604
+ onAddDiagnosis={handleAddDiagnosis}
605
+ searchResults={searchPrimaryResults}
606
+ t={t}
607
+ value={watch('primaryDiagnosisSearch')}
637
608
  />
638
- ) : null}
639
- <DiagnosesDisplay
640
- fieldName={'secondaryDiagnosisSearch'}
641
- isDiagnosisNotSelected={isDiagnosisNotSelected}
642
- isLoading={isLoadingSecondaryDiagnoses}
643
- isSearching={isSearching}
644
- onAddDiagnosis={handleAddDiagnosis}
645
- searchResults={searchSecondaryResults}
646
- t={t}
647
- value={watch('secondaryDiagnosisSearch')}
648
- />
649
- </FormGroup>
650
- </Column>
651
- </Row>
652
- <Row className={styles.row}>
653
- <Column sm={1}>
654
- <span className={styles.columnLabel}>{t('note', 'Note')}</span>
655
- </Column>
656
- <Column sm={3}>
657
- <Controller
658
- name="clinicalNote"
659
- control={control}
660
- render={({ field: { onChange, onBlur, value } }) => (
661
- <ResponsiveWrapper>
662
- <TextArea
663
- id="additionalNote"
664
- rows={rows}
665
- labelText={t('clinicalNoteLabel', 'Write your notes')}
666
- placeholder={t('clinicalNotePlaceholder', 'Write any notes here')}
667
- value={value}
668
- onBlur={onBlur}
669
- onChange={(event) => {
670
- onChange(event);
671
- const textareaLineHeight = 24; // This is the default line height for Carbon's TextArea component
672
- const newRows = Math.ceil(event.target.scrollHeight / textareaLineHeight);
673
- setRows(newRows);
674
- }}
609
+ </FormGroup>
610
+ </Column>
611
+ </Row>
612
+ <Row className={styles.row}>
613
+ <Column sm={1}>
614
+ <span className={styles.columnLabel}>{t('secondaryDiagnosis', 'Secondary diagnosis')}</span>
615
+ </Column>
616
+ <Column sm={3}>
617
+ <FormGroup legendText={t('searchForSecondaryDiagnosis', 'Search for a secondary diagnosis')}>
618
+ <DiagnosisSearch
619
+ name="secondaryDiagnosisSearch"
620
+ control={control}
621
+ labelText={t('enterSecondaryDiagnoses', 'Enter Secondary diagnoses')}
622
+ placeholder={t('secondaryDiagnosisInputPlaceholder', 'Choose a secondary diagnosis')}
623
+ handleSearch={handleSearch}
624
+ setIsSearching={setIsSearching}
625
+ />
626
+ {error ? (
627
+ <InlineNotification
628
+ className={styles.errorNotification}
629
+ lowContrast
630
+ title={t('error', 'Error')}
631
+ subtitle={t('errorFetchingConcepts', 'There was a problem fetching concepts') + '.'}
632
+ onClose={() => setError(null)}
675
633
  />
676
- </ResponsiveWrapper>
677
- )}
678
- />
679
- </Column>
680
- </Row>
681
- <Row className={styles.row}>
682
- <Column sm={1}>
683
- <span className={styles.columnLabel}>{t('image', 'Image')}</span>
684
- </Column>
685
- <Column sm={3}>
686
- <FormGroup legendText="">
687
- <p className={styles.imgUploadHelperText}>
688
- {t('imageUploadHelperText', "Upload images or use this device's camera to capture images")}
689
- </p>
690
- <Button
691
- className={styles.uploadButton}
692
- kind={isTablet ? 'ghost' : 'tertiary'}
693
- onClick={showImageCaptureModal}
694
- renderIcon={(props) => <Add size={16} {...props} />}
695
- >
696
- {t('addImage', 'Add image')}
697
- </Button>
698
- <div className={styles.imgThumbnailGrid}>
699
- {currentImages?.map((image, index) => (
700
- <div key={index} className={styles.imgThumbnailItem}>
701
- <div className={styles.imgThumbnailContainer}>
702
- <img
703
- className={styles.imgThumbnail}
704
- src={image.base64Content}
705
- alt={image.fileDescription ?? image.fileName}
706
- />
634
+ ) : null}
635
+ <DiagnosesDisplay
636
+ fieldName={'secondaryDiagnosisSearch'}
637
+ isDiagnosisNotSelected={isDiagnosisNotSelected}
638
+ isLoading={isLoadingSecondaryDiagnoses}
639
+ isSearching={isSearching}
640
+ onAddDiagnosis={handleAddDiagnosis}
641
+ searchResults={searchSecondaryResults}
642
+ t={t}
643
+ value={watch('secondaryDiagnosisSearch')}
644
+ />
645
+ </FormGroup>
646
+ </Column>
647
+ </Row>
648
+ <Row className={styles.row}>
649
+ <Column sm={1}>
650
+ <span className={styles.columnLabel}>{t('note', 'Note')}</span>
651
+ </Column>
652
+ <Column sm={3}>
653
+ <Controller
654
+ name="clinicalNote"
655
+ control={control}
656
+ render={({ field: { onChange, onBlur, value } }) => (
657
+ <ResponsiveWrapper>
658
+ <TextArea
659
+ id="additionalNote"
660
+ rows={rows}
661
+ labelText={t('clinicalNoteLabel', 'Write your notes')}
662
+ placeholder={t('clinicalNotePlaceholder', 'Write any notes here')}
663
+ value={value}
664
+ onBlur={onBlur}
665
+ onChange={(event) => {
666
+ onChange(event);
667
+ const textareaLineHeight = 24; // This is the default line height for Carbon's TextArea component
668
+ const newRows = Math.ceil(event.target.scrollHeight / textareaLineHeight);
669
+ setRows(newRows);
670
+ }}
671
+ />
672
+ </ResponsiveWrapper>
673
+ )}
674
+ />
675
+ </Column>
676
+ </Row>
677
+ <Row className={styles.row}>
678
+ <Column sm={1}>
679
+ <span className={styles.columnLabel}>{t('image', 'Image')}</span>
680
+ </Column>
681
+ <Column sm={3}>
682
+ <FormGroup legendText="">
683
+ <p className={styles.imgUploadHelperText}>
684
+ {t('imageUploadHelperText', "Upload images or use this device's camera to capture images")}
685
+ </p>
686
+ <Button
687
+ className={styles.uploadButton}
688
+ kind={isTablet ? 'ghost' : 'tertiary'}
689
+ onClick={showImageCaptureModal}
690
+ renderIcon={(props) => <Add size={16} {...props} />}
691
+ >
692
+ {t('addImage', 'Add image')}
693
+ </Button>
694
+ <div className={styles.imgThumbnailGrid}>
695
+ {currentImages?.map((image, index) => (
696
+ <div key={index} className={styles.imgThumbnailItem}>
697
+ <div className={styles.imgThumbnailContainer}>
698
+ <img
699
+ className={styles.imgThumbnail}
700
+ src={image.base64Content}
701
+ alt={image.fileDescription ?? image.fileName}
702
+ />
703
+ </div>
704
+ <Button kind="ghost" className={styles.removeButton} onClick={() => handleRemoveImage(index)}>
705
+ <CloseFilled size={16} className={styles.closeIcon} />
706
+ </Button>
707
707
  </div>
708
- <Button kind="ghost" className={styles.removeButton} onClick={() => handleRemoveImage(index)}>
709
- <CloseFilled size={16} className={styles.closeIcon} />
710
- </Button>
711
- </div>
712
- ))}
713
- </div>
714
- </FormGroup>
715
- </Column>
716
- </Row>
717
- </Stack>
718
- </div>
719
- <ButtonSet className={classnames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
720
- <Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
721
- {t('discard', 'Discard')}
722
- </Button>
723
- <Button
724
- className={styles.button}
725
- kind="primary"
726
- onClick={() => handleSubmit}
727
- disabled={isSubmitting}
728
- type="submit"
729
- >
730
- {isSubmitting ? (
731
- <InlineLoading description={t('saving', 'Saving') + '...'} />
732
- ) : (
733
- <span>{t('saveAndClose', 'Save and close')}</span>
734
- )}
735
- </Button>
736
- </ButtonSet>
737
- </Form>
708
+ ))}
709
+ </div>
710
+ </FormGroup>
711
+ </Column>
712
+ </Row>
713
+ </Stack>
714
+ </div>
715
+ <ButtonSet className={classnames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
716
+ <Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
717
+ {t('discard', 'Discard')}
718
+ </Button>
719
+ <Button
720
+ className={styles.button}
721
+ kind="primary"
722
+ onClick={() => handleSubmit}
723
+ disabled={isSubmitting}
724
+ type="submit"
725
+ >
726
+ {isSubmitting ? (
727
+ <InlineLoading description={t('saving', 'Saving') + '...'} />
728
+ ) : (
729
+ <span>{t('saveAndClose', 'Save and close')}</span>
730
+ )}
731
+ </Button>
732
+ </ButtonSet>
733
+ </Form>
734
+ </Workspace2>
738
735
  );
739
736
  };
740
737
 
package/src/routes.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.openmrs.org/routes.schema.json",
3
3
  "backendDependencies": {
4
4
  "fhir2": ">=1.2",
5
- "webservices.rest": "^2.2.0"
5
+ "webservices.rest": ">=2.2.0"
6
6
  },
7
7
  "extensions": [
8
8
  {
@@ -12,21 +12,21 @@
12
12
  "fullWidth": false
13
13
  },
14
14
  "order": 5
15
- },
16
- {
17
- "name": "visit-note-nav-button",
18
- "component": "visitNotesActionButton",
19
- "slot": "action-menu-patient-chart-items-slot",
20
- "order": 1
21
15
  }
22
16
  ],
23
- "workspaces": [
17
+ "workspaces2": [
24
18
  {
25
19
  "name": "visit-notes-form-workspace",
26
- "title": "visitNoteWorkspaceTitle",
27
20
  "component": "visitNotesFormWorkspace",
28
- "type": "visit-note",
29
- "canHide": true
21
+ "window": "visit-note"
22
+ }
23
+ ],
24
+ "workspaceWindows2": [
25
+ {
26
+ "name": "visit-note",
27
+ "icon": "visitNotesActionButton",
28
+ "group": "patient-chart",
29
+ "order": 2
30
30
  }
31
31
  ]
32
32
  }
@@ -1,20 +1,26 @@
1
1
  import React, { type ComponentProps } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
- import { ActionMenuButton, PenIcon } from '@openmrs/esm-framework';
4
- import { useLaunchWorkspaceRequiringVisit } from '@openmrs/esm-patient-common-lib';
3
+ import { ActionMenuButton2, PenIcon } from '@openmrs/esm-framework';
4
+ import { useStartVisitIfNeeded, type PatientChartWorkspaceActionButtonProps } from '@openmrs/esm-patient-common-lib';
5
5
 
6
- const VisitNoteActionButton: React.FC = () => {
6
+ /**
7
+ * This button uses the patient chart store and MUST only be used
8
+ * within the patient chart
9
+ */
10
+ const VisitNoteActionButton: React.FC<PatientChartWorkspaceActionButtonProps> = ({ groupProps: { patientUuid } }) => {
7
11
  const { t } = useTranslation();
8
12
 
9
- const launchVisitNotesWorkspace = useLaunchWorkspaceRequiringVisit('visit-notes-form-workspace');
13
+ const startVisitIfNeeded = useStartVisitIfNeeded(patientUuid);
10
14
 
11
15
  return (
12
- <ActionMenuButton
13
- getIcon={(props: ComponentProps<typeof PenIcon>) => <PenIcon {...props} />}
16
+ <ActionMenuButton2
17
+ icon={(props: ComponentProps<typeof PenIcon>) => <PenIcon {...props} />}
14
18
  label={t('visitNote', 'Visit note')}
15
- iconDescription={t('note', 'Note')}
16
- handler={launchVisitNotesWorkspace}
17
- type={'visit-note'}
19
+ workspaceToLaunch={{
20
+ workspaceName: 'visit-notes-form-workspace',
21
+ workspaceProps: {},
22
+ }}
23
+ onBeforeWorkspaceLaunch={startVisitIfNeeded}
18
24
  />
19
25
  );
20
26
  };