@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,359 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ Tag,
5
+ Button,
6
+ DataTable,
7
+ TableContainer,
8
+ Table,
9
+ TableHead,
10
+ TableRow,
11
+ TableHeader,
12
+ TableBody,
13
+ TableCell,
14
+ Pagination,
15
+ } from '@carbon/react';
16
+ import { Add, ChartColumn, Table as TableIcon } from '@carbon/react/icons';
17
+ import { LineChart } from '@carbon/charts-react';
18
+ import styles from '../partography.scss';
19
+ import { getColorForGraph, generateRange } from '../types';
20
+ import { usePaginationInfo } from '@openmrs/esm-patient-common-lib';
21
+
22
+ enum ScaleTypes {
23
+ LABELS = 'labels',
24
+ LINEAR = 'linear',
25
+ }
26
+
27
+ interface FetalHeartRateGraphProps {
28
+ data?: Array<{
29
+ hour: number;
30
+ value: number;
31
+ group: string;
32
+ time?: string;
33
+ date?: string;
34
+ id?: string;
35
+ }>;
36
+ tableData?: Array<{
37
+ id: string;
38
+ date: string;
39
+ time: string;
40
+ value: string;
41
+ hour: string;
42
+ }>;
43
+ viewMode?: 'graph' | 'table';
44
+ currentPage?: number;
45
+ pageSize?: number;
46
+ totalItems?: number;
47
+ controlSize?: 'sm' | 'md';
48
+ onAddData?: () => void;
49
+ onViewModeChange?: (mode: 'graph' | 'table') => void;
50
+ onPageChange?: (page: number) => void;
51
+ onPageSizeChange?: (size: number) => void;
52
+ isAddButtonDisabled?: boolean;
53
+ }
54
+
55
+ const FetalHeartRateGraph: React.FC<FetalHeartRateGraphProps> = ({
56
+ data = [],
57
+ tableData = [],
58
+ viewMode = 'graph',
59
+ currentPage = 1,
60
+ pageSize = 5,
61
+ totalItems = 0,
62
+ controlSize = 'sm',
63
+ onAddData,
64
+ onViewModeChange,
65
+ onPageChange,
66
+ onPageSizeChange,
67
+ isAddButtonDisabled = true,
68
+ }) => {
69
+ const { t } = useTranslation();
70
+ const startIndex = (currentPage - 1) * pageSize;
71
+ const endIndex = startIndex + pageSize;
72
+ const paginatedData = tableData.slice(startIndex, endIndex);
73
+ const { pageSizes: calculatedPageSizes, itemsDisplayed } = usePaginationInfo(
74
+ pageSize,
75
+ Math.ceil(totalItems / pageSize),
76
+ currentPage,
77
+ totalItems,
78
+ );
79
+ const getFetalHeartRateStatus = (value: string): { type: string; text: string; color: string } => {
80
+ const numValue = parseInt(value.replace(' bpm', ''));
81
+ if (numValue < 100) {
82
+ return { type: 'warm-gray', text: 'Low', color: getColorForGraph('gray') };
83
+ } else if (numValue >= 100 && numValue <= 180) {
84
+ return { type: 'green', text: 'Normal', color: getColorForGraph('green') };
85
+ } else {
86
+ return { type: 'red', text: 'High', color: getColorForGraph('red') };
87
+ }
88
+ };
89
+ const tableHeaders = [
90
+ { key: 'date', header: t('date', 'Date') },
91
+ { key: 'time', header: t('time', 'Time') },
92
+ { key: 'hour', header: t('hour', 'Hour') },
93
+ { key: 'value', header: t('fetalHeartRate', 'Fetal Heart Rate (bpm)') },
94
+ { key: 'status', header: t('status', 'Status') },
95
+ ];
96
+ const getFetalHeartRateColor = (value: number): string => {
97
+ if (value < 100) {
98
+ return getColorForGraph('gray');
99
+ } else if (value >= 100 && value <= 180) {
100
+ return getColorForGraph('green');
101
+ } else {
102
+ return getColorForGraph('red');
103
+ }
104
+ };
105
+
106
+ const enhancedChartData = React.useMemo(() => {
107
+ if (data && data.length > 0) {
108
+ return data.map((point) => ({
109
+ ...point,
110
+ group: 'Fetal Heart Rate',
111
+ color: getFetalHeartRateColor(point.value),
112
+ }));
113
+ }
114
+
115
+ return [
116
+ { hour: 0, value: 140, group: 'Fetal Heart Rate', time: '0', color: getColorForGraph('green') },
117
+ { hour: 10, value: 140, group: 'Fetal Heart Rate', time: '10', color: getColorForGraph('green') },
118
+ { hour: 20, value: 140, group: 'Fetal Heart Rate', time: '20', color: getColorForGraph('green') },
119
+ { hour: 30, value: 140, group: 'Fetal Heart Rate', time: '30', color: getColorForGraph('green') },
120
+ { hour: 40, value: 140, group: 'Fetal Heart Rate', time: '40', color: getColorForGraph('green') },
121
+ { hour: 50, value: 140, group: 'Fetal Heart Rate', time: '50', color: getColorForGraph('green') },
122
+ { hour: 60, value: 140, group: 'Fetal Heart Rate', time: '60', color: getColorForGraph('green') },
123
+ ];
124
+ }, [data]);
125
+
126
+ const chartData = enhancedChartData;
127
+ const chartKey = React.useMemo(() => JSON.stringify(enhancedChartData), [enhancedChartData]);
128
+
129
+ const chartOptions = {
130
+ title: '',
131
+ axes: {
132
+ bottom: {
133
+ title: '',
134
+ mapsTo: 'hour',
135
+ scaleType: ScaleTypes.LINEAR,
136
+ domain: [0, 10],
137
+ ticks: {
138
+ values: React.useMemo(() => generateRange(0, 10, 0.5), []),
139
+ formatter: (hour: number) => {
140
+ if (hour === 0) {
141
+ return '0';
142
+ }
143
+ if (hour === 0.5) {
144
+ return '0:30';
145
+ }
146
+ if (hour % 1 === 0) {
147
+ return `${hour}:00`;
148
+ }
149
+ if (hour % 1 === 0.5) {
150
+ return `${Math.floor(hour)}:30`;
151
+ }
152
+ return `${hour}`;
153
+ },
154
+ },
155
+ },
156
+ left: {
157
+ title: 'Fetal Heart Rate (bpm)',
158
+ mapsTo: 'value',
159
+ scaleType: ScaleTypes.LINEAR,
160
+ domain: [80, 200],
161
+ ticks: {
162
+ values: React.useMemo(() => generateRange(80, 200, 10), []),
163
+ formatter: (value: number) => `${value}`,
164
+ },
165
+ },
166
+ },
167
+ height: '600px',
168
+ curve: 'curveLinear',
169
+ points: {
170
+ enabled: true,
171
+ radius: 6,
172
+ filled: true,
173
+ },
174
+ grid: {
175
+ x: {
176
+ enabled: true,
177
+ },
178
+ y: {
179
+ enabled: true,
180
+ },
181
+ },
182
+ color: {
183
+ scale:
184
+ data && data.length > 0
185
+ ? (datapoint: any) => getFetalHeartRateColor(datapoint.value)
186
+ : {
187
+ 'Fetal Heart Rate': 'transparent',
188
+ },
189
+ },
190
+ legend: {
191
+ enabled: false,
192
+ },
193
+ theme: 'white',
194
+ toolbar: {
195
+ enabled: false,
196
+ },
197
+ };
198
+
199
+ return (
200
+ <div className={styles.fetalHeartRateSection}>
201
+ <div className={styles.fetalHeartRateContainer}>
202
+ <div className={styles.fetalHeartRateHeader}>
203
+ <div className={styles.fetalHeartRateHeaderLeft}>
204
+ <h3 className={styles.fetalHeartRateTitle}>Fetal Heart Rate</h3>
205
+ <div className={styles.fetalHeartRateControls}>
206
+ <Tag type="green" title="Normal Range">
207
+ Normal (100-180)
208
+ </Tag>
209
+ <Tag type="red" title="Abnormal Range">
210
+ Abnormal (&gt;180)
211
+ </Tag>
212
+ <Tag type="warm-gray" title="Low Range">
213
+ Low (&lt;100)
214
+ </Tag>
215
+ </div>
216
+ </div>
217
+ <div className={styles.fetalHeartRateHeaderRight}>
218
+ <div className={styles.fetalHeartRateActions}>
219
+ <div className={styles.viewSwitcher}>
220
+ <Button
221
+ kind={viewMode === 'graph' ? 'primary' : 'secondary'}
222
+ size={controlSize}
223
+ hasIconOnly
224
+ iconDescription={t('graphView', 'Graph View')}
225
+ onClick={() => onViewModeChange?.('graph')}
226
+ className={styles.viewButton}>
227
+ <ChartColumn />
228
+ </Button>
229
+ <Button
230
+ kind={viewMode === 'table' ? 'primary' : 'secondary'}
231
+ size={controlSize}
232
+ hasIconOnly
233
+ iconDescription={t('tableView', 'Table View')}
234
+ onClick={() => onViewModeChange?.('table')}
235
+ className={styles.viewButton}>
236
+ <TableIcon />
237
+ </Button>
238
+ </div>
239
+ <Button
240
+ kind="primary"
241
+ size={controlSize}
242
+ renderIcon={Add}
243
+ iconDescription="Add fetal heart rate data"
244
+ disabled={isAddButtonDisabled}
245
+ onClick={onAddData}
246
+ className={styles.addButton}>
247
+ Add
248
+ </Button>
249
+ </div>
250
+ </div>
251
+ </div>
252
+
253
+ {viewMode === 'graph' ? (
254
+ <div className={styles.fetalHeartRateChart}>
255
+ <LineChart data={chartData} options={chartOptions} key={chartKey} />
256
+ </div>
257
+ ) : (
258
+ <div className={styles.tableContainer}>
259
+ {paginatedData.length > 0 ? (
260
+ <>
261
+ <DataTable rows={paginatedData} headers={tableHeaders}>
262
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
263
+ <TableContainer title="" description="">
264
+ <Table {...getTableProps()} size="sm">
265
+ <TableHead>
266
+ <TableRow>
267
+ {headers.map((header) => (
268
+ <TableHeader {...getHeaderProps({ header })} key={header.key}>
269
+ {header.header}
270
+ </TableHeader>
271
+ ))}
272
+ </TableRow>
273
+ </TableHead>
274
+ <TableBody>
275
+ {rows.map((row) => (
276
+ <TableRow {...getRowProps({ row })} key={row.id}>
277
+ {row.cells.map((cell) => (
278
+ <TableCell key={cell.id}>
279
+ {cell.info.header === 'status'
280
+ ? (() => {
281
+ const status = getFetalHeartRateStatus(
282
+ row.cells.find((c) => c.info.header === 'value')?.value || '0 bpm',
283
+ );
284
+ return (
285
+ <Tag type={status.type as any} title={`Fetal Heart Rate: ${status.text}`}>
286
+ {status.text}
287
+ </Tag>
288
+ );
289
+ })()
290
+ : cell.info.header === 'value'
291
+ ? (() => {
292
+ const numValue = parseInt(cell.value.replace(' bpm', ''));
293
+
294
+ if (numValue < 100) {
295
+ return (
296
+ <span className={`${styles.fetalHeartRateValue} ${styles.low}`}>
297
+ <span className={styles.arrow}>↓</span>
298
+ {cell.value}
299
+ </span>
300
+ );
301
+ } else if (numValue > 180) {
302
+ return (
303
+ <span className={`${styles.fetalHeartRateValue} ${styles.high}`}>
304
+ <span className={styles.arrow}>↑</span>
305
+ {cell.value}
306
+ </span>
307
+ );
308
+ } else {
309
+ return (
310
+ <span className={`${styles.fetalHeartRateValue} ${styles.normal}`}>
311
+ {cell.value}
312
+ </span>
313
+ );
314
+ }
315
+ })()
316
+ : cell.value}
317
+ </TableCell>
318
+ ))}
319
+ </TableRow>
320
+ ))}
321
+ </TableBody>
322
+ </Table>
323
+ </TableContainer>
324
+ )}
325
+ </DataTable>
326
+
327
+ {totalItems > 0 && (
328
+ <Pagination
329
+ page={currentPage}
330
+ totalItems={totalItems}
331
+ pageSize={pageSize}
332
+ pageSizes={calculatedPageSizes}
333
+ onChange={(event) => {
334
+ onPageChange?.(event.page);
335
+ if (event.pageSize !== pageSize) {
336
+ onPageSizeChange?.(event.pageSize);
337
+ }
338
+ }}
339
+ size={controlSize}
340
+ />
341
+ )}
342
+ {totalItems > 0 && <div className={styles.paginationInfo}>{itemsDisplayed}</div>}
343
+ </>
344
+ ) : (
345
+ <div className={styles.emptyState}>
346
+ <p>{t('noDataAvailable', 'No data available for fetal heart rate')}</p>
347
+ <Button kind="primary" size={controlSize} renderIcon={Add} onClick={onAddData}>
348
+ {t('addFirstDataPoint', 'Add first data point')}
349
+ </Button>
350
+ </div>
351
+ )}
352
+ </div>
353
+ )}
354
+ </div>
355
+ </div>
356
+ );
357
+ };
358
+
359
+ export default FetalHeartRateGraph;
@@ -0,0 +1,10 @@
1
+ export { default as FetalHeartRateGraph } from './fetal-heart-rate-graph.component';
2
+ export { default as MembraneAmnioticFluidGraph } from './membrane-amniotic-fluid-graph.component';
3
+ export { default as CervicalContractionsGraph } from './cervical-contractions-graph.component';
4
+ export { default as PartographGraph } from './partograph-graph.component';
5
+ export { default as CervixGraph } from './cervix-graph.component';
6
+ export { default as OxytocinGraph } from './oxytocin-graph-wrapper.component';
7
+ export { default as DrugsIVFluidsGraph } from './drugs-iv-fluids-graph-wrapper.component';
8
+ export { default as PulseBPGraph } from './pulse-bp-graph-wrapper.component';
9
+ export { default as TemperatureGraph } from './temperature-graph.component';
10
+ export { default as UrineTestGraph } from './urine-test-graph.component';
@@ -0,0 +1,266 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ Button,
5
+ DataTable,
6
+ TableContainer,
7
+ Table,
8
+ TableHead,
9
+ TableRow,
10
+ TableHeader,
11
+ TableBody,
12
+ TableCell,
13
+ Pagination,
14
+ } from '@carbon/react';
15
+ import { Add, ChartColumn, Table as TableIcon } from '@carbon/react/icons';
16
+ import styles from '../partography.scss';
17
+ import { usePaginationInfo } from '@openmrs/esm-patient-common-lib';
18
+ import { AMNIOTIC_FLUID_INITIALS_MAP, AMNIOTIC_FLUID_LABEL_MAP, MOULDING_SYMBOL_MAP } from '../types';
19
+
20
+ interface MembraneAmnioticFluidData {
21
+ timeSlot: string;
22
+ exactTime: string;
23
+ amnioticFluid: string;
24
+ moulding: string;
25
+ date?: string;
26
+ id?: string;
27
+ }
28
+
29
+ interface MembraneAmnioticFluidGraphProps {
30
+ data?: MembraneAmnioticFluidData[];
31
+ tableData?: Array<{
32
+ id: string;
33
+ date: string;
34
+ timeSlot: string;
35
+ exactTime: string;
36
+ amnioticFluid: string;
37
+ moulding: string;
38
+ }>;
39
+ viewMode?: 'graph' | 'table';
40
+ currentPage?: number;
41
+ pageSize?: number;
42
+ totalItems?: number;
43
+ controlSize?: 'sm' | 'md';
44
+ onAddData?: () => void;
45
+ onViewModeChange?: (mode: 'graph' | 'table') => void;
46
+ onPageChange?: (page: number) => void;
47
+ onPageSizeChange?: (size: number) => void;
48
+ isAddButtonDisabled?: boolean;
49
+ }
50
+
51
+ const MembraneAmnioticFluidGraph: React.FC<MembraneAmnioticFluidGraphProps> = ({
52
+ data = [],
53
+ tableData = [],
54
+ viewMode = 'graph',
55
+ currentPage = 1,
56
+ pageSize = 5,
57
+ totalItems = 0,
58
+ controlSize = 'sm',
59
+ onAddData,
60
+ onViewModeChange,
61
+ onPageChange,
62
+ onPageSizeChange,
63
+ isAddButtonDisabled = true,
64
+ }) => {
65
+ const { t } = useTranslation();
66
+
67
+ const startIndex = (currentPage - 1) * pageSize;
68
+ const endIndex = startIndex + pageSize;
69
+ const paginatedData = tableData.slice(startIndex, endIndex);
70
+ const { pageSizes: calculatedPageSizes, itemsDisplayed } = usePaginationInfo(
71
+ pageSize,
72
+ Math.ceil(totalItems / pageSize),
73
+ currentPage,
74
+ totalItems,
75
+ );
76
+
77
+ const tableHeaders = [
78
+ { key: 'date', header: t('date', 'Date') },
79
+ { key: 'exactTime', header: t('exactTime', 'Exact Time') },
80
+ { key: 'amnioticFluid', header: t('amnioticFluid', 'Amniotic Fluid') },
81
+ { key: 'moulding', header: t('moulding', 'Moulding') },
82
+ ];
83
+
84
+ const getTimeColumns = () => {
85
+ const emptyColumns = Array.from({ length: 13 }, (_, i) => `grid-${i + 1}`);
86
+ if (data.length === 0) {
87
+ return emptyColumns;
88
+ }
89
+ const dataTimes = data.map((item, idx) => item.exactTime || String(idx));
90
+ if (dataTimes.length <= 13) {
91
+ const remainingEmpty = Array.from({ length: 13 - dataTimes.length }, (_, i) => `empty-${i + 1}`);
92
+ return [...dataTimes, ...remainingEmpty];
93
+ }
94
+ return dataTimes;
95
+ };
96
+
97
+ const timeColumns = getTimeColumns();
98
+
99
+ const getAmnioticFluidInitials = (value: string): string => {
100
+ return AMNIOTIC_FLUID_INITIALS_MAP[value] || value.charAt(0).toUpperCase();
101
+ };
102
+
103
+ const getAmnioticFluidLabel = (value: string): string => AMNIOTIC_FLUID_LABEL_MAP[value] || value;
104
+
105
+ const getMouldingSymbol = (value: string): string => MOULDING_SYMBOL_MAP[value] || value;
106
+
107
+ const createGridData = () => {
108
+ const gridData: Record<string, { amnioticFluid: string; moulding: string }> = {};
109
+ data.forEach((item, idx) => {
110
+ const key = item.exactTime || String(idx);
111
+ gridData[key] = {
112
+ amnioticFluid: getAmnioticFluidInitials(item.amnioticFluid),
113
+ moulding: getMouldingSymbol(item.moulding),
114
+ };
115
+ });
116
+ return gridData;
117
+ };
118
+ const gridData = createGridData();
119
+
120
+ return (
121
+ <div className={styles.fetalHeartRateSection}>
122
+ <div className={styles.fetalHeartRateContainer}>
123
+ <div className={styles.fetalHeartRateHeader}>
124
+ <div className={styles.fetalHeartRateHeaderLeft}>
125
+ <h3 className={styles.fetalHeartRateTitle}>Membrane Amniotic Fluid & Moulding</h3>
126
+ <div className={styles.fetalHeartRateControls}>
127
+ <span className={styles.legendText}>
128
+ M=Membrane intact, C=Clear, MS=Meconium, A=Absent, B=Blood | 0, +, ++, +++
129
+ </span>
130
+ </div>
131
+ </div>
132
+ <div className={styles.fetalHeartRateHeaderRight}>
133
+ <div className={styles.fetalHeartRateActions}>
134
+ <div className={styles.viewSwitcher}>
135
+ <Button
136
+ kind={viewMode === 'graph' ? 'primary' : 'secondary'}
137
+ size={controlSize}
138
+ hasIconOnly
139
+ iconDescription={t('graphView', 'Graph View')}
140
+ onClick={() => onViewModeChange?.('graph')}
141
+ className={styles.viewButton}>
142
+ <ChartColumn />
143
+ </Button>
144
+ <Button
145
+ kind={viewMode === 'table' ? 'primary' : 'secondary'}
146
+ size={controlSize}
147
+ hasIconOnly
148
+ iconDescription={t('tableView', 'Table View')}
149
+ onClick={() => onViewModeChange?.('table')}
150
+ className={styles.viewButton}>
151
+ <TableIcon />
152
+ </Button>
153
+ </div>
154
+ <Button
155
+ kind="primary"
156
+ size={controlSize}
157
+ renderIcon={Add}
158
+ iconDescription="Add membrane amniotic fluid data"
159
+ disabled={isAddButtonDisabled}
160
+ onClick={onAddData}
161
+ className={styles.addButton}>
162
+ Add
163
+ </Button>
164
+ </div>
165
+ </div>
166
+ </div>
167
+
168
+ {viewMode === 'graph' ? (
169
+ <div className={styles.membraneGrid}>
170
+ <div className={styles.gridContainer}>
171
+ <div className={styles.gridRow}>
172
+ <div className={styles.gridRowLabel}>{t('amnioticFluid', 'Amniotic Fluid')}</div>
173
+ {timeColumns.map((timeColumn) => {
174
+ const cellData = gridData[timeColumn];
175
+ return (
176
+ <div key={`af-${timeColumn}`} className={styles.gridCell}>
177
+ {cellData?.amnioticFluid || ''}
178
+ </div>
179
+ );
180
+ })}
181
+ </div>
182
+
183
+ <div className={styles.gridRow}>
184
+ <div className={styles.gridRowLabel}>{t('moulding', 'Moulding')}</div>
185
+ {timeColumns.map((timeColumn) => {
186
+ const cellData = gridData[timeColumn];
187
+ return (
188
+ <div key={`m-${timeColumn}`} className={styles.gridCell}>
189
+ {cellData?.moulding || ''}
190
+ </div>
191
+ );
192
+ })}
193
+ </div>
194
+ </div>
195
+ </div>
196
+ ) : (
197
+ <div className={styles.tableContainer}>
198
+ {paginatedData.length > 0 ? (
199
+ <>
200
+ <DataTable
201
+ rows={paginatedData.map((row) => ({
202
+ ...row,
203
+ exactTime: row.exactTime,
204
+ amnioticFluid: getAmnioticFluidLabel(row.amnioticFluid),
205
+ moulding: getMouldingSymbol(row.moulding),
206
+ }))}
207
+ headers={tableHeaders}>
208
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
209
+ <TableContainer title="" description="">
210
+ <Table {...getTableProps()} size="sm">
211
+ <TableHead>
212
+ <TableRow>
213
+ {headers.map((header) => (
214
+ <TableHeader {...getHeaderProps({ header })} key={header.key}>
215
+ {header.header}
216
+ </TableHeader>
217
+ ))}
218
+ </TableRow>
219
+ </TableHead>
220
+ <TableBody>
221
+ {rows.map((row) => (
222
+ <TableRow {...getRowProps({ row })} key={row.id}>
223
+ {row.cells.map((cell) => (
224
+ <TableCell key={cell.id}>{cell.value}</TableCell>
225
+ ))}
226
+ </TableRow>
227
+ ))}
228
+ </TableBody>
229
+ </Table>
230
+ </TableContainer>
231
+ )}
232
+ </DataTable>
233
+
234
+ {totalItems > 0 && (
235
+ <Pagination
236
+ page={currentPage}
237
+ totalItems={totalItems}
238
+ pageSize={pageSize}
239
+ pageSizes={calculatedPageSizes}
240
+ onChange={(event) => {
241
+ onPageChange?.(event.page);
242
+ if (event.pageSize !== pageSize) {
243
+ onPageSizeChange?.(event.pageSize);
244
+ }
245
+ }}
246
+ size={controlSize}
247
+ />
248
+ )}
249
+ {totalItems > 0 && <div className={styles.paginationInfo}>{itemsDisplayed}</div>}
250
+ </>
251
+ ) : (
252
+ <div className={styles.emptyState}>
253
+ <p>{t('noDataAvailable', 'No data available for membrane amniotic fluid & moulding')}</p>
254
+ <Button kind="primary" size={controlSize} renderIcon={Add} onClick={onAddData}>
255
+ {t('addFirstDataPoint', 'Add first data point')}
256
+ </Button>
257
+ </div>
258
+ )}
259
+ </div>
260
+ )}
261
+ </div>
262
+ </div>
263
+ );
264
+ };
265
+
266
+ export default MembraneAmnioticFluidGraph;