@openmrs/esm-patient-vitals-app 9.2.3-pre.7186 → 9.2.3-pre.7198

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.
Files changed (122) hide show
  1. package/.turbo/turbo-build.log +20 -20
  2. package/dist/101.js +2 -0
  3. package/dist/101.js.map +1 -0
  4. package/dist/1197.js +1 -1
  5. package/dist/{9057.js → 1423.js} +1 -1
  6. package/dist/1423.js.map +1 -0
  7. package/dist/2146.js +1 -1
  8. package/dist/2690.js +1 -1
  9. package/dist/3099.js +1 -1
  10. package/dist/3584.js +1 -1
  11. package/dist/4055.js +1 -1
  12. package/dist/4132.js +1 -1
  13. package/dist/4300.js +1 -1
  14. package/dist/4335.js +1 -1
  15. package/dist/4618.js +1 -1
  16. package/dist/4652.js +1 -1
  17. package/dist/4944.js +1 -1
  18. package/dist/5173.js +1 -1
  19. package/dist/5207.js +1 -0
  20. package/dist/5207.js.map +1 -0
  21. package/dist/5241.js +1 -1
  22. package/dist/5387.js +1 -0
  23. package/dist/5387.js.map +1 -0
  24. package/dist/5395.js +2 -0
  25. package/dist/{3368.js.LICENSE.txt → 5395.js.LICENSE.txt} +19 -1
  26. package/dist/5395.js.map +1 -0
  27. package/dist/5442.js +1 -1
  28. package/dist/5661.js +1 -1
  29. package/dist/6468.js +1 -1
  30. package/dist/6679.js +1 -1
  31. package/dist/6840.js +1 -1
  32. package/dist/6859.js +1 -1
  33. package/dist/7159.js +1 -1
  34. package/dist/723.js +1 -1
  35. package/dist/8163.js +1 -1
  36. package/dist/8295.js +2 -0
  37. package/dist/8295.js.LICENSE.txt +7 -0
  38. package/dist/8295.js.map +1 -0
  39. package/dist/8618.js +1 -1
  40. package/dist/890.js +1 -1
  41. package/dist/8953.js +1 -0
  42. package/dist/8953.js.map +1 -0
  43. package/dist/9214.js +1 -1
  44. package/dist/9538.js +1 -1
  45. package/dist/986.js +1 -1
  46. package/dist/9879.js +1 -1
  47. package/dist/9895.js +1 -1
  48. package/dist/9900.js +1 -1
  49. package/dist/9913.js +1 -1
  50. package/dist/main.js +1 -1
  51. package/dist/main.js.map +1 -1
  52. package/dist/openmrs-esm-patient-vitals-app.js +1 -1
  53. package/dist/openmrs-esm-patient-vitals-app.js.buildmanifest.json +238 -214
  54. package/dist/openmrs-esm-patient-vitals-app.js.map +1 -1
  55. package/dist/routes.json +1 -1
  56. package/package.json +2 -2
  57. package/src/biometrics/biometrics-base.component.tsx +0 -1
  58. package/src/biometrics/biometrics-overview.test.tsx +26 -5
  59. package/src/biometrics/paginated-biometrics.component.tsx +5 -0
  60. package/src/common/data.resource.ts +128 -75
  61. package/src/common/helpers.ts +50 -0
  62. package/src/common/index.ts +3 -2
  63. package/src/common/types.ts +2 -1
  64. package/src/components/action-menu/vitals-biometrics-action-menu.component.tsx +60 -0
  65. package/src/components/action-menu/vitals-biometrics-action-menu.scss +11 -0
  66. package/src/components/delete-vitals-biometrics-modal/delete-vitals-biometrics.modal.tsx +84 -0
  67. package/src/{weight-tile → components/weight-tile}/weight-tile.component.tsx +2 -2
  68. package/src/{weight-tile → components/weight-tile}/weight-tile.test.tsx +4 -4
  69. package/src/index.ts +6 -1
  70. package/src/routes.json +6 -0
  71. package/src/vitals/paginated-vitals.component.tsx +5 -0
  72. package/src/vitals/vitals-overview.component.tsx +3 -2
  73. package/src/vitals-biometrics-form/schema.ts +28 -0
  74. package/src/vitals-biometrics-form/vitals-biometrics-form.test.tsx +203 -21
  75. package/src/vitals-biometrics-form/vitals-biometrics-form.workspace.tsx +78 -60
  76. package/src/vitals-biometrics-form/vitals-biometrics-input.component.tsx +1 -1
  77. package/translations/am.json +14 -1
  78. package/translations/ar.json +14 -1
  79. package/translations/bn.json +14 -1
  80. package/translations/de.json +14 -1
  81. package/translations/en.json +14 -1
  82. package/translations/es.json +14 -1
  83. package/translations/fr.json +14 -1
  84. package/translations/he.json +14 -1
  85. package/translations/hi.json +14 -1
  86. package/translations/hi_IN.json +14 -1
  87. package/translations/id.json +14 -1
  88. package/translations/it.json +14 -1
  89. package/translations/km.json +14 -1
  90. package/translations/ku.json +14 -1
  91. package/translations/ky.json +14 -1
  92. package/translations/lg.json +14 -1
  93. package/translations/ne.json +14 -1
  94. package/translations/pt.json +14 -1
  95. package/translations/pt_BR.json +14 -1
  96. package/translations/qu.json +14 -1
  97. package/translations/ru_RU.json +14 -1
  98. package/translations/si.json +14 -1
  99. package/translations/sw.json +14 -1
  100. package/translations/sw_KE.json +14 -1
  101. package/translations/tr.json +14 -1
  102. package/translations/tr_TR.json +14 -1
  103. package/translations/uk.json +14 -1
  104. package/translations/uz.json +14 -1
  105. package/translations/uz_UZ.json +14 -1
  106. package/translations/vi.json +14 -1
  107. package/translations/zh.json +14 -1
  108. package/translations/zh_CN.json +14 -1
  109. package/dist/3368.js +0 -2
  110. package/dist/3368.js.map +0 -1
  111. package/dist/4716.js +0 -1
  112. package/dist/4716.js.map +0 -1
  113. package/dist/7738.js +0 -2
  114. package/dist/7738.js.LICENSE.txt +0 -25
  115. package/dist/7738.js.map +0 -1
  116. package/dist/8895.js +0 -2
  117. package/dist/8895.js.map +0 -1
  118. package/dist/8957.js +0 -1
  119. package/dist/8957.js.map +0 -1
  120. package/dist/9057.js.map +0 -1
  121. /package/dist/{8895.js.LICENSE.txt → 101.js.LICENSE.txt} +0 -0
  122. /package/src/{weight-tile → components/weight-tile}/weight-tile.scss +0 -0
@@ -0,0 +1,11 @@
1
+ .layer {
2
+ height: 100%;
3
+
4
+ :global(.cds--overflow-menu) {
5
+ min-height: unset;
6
+ }
7
+ }
8
+
9
+ .menuItem {
10
+ max-width: none;
11
+ }
@@ -0,0 +1,84 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { showSnackbar } from '@openmrs/esm-framework';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { deleteEncounter, invalidateCachedVitalsAndBiometrics } from '../../common';
5
+ import { ModalHeader, ModalBody, ModalFooter, Button, InlineLoading } from '@carbon/react';
6
+
7
+ interface DeleteVitalsAndBiometricsModalProps {
8
+ patientUuid: string;
9
+ encounterUuid: string;
10
+ closeDeleteModal: () => void;
11
+ }
12
+
13
+ const DeleteVitalsAndBiometricsModal: React.FC<DeleteVitalsAndBiometricsModalProps> = ({
14
+ encounterUuid,
15
+ closeDeleteModal,
16
+ }) => {
17
+ const { t } = useTranslation();
18
+ const [isDeleting, setIsDeleting] = useState(false);
19
+
20
+ const handleDelete = useCallback(async () => {
21
+ if (!encounterUuid) {
22
+ showSnackbar({
23
+ isLowContrast: false,
24
+ kind: 'error',
25
+ title: t('errorDeleting', 'Error deleting vitals and biometrics'),
26
+ subtitle: t('encounterUuidRequired', 'Encounter UUID is required to delete vitals and biometrics'),
27
+ });
28
+ return;
29
+ }
30
+
31
+ setIsDeleting(true);
32
+ deleteEncounter(encounterUuid)
33
+ .then(() => {
34
+ invalidateCachedVitalsAndBiometrics();
35
+ closeDeleteModal();
36
+ showSnackbar({
37
+ isLowContrast: true,
38
+ kind: 'success',
39
+ title: t('vitalsAndBiometricsDeleted', 'Vitals and biometrics deleted'),
40
+ });
41
+ })
42
+ .catch((error) => {
43
+ console.error('Error deleting encounter: ', error);
44
+ showSnackbar({
45
+ isLowContrast: false,
46
+ kind: 'error',
47
+ title: t('errorDeleting', 'Error deleting vitals and biometrics'),
48
+ subtitle: error?.message,
49
+ });
50
+ })
51
+ .finally(() => setIsDeleting(false));
52
+ }, [encounterUuid, t, closeDeleteModal]);
53
+
54
+ return (
55
+ <>
56
+ <ModalHeader
57
+ closeModal={closeDeleteModal}
58
+ title={t('deleteVitalsAndBiometrics', 'Delete vitals and biometrics')}
59
+ />
60
+ <ModalBody>
61
+ <p>
62
+ {t(
63
+ 'deleteConfirmationText',
64
+ 'Note: Deleting these entries will also remove related vitals or biometrics data. Are you sure you want to continue?',
65
+ )}
66
+ </p>
67
+ </ModalBody>
68
+ <ModalFooter>
69
+ <Button kind="secondary" onClick={closeDeleteModal}>
70
+ {t('cancel', 'Cancel')}
71
+ </Button>
72
+ <Button kind="danger" onClick={handleDelete} disabled={isDeleting}>
73
+ {isDeleting ? (
74
+ <InlineLoading description={t('deleting', 'Deleting') + '...'} />
75
+ ) : (
76
+ <span>{t('delete', 'Delete')}</span>
77
+ )}
78
+ </Button>
79
+ </ModalFooter>
80
+ </>
81
+ );
82
+ };
83
+
84
+ export default DeleteVitalsAndBiometricsModal;
@@ -2,8 +2,8 @@ import React from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { InlineLoading } from '@carbon/react';
4
4
  import { useConfig } from '@openmrs/esm-framework';
5
- import { useVitalsAndBiometrics, useVitalsConceptMetadata } from '../common';
6
- import { type ConfigObject } from '../config-schema';
5
+ import { useVitalsAndBiometrics, useVitalsConceptMetadata } from '../../common';
6
+ import { type ConfigObject } from '../../config-schema';
7
7
  import styles from './weight-tile.scss';
8
8
 
9
9
  interface WeightTileInterface {
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
2
  import { screen } from '@testing-library/react';
3
3
  import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
4
- import { configSchema, type ConfigObject } from '../config-schema';
4
+ import { configSchema, type ConfigObject } from '../../config-schema';
5
5
  import { getByTextWithMarkup, mockPatient, renderWithSwr, waitForLoadingToFinish } from 'tools';
6
6
  import { formattedBiometrics, mockBiometricsConfig, mockConceptMetadata, mockVitalsSignsConcepts } from '__mocks__';
7
- import { useVitalsAndBiometrics } from '../common';
7
+ import { useVitalsAndBiometrics } from '../../common';
8
8
  import WeightTile from './weight-tile.component';
9
9
 
10
10
  const mockUseConfig = jest.mocked(useConfig<ConfigObject>);
@@ -13,8 +13,8 @@ const mockConceptUnits = new Map<string, string>(
13
13
  mockVitalsSignsConcepts.data.results[0].setMembers.map((concept) => [concept.uuid, concept.units]),
14
14
  );
15
15
 
16
- jest.mock('../common', () => {
17
- const originalModule = jest.requireActual('../common');
16
+ jest.mock('../../common', () => {
17
+ const originalModule = jest.requireActual('../../common');
18
18
 
19
19
  return {
20
20
  ...originalModule,
package/src/index.ts CHANGED
@@ -58,10 +58,15 @@ export const vitalsAndBiometricsDashboardLink =
58
58
  options,
59
59
  );
60
60
 
61
- export const weightTile = getAsyncLifecycle(() => import('./weight-tile/weight-tile.component'), options);
61
+ export const weightTile = getAsyncLifecycle(() => import('./components/weight-tile/weight-tile.component'), options);
62
62
 
63
63
  // t('recordVitalsAndBiometrics', 'Record Vitals and Biometrics')
64
64
  export const vitalsBiometricsFormWorkspace = getAsyncLifecycle(
65
65
  () => import('./vitals-biometrics-form/vitals-biometrics-form.workspace'),
66
66
  options,
67
67
  );
68
+
69
+ export const vitalsAndBiometricsDeleteConfirmationModal = getAsyncLifecycle(
70
+ () => import('./components/delete-vitals-biometrics-modal/delete-vitals-biometrics.modal'),
71
+ options,
72
+ );
package/src/routes.json CHANGED
@@ -73,5 +73,11 @@
73
73
  "title": "recordVitalsAndBiometrics",
74
74
  "component": "vitalsBiometricsFormWorkspace"
75
75
  }
76
+ ],
77
+ "modals": [
78
+ {
79
+ "name": "vitals-biometrics-delete-confirmation-modal",
80
+ "component": "vitalsAndBiometricsDeleteConfirmationModal"
81
+ }
76
82
  ]
77
83
  }
@@ -13,6 +13,7 @@ import { useLayoutType, usePagination } from '@openmrs/esm-framework';
13
13
  import { PatientChartPagination } from '@openmrs/esm-patient-common-lib';
14
14
  import type { VitalsTableHeader, VitalsTableRow } from './types';
15
15
  import styles from './paginated-vitals.scss';
16
+ import { VitalsAndBiometricsActionMenu } from '../components/action-menu/vitals-biometrics-action-menu.component';
16
17
 
17
18
  interface PaginatedVitalsProps {
18
19
  isPrinting?: boolean;
@@ -108,6 +109,7 @@ const PaginatedVitals: React.FC<PaginatedVitalsProps> = ({
108
109
  {header.header?.content ?? header.header}
109
110
  </TableHeader>
110
111
  ))}
112
+ <TableHeader />
111
113
  </TableRow>
112
114
  </TableHead>
113
115
  <TableBody>
@@ -123,6 +125,9 @@ const PaginatedVitals: React.FC<PaginatedVitalsProps> = ({
123
125
  </StyledTableCell>
124
126
  );
125
127
  })}
128
+ <TableCell className="cds--table-column-menu" id="actions">
129
+ <VitalsAndBiometricsActionMenu encounterUuid={row.id} />
130
+ </TableCell>
126
131
  </TableRow>
127
132
  ))}
128
133
  </TableBody>
@@ -22,6 +22,7 @@ import PaginatedVitals from './paginated-vitals.component';
22
22
  import PrintComponent from './print/print.component';
23
23
  import VitalsChart from './vitals-chart.component';
24
24
  import styles from './vitals-overview.scss';
25
+ import { useEncounterVitalsAndBiometrics } from '../common/data.resource';
25
26
 
26
27
  interface VitalsOverviewProps {
27
28
  patientUuid: string;
@@ -50,6 +51,7 @@ const VitalsOverview: React.FC<VitalsOverviewProps> = ({ patientUuid, patient, p
50
51
  launchVitalsAndBiometricsForm(currentVisit, config);
51
52
  }, [config, currentVisit]);
52
53
 
54
+ useEncounterVitalsAndBiometrics('771bbc44-8d45-4ac3-af6e-059814dd7cde');
53
55
  const patientDetails = useMemo(() => {
54
56
  const getGender = (gender: string): string => {
55
57
  switch (gender) {
@@ -139,10 +141,9 @@ const VitalsOverview: React.FC<VitalsOverviewProps> = ({ patientUuid, patient, p
139
141
 
140
142
  const tableRows: Array<VitalsTableRow> = useMemo(
141
143
  () =>
142
- vitals?.map((vitalSigns, index) => {
144
+ vitals?.map((vitalSigns) => {
143
145
  return {
144
146
  ...vitalSigns,
145
- id: `${index}`,
146
147
  dateRender: formatDate(parseDate(vitalSigns.date.toString()), { mode: 'wide', time: true }),
147
148
  bloodPressureRender: `${vitalSigns.systolic ?? '--'} / ${vitalSigns.diastolic ?? '--'}`,
148
149
  pulseRender: vitalSigns.pulse ?? '--',
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+
3
+ export const VitalsAndBiometricsFormSchema = z
4
+ .object({
5
+ systolicBloodPressure: z.number(),
6
+ diastolicBloodPressure: z.number(),
7
+ respiratoryRate: z.number(),
8
+ oxygenSaturation: z.number(),
9
+ pulse: z.number(),
10
+ temperature: z.number(),
11
+ generalPatientNote: z.string(),
12
+ weight: z.number(),
13
+ height: z.number(),
14
+ midUpperArmCircumference: z.number(),
15
+ computedBodyMassIndex: z.number(),
16
+ })
17
+ .partial()
18
+ .refine(
19
+ (fields) => {
20
+ return Object.values(fields).some((value) => Boolean(value));
21
+ },
22
+ {
23
+ message: 'Please fill at least one field',
24
+ path: ['oneFieldRequired'],
25
+ },
26
+ );
27
+
28
+ export type VitalsBiometricsFormData = z.infer<typeof VitalsAndBiometricsFormSchema>;
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { screen, render } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
4
  import { type FetchResponse, showSnackbar, useConfig, getDefaultsFromConfigSchema } from '@openmrs/esm-framework';
5
- import { saveVitalsAndBiometrics } from '../common';
5
+ import { createOrUpdateVitalsAndBiometrics, useEncounterVitalsAndBiometrics } from '../common';
6
6
  import { type ConfigObject, configSchema } from '../config-schema';
7
7
  import { mockConceptMetadata, mockConceptRanges, mockConceptUnits, mockVitalsConfig } from '__mocks__';
8
8
  import { mockPatient } from 'tools';
@@ -28,8 +28,9 @@ const testProps = {
28
28
  };
29
29
 
30
30
  const mockShowSnackbar = jest.mocked(showSnackbar);
31
- const mockSavePatientVitals = jest.mocked(saveVitalsAndBiometrics);
31
+ const mockCreateOrUpdateVitalsAndBiometrics = jest.mocked(createOrUpdateVitalsAndBiometrics);
32
32
  const mockUseConfig = jest.mocked(useConfig<ConfigObject>);
33
+ const mockUseEncounterVitalsAndBiometrics = jest.mocked(useEncounterVitalsAndBiometrics);
33
34
 
34
35
  jest.mock('../common', () => ({
35
36
  assessValue: jest.fn(),
@@ -37,13 +38,18 @@ jest.mock('../common', () => ({
37
38
  generatePlaceholder: jest.fn(),
38
39
  interpretBloodPressure: jest.fn(),
39
40
  invalidateCachedVitalsAndBiometrics: jest.fn(),
40
- saveVitalsAndBiometrics: jest.fn(),
41
+ createOrUpdateVitalsAndBiometrics: jest.fn(),
41
42
  useVitalsAndBiometrics: jest.fn(),
42
43
  useVitalsConceptMetadata: jest.fn().mockImplementation(() => ({
43
44
  data: mockConceptUnits,
44
45
  conceptMetadata: mockConceptMetadata,
45
46
  conceptRanges: mockConceptRanges,
46
47
  })),
48
+ useEncounterVitalsAndBiometrics: jest.fn().mockImplementation(() => ({
49
+ isLoading: false,
50
+ vitalsAndBiometrics: null,
51
+ mutate: jest.fn(),
52
+ })),
47
53
  }));
48
54
 
49
55
  mockUseConfig.mockReturnValue({
@@ -51,6 +57,91 @@ mockUseConfig.mockReturnValue({
51
57
  ...mockVitalsConfig,
52
58
  });
53
59
 
60
+ function setupMockUseEncounterVitalsAndBiometrics() {
61
+ mockUseEncounterVitalsAndBiometrics.mockReturnValue({
62
+ isLoading: false,
63
+ vitalsAndBiometrics: new Map([
64
+ [
65
+ 'systolicBloodPressure',
66
+ {
67
+ value: 120,
68
+ obs: { uuid: '123e4567-e89b-12d3-a456-426614174001', display: 'Systolic Blood Pressure: 120' },
69
+ },
70
+ ],
71
+ [
72
+ 'diastolicBloodPressure',
73
+ {
74
+ value: 80,
75
+ obs: { uuid: '123e4567-e89b-12d3-a456-426614174002', display: 'Diastolic Blood Pressure: 80' },
76
+ },
77
+ ],
78
+ [
79
+ 'pulse',
80
+ {
81
+ value: 75,
82
+ obs: { uuid: '123e4567-e89b-12d3-a456-426614174003', display: 'Pulse Rate: 75' },
83
+ },
84
+ ],
85
+ [
86
+ 'temperature',
87
+ {
88
+ value: 36.5,
89
+ obs: { uuid: '123e4567-e89b-12d3-a456-426614174004', display: 'Body Temperature: 36.5°C' },
90
+ },
91
+ ],
92
+ [
93
+ 'oxygenSaturation',
94
+ {
95
+ value: 98,
96
+ obs: { uuid: '123e4567-e89b-12d3-a456-426614174005', display: 'Oxygen Saturation: 98%' },
97
+ },
98
+ ],
99
+ [
100
+ 'height',
101
+ {
102
+ value: 170,
103
+ obs: { uuid: '123e4567-e89b-12d3-a456-426614174006', display: 'Height: 170 cm' },
104
+ },
105
+ ],
106
+ [
107
+ 'weight',
108
+ {
109
+ value: 65,
110
+ obs: { uuid: '123e4567-e89b-12d3-a456-426614174007', display: 'Weight: 65 kg' },
111
+ },
112
+ ],
113
+ [
114
+ 'respiratoryRate',
115
+ {
116
+ value: 16,
117
+ obs: { uuid: '123e4567-e89b-12d3-a456-426614174008', display: 'Respiratory Rate: 16 breaths/min' },
118
+ },
119
+ ],
120
+ [
121
+ 'midUpperArmCircumference',
122
+ {
123
+ value: 25,
124
+ obs: { uuid: '123e4567-e89b-12d3-a456-426614174009', display: 'Mid-Upper Arm Circumference: 25 cm' },
125
+ },
126
+ ],
127
+ ]),
128
+ encounter: null,
129
+ error: null,
130
+ mutate: jest.fn(),
131
+ getRefinedInitialValues: () => ({
132
+ height: 170,
133
+ weight: 65,
134
+ systolicBloodPressure: 120,
135
+ diastolicBloodPressure: 80,
136
+ pulse: 75,
137
+ oxygenSaturation: 98,
138
+ respiratoryRate: 16,
139
+ temperature: 36.5,
140
+ midUpperArmCircumference: 25,
141
+ }),
142
+ });
143
+ }
144
+
54
145
  describe('VitalsBiometricsForm', () => {
55
146
  it('renders the vitals and biometrics form', async () => {
56
147
  render(<VitalsAndBiometricsForm {...testProps} />);
@@ -107,7 +198,9 @@ describe('VitalsBiometricsForm', () => {
107
198
  data: [],
108
199
  };
109
200
 
110
- mockSavePatientVitals.mockResolvedValue(response as ReturnType<typeof saveVitalsAndBiometrics>);
201
+ mockCreateOrUpdateVitalsAndBiometrics.mockResolvedValue(
202
+ response as ReturnType<typeof createOrUpdateVitalsAndBiometrics>,
203
+ );
111
204
 
112
205
  render(<VitalsAndBiometricsForm {...testProps} />);
113
206
 
@@ -141,24 +234,28 @@ describe('VitalsBiometricsForm', () => {
141
234
 
142
235
  await user.click(saveButton);
143
236
 
144
- expect(mockSavePatientVitals).toHaveBeenCalledTimes(1);
145
- expect(mockSavePatientVitals).toHaveBeenCalledWith(
146
- mockVitalsConfig.vitals.encounterTypeUuid,
147
- mockVitalsConfig.vitals.formUuid,
148
- mockVitalsConfig.concepts,
237
+ expect(mockCreateOrUpdateVitalsAndBiometrics).toHaveBeenCalledTimes(1);
238
+ expect(mockCreateOrUpdateVitalsAndBiometrics).toHaveBeenCalledWith(
149
239
  mockPatient.id,
240
+ mockVitalsConfig.vitals.encounterTypeUuid,
241
+ undefined,
242
+ undefined,
243
+ expect.arrayContaining([
244
+ { concept: '5085AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', value: 120 },
245
+ { concept: '5242AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', value: 16 },
246
+ { concept: '5092AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', value: 100 },
247
+ { concept: '5087AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', value: 80 },
248
+ { concept: '5088AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', value: 37 },
249
+ { concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', value: 62 },
250
+ { concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', value: 180 },
251
+ { concept: '1343AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', value: 23 },
252
+ ]),
150
253
  expect.objectContaining({
151
- height: heightValue,
152
- midUpperArmCircumference: muacValue,
153
- oxygenSaturation: oxygenSaturationValue,
154
- pulse: pulseValue,
155
- respiratoryRate: respiratoryRateValue,
156
- systolicBloodPressure: systolicBloodPressureValue,
157
- temperature: temperatureValue,
158
- weight: weightValue,
254
+ signal: {
255
+ aborted: false,
256
+ },
257
+ abort: expect.any(Function),
159
258
  }),
160
- new AbortController(),
161
- undefined,
162
259
  );
163
260
 
164
261
  expect(mockShowSnackbar).toHaveBeenCalledTimes(1);
@@ -172,6 +269,91 @@ describe('VitalsBiometricsForm', () => {
172
269
  );
173
270
  });
174
271
 
272
+ it('correctly initializes the form with existing vitals and biometrics data while in edit mode', async () => {
273
+ setupMockUseEncounterVitalsAndBiometrics();
274
+ render(<VitalsAndBiometricsForm {...testProps} formContext="editing" editEncounterUuid="encounter-uuid" />);
275
+
276
+ expect(screen.getByRole('spinbutton', { name: /height/i })).toHaveValue(170);
277
+ expect(screen.getByRole('spinbutton', { name: /weight/i })).toHaveValue(65);
278
+ expect(screen.getByRole('spinbutton', { name: /systolic/i })).toHaveValue(120);
279
+ expect(screen.getByRole('spinbutton', { name: /diastolic/i })).toHaveValue(80);
280
+ expect(screen.getByRole('spinbutton', { name: /pulse/i })).toHaveValue(75);
281
+ expect(screen.getByRole('spinbutton', { name: /oxygen saturation/i })).toHaveValue(98);
282
+ expect(screen.getByRole('spinbutton', { name: /respiration rate/i })).toHaveValue(16);
283
+ expect(screen.getByRole('spinbutton', { name: /temperature/i })).toHaveValue(36.5);
284
+ expect(screen.getByRole('spinbutton', { name: /muac/i })).toHaveValue(25);
285
+ });
286
+
287
+ it('edits patient vitals and biometrics', async () => {
288
+ const user = userEvent.setup();
289
+ setupMockUseEncounterVitalsAndBiometrics();
290
+
291
+ const response: Partial<FetchResponse> = {
292
+ statusText: 'created',
293
+ status: 201,
294
+ data: [],
295
+ };
296
+
297
+ mockCreateOrUpdateVitalsAndBiometrics.mockResolvedValue(
298
+ response as ReturnType<typeof createOrUpdateVitalsAndBiometrics>,
299
+ );
300
+
301
+ render(<VitalsAndBiometricsForm {...testProps} formContext="editing" editEncounterUuid="encounter-uuid" />);
302
+
303
+ const weightInput = screen.getByRole('spinbutton', { name: /weight/i });
304
+ const systolicInput = screen.getByRole('spinbutton', { name: /systolic/i });
305
+ const pulseInput = screen.getByRole('spinbutton', { name: /pulse/i });
306
+ const temperatureInput = screen.getByRole('spinbutton', { name: /temperature/i });
307
+ const saveButton = screen.getByRole('button', { name: /Save and close/i });
308
+
309
+ // the save button should be disabled until the user makes a change
310
+ expect(saveButton).toBeDisabled();
311
+ await user.clear(weightInput);
312
+ await user.type(weightInput, '70');
313
+ await user.clear(systolicInput);
314
+ await user.type(systolicInput, '130');
315
+ await user.clear(temperatureInput);
316
+ await user.type(temperatureInput, '37.5');
317
+ // delete the pulse value
318
+ await user.clear(pulseInput);
319
+
320
+ expect(saveButton).toBeEnabled();
321
+ await user.click(saveButton);
322
+
323
+ expect(mockCreateOrUpdateVitalsAndBiometrics).toHaveBeenCalledTimes(1);
324
+ expect(mockCreateOrUpdateVitalsAndBiometrics).toHaveBeenCalledWith(
325
+ mockPatient.id,
326
+ mockVitalsConfig.vitals.encounterTypeUuid,
327
+ 'encounter-uuid',
328
+ undefined,
329
+ expect.arrayContaining([
330
+ { concept: '5085AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', value: 130 },
331
+ { concept: '5088AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', value: 37.5 },
332
+ { concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', value: 70 },
333
+ { uuid: '123e4567-e89b-12d3-a456-426614174001', voided: true },
334
+ { uuid: '123e4567-e89b-12d3-a456-426614174003', voided: true },
335
+ { uuid: '123e4567-e89b-12d3-a456-426614174004', voided: true },
336
+ { uuid: '123e4567-e89b-12d3-a456-426614174007', voided: true },
337
+ ]),
338
+ expect.objectContaining({
339
+ signal: {
340
+ aborted: false,
341
+ },
342
+ abort: expect.any(Function),
343
+ }),
344
+ );
345
+
346
+ expect(mockShowSnackbar).toHaveBeenCalledTimes(1);
347
+ expect(mockShowSnackbar).toHaveBeenCalledWith(
348
+ expect.objectContaining({
349
+ isLowContrast: true,
350
+ kind: 'success',
351
+ subtitle: 'They are now visible on the Vitals and Biometrics page',
352
+ title: 'Vitals and Biometrics updated',
353
+ }),
354
+ );
355
+ });
356
+
175
357
  it('renders an error snackbar if there was a problem saving vitals and biometrics', async () => {
176
358
  const user = userEvent.setup();
177
359
 
@@ -183,7 +365,7 @@ describe('VitalsBiometricsForm', () => {
183
365
  },
184
366
  };
185
367
 
186
- mockSavePatientVitals.mockRejectedValueOnce(error);
368
+ mockCreateOrUpdateVitalsAndBiometrics.mockRejectedValueOnce(error);
187
369
 
188
370
  render(<VitalsAndBiometricsForm {...testProps} />);
189
371
 
@@ -214,7 +396,7 @@ describe('VitalsBiometricsForm', () => {
214
396
  isLowContrast: false,
215
397
  kind: 'error',
216
398
  subtitle: 'Some of the values entered are invalid',
217
- title: 'Error saving vitals and biometrics',
399
+ title: 'Error saving Vitals and Biometrics',
218
400
  });
219
401
  });
220
402