@openmrs/esm-patient-chart-app 11.3.1-patch.9064 → 11.3.1-patch.9508
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 +21 -21
- package/dist/1119.js +1 -1
- package/dist/1197.js +1 -1
- package/dist/2146.js +1 -1
- package/dist/2540.js +1 -0
- package/dist/2540.js.map +1 -0
- package/dist/2690.js +1 -1
- package/dist/276.js +1 -1
- package/dist/276.js.map +1 -1
- package/dist/2761.js +1 -1
- package/dist/2761.js.map +1 -1
- package/dist/2859.js +1 -1
- package/dist/2859.js.map +1 -1
- package/dist/3099.js +1 -1
- package/dist/3119.js +1 -1
- package/dist/3119.js.map +1 -1
- package/dist/3584.js +1 -1
- package/dist/3905.js +1 -1
- package/dist/4055.js +1 -1
- package/dist/4132.js +1 -1
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/439.js +1 -0
- package/dist/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4718.js +1 -1
- package/dist/4754.js +1 -1
- package/dist/4944.js +1 -1
- package/dist/5048.js +1 -1
- package/dist/5048.js.map +1 -1
- package/dist/506.js +2 -0
- package/dist/506.js.map +1 -0
- package/dist/5173.js +1 -1
- package/dist/5205.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/5442.js +1 -1
- package/dist/5661.js +1 -1
- package/dist/5670.js +1 -1
- package/dist/5670.js.map +1 -1
- package/dist/6022.js +1 -1
- package/dist/6336.js +1 -0
- package/dist/6336.js.map +1 -0
- package/dist/6411.js +1 -1
- package/dist/6468.js +1 -1
- package/dist/6529.js +1 -1
- package/dist/6568.js +1 -0
- package/dist/6568.js.map +1 -0
- package/dist/6589.js +1 -0
- package/dist/6679.js +1 -1
- package/dist/6840.js +1 -1
- package/dist/6859.js +1 -1
- package/dist/6924.js +1 -0
- package/dist/6924.js.map +1 -0
- package/dist/{4727.js → 6997.js} +1 -1
- package/dist/6997.js.map +1 -0
- package/dist/7097.js +1 -1
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7617.js +1 -1
- package/dist/7810.js +1 -0
- package/dist/7810.js.map +1 -0
- package/dist/7816.js +2 -0
- package/dist/7816.js.map +1 -0
- package/dist/7822.js +1 -1
- package/dist/7822.js.map +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8260.js +1 -0
- package/dist/8260.js.map +1 -0
- package/dist/8349.js +1 -1
- package/dist/8371.js +1 -0
- package/dist/8454.js +1 -1
- package/dist/8454.js.map +1 -1
- package/dist/8618.js +1 -1
- package/dist/8709.js +1 -1
- package/dist/890.js +1 -1
- package/dist/9214.js +1 -1
- package/dist/9538.js +1 -1
- package/dist/9569.js +1 -1
- package/dist/986.js +1 -1
- package/dist/9879.js +1 -1
- package/dist/9895.js +1 -1
- package/dist/9900.js +1 -1
- package/dist/9913.js +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-chart-app.js +1 -1
- package/dist/openmrs-esm-patient-chart-app.js.buildmanifest.json +436 -321
- package/dist/openmrs-esm-patient-chart-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +3 -3
- package/src/actions-buttons/delete-visit.component.tsx +8 -3
- package/src/actions-buttons/stop-visit.component.tsx +1 -1
- package/src/clinical-views/encounter-list/{encounter-list-tabs.component.tsx → encounter-list-tabs.extension.tsx} +11 -7
- package/src/clinical-views/encounter-list/tag.component.test.tsx +306 -0
- package/src/clinical-views/encounter-list/tag.component.tsx +27 -28
- package/src/clinical-views/utils/encounter-list-config-builder.ts +19 -6
- package/src/clinical-views/utils/helpers.ts +3 -2
- package/src/config-schema.ts +0 -7
- package/src/index.ts +9 -4
- package/src/mark-patient-deceased/mark-patient-deceased-form.test.tsx +9 -4
- package/src/patient-banner-tags/{visit-attribute-tags.component.tsx → visit-attribute-tags.extension.tsx} +28 -15
- package/src/patient-banner-tags/visit-attribute-tags.scss +8 -0
- package/src/patient-chart/chart-review/dashboard-view.scss +5 -0
- package/src/patient-chart/patient-chart.component.tsx +39 -65
- package/src/patient-chart/patient-chart.resources.ts +108 -0
- package/src/routes.json +1 -1
- package/src/visit/hooks/useDeleteVisit.test.tsx +39 -42
- package/src/visit/hooks/useDeleteVisit.tsx +33 -17
- package/src/visit/visit-form/base-visit-type.component.tsx +30 -21
- package/src/visit/visit-form/exported-visit-form.workspace.tsx +700 -0
- package/src/visit/visit-form/visit-attribute-type.component.tsx +2 -1
- package/src/visit/visit-form/visit-form.resource.ts +2 -1
- package/src/visit/visit-form/visit-form.test.tsx +3 -9
- package/src/visit/visit-form/visit-form.workspace.tsx +53 -15
- package/src/visit/visit-prompt/delete-visit-dialog.component.tsx +10 -4
- package/src/visit/visit-prompt/delete-visit-dialog.test.tsx +20 -2
- package/src/visit/visit-prompt/end-visit-dialog.component.tsx +7 -1
- package/src/visit/visit-prompt/end-visit-dialog.test.tsx +19 -0
- package/src/visit/visits-widget/active-visit-buttons/active-visit-buttons.tsx +7 -6
- package/src/visit/visits-widget/{current-visit-summary.component.tsx → current-visit-summary.extension.tsx} +13 -20
- package/src/visit/visits-widget/current-visit-summary.test.tsx +45 -25
- package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.component.tsx +4 -3
- package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.resource.ts +0 -1
- package/src/visit/visits-widget/visit-context/retrospective-data-date-time-picker/retrospective-date-time-picker.component.tsx +6 -8
- package/src/visit/visits-widget/visit-context/{visit-context-header.component.tsx → visit-context-header.extension.tsx} +17 -15
- package/src/visit/visits-widget/visit-context/visit-context-header.test.tsx +35 -29
- package/src/visit/visits-widget/visit-context/visit-context-switcher.modal.tsx +13 -11
- package/src/visit/visits-widget/visit-context/visit-context-switcher.test.tsx +50 -30
- package/src/visit/visits-widget/visit.resource.tsx +1 -1
- package/translations/am.json +1 -2
- package/translations/ar.json +2 -3
- package/translations/ar_SY.json +1 -2
- package/translations/bn.json +1 -2
- package/translations/cs.json +196 -0
- package/translations/de.json +1 -2
- package/translations/en.json +2 -3
- package/translations/en_US.json +1 -2
- package/translations/es.json +2 -3
- package/translations/es_MX.json +1 -2
- package/translations/fr.json +22 -23
- package/translations/he.json +2 -3
- package/translations/hi.json +1 -2
- package/translations/hi_IN.json +1 -2
- package/translations/id.json +2 -3
- package/translations/it.json +2 -3
- package/translations/ka.json +2 -3
- package/translations/km.json +2 -3
- package/translations/ku.json +1 -2
- package/translations/ky.json +1 -2
- package/translations/lg.json +1 -2
- package/translations/ne.json +1 -2
- package/translations/pl.json +1 -2
- package/translations/pt.json +2 -3
- package/translations/pt_BR.json +2 -3
- package/translations/qu.json +1 -2
- package/translations/ro_RO.json +2 -3
- package/translations/ru_RU.json +1 -2
- package/translations/si.json +1 -2
- package/translations/sq.json +196 -0
- package/translations/sw.json +1 -2
- package/translations/sw_KE.json +1 -2
- package/translations/tr.json +1 -2
- package/translations/tr_TR.json +1 -2
- package/translations/uk.json +1 -2
- package/translations/uz.json +1 -2
- package/translations/uz@Latn.json +1 -2
- package/translations/uz_UZ.json +1 -2
- package/translations/vi.json +2 -3
- package/translations/zh.json +2 -3
- package/translations/zh_CN.json +2 -3
- package/translations/zh_TW.json +196 -0
- package/dist/1568.js +0 -2
- package/dist/1568.js.map +0 -1
- package/dist/2442.js +0 -1
- package/dist/2442.js.map +0 -1
- package/dist/2537.js +0 -1
- package/dist/2537.js.map +0 -1
- package/dist/3042.js +0 -1
- package/dist/3042.js.map +0 -1
- package/dist/4713.js +0 -1
- package/dist/4713.js.map +0 -1
- package/dist/4727.js.map +0 -1
- package/dist/6650.js +0 -2
- package/dist/6650.js.map +0 -1
- /package/dist/{1568.js.LICENSE.txt → 506.js.LICENSE.txt} +0 -0
- /package/dist/{6650.js.LICENSE.txt → 7816.js.LICENSE.txt} +0 -0
|
@@ -115,7 +115,8 @@ const AttributeTypeField: React.FC<AttributeTypeFieldProps> = ({
|
|
|
115
115
|
} = useConceptAnswersForVisitAttributeType(data?.datatypeConfig);
|
|
116
116
|
const { t } = useTranslation();
|
|
117
117
|
const baseId = useId();
|
|
118
|
-
const
|
|
118
|
+
const displayText = data?.display?.trim() || data?.name?.trim() || '--';
|
|
119
|
+
const labelText = !required ? `${displayText} (${t('optional', 'optional')})` : displayText;
|
|
119
120
|
|
|
120
121
|
const {
|
|
121
122
|
formState: { errors },
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo, useState } from 'react';
|
|
2
|
-
import { useTranslation
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import type { TFunction } from 'i18next';
|
|
3
4
|
import dayjs from 'dayjs';
|
|
4
5
|
import { z } from 'zod';
|
|
5
6
|
import {
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
useConfig,
|
|
12
12
|
useEmrConfiguration,
|
|
13
13
|
useLocations,
|
|
14
|
-
useVisitContextStore,
|
|
15
14
|
useVisitTypes,
|
|
16
15
|
type Visit,
|
|
17
16
|
} from '@openmrs/esm-framework';
|
|
@@ -59,6 +58,7 @@ const visitAttributes = {
|
|
|
59
58
|
const mockCloseWorkspace = jest.fn();
|
|
60
59
|
const mockPromptBeforeClosing = jest.fn();
|
|
61
60
|
const mockSetTitle = jest.fn();
|
|
61
|
+
const mockMutateVisitContext = jest.fn();
|
|
62
62
|
|
|
63
63
|
const testProps = {
|
|
64
64
|
openedFrom: 'test',
|
|
@@ -68,6 +68,8 @@ const testProps = {
|
|
|
68
68
|
closeWorkspaceWithSavedChanges: mockCloseWorkspace,
|
|
69
69
|
promptBeforeClosing: mockPromptBeforeClosing,
|
|
70
70
|
setTitle: mockSetTitle,
|
|
71
|
+
visitContext: null,
|
|
72
|
+
mutateVisitContext: mockMutateVisitContext,
|
|
71
73
|
};
|
|
72
74
|
|
|
73
75
|
const mockSaveVisit = jest.mocked(saveVisit);
|
|
@@ -181,14 +183,6 @@ mockSaveVisit.mockResolvedValue({
|
|
|
181
183
|
},
|
|
182
184
|
} as unknown as FetchResponse<Visit>);
|
|
183
185
|
|
|
184
|
-
jest.mocked(useVisitContextStore).mockReturnValue({
|
|
185
|
-
manuallySetVisitUuid: null,
|
|
186
|
-
patientUuid: null,
|
|
187
|
-
setVisitContext: jest.fn(),
|
|
188
|
-
mutateVisitCallbacks: {},
|
|
189
|
-
mutateVisit: jest.fn(),
|
|
190
|
-
});
|
|
191
|
-
|
|
192
186
|
describe('Visit form', () => {
|
|
193
187
|
beforeEach(() => {
|
|
194
188
|
mockUseConfig.mockReturnValue({
|
|
@@ -36,12 +36,12 @@ import {
|
|
|
36
36
|
} from '@openmrs/esm-framework';
|
|
37
37
|
import {
|
|
38
38
|
createOfflineVisitForPatient,
|
|
39
|
+
invalidateVisitByUuid,
|
|
39
40
|
invalidateVisitAndEncounterData,
|
|
40
41
|
useActivePatientEnrollment,
|
|
42
|
+
usePatientChartStore,
|
|
41
43
|
type DefaultPatientWorkspaceProps,
|
|
42
44
|
} from '@openmrs/esm-patient-common-lib';
|
|
43
|
-
import { type ChartConfig } from '../../config-schema';
|
|
44
|
-
import { useVisitAttributeTypes } from '../hooks/useVisitAttributeType';
|
|
45
45
|
import { MemoizedRecommendedVisitType } from './recommended-visit-type.component';
|
|
46
46
|
import {
|
|
47
47
|
convertToDate,
|
|
@@ -61,8 +61,32 @@ import BaseVisitType from './base-visit-type.component';
|
|
|
61
61
|
import LocationSelector from './location-selector.component';
|
|
62
62
|
import VisitAttributeTypeFields from './visit-attribute-type.component';
|
|
63
63
|
import VisitDateTimeSection from './visit-date-time.component';
|
|
64
|
+
import { useVisitAttributeTypes } from '../hooks/useVisitAttributeType';
|
|
65
|
+
import { type ChartConfig } from '../../config-schema';
|
|
64
66
|
import styles from './visit-form.scss';
|
|
65
67
|
|
|
68
|
+
interface VisitAttribute {
|
|
69
|
+
attributeType: string;
|
|
70
|
+
value: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extra visit information provided by extensions via the extra-visit-attribute-slot.
|
|
75
|
+
* Extensions can use this to add custom attributes to visits.
|
|
76
|
+
*/
|
|
77
|
+
export interface ExtraVisitInfo {
|
|
78
|
+
/**
|
|
79
|
+
* Optional callback that extensions can provide to perform final
|
|
80
|
+
* preparation or validation before the visit is created/updated.
|
|
81
|
+
*/
|
|
82
|
+
handleCreateExtraVisitInfo?: () => void;
|
|
83
|
+
/**
|
|
84
|
+
* Array of visit attributes to be included in the visit payload.
|
|
85
|
+
* Each attribute must have an attributeType (UUID) and a value (string).
|
|
86
|
+
*/
|
|
87
|
+
attributes?: Array<VisitAttribute>;
|
|
88
|
+
}
|
|
89
|
+
|
|
66
90
|
interface VisitFormProps extends DefaultPatientWorkspaceProps {
|
|
67
91
|
/**
|
|
68
92
|
* A unique string identifying where the visit form is opened from.
|
|
@@ -98,16 +122,17 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
98
122
|
);
|
|
99
123
|
const visitHeaderSlotState = useMemo(() => ({ patientUuid }), [patientUuid]);
|
|
100
124
|
const { activePatientEnrollment, isLoading } = useActivePatientEnrollment(patientUuid);
|
|
101
|
-
const { mutate:
|
|
125
|
+
const { mutate: mutateActiveVisit } = useVisit(patientUuid);
|
|
102
126
|
const { mutate: globalMutate } = useSWRConfig();
|
|
103
127
|
const allVisitTypes = useConditionalVisitTypes();
|
|
128
|
+
const { setVisitContext } = usePatientChartStore(patientUuid);
|
|
104
129
|
|
|
105
130
|
const [errorFetchingResources, setErrorFetchingResources] = useState<{
|
|
106
131
|
blockSavingForm: boolean;
|
|
107
132
|
} | null>(null);
|
|
108
133
|
const { visitAttributeTypes } = useVisitAttributeTypes();
|
|
109
134
|
const [visitFormCallbacks, setVisitFormCallbacks] = useVisitFormCallbacks();
|
|
110
|
-
const [extraVisitInfo, setExtraVisitInfo] = useState(null);
|
|
135
|
+
const [extraVisitInfo, setExtraVisitInfo] = useState<ExtraVisitInfo | null>(null);
|
|
111
136
|
|
|
112
137
|
const { visitFormSchema, defaultValues, firstEncounterDateTime, lastEncounterDateTime } =
|
|
113
138
|
useVisitFormSchemaAndDefaultValues(visitToEdit);
|
|
@@ -135,6 +160,14 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
135
160
|
promptBeforeClosing(() => isDirty);
|
|
136
161
|
}, [isDirty, promptBeforeClosing]);
|
|
137
162
|
|
|
163
|
+
const isValidVisitAttributesArray = useCallback((attributes: unknown): boolean => {
|
|
164
|
+
return (
|
|
165
|
+
Array.isArray(attributes) &&
|
|
166
|
+
attributes.length > 0 &&
|
|
167
|
+
attributes.every((attr) => attr?.attributeType?.trim().length > 0 && attr?.value?.trim().length > 0)
|
|
168
|
+
);
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
138
171
|
const handleVisitAttributes = useCallback(
|
|
139
172
|
(visitAttributes: { [p: string]: string }, visitUuid: string) => {
|
|
140
173
|
const existingVisitAttributeTypes =
|
|
@@ -241,12 +274,10 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
241
274
|
stopDatetime: hasStopTime ? stopDatetime : null,
|
|
242
275
|
// The request throws 400 (Bad request) error when the patient is passed in the update payload for existing visit
|
|
243
276
|
...(!visitToEdit && { patient: patientUuid }),
|
|
244
|
-
...(
|
|
277
|
+
...(isValidVisitAttributesArray(extraAttributes) && { attributes: extraAttributes }),
|
|
245
278
|
};
|
|
246
279
|
|
|
247
|
-
|
|
248
|
-
handleCreateExtraVisitInfo?.();
|
|
249
|
-
}
|
|
280
|
+
handleCreateExtraVisitInfo?.();
|
|
250
281
|
|
|
251
282
|
const abortController = new AbortController();
|
|
252
283
|
if (isOnline) {
|
|
@@ -299,12 +330,15 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
299
330
|
// 1. Current visit data (for critical components like visit summary, action buttons)
|
|
300
331
|
// 2. Visit history table (for the paginated visit list)
|
|
301
332
|
|
|
302
|
-
// Update
|
|
303
|
-
|
|
333
|
+
// Update patient's visit data for critical components
|
|
334
|
+
const mutateSavedOrUpdatedVisit = () => invalidateVisitByUuid(globalMutate, visit.uuid);
|
|
335
|
+
mutateActiveVisit();
|
|
336
|
+
setVisitContext?.(visit, mutateSavedOrUpdatedVisit);
|
|
337
|
+
visitToEdit && mutateSavedOrUpdatedVisit();
|
|
304
338
|
|
|
305
339
|
// Use targeted SWR invalidation instead of global mutateVisit
|
|
306
340
|
// This will invalidate visit history and encounter tables for this patient
|
|
307
|
-
// (
|
|
341
|
+
// (if visitContext is updated, it should have been invalidated with mutateSavedOrUpdatedVisit)
|
|
308
342
|
invalidateVisitAndEncounterData(globalMutate, patientUuid);
|
|
309
343
|
|
|
310
344
|
// handleVisitAttributes already has code to show error snackbar when attribute fails to update
|
|
@@ -343,9 +377,12 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
343
377
|
config.offlineVisitTypeUuid,
|
|
344
378
|
payload.startDatetime,
|
|
345
379
|
).then(
|
|
346
|
-
() => {
|
|
380
|
+
(visit) => {
|
|
347
381
|
// Use same targeted approach for offline visits for consistency
|
|
348
|
-
|
|
382
|
+
const mutateSavedOrUpdatedVisit = () => invalidateVisitByUuid(globalMutate, visit.uuid);
|
|
383
|
+
mutateActiveVisit();
|
|
384
|
+
setVisitContext?.(visit, mutateSavedOrUpdatedVisit);
|
|
385
|
+
visitToEdit && mutateSavedOrUpdatedVisit();
|
|
349
386
|
|
|
350
387
|
// Also invalidate visit history and encounter tables
|
|
351
388
|
invalidateVisitAndEncounterData(globalMutate, patientUuid);
|
|
@@ -375,16 +412,17 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
375
412
|
[
|
|
376
413
|
closeWorkspace,
|
|
377
414
|
config.offlineVisitTypeUuid,
|
|
378
|
-
config.showExtraVisitAttributesSlot,
|
|
379
415
|
extraVisitInfo,
|
|
380
416
|
globalMutate,
|
|
381
417
|
handleVisitAttributes,
|
|
382
418
|
isOnline,
|
|
383
|
-
|
|
419
|
+
setVisitContext,
|
|
384
420
|
patientUuid,
|
|
385
421
|
t,
|
|
386
422
|
visitFormCallbacks,
|
|
387
423
|
visitToEdit,
|
|
424
|
+
isValidVisitAttributesArray,
|
|
425
|
+
mutateActiveVisit,
|
|
388
426
|
],
|
|
389
427
|
);
|
|
390
428
|
|
|
@@ -8,12 +8,18 @@ import styles from './start-visit-dialog.scss';
|
|
|
8
8
|
interface DeleteVisitDialogProps {
|
|
9
9
|
closeModal: () => void;
|
|
10
10
|
patientUuid: string;
|
|
11
|
-
|
|
11
|
+
activeVisit: Visit;
|
|
12
|
+
mutateActiveVisit: () => void;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
const DeleteVisitDialog: React.FC<DeleteVisitDialogProps> = ({
|
|
15
|
+
const DeleteVisitDialog: React.FC<DeleteVisitDialogProps> = ({
|
|
16
|
+
closeModal,
|
|
17
|
+
mutateActiveVisit,
|
|
18
|
+
patientUuid,
|
|
19
|
+
activeVisit,
|
|
20
|
+
}) => {
|
|
15
21
|
const { t } = useTranslation();
|
|
16
|
-
const { isDeletingVisit, initiateDeletingVisit } = useDeleteVisit(
|
|
22
|
+
const { isDeletingVisit, initiateDeletingVisit } = useDeleteVisit(activeVisit, mutateActiveVisit, closeModal);
|
|
17
23
|
|
|
18
24
|
return (
|
|
19
25
|
<div>
|
|
@@ -24,7 +30,7 @@ const DeleteVisitDialog: React.FC<DeleteVisitDialogProps> = ({ closeModal, patie
|
|
|
24
30
|
<ModalBody>
|
|
25
31
|
<p className={styles.body}>
|
|
26
32
|
{t('confirmDeleteVisitText', 'Deleting this {{visit}} will delete its associated encounters.', {
|
|
27
|
-
visit:
|
|
33
|
+
visit: activeVisit?.visitType?.display ?? t('visit', 'Visit'),
|
|
28
34
|
})}
|
|
29
35
|
</p>
|
|
30
36
|
</ModalBody>
|
|
@@ -9,6 +9,7 @@ import DeleteVisitDialog from './delete-visit-dialog.component';
|
|
|
9
9
|
const mockCloseModal = jest.fn();
|
|
10
10
|
const mockOpenmrsFetch = jest.mocked(openmrsFetch);
|
|
11
11
|
const mockShowSnackbar = jest.mocked(showSnackbar);
|
|
12
|
+
const mockActiveVisit = jest.fn();
|
|
12
13
|
|
|
13
14
|
describe('Delete visit', () => {
|
|
14
15
|
it('voids the visit and voids its associated encounters', async () => {
|
|
@@ -21,7 +22,14 @@ describe('Delete visit', () => {
|
|
|
21
22
|
|
|
22
23
|
mockOpenmrsFetch.mockResolvedValue(response as FetchResponse);
|
|
23
24
|
|
|
24
|
-
render(
|
|
25
|
+
render(
|
|
26
|
+
<DeleteVisitDialog
|
|
27
|
+
activeVisit={mockCurrentVisit}
|
|
28
|
+
mutateActiveVisit={mockActiveVisit}
|
|
29
|
+
closeModal={mockCloseModal}
|
|
30
|
+
patientUuid={mockPatient.id}
|
|
31
|
+
/>,
|
|
32
|
+
);
|
|
25
33
|
|
|
26
34
|
const cancelButton = screen.getByRole('button', { name: /^cancel$/i });
|
|
27
35
|
const deleteVisitButton = screen.getByRole('button', { name: /delete visit$/i });
|
|
@@ -46,6 +54,7 @@ describe('Delete visit', () => {
|
|
|
46
54
|
subtitle: 'Facility Visit deleted successfully',
|
|
47
55
|
}),
|
|
48
56
|
);
|
|
57
|
+
expect(mockActiveVisit).toHaveBeenCalledTimes(1);
|
|
49
58
|
});
|
|
50
59
|
|
|
51
60
|
it('displays an error notification if there was problem with deleting a visit', async () => {
|
|
@@ -53,7 +62,14 @@ describe('Delete visit', () => {
|
|
|
53
62
|
|
|
54
63
|
mockOpenmrsFetch.mockRejectedValueOnce({ message: 'Internal server error', status: 500 });
|
|
55
64
|
|
|
56
|
-
render(
|
|
65
|
+
render(
|
|
66
|
+
<DeleteVisitDialog
|
|
67
|
+
activeVisit={mockCurrentVisit}
|
|
68
|
+
mutateActiveVisit={mockActiveVisit}
|
|
69
|
+
closeModal={mockCloseModal}
|
|
70
|
+
patientUuid={mockPatient.id}
|
|
71
|
+
/>,
|
|
72
|
+
);
|
|
57
73
|
|
|
58
74
|
const cancelButton = screen.getByRole('button', { name: /^cancel$/i });
|
|
59
75
|
const deleteVisitButton = screen.getByRole('button', { name: /delete visit$/i });
|
|
@@ -74,5 +90,7 @@ describe('Delete visit', () => {
|
|
|
74
90
|
kind: 'error',
|
|
75
91
|
title: 'Error deleting visit',
|
|
76
92
|
});
|
|
93
|
+
|
|
94
|
+
expect(mockActiveVisit).toHaveBeenCalledTimes(1);
|
|
77
95
|
});
|
|
78
96
|
});
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
4
4
|
import { showSnackbar, updateVisit, useVisit } from '@openmrs/esm-framework';
|
|
5
|
+
import { usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
5
6
|
import styles from './end-visit-dialog.scss';
|
|
6
7
|
|
|
7
8
|
interface EndVisitDialogProps {
|
|
@@ -13,10 +14,13 @@ interface EndVisitDialogProps {
|
|
|
13
14
|
* This modal shows up when user clicks on the "End visit" button in the action menu within the
|
|
14
15
|
* patient banner. It should only show when the patient has an active visit. See stop-visit.component.tsx
|
|
15
16
|
* for the button.
|
|
17
|
+
*
|
|
18
|
+
* This dialog uses the patient chart store and SHOULD only be mounted within the patient chart
|
|
16
19
|
*/
|
|
17
20
|
const EndVisitDialog: React.FC<EndVisitDialogProps> = ({ patientUuid, closeModal }) => {
|
|
18
21
|
const { t } = useTranslation();
|
|
19
22
|
const { activeVisit, mutate } = useVisit(patientUuid);
|
|
23
|
+
const { visitContext, setVisitContext } = usePatientChartStore(patientUuid);
|
|
20
24
|
|
|
21
25
|
const handleEndVisit = () => {
|
|
22
26
|
if (activeVisit) {
|
|
@@ -31,7 +35,9 @@ const EndVisitDialog: React.FC<EndVisitDialogProps> = ({ patientUuid, closeModal
|
|
|
31
35
|
mutate();
|
|
32
36
|
window.dispatchEvent(new CustomEvent('queue-entry-updated'));
|
|
33
37
|
closeModal();
|
|
34
|
-
|
|
38
|
+
if (visitContext?.uuid === activeVisit.uuid) {
|
|
39
|
+
setVisitContext(null, null);
|
|
40
|
+
}
|
|
35
41
|
showSnackbar({
|
|
36
42
|
isLowContrast: true,
|
|
37
43
|
kind: 'success',
|
|
@@ -4,6 +4,7 @@ import { screen, render } from '@testing-library/react';
|
|
|
4
4
|
import { showSnackbar, updateVisit, useVisit, type Visit, type FetchResponse } from '@openmrs/esm-framework';
|
|
5
5
|
import { mockCurrentVisit } from '__mocks__';
|
|
6
6
|
import EndVisitDialog from './end-visit-dialog.component';
|
|
7
|
+
import { usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
7
8
|
|
|
8
9
|
const endVisitPayload = {
|
|
9
10
|
stopDatetime: expect.any(Date),
|
|
@@ -15,6 +16,22 @@ const mockShowSnackbar = jest.mocked(showSnackbar);
|
|
|
15
16
|
const mockUseVisit = jest.mocked(useVisit);
|
|
16
17
|
const mockUpdateVisit = jest.mocked(updateVisit);
|
|
17
18
|
|
|
19
|
+
const mockUsePatientChartStore = jest.mocked(usePatientChartStore);
|
|
20
|
+
const mockSetVisitContext = jest.fn();
|
|
21
|
+
|
|
22
|
+
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
23
|
+
usePatientChartStore: jest.fn(),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
27
|
+
patientUuid: 'patient-123',
|
|
28
|
+
patient: null,
|
|
29
|
+
visitContext: mockCurrentVisit,
|
|
30
|
+
mutateVisitContext: jest.fn(),
|
|
31
|
+
setPatient: jest.fn(),
|
|
32
|
+
setVisitContext: mockSetVisitContext,
|
|
33
|
+
});
|
|
34
|
+
|
|
18
35
|
describe('End visit dialog', () => {
|
|
19
36
|
beforeEach(() => {
|
|
20
37
|
mockUseVisit.mockReturnValue({
|
|
@@ -66,6 +83,8 @@ describe('End visit dialog', () => {
|
|
|
66
83
|
kind: 'success',
|
|
67
84
|
title: 'Visit ended',
|
|
68
85
|
});
|
|
86
|
+
|
|
87
|
+
expect(mockSetVisitContext).toHaveBeenCalledTimes(1);
|
|
69
88
|
});
|
|
70
89
|
|
|
71
90
|
test('displays an error snackbar if there was a problem ending a visit', async () => {
|
|
@@ -20,13 +20,14 @@ const ActiveVisitActions: React.FC<ActiveVisitActionsInterface> = ({ visit, pati
|
|
|
20
20
|
const isTablet = layout === 'tablet';
|
|
21
21
|
const isMobile = layout === 'phone';
|
|
22
22
|
|
|
23
|
-
const launchAllergiesFormWorkspace = useLaunchWorkspaceRequiringVisit('patient-allergy-form-workspace');
|
|
24
|
-
const launchAppointmentsFormWorkspace = useLaunchWorkspaceRequiringVisit('appointments-form-workspace');
|
|
25
|
-
const launchClinicalFormsWorkspace = useLaunchWorkspaceRequiringVisit('clinical-forms-workspace');
|
|
26
|
-
const launchConditionsFormWorkspace = useLaunchWorkspaceRequiringVisit('conditions-form-workspace');
|
|
27
|
-
const launchOrderBasketFormWorkspace = useLaunchWorkspaceRequiringVisit('order-basket');
|
|
28
|
-
const launchVisitNotesFormWorkspace = useLaunchWorkspaceRequiringVisit('visit-notes-form-workspace');
|
|
23
|
+
const launchAllergiesFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'patient-allergy-form-workspace');
|
|
24
|
+
const launchAppointmentsFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'appointments-form-workspace');
|
|
25
|
+
const launchClinicalFormsWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'clinical-forms-workspace');
|
|
26
|
+
const launchConditionsFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'conditions-form-workspace');
|
|
27
|
+
const launchOrderBasketFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'order-basket');
|
|
28
|
+
const launchVisitNotesFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'visit-notes-form-workspace');
|
|
29
29
|
const launchVitalsAndBiometricsFormWorkspace = useLaunchWorkspaceRequiringVisit(
|
|
30
|
+
patientUuid,
|
|
30
31
|
'patient-vitals-biometrics-form-workspace',
|
|
31
32
|
);
|
|
32
33
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { CardHeader, EmptyState } from '@openmrs/esm-patient-common-lib';
|
|
3
|
+
import { launchWorkspace } from '@openmrs/esm-framework';
|
|
4
|
+
import { CardHeader, EmptyState, usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
6
5
|
import VisitSummary from './past-visits-components/visit-summary.component';
|
|
7
6
|
import styles from './current-visit-summary.scss';
|
|
8
7
|
|
|
@@ -10,29 +9,23 @@ interface CurrentVisitSummaryProps {
|
|
|
10
9
|
patientUuid: string;
|
|
11
10
|
}
|
|
12
11
|
|
|
12
|
+
/**
|
|
13
|
+
* This extension is not used in the refapp.
|
|
14
|
+
* This extension uses the patient chart store and SHOULD only be mounted within the patient chart
|
|
15
|
+
*/
|
|
13
16
|
const CurrentVisitSummary: React.FC<CurrentVisitSummaryProps> = ({ patientUuid }) => {
|
|
14
17
|
const { t } = useTranslation();
|
|
15
|
-
const {
|
|
18
|
+
const { patientUuid: storePatientUuid, visitContext } = usePatientChartStore(patientUuid);
|
|
16
19
|
|
|
17
|
-
if (
|
|
18
|
-
return
|
|
19
|
-
<InlineLoading
|
|
20
|
-
status="active"
|
|
21
|
-
iconDescription={t('loading', 'Loading')}
|
|
22
|
-
description={t('loadingVisit', 'Loading current visit...')}
|
|
23
|
-
/>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (!!error) {
|
|
28
|
-
return <ErrorState headerTitle={t('failedToLoadCurrentVisit', 'Failed loading current visit')} error={error} />;
|
|
20
|
+
if (patientUuid !== storePatientUuid) {
|
|
21
|
+
return null;
|
|
29
22
|
}
|
|
30
23
|
|
|
31
|
-
if (!
|
|
24
|
+
if (!visitContext) {
|
|
32
25
|
return (
|
|
33
26
|
<EmptyState
|
|
34
27
|
headerTitle={t('currentVisit', 'Current visit')}
|
|
35
|
-
displayText={t('
|
|
28
|
+
displayText={t('activeVisits__lower', 'active visits')}
|
|
36
29
|
launchForm={() =>
|
|
37
30
|
launchWorkspace('start-visit-workspace-form', { openedFrom: 'patient-chart-current-visit-summary' })
|
|
38
31
|
}
|
|
@@ -43,10 +36,10 @@ const CurrentVisitSummary: React.FC<CurrentVisitSummaryProps> = ({ patientUuid }
|
|
|
43
36
|
return (
|
|
44
37
|
<div className={styles.container}>
|
|
45
38
|
<CardHeader title={t('currentVisit', 'Current visit')}>
|
|
46
|
-
<span
|
|
39
|
+
<span />
|
|
47
40
|
</CardHeader>
|
|
48
41
|
<div className={styles.visitSummaryCard}>
|
|
49
|
-
<VisitSummary visit={
|
|
42
|
+
<VisitSummary visit={visitContext} patientUuid={patientUuid} />
|
|
50
43
|
</div>
|
|
51
44
|
</div>
|
|
52
45
|
);
|
|
@@ -1,34 +1,52 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import {
|
|
4
|
-
import { waitForLoadingToFinish } from 'tools';
|
|
5
|
-
import
|
|
3
|
+
import { getConfig } from '@openmrs/esm-framework';
|
|
4
|
+
import { mockPatient, waitForLoadingToFinish } from 'tools';
|
|
5
|
+
import { usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
6
|
+
import CurrentVisitSummary from './current-visit-summary.extension';
|
|
6
7
|
|
|
7
8
|
const mockGetConfig = jest.mocked(getConfig);
|
|
8
|
-
const
|
|
9
|
+
const mockUsePatientChartStore = jest.mocked(usePatientChartStore);
|
|
10
|
+
|
|
11
|
+
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
12
|
+
...jest.requireActual('@openmrs/esm-patient-common-lib'),
|
|
13
|
+
usePatientChartStore: jest.fn(),
|
|
14
|
+
}));
|
|
9
15
|
|
|
10
16
|
describe('CurrentVisitSummary', () => {
|
|
11
17
|
test('renders an empty state when there is no active visit', () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
mutate: jest.fn(),
|
|
18
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
19
|
+
patientUuid: mockPatient.id,
|
|
20
|
+
patient: mockPatient,
|
|
21
|
+
visitContext: null,
|
|
22
|
+
mutateVisitContext: null,
|
|
23
|
+
setPatient: jest.fn(),
|
|
24
|
+
setVisitContext: jest.fn(),
|
|
20
25
|
});
|
|
21
|
-
|
|
22
|
-
render(<CurrentVisitSummary patientUuid="some-uuid" />);
|
|
26
|
+
render(<CurrentVisitSummary patientUuid={mockPatient.id} />);
|
|
23
27
|
expect(screen.getByText(/current visit/i)).toBeInTheDocument();
|
|
24
|
-
expect(screen.getByText('There are no active
|
|
28
|
+
expect(screen.getByText('There are no active visits to display for this patient')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('returns null when patientUuid does not match store patientUuid', () => {
|
|
32
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
33
|
+
patientUuid: 'different-patient-id',
|
|
34
|
+
patient: mockPatient,
|
|
35
|
+
visitContext: null,
|
|
36
|
+
mutateVisitContext: null,
|
|
37
|
+
setPatient: jest.fn(),
|
|
38
|
+
setVisitContext: jest.fn(),
|
|
39
|
+
});
|
|
40
|
+
render(<CurrentVisitSummary patientUuid={mockPatient.id} />);
|
|
41
|
+
expect(screen.queryByText(/current visit/i)).not.toBeInTheDocument();
|
|
25
42
|
});
|
|
26
43
|
|
|
27
|
-
test('renders a visit summary when
|
|
44
|
+
test('renders a visit summary when visit context exists', async () => {
|
|
28
45
|
mockGetConfig.mockResolvedValue({ htmlFormEntryForms: [] });
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
47
|
+
patientUuid: mockPatient.id,
|
|
48
|
+
patient: mockPatient,
|
|
49
|
+
visitContext: {
|
|
32
50
|
uuid: 'some-uuid',
|
|
33
51
|
display: 'Visit 1',
|
|
34
52
|
startDatetime: '2021-03-23T10:00:00.000+0300',
|
|
@@ -42,15 +60,17 @@ describe('CurrentVisitSummary', () => {
|
|
|
42
60
|
display: 'Visit Type 1',
|
|
43
61
|
},
|
|
44
62
|
encounters: [],
|
|
63
|
+
patient: {
|
|
64
|
+
uuid: mockPatient.id,
|
|
65
|
+
display: 'Test Patient',
|
|
66
|
+
},
|
|
45
67
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
isValidating: false,
|
|
50
|
-
mutate: jest.fn(),
|
|
68
|
+
mutateVisitContext: null,
|
|
69
|
+
setPatient: jest.fn(),
|
|
70
|
+
setVisitContext: jest.fn(),
|
|
51
71
|
});
|
|
52
72
|
|
|
53
|
-
render(<CurrentVisitSummary patientUuid=
|
|
73
|
+
render(<CurrentVisitSummary patientUuid={mockPatient.id} />);
|
|
54
74
|
|
|
55
75
|
await waitForLoadingToFinish();
|
|
56
76
|
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
type HtmlFormEntryForm,
|
|
45
45
|
launchFormEntryOrHtmlForms,
|
|
46
46
|
invalidateVisitAndEncounterData,
|
|
47
|
+
usePatientChartStore,
|
|
47
48
|
} from '@openmrs/esm-patient-common-lib';
|
|
48
49
|
import { jsonSchemaResourceName } from '../../../../constants';
|
|
49
50
|
import {
|
|
@@ -80,7 +81,7 @@ const EncountersTable: React.FC<EncountersTableProps> = ({
|
|
|
80
81
|
const pageSizes = [10, 20, 30, 40, 50];
|
|
81
82
|
const desktopLayout = isDesktop(useLayoutType());
|
|
82
83
|
const session = useSession();
|
|
83
|
-
const {
|
|
84
|
+
const { mutateVisitContext } = usePatientChartStore(patientUuid);
|
|
84
85
|
const { mutate } = useSWRConfig();
|
|
85
86
|
const responsiveSize = desktopLayout ? 'sm' : 'lg';
|
|
86
87
|
const { data: encounterTypes, isLoading: isLoadingEncounterTypes } = useEncounterTypes();
|
|
@@ -133,7 +134,7 @@ const EncountersTable: React.FC<EncountersTableProps> = ({
|
|
|
133
134
|
deleteEncounter(encounterUuid, abortController)
|
|
134
135
|
.then(() => {
|
|
135
136
|
// Update current visit data for critical components
|
|
136
|
-
|
|
137
|
+
mutateVisitContext?.();
|
|
137
138
|
|
|
138
139
|
// Also invalidate visit history and encounter tables since the encounter was deleted
|
|
139
140
|
invalidateVisitAndEncounterData(mutate, patientUuid);
|
|
@@ -160,7 +161,7 @@ const EncountersTable: React.FC<EncountersTableProps> = ({
|
|
|
160
161
|
},
|
|
161
162
|
});
|
|
162
163
|
},
|
|
163
|
-
[mutate,
|
|
164
|
+
[mutate, mutateVisitContext, patientUuid, t],
|
|
164
165
|
);
|
|
165
166
|
|
|
166
167
|
if (isLoadingEncounterTypes || isLoading) {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { SelectItem, TimePickerSelect, TimePicker, Checkbox } from '@carbon/react';
|
|
2
|
-
import { OpenmrsDatePicker, ResponsiveWrapper, useFeatureFlag,
|
|
2
|
+
import { OpenmrsDatePicker, ResponsiveWrapper, useFeatureFlag, type Visit } from '@openmrs/esm-framework';
|
|
3
3
|
import React, { useEffect, useState } from 'react';
|
|
4
4
|
import { type Control, Controller, useForm } from 'react-hook-form';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import styles from './restrospective-date-time-picker.scss';
|
|
7
|
-
import { useSystemVisitSetting } from '@openmrs/esm-patient-common-lib';
|
|
8
7
|
|
|
9
8
|
type FormValues = {
|
|
10
9
|
retrospectiveDate: Date;
|
|
@@ -13,23 +12,22 @@ type FormValues = {
|
|
|
13
12
|
};
|
|
14
13
|
|
|
15
14
|
type RetrospectiveDateTimePickerProps = {
|
|
16
|
-
|
|
15
|
+
visitContext: Visit;
|
|
17
16
|
control?: Control<FormValues>;
|
|
18
17
|
onChange?: (data: FormValues) => void;
|
|
19
18
|
};
|
|
20
19
|
|
|
21
20
|
const RetrospectiveDateTimePicker = ({
|
|
22
|
-
|
|
21
|
+
visitContext,
|
|
23
22
|
control: propControl,
|
|
24
23
|
onChange,
|
|
25
24
|
}: RetrospectiveDateTimePickerProps) => {
|
|
26
25
|
const { t } = useTranslation();
|
|
27
26
|
const isRdeEnabled = useFeatureFlag('rde');
|
|
28
27
|
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const minDate = currentVisit?.startDatetime;
|
|
28
|
+
const isActiveVisit = !Boolean(visitContext && visitContext.stopDatetime);
|
|
29
|
+
const maxDate = visitContext?.stopDatetime;
|
|
30
|
+
const minDate = visitContext?.startDatetime;
|
|
33
31
|
|
|
34
32
|
const [manuallyEnableDateTimePicker, setManuallyEnableDateTimePicker] = useState<boolean>(false);
|
|
35
33
|
|