@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.
- package/.turbo/turbo-build.log +4 -4
- package/dist/127.js +1 -1
- package/dist/{805.js → 189.js} +1 -1
- package/dist/189.js.map +1 -0
- package/dist/40.js +1 -1
- package/dist/916.js +1 -1
- package/dist/kenyaemr-esm-patient-clinical-view-app.js +2 -2
- 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 +58 -36
- package/src/maternal-and-child-health/partography/components/temperature-graph.component.tsx +0 -4
- package/src/maternal-and-child-health/partography/components/uterine-contractions-graph.component.tsx +33 -11
- package/src/maternal-and-child-health/partography/forms/cervical-contractions-form.component.tsx +124 -136
- package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +23 -14
- package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +6 -10
- package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +36 -13
- package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.component.tsx +27 -46
- package/src/maternal-and-child-health/partography/forms/useCervixData.ts +2 -2
- package/src/maternal-and-child-health/partography/graphs/cervix-graph.component.tsx +56 -18
- package/src/maternal-and-child-health/partography/graphs/fetal-heart-rate-graph.component.tsx +36 -23
- package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +10 -5
- package/src/maternal-and-child-health/partography/partograph.component.tsx +315 -371
- package/src/maternal-and-child-health/partography/partography.resource.ts +788 -230
- package/src/maternal-and-child-health/partography/partography.scss +68 -40
- package/src/maternal-and-child-health/partography/resources/cervix.resource.ts +79 -76
- package/src/maternal-and-child-health/partography/resources/uterine-contractions.resource.ts +33 -12
- package/src/maternal-and-child-health/partography/types/index.ts +94 -0
- package/translations/am.json +0 -8
- package/translations/en.json +0 -8
- package/translations/sw.json +0 -8
- 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.$
|
|
962
|
+
color: colors.$red-50;
|
|
931
963
|
|
|
932
964
|
.arrow {
|
|
933
|
-
color: colors.$
|
|
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 -
|
|
1144
|
+
// Mild - Black dots pattern
|
|
1113
1145
|
&.mild {
|
|
1114
|
-
background-color:
|
|
1115
|
-
color:
|
|
1116
|
-
border-color: colors.$
|
|
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 -
|
|
1153
|
+
// Moderate - Diagonal black lines pattern (tuned)
|
|
1120
1154
|
&.moderate {
|
|
1121
|
-
background-color:
|
|
1122
|
-
color:
|
|
1123
|
-
border-color: colors.$
|
|
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 -
|
|
1162
|
+
// Strong - Solid black
|
|
1127
1163
|
&.strong {
|
|
1128
|
-
background-color:
|
|
1129
|
-
color:
|
|
1130
|
-
border-color:
|
|
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:
|
|
1406
|
-
border: 2px solid
|
|
1407
|
-
|
|
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:
|
|
1412
|
-
border: 2px solid
|
|
1413
|
-
|
|
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:
|
|
1418
|
-
border: 2px solid
|
|
1419
|
-
box-shadow:
|
|
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
|
-
|
|
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:
|
|
1457
|
-
border:
|
|
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:
|
|
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() -
|
|
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
|
-
|
|
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
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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((
|
|
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((
|
|
210
|
+
timeObs = observations.find((obs) => obs.concept?.uuid === CERVIX_FORM_CONCEPTS.time);
|
|
217
211
|
}
|
|
218
212
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
|
|
225
|
+
} else if (typeof hourObs.value === 'number') {
|
|
226
|
+
hour = hourObs.value;
|
|
227
|
+
}
|
|
231
228
|
}
|
|
232
229
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (
|
|
241
|
-
|
|
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
|
-
|
|
253
|
-
|
|
240
|
+
} else {
|
|
241
|
+
time = String(timeObs.value);
|
|
242
|
+
}
|
|
254
243
|
}
|
|
255
244
|
|
|
256
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
306
|
-
time
|
|
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:
|
|
316
|
+
timeDisplay: time ? `${hour !== null ? hour : '??'}:${time}` : null,
|
|
314
317
|
};
|
|
315
318
|
});
|
|
316
319
|
const existingTimeEntries = transformedData
|
package/src/maternal-and-child-health/partography/resources/uterine-contractions.resource.ts
CHANGED
|
@@ -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
|
|
18
|
-
const obsDatetime = toOmrsIsoString(new Date(Date.now() - timeConfig.defaultEncounterOffset));
|
|
17
|
+
const obsDatetime = toOmrsIsoString(new Date());
|
|
19
18
|
const observations = [];
|
|
20
|
-
if (formData.
|
|
19
|
+
if (formData.contractionCount) {
|
|
21
20
|
observations.push({
|
|
22
|
-
concept:
|
|
23
|
-
value: parseFloat(formData.
|
|
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'],
|
package/translations/am.json
CHANGED
|
@@ -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": "የቀዶ ጥገና ማጠቃለያ",
|