@cc-openmrs/cc-esm-active-prescriptions 1.0.21 → 1.0.22
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/296.js +1 -1
- package/dist/openmrs-esm-template-app.js.buildmanifest.json +4 -4
- package/dist/routes.json +1 -1
- package/fonta_ia.txt +639 -0
- package/package.json +1 -1
- package/src/prescriptions-actions/prescriptions-action-button.component.tsx +1 -1
- package/src/prescriptions-actions/prescriptions-action-menu-item.component.tsx +2 -8
- package/src/routes.json +0 -17
package/dist/296.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";(globalThis.webpackChunk_cc_openmrs_cc_esm_active_prescriptions=globalThis.webpackChunk_cc_openmrs_cc_esm_active_prescriptions||[]).push([[296],{4296(e,t,r){r.r(t),r.d(t,{default:()=>s});var n=r(6072),c=r(3335),
|
|
1
|
+
"use strict";(globalThis.webpackChunk_cc_openmrs_cc_esm_active_prescriptions=globalThis.webpackChunk_cc_openmrs_cc_esm_active_prescriptions||[]).push([[296],{4296(e,t,r){r.r(t),r.d(t,{default:()=>s});var n=r(6072),c=r(3335),i=r(5987),o=r(2076),a=r(8150);function p(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}const s=function(e){var t=e.patientUuid,r=(0,o.useTranslation)().t,s=(0,n.useCallback)(function(){var e=function(e){var t;return null!=e?e:null===(t=window.location.pathname.match(/\/patient\/([^/]+)\/chart/))||void 0===t?void 0:t[1]}(t);e&&(0,i.launchWorkspace)("active-prescriptions-workspace",{patientUuid:e})},[t]),u=r("prescriptionsActionMenuItem","Active prescriptions");return n.createElement(a.ED,{getIcon:function(e){return n.createElement(c.KM,(t=function(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{},n=Object.keys(r);"function"==typeof Object.getOwnPropertySymbols&&(n=n.concat(Object.getOwnPropertySymbols(r).filter(function(e){return Object.getOwnPropertyDescriptor(r,e).enumerable}))),n.forEach(function(t){p(e,t,r[t])})}return e}({},e),r=null!=(r={"aria-hidden":"true"})?r:{},Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(r)):function(e){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t.push.apply(t,r)}return t}(Object(r)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(r,e))}),t));var t,r},handler:s,iconDescription:u,label:u,type:"active-prescriptions"})}}}]);
|
|
@@ -265,9 +265,9 @@
|
|
|
265
265
|
"initial": false,
|
|
266
266
|
"entry": false,
|
|
267
267
|
"recorded": false,
|
|
268
|
-
"size":
|
|
268
|
+
"size": 3357,
|
|
269
269
|
"sizes": {
|
|
270
|
-
"javascript":
|
|
270
|
+
"javascript": 3357
|
|
271
271
|
},
|
|
272
272
|
"names": [],
|
|
273
273
|
"idHints": [],
|
|
@@ -281,7 +281,7 @@
|
|
|
281
281
|
"auxiliaryFiles": [
|
|
282
282
|
"296.js.map"
|
|
283
283
|
],
|
|
284
|
-
"hash": "
|
|
284
|
+
"hash": "09d2e4b9da296d99",
|
|
285
285
|
"childrenByOrder": {}
|
|
286
286
|
},
|
|
287
287
|
{
|
|
@@ -480,7 +480,7 @@
|
|
|
480
480
|
"auxiliaryFiles": [
|
|
481
481
|
"477.js.map"
|
|
482
482
|
],
|
|
483
|
-
"hash": "
|
|
483
|
+
"hash": "75c8b897ebf3de67",
|
|
484
484
|
"childrenByOrder": {}
|
|
485
485
|
},
|
|
486
486
|
{
|
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":">=2.2.0"},"extensions":[{"name":"Red box","component":"redBox","slot":"Boxes"},{"name":"Blue box","component":"blueBox","slot":"Boxes"},{"name":"Brand box","component":"brandBox","slot":"Boxes"},{"name":"prescriptions-action-menu","component":"prescriptionsActionMenuItem","slot":"action-menu-patient-chart-items-slot","order":4,"meta":{"label":"Prescriptions 2","icon":"pill"}},{"name":"Prescriptions action","component":"prescriptionsActionButton","slot":"patient-actions-slot","order":50}],"pages":[{"component":"root","route":"root"}],"workspaces":[{"name":"active-prescriptions-workspace","title":"activePrescriptionsWorkspaceTitle","component":"root","type":"active-prescriptions","canHide":true,"width":"wider"}],"
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":">=2.2.0"},"extensions":[{"name":"Red box","component":"redBox","slot":"Boxes"},{"name":"Blue box","component":"blueBox","slot":"Boxes"},{"name":"Brand box","component":"brandBox","slot":"Boxes"},{"name":"prescriptions-action-menu","component":"prescriptionsActionMenuItem","slot":"action-menu-patient-chart-items-slot","order":4,"meta":{"label":"Prescriptions 2","icon":"pill"}},{"name":"Prescriptions action","component":"prescriptionsActionButton","slot":"patient-actions-slot","order":50}],"pages":[{"component":"root","route":"root"}],"workspaces":[{"name":"active-prescriptions-workspace","title":"activePrescriptionsWorkspaceTitle","component":"root","type":"active-prescriptions","canHide":true,"width":"wider"}],"version":"1.0.21"}
|
package/fonta_ia.txt
ADDED
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
# src/root.component.tsx
|
|
2
|
+
/**
|
|
3
|
+
* From here, the application is pretty typical React, but with lots of
|
|
4
|
+
* support from `@openmrs/esm-framework`. Check out `Greeter` to see
|
|
5
|
+
* usage of the configuration system, and check out `PatientGetter` to
|
|
6
|
+
* see data fetching using the OpenMRS FHIR API.
|
|
7
|
+
*
|
|
8
|
+
* Check out the Config docs:
|
|
9
|
+
* https://openmrs.github.io/openmrs-esm-core/#/main/config
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React, { useMemo, useState } from 'react';
|
|
13
|
+
import { PrescriptionService, PrescriptionPayload } from './prescriptions.service';
|
|
14
|
+
import {
|
|
15
|
+
Checkbox,
|
|
16
|
+
ComboBox,
|
|
17
|
+
InlineLoading,
|
|
18
|
+
InlineNotification,
|
|
19
|
+
Tab,
|
|
20
|
+
TabList,
|
|
21
|
+
TabPanel,
|
|
22
|
+
TabPanels,
|
|
23
|
+
Tabs,
|
|
24
|
+
TextInput,
|
|
25
|
+
} from '@carbon/react';
|
|
26
|
+
import { useTranslation } from 'react-i18next';
|
|
27
|
+
import { useLocations, type Location } from '@openmrs/esm-framework';
|
|
28
|
+
import { usePatientOrders } from './prescriptions-orders/usePatientOrders';
|
|
29
|
+
import styles from './root.scss';
|
|
30
|
+
|
|
31
|
+
interface RootProps {
|
|
32
|
+
patientUuid?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const Root: React.FC<RootProps> = ({ patientUuid }) => {
|
|
36
|
+
const { t } = useTranslation();
|
|
37
|
+
const [activeTab, setActiveTab] = useState<'new' | 'past'>('new');
|
|
38
|
+
const [selectedLocation, setSelectedLocation] = useState<Location | null>(null);
|
|
39
|
+
const [policyNumber, setPolicyNumber] = useState('');
|
|
40
|
+
const [selectedOrders, setSelectedOrders] = useState<Set<string>>(new Set());
|
|
41
|
+
const [digitallySigned, setDigitallySigned] = useState(false);
|
|
42
|
+
const [printRequested, setPrintRequested] = useState(false);
|
|
43
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
44
|
+
const locations = useLocations();
|
|
45
|
+
const { orders, isLoading, error } = usePatientOrders(patientUuid);
|
|
46
|
+
|
|
47
|
+
const locationItems = useMemo(() => locations ?? [], [locations]);
|
|
48
|
+
const selectedOrdersLabel =
|
|
49
|
+
selectedOrders.size > 0
|
|
50
|
+
? t('selectedOrdersCount', {
|
|
51
|
+
count: selectedOrders.size,
|
|
52
|
+
})
|
|
53
|
+
: null;
|
|
54
|
+
|
|
55
|
+
const toggleOrderSelection = (orderUuid: string) => {
|
|
56
|
+
setSelectedOrders((prev) => {
|
|
57
|
+
const updated = new Set(prev);
|
|
58
|
+
if (updated.has(orderUuid)) {
|
|
59
|
+
updated.delete(orderUuid);
|
|
60
|
+
} else {
|
|
61
|
+
updated.add(orderUuid);
|
|
62
|
+
}
|
|
63
|
+
return updated;
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const renderOrdersSection = () => {
|
|
68
|
+
if (!patientUuid) {
|
|
69
|
+
return (
|
|
70
|
+
<InlineNotification
|
|
71
|
+
lowContrast
|
|
72
|
+
kind="warning"
|
|
73
|
+
title={t('ordersMissingPatientTitle', 'Select a patient to continue')}
|
|
74
|
+
subtitle={t(
|
|
75
|
+
'ordersMissingPatientSubtitle',
|
|
76
|
+
'Launch this workspace from a patient chart to review their orders.',
|
|
77
|
+
)}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (error) {
|
|
83
|
+
return (
|
|
84
|
+
<InlineNotification
|
|
85
|
+
lowContrast
|
|
86
|
+
kind="error"
|
|
87
|
+
title={t('ordersErrorTitle', 'Unable to load orders')}
|
|
88
|
+
subtitle={t('ordersErrorSubtitle', 'Try again or refresh the patient chart.')}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (isLoading) {
|
|
94
|
+
return <InlineLoading description={`${t('loading', 'Loading')}...`} />;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!orders.length) {
|
|
98
|
+
return (
|
|
99
|
+
<InlineNotification
|
|
100
|
+
lowContrast
|
|
101
|
+
kind="info"
|
|
102
|
+
title={t('ordersEmptyState', 'There are no orders for this patient yet.')}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<>
|
|
109
|
+
<div className={styles.ordersList} role="group" aria-label={t('visitTypeHeading', 'Visit type')}>
|
|
110
|
+
{orders.map((order) => (
|
|
111
|
+
<Checkbox
|
|
112
|
+
key={order.uuid}
|
|
113
|
+
id={`order-${order.uuid}`}
|
|
114
|
+
labelText={
|
|
115
|
+
<span className={styles.orderLabel}>
|
|
116
|
+
<span className={styles.orderName}>{order.conceptName}</span>
|
|
117
|
+
{order.display && <span className={styles.orderMeta}>{order.display}</span>}
|
|
118
|
+
{!order.display && order.orderNumber && <span className={styles.orderMeta}>{order.orderNumber}</span>}
|
|
119
|
+
</span>
|
|
120
|
+
}
|
|
121
|
+
checked={selectedOrders.has(order.uuid)}
|
|
122
|
+
onChange={() => toggleOrderSelection(order.uuid)}
|
|
123
|
+
/>
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
{selectedOrdersLabel ? <p className={styles.selectionSummary}>{selectedOrdersLabel}</p> : null}
|
|
127
|
+
</>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<div className={styles.workspace}>
|
|
133
|
+
<div className={styles.header}>
|
|
134
|
+
<h2>{t('activePrescriptionsWorkspaceTitle', 'Active prescriptions')}</h2>
|
|
135
|
+
<p className={styles.subtitle}>
|
|
136
|
+
{t(
|
|
137
|
+
'activePrescriptionsHelper',
|
|
138
|
+
'Review the patient context, choose the visit details, and add the matching orders to the prescription.',
|
|
139
|
+
)}
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<section className={styles.section}>
|
|
144
|
+
<p className={styles.fieldLabel}>{t('visitTimingLabel', 'The visit is')}</p>
|
|
145
|
+
<Tabs
|
|
146
|
+
selectedIndex={activeTab === 'new' ? 0 : 1}
|
|
147
|
+
onChange={({ selectedIndex }) => setActiveTab(selectedIndex === 0 ? 'new' : 'past')}
|
|
148
|
+
>
|
|
149
|
+
<TabList aria-label={t('visitTimingLabel', 'The visit is')}>
|
|
150
|
+
<Tab>{t('visitTimingNew', 'New')}</Tab>
|
|
151
|
+
<Tab>{t('visitTimingPast', 'In the past')}</Tab>
|
|
152
|
+
</TabList>
|
|
153
|
+
<TabPanels>
|
|
154
|
+
<TabPanel className={styles.tabContent}>
|
|
155
|
+
<div className={styles.fieldGroup}>
|
|
156
|
+
<ComboBox
|
|
157
|
+
id="visit-location"
|
|
158
|
+
items={locationItems}
|
|
159
|
+
itemToString={(item) => item?.display ?? ''}
|
|
160
|
+
selectedItem={selectedLocation}
|
|
161
|
+
onChange={({ selectedItem }) => setSelectedLocation((selectedItem as Location) ?? null)}
|
|
162
|
+
placeholder={t('visitLocationPlaceholder', 'Select a location')}
|
|
163
|
+
titleText={t('visitLocationHeading', 'Visit location')}
|
|
164
|
+
helperText={t('visitLocationHelper', 'Select where the visit will take place.')}
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div className={styles.fieldGroup}>
|
|
169
|
+
<p className={styles.fieldLabel}>{t('visitTypeHeading', 'Visit type')}</p>
|
|
170
|
+
<p className={styles.helperText}>
|
|
171
|
+
{t('visitTypeHelper', 'Select the orders that should be included in this prescription.')}
|
|
172
|
+
</p>
|
|
173
|
+
{renderOrdersSection()}
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div className={styles.fieldGroup}>
|
|
177
|
+
<TextInput
|
|
178
|
+
id="insurance-policy"
|
|
179
|
+
labelText={t('insurancePolicyNumberLabel', 'Insurance Policy Number (optional)')}
|
|
180
|
+
placeholder={t('insurancePolicyNumberPlaceholder', 'Enter the policy number')}
|
|
181
|
+
value={policyNumber}
|
|
182
|
+
onChange={(event) => setPolicyNumber(event.target.value)}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
<div className={styles.fieldGroup}>
|
|
186
|
+
<Checkbox
|
|
187
|
+
id="sign-prescription-checkbox"
|
|
188
|
+
labelText={t('signPrescriptionLabel', 'Digitally sign the prescription')}
|
|
189
|
+
checked={digitallySigned}
|
|
190
|
+
onChange={() => setDigitallySigned((prev) => !prev)}
|
|
191
|
+
/>
|
|
192
|
+
<Checkbox
|
|
193
|
+
id="print-prescription-checkbox"
|
|
194
|
+
labelText={t('printPrescriptionLabel', 'Print the prescription')}
|
|
195
|
+
checked={printRequested}
|
|
196
|
+
onChange={() => setPrintRequested((prev) => !prev)}
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
<div className={styles.fieldGroup} style={{ flexDirection: 'row', gap: '1rem' }}>
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
id="discard-prescription-btn"
|
|
203
|
+
onClick={() => {
|
|
204
|
+
setSelectedLocation(null);
|
|
205
|
+
setPolicyNumber('');
|
|
206
|
+
setSelectedOrders(new Set());
|
|
207
|
+
setDigitallySigned(false);
|
|
208
|
+
setPrintRequested(false);
|
|
209
|
+
}}
|
|
210
|
+
disabled={isSubmitting}
|
|
211
|
+
>
|
|
212
|
+
{t('discardButtonLabel', 'Discard')}
|
|
213
|
+
</button>
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
id="create-prescription-btn"
|
|
217
|
+
onClick={async () => {
|
|
218
|
+
if (!patientUuid || !selectedLocation || selectedOrders.size === 0) return;
|
|
219
|
+
setIsSubmitting(true);
|
|
220
|
+
// TODO: obter dados reais do paciente e do médico
|
|
221
|
+
const payload: PrescriptionPayload = {
|
|
222
|
+
patient: {
|
|
223
|
+
uuid: patientUuid,
|
|
224
|
+
},
|
|
225
|
+
provider: {
|
|
226
|
+
uuid: 'provider-uuid', // Substituir pelo uuid real do médico
|
|
227
|
+
},
|
|
228
|
+
location: {
|
|
229
|
+
uuid: selectedLocation.uuid,
|
|
230
|
+
name: selectedLocation.display,
|
|
231
|
+
},
|
|
232
|
+
policyNumber,
|
|
233
|
+
orderUuids: Array.from(selectedOrders),
|
|
234
|
+
digitallySigned,
|
|
235
|
+
printRequested,
|
|
236
|
+
};
|
|
237
|
+
try {
|
|
238
|
+
await PrescriptionService.createPrescription(payload);
|
|
239
|
+
// Limpar formulário após sucesso
|
|
240
|
+
setSelectedLocation(null);
|
|
241
|
+
setPolicyNumber('');
|
|
242
|
+
setSelectedOrders(new Set());
|
|
243
|
+
setDigitallySigned(false);
|
|
244
|
+
setPrintRequested(false);
|
|
245
|
+
} catch (e) {
|
|
246
|
+
// TODO: tratar erro
|
|
247
|
+
} finally {
|
|
248
|
+
setIsSubmitting(false);
|
|
249
|
+
}
|
|
250
|
+
}}
|
|
251
|
+
disabled={isSubmitting}
|
|
252
|
+
>
|
|
253
|
+
{isSubmitting ? t('loading', 'Loading') : t('createPrescriptionButtonLabel', 'Create Prescription')}
|
|
254
|
+
</button>
|
|
255
|
+
</div>
|
|
256
|
+
</TabPanel>
|
|
257
|
+
<TabPanel className={styles.tabContent}>
|
|
258
|
+
<InlineNotification
|
|
259
|
+
lowContrast
|
|
260
|
+
kind="info"
|
|
261
|
+
title={t('inPastTabPlaceholderTitle', 'Historic prescriptions will be available soon')}
|
|
262
|
+
subtitle={t('inPastTabPlaceholderSubtitle', 'Switch back to the New tab to manage current orders.')}
|
|
263
|
+
/>
|
|
264
|
+
</TabPanel>
|
|
265
|
+
</TabPanels>
|
|
266
|
+
</Tabs>
|
|
267
|
+
</section>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export default Root;
|
|
273
|
+
|
|
274
|
+
# src/prescriptions-actions/prescriptions-action-button.component.tsx
|
|
275
|
+
import React, { useCallback } from 'react';
|
|
276
|
+
import { OverflowMenuItem } from '@carbon/react';
|
|
277
|
+
import { launchWorkspace, navigate } from '@openmrs/esm-framework';
|
|
278
|
+
import { useTranslation } from 'react-i18next';
|
|
279
|
+
|
|
280
|
+
interface PrescriptionsActionButtonProps {
|
|
281
|
+
patientUuid?: string;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const resolvePatientUuid = (patientUuid?: string) =>
|
|
285
|
+
patientUuid ?? window.location.pathname.match(/\/patient\/([^/]+)\/chart/)?.[1];
|
|
286
|
+
|
|
287
|
+
const PrescriptionsActionButton: React.FC<PrescriptionsActionButtonProps> = ({ patientUuid }) => {
|
|
288
|
+
const { t } = useTranslation();
|
|
289
|
+
const label = t('prescriptionsActionButton', 'Active prescriptions');
|
|
290
|
+
|
|
291
|
+
const handleClick = useCallback(() => {
|
|
292
|
+
const uuid = resolvePatientUuid(patientUuid);
|
|
293
|
+
if (!uuid) return;
|
|
294
|
+
|
|
295
|
+
launchWorkspace('active-prescriptions-workspace', { patientUuid: uuid });
|
|
296
|
+
}, [patientUuid]);
|
|
297
|
+
|
|
298
|
+
return <OverflowMenuItem itemText={label} onClick={handleClick} />;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
export default PrescriptionsActionButton;
|
|
302
|
+
|
|
303
|
+
# src/prescriptions-actions/prescriptions-action-menu-item.component.tsx
|
|
304
|
+
import React, { useCallback } from 'react';
|
|
305
|
+
import { CertificateCheck } from '@carbon/react/icons';
|
|
306
|
+
import { launchWorkspace, navigate } from '@openmrs/esm-framework';
|
|
307
|
+
import { navigateAndLaunchWorkspace } from '@openmrs/esm-framework';
|
|
308
|
+
import { useTranslation } from 'react-i18next';
|
|
309
|
+
import { ActionMenuButton } from '@openmrs/esm-styleguide';
|
|
310
|
+
|
|
311
|
+
interface PrescriptionsActionMenuItemProps {
|
|
312
|
+
patientUuid?: string;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const resolvePatientUuid = (patientUuid?: string) =>
|
|
316
|
+
patientUuid ?? window.location.pathname.match(/\/patient\/([^/]+)\/chart/)?.[1];
|
|
317
|
+
|
|
318
|
+
const PrescriptionsActionMenuItem: React.FC<PrescriptionsActionMenuItemProps> = ({ patientUuid }) => {
|
|
319
|
+
const { t } = useTranslation();
|
|
320
|
+
|
|
321
|
+
const handleClick = useCallback(() => {
|
|
322
|
+
const uuid = resolvePatientUuid(patientUuid);
|
|
323
|
+
if (!uuid) return;
|
|
324
|
+
|
|
325
|
+
navigateAndLaunchWorkspace({
|
|
326
|
+
targetUrl: `${window.getOpenmrsSpaBase()}patient/${uuid}/chart`,
|
|
327
|
+
workspaceName: 'active-prescriptions-workspace',
|
|
328
|
+
contextKey: `patient/${uuid}`,
|
|
329
|
+
additionalProps: { patientUuid: uuid },
|
|
330
|
+
});
|
|
331
|
+
}, [patientUuid]);
|
|
332
|
+
|
|
333
|
+
const label = t('prescriptionsActionMenuItem', 'Active prescriptions');
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<ActionMenuButton
|
|
337
|
+
getIcon={(props) => <CertificateCheck {...props} aria-hidden="true" />}
|
|
338
|
+
handler={handleClick}
|
|
339
|
+
iconDescription={label}
|
|
340
|
+
label={label}
|
|
341
|
+
type="active-prescriptions"
|
|
342
|
+
/>
|
|
343
|
+
);
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
export default PrescriptionsActionMenuItem;
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# src/prescriptions.service.ts
|
|
350
|
+
// prescription.service.ts
|
|
351
|
+
// Serviço para manipulação de prescrições e integração com backend
|
|
352
|
+
|
|
353
|
+
export interface PrescriptionPayload {
|
|
354
|
+
patient: {
|
|
355
|
+
uuid: string;
|
|
356
|
+
name?: string;
|
|
357
|
+
birthDate?: string;
|
|
358
|
+
gender?: string;
|
|
359
|
+
};
|
|
360
|
+
provider: {
|
|
361
|
+
uuid: string;
|
|
362
|
+
name?: string;
|
|
363
|
+
};
|
|
364
|
+
location: {
|
|
365
|
+
uuid: string;
|
|
366
|
+
name?: string;
|
|
367
|
+
};
|
|
368
|
+
policyNumber?: string;
|
|
369
|
+
orderUuids: string[];
|
|
370
|
+
digitallySigned: boolean;
|
|
371
|
+
printRequested: boolean;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export class PrescriptionService {
|
|
375
|
+
static async createPrescription(payload: PrescriptionPayload): Promise<Response> {
|
|
376
|
+
// Troque a URL abaixo pelo endpoint real do backend
|
|
377
|
+
const url = '/api/prescriptions';
|
|
378
|
+
return fetch(url, {
|
|
379
|
+
method: 'POST',
|
|
380
|
+
headers: {
|
|
381
|
+
'Content-Type': 'application/json',
|
|
382
|
+
},
|
|
383
|
+
body: JSON.stringify(payload),
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
# src/index.ts
|
|
390
|
+
/**
|
|
391
|
+
* This is the entrypoint file of the application. It communicates the
|
|
392
|
+
* important features of this microfrontend to the app shell. It
|
|
393
|
+
* connects the app shell to the React application(s) that make up this
|
|
394
|
+
* microfrontend.
|
|
395
|
+
*/
|
|
396
|
+
import { getAsyncLifecycle, defineConfigSchema } from '@openmrs/esm-framework';
|
|
397
|
+
import { configSchema } from './config-schema';
|
|
398
|
+
|
|
399
|
+
const moduleName = '@cc-openmrs/cc-esm-active-prescriptions';
|
|
400
|
+
|
|
401
|
+
const options = {
|
|
402
|
+
featureName: 'root-world',
|
|
403
|
+
moduleName,
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* This tells the app shell how to obtain translation files: that they
|
|
408
|
+
* are JSON files in the directory `../translations` (which you should
|
|
409
|
+
* see in the directory structure).
|
|
410
|
+
*/
|
|
411
|
+
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* This function performs any setup that should happen at microfrontend
|
|
415
|
+
* load-time (such as defining the config schema) and then returns an
|
|
416
|
+
* object which describes how the React application(s) should be
|
|
417
|
+
* rendered.
|
|
418
|
+
*/
|
|
419
|
+
export function startupApp() {
|
|
420
|
+
defineConfigSchema(moduleName, configSchema);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* This named export tells the app shell that the default export of `root.component.tsx`
|
|
425
|
+
* should be rendered when the route matches `root`. The full route
|
|
426
|
+
* will be `openmrsSpaBase() + 'root'`, which is usually
|
|
427
|
+
* `/openmrs/spa/root`.
|
|
428
|
+
*/
|
|
429
|
+
export const root = getAsyncLifecycle(() => import('./root.component'), options);
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* The following are named exports for the extensions defined in this frontend modules. See the `routes.json` file to see how these are used.
|
|
433
|
+
*/
|
|
434
|
+
export const redBox = getAsyncLifecycle(() => import('./boxes/extensions/red-box.component'), options);
|
|
435
|
+
|
|
436
|
+
export const blueBox = getAsyncLifecycle(() => import('./boxes/extensions/blue-box.component'), options);
|
|
437
|
+
|
|
438
|
+
export const brandBox = getAsyncLifecycle(() => import('./boxes/extensions/brand-box.component'), options);
|
|
439
|
+
|
|
440
|
+
export const prescriptionsActionButton = getAsyncLifecycle(
|
|
441
|
+
() => import('./prescriptions-actions/prescriptions-action-button.component'),
|
|
442
|
+
options,
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
export const prescriptionsActionMenuItem = getAsyncLifecycle(
|
|
446
|
+
() => import('./prescriptions-actions/prescriptions-action-menu-item.component'),
|
|
447
|
+
options,
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
# package.json
|
|
452
|
+
{
|
|
453
|
+
"name": "@cc-openmrs/cc-esm-active-prescriptions",
|
|
454
|
+
"version": "1.0.21",
|
|
455
|
+
"license": "MPL-2.0",
|
|
456
|
+
"description": "An OpenMRS seed application for building microfrontends",
|
|
457
|
+
"browser": "dist/openmrs-esm-template-app.js",
|
|
458
|
+
"main": "src/index.ts",
|
|
459
|
+
"source": true,
|
|
460
|
+
"scripts": {
|
|
461
|
+
"start": "openmrs develop",
|
|
462
|
+
"serve": "webpack serve --mode=development",
|
|
463
|
+
"build": "webpack --mode production",
|
|
464
|
+
"analyze": "webpack --mode=production --env analyze=true",
|
|
465
|
+
"lint": "eslint src --ext js,jsx,ts,tsx --max-warnings 0",
|
|
466
|
+
"prettier": "prettier --write \"src/**/*.{ts,tsx}\" --list-different",
|
|
467
|
+
"typescript": "tsc",
|
|
468
|
+
"test": "jest --config jest.config.js --passWithNoTests",
|
|
469
|
+
"verify": "turbo lint typescript coverage",
|
|
470
|
+
"coverage": "yarn test --coverage",
|
|
471
|
+
"prepare": "husky install",
|
|
472
|
+
"extract-translations": "i18next 'src/**/*.component.tsx' --config ./tools/i18next-parser.config.js",
|
|
473
|
+
"test-e2e": "playwright test"
|
|
474
|
+
},
|
|
475
|
+
"browserslist": [
|
|
476
|
+
"extends browserslist-config-openmrs"
|
|
477
|
+
],
|
|
478
|
+
"keywords": [
|
|
479
|
+
"openmrs",
|
|
480
|
+
"microfrontends"
|
|
481
|
+
],
|
|
482
|
+
"repository": {
|
|
483
|
+
"type": "git",
|
|
484
|
+
"url": "git+https://github.com/openmrs/openmrs-esm-template-app.git"
|
|
485
|
+
},
|
|
486
|
+
"homepage": "https://github.com/openmrs/openmrs-esm-template-app#readme",
|
|
487
|
+
"publishConfig": {
|
|
488
|
+
"access": "public"
|
|
489
|
+
},
|
|
490
|
+
"bugs": {
|
|
491
|
+
"url": "https://github.com/openmrs/openmrs-esm-template-app/issues"
|
|
492
|
+
},
|
|
493
|
+
"openmrs": {
|
|
494
|
+
"frontendModuleName": "@cc-openmrs/cc-esm-active-prescriptions",
|
|
495
|
+
"routes": "src/routes.json"
|
|
496
|
+
},
|
|
497
|
+
"dependencies": {
|
|
498
|
+
"@carbon/react": "^1.83.0",
|
|
499
|
+
"lodash-es": "^4.17.21"
|
|
500
|
+
},
|
|
501
|
+
"peerDependencies": {
|
|
502
|
+
"@openmrs/esm-framework": "*",
|
|
503
|
+
"dayjs": "1.x",
|
|
504
|
+
"react": "18.x",
|
|
505
|
+
"react-i18next": "16.x",
|
|
506
|
+
"react-router-dom": "6.x",
|
|
507
|
+
"rxjs": "6.x"
|
|
508
|
+
},
|
|
509
|
+
"devDependencies": {
|
|
510
|
+
"@openmrs/esm-framework": "8.0.0",
|
|
511
|
+
"@openmrs/esm-styleguide": "8.0.0",
|
|
512
|
+
"@playwright/test": "^1.52.0",
|
|
513
|
+
"@swc/cli": "^0.3.12",
|
|
514
|
+
"@swc/core": "^1.3.68",
|
|
515
|
+
"@swc/jest": "^0.2.36",
|
|
516
|
+
"@testing-library/dom": "^10.1.0",
|
|
517
|
+
"@testing-library/jest-dom": "^6.4.5",
|
|
518
|
+
"@testing-library/react": "^15.0.6",
|
|
519
|
+
"@testing-library/user-event": "^14.5.2",
|
|
520
|
+
"@types/jest": "^29.5.12",
|
|
521
|
+
"@types/react": "^18.3.21",
|
|
522
|
+
"@types/react-dom": "^18.3.0",
|
|
523
|
+
"@types/react-router": "^5.1.20",
|
|
524
|
+
"@types/react-router-dom": "^5.3.3",
|
|
525
|
+
"@types/webpack-env": "^1.18.1",
|
|
526
|
+
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
|
527
|
+
"@typescript-eslint/parser": "^7.8.0",
|
|
528
|
+
"css-loader": "^6.8.1",
|
|
529
|
+
"dayjs": "^1.11.13",
|
|
530
|
+
"dotenv": "^16.0.3",
|
|
531
|
+
"eslint": "^8.50.0",
|
|
532
|
+
"eslint-config-prettier": "^8.8.0",
|
|
533
|
+
"eslint-plugin-jest-dom": "^5.4.0",
|
|
534
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
535
|
+
"eslint-plugin-react-hooks": "^4.6.2",
|
|
536
|
+
"husky": "^8.0.3",
|
|
537
|
+
"i18next": "^25.0.0",
|
|
538
|
+
"i18next-parser": "^9.3.0",
|
|
539
|
+
"identity-obj-proxy": "^3.0.0",
|
|
540
|
+
"jest": "^29.7.0",
|
|
541
|
+
"jest-cli": "^29.7.0",
|
|
542
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
543
|
+
"lint-staged": "^15.2.2",
|
|
544
|
+
"openmrs": "8.0.0",
|
|
545
|
+
"prettier": "^3.3.3",
|
|
546
|
+
"react": "^18.3.1",
|
|
547
|
+
"react-dom": "^18.3.1",
|
|
548
|
+
"react-i18next": "^16.0.0",
|
|
549
|
+
"react-router-dom": "^6.14.1",
|
|
550
|
+
"rxjs": "^6.6.7",
|
|
551
|
+
"swc-loader": "^0.2.3",
|
|
552
|
+
"turbo": "^2.5.2",
|
|
553
|
+
"typescript": "^5.0.0",
|
|
554
|
+
"webpack": "^5.99.9",
|
|
555
|
+
"webpack-cli": "^6.0.1"
|
|
556
|
+
},
|
|
557
|
+
"lint-staged": {
|
|
558
|
+
"packages/**/src/**/*.{ts,tsx}": "eslint --cache --fix --max-warnings 0",
|
|
559
|
+
"*.{css,scss,ts,tsx}": "prettier --write --list-different"
|
|
560
|
+
},
|
|
561
|
+
"packageManager": "yarn@4.10.3"
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
# src/routes.json
|
|
566
|
+
{
|
|
567
|
+
"$schema": "https://json.openmrs.org/routes.schema.json",
|
|
568
|
+
"backendDependencies": {
|
|
569
|
+
"fhir2": ">=1.2",
|
|
570
|
+
"webservices.rest": ">=2.2.0"
|
|
571
|
+
},
|
|
572
|
+
"extensions": [
|
|
573
|
+
{
|
|
574
|
+
"name": "Red box",
|
|
575
|
+
"component": "redBox",
|
|
576
|
+
"slot": "Boxes"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"name": "Blue box",
|
|
580
|
+
"component": "blueBox",
|
|
581
|
+
"slot": "Boxes"
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
"name": "Brand box",
|
|
585
|
+
"component": "brandBox",
|
|
586
|
+
"slot": "Boxes"
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
"name": "prescriptions-action-menu",
|
|
590
|
+
"component": "prescriptionsActionMenuItem",
|
|
591
|
+
"slot": "action-menu-patient-chart-items-slot",
|
|
592
|
+
"order": 4,
|
|
593
|
+
"meta": {
|
|
594
|
+
"label": "Prescriptions 2",
|
|
595
|
+
"icon": "pill"
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
"name": "Prescriptions action",
|
|
600
|
+
"component": "prescriptionsActionButton",
|
|
601
|
+
"slot": "patient-actions-slot",
|
|
602
|
+
"order": 50
|
|
603
|
+
}
|
|
604
|
+
],
|
|
605
|
+
"pages": [
|
|
606
|
+
{
|
|
607
|
+
"component": "root",
|
|
608
|
+
"route": "root"
|
|
609
|
+
}
|
|
610
|
+
],
|
|
611
|
+
"workspaces": [
|
|
612
|
+
{
|
|
613
|
+
"name": "active-prescriptions-workspace",
|
|
614
|
+
"title": "activePrescriptionsWorkspaceTitle",
|
|
615
|
+
"component": "root",
|
|
616
|
+
"type": "active-prescriptions",
|
|
617
|
+
"canHide": true,
|
|
618
|
+
"width": "wider"
|
|
619
|
+
}
|
|
620
|
+
],
|
|
621
|
+
"workspaces2": [
|
|
622
|
+
{
|
|
623
|
+
"name": "active-prescriptions-workspace",
|
|
624
|
+
"component": "root",
|
|
625
|
+
"window": "patient-chart-active-prescriptions"
|
|
626
|
+
}
|
|
627
|
+
],
|
|
628
|
+
"workspaceWindows2": [
|
|
629
|
+
{
|
|
630
|
+
"name": "patient-chart-active-prescriptions",
|
|
631
|
+
"group": "patient-chart",
|
|
632
|
+
"icon": "prescriptionsActionMenuItem",
|
|
633
|
+
"order": 4,
|
|
634
|
+
"width": "wider",
|
|
635
|
+
"canMaximize": true
|
|
636
|
+
}
|
|
637
|
+
]
|
|
638
|
+
}
|
|
639
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useCallback } from 'react';
|
|
2
2
|
import { OverflowMenuItem } from '@carbon/react';
|
|
3
|
-
import { launchWorkspace
|
|
3
|
+
import { launchWorkspace } from '@openmrs/esm-framework';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
|
|
6
6
|
interface PrescriptionsActionButtonProps {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useCallback } from 'react';
|
|
2
2
|
import { CertificateCheck } from '@carbon/react/icons';
|
|
3
|
-
import { launchWorkspace
|
|
4
|
-
import { navigateAndLaunchWorkspace } from '@openmrs/esm-framework';
|
|
3
|
+
import { launchWorkspace } from '@openmrs/esm-framework';
|
|
5
4
|
import { useTranslation } from 'react-i18next';
|
|
6
5
|
import { ActionMenuButton } from '@openmrs/esm-styleguide';
|
|
7
6
|
|
|
@@ -19,12 +18,7 @@ const PrescriptionsActionMenuItem: React.FC<PrescriptionsActionMenuItemProps> =
|
|
|
19
18
|
const uuid = resolvePatientUuid(patientUuid);
|
|
20
19
|
if (!uuid) return;
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
targetUrl: `${window.getOpenmrsSpaBase()}patient/${uuid}/chart`,
|
|
24
|
-
workspaceName: 'active-prescriptions-workspace',
|
|
25
|
-
contextKey: `patient/${uuid}`,
|
|
26
|
-
additionalProps: { patientUuid: uuid },
|
|
27
|
-
});
|
|
21
|
+
launchWorkspace('active-prescriptions-workspace', { patientUuid: uuid });
|
|
28
22
|
}, [patientUuid]);
|
|
29
23
|
|
|
30
24
|
const label = t('prescriptionsActionMenuItem', 'Active prescriptions');
|
package/src/routes.json
CHANGED
|
@@ -52,22 +52,5 @@
|
|
|
52
52
|
"canHide": true,
|
|
53
53
|
"width": "wider"
|
|
54
54
|
}
|
|
55
|
-
],
|
|
56
|
-
"workspaces2": [
|
|
57
|
-
{
|
|
58
|
-
"name": "active-prescriptions-workspace",
|
|
59
|
-
"component": "root",
|
|
60
|
-
"window": "patient-chart-active-prescriptions"
|
|
61
|
-
}
|
|
62
|
-
],
|
|
63
|
-
"workspaceWindows2": [
|
|
64
|
-
{
|
|
65
|
-
"name": "patient-chart-active-prescriptions",
|
|
66
|
-
"group": "patient-chart",
|
|
67
|
-
"icon": "prescriptionsActionMenuItem",
|
|
68
|
-
"order": 4,
|
|
69
|
-
"width": "wider",
|
|
70
|
-
"canMaximize": true
|
|
71
|
-
}
|
|
72
55
|
]
|
|
73
56
|
}
|