@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2716 → 5.4.2-pre.2724

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/dist/127.js +1 -1
  3. package/dist/40.js +1 -1
  4. package/dist/805.js +1 -0
  5. package/dist/805.js.map +1 -0
  6. package/dist/916.js +1 -1
  7. package/dist/kenyaemr-esm-patient-clinical-view-app.js +2 -2
  8. package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +36 -36
  9. package/dist/main.js +27 -27
  10. package/dist/main.js.map +1 -1
  11. package/dist/routes.json +1 -1
  12. package/package.json +1 -1
  13. package/src/config-schema.ts +97 -0
  14. package/src/contact-list/contact-tracing-history.component.tsx +18 -15
  15. package/src/maternal-and-child-health/partography/components/pulse-bp-graph.component.tsx +1 -0
  16. package/src/maternal-and-child-health/partography/components/temperature-graph.component.tsx +218 -0
  17. package/src/maternal-and-child-health/partography/components/uterine-contractions-graph.component.tsx +209 -0
  18. package/src/maternal-and-child-health/partography/forms/cervical-contractions-form.component.tsx +211 -0
  19. package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +354 -0
  20. package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +321 -0
  21. package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +275 -0
  22. package/src/maternal-and-child-health/partography/forms/index.ts +9 -0
  23. package/src/maternal-and-child-health/partography/forms/membrane-amniotic-fluid-form.component.tsx +330 -0
  24. package/src/maternal-and-child-health/partography/forms/oxytocin-form.component.tsx +207 -0
  25. package/src/maternal-and-child-health/partography/forms/pulse-bp-form.component.tsx +174 -0
  26. package/src/maternal-and-child-health/partography/forms/temperature-form.component.tsx +210 -0
  27. package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.component.tsx +218 -0
  28. package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.scss +107 -0
  29. package/src/maternal-and-child-health/partography/forms/time-picker-with-clock.component.tsx +174 -0
  30. package/src/maternal-and-child-health/partography/forms/time-picker-with-clock.scss +178 -0
  31. package/src/maternal-and-child-health/partography/forms/urine-test-form.component.tsx +255 -0
  32. package/src/maternal-and-child-health/partography/forms/useCervixData.ts +16 -0
  33. package/src/maternal-and-child-health/partography/graphs/cervical-contractions-graph.component.tsx +266 -0
  34. package/src/maternal-and-child-health/partography/graphs/cervix-graph.component.tsx +429 -0
  35. package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph-wrapper.component.tsx +163 -0
  36. package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph.component.tsx +82 -0
  37. package/src/maternal-and-child-health/partography/graphs/fetal-heart-rate-graph.component.tsx +359 -0
  38. package/src/maternal-and-child-health/partography/graphs/index.ts +10 -0
  39. package/src/maternal-and-child-health/partography/graphs/membrane-amniotic-fluid-graph.component.tsx +266 -0
  40. package/src/maternal-and-child-health/partography/graphs/oxytocin-graph-wrapper.component.tsx +190 -0
  41. package/src/maternal-and-child-health/partography/graphs/oxytocin-graph.component.tsx +126 -0
  42. package/src/maternal-and-child-health/partography/graphs/partograph-graph.component.tsx +266 -0
  43. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph-wrapper.component.tsx +298 -0
  44. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +267 -0
  45. package/src/maternal-and-child-health/partography/graphs/temperature-graph.component.tsx +242 -0
  46. package/src/maternal-and-child-health/partography/graphs/urine-test-graph.component.tsx +246 -0
  47. package/src/maternal-and-child-health/partography/partograph.component.tsx +2141 -118
  48. package/src/maternal-and-child-health/partography/partography-dashboard.meta.ts +8 -0
  49. package/src/maternal-and-child-health/partography/partography-data-form.scss +163 -0
  50. package/src/maternal-and-child-health/partography/partography.resource.ts +233 -326
  51. package/src/maternal-and-child-health/partography/partography.scss +1341 -3
  52. package/src/maternal-and-child-health/partography/resources/blood-pressure.resource.ts +96 -0
  53. package/src/maternal-and-child-health/partography/resources/cervical-dilation.resource.ts +109 -0
  54. package/src/maternal-and-child-health/partography/resources/cervix.resource.ts +362 -0
  55. package/src/maternal-and-child-health/partography/resources/descent-of-head.resource.ts +101 -0
  56. package/src/maternal-and-child-health/partography/resources/drugs-fluids.resource.ts +88 -0
  57. package/src/maternal-and-child-health/partography/resources/fetal-heart-rate.resource.ts +122 -0
  58. package/src/maternal-and-child-health/partography/resources/maternal-pulse.resource.ts +77 -0
  59. package/src/maternal-and-child-health/partography/resources/membrane-amniotic-fluid.resource.ts +108 -0
  60. package/src/maternal-and-child-health/partography/resources/oxytocin.resource.ts +159 -0
  61. package/src/maternal-and-child-health/partography/resources/progress-events.resource.ts +6 -0
  62. package/src/maternal-and-child-health/partography/resources/pulse-bp-combined.resource.ts +53 -0
  63. package/src/maternal-and-child-health/partography/resources/temperature.resource.ts +84 -0
  64. package/src/maternal-and-child-health/partography/resources/uterine-contractions.resource.ts +173 -0
  65. package/src/maternal-and-child-health/partography/table/temperature-table.component.tsx +99 -0
  66. package/src/maternal-and-child-health/partography/table/uterine-contractions-table.component.tsx +86 -0
  67. package/src/maternal-and-child-health/partography/types/index.ts +319 -101
  68. package/translations/am.json +121 -1
  69. package/translations/en.json +121 -1
  70. package/translations/sw.json +121 -1
  71. package/dist/397.js +0 -1
  72. package/dist/397.js.map +0 -1
@@ -0,0 +1,429 @@
1
+ import React, { useEffect } 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 { usePaginationInfo } from '@openmrs/esm-patient-common-lib';
18
+ import { LineChart } from '@carbon/charts-react';
19
+ import styles from '../partography.scss';
20
+ import { SVG_NAMESPACE, getColorForGraph } from '../types';
21
+
22
+ enum ScaleTypes {
23
+ LABELS = 'labels',
24
+ LINEAR = 'linear',
25
+ }
26
+
27
+ interface ChartDataPoint {
28
+ hour: number;
29
+ time?: string;
30
+ group: string;
31
+ value: number;
32
+ }
33
+
34
+ interface CervixFormData {
35
+ hour: number;
36
+ time: string;
37
+ cervicalDilation: number;
38
+ descentOfHead: number;
39
+ entryDate: string;
40
+ entryTime: string;
41
+ }
42
+
43
+ interface CervixGraphProps {
44
+ cervixFormData: CervixFormData[];
45
+ tableData: any[];
46
+ viewMode: 'graph' | 'table';
47
+ currentPage: number;
48
+ pageSize: number;
49
+ totalItems: number;
50
+ isLoading: boolean;
51
+ controlSize: 'sm' | 'md';
52
+ onAddData: () => void;
53
+ onViewModeChange: (mode: 'graph' | 'table') => void;
54
+ onPageChange: (page: number) => void;
55
+ onPageSizeChange: (size: number) => void;
56
+ getTableHeaders: () => Array<{ key: string; header: string }>;
57
+ }
58
+
59
+ const CERVIX_CHART_OPTIONS = {
60
+ axes: {
61
+ bottom: {
62
+ title: '',
63
+ mapsTo: 'hour',
64
+ scaleType: ScaleTypes.LINEAR,
65
+ domain: [0, 10],
66
+ ticks: {
67
+ values: [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10],
68
+ formatter: (hour: number) => {
69
+ if (hour === 0) {
70
+ return '0';
71
+ } else if (hour === 0.5) {
72
+ return '0.30';
73
+ } else if (hour % 1 === 0) {
74
+ return `${hour}`;
75
+ } else if (hour % 1 === 0.5) {
76
+ return `${Math.floor(hour)}.30`;
77
+ } else {
78
+ return `${hour}`;
79
+ }
80
+ },
81
+ },
82
+ },
83
+ left: {
84
+ title: 'Cervical Dilation (cm) / Descent of Head (5=high → 1=descended)',
85
+ mapsTo: 'value',
86
+ domain: [0, 10],
87
+ ticks: {
88
+ values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
89
+ formatter: (value: number) => {
90
+ if (value >= 1 && value <= 5) {
91
+ return `${value}cm / D${value}`;
92
+ } else if (value === 0) {
93
+ return '0cm';
94
+ } else {
95
+ return `${value}cm`;
96
+ }
97
+ },
98
+ },
99
+ scaleType: ScaleTypes.LINEAR,
100
+ },
101
+ },
102
+ points: {
103
+ enabled: true,
104
+ radius: 6,
105
+ filled: true,
106
+ },
107
+ curve: 'curveLinear',
108
+ height: '500px',
109
+ theme: 'white',
110
+ toolbar: {
111
+ enabled: false,
112
+ },
113
+ legend: {
114
+ position: 'top',
115
+ clickable: false,
116
+ },
117
+ };
118
+
119
+ const CervixGraph: React.FC<CervixGraphProps> = ({
120
+ cervixFormData,
121
+ tableData,
122
+ viewMode,
123
+ currentPage,
124
+ pageSize,
125
+ totalItems,
126
+ isLoading,
127
+ controlSize,
128
+ onAddData,
129
+ onViewModeChange,
130
+ onPageChange,
131
+ onPageSizeChange,
132
+ getTableHeaders,
133
+ }) => {
134
+ const { t } = useTranslation();
135
+
136
+ const startIndex = (currentPage - 1) * pageSize;
137
+ const endIndex = startIndex + pageSize;
138
+ const paginatedData = tableData.slice(startIndex, endIndex);
139
+ const ALERT_START_CM = 4;
140
+ const CERVIX_DILATION_MAX = 10;
141
+ const ALERT_ACTION_DIFFERENCE_HOURS = 4;
142
+ const EXPECTED_LABOR_DURATION_HOURS = 6;
143
+
144
+ const staticLinesData: ChartDataPoint[] = [
145
+ { hour: 0, value: ALERT_START_CM, group: 'Alert Line' },
146
+ { hour: EXPECTED_LABOR_DURATION_HOURS, value: CERVIX_DILATION_MAX, group: 'Alert Line' },
147
+ { hour: ALERT_ACTION_DIFFERENCE_HOURS, value: ALERT_START_CM, group: 'Action Line' },
148
+ {
149
+ hour: ALERT_ACTION_DIFFERENCE_HOURS + EXPECTED_LABOR_DURATION_HOURS,
150
+ value: CERVIX_DILATION_MAX,
151
+ group: 'Action Line',
152
+ },
153
+ ];
154
+
155
+ const cervicalDilationData: ChartDataPoint[] = cervixFormData.map((data) => ({
156
+ hour: data.hour,
157
+ value: data.cervicalDilation,
158
+ group: 'Cervical Dilation',
159
+ time: data.time,
160
+ }));
161
+
162
+ const descentOfHeadData: ChartDataPoint[] = cervixFormData.map((data) => ({
163
+ hour: data.hour,
164
+ value: data.descentOfHead,
165
+ group: 'Descent of Head',
166
+ time: data.time,
167
+ }));
168
+
169
+ const finalChartData = [...staticLinesData, ...cervicalDilationData, ...descentOfHeadData];
170
+ const chartOptions = {
171
+ ...CERVIX_CHART_OPTIONS,
172
+ title: 'Cervical Dilation and Descent of Head',
173
+ color: {
174
+ scale: {
175
+ 'Alert Line': getColorForGraph('yellow'),
176
+ 'Action Line': getColorForGraph('red'),
177
+ 'Cervical Dilation': getColorForGraph('green'),
178
+ 'Descent of Head': getColorForGraph('blue'),
179
+ },
180
+ },
181
+ };
182
+ const timeLabelsData = [];
183
+ const maxHours = Math.max(10, Math.max(...cervixFormData.map((d) => d.hour), 0) + 1);
184
+
185
+ for (let i = 0; i < Math.min(maxHours, 10); i++) {
186
+ const hourLabel = i === 0 ? '0' : `${i}hr`;
187
+ const formDataForHour = cervixFormData.find((data) => data.hour === i);
188
+ const timeValue = formDataForHour ? formDataForHour.time : '--:--';
189
+
190
+ timeLabelsData.push({
191
+ hours: hourLabel,
192
+ time: timeValue,
193
+ span: 1,
194
+ });
195
+ }
196
+ useEffect(() => {
197
+ const applyChartStyling = () => {
198
+ const chartContainer = document.querySelector(`[data-chart-id="cervix"]`);
199
+ if (chartContainer) {
200
+ const svgPaths = chartContainer.querySelectorAll('svg path');
201
+ svgPaths.forEach((path) => {
202
+ const pathElement = path as SVGPathElement;
203
+ const pathData = pathElement.getAttribute('d');
204
+ if (pathData) {
205
+ if (pathData.includes('M0') || pathData.includes('L0')) {
206
+ pathElement.style.stroke = getColorForGraph('yellow');
207
+ pathElement.style.strokeWidth = '3px';
208
+ pathElement.style.strokeDasharray = '8,4';
209
+ } else if (pathData.includes('M4') || pathData.includes('L4')) {
210
+ pathElement.style.stroke = getColorForGraph('red');
211
+ pathElement.style.strokeWidth = '3px';
212
+ pathElement.style.strokeDasharray = '8,4';
213
+ }
214
+ }
215
+ });
216
+ const svgCircles = chartContainer.querySelectorAll('svg circle');
217
+ svgCircles.forEach((circle) => {
218
+ const circleElement = circle as SVGCircleElement;
219
+ const parentGroup = circleElement.closest('g');
220
+ if (parentGroup) {
221
+ const stroke = circleElement.getAttribute('stroke') || circleElement.style.stroke;
222
+ const fill = circleElement.getAttribute('fill') || circleElement.style.fill;
223
+
224
+ if (stroke === getColorForGraph('green') || fill === getColorForGraph('green')) {
225
+ circleElement.style.display = 'none';
226
+
227
+ const cx = parseFloat(circleElement.getAttribute('cx') || '0');
228
+ const cy = parseFloat(circleElement.getAttribute('cy') || '0');
229
+ const size = 6;
230
+
231
+ const svg = circleElement.ownerSVGElement;
232
+ if (svg) {
233
+ const line1 = document.createElementNS(SVG_NAMESPACE, 'line');
234
+ line1.setAttribute('x1', (cx - size).toString());
235
+ line1.setAttribute('y1', (cy - size).toString());
236
+ line1.setAttribute('x2', (cx + size).toString());
237
+ line1.setAttribute('y2', (cy + size).toString());
238
+ line1.setAttribute('stroke', getColorForGraph('green'));
239
+ line1.setAttribute('stroke-width', '3');
240
+ line1.setAttribute('stroke-linecap', 'round');
241
+
242
+ const line2 = document.createElementNS(SVG_NAMESPACE, 'line');
243
+ line2.setAttribute('x1', (cx + size).toString());
244
+ line2.setAttribute('y1', (cy - size).toString());
245
+ line2.setAttribute('x2', (cx - size).toString());
246
+ line2.setAttribute('y2', (cy + size).toString());
247
+ line2.setAttribute('stroke', getColorForGraph('green'));
248
+ line2.setAttribute('stroke-width', '3');
249
+ line2.setAttribute('stroke-linecap', 'round');
250
+
251
+ parentGroup.appendChild(line1);
252
+ parentGroup.appendChild(line2);
253
+ }
254
+ }
255
+ }
256
+ });
257
+ }
258
+ };
259
+
260
+ const timer = setTimeout(applyChartStyling, 100);
261
+ return () => clearTimeout(timer);
262
+ }, [cervixFormData, isLoading]);
263
+
264
+ const shouldRenderChart = finalChartData.length > 0;
265
+
266
+ const totalPages = Math.ceil(totalItems / (pageSize || 1)) || 1;
267
+ const { pageSizes: calculatedPageSizes, itemsDisplayed } = usePaginationInfo(
268
+ pageSize,
269
+ totalPages,
270
+ currentPage,
271
+ totalItems,
272
+ );
273
+
274
+ return (
275
+ <div className={styles.graphContainer}>
276
+ <div className={styles.graphHeader}>
277
+ <div className={styles.graphHeaderLeft}>
278
+ <h6>Cervical Dilation and Descent of Head</h6>
279
+ <Tag type="outline">Normal progression varies</Tag>
280
+ </div>
281
+ <div className={styles.graphHeaderRight}>
282
+ <div className={styles.viewSwitcher}>
283
+ <Button
284
+ kind={viewMode === 'graph' ? 'primary' : 'secondary'}
285
+ size={controlSize}
286
+ hasIconOnly
287
+ iconDescription={t('graphView', 'Graph View')}
288
+ onClick={() => onViewModeChange('graph')}
289
+ className={styles.viewButton}>
290
+ <ChartColumn />
291
+ </Button>
292
+ <Button
293
+ kind={viewMode === 'table' ? 'primary' : 'secondary'}
294
+ size={controlSize}
295
+ hasIconOnly
296
+ iconDescription={t('tableView', 'Table View')}
297
+ onClick={() => onViewModeChange('table')}
298
+ className={styles.viewButton}>
299
+ <TableIcon />
300
+ </Button>
301
+ </div>
302
+ <Button kind="primary" size={controlSize} renderIcon={Add} onClick={onAddData}>
303
+ {t('add', 'Add')}
304
+ </Button>
305
+ </div>
306
+ </div>
307
+ <p className={styles.graphDescription}>
308
+ Track cervical dilation and descent of fetal head during labor progression
309
+ </p>
310
+
311
+ {viewMode === 'graph' ? (
312
+ <>
313
+ <div className={styles.chartContainer} data-chart-id="cervix">
314
+ {shouldRenderChart ? (
315
+ <div className="cervix-chart-wrapper">
316
+ <LineChart data={finalChartData} options={chartOptions} />
317
+ </div>
318
+ ) : (
319
+ <LineChart
320
+ data={[{ group: 'No Data', time: t('noData', 'No Data'), value: 0 }]}
321
+ options={{
322
+ ...chartOptions,
323
+ legend: { enabled: false },
324
+ points: { enabled: false },
325
+ color: { scale: { 'No Data': '#d0d0d0' } },
326
+ }}
327
+ />
328
+ )}
329
+ </div>
330
+ {timeLabelsData.length > 0 && (
331
+ <div
332
+ className={styles.customTimeLabelsContainer}
333
+ style={{ '--visible-columns': Math.min(10, timeLabelsData.length) } as React.CSSProperties}>
334
+ <div className={styles.customTimeLabelsRow}>
335
+ <div className={styles.customTimeLabelHeader}>Hours</div>
336
+ {timeLabelsData.map((data, index) => (
337
+ <div
338
+ key={`hours-${index}`}
339
+ className={styles.customTimeLabelCell}
340
+ style={{
341
+ gridColumnEnd: `span ${data.span}`,
342
+ backgroundColor: '#f4f4f4',
343
+ fontWeight: 700,
344
+ }}>
345
+ {data.hours}
346
+ </div>
347
+ ))}
348
+ </div>
349
+ <div className={styles.customTimeLabelsRow}>
350
+ <div className={styles.customTimeLabelHeader}>Time</div>
351
+ {timeLabelsData.map((data, index) => (
352
+ <div
353
+ key={`time-${index}`}
354
+ className={styles.customTimeLabelCell}
355
+ style={{ gridColumnEnd: `span ${data.span}` }}>
356
+ {data.time}
357
+ </div>
358
+ ))}
359
+ </div>
360
+ </div>
361
+ )}
362
+ </>
363
+ ) : (
364
+ <div className={styles.tableContainer}>
365
+ {paginatedData.length > 0 ? (
366
+ <>
367
+ <DataTable rows={paginatedData} headers={getTableHeaders()}>
368
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
369
+ <TableContainer title="" description="">
370
+ <Table {...getTableProps()} size="sm">
371
+ <TableHead>
372
+ <TableRow>
373
+ {headers.map((header) => (
374
+ <TableHeader {...getHeaderProps({ header })} key={header.key}>
375
+ {header.header}
376
+ </TableHeader>
377
+ ))}
378
+ </TableRow>
379
+ </TableHead>
380
+ <TableBody>
381
+ {rows.map((row) => (
382
+ <TableRow {...getRowProps({ row })} key={row.id}>
383
+ {row.cells.map((cell) => (
384
+ <TableCell key={cell.id}>{cell.value}</TableCell>
385
+ ))}
386
+ </TableRow>
387
+ ))}
388
+ </TableBody>
389
+ </Table>
390
+ </TableContainer>
391
+ )}
392
+ </DataTable>
393
+
394
+ {totalItems > 0 && (
395
+ <>
396
+ <Pagination
397
+ page={currentPage}
398
+ totalItems={totalItems}
399
+ pageSize={pageSize}
400
+ pageSizes={calculatedPageSizes}
401
+ onChange={(event) => {
402
+ onPageChange(event.page);
403
+ if (event.pageSize !== pageSize) {
404
+ onPageSizeChange(event.pageSize);
405
+ }
406
+ }}
407
+ size={controlSize}
408
+ />
409
+ <div className={styles.tableStats}>
410
+ <span className={styles.recordCount}>{itemsDisplayed}</span>
411
+ </div>
412
+ </>
413
+ )}
414
+ </>
415
+ ) : (
416
+ <div className={styles.emptyState}>
417
+ <p>{t('noDataAvailable', 'No data available for this graph')}</p>
418
+ <Button kind="primary" size={controlSize} renderIcon={Add} onClick={onAddData}>
419
+ {t('addFirstDataPoint', 'Add first data point')}
420
+ </Button>
421
+ </div>
422
+ )}
423
+ </div>
424
+ )}
425
+ </div>
426
+ );
427
+ };
428
+
429
+ export default CervixGraph;
@@ -0,0 +1,163 @@
1
+ import React, { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button } from '@carbon/react';
4
+ import { Add, ChartColumn, Table as TableIcon } from '@carbon/react/icons';
5
+ import DrugsIVFluidsGraph, { DrugsIVFluidsData } from './drugs-iv-fluids-graph.component';
6
+ import DrugsIVFluidsForm from '../forms/drugs-iv-fluids-form.component';
7
+ import styles from '../partography.scss';
8
+
9
+ interface DrugsIVFluidsGraphWrapperProps {
10
+ data: DrugsIVFluidsData[];
11
+ tableData?: Array<{
12
+ date: string;
13
+ drugName: string;
14
+ dosage: string;
15
+ route?: string;
16
+ frequency?: string;
17
+ source?: string;
18
+ }>;
19
+ viewMode?: 'graph' | 'table';
20
+ currentPage?: number;
21
+ pageSize?: number;
22
+ totalItems?: number;
23
+ controlSize?: 'sm' | 'md' | 'lg';
24
+ onAddData?: () => void;
25
+ onViewModeChange?: (mode: 'graph' | 'table') => void;
26
+ onPageChange?: (page: number) => void;
27
+ onPageSizeChange?: (pageSize: number) => void;
28
+ isAddButtonDisabled?: boolean;
29
+ patient?: {
30
+ uuid: string;
31
+ name: string;
32
+ gender: string;
33
+ age: string;
34
+ };
35
+ onDrugsIVFluidsSubmit?: (data: { drugName: string; dosage: string; route: string; frequency: string }) => void;
36
+ onDataSaved?: () => void;
37
+ }
38
+
39
+ const DrugsIVFluidsGraphWrapper: React.FC<DrugsIVFluidsGraphWrapperProps> = ({
40
+ data = [],
41
+ tableData = [],
42
+ viewMode = 'graph',
43
+ controlSize = 'sm',
44
+ onAddData,
45
+ onViewModeChange,
46
+ isAddButtonDisabled = false,
47
+ patient,
48
+ onDrugsIVFluidsSubmit,
49
+ onDataSaved,
50
+ }) => {
51
+ const { t } = useTranslation();
52
+ const [isFormOpen, setIsFormOpen] = useState(false);
53
+
54
+ const handleAddData = () => {
55
+ setIsFormOpen(true);
56
+ onAddData?.();
57
+ };
58
+
59
+ const handleFormSubmit = (formData: { drugName: string; dosage: string; route: string; frequency: string }) => {
60
+ onDrugsIVFluidsSubmit?.(formData);
61
+ setIsFormOpen(false);
62
+ };
63
+
64
+ const handleFormClose = () => {
65
+ setIsFormOpen(false);
66
+ };
67
+
68
+ return (
69
+ <div className={styles.fetalHeartRateSection}>
70
+ <div className={styles.fetalHeartRateContainer}>
71
+ <div className={styles.fetalHeartRateHeader}>
72
+ <div className={styles.fetalHeartRateTitle}>
73
+ <h3 className={styles.fetalHeartRateHeading}>{t('drugsAndIVFluidsGiven', 'Drugs given and IV fluids')}</h3>
74
+ </div>
75
+ <div className={styles.fetalHeartRateControls}>
76
+ <div className={styles.viewToggle}>
77
+ <Button
78
+ kind={viewMode === 'graph' ? 'primary' : 'secondary'}
79
+ size={controlSize}
80
+ hasIconOnly
81
+ iconDescription={t('graphView', 'Graph View')}
82
+ onClick={() => onViewModeChange?.('graph')}
83
+ className={styles.viewButton}>
84
+ <ChartColumn />
85
+ </Button>
86
+ <Button
87
+ kind={viewMode === 'table' ? 'primary' : 'secondary'}
88
+ size={controlSize}
89
+ hasIconOnly
90
+ iconDescription={t('tableView', 'Table View')}
91
+ onClick={() => onViewModeChange?.('table')}
92
+ className={styles.viewButton}>
93
+ <TableIcon />
94
+ </Button>
95
+ </div>
96
+ <Button
97
+ kind="primary"
98
+ size={controlSize}
99
+ renderIcon={Add}
100
+ iconDescription="Add drugs and IV fluids data"
101
+ disabled={isAddButtonDisabled}
102
+ onClick={handleAddData}
103
+ className={styles.addButton}>
104
+ Add
105
+ </Button>
106
+ </div>
107
+ </div>
108
+
109
+ {viewMode === 'graph' ? (
110
+ <DrugsIVFluidsGraph data={data} />
111
+ ) : (
112
+ <div className={styles.tableContainer}>
113
+ {tableData && tableData.length > 0 ? (
114
+ <div className={styles.drugsIVFluidsTable}>
115
+ <table className={styles.dataTable}>
116
+ <thead>
117
+ <tr>
118
+ <th>{t('date', 'Date')}</th>
119
+ <th>{t('drugName', 'Drug Name')}</th>
120
+ <th>{t('dosage', 'Dosage')}</th>
121
+ <th>{t('route', 'Route')}</th>
122
+ <th>{t('frequency', 'Frequency')}</th>
123
+ <th>{t('source', 'Source')}</th>
124
+ </tr>
125
+ </thead>
126
+ <tbody>
127
+ {tableData.map((item, index) => (
128
+ <tr key={index}>
129
+ <td>{item.date}</td>
130
+ <td>{item.drugName}</td>
131
+ <td>{item.dosage}</td>
132
+ <td>{item.route || '-'}</td>
133
+ <td>{item.frequency || '-'}</td>
134
+ <td>
135
+ <span className={item.source === 'order' ? styles.orderSource : styles.manualSource}>
136
+ {item.source === 'order' ? t('drugOrder', 'Drug Order') : t('manual', 'Manual')}
137
+ </span>
138
+ </td>
139
+ </tr>
140
+ ))}
141
+ </tbody>
142
+ </table>
143
+ </div>
144
+ ) : (
145
+ <div className={styles.emptyTable}>
146
+ <p>{t('noDrugsIVFluidsData', 'No drugs and IV fluids data available')}</p>
147
+ </div>
148
+ )}
149
+ </div>
150
+ )}
151
+ </div>
152
+ <DrugsIVFluidsForm
153
+ isOpen={isFormOpen}
154
+ onClose={handleFormClose}
155
+ onSubmit={handleFormSubmit}
156
+ patient={patient}
157
+ onDataSaved={onDataSaved}
158
+ />
159
+ </div>
160
+ );
161
+ };
162
+
163
+ export default DrugsIVFluidsGraphWrapper;
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import styles from '../partography.scss';
4
+
5
+ export interface DrugsIVFluidsData {
6
+ drugName: string;
7
+ dosage: string;
8
+ index?: number;
9
+ }
10
+
11
+ interface DrugsIVFluidsGraphProps {
12
+ data: DrugsIVFluidsData[];
13
+ }
14
+ const DrugsIVFluidsGraph: React.FC<DrugsIVFluidsGraphProps> = ({ data }) => {
15
+ const { t } = useTranslation();
16
+ const getColumns = () => {
17
+ const emptyColumns = Array.from({ length: 13 }, (_, i) => `grid-${i + 1}`);
18
+
19
+ if (data.length === 0) {
20
+ return emptyColumns;
21
+ }
22
+ if (data.length <= 13) {
23
+ const dataColumns = data.map((_, index) => `data-${index}`);
24
+ const remainingEmpty = Array.from({ length: 13 - data.length }, (_, i) => `empty-${i + 1}`);
25
+ return [...dataColumns, ...remainingEmpty];
26
+ }
27
+ return data.map((_, index) => `data-${index}`);
28
+ };
29
+
30
+ const columns = getColumns();
31
+ const rows = [
32
+ { id: 'drugs', label: t('drugName', 'Drug Name') },
33
+ { id: 'dosage', label: t('dosage', 'Dosage') },
34
+ ];
35
+
36
+ const getDataForColumn = (column: string): DrugsIVFluidsData | undefined => {
37
+ if (column.startsWith('grid-') || column.startsWith('empty-')) {
38
+ return undefined;
39
+ }
40
+ const index = parseInt(column.replace('data-', ''));
41
+ return data[index];
42
+ };
43
+ const getCellContent = (column: string, rowId: string) => {
44
+ const dataItem = getDataForColumn(column);
45
+
46
+ if (!dataItem) {
47
+ return '';
48
+ }
49
+
50
+ if (rowId === 'drugs') {
51
+ return dataItem.drugName;
52
+ } else if (rowId === 'dosage') {
53
+ return dataItem.dosage;
54
+ }
55
+
56
+ return '';
57
+ };
58
+ return (
59
+ <div className={styles.drugsIVFluidsGraph}>
60
+ <div className={styles.membraneGrid}>
61
+ <div className={styles.gridContainer}>
62
+ {rows.map((row) => (
63
+ <div key={row.id} className={styles.gridRow}>
64
+ <div className={styles.gridRowLabel}>{row.label}</div>
65
+ {columns.map((column) => (
66
+ <div
67
+ key={`${row.id}-${column}`}
68
+ className={`${styles.gridCell} ${styles.drugsCell}`}
69
+ data-column={column}
70
+ data-row={row.id}>
71
+ {getCellContent(column, row.id)}
72
+ </div>
73
+ ))}
74
+ </div>
75
+ ))}
76
+ </div>
77
+ </div>
78
+ </div>
79
+ );
80
+ };
81
+
82
+ export default DrugsIVFluidsGraph;