@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2788 → 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
@@ -107,13 +107,26 @@ import {
107
107
  PARTOGRAPHY_CONCEPTS,
108
108
  PARTOGRAPHY_ENCOUNTER_TYPES,
109
109
  MCH_PARTOGRAPHY_ENCOUNTER_UUID,
110
+ URINE_TEST_TIME_SLOT_CONCEPT,
110
111
  getPartographyUnit,
111
112
  type OpenMRSResponse,
112
113
  type PartographyObservation,
113
114
  type PartographyEncounter,
114
115
  type PartographyGraphType,
115
116
  } from './types';
116
- import { configSchema, type ConfigObject } from '../../config-schema';
117
+ import {
118
+ configSchema,
119
+ type ConfigObject,
120
+ TIME_RESULTS_RETURNED,
121
+ TIME_SAMPLE_COLLECTED,
122
+ EVENT_DESCRIPTION_CONCEPT,
123
+ CONTRACTION_COUNT_CONCEPT,
124
+ UTERINE_CONTRACTIONS_CONCEPT,
125
+ CONTRACTION_LEVEL_MILD_CONCEPT,
126
+ CONTRACTION_LEVEL_MODERATE_CONCEPT,
127
+ CONTRACTION_LEVEL_STRONG_CONCEPT,
128
+ MOULDING_NONE_CONCEPT,
129
+ } from '../../config-schema';
117
130
 
118
131
  export type { PartographyObservation, PartographyEncounter };
119
132
  const defaultPartographyConfig = configSchema.partography._default;
@@ -359,6 +372,7 @@ export async function createPartographyEncounter(
359
372
  formData: any,
360
373
  locationUuid?: string,
361
374
  providerUuid?: string,
375
+ signal?: AbortSignal,
362
376
  t?: (key: string, fallback?: string) => string,
363
377
  ): Promise<{ success: boolean; message: string; encounter?: PartographyEncounter }> {
364
378
  try {
@@ -408,7 +422,7 @@ export async function createPartographyEncounter(
408
422
  location: finalLocationUuid,
409
423
  encounterDatetime: encounterDatetime,
410
424
  obs: observations,
411
- encounterType: encounterTypeUuid,
425
+ encounterType: { uuid: encounterTypeUuid },
412
426
  };
413
427
 
414
428
  let finalProviderUuid = providerUuid;
@@ -475,13 +489,12 @@ export async function createPartographyEncounter(
475
489
  throw new Error('At least one observation is required');
476
490
  }
477
491
 
478
- // Add debugging to see what payload is being sent
479
- // Debug: encounterPayload being sent
480
-
481
- const response = await openmrsFetch(`${restBaseUrl}/encounter`, {
492
+ const encounterUrl = `${restBaseUrl}/encounter`;
493
+ const response = await openmrsFetch(encounterUrl, {
482
494
  method: 'POST',
483
495
  headers: { 'Content-Type': 'application/json' },
484
496
  body: JSON.stringify(encounterPayload),
497
+ signal: signal, // Add abort signal support
485
498
  });
486
499
 
487
500
  if (!response.ok) {
@@ -651,14 +664,14 @@ function buildObservations(graphType: string, formData: any): any[] {
651
664
  });
652
665
  }
653
666
 
654
- // Save hour as a number (not string)
667
+ // Save hour as a string in the format 'Hour: X' to match backend expectations
655
668
  if (formData.hour !== undefined && formData.hour !== '') {
656
669
  try {
657
670
  const hourValue = parseFloat(formData.hour);
658
- if (hourValue >= 0 && hourValue <= 24) {
671
+ if (!isNaN(hourValue) && hourValue >= 0 && hourValue <= 24) {
659
672
  observations.push({
660
673
  concept: PARTOGRAPHY_CONCEPTS['fetal-heart-rate-hour'],
661
- value: hourValue,
674
+ value: `Hour: ${hourValue}`,
662
675
  obsDatetime,
663
676
  });
664
677
  }
@@ -740,7 +753,7 @@ function buildObservations(graphType: string, formData: any): any[] {
740
753
  }
741
754
  if (formData.timeSlot) {
742
755
  observations.push({
743
- concept: '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
756
+ concept: URINE_TEST_TIME_SLOT_CONCEPT,
744
757
  value: `Time: ${formData.timeSlot}`,
745
758
  obsDatetime,
746
759
  });
@@ -821,7 +834,22 @@ function buildObservations(graphType: string, formData: any): any[] {
821
834
  obsDatetime,
822
835
  });
823
836
  }
824
- // Remove time-slot obs to avoid OpenMRS errors
837
+
838
+ if (formData.timeSlot) {
839
+ observations.push({
840
+ concept: URINE_TEST_TIME_SLOT_CONCEPT,
841
+ value: `Time: ${formData.timeSlot}`,
842
+ obsDatetime,
843
+ });
844
+ }
845
+
846
+ if (formData.timeSampleCollected) {
847
+ observations.push({
848
+ concept: TIME_SAMPLE_COLLECTED,
849
+ value: formData.timeSampleCollected,
850
+ obsDatetime,
851
+ });
852
+ }
825
853
  if (formData.eventDescription) {
826
854
  observations.push({
827
855
  concept: PARTOGRAPHY_CONCEPTS['event-description'],
@@ -829,10 +857,11 @@ function buildObservations(graphType: string, formData: any): any[] {
829
857
  obsDatetime,
830
858
  });
831
859
  }
860
+
832
861
  if (formData.timeResultsReturned) {
833
862
  observations.push({
834
- concept: PARTOGRAPHY_CONCEPTS['event-description'],
835
- value: `Results Returned: ${formData.timeResultsReturned}`,
863
+ concept: TIME_RESULTS_RETURNED,
864
+ value: formData.timeResultsReturned,
836
865
  obsDatetime,
837
866
  });
838
867
  }
@@ -853,7 +882,7 @@ function buildObservations(graphType: string, formData: any): any[] {
853
882
  obsDatetime,
854
883
  });
855
884
  }
856
- // Add route and frequency as event descriptions
885
+
857
886
  if (formData.route) {
858
887
  observations.push({
859
888
  concept: PARTOGRAPHY_CONCEPTS['event-description'],
@@ -934,22 +963,20 @@ export function transformEncounterToChartData(encounters: PartographyEncounter[]
934
963
  const conceptUuid = obs.concept.uuid;
935
964
  let value = null;
936
965
  let groupName = '';
937
- // Robustly handle uterine contractions: both numeric and coded contraction level
966
+
938
967
  if (graphType === 'uterine-contractions') {
939
- // Numeric value for graphing
940
968
  if (conceptUuid === PARTOGRAPHY_CONCEPTS['uterine-contractions']) {
941
969
  value = parseFloat(obs.value as string);
942
970
  groupName = getGraphTypeDisplayName('uterine-contractions');
943
971
  }
944
- // Coded contraction level (none, mild, moderate, strong)
972
+
945
973
  const contractionLevelMap: Record<string, string> = {
946
- '1107AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': 'none',
947
- '1498AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': 'mild',
948
- '1499AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': 'moderate',
949
- '166788AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': 'strong',
974
+ MOULDING_NONE_CONCEPT: 'none',
975
+ CONTRACTION_LEVEL_MILD_CONCEPT: 'mild',
976
+ CONTRACTION_LEVEL_MODERATE_CONCEPT: 'moderate',
977
+ CONTRACTION_LEVEL_STRONG_CONCEPT: 'strong',
950
978
  };
951
979
  if (contractionLevelMap[conceptUuid]) {
952
- // For charting, you may want to assign a numeric value for each level
953
980
  const contractionLevelValueMap: Record<string, number> = {
954
981
  none: 0,
955
982
  mild: 1,
@@ -1025,11 +1052,8 @@ export function transformEncounterToChartData(encounters: PartographyEncounter[]
1025
1052
  });
1026
1053
 
1027
1054
  return chartData.sort((a, b) => {
1028
- // Parse time strings (HH:MM format) for comparison
1029
1055
  const timeA = a.time.split(':').map(Number);
1030
1056
  const timeB = b.time.split(':').map(Number);
1031
-
1032
- // Convert to minutes for easy comparison
1033
1057
  const minutesA = timeA[0] * 60 + (timeA[1] || 0);
1034
1058
  const minutesB = timeB[0] * 60 + (timeB[1] || 0);
1035
1059
 
@@ -1104,16 +1128,14 @@ export function transformEncounterToTableData(
1104
1128
  })}`;
1105
1129
 
1106
1130
  if (graphType === 'uterine-contractions') {
1107
- // Group obs by time for uterine contractions
1108
1131
  let timeSlot = '';
1109
1132
  let contractionCount = '';
1110
1133
  let contractionLevel = 'none';
1111
1134
  if (Array.isArray(encounter.obs)) {
1112
- // Always use the last seen contraction level in the encounter (in case order is not guaranteed)
1113
1135
  for (const obs of encounter.obs) {
1114
1136
  // Time slot
1115
1137
  if (
1116
- obs.concept.uuid === '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' &&
1138
+ obs.concept.uuid === EVENT_DESCRIPTION_CONCEPT &&
1117
1139
  typeof obs.value === 'string' &&
1118
1140
  obs.value.startsWith('Time:')
1119
1141
  ) {
@@ -1122,30 +1144,30 @@ export function transformEncounterToTableData(
1122
1144
  timeSlot = match[1].trim();
1123
1145
  }
1124
1146
  }
1125
- // Contraction count (numeric)
1126
- if (obs.concept.uuid === '159682AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') {
1147
+
1148
+ if (obs.concept.uuid === CONTRACTION_COUNT_CONCEPT) {
1127
1149
  contractionCount = String(obs.value);
1128
1150
  }
1129
1151
  }
1130
- // Now, after looping, get the latest contraction level (if any)
1152
+
1131
1153
  for (const obs of encounter.obs) {
1132
- if (obs.concept.uuid === '163750AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') {
1154
+ if (obs.concept.uuid === UTERINE_CONTRACTIONS_CONCEPT) {
1133
1155
  let levelUuid = '';
1134
1156
  if (typeof obs.value === 'string') {
1135
1157
  levelUuid = obs.value;
1136
1158
  } else if (obs.value != null && typeof obs.value === 'object' && (obs.value as any)?.uuid) {
1137
1159
  levelUuid = (obs.value as any).uuid;
1138
1160
  }
1139
- if (levelUuid === '1107AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') {
1161
+ if (levelUuid === MOULDING_NONE_CONCEPT) {
1140
1162
  contractionLevel = 'none';
1141
1163
  }
1142
- if (levelUuid === '1498AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') {
1164
+ if (levelUuid === CONTRACTION_LEVEL_MILD_CONCEPT) {
1143
1165
  contractionLevel = 'mild';
1144
1166
  }
1145
- if (levelUuid === '1499AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') {
1167
+ if (levelUuid === CONTRACTION_LEVEL_MODERATE_CONCEPT) {
1146
1168
  contractionLevel = 'moderate';
1147
1169
  }
1148
- if (levelUuid === '166788AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') {
1170
+ if (levelUuid === CONTRACTION_LEVEL_STRONG_CONCEPT) {
1149
1171
  contractionLevel = 'strong';
1150
1172
  }
1151
1173
  }
@@ -1162,7 +1184,6 @@ export function transformEncounterToTableData(
1162
1184
  });
1163
1185
  }
1164
1186
  } else {
1165
- // ...existing code for other graph types...
1166
1187
  encounter.obs.forEach((obs, obsIndex) => {
1167
1188
  try {
1168
1189
  if (typeof obs.value === 'string') {
@@ -1266,11 +1287,9 @@ export function transformEncounterToTableData(
1266
1287
  );
1267
1288
  }
1268
1289
 
1269
- // Hook to fetch and manage fetal heart rate data from OpenMRS
1270
1290
  export function useFetalHeartRateData(patientUuid: string) {
1271
1291
  const fetcher = (url: string) => openmrsFetch(url).then((res) => res.json());
1272
1292
 
1273
- // Fetch encounters instead of just observations to get all related data
1274
1293
  const { data, error, isLoading, mutate } = useSWR(
1275
1294
  patientUuid
1276
1295
  ? `${restBaseUrl}/encounter?patient=${patientUuid}&encounterType=${MCH_PARTOGRAPHY_ENCOUNTER_UUID}&v=full&limit=100&order=desc`
@@ -1296,7 +1315,6 @@ export function useFetalHeartRateData(patientUuid: string) {
1296
1315
  continue;
1297
1316
  }
1298
1317
 
1299
- // Look for fetal heart rate observations in this encounter
1300
1318
  const fetalHeartRateObs = encounter.obs.find(
1301
1319
  (obs) => obs.concept.uuid === PARTOGRAPHY_CONCEPTS['fetal-heart-rate'],
1302
1320
  );
@@ -1304,11 +1322,9 @@ export function useFetalHeartRateData(patientUuid: string) {
1304
1322
  if (fetalHeartRateObs) {
1305
1323
  const encounterDatetime = new Date(encounter.encounterDatetime);
1306
1324
 
1307
- // Look for hour and time observations with text format
1308
1325
  let hour = 0;
1309
1326
  let time = '';
1310
1327
 
1311
- // Find hour observation (saved as "Hour: X.X")
1312
1328
  const hourObs = encounter.obs.find(
1313
1329
  (obs) =>
1314
1330
  obs.concept.uuid === PARTOGRAPHY_CONCEPTS['fetal-heart-rate-hour'] &&
@@ -1317,7 +1333,6 @@ export function useFetalHeartRateData(patientUuid: string) {
1317
1333
  obs.value.startsWith('Hour:'),
1318
1334
  );
1319
1335
 
1320
- // Find time observation (saved as "Time: XX:XX")
1321
1336
  const timeObs = encounter.obs.find(
1322
1337
  (obs) =>
1323
1338
  obs.concept.uuid === PARTOGRAPHY_CONCEPTS['fetal-heart-rate-time'] &&
@@ -1326,7 +1341,6 @@ export function useFetalHeartRateData(patientUuid: string) {
1326
1341
  obs.value.startsWith('Time:'),
1327
1342
  );
1328
1343
 
1329
- // Parse hour from text format "Hour: 2.5"
1330
1344
  if (hourObs && typeof hourObs.value === 'string') {
1331
1345
  const hourMatch = hourObs.value.match(/Hour:\s*([0-9.]+)/);
1332
1346
  if (hourMatch) {
@@ -1334,7 +1348,6 @@ export function useFetalHeartRateData(patientUuid: string) {
1334
1348
  }
1335
1349
  }
1336
1350
 
1337
- // Parse time from text format "Time: 03:15"
1338
1351
  if (timeObs && typeof timeObs.value === 'string') {
1339
1352
  const timeMatch = timeObs.value.match(/Time:\s*(.+)/);
1340
1353
  if (timeMatch) {
@@ -1412,11 +1425,19 @@ export async function saveDrugOrderData(
1412
1425
  route: string;
1413
1426
  frequency: string;
1414
1427
  },
1428
+ locationUuid?: string,
1429
+ providerUuid?: string,
1430
+ signal?: AbortSignal,
1415
1431
  ) {
1416
1432
  try {
1417
- // Use the existing createPartographyEncounter function with drugs-fluids graph type
1418
- const result = await createPartographyEncounter(patientUuid, 'drugs-fluids', formData);
1419
-
1433
+ const result = await createPartographyEncounter(
1434
+ patientUuid,
1435
+ 'drugs-fluids',
1436
+ formData,
1437
+ locationUuid,
1438
+ providerUuid,
1439
+ signal,
1440
+ );
1420
1441
  return result;
1421
1442
  } catch (error) {
1422
1443
  console.error('Error saving drug order data:', error);
@@ -1427,9 +1448,7 @@ export async function saveDrugOrderData(
1427
1448
  }
1428
1449
  }
1429
1450
 
1430
- // Function to fetch drug orders for a patient
1431
1451
  export function useDrugOrders(patientUuid: string) {
1432
- // Remove the problematic sort parameter that's causing 500 error
1433
1452
  const apiUrl = patientUuid
1434
1453
  ? `${restBaseUrl}/order?patient=${patientUuid}&orderType=131168f4-15f5-102d-96e4-000c29c2a5d7&v=full&limit=50`
1435
1454
  : null;
@@ -1440,23 +1459,18 @@ export function useDrugOrders(patientUuid: string) {
1440
1459
  const responseData = data?.data as any;
1441
1460
 
1442
1461
  if (!responseData?.results || !Array.isArray(responseData.results)) {
1443
- // console.log('Drug Orders: No results found');
1444
1462
  return [];
1445
1463
  }
1446
1464
 
1447
1465
  const allOrders = responseData.results;
1448
- // Filter for active orders and manually sort by dateActivated (newest first)
1449
1466
  const activeOrders = allOrders
1450
1467
  .filter((order: any) => order.action === 'NEW' && !order.dateStopped)
1451
1468
  .sort((a: any, b: any) => {
1452
- // Manual sorting by dateActivated, handling null values
1453
1469
  const dateA = a.dateActivated ? new Date(a.dateActivated).getTime() : 0;
1454
1470
  const dateB = b.dateActivated ? new Date(b.dateActivated).getTime() : 0;
1455
- return dateB - dateA; // Descending order (newest first)
1471
+ return dateB - dateA;
1456
1472
  });
1457
1473
 
1458
- // console.log(`Drug Orders: Found ${allOrders.length} total orders, ${activeOrders.length} active orders`);
1459
-
1460
1474
  const processedOrders = activeOrders.map((order: any) => {
1461
1475
  const processed = {
1462
1476
  id: order.uuid,
@@ -204,10 +204,10 @@
204
204
 
205
205
  .chartContainer {
206
206
  background-color: colors.$white;
207
- padding: layout.$spacing-04;
207
+ padding: 0;
208
208
  border-radius: layout.$spacing-02;
209
209
  border: 2px solid colors.$gray-40;
210
- margin-bottom: layout.$spacing-04;
210
+ margin-bottom: 0;
211
211
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
212
212
 
213
213
  // Medical chart styling
@@ -888,6 +888,13 @@
888
888
  flex-wrap: wrap;
889
889
  }
890
890
 
891
+ // Orange tag style for low range
892
+ .tagLowRange {
893
+ background-color: #ff9800 !important;
894
+ color: #fff !important;
895
+ border-color: #ff9800 !important;
896
+ }
897
+
891
898
  .fetalHeartRateChart {
892
899
  min-height: 600px; // Increased height to match cervix graph
893
900
  width: 100%;
@@ -996,7 +1003,7 @@
996
1003
  border-radius: layout.$spacing-02;
997
1004
  overflow-x: auto;
998
1005
  max-height: 300px; // Set a reasonable height
999
- max-width: 1050px; // Show approximately 13 grids (140px label + 70px * 13 columns = 1050px)
1006
+ max-width: 1540px; // Show approximately 20 grids (140px label + 70px * 20 columns = 1540px)
1000
1007
  }
1001
1008
 
1002
1009
  .gridContainer {
@@ -1265,7 +1272,7 @@
1265
1272
  }
1266
1273
  }
1267
1274
 
1268
- // Detailed Cervical Contractions Grid - Partograph Format
1275
+ // Detailed Contractions Grid - Partograph Format
1269
1276
  .contractionsContainer {
1270
1277
  display: flex;
1271
1278
  flex-direction: column;
@@ -1515,13 +1522,13 @@
1515
1522
  color: colors.$gray-80;
1516
1523
  }
1517
1524
 
1518
- // Grid container for cervical contractions
1525
+ // Grid container for contractions
1519
1526
  .contractionsGrid {
1520
1527
  position: relative;
1521
1528
  margin: layout.$spacing-04 0;
1522
1529
  }
1523
1530
 
1524
- // Table view styles for cervical contractions
1531
+ // Table view styles for contractions
1525
1532
  .contractionsTable {
1526
1533
  background-color: colors.$white;
1527
1534
  border: 1px solid colors.$gray-30;
@@ -1589,6 +1596,58 @@
1589
1596
  margin: layout.$spacing-05 0;
1590
1597
  }
1591
1598
 
1599
+ .oxytocinGraph {
1600
+ width: 100%;
1601
+ margin: 0;
1602
+
1603
+ .membraneGrid {
1604
+ max-height: fit-content;
1605
+ min-height: auto;
1606
+ margin-top: 0;
1607
+ margin-bottom: 0;
1608
+ padding-bottom: 0;
1609
+ }
1610
+
1611
+ .gridContainer {
1612
+ padding: 0;
1613
+ margin-bottom: 0;
1614
+ }
1615
+
1616
+ .gridRow {
1617
+ min-height: 50px;
1618
+ margin-bottom: 0;
1619
+
1620
+ &:last-child {
1621
+ border-bottom: 1px solid colors.$gray-20;
1622
+ }
1623
+ }
1624
+
1625
+ .gridCell {
1626
+ border: 1px solid colors.$gray-30;
1627
+ min-height: 50px;
1628
+ }
1629
+ }
1630
+
1631
+ // Oxytocin specific styling when inside fetalHeartRateContainer
1632
+ .fetalHeartRateContainer {
1633
+ .membraneGrid {
1634
+ margin: 0;
1635
+ padding: 0;
1636
+ max-height: fit-content;
1637
+ min-height: auto;
1638
+ }
1639
+
1640
+ .gridContainer {
1641
+ padding: 0;
1642
+ margin: 0;
1643
+ }
1644
+
1645
+ .gridRow {
1646
+ margin: 0;
1647
+ min-height: 50px;
1648
+ }
1649
+ }
1650
+
1592
1651
  .drugsCell {
1593
1652
  position: relative;
1594
1653
  background-color: colors.$white;
@@ -4,6 +4,7 @@ import useSWR from 'swr';
4
4
  import { MCH_PARTOGRAPHY_ENCOUNTER_UUID, PARTOGRAPHY_CONCEPTS } from '../types';
5
5
  import { createPartographyEncounter } from '../partography.resource';
6
6
  import { useTranslation } from 'react-i18next';
7
+ import { FETAL_HEART_RATE_HOUR_CONCEPT } from '../../../config-schema';
7
8
 
8
9
  export interface FetalHeartRateEntry {
9
10
  id: string;
@@ -42,12 +43,9 @@ export function useFetalHeartRateData(patientUuid: string) {
42
43
  const encounterDatetime = new Date(encounter.encounterDatetime);
43
44
  let hour = 0;
44
45
  let time = '';
46
+ // Find hour obs by concept and use its value as number
45
47
  const hourObs = encounter.obs.find(
46
- (obs) =>
47
- obs.concept.uuid === PARTOGRAPHY_CONCEPTS['fetal-heart-rate-hour'] &&
48
- obs.value &&
49
- typeof obs.value === 'string' &&
50
- obs.value.startsWith('Hour:'),
48
+ (obs) => obs.concept.uuid === FETAL_HEART_RATE_HOUR_CONCEPT && typeof obs.value === 'number',
51
49
  );
52
50
  const timeObs = encounter.obs.find(
53
51
  (obs) =>
@@ -56,11 +54,8 @@ export function useFetalHeartRateData(patientUuid: string) {
56
54
  typeof obs.value === 'string' &&
57
55
  obs.value.startsWith('Time:'),
58
56
  );
59
- if (hourObs && typeof hourObs.value === 'string') {
60
- const hourMatch = hourObs.value.match(/Hour:\s*([0-9.]+)/);
61
- if (hourMatch) {
62
- hour = parseFloat(hourMatch[1]) || 0;
63
- }
57
+ if (hourObs && typeof hourObs.value === 'number') {
58
+ hour = hourObs.value;
64
59
  }
65
60
  if (timeObs && typeof timeObs.value === 'string') {
66
61
  const timeMatch = timeObs.value.match(/Time:\s*(.+)/);
@@ -96,13 +96,15 @@ export async function saveOxytocinFormData(
96
96
  if (!finalLocationUuid) {
97
97
  return { success: false, message: t('Location information is required'), error: 'NO_LOCATION' };
98
98
  }
99
- const now = new Date();
100
- now.setMinutes(now.getMinutes() - 1);
99
+ // Use utility to ensure encounterDatetime is always in the past
100
+ // Import getValidEncounterDatetime from types/index if not already imported
101
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
102
+ const { getValidEncounterDatetime } = require('../types');
101
103
  const encounterPayload = {
102
104
  patient: patientUuid,
103
105
  encounterType: MCH_PARTOGRAPHY_ENCOUNTER_UUID,
104
106
  location: finalLocationUuid,
105
- encounterDatetime: toOmrsIsoString(now),
107
+ encounterDatetime: getValidEncounterDatetime(),
106
108
  obs: observations,
107
109
  encounterProviders: [
108
110
  {
@@ -35,11 +35,13 @@ export const contractionLevelUuidMap: Record<string, string> = {
35
35
  moderate: '1499AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
36
36
  strong: '166788AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
37
37
  };
38
- // Utility: Map urine test code/label to plus notation
39
- // Default Fetal Heart Rate Chart Data
40
- // ...existing code...
41
- // ...existing code...
42
- // Uterine Contraction Level Options (for Cervical Contractions Form)
38
+ export function getValidEncounterDatetime(): string {
39
+ const now = new Date();
40
+ now.setMinutes(now.getMinutes() - 4);
41
+ return now.toISOString();
42
+ }
43
+ export const URINE_TEST_TIME_SLOT_CONCEPT = 'bb3724c9-fbcc-49c5-9702-6cde0be325ca';
44
+
43
45
  export const contractionLevelOptions = [
44
46
  {
45
47
  value: 'none',
@@ -140,6 +142,8 @@ import {
140
142
  CONTRACTION_LEVEL_MILD_CONCEPT,
141
143
  CONTRACTION_LEVEL_MODERATE_CONCEPT,
142
144
  CONTRACTION_LEVEL_STRONG_CONCEPT,
145
+ OXYTOCIN_DROPS_PER_MINUTE_CONCEPT,
146
+ OXYTOCIN_TIME_CONCEPT,
143
147
  } from '../../../config-schema';
144
148
 
145
149
  const _CODE_TO_PLUS_MAP: Record<string, string> = {
@@ -151,10 +155,20 @@ const _CODE_TO_PLUS_MAP: Record<string, string> = {
151
155
  '++': '++',
152
156
  [MOULDING_SEVERE_CONCEPT]: '+++',
153
157
  '+++': '+++',
158
+ NEGATIVE: '0',
159
+ ZERO: '0',
160
+ 'ONE PLUS': '+',
161
+ '1+': '+',
162
+ 'TWO PLUS': '++',
163
+ '2+': '++',
164
+ 'THREE PLUS': '+++',
165
+ '3+': '+++',
166
+ TRACE: '+/-',
167
+ '+/-': '+/-',
154
168
  };
155
169
 
156
170
  export const codeToPlus = (raw?: string): string => {
157
- const key = (raw ?? '').toString().trim();
171
+ const key = (raw ?? '').toString().trim().toUpperCase();
158
172
  return _CODE_TO_PLUS_MAP[key] ?? raw ?? '';
159
173
  };
160
174
  const _partoConcepts = (configSchema as any)?.partography?._default?.conceptUuids ?? {};
@@ -232,11 +246,8 @@ export const PARTOGRAPHY_GRAPH_TYPES = [
232
246
  export const PARTOGRAPHY_ENCOUNTER_TYPES = Object.fromEntries(
233
247
  PARTOGRAPHY_GRAPH_TYPES.map((type) => [
234
248
  type,
235
- type === 'drugs-fluids'
236
- ? _partoUuids.drugsFluidsEncounterUuid ?? '39da3525-afe4-45ff-8977-c53b7b359158'
237
- : type === 'pulse-bp-combined'
238
- ? MCH_PARTOGRAPHY_ENCOUNTER_UUID
239
- : MCH_PARTOGRAPHY_ENCOUNTER_UUID,
249
+ /* For testing: set drugs-fluids to use the same encounter type as temperature/others */
250
+ MCH_PARTOGRAPHY_ENCOUNTER_UUID,
240
251
  ]),
241
252
  ) as Record<(typeof PARTOGRAPHY_GRAPH_TYPES)[number], string>;
242
253
 
@@ -535,16 +546,14 @@ export const getColorForGraph = (colorName: string): string => {
535
546
  return PARTOGRAPHY_COLOR_MAPPINGS[colorName as keyof typeof PARTOGRAPHY_COLOR_MAPPINGS] || '#525252';
536
547
  };
537
548
 
538
- // Default Fetal Heart Rate Chart Data
539
- export const defaultFetalHeartRateChartData = [
540
- { hour: 0, value: 140, group: 'Fetal Heart Rate', time: '0', color: getColorForGraph('green') },
541
- { hour: 10, value: 140, group: 'Fetal Heart Rate', time: '10', color: getColorForGraph('green') },
542
- { hour: 20, value: 140, group: 'Fetal Heart Rate', time: '20', color: getColorForGraph('green') },
543
- { hour: 30, value: 140, group: 'Fetal Heart Rate', time: '30', color: getColorForGraph('green') },
544
- { hour: 40, value: 140, group: 'Fetal Heart Rate', time: '40', color: getColorForGraph('green') },
545
- { hour: 50, value: 140, group: 'Fetal Heart Rate', time: '50', color: getColorForGraph('green') },
546
- { hour: 60, value: 140, group: 'Fetal Heart Rate', time: '60', color: getColorForGraph('green') },
547
- ];
549
+ // Default Fetal Heart Rate Chart Data - Empty for no data state
550
+ export const defaultFetalHeartRateChartData: Array<{
551
+ hour: number;
552
+ value: number;
553
+ group: string;
554
+ time: string;
555
+ color: string;
556
+ }> = [];
548
557
 
549
558
  export const FORM_OPTION_CONCEPTS = {
550
559
  AMNIOTIC_FLUID: {
@@ -787,20 +796,14 @@ export const TIME_SLOT_OPTIONS = [
787
796
  ] as const;
788
797
 
789
798
  export const MEMBRANE_TIME_SLOT_OPTIONS = [
790
- { value: '16:00', label: '16:00' },
791
- { value: '17:00', label: '17:00' },
792
- { value: '18:00', label: '18:00' },
793
- { value: '19:00', label: '19:00' },
794
- { value: '20:00', label: '20:00' },
795
- { value: '21:00', label: '21:00' },
796
- { value: '22:00', label: '22:00' },
797
- { value: '23:00', label: '23:00' },
798
- { value: '00:00', label: '00:00' },
799
- { value: '01:00', label: '01:00' },
800
- { value: '02:00', label: '02:00' },
801
- { value: '03:00', label: '03:00' },
802
- { value: '04:00', label: '04:00' },
803
- { value: '05:00', label: '05:00' },
799
+ // Add 0hr option first
800
+ { value: '0', label: '0hr' },
801
+ // Then add the existing 0.5hr increments
802
+ ...Array.from({ length: 48 }, (_, i) => {
803
+ const value = ((i + 1) * 0.5).toString();
804
+ const label = i % 2 === 1 ? `${(i + 1) * 0.5}hr` : `${(i + 1) / 2}hr`;
805
+ return { value, label };
806
+ }),
804
807
  ] as const;
805
808
 
806
809
  export const EVENT_TYPE_OPTIONS = [
@@ -1281,8 +1284,8 @@ export const MOULDING_SYMBOL_MAP: Record<string, string> = {
1281
1284
  };
1282
1285
 
1283
1286
  export const OXYTOCIN_FORM_CONCEPTS = {
1284
- time: FETAL_HEART_RATE_HOUR_CONCEPT,
1285
- oxytocinDropsPerMinute: OXYTOCIN_DOSE_CONCEPT,
1287
+ time: OXYTOCIN_TIME_CONCEPT,
1288
+ oxytocinDropsPerMinute: OXYTOCIN_DROPS_PER_MINUTE_CONCEPT,
1286
1289
  } as const;
1287
1290
  export const CERVIX_FORM_CONCEPTS = {
1288
1291
  hour: FETAL_HEART_RATE_HOUR_CONCEPT,
@@ -47,7 +47,7 @@
47
47
  "caseEncounter": "የጉዳይ አስተዳደር ግኝቶች",
48
48
  "caseManagement": "Case Management",
49
49
  "causeOfDeath": "የሞት መንስኤ",
50
- "cervicalContractionsData": "Cervical Contractions",
50
+ "cervicalContractionsData": "Contractions",
51
51
  "cervicalDilation": "Cervical Dilation",
52
52
  "chooseAnOption": "Choose an option",
53
53
  "chooseCount": "Choose count",
@@ -47,7 +47,7 @@
47
47
  "caseEncounter": "Case management encounters",
48
48
  "caseManagement": "Case Management",
49
49
  "causeOfDeath": "Cause of Death",
50
- "cervicalContractionsData": "Cervical Contractions",
50
+ "cervicalContractionsData": "Contractions",
51
51
  "cervicalDilation": "Cervical Dilation",
52
52
  "chooseAnOption": "Choose an option",
53
53
  "chooseCount": "Choose count",