@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2763 → 5.4.2-pre.2765

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 (34) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/dist/127.js +1 -1
  3. package/dist/{805.js → 189.js} +1 -1
  4. package/dist/189.js.map +1 -0
  5. package/dist/40.js +1 -1
  6. package/dist/916.js +1 -1
  7. package/dist/kenyaemr-esm-patient-clinical-view-app.js +2 -2
  8. package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +36 -36
  9. package/dist/main.js +3 -3
  10. package/dist/main.js.map +1 -1
  11. package/dist/routes.json +1 -1
  12. package/package.json +1 -1
  13. package/src/config-schema.ts +58 -36
  14. package/src/maternal-and-child-health/partography/components/temperature-graph.component.tsx +0 -4
  15. package/src/maternal-and-child-health/partography/components/uterine-contractions-graph.component.tsx +33 -11
  16. package/src/maternal-and-child-health/partography/forms/cervical-contractions-form.component.tsx +124 -136
  17. package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +23 -14
  18. package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +6 -10
  19. package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +36 -13
  20. package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.component.tsx +27 -46
  21. package/src/maternal-and-child-health/partography/forms/useCervixData.ts +2 -2
  22. package/src/maternal-and-child-health/partography/graphs/cervix-graph.component.tsx +56 -18
  23. package/src/maternal-and-child-health/partography/graphs/fetal-heart-rate-graph.component.tsx +36 -23
  24. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +10 -5
  25. package/src/maternal-and-child-health/partography/partograph.component.tsx +315 -371
  26. package/src/maternal-and-child-health/partography/partography.resource.ts +788 -230
  27. package/src/maternal-and-child-health/partography/partography.scss +68 -40
  28. package/src/maternal-and-child-health/partography/resources/cervix.resource.ts +79 -76
  29. package/src/maternal-and-child-health/partography/resources/uterine-contractions.resource.ts +33 -12
  30. package/src/maternal-and-child-health/partography/types/index.ts +94 -0
  31. package/translations/am.json +0 -8
  32. package/translations/en.json +0 -8
  33. package/translations/sw.json +0 -8
  34. package/dist/805.js.map +0 -1
@@ -45,7 +45,7 @@ const CervixForm: React.FC<CervixFormProps> = ({
45
45
  hour: '',
46
46
  time: '',
47
47
  cervicalDilation: '',
48
- descent: '5',
48
+ descent: '',
49
49
  },
50
50
  });
51
51
 
@@ -66,24 +66,25 @@ const CervixForm: React.FC<CervixFormProps> = ({
66
66
  };
67
67
  }, [existingCervixData]);
68
68
 
69
- const maxSelectedHour = useMemo(
70
- () => (selectedHours && selectedHours.length > 0 ? Math.max(...selectedHours) : -1),
71
- [selectedHours],
72
- );
69
+ const usedHours = useMemo(() => {
70
+ if (!selectedHours || selectedHours.length === 0) {
71
+ return [];
72
+ }
73
+ return selectedHours;
74
+ }, [selectedHours]);
73
75
 
74
76
  const hourOptions = useMemo(() => {
75
77
  return Array.from({ length: 24 }, (_, i) => {
76
78
  const hourValue = String(i).padStart(2, '0');
77
- const isDisabled = i <= maxSelectedHour;
79
+ const isDisabled = usedHours.includes(i);
78
80
  const displayText = isDisabled ? `${hourValue} (used)` : hourValue;
79
-
80
81
  return {
81
82
  value: hourValue,
82
83
  text: displayText,
83
84
  disabled: isDisabled,
84
85
  };
85
86
  });
86
- }, [maxSelectedHour]);
87
+ }, [usedHours]);
87
88
 
88
89
  const onSubmitForm = async (data: CervixFormData) => {
89
90
  if (!data.hour || data.hour === '') {
@@ -99,6 +100,7 @@ const CervixForm: React.FC<CervixFormProps> = ({
99
100
  type: 'manual',
100
101
  message: 'Time selection is required',
101
102
  });
103
+ console.warn('[CervixForm] Attempted to submit with empty time value:', data);
102
104
  return;
103
105
  }
104
106
 
@@ -178,7 +180,10 @@ const CervixForm: React.FC<CervixFormProps> = ({
178
180
  }
179
181
 
180
182
  clearErrors();
181
-
183
+ if (!data.time || data.time.trim() === '') {
184
+ alert('Time cannot be empty. Please select a valid time.');
185
+ return;
186
+ }
182
187
  onSubmit({
183
188
  hour: hourValue,
184
189
  time: data.time,
@@ -286,12 +291,16 @@ const CervixForm: React.FC<CervixFormProps> = ({
286
291
  <NumberInput
287
292
  id="cervical-dilation-input"
288
293
  label="Cervical Dilation (cm) *"
289
- placeholder={`Enter dilation (min: ${validationLimits.cervicalDilationMin}cm, max: 10cm)`}
294
+ placeholder="Enter dilation (min: 4cm, max: 10cm)"
290
295
  value={field.value || ''}
291
- onChange={(e, { value }) => field.onChange(String(value))}
292
- min={validationLimits.cervicalDilationMin}
296
+ onChange={(e, { value }) => {
297
+ // Only allow whole numbers
298
+ const intValue = String(value).replace(/[^\d]/g, '');
299
+ field.onChange(intValue);
300
+ }}
301
+ min={4}
293
302
  max={10}
294
- step={0.5}
303
+ step={1}
295
304
  invalid={!!fieldState.error}
296
305
  invalidText={fieldState.error?.message}
297
306
  />
@@ -333,7 +342,7 @@ const CervixForm: React.FC<CervixFormProps> = ({
333
342
  ? `Default: 5 (high position), can decrement to lower values`
334
343
  : `Enter descent (1=most descended, 5=high position)`
335
344
  }
336
- value={field.value || '5'}
345
+ value={field.value || ''}
337
346
  onChange={(e, { value }) => field.onChange(String(value))}
338
347
  min={1}
339
348
  max={5}
@@ -109,16 +109,12 @@ const DrugsIVFluidsForm: React.FC<DrugsIVFluidsFormProps> = ({ isOpen, onClose,
109
109
  if (patient?.uuid) {
110
110
  setIsSaving(true);
111
111
  try {
112
- const result = await saveDrugOrderData(
113
- patient.uuid,
114
- {
115
- drugName: data.drugName,
116
- dosage: data.dosage,
117
- route: data.route,
118
- frequency: data.frequency,
119
- },
120
- t,
121
- );
112
+ const result = await saveDrugOrderData(patient.uuid, {
113
+ drugName: data.drugName,
114
+ dosage: data.dosage,
115
+ route: data.route,
116
+ frequency: data.frequency,
117
+ });
122
118
 
123
119
  if (result.success) {
124
120
  setSaveSuccess(true);
@@ -57,22 +57,35 @@ const FetalHeartRateForm: React.FC<FetalHeartRateFormProps> = ({
57
57
  });
58
58
  const generateHourOptions = () => {
59
59
  const options = [];
60
- for (let i = 0; i <= 24; i++) {
61
- if (i === 0) {
62
- options.push({ value: '0.5', label: '30min' });
63
- } else {
64
- options.push({ value: i.toString(), label: `${i}hr` });
65
- }
60
+ options.push({ value: '0', label: '00' });
61
+ options.push({ value: '0.5', label: '30min' });
62
+ for (let i = 1; i <= 24; i++) {
63
+ options.push({ value: i.toString(), label: `${i}hr` });
66
64
  if (i < 24) {
67
- const halfHourValue = (i + 0.5).toString();
68
- const halfHourLabel = i === 0 ? '1hr' : `${i}hr 30min`;
69
- options.push({ value: halfHourValue, label: halfHourLabel });
65
+ options.push({ value: (i + 0.5).toString(), label: `${i}hr 30min` });
70
66
  }
71
67
  }
72
68
  return options;
73
69
  };
74
70
 
75
- const hourOptions = generateHourOptions();
71
+ const latestHour = React.useMemo(() => {
72
+ if (!existingTimeEntries || existingTimeEntries.length === 0) {
73
+ return null;
74
+ }
75
+ // Find the max hour value from existingTimeEntries (hour is already a number)
76
+ return Math.max(...existingTimeEntries.map((e) => e.hour));
77
+ }, [existingTimeEntries]);
78
+
79
+ // Generate hour options, disabling those before the latest entered hour (float comparison)
80
+ const hourOptionsWithDisabled = React.useMemo(() => {
81
+ return generateHourOptions().map((option) => {
82
+ const hourValue = parseFloat(option.value);
83
+ return {
84
+ ...option,
85
+ disabled: latestHour !== null && hourValue <= latestHour,
86
+ };
87
+ });
88
+ }, [latestHour]);
76
89
 
77
90
  const handleFormSubmit = async (data: FetalHeartRateFormData) => {
78
91
  const hourValue = parseFloat(data.hour);
@@ -213,8 +226,13 @@ const FetalHeartRateForm: React.FC<FetalHeartRateFormProps> = ({
213
226
  value={field.value}
214
227
  onChange={(e) => field.onChange(e.target.value)}>
215
228
  <SelectItem value="" text={t('admissionTime', 'Admission')} />
216
- {hourOptions.map((option) => (
217
- <SelectItem key={option.value} value={option.value} text={option.label} />
229
+ {hourOptionsWithDisabled.map((option) => (
230
+ <SelectItem
231
+ key={option.value}
232
+ value={option.value}
233
+ text={option.label}
234
+ disabled={option.disabled}
235
+ />
218
236
  ))}
219
237
  </Select>
220
238
  )}
@@ -258,10 +276,15 @@ const FetalHeartRateForm: React.FC<FetalHeartRateFormProps> = ({
258
276
  max={200}
259
277
  step={1}
260
278
  value={field.value}
261
- onChange={(_, state) => field.onChange(state.value)}
279
+ onChange={(e) => {
280
+ let val = (e.target as HTMLInputElement).value;
281
+ val = val.replace(/[^\d]/g, '');
282
+ field.onChange(val);
283
+ }}
262
284
  invalid={!!fieldState.error}
263
285
  invalidText={fieldState.error?.message}
264
286
  size="lg"
287
+ allowEmpty
265
288
  />
266
289
  )}
267
290
  />
@@ -22,8 +22,6 @@ const TimePickerDropdown: React.FC<TimePickerDropdownProps> = ({
22
22
  existingTimeEntries = [],
23
23
  }) => {
24
24
  const [hours, minutes] = value && value.includes(':') ? value.split(':') : ['', ''];
25
-
26
- // Memoize sorted entries and latest entry to avoid re-sorting on every render
27
25
  const latestEntry = useMemo(() => {
28
26
  if (!existingTimeEntries || existingTimeEntries.length === 0) {
29
27
  return null;
@@ -50,18 +48,24 @@ const TimePickerDropdown: React.FC<TimePickerDropdownProps> = ({
50
48
  return currentTimeInMinutes <= latestTimeInMinutes;
51
49
  };
52
50
 
53
- // Memoize options generation so we don't recreate arrays on every render
54
51
  const hourOptions = useMemo(() => {
52
+ if (!latestEntry) {
53
+ return Array.from({ length: 24 }, (_, i) => {
54
+ const hour = i.toString().padStart(2, '0');
55
+ return { value: hour, text: hour, disabled: false, reason: '' };
56
+ });
57
+ }
58
+ const [latestHour, latestMinute] = latestEntry.time.split(':').map(Number);
55
59
  return Array.from({ length: 24 }, (_, i) => {
56
60
  const hour = i.toString().padStart(2, '0');
57
61
  let isDisabled = false;
58
62
  let disableReason = '';
59
- if (existingTimeEntries && existingTimeEntries.length > 0 && latestEntry) {
60
- const [latestHour] = latestEntry.time.split(':').map(Number);
61
- if (i < latestHour) {
62
- isDisabled = true;
63
- disableReason = 'before latest entry';
64
- }
63
+ if (i < latestHour) {
64
+ isDisabled = true;
65
+ disableReason = 'before latest entry';
66
+ } else if (i === latestHour && latestMinute >= 55) {
67
+ isDisabled = true;
68
+ disableReason = 'latest entry full hour';
65
69
  }
66
70
  const displayText = isDisabled ? `${hour} ${disableReason}` : hour;
67
71
  return {
@@ -74,18 +78,24 @@ const TimePickerDropdown: React.FC<TimePickerDropdownProps> = ({
74
78
  }, [existingTimeEntries, latestEntry]);
75
79
 
76
80
  const minuteOptions = useMemo(() => {
81
+ if (!latestEntry || !hours) {
82
+ return Array.from({ length: 12 }, (_, i) => {
83
+ const minute = (i * 5).toString().padStart(2, '0');
84
+ return { value: minute, text: minute, disabled: false };
85
+ });
86
+ }
87
+ const [latestHour, latestMinute] = latestEntry.time.split(':').map(Number);
88
+ const currentHour = parseInt(hours);
77
89
  return Array.from({ length: 12 }, (_, i) => {
78
90
  const minute = (i * 5).toString().padStart(2, '0');
79
91
  let isDisabled = false;
80
92
  let disableReason = '';
81
- if (hours && existingTimeEntries.length > 0 && latestEntry) {
82
- const currentHour = parseInt(hours);
83
- const currentMinute = i * 5;
84
- const [latestHour, latestMinute] = latestEntry.time.split(':').map(Number);
85
- if (currentHour === latestHour && currentMinute <= latestMinute) {
86
- isDisabled = true;
87
- disableReason = `≤ ${latestMinute} min`;
88
- }
93
+ if (currentHour < latestHour) {
94
+ isDisabled = true;
95
+ disableReason = 'before latest entry';
96
+ } else if (currentHour === latestHour && i * 5 <= latestMinute) {
97
+ isDisabled = true;
98
+ disableReason = `≤ ${latestMinute} min`;
89
99
  }
90
100
  const displayText = isDisabled ? `${minute} ${disableReason}` : minute;
91
101
  return { value: minute, text: displayText, disabled: isDisabled };
@@ -181,35 +191,6 @@ const TimePickerDropdown: React.FC<TimePickerDropdownProps> = ({
181
191
  </div>
182
192
 
183
193
  {invalid && invalidText && <div className={styles.errorText}>{invalidText}</div>}
184
-
185
- {latestEntry && (
186
- <div className={styles.timeConstraint}>
187
- Latest entry: <strong>{latestEntry.time}</strong> - Next measurement must be after this time
188
- </div>
189
- )}
190
-
191
- {existingTimeEntries.length > 0 && (
192
- <div className={styles.usedTimesIndicator}>
193
- <strong>Time Restrictions:</strong>
194
- <br />
195
- <strong>Used Times:</strong> {existingTimeEntries.map((entry) => entry.time).join(', ')}
196
- <br />
197
- <strong>Blocked Hours:</strong>{' '}
198
- {hourOptions
199
- .filter((opt) => opt.disabled)
200
- .map((opt) => opt.value)
201
- .join(', ')}{' '}
202
- (grayed out)
203
- <br />
204
- <small>You can only select times after the latest entry</small>
205
- </div>
206
- )}
207
-
208
- {value && (
209
- <div className={styles.timePreview}>
210
- Selected Time: <strong>{value}</strong>
211
- </div>
212
- )}
213
194
  </FormGroup>
214
195
  </div>
215
196
  );
@@ -1,5 +1,5 @@
1
- import { useCervixData } from '../resources/cervix.resource';
2
- export function useCervixFormData(patientUuid: string) {
1
+ import { useCervixData, UseCervixDataResult } from '../resources/cervix.resource';
2
+ export function useCervixFormData(patientUuid: string): UseCervixDataResult {
3
3
  return useCervixData(patientUuid);
4
4
  }
5
5
  export type {
@@ -194,6 +194,7 @@ const CervixGraph: React.FC<CervixGraphProps> = ({
194
194
  });
195
195
  }
196
196
  useEffect(() => {
197
+ let observer: MutationObserver | null = null;
197
198
  const applyChartStyling = () => {
198
199
  const chartContainer = document.querySelector(`[data-chart-id="cervix"]`);
199
200
  if (chartContainer) {
@@ -217,36 +218,60 @@ const CervixGraph: React.FC<CervixGraphProps> = ({
217
218
  svgCircles.forEach((circle) => {
218
219
  const circleElement = circle as SVGCircleElement;
219
220
  const parentGroup = circleElement.closest('g');
220
- if (parentGroup) {
221
- const stroke = circleElement.getAttribute('stroke') || circleElement.style.stroke;
222
- const fill = circleElement.getAttribute('fill') || circleElement.style.fill;
223
-
224
- if (stroke === getColorForGraph('green') || fill === getColorForGraph('green')) {
225
- circleElement.style.display = 'none';
226
-
227
- const cx = parseFloat(circleElement.getAttribute('cx') || '0');
228
- const cy = parseFloat(circleElement.getAttribute('cy') || '0');
229
- const size = 6;
230
-
231
- const svg = circleElement.ownerSVGElement;
232
- if (svg) {
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) {
247
+ circleElement.style.display = 'none';
248
+ const cx = parseFloat(circleElement.getAttribute('cx') || '0');
249
+ 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]')) {
233
256
  const line1 = document.createElementNS(SVG_NAMESPACE, 'line');
234
257
  line1.setAttribute('x1', (cx - size).toString());
235
258
  line1.setAttribute('y1', (cy - size).toString());
236
259
  line1.setAttribute('x2', (cx + size).toString());
237
260
  line1.setAttribute('y2', (cy + size).toString());
238
- line1.setAttribute('stroke', getColorForGraph('green'));
239
- line1.setAttribute('stroke-width', '3');
261
+ line1.setAttribute('stroke', xColor);
262
+ line1.setAttribute('stroke-width', '5');
240
263
  line1.setAttribute('stroke-linecap', 'round');
264
+ line1.setAttribute('data-x-marker', 'true');
241
265
 
242
266
  const line2 = document.createElementNS(SVG_NAMESPACE, 'line');
243
267
  line2.setAttribute('x1', (cx + size).toString());
244
268
  line2.setAttribute('y1', (cy - size).toString());
245
269
  line2.setAttribute('x2', (cx - size).toString());
246
270
  line2.setAttribute('y2', (cy + size).toString());
247
- line2.setAttribute('stroke', getColorForGraph('green'));
248
- line2.setAttribute('stroke-width', '3');
271
+ line2.setAttribute('stroke', xColor);
272
+ line2.setAttribute('stroke-width', '5');
249
273
  line2.setAttribute('stroke-linecap', 'round');
274
+ line2.setAttribute('data-x-marker', 'true');
250
275
 
251
276
  parentGroup.appendChild(line1);
252
277
  parentGroup.appendChild(line2);
@@ -258,7 +283,20 @@ const CervixGraph: React.FC<CervixGraphProps> = ({
258
283
  };
259
284
 
260
285
  const timer = setTimeout(applyChartStyling, 100);
261
- return () => clearTimeout(timer);
286
+ const chartContainer = document.querySelector(`[data-chart-id="cervix"]`);
287
+ if (chartContainer && window.MutationObserver) {
288
+ observer = new MutationObserver(() => {
289
+ applyChartStyling();
290
+ });
291
+ observer.observe(chartContainer, { childList: true, subtree: true });
292
+ }
293
+
294
+ return () => {
295
+ clearTimeout(timer);
296
+ if (observer) {
297
+ observer.disconnect();
298
+ }
299
+ };
262
300
  }, [cervixFormData, isLoading]);
263
301
 
264
302
  const shouldRenderChart = finalChartData.length > 0;
@@ -16,7 +16,7 @@ import {
16
16
  import { Add, ChartColumn, Table as TableIcon } from '@carbon/react/icons';
17
17
  import { LineChart } from '@carbon/charts-react';
18
18
  import styles from '../partography.scss';
19
- import { getColorForGraph, generateRange } from '../types';
19
+ import { getColorForGraph, generateRange, defaultFetalHeartRateChartData } from '../types';
20
20
  import { usePaginationInfo } from '@openmrs/esm-patient-common-lib';
21
21
 
22
22
  enum ScaleTypes {
@@ -78,9 +78,9 @@ const FetalHeartRateGraph: React.FC<FetalHeartRateGraphProps> = ({
78
78
  );
79
79
  const getFetalHeartRateStatus = (value: string): { type: string; text: string; color: string } => {
80
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) {
81
+ if (numValue < 110) {
82
+ return { type: 'red', text: 'Low', color: getColorForGraph('red') };
83
+ } else if (numValue >= 110 && numValue <= 160) {
84
84
  return { type: 'green', text: 'Normal', color: getColorForGraph('green') };
85
85
  } else {
86
86
  return { type: 'red', text: 'High', color: getColorForGraph('red') };
@@ -94,9 +94,9 @@ const FetalHeartRateGraph: React.FC<FetalHeartRateGraphProps> = ({
94
94
  { key: 'status', header: t('status', 'Status') },
95
95
  ];
96
96
  const getFetalHeartRateColor = (value: number): string => {
97
- if (value < 100) {
98
- return getColorForGraph('gray');
99
- } else if (value >= 100 && value <= 180) {
97
+ if (value < 110) {
98
+ return getColorForGraph('red');
99
+ } else if (value >= 110 && value <= 160) {
100
100
  return getColorForGraph('green');
101
101
  } else {
102
102
  return getColorForGraph('red');
@@ -112,15 +112,7 @@ const FetalHeartRateGraph: React.FC<FetalHeartRateGraphProps> = ({
112
112
  }));
113
113
  }
114
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
- ];
115
+ return defaultFetalHeartRateChartData;
124
116
  }, [data]);
125
117
 
126
118
  const chartData = enhancedChartData;
@@ -179,6 +171,28 @@ const FetalHeartRateGraph: React.FC<FetalHeartRateGraphProps> = ({
179
171
  enabled: true,
180
172
  },
181
173
  },
174
+ referenceLines: [
175
+ {
176
+ axis: 'left',
177
+ value: 110,
178
+ style: {
179
+ stroke: '#111',
180
+ strokeWidth: 4,
181
+ strokeDasharray: '0',
182
+ },
183
+ label: '110',
184
+ },
185
+ {
186
+ axis: 'left',
187
+ value: 160,
188
+ style: {
189
+ stroke: '#111',
190
+ strokeWidth: 4,
191
+ strokeDasharray: '0',
192
+ },
193
+ label: '160',
194
+ },
195
+ ],
182
196
  color: {
183
197
  scale:
184
198
  data && data.length > 0
@@ -204,13 +218,13 @@ const FetalHeartRateGraph: React.FC<FetalHeartRateGraphProps> = ({
204
218
  <h3 className={styles.fetalHeartRateTitle}>Fetal Heart Rate</h3>
205
219
  <div className={styles.fetalHeartRateControls}>
206
220
  <Tag type="green" title="Normal Range">
207
- Normal (100-180)
221
+ Normal (110-160)
208
222
  </Tag>
209
223
  <Tag type="red" title="Abnormal Range">
210
- Abnormal (&gt;180)
224
+ Abnormal (&gt;160)
211
225
  </Tag>
212
- <Tag type="warm-gray" title="Low Range">
213
- Low (&lt;100)
226
+ <Tag type="red" title="Low Range">
227
+ Low (&lt;110)
214
228
  </Tag>
215
229
  </div>
216
230
  </div>
@@ -290,15 +304,14 @@ const FetalHeartRateGraph: React.FC<FetalHeartRateGraphProps> = ({
290
304
  : cell.info.header === 'value'
291
305
  ? (() => {
292
306
  const numValue = parseInt(cell.value.replace(' bpm', ''));
293
-
294
- if (numValue < 100) {
307
+ if (numValue < 110) {
295
308
  return (
296
309
  <span className={`${styles.fetalHeartRateValue} ${styles.low}`}>
297
310
  <span className={styles.arrow}>↓</span>
298
311
  {cell.value}
299
312
  </span>
300
313
  );
301
- } else if (numValue > 180) {
314
+ } else if (numValue > 160) {
302
315
  return (
303
316
  <span className={`${styles.fetalHeartRateValue} ${styles.high}`}>
304
317
  <span className={styles.arrow}>↑</span>
@@ -58,7 +58,7 @@ const PULSE_BP_CHART_OPTIONS = {
58
58
  filled: true,
59
59
  },
60
60
  curve: 'curveLinear',
61
- height: '400px',
61
+ height: '600px',
62
62
  theme: 'white',
63
63
  toolbar: {
64
64
  enabled: false,
@@ -74,7 +74,7 @@ const PULSE_BP_CHART_OPTIONS = {
74
74
  },
75
75
  y: {
76
76
  enabled: true,
77
- numberOfTicks: 13,
77
+ numberOfTicks: 7, // Fewer ticks for larger row spacing
78
78
  },
79
79
  },
80
80
  zoomBar: {
@@ -174,6 +174,9 @@ const PulseBPGraph: React.FC<PulseBPGraphProps> = ({ data }) => {
174
174
  const systolicYPosition = chartMarginTop + ((180 - item.systolicBP) / (180 - 60)) * chartHeight;
175
175
  const diastolicYPosition = chartMarginTop + ((180 - item.diastolicBP) / (180 - 60)) * chartHeight;
176
176
 
177
+ const greenArrowStartY = Math.min(pulseYPosition, diastolicYPosition);
178
+ const greenArrowEndY = Math.max(pulseYPosition, diastolicYPosition);
179
+
177
180
  return (
178
181
  <g key={index}>
179
182
  <defs>
@@ -195,7 +198,8 @@ const PulseBPGraph: React.FC<PulseBPGraphProps> = ({ data }) => {
195
198
  refY="5"
196
199
  orient="auto"
197
200
  markerUnits="strokeWidth">
198
- <polygon points="0,0 10,5 0,10 3,5" fill={getColorForGraph('green')} />
201
+ {/* Downward arrow for green (diastolic) */}
202
+ <polygon points="0,10 10,5 0,0 3,5" fill={getColorForGraph('green')} />
199
203
  </marker>
200
204
  </defs>
201
205
 
@@ -209,11 +213,12 @@ const PulseBPGraph: React.FC<PulseBPGraphProps> = ({ data }) => {
209
213
  markerEnd={`url(#systolic-arrow-${index})`}
210
214
  />
211
215
 
216
+ {/* Always draw green arrow down, regardless of value */}
212
217
  <line
213
218
  x1={`${bpXPosition}%`}
214
- y1={`${pulseYPosition}%`}
219
+ y1={`${greenArrowStartY}%`}
215
220
  x2={`${bpXPosition}%`}
216
- y2={`${diastolicYPosition}%`}
221
+ y2={`${greenArrowEndY}%`}
217
222
  stroke={getColorForGraph('green')}
218
223
  strokeWidth="2"
219
224
  markerEnd={`url(#diastolic-arrow-${index})`}