@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.
- package/.turbo/turbo-build.log +22 -19
- package/dist/2499.js +2 -0
- package/dist/2499.js.map +1 -0
- package/dist/4400.js +1 -0
- package/dist/4400.js.map +1 -0
- package/dist/5670.js +1 -0
- package/dist/5670.js.map +1 -0
- package/dist/6336.js +1 -0
- package/dist/6336.js.map +1 -0
- package/dist/6554.js +2 -0
- package/dist/6554.js.map +1 -0
- package/dist/8051.js +1 -1
- package/dist/8051.js.map +1 -1
- package/dist/9444.js +1 -0
- package/dist/9444.js.map +1 -0
- package/dist/9538.js +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-notes-app.js +1 -1
- package/dist/openmrs-esm-patient-notes-app.js.buildmanifest.json +168 -168
- package/dist/openmrs-esm-patient-notes-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +5 -4
- package/src/dashboard.meta.ts +3 -1
- package/src/index.ts +0 -1
- package/src/notes/notes-overview.extension.tsx +57 -3
- package/src/notes/visit-notes-form.test.tsx +32 -13
- package/src/notes/visit-notes-form.workspace.tsx +238 -241
- package/src/routes.json +11 -11
- package/src/visit-note-action-button.extension.tsx +15 -9
- package/src/visit-note-action-button.test.tsx +13 -30
- package/translations/it.json +1 -1
- package/dist/1433.js +0 -1
- package/dist/1433.js.map +0 -1
- package/dist/2356.js +0 -1
- package/dist/2356.js.map +0 -1
- package/dist/3691.js +0 -2
- package/dist/3691.js.map +0 -1
- package/dist/521.js +0 -2
- package/dist/521.js.map +0 -1
- package/dist/5639.js +0 -1
- package/dist/5639.js.map +0 -1
- package/dist/717.js +0 -1
- package/dist/717.js.map +0 -1
- package/src/notes/notes-main.component.tsx +0 -62
- package/src/notes/notes-main.test.tsx +0 -101
- /package/dist/{521.js.LICENSE.txt → 2499.js.LICENSE.txt} +0 -0
- /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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
505
|
-
<
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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": "
|
|
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
|
-
"
|
|
17
|
+
"workspaces2": [
|
|
24
18
|
{
|
|
25
19
|
"name": "visit-notes-form-workspace",
|
|
26
|
-
"title": "visitNoteWorkspaceTitle",
|
|
27
20
|
"component": "visitNotesFormWorkspace",
|
|
28
|
-
"
|
|
29
|
-
|
|
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 {
|
|
4
|
-
import {
|
|
3
|
+
import { ActionMenuButton2, PenIcon } from '@openmrs/esm-framework';
|
|
4
|
+
import { useStartVisitIfNeeded, type PatientChartWorkspaceActionButtonProps } from '@openmrs/esm-patient-common-lib';
|
|
5
5
|
|
|
6
|
-
|
|
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
|
|
13
|
+
const startVisitIfNeeded = useStartVisitIfNeeded(patientUuid);
|
|
10
14
|
|
|
11
15
|
return (
|
|
12
|
-
<
|
|
13
|
-
|
|
16
|
+
<ActionMenuButton2
|
|
17
|
+
icon={(props: ComponentProps<typeof PenIcon>) => <PenIcon {...props} />}
|
|
14
18
|
label={t('visitNote', 'Visit note')}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
workspaceToLaunch={{
|
|
20
|
+
workspaceName: 'visit-notes-form-workspace',
|
|
21
|
+
workspaceProps: {},
|
|
22
|
+
}}
|
|
23
|
+
onBeforeWorkspaceLaunch={startVisitIfNeeded}
|
|
18
24
|
/>
|
|
19
25
|
);
|
|
20
26
|
};
|