@openmrs/esm-patient-vitals-app 9.2.3-pre.7474 → 9.2.3-pre.7482

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,13 +1,18 @@
1
- import { type PatientVitalsAndBiometrics } from '../common';
1
+ import { type PatientVitalsAndBiometrics, type ObservationInterpretation } from '../common';
2
2
 
3
3
  export interface VitalsTableRow extends PatientVitalsAndBiometrics {
4
4
  id: string;
5
5
  dateRender: string;
6
6
  bloodPressureRender: string;
7
+ bloodPressureRenderInterpretation?: ObservationInterpretation;
7
8
  pulseRender: string | number;
9
+ pulseRenderInterpretation?: ObservationInterpretation;
8
10
  spo2Render: string | number;
11
+ spo2RenderInterpretation?: ObservationInterpretation;
9
12
  temperatureRender: string | number;
13
+ temperatureRenderInterpretation?: ObservationInterpretation;
10
14
  respiratoryRateRender: string | number;
15
+ respiratoryRateRenderInterpretation?: ObservationInterpretation;
11
16
  }
12
17
 
13
18
  export interface VitalsTableHeader {
@@ -15,14 +15,13 @@ import {
15
15
  useLayoutType,
16
16
  } from '@openmrs/esm-framework';
17
17
  import type { ConfigObject } from '../config-schema';
18
- import { useLaunchVitalsAndBiometricsForm } from '../utils';
19
- import { useVitalsAndBiometrics, useVitalsConceptMetadata, withUnit } from '../common';
20
18
  import type { VitalsTableHeader, VitalsTableRow } from './types';
19
+ import { useLaunchVitalsAndBiometricsForm } from '../utils';
20
+ import { useVitalsAndBiometrics, useConceptUnits, withUnit } from '../common';
21
21
  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';
26
25
 
27
26
  interface VitalsOverviewProps {
28
27
  patientUuid: string;
@@ -44,10 +43,9 @@ const VitalsOverview: React.FC<VitalsOverviewProps> = ({ patientUuid, patient, p
44
43
 
45
44
  const { excludePatientIdentifierCodeTypes } = useConfig();
46
45
  const { data: vitals, error, isLoading, isValidating } = useVitalsAndBiometrics(patientUuid);
47
- const { data: conceptUnits } = useVitalsConceptMetadata();
46
+ const { conceptUnits } = useConceptUnits();
48
47
  const showPrintButton = config.vitals.showPrintButton && !chartView;
49
48
 
50
- useEncounterVitalsAndBiometrics('771bbc44-8d45-4ac3-af6e-059814dd7cde');
51
49
  const patientDetails = useMemo(() => {
52
50
  const getGender = (gender: string): string => {
53
51
  switch (gender) {
@@ -89,7 +87,6 @@ const VitalsOverview: React.FC<VitalsOverviewProps> = ({ patientUuid, patient, p
89
87
  key: 'temperatureRender',
90
88
  header: withUnit(t('temperatureAbbreviated', 'Temp'), conceptUnits.get(config.concepts.temperatureUuid) ?? ''),
91
89
  isSortable: true,
92
-
93
90
  sortFunc: (valueA, valueB) =>
94
91
  valueA.temperature && valueB.temperature ? valueA.temperature - valueB.temperature : 0,
95
92
  },
@@ -100,7 +97,6 @@ const VitalsOverview: React.FC<VitalsOverviewProps> = ({ patientUuid, patient, p
100
97
  conceptUnits.get(config.concepts.systolicBloodPressureUuid) ?? '',
101
98
  ),
102
99
  isSortable: true,
103
-
104
100
  sortFunc: (valueA, valueB) =>
105
101
  valueA.systolic && valueB.systolic && valueA.diastolic && valueB.diastolic
106
102
  ? valueA.systolic !== valueB.systolic
@@ -112,7 +108,6 @@ const VitalsOverview: React.FC<VitalsOverviewProps> = ({ patientUuid, patient, p
112
108
  key: 'pulseRender',
113
109
  header: withUnit(t('pulse', 'Pulse'), conceptUnits.get(config.concepts.pulseUuid) ?? ''),
114
110
  isSortable: true,
115
-
116
111
  sortFunc: (valueA, valueB) => (valueA.pulse && valueB.pulse ? valueA.pulse - valueB.pulse : 0),
117
112
  },
118
113
  {
@@ -122,7 +117,6 @@ const VitalsOverview: React.FC<VitalsOverviewProps> = ({ patientUuid, patient, p
122
117
  conceptUnits.get(config.concepts.respiratoryRateUuid) ?? '',
123
118
  ),
124
119
  isSortable: true,
125
-
126
120
  sortFunc: (valueA, valueB) =>
127
121
  valueA.respiratoryRate && valueB.respiratoryRate ? valueA.respiratoryRate - valueB.respiratoryRate : 0,
128
122
  },
@@ -130,7 +124,6 @@ const VitalsOverview: React.FC<VitalsOverviewProps> = ({ patientUuid, patient, p
130
124
  key: 'spo2Render',
131
125
  header: withUnit(t('spo2', 'SpO2'), conceptUnits.get(config.concepts.oxygenSaturationUuid) ?? ''),
132
126
  isSortable: true,
133
-
134
127
  sortFunc: (valueA, valueB) => (valueA.spo2 && valueB.spo2 ? valueA.spo2 - valueB.spo2 : 0),
135
128
  },
136
129
  ];
@@ -142,10 +135,15 @@ const VitalsOverview: React.FC<VitalsOverviewProps> = ({ patientUuid, patient, p
142
135
  ...vitalSigns,
143
136
  dateRender: formatDate(parseDate(vitalSigns.date.toString()), { mode: 'wide', time: true }),
144
137
  bloodPressureRender: `${vitalSigns.systolic ?? '--'} / ${vitalSigns.diastolic ?? '--'}`,
138
+ bloodPressureRenderInterpretation: vitalSigns.bloodPressureRenderInterpretation,
145
139
  pulseRender: vitalSigns.pulse ?? '--',
140
+ pulseRenderInterpretation: vitalSigns.pulseRenderInterpretation,
146
141
  spo2Render: vitalSigns.spo2 ?? '--',
142
+ spo2RenderInterpretation: vitalSigns.spo2RenderInterpretation,
147
143
  temperatureRender: vitalSigns.temperature ?? '--',
144
+ temperatureRenderInterpretation: vitalSigns.temperatureRenderInterpretation,
148
145
  respiratoryRateRender: vitalSigns.respiratoryRate ?? '--',
146
+ respiratoryRateRenderInterpretation: vitalSigns.respiratoryRateRenderInterpretation,
149
147
  };
150
148
  }),
151
149
  [vitals],
@@ -178,8 +176,14 @@ const VitalsOverview: React.FC<VitalsOverviewProps> = ({ patientUuid, patient, p
178
176
  return (
179
177
  <>
180
178
  {(() => {
181
- if (isLoading) return <DataTableSkeleton role="progressbar" compact={!isTablet} zebra />;
182
- if (error) return <ErrorState error={error} headerTitle={headerTitle} />;
179
+ if (isLoading) {
180
+ return <DataTableSkeleton role="progressbar" compact={!isTablet} zebra />;
181
+ }
182
+
183
+ if (error) {
184
+ return <ErrorState error={error} headerTitle={headerTitle} />;
185
+ }
186
+
183
187
  if (vitals?.length) {
184
188
  return (
185
189
  <div className={styles.widgetCard}>
@@ -232,11 +236,11 @@ const VitalsOverview: React.FC<VitalsOverviewProps> = ({ patientUuid, patient, p
232
236
  <PrintComponent subheader={headerTitle} patientDetails={patientDetails} />
233
237
  <PaginatedVitals
234
238
  isPrinting={isPrinting}
235
- tableRows={tableRows}
236
239
  pageSize={pageSize}
237
- urlLabel={urlLabel}
238
240
  pageUrl={pageUrl}
239
241
  tableHeaders={tableHeaders}
242
+ tableRows={tableRows}
243
+ urlLabel={urlLabel}
240
244
  />
241
245
  </div>
242
246
  )}
@@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event';
3
3
  import { screen } from '@testing-library/react';
4
4
  import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
5
5
  import { type ConfigObject, configSchema } from '../config-schema';
6
- import { formattedVitals, mockConceptMetadata, mockConceptUnits, mockVitalsConfig } from '__mocks__';
6
+ import { formattedVitals, mockConceptUnits, mockVitalsConfig } from '__mocks__';
7
7
  import { mockPatient, renderWithSwr, waitForLoadingToFinish } from 'tools';
8
8
  import { useVitalsAndBiometrics } from '../common';
9
9
  import VitalsOverview from './vitals-overview.component';
@@ -40,9 +40,9 @@ jest.mock('../common', () => {
40
40
  return {
41
41
  ...originalModule,
42
42
  launchPatientWorkspace: jest.fn(),
43
- useVitalsConceptMetadata: jest.fn().mockImplementation(() => ({
44
- data: mockConceptUnits,
45
- conceptMetadata: mockConceptMetadata,
43
+ useConceptUnits: jest.fn().mockImplementation(() => ({
44
+ conceptUnits: mockConceptUnits,
45
+ error: null,
46
46
  isLoading: false,
47
47
  })),
48
48
  useVitalsAndBiometrics: jest.fn(),
@@ -14,6 +14,7 @@ import {
14
14
  assessValue,
15
15
  getReferenceRangesForConcept,
16
16
  interpretBloodPressure,
17
+ useConceptUnits,
17
18
  useVitalsAndBiometrics,
18
19
  useVitalsConceptMetadata,
19
20
  } from '../common';
@@ -34,8 +35,9 @@ interface VitalsHeaderProps {
34
35
  const VitalsHeader: React.FC<VitalsHeaderProps> = ({ patientUuid, hideLinks = false }) => {
35
36
  const { t } = useTranslation();
36
37
  const config = useConfig<ConfigObject>();
37
- const { data: conceptUnits, conceptMetadata } = useVitalsConceptMetadata();
38
+ const { conceptUnits } = useConceptUnits();
38
39
  const { data: vitals, isLoading, isValidating } = useVitalsAndBiometrics(patientUuid, 'both');
40
+ const { conceptRanges } = useVitalsConceptMetadata(patientUuid);
39
41
  const latestVitals = vitals?.[0];
40
42
  const [showDetailsPanel, setShowDetailsPanel] = useState(false);
41
43
  const toggleDetailsPanel = () => setShowDetailsPanel(!showDetailsPanel);
@@ -59,7 +61,7 @@ const VitalsHeader: React.FC<VitalsHeaderProps> = ({ patientUuid, hideLinks = fa
59
61
  );
60
62
  }
61
63
 
62
- if (latestVitals && Object.keys(latestVitals)?.length && conceptMetadata?.length) {
64
+ if (latestVitals && Object.keys(latestVitals)?.length && conceptRanges?.length) {
63
65
  const hasActiveVisit = Boolean(currentVisit?.uuid);
64
66
  const vitalsTakenToday = Boolean(dayjs(latestVitals?.date).isToday());
65
67
  const vitalsOverdue = hasActiveVisit && !vitalsTakenToday;
@@ -148,7 +150,7 @@ const VitalsHeader: React.FC<VitalsHeaderProps> = ({ patientUuid, hideLinks = fa
148
150
  latestVitals?.systolic,
149
151
  latestVitals?.diastolic,
150
152
  config?.concepts,
151
- conceptMetadata,
153
+ conceptRanges,
152
154
  )}
153
155
  unitName={t('bp', 'BP')}
154
156
  unitSymbol={(latestVitals?.systolic && conceptUnits.get(config.concepts.systolicBloodPressureUuid)) ?? ''}
@@ -157,7 +159,7 @@ const VitalsHeader: React.FC<VitalsHeaderProps> = ({ patientUuid, hideLinks = fa
157
159
  <VitalsHeaderItem
158
160
  interpretation={assessValue(
159
161
  latestVitals?.pulse,
160
- getReferenceRangesForConcept(config.concepts.pulseUuid, conceptMetadata),
162
+ getReferenceRangesForConcept(config.concepts.pulseUuid, conceptRanges),
161
163
  )}
162
164
  unitName={t('heartRate', 'Heart rate')}
163
165
  unitSymbol={(latestVitals?.pulse && conceptUnits.get(config.concepts.pulseUuid)) ?? ''}
@@ -166,7 +168,7 @@ const VitalsHeader: React.FC<VitalsHeaderProps> = ({ patientUuid, hideLinks = fa
166
168
  <VitalsHeaderItem
167
169
  interpretation={assessValue(
168
170
  latestVitals?.respiratoryRate,
169
- getReferenceRangesForConcept(config.concepts.respiratoryRateUuid, conceptMetadata),
171
+ getReferenceRangesForConcept(config.concepts.respiratoryRateUuid, conceptRanges),
170
172
  )}
171
173
  unitName={t('respiratoryRate', 'R. rate')}
172
174
  unitSymbol={
@@ -177,7 +179,7 @@ const VitalsHeader: React.FC<VitalsHeaderProps> = ({ patientUuid, hideLinks = fa
177
179
  <VitalsHeaderItem
178
180
  interpretation={assessValue(
179
181
  latestVitals?.spo2,
180
- getReferenceRangesForConcept(config.concepts.oxygenSaturationUuid, conceptMetadata),
182
+ getReferenceRangesForConcept(config.concepts.oxygenSaturationUuid, conceptRanges),
181
183
  )}
182
184
  unitName={t('spo2', 'SpO2')}
183
185
  unitSymbol={(latestVitals?.spo2 && conceptUnits.get(config.concepts.oxygenSaturationUuid)) ?? ''}
@@ -186,7 +188,7 @@ const VitalsHeader: React.FC<VitalsHeaderProps> = ({ patientUuid, hideLinks = fa
186
188
  <VitalsHeaderItem
187
189
  interpretation={assessValue(
188
190
  latestVitals?.temperature,
189
- getReferenceRangesForConcept(config.concepts.temperatureUuid, conceptMetadata),
191
+ getReferenceRangesForConcept(config.concepts.temperatureUuid, conceptRanges),
190
192
  )}
191
193
  unitName={t('temperatureAbbreviated', 'Temp')}
192
194
  unitSymbol={(latestVitals?.temperature && conceptUnits.get(config.concepts.temperatureUuid)) ?? ''}
@@ -5,10 +5,15 @@ import userEvent from '@testing-library/user-event';
5
5
  import { type WorkspacesInfo, getDefaultsFromConfigSchema, useConfig, useWorkspaces } from '@openmrs/esm-framework';
6
6
  import { launchPatientWorkspace } from '@openmrs/esm-patient-common-lib';
7
7
  import { mockPatient, getByTextWithMarkup, renderWithSwr, waitForLoadingToFinish } from 'tools';
8
- import { mockVitalsConfig, mockCurrentVisit, mockConceptUnits, mockConceptMetadata, formattedVitals } from '__mocks__';
8
+ import {
9
+ formattedVitals,
10
+ mockConceptUnits,
11
+ mockCurrentVisit,
12
+ mockVitalsConceptMetadata,
13
+ mockVitalsConfig,
14
+ } from '__mocks__';
9
15
  import { configSchema, type ConfigObject } from '../config-schema';
10
- import { patientVitalsBiometricsFormWorkspace } from '../constants';
11
- import { invalidateCachedVitalsAndBiometrics, useVitalsAndBiometrics } from '../common';
16
+ import { useVitalsAndBiometrics } from '../common';
12
17
  import VitalsHeader from './vitals-header.component';
13
18
 
14
19
  const testProps = {
@@ -38,16 +43,17 @@ jest.mock('@openmrs/esm-patient-common-lib', () => {
38
43
  };
39
44
  });
40
45
 
41
- jest.mock('../common', () => {
42
- const originalModule = jest.requireActual('../common');
46
+ jest.mock('../common/data.resource', () => {
47
+ const originalModule = jest.requireActual('../common/data.resource');
43
48
 
44
49
  return {
45
50
  ...originalModule,
46
- useVitalsConceptMetadata: jest.fn().mockImplementation(() => ({
47
- data: mockConceptUnits,
48
- conceptMetadata: mockConceptMetadata,
51
+ useConceptUnits: jest.fn().mockImplementation(() => ({
52
+ conceptUnits: mockConceptUnits,
53
+ error: null,
49
54
  isLoading: false,
50
55
  })),
56
+ useVitalsConceptMetadata: jest.fn().mockImplementation(() => mockVitalsConceptMetadata),
51
57
  useVitalsAndBiometrics: jest.fn(),
52
58
  };
53
59
  });
@@ -74,7 +80,20 @@ describe('VitalsHeader', () => {
74
80
 
75
81
  it('renders the most recently recorded values in the vitals header', async () => {
76
82
  mockUseVitalsAndBiometrics.mockReturnValue({
77
- data: formattedVitals,
83
+ data: [
84
+ {
85
+ id: '0',
86
+ date: '2021-05-19T04:26:51.000Z',
87
+ pulse: 76,
88
+ temperature: 37,
89
+ respiratoryRate: 12,
90
+ diastolic: 89,
91
+ systolic: 121,
92
+ bmi: null,
93
+ muac: 23,
94
+ bloodPressureRenderInterpretation: 'normal',
95
+ },
96
+ ],
78
97
  } as ReturnType<typeof useVitalsAndBiometrics>);
79
98
 
80
99
  renderWithSwr(<VitalsHeader {...testProps} />);
@@ -134,8 +153,23 @@ describe('VitalsHeader', () => {
134
153
  });
135
154
 
136
155
  it('does not flag normal values that lie within the provided reference ranges', async () => {
156
+ const normalVitals = [
157
+ {
158
+ id: '0',
159
+ date: '2021-05-19T04:26:51.000Z',
160
+ pulse: 76,
161
+ temperature: 37,
162
+ respiratoryRate: 12,
163
+ diastolic: 75, // Within normal range (60-80)
164
+ systolic: 115, // Within normal range (90-120)
165
+ bmi: null,
166
+ muac: 23,
167
+ bloodPressureRenderInterpretation: 'normal',
168
+ },
169
+ ];
170
+
137
171
  mockUseVitalsAndBiometrics.mockReturnValue({
138
- data: formattedVitals,
172
+ data: normalVitals,
139
173
  } as ReturnType<typeof useVitalsAndBiometrics>);
140
174
 
141
175
  renderWithSwr(<VitalsHeader {...testProps} />);
@@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event';
4
4
  import { type FetchResponse, showSnackbar, useConfig, getDefaultsFromConfigSchema } from '@openmrs/esm-framework';
5
5
  import { createOrUpdateVitalsAndBiometrics, useEncounterVitalsAndBiometrics } from '../common';
6
6
  import { type ConfigObject, configSchema } from '../config-schema';
7
- import { mockConceptMetadata, mockConceptRanges, mockConceptUnits, mockVitalsConfig } from '__mocks__';
7
+ import { mockConceptUnits, mockVitalsConceptMetadata, mockVitalsConfig } from '__mocks__';
8
8
  import { mockPatient } from 'tools';
9
9
  import VitalsAndBiometricsForm from './vitals-biometrics-form.workspace';
10
10
 
@@ -40,16 +40,17 @@ jest.mock('../common', () => ({
40
40
  invalidateCachedVitalsAndBiometrics: jest.fn(),
41
41
  createOrUpdateVitalsAndBiometrics: jest.fn(),
42
42
  useVitalsAndBiometrics: jest.fn(),
43
- useVitalsConceptMetadata: jest.fn().mockImplementation(() => ({
44
- data: mockConceptUnits,
45
- conceptMetadata: mockConceptMetadata,
46
- conceptRanges: mockConceptRanges,
43
+ useConceptUnits: jest.fn().mockImplementation(() => ({
44
+ conceptUnits: mockConceptUnits,
45
+ error: null,
46
+ isLoading: false,
47
47
  })),
48
48
  useEncounterVitalsAndBiometrics: jest.fn().mockImplementation(() => ({
49
49
  isLoading: false,
50
50
  vitalsAndBiometrics: null,
51
51
  mutate: jest.fn(),
52
52
  })),
53
+ useVitalsConceptMetadata: jest.fn().mockImplementation(() => mockVitalsConceptMetadata),
53
54
  }));
54
55
 
55
56
  mockUseConfig.mockReturnValue({
@@ -399,32 +400,4 @@ describe('VitalsBiometricsForm', () => {
399
400
  title: 'Error saving Vitals and Biometrics',
400
401
  });
401
402
  });
402
-
403
- it('Display an inline error notification on submit if value of vitals entered is invalid', async () => {
404
- const user = userEvent.setup();
405
-
406
- render(<VitalsAndBiometricsForm {...testProps} />);
407
-
408
- const systolic = screen.getByRole('spinbutton', { name: /systolic/i });
409
- const pulse = screen.getByRole('spinbutton', { name: /pulse/i });
410
- const oxygenSaturation = screen.getByRole('spinbutton', { name: /oxygen saturation/i });
411
- const temperature = screen.getByRole('spinbutton', { name: /temperature/i });
412
-
413
- await user.type(systolic, '1000');
414
- await user.type(pulse, pulseValue.toString());
415
- await user.type(oxygenSaturation, '200');
416
- await user.type(temperature, temperatureValue.toString());
417
-
418
- const saveButton = screen.getByRole('button', { name: /save and close/i });
419
- await user.click(saveButton);
420
-
421
- expect(screen.getByText(/Some of the values entered are invalid/i)).toBeInTheDocument();
422
-
423
- // close the inline notification --> resubmit --> check for presence of inline notification
424
- const closeInlineNotificationButton = screen.getByTitle(/close notification/i);
425
- await user.click(closeInlineNotificationButton);
426
- expect(screen.queryByText(/some of the values entered are invalid/i)).not.toBeInTheDocument();
427
- await user.click(saveButton);
428
- expect(screen.getByText(/Some of the values entered are invalid/i)).toBeInTheDocument();
429
- });
430
403
  });