@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2716 → 5.4.2-pre.2722
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/805.js +1 -0
- package/dist/805.js.map +1 -0
- package/dist/kenyaemr-esm-patient-clinical-view-app.js +2 -2
- package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +27 -27
- package/dist/main.js +27 -27
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +97 -0
- package/src/contact-list/contact-tracing-history.component.tsx +18 -15
- package/src/maternal-and-child-health/partography/components/pulse-bp-graph.component.tsx +1 -0
- package/src/maternal-and-child-health/partography/components/temperature-graph.component.tsx +218 -0
- package/src/maternal-and-child-health/partography/components/uterine-contractions-graph.component.tsx +209 -0
- package/src/maternal-and-child-health/partography/forms/cervical-contractions-form.component.tsx +211 -0
- package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +354 -0
- package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +321 -0
- package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +275 -0
- package/src/maternal-and-child-health/partography/forms/index.ts +9 -0
- package/src/maternal-and-child-health/partography/forms/membrane-amniotic-fluid-form.component.tsx +330 -0
- package/src/maternal-and-child-health/partography/forms/oxytocin-form.component.tsx +207 -0
- package/src/maternal-and-child-health/partography/forms/pulse-bp-form.component.tsx +174 -0
- package/src/maternal-and-child-health/partography/forms/temperature-form.component.tsx +210 -0
- package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.component.tsx +218 -0
- package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.scss +107 -0
- package/src/maternal-and-child-health/partography/forms/time-picker-with-clock.component.tsx +174 -0
- package/src/maternal-and-child-health/partography/forms/time-picker-with-clock.scss +178 -0
- package/src/maternal-and-child-health/partography/forms/urine-test-form.component.tsx +255 -0
- package/src/maternal-and-child-health/partography/forms/useCervixData.ts +16 -0
- package/src/maternal-and-child-health/partography/graphs/cervical-contractions-graph.component.tsx +266 -0
- package/src/maternal-and-child-health/partography/graphs/cervix-graph.component.tsx +429 -0
- package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph-wrapper.component.tsx +163 -0
- package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph.component.tsx +82 -0
- package/src/maternal-and-child-health/partography/graphs/fetal-heart-rate-graph.component.tsx +359 -0
- package/src/maternal-and-child-health/partography/graphs/index.ts +10 -0
- package/src/maternal-and-child-health/partography/graphs/membrane-amniotic-fluid-graph.component.tsx +266 -0
- package/src/maternal-and-child-health/partography/graphs/oxytocin-graph-wrapper.component.tsx +190 -0
- package/src/maternal-and-child-health/partography/graphs/oxytocin-graph.component.tsx +126 -0
- package/src/maternal-and-child-health/partography/graphs/partograph-graph.component.tsx +266 -0
- package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph-wrapper.component.tsx +298 -0
- package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +267 -0
- package/src/maternal-and-child-health/partography/graphs/temperature-graph.component.tsx +242 -0
- package/src/maternal-and-child-health/partography/graphs/urine-test-graph.component.tsx +246 -0
- package/src/maternal-and-child-health/partography/partograph.component.tsx +2141 -118
- package/src/maternal-and-child-health/partography/partography-dashboard.meta.ts +8 -0
- package/src/maternal-and-child-health/partography/partography-data-form.scss +163 -0
- package/src/maternal-and-child-health/partography/partography.resource.ts +233 -326
- package/src/maternal-and-child-health/partography/partography.scss +1341 -3
- package/src/maternal-and-child-health/partography/resources/blood-pressure.resource.ts +96 -0
- package/src/maternal-and-child-health/partography/resources/cervical-dilation.resource.ts +109 -0
- package/src/maternal-and-child-health/partography/resources/cervix.resource.ts +362 -0
- package/src/maternal-and-child-health/partography/resources/descent-of-head.resource.ts +101 -0
- package/src/maternal-and-child-health/partography/resources/drugs-fluids.resource.ts +88 -0
- package/src/maternal-and-child-health/partography/resources/fetal-heart-rate.resource.ts +122 -0
- package/src/maternal-and-child-health/partography/resources/maternal-pulse.resource.ts +77 -0
- package/src/maternal-and-child-health/partography/resources/membrane-amniotic-fluid.resource.ts +108 -0
- package/src/maternal-and-child-health/partography/resources/oxytocin.resource.ts +159 -0
- package/src/maternal-and-child-health/partography/resources/progress-events.resource.ts +6 -0
- package/src/maternal-and-child-health/partography/resources/pulse-bp-combined.resource.ts +53 -0
- package/src/maternal-and-child-health/partography/resources/temperature.resource.ts +84 -0
- package/src/maternal-and-child-health/partography/resources/uterine-contractions.resource.ts +173 -0
- package/src/maternal-and-child-health/partography/table/temperature-table.component.tsx +99 -0
- package/src/maternal-and-child-health/partography/table/uterine-contractions-table.component.tsx +86 -0
- package/src/maternal-and-child-health/partography/types/index.ts +319 -101
- package/dist/397.js +0 -1
- package/dist/397.js.map +0 -1
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import {
|
|
4
|
+
Tag,
|
|
5
|
+
Button,
|
|
6
|
+
DataTable,
|
|
7
|
+
TableContainer,
|
|
8
|
+
Table,
|
|
9
|
+
TableHead,
|
|
10
|
+
TableRow,
|
|
11
|
+
TableHeader,
|
|
12
|
+
TableBody,
|
|
13
|
+
TableCell,
|
|
14
|
+
Pagination,
|
|
15
|
+
} from '@carbon/react';
|
|
16
|
+
import { Add, ChartColumn, Table as TableIcon } from '@carbon/react/icons';
|
|
17
|
+
import { LineChart } from '@carbon/charts-react';
|
|
18
|
+
import styles from '../partography.scss';
|
|
19
|
+
import { getColorForGraph, generateRange } from '../types';
|
|
20
|
+
import { usePaginationInfo } from '@openmrs/esm-patient-common-lib';
|
|
21
|
+
|
|
22
|
+
enum ScaleTypes {
|
|
23
|
+
LABELS = 'labels',
|
|
24
|
+
LINEAR = 'linear',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface FetalHeartRateGraphProps {
|
|
28
|
+
data?: Array<{
|
|
29
|
+
hour: number;
|
|
30
|
+
value: number;
|
|
31
|
+
group: string;
|
|
32
|
+
time?: string;
|
|
33
|
+
date?: string;
|
|
34
|
+
id?: string;
|
|
35
|
+
}>;
|
|
36
|
+
tableData?: Array<{
|
|
37
|
+
id: string;
|
|
38
|
+
date: string;
|
|
39
|
+
time: string;
|
|
40
|
+
value: string;
|
|
41
|
+
hour: string;
|
|
42
|
+
}>;
|
|
43
|
+
viewMode?: 'graph' | 'table';
|
|
44
|
+
currentPage?: number;
|
|
45
|
+
pageSize?: number;
|
|
46
|
+
totalItems?: number;
|
|
47
|
+
controlSize?: 'sm' | 'md';
|
|
48
|
+
onAddData?: () => void;
|
|
49
|
+
onViewModeChange?: (mode: 'graph' | 'table') => void;
|
|
50
|
+
onPageChange?: (page: number) => void;
|
|
51
|
+
onPageSizeChange?: (size: number) => void;
|
|
52
|
+
isAddButtonDisabled?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const FetalHeartRateGraph: React.FC<FetalHeartRateGraphProps> = ({
|
|
56
|
+
data = [],
|
|
57
|
+
tableData = [],
|
|
58
|
+
viewMode = 'graph',
|
|
59
|
+
currentPage = 1,
|
|
60
|
+
pageSize = 5,
|
|
61
|
+
totalItems = 0,
|
|
62
|
+
controlSize = 'sm',
|
|
63
|
+
onAddData,
|
|
64
|
+
onViewModeChange,
|
|
65
|
+
onPageChange,
|
|
66
|
+
onPageSizeChange,
|
|
67
|
+
isAddButtonDisabled = true,
|
|
68
|
+
}) => {
|
|
69
|
+
const { t } = useTranslation();
|
|
70
|
+
const startIndex = (currentPage - 1) * pageSize;
|
|
71
|
+
const endIndex = startIndex + pageSize;
|
|
72
|
+
const paginatedData = tableData.slice(startIndex, endIndex);
|
|
73
|
+
const { pageSizes: calculatedPageSizes, itemsDisplayed } = usePaginationInfo(
|
|
74
|
+
pageSize,
|
|
75
|
+
Math.ceil(totalItems / pageSize),
|
|
76
|
+
currentPage,
|
|
77
|
+
totalItems,
|
|
78
|
+
);
|
|
79
|
+
const getFetalHeartRateStatus = (value: string): { type: string; text: string; color: string } => {
|
|
80
|
+
const numValue = parseInt(value.replace(' bpm', ''));
|
|
81
|
+
if (numValue < 100) {
|
|
82
|
+
return { type: 'warm-gray', text: 'Low', color: getColorForGraph('gray') };
|
|
83
|
+
} else if (numValue >= 100 && numValue <= 180) {
|
|
84
|
+
return { type: 'green', text: 'Normal', color: getColorForGraph('green') };
|
|
85
|
+
} else {
|
|
86
|
+
return { type: 'red', text: 'High', color: getColorForGraph('red') };
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const tableHeaders = [
|
|
90
|
+
{ key: 'date', header: t('date', 'Date') },
|
|
91
|
+
{ key: 'time', header: t('time', 'Time') },
|
|
92
|
+
{ key: 'hour', header: t('hour', 'Hour') },
|
|
93
|
+
{ key: 'value', header: t('fetalHeartRate', 'Fetal Heart Rate (bpm)') },
|
|
94
|
+
{ key: 'status', header: t('status', 'Status') },
|
|
95
|
+
];
|
|
96
|
+
const getFetalHeartRateColor = (value: number): string => {
|
|
97
|
+
if (value < 100) {
|
|
98
|
+
return getColorForGraph('gray');
|
|
99
|
+
} else if (value >= 100 && value <= 180) {
|
|
100
|
+
return getColorForGraph('green');
|
|
101
|
+
} else {
|
|
102
|
+
return getColorForGraph('red');
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const enhancedChartData = React.useMemo(() => {
|
|
107
|
+
if (data && data.length > 0) {
|
|
108
|
+
return data.map((point) => ({
|
|
109
|
+
...point,
|
|
110
|
+
group: 'Fetal Heart Rate',
|
|
111
|
+
color: getFetalHeartRateColor(point.value),
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return [
|
|
116
|
+
{ hour: 0, value: 140, group: 'Fetal Heart Rate', time: '0', color: getColorForGraph('green') },
|
|
117
|
+
{ hour: 10, value: 140, group: 'Fetal Heart Rate', time: '10', color: getColorForGraph('green') },
|
|
118
|
+
{ hour: 20, value: 140, group: 'Fetal Heart Rate', time: '20', color: getColorForGraph('green') },
|
|
119
|
+
{ hour: 30, value: 140, group: 'Fetal Heart Rate', time: '30', color: getColorForGraph('green') },
|
|
120
|
+
{ hour: 40, value: 140, group: 'Fetal Heart Rate', time: '40', color: getColorForGraph('green') },
|
|
121
|
+
{ hour: 50, value: 140, group: 'Fetal Heart Rate', time: '50', color: getColorForGraph('green') },
|
|
122
|
+
{ hour: 60, value: 140, group: 'Fetal Heart Rate', time: '60', color: getColorForGraph('green') },
|
|
123
|
+
];
|
|
124
|
+
}, [data]);
|
|
125
|
+
|
|
126
|
+
const chartData = enhancedChartData;
|
|
127
|
+
const chartKey = React.useMemo(() => JSON.stringify(enhancedChartData), [enhancedChartData]);
|
|
128
|
+
|
|
129
|
+
const chartOptions = {
|
|
130
|
+
title: '',
|
|
131
|
+
axes: {
|
|
132
|
+
bottom: {
|
|
133
|
+
title: '',
|
|
134
|
+
mapsTo: 'hour',
|
|
135
|
+
scaleType: ScaleTypes.LINEAR,
|
|
136
|
+
domain: [0, 10],
|
|
137
|
+
ticks: {
|
|
138
|
+
values: React.useMemo(() => generateRange(0, 10, 0.5), []),
|
|
139
|
+
formatter: (hour: number) => {
|
|
140
|
+
if (hour === 0) {
|
|
141
|
+
return '0';
|
|
142
|
+
}
|
|
143
|
+
if (hour === 0.5) {
|
|
144
|
+
return '0:30';
|
|
145
|
+
}
|
|
146
|
+
if (hour % 1 === 0) {
|
|
147
|
+
return `${hour}:00`;
|
|
148
|
+
}
|
|
149
|
+
if (hour % 1 === 0.5) {
|
|
150
|
+
return `${Math.floor(hour)}:30`;
|
|
151
|
+
}
|
|
152
|
+
return `${hour}`;
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
left: {
|
|
157
|
+
title: 'Fetal Heart Rate (bpm)',
|
|
158
|
+
mapsTo: 'value',
|
|
159
|
+
scaleType: ScaleTypes.LINEAR,
|
|
160
|
+
domain: [80, 200],
|
|
161
|
+
ticks: {
|
|
162
|
+
values: React.useMemo(() => generateRange(80, 200, 10), []),
|
|
163
|
+
formatter: (value: number) => `${value}`,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
height: '600px',
|
|
168
|
+
curve: 'curveLinear',
|
|
169
|
+
points: {
|
|
170
|
+
enabled: true,
|
|
171
|
+
radius: 6,
|
|
172
|
+
filled: true,
|
|
173
|
+
},
|
|
174
|
+
grid: {
|
|
175
|
+
x: {
|
|
176
|
+
enabled: true,
|
|
177
|
+
},
|
|
178
|
+
y: {
|
|
179
|
+
enabled: true,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
color: {
|
|
183
|
+
scale:
|
|
184
|
+
data && data.length > 0
|
|
185
|
+
? (datapoint: any) => getFetalHeartRateColor(datapoint.value)
|
|
186
|
+
: {
|
|
187
|
+
'Fetal Heart Rate': 'transparent',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
legend: {
|
|
191
|
+
enabled: false,
|
|
192
|
+
},
|
|
193
|
+
theme: 'white',
|
|
194
|
+
toolbar: {
|
|
195
|
+
enabled: false,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<div className={styles.fetalHeartRateSection}>
|
|
201
|
+
<div className={styles.fetalHeartRateContainer}>
|
|
202
|
+
<div className={styles.fetalHeartRateHeader}>
|
|
203
|
+
<div className={styles.fetalHeartRateHeaderLeft}>
|
|
204
|
+
<h3 className={styles.fetalHeartRateTitle}>Fetal Heart Rate</h3>
|
|
205
|
+
<div className={styles.fetalHeartRateControls}>
|
|
206
|
+
<Tag type="green" title="Normal Range">
|
|
207
|
+
Normal (100-180)
|
|
208
|
+
</Tag>
|
|
209
|
+
<Tag type="red" title="Abnormal Range">
|
|
210
|
+
Abnormal (>180)
|
|
211
|
+
</Tag>
|
|
212
|
+
<Tag type="warm-gray" title="Low Range">
|
|
213
|
+
Low (<100)
|
|
214
|
+
</Tag>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
<div className={styles.fetalHeartRateHeaderRight}>
|
|
218
|
+
<div className={styles.fetalHeartRateActions}>
|
|
219
|
+
<div className={styles.viewSwitcher}>
|
|
220
|
+
<Button
|
|
221
|
+
kind={viewMode === 'graph' ? 'primary' : 'secondary'}
|
|
222
|
+
size={controlSize}
|
|
223
|
+
hasIconOnly
|
|
224
|
+
iconDescription={t('graphView', 'Graph View')}
|
|
225
|
+
onClick={() => onViewModeChange?.('graph')}
|
|
226
|
+
className={styles.viewButton}>
|
|
227
|
+
<ChartColumn />
|
|
228
|
+
</Button>
|
|
229
|
+
<Button
|
|
230
|
+
kind={viewMode === 'table' ? 'primary' : 'secondary'}
|
|
231
|
+
size={controlSize}
|
|
232
|
+
hasIconOnly
|
|
233
|
+
iconDescription={t('tableView', 'Table View')}
|
|
234
|
+
onClick={() => onViewModeChange?.('table')}
|
|
235
|
+
className={styles.viewButton}>
|
|
236
|
+
<TableIcon />
|
|
237
|
+
</Button>
|
|
238
|
+
</div>
|
|
239
|
+
<Button
|
|
240
|
+
kind="primary"
|
|
241
|
+
size={controlSize}
|
|
242
|
+
renderIcon={Add}
|
|
243
|
+
iconDescription="Add fetal heart rate data"
|
|
244
|
+
disabled={isAddButtonDisabled}
|
|
245
|
+
onClick={onAddData}
|
|
246
|
+
className={styles.addButton}>
|
|
247
|
+
Add
|
|
248
|
+
</Button>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
{viewMode === 'graph' ? (
|
|
254
|
+
<div className={styles.fetalHeartRateChart}>
|
|
255
|
+
<LineChart data={chartData} options={chartOptions} key={chartKey} />
|
|
256
|
+
</div>
|
|
257
|
+
) : (
|
|
258
|
+
<div className={styles.tableContainer}>
|
|
259
|
+
{paginatedData.length > 0 ? (
|
|
260
|
+
<>
|
|
261
|
+
<DataTable rows={paginatedData} headers={tableHeaders}>
|
|
262
|
+
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
|
|
263
|
+
<TableContainer title="" description="">
|
|
264
|
+
<Table {...getTableProps()} size="sm">
|
|
265
|
+
<TableHead>
|
|
266
|
+
<TableRow>
|
|
267
|
+
{headers.map((header) => (
|
|
268
|
+
<TableHeader {...getHeaderProps({ header })} key={header.key}>
|
|
269
|
+
{header.header}
|
|
270
|
+
</TableHeader>
|
|
271
|
+
))}
|
|
272
|
+
</TableRow>
|
|
273
|
+
</TableHead>
|
|
274
|
+
<TableBody>
|
|
275
|
+
{rows.map((row) => (
|
|
276
|
+
<TableRow {...getRowProps({ row })} key={row.id}>
|
|
277
|
+
{row.cells.map((cell) => (
|
|
278
|
+
<TableCell key={cell.id}>
|
|
279
|
+
{cell.info.header === 'status'
|
|
280
|
+
? (() => {
|
|
281
|
+
const status = getFetalHeartRateStatus(
|
|
282
|
+
row.cells.find((c) => c.info.header === 'value')?.value || '0 bpm',
|
|
283
|
+
);
|
|
284
|
+
return (
|
|
285
|
+
<Tag type={status.type as any} title={`Fetal Heart Rate: ${status.text}`}>
|
|
286
|
+
{status.text}
|
|
287
|
+
</Tag>
|
|
288
|
+
);
|
|
289
|
+
})()
|
|
290
|
+
: cell.info.header === 'value'
|
|
291
|
+
? (() => {
|
|
292
|
+
const numValue = parseInt(cell.value.replace(' bpm', ''));
|
|
293
|
+
|
|
294
|
+
if (numValue < 100) {
|
|
295
|
+
return (
|
|
296
|
+
<span className={`${styles.fetalHeartRateValue} ${styles.low}`}>
|
|
297
|
+
<span className={styles.arrow}>↓</span>
|
|
298
|
+
{cell.value}
|
|
299
|
+
</span>
|
|
300
|
+
);
|
|
301
|
+
} else if (numValue > 180) {
|
|
302
|
+
return (
|
|
303
|
+
<span className={`${styles.fetalHeartRateValue} ${styles.high}`}>
|
|
304
|
+
<span className={styles.arrow}>↑</span>
|
|
305
|
+
{cell.value}
|
|
306
|
+
</span>
|
|
307
|
+
);
|
|
308
|
+
} else {
|
|
309
|
+
return (
|
|
310
|
+
<span className={`${styles.fetalHeartRateValue} ${styles.normal}`}>
|
|
311
|
+
{cell.value}
|
|
312
|
+
</span>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
})()
|
|
316
|
+
: cell.value}
|
|
317
|
+
</TableCell>
|
|
318
|
+
))}
|
|
319
|
+
</TableRow>
|
|
320
|
+
))}
|
|
321
|
+
</TableBody>
|
|
322
|
+
</Table>
|
|
323
|
+
</TableContainer>
|
|
324
|
+
)}
|
|
325
|
+
</DataTable>
|
|
326
|
+
|
|
327
|
+
{totalItems > 0 && (
|
|
328
|
+
<Pagination
|
|
329
|
+
page={currentPage}
|
|
330
|
+
totalItems={totalItems}
|
|
331
|
+
pageSize={pageSize}
|
|
332
|
+
pageSizes={calculatedPageSizes}
|
|
333
|
+
onChange={(event) => {
|
|
334
|
+
onPageChange?.(event.page);
|
|
335
|
+
if (event.pageSize !== pageSize) {
|
|
336
|
+
onPageSizeChange?.(event.pageSize);
|
|
337
|
+
}
|
|
338
|
+
}}
|
|
339
|
+
size={controlSize}
|
|
340
|
+
/>
|
|
341
|
+
)}
|
|
342
|
+
{totalItems > 0 && <div className={styles.paginationInfo}>{itemsDisplayed}</div>}
|
|
343
|
+
</>
|
|
344
|
+
) : (
|
|
345
|
+
<div className={styles.emptyState}>
|
|
346
|
+
<p>{t('noDataAvailable', 'No data available for fetal heart rate')}</p>
|
|
347
|
+
<Button kind="primary" size={controlSize} renderIcon={Add} onClick={onAddData}>
|
|
348
|
+
{t('addFirstDataPoint', 'Add first data point')}
|
|
349
|
+
</Button>
|
|
350
|
+
</div>
|
|
351
|
+
)}
|
|
352
|
+
</div>
|
|
353
|
+
)}
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
);
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
export default FetalHeartRateGraph;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { default as FetalHeartRateGraph } from './fetal-heart-rate-graph.component';
|
|
2
|
+
export { default as MembraneAmnioticFluidGraph } from './membrane-amniotic-fluid-graph.component';
|
|
3
|
+
export { default as CervicalContractionsGraph } from './cervical-contractions-graph.component';
|
|
4
|
+
export { default as PartographGraph } from './partograph-graph.component';
|
|
5
|
+
export { default as CervixGraph } from './cervix-graph.component';
|
|
6
|
+
export { default as OxytocinGraph } from './oxytocin-graph-wrapper.component';
|
|
7
|
+
export { default as DrugsIVFluidsGraph } from './drugs-iv-fluids-graph-wrapper.component';
|
|
8
|
+
export { default as PulseBPGraph } from './pulse-bp-graph-wrapper.component';
|
|
9
|
+
export { default as TemperatureGraph } from './temperature-graph.component';
|
|
10
|
+
export { default as UrineTestGraph } from './urine-test-graph.component';
|
package/src/maternal-and-child-health/partography/graphs/membrane-amniotic-fluid-graph.component.tsx
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
DataTable,
|
|
6
|
+
TableContainer,
|
|
7
|
+
Table,
|
|
8
|
+
TableHead,
|
|
9
|
+
TableRow,
|
|
10
|
+
TableHeader,
|
|
11
|
+
TableBody,
|
|
12
|
+
TableCell,
|
|
13
|
+
Pagination,
|
|
14
|
+
} from '@carbon/react';
|
|
15
|
+
import { Add, ChartColumn, Table as TableIcon } from '@carbon/react/icons';
|
|
16
|
+
import styles from '../partography.scss';
|
|
17
|
+
import { usePaginationInfo } from '@openmrs/esm-patient-common-lib';
|
|
18
|
+
import { AMNIOTIC_FLUID_INITIALS_MAP, AMNIOTIC_FLUID_LABEL_MAP, MOULDING_SYMBOL_MAP } from '../types';
|
|
19
|
+
|
|
20
|
+
interface MembraneAmnioticFluidData {
|
|
21
|
+
timeSlot: string;
|
|
22
|
+
exactTime: string;
|
|
23
|
+
amnioticFluid: string;
|
|
24
|
+
moulding: string;
|
|
25
|
+
date?: string;
|
|
26
|
+
id?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface MembraneAmnioticFluidGraphProps {
|
|
30
|
+
data?: MembraneAmnioticFluidData[];
|
|
31
|
+
tableData?: Array<{
|
|
32
|
+
id: string;
|
|
33
|
+
date: string;
|
|
34
|
+
timeSlot: string;
|
|
35
|
+
exactTime: string;
|
|
36
|
+
amnioticFluid: string;
|
|
37
|
+
moulding: string;
|
|
38
|
+
}>;
|
|
39
|
+
viewMode?: 'graph' | 'table';
|
|
40
|
+
currentPage?: number;
|
|
41
|
+
pageSize?: number;
|
|
42
|
+
totalItems?: number;
|
|
43
|
+
controlSize?: 'sm' | 'md';
|
|
44
|
+
onAddData?: () => void;
|
|
45
|
+
onViewModeChange?: (mode: 'graph' | 'table') => void;
|
|
46
|
+
onPageChange?: (page: number) => void;
|
|
47
|
+
onPageSizeChange?: (size: number) => void;
|
|
48
|
+
isAddButtonDisabled?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const MembraneAmnioticFluidGraph: React.FC<MembraneAmnioticFluidGraphProps> = ({
|
|
52
|
+
data = [],
|
|
53
|
+
tableData = [],
|
|
54
|
+
viewMode = 'graph',
|
|
55
|
+
currentPage = 1,
|
|
56
|
+
pageSize = 5,
|
|
57
|
+
totalItems = 0,
|
|
58
|
+
controlSize = 'sm',
|
|
59
|
+
onAddData,
|
|
60
|
+
onViewModeChange,
|
|
61
|
+
onPageChange,
|
|
62
|
+
onPageSizeChange,
|
|
63
|
+
isAddButtonDisabled = true,
|
|
64
|
+
}) => {
|
|
65
|
+
const { t } = useTranslation();
|
|
66
|
+
|
|
67
|
+
const startIndex = (currentPage - 1) * pageSize;
|
|
68
|
+
const endIndex = startIndex + pageSize;
|
|
69
|
+
const paginatedData = tableData.slice(startIndex, endIndex);
|
|
70
|
+
const { pageSizes: calculatedPageSizes, itemsDisplayed } = usePaginationInfo(
|
|
71
|
+
pageSize,
|
|
72
|
+
Math.ceil(totalItems / pageSize),
|
|
73
|
+
currentPage,
|
|
74
|
+
totalItems,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const tableHeaders = [
|
|
78
|
+
{ key: 'date', header: t('date', 'Date') },
|
|
79
|
+
{ key: 'exactTime', header: t('exactTime', 'Exact Time') },
|
|
80
|
+
{ key: 'amnioticFluid', header: t('amnioticFluid', 'Amniotic Fluid') },
|
|
81
|
+
{ key: 'moulding', header: t('moulding', 'Moulding') },
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const getTimeColumns = () => {
|
|
85
|
+
const emptyColumns = Array.from({ length: 13 }, (_, i) => `grid-${i + 1}`);
|
|
86
|
+
if (data.length === 0) {
|
|
87
|
+
return emptyColumns;
|
|
88
|
+
}
|
|
89
|
+
const dataTimes = data.map((item, idx) => item.exactTime || String(idx));
|
|
90
|
+
if (dataTimes.length <= 13) {
|
|
91
|
+
const remainingEmpty = Array.from({ length: 13 - dataTimes.length }, (_, i) => `empty-${i + 1}`);
|
|
92
|
+
return [...dataTimes, ...remainingEmpty];
|
|
93
|
+
}
|
|
94
|
+
return dataTimes;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const timeColumns = getTimeColumns();
|
|
98
|
+
|
|
99
|
+
const getAmnioticFluidInitials = (value: string): string => {
|
|
100
|
+
return AMNIOTIC_FLUID_INITIALS_MAP[value] || value.charAt(0).toUpperCase();
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const getAmnioticFluidLabel = (value: string): string => AMNIOTIC_FLUID_LABEL_MAP[value] || value;
|
|
104
|
+
|
|
105
|
+
const getMouldingSymbol = (value: string): string => MOULDING_SYMBOL_MAP[value] || value;
|
|
106
|
+
|
|
107
|
+
const createGridData = () => {
|
|
108
|
+
const gridData: Record<string, { amnioticFluid: string; moulding: string }> = {};
|
|
109
|
+
data.forEach((item, idx) => {
|
|
110
|
+
const key = item.exactTime || String(idx);
|
|
111
|
+
gridData[key] = {
|
|
112
|
+
amnioticFluid: getAmnioticFluidInitials(item.amnioticFluid),
|
|
113
|
+
moulding: getMouldingSymbol(item.moulding),
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
return gridData;
|
|
117
|
+
};
|
|
118
|
+
const gridData = createGridData();
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div className={styles.fetalHeartRateSection}>
|
|
122
|
+
<div className={styles.fetalHeartRateContainer}>
|
|
123
|
+
<div className={styles.fetalHeartRateHeader}>
|
|
124
|
+
<div className={styles.fetalHeartRateHeaderLeft}>
|
|
125
|
+
<h3 className={styles.fetalHeartRateTitle}>Membrane Amniotic Fluid & Moulding</h3>
|
|
126
|
+
<div className={styles.fetalHeartRateControls}>
|
|
127
|
+
<span className={styles.legendText}>
|
|
128
|
+
M=Membrane intact, C=Clear, MS=Meconium, A=Absent, B=Blood | 0, +, ++, +++
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
<div className={styles.fetalHeartRateHeaderRight}>
|
|
133
|
+
<div className={styles.fetalHeartRateActions}>
|
|
134
|
+
<div className={styles.viewSwitcher}>
|
|
135
|
+
<Button
|
|
136
|
+
kind={viewMode === 'graph' ? 'primary' : 'secondary'}
|
|
137
|
+
size={controlSize}
|
|
138
|
+
hasIconOnly
|
|
139
|
+
iconDescription={t('graphView', 'Graph View')}
|
|
140
|
+
onClick={() => onViewModeChange?.('graph')}
|
|
141
|
+
className={styles.viewButton}>
|
|
142
|
+
<ChartColumn />
|
|
143
|
+
</Button>
|
|
144
|
+
<Button
|
|
145
|
+
kind={viewMode === 'table' ? 'primary' : 'secondary'}
|
|
146
|
+
size={controlSize}
|
|
147
|
+
hasIconOnly
|
|
148
|
+
iconDescription={t('tableView', 'Table View')}
|
|
149
|
+
onClick={() => onViewModeChange?.('table')}
|
|
150
|
+
className={styles.viewButton}>
|
|
151
|
+
<TableIcon />
|
|
152
|
+
</Button>
|
|
153
|
+
</div>
|
|
154
|
+
<Button
|
|
155
|
+
kind="primary"
|
|
156
|
+
size={controlSize}
|
|
157
|
+
renderIcon={Add}
|
|
158
|
+
iconDescription="Add membrane amniotic fluid data"
|
|
159
|
+
disabled={isAddButtonDisabled}
|
|
160
|
+
onClick={onAddData}
|
|
161
|
+
className={styles.addButton}>
|
|
162
|
+
Add
|
|
163
|
+
</Button>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
{viewMode === 'graph' ? (
|
|
169
|
+
<div className={styles.membraneGrid}>
|
|
170
|
+
<div className={styles.gridContainer}>
|
|
171
|
+
<div className={styles.gridRow}>
|
|
172
|
+
<div className={styles.gridRowLabel}>{t('amnioticFluid', 'Amniotic Fluid')}</div>
|
|
173
|
+
{timeColumns.map((timeColumn) => {
|
|
174
|
+
const cellData = gridData[timeColumn];
|
|
175
|
+
return (
|
|
176
|
+
<div key={`af-${timeColumn}`} className={styles.gridCell}>
|
|
177
|
+
{cellData?.amnioticFluid || ''}
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
})}
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<div className={styles.gridRow}>
|
|
184
|
+
<div className={styles.gridRowLabel}>{t('moulding', 'Moulding')}</div>
|
|
185
|
+
{timeColumns.map((timeColumn) => {
|
|
186
|
+
const cellData = gridData[timeColumn];
|
|
187
|
+
return (
|
|
188
|
+
<div key={`m-${timeColumn}`} className={styles.gridCell}>
|
|
189
|
+
{cellData?.moulding || ''}
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
})}
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
) : (
|
|
197
|
+
<div className={styles.tableContainer}>
|
|
198
|
+
{paginatedData.length > 0 ? (
|
|
199
|
+
<>
|
|
200
|
+
<DataTable
|
|
201
|
+
rows={paginatedData.map((row) => ({
|
|
202
|
+
...row,
|
|
203
|
+
exactTime: row.exactTime,
|
|
204
|
+
amnioticFluid: getAmnioticFluidLabel(row.amnioticFluid),
|
|
205
|
+
moulding: getMouldingSymbol(row.moulding),
|
|
206
|
+
}))}
|
|
207
|
+
headers={tableHeaders}>
|
|
208
|
+
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
|
|
209
|
+
<TableContainer title="" description="">
|
|
210
|
+
<Table {...getTableProps()} size="sm">
|
|
211
|
+
<TableHead>
|
|
212
|
+
<TableRow>
|
|
213
|
+
{headers.map((header) => (
|
|
214
|
+
<TableHeader {...getHeaderProps({ header })} key={header.key}>
|
|
215
|
+
{header.header}
|
|
216
|
+
</TableHeader>
|
|
217
|
+
))}
|
|
218
|
+
</TableRow>
|
|
219
|
+
</TableHead>
|
|
220
|
+
<TableBody>
|
|
221
|
+
{rows.map((row) => (
|
|
222
|
+
<TableRow {...getRowProps({ row })} key={row.id}>
|
|
223
|
+
{row.cells.map((cell) => (
|
|
224
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
225
|
+
))}
|
|
226
|
+
</TableRow>
|
|
227
|
+
))}
|
|
228
|
+
</TableBody>
|
|
229
|
+
</Table>
|
|
230
|
+
</TableContainer>
|
|
231
|
+
)}
|
|
232
|
+
</DataTable>
|
|
233
|
+
|
|
234
|
+
{totalItems > 0 && (
|
|
235
|
+
<Pagination
|
|
236
|
+
page={currentPage}
|
|
237
|
+
totalItems={totalItems}
|
|
238
|
+
pageSize={pageSize}
|
|
239
|
+
pageSizes={calculatedPageSizes}
|
|
240
|
+
onChange={(event) => {
|
|
241
|
+
onPageChange?.(event.page);
|
|
242
|
+
if (event.pageSize !== pageSize) {
|
|
243
|
+
onPageSizeChange?.(event.pageSize);
|
|
244
|
+
}
|
|
245
|
+
}}
|
|
246
|
+
size={controlSize}
|
|
247
|
+
/>
|
|
248
|
+
)}
|
|
249
|
+
{totalItems > 0 && <div className={styles.paginationInfo}>{itemsDisplayed}</div>}
|
|
250
|
+
</>
|
|
251
|
+
) : (
|
|
252
|
+
<div className={styles.emptyState}>
|
|
253
|
+
<p>{t('noDataAvailable', 'No data available for membrane amniotic fluid & moulding')}</p>
|
|
254
|
+
<Button kind="primary" size={controlSize} renderIcon={Add} onClick={onAddData}>
|
|
255
|
+
{t('addFirstDataPoint', 'Add first data point')}
|
|
256
|
+
</Button>
|
|
257
|
+
</div>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export default MembraneAmnioticFluidGraph;
|