@openmrs/esm-ward-app 9.2.1-pre.7254 → 9.2.1-pre.7261
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 +8 -8
- package/dist/1741.js +1 -1
- package/dist/1741.js.map +1 -1
- package/dist/1987.js +1 -0
- package/dist/1987.js.map +1 -0
- package/dist/2216.js +1 -0
- package/dist/2216.js.map +1 -0
- package/dist/2728.js +1 -1
- package/dist/2728.js.map +1 -1
- package/dist/283.js +1 -1
- package/dist/283.js.map +1 -1
- package/dist/2948.js +1 -1
- package/dist/2948.js.map +1 -1
- package/dist/3365.js +1 -1
- package/dist/3365.js.map +1 -1
- package/dist/3413.js +1 -1
- package/dist/3413.js.map +1 -1
- package/dist/3673.js +1 -0
- package/dist/3673.js.map +1 -0
- package/dist/3982.js +1 -1
- package/dist/3982.js.map +1 -1
- package/dist/4189.js +1 -0
- package/dist/4189.js.map +1 -0
- package/dist/4300.js +1 -1
- package/dist/5603.js +1 -0
- package/dist/5603.js.map +1 -0
- package/dist/581.js +1 -1
- package/dist/581.js.map +1 -1
- package/dist/7179.js +1 -1
- package/dist/7179.js.map +1 -1
- package/dist/7512.js +1 -1
- package/dist/7512.js.map +1 -1
- package/dist/7661.js +1 -1
- package/dist/7661.js.map +1 -1
- package/dist/8501.js +1 -1
- package/dist/8501.js.map +1 -1
- package/dist/8522.js +1 -1
- package/dist/8522.js.map +1 -1
- package/dist/8610.js +1 -1
- package/dist/8610.js.map +1 -1
- package/dist/89.js +2 -1
- package/dist/89.js.map +1 -1
- package/dist/9117.js +1 -1
- package/dist/9117.js.map +1 -1
- package/dist/917.js +1 -0
- package/dist/917.js.map +1 -0
- package/dist/9756.js +1 -0
- package/dist/9756.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-ward-app.js +1 -1
- package/dist/openmrs-esm-ward-app.js.buildmanifest.json +226 -177
- package/dist/openmrs-esm-ward-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx +16 -24
- package/src/action-menu-buttons/discharge-workspace-siderail.component.tsx +6 -6
- package/src/action-menu-buttons/order-basket-action-button.component.tsx +31 -0
- package/src/action-menu-buttons/transfer-workspace-siderail.component.tsx +7 -18
- package/src/hooks/useEmrConfiguration.ts +19 -19
- package/src/index.ts +14 -16
- package/src/routes.json +127 -80
- package/src/types/index.ts +7 -3
- package/src/ward-patient-card/row-elements/ward-patient-pending-transfer.component.tsx +9 -4
- package/src/ward-patient-card/ward-patient-card.component.tsx +3 -11
- package/src/ward-view-header/admission-requests-bar.component.tsx +10 -6
- package/src/ward-view-header/admission-requests-bar.test.tsx +3 -3
- package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +8 -6
- package/src/ward-workspace/admission-request-workspace/admission-requests-action-button.extension.tsx +6 -7
- package/src/ward-workspace/admission-request-workspace/admission-requests-empty-state.component.tsx +16 -29
- package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +23 -8
- package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +28 -28
- package/src/ward-workspace/admit-patient-button.component.tsx +3 -2
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +17 -16
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +72 -69
- package/src/ward-workspace/cancel-admission-request-workspace/cancel-admission-request.component.tsx +176 -0
- package/src/ward-workspace/cancel-admission-request-workspace/cancel-admission-request.test.tsx +11 -9
- package/src/ward-workspace/cancel-admission-request-workspace/cancel-admission-request.workspace.tsx +17 -167
- package/src/ward-workspace/cancel-admission-request-workspace/ward-patient-cancel-admission-request.workspace.tsx +16 -0
- package/src/ward-workspace/create-admission-encounter/create-admission-encounter-action-button.extension.tsx +23 -34
- package/src/ward-workspace/create-admission-encounter/create-admission-encounter.test.tsx +9 -4
- package/src/ward-workspace/create-admission-encounter/create-admission-encounter.workspace.tsx +39 -19
- package/src/ward-workspace/patient-details/ward-patient-action-button.component.tsx +17 -0
- package/src/ward-workspace/patient-details/ward-patient.workspace.tsx +27 -7
- package/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx +46 -40
- package/src/ward-workspace/patient-transfer-bed-swap/patient-admit-or-transfer-request-form.component.tsx +21 -13
- package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +10 -14
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace.tsx +42 -24
- package/src/ward-workspace/patient-transfer-request-workspace/patient-transfer-request.workspace.tsx +22 -8
- package/src/ward-workspace/ward-patient-notes/notes-action-button.component.tsx +17 -0
- package/src/ward-workspace/ward-patient-notes/{form/notes-form.scss → notes.scss} +0 -1
- package/src/ward-workspace/ward-patient-notes/notes.test.tsx +134 -0
- package/src/ward-workspace/ward-patient-notes/notes.workspace.tsx +174 -13
- package/translations/en.json +3 -1
- package/dist/1663.js +0 -1
- package/dist/1663.js.map +0 -1
- package/dist/2557.js +0 -1
- package/dist/2557.js.map +0 -1
- package/dist/7232.js +0 -2
- package/dist/7232.js.map +0 -1
- package/dist/7886.js +0 -1
- package/dist/7886.js.map +0 -1
- package/dist/9045.js +0 -1
- package/dist/9045.js.map +0 -1
- package/src/ward-workspace/admission-request-workspace/admission-requests-context.ts +0 -20
- package/src/ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace.tsx +0 -29
- package/src/ward-workspace/patient-details/ward-patient-action-button.extension.tsx +0 -18
- package/src/ward-workspace/ward-patient-notes/form/notes-form.component.tsx +0 -186
- package/src/ward-workspace/ward-patient-notes/form/notes-form.test.tsx +0 -116
- package/src/ward-workspace/ward-patient-notes/notes-action-button.extension.tsx +0 -18
- /package/dist/{7232.js.LICENSE.txt → 89.js.LICENSE.txt} +0 -0
|
@@ -11,19 +11,32 @@ import {
|
|
|
11
11
|
TextArea,
|
|
12
12
|
} from '@carbon/react';
|
|
13
13
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
14
|
-
import { ResponsiveWrapper, showSnackbar, useAppContext } from '@openmrs/esm-framework';
|
|
14
|
+
import { ResponsiveWrapper, showSnackbar, useAppContext, Workspace2 } from '@openmrs/esm-framework';
|
|
15
15
|
import classNames from 'classnames';
|
|
16
16
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
17
17
|
import { Controller, useForm } from 'react-hook-form';
|
|
18
18
|
import { useTranslation } from 'react-i18next';
|
|
19
19
|
import { z } from 'zod';
|
|
20
20
|
import LocationSelector from '../../location-selector/location-selector.component';
|
|
21
|
-
import type { ObsPayload, WardPatientWorkspaceProps, WardViewContext } from '../../types';
|
|
21
|
+
import type { ObsPayload, WardPatient, WardPatientWorkspaceProps, WardViewContext } from '../../types';
|
|
22
22
|
import { useCreateEncounter } from '../../ward.resource';
|
|
23
23
|
import styles from './patient-transfer-swap.scss';
|
|
24
24
|
import WardPatientName from '../../ward-patient-card/row-elements/ward-patient-name.component';
|
|
25
25
|
import WardPatientIdentifier from '../../ward-patient-card/row-elements/ward-patient-identifier.component';
|
|
26
26
|
|
|
27
|
+
export interface PatientAdmitOrTransferFormProps {
|
|
28
|
+
wardPatient: WardPatient;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Related patients that are in the same bed as wardPatient. On transfer or bed swap
|
|
32
|
+
* these related patients have the option to be transferred / swapped together
|
|
33
|
+
*/
|
|
34
|
+
relatedTransferPatients?: WardPatient[];
|
|
35
|
+
|
|
36
|
+
onSuccess(): void;
|
|
37
|
+
onCancel(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
/**
|
|
28
41
|
* Form to fill out for:
|
|
29
42
|
* - an admitted patient without pending transfer request, to initiate a transfer request for a patient
|
|
@@ -31,11 +44,11 @@ import WardPatientIdentifier from '../../ward-patient-card/row-elements/ward-pat
|
|
|
31
44
|
* - an un-admitted patient, to create a request to admit
|
|
32
45
|
*/
|
|
33
46
|
export default function PatientAdmitOrTransferForm({
|
|
34
|
-
closeWorkspaceWithSavedChanges,
|
|
35
47
|
wardPatient,
|
|
36
|
-
promptBeforeClosing,
|
|
37
48
|
relatedTransferPatients = [],
|
|
38
|
-
|
|
49
|
+
onSuccess,
|
|
50
|
+
onCancel,
|
|
51
|
+
}: PatientAdmitOrTransferFormProps) {
|
|
39
52
|
const { t } = useTranslation();
|
|
40
53
|
const { patient, inpatientRequest, visit } = wardPatient ?? {};
|
|
41
54
|
const [showErrorNotifications, setShowErrorNotifications] = useState(false);
|
|
@@ -90,11 +103,6 @@ export default function PatientAdmitOrTransferForm({
|
|
|
90
103
|
}
|
|
91
104
|
}, [dispositionsWithTypeTransfer, setValue]);
|
|
92
105
|
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
promptBeforeClosing(() => isDirty);
|
|
95
|
-
return () => promptBeforeClosing(null);
|
|
96
|
-
}, [isDirty, promptBeforeClosing]);
|
|
97
|
-
|
|
98
106
|
const onSubmit = useCallback(
|
|
99
107
|
(values: FormValues) => {
|
|
100
108
|
setIsSubmitting(true);
|
|
@@ -144,6 +152,7 @@ export default function PatientAdmitOrTransferForm({
|
|
|
144
152
|
title: t('patientTransferRequestCreated', 'Patient transfer request created'),
|
|
145
153
|
kind: 'success',
|
|
146
154
|
});
|
|
155
|
+
onSuccess();
|
|
147
156
|
})
|
|
148
157
|
.catch((err: Error) => {
|
|
149
158
|
showSnackbar({
|
|
@@ -154,12 +163,11 @@ export default function PatientAdmitOrTransferForm({
|
|
|
154
163
|
})
|
|
155
164
|
.finally(() => {
|
|
156
165
|
setIsSubmitting(false);
|
|
157
|
-
closeWorkspaceWithSavedChanges();
|
|
158
166
|
wardPatientGroupDetails.mutate();
|
|
159
167
|
});
|
|
160
168
|
},
|
|
161
169
|
[
|
|
162
|
-
|
|
170
|
+
onSuccess,
|
|
163
171
|
createEncounter,
|
|
164
172
|
dispositionsWithTypeTransfer,
|
|
165
173
|
emrConfiguration,
|
|
@@ -303,7 +311,7 @@ export default function PatientAdmitOrTransferForm({
|
|
|
303
311
|
)}
|
|
304
312
|
</Stack>
|
|
305
313
|
<ButtonSet className={styles.buttonSet}>
|
|
306
|
-
<Button size="xl" kind="secondary" onClick={
|
|
314
|
+
<Button size="xl" kind="secondary" onClick={onCancel}>
|
|
307
315
|
{t('cancel', 'Cancel')}
|
|
308
316
|
</Button>
|
|
309
317
|
<Button
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback,
|
|
1
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { Button, ButtonSet, Form, InlineNotification, CheckboxGroup, Checkbox, Stack } from '@carbon/react';
|
|
4
4
|
import { Controller, useForm } from 'react-hook-form';
|
|
@@ -7,19 +7,20 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { showSnackbar, useAppContext } from '@openmrs/esm-framework';
|
|
9
9
|
import { assignPatientToBed, removePatientFromBed, useCreateEncounter } from '../../ward.resource';
|
|
10
|
-
import type {
|
|
10
|
+
import type { WardViewContext } from '../../types';
|
|
11
11
|
import BedSelector from '../bed-selector.component';
|
|
12
12
|
import WardPatientIdentifier from '../../ward-patient-card/row-elements/ward-patient-identifier.component';
|
|
13
13
|
import WardPatientName from '../../ward-patient-card/row-elements/ward-patient-name.component';
|
|
14
14
|
import styles from './patient-transfer-swap.scss';
|
|
15
|
+
import { type PatientAdmitOrTransferFormProps } from './patient-admit-or-transfer-request-form.component';
|
|
15
16
|
|
|
16
17
|
export default function PatientBedSwapForm({
|
|
17
|
-
promptBeforeClosing,
|
|
18
|
-
closeWorkspaceWithSavedChanges,
|
|
19
18
|
wardPatient,
|
|
20
19
|
relatedTransferPatients = [],
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
onCancel,
|
|
21
|
+
onSuccess
|
|
22
|
+
}: PatientAdmitOrTransferFormProps) {
|
|
23
|
+
const { patient } = wardPatient;
|
|
23
24
|
const { t } = useTranslation();
|
|
24
25
|
const [showErrorNotifications, setShowErrorNotifications] = useState(false);
|
|
25
26
|
const { createEncounter, emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } =
|
|
@@ -47,11 +48,6 @@ export default function PatientBedSwapForm({
|
|
|
47
48
|
handleSubmit,
|
|
48
49
|
} = useForm<FormValues>({ resolver: zodResolver(zodSchema) });
|
|
49
50
|
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
promptBeforeClosing(() => isDirty);
|
|
52
|
-
return () => promptBeforeClosing(null);
|
|
53
|
-
}, [isDirty, promptBeforeClosing]);
|
|
54
|
-
|
|
55
51
|
const beds = useMemo(() => wardPatientGroupDetails?.bedLayouts ?? [], [wardPatientGroupDetails]);
|
|
56
52
|
|
|
57
53
|
const onSubmit = useCallback(
|
|
@@ -112,6 +108,7 @@ export default function PatientBedSwapForm({
|
|
|
112
108
|
}),
|
|
113
109
|
});
|
|
114
110
|
}
|
|
111
|
+
onSuccess();
|
|
115
112
|
}
|
|
116
113
|
})
|
|
117
114
|
.catch((error: Error) => {
|
|
@@ -124,7 +121,6 @@ export default function PatientBedSwapForm({
|
|
|
124
121
|
.finally(() => {
|
|
125
122
|
setIsSubmitting(false);
|
|
126
123
|
wardPatientGroupDetails.mutate();
|
|
127
|
-
closeWorkspaceWithSavedChanges();
|
|
128
124
|
});
|
|
129
125
|
},
|
|
130
126
|
[
|
|
@@ -134,7 +130,7 @@ export default function PatientBedSwapForm({
|
|
|
134
130
|
emrConfiguration,
|
|
135
131
|
t,
|
|
136
132
|
wardPatientGroupDetails,
|
|
137
|
-
|
|
133
|
+
onSuccess,
|
|
138
134
|
selectedRelatedPatient,
|
|
139
135
|
relatedTransferPatients,
|
|
140
136
|
wardPatient,
|
|
@@ -222,7 +218,7 @@ export default function PatientBedSwapForm({
|
|
|
222
218
|
)}
|
|
223
219
|
</Stack>
|
|
224
220
|
<ButtonSet className={styles.buttonSet}>
|
|
225
|
-
<Button size="xl" kind="secondary" onClick={
|
|
221
|
+
<Button size="xl" kind="secondary" onClick={onCancel}>
|
|
226
222
|
{t('cancel', 'Cancel')}
|
|
227
223
|
</Button>
|
|
228
224
|
<Button
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ContentSwitcher, Switch } from '@carbon/react';
|
|
2
|
-
import { useFeatureFlag } from '@openmrs/esm-framework';
|
|
2
|
+
import { closeWorkspaceGroup2, useFeatureFlag, Workspace2, Workspace2DefinitionProps } from '@openmrs/esm-framework';
|
|
3
3
|
import React, { useState } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import type { WardPatientWorkspaceProps } from '../../types';
|
|
5
|
+
import type { WardPatientWorkspaceDefinition, WardPatientWorkspaceProps } from '../../types';
|
|
6
6
|
import WardPatientWorkspaceBanner from '../patient-banner/patient-banner.component';
|
|
7
|
-
import PatientAdmitOrTransferForm
|
|
7
|
+
import PatientAdmitOrTransferForm, {
|
|
8
|
+
type PatientAdmitOrTransferFormProps,
|
|
9
|
+
} from './patient-admit-or-transfer-request-form.component';
|
|
8
10
|
import PatientBedSwapForm from './patient-bed-swap-form.component';
|
|
9
11
|
import styles from './patient-transfer-swap.scss';
|
|
10
12
|
|
|
@@ -19,34 +21,50 @@ type TransferSectionValues = (typeof TransferSection)[keyof typeof TransferSecti
|
|
|
19
21
|
* This workspace opens the form to either transfer a patient to a different ward location
|
|
20
22
|
* or to change their currently assigned bed
|
|
21
23
|
*/
|
|
22
|
-
export default function PatientTransferAndSwapWorkspace(
|
|
24
|
+
export default function PatientTransferAndSwapWorkspace({
|
|
25
|
+
groupProps: { wardPatient },
|
|
26
|
+
closeWorkspace,
|
|
27
|
+
}: WardPatientWorkspaceDefinition) {
|
|
23
28
|
const { t } = useTranslation();
|
|
24
29
|
const [selectedSection, setSelectedSection] = useState<TransferSectionValues>(TransferSection.TRANSFER);
|
|
25
30
|
const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
|
|
26
31
|
|
|
32
|
+
const props: PatientAdmitOrTransferFormProps = {
|
|
33
|
+
wardPatient,
|
|
34
|
+
onSuccess: async () => {
|
|
35
|
+
await closeWorkspace({ discardUnsavedChanges: true });
|
|
36
|
+
closeWorkspaceGroup2();
|
|
37
|
+
},
|
|
38
|
+
onCancel: () => {
|
|
39
|
+
closeWorkspace();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
27
43
|
return (
|
|
28
|
-
<
|
|
29
|
-
<div className={styles.
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
{isBedManagementModuleInstalled && (
|
|
33
|
-
<div className={styles.contentSwitcherWrapper}>
|
|
34
|
-
<h2 className={styles.productiveHeading02}>{t('typeOfTransfer', 'Type of transfer')}</h2>
|
|
35
|
-
<div className={styles.contentSwitcher}>
|
|
36
|
-
<ContentSwitcher onChange={({ name }) => setSelectedSection(name)}>
|
|
37
|
-
<Switch name={TransferSection.TRANSFER} text={t('transfer', 'Transfer')} />
|
|
38
|
-
<Switch name={TransferSection.BED_SWAP} text={t('bedSwap', 'Bed swap')} />
|
|
39
|
-
</ContentSwitcher>
|
|
40
|
-
</div>
|
|
44
|
+
<Workspace2 title={t('transfers', 'Transfers')}>
|
|
45
|
+
<div className={styles.flexWrapper}>
|
|
46
|
+
<div className={styles.patientWorkspaceBanner}>
|
|
47
|
+
<WardPatientWorkspaceBanner wardPatient={wardPatient} />
|
|
41
48
|
</div>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
{isBedManagementModuleInstalled && (
|
|
50
|
+
<div className={styles.contentSwitcherWrapper}>
|
|
51
|
+
<h2 className={styles.productiveHeading02}>{t('typeOfTransfer', 'Type of transfer')}</h2>
|
|
52
|
+
<div className={styles.contentSwitcher}>
|
|
53
|
+
<ContentSwitcher onChange={({ name }) => setSelectedSection(name)}>
|
|
54
|
+
<Switch name={TransferSection.TRANSFER} text={t('transfer', 'Transfer')} />
|
|
55
|
+
<Switch name={TransferSection.BED_SWAP} text={t('bedSwap', 'Bed swap')} />
|
|
56
|
+
</ContentSwitcher>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
48
59
|
)}
|
|
60
|
+
<div className={styles.workspaceForm}>
|
|
61
|
+
{selectedSection === TransferSection.TRANSFER ? (
|
|
62
|
+
<PatientAdmitOrTransferForm {...props} />
|
|
63
|
+
) : (
|
|
64
|
+
<PatientBedSwapForm {...props} />
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
49
67
|
</div>
|
|
50
|
-
</
|
|
68
|
+
</Workspace2>
|
|
51
69
|
);
|
|
52
70
|
}
|
package/src/ward-workspace/patient-transfer-request-workspace/patient-transfer-request.workspace.tsx
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { closeWorkspaceGroup2, Workspace2, type Workspace2DefinitionProps } from '@openmrs/esm-framework';
|
|
2
3
|
import { type WardPatientWorkspaceProps } from '../../types';
|
|
3
4
|
import WardPatientWorkspaceBanner from '../patient-banner/patient-banner.component';
|
|
4
5
|
import PatientAdmitOrTransferForm from '../patient-transfer-bed-swap/patient-admit-or-transfer-request-form.component';
|
|
5
6
|
import styles from './patient-transfer-request.scss';
|
|
6
|
-
|
|
7
|
-
interface PatientTransferRequestWorkspaceProps extends WardPatientWorkspaceProps {}
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* This workspace is launched when the "Transfer elsewhere" / "Admit elsewhere"
|
|
11
11
|
* button on a pending request patient card is clicked on
|
|
12
12
|
*/
|
|
13
|
-
const PatientTransferRequestWorkspace: React.FC<
|
|
14
|
-
|
|
13
|
+
const PatientTransferRequestWorkspace: React.FC<Workspace2DefinitionProps<WardPatientWorkspaceProps>> = ({
|
|
14
|
+
workspaceProps: { wardPatient },
|
|
15
|
+
closeWorkspace,
|
|
16
|
+
}) => {
|
|
17
|
+
const { t } = useTranslation();
|
|
18
|
+
const isTransfer = wardPatient.inpatientRequest.dispositionType == 'TRANSFER';
|
|
15
19
|
|
|
16
20
|
return (
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
+
<Workspace2
|
|
22
|
+
title={isTransfer ? t('transferElsewhere', 'Transfer elsewhere') : t('admitElsewhere', 'Admit elsewhere')}>
|
|
23
|
+
<div className={styles.patientTransferRequestWorkspace}>
|
|
24
|
+
<WardPatientWorkspaceBanner {...{ wardPatient }} />
|
|
25
|
+
<PatientAdmitOrTransferForm
|
|
26
|
+
wardPatient={wardPatient}
|
|
27
|
+
onSuccess={async () => {
|
|
28
|
+
await closeWorkspace({ discardUnsavedChanges: true });
|
|
29
|
+
closeWorkspaceGroup2();
|
|
30
|
+
}}
|
|
31
|
+
onCancel={() => closeWorkspace()}
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
</Workspace2>
|
|
21
35
|
);
|
|
22
36
|
};
|
|
23
37
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ActionMenuButton2, StickyNoteAddIcon } from '@openmrs/esm-framework';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
export default function WardPatientNotesActionButton() {
|
|
6
|
+
const { t } = useTranslation();
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<ActionMenuButton2
|
|
10
|
+
icon={(props) => <StickyNoteAddIcon {...props} size={16} />}
|
|
11
|
+
label={t('PatientNote', 'Patient Note')}
|
|
12
|
+
workspaceToLaunch={{
|
|
13
|
+
workspaceName: 'ward-patient-notes-workspace',
|
|
14
|
+
}}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { createErrorHandler, ResponsiveWrapper, showSnackbar, translateFrom, useSession } from '@openmrs/esm-framework';
|
|
5
|
+
import { savePatientNote, usePatientNotes } from './notes.resource';
|
|
6
|
+
import WardPatientNotesWorkspace from './notes.workspace';
|
|
7
|
+
import { emrConfigurationMock, mockInpatientRequestAlice, mockPatient, mockPatientAlice, mockSession } from '__mocks__';
|
|
8
|
+
import useEmrConfiguration from '../../hooks/useEmrConfiguration';
|
|
9
|
+
import { type WardPatient, type WardPatientWorkspaceDefinition } from '../../types';
|
|
10
|
+
|
|
11
|
+
const mockWardPatientAlice: WardPatient = {
|
|
12
|
+
visit: mockInpatientRequestAlice.visit,
|
|
13
|
+
patient: mockPatientAlice,
|
|
14
|
+
bed: null,
|
|
15
|
+
inpatientAdmission: null,
|
|
16
|
+
inpatientRequest: mockInpatientRequestAlice,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const testProps: WardPatientWorkspaceDefinition = {
|
|
20
|
+
groupProps: {
|
|
21
|
+
wardPatient: mockWardPatientAlice,
|
|
22
|
+
},
|
|
23
|
+
closeWorkspace: jest.fn(),
|
|
24
|
+
launchChildWorkspace: jest.fn(),
|
|
25
|
+
workspaceProps: undefined,
|
|
26
|
+
windowProps: undefined,
|
|
27
|
+
workspaceName: '',
|
|
28
|
+
windowName: '',
|
|
29
|
+
isRootWorkspace: false,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const mockSavePatientNote = savePatientNote as jest.Mock;
|
|
33
|
+
const mockedShowSnackbar = jest.mocked(showSnackbar);
|
|
34
|
+
|
|
35
|
+
jest.mock('./notes.resource', () => ({
|
|
36
|
+
savePatientNote: jest.fn(),
|
|
37
|
+
usePatientNotes: jest.fn(),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
jest.mock('../../hooks/useEmrConfiguration', () => jest.fn());
|
|
41
|
+
|
|
42
|
+
const mockedUseEmrConfiguration = jest.mocked(useEmrConfiguration);
|
|
43
|
+
const mockedUsePatientNotes = jest.mocked(usePatientNotes);
|
|
44
|
+
|
|
45
|
+
mockedUseEmrConfiguration.mockReturnValue({
|
|
46
|
+
emrConfiguration: emrConfigurationMock,
|
|
47
|
+
mutateEmrConfiguration: jest.fn(),
|
|
48
|
+
isLoadingEmrConfiguration: false,
|
|
49
|
+
errorFetchingEmrConfiguration: null,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('<WardPatientNotesWorkspace>', () => {
|
|
53
|
+
mockedUsePatientNotes.mockReturnValue({
|
|
54
|
+
patientNotes: [],
|
|
55
|
+
errorFetchingPatientNotes: undefined,
|
|
56
|
+
isLoadingPatientNotes: false,
|
|
57
|
+
mutatePatientNotes: jest.fn(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('renders the visit notes form with all the relevant fields and values', () => {
|
|
61
|
+
renderWardPatientNotesForm();
|
|
62
|
+
|
|
63
|
+
expect(screen.getByRole('textbox', { name: /Write your notes/i })).toBeInTheDocument();
|
|
64
|
+
expect(screen.getByRole('button', { name: /Save/i })).toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('renders a success snackbar upon successfully recording a visit note', async () => {
|
|
68
|
+
const successPayload = {
|
|
69
|
+
encounterProviders: expect.arrayContaining([
|
|
70
|
+
{
|
|
71
|
+
encounterRole: emrConfigurationMock?.clinicianEncounterRole?.uuid,
|
|
72
|
+
provider: undefined,
|
|
73
|
+
},
|
|
74
|
+
]),
|
|
75
|
+
encounterType: emrConfigurationMock?.inpatientNoteEncounterType?.uuid,
|
|
76
|
+
location: undefined,
|
|
77
|
+
obs: expect.arrayContaining([
|
|
78
|
+
{
|
|
79
|
+
concept: { display: '', uuid: '162169AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' },
|
|
80
|
+
value: 'Sample clinical note',
|
|
81
|
+
},
|
|
82
|
+
]),
|
|
83
|
+
patient: mockPatientAlice.uuid,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
mockSavePatientNote.mockResolvedValue({ status: 201, body: 'Condition created' });
|
|
87
|
+
|
|
88
|
+
renderWardPatientNotesForm();
|
|
89
|
+
|
|
90
|
+
const note = screen.getByRole('textbox', { name: /Write your notes/i });
|
|
91
|
+
await userEvent.clear(note);
|
|
92
|
+
await userEvent.type(note, 'Sample clinical note');
|
|
93
|
+
expect(note).toHaveValue('Sample clinical note');
|
|
94
|
+
|
|
95
|
+
const submitButton = screen.getByRole('button', { name: /Save/i });
|
|
96
|
+
await userEvent.click(submitButton);
|
|
97
|
+
|
|
98
|
+
expect(mockSavePatientNote).toHaveBeenCalledTimes(1);
|
|
99
|
+
expect(mockSavePatientNote).toHaveBeenCalledWith(expect.objectContaining(successPayload), new AbortController());
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('renders an error snackbar if there was a problem recording a visit note', async () => {
|
|
103
|
+
const error = {
|
|
104
|
+
message: 'Internal Server Error',
|
|
105
|
+
response: {
|
|
106
|
+
status: 500,
|
|
107
|
+
statusText: 'Internal Server Error',
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
mockSavePatientNote.mockRejectedValueOnce(error);
|
|
112
|
+
renderWardPatientNotesForm();
|
|
113
|
+
|
|
114
|
+
const note = screen.getByRole('textbox', { name: /Write your notes/i });
|
|
115
|
+
await userEvent.clear(note);
|
|
116
|
+
await userEvent.type(note, 'Sample clinical note');
|
|
117
|
+
expect(note).toHaveValue('Sample clinical note');
|
|
118
|
+
|
|
119
|
+
const submitButton = screen.getByRole('button', { name: /Save/i });
|
|
120
|
+
|
|
121
|
+
await userEvent.click(submitButton);
|
|
122
|
+
|
|
123
|
+
expect(mockedShowSnackbar).toHaveBeenCalledWith({
|
|
124
|
+
isLowContrast: false,
|
|
125
|
+
kind: 'error',
|
|
126
|
+
subtitle: 'Internal Server Error',
|
|
127
|
+
title: 'Error saving patient note',
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
function renderWardPatientNotesForm() {
|
|
133
|
+
render(<WardPatientNotesWorkspace {...testProps} />);
|
|
134
|
+
}
|
|
@@ -1,24 +1,185 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import React, { useCallback, useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
5
|
+
import { Controller, useForm } from 'react-hook-form';
|
|
6
|
+
import { Button, Column, Form, InlineLoading, InlineNotification, Row, Stack, TextArea } from '@carbon/react';
|
|
7
|
+
import {
|
|
8
|
+
closeWorkspaceGroup2,
|
|
9
|
+
ResponsiveWrapper,
|
|
10
|
+
showSnackbar,
|
|
11
|
+
translateFrom,
|
|
12
|
+
useSession,
|
|
13
|
+
Workspace2,
|
|
14
|
+
} from '@openmrs/esm-framework';
|
|
15
|
+
import { moduleName } from '../../constant';
|
|
16
|
+
import { savePatientNote } from './notes.resource';
|
|
17
|
+
import useEmrConfiguration from '../../hooks/useEmrConfiguration';
|
|
18
|
+
import styles from './notes.scss';
|
|
19
|
+
import { type WardPatientWorkspaceDefinition, type EncounterPayload } from '../../types';
|
|
3
20
|
import WardPatientWorkspaceBanner from '../patient-banner/patient-banner.component';
|
|
4
|
-
import PatientNotesForm from './form/notes-form.component';
|
|
5
21
|
import PatientNotesHistory from './history/notes-container.component';
|
|
6
22
|
|
|
7
|
-
|
|
8
|
-
const { wardPatient, ...restWorkspaceProps } = props;
|
|
9
|
-
const patientUuid = wardPatient?.patient?.uuid;
|
|
23
|
+
type NotesFormData = z.infer<typeof noteFormSchema>;
|
|
10
24
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
const noteFormSchema = z.object({
|
|
26
|
+
wardClinicalNote: z.string().refine((val) => val.trim().length > 0, {
|
|
27
|
+
//t('clinicalNoteErrorMessage','Clinical note is required')
|
|
28
|
+
message: translateFrom(moduleName, 'clinicalNoteErrorMessage', 'Clinical note is required'),
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const WardPatientNotesWorkspace: React.FC<WardPatientWorkspaceDefinition> = ({
|
|
33
|
+
groupProps: { wardPatient },
|
|
34
|
+
closeWorkspace,
|
|
35
|
+
}) => {
|
|
36
|
+
const patientUuid = wardPatient.patient.uuid;
|
|
37
|
+
const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
|
|
38
|
+
const { t } = useTranslation();
|
|
39
|
+
const session = useSession();
|
|
40
|
+
|
|
41
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
42
|
+
const [rows, setRows] = useState(0);
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
control,
|
|
46
|
+
handleSubmit,
|
|
47
|
+
formState: { errors, isDirty },
|
|
48
|
+
} = useForm<NotesFormData>({
|
|
49
|
+
mode: 'onSubmit',
|
|
50
|
+
resolver: zodResolver(noteFormSchema),
|
|
51
|
+
defaultValues: {
|
|
52
|
+
wardClinicalNote: '',
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const locationUuid = session?.sessionLocation?.uuid;
|
|
57
|
+
const providerUuid = session?.currentProvider?.uuid;
|
|
58
|
+
|
|
59
|
+
const onSubmit = useCallback(
|
|
60
|
+
(data: NotesFormData) => {
|
|
61
|
+
const { wardClinicalNote } = data;
|
|
62
|
+
setIsSubmitting(true);
|
|
63
|
+
|
|
64
|
+
const notePayload: EncounterPayload = {
|
|
65
|
+
patient: patientUuid,
|
|
66
|
+
location: locationUuid,
|
|
67
|
+
encounterType: emrConfiguration?.inpatientNoteEncounterType?.uuid,
|
|
68
|
+
encounterProviders: [
|
|
69
|
+
{
|
|
70
|
+
encounterRole: emrConfiguration?.clinicianEncounterRole?.uuid,
|
|
71
|
+
provider: providerUuid,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
obs: wardClinicalNote
|
|
75
|
+
? [
|
|
76
|
+
{
|
|
77
|
+
concept: { uuid: emrConfiguration?.consultFreeTextCommentsConcept.uuid, display: '' },
|
|
78
|
+
value: wardClinicalNote,
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
: [],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const abortController = new AbortController();
|
|
85
|
+
|
|
86
|
+
savePatientNote(notePayload, abortController)
|
|
87
|
+
.then(async () => {
|
|
88
|
+
showSnackbar({
|
|
89
|
+
isLowContrast: true,
|
|
90
|
+
kind: 'success',
|
|
91
|
+
subtitle: t('patientNoteNowVisible', 'It should be now visible in the notes history'),
|
|
92
|
+
title: t('visitNoteSaved', 'Patient note saved'),
|
|
93
|
+
});
|
|
94
|
+
await closeWorkspace({ discardUnsavedChanges: true });
|
|
95
|
+
closeWorkspaceGroup2();
|
|
96
|
+
})
|
|
97
|
+
.catch((err) => {
|
|
98
|
+
showSnackbar({
|
|
99
|
+
isLowContrast: false,
|
|
100
|
+
kind: 'error',
|
|
101
|
+
subtitle: err?.message,
|
|
102
|
+
title: t('patientNoteSaveError', 'Error saving patient note'),
|
|
103
|
+
});
|
|
104
|
+
})
|
|
105
|
+
.finally(() => setIsSubmitting(false));
|
|
106
|
+
},
|
|
107
|
+
[
|
|
108
|
+
emrConfiguration?.clinicianEncounterRole?.uuid,
|
|
109
|
+
emrConfiguration?.consultFreeTextCommentsConcept?.uuid,
|
|
110
|
+
emrConfiguration?.inpatientNoteEncounterType?.uuid,
|
|
111
|
+
locationUuid,
|
|
112
|
+
patientUuid,
|
|
113
|
+
providerUuid,
|
|
114
|
+
t,
|
|
115
|
+
closeWorkspace,
|
|
116
|
+
],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const onError = (errors) => console.error(errors);
|
|
15
120
|
|
|
16
121
|
return (
|
|
17
|
-
<
|
|
122
|
+
<Workspace2 hasUnsavedChanges={isDirty} title={t('inpatientNotesWorkspaceTitle', 'In-patient notes')}>
|
|
18
123
|
<WardPatientWorkspaceBanner {...{ wardPatient }} />
|
|
19
|
-
<
|
|
124
|
+
<Form className={styles.form} onSubmit={handleSubmit(onSubmit, onError)}>
|
|
125
|
+
{errorFetchingEmrConfiguration && (
|
|
126
|
+
<div className={styles.formError}>
|
|
127
|
+
<InlineNotification
|
|
128
|
+
kind="error"
|
|
129
|
+
title={t('somePartsOfTheFormDidntLoad', "Some parts of the form didn't load")}
|
|
130
|
+
subtitle={t(
|
|
131
|
+
'fetchingEmrConfigurationFailed',
|
|
132
|
+
'Fetching EMR configuration failed. Try refreshing the page or contact your system administrator.',
|
|
133
|
+
)}
|
|
134
|
+
lowContrast
|
|
135
|
+
hideCloseButton
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
<Stack className={styles.formContainer} gap={2}>
|
|
140
|
+
<Row className={styles.row}>
|
|
141
|
+
<Column sm={1}>
|
|
142
|
+
<span className={styles.columnLabel}>{t('note', 'Note')}</span>
|
|
143
|
+
</Column>
|
|
144
|
+
<Column sm={3}>
|
|
145
|
+
<Controller
|
|
146
|
+
name="wardClinicalNote"
|
|
147
|
+
control={control}
|
|
148
|
+
render={({ field: { onChange, onBlur, value } }) => (
|
|
149
|
+
<ResponsiveWrapper>
|
|
150
|
+
<TextArea
|
|
151
|
+
id="additionalNote"
|
|
152
|
+
invalid={!!errors.wardClinicalNote}
|
|
153
|
+
invalidText={errors.wardClinicalNote?.message}
|
|
154
|
+
labelText={t('clinicalNoteLabel', 'Write your notes')}
|
|
155
|
+
onBlur={onBlur}
|
|
156
|
+
onChange={(event) => {
|
|
157
|
+
onChange(event);
|
|
158
|
+
const textAreaLineHeight = 24; // This is the default line height for Carbon's TextArea component
|
|
159
|
+
const newRows = Math.ceil(event.target.scrollHeight / textAreaLineHeight);
|
|
160
|
+
setRows(newRows);
|
|
161
|
+
}}
|
|
162
|
+
placeholder={t('wardClinicalNotePlaceholder', 'Write any notes here')}
|
|
163
|
+
rows={rows}
|
|
164
|
+
value={value}
|
|
165
|
+
/>
|
|
166
|
+
</ResponsiveWrapper>
|
|
167
|
+
)}
|
|
168
|
+
/>
|
|
169
|
+
</Column>
|
|
170
|
+
</Row>
|
|
171
|
+
</Stack>
|
|
172
|
+
<Button
|
|
173
|
+
className={styles.saveButton}
|
|
174
|
+
disabled={isSubmitting || isLoadingEmrConfiguration || errorFetchingEmrConfiguration}
|
|
175
|
+
kind="primary"
|
|
176
|
+
type="submit">
|
|
177
|
+
{isSubmitting ? <InlineLoading description={t('saving', 'Saving...')} /> : <span>{t('save', 'Save')}</span>}
|
|
178
|
+
</Button>
|
|
179
|
+
</Form>
|
|
180
|
+
|
|
20
181
|
<PatientNotesHistory patientUuid={patientUuid} visitUuid={wardPatient?.visit?.uuid} />
|
|
21
|
-
</
|
|
182
|
+
</Workspace2>
|
|
22
183
|
);
|
|
23
184
|
};
|
|
24
185
|
|