@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.
Files changed (66) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/dist/805.js +1 -0
  3. package/dist/805.js.map +1 -0
  4. package/dist/kenyaemr-esm-patient-clinical-view-app.js +2 -2
  5. package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +27 -27
  6. package/dist/main.js +27 -27
  7. package/dist/main.js.map +1 -1
  8. package/dist/routes.json +1 -1
  9. package/package.json +1 -1
  10. package/src/config-schema.ts +97 -0
  11. package/src/contact-list/contact-tracing-history.component.tsx +18 -15
  12. package/src/maternal-and-child-health/partography/components/pulse-bp-graph.component.tsx +1 -0
  13. package/src/maternal-and-child-health/partography/components/temperature-graph.component.tsx +218 -0
  14. package/src/maternal-and-child-health/partography/components/uterine-contractions-graph.component.tsx +209 -0
  15. package/src/maternal-and-child-health/partography/forms/cervical-contractions-form.component.tsx +211 -0
  16. package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +354 -0
  17. package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +321 -0
  18. package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +275 -0
  19. package/src/maternal-and-child-health/partography/forms/index.ts +9 -0
  20. package/src/maternal-and-child-health/partography/forms/membrane-amniotic-fluid-form.component.tsx +330 -0
  21. package/src/maternal-and-child-health/partography/forms/oxytocin-form.component.tsx +207 -0
  22. package/src/maternal-and-child-health/partography/forms/pulse-bp-form.component.tsx +174 -0
  23. package/src/maternal-and-child-health/partography/forms/temperature-form.component.tsx +210 -0
  24. package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.component.tsx +218 -0
  25. package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.scss +107 -0
  26. package/src/maternal-and-child-health/partography/forms/time-picker-with-clock.component.tsx +174 -0
  27. package/src/maternal-and-child-health/partography/forms/time-picker-with-clock.scss +178 -0
  28. package/src/maternal-and-child-health/partography/forms/urine-test-form.component.tsx +255 -0
  29. package/src/maternal-and-child-health/partography/forms/useCervixData.ts +16 -0
  30. package/src/maternal-and-child-health/partography/graphs/cervical-contractions-graph.component.tsx +266 -0
  31. package/src/maternal-and-child-health/partography/graphs/cervix-graph.component.tsx +429 -0
  32. package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph-wrapper.component.tsx +163 -0
  33. package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph.component.tsx +82 -0
  34. package/src/maternal-and-child-health/partography/graphs/fetal-heart-rate-graph.component.tsx +359 -0
  35. package/src/maternal-and-child-health/partography/graphs/index.ts +10 -0
  36. package/src/maternal-and-child-health/partography/graphs/membrane-amniotic-fluid-graph.component.tsx +266 -0
  37. package/src/maternal-and-child-health/partography/graphs/oxytocin-graph-wrapper.component.tsx +190 -0
  38. package/src/maternal-and-child-health/partography/graphs/oxytocin-graph.component.tsx +126 -0
  39. package/src/maternal-and-child-health/partography/graphs/partograph-graph.component.tsx +266 -0
  40. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph-wrapper.component.tsx +298 -0
  41. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +267 -0
  42. package/src/maternal-and-child-health/partography/graphs/temperature-graph.component.tsx +242 -0
  43. package/src/maternal-and-child-health/partography/graphs/urine-test-graph.component.tsx +246 -0
  44. package/src/maternal-and-child-health/partography/partograph.component.tsx +2141 -118
  45. package/src/maternal-and-child-health/partography/partography-dashboard.meta.ts +8 -0
  46. package/src/maternal-and-child-health/partography/partography-data-form.scss +163 -0
  47. package/src/maternal-and-child-health/partography/partography.resource.ts +233 -326
  48. package/src/maternal-and-child-health/partography/partography.scss +1341 -3
  49. package/src/maternal-and-child-health/partography/resources/blood-pressure.resource.ts +96 -0
  50. package/src/maternal-and-child-health/partography/resources/cervical-dilation.resource.ts +109 -0
  51. package/src/maternal-and-child-health/partography/resources/cervix.resource.ts +362 -0
  52. package/src/maternal-and-child-health/partography/resources/descent-of-head.resource.ts +101 -0
  53. package/src/maternal-and-child-health/partography/resources/drugs-fluids.resource.ts +88 -0
  54. package/src/maternal-and-child-health/partography/resources/fetal-heart-rate.resource.ts +122 -0
  55. package/src/maternal-and-child-health/partography/resources/maternal-pulse.resource.ts +77 -0
  56. package/src/maternal-and-child-health/partography/resources/membrane-amniotic-fluid.resource.ts +108 -0
  57. package/src/maternal-and-child-health/partography/resources/oxytocin.resource.ts +159 -0
  58. package/src/maternal-and-child-health/partography/resources/progress-events.resource.ts +6 -0
  59. package/src/maternal-and-child-health/partography/resources/pulse-bp-combined.resource.ts +53 -0
  60. package/src/maternal-and-child-health/partography/resources/temperature.resource.ts +84 -0
  61. package/src/maternal-and-child-health/partography/resources/uterine-contractions.resource.ts +173 -0
  62. package/src/maternal-and-child-health/partography/table/temperature-table.component.tsx +99 -0
  63. package/src/maternal-and-child-health/partography/table/uterine-contractions-table.component.tsx +86 -0
  64. package/src/maternal-and-child-health/partography/types/index.ts +319 -101
  65. package/dist/397.js +0 -1
  66. package/dist/397.js.map +0 -1
@@ -0,0 +1,298 @@
1
+ import React, { useState } 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
+ Tag,
14
+ Pagination,
15
+ } from '@carbon/react';
16
+ import { Add, ChartColumn, Table as TableIcon } from '@carbon/react/icons';
17
+ import PulseBPGraph, { PulseBPData } from './pulse-bp-graph.component';
18
+ import PulseBPForm from '../forms/pulse-bp-form.component';
19
+ import styles from '../partography.scss';
20
+ import { usePaginationInfo } from '@openmrs/esm-patient-common-lib';
21
+
22
+ interface PulseBPGraphWrapperProps {
23
+ data: PulseBPData[];
24
+ tableData?: Array<{
25
+ pulse: number;
26
+ systolicBP: number;
27
+ diastolicBP: number;
28
+ date?: string;
29
+ time?: string;
30
+ timestamp?: Date;
31
+ }>;
32
+ viewMode?: 'graph' | 'table';
33
+ currentPage?: number;
34
+ pageSize?: number;
35
+ totalItems?: number;
36
+ controlSize?: 'sm' | 'md' | 'lg';
37
+ onAddData?: () => void;
38
+ onViewModeChange?: (mode: 'graph' | 'table') => void;
39
+ onPageChange?: (page: number) => void;
40
+ onPageSizeChange?: (pageSize: number) => void;
41
+ isAddButtonDisabled?: boolean;
42
+ patient?: {
43
+ uuid: string;
44
+ name: string;
45
+ gender: string;
46
+ age: string;
47
+ };
48
+ onPulseBPSubmit?: (data: { pulse: number; systolicBP: number; diastolicBP: number }) => void;
49
+ }
50
+
51
+ const PulseBPGraphWrapper: React.FC<PulseBPGraphWrapperProps> = ({
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 = false,
64
+ patient,
65
+ onPulseBPSubmit,
66
+ }) => {
67
+ const { t } = useTranslation();
68
+ const [isFormOpen, setIsFormOpen] = useState(false);
69
+
70
+ const formatDateTime = (item: any, index: number): string => {
71
+ if (item.timestamp) {
72
+ return (
73
+ item.timestamp.toLocaleDateString() +
74
+ ' ' +
75
+ item.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
76
+ );
77
+ }
78
+
79
+ if (item.date && item.time) {
80
+ return `${item.date} ${item.time}`;
81
+ }
82
+
83
+ if (item.date) {
84
+ return item.date;
85
+ }
86
+
87
+ if (item.time) {
88
+ return item.time;
89
+ }
90
+
91
+ const now = new Date();
92
+ return now.toLocaleDateString() + ' ' + now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
93
+ };
94
+
95
+ const handleAddData = () => {
96
+ setIsFormOpen(true);
97
+ onAddData?.();
98
+ };
99
+
100
+ const handleFormSubmit = (formData: { pulse: number; systolicBP: number; diastolicBP: number }) => {
101
+ onPulseBPSubmit?.(formData);
102
+ setIsFormOpen(false);
103
+ };
104
+
105
+ const handleFormClose = () => {
106
+ setIsFormOpen(false);
107
+ };
108
+
109
+ const getPulseStatus = (pulse: number): { type: string; text: string } => {
110
+ if (isNaN(pulse)) {
111
+ return { type: 'gray', text: 'Invalid' };
112
+ }
113
+
114
+ if (pulse < 60) {
115
+ return { type: 'warm-gray', text: 'Low' };
116
+ } else if (pulse >= 60 && pulse <= 100) {
117
+ return { type: 'green', text: 'Normal' };
118
+ } else {
119
+ return { type: 'red', text: 'Abnormal' };
120
+ }
121
+ };
122
+
123
+ const getBPStatus = (systolic: number, diastolic: number): { type: string; text: string } => {
124
+ if (isNaN(systolic) || isNaN(diastolic)) {
125
+ return { type: 'gray', text: 'Invalid' };
126
+ }
127
+
128
+ if (systolic > 149 || diastolic > 80) {
129
+ return { type: 'red', text: 'High' };
130
+ } else if (systolic <= 90 || diastolic <= 60) {
131
+ return { type: 'warm-gray', text: 'Low' };
132
+ } else {
133
+ return { type: 'green', text: 'Normal' };
134
+ }
135
+ };
136
+
137
+ const [internalPage, setInternalPage] = useState(currentPage);
138
+ const [internalPageSize, setInternalPageSize] = useState(pageSize);
139
+ React.useEffect(() => {
140
+ setInternalPage(currentPage);
141
+ }, [currentPage]);
142
+ React.useEffect(() => {
143
+ setInternalPageSize(pageSize);
144
+ }, [pageSize]);
145
+
146
+ const { pageSizes: calculatedPageSizes, itemsDisplayed } = usePaginationInfo(
147
+ internalPageSize,
148
+ Math.ceil((totalItems || 0) / internalPageSize),
149
+ internalPage,
150
+ totalItems || 0,
151
+ );
152
+
153
+ const paginatedTableData = tableData.slice((internalPage - 1) * internalPageSize, internalPage * internalPageSize);
154
+
155
+ const headers = [
156
+ { key: 'dateTime', header: t('dateTime', 'Date & Time') },
157
+ { key: 'pulse', header: t('pulse', 'Pulse (bpm)') },
158
+ { key: 'bloodPressure', header: t('bloodPressure', 'Blood Pressure (mmHg)') },
159
+ { key: 'pulseStatus', header: t('pulseStatus', 'Pulse Status') },
160
+ { key: 'bpStatus', header: t('bpStatus', 'BP Status') },
161
+ ];
162
+
163
+ const rows = paginatedTableData.map((item, index) => {
164
+ const pulseStatus = getPulseStatus(item.pulse);
165
+ const bpStatus = getBPStatus(item.systolicBP, item.diastolicBP);
166
+ return {
167
+ id: `${internalPage}-${index}`,
168
+ dateTime: formatDateTime(item, index),
169
+ pulse: item.pulse,
170
+ bloodPressure: `${item.systolicBP}/${item.diastolicBP}`,
171
+ pulseStatus: <Tag type={pulseStatus.type as any}>{pulseStatus.text}</Tag>,
172
+ bpStatus: <Tag type={bpStatus.type as any}>{bpStatus.text}</Tag>,
173
+ };
174
+ });
175
+
176
+ return (
177
+ <div className={styles.fetalHeartRateSection}>
178
+ <div className={styles.fetalHeartRateContainer}>
179
+ <div className={styles.fetalHeartRateHeader}>
180
+ <div className={styles.fetalHeartRateTitle}>
181
+ <h3 className={styles.fetalHeartRateHeading}>{t('pulseAndBP', 'Pulse & BP')}</h3>
182
+ <div className={styles.fetalHeartRateControls}>
183
+ <Tag type="warm-gray" title="Low Pulse">
184
+ Low(&lt;60)
185
+ </Tag>
186
+ <Tag type="green" title="Normal Pulse">
187
+ Normal(60-100)
188
+ </Tag>
189
+ <Tag type="red" title="Abnormal Pulse">
190
+ Abnormal(&gt;100)
191
+ </Tag>
192
+ <Tag type="warm-gray" title="Low BP">
193
+ Low(S≤90 or D≤60)
194
+ </Tag>
195
+ <Tag type="green" title="Normal BP">
196
+ Normal(90&lt;S&lt;149 and 60&lt;D&lt;80)
197
+ </Tag>
198
+ <Tag type="red" title="High BP">
199
+ High(S≥149 or D≥80)
200
+ </Tag>
201
+ </div>
202
+ </div>
203
+ <div className={styles.fetalHeartRateControls}>
204
+ <div className={styles.viewToggle}>
205
+ <Button
206
+ kind={viewMode === 'graph' ? 'primary' : 'secondary'}
207
+ size={controlSize}
208
+ hasIconOnly
209
+ iconDescription={t('graphView', 'Graph View')}
210
+ onClick={() => onViewModeChange?.('graph')}
211
+ className={styles.viewButton}>
212
+ <ChartColumn />
213
+ </Button>
214
+ <Button
215
+ kind={viewMode === 'table' ? 'primary' : 'secondary'}
216
+ size={controlSize}
217
+ hasIconOnly
218
+ iconDescription={t('tableView', 'Table View')}
219
+ onClick={() => onViewModeChange?.('table')}
220
+ className={styles.viewButton}>
221
+ <TableIcon />
222
+ </Button>
223
+ </div>
224
+ <Button
225
+ kind="primary"
226
+ size={controlSize}
227
+ renderIcon={Add}
228
+ iconDescription="Add pulse and BP data"
229
+ disabled={isAddButtonDisabled}
230
+ onClick={handleAddData}
231
+ className={styles.addButton}>
232
+ Add
233
+ </Button>
234
+ </div>
235
+ </div>
236
+
237
+ {viewMode === 'graph' ? (
238
+ <PulseBPGraph data={data} />
239
+ ) : (
240
+ <div className={styles.tableContainer}>
241
+ {tableData && tableData.length > 0 ? (
242
+ <>
243
+ <TableContainer title="">
244
+ <DataTable rows={rows} headers={headers} isSortable={false}>
245
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
246
+ <Table {...getTableProps()} size={controlSize} className={styles.dataTable}>
247
+ <TableHead>
248
+ <TableRow>
249
+ {headers.map((header) => (
250
+ <TableHeader {...getHeaderProps({ header })} key={header.key}>
251
+ {header.header}
252
+ </TableHeader>
253
+ ))}
254
+ </TableRow>
255
+ </TableHead>
256
+ <TableBody>
257
+ {rows.map((row) => (
258
+ <TableRow {...getRowProps({ row })} key={row.id}>
259
+ {row.cells.map((cell) => (
260
+ <TableCell key={cell.id}>{cell.value}</TableCell>
261
+ ))}
262
+ </TableRow>
263
+ ))}
264
+ </TableBody>
265
+ </Table>
266
+ )}
267
+ </DataTable>
268
+ </TableContainer>
269
+ <Pagination
270
+ page={internalPage}
271
+ pageSize={internalPageSize}
272
+ pageSizes={calculatedPageSizes}
273
+ totalItems={totalItems}
274
+ onChange={({ page, pageSize }) => {
275
+ setInternalPage(page);
276
+ setInternalPageSize(pageSize);
277
+ onPageChange?.(page);
278
+ onPageSizeChange?.(pageSize);
279
+ }}
280
+ className={styles.pagination}
281
+ />
282
+ {totalItems > 0 && <div className={styles.paginationInfo}>{itemsDisplayed}</div>}
283
+ </>
284
+ ) : (
285
+ <div className={styles.emptyTable}>
286
+ <p>{t('noPulseBPData', 'No pulse and BP data available')}</p>
287
+ </div>
288
+ )}
289
+ </div>
290
+ )}
291
+ </div>
292
+
293
+ <PulseBPForm isOpen={isFormOpen} onClose={handleFormClose} onSubmit={handleFormSubmit} patient={patient} />
294
+ </div>
295
+ );
296
+ };
297
+
298
+ export default PulseBPGraphWrapper;
@@ -0,0 +1,267 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { LineChart } from '@carbon/charts-react';
4
+ import styles from '../partography.scss';
5
+ import { getColorForGraph } from '../types';
6
+
7
+ export interface PulseBPData {
8
+ pulse: number;
9
+ systolicBP: number;
10
+ diastolicBP: number;
11
+ index?: number;
12
+ date?: string;
13
+ time?: string;
14
+ timestamp?: Date;
15
+ }
16
+
17
+ interface ChartDataPoint {
18
+ index: number;
19
+ group: string;
20
+ value: number;
21
+ }
22
+
23
+ interface PulseBPGraphProps {
24
+ data: PulseBPData[];
25
+ }
26
+
27
+ enum ScaleTypes {
28
+ LABELS = 'labels',
29
+ LINEAR = 'linear',
30
+ }
31
+
32
+ const PULSE_BP_CHART_OPTIONS = {
33
+ axes: {
34
+ bottom: {
35
+ title: '',
36
+ mapsTo: 'index',
37
+ scaleType: ScaleTypes.LINEAR,
38
+ domain: [0, 12],
39
+ ticks: {
40
+ values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
41
+ formatter: (index: number) => '',
42
+ },
43
+ },
44
+ left: {
45
+ title: 'Pulse',
46
+ mapsTo: 'value',
47
+ domain: [60, 180],
48
+ ticks: {
49
+ values: [60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180],
50
+ formatter: (value: number) => `${value}`,
51
+ },
52
+ scaleType: ScaleTypes.LINEAR,
53
+ },
54
+ },
55
+ points: {
56
+ enabled: true,
57
+ radius: 4,
58
+ filled: true,
59
+ },
60
+ curve: 'curveLinear',
61
+ height: '400px',
62
+ theme: 'white',
63
+ toolbar: {
64
+ enabled: false,
65
+ },
66
+ legend: {
67
+ position: 'top',
68
+ clickable: false,
69
+ },
70
+ grid: {
71
+ x: {
72
+ enabled: true,
73
+ numberOfTicks: 13,
74
+ },
75
+ y: {
76
+ enabled: true,
77
+ numberOfTicks: 13,
78
+ },
79
+ },
80
+ zoomBar: {
81
+ top: {
82
+ enabled: false,
83
+ },
84
+ },
85
+ };
86
+
87
+ const PulseBPGraph: React.FC<PulseBPGraphProps> = ({ data }) => {
88
+ const { t } = useTranslation();
89
+
90
+ const formatDateTime = (item: PulseBPData, index: number): string => {
91
+ if (item.timestamp) {
92
+ return item.timestamp.toLocaleDateString() + ' ' + item.timestamp.toLocaleTimeString();
93
+ }
94
+ if (item.date && item.time) {
95
+ return `${item.date} ${item.time}`;
96
+ }
97
+ if (item.date) {
98
+ return item.date;
99
+ }
100
+ if (item.time) {
101
+ return item.time;
102
+ }
103
+ const now = new Date();
104
+ return now.toLocaleDateString() + ' ' + now.toLocaleTimeString();
105
+ };
106
+
107
+ const actualData: PulseBPData[] = data.length > 0 ? data : [];
108
+
109
+ const pulseChartData: ChartDataPoint[] = [];
110
+
111
+ if (actualData.length > 0) {
112
+ pulseChartData.push({ index: 0, value: actualData[0].pulse, group: 'Pulse' });
113
+ actualData.forEach((item, index) => {
114
+ pulseChartData.push({
115
+ index: index + 1,
116
+ value: item.pulse,
117
+ group: 'Pulse',
118
+ });
119
+ });
120
+ } else {
121
+ pulseChartData.push({ index: 0, value: 120, group: 'Pulse' });
122
+ }
123
+
124
+ const finalChartData = [...pulseChartData];
125
+
126
+ const chartOptions = {
127
+ ...PULSE_BP_CHART_OPTIONS,
128
+ title: 'Pulse and Blood Pressure Monitoring',
129
+ color: {
130
+ scale: {
131
+ Pulse: actualData.length > 0 ? getColorForGraph('blue') : 'transparent',
132
+ },
133
+ },
134
+ points: {
135
+ enabled: actualData.length > 0,
136
+ radius: 4,
137
+ filled: true,
138
+ },
139
+ };
140
+
141
+ return (
142
+ <div className={styles.pulseBPGraph}>
143
+ <div className={styles.chartContainer} data-chart-id="pulse-bp">
144
+ <div style={{ position: 'relative' }}>
145
+ <LineChart data={finalChartData} options={chartOptions} />
146
+
147
+ {actualData.length > 0 && (
148
+ <svg
149
+ style={{
150
+ position: 'absolute',
151
+ top: 0,
152
+ left: 0,
153
+ width: '100%',
154
+ height: '100%',
155
+ pointerEvents: 'none',
156
+ zIndex: 5,
157
+ }}>
158
+ {actualData.map((item, index) => {
159
+ const chartMarginTop = 20;
160
+ const chartMarginBottom = 10;
161
+ const chartMarginLeft = 12;
162
+ const chartMarginRight = 5;
163
+
164
+ const chartWidth = 100 - chartMarginLeft - chartMarginRight;
165
+ const chartHeight = 100 - chartMarginTop - chartMarginBottom;
166
+
167
+ const dataPointIndex = index + 1;
168
+ const xPosition = chartMarginLeft + (dataPointIndex / 12) * chartWidth;
169
+
170
+ const gridUnit = chartWidth / 12;
171
+ const bpXPosition = chartMarginLeft + ((dataPointIndex - 1) / 12) * chartWidth;
172
+
173
+ const pulseYPosition = chartMarginTop + ((180 - item.pulse) / (180 - 60)) * chartHeight;
174
+ const systolicYPosition = chartMarginTop + ((180 - item.systolicBP) / (180 - 60)) * chartHeight;
175
+ const diastolicYPosition = chartMarginTop + ((180 - item.diastolicBP) / (180 - 60)) * chartHeight;
176
+
177
+ return (
178
+ <g key={index}>
179
+ <defs>
180
+ <marker
181
+ id={`systolic-arrow-${index}`}
182
+ markerWidth="10"
183
+ markerHeight="10"
184
+ refX="5"
185
+ refY="5"
186
+ orient="auto"
187
+ markerUnits="strokeWidth">
188
+ <polygon points="0,0 10,5 0,10 3,5" fill={getColorForGraph('red')} />
189
+ </marker>
190
+ <marker
191
+ id={`diastolic-arrow-${index}`}
192
+ markerWidth="10"
193
+ markerHeight="10"
194
+ refX="5"
195
+ refY="5"
196
+ orient="auto"
197
+ markerUnits="strokeWidth">
198
+ <polygon points="0,0 10,5 0,10 3,5" fill={getColorForGraph('green')} />
199
+ </marker>
200
+ </defs>
201
+
202
+ <line
203
+ x1={`${bpXPosition}%`}
204
+ y1={`${pulseYPosition}%`}
205
+ x2={`${bpXPosition}%`}
206
+ y2={`${systolicYPosition}%`}
207
+ stroke={getColorForGraph('red')}
208
+ strokeWidth="2"
209
+ markerEnd={`url(#systolic-arrow-${index})`}
210
+ />
211
+
212
+ <line
213
+ x1={`${bpXPosition}%`}
214
+ y1={`${pulseYPosition}%`}
215
+ x2={`${bpXPosition}%`}
216
+ y2={`${diastolicYPosition}%`}
217
+ stroke={getColorForGraph('green')}
218
+ strokeWidth="2"
219
+ markerEnd={`url(#diastolic-arrow-${index})`}
220
+ />
221
+
222
+ <circle
223
+ cx={`${bpXPosition}%`}
224
+ cy={`${systolicYPosition}%`}
225
+ r="3"
226
+ fill={getColorForGraph('red')}
227
+ opacity="0.8">
228
+ <title>
229
+ {formatDateTime(item, index)} - Systolic BP: {item.systolicBP} mmHg
230
+ </title>
231
+ </circle>
232
+
233
+ <circle
234
+ cx={`${bpXPosition}%`}
235
+ cy={`${diastolicYPosition}%`}
236
+ r="3"
237
+ fill={getColorForGraph('green')}
238
+ opacity="0.8">
239
+ <title>
240
+ {formatDateTime(item, index)} - Diastolic BP: {item.diastolicBP} mmHg
241
+ </title>
242
+ </circle>
243
+
244
+ <circle
245
+ cx={`${bpXPosition}%`}
246
+ cy={`${pulseYPosition}%`}
247
+ r="4"
248
+ fill={getColorForGraph('blue')}
249
+ stroke="#fff"
250
+ strokeWidth="1"
251
+ opacity="0.9">
252
+ <title>
253
+ {formatDateTime(item, index)} - Pulse: {item.pulse} bpm
254
+ </title>
255
+ </circle>
256
+ </g>
257
+ );
258
+ })}
259
+ </svg>
260
+ )}
261
+ </div>
262
+ </div>
263
+ </div>
264
+ );
265
+ };
266
+
267
+ export default PulseBPGraph;