@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2793 → 5.4.2-pre.2797

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 (44) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/dist/127.js +1 -1
  3. package/dist/40.js +1 -1
  4. package/dist/{189.js → 791.js} +1 -1
  5. package/dist/791.js.map +1 -0
  6. package/dist/916.js +1 -1
  7. package/dist/kenyaemr-esm-patient-clinical-view-app.js +3 -3
  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 +8 -2
  14. package/src/maternal-and-child-health/partography/forms/cervical-contractions-form.component.tsx +63 -28
  15. package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +52 -108
  16. package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +110 -100
  17. package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +7 -7
  18. package/src/maternal-and-child-health/partography/forms/membrane-amniotic-fluid-form.component.tsx +72 -75
  19. package/src/maternal-and-child-health/partography/forms/oxytocin-form.component.tsx +14 -19
  20. package/src/maternal-and-child-health/partography/forms/pulse-bp-form.component.tsx +34 -28
  21. package/src/maternal-and-child-health/partography/forms/temperature-form.component.tsx +10 -9
  22. package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.component.tsx +10 -10
  23. package/src/maternal-and-child-health/partography/forms/urine-test-form.component.tsx +7 -6
  24. package/src/maternal-and-child-health/partography/graphs/cervical-contractions-graph.component.tsx +51 -61
  25. package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph-wrapper.component.tsx +0 -6
  26. package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph.component.tsx +3 -3
  27. package/src/maternal-and-child-health/partography/graphs/fetal-heart-rate-graph.component.tsx +53 -30
  28. package/src/maternal-and-child-health/partography/graphs/membrane-amniotic-fluid-graph.component.tsx +25 -3
  29. package/src/maternal-and-child-health/partography/graphs/oxytocin-graph-wrapper.component.tsx +102 -97
  30. package/src/maternal-and-child-health/partography/graphs/oxytocin-graph.component.tsx +21 -22
  31. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +21 -24
  32. package/src/maternal-and-child-health/partography/graphs/temperature-graph.component.tsx +23 -26
  33. package/src/maternal-and-child-health/partography/graphs/urine-test-graph.component.tsx +1 -1
  34. package/src/maternal-and-child-health/partography/partograph.component.tsx +323 -171
  35. package/src/maternal-and-child-health/partography/partography-data-form.scss +23 -0
  36. package/src/maternal-and-child-health/partography/partography.resource.ts +70 -56
  37. package/src/maternal-and-child-health/partography/partography.scss +65 -6
  38. package/src/maternal-and-child-health/partography/resources/fetal-heart-rate.resource.ts +5 -10
  39. package/src/maternal-and-child-health/partography/resources/oxytocin.resource.ts +5 -3
  40. package/src/maternal-and-child-health/partography/types/index.ts +40 -37
  41. package/translations/am.json +1 -1
  42. package/translations/en.json +1 -1
  43. package/translations/sw.json +1 -1
  44. package/dist/189.js.map +0 -1
@@ -17,7 +17,7 @@ import {
17
17
  Tag,
18
18
  } from '@carbon/react';
19
19
  import { Add, ChartColumn, Table as TableIcon } from '@carbon/react/icons';
20
- import { openmrsFetch, useLayoutType, useSession } from '@openmrs/esm-framework';
20
+ import { openmrsFetch, useLayoutType, useSession, usePatient } from '@openmrs/esm-framework';
21
21
  import React, { useEffect, useMemo, useState } from 'react';
22
22
  import { codeToPlus, contractionLevelUuidMap, labelToUuid, contractionLevelUuidToLabel } from './types';
23
23
  import { useTranslation } from 'react-i18next';
@@ -50,6 +50,7 @@ import {
50
50
  useDrugOrders,
51
51
  useFetalHeartRateData,
52
52
  usePartographyData,
53
+ saveFetalHeartRateData,
53
54
  } from './partography.resource';
54
55
  import styles from './partography.scss';
55
56
  import { useMembraneAmnioticFluidData } from './resources/membrane-amniotic-fluid.resource';
@@ -276,6 +277,37 @@ const TableSkeleton: React.FC = () => {
276
277
  };
277
278
 
278
279
  const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
280
+ // Fetch patient demographics for drugs IV fluids form
281
+ // Always call usePatient hook at the top level to comply with React rules of hooks
282
+ const { patient: patientData } = usePatient(patientUuid);
283
+ // Compute age from birthDate
284
+ function calculateAgeFromBirthDate(birthDateStr?: string): string {
285
+ if (!birthDateStr) {
286
+ return '';
287
+ }
288
+ const today = new Date();
289
+ const birthDate = new Date(birthDateStr);
290
+ let years = today.getFullYear() - birthDate.getFullYear();
291
+ let months = today.getMonth() - birthDate.getMonth();
292
+ let days = today.getDate() - birthDate.getDate();
293
+ if (months < 0 || (months === 0 && days < 0)) {
294
+ years--;
295
+ months += 12;
296
+ }
297
+ return String(years);
298
+ }
299
+ const patientProp = patientData
300
+ ? {
301
+ uuid: patientData.id,
302
+ name:
303
+ patientData.name && patientData.name.length > 0
304
+ ? (patientData.name[0].given ? patientData.name[0].given.join(' ') + ' ' : '') +
305
+ (patientData.name[0].family || '')
306
+ : '',
307
+ gender: patientData.gender || '',
308
+ age: calculateAgeFromBirthDate(patientData.birthDate),
309
+ }
310
+ : undefined;
279
311
  const { t } = useTranslation();
280
312
 
281
313
  const ENABLE_DUMMY_DATA = false;
@@ -298,7 +330,7 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
298
330
  mutate: mutateMembraneAmnioticFluidData,
299
331
  } = useMembraneAmnioticFluidData(patientUuid || '');
300
332
 
301
- // Cervical contractions backend data
333
+ //Contractions backend data
302
334
  const {
303
335
  data: cervicalContractionsData,
304
336
  isLoading: isCervicalContractionsLoading,
@@ -538,11 +570,19 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
538
570
  mutate: mutateDrugOrders = () => {},
539
571
  } = useDrugOrders(patientUuid || '');
540
572
 
573
+ // Fetch backend drugs/IV fluids data (partography encounters)
574
+ const {
575
+ data: loadedDrugsIVFluidsData = [],
576
+ isLoading: isDrugsIVFluidsLoading,
577
+ error: drugsIVFluidsError,
578
+ mutate: mutateDrugsIVFluidsData,
579
+ } = usePartographyData(patientUuid || '', 'drugs-fluids');
580
+
541
581
  useEffect(() => {
542
582
  if (patientUuid) {
543
- if (drugOrdersError) {
544
- console.error('Drug Orders Error:', drugOrdersError);
545
- }
583
+ // if (drugOrdersError) {
584
+ // console.error('Drug Orders Error:', drugOrdersError);
585
+ // }
546
586
  }
547
587
  }, [patientUuid, loadedDrugOrders, isDrugOrdersLoading, drugOrdersError]);
548
588
 
@@ -969,10 +1009,10 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
969
1009
  }
970
1010
 
971
1011
  // Show error if cervix data failed to load
972
- if (cervixDataError) {
973
- console.error('Error loading cervix data:', cervixDataError);
974
- // Continue rendering but show warning - don't block the entire UI
975
- }
1012
+ // if (cervixDataError) {
1013
+ // console.error('Error loading cervix data:', cervixDataError);
1014
+ // // Continue rendering but show warning - don't block the entire UI
1015
+ // }
976
1016
 
977
1017
  const handleAddDataPoint = (graphId: string) => {
978
1018
  if (graphId === 'cervix') {
@@ -1095,18 +1135,28 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1095
1135
  return;
1096
1136
  }
1097
1137
 
1098
- // Add to local state for immediate UI update
1099
- const newEntry = {
1100
- hour: formData.hour,
1101
- value: formData.fetalHeartRate,
1102
- group: 'Fetal Heart Rate',
1103
- time: formData.time,
1104
- date: new Date().toLocaleDateString(),
1105
- id: `fhr-${Date.now()}`,
1106
- };
1107
-
1108
- setLocalFetalHeartRateData((prev) => [...prev, newEntry]);
1109
- setIsFetalHeartRateFormOpen(false);
1138
+ (async () => {
1139
+ try {
1140
+ const result = await saveFetalHeartRateData(
1141
+ patientUuid,
1142
+ {
1143
+ hour: formData.hour,
1144
+ time: formData.time,
1145
+ fetalHeartRate: formData.fetalHeartRate,
1146
+ },
1147
+ session?.sessionLocation?.uuid,
1148
+ session?.currentProvider?.uuid,
1149
+ );
1150
+ if (result.success) {
1151
+ await mutateFetalHeartRateData();
1152
+ setIsFetalHeartRateFormOpen(false);
1153
+ } else {
1154
+ alert(result.message || 'Failed to save fetal heart rate data.');
1155
+ }
1156
+ } catch (error) {
1157
+ alert(error?.message || 'Failed to save fetal heart rate data.');
1158
+ }
1159
+ })();
1110
1160
  };
1111
1161
 
1112
1162
  // Callback for when fetal heart rate data is saved to OpenMRS
@@ -1181,10 +1231,10 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1181
1231
  }
1182
1232
  setIsCervicalContractionsFormOpen(false);
1183
1233
  } else {
1184
- console.error('Failed to save cervical contractions:', saveResult.message);
1234
+ alert('Failed to save contractions data: ' + saveResult.message);
1185
1235
  }
1186
1236
  } catch (err) {
1187
- console.error('Error during cervical contractions save:', err);
1237
+ alert('Failed to save contractions data.');
1188
1238
  }
1189
1239
  };
1190
1240
 
@@ -1233,7 +1283,7 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1233
1283
  alert('Failed to save oxytocin data: ' + saveResult.message);
1234
1284
  }
1235
1285
  } catch (err) {
1236
- console.error('Error saving oxytocin data:', err);
1286
+ // console.error('Error saving oxytocin data:', err);
1237
1287
  alert('Failed to save oxytocin data.');
1238
1288
  }
1239
1289
  } else {
@@ -1369,96 +1419,173 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1369
1419
  };
1370
1420
 
1371
1421
  const getMembraneAmnioticFluidTableData = () => {
1372
- return membraneAmnioticFluidEntries.map((data, index) => ({
1373
- id: data.id || `maf-${index}`,
1374
- date: data.date,
1375
- timeSlot: data.timeSlot || '',
1376
- exactTime: data.time || '',
1377
- amnioticFluid: data.amnioticFluid,
1378
- moulding: data.moulding,
1379
- }));
1422
+ return membraneAmnioticFluidEntries
1423
+ .filter((data) => data.amnioticFluid !== undefined && data.amnioticFluid !== null && data.amnioticFluid !== '')
1424
+ .map((data, index) => ({
1425
+ id: data.id || `maf-${index}`,
1426
+ date: data.date,
1427
+ timeSlot: data.timeSlot || '',
1428
+ exactTime: data.time || '',
1429
+ amnioticFluid: data.amnioticFluid,
1430
+ moulding: data.moulding,
1431
+ }));
1380
1432
  };
1381
1433
 
1382
1434
  // Generate table data for fetal heart rate
1383
1435
  const getFetalHeartRateTableData = () => {
1384
- return computedFetalHeartRateData.map((data, index) => {
1385
- const getStatus = (value: number) => {
1386
- if (value < 100) {
1387
- return 'Low';
1388
- }
1389
- if (value >= 100 && value <= 180) {
1390
- return 'Normal';
1391
- }
1392
- return 'High';
1393
- };
1436
+ return computedFetalHeartRateData
1437
+ .filter((data) => data.value !== undefined && data.value !== null)
1438
+ .map((data, index) => {
1439
+ const getStatus = (value: number) => {
1440
+ if (value < 100) {
1441
+ return 'Low';
1442
+ }
1443
+ if (value >= 100 && value <= 180) {
1444
+ return 'Normal';
1445
+ }
1446
+ return 'High';
1447
+ };
1394
1448
 
1395
- return {
1396
- id: `fhr-${index}`,
1397
- date: new Date().toLocaleDateString(),
1398
- time: data.time || 'N/A',
1399
- hour: `${data.hour}${data.hour % 1 === 0.5 ? '.5' : ''}hr`,
1400
- value: `${data.value} bpm`,
1401
- status: getStatus(data.value),
1402
- };
1403
- });
1449
+ return {
1450
+ id: `fhr-${index}`,
1451
+ date: new Date().toLocaleDateString(),
1452
+ time: data.time || 'N/A',
1453
+ hour: `${data.hour}${data.hour % 1 === 0.5 ? '.5' : ''}hr`,
1454
+ value: `${data.value} bpm`,
1455
+ status: getStatus(data.value),
1456
+ };
1457
+ });
1404
1458
  };
1405
1459
 
1406
- // Generate table data for cervical contractions
1407
1460
  const getCervicalContractionsTableData = () => {
1408
- return cervicalContractionsData.map((encounter, index) => {
1409
- // Find relevant obs for timeSlot, contractionCount, contractionLevel
1410
- let timeSlot = '';
1411
- let contractionCount = '';
1412
- let contractionLevel = '';
1413
- if (Array.isArray(encounter.obs)) {
1414
- for (const obs of encounter.obs) {
1415
- // TimeSlot is stored as a string value starting with 'Time:'
1416
- if (
1417
- obs.concept.uuid === '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' &&
1418
- typeof obs.value === 'string' &&
1419
- obs.value.startsWith('Time:')
1420
- ) {
1421
- const match = obs.value.match(/Time:\s*(.+)/);
1422
- if (match) {
1423
- timeSlot = match[1].trim();
1461
+ return cervicalContractionsData
1462
+ .filter((encounter) => {
1463
+ // Only include if contractionCount or contractionLevel is present
1464
+ let contractionCount = '';
1465
+ let contractionLevel = '';
1466
+ if (Array.isArray(encounter.obs)) {
1467
+ for (const obs of encounter.obs) {
1468
+ if (obs.concept.uuid === '159682AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') {
1469
+ contractionCount = String(obs.value);
1470
+ }
1471
+ const contractionLabel = contractionLevelUuidToLabel(obs.concept.uuid);
1472
+ if (contractionLabel !== obs.concept.uuid) {
1473
+ contractionLevel = contractionLabel;
1424
1474
  }
1425
1475
  }
1426
- // Contraction count concept
1427
- if (obs.concept.uuid === '159682AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') {
1428
- contractionCount = String(obs.value);
1429
- }
1430
- const contractionLabel = contractionLevelUuidToLabel(obs.concept.uuid);
1431
- if (contractionLabel !== obs.concept.uuid) {
1432
- contractionLevel = contractionLabel;
1476
+ }
1477
+ return contractionCount !== '' || contractionLevel !== '';
1478
+ })
1479
+ .map((encounter, index) => {
1480
+ let timeSlot = '';
1481
+ let contractionCount = '';
1482
+ let contractionLevel = '';
1483
+ if (Array.isArray(encounter.obs)) {
1484
+ for (const obs of encounter.obs) {
1485
+ if (
1486
+ obs.concept.uuid === '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' &&
1487
+ typeof obs.value === 'string' &&
1488
+ obs.value.startsWith('Time:')
1489
+ ) {
1490
+ const match = obs.value.match(/Time:\s*(.+)/);
1491
+ if (match) {
1492
+ timeSlot = match[1].trim();
1493
+ }
1494
+ }
1495
+ // Contraction count concept
1496
+ if (obs.concept.uuid === '159682AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') {
1497
+ contractionCount = String(obs.value);
1498
+ }
1499
+ const contractionLabel = contractionLevelUuidToLabel(obs.concept.uuid);
1500
+ if (contractionLabel !== obs.concept.uuid) {
1501
+ contractionLevel = contractionLabel;
1502
+ }
1433
1503
  }
1434
1504
  }
1435
- }
1436
- return {
1437
- id: `cc-${index}`,
1438
- date: new Date(encounter.encounterDatetime).toLocaleDateString(),
1439
- timeSlot,
1440
- contractionCount,
1441
- contractionLevel,
1442
- };
1443
- });
1505
+ return {
1506
+ id: `cc-${index}`,
1507
+ date: new Date(encounter.encounterDatetime).toLocaleDateString(),
1508
+ timeSlot,
1509
+ contractionCount,
1510
+ contractionLevel,
1511
+ };
1512
+ });
1444
1513
  };
1445
1514
 
1446
1515
  const getOxytocinTableData = () => {
1447
- return loadedOxytocinData.map((data, index) => {
1448
- return {
1449
- id: `oxy-${index}`,
1450
- date: data.encounterDatetime ? new Date(data.encounterDatetime).toLocaleDateString() : '',
1451
- time: data.time || '',
1452
- dropsPerMinute:
1453
- data.dropsPerMinute !== null && data.dropsPerMinute !== undefined
1454
- ? `${data.dropsPerMinute} drops/min`
1455
- : 'N/A',
1456
- };
1457
- });
1516
+ return loadedOxytocinData
1517
+ .filter((data) => data.dropsPerMinute !== undefined && data.dropsPerMinute !== null)
1518
+ .map((data, index) => {
1519
+ return {
1520
+ id: `oxy-${index}`,
1521
+ date: data.encounterDatetime ? new Date(data.encounterDatetime).toLocaleDateString() : '',
1522
+ time: data.time || '',
1523
+ dropsPerMinute:
1524
+ data.dropsPerMinute !== null && data.dropsPerMinute !== undefined
1525
+ ? `${data.dropsPerMinute} drops/min`
1526
+ : 'N/A',
1527
+ };
1528
+ });
1458
1529
  };
1459
1530
 
1460
1531
  // Generate table data for drugs and IV fluids
1461
1532
  const getDrugsIVFluidsTableData = () => {
1533
+ // Map backend (saved) drugs/IV fluids data
1534
+ const backendData = (loadedDrugsIVFluidsData || [])
1535
+ .filter((encounter) => {
1536
+ const obs = encounter.obs || [];
1537
+ // UUIDs from payload
1538
+ const DRUG_NAME_UUID = 'c3082af8-f935-40c5-aa5b-74c684e81aea';
1539
+ const DOSAGE_UUID = 'b71ddb80-2d7f-4bde-a44b-236e62d4c1b6';
1540
+ // Only include if drugName or dosage is present
1541
+ const drugName = obs.find((o) => o.concept.uuid === DRUG_NAME_UUID)?.value;
1542
+ const dosage = obs.find((o) => o.concept.uuid === DOSAGE_UUID)?.value;
1543
+ return (
1544
+ (drugName !== undefined && drugName !== null && drugName !== '') ||
1545
+ (dosage !== undefined && dosage !== null && dosage !== '')
1546
+ );
1547
+ })
1548
+ .map((encounter, index) => {
1549
+ // Find relevant obs for drug name, dosage, route, frequency using concept UUIDs
1550
+ const obs = encounter.obs || [];
1551
+ // UUIDs from payload
1552
+ const DRUG_NAME_UUID = 'c3082af8-f935-40c5-aa5b-74c684e81aea';
1553
+ const DOSAGE_UUID = 'b71ddb80-2d7f-4bde-a44b-236e62d4c1b6';
1554
+ const ROUTE_FREQ_UUID = '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
1555
+
1556
+ const getObsValueByUuid = (uuid) => {
1557
+ const found = obs.find((o) => o.concept.uuid === uuid);
1558
+ if (!found) {
1559
+ return '';
1560
+ }
1561
+ return found.value != null ? String(found.value) : '';
1562
+ };
1563
+
1564
+ let route = '';
1565
+ let frequency = '';
1566
+ obs
1567
+ .filter((o) => o.concept.uuid === ROUTE_FREQ_UUID)
1568
+ .forEach((o) => {
1569
+ if (typeof o.value === 'string') {
1570
+ if (o.value.startsWith('Route:')) {
1571
+ route = o.value.replace('Route:', '').trim();
1572
+ } else if (o.value.startsWith('Frequency:')) {
1573
+ frequency = o.value.replace('Frequency:', '').trim();
1574
+ }
1575
+ }
1576
+ });
1577
+
1578
+ return {
1579
+ id: encounter.uuid,
1580
+ date: encounter.encounterDatetime ? new Date(encounter.encounterDatetime).toLocaleDateString() : '',
1581
+ drugName: getObsValueByUuid(DRUG_NAME_UUID),
1582
+ dosage: getObsValueByUuid(DOSAGE_UUID),
1583
+ route,
1584
+ frequency,
1585
+ source: 'backend',
1586
+ };
1587
+ });
1588
+
1462
1589
  // Combine loaded drug orders with local manual entries
1463
1590
  const drugOrdersData = loadedDrugOrders.map((order) => ({
1464
1591
  id: order.id,
@@ -1480,8 +1607,8 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1480
1607
  source: 'manual', // Mark as manual entry
1481
1608
  }));
1482
1609
 
1483
- const combinedData = [...drugOrdersData, ...manualEntriesData];
1484
-
1610
+ // Show backend data first, then orders, then manual
1611
+ const combinedData = [...backendData, ...drugOrdersData, ...manualEntriesData];
1485
1612
  return combinedData;
1486
1613
  };
1487
1614
 
@@ -1504,72 +1631,92 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1504
1631
  .filter(Boolean);
1505
1632
 
1506
1633
  // For each pulse, find the closest BP entry in time (within 1 hour)
1507
- return (loadedPulseData || []).map((encounter, index) => {
1508
- const pulseObs = encounter.obs.find((obs) => obs.concept.uuid === PARTOGRAPHY_CONCEPTS['maternal-pulse']);
1509
- const pulse = pulseObs
1510
- ? typeof pulseObs.value === 'number'
1511
- ? pulseObs.value
1512
- : parseFloat(pulseObs.value)
1513
- : null;
1514
- const pulseDate = new Date(encounter.encounterDatetime);
1515
- const date = pulseDate.toLocaleDateString();
1516
- const time = pulseDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
1517
-
1518
- // Find closest BP entry within 1 hour
1519
- let closestBP = null;
1520
- let minDiff = 60 * 60 * 1000; // 1 hour in ms
1521
- for (const bp of bpEntries) {
1522
- const diff = Math.abs(bp.datetime.getTime() - pulseDate.getTime());
1523
- if (diff < minDiff) {
1524
- minDiff = diff;
1525
- closestBP = bp;
1634
+ return (loadedPulseData || [])
1635
+ .map((encounter, index) => {
1636
+ const pulseObs = encounter.obs.find((obs) => obs.concept.uuid === PARTOGRAPHY_CONCEPTS['maternal-pulse']);
1637
+ const pulse = pulseObs
1638
+ ? typeof pulseObs.value === 'number'
1639
+ ? pulseObs.value
1640
+ : parseFloat(pulseObs.value)
1641
+ : null;
1642
+ const pulseDate = new Date(encounter.encounterDatetime);
1643
+ const date = pulseDate.toLocaleDateString();
1644
+ const time = pulseDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
1645
+
1646
+ // Find closest BP entry within 1 hour
1647
+ let closestBP = null;
1648
+ let minDiff = 60 * 60 * 1000; // 1 hour in ms
1649
+ for (const bp of bpEntries) {
1650
+ const diff = Math.abs(bp.datetime.getTime() - pulseDate.getTime());
1651
+ if (diff < minDiff) {
1652
+ minDiff = diff;
1653
+ closestBP = bp;
1654
+ }
1526
1655
  }
1527
- }
1528
1656
 
1529
- return {
1530
- id: `pulse-bp-${index}`,
1531
- pulse,
1532
- systolicBP: closestBP ? closestBP.systolicBP : '',
1533
- diastolicBP: closestBP ? closestBP.diastolicBP : '',
1534
- date,
1535
- time,
1536
- };
1537
- });
1657
+ return {
1658
+ id: `pulse-bp-${index}`,
1659
+ pulse,
1660
+ systolicBP: closestBP ? closestBP.systolicBP : '',
1661
+ diastolicBP: closestBP ? closestBP.diastolicBP : '',
1662
+ date,
1663
+ time,
1664
+ };
1665
+ })
1666
+ .filter(
1667
+ (row) =>
1668
+ row.pulse !== null &&
1669
+ row.pulse !== undefined &&
1670
+ !isNaN(row.pulse) &&
1671
+ ((typeof row.systolicBP === 'number' && !isNaN(row.systolicBP)) ||
1672
+ (typeof row.diastolicBP === 'number' && !isNaN(row.diastolicBP))),
1673
+ );
1538
1674
  };
1539
1675
 
1540
1676
  // Generate table data for temperature from backend only
1541
1677
  const getTemperatureTableData = () => {
1542
- return loadedTemperatureData.map((encounter, index) => {
1543
- const tempObs = encounter.obs.find((obs) => obs.concept.uuid === '5088AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
1544
- const timeObs = encounter.obs.find(
1545
- (obs) =>
1546
- obs.concept.uuid === '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' &&
1547
- typeof obs.value === 'string' &&
1548
- obs.value.startsWith('Time:'),
1549
- );
1550
- let time = '';
1551
- if (timeObs && typeof timeObs.value === 'string') {
1552
- const timeMatch = timeObs.value.match(/Time:\s*(.+)/);
1553
- if (timeMatch) {
1554
- time = timeMatch[1].trim();
1678
+ return loadedTemperatureData
1679
+ .map((encounter, index) => {
1680
+ const tempObs = encounter.obs.find((obs) => obs.concept.uuid === '5088AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
1681
+ const timeObs = encounter.obs.find(
1682
+ (obs) =>
1683
+ obs.concept.uuid === '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' &&
1684
+ typeof obs.value === 'string' &&
1685
+ obs.value.startsWith('Time:'),
1686
+ );
1687
+ let time = '';
1688
+ if (timeObs && typeof timeObs.value === 'string') {
1689
+ const timeMatch = timeObs.value.match(/Time:\s*(.+)/);
1690
+ if (timeMatch) {
1691
+ time = timeMatch[1].trim();
1692
+ }
1555
1693
  }
1556
- }
1557
- let temperature = tempObs?.value ?? null;
1558
- if (typeof temperature === 'string') {
1559
- const parsed = parseFloat(temperature);
1560
- temperature = isNaN(parsed) ? null : parsed;
1561
- }
1562
- return {
1563
- id: `temperature-${index}`,
1564
- date: new Date(encounter.encounterDatetime).toLocaleDateString(),
1565
- timeSlot: '',
1566
- exactTime: time,
1567
- temperature,
1568
- };
1569
- });
1694
+ let temperature = tempObs?.value ?? null;
1695
+ if (typeof temperature === 'string') {
1696
+ const parsed = parseFloat(temperature);
1697
+ temperature = isNaN(parsed) ? null : parsed;
1698
+ }
1699
+ return {
1700
+ id: `temperature-${index}`,
1701
+ date: new Date(encounter.encounterDatetime).toLocaleDateString(),
1702
+ timeSlot: '',
1703
+ exactTime: time,
1704
+ temperature,
1705
+ };
1706
+ })
1707
+ .filter((row) => row.temperature != null);
1570
1708
  };
1571
1709
 
1572
1710
  const getUrineTestTableData = () => urineTestData;
1711
+ // Only include urine test rows with at least one non-empty value (protein, acetone, volume)
1712
+ const getFilteredUrineTestTableData = () =>
1713
+ (urineTestData || []).filter(
1714
+ (row) =>
1715
+ row &&
1716
+ ((row.protein !== undefined && row.protein !== null && row.protein !== '') ||
1717
+ (row.acetone !== undefined && row.acetone !== null && row.acetone !== '') ||
1718
+ (row.volume !== undefined && row.volume !== null)),
1719
+ );
1573
1720
 
1574
1721
  const handleViewModeChange = (graphId: string, mode: 'graph' | 'table') => {
1575
1722
  setViewMode((prev) => ({
@@ -1624,25 +1771,29 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1624
1771
 
1625
1772
  // Membrane amniotic fluid: use backend data only
1626
1773
  if (graph.id === 'membrane-amniotic-fluid') {
1627
- return membraneAmnioticFluidEntries.map((data, index) => ({
1628
- id: data.id || `maf-${index}`,
1629
- date: data.date,
1630
- timeSlot: data.timeSlot || '',
1631
- exactTime: data.time || '',
1632
- amnioticFluid: data.amnioticFluid,
1633
- moulding: data.moulding,
1634
- }));
1774
+ return membraneAmnioticFluidEntries
1775
+ .filter((data) => data.amnioticFluid !== undefined && data.amnioticFluid !== null && data.amnioticFluid !== '')
1776
+ .map((data, index) => ({
1777
+ id: data.id || `maf-${index}`,
1778
+ date: data.date,
1779
+ timeSlot: data.timeSlot || '',
1780
+ exactTime: data.time || '',
1781
+ amnioticFluid: data.amnioticFluid,
1782
+ moulding: data.moulding,
1783
+ }));
1635
1784
  }
1636
1785
 
1637
1786
  // Fallback for other graphs: use local state if available
1638
1787
  if (localPartographyData[graph.id] && localPartographyData[graph.id].length > 0) {
1639
- return localPartographyData[graph.id].map((item, index) => ({
1640
- id: `${graph.id}-${index}`,
1641
- time: item.time || 'N/A',
1642
- value: item.value || item.measurementValue || 'N/A',
1643
- date: new Date(item.timestamp).toLocaleDateString() || 'N/A',
1644
- ...item, // Include any additional fields
1645
- }));
1788
+ return localPartographyData[graph.id]
1789
+ .filter((item) => item.value !== undefined && item.value !== null && item.value !== '')
1790
+ .map((item, index) => ({
1791
+ id: `${graph.id}-${index}`,
1792
+ time: item.time || 'N/A',
1793
+ value: item.value || item.measurementValue || 'N/A',
1794
+ date: new Date(item.timestamp).toLocaleDateString() || 'N/A',
1795
+ ...item, // Include any additional fields
1796
+ }));
1646
1797
  }
1647
1798
 
1648
1799
  // Otherwise, return empty or dummy data
@@ -2337,6 +2488,7 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
2337
2488
  isAddButtonDisabled={false}
2338
2489
  onDrugsIVFluidsSubmit={handleDrugsIVFluidsFormSubmit}
2339
2490
  onDataSaved={handleDrugOrderDataSaved}
2491
+ patient={patientProp}
2340
2492
  />
2341
2493
 
2342
2494
  <PulseBPGraph
@@ -2383,12 +2535,12 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
2383
2535
  />
2384
2536
 
2385
2537
  <UrineTestGraph
2386
- data={urineTestData}
2387
- tableData={urineTestData}
2538
+ data={getFilteredUrineTestTableData()}
2539
+ tableData={getFilteredUrineTestTableData()}
2388
2540
  viewMode={urineTestViewMode}
2389
2541
  currentPage={urineTestCurrentPage}
2390
2542
  pageSize={urineTestPageSize}
2391
- totalItems={urineTestData.length}
2543
+ totalItems={getFilteredUrineTestTableData().length}
2392
2544
  controlSize={controlSize}
2393
2545
  onAddData={() => setIsUrineTestFormOpen(true)}
2394
2546
  onViewModeChange={setUrineTestViewMode}
@@ -211,3 +211,26 @@
211
211
  font-weight: 500;
212
212
  text-transform: uppercase;
213
213
  }
214
+
215
+ /* Override validation styling when not needed */
216
+ .noValidation {
217
+ .cds--select__invalid-icon,
218
+ .cds--number__invalid-icon {
219
+ display: none !important;
220
+ }
221
+
222
+ .cds--select--invalid,
223
+ .cds--number--invalid {
224
+ border-bottom-color: var(--cds-border-strong-01) !important;
225
+ }
226
+
227
+ .cds--select--invalid .cds--select-input,
228
+ .cds--number--invalid .cds--number input {
229
+ border-bottom-color: var(--cds-border-strong-01) !important;
230
+ background-color: var(--cds-field-01) !important;
231
+ }
232
+
233
+ .cds--form-requirement {
234
+ display: none !important;
235
+ }
236
+ }