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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/dist/127.js +1 -1
  3. package/dist/{805.js → 189.js} +1 -1
  4. package/dist/189.js.map +1 -0
  5. package/dist/40.js +1 -1
  6. package/dist/916.js +1 -1
  7. package/dist/kenyaemr-esm-patient-clinical-view-app.js +2 -2
  8. package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +36 -36
  9. package/dist/main.js +3 -3
  10. package/dist/main.js.map +1 -1
  11. package/dist/routes.json +1 -1
  12. package/package.json +1 -1
  13. package/src/config-schema.ts +58 -36
  14. package/src/maternal-and-child-health/partography/components/temperature-graph.component.tsx +0 -4
  15. package/src/maternal-and-child-health/partography/components/uterine-contractions-graph.component.tsx +33 -11
  16. package/src/maternal-and-child-health/partography/forms/cervical-contractions-form.component.tsx +124 -136
  17. package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +23 -14
  18. package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +6 -10
  19. package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +36 -13
  20. package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.component.tsx +27 -46
  21. package/src/maternal-and-child-health/partography/forms/useCervixData.ts +2 -2
  22. package/src/maternal-and-child-health/partography/graphs/cervix-graph.component.tsx +56 -18
  23. package/src/maternal-and-child-health/partography/graphs/fetal-heart-rate-graph.component.tsx +36 -23
  24. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +10 -5
  25. package/src/maternal-and-child-health/partography/partograph.component.tsx +315 -371
  26. package/src/maternal-and-child-health/partography/partography.resource.ts +788 -230
  27. package/src/maternal-and-child-health/partography/partography.scss +68 -40
  28. package/src/maternal-and-child-health/partography/resources/cervix.resource.ts +79 -76
  29. package/src/maternal-and-child-health/partography/resources/uterine-contractions.resource.ts +33 -12
  30. package/src/maternal-and-child-health/partography/types/index.ts +94 -0
  31. package/translations/am.json +0 -8
  32. package/translations/en.json +0 -8
  33. package/translations/sw.json +0 -8
  34. package/dist/805.js.map +0 -1
@@ -2,6 +2,38 @@
2
2
  @use '@carbon/type';
3
3
  @use '@carbon/colors';
4
4
 
5
+ [data-chart-id='cervix'] svg .axis path,
6
+ [data-chart-id='cervix'] svg .axis line {
7
+ stroke: #111 !important;
8
+ stroke-width: 2px !important;
9
+ stroke-dasharray: none !important;
10
+ }
11
+ [data-chart-id='cervix'] svg .axis text {
12
+ fill: #111 !important;
13
+ }
14
+
15
+ .contractionsGridCell[data-contraction-level='strong'] {
16
+ background-color: #111 !important;
17
+ color: #fff !important;
18
+ font-weight: 700 !important;
19
+ }
20
+
21
+ .contractionsGridCell[data-contraction-level='moderate'] {
22
+ background-color: #fff !important;
23
+ color: #111 !important;
24
+ font-weight: 700 !important;
25
+ background-image: repeating-linear-gradient(45deg, #111 0 2px, transparent 2px 8px) !important;
26
+ background-size: 12px 12px !important;
27
+ }
28
+
29
+ .contractionsGridCell[data-contraction-level='mild'] {
30
+ background-color: #fff !important;
31
+ color: #111 !important;
32
+ font-weight: 700 !important;
33
+ background-image: radial-gradient(#111 2.5px, transparent 2.5px) !important;
34
+ background-size: 10px 10px !important;
35
+ }
36
+
5
37
  .partographyContainer {
6
38
  padding: layout.$spacing-05;
7
39
  background-color: colors.$gray-10;
@@ -927,10 +959,10 @@
927
959
  gap: layout.$spacing-02;
928
960
 
929
961
  &.low {
930
- color: colors.$gray-70;
962
+ color: colors.$red-50;
931
963
 
932
964
  .arrow {
933
- color: colors.$gray-70;
965
+ color: colors.$red-50;
934
966
  font-size: 1.4em;
935
967
  font-weight: bold;
936
968
  margin-right: layout.$spacing-02;
@@ -1109,25 +1141,29 @@
1109
1141
  border-color: colors.$gray-50;
1110
1142
  }
1111
1143
 
1112
- // Mild - Light blue background
1144
+ // Mild - Black dots pattern
1113
1145
  &.mild {
1114
- background-color: colors.$blue-30;
1115
- color: colors.$blue-80;
1116
- border-color: colors.$blue-50;
1146
+ background-color: #fff;
1147
+ color: #111;
1148
+ border-color: colors.$gray-50;
1149
+ background-image: radial-gradient(#111 3px, transparent 3px);
1150
+ background-size: 12px 12px;
1117
1151
  }
1118
1152
 
1119
- // Moderate - Orange background
1153
+ // Moderate - Diagonal black lines pattern (tuned)
1120
1154
  &.moderate {
1121
- background-color: colors.$orange-40;
1122
- color: colors.$orange-90;
1123
- border-color: colors.$orange-60;
1155
+ background-color: #fff;
1156
+ color: #111;
1157
+ border-color: colors.$gray-50;
1158
+ background-image: repeating-linear-gradient(135deg, #111 0 4px, transparent 4px 12px);
1159
+ background-size: 16px 16px;
1124
1160
  }
1125
1161
 
1126
- // Strong - Dark red background
1162
+ // Strong - Solid black
1127
1163
  &.strong {
1128
- background-color: colors.$red-60;
1129
- color: colors.$white;
1130
- border-color: colors.$red-70;
1164
+ background-color: #111;
1165
+ color: #fff;
1166
+ border-color: #111;
1131
1167
  }
1132
1168
  }
1133
1169
 
@@ -1402,21 +1438,25 @@
1402
1438
  }
1403
1439
 
1404
1440
  .contractionBarMild {
1405
- background-color: colors.$blue-40;
1406
- border: 2px solid colors.$blue-60;
1407
- box-shadow: inset 0 0 4px rgba(13, 90, 170, 0.3);
1441
+ background-color: #fff;
1442
+ border: 2px solid #111;
1443
+ background-image: radial-gradient(#111 3px, transparent 3px);
1444
+ background-size: 12px 12px;
1445
+ box-shadow: none;
1408
1446
  }
1409
1447
 
1410
1448
  .contractionBarModerate {
1411
- background-color: colors.$orange-50;
1412
- border: 2px solid colors.$orange-70;
1413
- box-shadow: inset 0 0 4px rgba(255, 131, 28, 0.3);
1449
+ background-color: #fff;
1450
+ border: 2px solid #111;
1451
+ background-image: repeating-linear-gradient(135deg, #111 0 4px, transparent 4px 12px);
1452
+ background-size: 16px 16px;
1453
+ box-shadow: none;
1414
1454
  }
1415
1455
 
1416
1456
  .contractionBarStrong {
1417
- background-color: colors.$red-60;
1418
- border: 2px solid colors.$red-80;
1419
- box-shadow: inset 0 0 4px rgba(218, 30, 40, 0.3);
1457
+ background-color: #111;
1458
+ border: 2px solid #111;
1459
+ box-shadow: none;
1420
1460
  }
1421
1461
 
1422
1462
  // Oxytocin Bar Visual Indicators
@@ -1438,26 +1478,14 @@
1438
1478
  box-shadow: inset 0 0 4px rgba(218, 30, 40, 0.3);
1439
1479
  }
1440
1480
 
1441
- // Grid cell states for filled contractions
1442
1481
  .contractionsGridCell {
1443
- // ...existing styles...
1444
-
1445
- &.contractionBarMild {
1446
- background-color: colors.$blue-10;
1447
- border: 1px solid colors.$blue-30;
1448
- }
1449
-
1450
- &.contractionBarModerate {
1451
- background-color: colors.$orange-10;
1452
- border: 1px solid colors.$orange-30;
1453
- }
1454
-
1482
+ &.contractionBarMild,
1483
+ &.contractionBarModerate,
1455
1484
  &.contractionBarStrong {
1456
- background-color: colors.$red-10;
1457
- border: 1px solid colors.$red-30;
1485
+ background-color: transparent;
1486
+ border: none;
1458
1487
  }
1459
1488
 
1460
- // Add visual feedback for filled cells
1461
1489
  &[data-filled='true'] {
1462
1490
  position: relative;
1463
1491
 
@@ -1611,7 +1639,7 @@
1611
1639
  border: 2px solid colors.$gray-40;
1612
1640
  margin-bottom: layout.$spacing-04;
1613
1641
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1614
- min-height: 400px;
1642
+ min-height: 650px; // Match or exceed chart height
1615
1643
 
1616
1644
  // Medical chart styling
1617
1645
  :global(.cds--chart-wrapper) {
@@ -1,3 +1,4 @@
1
+ export type UseCervixDataResult = ReturnType<typeof useCervixData>;
1
2
  import { openmrsFetch, restBaseUrl, toOmrsIsoString } from '@openmrs/esm-framework';
2
3
  import useSWR from 'swr';
3
4
  import { useTranslation } from 'react-i18next';
@@ -81,7 +82,7 @@ export async function saveCervixFormData(
81
82
  return { success: false, message: t('Location information is required'), error: 'NO_LOCATION' };
82
83
  }
83
84
  const now = new Date();
84
- now.setMinutes(now.getMinutes() - 1);
85
+ now.setMinutes(now.getMinutes() - 3);
85
86
  const encounterPayload = {
86
87
  patient: patientUuid,
87
88
  encounterType: MCH_PARTOGRAPHY_ENCOUNTER_UUID,
@@ -125,14 +126,18 @@ export async function saveCervixFormData(
125
126
  function buildCervixObservations(formData: CervixFormData): any[] {
126
127
  const observations = [];
127
128
  const obsDatetime = toOmrsIsoString(new Date());
129
+ // Save hour as a number (not string)
128
130
  if (formData.hour && formData.hour !== '') {
129
131
  const hourValue = parseFloat(formData.hour);
130
132
  if (!isNaN(hourValue)) {
131
133
  observations.push({ concept: CERVIX_FORM_CONCEPTS.hour, value: hourValue, obsDatetime });
132
134
  }
133
135
  }
136
+ // Always save time as 'Time: HH:mm' string
134
137
  if (formData.time && formData.time !== '') {
135
- observations.push({ concept: CERVIX_FORM_CONCEPTS.time, value: formData.time, obsDatetime });
138
+ // If already prefixed, don't double-prefix
139
+ const timeValue = formData.time.startsWith('Time:') ? formData.time : `Time: ${formData.time}`;
140
+ observations.push({ concept: CERVIX_FORM_CONCEPTS.time, value: timeValue, obsDatetime });
136
141
  }
137
142
  if (formData.cervicalDilation && formData.cervicalDilation !== '') {
138
143
  const dilationValue = parseFloat(formData.cervicalDilation);
@@ -143,11 +148,6 @@ function buildCervixObservations(formData: CervixFormData): any[] {
143
148
  if (formData.descent && formData.descent !== '') {
144
149
  const descentValue = parseFloat(formData.descent);
145
150
  if (!isNaN(descentValue)) {
146
- // The descent of head is represented in the system as a coded concept
147
- // where each station (1..5) maps to an answer concept UUID. The UI
148
- // currently provides numeric station values (1..5), so convert that
149
- // into the appropriate concept UUID before sending the obs. If we
150
- // can't find a mapping, fall back to sending the numeric value.
151
151
  const matchingOption = DESCENT_OF_HEAD_OPTIONS.find((opt) => opt.stationValue === descentValue);
152
152
  if (matchingOption && matchingOption.value) {
153
153
  observations.push({ concept: CERVIX_FORM_CONCEPTS.descentOfHead, value: matchingOption.value, obsDatetime });
@@ -189,86 +189,88 @@ export function useCervixData(patientUuid: string) {
189
189
  const transformedData = sortedEncounters.map((encounter) => {
190
190
  const observations = encounter.obs || [];
191
191
 
192
- // Helper: try exact concept match first
193
- const byConcept = (uuid: string) => observations.find((obs) => obs.concept?.uuid === uuid);
194
-
195
- let hourObs = byConcept(CERVIX_FORM_CONCEPTS.hour);
196
- let timeObs = byConcept(CERVIX_FORM_CONCEPTS.time);
197
- let cervicalDilationObs = byConcept(CERVIX_FORM_CONCEPTS.cervicalDilation);
198
- let descentObs = byConcept(CERVIX_FORM_CONCEPTS.descentOfHead);
199
-
200
- // If any primary obs were not found, attempt heuristics based on value shapes
201
- const unused = new Set(observations);
192
+ // Fix: extract hour and time from all obs with the same concept UUID by value prefix
193
+ let hourObs = observations.find(
194
+ (obs) =>
195
+ obs.concept?.uuid === CERVIX_FORM_CONCEPTS.hour &&
196
+ typeof obs.value === 'string' &&
197
+ obs.value.startsWith('Hour:'),
198
+ );
199
+ let timeObs = observations.find(
200
+ (obs) =>
201
+ obs.concept?.uuid === CERVIX_FORM_CONCEPTS.time &&
202
+ typeof obs.value === 'string' &&
203
+ obs.value.startsWith('Time:'),
204
+ );
205
+ // fallback: if not found by prefix, just get the first for each
202
206
  if (!hourObs) {
203
- hourObs = observations.find((o) => {
204
- const v = o.value;
205
- if (typeof v === 'number') {
206
- return v >= 0 && v <= 23;
207
- }
208
- if (typeof v === 'string' && /^\d{1,2}$/.test(v)) {
209
- return Number(v) >= 0 && Number(v) <= 23;
210
- }
211
- return false;
212
- });
207
+ hourObs = observations.find((obs) => obs.concept?.uuid === CERVIX_FORM_CONCEPTS.hour);
213
208
  }
214
-
215
209
  if (!timeObs) {
216
- timeObs = observations.find((o) => typeof o.value === 'string' && /^\d{1,2}:\d{2}$/.test(o.value));
210
+ timeObs = observations.find((obs) => obs.concept?.uuid === CERVIX_FORM_CONCEPTS.time);
217
211
  }
218
212
 
219
- if (!cervicalDilationObs) {
220
- cervicalDilationObs = observations.find((o) => {
221
- const v = o.value;
222
- if (typeof v === 'number') {
223
- return v >= 0 && v <= 10;
224
- }
225
- if (typeof v === 'string' && !/^\d{1,2}:\d{2}$/.test(v)) {
226
- const parsed = Number(v);
227
- return !isNaN(parsed) && parsed >= 0 && parsed <= 10;
213
+ let cervicalDilationObs = observations.find((obs) => obs.concept?.uuid === CERVIX_FORM_CONCEPTS.cervicalDilation);
214
+ let descentObs = observations.find((obs) => obs.concept?.uuid === CERVIX_FORM_CONCEPTS.descentOfHead);
215
+
216
+ let hour: number | null = null;
217
+ if (hourObs && hourObs.value !== undefined && hourObs.value !== null) {
218
+ if (typeof hourObs.value === 'string') {
219
+ const match = hourObs.value.match(/Hour:\s*([0-9]+(?:\.[0-9]+)?)/);
220
+ if (match) {
221
+ hour = Number(match[1]);
222
+ } else if (/^\d{1,2}(?:\.\d+)?$/.test(hourObs.value)) {
223
+ hour = Number(hourObs.value);
228
224
  }
229
- return false;
230
- });
225
+ } else if (typeof hourObs.value === 'number') {
226
+ hour = hourObs.value;
227
+ }
231
228
  }
232
229
 
233
- if (!descentObs) {
234
- // descent might be numeric 1-5, a numeric string, or a coded answer (uuid string)
235
- descentObs = observations.find((o) => {
236
- const v = o.value;
237
- if (typeof v === 'number') {
238
- return v >= 1 && v <= 5;
239
- }
240
- if (typeof v === 'string') {
241
- if (/^\d{1,2}$/.test(v)) {
242
- return Number(v) >= 1 && Number(v) <= 5;
243
- }
244
- // likely a UUID (coded answer)
245
- if (/^[0-9a-fA-F-]{8,}$/.test(v)) {
246
- return true;
247
- }
248
- }
249
- if (typeof v === 'object' && v !== null && ((v as any).uuid || (v as any).display)) {
250
- return true;
230
+ // Parse time from 'Time: HH:mm' or fallback to string
231
+ let time = null;
232
+ if (timeObs && timeObs.value !== undefined && timeObs.value !== null) {
233
+ if (typeof timeObs.value === 'string') {
234
+ const match = timeObs.value.match(/Time:\s*([0-9]{2}:[0-9]{2})/);
235
+ if (match) {
236
+ time = match[1];
237
+ } else if (/^\d{1,2}:\d{2}$/.test(timeObs.value)) {
238
+ time = timeObs.value;
251
239
  }
252
- return false;
253
- });
240
+ } else {
241
+ time = String(timeObs.value);
242
+ }
254
243
  }
255
244
 
256
- // Normalize descentOfHead to numeric station where possible
245
+ // Cervical dilation
246
+ let cervicalDilation = null;
247
+ if (cervicalDilationObs && cervicalDilationObs.value !== undefined && cervicalDilationObs.value !== null) {
248
+ if (typeof cervicalDilationObs.value === 'string') {
249
+ const parsed = Number(cervicalDilationObs.value);
250
+ cervicalDilation = !isNaN(parsed) ? parsed : null;
251
+ } else if (typeof cervicalDilationObs.value === 'number') {
252
+ cervicalDilation = cervicalDilationObs.value;
253
+ }
254
+ }
255
+
256
+ // Descent of head (existing logic)
257
257
  let descentOfHead = null;
258
258
  if (descentObs?.value !== undefined && descentObs?.value !== null) {
259
259
  const val = descentObs.value;
260
260
  if (typeof val === 'number') {
261
261
  descentOfHead = val;
262
262
  } else if (typeof val === 'string') {
263
- // If the server returned a coded answer (concept UUID) for descent
264
- // map it back to the numeric station value using DESCENT_OF_HEAD_OPTIONS.
265
263
  const match = DESCENT_OF_HEAD_OPTIONS.find((opt) => opt.value === val || opt.conceptUuid === val);
266
264
  if (match && typeof match.stationValue === 'number') {
267
265
  descentOfHead = match.stationValue;
268
266
  } else {
269
- // Fallback: try parsing numeric string
270
267
  const parsed = Number(val);
271
- descentOfHead = isNaN(parsed) ? null : parsed;
268
+ if (!isNaN(parsed)) {
269
+ descentOfHead = parsed;
270
+ } else {
271
+ console.warn('[Cervix] descentOfHead unmapped value:', val, '-> fallback to 5');
272
+ descentOfHead = 5;
273
+ }
272
274
  }
273
275
  } else if (typeof val === 'object' && val !== null) {
274
276
  const uuid = (val as any).uuid || (val as any).value || null;
@@ -276,14 +278,19 @@ export function useCervixData(patientUuid: string) {
276
278
  const match = DESCENT_OF_HEAD_OPTIONS.find((opt) => opt.value === uuid || opt.conceptUuid === uuid);
277
279
  if (match && typeof match.stationValue === 'number') {
278
280
  descentOfHead = match.stationValue;
281
+ } else {
282
+ console.warn('[Cervix] descentOfHead unmapped object value:', uuid, '-> fallback to 5');
283
+ descentOfHead = 5;
279
284
  }
280
285
  }
281
286
  }
282
287
  }
288
+ if (descentOfHead === null || isNaN(descentOfHead)) {
289
+ console.warn('[Cervix] descentOfHead was null/NaN after all mapping, fallback to 5');
290
+ descentOfHead = 5;
291
+ }
283
292
 
284
- // Also compute a human-friendly label for descent (when server returns
285
- // a coded answer UUID we map it to the option's text). Keep numeric
286
- // `descentOfHead` for charting but expose `descentLabel` for tables/UI.
293
+ // Human-friendly label for descent
287
294
  let descentLabel: string | null = null;
288
295
  if (descentObs && descentObs.value !== undefined && descentObs.value !== null) {
289
296
  const val = descentObs.value;
@@ -293,7 +300,6 @@ export function useCervixData(patientUuid: string) {
293
300
  descentLabel = match.text;
294
301
  }
295
302
  }
296
- // if no label found and we have a numeric station, use that
297
303
  if (!descentLabel && typeof descentOfHead === 'number') {
298
304
  descentLabel = String(descentOfHead);
299
305
  }
@@ -302,15 +308,12 @@ export function useCervixData(patientUuid: string) {
302
308
  return {
303
309
  uuid: encounter.uuid,
304
310
  encounterDatetime: encounter.encounterDatetime,
305
- hour: hourObs?.value !== undefined && hourObs?.value !== null ? Number(hourObs.value) : null,
306
- time: timeObs?.value !== undefined && timeObs?.value !== null ? String(timeObs.value) : null,
307
- cervicalDilation:
308
- cervicalDilationObs?.value !== undefined && cervicalDilationObs?.value !== null
309
- ? Number(cervicalDilationObs.value)
310
- : null,
311
+ hour,
312
+ time,
313
+ cervicalDilation,
311
314
  descentOfHead,
312
315
  descentLabel,
313
- timeDisplay: timeObs?.value ? `${hourObs?.value || '??'}:${timeObs.value}` : null,
316
+ timeDisplay: time ? `${hour !== null ? hour : '??'}:${time}` : null,
314
317
  };
315
318
  });
316
319
  const existingTimeEntries = transformedData
@@ -14,16 +14,44 @@ import { toOmrsIsoString } from '@openmrs/esm-framework';
14
14
  import { useTranslation } from 'react-i18next';
15
15
 
16
16
  export function buildUterineContractionsObservation(formData: any): any[] {
17
- const timeConfig = { defaultEncounterOffset: 0 };
18
- const obsDatetime = toOmrsIsoString(new Date(Date.now() - timeConfig.defaultEncounterOffset));
17
+ const obsDatetime = toOmrsIsoString(new Date());
19
18
  const observations = [];
20
- if (formData.value || formData.measurementValue) {
19
+ if (formData.contractionCount) {
21
20
  observations.push({
22
- concept: PARTOGRAPHY_CONCEPTS['uterine-contractions'],
23
- value: parseFloat(formData.value || formData.measurementValue),
21
+ concept: '159682AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
22
+ value: parseFloat(formData.contractionCount),
24
23
  obsDatetime,
25
24
  });
26
25
  }
26
+
27
+ if (formData.contractionLevel) {
28
+ const contractionLevelMap = {
29
+ none: 0,
30
+ mild: 1,
31
+ moderate: 2,
32
+ strong: 3,
33
+ '1107AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': 0, // none
34
+ '1498AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': 1, // mild
35
+ '1499AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': 2, // moderate
36
+ '166788AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': 3, // strong
37
+ };
38
+ const contractionLevelValue = contractionLevelMap[formData.contractionLevel] ?? 0;
39
+ observations.push({
40
+ concept: '163750AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
41
+ value: contractionLevelValue,
42
+ obsDatetime,
43
+ });
44
+ }
45
+
46
+ // 3. Time slot (string, 'Time: HH:mm')
47
+ if (formData.timeSlot) {
48
+ observations.push({
49
+ concept: '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
50
+ value: `Time: ${formData.timeSlot}`,
51
+ obsDatetime,
52
+ });
53
+ }
54
+
27
55
  return observations;
28
56
  }
29
57
 
@@ -56,9 +84,7 @@ export function transformUterineContractionsEncounterToTableData(
56
84
  t: (key: string, defaultValue?: string, options?: any) => string,
57
85
  ): any[] {
58
86
  const tableData = [];
59
- // Helper to map protein/acetone values to display string
60
87
  const codeToPlus = (code) => {
61
- // Accept both string and number
62
88
  if (!code) {
63
89
  return '';
64
90
  }
@@ -80,7 +106,6 @@ export function transformUterineContractionsEncounterToTableData(
80
106
  encounters.forEach((encounter, index) => {
81
107
  const encounterDate = new Date(encounter.encounterDatetime);
82
108
  const date = encounterDate.toLocaleDateString();
83
- // Find all relevant obs for this encounter
84
109
  let timeSlot = '';
85
110
  let contractionCount = '';
86
111
  let contractionLevel = '';
@@ -90,7 +115,6 @@ export function transformUterineContractionsEncounterToTableData(
90
115
 
91
116
  if (Array.isArray(encounter.obs)) {
92
117
  for (const obs of encounter.obs) {
93
- // Time Slot: use concept or value string
94
118
  if (
95
119
  obs.concept.uuid === PARTOGRAPHY_CONCEPTS['time-slot'] ||
96
120
  (typeof obs.value === 'string' && obs.value.startsWith('TimeSlot:'))
@@ -106,11 +130,9 @@ export function transformUterineContractionsEncounterToTableData(
106
130
  timeSlot = String(obs.value);
107
131
  }
108
132
  }
109
- // Contraction count
110
133
  if (obs.concept.uuid === CONTRACTION_COUNT_CONCEPT) {
111
134
  contractionCount = String(obs.value);
112
135
  }
113
- // Contraction level (none, mild, moderate, strong)
114
136
  if (
115
137
  obs.concept.uuid === MOULDING_NONE_CONCEPT || // none
116
138
  obs.concept.uuid === CONTRACTION_LEVEL_MILD_CONCEPT || // mild
@@ -144,7 +166,6 @@ export function transformUterineContractionsEncounterToTableData(
144
166
  }
145
167
  }
146
168
  }
147
- // Show row if date is present (do not require timeSlot/volume)
148
169
  if (date) {
149
170
  tableData.push({
150
171
  id: `uterine-contractions-${index}`,
@@ -1,3 +1,86 @@
1
+ // Utility: Map contraction level UUID to label
2
+ export function contractionLevelUuidToLabel(uuid: string): string {
3
+ switch (uuid) {
4
+ case '1107AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA':
5
+ return 'none';
6
+ case '1498AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA':
7
+ return 'mild';
8
+ case '1499AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA':
9
+ return 'moderate';
10
+ case '166788AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA':
11
+ return 'strong';
12
+ default:
13
+ return uuid;
14
+ }
15
+ }
16
+ // Utility: Map urine test label/plus to UUID
17
+ export function labelToUuid(label: string): string {
18
+ switch (label) {
19
+ case '0':
20
+ return '1107AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
21
+ case '+':
22
+ return '1362AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
23
+ case '++':
24
+ return '1363AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
25
+ case '+++':
26
+ return '1364AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
27
+ default:
28
+ return label;
29
+ }
30
+ }
31
+ // Uterine Contraction Level UUID Map (for forms and lookups)
32
+ export const contractionLevelUuidMap: Record<string, string> = {
33
+ none: '1107AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
34
+ mild: '1498AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
35
+ moderate: '1499AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
36
+ strong: '166788AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
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)
43
+ export const contractionLevelOptions = [
44
+ {
45
+ value: 'none',
46
+ labelKey: 'noContractions',
47
+ defaultLabel: 'No Contractions',
48
+ concept: '1107AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
49
+ visual: '0',
50
+ visualClass: 'none',
51
+ titleKey: 'none',
52
+ defaultTitle: 'None',
53
+ },
54
+ {
55
+ value: 'mild',
56
+ labelKey: 'mildContractions',
57
+ defaultLabel: 'Mild Contractions',
58
+ concept: '1498AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
59
+ visual: '1',
60
+ visualClass: 'mild',
61
+ titleKey: 'mild',
62
+ defaultTitle: 'Mild',
63
+ },
64
+ {
65
+ value: 'moderate',
66
+ labelKey: 'moderateContractions',
67
+ defaultLabel: 'Moderate Contractions',
68
+ concept: '1499AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
69
+ visual: '2',
70
+ visualClass: 'moderate',
71
+ titleKey: 'moderate',
72
+ defaultTitle: 'Moderate',
73
+ },
74
+ {
75
+ value: 'strong',
76
+ labelKey: 'strongContractions',
77
+ defaultLabel: 'Strong Contractions',
78
+ concept: '4b90b73a-ad11-4650-91b0-baea131974e0',
79
+ visual: '3',
80
+ visualClass: 'strong',
81
+ title: 'Strong',
82
+ },
83
+ ];
1
84
  import {
2
85
  configSchema,
3
86
  MOULDING_NONE_CONCEPT,
@@ -452,6 +535,17 @@ export const getColorForGraph = (colorName: string): string => {
452
535
  return PARTOGRAPHY_COLOR_MAPPINGS[colorName as keyof typeof PARTOGRAPHY_COLOR_MAPPINGS] || '#525252';
453
536
  };
454
537
 
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
+ ];
548
+
455
549
  export const FORM_OPTION_CONCEPTS = {
456
550
  AMNIOTIC_FLUID: {
457
551
  MEMBRANE_INTACT: PARTOGRAPHY_CONCEPTS['amniotic-fluid'],
@@ -70,7 +70,6 @@
70
70
  "contractionCount": "Number of Contractions",
71
71
  "contractionCountRequired": "Please select number of contractions",
72
72
  "contractionLevel": "Contraction Level",
73
- "contractionLevelDescription": "Choose the intensity of uterine contractions observed",
74
73
  "contractionLevelRequired": "Please select contraction level",
75
74
  "contractions": "Contractions",
76
75
  "contractionsPerTenMin": "Contractions per 10 min",
@@ -181,12 +180,8 @@
181
180
  "medicalHistory": "የህክምና ታሪክ",
182
181
  "membraneAmnioticFluidData": "Membrane Amniotic Fluid & Moulding",
183
182
  "membraneIntact": "Membrane intact",
184
- "mild": "Mild",
185
- "mildContractions": "Mild Contractions",
186
183
  "missedAppointmentDate": "ቀጠሮ ያመለጠበት ቀን",
187
184
  "modeOfDelivery": "የወሊድ ዘዴ",
188
- "moderate": "Moderate",
189
- "moderateContractions": "Moderate Contractions",
190
185
  "motherGeneralCondition": "አጠቃላይ ሁኔታ",
191
186
  "moulding": "Moulding",
192
187
  "mouldingRequired": "Please select moulding status",
@@ -199,12 +194,10 @@
199
194
  "no": "አይ",
200
195
  "noContactToDisplay": "ለዚህ ታካሚ የሚታይ የእውቂያ ዳታ የለም።",
201
196
  "noContractionData": "No contraction data available",
202
- "noContractions": "No Contractions",
203
197
  "noData": "No Data",
204
198
  "noDataAvailable": "No data available for this graph",
205
199
  "noDrugsIVFluidsData": "No drugs and IV fluids data available",
206
200
  "noEncounterToDisplay": "ለዚህ ታካሚ የሚታዩ ግኝቶች የሉም።",
207
- "none": "None",
208
201
  "noObservationsFound": "ምንም ምልከታዎች አልተገኙም",
209
202
  "noOxytocinData": "No oxytocin data available",
210
203
  "noPatientSelected": "No patient selected",
@@ -276,7 +269,6 @@
276
269
  "specialClinics": "ልዩ ክሊኒኮች",
277
270
  "status": "ሁኔታ",
278
271
  "statusAtDischarge": "በመልቀቂያ ጊዜ ሁኔታ",
279
- "strongContractions": "Strong Contractions",
280
272
  "successfullyDeleted": "በተሳካ ሁኔታ ተሰርዟል",
281
273
  "surgicalHistory": "የቀዶ ጥገና ታሪክ",
282
274
  "surgicalSummary": "የቀዶ ጥገና ማጠቃለያ",