@openmrs/esm-patient-orders-app 5.0.1-pre.2175
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 +36 -0
- package/LICENSE +401 -0
- package/README.md +4 -0
- package/dist/130.js +2 -0
- package/dist/130.js.LICENSE.txt +3 -0
- package/dist/130.js.map +1 -0
- package/dist/193.js +1 -0
- package/dist/193.js.map +1 -0
- package/dist/232.js +1 -0
- package/dist/232.js.map +1 -0
- package/dist/235.js +1 -0
- package/dist/235.js.map +1 -0
- package/dist/255.js +2 -0
- package/dist/255.js.LICENSE.txt +9 -0
- package/dist/255.js.map +1 -0
- package/dist/319.js +1 -0
- package/dist/37.js +1 -0
- package/dist/37.js.map +1 -0
- package/dist/442.js +2 -0
- package/dist/442.js.LICENSE.txt +14 -0
- package/dist/442.js.map +1 -0
- package/dist/47.js +1 -0
- package/dist/47.js.map +1 -0
- package/dist/574.js +1 -0
- package/dist/588.js +1 -0
- package/dist/588.js.map +1 -0
- package/dist/591.js +2 -0
- package/dist/591.js.LICENSE.txt +32 -0
- package/dist/591.js.map +1 -0
- package/dist/712.js +2 -0
- package/dist/712.js.LICENSE.txt +5 -0
- package/dist/712.js.map +1 -0
- package/dist/757.js +1 -0
- package/dist/784.js +2 -0
- package/dist/784.js.LICENSE.txt +9 -0
- package/dist/784.js.map +1 -0
- package/dist/788.js +1 -0
- package/dist/807.js +1 -0
- package/dist/833.js +1 -0
- package/dist/842.js +1 -0
- package/dist/842.js.map +1 -0
- package/dist/95.js +1 -0
- package/dist/95.js.map +1 -0
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -0
- package/dist/openmrs-esm-patient-orders-app.js +1 -0
- package/dist/openmrs-esm-patient-orders-app.js.buildmanifest.json +618 -0
- package/dist/openmrs-esm-patient-orders-app.js.map +1 -0
- package/dist/routes.json +1 -0
- package/jest.config.js +3 -0
- package/package.json +57 -0
- package/src/api/api.ts +137 -0
- package/src/config-schema.ts +13 -0
- package/src/declarations.d.ts +3 -0
- package/src/index.ts +28 -0
- package/src/order-basket/order-basket.scss +39 -0
- package/src/order-basket/order-basket.workspace.tsx +177 -0
- package/src/order-basket-action-button/order-basket-action-button.extension.tsx +56 -0
- package/src/order-basket-action-button/order-basket-action-button.scss +80 -0
- package/src/order-basket-action-button/order-basket-action-button.test.tsx +121 -0
- package/src/root.scss +67 -0
- package/src/routes.json +14 -0
- package/src/types/order.ts +5 -0
- package/translations/am.json +3 -0
- package/translations/en.json +3 -0
- package/translations/es.json +3 -0
- package/translations/fr.json +3 -0
- package/translations/he.json +3 -0
- package/translations/km.json +3 -0
- package/tsconfig.json +5 -0
- package/webpack.config.js +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openmrs/esm-patient-orders-app",
|
|
3
|
+
"version": "5.0.1-pre.2175",
|
|
4
|
+
"license": "MPL-2.0",
|
|
5
|
+
"description": "Microfrontend for the OpenMRS SPA providing the order basket for the patient chart",
|
|
6
|
+
"browser": "dist/openmrs-esm-patient-orders-app.js",
|
|
7
|
+
"main": "src/index.ts",
|
|
8
|
+
"source": true,
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "openmrs develop",
|
|
11
|
+
"serve": "webpack serve --mode=development",
|
|
12
|
+
"debug": "npm run serve",
|
|
13
|
+
"build": "webpack --mode production",
|
|
14
|
+
"analyze": "webpack --mode=production --env analyze=true",
|
|
15
|
+
"lint": "cross-env TIMING=1 eslint src --ext tsx,ts --fix --max-warnings=0",
|
|
16
|
+
"test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests",
|
|
17
|
+
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js",
|
|
18
|
+
"coverage": "yarn test --coverage",
|
|
19
|
+
"typescript": "tsc",
|
|
20
|
+
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js"
|
|
21
|
+
},
|
|
22
|
+
"browserslist": [
|
|
23
|
+
"extends browserslist-config-openmrs"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"openmrs"
|
|
27
|
+
],
|
|
28
|
+
"homepage": "https://github.com/openmrs/openmrs-esm-patient-chart#readme",
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/openmrs/openmrs-esm-patient-chart.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/openmrs/openmrs-esm-patient-chart/issues"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@carbon/react": "^1.12.0",
|
|
41
|
+
"@openmrs/esm-patient-common-lib": "^5.0.1-pre.2175",
|
|
42
|
+
"lodash-es": "^4.17.15"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@openmrs/esm-framework": "5.x",
|
|
46
|
+
"@openmrs/esm-patient-common-lib": "5.x",
|
|
47
|
+
"dayjs": "1.x",
|
|
48
|
+
"react": "18.x",
|
|
49
|
+
"react-i18next": "11.x",
|
|
50
|
+
"react-router-dom": "6.x",
|
|
51
|
+
"rxjs": "6.x"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"webpack": "^5.74.0"
|
|
55
|
+
},
|
|
56
|
+
"gitHead": "73d5cacb9bd7310f741583719a101e4382093ef0"
|
|
57
|
+
}
|
package/src/api/api.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
import useSWR, { useSWRConfig } from 'swr';
|
|
3
|
+
import { FetchResponse, openmrsFetch, OpenmrsResource, parseDate, Visit } from '@openmrs/esm-framework';
|
|
4
|
+
import { OrderPost, useVisitOrOfflineVisit, useSystemVisitSetting } from '@openmrs/esm-patient-common-lib';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns a function which refreshes the patient orders cache. Uses SWR's mutate function.
|
|
8
|
+
* Refreshes patient orders for all kinds of orders.
|
|
9
|
+
*
|
|
10
|
+
* TODO: This isn't working. See https://github.com/vercel/swr/issues/2746
|
|
11
|
+
*
|
|
12
|
+
* @param patientUuid The UUID of the patient to get an order mutate function for.
|
|
13
|
+
*/
|
|
14
|
+
export function useMutatePatientOrders(patientUuid: string) {
|
|
15
|
+
const { mutate } = useSWRConfig();
|
|
16
|
+
const mutateOrders = useCallback(
|
|
17
|
+
() =>
|
|
18
|
+
mutate((key) => {
|
|
19
|
+
return typeof key === 'string' && key.startsWith(`/ws/rest/v1/order?patient=${patientUuid}`);
|
|
20
|
+
}),
|
|
21
|
+
[patientUuid, mutate],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
mutate: mutateOrders,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getPatientEncounterId(patientUuid: string, abortController: AbortController) {
|
|
30
|
+
return openmrsFetch(`/ws/rest/v1/encounter?patient=${patientUuid}&order=desc&limit=1&v=custom:(uuid)`, {
|
|
31
|
+
signal: abortController.signal,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getMedicationByUuid(abortController: AbortController, orderUuid: string) {
|
|
36
|
+
return openmrsFetch(
|
|
37
|
+
`/ws/rest/v1/order/${orderUuid}?v=custom:(uuid,route:(uuid,display),action,urgency,display,drug:(display,strength),frequency:(display),dose,doseUnits:(display),orderer,dateStopped,dateActivated,previousOrder,numRefills,duration,durationUnits:(display),dosingInstructions)`,
|
|
38
|
+
{
|
|
39
|
+
signal: abortController.signal,
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createEmptyEncounter(
|
|
45
|
+
patientUuid: string,
|
|
46
|
+
orderEncounterType: string,
|
|
47
|
+
activeVisit: Visit | null,
|
|
48
|
+
sessionLocationUuid: string,
|
|
49
|
+
abortController?: AbortController,
|
|
50
|
+
) {
|
|
51
|
+
const now = new Date();
|
|
52
|
+
const visitStartDate = parseDate(activeVisit?.startDatetime);
|
|
53
|
+
const visitEndDate = parseDate(activeVisit?.stopDatetime);
|
|
54
|
+
let encounterDate: Date;
|
|
55
|
+
if (!activeVisit || (visitStartDate < now && (!visitEndDate || visitEndDate > now))) {
|
|
56
|
+
now;
|
|
57
|
+
} else {
|
|
58
|
+
console.warn(
|
|
59
|
+
'createEmptyEncounter received an active visit that is not currently active. This is a programming error. Attempting to place the order using the visit start date.',
|
|
60
|
+
);
|
|
61
|
+
visitStartDate;
|
|
62
|
+
}
|
|
63
|
+
const emptyEncounter = {
|
|
64
|
+
patient: patientUuid,
|
|
65
|
+
location: sessionLocationUuid,
|
|
66
|
+
encounterType: orderEncounterType,
|
|
67
|
+
encounterDatetime: encounterDate,
|
|
68
|
+
visit: activeVisit?.uuid,
|
|
69
|
+
obs: [],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return openmrsFetch<OpenmrsResource>('/ws/rest/v1/encounter', {
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
},
|
|
76
|
+
method: 'POST',
|
|
77
|
+
body: emptyEncounter,
|
|
78
|
+
signal: abortController?.signal,
|
|
79
|
+
}).then((res) => res?.data?.uuid);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function postOrder(body: OrderPost, abortController?: AbortController) {
|
|
83
|
+
return openmrsFetch(`/ws/rest/v1/order`, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
signal: abortController?.signal,
|
|
86
|
+
headers: { 'Content-Type': 'application/json' },
|
|
87
|
+
body,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function useOrderEncounter(patientUuid: string): {
|
|
92
|
+
activeVisitRequired: boolean;
|
|
93
|
+
isLoading: boolean;
|
|
94
|
+
error: Error;
|
|
95
|
+
encounterUuid: string;
|
|
96
|
+
mutate: Function;
|
|
97
|
+
} {
|
|
98
|
+
const { systemVisitEnabled, isLoadingSystemVisitSetting, errorFetchingSystemVisitSetting } = useSystemVisitSetting();
|
|
99
|
+
|
|
100
|
+
const now = new Date();
|
|
101
|
+
const nowDateString = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
|
|
102
|
+
const todayEncounter = useSWR<FetchResponse<{ results: Array<OpenmrsResource> }>, Error>(
|
|
103
|
+
!isLoadingSystemVisitSetting && !systemVisitEnabled && patientUuid
|
|
104
|
+
? `/ws/rest/v1/encounter?patient=${patientUuid}&fromdate=${nowDateString}&limit=1`
|
|
105
|
+
: null,
|
|
106
|
+
openmrsFetch,
|
|
107
|
+
);
|
|
108
|
+
const visit = useVisitOrOfflineVisit(patientUuid);
|
|
109
|
+
|
|
110
|
+
const results = useMemo(() => {
|
|
111
|
+
if (isLoadingSystemVisitSetting || errorFetchingSystemVisitSetting) {
|
|
112
|
+
return {
|
|
113
|
+
activeVisitRequired: false,
|
|
114
|
+
isLoading: isLoadingSystemVisitSetting,
|
|
115
|
+
error: errorFetchingSystemVisitSetting,
|
|
116
|
+
encounterUuid: null,
|
|
117
|
+
mutate: () => {},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return systemVisitEnabled
|
|
121
|
+
? {
|
|
122
|
+
activeVisitRequired: true,
|
|
123
|
+
isLoading: visit?.isLoading,
|
|
124
|
+
encounterUuid: visit?.currentVisit?.encounters?.[0]?.uuid,
|
|
125
|
+
error: visit?.error,
|
|
126
|
+
mutate: visit?.mutate,
|
|
127
|
+
}
|
|
128
|
+
: {
|
|
129
|
+
activeVisitRequired: false,
|
|
130
|
+
isLoading: todayEncounter?.isLoading,
|
|
131
|
+
encounterUuid: todayEncounter?.data?.data?.results?.[0]?.uuid,
|
|
132
|
+
error: todayEncounter?.error,
|
|
133
|
+
mutate: todayEncounter?.mutate,
|
|
134
|
+
};
|
|
135
|
+
}, [isLoadingSystemVisitSetting, errorFetchingSystemVisitSetting, visit, todayEncounter, systemVisitEnabled]);
|
|
136
|
+
return results;
|
|
137
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Type } from '@openmrs/esm-framework';
|
|
2
|
+
|
|
3
|
+
export const configSchema = {
|
|
4
|
+
orderEncounterType: {
|
|
5
|
+
_type: Type.UUID,
|
|
6
|
+
_description: 'The encounter type of the encounter encapsulating orders',
|
|
7
|
+
_default: '39da3525-afe4-45ff-8977-c53b7b359158',
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export interface ConfigObject {
|
|
12
|
+
orderEncounterType: string;
|
|
13
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineConfigSchema, getAsyncLifecycle } from '@openmrs/esm-framework';
|
|
2
|
+
import { registerWorkspace } from '@openmrs/esm-patient-common-lib';
|
|
3
|
+
import { configSchema } from './config-schema';
|
|
4
|
+
|
|
5
|
+
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
|
|
6
|
+
|
|
7
|
+
const moduleName = '@openmrs/esm-patient-orders-app';
|
|
8
|
+
|
|
9
|
+
const options = {
|
|
10
|
+
featureName: 'patient-orders',
|
|
11
|
+
moduleName,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function startupApp() {
|
|
15
|
+
defineConfigSchema(moduleName, configSchema);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
registerWorkspace({
|
|
19
|
+
name: 'order-basket',
|
|
20
|
+
title: 'Order Basket',
|
|
21
|
+
load: getAsyncLifecycle(() => import('./order-basket/order-basket.workspace'), options),
|
|
22
|
+
type: 'order',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const orderBasketActionMenu = getAsyncLifecycle(
|
|
26
|
+
() => import('./order-basket-action-button/order-basket-action-button.extension'),
|
|
27
|
+
options,
|
|
28
|
+
);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
@use '@carbon/styles/scss/spacing';
|
|
2
|
+
@import "../root";
|
|
3
|
+
|
|
4
|
+
.container {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
height: calc(100vh - 9rem);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.orderBasketContainer {
|
|
12
|
+
margin: spacing.$spacing-05;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.orderBasketContainer :global(.cds--btn-set .cds--btn) {
|
|
16
|
+
max-width: 50%;
|
|
17
|
+
height: 4rem;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.tablet {
|
|
21
|
+
padding: 1.5rem 1rem;
|
|
22
|
+
background-color: $ui-02;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.desktop {
|
|
26
|
+
padding: 0rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.bottomButton {
|
|
30
|
+
height: 4rem;
|
|
31
|
+
display: flex;
|
|
32
|
+
align-content: flex-start;
|
|
33
|
+
align-items: baseline;
|
|
34
|
+
min-width: 50%;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.inlineNotification, .actionNotification {
|
|
38
|
+
max-width: unset !important;
|
|
39
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import React, { useCallback, useState } from 'react';
|
|
2
|
+
import { TFunction, useTranslation } from 'react-i18next';
|
|
3
|
+
import { ActionableNotification, Button, ButtonSet, InlineNotification } from '@carbon/react';
|
|
4
|
+
import { ExtensionSlot, showModal, showToast, useConfig, useLayoutType, useSession } from '@openmrs/esm-framework';
|
|
5
|
+
import {
|
|
6
|
+
postOrders,
|
|
7
|
+
useOrderBasket,
|
|
8
|
+
useVisitOrOfflineVisit,
|
|
9
|
+
OrderBasketItem,
|
|
10
|
+
DefaultWorkspaceProps,
|
|
11
|
+
} from '@openmrs/esm-patient-common-lib';
|
|
12
|
+
import { ConfigObject } from '../config-schema';
|
|
13
|
+
import { createEmptyEncounter, useOrderEncounter, useMutatePatientOrders } from '../api/api';
|
|
14
|
+
import styles from './order-basket.scss';
|
|
15
|
+
|
|
16
|
+
const OrderBasket: React.FC<DefaultWorkspaceProps> = ({ patientUuid, closeWorkspace }) => {
|
|
17
|
+
const { t } = useTranslation();
|
|
18
|
+
const isTablet = useLayoutType() === 'tablet';
|
|
19
|
+
const config = useConfig() as ConfigObject;
|
|
20
|
+
const session = useSession();
|
|
21
|
+
const { activeVisit } = useVisitOrOfflineVisit(patientUuid);
|
|
22
|
+
const { orders, clearOrders } = useOrderBasket();
|
|
23
|
+
const {
|
|
24
|
+
activeVisitRequired,
|
|
25
|
+
isLoading: isLoadingEncounterUuid,
|
|
26
|
+
encounterUuid,
|
|
27
|
+
error: errorFetchingEncounterUuid,
|
|
28
|
+
mutate: mutateEncounterUuid,
|
|
29
|
+
} = useOrderEncounter(patientUuid);
|
|
30
|
+
const [creatingEncounterError, setCreatingEncounterError] = useState('');
|
|
31
|
+
const { mutate: mutateOrders } = useMutatePatientOrders(patientUuid);
|
|
32
|
+
|
|
33
|
+
const openStartVisitDialog = useCallback(() => {
|
|
34
|
+
const dispose = showModal('start-visit-dialog', {
|
|
35
|
+
patientUuid,
|
|
36
|
+
closeModal: () => dispose(),
|
|
37
|
+
});
|
|
38
|
+
}, [patientUuid]);
|
|
39
|
+
|
|
40
|
+
const handleSave = useCallback(async () => {
|
|
41
|
+
const abortController = new AbortController();
|
|
42
|
+
setCreatingEncounterError('');
|
|
43
|
+
let orderEncounterUuid = encounterUuid;
|
|
44
|
+
// If there's no encounter present, create an encounter for the order.
|
|
45
|
+
if (!orderEncounterUuid) {
|
|
46
|
+
try {
|
|
47
|
+
orderEncounterUuid = await createEmptyEncounter(
|
|
48
|
+
patientUuid,
|
|
49
|
+
config?.orderEncounterType,
|
|
50
|
+
activeVisitRequired ? activeVisit : null,
|
|
51
|
+
session?.sessionLocation?.uuid,
|
|
52
|
+
abortController,
|
|
53
|
+
);
|
|
54
|
+
mutateEncounterUuid();
|
|
55
|
+
} catch (e) {
|
|
56
|
+
setCreatingEncounterError(e);
|
|
57
|
+
console.error(e);
|
|
58
|
+
return; // don't try to create the orders if we couldn't create the encounter
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const erroredItems = await postOrders(orderEncounterUuid, abortController);
|
|
63
|
+
clearOrders({ exceptThoseMatching: (item) => erroredItems.includes(item) });
|
|
64
|
+
mutateOrders();
|
|
65
|
+
if (erroredItems.length == 0) {
|
|
66
|
+
closeWorkspace();
|
|
67
|
+
showOrderSuccessToast(t, orders);
|
|
68
|
+
} else {
|
|
69
|
+
showOrderFailureToast(t);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return () => abortController.abort();
|
|
73
|
+
}, [
|
|
74
|
+
activeVisit,
|
|
75
|
+
activeVisitRequired,
|
|
76
|
+
clearOrders,
|
|
77
|
+
closeWorkspace,
|
|
78
|
+
config,
|
|
79
|
+
encounterUuid,
|
|
80
|
+
mutateEncounterUuid,
|
|
81
|
+
mutateOrders,
|
|
82
|
+
orders,
|
|
83
|
+
patientUuid,
|
|
84
|
+
session,
|
|
85
|
+
t,
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
const handleCancel = useCallback(() => {
|
|
89
|
+
clearOrders();
|
|
90
|
+
closeWorkspace();
|
|
91
|
+
}, [clearOrders, closeWorkspace]);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<>
|
|
95
|
+
<div className={styles.container}>
|
|
96
|
+
<div className={styles.orderBasketContainer}>
|
|
97
|
+
<ExtensionSlot name="order-basket-slot" />
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div>
|
|
101
|
+
{(creatingEncounterError || errorFetchingEncounterUuid) && (
|
|
102
|
+
<InlineNotification
|
|
103
|
+
kind="error"
|
|
104
|
+
title={t('errorCreatingAnEncounter', 'Error when creating an encounter')}
|
|
105
|
+
subtitle={t('tryReopeningTheWorkspaceAgain', 'Please try launching the workspace again')}
|
|
106
|
+
lowContrast={true}
|
|
107
|
+
className={styles.inlineNotification}
|
|
108
|
+
inline
|
|
109
|
+
/>
|
|
110
|
+
)}
|
|
111
|
+
<ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
|
|
112
|
+
<Button className={styles.bottomButton} kind="secondary" onClick={handleCancel}>
|
|
113
|
+
{t('cancel', 'Cancel')}
|
|
114
|
+
</Button>
|
|
115
|
+
<Button
|
|
116
|
+
className={styles.bottomButton}
|
|
117
|
+
kind="primary"
|
|
118
|
+
onClick={handleSave}
|
|
119
|
+
disabled={!orders?.length || isLoadingEncounterUuid || (activeVisitRequired && !activeVisit)}
|
|
120
|
+
>
|
|
121
|
+
{t('signAndClose', 'Sign and close')}
|
|
122
|
+
</Button>
|
|
123
|
+
</ButtonSet>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
{activeVisitRequired && !activeVisit && (
|
|
127
|
+
<ActionableNotification
|
|
128
|
+
kind="error"
|
|
129
|
+
actionButtonLabel={t('startVisit', 'Start visit')}
|
|
130
|
+
onActionButtonClick={openStartVisitDialog}
|
|
131
|
+
title={t('startAVisitToRecordOrders', 'Start a visit to order')}
|
|
132
|
+
subtitle={t('activeVisitRequired', 'An active visit is required to make orders')}
|
|
133
|
+
lowContrast={true}
|
|
134
|
+
inline
|
|
135
|
+
className={styles.actionNotification}
|
|
136
|
+
hasFocus
|
|
137
|
+
/>
|
|
138
|
+
)}
|
|
139
|
+
</>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
function showOrderSuccessToast(t: TFunction, patientOrderItems: OrderBasketItem[]) {
|
|
144
|
+
const orderedString = patientOrderItems
|
|
145
|
+
.filter((item) => ['NEW', 'RENEW'].includes(item.action))
|
|
146
|
+
.map((item) => item.drug?.display)
|
|
147
|
+
.join(', ');
|
|
148
|
+
const updatedString = patientOrderItems
|
|
149
|
+
.filter((item) => item.action === 'REVISE')
|
|
150
|
+
.map((item) => item.drug?.display)
|
|
151
|
+
.join(', ');
|
|
152
|
+
const discontinuedString = patientOrderItems
|
|
153
|
+
.filter((item) => item.action === 'DISCONTINUE')
|
|
154
|
+
.map((item) => item.drug?.display)
|
|
155
|
+
.join(', ');
|
|
156
|
+
|
|
157
|
+
showToast({
|
|
158
|
+
critical: true,
|
|
159
|
+
kind: 'success',
|
|
160
|
+
title: t('orderCompleted', 'Medications updated'),
|
|
161
|
+
description:
|
|
162
|
+
(orderedString && `${t('ordered', 'Placed order for')} ${orderedString}. `) +
|
|
163
|
+
(updatedString && `${t('updated', 'Updated')} ${updatedString}. `) +
|
|
164
|
+
(discontinuedString && `${t('discontinued', 'Discontinued')} ${discontinuedString}.`),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function showOrderFailureToast(t: TFunction) {
|
|
169
|
+
showToast({
|
|
170
|
+
critical: true,
|
|
171
|
+
kind: 'error',
|
|
172
|
+
title: t('error', 'Error'),
|
|
173
|
+
description: t('errorSavingMedicationOrder', 'There were errors saving some medication orders.'),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export default OrderBasket;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, Tag } from '@carbon/react';
|
|
4
|
+
import { ShoppingCart } from '@carbon/react/icons';
|
|
5
|
+
import { useLayoutType } from '@openmrs/esm-framework';
|
|
6
|
+
import { useLaunchWorkspaceRequiringVisit, useOrderBasket, useWorkspaces } from '@openmrs/esm-patient-common-lib';
|
|
7
|
+
import styles from './order-basket-action-button.scss';
|
|
8
|
+
|
|
9
|
+
const OrderBasketActionButton: React.FC = () => {
|
|
10
|
+
const layout = useLayoutType();
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
const { workspaces } = useWorkspaces();
|
|
13
|
+
const { orders } = useOrderBasket();
|
|
14
|
+
const isActiveWorkspace = workspaces?.[0]?.name?.match(/order-basket/i);
|
|
15
|
+
const launchOrderBasket = useLaunchWorkspaceRequiringVisit('order-basket');
|
|
16
|
+
|
|
17
|
+
if (layout === 'tablet') {
|
|
18
|
+
return (
|
|
19
|
+
<Button
|
|
20
|
+
kind="ghost"
|
|
21
|
+
className={`${styles.container} ${isActiveWorkspace ? styles.active : ''}`}
|
|
22
|
+
role="button"
|
|
23
|
+
tabIndex={0}
|
|
24
|
+
onClick={() => launchOrderBasket()}
|
|
25
|
+
>
|
|
26
|
+
<div className={styles.elementContainer}>
|
|
27
|
+
<ShoppingCart size={16} />
|
|
28
|
+
{orders?.length > 0 && <Tag className={styles.countTag}>{orders?.length}</Tag>}
|
|
29
|
+
</div>
|
|
30
|
+
<span>{t('orderBasket', 'Order basket')}</span>
|
|
31
|
+
</Button>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Button
|
|
37
|
+
className={isActiveWorkspace && styles.active}
|
|
38
|
+
kind="ghost"
|
|
39
|
+
size="sm"
|
|
40
|
+
renderIcon={(props) => (
|
|
41
|
+
<div className={styles.elementContainer}>
|
|
42
|
+
<ShoppingCart size={20} {...props} />{' '}
|
|
43
|
+
{orders?.length > 0 && <Tag className={styles.countTag}>{orders?.length}</Tag>}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
hasIconOnly
|
|
47
|
+
iconDescription={t('medications', 'Medications')}
|
|
48
|
+
enterDelayMs={1000}
|
|
49
|
+
tooltipAlignment="center"
|
|
50
|
+
tooltipPosition="left"
|
|
51
|
+
onClick={() => launchOrderBasket()}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default OrderBasketActionButton;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
@use '@carbon/styles/scss/spacing';
|
|
2
|
+
@use '@carbon/styles/scss/type';
|
|
3
|
+
@import '~@openmrs/esm-styleguide/src/vars';
|
|
4
|
+
|
|
5
|
+
.elementContainer {
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
position: relative;
|
|
9
|
+
|
|
10
|
+
.countTag {
|
|
11
|
+
position: absolute;
|
|
12
|
+
transform: scale(0.7);
|
|
13
|
+
min-width: initial;
|
|
14
|
+
min-height: initial;
|
|
15
|
+
background-color: $carbon--red-50;
|
|
16
|
+
color: $ui-02;
|
|
17
|
+
right: -10px;
|
|
18
|
+
top: -15px;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.container {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
align-items: center;
|
|
27
|
+
height: 100%;
|
|
28
|
+
width: 100%;
|
|
29
|
+
color: $ui-05;
|
|
30
|
+
max-width: none;
|
|
31
|
+
|
|
32
|
+
&:hover {
|
|
33
|
+
color: $text-02;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
& > span {
|
|
37
|
+
margin-top: spacing.$spacing-02;
|
|
38
|
+
@include type.type-style("body-compact-01");
|
|
39
|
+
color: $text-02;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Desktop */
|
|
44
|
+
:global(.omrs-breakpoint-gt-tablet) {
|
|
45
|
+
.active {
|
|
46
|
+
border: spacing.$spacing-01 solid;
|
|
47
|
+
@include brand-03(border-color);
|
|
48
|
+
border-radius: 50%;
|
|
49
|
+
background-color: $ui-02;
|
|
50
|
+
|
|
51
|
+
&:focus {
|
|
52
|
+
border-color: var(--brand-03) !important;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.elementContainer {
|
|
57
|
+
color: $text-02;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Tablet */
|
|
62
|
+
:global(.omrs-breakpoint-lt-desktop) {
|
|
63
|
+
.active {
|
|
64
|
+
background-color: $color-blue-10;
|
|
65
|
+
color: $interactive-01;
|
|
66
|
+
|
|
67
|
+
&:hover {
|
|
68
|
+
color: $interactive-01;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
& > span {
|
|
72
|
+
color: $interactive-01;
|
|
73
|
+
@include type.type-style("heading-compact-01");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
svg {
|
|
77
|
+
fill: $interactive-01;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|