@openmrs/esm-patient-vitals-app 9.2.3-pre.7164 → 9.2.3-pre.7171
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 +4 -4
- package/dist/3368.js +1 -1
- package/dist/3368.js.map +1 -1
- package/dist/4716.js +1 -1
- package/dist/4716.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-vitals-app.js.buildmanifest.json +9 -9
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/biometrics/biometrics-base.component.tsx +9 -3
- package/src/biometrics/biometrics-chart.component.tsx +52 -46
- package/src/biometrics/biometrics-chart.scss +3 -45
- package/src/biometrics/biometrics-overview.test.tsx +16 -0
- package/src/biometrics/paginated-biometrics.component.tsx +10 -11
- package/src/vitals/vitals-chart.component.tsx +67 -65
- package/src/vitals/vitals-chart.scss +3 -45
- package/src/vitals/vitals-overview.test.tsx +16 -0
|
@@ -354,7 +354,7 @@
|
|
|
354
354
|
"auxiliaryFiles": [
|
|
355
355
|
"3368.js.map"
|
|
356
356
|
],
|
|
357
|
-
"hash": "
|
|
357
|
+
"hash": "4c804e3f02159034",
|
|
358
358
|
"childrenByOrder": {}
|
|
359
359
|
},
|
|
360
360
|
{
|
|
@@ -591,9 +591,9 @@
|
|
|
591
591
|
"initial": false,
|
|
592
592
|
"entry": false,
|
|
593
593
|
"recorded": false,
|
|
594
|
-
"size":
|
|
594
|
+
"size": 396755,
|
|
595
595
|
"sizes": {
|
|
596
|
-
"javascript":
|
|
596
|
+
"javascript": 396671,
|
|
597
597
|
"consume-shared": 84
|
|
598
598
|
},
|
|
599
599
|
"names": [],
|
|
@@ -607,7 +607,7 @@
|
|
|
607
607
|
"auxiliaryFiles": [
|
|
608
608
|
"4716.js.map"
|
|
609
609
|
],
|
|
610
|
-
"hash": "
|
|
610
|
+
"hash": "7c47964fe1e91ab2",
|
|
611
611
|
"childrenByOrder": {}
|
|
612
612
|
},
|
|
613
613
|
{
|
|
@@ -954,10 +954,10 @@
|
|
|
954
954
|
"initial": true,
|
|
955
955
|
"entry": true,
|
|
956
956
|
"recorded": false,
|
|
957
|
-
"size":
|
|
957
|
+
"size": 3350635,
|
|
958
958
|
"sizes": {
|
|
959
959
|
"consume-shared": 294,
|
|
960
|
-
"javascript":
|
|
960
|
+
"javascript": 3327779,
|
|
961
961
|
"share-init": 378,
|
|
962
962
|
"runtime": 22184
|
|
963
963
|
},
|
|
@@ -974,7 +974,7 @@
|
|
|
974
974
|
"auxiliaryFiles": [
|
|
975
975
|
"main.js.map"
|
|
976
976
|
],
|
|
977
|
-
"hash": "
|
|
977
|
+
"hash": "8819b34bdf75e69a",
|
|
978
978
|
"childrenByOrder": {}
|
|
979
979
|
},
|
|
980
980
|
{
|
|
@@ -1046,7 +1046,7 @@
|
|
|
1046
1046
|
"auxiliaryFiles": [
|
|
1047
1047
|
"8957.js.map"
|
|
1048
1048
|
],
|
|
1049
|
-
"hash": "
|
|
1049
|
+
"hash": "618fc8eefb88755c",
|
|
1050
1050
|
"childrenByOrder": {}
|
|
1051
1051
|
},
|
|
1052
1052
|
{
|
|
@@ -1070,7 +1070,7 @@
|
|
|
1070
1070
|
"auxiliaryFiles": [
|
|
1071
1071
|
"9057.js.map"
|
|
1072
1072
|
],
|
|
1073
|
-
"hash": "
|
|
1073
|
+
"hash": "d607f95afd4e74fc",
|
|
1074
1074
|
"childrenByOrder": {}
|
|
1075
1075
|
},
|
|
1076
1076
|
{
|
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":"vitals-overview-widget","component":"vitalsSummary","slot":"patient-chart-summary-dashboard-slot","meta":{"fullWidth":true},"order":1},{"name":"vitals-details-widget","component":"vitalsMain","slot":"patient-chart-vitals-biometrics-dashboard-slot","meta":{"title":"Vitals","view":"vitals","fullWidth":true},"order":1},{"name":"patient-vitals-info","component":"vitalsHeader","slot":"patient-info-slot"},{"name":"biometrics-overview-widget","component":"biometricsOverview","slot":"patient-chart-summary-dashboard-slot","meta":{"fullWidth":false},"order":2},{"name":"biometrics-details-widget","component":"biometricsDetailedSummary","slot":"patient-chart-vitals-biometrics-dashboard-slot","meta":{"view":"biometrics","title":"Biometrics","fullWidth":true}},{"name":"results-summary-dashboard","component":"vitalsAndBiometricsDashboardLink","slot":"patient-chart-dashboard-slot","order":2,"meta":{"slot":"patient-chart-vitals-biometrics-dashboard-slot","path":"Vitals & Biometrics"}},{"name":"weight-tile","component":"weightTile","slot":"visit-form-header-slot","order":2}],"pages":[],"workspaces":[{"name":"patient-vitals-biometrics-form-workspace","title":"recordVitalsAndBiometrics","component":"vitalsBiometricsFormWorkspace"}],"version":"9.2.3-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.2.0"},"extensions":[{"name":"vitals-overview-widget","component":"vitalsSummary","slot":"patient-chart-summary-dashboard-slot","meta":{"fullWidth":true},"order":1},{"name":"vitals-details-widget","component":"vitalsMain","slot":"patient-chart-vitals-biometrics-dashboard-slot","meta":{"title":"Vitals","view":"vitals","fullWidth":true},"order":1},{"name":"patient-vitals-info","component":"vitalsHeader","slot":"patient-info-slot"},{"name":"biometrics-overview-widget","component":"biometricsOverview","slot":"patient-chart-summary-dashboard-slot","meta":{"fullWidth":false},"order":2},{"name":"biometrics-details-widget","component":"biometricsDetailedSummary","slot":"patient-chart-vitals-biometrics-dashboard-slot","meta":{"view":"biometrics","title":"Biometrics","fullWidth":true}},{"name":"results-summary-dashboard","component":"vitalsAndBiometricsDashboardLink","slot":"patient-chart-dashboard-slot","order":2,"meta":{"slot":"patient-chart-vitals-biometrics-dashboard-slot","path":"Vitals & Biometrics"}},{"name":"weight-tile","component":"weightTile","slot":"visit-form-header-slot","order":2}],"pages":[],"workspaces":[{"name":"patient-vitals-biometrics-form-workspace","title":"recordVitalsAndBiometrics","component":"vitalsBiometricsFormWorkspace"}],"version":"9.2.3-pre.7171"}
|
package/package.json
CHANGED
|
@@ -7,9 +7,9 @@ import { CardHeader, EmptyState, ErrorState, useVisitOrOfflineVisit } from '@ope
|
|
|
7
7
|
import { launchVitalsAndBiometricsForm } from '../utils';
|
|
8
8
|
import { useVitalsConceptMetadata, useVitalsAndBiometrics, withUnit } from '../common';
|
|
9
9
|
import { type ConfigObject } from '../config-schema';
|
|
10
|
+
import type { BiometricsTableHeader, BiometricsTableRow } from './types';
|
|
10
11
|
import BiometricsChart from './biometrics-chart.component';
|
|
11
12
|
import PaginatedBiometrics from './paginated-biometrics.component';
|
|
12
|
-
import type { BiometricsTableHeader, BiometricsTableRow } from './types';
|
|
13
13
|
import styles from './biometrics-base.scss';
|
|
14
14
|
|
|
15
15
|
interface BiometricsBaseProps {
|
|
@@ -86,8 +86,14 @@ const BiometricsBase: React.FC<BiometricsBaseProps> = ({ patientUuid, pageSize,
|
|
|
86
86
|
[biometrics],
|
|
87
87
|
);
|
|
88
88
|
|
|
89
|
-
if (isLoading)
|
|
90
|
-
|
|
89
|
+
if (isLoading) {
|
|
90
|
+
return <DataTableSkeleton role="progressbar" />;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (error) {
|
|
94
|
+
return <ErrorState error={error} headerTitle={headerTitle} />;
|
|
95
|
+
}
|
|
96
|
+
|
|
91
97
|
if (biometrics?.length) {
|
|
92
98
|
return (
|
|
93
99
|
<div className={styles.widgetCard}>
|
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
import React, { useMemo, useState } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { Tab,
|
|
5
|
-
import { LineChart } from '@carbon/charts-react';
|
|
4
|
+
import { Tab, TabListVertical, TabPanel, TabPanels, TabsVertical } from '@carbon/react';
|
|
5
|
+
import { LineChart, ScaleTypes } from '@carbon/charts-react';
|
|
6
6
|
import { formatDate, parseDate } from '@openmrs/esm-framework';
|
|
7
7
|
import { type ConfigObject } from '../config-schema';
|
|
8
8
|
import { type PatientVitalsAndBiometrics } from '../common';
|
|
9
9
|
import styles from './biometrics-chart.scss';
|
|
10
10
|
|
|
11
|
-
enum ScaleTypes {
|
|
12
|
-
LABELS = 'labels',
|
|
13
|
-
LABELS_RATIO = 'labels-ratio',
|
|
14
|
-
LINEAR = 'linear',
|
|
15
|
-
LOG = 'log',
|
|
16
|
-
TIME = 'time',
|
|
17
|
-
}
|
|
18
|
-
|
|
19
11
|
interface BiometricsChartProps {
|
|
20
12
|
conceptUnits: Map<string, string>;
|
|
21
13
|
config: ConfigObject;
|
|
@@ -23,9 +15,9 @@ interface BiometricsChartProps {
|
|
|
23
15
|
}
|
|
24
16
|
|
|
25
17
|
interface BiometricChartData {
|
|
18
|
+
groupName: 'Weight' | 'Height' | 'Body mass index' | string;
|
|
26
19
|
title: string;
|
|
27
20
|
value: number | string;
|
|
28
|
-
groupName: 'Weight' | 'Height' | 'Body mass index' | string;
|
|
29
21
|
}
|
|
30
22
|
|
|
31
23
|
const chartColors = { weight: '#6929c4', height: '#6929c4', bmi: '#6929c4' };
|
|
@@ -39,23 +31,40 @@ const BiometricsChart: React.FC<BiometricsChartProps> = ({ patientBiometrics, co
|
|
|
39
31
|
groupName: 'weight',
|
|
40
32
|
});
|
|
41
33
|
|
|
34
|
+
const biometrics = [
|
|
35
|
+
{
|
|
36
|
+
id: 'weight',
|
|
37
|
+
title: `${t('weight', 'Weight')} (${conceptUnits.get(config.concepts.weightUuid) ?? ''})`,
|
|
38
|
+
value: 'weight',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'height',
|
|
42
|
+
title: `${t('height', 'Height')} (${conceptUnits.get(config.concepts.heightUuid) ?? ''})`,
|
|
43
|
+
value: 'height',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'bmi',
|
|
47
|
+
title: `${t('bmi', 'BMI')} (${bmiUnit})`,
|
|
48
|
+
value: 'bmi',
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
42
52
|
const chartData = useMemo(
|
|
43
53
|
() =>
|
|
44
54
|
patientBiometrics
|
|
45
55
|
.filter((biometrics) => biometrics[selectedBiometrics.value])
|
|
46
|
-
.
|
|
56
|
+
.slice(0, 10)
|
|
47
57
|
.sort((biometricA, biometricB) => new Date(biometricA.date).getTime() - new Date(biometricB.date).getTime())
|
|
48
|
-
.map(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
group: selectedBiometrics.
|
|
52
|
-
key: formatDate(new Date(
|
|
53
|
-
value:
|
|
54
|
-
date:
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
[patientBiometrics, selectedBiometrics.groupName, selectedBiometrics.value],
|
|
58
|
+
.map(
|
|
59
|
+
(biometrics) =>
|
|
60
|
+
biometrics[selectedBiometrics.value] && {
|
|
61
|
+
group: selectedBiometrics.title,
|
|
62
|
+
key: formatDate(new Date(biometrics.date), { mode: 'wide', year: false, time: false }),
|
|
63
|
+
value: biometrics[selectedBiometrics.value],
|
|
64
|
+
date: biometrics.date,
|
|
65
|
+
},
|
|
66
|
+
),
|
|
67
|
+
[patientBiometrics, selectedBiometrics.title, selectedBiometrics.value],
|
|
59
68
|
);
|
|
60
69
|
|
|
61
70
|
const chartOptions = useMemo(() => {
|
|
@@ -78,7 +87,9 @@ const BiometricsChart: React.FC<BiometricsChartProps> = ({ patientBiometrics, co
|
|
|
78
87
|
enabled: false,
|
|
79
88
|
},
|
|
80
89
|
color: {
|
|
81
|
-
scale:
|
|
90
|
+
scale: {
|
|
91
|
+
[selectedBiometrics.title]: '#6929c4',
|
|
92
|
+
},
|
|
82
93
|
},
|
|
83
94
|
tooltip: {
|
|
84
95
|
customHTML: ([{ value, date }]) =>
|
|
@@ -98,40 +109,35 @@ const BiometricsChart: React.FC<BiometricsChartProps> = ({ patientBiometrics, co
|
|
|
98
109
|
<label className={styles.biometricLabel} htmlFor="biometrics-chart-radio-group">
|
|
99
110
|
{t('biometricDisplayed', 'Biometric displayed')}
|
|
100
111
|
</label>
|
|
101
|
-
<
|
|
102
|
-
<
|
|
103
|
-
{
|
|
104
|
-
{
|
|
105
|
-
id: 'weight',
|
|
106
|
-
label: `${t('weight', 'Weight')} (${conceptUnits.get(config.concepts.weightUuid) ?? ''})`,
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
id: 'height',
|
|
110
|
-
label: `${t('height', 'Height')} (${conceptUnits.get(config.concepts.heightUuid) ?? ''})`,
|
|
111
|
-
},
|
|
112
|
-
{ id: 'bmi', label: `${t('bmi', 'BMI')} (${bmiUnit})` },
|
|
113
|
-
].map(({ id, label }) => (
|
|
112
|
+
<TabsVertical>
|
|
113
|
+
<TabListVertical aria-label="Biometrics tabs">
|
|
114
|
+
{biometrics.map(({ id, title, value }) => (
|
|
114
115
|
<Tab
|
|
115
116
|
className={classNames(styles.tab, styles.bodyLong01, {
|
|
116
|
-
[styles.selectedTab]: selectedBiometrics.title ===
|
|
117
|
+
[styles.selectedTab]: selectedBiometrics.title === title,
|
|
117
118
|
})}
|
|
119
|
+
id={`${id}-tab`}
|
|
118
120
|
key={id}
|
|
119
121
|
onClick={() =>
|
|
120
122
|
setSelectedBiometrics({
|
|
121
|
-
title:
|
|
122
|
-
value:
|
|
123
|
+
title: title,
|
|
124
|
+
value: value,
|
|
123
125
|
groupName: id,
|
|
124
126
|
})
|
|
125
127
|
}
|
|
126
128
|
>
|
|
127
|
-
{
|
|
129
|
+
{title}
|
|
128
130
|
</Tab>
|
|
129
131
|
))}
|
|
130
|
-
</
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
</TabListVertical>
|
|
133
|
+
<TabPanels>
|
|
134
|
+
{biometrics.map(({ id }) => (
|
|
135
|
+
<TabPanel key={id}>
|
|
136
|
+
<LineChart data={chartData} options={chartOptions} key={id} />
|
|
137
|
+
</TabPanel>
|
|
138
|
+
))}
|
|
139
|
+
</TabPanels>
|
|
140
|
+
</TabsVertical>
|
|
135
141
|
</div>
|
|
136
142
|
</div>
|
|
137
143
|
);
|
|
@@ -23,47 +23,17 @@
|
|
|
23
23
|
padding-right: layout.$spacing-05;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
.biometricsChartArea {
|
|
27
|
-
flex-grow: 4;
|
|
28
|
-
padding: 0 layout.$spacing-05 layout.$spacing-09;
|
|
29
|
-
|
|
30
|
-
:global(.cds--cc--layout-row) {
|
|
31
|
-
height: 0;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
:global(.layout-child) {
|
|
35
|
-
margin-top: layout.$spacing-03;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
26
|
.biometricLabel {
|
|
40
27
|
@extend .label01;
|
|
41
28
|
margin-bottom: layout.$spacing-05;
|
|
42
29
|
display: inline-block;
|
|
43
30
|
}
|
|
44
31
|
|
|
45
|
-
.verticalTabs {
|
|
46
|
-
margin: layout.$spacing-05 0;
|
|
47
|
-
scroll-behavior: smooth;
|
|
48
|
-
|
|
49
|
-
> ul {
|
|
50
|
-
flex-direction: column !important;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
:global(.cds--tabs--scrollable .cds--tabs--scrollable__nav-item + .cds--tabs--scrollable__nav-item) {
|
|
54
|
-
margin-left: 0;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
:global(.cds--tabs--scrollable .cds--tabs--scrollable__nav-link) {
|
|
58
|
-
border-bottom: 0 !important;
|
|
59
|
-
border-left: layout.$spacing-01 solid $color-gray-30;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
32
|
.tab {
|
|
64
33
|
outline: 0;
|
|
65
34
|
outline-offset: 0;
|
|
66
|
-
min-height: layout.$spacing-07;
|
|
35
|
+
min-height: layout.$spacing-07 !important;
|
|
36
|
+
block-size: layout.$spacing-05 !important;
|
|
67
37
|
|
|
68
38
|
&:active,
|
|
69
39
|
&:focus {
|
|
@@ -71,7 +41,7 @@
|
|
|
71
41
|
}
|
|
72
42
|
|
|
73
43
|
&[aria-selected='true'] {
|
|
74
|
-
|
|
44
|
+
box-shadow: inset 4px 0 0 0 var(--brand-03) !important;
|
|
75
45
|
border-bottom: none;
|
|
76
46
|
font-weight: 600;
|
|
77
47
|
margin-left: 0 !important;
|
|
@@ -83,15 +53,3 @@
|
|
|
83
53
|
margin-left: 0 !important;
|
|
84
54
|
}
|
|
85
55
|
}
|
|
86
|
-
|
|
87
|
-
.tablist {
|
|
88
|
-
:global(.cds--tab--list) {
|
|
89
|
-
flex-direction: column;
|
|
90
|
-
max-height: fit-content;
|
|
91
|
-
overflow-x: visible;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
> button :global(.cds--tabs .cds--tabs__nav-link) {
|
|
95
|
-
border-bottom: none;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -17,6 +17,22 @@ const testProps = {
|
|
|
17
17
|
const mockUseConfig = jest.mocked(useConfig<ConfigObject>);
|
|
18
18
|
const mockUseVitalsAndBiometrics = jest.mocked(useVitalsAndBiometrics);
|
|
19
19
|
|
|
20
|
+
jest.mock('@carbon/charts-react', () => ({
|
|
21
|
+
LineChart: () => <div data-testid="line-chart">Line Chart</div>,
|
|
22
|
+
ScaleTypes: {
|
|
23
|
+
TIME: 'time',
|
|
24
|
+
LINEAR: 'linear',
|
|
25
|
+
LOG: 'log',
|
|
26
|
+
LABELS: 'labels',
|
|
27
|
+
LABELS_RATIO: 'labels-ratio',
|
|
28
|
+
},
|
|
29
|
+
TickRotations: {
|
|
30
|
+
ALWAYS: 'always',
|
|
31
|
+
AUTO: 'auto',
|
|
32
|
+
NEVER: 'never',
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
|
|
20
36
|
jest.mock('../common', () => {
|
|
21
37
|
const originalModule = jest.requireActual('../common');
|
|
22
38
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useMemo, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
DataTable,
|
|
4
|
-
type DataTableRow,
|
|
5
4
|
Table,
|
|
6
5
|
TableCell,
|
|
7
6
|
TableContainer,
|
|
@@ -12,8 +11,8 @@ import {
|
|
|
12
11
|
} from '@carbon/react';
|
|
13
12
|
import { useLayoutType, usePagination } from '@openmrs/esm-framework';
|
|
14
13
|
import { PatientChartPagination } from '@openmrs/esm-patient-common-lib';
|
|
15
|
-
import styles from './paginated-biometrics.scss';
|
|
16
14
|
import type { BiometricsTableHeader, BiometricsTableRow } from './types';
|
|
15
|
+
import styles from './paginated-biometrics.scss';
|
|
17
16
|
|
|
18
17
|
interface PaginatedBiometricsProps {
|
|
19
18
|
tableRows: Array<BiometricsTableRow>;
|
|
@@ -73,14 +72,14 @@ const PaginatedBiometrics: React.FC<PaginatedBiometricsProps> = ({
|
|
|
73
72
|
return (
|
|
74
73
|
<>
|
|
75
74
|
<DataTable
|
|
76
|
-
rows={paginatedBiometrics}
|
|
77
75
|
headers={tableHeaders}
|
|
76
|
+
isSortable
|
|
77
|
+
rows={paginatedBiometrics}
|
|
78
78
|
size={isTablet ? 'lg' : 'sm'}
|
|
79
|
-
useZebraStyles
|
|
80
79
|
sortRow={handleSorting}
|
|
81
|
-
|
|
80
|
+
useZebraStyles
|
|
82
81
|
>
|
|
83
|
-
{({
|
|
82
|
+
{({ getHeaderProps, getTableProps, headers, rows }) => (
|
|
84
83
|
<TableContainer className={styles.tableContainer}>
|
|
85
84
|
<Table aria-label="biometrics" className={styles.table} {...getTableProps()}>
|
|
86
85
|
<TableHead>
|
|
@@ -111,13 +110,13 @@ const PaginatedBiometrics: React.FC<PaginatedBiometricsProps> = ({
|
|
|
111
110
|
)}
|
|
112
111
|
</DataTable>
|
|
113
112
|
<PatientChartPagination
|
|
114
|
-
pageNumber={currentPage}
|
|
115
|
-
totalItems={tableRows.length}
|
|
116
113
|
currentItems={paginatedBiometrics.length}
|
|
117
|
-
pageSize={pageSize}
|
|
118
|
-
onPageNumberChange={({ page }) => goTo(page)}
|
|
119
|
-
dashboardLinkUrl={pageUrl}
|
|
120
114
|
dashboardLinkLabel={urlLabel}
|
|
115
|
+
dashboardLinkUrl={pageUrl}
|
|
116
|
+
onPageNumberChange={({ page }) => goTo(page)}
|
|
117
|
+
pageNumber={currentPage}
|
|
118
|
+
pageSize={pageSize}
|
|
119
|
+
totalItems={tableRows.length}
|
|
121
120
|
/>
|
|
122
121
|
</>
|
|
123
122
|
);
|
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
import React, { useId, useMemo } from 'react';
|
|
1
|
+
import React, { useId, useMemo, useState } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { Tab,
|
|
5
|
-
import { LineChart } from '@carbon/charts-react';
|
|
4
|
+
import { Tab, TabListVertical, TabPanel, TabPanels, TabsVertical } from '@carbon/react';
|
|
5
|
+
import { LineChart, ScaleTypes } from '@carbon/charts-react';
|
|
6
6
|
import { formatDate, parseDate } from '@openmrs/esm-framework';
|
|
7
7
|
import { type ConfigObject } from '../config-schema';
|
|
8
8
|
import { withUnit, type PatientVitalsAndBiometrics } from '../common';
|
|
9
9
|
import styles from './vitals-chart.scss';
|
|
10
10
|
|
|
11
|
-
enum ScaleTypes {
|
|
12
|
-
LABELS = 'labels',
|
|
13
|
-
LABELS_RATIO = 'labels-ratio',
|
|
14
|
-
LINEAR = 'linear',
|
|
15
|
-
LOG = 'log',
|
|
16
|
-
TIME = 'time',
|
|
17
|
-
}
|
|
18
|
-
|
|
19
11
|
interface VitalsChartProps {
|
|
20
12
|
conceptUnits: Map<string, string>;
|
|
21
13
|
config: ConfigObject;
|
|
@@ -30,7 +22,7 @@ interface VitalsChartData {
|
|
|
30
22
|
const VitalsChart: React.FC<VitalsChartProps> = ({ patientVitals, conceptUnits, config }) => {
|
|
31
23
|
const { t } = useTranslation();
|
|
32
24
|
const id = useId();
|
|
33
|
-
const [
|
|
25
|
+
const [selectedVitalsSign, setSelectedVitalsSign] = useState<VitalsChartData>({
|
|
34
26
|
title: `${t('bp', 'BP')} (${conceptUnits.get(config.concepts.systolicBloodPressureUuid)})`,
|
|
35
27
|
value: 'systolic',
|
|
36
28
|
});
|
|
@@ -65,40 +57,39 @@ const VitalsChart: React.FC<VitalsChartProps> = ({ patientVitals, conceptUnits,
|
|
|
65
57
|
|
|
66
58
|
const chartData = useMemo(() => {
|
|
67
59
|
return patientVitals
|
|
68
|
-
.filter((vitals) => vitals[
|
|
69
|
-
.
|
|
60
|
+
.filter((vitals) => vitals[selectedVitalsSign.value])
|
|
61
|
+
.slice(0, 10)
|
|
70
62
|
.sort((vitalA, vitalB) => new Date(vitalA.date).getTime() - new Date(vitalB.date).getTime())
|
|
71
63
|
.map((vitals) => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
group: 'Diastolic blood pressure',
|
|
83
|
-
key: formatDate(parseDate(vitals.date.toString()), { year: false }),
|
|
84
|
-
value: vitals.diastolic,
|
|
85
|
-
date: vitals.date,
|
|
86
|
-
},
|
|
87
|
-
];
|
|
88
|
-
} else {
|
|
89
|
-
return {
|
|
90
|
-
group: selectedVitalSign.title,
|
|
91
|
-
key: formatDate(parseDate(vitals.date.toString()), { year: false }),
|
|
92
|
-
value: vitals[selectedVitalSign.value],
|
|
64
|
+
const formattedDate = formatDate(parseDate(vitals.date.toString()), { year: false });
|
|
65
|
+
|
|
66
|
+
if (['systolic', 'diastolic'].includes(selectedVitalsSign.value)) {
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
group: 'Systolic blood pressure',
|
|
70
|
+
key: formattedDate,
|
|
71
|
+
value: vitals.systolic,
|
|
93
72
|
date: vitals.date,
|
|
94
|
-
}
|
|
95
|
-
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
group: 'Diastolic blood pressure',
|
|
76
|
+
key: formattedDate,
|
|
77
|
+
value: vitals.diastolic,
|
|
78
|
+
date: vitals.date,
|
|
79
|
+
},
|
|
80
|
+
];
|
|
96
81
|
}
|
|
82
|
+
return {
|
|
83
|
+
group: selectedVitalsSign.value,
|
|
84
|
+
key: formattedDate,
|
|
85
|
+
value: vitals[selectedVitalsSign.value],
|
|
86
|
+
date: vitals.date,
|
|
87
|
+
};
|
|
97
88
|
});
|
|
98
|
-
}, [patientVitals,
|
|
89
|
+
}, [patientVitals, selectedVitalsSign]);
|
|
99
90
|
|
|
100
91
|
const chartOptions = {
|
|
101
|
-
title:
|
|
92
|
+
title: selectedVitalsSign.title,
|
|
102
93
|
axes: {
|
|
103
94
|
bottom: {
|
|
104
95
|
title: t('date', 'Date'),
|
|
@@ -107,7 +98,7 @@ const VitalsChart: React.FC<VitalsChartProps> = ({ patientVitals, conceptUnits,
|
|
|
107
98
|
},
|
|
108
99
|
left: {
|
|
109
100
|
mapsTo: 'value',
|
|
110
|
-
title:
|
|
101
|
+
title: selectedVitalsSign.title,
|
|
111
102
|
scaleType: ScaleTypes.LINEAR,
|
|
112
103
|
includeZero: false,
|
|
113
104
|
},
|
|
@@ -117,7 +108,7 @@ const VitalsChart: React.FC<VitalsChartProps> = ({ patientVitals, conceptUnits,
|
|
|
117
108
|
},
|
|
118
109
|
color: {
|
|
119
110
|
scale: {
|
|
120
|
-
[
|
|
111
|
+
[selectedVitalsSign.title]: '#6929c4',
|
|
121
112
|
},
|
|
122
113
|
},
|
|
123
114
|
tooltip: {
|
|
@@ -136,30 +127,41 @@ const VitalsChart: React.FC<VitalsChartProps> = ({ patientVitals, conceptUnits,
|
|
|
136
127
|
<label className={styles.vitalsSignLabel} htmlFor={`${id}-tab`}>
|
|
137
128
|
{t('vitalSignDisplayed', 'Vital sign displayed')}
|
|
138
129
|
</label>
|
|
139
|
-
<
|
|
140
|
-
<
|
|
141
|
-
{vitalSigns.map(({ id, title, value }) =>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
130
|
+
<TabsVertical>
|
|
131
|
+
<TabListVertical aria-label="Vitals tabs">
|
|
132
|
+
{vitalSigns.map(({ id, title, value }) => (
|
|
133
|
+
<Tab
|
|
134
|
+
className={classNames(styles.tab, { [styles.selectedTab]: selectedVitalsSign.title === title })}
|
|
135
|
+
id={`${id}-tab`}
|
|
136
|
+
key={id}
|
|
137
|
+
onClick={() =>
|
|
138
|
+
setSelectedVitalsSign({
|
|
139
|
+
title: title,
|
|
140
|
+
value: value,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
>
|
|
144
|
+
{title}
|
|
145
|
+
</Tab>
|
|
146
|
+
))}
|
|
147
|
+
</TabListVertical>
|
|
148
|
+
<TabPanels>
|
|
149
|
+
{vitalSigns.map(({ id, title, value }) => (
|
|
150
|
+
<TabPanel key={id}>
|
|
151
|
+
<LineChart
|
|
152
|
+
data={chartData
|
|
153
|
+
.flat()
|
|
154
|
+
.filter((data) =>
|
|
155
|
+
value === 'systolic'
|
|
156
|
+
? data.group === 'Systolic blood pressure' || data.group === 'Diastolic blood pressure'
|
|
157
|
+
: data.group === value,
|
|
158
|
+
)}
|
|
159
|
+
options={chartOptions}
|
|
160
|
+
/>
|
|
161
|
+
</TabPanel>
|
|
162
|
+
))}
|
|
163
|
+
</TabPanels>
|
|
164
|
+
</TabsVertical>
|
|
163
165
|
</div>
|
|
164
166
|
</div>
|
|
165
167
|
);
|