@openmrs/esm-patient-vitals-app 11.3.1-pre.9452 → 11.3.1-pre.9455

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.
@@ -1,613 +1,30 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import { useForm } from 'react-hook-form';
3
- import { useTranslation } from 'react-i18next';
4
- import { zodResolver } from '@hookform/resolvers/zod';
5
- import {
6
- Button,
7
- ButtonSkeleton,
8
- ButtonSet,
9
- Column,
10
- Form,
11
- InlineNotification,
12
- NumberInputSkeleton,
13
- Row,
14
- Stack,
15
- } from '@carbon/react';
16
- import {
17
- age,
18
- ExtensionSlot,
19
- showSnackbar,
20
- useAbortController,
21
- useConfig,
22
- useLayoutType,
23
- useSession,
24
- } from '@openmrs/esm-framework';
25
- import { type DefaultPatientWorkspaceProps, useOptimisticVisitMutations } from '@openmrs/esm-patient-common-lib';
26
- import { type ConfigObject } from '../config-schema';
27
- import {
28
- calculateBodyMassIndex,
29
- extractNumbers,
30
- getMuacColorCode,
31
- isValueWithinReferenceRange,
32
- } from './vitals-biometrics-form.utils';
33
- import {
34
- assessValue,
35
- createOrUpdateVitalsAndBiometrics,
36
- getReferenceRangesForConcept,
37
- interpretBloodPressure,
38
- invalidateCachedVitalsAndBiometrics,
39
- useConceptUnits,
40
- useEncounterVitalsAndBiometrics,
41
- } from '../common';
42
- import { prepareObsForSubmission } from '../common/helpers';
43
- import { useVitalsConceptMetadata } from '../common/data.resource';
44
- import { VitalsAndBiometricsFormSchema, type VitalsBiometricsFormData } from './schema';
45
- import VitalsAndBiometricsInput from './vitals-biometrics-input.component';
46
- import styles from './vitals-biometrics-form.scss';
1
+ import React from 'react';
2
+ import { type PatientWorkspace2DefinitionProps } from '@openmrs/esm-patient-common-lib';
3
+ import ExportedVitalsAndBiometricsForm from './exported-vitals-biometrics-form.workspace';
47
4
 
48
- export interface VitalsAndBiometricsFormProps extends DefaultPatientWorkspaceProps {
5
+ export interface VitalsAndBiometricsFormProps {
49
6
  formContext: 'creating' | 'editing';
50
7
  editEncounterUuid?: string;
51
8
  }
52
9
 
53
- const VitalsAndBiometricsForm: React.FC<VitalsAndBiometricsFormProps> = ({
54
- patientUuid,
55
- patient,
56
- editEncounterUuid,
57
- formContext = 'creating',
58
- closeWorkspace,
59
- closeWorkspaceWithSavedChanges,
60
- promptBeforeClosing,
61
- visitContext,
10
+ /**
11
+ * This workspace displays the form to input patient vitals and biometrics.
12
+ *
13
+ * This workspace must only be used within the patient chart.
14
+ * @see exported-vitals-biometrics-form.workspace.tsx
15
+ */
16
+ const VitalsAndBiometricsForm: React.FC<PatientWorkspace2DefinitionProps<VitalsAndBiometricsFormProps, {}>> = ({
17
+ workspaceProps: { editEncounterUuid, formContext = 'creating' },
18
+ groupProps: { patientUuid, patient, visitContext },
19
+ ...rest
62
20
  }) => {
63
- const { t } = useTranslation();
64
- const isTablet = useLayoutType() === 'tablet';
65
- const config = useConfig<ConfigObject>();
66
- const biometricsUnitsSymbols = config.biometrics;
67
- const useMuacColorStatus = config.vitals.useMuacColors;
68
-
69
- const session = useSession();
70
- const { conceptUnits, isLoading: isLoadingConceptUnits } = useConceptUnits();
71
- const { conceptRanges, conceptRangeMap } = useVitalsConceptMetadata(patientUuid);
72
- const {
73
- getRefinedInitialValues,
74
- isLoading: isLoadingEncounter,
75
- mutate: mutateEncounter,
76
- vitalsAndBiometrics: initialFieldValuesMap,
77
- } = useEncounterVitalsAndBiometrics(formContext === 'editing' ? editEncounterUuid : null);
78
- const [hasInvalidVitals, setHasInvalidVitals] = useState(false);
79
- const [muacColorCode, setMuacColorCode] = useState('');
80
- const [showErrorNotification, setShowErrorNotification] = useState(false);
81
- const [showErrorMessage, setShowErrorMessage] = useState(false);
82
- const abortController = useAbortController();
83
- const { invalidateVisitRelatedData } = useOptimisticVisitMutations(patientUuid);
84
-
85
- const isLoadingInitialValues = useMemo(
86
- () => (formContext === 'creating' ? false : isLoadingEncounter),
87
- [formContext, isLoadingEncounter],
88
- );
89
-
90
- const {
91
- control,
92
- handleSubmit,
93
- watch,
94
- setValue,
95
- formState: { isDirty, isSubmitting, dirtyFields },
96
- reset,
97
- } = useForm<VitalsBiometricsFormData>({
98
- mode: 'all',
99
- resolver: zodResolver(VitalsAndBiometricsFormSchema),
100
- });
101
-
102
- useEffect(() => {
103
- if (formContext === 'editing' && !isLoadingInitialValues && initialFieldValuesMap) {
104
- reset(getRefinedInitialValues());
105
- }
106
- }, [formContext, isLoadingInitialValues, initialFieldValuesMap, getRefinedInitialValues, reset]);
107
-
108
- useEffect(() => promptBeforeClosing(() => isDirty), [isDirty, promptBeforeClosing]);
109
-
110
- const encounterUuid = visitContext?.encounters?.find(
111
- (encounter) => encounter?.form?.uuid === config.vitals.formUuid,
112
- )?.uuid;
113
-
114
- const midUpperArmCircumference = watch('midUpperArmCircumference');
115
- const systolicBloodPressure = watch('systolicBloodPressure');
116
- const diastolicBloodPressure = watch('diastolicBloodPressure');
117
- const respiratoryRate = watch('respiratoryRate');
118
- const oxygenSaturation = watch('oxygenSaturation');
119
- const temperature = watch('temperature');
120
- const pulse = watch('pulse');
121
- const weight = watch('weight');
122
- const height = watch('height');
123
-
124
- useEffect(() => {
125
- const patientBirthDate = patient?.birthDate;
126
- if (patientBirthDate && midUpperArmCircumference) {
127
- const patientAge = extractNumbers(age(patientBirthDate));
128
- getMuacColorCode(patientAge, midUpperArmCircumference, setMuacColorCode);
129
- }
130
- }, [watch, patient?.birthDate, midUpperArmCircumference]);
131
-
132
- useEffect(() => {
133
- if (height && weight) {
134
- const computedBodyMassIndex = calculateBodyMassIndex(
135
- weight,
136
- height,
137
- conceptUnits.get(config.concepts.weightUuid) as 'lb' | 'lbs' | 'g',
138
- conceptUnits.get(config.concepts.heightUuid) as 'm' | 'cm' | 'in',
139
- );
140
- setValue('computedBodyMassIndex', computedBodyMassIndex);
141
- }
142
- }, [weight, height, setValue, conceptUnits, config.concepts.weightUuid, config.concepts.heightUuid]);
143
-
144
- function onError(err) {
145
- if (err?.oneFieldRequired) {
146
- setShowErrorNotification(true);
147
- }
148
- }
149
-
150
- const concepts = useMemo(
151
- () => ({
152
- midUpperArmCircumferenceRange: conceptRangeMap.get(config.concepts.midUpperArmCircumferenceUuid),
153
- diastolicBloodPressureRange: conceptRangeMap.get(config.concepts.diastolicBloodPressureUuid),
154
- systolicBloodPressureRange: conceptRangeMap.get(config.concepts.systolicBloodPressureUuid),
155
- oxygenSaturationRange: conceptRangeMap.get(config.concepts.oxygenSaturationUuid),
156
- respiratoryRateRange: conceptRangeMap.get(config.concepts.respiratoryRateUuid),
157
- temperatureRange: conceptRangeMap.get(config.concepts.temperatureUuid),
158
- weightRange: conceptRangeMap.get(config.concepts.weightUuid),
159
- heightRange: conceptRangeMap.get(config.concepts.heightUuid),
160
- pulseRange: conceptRangeMap.get(config.concepts.pulseUuid),
161
- }),
162
- [conceptRangeMap, config.concepts],
163
- );
164
-
165
- const savePatientVitalsAndBiometrics = useCallback(
166
- (data: VitalsBiometricsFormData) => {
167
- const formData = data;
168
- setShowErrorMessage(true);
169
- setShowErrorNotification(false);
170
-
171
- data?.computedBodyMassIndex && delete data.computedBodyMassIndex;
172
-
173
- const allFieldsAreValid = Object.entries(formData)
174
- .filter(([, value]) => Boolean(value))
175
- .every(([key, value]) => isValueWithinReferenceRange(conceptRanges, config.concepts[`${key}Uuid`], value));
176
-
177
- if (allFieldsAreValid) {
178
- setShowErrorMessage(false);
179
- const { newObs, toBeVoided } = prepareObsForSubmission(
180
- formData,
181
- dirtyFields,
182
- formContext,
183
- initialFieldValuesMap,
184
- config.concepts,
185
- );
186
-
187
- createOrUpdateVitalsAndBiometrics(
188
- patientUuid,
189
- config.vitals.encounterTypeUuid,
190
- editEncounterUuid,
191
- session?.sessionLocation?.uuid,
192
- [...newObs, ...toBeVoided],
193
- abortController,
194
- )
195
- .then(() => {
196
- if (mutateEncounter) {
197
- mutateEncounter();
198
- }
199
- // Only invalidate observations data since we created new vitals/biometrics observations
200
- invalidateVisitRelatedData({ observations: true, encounters: true });
201
- invalidateCachedVitalsAndBiometrics();
202
- closeWorkspaceWithSavedChanges();
203
- showSnackbar({
204
- isLowContrast: true,
205
- kind: 'success',
206
- title:
207
- formContext === 'creating'
208
- ? t('vitalsAndBiometricsSaved', 'Vitals and Biometrics saved')
209
- : t('vitalsAndBiometricsUpdated', 'Vitals and Biometrics updated'),
210
- subtitle: t('vitalsAndBiometricsNowAvailable', 'They are now visible on the Vitals and Biometrics page'),
211
- });
212
- })
213
- .catch(() => {
214
- showSnackbar({
215
- title:
216
- formContext === 'creating'
217
- ? t('vitalsAndBiometricsSaveError', 'Error saving Vitals and Biometrics')
218
- : t('vitalsAndBiometricsUpdateError', 'Error updating Vitals and Biometrics'),
219
- kind: 'error',
220
- isLowContrast: false,
221
- subtitle: t('checkForValidity', 'Some of the values entered are invalid'),
222
- });
223
- });
224
- } else {
225
- setHasInvalidVitals(true);
226
- }
227
- },
228
- [
229
- abortController,
230
- closeWorkspaceWithSavedChanges,
231
- config.concepts,
232
- config.vitals.encounterTypeUuid,
233
- dirtyFields,
234
- editEncounterUuid,
235
- conceptRanges,
236
- formContext,
237
- initialFieldValuesMap,
238
- mutateEncounter,
239
- invalidateVisitRelatedData,
240
- patientUuid,
241
- session?.sessionLocation?.uuid,
242
- t,
243
- ],
244
- );
245
-
246
- if (config.vitals.useFormEngine) {
247
- return (
248
- <ExtensionSlot
249
- name="form-widget-slot"
250
- state={{
251
- view: 'form',
252
- formUuid: config.vitals.formUuid,
253
- visitUuid: visitContext?.uuid,
254
- visitTypeUuid: visitContext?.visitType?.uuid,
255
- patientUuid: patientUuid ?? null,
256
- patient,
257
- encounterUuid,
258
- closeWorkspaceWithSavedChanges,
259
- }}
260
- />
261
- );
262
- }
263
-
264
- if (isLoadingConceptUnits || isLoadingInitialValues) {
265
- return (
266
- <Form className={styles.form}>
267
- <ExtensionSlot name="visit-context-header-slot" state={{ patientUuid }} />
268
- <div className={styles.grid}>
269
- <Stack>
270
- <Column>
271
- <p className={styles.title}>{t('recordVitals', 'Record vitals')}</p>
272
- </Column>
273
- <Row className={styles.row}>
274
- <Column>
275
- <NumberInputSkeleton />
276
- </Column>
277
- <Column>
278
- <NumberInputSkeleton />
279
- </Column>
280
- <Column>
281
- <NumberInputSkeleton />
282
- </Column>
283
- <Column>
284
- <NumberInputSkeleton />
285
- </Column>
286
- </Row>
287
- </Stack>
288
- </div>
289
- <ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
290
- <ButtonSkeleton className={styles.button} />
291
- <ButtonSkeleton className={styles.button} />
292
- </ButtonSet>
293
- </Form>
294
- );
295
- }
296
-
297
21
  return (
298
- <Form className={styles.form} data-openmrs-role="Vitals and Biometrics Form">
299
- <ExtensionSlot name="visit-context-header-slot" state={{ patientUuid }} />
300
- <div className={styles.grid}>
301
- <Stack>
302
- <Column>
303
- <p className={styles.title}>{t('recordVitals', 'Record vitals')}</p>
304
- </Column>
305
- <Row className={styles.row}>
306
- <Column>
307
- <VitalsAndBiometricsInput
308
- control={control}
309
- fieldProperties={[
310
- {
311
- id: 'temperature',
312
- max: concepts.temperatureRange?.hiAbsolute,
313
- min: concepts.temperatureRange?.lowAbsolute,
314
- name: t('temperature', 'Temperature'),
315
- type: 'number',
316
- },
317
- ]}
318
- interpretation={
319
- temperature &&
320
- assessValue(temperature, getReferenceRangesForConcept(config.concepts.temperatureUuid, conceptRanges))
321
- }
322
- isValueWithinReferenceRange={
323
- temperature
324
- ? isValueWithinReferenceRange(conceptRanges, config.concepts['temperatureUuid'], temperature)
325
- : true
326
- }
327
- showErrorMessage={showErrorMessage}
328
- label={t('temperature', 'Temperature')}
329
- unitSymbol={conceptUnits.get(config.concepts.temperatureUuid) ?? ''}
330
- />
331
- </Column>
332
- <Column>
333
- <VitalsAndBiometricsInput
334
- control={control}
335
- fieldProperties={[
336
- {
337
- name: t('systolic', 'systolic'),
338
- separator: '/',
339
- type: 'number',
340
- min: concepts.systolicBloodPressureRange?.lowAbsolute,
341
- max: concepts.systolicBloodPressureRange?.hiAbsolute,
342
- id: 'systolicBloodPressure',
343
- },
344
- {
345
- name: t('diastolic', 'diastolic'),
346
- type: 'number',
347
- min: concepts.diastolicBloodPressureRange?.lowAbsolute,
348
- max: concepts.diastolicBloodPressureRange?.hiAbsolute,
349
- id: 'diastolicBloodPressure',
350
- },
351
- ]}
352
- interpretation={
353
- systolicBloodPressure &&
354
- diastolicBloodPressure &&
355
- interpretBloodPressure(systolicBloodPressure, diastolicBloodPressure, config.concepts, conceptRanges)
356
- }
357
- isValueWithinReferenceRange={
358
- systolicBloodPressure &&
359
- diastolicBloodPressure &&
360
- isValueWithinReferenceRange(
361
- conceptRanges,
362
- config.concepts.systolicBloodPressureUuid,
363
- systolicBloodPressure,
364
- ) &&
365
- isValueWithinReferenceRange(
366
- conceptRanges,
367
- config.concepts.diastolicBloodPressureUuid,
368
- diastolicBloodPressure,
369
- )
370
- }
371
- showErrorMessage={showErrorMessage}
372
- label={t('bloodPressure', 'Blood pressure')}
373
- unitSymbol={conceptUnits.get(config.concepts.systolicBloodPressureUuid) ?? ''}
374
- />
375
- </Column>
376
- <Column>
377
- <VitalsAndBiometricsInput
378
- control={control}
379
- fieldProperties={[
380
- {
381
- name: t('pulse', 'Pulse'),
382
- type: 'number',
383
- min: concepts.pulseRange?.lowAbsolute,
384
- max: concepts.pulseRange?.hiAbsolute,
385
- id: 'pulse',
386
- },
387
- ]}
388
- interpretation={
389
- pulse && assessValue(pulse, getReferenceRangesForConcept(config.concepts.pulseUuid, conceptRanges))
390
- }
391
- isValueWithinReferenceRange={
392
- pulse && isValueWithinReferenceRange(conceptRanges, config.concepts['pulseUuid'], pulse)
393
- }
394
- label={t('heartRate', 'Heart rate')}
395
- showErrorMessage={showErrorMessage}
396
- unitSymbol={conceptUnits.get(config.concepts.pulseUuid) ?? ''}
397
- />
398
- </Column>
399
- <Column>
400
- <VitalsAndBiometricsInput
401
- control={control}
402
- fieldProperties={[
403
- {
404
- name: t('respirationRate', 'Respiration rate'),
405
- type: 'number',
406
- min: concepts.respiratoryRateRange?.lowAbsolute,
407
- max: concepts.respiratoryRateRange?.hiAbsolute,
408
- id: 'respiratoryRate',
409
- },
410
- ]}
411
- interpretation={
412
- respiratoryRate &&
413
- assessValue(
414
- respiratoryRate,
415
- getReferenceRangesForConcept(config.concepts.respiratoryRateUuid, conceptRanges),
416
- )
417
- }
418
- isValueWithinReferenceRange={
419
- respiratoryRate &&
420
- isValueWithinReferenceRange(conceptRanges, config.concepts['respiratoryRateUuid'], respiratoryRate)
421
- }
422
- showErrorMessage={showErrorMessage}
423
- label={t('respirationRate', 'Respiration rate')}
424
- unitSymbol={conceptUnits.get(config.concepts.respiratoryRateUuid) ?? ''}
425
- />
426
- </Column>
427
- <Column>
428
- <VitalsAndBiometricsInput
429
- control={control}
430
- fieldProperties={[
431
- {
432
- name: t('oxygenSaturation', 'Oxygen saturation'),
433
- type: 'number',
434
- min: concepts.oxygenSaturationRange?.lowAbsolute,
435
- max: concepts.oxygenSaturationRange?.hiAbsolute,
436
- id: 'oxygenSaturation',
437
- },
438
- ]}
439
- interpretation={
440
- oxygenSaturation &&
441
- assessValue(
442
- oxygenSaturation,
443
- getReferenceRangesForConcept(config.concepts.oxygenSaturationUuid, conceptRanges),
444
- )
445
- }
446
- isValueWithinReferenceRange={
447
- oxygenSaturation &&
448
- isValueWithinReferenceRange(conceptRanges, config.concepts['oxygenSaturationUuid'], oxygenSaturation)
449
- }
450
- showErrorMessage={showErrorMessage}
451
- label={t('spo2', 'SpO2')}
452
- unitSymbol={conceptUnits.get(config.concepts.oxygenSaturationUuid) ?? ''}
453
- />
454
- </Column>
455
- </Row>
456
-
457
- <Row className={styles.row}>
458
- <Column className={styles.noteInput}>
459
- <VitalsAndBiometricsInput
460
- control={control}
461
- fieldWidth={isTablet ? '70%' : '100%'}
462
- fieldProperties={[
463
- {
464
- name: t('notes', 'Notes'),
465
- type: 'textarea',
466
- id: 'generalPatientNote',
467
- },
468
- ]}
469
- placeholder={t('additionalNoteText', 'Type any additional notes here')}
470
- label={t('notes', 'Notes')}
471
- />
472
- </Column>
473
- </Row>
474
- </Stack>
475
- <Stack className={styles.spacer}>
476
- <Column>
477
- <p className={styles.title}>{t('recordBiometrics', 'Record biometrics')}</p>
478
- </Column>
479
- <Row className={styles.row}>
480
- <Column>
481
- <VitalsAndBiometricsInput
482
- control={control}
483
- fieldProperties={[
484
- {
485
- name: t('weight', 'Weight'),
486
- type: 'number',
487
- min: concepts.weightRange?.lowAbsolute,
488
- max: concepts.weightRange?.hiAbsolute,
489
- id: 'weight',
490
- },
491
- ]}
492
- interpretation={
493
- weight && assessValue(weight, getReferenceRangesForConcept(config.concepts.weightUuid, conceptRanges))
494
- }
495
- isValueWithinReferenceRange={
496
- height && isValueWithinReferenceRange(conceptRanges, config.concepts['weightUuid'], weight)
497
- }
498
- showErrorMessage={showErrorMessage}
499
- label={t('weight', 'Weight')}
500
- unitSymbol={conceptUnits.get(config.concepts.weightUuid) ?? ''}
501
- />
502
- </Column>
503
- <Column>
504
- <VitalsAndBiometricsInput
505
- control={control}
506
- fieldProperties={[
507
- {
508
- name: t('height', 'Height'),
509
- type: 'number',
510
- min: concepts.heightRange?.lowAbsolute,
511
- max: concepts.heightRange?.hiAbsolute,
512
- id: 'height',
513
- },
514
- ]}
515
- interpretation={
516
- height && assessValue(height, getReferenceRangesForConcept(config.concepts.heightUuid, conceptRanges))
517
- }
518
- isValueWithinReferenceRange={
519
- weight && isValueWithinReferenceRange(conceptRanges, config.concepts['heightUuid'], height)
520
- }
521
- showErrorMessage={showErrorMessage}
522
- label={t('height', 'Height')}
523
- unitSymbol={conceptUnits.get(config.concepts.heightUuid) ?? ''}
524
- />
525
- </Column>
526
- <Column>
527
- <VitalsAndBiometricsInput
528
- control={control}
529
- fieldProperties={[
530
- {
531
- name: t('bmi', 'BMI'),
532
- type: 'number',
533
- id: 'computedBodyMassIndex',
534
- },
535
- ]}
536
- readOnly
537
- label={t('calculatedBmi', 'BMI (calc.)')}
538
- unitSymbol={biometricsUnitsSymbols['bmiUnit']}
539
- />
540
- </Column>
541
- <Column>
542
- <VitalsAndBiometricsInput
543
- control={control}
544
- fieldProperties={[
545
- {
546
- name: t('muac', 'MUAC'),
547
- type: 'number',
548
- min: concepts.midUpperArmCircumferenceRange?.lowAbsolute,
549
- max: concepts.midUpperArmCircumferenceRange?.hiAbsolute,
550
- id: 'midUpperArmCircumference',
551
- },
552
- ]}
553
- muacColorCode={muacColorCode}
554
- isValueWithinReferenceRange={
555
- height &&
556
- weight &&
557
- isValueWithinReferenceRange(
558
- conceptRanges,
559
- config.concepts['midUpperArmCircumferenceUuid'],
560
- midUpperArmCircumference,
561
- )
562
- }
563
- showErrorMessage={showErrorMessage}
564
- label={t('muac', 'MUAC')}
565
- unitSymbol={conceptUnits.get(config.concepts.midUpperArmCircumferenceUuid) ?? ''}
566
- useMuacColors={useMuacColorStatus}
567
- />
568
- </Column>
569
- </Row>
570
- </Stack>
571
- </div>
572
-
573
- {showErrorNotification && (
574
- <Column className={styles.errorContainer}>
575
- <InlineNotification
576
- lowContrast
577
- title={t('error', 'Error')}
578
- subtitle={t('pleaseFillField', 'Please fill at least one field') + '.'}
579
- onClose={() => setShowErrorNotification(false)}
580
- />
581
- </Column>
582
- )}
583
-
584
- {hasInvalidVitals && (
585
- <Column className={styles.errorContainer}>
586
- <InlineNotification
587
- className={styles.errorNotification}
588
- lowContrast={false}
589
- onClose={() => setHasInvalidVitals(false)}
590
- title={t('vitalsAndBiometricsSaveError', 'Error saving Vitals and Biometrics')}
591
- subtitle={t('checkForValidity', 'Some of the values entered are invalid')}
592
- />
593
- </Column>
594
- )}
595
-
596
- <ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
597
- <Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
598
- {t('discard', 'Discard')}
599
- </Button>
600
- <Button
601
- className={styles.button}
602
- kind="primary"
603
- onClick={handleSubmit(savePatientVitalsAndBiometrics, onError)}
604
- disabled={!isDirty || isSubmitting}
605
- type="submit"
606
- >
607
- {t('saveAndClose', 'Save and close')}
608
- </Button>
609
- </ButtonSet>
610
- </Form>
22
+ <ExportedVitalsAndBiometricsForm
23
+ workspaceProps={{ editEncounterUuid, formContext, patientUuid, patient, visitContext }}
24
+ windowProps={null}
25
+ groupProps={null}
26
+ {...rest}
27
+ />
611
28
  );
612
29
  };
613
30