@ampath/esm-dispensing-app 1.10.0-next.2 → 1.10.0-next.21
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/dist/3568.js +1 -1
- package/dist/3568.js.map +1 -1
- package/dist/3693.js +1 -0
- package/dist/3693.js.map +1 -0
- package/dist/{7252.js → 4099.js} +1 -1
- package/dist/{7252.js.map → 4099.js.map} +1 -1
- package/dist/4300.js +1 -1
- package/dist/6841.js +1 -1
- package/dist/6841.js.map +1 -1
- package/dist/963.js +1 -1
- package/dist/963.js.map +1 -1
- package/dist/9764.js +1 -0
- package/dist/9764.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-dispensing-app.js +1 -1
- package/dist/openmrs-esm-dispensing-app.js.buildmanifest.json +87 -87
- package/dist/routes.json +1 -1
- package/package.json +2 -1
- package/src/bill/bill.resource.ts +89 -0
- package/src/components/action-buttons.component.tsx +97 -5
- package/src/components/action-buttons.test.tsx +22 -2
- package/src/components/prescription-actions/close-action-button.component.tsx +3 -0
- package/src/components/prescription-actions/dispense-action-button.component.tsx +17 -4
- package/src/components/prescription-actions/generate-bill-action-button.component.tsx +53 -0
- package/src/components/prescription-actions/pause-action-button.component.tsx +3 -0
- package/src/config-schema.ts +36 -0
- package/src/forms/close-dispense-form.workspace.tsx +13 -1
- package/src/forms/pause-dispense-form.workspace.tsx +13 -1
- package/src/index.ts +3 -1
- package/src/location/location.resource.test.tsx +46 -8
- package/src/location/location.resource.tsx +7 -4
- package/src/medication-request/medication-request.resource.test.tsx +2 -6
- package/src/medication-request/medication-request.resource.tsx +97 -11
- package/src/prescriptions/prescription-details.component.tsx +59 -18
- package/src/prescriptions/prescription-details.test.tsx +70 -1
- package/src/prescriptions/prescription-tab-panel.component.tsx +23 -2
- package/src/prescriptions/prescriptions-table.component.tsx +53 -23
- package/src/prescriptions/priority-tag.component.tsx +44 -0
- package/src/prescriptions/priority-tag.scss +12 -0
- package/src/print-prescription/prescription-printout.component.tsx +10 -3
- package/src/routes.json +5 -0
- package/src/types.ts +53 -1
- package/src/utils.test.ts +9 -9
- package/src/utils.ts +36 -2
- package/src/visit/visit.resource.ts +1 -1
- package/translations/en.json +5 -0
- package/dist/609.js +0 -1
- package/dist/609.js.map +0 -1
- package/dist/8885.js +0 -1
- package/dist/8885.js.map +0 -1
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import dayjs from 'dayjs';
|
|
3
3
|
import useSWR from 'swr';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
fhirBaseUrl,
|
|
6
|
+
openmrsFetch,
|
|
7
|
+
type Order,
|
|
8
|
+
parseDate,
|
|
9
|
+
restBaseUrl,
|
|
10
|
+
useConfig,
|
|
11
|
+
useSession,
|
|
12
|
+
} from '@openmrs/esm-framework';
|
|
5
13
|
import { JSON_MERGE_PATH_MIME_TYPE, OPENMRS_FHIR_EXT_REQUEST_FULFILLER_STATUS } from '../constants';
|
|
6
14
|
import {
|
|
7
15
|
type AllergyIntoleranceResponse,
|
|
@@ -14,6 +22,7 @@ import {
|
|
|
14
22
|
type MedicationRequestFulfillerStatus,
|
|
15
23
|
type MedicationRequestBundle,
|
|
16
24
|
type SimpleLocation,
|
|
25
|
+
type QueueEntryResult,
|
|
17
26
|
} from '../types';
|
|
18
27
|
import {
|
|
19
28
|
getPrescriptionDetailsEndpoint,
|
|
@@ -23,7 +32,11 @@ import {
|
|
|
23
32
|
sortMedicationDispensesByWhenHandedOver,
|
|
24
33
|
computePrescriptionStatusMessageCode,
|
|
25
34
|
getAssociatedMedicationDispenses,
|
|
35
|
+
getEtlBaseUrl,
|
|
26
36
|
} from '../utils';
|
|
37
|
+
import { type PharmacyConfig } from '../config-schema';
|
|
38
|
+
|
|
39
|
+
const ACTIVE_STATUS_FETCH_COUNT = 100;
|
|
27
40
|
|
|
28
41
|
export function usePrescriptionsTable(
|
|
29
42
|
loadData: boolean,
|
|
@@ -36,14 +49,16 @@ export function usePrescriptionsTable(
|
|
|
36
49
|
medicationRequestExpirationPeriodInDays: number,
|
|
37
50
|
refreshInterval: number,
|
|
38
51
|
) {
|
|
52
|
+
const fetchPageSize = status === 'ACTIVE' ? ACTIVE_STATUS_FETCH_COUNT : pageSize;
|
|
53
|
+
const fetchPageOffset = status === 'ACTIVE' ? 0 : pageOffset;
|
|
39
54
|
const { data, error } = useSWR<{ data: EncounterResponse }, Error>(
|
|
40
55
|
loadData
|
|
41
56
|
? getPrescriptionTableEndpoint(
|
|
42
57
|
customPrescriptionsTableEndpoint,
|
|
43
58
|
status,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
fetchPageOffset,
|
|
60
|
+
fetchPageSize,
|
|
61
|
+
'',
|
|
47
62
|
patientSearchTerm,
|
|
48
63
|
locations?.map((location) => location.id).join(','),
|
|
49
64
|
)
|
|
@@ -51,18 +66,28 @@ export function usePrescriptionsTable(
|
|
|
51
66
|
openmrsFetch,
|
|
52
67
|
{ refreshInterval: refreshInterval },
|
|
53
68
|
);
|
|
69
|
+
const { queueEntries } = useQueueEntries();
|
|
54
70
|
|
|
55
71
|
let prescriptionsTableRows: PrescriptionsTableRow[];
|
|
56
72
|
if (data) {
|
|
57
73
|
const entries = data?.data.entry;
|
|
58
|
-
|
|
59
|
-
|
|
74
|
+
const filteredEntries =
|
|
75
|
+
status === 'ACTIVE' && entries
|
|
76
|
+
? entries.filter((entry) =>
|
|
77
|
+
dayjs(entry?.resource?.meta?.lastUpdated).isAfter(
|
|
78
|
+
dayjs().startOf('day').subtract(medicationRequestExpirationPeriodInDays, 'day'),
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
: entries;
|
|
82
|
+
|
|
83
|
+
if (filteredEntries) {
|
|
84
|
+
const encounters = filteredEntries
|
|
60
85
|
.filter((entry) => entry?.resource?.resourceType == 'Encounter')
|
|
61
86
|
.map((entry) => entry.resource as Encounter);
|
|
62
|
-
const medicationRequests =
|
|
87
|
+
const medicationRequests = filteredEntries
|
|
63
88
|
.filter((entry) => entry?.resource?.resourceType == 'MedicationRequest')
|
|
64
89
|
.map((entry) => entry.resource as MedicationRequest);
|
|
65
|
-
const medicationDispenses =
|
|
90
|
+
const medicationDispenses = filteredEntries
|
|
66
91
|
.filter((entry) => entry?.resource?.resourceType == 'MedicationDispense')
|
|
67
92
|
.map((entry) => entry.resource as MedicationDispense)
|
|
68
93
|
.sort(sortMedicationDispensesByWhenHandedOver);
|
|
@@ -77,11 +102,16 @@ export function usePrescriptionsTable(
|
|
|
77
102
|
const medicationDispensesForMedicationRequests = medicationDispenses.filter((medicationDispense) =>
|
|
78
103
|
medicationRequestReferences.includes(medicationDispense.authorizingPrescription[0]?.reference),
|
|
79
104
|
);
|
|
105
|
+
|
|
106
|
+
const patientUuid = encounter?.subject?.reference?.split('/')[1];
|
|
107
|
+
const priority = queueEntries?.find((q) => q.patient_uuid === patientUuid)?.priority ?? 'NON-URGENT';
|
|
108
|
+
|
|
80
109
|
return buildPrescriptionsTableRow(
|
|
81
110
|
encounter,
|
|
82
111
|
medicationRequestsForEncounter,
|
|
83
112
|
medicationDispensesForMedicationRequests,
|
|
84
113
|
medicationRequestExpirationPeriodInDays,
|
|
114
|
+
priority,
|
|
85
115
|
);
|
|
86
116
|
});
|
|
87
117
|
prescriptionsTableRows.sort((a, b) => (a.created < b.created ? 1 : -1));
|
|
@@ -94,7 +124,7 @@ export function usePrescriptionsTable(
|
|
|
94
124
|
prescriptionsTableRows,
|
|
95
125
|
error: error,
|
|
96
126
|
isLoading: !prescriptionsTableRows && !error,
|
|
97
|
-
totalOrders: data?.data.total,
|
|
127
|
+
totalOrders: status === 'ACTIVE' ? prescriptionsTableRows?.length ?? 0 : data?.data.total,
|
|
98
128
|
};
|
|
99
129
|
}
|
|
100
130
|
|
|
@@ -103,10 +133,11 @@ function buildPrescriptionsTableRow(
|
|
|
103
133
|
medicationRequests: Array<MedicationRequest>,
|
|
104
134
|
medicationDispense: Array<MedicationDispense>,
|
|
105
135
|
medicationRequestExpirationPeriodInDays: number,
|
|
136
|
+
priority: string,
|
|
106
137
|
): PrescriptionsTableRow {
|
|
107
138
|
return {
|
|
108
139
|
id: encounter?.id,
|
|
109
|
-
created: encounter?.period?.start,
|
|
140
|
+
created: encounter?.meta?.lastUpdated, //encounter?.period?.start,
|
|
110
141
|
patient: {
|
|
111
142
|
name: encounter?.subject?.display,
|
|
112
143
|
uuid: encounter?.subject?.reference?.split('/')[1],
|
|
@@ -125,6 +156,7 @@ function buildPrescriptionsTableRow(
|
|
|
125
156
|
prescriber: [...new Set(medicationRequests.map((o) => o.requester.display))].join(', '),
|
|
126
157
|
status: computePrescriptionStatusMessageCode(medicationRequests, medicationRequestExpirationPeriodInDays),
|
|
127
158
|
location: encounter?.location ? encounter?.location[0]?.location.display : null,
|
|
159
|
+
priority: priority,
|
|
128
160
|
};
|
|
129
161
|
}
|
|
130
162
|
|
|
@@ -255,3 +287,57 @@ export function updateMedicationRequestFulfillerStatus(
|
|
|
255
287
|
},
|
|
256
288
|
});
|
|
257
289
|
}
|
|
290
|
+
|
|
291
|
+
export function useOrders(encounterUuid: string) {
|
|
292
|
+
// const customRepresentation = `custom:(uuid,display,orders:(uuid,orderNumber,concept:(uuid,display)))`;
|
|
293
|
+
const customRepresentation = `full`;
|
|
294
|
+
const url = `${restBaseUrl}/encounter/${encounterUuid}?v=${customRepresentation}`;
|
|
295
|
+
const { data, error, mutate, isLoading, isValidating } = useSWR<{
|
|
296
|
+
data: {
|
|
297
|
+
orders: Array<Order>;
|
|
298
|
+
};
|
|
299
|
+
}>(`${url}`, openmrsFetch);
|
|
300
|
+
|
|
301
|
+
const orders = data?.data?.orders;
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
orders: orders ?? [],
|
|
305
|
+
isLoading,
|
|
306
|
+
isError: error,
|
|
307
|
+
mutate,
|
|
308
|
+
isValidating,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function useQueueEntries(patientUuid: string = '') {
|
|
313
|
+
const [etlBaseUrl, setEtlBaseUrl] = useState('');
|
|
314
|
+
const { sessionLocation } = useSession();
|
|
315
|
+
const { serviceUuid } = useConfig<PharmacyConfig>();
|
|
316
|
+
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
const fetchEtlBaseUrl = async () => {
|
|
319
|
+
const baseUrl = await getEtlBaseUrl();
|
|
320
|
+
setEtlBaseUrl(baseUrl);
|
|
321
|
+
};
|
|
322
|
+
fetchEtlBaseUrl();
|
|
323
|
+
}, []);
|
|
324
|
+
|
|
325
|
+
const url = `${etlBaseUrl}/queue-entry?locationUuid=${sessionLocation?.uuid}&serviceUuid=${serviceUuid}`;
|
|
326
|
+
const { data, error, mutate, isLoading, isValidating } = useSWR<{
|
|
327
|
+
data: { data: Array<QueueEntryResult> };
|
|
328
|
+
}>(etlBaseUrl ? `${url}` : null, openmrsFetch);
|
|
329
|
+
|
|
330
|
+
let filteredQueueEntries = data?.data?.data;
|
|
331
|
+
|
|
332
|
+
if (patientUuid) {
|
|
333
|
+
filteredQueueEntries = filteredQueueEntries?.filter((queueEntry) => queueEntry.patient_uuid === patientUuid);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
queueEntries: filteredQueueEntries ?? [],
|
|
338
|
+
isLoading,
|
|
339
|
+
isError: error,
|
|
340
|
+
mutate,
|
|
341
|
+
isValidating,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
@@ -1,17 +1,27 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { SkeletonText, Tag, Tile } from '@carbon/react';
|
|
3
3
|
import { WarningFilled } from '@carbon/react/icons';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { type PatientUuid, useConfig, UserHasAccess } from '@openmrs/esm-framework';
|
|
6
6
|
import { computeMedicationRequestCombinedStatus, getConceptCodingDisplay, useStaleEncounterUuids } from '../utils';
|
|
7
7
|
import { PRIVILEGE_CREATE_DISPENSE } from '../constants';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
type AllergyIntolerance,
|
|
10
|
+
type MedicationRequest,
|
|
11
|
+
MedicationRequestCombinedStatus,
|
|
12
|
+
MedicationRequestStatus,
|
|
13
|
+
} from '../types';
|
|
9
14
|
import { type PharmacyConfig } from '../config-schema';
|
|
10
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
useOrders,
|
|
17
|
+
usePatientAllergies,
|
|
18
|
+
usePrescriptionDetails,
|
|
19
|
+
} from '../medication-request/medication-request.resource';
|
|
11
20
|
import ActionButtons from '../components/action-buttons.component';
|
|
12
21
|
import MedicationEvent from '../components/medication-event.component';
|
|
13
22
|
import PrescriptionsActionsFooter from './prescription-actions.component';
|
|
14
23
|
import styles from './prescription-details.scss';
|
|
24
|
+
import { useBills, useInvalidateBills } from '../bill/bill.resource';
|
|
15
25
|
|
|
16
26
|
const PrescriptionDetails: React.FC<{
|
|
17
27
|
encounterUuid: string;
|
|
@@ -27,6 +37,21 @@ const PrescriptionDetails: React.FC<{
|
|
|
27
37
|
} = usePatientAllergies(patientUuid, config.refreshInterval);
|
|
28
38
|
const { medicationRequestBundles, error, isLoading } = usePrescriptionDetails(encounterUuid, config.refreshInterval);
|
|
29
39
|
const { staleEncounterUuids } = useStaleEncounterUuids();
|
|
40
|
+
const { orders, isLoading: isLoadingOrders } = useOrders(encounterUuid);
|
|
41
|
+
const { bills, isLoading: loadingBills } = useBills(patientUuid);
|
|
42
|
+
const hasActiveRequests = useMemo(() => {
|
|
43
|
+
return medicationRequestBundles.some(
|
|
44
|
+
(bundle) =>
|
|
45
|
+
computeMedicationRequestCombinedStatus(bundle.request, config.medicationRequestExpirationPeriodInDays) ===
|
|
46
|
+
MedicationRequestCombinedStatus.active,
|
|
47
|
+
);
|
|
48
|
+
}, [medicationRequestBundles, config]);
|
|
49
|
+
|
|
50
|
+
const invalidateBills = useInvalidateBills(patientUuid);
|
|
51
|
+
|
|
52
|
+
const mutated = () => {
|
|
53
|
+
invalidateBills();
|
|
54
|
+
};
|
|
30
55
|
|
|
31
56
|
const generateStatusTag = (medicationRequest: MedicationRequest): React.ReactNode => {
|
|
32
57
|
const combinedStatus: MedicationRequestCombinedStatus = computeMedicationRequestCombinedStatus(
|
|
@@ -124,21 +149,37 @@ const PrescriptionDetails: React.FC<{
|
|
|
124
149
|
)}
|
|
125
150
|
{medicationRequestBundles &&
|
|
126
151
|
(medicationRequestBundles.length > 0 ? (
|
|
127
|
-
medicationRequestBundles.map((bundle) =>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
152
|
+
medicationRequestBundles.map((bundle) => {
|
|
153
|
+
const medicationEvent =
|
|
154
|
+
bundle.request.status === MedicationRequestStatus.completed
|
|
155
|
+
? bundle.dispenses.find(
|
|
156
|
+
(b) =>
|
|
157
|
+
b.quantity.code === bundle.request.dispenseRequest.quantity.code ||
|
|
158
|
+
b.medicationReference.reference === bundle.request.medicationReference.reference,
|
|
159
|
+
)
|
|
160
|
+
: bundle.request;
|
|
161
|
+
return (
|
|
162
|
+
<MedicationEvent
|
|
163
|
+
key={bundle.request.id}
|
|
164
|
+
medicationEvent={medicationEvent}
|
|
165
|
+
status={generateStatusTag(bundle.request)}>
|
|
166
|
+
<UserHasAccess privilege={PRIVILEGE_CREATE_DISPENSE}>
|
|
167
|
+
<ActionButtons
|
|
168
|
+
patientUuid={patientUuid}
|
|
169
|
+
encounterUuid={encounterUuid}
|
|
170
|
+
medicationRequestBundle={bundle}
|
|
171
|
+
disabled={staleEncounterUuids.includes(encounterUuid)}
|
|
172
|
+
orders={orders}
|
|
173
|
+
bills={bills}
|
|
174
|
+
isLoading={loadingBills}
|
|
175
|
+
isLoadingOrders={isLoadingOrders}
|
|
176
|
+
hasActiveRequests={hasActiveRequests}
|
|
177
|
+
mutated={mutated}
|
|
178
|
+
/>
|
|
179
|
+
</UserHasAccess>
|
|
180
|
+
</MedicationEvent>
|
|
181
|
+
);
|
|
182
|
+
})
|
|
142
183
|
) : (
|
|
143
184
|
<p className={styles.emptyState}>{t('noPrescriptionsFound', 'No prescriptions found')}</p>
|
|
144
185
|
))}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import { useConfig } from '@openmrs/esm-framework';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
usePrescriptionDetails,
|
|
6
|
+
usePatientAllergies,
|
|
7
|
+
useOrders,
|
|
8
|
+
} from '../medication-request/medication-request.resource';
|
|
5
9
|
import { useStaleEncounterUuids } from '../utils';
|
|
6
10
|
import PrescriptionDetails from './prescription-details.component';
|
|
7
11
|
|
|
@@ -15,6 +19,7 @@ const mockUseConfig = jest.mocked(useConfig);
|
|
|
15
19
|
const mockUsePrescriptionDetails = jest.mocked(usePrescriptionDetails);
|
|
16
20
|
const mockUsePatientAllergies = jest.mocked(usePatientAllergies);
|
|
17
21
|
const mockUseStaleEncounterUuids = jest.mocked(useStaleEncounterUuids);
|
|
22
|
+
const mockUseOrders = jest.mocked(useOrders);
|
|
18
23
|
|
|
19
24
|
const mockEncounterUuid = 'test-encounter-uuid';
|
|
20
25
|
const mockPatientUuid = 'test-patient-uuid';
|
|
@@ -51,6 +56,14 @@ describe('PrescriptionDetails', () => {
|
|
|
51
56
|
isValidating: false,
|
|
52
57
|
});
|
|
53
58
|
|
|
59
|
+
mockUseOrders.mockReturnValue({
|
|
60
|
+
orders: [],
|
|
61
|
+
isLoading: false,
|
|
62
|
+
isValidating: false,
|
|
63
|
+
isError: undefined,
|
|
64
|
+
mutate: jest.fn(),
|
|
65
|
+
});
|
|
66
|
+
|
|
54
67
|
render(<PrescriptionDetails encounterUuid={mockEncounterUuid} patientUuid={mockPatientUuid} />);
|
|
55
68
|
|
|
56
69
|
// While loading allergies, should not show allergy details or no allergies message
|
|
@@ -73,6 +86,13 @@ describe('PrescriptionDetails', () => {
|
|
|
73
86
|
mutate: jest.fn(),
|
|
74
87
|
isValidating: false,
|
|
75
88
|
});
|
|
89
|
+
mockUseOrders.mockReturnValue({
|
|
90
|
+
orders: [],
|
|
91
|
+
isLoading: false,
|
|
92
|
+
isValidating: false,
|
|
93
|
+
isError: undefined,
|
|
94
|
+
mutate: jest.fn(),
|
|
95
|
+
});
|
|
76
96
|
|
|
77
97
|
render(<PrescriptionDetails encounterUuid={mockEncounterUuid} patientUuid={mockPatientUuid} />);
|
|
78
98
|
|
|
@@ -94,6 +114,13 @@ describe('PrescriptionDetails', () => {
|
|
|
94
114
|
mutate: jest.fn(),
|
|
95
115
|
isValidating: false,
|
|
96
116
|
});
|
|
117
|
+
mockUseOrders.mockReturnValue({
|
|
118
|
+
orders: [],
|
|
119
|
+
isLoading: false,
|
|
120
|
+
isValidating: false,
|
|
121
|
+
isError: undefined,
|
|
122
|
+
mutate: jest.fn(),
|
|
123
|
+
});
|
|
97
124
|
|
|
98
125
|
render(<PrescriptionDetails encounterUuid={mockEncounterUuid} patientUuid={mockPatientUuid} />);
|
|
99
126
|
|
|
@@ -130,6 +157,13 @@ describe('PrescriptionDetails', () => {
|
|
|
130
157
|
mutate: jest.fn(),
|
|
131
158
|
isValidating: false,
|
|
132
159
|
});
|
|
160
|
+
mockUseOrders.mockReturnValue({
|
|
161
|
+
orders: [],
|
|
162
|
+
isLoading: false,
|
|
163
|
+
isValidating: false,
|
|
164
|
+
isError: undefined,
|
|
165
|
+
mutate: jest.fn(),
|
|
166
|
+
});
|
|
133
167
|
|
|
134
168
|
render(<PrescriptionDetails encounterUuid={mockEncounterUuid} patientUuid={mockPatientUuid} />);
|
|
135
169
|
|
|
@@ -164,6 +198,13 @@ describe('PrescriptionDetails', () => {
|
|
|
164
198
|
mutate: jest.fn(),
|
|
165
199
|
isValidating: false,
|
|
166
200
|
});
|
|
201
|
+
mockUseOrders.mockReturnValue({
|
|
202
|
+
orders: [],
|
|
203
|
+
isLoading: false,
|
|
204
|
+
isValidating: false,
|
|
205
|
+
isError: undefined,
|
|
206
|
+
mutate: jest.fn(),
|
|
207
|
+
});
|
|
167
208
|
|
|
168
209
|
render(<PrescriptionDetails encounterUuid={mockEncounterUuid} patientUuid={mockPatientUuid} />);
|
|
169
210
|
|
|
@@ -193,6 +234,13 @@ describe('PrescriptionDetails', () => {
|
|
|
193
234
|
mutate: jest.fn(),
|
|
194
235
|
isValidating: false,
|
|
195
236
|
});
|
|
237
|
+
mockUseOrders.mockReturnValue({
|
|
238
|
+
orders: [],
|
|
239
|
+
isLoading: false,
|
|
240
|
+
isValidating: false,
|
|
241
|
+
isError: undefined,
|
|
242
|
+
mutate: jest.fn(),
|
|
243
|
+
});
|
|
196
244
|
|
|
197
245
|
render(<PrescriptionDetails encounterUuid={mockEncounterUuid} patientUuid={mockPatientUuid} />);
|
|
198
246
|
|
|
@@ -216,6 +264,13 @@ describe('PrescriptionDetails', () => {
|
|
|
216
264
|
mutate: jest.fn(),
|
|
217
265
|
isValidating: false,
|
|
218
266
|
});
|
|
267
|
+
mockUseOrders.mockReturnValue({
|
|
268
|
+
orders: [],
|
|
269
|
+
isLoading: false,
|
|
270
|
+
isValidating: false,
|
|
271
|
+
isError: undefined,
|
|
272
|
+
mutate: jest.fn(),
|
|
273
|
+
});
|
|
219
274
|
|
|
220
275
|
render(<PrescriptionDetails encounterUuid={mockEncounterUuid} patientUuid={mockPatientUuid} />);
|
|
221
276
|
|
|
@@ -237,6 +292,13 @@ describe('PrescriptionDetails', () => {
|
|
|
237
292
|
mutate: jest.fn(),
|
|
238
293
|
isValidating: false,
|
|
239
294
|
});
|
|
295
|
+
mockUseOrders.mockReturnValue({
|
|
296
|
+
orders: [],
|
|
297
|
+
isLoading: false,
|
|
298
|
+
isValidating: false,
|
|
299
|
+
isError: undefined,
|
|
300
|
+
mutate: jest.fn(),
|
|
301
|
+
});
|
|
240
302
|
|
|
241
303
|
render(<PrescriptionDetails encounterUuid={mockEncounterUuid} patientUuid={mockPatientUuid} />);
|
|
242
304
|
|
|
@@ -258,6 +320,13 @@ describe('PrescriptionDetails', () => {
|
|
|
258
320
|
mutate: jest.fn(),
|
|
259
321
|
isValidating: false,
|
|
260
322
|
});
|
|
323
|
+
mockUseOrders.mockReturnValue({
|
|
324
|
+
orders: [],
|
|
325
|
+
isLoading: false,
|
|
326
|
+
isValidating: false,
|
|
327
|
+
isError: undefined,
|
|
328
|
+
mutate: jest.fn(),
|
|
329
|
+
});
|
|
261
330
|
|
|
262
331
|
render(<PrescriptionDetails encounterUuid={mockEncounterUuid} patientUuid={mockPatientUuid} />);
|
|
263
332
|
|
|
@@ -31,10 +31,31 @@ const PrescriptionTabPanel: React.FC<PrescriptionTabPanelProps> = ({
|
|
|
31
31
|
// set any initially selected locations
|
|
32
32
|
useEffect(() => {
|
|
33
33
|
if (!isInitialized.current && !isFilterLocationsLoading && sessionLocation?.uuid) {
|
|
34
|
-
|
|
34
|
+
const initialLocations: SimpleLocation[] = [];
|
|
35
|
+
// Should display from the location associated with the pharmacy location
|
|
36
|
+
initialLocations.push(
|
|
37
|
+
...(locations?.filter((l) => l.associatedPharmacyLocations?.includes(sessionLocation?.uuid)) || []),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Should display from all the associated pharmacy locations of the current location
|
|
41
|
+
if (config.locationBehavior.locationFilter.useAssociatedPharmacyLocations) {
|
|
42
|
+
initialLocations.push(
|
|
43
|
+
...(locations?.filter((v) =>
|
|
44
|
+
locations?.find((l) => l.id === sessionLocation?.uuid).associatedPharmacyLocations?.includes(v.id),
|
|
45
|
+
) || []),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Should display from current location
|
|
50
|
+
if (config.locationBehavior.locationFilter.useCurrentLocation) {
|
|
51
|
+
initialLocations.push(...(locations?.filter((l) => l.id === sessionLocation?.uuid) || []));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
setFilterLocations(initialLocations || []);
|
|
55
|
+
|
|
35
56
|
isInitialized.current = true; // we only want to run when the component is first mounted so we don't override user changes
|
|
36
57
|
}
|
|
37
|
-
}, [isFilterLocationsLoading, sessionLocation, locations]);
|
|
58
|
+
}, [isFilterLocationsLoading, sessionLocation, locations, config]);
|
|
38
59
|
|
|
39
60
|
return (
|
|
40
61
|
<TabPanel>
|
|
@@ -17,13 +17,14 @@ import {
|
|
|
17
17
|
Tile,
|
|
18
18
|
} from '@carbon/react';
|
|
19
19
|
import { useTranslation } from 'react-i18next';
|
|
20
|
-
import { formatDatetime, parseDate, useConfig } from '@openmrs/esm-framework';
|
|
20
|
+
import { formatDatetime, parseDate, useConfig, usePagination } from '@openmrs/esm-framework';
|
|
21
21
|
import { usePrescriptionsTable } from '../medication-request/medication-request.resource';
|
|
22
22
|
import { type PharmacyConfig } from '../config-schema';
|
|
23
23
|
import { type SimpleLocation } from '../types';
|
|
24
24
|
import PatientInfoCell from '../patient/patient-info-cell.component';
|
|
25
25
|
import PrescriptionExpanded from './prescription-expanded.component';
|
|
26
26
|
import styles from './prescriptions.scss';
|
|
27
|
+
import PriorityTag from './priority-tag.component';
|
|
27
28
|
|
|
28
29
|
interface PrescriptionsTableProps {
|
|
29
30
|
loadData: boolean;
|
|
@@ -57,10 +58,20 @@ const PrescriptionsTable: React.FC<PrescriptionsTableProps> = ({
|
|
|
57
58
|
config.refreshInterval,
|
|
58
59
|
);
|
|
59
60
|
|
|
61
|
+
const isActiveClientPaged = status === 'ACTIVE';
|
|
62
|
+
const { results: paginatedRows, currentPage, goTo } = usePagination(prescriptionsTableRows ?? [], pageSize);
|
|
63
|
+
const rowsToDisplay = isActiveClientPaged ? paginatedRows : prescriptionsTableRows;
|
|
64
|
+
const paginationPage = isActiveClientPaged ? currentPage : page;
|
|
65
|
+
const paginationTotalItems = isActiveClientPaged ? prescriptionsTableRows?.length ?? 0 : totalOrders;
|
|
66
|
+
|
|
60
67
|
// reset back to page 1 whenever search term changes
|
|
61
68
|
useEffect(() => {
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
if (isActiveClientPaged) {
|
|
70
|
+
goTo(1);
|
|
71
|
+
} else {
|
|
72
|
+
setPage(1);
|
|
73
|
+
}
|
|
74
|
+
}, [debouncedSearchTerm, isActiveClientPaged, goTo]);
|
|
64
75
|
|
|
65
76
|
// dynamic status keys we need to maintain
|
|
66
77
|
// t('active', 'Active')
|
|
@@ -76,6 +87,7 @@ const PrescriptionsTable: React.FC<PrescriptionsTableProps> = ({
|
|
|
76
87
|
{ header: t('prescriber', 'Prescriber'), key: 'prescriber' },
|
|
77
88
|
{ header: t('drugs', 'Drugs'), key: 'drugs' },
|
|
78
89
|
{ header: t('lastDispenser', 'Last dispenser'), key: 'lastDispenser' },
|
|
90
|
+
{ header: t('priority', 'Priority'), key: 'priority' },
|
|
79
91
|
{ header: t('status', 'Status'), key: 'status' },
|
|
80
92
|
];
|
|
81
93
|
|
|
@@ -101,7 +113,7 @@ const PrescriptionsTable: React.FC<PrescriptionsTableProps> = ({
|
|
|
101
113
|
)}
|
|
102
114
|
{prescriptionsTableRows && (
|
|
103
115
|
<>
|
|
104
|
-
<DataTable rows={
|
|
116
|
+
<DataTable rows={rowsToDisplay} headers={columns} isSortable>
|
|
105
117
|
{({ rows, headers, getExpandHeaderProps, getHeaderProps, getRowProps, getTableProps }) => (
|
|
106
118
|
<TableContainer>
|
|
107
119
|
<Table {...getTableProps()} useZebraStyles>
|
|
@@ -117,19 +129,28 @@ const PrescriptionsTable: React.FC<PrescriptionsTableProps> = ({
|
|
|
117
129
|
{rows.map((row) => (
|
|
118
130
|
<React.Fragment key={row.id}>
|
|
119
131
|
<TableExpandRow {...getRowProps({ row })}>
|
|
120
|
-
{row.cells.map((cell) =>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
{row.cells.map((cell) => {
|
|
133
|
+
if (cell.info.header === 'priority') {
|
|
134
|
+
return (
|
|
135
|
+
<TableCell key={cell.id}>
|
|
136
|
+
<PriorityTag status={cell.value?.content ?? cell.value} />
|
|
137
|
+
</TableCell>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return (
|
|
141
|
+
<TableCell key={cell.id}>
|
|
142
|
+
{cell.id.endsWith('created') ? (
|
|
143
|
+
formatDatetime(parseDate(cell.value))
|
|
144
|
+
) : cell.id.endsWith('patient') ? (
|
|
145
|
+
<PatientInfoCell patient={cell.value} />
|
|
146
|
+
) : cell.id.endsWith('status') ? (
|
|
147
|
+
t(cell.value)
|
|
148
|
+
) : (
|
|
149
|
+
cell.value
|
|
150
|
+
)}
|
|
151
|
+
</TableCell>
|
|
152
|
+
);
|
|
153
|
+
})}
|
|
133
154
|
</TableExpandRow>
|
|
134
155
|
{row.isExpanded ? (
|
|
135
156
|
<TableExpandedRow colSpan={headers.length + 1}>
|
|
@@ -165,17 +186,26 @@ const PrescriptionsTable: React.FC<PrescriptionsTableProps> = ({
|
|
|
165
186
|
{prescriptionsTableRows?.length > 0 && (
|
|
166
187
|
<div className={styles.paginationContainer}>
|
|
167
188
|
<Pagination
|
|
168
|
-
page={
|
|
189
|
+
page={paginationPage}
|
|
169
190
|
pageSize={pageSize}
|
|
170
191
|
pageSizes={[10, 20, 30, 40, 50, 100]}
|
|
171
|
-
totalItems={
|
|
192
|
+
totalItems={paginationTotalItems}
|
|
172
193
|
onChange={({ page: newPage, pageSize: newPageSize }) => {
|
|
173
|
-
if (
|
|
174
|
-
|
|
194
|
+
if (isActiveClientPaged) {
|
|
195
|
+
if (newPageSize !== pageSize) {
|
|
196
|
+
setPageSize(newPageSize);
|
|
197
|
+
goTo(1);
|
|
198
|
+
} else {
|
|
199
|
+
goTo(newPage);
|
|
200
|
+
}
|
|
175
201
|
} else {
|
|
176
|
-
|
|
202
|
+
if (newPageSize !== pageSize) {
|
|
203
|
+
setPage(1);
|
|
204
|
+
} else {
|
|
205
|
+
setPage(newPage);
|
|
206
|
+
}
|
|
207
|
+
setPageSize(newPageSize);
|
|
177
208
|
}
|
|
178
|
-
setPageSize(newPageSize);
|
|
179
209
|
}}
|
|
180
210
|
/>
|
|
181
211
|
</div>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Tag } from '@carbon/react';
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
import styles from './priority-tag.scss';
|
|
4
|
+
|
|
5
|
+
interface PriorityTagProps {
|
|
6
|
+
status: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export enum QueueEntryPriority {
|
|
10
|
+
Emergency = 'EMERGENCY',
|
|
11
|
+
Priority = 'PRIORITY',
|
|
12
|
+
NonUrgent = 'NON-URGENT',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const PriorityTag: React.FC<PriorityTagProps> = ({ status }) => {
|
|
16
|
+
const tagClassName = useMemo(() => {
|
|
17
|
+
let className = 'gray';
|
|
18
|
+
if (
|
|
19
|
+
status.toUpperCase() === `${QueueEntryPriority.Emergency}` ||
|
|
20
|
+
status.toUpperCase() === `${QueueEntryPriority.Emergency} PRIORITY`
|
|
21
|
+
)
|
|
22
|
+
className = 'emergencyTag';
|
|
23
|
+
if (
|
|
24
|
+
status.toUpperCase() === `${QueueEntryPriority.Priority}` ||
|
|
25
|
+
status.toUpperCase() === `${QueueEntryPriority.Priority} PRIORITY` ||
|
|
26
|
+
status.toUpperCase() === 'NORMAL PRIORITY' ||
|
|
27
|
+
status.toUpperCase() === 'NOT URGENT'
|
|
28
|
+
)
|
|
29
|
+
className = 'priorityTag';
|
|
30
|
+
if (
|
|
31
|
+
status.toUpperCase() === `${QueueEntryPriority.NonUrgent}` ||
|
|
32
|
+
status.toUpperCase() === `${QueueEntryPriority.NonUrgent} PRIORITY`
|
|
33
|
+
)
|
|
34
|
+
className = 'nonUrgentTag';
|
|
35
|
+
return className;
|
|
36
|
+
}, [status]);
|
|
37
|
+
return (
|
|
38
|
+
<Tag size="md" className={styles[tagClassName]}>
|
|
39
|
+
{status}
|
|
40
|
+
</Tag>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default PriorityTag;
|