@openmrs/esm-patient-orders-app 11.3.1-patch.9310 → 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 +15 -18
- package/dist/1119.js +1 -1
- package/dist/1197.js +1 -1
- package/dist/1571.js +1 -0
- package/dist/1571.js.map +1 -0
- package/dist/2146.js +1 -1
- package/dist/2690.js +1 -1
- package/dist/2717.js +1 -0
- package/dist/2717.js.map +1 -0
- package/dist/3099.js +1 -1
- package/dist/3584.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/4937.js +1 -1
- package/dist/4937.js.map +1 -1
- package/dist/4944.js +1 -1
- package/dist/5134.js +2 -0
- package/dist/5134.js.map +1 -0
- package/dist/5173.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/6468.js +1 -1
- 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/7097.js +1 -1
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7617.js +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8349.js +1 -1
- package/dist/8371.js +1 -0
- package/dist/8618.js +1 -1
- package/dist/8625.js +1 -0
- package/dist/8625.js.map +1 -0
- package/dist/8803.js +1 -0
- package/dist/8803.js.map +1 -0
- package/dist/890.js +1 -1
- package/dist/8960.js +1 -0
- package/dist/8960.js.map +1 -0
- 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-orders-app.js +1 -1
- package/dist/openmrs-esm-patient-orders-app.js.buildmanifest.json +276 -259
- package/dist/openmrs-esm-patient-orders-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +2 -2
- package/src/api/api.ts +25 -0
- package/src/components/order-details-table.component.tsx +40 -53
- package/src/components/test-order.component.tsx +53 -49
- package/src/index.ts +9 -12
- package/src/lab-results/lab-results-form.component.tsx +343 -0
- package/src/lab-results/lab-results-form.test.tsx +455 -306
- package/src/lab-results/lab-results-schema.resource.tsx +13 -4
- package/src/lab-results/lab-results.resource.ts +113 -0
- package/src/order-basket/general-order-type/general-order-form/general-order-form.component.tsx +25 -71
- package/src/order-basket/general-order-type/{general-order-panel.component.tsx → general-order-type.component.tsx} +41 -23
- package/src/order-basket/general-order-type/{add-general-order/add-general-order.component.tsx → orderable-concept-search/orderable-concept-search.workspace.tsx} +83 -78
- package/src/order-basket/general-order-type/{add-general-order → orderable-concept-search}/search-results.component.tsx +14 -15
- package/src/order-basket/general-order-type/resources.ts +2 -3
- package/src/order-basket/order-basket.workspace.tsx +251 -35
- package/src/order-basket-action-button/order-basket-action-button.extension.tsx +31 -0
- package/src/order-basket-action-button/order-basket-action-button.test.tsx +36 -27
- package/src/order-cancellation-form/cancel-order-form.component.tsx +85 -82
- package/src/routes.json +25 -17
- package/src/utils/index.ts +3 -15
- package/translations/am.json +6 -20
- package/translations/ar.json +6 -20
- package/translations/ar_SY.json +6 -20
- package/translations/bn.json +6 -20
- package/translations/cs.json +114 -0
- package/translations/de.json +6 -20
- package/translations/en.json +12 -4
- package/translations/en_US.json +6 -20
- package/translations/es.json +6 -20
- package/translations/es_MX.json +6 -20
- package/translations/fr.json +7 -21
- package/translations/he.json +6 -20
- package/translations/hi.json +6 -20
- package/translations/hi_IN.json +6 -20
- package/translations/id.json +6 -20
- package/translations/it.json +6 -20
- package/translations/ka.json +6 -20
- package/translations/km.json +6 -20
- package/translations/ku.json +6 -20
- package/translations/ky.json +6 -20
- package/translations/lg.json +6 -20
- package/translations/ne.json +6 -20
- package/translations/pl.json +6 -20
- package/translations/pt.json +6 -20
- package/translations/pt_BR.json +6 -20
- package/translations/qu.json +6 -20
- package/translations/ro_RO.json +6 -20
- package/translations/ru_RU.json +6 -20
- package/translations/si.json +6 -20
- package/translations/sq.json +114 -0
- package/translations/sw.json +6 -20
- package/translations/sw_KE.json +6 -20
- package/translations/tr.json +6 -20
- package/translations/tr_TR.json +6 -20
- package/translations/uk.json +6 -20
- package/translations/uz.json +6 -20
- package/translations/uz@Latn.json +6 -20
- package/translations/uz_UZ.json +6 -20
- package/translations/vi.json +6 -20
- package/translations/zh.json +6 -20
- package/translations/zh_CN.json +6 -20
- package/translations/zh_TW.json +114 -0
- package/dist/1253.js +0 -1
- package/dist/1253.js.map +0 -1
- package/dist/1268.js +0 -2
- package/dist/1268.js.map +0 -1
- package/dist/375.js +0 -1
- package/dist/375.js.map +0 -1
- package/dist/4341.js +0 -1
- package/dist/4341.js.map +0 -1
- package/dist/4687.js +0 -1
- package/dist/4687.js.map +0 -1
- package/dist/6364.js +0 -1
- package/dist/6364.js.map +0 -1
- package/dist/6473.js +0 -1
- package/dist/6473.js.map +0 -1
- package/dist/8416.js +0 -1
- package/dist/8416.js.map +0 -1
- package/src/lab-results/exported-lab-results-form.workspace.tsx +0 -264
- package/src/lab-results/lab-results-form.workspace.tsx +0 -25
- package/src/order-basket/exported-order-basket.workspace.tsx +0 -54
- package/src/order-basket/general-order-type/add-general-order/add-general-order.workspace.tsx +0 -35
- package/src/order-basket/general-order-type/add-general-order/exported-add-general-order.workspace.tsx +0 -32
- package/src/order-basket/order-basket.component.tsx +0 -213
- package/src/order-basket-action-button/order-basket-action-button.component.tsx +0 -35
- /package/dist/{1268.js.LICENSE.txt → 5134.js.LICENSE.txt} +0 -0
- /package/src/order-basket/general-order-type/{add-general-order → orderable-concept-search}/orderable-concept-search.scss +0 -0
- /package/src/order-basket/general-order-type/{add-general-order → orderable-concept-search}/search-results.scss +0 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { Button, ButtonSet, Form, Layer, InlineLoading, InlineNotification, Stack } from '@carbon/react';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import { type Control, useForm } from 'react-hook-form';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { useSWRConfig } from 'swr';
|
|
7
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
8
|
+
import {
|
|
9
|
+
restBaseUrl,
|
|
10
|
+
showSnackbar,
|
|
11
|
+
useAbortController,
|
|
12
|
+
useLayoutType,
|
|
13
|
+
ExtensionSlot,
|
|
14
|
+
usePatient,
|
|
15
|
+
} from '@openmrs/esm-framework';
|
|
16
|
+
import { type DefaultPatientWorkspaceProps, type Order, useOrderBasket } from '@openmrs/esm-patient-common-lib';
|
|
17
|
+
import { type ObservationValue } from '../types/encounter';
|
|
18
|
+
import {
|
|
19
|
+
createCompositeObservationPayload,
|
|
20
|
+
isCoded,
|
|
21
|
+
isNumeric,
|
|
22
|
+
isPanel,
|
|
23
|
+
isText,
|
|
24
|
+
updateObservation,
|
|
25
|
+
updateOrderResult,
|
|
26
|
+
useCompletedLabResultsArray,
|
|
27
|
+
useOrderConceptsByUuids,
|
|
28
|
+
} from './lab-results.resource';
|
|
29
|
+
import { createLabResultsFormCompositeSchema } from './lab-results-schema.resource';
|
|
30
|
+
import ResultFormField from './lab-results-form-field.component';
|
|
31
|
+
import styles from './lab-results-form.scss';
|
|
32
|
+
import orderStyles from '../order-basket/order-basket.scss';
|
|
33
|
+
|
|
34
|
+
export interface LabResultsFormProps extends DefaultPatientWorkspaceProps {
|
|
35
|
+
order: Order;
|
|
36
|
+
invalidateLabOrders?: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface OrderBasketSlotProps {
|
|
40
|
+
patient: fhir.Patient;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const LabResultsForm: React.FC<LabResultsFormProps> = ({
|
|
44
|
+
closeWorkspace,
|
|
45
|
+
closeWorkspaceWithSavedChanges,
|
|
46
|
+
order,
|
|
47
|
+
promptBeforeClosing,
|
|
48
|
+
/* Callback to refresh lab orders in the Laboratory app after results are saved.
|
|
49
|
+
* This ensures the orders list stays in sync across the different tabs in the Laboratory app.
|
|
50
|
+
* @see https://github.com/openmrs/openmrs-esm-laboratory-app/pull/117
|
|
51
|
+
*/
|
|
52
|
+
invalidateLabOrders,
|
|
53
|
+
}) => {
|
|
54
|
+
const { t } = useTranslation();
|
|
55
|
+
const abortController = useAbortController();
|
|
56
|
+
const isTablet = useLayoutType() === 'tablet';
|
|
57
|
+
const [orderConceptUuids, setOrderConceptUuids] = useState([order.concept.uuid]);
|
|
58
|
+
const { isLoading: isLoadingResultConcepts, concepts: conceptArray } = useOrderConceptsByUuids(orderConceptUuids);
|
|
59
|
+
const [showEmptyFormErrorNotification, setShowEmptyFormErrorNotification] = useState(false);
|
|
60
|
+
const compositeSchema = useMemo(() => createLabResultsFormCompositeSchema(conceptArray), [conceptArray]);
|
|
61
|
+
const { mutate } = useSWRConfig();
|
|
62
|
+
const { isLoading: isLoadingPatient, patient } = usePatient(order.patient.uuid);
|
|
63
|
+
const { orders, clearOrders } = useOrderBasket(patient);
|
|
64
|
+
const [isSavingOrders, setIsSavingOrders] = useState(false);
|
|
65
|
+
const { isLoading, completeLabResults, mutate: mutateResults } = useCompletedLabResultsArray(order);
|
|
66
|
+
|
|
67
|
+
const mutateOrderData = useCallback(() => {
|
|
68
|
+
mutate(
|
|
69
|
+
(key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/order?patient=${order.patient.uuid}`),
|
|
70
|
+
undefined,
|
|
71
|
+
{ revalidate: true },
|
|
72
|
+
);
|
|
73
|
+
}, [mutate, order.patient.uuid]);
|
|
74
|
+
|
|
75
|
+
const handleCancel = useCallback(() => {
|
|
76
|
+
clearOrders();
|
|
77
|
+
}, [clearOrders]);
|
|
78
|
+
|
|
79
|
+
const handleSave = useCallback(() => {
|
|
80
|
+
const newConceptUuids = orders.map((order) => order['testType']['conceptUuid']);
|
|
81
|
+
setOrderConceptUuids([order.concept.uuid, ...newConceptUuids]);
|
|
82
|
+
clearOrders();
|
|
83
|
+
}, [clearOrders, orders, order.concept.uuid]);
|
|
84
|
+
|
|
85
|
+
const {
|
|
86
|
+
control,
|
|
87
|
+
formState: { errors, isDirty, isSubmitting },
|
|
88
|
+
setValue,
|
|
89
|
+
handleSubmit,
|
|
90
|
+
} = useForm<Record<string, ObservationValue>>({
|
|
91
|
+
defaultValues: {} as Record<string, ObservationValue>,
|
|
92
|
+
resolver: zodResolver(compositeSchema),
|
|
93
|
+
mode: 'all',
|
|
94
|
+
});
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (Array.isArray(completeLabResults) && completeLabResults.length > 1) {
|
|
97
|
+
const conceptUuids = completeLabResults.map((r) => r.concept.uuid);
|
|
98
|
+
setOrderConceptUuids(conceptUuids);
|
|
99
|
+
}
|
|
100
|
+
}, [completeLabResults]);
|
|
101
|
+
|
|
102
|
+
const extensionProps = {
|
|
103
|
+
patient,
|
|
104
|
+
} satisfies OrderBasketSlotProps;
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
conceptArray.forEach((concept, index) => {
|
|
108
|
+
const completeLabResult = completeLabResults.find((r) => r.concept.uuid === concept.uuid);
|
|
109
|
+
if (concept && completeLabResult && order?.fulfillerStatus === 'COMPLETED') {
|
|
110
|
+
if (isCoded(concept) && typeof completeLabResult?.value === 'object' && completeLabResult?.value?.uuid) {
|
|
111
|
+
setValue(concept.uuid, completeLabResult.value.uuid);
|
|
112
|
+
} else if (isNumeric(concept) && completeLabResult?.value) {
|
|
113
|
+
setValue(concept.uuid, parseFloat(completeLabResult.value as string));
|
|
114
|
+
} else if (isText(concept) && completeLabResult?.value) {
|
|
115
|
+
setValue(concept.uuid, completeLabResult?.value);
|
|
116
|
+
} else if (isPanel(concept)) {
|
|
117
|
+
concept.setMembers.forEach((member) => {
|
|
118
|
+
const obs = completeLabResult.groupMembers.find((v) => v.concept.uuid === member.uuid);
|
|
119
|
+
let value: ObservationValue;
|
|
120
|
+
if (isCoded(member)) {
|
|
121
|
+
value = typeof obs?.value === 'object' ? obs.value.uuid : obs?.value;
|
|
122
|
+
} else if (isNumeric(member)) {
|
|
123
|
+
value = obs?.value ? parseFloat(obs.value as string) : undefined;
|
|
124
|
+
} else if (isText(member)) {
|
|
125
|
+
value = obs?.value;
|
|
126
|
+
}
|
|
127
|
+
if (value) setValue(member.uuid, value);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}, [conceptArray, completeLabResults, order?.fulfillerStatus, setValue]);
|
|
133
|
+
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
promptBeforeClosing(() => isDirty);
|
|
136
|
+
}, [isDirty, promptBeforeClosing]);
|
|
137
|
+
|
|
138
|
+
if (isLoadingResultConcepts) {
|
|
139
|
+
return (
|
|
140
|
+
<div className={styles.loaderContainer}>
|
|
141
|
+
<InlineLoading
|
|
142
|
+
className={styles.loader}
|
|
143
|
+
description={t('loadingTestDetails', 'Loading test details') + '...'}
|
|
144
|
+
iconDescription={t('loading', 'Loading')}
|
|
145
|
+
status="active"
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const saveLabResults = async (formValues: Record<string, unknown>) => {
|
|
152
|
+
const isEmptyForm = Object.values(formValues).every(
|
|
153
|
+
(value) => value === '' || value === null || value === undefined,
|
|
154
|
+
);
|
|
155
|
+
if (isEmptyForm) {
|
|
156
|
+
setShowEmptyFormErrorNotification(true);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const showNotification = (kind: 'error' | 'success', message: string) => {
|
|
161
|
+
showSnackbar({
|
|
162
|
+
title:
|
|
163
|
+
kind === 'success'
|
|
164
|
+
? t('saveLabResults', 'Save lab results')
|
|
165
|
+
: t('errorSavingLabResults', 'Error saving lab results'),
|
|
166
|
+
kind: kind,
|
|
167
|
+
subtitle: message,
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Handle update operation for completed lab order results
|
|
172
|
+
if (order.fulfillerStatus === 'COMPLETED') {
|
|
173
|
+
const updateTasks = Object.entries(formValues).map(([conceptUuid, value]) => {
|
|
174
|
+
const completeLabResult = completeLabResults.find((r) => r.concept.uuid === conceptUuid);
|
|
175
|
+
const obs = completeLabResult?.groupMembers?.find((v) => v.concept.uuid === conceptUuid) ?? completeLabResult;
|
|
176
|
+
return updateObservation(obs?.uuid, { value });
|
|
177
|
+
});
|
|
178
|
+
const updateResults = await Promise.allSettled(updateTasks);
|
|
179
|
+
const failedObsconceptUuids = updateResults.reduce((prev, curr, index) => {
|
|
180
|
+
if (curr.status === 'rejected') {
|
|
181
|
+
return [...prev, Object.keys(formValues).at(index)];
|
|
182
|
+
}
|
|
183
|
+
return prev;
|
|
184
|
+
}, []);
|
|
185
|
+
|
|
186
|
+
if (failedObsconceptUuids.length) {
|
|
187
|
+
showNotification('error', 'Could not save obs with concept uuids ' + failedObsconceptUuids.join(', '));
|
|
188
|
+
} else {
|
|
189
|
+
closeWorkspaceWithSavedChanges();
|
|
190
|
+
showNotification(
|
|
191
|
+
'success',
|
|
192
|
+
t('successfullySavedLabResults', 'Lab results for {{orderNumber}} have been successfully updated', {
|
|
193
|
+
orderNumber: order?.orderNumber,
|
|
194
|
+
}),
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
mutateResults();
|
|
198
|
+
return setShowEmptyFormErrorNotification(false);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Handle Creation logic
|
|
202
|
+
|
|
203
|
+
// Set the observation status to 'FINAL' as we're not capturing it in the form
|
|
204
|
+
const obsPayload = createCompositeObservationPayload(conceptArray, order, formValues, 'FINAL');
|
|
205
|
+
const orderDiscontinuationPayload = {
|
|
206
|
+
previousOrder: order.uuid,
|
|
207
|
+
type: 'testorder',
|
|
208
|
+
action: 'DISCONTINUE',
|
|
209
|
+
careSetting: order.careSetting.uuid,
|
|
210
|
+
encounter: order.encounter.uuid,
|
|
211
|
+
patient: order.patient.uuid,
|
|
212
|
+
concept: order.concept.uuid,
|
|
213
|
+
orderer: order.orderer,
|
|
214
|
+
};
|
|
215
|
+
const resultsStatusPayload = {
|
|
216
|
+
fulfillerStatus: 'COMPLETED',
|
|
217
|
+
fulfillerComment: 'Test Results Entered',
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
await updateOrderResult(
|
|
222
|
+
order.uuid,
|
|
223
|
+
order.encounter.uuid,
|
|
224
|
+
obsPayload,
|
|
225
|
+
resultsStatusPayload,
|
|
226
|
+
orderDiscontinuationPayload,
|
|
227
|
+
abortController,
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
closeWorkspaceWithSavedChanges();
|
|
231
|
+
mutateOrderData();
|
|
232
|
+
mutateResults();
|
|
233
|
+
invalidateLabOrders?.();
|
|
234
|
+
|
|
235
|
+
showNotification(
|
|
236
|
+
'success',
|
|
237
|
+
t('successfullySavedLabResults', 'Lab results for {{orderNumber}} have been successfully updated', {
|
|
238
|
+
orderNumber: order?.orderNumber,
|
|
239
|
+
}),
|
|
240
|
+
);
|
|
241
|
+
} catch (err) {
|
|
242
|
+
showNotification('error', err?.message);
|
|
243
|
+
} finally {
|
|
244
|
+
setShowEmptyFormErrorNotification(false);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<Form className={styles.form} onSubmit={handleSubmit(saveLabResults)}>
|
|
250
|
+
<Layer level={isTablet ? 1 : 0}>
|
|
251
|
+
<div className={styles.grid}>
|
|
252
|
+
{conceptArray?.length > 0 && (
|
|
253
|
+
<Stack gap={5}>
|
|
254
|
+
{!isLoading ? (
|
|
255
|
+
conceptArray.map((c) => (
|
|
256
|
+
<ResultFormField
|
|
257
|
+
defaultValue={completeLabResults.find((r) => r.concept.uuid === c.uuid)}
|
|
258
|
+
concept={c}
|
|
259
|
+
control={control as unknown as Control<Record<string, unknown>>}
|
|
260
|
+
/>
|
|
261
|
+
))
|
|
262
|
+
) : (
|
|
263
|
+
<InlineLoading description={t('loadingInitialValues', 'Loading initial values') + '...'} />
|
|
264
|
+
)}
|
|
265
|
+
{order.fulfillerStatus !== 'COMPLETED' && (
|
|
266
|
+
<div className={orderStyles.orderBasketContainer}>
|
|
267
|
+
<div className={styles.heading}>
|
|
268
|
+
<span>{t('addOrderTests', 'Add Tests to this order')}</span>
|
|
269
|
+
</div>
|
|
270
|
+
<ExtensionSlot
|
|
271
|
+
className={classNames(orderStyles.orderBasketSlot, {
|
|
272
|
+
[orderStyles.orderBasketSlotTablet]: isTablet,
|
|
273
|
+
})}
|
|
274
|
+
name="result-order-basket-slot"
|
|
275
|
+
state={extensionProps}
|
|
276
|
+
/>
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
|
|
280
|
+
{orders?.length > 0 && (
|
|
281
|
+
<div className={orderStyles.orderBasketContainer}>
|
|
282
|
+
<ButtonSet className={styles.buttonSet}>
|
|
283
|
+
<Button size="sm" className={styles.actionButton} kind="secondary" onClick={handleCancel}>
|
|
284
|
+
{t('cancelOrder', 'Cancel order')}
|
|
285
|
+
</Button>
|
|
286
|
+
<Button
|
|
287
|
+
className={styles.actionButton}
|
|
288
|
+
kind="primary"
|
|
289
|
+
onClick={handleSave}
|
|
290
|
+
size="sm"
|
|
291
|
+
disabled={
|
|
292
|
+
isSavingOrders || !orders?.length || orders?.some(({ isOrderIncomplete }) => isOrderIncomplete)
|
|
293
|
+
}
|
|
294
|
+
>
|
|
295
|
+
{isSavingOrders ? (
|
|
296
|
+
<InlineLoading description={t('saving', 'Saving') + '...'} />
|
|
297
|
+
) : (
|
|
298
|
+
<span>{t('saveTests', 'Save Tests')}</span>
|
|
299
|
+
)}
|
|
300
|
+
</Button>
|
|
301
|
+
</ButtonSet>
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
304
|
+
</Stack>
|
|
305
|
+
)}
|
|
306
|
+
{showEmptyFormErrorNotification && (
|
|
307
|
+
<InlineNotification
|
|
308
|
+
className={styles.emptyFormError}
|
|
309
|
+
lowContrast
|
|
310
|
+
title={t('error', 'Error')}
|
|
311
|
+
subtitle={t('pleaseFillField', 'Please fill at least one field') + '.'}
|
|
312
|
+
/>
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
</Layer>
|
|
316
|
+
|
|
317
|
+
<ButtonSet
|
|
318
|
+
className={classNames({
|
|
319
|
+
[styles.tablet]: isTablet,
|
|
320
|
+
[styles.desktop]: !isTablet,
|
|
321
|
+
})}
|
|
322
|
+
>
|
|
323
|
+
<Button className={styles.button} kind="secondary" disabled={isSubmitting} onClick={() => closeWorkspace()}>
|
|
324
|
+
{t('discard', 'Discard')}
|
|
325
|
+
</Button>
|
|
326
|
+
<Button
|
|
327
|
+
className={styles.button}
|
|
328
|
+
kind="primary"
|
|
329
|
+
disabled={isSubmitting || Object.keys(errors).length > 0}
|
|
330
|
+
type="submit"
|
|
331
|
+
>
|
|
332
|
+
{isSubmitting ? (
|
|
333
|
+
<InlineLoading description={t('saving', 'Saving') + '...'} />
|
|
334
|
+
) : (
|
|
335
|
+
t('saveAndClose', 'Save and close')
|
|
336
|
+
)}
|
|
337
|
+
</Button>
|
|
338
|
+
</ButtonSet>
|
|
339
|
+
</Form>
|
|
340
|
+
);
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
export default LabResultsForm;
|