@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2823 → 5.4.2-pre.2832

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 (25) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/dist/{791.js → 343.js} +1 -1
  3. package/dist/343.js.map +1 -0
  4. package/dist/kenyaemr-esm-patient-clinical-view-app.js +3 -3
  5. package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +27 -27
  6. package/dist/main.js +17 -17
  7. package/dist/main.js.map +1 -1
  8. package/dist/routes.json +1 -1
  9. package/package.json +1 -1
  10. package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +60 -9
  11. package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +21 -17
  12. package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +70 -6
  13. package/src/maternal-and-child-health/partography/forms/membrane-amniotic-fluid-form.component.tsx +30 -7
  14. package/src/maternal-and-child-health/partography/forms/urine-test-form.component.tsx +63 -6
  15. package/src/maternal-and-child-health/partography/graphs/cervix-graph.component.tsx +100 -46
  16. package/src/maternal-and-child-health/partography/graphs/oxytocin-graph.component.tsx +2 -1
  17. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +231 -133
  18. package/src/maternal-and-child-health/partography/partograph.component.tsx +141 -30
  19. package/src/maternal-and-child-health/partography/partography-data-form.scss +31 -12
  20. package/src/maternal-and-child-health/partography/partography.scss +22 -86
  21. package/src/maternal-and-child-health/partography/resources/cervix.resource.ts +15 -1
  22. package/src/maternal-and-child-health/partography/resources/membrane-amniotic-fluid.resource.ts +170 -1
  23. package/src/maternal-and-child-health/partography/resources/oxytocin.resource.ts +88 -15
  24. package/src/maternal-and-child-health/partography/resources/temperature.resource.ts +138 -1
  25. package/dist/791.js.map +0 -1
@@ -80,18 +80,13 @@
80
80
  justify-content: space-between;
81
81
  align-items: center;
82
82
  margin-bottom: layout.$spacing-05;
83
-
84
- h5 {
85
- @include type.type-style('heading-compact-01');
86
- margin: 0;
87
- }
88
- }
89
-
90
- .graphsSection {
91
- margin-bottom: layout.$spacing-08;
92
- }
93
-
94
- .graphContainer {
83
+ /* Allow cell to expand and wrap text */
84
+ width: auto;
85
+ min-width: 0;
86
+ max-width: none;
87
+ white-space: normal;
88
+ word-break: break-word;
89
+ overflow-wrap: anywhere;
95
90
  padding: layout.$spacing-04;
96
91
  background-color: colors.$white;
97
92
  border-radius: layout.$spacing-02;
@@ -664,7 +659,7 @@
664
659
  display: flex;
665
660
  align-items: center;
666
661
  justify-content: center;
667
- min-width: 60px; // Minimum column width
662
+ min-width: 60px;
668
663
 
669
664
  &:last-child {
670
665
  border-right: none;
@@ -675,21 +670,12 @@
675
670
  }
676
671
  }
677
672
 
678
- // Optional: Adjust chart's axis label styling if needed to make space or match font
679
673
  .cds--chart-wrapper {
680
- // You might need to add specific CSS here to the Carbon Chart itself
681
- // to fine-tune the bottom axis's appearance or padding if it overlaps
682
- // or leaves too much space. This can be complex due to shadow DOM.
683
- // E.g., .cds--chart-wrapper .cds--bottom-axis .cds--axis-labels { display: none; }
684
- // or .cds--chart-wrapper .cds--bottom-axis .cds--axis-title { padding-bottom: 0; }
685
-
686
674
  .cds--bottom-axis .cds--axis-title {
687
675
  padding-bottom: 0;
688
676
  }
689
677
 
690
- // Override line colors for Alert and Action lines
691
678
  .cds--line-path {
692
- // Alert Line - Yellow dotted line
693
679
  &[data-group-id='Alert Line'],
694
680
  &[aria-label*='Alert Line'] {
695
681
  stroke: #ffd700 !important;
@@ -697,7 +683,6 @@
697
683
  stroke-dasharray: 8, 4 !important;
698
684
  }
699
685
 
700
- // Action Line - Red dotted line
701
686
  &[data-group-id='Action Line'],
702
687
  &[aria-label*='Action Line'] {
703
688
  stroke: #ff0000 !important;
@@ -706,7 +691,6 @@
706
691
  }
707
692
  }
708
693
 
709
- // Alternative selector for different Carbon Charts versions
710
694
  svg path {
711
695
  &[data-name='Alert Line'] {
712
696
  stroke: #ffd700 !important;
@@ -786,9 +770,7 @@
786
770
  }
787
771
  }
788
772
 
789
- // Global overrides for Alert and Action lines (outside any wrapper)
790
773
  :global {
791
- // Try to target lines by data attributes
792
774
  [data-group='Alert Line'] {
793
775
  stroke: #ffd700 !important;
794
776
  stroke-width: 3px !important;
@@ -801,10 +783,7 @@
801
783
  stroke-dasharray: 8, 4 !important;
802
784
  }
803
785
 
804
- // Try broad SVG path targeting in cervix chart
805
786
  [data-chart-id='cervix'] svg path {
806
- // If there are 3 or more lines (cervix data + alert + action)
807
- // Target the last two as Alert and Action lines
808
787
  &:nth-last-child(2) {
809
788
  stroke: #ffd700 !important;
810
789
  stroke-width: 3px !important;
@@ -819,14 +798,10 @@
819
798
  }
820
799
  }
821
800
 
822
- // Optional: Adjust the height of the chart if the new labels cause overflow
823
801
  .chartContainer {
824
- height: 500px; // Keep this consistent
825
- // If the new labels cause overlap, you might need to make the chart slightly shorter
826
- // e.g., height: 450px; and then adjust overall graphContainer height.
802
+ height: 500px;
827
803
  }
828
804
 
829
- /* Fetal Heart Rate Section Styles */
830
805
  .fetalHeartRateSection {
831
806
  margin-bottom: layout.$spacing-07;
832
807
  width: 100%;
@@ -888,7 +863,6 @@
888
863
  flex-wrap: wrap;
889
864
  }
890
865
 
891
- // Orange tag style for low range
892
866
  .tagLowRange {
893
867
  background-color: #ff9800 !important;
894
868
  color: #fff !important;
@@ -896,57 +870,46 @@
896
870
  }
897
871
 
898
872
  .fetalHeartRateChart {
899
- min-height: 600px; // Increased height to match cervix graph
873
+ min-height: 600px;
900
874
  width: 100%;
901
875
  margin-top: layout.$spacing-04;
902
876
 
903
- // Override Carbon chart styles for this specific chart
904
877
  :global(.cds--cc--chart-svg) {
905
878
  border: 1px solid colors.$gray-30;
906
879
  border-radius: layout.$spacing-02;
907
- min-height: 600px; // Ensure minimum height
880
+ min-height: 600px;
908
881
  }
909
882
 
910
- // Chart wrapper to ensure proper sizing
911
883
  :global(.cds--cc--chart-wrapper) {
912
884
  min-height: 600px;
913
- padding-bottom: layout.$spacing-06; // Extra space for x-axis labels
885
+ padding-bottom: layout.$spacing-06;
914
886
  }
915
-
916
- // Grid line styling to match cervix graph
917
887
  :global(.cds--cc--grid-line) {
918
888
  stroke: colors.$gray-30;
919
889
  stroke-opacity: 0.3;
920
890
  }
921
-
922
- // Ensure x-axis labels have proper spacing and are horizontal
923
891
  :global(.cds--cc--axis-tick-label) {
924
892
  font-size: 12px;
925
893
  fill: colors.$gray-70;
926
894
  }
927
895
 
928
- // X-axis label positioning - ensure they are horizontal
929
896
  :global(.cds--cc--axis.cds--cc--axis--bottom .cds--cc--axis-tick-label) {
930
897
  text-anchor: middle;
931
- transform: none !important; // Force no rotation
898
+ transform: none !important;
932
899
  }
933
900
 
934
- // Y-axis styling
935
901
  :global(.cds--cc--axis.cds--cc--axis--left .cds--cc--axis-tick-label) {
936
902
  text-anchor: end;
937
903
  }
938
904
 
939
- // Ensure chart container has enough bottom padding for labels
940
905
  :global(.cds--cc--chart-holder) {
941
906
  padding-bottom: layout.$spacing-08 !important;
942
907
  }
943
908
 
944
- // Disable any text rotation on bottom axis
945
909
  :global(.cds--cc--axis--bottom text) {
946
910
  transform: rotate(0deg) !important;
947
911
  }
948
912
 
949
- // Axis styling
950
913
  :global(.cds--cc--axis) {
951
914
  .tick text {
952
915
  font-size: 12px;
@@ -959,7 +922,6 @@
959
922
  }
960
923
  }
961
924
 
962
- // Fetal Heart Rate Table Value Styling
963
925
  .fetalHeartRateValue {
964
926
  display: inline-flex;
965
927
  align-items: center;
@@ -994,7 +956,6 @@
994
956
  }
995
957
  }
996
958
 
997
- /* Membrane Amniotic Fluid Grid Styles */
998
959
  .membraneGrid {
999
960
  width: 100%;
1000
961
  margin-top: layout.$spacing-04;
@@ -1002,14 +963,16 @@
1002
963
  border: 1px solid colors.$gray-30;
1003
964
  border-radius: layout.$spacing-02;
1004
965
  overflow-x: auto;
1005
- max-height: 300px; // Set a reasonable height
1006
- max-width: 1540px; // Show approximately 20 grids (140px label + 70px * 20 columns = 1540px)
966
+ max-height: 300px;
967
+ max-width: 1540px;
1007
968
  }
1008
969
 
1009
970
  .gridContainer {
1010
971
  display: flex;
1011
972
  flex-direction: column;
1012
- min-width: fit-content; // Allow natural width based on content
973
+ min-width: 0;
974
+ width: auto;
975
+ max-width: 100%;
1013
976
  }
1014
977
 
1015
978
  .gridHeader {
@@ -1029,7 +992,7 @@
1029
992
  }
1030
993
 
1031
994
  .gridCell {
1032
- flex: 0 0 70px; // Fixed width for time columns for consistent layout
995
+ flex: 0 0 70px;
1033
996
  min-width: 70px;
1034
997
  padding: layout.$spacing-03;
1035
998
  border-right: 1px solid colors.$gray-20;
@@ -1071,7 +1034,6 @@
1071
1034
  font-style: italic;
1072
1035
  }
1073
1036
 
1074
- // Contraction Level Selector - Simple colored buttons
1075
1037
  .contractionLevelSelector {
1076
1038
  display: flex;
1077
1039
  flex-direction: row;
@@ -1141,14 +1103,12 @@
1141
1103
  cursor: pointer;
1142
1104
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
1143
1105
 
1144
- // None - Light gray background
1145
1106
  &.none {
1146
1107
  background-color: colors.$gray-30;
1147
1108
  color: colors.$gray-100;
1148
1109
  border-color: colors.$gray-50;
1149
1110
  }
1150
1111
 
1151
- // Mild - Black dots pattern
1152
1112
  &.mild {
1153
1113
  background-color: #fff;
1154
1114
  color: #111;
@@ -1157,7 +1117,6 @@
1157
1117
  background-size: 12px 12px;
1158
1118
  }
1159
1119
 
1160
- // Moderate - Diagonal black lines pattern (tuned)
1161
1120
  &.moderate {
1162
1121
  background-color: #fff;
1163
1122
  color: #111;
@@ -1166,7 +1125,6 @@
1166
1125
  background-size: 16px 16px;
1167
1126
  }
1168
1127
 
1169
- // Strong - Solid black
1170
1128
  &.strong {
1171
1129
  background-color: #111;
1172
1130
  color: #fff;
@@ -1182,7 +1140,6 @@
1182
1140
  text-align: center;
1183
1141
  }
1184
1142
 
1185
- // Contractions Form Container
1186
1143
  .contractionsFormContainer {
1187
1144
  padding: layout.$spacing-03 layout.$spacing-03;
1188
1145
  max-width: 700px;
@@ -1272,7 +1229,6 @@
1272
1229
  }
1273
1230
  }
1274
1231
 
1275
- // Detailed Contractions Grid - Partograph Format
1276
1232
  .contractionsContainer {
1277
1233
  display: flex;
1278
1234
  flex-direction: column;
@@ -1357,7 +1313,6 @@
1357
1313
  color: colors.$gray-100;
1358
1314
  position: relative;
1359
1315
 
1360
- // Ensure the numbers are clearly visible
1361
1316
  &::before {
1362
1317
  content: attr(data-label);
1363
1318
  position: absolute;
@@ -1424,7 +1379,6 @@
1424
1379
  }
1425
1380
  }
1426
1381
 
1427
- // Contraction Bar Visual Indicators
1428
1382
  .contractionBar {
1429
1383
  width: 100%;
1430
1384
  height: 100%;
@@ -1466,7 +1420,6 @@
1466
1420
  box-shadow: none;
1467
1421
  }
1468
1422
 
1469
- // Oxytocin Bar Visual Indicators
1470
1423
  .oxytocinBarLow {
1471
1424
  background-color: colors.$green-40;
1472
1425
  border: 2px solid colors.$green-60;
@@ -1522,13 +1475,11 @@
1522
1475
  color: colors.$gray-80;
1523
1476
  }
1524
1477
 
1525
- // Grid container for contractions
1526
1478
  .contractionsGrid {
1527
1479
  position: relative;
1528
1480
  margin: layout.$spacing-04 0;
1529
1481
  }
1530
1482
 
1531
- // Table view styles for contractions
1532
1483
  .contractionsTable {
1533
1484
  background-color: colors.$white;
1534
1485
  border: 1px solid colors.$gray-30;
@@ -1590,7 +1541,6 @@
1590
1541
  }
1591
1542
  }
1592
1543
 
1593
- // ===== DRUGS AND IV FLUIDS GRAPH STYLES =====
1594
1544
  .drugsIVFluidsGraph {
1595
1545
  width: 100%;
1596
1546
  margin: layout.$spacing-05 0;
@@ -1628,7 +1578,6 @@
1628
1578
  }
1629
1579
  }
1630
1580
 
1631
- // Oxytocin specific styling when inside fetalHeartRateContainer
1632
1581
  .fetalHeartRateContainer {
1633
1582
  .membraneGrid {
1634
1583
  margin: 0;
@@ -1660,12 +1609,13 @@
1660
1609
  text-align: center;
1661
1610
  font-size: 12px;
1662
1611
  line-height: 1.2;
1612
+ white-space: normal;
1613
+ word-break: break-word;
1663
1614
 
1664
1615
  &:hover {
1665
1616
  background-color: colors.$gray-10;
1666
1617
  }
1667
1618
  }
1668
-
1669
1619
  .drugInfo {
1670
1620
  display: flex;
1671
1621
  flex-direction: column;
@@ -1677,16 +1627,10 @@
1677
1627
  color: colors.$gray-100;
1678
1628
  font-size: 11px;
1679
1629
  line-height: 1.2;
1680
- }
1681
-
1682
- .drugDosage {
1683
- font-weight: 400;
1684
- color: colors.$gray-70;
1685
1630
  font-size: 10px;
1686
1631
  line-height: 1.1;
1687
1632
  }
1688
1633
 
1689
- // Pulse and BP Graph Styling
1690
1634
  .pulseBPGraph {
1691
1635
  width: 100%;
1692
1636
  margin: layout.$spacing-05 0;
@@ -1698,28 +1642,20 @@
1698
1642
  border: 2px solid colors.$gray-40;
1699
1643
  margin-bottom: layout.$spacing-04;
1700
1644
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1701
- min-height: 650px; // Match or exceed chart height
1702
-
1703
- // Medical chart styling
1645
+ min-height: 650px;
1704
1646
  :global(.cds--chart-wrapper) {
1705
1647
  border: 1px solid colors.$gray-30;
1706
1648
  border-radius: layout.$spacing-02;
1707
1649
  overflow: hidden;
1708
-
1709
- // Enhanced grid lines for medical chart appearance
1710
1650
  :global(.cds--chart) {
1711
1651
  background-color: colors.$white;
1712
-
1713
- // Grid styling for medical charts
1714
1652
  :global(.cds--chart-grid-line) {
1715
1653
  stroke: colors.$gray-30;
1716
1654
  stroke-width: 1;
1717
1655
  }
1718
-
1719
1656
  :global(.cds--chart-axis) {
1720
1657
  stroke: colors.$gray-50;
1721
1658
  }
1722
-
1723
1659
  :global(.cds--chart-axis text) {
1724
1660
  fill: colors.$gray-90;
1725
1661
  font-size: 12px;
@@ -183,7 +183,21 @@ export function useCervixData(patientUuid: string) {
183
183
  openmrsFetch,
184
184
  );
185
185
  const cervixData = data?.data?.results || [];
186
- const sortedEncounters = cervixData.sort(
186
+
187
+ // Filter encounters to only include those with cervix-specific concepts
188
+ const cervixSpecificEncounters = cervixData.filter((encounter) => {
189
+ if (!encounter.obs || !Array.isArray(encounter.obs)) {
190
+ return false;
191
+ }
192
+ // An encounter is cervix-specific if it contains cervical dilation OR descent of head concepts
193
+ return encounter.obs.some(
194
+ (obs) =>
195
+ obs.concept?.uuid === CERVIX_FORM_CONCEPTS.cervicalDilation ||
196
+ obs.concept?.uuid === CERVIX_FORM_CONCEPTS.descentOfHead,
197
+ );
198
+ });
199
+
200
+ const sortedEncounters = cervixSpecificEncounters.sort(
187
201
  (a, b) => new Date(b.encounterDatetime).getTime() - new Date(a.encounterDatetime).getTime(),
188
202
  );
189
203
  const transformedData = sortedEncounters.map((encounter) => {
@@ -2,9 +2,26 @@ import { useMemo } from 'react';
2
2
  import useSWR from 'swr';
3
3
  import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
4
4
  import { PARTOGRAPHY_CONCEPTS, MCH_PARTOGRAPHY_ENCOUNTER_UUID } from '../types';
5
- import { createPartographyEncounter } from '../partography.resource';
5
+ import { createPartographyEncounter, usePartographyData } from '../partography.resource';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
+ export interface MembraneAmnioticFluidTimeEntry {
9
+ hour: number;
10
+ time: string;
11
+ timeSlot: string;
12
+ encounterDatetime: string;
13
+ amnioticFluid?: string;
14
+ moulding?: string;
15
+ }
16
+
17
+ export interface UseMembraneAmnioticFluidFormDataResult {
18
+ membraneAmnioticFluidData: any[];
19
+ membraneAmnioticFluidTimeEntries: MembraneAmnioticFluidTimeEntry[];
20
+ isLoading: boolean;
21
+ error: any;
22
+ mutate: () => void;
23
+ }
24
+
8
25
  export function useMembraneAmnioticFluidData(patientUuid: string) {
9
26
  const { t } = useTranslation();
10
27
  const fetcher = (url: string) => openmrsFetch(url).then((res) => res.json());
@@ -76,6 +93,115 @@ export function useMembraneAmnioticFluidData(patientUuid: string) {
76
93
  };
77
94
  }
78
95
 
96
+ /**
97
+ * Custom hook for membrane amniotic fluid form-specific data isolation
98
+ * This ensures membrane amniotic fluid form validations only consider membrane amniotic fluid data
99
+ */
100
+ export const useMembraneAmnioticFluidFormData = (patientUuid: string): UseMembraneAmnioticFluidFormDataResult => {
101
+ const {
102
+ data: membraneAmnioticFluidEncounters = [],
103
+ isLoading,
104
+ error,
105
+ mutate,
106
+ } = usePartographyData(patientUuid, 'membrane-amniotic-fluid');
107
+
108
+ // Extract membrane amniotic fluid-specific time entries for form validation
109
+ const membraneAmnioticFluidTimeEntries = useMemo(() => {
110
+ if (!membraneAmnioticFluidEncounters || membraneAmnioticFluidEncounters.length === 0) {
111
+ return [];
112
+ }
113
+
114
+ return membraneAmnioticFluidEncounters
115
+ .map((encounter) => {
116
+ // Find amniotic fluid observation
117
+ const amnioticFluidObs = encounter.obs.find(
118
+ (obs) => obs.concept.uuid === PARTOGRAPHY_CONCEPTS['amniotic-fluid'],
119
+ );
120
+
121
+ // Find moulding observation
122
+ const mouldingObs = encounter.obs.find((obs) => obs.concept.uuid === PARTOGRAPHY_CONCEPTS['moulding']);
123
+
124
+ // Find time observation
125
+ const timeObs = encounter.obs.find(
126
+ (obs) =>
127
+ obs.concept.uuid === PARTOGRAPHY_CONCEPTS['fetal-heart-rate-time'] &&
128
+ typeof obs.value === 'string' &&
129
+ obs.value.startsWith('Time:'),
130
+ );
131
+
132
+ if (!amnioticFluidObs && !mouldingObs) {
133
+ return null; // Skip if no membrane amniotic fluid data
134
+ }
135
+
136
+ // Extract time from observation value
137
+ let time = '';
138
+ if (timeObs && typeof timeObs.value === 'string') {
139
+ const timeMatch = timeObs.value.match(/Time:\s*(.+)/);
140
+ if (timeMatch) {
141
+ time = timeMatch[1].trim();
142
+ }
143
+ }
144
+
145
+ if (!time || !time.match(/^\d{1,2}:\d{2}$/)) {
146
+ return null; // Skip if invalid time format
147
+ }
148
+
149
+ // Convert time to hour value for progressive validation
150
+ const [hours, minutes] = time.split(':').map(Number);
151
+ const hourValue = hours + minutes / 60; // Convert to decimal hour
152
+
153
+ // Create timeSlot from hour (for backward compatibility with existing form logic)
154
+ const timeSlot = hourValue.toString();
155
+
156
+ // Extract amniotic fluid value
157
+ let amnioticFluid: string | undefined;
158
+ if (amnioticFluidObs?.value) {
159
+ if (
160
+ typeof amnioticFluidObs.value === 'object' &&
161
+ amnioticFluidObs.value &&
162
+ 'display' in amnioticFluidObs.value
163
+ ) {
164
+ amnioticFluid = (amnioticFluidObs.value as any).display;
165
+ } else {
166
+ amnioticFluid = String(amnioticFluidObs.value);
167
+ }
168
+ }
169
+
170
+ // Extract moulding value
171
+ let moulding: string | undefined;
172
+ if (mouldingObs?.value) {
173
+ if (typeof mouldingObs.value === 'object' && mouldingObs.value && 'display' in mouldingObs.value) {
174
+ moulding = (mouldingObs.value as any).display;
175
+ } else {
176
+ moulding = String(mouldingObs.value);
177
+ }
178
+ }
179
+
180
+ return {
181
+ hour: hourValue,
182
+ time: time,
183
+ timeSlot: timeSlot,
184
+ encounterDatetime: encounter.encounterDatetime,
185
+ amnioticFluid,
186
+ moulding,
187
+ };
188
+ })
189
+ .filter((entry) => entry !== null)
190
+ .sort((a, b) => {
191
+ // Sort by encounter datetime for chronological order
192
+ return new Date(a!.encounterDatetime).getTime() - new Date(b!.encounterDatetime).getTime();
193
+ }) as MembraneAmnioticFluidTimeEntry[];
194
+ }, [membraneAmnioticFluidEncounters]);
195
+
196
+ return {
197
+ membraneAmnioticFluidData: membraneAmnioticFluidEncounters,
198
+ membraneAmnioticFluidTimeEntries,
199
+ isLoading,
200
+ error,
201
+ mutate,
202
+ };
203
+ };
204
+
79
205
  export async function saveMembraneAmnioticFluidData(
80
206
  patientUuid: string,
81
207
  formData: { amnioticFluid: string; moulding: string; time: string },
@@ -106,3 +232,46 @@ export async function saveMembraneAmnioticFluidData(
106
232
  };
107
233
  }
108
234
  }
235
+
236
+ /**
237
+ * Helper function to convert time string to decimal hour
238
+ * Example: "14:30" -> 14.5
239
+ */
240
+ export const convertMembraneTimeToHour = (timeString: string): number => {
241
+ if (!timeString || !timeString.match(/^\d{1,2}:\d{2}$/)) {
242
+ return 0;
243
+ }
244
+
245
+ const [hours, minutes] = timeString.split(':').map(Number);
246
+ return hours + minutes / 60;
247
+ };
248
+
249
+ /**
250
+ * Helper function to extract time from membrane amniotic fluid observation value
251
+ */
252
+ export const extractTimeFromMembraneObs = (obsValue: any): string => {
253
+ if (!obsValue) {
254
+ return '';
255
+ }
256
+
257
+ if (typeof obsValue === 'string') {
258
+ // Handle "Time: HH:MM" format
259
+ if (obsValue.startsWith('Time:')) {
260
+ const match = obsValue.match(/Time:\s*(.+)/);
261
+ if (match) {
262
+ const time = match[1].trim();
263
+ // Validate HH:MM format
264
+ if (time.match(/^\d{1,2}:\d{2}$/)) {
265
+ return time;
266
+ }
267
+ }
268
+ }
269
+
270
+ // Handle direct HH:MM format
271
+ if (obsValue.match(/^\d{1,2}:\d{2}$/)) {
272
+ return obsValue;
273
+ }
274
+ }
275
+
276
+ return '';
277
+ };