@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.
- package/.turbo/turbo-build.log +4 -4
- package/dist/127.js +1 -1
- package/dist/40.js +1 -1
- package/dist/{189.js → 791.js} +1 -1
- package/dist/791.js.map +1 -0
- package/dist/916.js +1 -1
- package/dist/kenyaemr-esm-patient-clinical-view-app.js +3 -3
- package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +36 -36
- package/dist/main.js +3 -3
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +8 -2
- package/src/maternal-and-child-health/partography/forms/cervical-contractions-form.component.tsx +63 -28
- package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +52 -108
- package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +110 -100
- package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +7 -7
- package/src/maternal-and-child-health/partography/forms/membrane-amniotic-fluid-form.component.tsx +72 -75
- package/src/maternal-and-child-health/partography/forms/oxytocin-form.component.tsx +14 -19
- package/src/maternal-and-child-health/partography/forms/pulse-bp-form.component.tsx +34 -28
- package/src/maternal-and-child-health/partography/forms/temperature-form.component.tsx +10 -9
- package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.component.tsx +10 -10
- package/src/maternal-and-child-health/partography/forms/urine-test-form.component.tsx +7 -6
- package/src/maternal-and-child-health/partography/graphs/cervical-contractions-graph.component.tsx +51 -61
- package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph-wrapper.component.tsx +0 -6
- package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph.component.tsx +3 -3
- package/src/maternal-and-child-health/partography/graphs/fetal-heart-rate-graph.component.tsx +53 -30
- package/src/maternal-and-child-health/partography/graphs/membrane-amniotic-fluid-graph.component.tsx +25 -3
- package/src/maternal-and-child-health/partography/graphs/oxytocin-graph-wrapper.component.tsx +102 -97
- package/src/maternal-and-child-health/partography/graphs/oxytocin-graph.component.tsx +21 -22
- package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +21 -24
- package/src/maternal-and-child-health/partography/graphs/temperature-graph.component.tsx +23 -26
- package/src/maternal-and-child-health/partography/graphs/urine-test-graph.component.tsx +1 -1
- package/src/maternal-and-child-health/partography/partograph.component.tsx +323 -171
- package/src/maternal-and-child-health/partography/partography-data-form.scss +23 -0
- package/src/maternal-and-child-health/partography/partography.resource.ts +70 -56
- package/src/maternal-and-child-health/partography/partography.scss +65 -6
- package/src/maternal-and-child-health/partography/resources/fetal-heart-rate.resource.ts +5 -10
- package/src/maternal-and-child-health/partography/resources/oxytocin.resource.ts +5 -3
- package/src/maternal-and-child-health/partography/types/index.ts +40 -37
- package/translations/am.json +1 -1
- package/translations/en.json +1 -1
- package/translations/sw.json +1 -1
- 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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
974
|
-
|
|
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
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
-
|
|
1234
|
+
alert('Failed to save contractions data: ' + saveResult.message);
|
|
1185
1235
|
}
|
|
1186
1236
|
} catch (err) {
|
|
1187
|
-
|
|
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
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
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
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
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
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
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
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
obs.
|
|
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
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
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
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
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
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
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
|
-
|
|
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 || [])
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
? pulseObs.value
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
const
|
|
1523
|
-
|
|
1524
|
-
minDiff
|
|
1525
|
-
|
|
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
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
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
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
(
|
|
1546
|
-
obs
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
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
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
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
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
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]
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
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={
|
|
2387
|
-
tableData={
|
|
2538
|
+
data={getFilteredUrineTestTableData()}
|
|
2539
|
+
tableData={getFilteredUrineTestTableData()}
|
|
2388
2540
|
viewMode={urineTestViewMode}
|
|
2389
2541
|
currentPage={urineTestCurrentPage}
|
|
2390
2542
|
pageSize={urineTestPageSize}
|
|
2391
|
-
totalItems={
|
|
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
|
+
}
|