@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2714 → 5.4.2-pre.2722
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/805.js +1 -0
- package/dist/805.js.map +1 -0
- package/dist/kenyaemr-esm-patient-clinical-view-app.js +2 -2
- package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +27 -27
- 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/dist/397.js +0 -1
- package/dist/397.js.map +0 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
|
|
4
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
5
|
+
import { createPartographyEncounter } from '../partography.resource';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { DRUG_ORDER_TYPE_UUID } from '../../../config-schema';
|
|
8
|
+
|
|
9
|
+
export async function saveDrugOrderData(
|
|
10
|
+
patientUuid: string,
|
|
11
|
+
formData: {
|
|
12
|
+
drugName: string;
|
|
13
|
+
dosage: string;
|
|
14
|
+
route: string;
|
|
15
|
+
frequency: string;
|
|
16
|
+
},
|
|
17
|
+
t: (key: string, defaultValue?: string) => string,
|
|
18
|
+
): Promise<{ success: boolean; message: string }> {
|
|
19
|
+
try {
|
|
20
|
+
const result = await createPartographyEncounter(patientUuid, 'drugs-fluids', formData);
|
|
21
|
+
return result;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
message: error?.message || t('Failed to save drug order data'),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useDrugOrders(patientUuid: string) {
|
|
31
|
+
const { t } = useTranslation();
|
|
32
|
+
const apiUrl = patientUuid
|
|
33
|
+
? `${restBaseUrl}/order?patient=${patientUuid}&orderType=${DRUG_ORDER_TYPE_UUID}&v=full&limit=50`
|
|
34
|
+
: null;
|
|
35
|
+
|
|
36
|
+
const { data, error, isLoading, mutate } = useSWR(apiUrl, openmrsFetch);
|
|
37
|
+
|
|
38
|
+
const drugOrders = useMemo(() => {
|
|
39
|
+
const responseData = data?.data as any;
|
|
40
|
+
|
|
41
|
+
if (!responseData?.results || !Array.isArray(responseData.results)) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const allOrders = responseData.results;
|
|
46
|
+
const activeOrders = allOrders
|
|
47
|
+
.filter((order: any) => order.action === 'NEW' && !order.dateStopped)
|
|
48
|
+
.sort((a: any, b: any) => {
|
|
49
|
+
const dateA = a.dateActivated ? new Date(a.dateActivated).getTime() : 0;
|
|
50
|
+
const dateB = b.dateActivated ? new Date(b.dateActivated).getTime() : 0;
|
|
51
|
+
return dateB - dateA;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const processedOrders = activeOrders.map((order: any) => {
|
|
55
|
+
const processed = {
|
|
56
|
+
id: order.uuid,
|
|
57
|
+
drugName: order.drug?.display || order.drugNonCoded || t('Unknown Drug'),
|
|
58
|
+
dosage: `${order.dose || ''} ${order.doseUnits?.display || ''}`.trim(),
|
|
59
|
+
route: order.route?.display || '',
|
|
60
|
+
frequency: order.frequency?.display || '',
|
|
61
|
+
date: order.dateActivated ? new Date(order.dateActivated).toLocaleDateString() : '',
|
|
62
|
+
orderNumber: order.orderNumber,
|
|
63
|
+
display: order.display,
|
|
64
|
+
quantity: order.quantity,
|
|
65
|
+
duration: order.duration,
|
|
66
|
+
durationUnits: order.durationUnits?.display,
|
|
67
|
+
asNeeded: order.asNeeded,
|
|
68
|
+
instructions: order.instructions,
|
|
69
|
+
orderer: order.orderer?.display,
|
|
70
|
+
};
|
|
71
|
+
return processed;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return processedOrders;
|
|
75
|
+
}, [data, t]);
|
|
76
|
+
|
|
77
|
+
let localizedError = error;
|
|
78
|
+
if (error) {
|
|
79
|
+
localizedError = t('Failed to load drug orders');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
drugOrders,
|
|
84
|
+
isLoading,
|
|
85
|
+
error: localizedError,
|
|
86
|
+
mutate,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { openmrsFetch, restBaseUrl, toOmrsIsoString } from '@openmrs/esm-framework';
|
|
3
|
+
import useSWR from 'swr';
|
|
4
|
+
import { MCH_PARTOGRAPHY_ENCOUNTER_UUID, PARTOGRAPHY_CONCEPTS } from '../types';
|
|
5
|
+
import { createPartographyEncounter } from '../partography.resource';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
|
|
8
|
+
export interface FetalHeartRateEntry {
|
|
9
|
+
id: string;
|
|
10
|
+
uuid: string;
|
|
11
|
+
encounterUuid: string;
|
|
12
|
+
fetalHeartRate: number;
|
|
13
|
+
hour: number;
|
|
14
|
+
time: string;
|
|
15
|
+
date: string;
|
|
16
|
+
encounterDatetime: string;
|
|
17
|
+
obsDatetime: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useFetalHeartRateData(patientUuid: string) {
|
|
21
|
+
const { t } = useTranslation();
|
|
22
|
+
const fetcher = (url: string) => openmrsFetch(url).then((res) => res.json());
|
|
23
|
+
const { data, error, isLoading, mutate } = useSWR(
|
|
24
|
+
patientUuid
|
|
25
|
+
? `${restBaseUrl}/encounter?patient=${patientUuid}&encounterType=${MCH_PARTOGRAPHY_ENCOUNTER_UUID}&v=full&limit=100&order=desc`
|
|
26
|
+
: null,
|
|
27
|
+
fetcher,
|
|
28
|
+
);
|
|
29
|
+
const fetalHeartRateData: FetalHeartRateEntry[] = React.useMemo(() => {
|
|
30
|
+
if (!data?.results || !Array.isArray(data.results)) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
const fetalHeartRateEntries: FetalHeartRateEntry[] = [];
|
|
34
|
+
for (const encounter of data.results) {
|
|
35
|
+
if (!encounter.obs || !Array.isArray(encounter.obs)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const fetalHeartRateObs = encounter.obs.find(
|
|
39
|
+
(obs) => obs.concept.uuid === PARTOGRAPHY_CONCEPTS['fetal-heart-rate'],
|
|
40
|
+
);
|
|
41
|
+
if (fetalHeartRateObs) {
|
|
42
|
+
const encounterDatetime = new Date(encounter.encounterDatetime);
|
|
43
|
+
let hour = 0;
|
|
44
|
+
let time = '';
|
|
45
|
+
const hourObs = encounter.obs.find(
|
|
46
|
+
(obs) =>
|
|
47
|
+
obs.concept.uuid === PARTOGRAPHY_CONCEPTS['fetal-heart-rate-hour'] &&
|
|
48
|
+
obs.value &&
|
|
49
|
+
typeof obs.value === 'string' &&
|
|
50
|
+
obs.value.startsWith('Hour:'),
|
|
51
|
+
);
|
|
52
|
+
const timeObs = encounter.obs.find(
|
|
53
|
+
(obs) =>
|
|
54
|
+
obs.concept.uuid === PARTOGRAPHY_CONCEPTS['fetal-heart-rate-time'] &&
|
|
55
|
+
obs.value &&
|
|
56
|
+
typeof obs.value === 'string' &&
|
|
57
|
+
obs.value.startsWith('Time:'),
|
|
58
|
+
);
|
|
59
|
+
if (hourObs && typeof hourObs.value === 'string') {
|
|
60
|
+
const hourMatch = hourObs.value.match(/Hour:\s*([0-9.]+)/);
|
|
61
|
+
if (hourMatch) {
|
|
62
|
+
hour = parseFloat(hourMatch[1]) || 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (timeObs && typeof timeObs.value === 'string') {
|
|
66
|
+
const timeMatch = timeObs.value.match(/Time:\s*(.+)/);
|
|
67
|
+
if (timeMatch) {
|
|
68
|
+
time = timeMatch[1].trim();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
fetalHeartRateEntries.push({
|
|
72
|
+
id: `fhr-${fetalHeartRateObs.uuid}`,
|
|
73
|
+
uuid: fetalHeartRateObs.uuid,
|
|
74
|
+
encounterUuid: encounter.uuid,
|
|
75
|
+
fetalHeartRate: parseFloat(fetalHeartRateObs.value) || 0,
|
|
76
|
+
hour,
|
|
77
|
+
time,
|
|
78
|
+
date: encounterDatetime.toLocaleDateString(),
|
|
79
|
+
encounterDatetime: encounterDatetime.toISOString(),
|
|
80
|
+
obsDatetime: fetalHeartRateObs.obsDatetime,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return fetalHeartRateEntries.sort(
|
|
85
|
+
(a, b) => new Date(b.encounterDatetime).getTime() - new Date(a.encounterDatetime).getTime(),
|
|
86
|
+
);
|
|
87
|
+
}, [data]);
|
|
88
|
+
let localizedError = error;
|
|
89
|
+
if (error) {
|
|
90
|
+
localizedError = t('Failed to load fetal heart rate data');
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
fetalHeartRateData,
|
|
94
|
+
isLoading,
|
|
95
|
+
error: localizedError,
|
|
96
|
+
mutate,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function saveFetalHeartRateData(
|
|
101
|
+
patientUuid: string,
|
|
102
|
+
formData: { hour: number; time: string; fetalHeartRate: number },
|
|
103
|
+
t: (key: string, defaultValue?: string, options?: any) => string,
|
|
104
|
+
locationUuid?: string,
|
|
105
|
+
providerUuid?: string,
|
|
106
|
+
): Promise<{ success: boolean; message: string }> {
|
|
107
|
+
try {
|
|
108
|
+
const result = await createPartographyEncounter(
|
|
109
|
+
patientUuid,
|
|
110
|
+
'fetal-heart-rate',
|
|
111
|
+
formData,
|
|
112
|
+
locationUuid,
|
|
113
|
+
providerUuid,
|
|
114
|
+
);
|
|
115
|
+
return result;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
message: error?.message || t('Failed to save fetal heart rate data'),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next';
|
|
2
|
+
import { usePartographyEncounters } from '../partography.resource';
|
|
3
|
+
import { PARTOGRAPHY_CONCEPTS } from '../types';
|
|
4
|
+
import { toOmrsIsoString } from '@openmrs/esm-framework';
|
|
5
|
+
|
|
6
|
+
export function transformMaternalPulseEncounterToChartData(
|
|
7
|
+
encounters: any[],
|
|
8
|
+
t: (key: string, defaultValue?: string, options?: any) => string,
|
|
9
|
+
): any[] {
|
|
10
|
+
const chartData = [];
|
|
11
|
+
encounters.forEach((encounter) => {
|
|
12
|
+
const encounterTime = new Date(encounter.encounterDatetime).toLocaleTimeString('en-US', {
|
|
13
|
+
hour: '2-digit',
|
|
14
|
+
minute: '2-digit',
|
|
15
|
+
hour12: false,
|
|
16
|
+
});
|
|
17
|
+
encounter.obs?.forEach((obs) => {
|
|
18
|
+
if (obs.concept.uuid === PARTOGRAPHY_CONCEPTS['maternal-pulse']) {
|
|
19
|
+
chartData.push({ group: t('Maternal Pulse'), time: encounterTime, value: parseFloat(obs.value) });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
return chartData;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function transformMaternalPulseEncounterToTableData(
|
|
27
|
+
encounters: any[],
|
|
28
|
+
t: (key: string, defaultValue?: string, options?: any) => string,
|
|
29
|
+
): any[] {
|
|
30
|
+
const tableData = [];
|
|
31
|
+
encounters.forEach((encounter, index) => {
|
|
32
|
+
const encounterDate = new Date(encounter.encounterDatetime);
|
|
33
|
+
const dateTime = `${encounterDate.toLocaleDateString()} ${encounterDate.toLocaleTimeString('en-US', {
|
|
34
|
+
hour: '2-digit',
|
|
35
|
+
minute: '2-digit',
|
|
36
|
+
hour12: false,
|
|
37
|
+
})}`;
|
|
38
|
+
encounter.obs?.forEach((obs, obsIndex) => {
|
|
39
|
+
if (obs.concept.uuid === PARTOGRAPHY_CONCEPTS['maternal-pulse']) {
|
|
40
|
+
tableData.push({
|
|
41
|
+
id: `maternal-pulse-${index}-${obsIndex}`,
|
|
42
|
+
dateTime,
|
|
43
|
+
value: parseFloat(obs.value),
|
|
44
|
+
unit: t('BPM'),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return tableData;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useMaternalPulseData(patientUuid: string) {
|
|
53
|
+
const { encounters, isLoading, error, mutate } = usePartographyEncounters(patientUuid, 'maternal-pulse');
|
|
54
|
+
return { data: encounters, isLoading, error, mutate };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function buildMaternalPulseObservation(formData: any): any[] {
|
|
58
|
+
const timeConfig = { defaultEncounterOffset: 0 };
|
|
59
|
+
const obsDatetime = toOmrsIsoString(new Date(Date.now() - timeConfig.defaultEncounterOffset));
|
|
60
|
+
const observations = [];
|
|
61
|
+
if (formData.value || formData.measurementValue) {
|
|
62
|
+
observations.push({
|
|
63
|
+
concept: PARTOGRAPHY_CONCEPTS['maternal-pulse'],
|
|
64
|
+
value: parseFloat(formData.value || formData.measurementValue),
|
|
65
|
+
obsDatetime,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
// Optional time slot observation for pulse/BP graph (if provided by the form)
|
|
69
|
+
if (formData.time || formData.timeSlot) {
|
|
70
|
+
observations.push({
|
|
71
|
+
concept: PARTOGRAPHY_CONCEPTS['pulse-time-slot'] || PARTOGRAPHY_CONCEPTS['time-slot'],
|
|
72
|
+
value: formData.time || formData.timeSlot,
|
|
73
|
+
obsDatetime,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return observations;
|
|
77
|
+
}
|
package/src/maternal-and-child-health/partography/resources/membrane-amniotic-fluid.resource.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
4
|
+
import { PARTOGRAPHY_CONCEPTS, MCH_PARTOGRAPHY_ENCOUNTER_UUID } from '../types';
|
|
5
|
+
import { createPartographyEncounter } from '../partography.resource';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
|
|
8
|
+
export function useMembraneAmnioticFluidData(patientUuid: string) {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
const fetcher = (url: string) => openmrsFetch(url).then((res) => res.json());
|
|
11
|
+
|
|
12
|
+
const { data, error, isLoading, mutate } = useSWR(
|
|
13
|
+
patientUuid
|
|
14
|
+
? `${restBaseUrl}/encounter?patient=${patientUuid}&encounterType=${MCH_PARTOGRAPHY_ENCOUNTER_UUID}&v=full&limit=100&order=desc`
|
|
15
|
+
: null,
|
|
16
|
+
fetcher,
|
|
17
|
+
{
|
|
18
|
+
revalidateOnFocus: true,
|
|
19
|
+
revalidateOnReconnect: true,
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const membraneAmnioticFluidEntries = useMemo(() => {
|
|
24
|
+
if (!data?.results || !Array.isArray(data.results)) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const entries = [];
|
|
29
|
+
for (const encounter of data.results) {
|
|
30
|
+
if (!encounter.obs || !Array.isArray(encounter.obs)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const amnioticFluidObs = encounter.obs.find((obs) => obs.concept.uuid === PARTOGRAPHY_CONCEPTS['amniotic-fluid']);
|
|
35
|
+
const mouldingObs = encounter.obs.find((obs) => obs.concept.uuid === PARTOGRAPHY_CONCEPTS['moulding']);
|
|
36
|
+
const timeObs = encounter.obs.find(
|
|
37
|
+
(obs) =>
|
|
38
|
+
obs.concept.uuid === PARTOGRAPHY_CONCEPTS['fetal-heart-rate-time'] &&
|
|
39
|
+
typeof obs.value === 'string' &&
|
|
40
|
+
obs.value.startsWith('Time:'),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
let time = '';
|
|
44
|
+
if (timeObs && typeof timeObs.value === 'string') {
|
|
45
|
+
const timeMatch = timeObs.value.match(/Time:\s*(.+)/);
|
|
46
|
+
if (timeMatch) {
|
|
47
|
+
time = timeMatch[1].trim();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (amnioticFluidObs || mouldingObs) {
|
|
52
|
+
entries.push({
|
|
53
|
+
id: `maf-${encounter.uuid}`,
|
|
54
|
+
uuid: encounter.uuid,
|
|
55
|
+
amnioticFluid: amnioticFluidObs?.value?.display || amnioticFluidObs?.value || '',
|
|
56
|
+
moulding: mouldingObs?.value?.display || mouldingObs?.value || '',
|
|
57
|
+
time,
|
|
58
|
+
date: new Date(encounter.encounterDatetime).toLocaleDateString(),
|
|
59
|
+
encounterDatetime: encounter.encounterDatetime,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return entries;
|
|
64
|
+
}, [data]);
|
|
65
|
+
|
|
66
|
+
let localizedError = error;
|
|
67
|
+
if (error) {
|
|
68
|
+
localizedError = t('Failed to load membrane amniotic fluid data');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
membraneAmnioticFluidEntries,
|
|
73
|
+
isLoading,
|
|
74
|
+
error: localizedError,
|
|
75
|
+
mutate,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function saveMembraneAmnioticFluidData(
|
|
80
|
+
patientUuid: string,
|
|
81
|
+
formData: { amnioticFluid: string; moulding: string; time: string },
|
|
82
|
+
t: (key: string, defaultValue?: string, options?: any) => string,
|
|
83
|
+
locationUuid?: string,
|
|
84
|
+
providerUuid?: string,
|
|
85
|
+
): Promise<{ success: boolean; message: string }> {
|
|
86
|
+
try {
|
|
87
|
+
const result = await createPartographyEncounter(
|
|
88
|
+
patientUuid,
|
|
89
|
+
'membrane-amniotic-fluid',
|
|
90
|
+
formData,
|
|
91
|
+
locationUuid,
|
|
92
|
+
providerUuid,
|
|
93
|
+
);
|
|
94
|
+
if (result?.success && result?.encounter) {
|
|
95
|
+
return result;
|
|
96
|
+
} else {
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
message: result?.message || t('Failed to save membrane amniotic fluid data'),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return {
|
|
104
|
+
success: false,
|
|
105
|
+
message: error?.message || t('Failed to save membrane amniotic fluid data'),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl, toOmrsIsoString } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { MCH_PARTOGRAPHY_ENCOUNTER_UUID } from '../forms/useCervixData';
|
|
5
|
+
import { ENCOUNTER_ROLE } from '../../../config-schema';
|
|
6
|
+
import { OXYTOCIN_FORM_CONCEPTS } from '../types';
|
|
7
|
+
|
|
8
|
+
export interface OxytocinFormData {
|
|
9
|
+
time: string;
|
|
10
|
+
dropsPerMinute: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SaveOxytocinDataResponse {
|
|
14
|
+
success: boolean;
|
|
15
|
+
message: string;
|
|
16
|
+
encounter?: any;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useOxytocinData(patientUuid: string) {
|
|
21
|
+
const { t } = useTranslation();
|
|
22
|
+
const encounterRepresentation =
|
|
23
|
+
'custom:(uuid,encounterDatetime,encounterType:(uuid,display),' +
|
|
24
|
+
'obs:(uuid,concept:(uuid,display),value,obsDatetime,display),' +
|
|
25
|
+
'patient:(uuid))';
|
|
26
|
+
|
|
27
|
+
const url = `/ws/rest/v1/encounter?patient=${patientUuid}&encounterType=${MCH_PARTOGRAPHY_ENCOUNTER_UUID}&v=${encounterRepresentation}`;
|
|
28
|
+
|
|
29
|
+
const { data, error, isLoading, mutate } = useSWR<{ data: { results: any[] } }>(
|
|
30
|
+
patientUuid ? url : null,
|
|
31
|
+
openmrsFetch,
|
|
32
|
+
);
|
|
33
|
+
const oxytocinData = data?.data?.results || [];
|
|
34
|
+
const sortedEncounters = oxytocinData.sort(
|
|
35
|
+
(a, b) => new Date(b.encounterDatetime).getTime() - new Date(a.encounterDatetime).getTime(),
|
|
36
|
+
);
|
|
37
|
+
const transformedData = sortedEncounters.map((encounter) => {
|
|
38
|
+
const observations = encounter.obs || [];
|
|
39
|
+
const timeObs = observations.find((obs) => obs.concept.uuid === OXYTOCIN_FORM_CONCEPTS.time);
|
|
40
|
+
const dropsObs = observations.find((obs) => obs.concept.uuid === OXYTOCIN_FORM_CONCEPTS.oxytocinDropsPerMinute);
|
|
41
|
+
return {
|
|
42
|
+
uuid: encounter.uuid,
|
|
43
|
+
encounterDatetime: encounter.encounterDatetime,
|
|
44
|
+
time: timeObs?.value ? String(timeObs.value) : null,
|
|
45
|
+
dropsPerMinute: dropsObs?.value ? Number(dropsObs.value) : null,
|
|
46
|
+
timeDisplay: timeObs?.value ? `${timeObs.value}` : null,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
const existingOxytocinEntries = transformedData
|
|
50
|
+
.filter((data) => data.time !== null && data.dropsPerMinute !== null)
|
|
51
|
+
.map((data) => ({ time: data.time!, dropsPerMinute: data.dropsPerMinute! }));
|
|
52
|
+
|
|
53
|
+
let localizedError = error;
|
|
54
|
+
if (error) {
|
|
55
|
+
localizedError = t('Failed to load oxytocin data');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
encounters: sortedEncounters,
|
|
60
|
+
oxytocinData: transformedData,
|
|
61
|
+
existingOxytocinEntries,
|
|
62
|
+
isLoading,
|
|
63
|
+
error: localizedError,
|
|
64
|
+
mutate,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function saveOxytocinFormData(
|
|
69
|
+
patientUuid: string,
|
|
70
|
+
formData: OxytocinFormData,
|
|
71
|
+
t: (key: string, defaultValue?: string, options?: any) => string,
|
|
72
|
+
providerUuid?: string,
|
|
73
|
+
locationUuid?: string,
|
|
74
|
+
): Promise<SaveOxytocinDataResponse> {
|
|
75
|
+
const observations = buildOxytocinObservations(formData);
|
|
76
|
+
const missingFields = [];
|
|
77
|
+
if (!formData.time || formData.time.trim() === '') {
|
|
78
|
+
missingFields.push('time');
|
|
79
|
+
}
|
|
80
|
+
if (!formData.dropsPerMinute || isNaN(parseFloat(formData.dropsPerMinute))) {
|
|
81
|
+
missingFields.push('dropsPerMinute');
|
|
82
|
+
}
|
|
83
|
+
if (missingFields.length > 0) {
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
message: t(`Missing or invalid fields: ${missingFields.join(', ')}`),
|
|
87
|
+
error: 'INVALID_FIELDS',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const session = await getCurrentSession();
|
|
91
|
+
const finalProviderUuid = providerUuid || session?.currentProvider?.uuid;
|
|
92
|
+
const finalLocationUuid = locationUuid || session?.sessionLocation?.uuid;
|
|
93
|
+
if (!finalProviderUuid) {
|
|
94
|
+
return { success: false, message: t('Provider information is required'), error: 'NO_PROVIDER' };
|
|
95
|
+
}
|
|
96
|
+
if (!finalLocationUuid) {
|
|
97
|
+
return { success: false, message: t('Location information is required'), error: 'NO_LOCATION' };
|
|
98
|
+
}
|
|
99
|
+
const now = new Date();
|
|
100
|
+
now.setMinutes(now.getMinutes() - 1);
|
|
101
|
+
const encounterPayload = {
|
|
102
|
+
patient: patientUuid,
|
|
103
|
+
encounterType: MCH_PARTOGRAPHY_ENCOUNTER_UUID,
|
|
104
|
+
location: finalLocationUuid,
|
|
105
|
+
encounterDatetime: toOmrsIsoString(now),
|
|
106
|
+
obs: observations,
|
|
107
|
+
encounterProviders: [
|
|
108
|
+
{
|
|
109
|
+
provider: finalProviderUuid,
|
|
110
|
+
encounterRole: ENCOUNTER_ROLE,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
const response = await openmrsFetch(`${restBaseUrl}/encounter`, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: { 'Content-Type': 'application/json' },
|
|
117
|
+
body: JSON.stringify(encounterPayload),
|
|
118
|
+
});
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
const errorText = await response.text();
|
|
121
|
+
let errorMessage = t(`Failed to save oxytocin data: ${response.status} ${response.statusText}`);
|
|
122
|
+
try {
|
|
123
|
+
const errorData = JSON.parse(errorText);
|
|
124
|
+
if (errorData.error?.message) {
|
|
125
|
+
errorMessage = t(`Failed to save oxytocin data: ${errorData.error.message}`);
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {}
|
|
128
|
+
return { success: false, message: errorMessage, error: 'SAVE_FAILED' };
|
|
129
|
+
}
|
|
130
|
+
const encounter = await response.json();
|
|
131
|
+
return { success: true, message: t('Oxytocin data saved successfully'), encounter };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildOxytocinObservations(formData: OxytocinFormData): any[] {
|
|
135
|
+
const observations = [];
|
|
136
|
+
const obsDatetime = toOmrsIsoString(new Date());
|
|
137
|
+
if (formData.time && formData.time !== '') {
|
|
138
|
+
observations.push({ concept: OXYTOCIN_FORM_CONCEPTS.time, value: formData.time, obsDatetime });
|
|
139
|
+
}
|
|
140
|
+
if (formData.dropsPerMinute && formData.dropsPerMinute !== '') {
|
|
141
|
+
const dropsValue = parseFloat(formData.dropsPerMinute);
|
|
142
|
+
if (!isNaN(dropsValue)) {
|
|
143
|
+
observations.push({ concept: OXYTOCIN_FORM_CONCEPTS.oxytocinDropsPerMinute, value: dropsValue, obsDatetime });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return observations;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function getCurrentSession() {
|
|
150
|
+
try {
|
|
151
|
+
const response = await openmrsFetch(`${restBaseUrl}/session`);
|
|
152
|
+
if (response.ok) {
|
|
153
|
+
return await response.json();
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
// Swallow error silently
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useMaternalPulseData,
|
|
3
|
+
transformMaternalPulseEncounterToChartData,
|
|
4
|
+
transformMaternalPulseEncounterToTableData,
|
|
5
|
+
} from './maternal-pulse.resource';
|
|
6
|
+
import {
|
|
7
|
+
useBloodPressureData,
|
|
8
|
+
transformBloodPressureEncounterToChartData,
|
|
9
|
+
transformBloodPressureEncounterToTableData,
|
|
10
|
+
} from './blood-pressure.resource';
|
|
11
|
+
import { useTranslation } from 'react-i18next';
|
|
12
|
+
|
|
13
|
+
export function usePulseBpCombinedData(patientUuid: string) {
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
const pulse = useMaternalPulseData(patientUuid);
|
|
16
|
+
const bp = useBloodPressureData(patientUuid);
|
|
17
|
+
let localizedError = pulse.error || bp.error;
|
|
18
|
+
if (pulse.error || bp.error) {
|
|
19
|
+
localizedError = t('Failed to load pulse or blood pressure data');
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
pulse,
|
|
23
|
+
bp,
|
|
24
|
+
isLoading: pulse.isLoading || bp.isLoading,
|
|
25
|
+
error: localizedError,
|
|
26
|
+
mutate: async () => {
|
|
27
|
+
await pulse.mutate();
|
|
28
|
+
await bp.mutate();
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function transformPulseBpCombinedToChartData(
|
|
34
|
+
pulseEncounters: any[],
|
|
35
|
+
bpEncounters: any[],
|
|
36
|
+
t: (key: string, defaultValue?: string, options?: any) => string,
|
|
37
|
+
): { pulse: any; bloodPressure: any } {
|
|
38
|
+
return {
|
|
39
|
+
pulse: transformMaternalPulseEncounterToChartData(pulseEncounters, t),
|
|
40
|
+
bloodPressure: transformBloodPressureEncounterToChartData(bpEncounters, t),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function transformPulseBpCombinedToTableData(
|
|
45
|
+
pulseEncounters: any[],
|
|
46
|
+
bpEncounters: any[],
|
|
47
|
+
t: (key: string, defaultValue?: string, options?: any) => string,
|
|
48
|
+
): { pulse: any; bloodPressure: any } {
|
|
49
|
+
return {
|
|
50
|
+
pulse: transformMaternalPulseEncounterToTableData(pulseEncounters, t),
|
|
51
|
+
bloodPressure: transformBloodPressureEncounterToTableData(bpEncounters, t),
|
|
52
|
+
};
|
|
53
|
+
}
|