@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.
Files changed (66) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/dist/805.js +1 -0
  3. package/dist/805.js.map +1 -0
  4. package/dist/kenyaemr-esm-patient-clinical-view-app.js +2 -2
  5. package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +27 -27
  6. package/dist/main.js +27 -27
  7. package/dist/main.js.map +1 -1
  8. package/dist/routes.json +1 -1
  9. package/package.json +1 -1
  10. package/src/config-schema.ts +97 -0
  11. package/src/contact-list/contact-tracing-history.component.tsx +18 -15
  12. package/src/maternal-and-child-health/partography/components/pulse-bp-graph.component.tsx +1 -0
  13. package/src/maternal-and-child-health/partography/components/temperature-graph.component.tsx +218 -0
  14. package/src/maternal-and-child-health/partography/components/uterine-contractions-graph.component.tsx +209 -0
  15. package/src/maternal-and-child-health/partography/forms/cervical-contractions-form.component.tsx +211 -0
  16. package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +354 -0
  17. package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +321 -0
  18. package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +275 -0
  19. package/src/maternal-and-child-health/partography/forms/index.ts +9 -0
  20. package/src/maternal-and-child-health/partography/forms/membrane-amniotic-fluid-form.component.tsx +330 -0
  21. package/src/maternal-and-child-health/partography/forms/oxytocin-form.component.tsx +207 -0
  22. package/src/maternal-and-child-health/partography/forms/pulse-bp-form.component.tsx +174 -0
  23. package/src/maternal-and-child-health/partography/forms/temperature-form.component.tsx +210 -0
  24. package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.component.tsx +218 -0
  25. package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.scss +107 -0
  26. package/src/maternal-and-child-health/partography/forms/time-picker-with-clock.component.tsx +174 -0
  27. package/src/maternal-and-child-health/partography/forms/time-picker-with-clock.scss +178 -0
  28. package/src/maternal-and-child-health/partography/forms/urine-test-form.component.tsx +255 -0
  29. package/src/maternal-and-child-health/partography/forms/useCervixData.ts +16 -0
  30. package/src/maternal-and-child-health/partography/graphs/cervical-contractions-graph.component.tsx +266 -0
  31. package/src/maternal-and-child-health/partography/graphs/cervix-graph.component.tsx +429 -0
  32. package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph-wrapper.component.tsx +163 -0
  33. package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph.component.tsx +82 -0
  34. package/src/maternal-and-child-health/partography/graphs/fetal-heart-rate-graph.component.tsx +359 -0
  35. package/src/maternal-and-child-health/partography/graphs/index.ts +10 -0
  36. package/src/maternal-and-child-health/partography/graphs/membrane-amniotic-fluid-graph.component.tsx +266 -0
  37. package/src/maternal-and-child-health/partography/graphs/oxytocin-graph-wrapper.component.tsx +190 -0
  38. package/src/maternal-and-child-health/partography/graphs/oxytocin-graph.component.tsx +126 -0
  39. package/src/maternal-and-child-health/partography/graphs/partograph-graph.component.tsx +266 -0
  40. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph-wrapper.component.tsx +298 -0
  41. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +267 -0
  42. package/src/maternal-and-child-health/partography/graphs/temperature-graph.component.tsx +242 -0
  43. package/src/maternal-and-child-health/partography/graphs/urine-test-graph.component.tsx +246 -0
  44. package/src/maternal-and-child-health/partography/partograph.component.tsx +2141 -118
  45. package/src/maternal-and-child-health/partography/partography-dashboard.meta.ts +8 -0
  46. package/src/maternal-and-child-health/partography/partography-data-form.scss +163 -0
  47. package/src/maternal-and-child-health/partography/partography.resource.ts +233 -326
  48. package/src/maternal-and-child-health/partography/partography.scss +1341 -3
  49. package/src/maternal-and-child-health/partography/resources/blood-pressure.resource.ts +96 -0
  50. package/src/maternal-and-child-health/partography/resources/cervical-dilation.resource.ts +109 -0
  51. package/src/maternal-and-child-health/partography/resources/cervix.resource.ts +362 -0
  52. package/src/maternal-and-child-health/partography/resources/descent-of-head.resource.ts +101 -0
  53. package/src/maternal-and-child-health/partography/resources/drugs-fluids.resource.ts +88 -0
  54. package/src/maternal-and-child-health/partography/resources/fetal-heart-rate.resource.ts +122 -0
  55. package/src/maternal-and-child-health/partography/resources/maternal-pulse.resource.ts +77 -0
  56. package/src/maternal-and-child-health/partography/resources/membrane-amniotic-fluid.resource.ts +108 -0
  57. package/src/maternal-and-child-health/partography/resources/oxytocin.resource.ts +159 -0
  58. package/src/maternal-and-child-health/partography/resources/progress-events.resource.ts +6 -0
  59. package/src/maternal-and-child-health/partography/resources/pulse-bp-combined.resource.ts +53 -0
  60. package/src/maternal-and-child-health/partography/resources/temperature.resource.ts +84 -0
  61. package/src/maternal-and-child-health/partography/resources/uterine-contractions.resource.ts +173 -0
  62. package/src/maternal-and-child-health/partography/table/temperature-table.component.tsx +99 -0
  63. package/src/maternal-and-child-health/partography/table/uterine-contractions-table.component.tsx +86 -0
  64. package/src/maternal-and-child-health/partography/types/index.ts +319 -101
  65. package/dist/397.js +0 -1
  66. 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
+ }
@@ -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,6 @@
1
+ // Resource file for progress events graph
2
+
3
+ // TODO: Move progress events hooks and logic here from partography.resource.ts
4
+
5
+ // Placeholder export to resolve module error
6
+ export const progressEventsResource = {};
@@ -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
+ }