@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2823 → 5.4.2-pre.2832

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 (25) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/dist/{791.js → 343.js} +1 -1
  3. package/dist/343.js.map +1 -0
  4. package/dist/kenyaemr-esm-patient-clinical-view-app.js +3 -3
  5. package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +27 -27
  6. package/dist/main.js +17 -17
  7. package/dist/main.js.map +1 -1
  8. package/dist/routes.json +1 -1
  9. package/package.json +1 -1
  10. package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +60 -9
  11. package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +21 -17
  12. package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +70 -6
  13. package/src/maternal-and-child-health/partography/forms/membrane-amniotic-fluid-form.component.tsx +30 -7
  14. package/src/maternal-and-child-health/partography/forms/urine-test-form.component.tsx +63 -6
  15. package/src/maternal-and-child-health/partography/graphs/cervix-graph.component.tsx +100 -46
  16. package/src/maternal-and-child-health/partography/graphs/oxytocin-graph.component.tsx +2 -1
  17. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +231 -133
  18. package/src/maternal-and-child-health/partography/partograph.component.tsx +141 -30
  19. package/src/maternal-and-child-health/partography/partography-data-form.scss +31 -12
  20. package/src/maternal-and-child-health/partography/partography.scss +22 -86
  21. package/src/maternal-and-child-health/partography/resources/cervix.resource.ts +15 -1
  22. package/src/maternal-and-child-health/partography/resources/membrane-amniotic-fluid.resource.ts +170 -1
  23. package/src/maternal-and-child-health/partography/resources/oxytocin.resource.ts +88 -15
  24. package/src/maternal-and-child-health/partography/resources/temperature.resource.ts +138 -1
  25. package/dist/791.js.map +0 -1
@@ -214,54 +214,98 @@ const CervixGraph: React.FC<CervixGraphProps> = ({
214
214
  }
215
215
  }
216
216
  });
217
- const svgCircles = chartContainer.querySelectorAll('svg circle');
218
- svgCircles.forEach((circle) => {
217
+ const svg = chartContainer.querySelector('svg');
218
+ if (svg) {
219
+ svg.querySelectorAll('[data-custom-marker]').forEach((marker) => marker.remove());
220
+ }
221
+
222
+ const circles = chartContainer.querySelectorAll('svg circle');
223
+ let processedCount = 0;
224
+ circles.forEach((circle, index) => {
219
225
  const circleElement = circle as SVGCircleElement;
220
- const parentGroup = circleElement.closest('g');
221
- let isCervicalDilation = false;
222
- if (circleElement.hasAttribute('data-group')) {
223
- isCervicalDilation = circleElement.getAttribute('data-group')?.toLowerCase().includes('cervical dilation');
224
- }
225
- if (!isCervicalDilation && parentGroup) {
226
- const groupLabel = parentGroup.getAttribute('aria-label') || parentGroup.getAttribute('data-name') || '';
227
- if (groupLabel.toLowerCase().includes('cervical dilation')) {
228
- isCervicalDilation = true;
229
- }
230
- }
231
- if (!isCervicalDilation) {
232
- const stroke = (circleElement.getAttribute('stroke') || circleElement.style.stroke || '').toLowerCase();
233
- const fill = (circleElement.getAttribute('fill') || circleElement.style.fill || '').toLowerCase();
234
- isCervicalDilation =
235
- stroke.includes('green') ||
236
- fill.includes('green') ||
237
- stroke.includes('#42be65') ||
238
- fill.includes('#42be65') ||
239
- stroke.includes('#24a148') ||
240
- fill.includes('#24a148') ||
241
- stroke.includes('rgb(36, 161, 72)') ||
242
- fill.includes('rgb(36, 161, 72)') ||
243
- stroke.includes('rgb(66, 190, 101)') ||
244
- fill.includes('rgb(66, 190, 101)');
245
- }
246
- if (isCervicalDilation && parentGroup) {
226
+ const cx = parseFloat(circleElement.getAttribute('cx') || '0');
227
+ const cy = parseFloat(circleElement.getAttribute('cy') || '0');
228
+ const fill = (circleElement.getAttribute('fill') || circleElement.style.fill || '').toLowerCase();
229
+ const stroke = (circleElement.getAttribute('stroke') || circleElement.style.stroke || '').toLowerCase();
230
+ const isGreen =
231
+ fill.includes('green') ||
232
+ stroke.includes('green') ||
233
+ fill.includes('#42be65') ||
234
+ stroke.includes('#42be65') ||
235
+ fill.includes('#24a148') ||
236
+ stroke.includes('#24a148') ||
237
+ fill.includes('#198038') ||
238
+ stroke.includes('#198038') ||
239
+ fill.includes('rgb(66, 190, 101)') ||
240
+ stroke.includes('rgb(66, 190, 101)') ||
241
+ fill.includes('rgb(36, 161, 72)') ||
242
+ stroke.includes('rgb(36, 161, 72)') ||
243
+ fill.includes('rgb(25, 128, 56)') ||
244
+ stroke.includes('rgb(25, 128, 56)') ||
245
+ fill.includes('#008000') ||
246
+ stroke.includes('#008000') ||
247
+ fill.includes('#00ff00') ||
248
+ stroke.includes('#00ff00') ||
249
+ fill.includes('#228b22') ||
250
+ stroke.includes('#228b22') ||
251
+ fill.includes('#32cd32') ||
252
+ stroke.includes('#32cd32') ||
253
+ /^#[0-4][0-9a-f][a-f0-9][0-9a-f][a-f0-9][0-9a-f]/.test(fill) ||
254
+ /^#[0-4][0-9a-f][a-f0-9][0-9a-f][a-f0-9][0-9a-f]/.test(stroke);
255
+
256
+ if (isGreen && svg && cx > 0 && cy > 0) {
257
+ processedCount++;
258
+ circleElement.style.opacity = '0';
247
259
  circleElement.style.display = 'none';
260
+ const size = 8;
261
+ const xColor = '#24a148';
262
+
263
+ const line1 = document.createElementNS(SVG_NAMESPACE, 'line');
264
+ line1.setAttribute('x1', (cx - size).toString());
265
+ line1.setAttribute('y1', (cy - size).toString());
266
+ line1.setAttribute('x2', (cx + size).toString());
267
+ line1.setAttribute('y2', (cy + size).toString());
268
+ line1.setAttribute('stroke', xColor);
269
+ line1.setAttribute('stroke-width', '3');
270
+ line1.setAttribute('stroke-linecap', 'round');
271
+ line1.setAttribute('data-custom-marker', 'cervical-x');
272
+
273
+ const line2 = document.createElementNS(SVG_NAMESPACE, 'line');
274
+ line2.setAttribute('x1', (cx + size).toString());
275
+ line2.setAttribute('y1', (cy - size).toString());
276
+ line2.setAttribute('x2', (cx - size).toString());
277
+ line2.setAttribute('y2', (cy + size).toString());
278
+ line2.setAttribute('stroke', xColor);
279
+ line2.setAttribute('stroke-width', '3');
280
+ line2.setAttribute('stroke-linecap', 'round');
281
+ line2.setAttribute('data-custom-marker', 'cervical-x');
282
+
283
+ svg.appendChild(line1);
284
+ svg.appendChild(line2);
285
+ }
286
+ });
287
+ if (processedCount === 0) {
288
+ circles.forEach((circle, index) => {
289
+ const circleElement = circle as SVGCircleElement;
248
290
  const cx = parseFloat(circleElement.getAttribute('cx') || '0');
249
291
  const cy = parseFloat(circleElement.getAttribute('cy') || '0');
250
- const size = 12;
251
- const xColor = '#8a3ffc';
252
- const svg = circleElement.ownerSVGElement;
253
- if (svg) {
254
- // Prevent duplicate Xs
255
- if (!parentGroup.querySelector('line[data-x-marker]')) {
292
+ if (cy > 200 && cy < 400 && cx > 100 && svg) {
293
+ const isAlreadyHidden = circleElement.style.opacity === '0' || circleElement.style.display === 'none';
294
+
295
+ if (!isAlreadyHidden) {
296
+ circleElement.style.opacity = '0';
297
+ const size = 8;
298
+ const xColor = '#24a148';
299
+
256
300
  const line1 = document.createElementNS(SVG_NAMESPACE, 'line');
257
301
  line1.setAttribute('x1', (cx - size).toString());
258
302
  line1.setAttribute('y1', (cy - size).toString());
259
303
  line1.setAttribute('x2', (cx + size).toString());
260
304
  line1.setAttribute('y2', (cy + size).toString());
261
305
  line1.setAttribute('stroke', xColor);
262
- line1.setAttribute('stroke-width', '5');
306
+ line1.setAttribute('stroke-width', '3');
263
307
  line1.setAttribute('stroke-linecap', 'round');
264
- line1.setAttribute('data-x-marker', 'true');
308
+ line1.setAttribute('data-custom-marker', 'cervical-x-alt');
265
309
 
266
310
  const line2 = document.createElementNS(SVG_NAMESPACE, 'line');
267
311
  line2.setAttribute('x1', (cx + size).toString());
@@ -269,30 +313,40 @@ const CervixGraph: React.FC<CervixGraphProps> = ({
269
313
  line2.setAttribute('x2', (cx - size).toString());
270
314
  line2.setAttribute('y2', (cy + size).toString());
271
315
  line2.setAttribute('stroke', xColor);
272
- line2.setAttribute('stroke-width', '5');
316
+ line2.setAttribute('stroke-width', '3');
273
317
  line2.setAttribute('stroke-linecap', 'round');
274
- line2.setAttribute('data-x-marker', 'true');
318
+ line2.setAttribute('data-custom-marker', 'cervical-x-alt');
275
319
 
276
- parentGroup.appendChild(line1);
277
- parentGroup.appendChild(line2);
320
+ svg.appendChild(line1);
321
+ svg.appendChild(line2);
278
322
  }
279
323
  }
280
- }
281
- });
324
+ });
325
+ }
282
326
  }
283
327
  };
284
328
 
285
- const timer = setTimeout(applyChartStyling, 100);
329
+ // Apply styling with multiple aggressive timing attempts
330
+ const timer1 = setTimeout(applyChartStyling, 50);
331
+ const timer2 = setTimeout(applyChartStyling, 200);
332
+ const timer3 = setTimeout(applyChartStyling, 500);
333
+ const timer4 = setTimeout(applyChartStyling, 1000);
334
+ const timer5 = setTimeout(applyChartStyling, 2000);
335
+
286
336
  const chartContainer = document.querySelector(`[data-chart-id="cervix"]`);
287
337
  if (chartContainer && window.MutationObserver) {
288
338
  observer = new MutationObserver(() => {
289
- applyChartStyling();
339
+ setTimeout(applyChartStyling, 100);
290
340
  });
291
341
  observer.observe(chartContainer, { childList: true, subtree: true });
292
342
  }
293
343
 
294
344
  return () => {
295
- clearTimeout(timer);
345
+ clearTimeout(timer1);
346
+ clearTimeout(timer2);
347
+ clearTimeout(timer3);
348
+ clearTimeout(timer4);
349
+ clearTimeout(timer5);
296
350
  if (observer) {
297
351
  observer.disconnect();
298
352
  }
@@ -19,7 +19,8 @@ const OxytocinGraph: React.FC<OxytocinGraphProps> = ({ data }) => {
19
19
  const emptyColumns = Array.from({ length: 20 }, (_, i) => `grid-${i + 1}`);
20
20
 
21
21
  if (data && data.length > 0) {
22
- const dataSlots = data.map((item, i) => `data-${i + 1}`);
22
+ // Use actual time slots from data instead of generic data-1, data-2
23
+ const dataSlots = data.map((item) => item.timeSlot);
23
24
  const remainingEmpty = Array.from({ length: 20 - data.length }, (_, i) => `empty-${i + 1}`);
24
25
  return [...dataSlots, ...remainingEmpty];
25
26
  }
@@ -1,8 +1,8 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { LineChart } from '@carbon/charts-react';
4
4
  import styles from '../partography.scss';
5
- import { getColorForGraph } from '../types';
5
+ import { getColorForGraph, SVG_NAMESPACE } from '../types';
6
6
 
7
7
  export interface PulseBPData {
8
8
  pulse: number;
@@ -35,9 +35,9 @@ const PULSE_BP_CHART_OPTIONS = {
35
35
  title: '',
36
36
  mapsTo: 'index',
37
37
  scaleType: ScaleTypes.LINEAR,
38
- domain: [0, 12],
38
+ domain: [0, 20],
39
39
  ticks: {
40
- values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
40
+ values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
41
41
  formatter: (index: number) => '',
42
42
  },
43
43
  },
@@ -70,7 +70,7 @@ const PULSE_BP_CHART_OPTIONS = {
70
70
  grid: {
71
71
  x: {
72
72
  enabled: true,
73
- numberOfTicks: 13,
73
+ numberOfTicks: 21,
74
74
  },
75
75
  y: {
76
76
  enabled: true,
@@ -104,7 +104,7 @@ const PulseBPGraph: React.FC<PulseBPGraphProps> = ({ data }) => {
104
104
  return now.toLocaleDateString() + ' ' + now.toLocaleTimeString();
105
105
  };
106
106
 
107
- const actualData: PulseBPData[] = data.length > 0 ? data : [];
107
+ const actualData: PulseBPData[] = React.useMemo(() => (data.length > 0 ? data : []), [data]);
108
108
 
109
109
  const pulseChartData: ChartDataPoint[] = [];
110
110
 
@@ -113,154 +113,252 @@ const PulseBPGraph: React.FC<PulseBPGraphProps> = ({ data }) => {
113
113
  pulseChartData.push({
114
114
  index: index,
115
115
  value: item.pulse,
116
- group: 'Pulse',
116
+ group: 'Pulse-Line',
117
+ });
118
+ });
119
+ actualData.forEach((item, index) => {
120
+ pulseChartData.push({
121
+ index: index,
122
+ value: item.systolicBP,
123
+ group: 'Systolic-Points',
124
+ });
125
+ pulseChartData.push({
126
+ index: index,
127
+ value: item.diastolicBP,
128
+ group: 'Diastolic-Points',
117
129
  });
118
130
  });
119
131
  } else {
120
- pulseChartData.push({ index: 0, value: 120, group: 'Pulse' });
132
+ pulseChartData.push({ index: 0, value: 120, group: 'Pulse-Line' });
121
133
  }
122
134
 
123
135
  const finalChartData = [...pulseChartData];
136
+ const colorScale: { [key: string]: string } = {
137
+ 'Pulse-Line': actualData.length > 0 ? getColorForGraph('blue') : 'transparent',
138
+ 'Systolic-Points': 'transparent',
139
+ 'Diastolic-Points': 'transparent',
140
+ };
124
141
 
125
142
  const chartOptions = {
126
143
  ...PULSE_BP_CHART_OPTIONS,
127
144
  title: 'Pulse and Blood Pressure Monitoring',
128
145
  color: {
129
- scale: {
130
- Pulse: actualData.length > 0 ? getColorForGraph('blue') : 'transparent',
131
- },
146
+ scale: colorScale,
132
147
  },
133
148
  points: {
134
149
  enabled: actualData.length > 0,
135
- radius: 4,
150
+ radius: 5,
136
151
  filled: true,
152
+ strokeWidth: 2,
153
+ },
154
+ legend: {
155
+ position: 'top',
156
+ clickable: false,
157
+ enabled: false,
158
+ },
159
+ tooltip: {
160
+ showTotal: false,
161
+ customHTML: (data: any) => {
162
+ if (data && data.length > 0) {
163
+ const point = data[0];
164
+ // Just return the value - clean and simple
165
+ return `<div style="background: #333; color: white; padding: 8px; border-radius: 4px; font-size: 14px; border: none;">
166
+ ${point.value}
167
+ </div>`;
168
+ }
169
+ return '';
170
+ },
137
171
  },
138
172
  };
139
173
 
174
+ useEffect(() => {
175
+ let observer: MutationObserver | null = null;
176
+ const addXMarker = (svg: SVGElement, cx: number, cy: number) => {
177
+ const size = 3;
178
+ const systolicColor = '#da1e28';
179
+ const line1 = document.createElementNS(SVG_NAMESPACE, 'line');
180
+ line1.setAttribute('x1', (cx - size).toString());
181
+ line1.setAttribute('y1', (cy + size).toString());
182
+ line1.setAttribute('x2', cx.toString());
183
+ line1.setAttribute('y2', (cy - size).toString());
184
+ line1.setAttribute('stroke', systolicColor);
185
+ line1.setAttribute('stroke-width', '3');
186
+ line1.setAttribute('stroke-linecap', 'round');
187
+ line1.setAttribute('data-custom-marker', 'systolic-inverted-v');
188
+
189
+ const line2 = document.createElementNS(SVG_NAMESPACE, 'line');
190
+ line2.setAttribute('x1', cx.toString());
191
+ line2.setAttribute('y1', (cy - size).toString());
192
+ line2.setAttribute('x2', (cx + size).toString());
193
+ line2.setAttribute('y2', (cy + size).toString());
194
+ line2.setAttribute('stroke', systolicColor);
195
+ line2.setAttribute('stroke-width', '3');
196
+ line2.setAttribute('stroke-linecap', 'round');
197
+ line2.setAttribute('data-custom-marker', 'systolic-inverted-v');
198
+
199
+ svg.appendChild(line1);
200
+ svg.appendChild(line2);
201
+ };
202
+
203
+ const addVMarker = (svg: SVGElement, cx: number, cy: number) => {
204
+ const size = 3;
205
+ const vColor = '#24a148';
206
+
207
+ const line1 = document.createElementNS(SVG_NAMESPACE, 'line');
208
+ line1.setAttribute('x1', (cx - size).toString());
209
+ line1.setAttribute('y1', (cy - size).toString());
210
+ line1.setAttribute('x2', cx.toString());
211
+ line1.setAttribute('y2', (cy + size).toString());
212
+ line1.setAttribute('stroke', vColor);
213
+ line1.setAttribute('stroke-width', '3');
214
+ line1.setAttribute('stroke-linecap', 'round');
215
+ line1.setAttribute('data-custom-marker', 'diastolic-v');
216
+
217
+ const line2 = document.createElementNS(SVG_NAMESPACE, 'line');
218
+ line2.setAttribute('x1', cx.toString());
219
+ line2.setAttribute('y1', (cy + size).toString());
220
+ line2.setAttribute('x2', (cx + size).toString());
221
+ line2.setAttribute('y2', (cy - size).toString());
222
+ line2.setAttribute('stroke', vColor);
223
+ line2.setAttribute('stroke-width', '3');
224
+ line2.setAttribute('stroke-linecap', 'round');
225
+ line2.setAttribute('data-custom-marker', 'diastolic-v');
226
+
227
+ svg.appendChild(line1);
228
+ svg.appendChild(line2);
229
+ };
230
+
231
+ const addVerticalConnector = (svg: SVGElement, x: number, y1: number, y2: number, color: string) => {
232
+ const line = document.createElementNS(SVG_NAMESPACE, 'line');
233
+ line.setAttribute('x1', x.toString());
234
+ line.setAttribute('y1', y1.toString());
235
+ line.setAttribute('x2', x.toString());
236
+ line.setAttribute('y2', y2.toString());
237
+ line.setAttribute('stroke', color);
238
+ line.setAttribute('stroke-width', '2');
239
+ line.setAttribute('data-custom-marker', 'vertical-connector');
240
+
241
+ svg.appendChild(line);
242
+ };
243
+
244
+ const addCustomMarkers = () => {
245
+ const chartContainer = document.querySelector(`[data-chart-id="pulse-bp"]`);
246
+ if (chartContainer && actualData.length > 0) {
247
+ const svg = chartContainer.querySelector('svg');
248
+ if (!svg) {
249
+ return;
250
+ }
251
+
252
+ svg.querySelectorAll('[data-custom-marker]').forEach((marker) => marker.remove());
253
+
254
+ const circles = svg.querySelectorAll('circle');
255
+
256
+ circles.forEach((circle, index) => {
257
+ const cx = parseFloat(circle.getAttribute('cx') || '0');
258
+ const cy = parseFloat(circle.getAttribute('cy') || '0');
259
+ const fill = circle.getAttribute('fill') || circle.style.fill || '';
260
+ const stroke = circle.getAttribute('stroke') || circle.style.stroke || '';
261
+ const className = circle.getAttribute('class') || '';
262
+ });
263
+
264
+ const allXPositions = Array.from(circles)
265
+ .map((circle) => {
266
+ return parseFloat(circle.getAttribute('cx') || '0');
267
+ })
268
+ .filter((x) => x > 0)
269
+ .sort((a, b) => a - b);
270
+
271
+ const uniqueXPositions: number[] = [];
272
+ allXPositions.forEach((x) => {
273
+ if (uniqueXPositions.length === 0 || Math.abs(x - uniqueXPositions[uniqueXPositions.length - 1]) > 20) {
274
+ uniqueXPositions.push(x);
275
+ }
276
+ });
277
+
278
+ uniqueXPositions.forEach((xPos, timeIndex) => {
279
+ const circlesAtThisX = Array.from(circles).filter((circle) => {
280
+ const cx = parseFloat(circle.getAttribute('cx') || '0');
281
+ return Math.abs(cx - xPos) < 15;
282
+ });
283
+ if (circlesAtThisX.length === 0) {
284
+ return;
285
+ }
286
+ circlesAtThisX.sort((a, b) => {
287
+ const aY = parseFloat(a.getAttribute('cy') || '0');
288
+ const bY = parseFloat(b.getAttribute('cy') || '0');
289
+ return aY - bY;
290
+ });
291
+
292
+ circlesAtThisX.forEach((circle, idx) => {
293
+ const cx = parseFloat(circle.getAttribute('cx') || '0');
294
+ const cy = parseFloat(circle.getAttribute('cy') || '0');
295
+ });
296
+ let pulseY: number | null = null;
297
+ let systolicY: number | null = null;
298
+ let diastolicY: number | null = null;
299
+
300
+ circlesAtThisX.forEach((circle, circleIndex) => {
301
+ const cx = parseFloat(circle.getAttribute('cx') || '0');
302
+ const cy = parseFloat(circle.getAttribute('cy') || '0');
303
+ if (circlesAtThisX.length >= 3) {
304
+ if (circleIndex === 0) {
305
+ systolicY = cy;
306
+ addXMarker(svg, cx, cy);
307
+ circle.style.opacity = '0';
308
+ } else if (circleIndex === circlesAtThisX.length - 1) {
309
+ diastolicY = cy;
310
+ addVMarker(svg, cx, cy);
311
+ circle.style.opacity = '0';
312
+ } else {
313
+ pulseY = cy;
314
+ }
315
+ } else if (circlesAtThisX.length === 2) {
316
+ if (circleIndex === 0) {
317
+ pulseY = cy;
318
+ } else {
319
+ diastolicY = cy;
320
+ addVMarker(svg, cx, cy);
321
+ circle.style.opacity = '0';
322
+ }
323
+ } else if (circlesAtThisX.length === 1) {
324
+ pulseY = cy;
325
+ }
326
+ });
327
+ const currentX = circlesAtThisX[0] ? parseFloat(circlesAtThisX[0].getAttribute('cx') || '0') : 0;
328
+
329
+ if (pulseY !== null && systolicY !== null) {
330
+ addVerticalConnector(svg, currentX, pulseY, systolicY, '#da1e28'); // Red line to systolic
331
+ }
332
+
333
+ if (pulseY !== null && diastolicY !== null) {
334
+ addVerticalConnector(svg, currentX, pulseY, diastolicY, '#24a148'); // Green line to diastolic
335
+ }
336
+ });
337
+ }
338
+ };
339
+ const timer = setTimeout(addCustomMarkers, 500);
340
+ const timer2 = setTimeout(addCustomMarkers, 1000);
341
+ const chartContainer = document.querySelector(`[data-chart-id="pulse-bp"]`);
342
+ if (chartContainer && window.MutationObserver) {
343
+ observer = new MutationObserver(() => {
344
+ setTimeout(addCustomMarkers, 100);
345
+ });
346
+ observer.observe(chartContainer, { childList: true, subtree: true });
347
+ }
348
+
349
+ return () => {
350
+ clearTimeout(timer);
351
+ clearTimeout(timer2);
352
+ if (observer) {
353
+ observer.disconnect();
354
+ }
355
+ };
356
+ }, [actualData]);
357
+
140
358
  return (
141
359
  <div className={styles.pulseBPGraph}>
142
360
  <div className={styles.chartContainer} data-chart-id="pulse-bp">
143
- <div style={{ position: 'relative' }}>
144
- <LineChart data={finalChartData} options={chartOptions} />
145
-
146
- {actualData.length > 0 && (
147
- <svg
148
- style={{
149
- position: 'absolute',
150
- top: 0,
151
- left: 0,
152
- width: '100%',
153
- height: '100%',
154
- pointerEvents: 'none',
155
- zIndex: 5,
156
- }}>
157
- {actualData.map((item, index) => {
158
- const chartMarginTop = 5;
159
- const chartMarginBottom = 8;
160
- const chartMarginLeft = 3.59;
161
- const chartMarginRight = 8;
162
- const chartWidth = 100 - chartMarginLeft - chartMarginRight;
163
- const chartHeight = 100 - chartMarginTop - chartMarginBottom;
164
- const dataPointIndex = index;
165
- const bpXPosition = chartMarginLeft + ((dataPointIndex + 0) / 12) * chartWidth;
166
- const yOffset = 23;
167
- const yOffsetDiastolic = 40;
168
- const pulseYPosition = chartMarginTop + yOffset + ((260 - item.pulse) / 260) * (chartHeight - yOffset);
169
- const systolicYPosition =
170
- chartMarginTop + yOffset + ((260 - item.systolicBP) / 260) * (chartHeight - yOffset);
171
- const diastolicYPosition =
172
- chartMarginTop +
173
- yOffsetDiastolic +
174
- ((260 - item.diastolicBP) / 260) * (chartHeight - yOffsetDiastolic);
175
-
176
- const greenArrowStartY = Math.min(pulseYPosition, diastolicYPosition);
177
- const greenArrowEndY = Math.max(pulseYPosition, diastolicYPosition);
178
-
179
- return (
180
- <g key={index}>
181
- <defs>
182
- <marker
183
- id={`systolic-arrow-${index}`}
184
- markerWidth="10"
185
- markerHeight="10"
186
- refX="5"
187
- refY="5"
188
- orient="auto"
189
- markerUnits="strokeWidth">
190
- <polygon points="0,0 10,5 0,10 3,5" fill={getColorForGraph('red')} />
191
- </marker>
192
- <marker
193
- id={`diastolic-arrow-${index}`}
194
- markerWidth="10"
195
- markerHeight="10"
196
- refX="5"
197
- refY="5"
198
- orient="auto"
199
- markerUnits="strokeWidth">
200
- <polygon points="0,10 10,5 0,0 3,5" fill={getColorForGraph('green')} />
201
- </marker>
202
- </defs>
203
-
204
- <line
205
- x1={`${bpXPosition}%`}
206
- y1={`${pulseYPosition}%`}
207
- x2={`${bpXPosition}%`}
208
- y2={`${systolicYPosition}%`}
209
- stroke={getColorForGraph('red')}
210
- strokeWidth="2"
211
- markerEnd={`url(#systolic-arrow-${index})`}
212
- />
213
-
214
- <line
215
- x1={`${bpXPosition}%`}
216
- y1={`${pulseYPosition}%`}
217
- x2={`${bpXPosition}%`}
218
- y2={`${diastolicYPosition}%`}
219
- stroke={getColorForGraph('green')}
220
- strokeWidth="2"
221
- markerEnd={`url(#diastolic-arrow-${index})`}
222
- />
223
-
224
- <circle
225
- cx={`${bpXPosition}%`}
226
- cy={`${systolicYPosition}%`}
227
- r="3"
228
- fill={getColorForGraph('red')}
229
- opacity="0.8">
230
- <title>
231
- {formatDateTime(item, index)} - Systolic BP: {item.systolicBP} mmHg
232
- </title>
233
- </circle>
234
-
235
- <circle
236
- cx={`${bpXPosition}%`}
237
- cy={`${diastolicYPosition}%`}
238
- r="3"
239
- fill={getColorForGraph('green')}
240
- opacity="0.8">
241
- <title>
242
- {formatDateTime(item, index)} - Diastolic BP: {item.diastolicBP} mmHg
243
- </title>
244
- </circle>
245
-
246
- <circle
247
- cx={`${bpXPosition}%`}
248
- cy={`${pulseYPosition}%`}
249
- r="4"
250
- fill={getColorForGraph('blue')}
251
- stroke="#fff"
252
- strokeWidth="1"
253
- opacity="0.9">
254
- <title>
255
- {formatDateTime(item, index)} - Pulse: {item.pulse} bpm
256
- </title>
257
- </circle>
258
- </g>
259
- );
260
- })}
261
- </svg>
262
- )}
263
- </div>
361
+ <LineChart data={finalChartData} options={chartOptions} />
264
362
  </div>
265
363
  </div>
266
364
  );