@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2716 → 5.4.2-pre.2724
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -4
- package/dist/127.js +1 -1
- package/dist/40.js +1 -1
- package/dist/805.js +1 -0
- package/dist/805.js.map +1 -0
- 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 +27 -27
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +97 -0
- package/src/contact-list/contact-tracing-history.component.tsx +18 -15
- package/src/maternal-and-child-health/partography/components/pulse-bp-graph.component.tsx +1 -0
- package/src/maternal-and-child-health/partography/components/temperature-graph.component.tsx +218 -0
- package/src/maternal-and-child-health/partography/components/uterine-contractions-graph.component.tsx +209 -0
- package/src/maternal-and-child-health/partography/forms/cervical-contractions-form.component.tsx +211 -0
- package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +354 -0
- package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +321 -0
- package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +275 -0
- package/src/maternal-and-child-health/partography/forms/index.ts +9 -0
- package/src/maternal-and-child-health/partography/forms/membrane-amniotic-fluid-form.component.tsx +330 -0
- package/src/maternal-and-child-health/partography/forms/oxytocin-form.component.tsx +207 -0
- package/src/maternal-and-child-health/partography/forms/pulse-bp-form.component.tsx +174 -0
- package/src/maternal-and-child-health/partography/forms/temperature-form.component.tsx +210 -0
- package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.component.tsx +218 -0
- package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.scss +107 -0
- package/src/maternal-and-child-health/partography/forms/time-picker-with-clock.component.tsx +174 -0
- package/src/maternal-and-child-health/partography/forms/time-picker-with-clock.scss +178 -0
- package/src/maternal-and-child-health/partography/forms/urine-test-form.component.tsx +255 -0
- package/src/maternal-and-child-health/partography/forms/useCervixData.ts +16 -0
- package/src/maternal-and-child-health/partography/graphs/cervical-contractions-graph.component.tsx +266 -0
- package/src/maternal-and-child-health/partography/graphs/cervix-graph.component.tsx +429 -0
- package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph-wrapper.component.tsx +163 -0
- package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph.component.tsx +82 -0
- package/src/maternal-and-child-health/partography/graphs/fetal-heart-rate-graph.component.tsx +359 -0
- package/src/maternal-and-child-health/partography/graphs/index.ts +10 -0
- package/src/maternal-and-child-health/partography/graphs/membrane-amniotic-fluid-graph.component.tsx +266 -0
- package/src/maternal-and-child-health/partography/graphs/oxytocin-graph-wrapper.component.tsx +190 -0
- package/src/maternal-and-child-health/partography/graphs/oxytocin-graph.component.tsx +126 -0
- package/src/maternal-and-child-health/partography/graphs/partograph-graph.component.tsx +266 -0
- package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph-wrapper.component.tsx +298 -0
- package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +267 -0
- package/src/maternal-and-child-health/partography/graphs/temperature-graph.component.tsx +242 -0
- package/src/maternal-and-child-health/partography/graphs/urine-test-graph.component.tsx +246 -0
- package/src/maternal-and-child-health/partography/partograph.component.tsx +2141 -118
- package/src/maternal-and-child-health/partography/partography-dashboard.meta.ts +8 -0
- package/src/maternal-and-child-health/partography/partography-data-form.scss +163 -0
- package/src/maternal-and-child-health/partography/partography.resource.ts +233 -326
- package/src/maternal-and-child-health/partography/partography.scss +1341 -3
- package/src/maternal-and-child-health/partography/resources/blood-pressure.resource.ts +96 -0
- package/src/maternal-and-child-health/partography/resources/cervical-dilation.resource.ts +109 -0
- package/src/maternal-and-child-health/partography/resources/cervix.resource.ts +362 -0
- package/src/maternal-and-child-health/partography/resources/descent-of-head.resource.ts +101 -0
- package/src/maternal-and-child-health/partography/resources/drugs-fluids.resource.ts +88 -0
- package/src/maternal-and-child-health/partography/resources/fetal-heart-rate.resource.ts +122 -0
- package/src/maternal-and-child-health/partography/resources/maternal-pulse.resource.ts +77 -0
- package/src/maternal-and-child-health/partography/resources/membrane-amniotic-fluid.resource.ts +108 -0
- package/src/maternal-and-child-health/partography/resources/oxytocin.resource.ts +159 -0
- package/src/maternal-and-child-health/partography/resources/progress-events.resource.ts +6 -0
- package/src/maternal-and-child-health/partography/resources/pulse-bp-combined.resource.ts +53 -0
- package/src/maternal-and-child-health/partography/resources/temperature.resource.ts +84 -0
- package/src/maternal-and-child-health/partography/resources/uterine-contractions.resource.ts +173 -0
- package/src/maternal-and-child-health/partography/table/temperature-table.component.tsx +99 -0
- package/src/maternal-and-child-health/partography/table/uterine-contractions-table.component.tsx +86 -0
- package/src/maternal-and-child-health/partography/types/index.ts +319 -101
- package/translations/am.json +121 -1
- package/translations/en.json +121 -1
- package/translations/sw.json +121 -1
- package/dist/397.js +0 -1
- package/dist/397.js.map +0 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { buildUterineContractionsObservation } from './resources/uterine-contractions.resource';
|
|
2
|
+
import { buildBloodPressureObservation } from './resources/blood-pressure.resource';
|
|
3
|
+
import { buildMaternalPulseObservation } from './resources/maternal-pulse.resource';
|
|
1
4
|
import { openmrsFetch, restBaseUrl, toOmrsIsoString, useConfig } from '@openmrs/esm-framework';
|
|
2
5
|
import useSWR from 'swr';
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
6
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
4
8
|
import {
|
|
5
9
|
PARTOGRAPHY_CONCEPTS,
|
|
6
10
|
PARTOGRAPHY_ENCOUNTER_TYPES,
|
|
@@ -9,8 +13,13 @@ import {
|
|
|
9
13
|
type PartographyObservation,
|
|
10
14
|
type PartographyEncounter,
|
|
11
15
|
type PartographyGraphType,
|
|
16
|
+
CONTRACTION_INTENSITY_OPTIONS,
|
|
12
17
|
} from './types';
|
|
13
18
|
import { configSchema, type ConfigObject } from '../../config-schema';
|
|
19
|
+
import {
|
|
20
|
+
buildTemperatureObservation,
|
|
21
|
+
transformTemperatureEncounterToChartData,
|
|
22
|
+
} from './resources/temperature.resource';
|
|
14
23
|
|
|
15
24
|
export type { PartographyObservation, PartographyEncounter };
|
|
16
25
|
const defaultPartographyConfig = configSchema.partography._default;
|
|
@@ -192,8 +201,12 @@ export function usePartographyEncounters(patientUuid: string, graphType: string)
|
|
|
192
201
|
export function usePartographyData(patientUuid: string, graphType: string) {
|
|
193
202
|
const { encounters, isLoading, error, mutate } = usePartographyEncounters(patientUuid, graphType);
|
|
194
203
|
|
|
204
|
+
const shouldUseLocalFallback = graphType !== 'fetal-heart-rate';
|
|
205
|
+
|
|
195
206
|
const localDataFallback = useSWR(
|
|
196
|
-
!isLoading && encounters.length === 0
|
|
207
|
+
!isLoading && encounters.length === 0 && shouldUseLocalFallback
|
|
208
|
+
? `partography_local_${patientUuid}_${graphType}`
|
|
209
|
+
: null,
|
|
197
210
|
() => {
|
|
198
211
|
const localData = loadPartographyData(patientUuid, graphType);
|
|
199
212
|
return localData;
|
|
@@ -219,7 +232,6 @@ function loadPartographyData(patientUuid: string, graphType: string): Partograph
|
|
|
219
232
|
try {
|
|
220
233
|
const storageKey = generateStorageKey(patientUuid, graphType);
|
|
221
234
|
const localData = JSON.parse(localStorage.getItem(storageKey) || '[]');
|
|
222
|
-
|
|
223
235
|
const localEncounters = localData.map((item) => ({
|
|
224
236
|
uuid: item.id,
|
|
225
237
|
encounterDatetime: item.timestamp,
|
|
@@ -241,11 +253,10 @@ function loadPartographyData(patientUuid: string, graphType: string): Partograph
|
|
|
241
253
|
},
|
|
242
254
|
],
|
|
243
255
|
}));
|
|
244
|
-
|
|
245
256
|
return localEncounters.sort(
|
|
246
257
|
(a, b) => new Date(b.encounterDatetime).getTime() - new Date(a.encounterDatetime).getTime(),
|
|
247
258
|
);
|
|
248
|
-
} catch
|
|
259
|
+
} catch {
|
|
249
260
|
return [];
|
|
250
261
|
}
|
|
251
262
|
}
|
|
@@ -258,28 +269,33 @@ export async function createPartographyEncounter(
|
|
|
258
269
|
providerUuid?: string,
|
|
259
270
|
t?: (key: string, fallback?: string) => string,
|
|
260
271
|
): Promise<{ success: boolean; message: string; encounter?: PartographyEncounter }> {
|
|
272
|
+
const translate = t || ((key, fallback) => fallback || key);
|
|
261
273
|
try {
|
|
262
274
|
const observations = buildObservations(graphType, formData);
|
|
263
|
-
|
|
264
275
|
if (observations.length === 0) {
|
|
265
|
-
throw new Error(
|
|
276
|
+
throw new Error(translate('noValidObservations', 'No valid observations to save'));
|
|
266
277
|
}
|
|
267
|
-
|
|
268
278
|
const encounterTypeUuid = await getEncounterTypeForGraph(graphType);
|
|
269
279
|
if (!encounterTypeUuid) {
|
|
270
|
-
throw new Error(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
280
|
+
throw new Error(translate('noEncounterTypeFound', `No encounter type found for graph: ${graphType}`));
|
|
281
|
+
}
|
|
282
|
+
let finalLocationUuid = locationUuid;
|
|
283
|
+
if (!finalLocationUuid) {
|
|
284
|
+
try {
|
|
285
|
+
const sessionResponse = await openmrsFetch(`${restBaseUrl}/session`);
|
|
286
|
+
if (sessionResponse.ok) {
|
|
287
|
+
const sessionData = await sessionResponse.json();
|
|
288
|
+
if (sessionData.sessionLocation?.uuid) {
|
|
289
|
+
finalLocationUuid = sessionData.sessionLocation.uuid;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} catch {}
|
|
293
|
+
if (!finalLocationUuid) {
|
|
294
|
+
finalLocationUuid = getDefaultLocationUuid();
|
|
295
|
+
}
|
|
276
296
|
}
|
|
277
|
-
|
|
278
|
-
const finalLocationUuid = locationUuid || getDefaultLocationUuid();
|
|
279
|
-
|
|
280
297
|
const timeConfig = getTimeConfig();
|
|
281
298
|
const encounterDatetime = toOmrsIsoString(new Date(Date.now() - timeConfig.defaultEncounterOffset));
|
|
282
|
-
|
|
283
299
|
const encounterPayload: any = {
|
|
284
300
|
patient: patientUuid,
|
|
285
301
|
location: finalLocationUuid,
|
|
@@ -287,9 +303,7 @@ export async function createPartographyEncounter(
|
|
|
287
303
|
obs: observations,
|
|
288
304
|
encounterType: encounterTypeUuid,
|
|
289
305
|
};
|
|
290
|
-
|
|
291
306
|
let finalProviderUuid = providerUuid;
|
|
292
|
-
|
|
293
307
|
if (!finalProviderUuid) {
|
|
294
308
|
try {
|
|
295
309
|
const sessionResponse = await openmrsFetch(`${restBaseUrl}/session`);
|
|
@@ -308,8 +322,7 @@ export async function createPartographyEncounter(
|
|
|
308
322
|
}
|
|
309
323
|
}
|
|
310
324
|
}
|
|
311
|
-
} catch
|
|
312
|
-
|
|
325
|
+
} catch {}
|
|
313
326
|
if (!finalProviderUuid) {
|
|
314
327
|
try {
|
|
315
328
|
const anyProviderResponse = await openmrsFetch(`${restBaseUrl}/provider?v=default&limit=1`);
|
|
@@ -319,10 +332,9 @@ export async function createPartographyEncounter(
|
|
|
319
332
|
finalProviderUuid = anyProviderData.results[0].uuid;
|
|
320
333
|
}
|
|
321
334
|
}
|
|
322
|
-
} catch
|
|
335
|
+
} catch {}
|
|
323
336
|
}
|
|
324
337
|
}
|
|
325
|
-
|
|
326
338
|
if (finalProviderUuid) {
|
|
327
339
|
try {
|
|
328
340
|
const providerValidationResponse = await openmrsFetch(`${restBaseUrl}/provider/${finalProviderUuid}?v=default`);
|
|
@@ -335,128 +347,32 @@ export async function createPartographyEncounter(
|
|
|
335
347
|
},
|
|
336
348
|
];
|
|
337
349
|
}
|
|
338
|
-
} catch
|
|
350
|
+
} catch {}
|
|
351
|
+
}
|
|
352
|
+
if (!patientUuid) {
|
|
353
|
+
throw new Error(translate('Patient UUID is required'));
|
|
354
|
+
}
|
|
355
|
+
if (!finalLocationUuid) {
|
|
356
|
+
throw new Error(translate('Location UUID is required'));
|
|
357
|
+
}
|
|
358
|
+
if (!encounterTypeUuid) {
|
|
359
|
+
throw new Error(translate('Encounter type UUID is required'));
|
|
360
|
+
}
|
|
361
|
+
if (!observations || observations.length === 0) {
|
|
362
|
+
throw new Error(translate('At least one observation is required'));
|
|
339
363
|
}
|
|
340
|
-
|
|
341
364
|
const response = await openmrsFetch(`${restBaseUrl}/encounter`, {
|
|
342
365
|
method: 'POST',
|
|
343
366
|
headers: { 'Content-Type': 'application/json' },
|
|
344
367
|
body: JSON.stringify(encounterPayload),
|
|
345
368
|
});
|
|
346
|
-
|
|
347
369
|
if (!response.ok) {
|
|
348
370
|
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
349
|
-
|
|
350
371
|
try {
|
|
351
372
|
const responseText = await response.text();
|
|
352
373
|
if (responseText) {
|
|
353
374
|
try {
|
|
354
375
|
const detailedError = JSON.parse(responseText);
|
|
355
|
-
|
|
356
|
-
if (
|
|
357
|
-
detailedError.error &&
|
|
358
|
-
detailedError.error.fieldErrors &&
|
|
359
|
-
detailedError.error.fieldErrors.encounterType
|
|
360
|
-
) {
|
|
361
|
-
const availableTypes = await discoverEncounterTypes();
|
|
362
|
-
let retryEncounterType = null;
|
|
363
|
-
|
|
364
|
-
const retryFallbackTypes = getRetryFallbackTypes();
|
|
365
|
-
for (const fallback of retryFallbackTypes) {
|
|
366
|
-
if (availableTypes[fallback]) {
|
|
367
|
-
retryEncounterType = availableTypes[fallback];
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (!retryEncounterType && Object.keys(availableTypes).length > 0) {
|
|
372
|
-
retryEncounterType = Object.values(availableTypes)[0];
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (retryEncounterType) {
|
|
376
|
-
const retryPayload = { ...encounterPayload, encounterType: retryEncounterType };
|
|
377
|
-
const retryResponse = await openmrsFetch(`${restBaseUrl}/encounter`, {
|
|
378
|
-
method: 'POST',
|
|
379
|
-
headers: { 'Content-Type': 'application/json' },
|
|
380
|
-
body: JSON.stringify(retryPayload),
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
if (retryResponse.ok) {
|
|
384
|
-
const encounter = await retryResponse.json();
|
|
385
|
-
|
|
386
|
-
try {
|
|
387
|
-
await saveToLocalStorage(patientUuid, graphType, formData, encounter.uuid);
|
|
388
|
-
} catch (localError) {}
|
|
389
|
-
|
|
390
|
-
return {
|
|
391
|
-
success: true,
|
|
392
|
-
message:
|
|
393
|
-
t?.('partographyDataSavedFallbackEncounter', 'Partography data saved successfully') ||
|
|
394
|
-
'Partography data saved successfully',
|
|
395
|
-
encounter,
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const isProviderConstraintError =
|
|
402
|
-
(detailedError.error.fieldErrors && detailedError.error.fieldErrors.encounterProviders) ||
|
|
403
|
-
(detailedError.error.message && detailedError.error.message.includes('provider_id'));
|
|
404
|
-
|
|
405
|
-
if (isProviderConstraintError) {
|
|
406
|
-
const retryPayloadWithoutProvider = { ...encounterPayload };
|
|
407
|
-
delete retryPayloadWithoutProvider.encounterProviders;
|
|
408
|
-
|
|
409
|
-
const retryResponse = await openmrsFetch(`${restBaseUrl}/encounter`, {
|
|
410
|
-
method: 'POST',
|
|
411
|
-
headers: { 'Content-Type': 'application/json' },
|
|
412
|
-
body: JSON.stringify(retryPayloadWithoutProvider),
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
if (retryResponse.ok) {
|
|
416
|
-
const encounter = await retryResponse.json();
|
|
417
|
-
|
|
418
|
-
try {
|
|
419
|
-
await saveToLocalStorage(patientUuid, graphType, formData, encounter.uuid);
|
|
420
|
-
} catch (localError) {}
|
|
421
|
-
|
|
422
|
-
return {
|
|
423
|
-
success: true,
|
|
424
|
-
message:
|
|
425
|
-
t?.('partographyDataSavedWithoutProvider', 'Partography data saved successfully') ||
|
|
426
|
-
'Partography data saved successfully',
|
|
427
|
-
encounter,
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if (detailedError.error.fieldErrors && detailedError.error.fieldErrors.encounterDatetime) {
|
|
433
|
-
const timeConfig = getTimeConfig();
|
|
434
|
-
const earlierDatetime = toOmrsIsoString(new Date(Date.now() - timeConfig.retryEncounterOffset));
|
|
435
|
-
const retryPayload = { ...encounterPayload, encounterDatetime: earlierDatetime };
|
|
436
|
-
|
|
437
|
-
const retryResponse = await openmrsFetch(`${restBaseUrl}/encounter`, {
|
|
438
|
-
method: 'POST',
|
|
439
|
-
headers: { 'Content-Type': 'application/json' },
|
|
440
|
-
body: JSON.stringify(retryPayload),
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
if (retryResponse.ok) {
|
|
444
|
-
const encounter = await retryResponse.json();
|
|
445
|
-
|
|
446
|
-
try {
|
|
447
|
-
await saveToLocalStorage(patientUuid, graphType, formData, encounter.uuid);
|
|
448
|
-
} catch (localError) {}
|
|
449
|
-
|
|
450
|
-
return {
|
|
451
|
-
success: true,
|
|
452
|
-
message:
|
|
453
|
-
t?.('partographyDataSavedAdjustedDatetime', 'Partography data saved successfully') ||
|
|
454
|
-
'Partography data saved successfully',
|
|
455
|
-
encounter,
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
376
|
if (detailedError.error) {
|
|
461
377
|
if (detailedError.error.message) {
|
|
462
378
|
errorMessage += ` - ${detailedError.error.message}`;
|
|
@@ -465,17 +381,26 @@ export async function createPartographyEncounter(
|
|
|
465
381
|
errorMessage += ` (${detailedError.error.detail})`;
|
|
466
382
|
}
|
|
467
383
|
}
|
|
468
|
-
} catch
|
|
384
|
+
} catch {
|
|
469
385
|
errorMessage += ` - Raw Response: ${responseText}`;
|
|
470
386
|
}
|
|
471
387
|
}
|
|
472
|
-
} catch
|
|
473
|
-
|
|
388
|
+
} catch {}
|
|
389
|
+
// eslint-disable-next-line no-console
|
|
390
|
+
console.error('Encounter POST failed', { status: response.status, statusText: response.statusText });
|
|
391
|
+
try {
|
|
392
|
+
const raw = await response.text();
|
|
393
|
+
// eslint-disable-next-line no-console
|
|
394
|
+
console.error('Encounter POST response body:', raw);
|
|
395
|
+
if (raw) {
|
|
396
|
+
errorMessage += ` - Response Body: ${raw}`;
|
|
397
|
+
}
|
|
398
|
+
} catch (e) {
|
|
399
|
+
// ignore
|
|
400
|
+
}
|
|
474
401
|
throw new Error(errorMessage);
|
|
475
402
|
}
|
|
476
|
-
|
|
477
403
|
const encounter = await response.json();
|
|
478
|
-
|
|
479
404
|
const cacheKey = generateCacheKey(patientUuid, graphType);
|
|
480
405
|
try {
|
|
481
406
|
const { mutate: globalMutate } = await import('swr');
|
|
@@ -484,38 +409,23 @@ export async function createPartographyEncounter(
|
|
|
484
409
|
setTimeout(async () => {
|
|
485
410
|
await globalMutate(cacheKey);
|
|
486
411
|
}, timeConfig.cacheInvalidationDelay);
|
|
487
|
-
} catch
|
|
488
|
-
|
|
489
|
-
try {
|
|
490
|
-
await saveToLocalStorage(patientUuid, graphType, formData, encounter.uuid);
|
|
491
|
-
} catch (localError) {}
|
|
492
|
-
|
|
412
|
+
} catch {}
|
|
493
413
|
return {
|
|
494
414
|
success: true,
|
|
495
|
-
message:
|
|
496
|
-
t?.('partographyDataSavedSuccessfully', 'Partography data saved successfully') ||
|
|
497
|
-
'Partography data saved successfully',
|
|
415
|
+
message: translate('partographyDataSavedSuccessfully', 'Partography data saved successfully'),
|
|
498
416
|
encounter,
|
|
499
417
|
};
|
|
500
|
-
} catch (error) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
success: false,
|
|
512
|
-
message:
|
|
513
|
-
t?.('failedToSavePartographyData', 'Failed to save partography data: {{error}}')?.replace(
|
|
514
|
-
'{{error}}',
|
|
515
|
-
error.message,
|
|
516
|
-
) || `Failed to save partography data: ${error.message}`,
|
|
517
|
-
};
|
|
518
|
-
}
|
|
418
|
+
} catch (error: any) {
|
|
419
|
+
// Log error details for debugging
|
|
420
|
+
// eslint-disable-next-line no-console
|
|
421
|
+
console.error('createPartographyEncounter error:', error);
|
|
422
|
+
return {
|
|
423
|
+
success: false,
|
|
424
|
+
message: translate(
|
|
425
|
+
'failedToSavePartographyData',
|
|
426
|
+
`Failed to save partography data: ${error && error.message ? error.message : String(error)}`,
|
|
427
|
+
),
|
|
428
|
+
};
|
|
519
429
|
}
|
|
520
430
|
}
|
|
521
431
|
|
|
@@ -555,14 +465,72 @@ function buildObservations(graphType: string, formData: any): any[] {
|
|
|
555
465
|
const obsDatetime = toOmrsIsoString(new Date(Date.now() - timeConfig.defaultEncounterOffset));
|
|
556
466
|
|
|
557
467
|
switch (graphType) {
|
|
468
|
+
case 'membrane-amniotic-fluid':
|
|
469
|
+
if (formData.time) {
|
|
470
|
+
observations.push({
|
|
471
|
+
concept: PARTOGRAPHY_CONCEPTS['fetal-heart-rate-time'],
|
|
472
|
+
value: `Time: ${formData.time}`,
|
|
473
|
+
obsDatetime,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
const amnioticFluidMap = defaultPartographyConfig.amnioticFluidMap;
|
|
477
|
+
let amnioticFluidValue = formData.amnioticFluid;
|
|
478
|
+
if (amnioticFluidValue && amnioticFluidMap[amnioticFluidValue]) {
|
|
479
|
+
amnioticFluidValue = amnioticFluidMap[amnioticFluidValue];
|
|
480
|
+
}
|
|
481
|
+
if (amnioticFluidValue) {
|
|
482
|
+
observations.push({
|
|
483
|
+
concept: PARTOGRAPHY_CONCEPTS['amniotic-fluid'],
|
|
484
|
+
value: amnioticFluidValue,
|
|
485
|
+
obsDatetime,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
const mouldingMap = defaultPartographyConfig.mouldingMap;
|
|
489
|
+
let mouldingValue = formData.moulding;
|
|
490
|
+
if (mouldingValue && mouldingMap[mouldingValue]) {
|
|
491
|
+
mouldingValue = mouldingMap[mouldingValue];
|
|
492
|
+
}
|
|
493
|
+
if (mouldingValue) {
|
|
494
|
+
observations.push({
|
|
495
|
+
concept: PARTOGRAPHY_CONCEPTS['moulding'],
|
|
496
|
+
value: mouldingValue,
|
|
497
|
+
obsDatetime,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
break;
|
|
558
501
|
case 'fetal-heart-rate':
|
|
559
|
-
if (formData.value || formData.measurementValue) {
|
|
502
|
+
if (formData.fetalHeartRate || formData.value || formData.measurementValue) {
|
|
560
503
|
observations.push({
|
|
561
504
|
concept: PARTOGRAPHY_CONCEPTS['fetal-heart-rate'],
|
|
562
|
-
value: parseFloat(formData.value || formData.measurementValue),
|
|
505
|
+
value: parseFloat(formData.fetalHeartRate || formData.value || formData.measurementValue),
|
|
563
506
|
obsDatetime,
|
|
564
507
|
});
|
|
565
508
|
}
|
|
509
|
+
|
|
510
|
+
if (formData.hour !== undefined && formData.hour !== '') {
|
|
511
|
+
try {
|
|
512
|
+
const hourValue = parseFloat(formData.hour);
|
|
513
|
+
|
|
514
|
+
if (hourValue >= 0 && hourValue <= 24) {
|
|
515
|
+
const hourText = `Hour: ${hourValue}`;
|
|
516
|
+
observations.push({
|
|
517
|
+
concept: PARTOGRAPHY_CONCEPTS['fetal-heart-rate-hour'],
|
|
518
|
+
value: hourText,
|
|
519
|
+
obsDatetime,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
} catch (error) {}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (formData.time) {
|
|
526
|
+
try {
|
|
527
|
+
observations.push({
|
|
528
|
+
concept: PARTOGRAPHY_CONCEPTS['fetal-heart-rate-time'],
|
|
529
|
+
value: `Time: ${formData.time}`,
|
|
530
|
+
obsDatetime,
|
|
531
|
+
});
|
|
532
|
+
} catch (error) {}
|
|
533
|
+
}
|
|
566
534
|
break;
|
|
567
535
|
|
|
568
536
|
case 'cervical-dilation':
|
|
@@ -591,14 +559,12 @@ function buildObservations(graphType: string, formData: any): any[] {
|
|
|
591
559
|
}
|
|
592
560
|
break;
|
|
593
561
|
|
|
594
|
-
case 'descent-of-head':
|
|
562
|
+
case 'descent-of-head': {
|
|
595
563
|
if (formData.value || formData.measurementValue) {
|
|
596
564
|
let conceptValue = formData.value || formData.measurementValue;
|
|
597
|
-
|
|
598
565
|
if (formData.conceptUuid) {
|
|
599
566
|
conceptValue = formData.conceptUuid;
|
|
600
567
|
}
|
|
601
|
-
|
|
602
568
|
observations.push({
|
|
603
569
|
concept: PARTOGRAPHY_CONCEPTS['descent-of-head'],
|
|
604
570
|
value: conceptValue,
|
|
@@ -606,55 +572,86 @@ function buildObservations(graphType: string, formData: any): any[] {
|
|
|
606
572
|
});
|
|
607
573
|
}
|
|
608
574
|
break;
|
|
609
|
-
|
|
610
|
-
case '
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
575
|
+
}
|
|
576
|
+
case 'pulse-bp-combined': {
|
|
577
|
+
// Save both maternal pulse and blood pressure observations
|
|
578
|
+
const pulseObs = buildMaternalPulseObservation({ value: formData.pulse });
|
|
579
|
+
const bpObs = buildBloodPressureObservation({ systolic: formData.systolic, diastolic: formData.diastolic });
|
|
580
|
+
return [...pulseObs, ...bpObs];
|
|
581
|
+
}
|
|
582
|
+
case 'uterine-contractions': {
|
|
583
|
+
// Build the primary numeric observation for uterine contractions
|
|
584
|
+
const uterineObs = buildUterineContractionsObservation(formData) || [];
|
|
585
|
+
// Allow additional related observations: contraction count, intensity, and time-slot
|
|
586
|
+
const extra: any[] = [];
|
|
587
|
+
|
|
588
|
+
if (formData.contractionCount !== undefined && formData.contractionCount !== null) {
|
|
589
|
+
extra.push({
|
|
590
|
+
concept: PARTOGRAPHY_CONCEPTS['contraction-count'],
|
|
591
|
+
value: formData.contractionCount,
|
|
625
592
|
obsDatetime,
|
|
626
593
|
});
|
|
627
594
|
}
|
|
628
|
-
|
|
595
|
+
// Defensive normalization: sometimes the UI mistakenly provides the
|
|
596
|
+
// question concept UUID (uterine-contractions) instead of the answer
|
|
597
|
+
// concept UUID. Try to map common shapes to a valid answer UUID and
|
|
598
|
+
// only send the coded obs when we have a reasonable answer UUID.
|
|
599
|
+
if (formData.contractionLevelUuid) {
|
|
600
|
+
let intensityUuid = String(formData.contractionLevelUuid);
|
|
601
|
+
|
|
602
|
+
// If the provided value equals the question concept UUID, attempt to
|
|
603
|
+
// resolve the intended answer using other form fields (value/label).
|
|
604
|
+
if (intensityUuid === PARTOGRAPHY_CONCEPTS['uterine-contractions']) {
|
|
605
|
+
// Try mapping from any provided label or option value
|
|
606
|
+
const candidate =
|
|
607
|
+
CONTRACTION_INTENSITY_OPTIONS.find((opt) => opt.value === String(formData.contractionLevelValue)) ||
|
|
608
|
+
CONTRACTION_INTENSITY_OPTIONS.find((opt) => opt.conceptUuid === String(formData.contractionLevelValue));
|
|
609
|
+
if (candidate && candidate.conceptUuid) {
|
|
610
|
+
intensityUuid = candidate.conceptUuid;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
629
613
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
614
|
+
// If intensityUuid still looks like the question UUID or is empty,
|
|
615
|
+
// skip sending it to avoid server conversion errors.
|
|
616
|
+
if (intensityUuid && intensityUuid !== PARTOGRAPHY_CONCEPTS['uterine-contractions']) {
|
|
617
|
+
// Send coded obs as the answer UUID string. Some OpenMRS instances
|
|
618
|
+
// accept either a plain UUID string or an object; prefer string to
|
|
619
|
+
// avoid server-side conversion issues seen in some deployments.
|
|
620
|
+
extra.push({
|
|
621
|
+
concept:
|
|
622
|
+
PARTOGRAPHY_CONCEPTS['uterine-contraction-duration'] || PARTOGRAPHY_CONCEPTS['uterine-contractions'],
|
|
623
|
+
value: intensityUuid,
|
|
624
|
+
obsDatetime,
|
|
625
|
+
});
|
|
626
|
+
} else {
|
|
627
|
+
// eslint-disable-next-line no-console
|
|
628
|
+
console.warn(
|
|
629
|
+
'Skipping contraction intensity obs due to invalid/mismatched UUID',
|
|
630
|
+
formData.contractionLevelUuid,
|
|
631
|
+
);
|
|
632
|
+
}
|
|
644
633
|
}
|
|
645
|
-
break;
|
|
646
634
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
value: parseFloat(formData.value || formData.measurementValue),
|
|
635
|
+
if (formData.timeSlot) {
|
|
636
|
+
extra.push({
|
|
637
|
+
concept: PARTOGRAPHY_CONCEPTS['time-slot'] || PARTOGRAPHY_CONCEPTS['event-description'],
|
|
638
|
+
value: `TimeSlot: ${formData.timeSlot}`,
|
|
652
639
|
obsDatetime,
|
|
653
640
|
});
|
|
654
641
|
}
|
|
655
|
-
break;
|
|
656
642
|
|
|
657
|
-
|
|
643
|
+
return [...uterineObs, ...extra];
|
|
644
|
+
}
|
|
645
|
+
case 'maternal-pulse': {
|
|
646
|
+
return [];
|
|
647
|
+
}
|
|
648
|
+
case 'blood-pressure': {
|
|
649
|
+
return [];
|
|
650
|
+
}
|
|
651
|
+
case 'temperature': {
|
|
652
|
+
return buildTemperatureObservation(formData);
|
|
653
|
+
}
|
|
654
|
+
case 'urine-analysis': {
|
|
658
655
|
if (formData.proteinLevel) {
|
|
659
656
|
observations.push({
|
|
660
657
|
concept: PARTOGRAPHY_CONCEPTS['protein-level'],
|
|
@@ -677,12 +674,13 @@ function buildObservations(graphType: string, formData: any): any[] {
|
|
|
677
674
|
});
|
|
678
675
|
}
|
|
679
676
|
break;
|
|
677
|
+
}
|
|
680
678
|
|
|
681
679
|
case 'drugs-fluids':
|
|
682
|
-
if (formData.medication) {
|
|
680
|
+
if (formData.medication || formData.drugName) {
|
|
683
681
|
observations.push({
|
|
684
682
|
concept: PARTOGRAPHY_CONCEPTS['medication'],
|
|
685
|
-
value: formData.medication,
|
|
683
|
+
value: formData.medication || formData.drugName,
|
|
686
684
|
obsDatetime,
|
|
687
685
|
});
|
|
688
686
|
}
|
|
@@ -693,6 +691,20 @@ function buildObservations(graphType: string, formData: any): any[] {
|
|
|
693
691
|
obsDatetime,
|
|
694
692
|
});
|
|
695
693
|
}
|
|
694
|
+
if (formData.route) {
|
|
695
|
+
observations.push({
|
|
696
|
+
concept: PARTOGRAPHY_CONCEPTS['route'] || PARTOGRAPHY_CONCEPTS['event-description'],
|
|
697
|
+
value: formData.route,
|
|
698
|
+
obsDatetime,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
if (formData.frequency) {
|
|
702
|
+
observations.push({
|
|
703
|
+
concept: PARTOGRAPHY_CONCEPTS['frequency'] || PARTOGRAPHY_CONCEPTS['event-description'],
|
|
704
|
+
value: formData.frequency,
|
|
705
|
+
obsDatetime,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
696
708
|
break;
|
|
697
709
|
|
|
698
710
|
case 'progress-events':
|
|
@@ -729,120 +741,11 @@ function buildObservations(graphType: string, formData: any): any[] {
|
|
|
729
741
|
return observations;
|
|
730
742
|
}
|
|
731
743
|
|
|
732
|
-
export function transformEncounterToChartData(encounters:
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
hour: '2-digit',
|
|
738
|
-
minute: '2-digit',
|
|
739
|
-
hour12: false,
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
encounter.obs?.forEach((obs) => {
|
|
743
|
-
try {
|
|
744
|
-
if (typeof obs.value === 'string' && obs.value.startsWith('{')) {
|
|
745
|
-
const parsedData = JSON.parse(obs.value);
|
|
746
|
-
|
|
747
|
-
if (parsedData.graphType === graphType && parsedData.data) {
|
|
748
|
-
const formData = parsedData.data;
|
|
749
|
-
const value = formData.value || formData.measurementValue;
|
|
750
|
-
|
|
751
|
-
if (value) {
|
|
752
|
-
const dataPoint = {
|
|
753
|
-
group: getGraphTypeDisplayName(graphType),
|
|
754
|
-
time: formData.time || encounterTime,
|
|
755
|
-
value: parseFloat(value),
|
|
756
|
-
};
|
|
757
|
-
chartData.push(dataPoint);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
} else {
|
|
761
|
-
const conceptUuid = obs.concept.uuid;
|
|
762
|
-
let value = null;
|
|
763
|
-
let groupName = '';
|
|
764
|
-
|
|
765
|
-
switch (conceptUuid) {
|
|
766
|
-
case PARTOGRAPHY_CONCEPTS['fetal-heart-rate']:
|
|
767
|
-
if (graphType === 'fetal-heart-rate') {
|
|
768
|
-
value = parseFloat(obs.value as string);
|
|
769
|
-
groupName = getGraphTypeDisplayName('fetal-heart-rate');
|
|
770
|
-
}
|
|
771
|
-
break;
|
|
772
|
-
case PARTOGRAPHY_CONCEPTS['cervical-dilation']:
|
|
773
|
-
if (graphType === 'cervical-dilation') {
|
|
774
|
-
value = parseFloat(obs.value as string);
|
|
775
|
-
groupName = getGraphTypeDisplayName('cervical-dilation');
|
|
776
|
-
}
|
|
777
|
-
break;
|
|
778
|
-
case PARTOGRAPHY_CONCEPTS['descent-of-head']:
|
|
779
|
-
if (graphType === 'descent-of-head') {
|
|
780
|
-
value = getStationValue(obs.value as string) ?? parseFloat(obs.value as string);
|
|
781
|
-
groupName = getGraphTypeDisplayName('descent-of-head');
|
|
782
|
-
}
|
|
783
|
-
break;
|
|
784
|
-
case PARTOGRAPHY_CONCEPTS['uterine-contractions']:
|
|
785
|
-
if (graphType === 'uterine-contractions') {
|
|
786
|
-
value = parseFloat(obs.value as string);
|
|
787
|
-
groupName = getGraphTypeDisplayName('uterine-contractions');
|
|
788
|
-
}
|
|
789
|
-
break;
|
|
790
|
-
case PARTOGRAPHY_CONCEPTS['maternal-pulse']:
|
|
791
|
-
if (graphType === 'maternal-pulse') {
|
|
792
|
-
value = parseFloat(obs.value as string);
|
|
793
|
-
groupName = getGraphTypeDisplayName('maternal-pulse');
|
|
794
|
-
}
|
|
795
|
-
break;
|
|
796
|
-
case PARTOGRAPHY_CONCEPTS['systolic-bp']:
|
|
797
|
-
if (graphType === 'blood-pressure') {
|
|
798
|
-
value = parseFloat(obs.value as string);
|
|
799
|
-
groupName = 'Systolic';
|
|
800
|
-
}
|
|
801
|
-
break;
|
|
802
|
-
case PARTOGRAPHY_CONCEPTS['diastolic-bp']:
|
|
803
|
-
if (graphType === 'blood-pressure') {
|
|
804
|
-
value = parseFloat(obs.value as string);
|
|
805
|
-
groupName = 'Diastolic';
|
|
806
|
-
}
|
|
807
|
-
break;
|
|
808
|
-
case PARTOGRAPHY_CONCEPTS['temperature']:
|
|
809
|
-
if (graphType === 'temperature') {
|
|
810
|
-
value = parseFloat(obs.value as string);
|
|
811
|
-
groupName = getGraphTypeDisplayName('temperature');
|
|
812
|
-
}
|
|
813
|
-
break;
|
|
814
|
-
default:
|
|
815
|
-
if (typeof obs.value === 'number' || !isNaN(parseFloat(obs.value as string))) {
|
|
816
|
-
value = parseFloat(obs.value as string);
|
|
817
|
-
groupName = getGraphTypeDisplayName(graphType);
|
|
818
|
-
}
|
|
819
|
-
break;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
if (value !== null && groupName) {
|
|
823
|
-
const dataPoint = {
|
|
824
|
-
group: groupName,
|
|
825
|
-
time: encounterTime,
|
|
826
|
-
value: value,
|
|
827
|
-
};
|
|
828
|
-
chartData.push(dataPoint);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
} catch (e) {}
|
|
832
|
-
});
|
|
833
|
-
});
|
|
834
|
-
|
|
835
|
-
return chartData.sort((a, b) => {
|
|
836
|
-
// Parse time strings (HH:MM format) for comparison
|
|
837
|
-
const timeA = a.time.split(':').map(Number);
|
|
838
|
-
const timeB = b.time.split(':').map(Number);
|
|
839
|
-
|
|
840
|
-
// Convert to minutes for easy comparison
|
|
841
|
-
const minutesA = timeA[0] * 60 + (timeA[1] || 0);
|
|
842
|
-
const minutesB = timeB[0] * 60 + (timeB[1] || 0);
|
|
843
|
-
|
|
844
|
-
return minutesA - minutesB;
|
|
845
|
-
});
|
|
744
|
+
export function transformEncounterToChartData(encounters: any[], graphType: string): any[] {
|
|
745
|
+
if (graphType === 'temperature') {
|
|
746
|
+
return transformTemperatureEncounterToChartData(encounters);
|
|
747
|
+
}
|
|
748
|
+
return [];
|
|
846
749
|
}
|
|
847
750
|
|
|
848
751
|
function getGroupNameForGraph(graphType: string): string {
|
|
@@ -1022,3 +925,7 @@ export function transformEncounterToTableData(
|
|
|
1022
925
|
(a, b) => new Date(a.dateTime.split(' — ')[0]).getTime() - new Date(b.dateTime.split(' — ')[0]).getTime(),
|
|
1023
926
|
);
|
|
1024
927
|
}
|
|
928
|
+
|
|
929
|
+
export { useFetalHeartRateData } from './resources/fetal-heart-rate.resource';
|
|
930
|
+
export { useDrugOrders, saveDrugOrderData } from './resources/drugs-fluids.resource';
|
|
931
|
+
export { saveMembraneAmnioticFluidData } from './resources/membrane-amniotic-fluid.resource';
|