@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2821 → 5.4.2-pre.2825

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
@@ -53,14 +53,19 @@ import {
53
53
  saveFetalHeartRateData,
54
54
  } from './partography.resource';
55
55
  import styles from './partography.scss';
56
- import { useMembraneAmnioticFluidData } from './resources/membrane-amniotic-fluid.resource';
56
+ import {
57
+ useMembraneAmnioticFluidData,
58
+ useMembraneAmnioticFluidFormData,
59
+ } from './resources/membrane-amniotic-fluid.resource';
57
60
  import { saveOxytocinFormData, useOxytocinData } from './resources/oxytocin.resource';
61
+ import { useTemperatureFormData } from './resources/temperature.resource';
58
62
  import {
59
63
  getColorForGraph,
60
64
  getPartographyTableHeaders,
61
65
  getTranslatedPartographyGraphs,
62
66
  PARTOGRAPHY_CONCEPTS,
63
67
  } from './types/index';
68
+ import { TIME_SAMPLE_COLLECTED, TIME_RESULTS_RETURNED } from '../../config-schema';
64
69
 
65
70
  enum ScaleTypes {
66
71
  LABELS = 'labels',
@@ -330,6 +335,15 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
330
335
  mutate: mutateMembraneAmnioticFluidData,
331
336
  } = useMembraneAmnioticFluidData(patientUuid || '');
332
337
 
338
+ // Membrane amniotic fluid form-specific data for isolated validation
339
+ const {
340
+ membraneAmnioticFluidData: isolatedMembraneData,
341
+ membraneAmnioticFluidTimeEntries,
342
+ isLoading: isMembraneFormDataLoading,
343
+ error: membraneFormDataError,
344
+ mutate: mutateMembraneFormData,
345
+ } = useMembraneAmnioticFluidFormData(patientUuid || '');
346
+
333
347
  //Contractions backend data
334
348
  const {
335
349
  data: cervicalContractionsData,
@@ -341,6 +355,7 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
341
355
  const {
342
356
  oxytocinData: loadedOxytocinData,
343
357
  existingOxytocinEntries,
358
+ existingTimeEntries: oxytocinExistingTimeEntries,
344
359
  isLoading: isOxytocinDataLoading,
345
360
  error: oxytocinDataError,
346
361
  mutate: mutateOxytocinData,
@@ -378,6 +393,15 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
378
393
  mutate: mutateTemperatureData,
379
394
  } = usePartographyData(patientUuid || '', 'temperature');
380
395
 
396
+ // Temperature form-specific data for isolated validation
397
+ const {
398
+ temperatureData: isolatedTemperatureData,
399
+ temperatureTimeEntries,
400
+ isLoading: isTemperatureFormDataLoading,
401
+ error: temperatureFormDataError,
402
+ mutate: mutateTemperatureFormData,
403
+ } = useTemperatureFormData(patientUuid || '');
404
+
381
405
  const {
382
406
  data: urineTestEncounters = [],
383
407
  isLoading: isUrineTestLoading,
@@ -437,22 +461,45 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
437
461
  };
438
462
 
439
463
  const getSampleCollected = () => {
440
- const found = obs.find(
441
- (o) =>
442
- (o.concept.display && o.concept.display.toLowerCase().includes('collected')) ||
443
- (o.concept.uuid && o.concept.uuid.toLowerCase().includes('collected')),
444
- );
464
+ // Try specific TIME_SAMPLE_COLLECTED concept first
465
+ let found = obs.find((o) => {
466
+ // Handle both obs.concept.uuid and obs.concept cases
467
+ const conceptUuid = o.concept?.uuid || o.concept;
468
+ return conceptUuid === TIME_SAMPLE_COLLECTED;
469
+ });
470
+
471
+ // Fallback to display name search
472
+ if (!found) {
473
+ found = obs.find(
474
+ (o) =>
475
+ (o.concept.display && o.concept.display.toLowerCase().includes('collected')) ||
476
+ (o.concept.uuid && o.concept.uuid.toLowerCase().includes('collected')),
477
+ );
478
+ }
479
+
445
480
  if (found) {
446
481
  return found.value != null ? String(found.value) : '';
447
482
  }
448
483
  return '';
449
484
  };
485
+
450
486
  const getResultReturned = () => {
451
- const found = obs.find(
452
- (o) =>
453
- (o.concept.display && o.concept.display.toLowerCase().includes('returned')) ||
454
- (o.concept.uuid && o.concept.uuid.toLowerCase().includes('returned')),
455
- );
487
+ // Try specific TIME_RESULTS_RETURNED concept first
488
+ let found = obs.find((o) => {
489
+ // Handle both obs.concept.uuid and obs.concept cases
490
+ const conceptUuid = o.concept?.uuid || o.concept;
491
+ return conceptUuid === TIME_RESULTS_RETURNED;
492
+ });
493
+
494
+ // Fallback to display name search
495
+ if (!found) {
496
+ found = obs.find(
497
+ (o) =>
498
+ (o.concept.display && o.concept.display.toLowerCase().includes('returned')) ||
499
+ (o.concept.uuid && o.concept.uuid.toLowerCase().includes('returned')),
500
+ );
501
+ }
502
+
456
503
  if (found) {
457
504
  return found.value != null ? String(found.value) : '';
458
505
  }
@@ -523,6 +570,37 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
523
570
  });
524
571
  }, [urineTestEncounters]);
525
572
 
573
+ // Extract dual time arrays for independent progressive validation
574
+ const urineTestTimeArrays = useMemo(() => {
575
+ const sampleCollectedTimes: string[] = [];
576
+ const resultsReturnedTimes: string[] = [];
577
+
578
+ urineTestData.forEach((entry) => {
579
+ if (entry.timeSampleCollected && entry.timeSampleCollected.trim() !== '') {
580
+ sampleCollectedTimes.push(entry.timeSampleCollected.trim());
581
+ }
582
+ if (entry.timeResultsReturned && entry.timeResultsReturned.trim() !== '') {
583
+ resultsReturnedTimes.push(entry.timeResultsReturned.trim());
584
+ }
585
+ });
586
+
587
+ // Sort times chronologically for progressive validation
588
+ const sortTimeArray = (times: string[]) => {
589
+ return times.sort((a, b) => {
590
+ const timeA = a.split(':').map(Number);
591
+ const timeB = b.split(':').map(Number);
592
+ const minutesA = timeA[0] * 60 + timeA[1];
593
+ const minutesB = timeB[0] * 60 + timeB[1];
594
+ return minutesA - minutesB;
595
+ });
596
+ };
597
+
598
+ return {
599
+ sampleCollectedTimes: sortTimeArray([...sampleCollectedTimes]),
600
+ resultsReturnedTimes: sortTimeArray([...resultsReturnedTimes]),
601
+ };
602
+ }, [urineTestData]);
603
+
526
604
  const generateExtendedDummyData = () => {
527
605
  const baseTime = new Date();
528
606
  baseTime.setHours(8, 0, 0, 0);
@@ -724,6 +802,15 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
724
802
 
725
803
  return sorted;
726
804
  }, [localFetalHeartRateData, loadedFetalHeartRateData]);
805
+
806
+ // Create fetal heart rate specific existing time entries for proper validation
807
+ const fetalHeartRateExistingEntries = useMemo(() => {
808
+ return computedFetalHeartRateData.map((entry) => ({
809
+ hour: entry.hour,
810
+ time: entry.time || '',
811
+ }));
812
+ }, [computedFetalHeartRateData]);
813
+
727
814
  const partographGraphs: GraphDefinition[] = useMemo(
728
815
  () => getTranslatedPartographyGraphs(t) as GraphDefinition[],
729
816
  [t],
@@ -1200,7 +1287,9 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1200
1287
  session?.sessionLocation?.uuid,
1201
1288
  session?.currentProvider?.uuid,
1202
1289
  );
1290
+ // Refresh both general membrane data and form-specific data
1203
1291
  await mutateMembraneAmnioticFluidData();
1292
+ await mutateMembraneFormData();
1204
1293
  setIsMembraneAmnioticFluidFormOpen(false);
1205
1294
  };
1206
1295
 
@@ -1322,24 +1411,17 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1322
1411
  };
1323
1412
 
1324
1413
  const handleDrugOrderDataSaved = () => {
1325
- // Drug order data saved, refreshing drug orders...
1326
- // Force refresh the drug orders data from OpenMRS
1327
1414
  mutateDrugOrders();
1328
- // Also trigger a revalidation after a short delay
1329
1415
  setTimeout(() => {
1330
- // Triggering second refresh...
1331
1416
  mutateDrugOrders();
1332
1417
  }, 2000);
1333
1418
  };
1334
-
1335
- // Pulse and BP handlers
1336
1419
  const handlePulseBPFormSubmit = async (formData: { pulse: number; systolicBP: number; diastolicBP: number }) => {
1337
1420
  if (!formData.pulse || !formData.systolicBP || !formData.diastolicBP) {
1338
1421
  alert('Invalid data detected. Please ensure all fields are properly filled.');
1339
1422
  return;
1340
1423
  }
1341
1424
  try {
1342
- // Save both pulse and BP in a single encounter
1343
1425
  await createPartographyEncounter(patientUuid, 'pulse-bp-combined', {
1344
1426
  pulse: formData.pulse,
1345
1427
  systolic: formData.systolicBP,
@@ -1347,15 +1429,12 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1347
1429
  });
1348
1430
  await mutatePulseData();
1349
1431
  await mutateBPData();
1350
- // Force refresh of graph/table by toggling view mode
1351
1432
  setPulseBPViewMode((prev) => (prev === 'table' ? 'graph' : 'table'));
1352
1433
  setTimeout(() => setPulseBPViewMode('graph'), 0);
1353
1434
  } catch (error) {
1354
1435
  alert('Failed to save Pulse & BP data.');
1355
1436
  }
1356
1437
  };
1357
-
1358
- // Temperature backend save handler
1359
1438
  const handleTemperatureFormSubmit = async (formData: {
1360
1439
  timeSlot: string;
1361
1440
  exactTime: string;
@@ -1365,13 +1444,13 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1365
1444
  alert('Invalid data detected. Please ensure all fields are properly filled.');
1366
1445
  return;
1367
1446
  }
1368
- // Save to backend
1369
1447
  try {
1370
1448
  await createPartographyEncounter(patientUuid, 'temperature', {
1371
1449
  value: formData.temperature,
1372
1450
  time: formData.exactTime,
1373
1451
  });
1374
1452
  await mutateTemperatureData();
1453
+ await mutateTemperatureFormData();
1375
1454
  setIsTemperatureFormOpen(false);
1376
1455
  } catch (error) {
1377
1456
  alert('Failed to save temperature data.');
@@ -1412,9 +1491,11 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1412
1491
  timeResultsReturned: formData.timeResultsReturned,
1413
1492
  eventDescription: `Time Slot: ${currentTime}`,
1414
1493
  });
1494
+ await mutateUrineTestData();
1495
+
1415
1496
  setIsUrineTestFormOpen(false);
1416
1497
  } catch (error) {
1417
- alert('Failed to save urine test data.');
1498
+ alert('Failed to save data.');
1418
1499
  }
1419
1500
  };
1420
1501
 
@@ -1431,7 +1512,6 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
1431
1512
  }));
1432
1513
  };
1433
1514
 
1434
- // Generate table data for fetal heart rate
1435
1515
  const getFetalHeartRateTableData = () => {
1436
1516
  return computedFetalHeartRateData
1437
1517
  .filter((data) => data.value !== undefined && data.value !== null)
@@ -2442,7 +2522,7 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
2442
2522
 
2443
2523
  <OxytocinGraph
2444
2524
  data={loadedOxytocinData.map((item) => ({
2445
- timeSlot: item.time ?? '',
2525
+ timeSlot: item.timeDisplay ?? item.time ?? '',
2446
2526
  oxytocinUsed: typeof item.dropsPerMinute === 'number' && item.dropsPerMinute > 0 ? 'yes' : 'no',
2447
2527
  dropsPerMinute: typeof item.dropsPerMinute === 'number' ? item.dropsPerMinute : 0,
2448
2528
  date: item.encounterDatetime ? new Date(item.encounterDatetime).toLocaleDateString() : '',
@@ -2451,7 +2531,7 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
2451
2531
  tableData={loadedOxytocinData.map((item, index) => ({
2452
2532
  id: item.uuid || `oxy-${index}`,
2453
2533
  date: item.encounterDatetime ? new Date(item.encounterDatetime).toLocaleDateString() : '',
2454
- timeSlot: item.time ?? '',
2534
+ timeSlot: item.timeDisplay ?? item.time ?? '',
2455
2535
  oxytocinUsed: typeof item.dropsPerMinute === 'number' && item.dropsPerMinute > 0 ? 'yes' : 'no',
2456
2536
  dropsPerMinute: typeof item.dropsPerMinute === 'number' ? `${item.dropsPerMinute} drops/min` : 'N/A',
2457
2537
  }))}
@@ -2578,7 +2658,7 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
2578
2658
  onClose={handleFetalHeartRateFormClose}
2579
2659
  onSubmit={handleFetalHeartRateFormSubmit}
2580
2660
  onDataSaved={handleFetalHeartRateDataSaved}
2581
- existingTimeEntries={computedExistingTimeEntries}
2661
+ existingTimeEntries={fetalHeartRateExistingEntries}
2582
2662
  />
2583
2663
  )}
2584
2664
  {isMembraneAmnioticFluidFormOpen && (
@@ -2586,6 +2666,15 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
2586
2666
  isOpen={isMembraneAmnioticFluidFormOpen}
2587
2667
  onClose={handleMembraneAmnioticFluidFormClose}
2588
2668
  onSubmit={handleMembraneAmnioticFluidFormSubmit}
2669
+ existingTimeEntries={membraneAmnioticFluidTimeEntries.map((entry) => ({
2670
+ timeSlot: entry.timeSlot,
2671
+ exactTime: entry.time,
2672
+ }))}
2673
+ patient={patientProp}
2674
+ onDataSaved={() => {
2675
+ mutateMembraneFormData();
2676
+ mutateMembraneAmnioticFluidData();
2677
+ }}
2589
2678
  />
2590
2679
  )}
2591
2680
  {isCervicalContractionsFormOpen && (
@@ -2600,7 +2689,19 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
2600
2689
  isOpen={isOxytocinFormOpen}
2601
2690
  onClose={handleOxytocinFormClose}
2602
2691
  onSubmit={handleOxytocinFormSubmit}
2603
- existingTimeEntries={existingTimeEntries}
2692
+ existingTimeEntries={oxytocinExistingTimeEntries}
2693
+ patient={{
2694
+ uuid: patientUuid || '',
2695
+ name:
2696
+ patientData?.name && patientData.name.length > 0
2697
+ ? (patientData.name[0].given ? patientData.name[0].given.join(' ') + ' ' : '') +
2698
+ (patientData.name[0].family || '')
2699
+ : '',
2700
+ gender: patientData?.gender || '',
2701
+ age: patientData?.birthDate
2702
+ ? new Date().getFullYear() - new Date(patientData.birthDate).getFullYear() + ''
2703
+ : '',
2704
+ }}
2604
2705
  />
2605
2706
  )}
2606
2707
  {isTemperatureFormOpen && (
@@ -2609,7 +2710,12 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
2609
2710
  onClose={() => setIsTemperatureFormOpen(false)}
2610
2711
  onSubmit={handleTemperatureFormSubmit}
2611
2712
  initialTime={temperatureFormInitialTime}
2612
- existingTimeEntries={existingTimeEntries}
2713
+ existingTimeEntries={temperatureTimeEntries}
2714
+ patient={patientProp}
2715
+ onDataSaved={() => {
2716
+ mutateTemperatureFormData();
2717
+ mutateTemperatureData();
2718
+ }}
2613
2719
  />
2614
2720
  )}
2615
2721
  {isUrineTestFormOpen && (
@@ -2617,7 +2723,12 @@ const Partograph: React.FC<PartographyProps> = ({ patientUuid }) => {
2617
2723
  isOpen={isUrineTestFormOpen}
2618
2724
  onClose={() => setIsUrineTestFormOpen(false)}
2619
2725
  onSubmit={handleUrineTestFormSubmit}
2620
- existingTimeEntries={existingTimeEntries}
2726
+ encounters={urineTestEncounters}
2727
+ isLoading={isUrineTestLoading}
2728
+ error={urineTestError}
2729
+ patient={patientProp}
2730
+ sampleCollectedTimes={urineTestTimeArrays.sampleCollectedTimes}
2731
+ resultsReturnedTimes={urineTestTimeArrays.resultsReturnedTimes}
2621
2732
  />
2622
2733
  )}
2623
2734
  </Column>
@@ -49,8 +49,6 @@
49
49
  }
50
50
  }
51
51
 
52
- /* ===== Merged from cervix-form.scss ===== */
53
-
54
52
  .formGrid {
55
53
  gap: 1rem;
56
54
  }
@@ -62,6 +60,19 @@
62
60
  .formField {
63
61
  margin-bottom: 1rem;
64
62
  }
63
+ // Ensure Carbon dropdown menus overlap modal buttons
64
+ :global(.cds--list-box__menu) {
65
+ z-index: 9999 !important;
66
+ position: absolute !important;
67
+ overflow: visible !important;
68
+ }
69
+ // Prevent parent overflow from hiding dropdowns
70
+ .modalContent,
71
+ .cds--modal-content,
72
+ .cds--modal-container {
73
+ overflow: visible !important;
74
+ z-index: 1 !important;
75
+ }
65
76
 
66
77
  .fieldLabel {
67
78
  @include type.type-style('label-01');
@@ -70,12 +81,10 @@
70
81
  display: block;
71
82
  }
72
83
 
73
- /* Ensure number inputs have proper styling */
74
84
  .cds--number input {
75
85
  text-align: left;
76
86
  }
77
87
 
78
- /* Modal specific styling */
79
88
  .modal {
80
89
  .cds--modal-content {
81
90
  padding: 0;
@@ -96,12 +105,10 @@
96
105
  }
97
106
  }
98
107
 
99
- /* Form field spacing */
100
108
  .cds--form-item {
101
109
  margin-bottom: 1rem;
102
110
  }
103
111
 
104
- /* Number input stepper buttons */
105
112
  .cds--number__controls {
106
113
  display: flex;
107
114
  flex-direction: column;
@@ -115,7 +122,6 @@
115
122
  justify-content: center;
116
123
  }
117
124
 
118
- /* Validation hints */
119
125
  .validationHint {
120
126
  font-size: 0.75rem;
121
127
  color: #6f6f6f;
@@ -127,7 +133,6 @@
127
133
  border-left: 3px solid #0f62fe;
128
134
  }
129
135
 
130
- /* Required fields note */
131
136
  .requiredFieldsNote {
132
137
  margin-bottom: 1rem;
133
138
  padding: 0.5rem;
@@ -143,7 +148,6 @@
143
148
  }
144
149
  }
145
150
 
146
- /* Drug Order Workspace Launcher Styles */
147
151
  .workspaceLauncherSection {
148
152
  margin-bottom: layout.$spacing-06;
149
153
  padding: layout.$spacing-05;
@@ -168,7 +172,6 @@
168
172
  }
169
173
  }
170
174
 
171
- /* Manual Entry Section Styles */
172
175
  .manualEntrySection {
173
176
  background-color: var(--cds-layer-01);
174
177
  border: 1px solid var(--cds-border-subtle-01);
@@ -191,7 +194,6 @@
191
194
  }
192
195
  }
193
196
 
194
- // Source indicators for drug orders vs manual entries
195
197
  .orderSource {
196
198
  background-color: var(--cds-support-success);
197
199
  color: var(--cds-text-on-color);
@@ -212,7 +214,6 @@
212
214
  text-transform: uppercase;
213
215
  }
214
216
 
215
- /* Override validation styling when not needed */
216
217
  .noValidation {
217
218
  .cds--select__invalid-icon,
218
219
  .cds--number__invalid-icon {
@@ -234,3 +235,21 @@
234
235
  display: none !important;
235
236
  }
236
237
  }
238
+
239
+ .disabledHourOption {
240
+ color: colors.$gray-40 !important;
241
+ background-color: colors.$gray-10 !important;
242
+ opacity: 0.6 !important;
243
+ font-style: italic;
244
+
245
+ &:hover {
246
+ background-color: colors.$gray-10 !important;
247
+ color: colors.$gray-40 !important;
248
+ }
249
+ }
250
+
251
+ .hourSelectionHelper {
252
+ @include type.type-style('helper-text-01');
253
+ color: colors.$blue-60;
254
+ margin-top: layout.$spacing-02;
255
+ }