ui-soxo-bootstrap-core 2.6.44 → 2.6.46-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/lib/components/global-header/global-header.js +1 -1
- package/core/lib/modules/generic/generic-list/ExportReactCSV.js +393 -8
- package/core/lib/modules/generic/generic-list/generic-list.scss +34 -0
- package/core/models/core-scripts/core-scripts.js +8 -0
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +56 -35
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +1 -0
- package/package.json +1 -1
|
@@ -85,7 +85,7 @@ function GlobalHeaderContent({ loading, appSettings, children, isConnected, hist
|
|
|
85
85
|
className={`global-header ${process.env.REACT_APP_THEME} ${isConnected && !kiosk ? 'connected' : ''}`}
|
|
86
86
|
style={{
|
|
87
87
|
// background: state.theme.colors.bodyBackground,
|
|
88
|
-
height: auto,
|
|
88
|
+
height: 'auto',
|
|
89
89
|
}}
|
|
90
90
|
>
|
|
91
91
|
{/* <MenuOutlined style={{left:'1%',top:'1%', fontSize:18, position:'absolute', zIndex:999}} onClick={showSidebar}/> */}
|
|
@@ -6,24 +6,62 @@
|
|
|
6
6
|
* @param {Array<{ label: string, key: string }>} [headers] - Optional column headers and field mappings.
|
|
7
7
|
* @param {string} [fileName='Report.xlsx'] - Optional name for the downloaded Excel file.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
|
-
import React from 'react';
|
|
9
|
+
|
|
10
|
+
import React, { useState } from 'react';
|
|
11
11
|
// Import XLSX library to handle Excel file creation
|
|
12
12
|
import * as XLSX from 'xlsx';
|
|
13
|
+
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
|
13
14
|
// import * as XLSX from 'xlsx-js-style';
|
|
14
15
|
// import * as XLSX from 'xlsx-js-style';
|
|
15
16
|
// Import saveAs from file-saver to trigger file download in browser
|
|
16
17
|
import { saveAs } from 'file-saver';
|
|
17
|
-
import { Button } from 'antd';
|
|
18
|
+
import { Button, Dropdown, Form, Input, Menu, Modal, Radio, message } from 'antd';
|
|
19
|
+
import { DownOutlined, FileExcelFilled, FilePdfFilled, MailFilled, DownloadOutlined } from '@ant-design/icons';
|
|
18
20
|
import { Trans } from 'react-i18next';
|
|
21
|
+
import { CoreScripts } from '../../../../models';
|
|
22
|
+
import './generic-list.scss';
|
|
23
|
+
|
|
24
|
+
const normalizeScriptId = (value) => {
|
|
25
|
+
if (value === undefined || value === null || value === '') return value;
|
|
26
|
+
|
|
27
|
+
const numberValue = Number(value);
|
|
28
|
+
return Number.isNaN(numberValue) ? value : numberValue;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getRecipients = (value) => {
|
|
32
|
+
if (Array.isArray(value)) return value.filter(Boolean);
|
|
33
|
+
|
|
34
|
+
return String(value || '')
|
|
35
|
+
.split(/[\n,;]+/)
|
|
36
|
+
.map((email) => email.trim())
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export const ExportReactCSV = ({
|
|
42
|
+
csvData,
|
|
43
|
+
headers,
|
|
44
|
+
fileName,
|
|
45
|
+
pdfFileName,
|
|
46
|
+
title,
|
|
47
|
+
format = 'excel',
|
|
48
|
+
buttonText,
|
|
49
|
+
dropdown = false,
|
|
50
|
+
onSendReportToMail,
|
|
51
|
+
scriptId,
|
|
52
|
+
inputParameters,
|
|
53
|
+
}) => {
|
|
54
|
+
const [mailModalVisible, setMailModalVisible] = useState(false);
|
|
55
|
+
const [sendingMail, setSendingMail] = useState(false);
|
|
56
|
+
const [selectedFormat, setSelectedFormat] = useState('excel');
|
|
57
|
+
const [mailForm] = Form.useForm();
|
|
19
58
|
|
|
20
|
-
export const ExportReactCSV = ({ csvData, headers, fileName,title }) => {
|
|
21
59
|
/**
|
|
22
60
|
* exportToExcel function
|
|
23
61
|
* Triggered on button click, generates and downloads an Excel file
|
|
24
62
|
*/
|
|
25
63
|
|
|
26
|
-
const exportToExcel = () => {
|
|
64
|
+
const exportToExcel = (downloadFileName) => {
|
|
27
65
|
// if (!csvData?.length) return;
|
|
28
66
|
|
|
29
67
|
let worksheet;
|
|
@@ -75,14 +113,361 @@ export const ExportReactCSV = ({ csvData, headers, fileName,title }) => {
|
|
|
75
113
|
const blob = new Blob([excelBuffer], {
|
|
76
114
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
77
115
|
});
|
|
78
|
-
const flName = fileName || 'Report.xlsx';
|
|
116
|
+
const flName = downloadFileName || fileName || 'Report.xlsx';
|
|
79
117
|
// Trigger download using file-saver
|
|
80
118
|
saveAs(blob, flName);
|
|
81
119
|
};
|
|
120
|
+
|
|
121
|
+
const exportToPdf = async (downloadFileName) => {
|
|
122
|
+
const pdfDoc = await PDFDocument.create();
|
|
123
|
+
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
|
124
|
+
const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
125
|
+
const headersToUse = headers?.length
|
|
126
|
+
? headers
|
|
127
|
+
: Object.keys(csvData?.[0] || {}).map((key) => ({ label: key, key }));
|
|
128
|
+
const pageSize = headersToUse.length > 12 ? [1190, 842] : [842, 595];
|
|
129
|
+
const margin = 30;
|
|
130
|
+
const minRowHeight = 20;
|
|
131
|
+
const titleHeight = 38;
|
|
132
|
+
const fontSize = headersToUse.length > 12 ? 7 : 8;
|
|
133
|
+
const headerFontSize = headersToUse.length > 12 ? 7 : 8;
|
|
134
|
+
const lineHeight = fontSize + 2;
|
|
135
|
+
const headerLineHeight = headerFontSize + 2;
|
|
136
|
+
const cellPaddingX = 4;
|
|
137
|
+
const cellPaddingY = 5;
|
|
138
|
+
const availableWidth = pageSize[0] - margin * 2;
|
|
139
|
+
let page;
|
|
140
|
+
let y;
|
|
141
|
+
|
|
142
|
+
const sanitizeValue = (value) => {
|
|
143
|
+
if (value === undefined || value === null) return '';
|
|
144
|
+
if (typeof value === 'object') return JSON.stringify(value);
|
|
145
|
+
return String(value);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const getTextWidth = (text, textFont, size) => textFont.widthOfTextAtSize(sanitizeValue(text), size);
|
|
149
|
+
|
|
150
|
+
const splitLongWord = (word, maxWidth, textFont, size) => {
|
|
151
|
+
const characters = Array.from(word);
|
|
152
|
+
const chunks = [];
|
|
153
|
+
let chunk = '';
|
|
154
|
+
|
|
155
|
+
characters.forEach((character) => {
|
|
156
|
+
const nextChunk = `${chunk}${character}`;
|
|
157
|
+
|
|
158
|
+
if (chunk && getTextWidth(nextChunk, textFont, size) > maxWidth) {
|
|
159
|
+
chunks.push(chunk);
|
|
160
|
+
chunk = character;
|
|
161
|
+
} else {
|
|
162
|
+
chunk = nextChunk;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (chunk) chunks.push(chunk);
|
|
167
|
+
return chunks;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const wrapText = (value, maxWidth, textFont, size) => {
|
|
171
|
+
const text = sanitizeValue(value);
|
|
172
|
+
const usableWidth = Math.max(maxWidth, 1);
|
|
173
|
+
const paragraphs = text.split(/\r?\n/);
|
|
174
|
+
|
|
175
|
+
return paragraphs.flatMap((paragraph) => {
|
|
176
|
+
const words = paragraph.split(/\s+/).filter(Boolean);
|
|
177
|
+
const lines = [];
|
|
178
|
+
let line = '';
|
|
179
|
+
|
|
180
|
+
if (!words.length) return [''];
|
|
181
|
+
|
|
182
|
+
words.forEach((word) => {
|
|
183
|
+
if (getTextWidth(word, textFont, size) > usableWidth) {
|
|
184
|
+
if (line) {
|
|
185
|
+
lines.push(line);
|
|
186
|
+
line = '';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const chunks = splitLongWord(word, usableWidth, textFont, size);
|
|
190
|
+
chunks.forEach((chunk, chunkIndex) => {
|
|
191
|
+
if (chunkIndex === chunks.length - 1) {
|
|
192
|
+
line = chunk;
|
|
193
|
+
} else {
|
|
194
|
+
lines.push(chunk);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const nextLine = line ? `${line} ${word}` : word;
|
|
202
|
+
if (getTextWidth(nextLine, textFont, size) <= usableWidth) {
|
|
203
|
+
line = nextLine;
|
|
204
|
+
} else {
|
|
205
|
+
if (line) lines.push(line);
|
|
206
|
+
line = word;
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (line) lines.push(line);
|
|
211
|
+
return lines;
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const getColumnWidths = () => {
|
|
216
|
+
if (!headersToUse.length) return [availableWidth];
|
|
217
|
+
|
|
218
|
+
const sampleRows = (csvData || []).slice(0, 100);
|
|
219
|
+
const minWidths = [];
|
|
220
|
+
const preferredWidths = headersToUse.map((header, index) => {
|
|
221
|
+
const label = sanitizeValue(header.label);
|
|
222
|
+
const compactColumn = label === '#' || ['slno', 'sl no'].includes(label.toLowerCase());
|
|
223
|
+
const minWidth = compactColumn ? 28 : 36;
|
|
224
|
+
const values = [label, ...sampleRows.map((row) => sanitizeValue(row[header.key]))];
|
|
225
|
+
const longestValueWidth = values.reduce((maxWidth, value) => Math.max(maxWidth, getTextWidth(value, font, fontSize)), 0);
|
|
226
|
+
const longestWordWidth = values.reduce((maxWidth, value) => {
|
|
227
|
+
const words = value.split(/\s+/).filter(Boolean);
|
|
228
|
+
return words.reduce((wordMaxWidth, word) => Math.max(wordMaxWidth, getTextWidth(word, font, fontSize)), maxWidth);
|
|
229
|
+
}, 0);
|
|
230
|
+
const headerWidth = getTextWidth(label, boldFont, headerFontSize);
|
|
231
|
+
const preferredWidth = Math.max(
|
|
232
|
+
minWidth,
|
|
233
|
+
Math.min(longestValueWidth + cellPaddingX * 2, compactColumn ? 48 : 130),
|
|
234
|
+
Math.min(longestWordWidth + cellPaddingX * 2, compactColumn ? 48 : 95),
|
|
235
|
+
headerWidth + cellPaddingX * 2
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
minWidths[index] = minWidth;
|
|
239
|
+
return preferredWidth;
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const minTotal = minWidths.reduce((total, width) => total + width, 0);
|
|
243
|
+
const preferredTotal = preferredWidths.reduce((total, width) => total + width, 0);
|
|
244
|
+
|
|
245
|
+
if (preferredTotal <= availableWidth) {
|
|
246
|
+
const extraWidth = availableWidth - preferredTotal;
|
|
247
|
+
return preferredWidths.map((width) => width + (extraWidth * width) / preferredTotal);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (minTotal >= availableWidth) {
|
|
251
|
+
return minWidths.map((width) => (width * availableWidth) / minTotal);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const flexibleTotal = preferredWidths.reduce((total, width, index) => total + (width - minWidths[index]), 0);
|
|
255
|
+
const extraWidth = availableWidth - minTotal;
|
|
256
|
+
|
|
257
|
+
return preferredWidths.map((width, index) => {
|
|
258
|
+
const flexibleWidth = width - minWidths[index];
|
|
259
|
+
return minWidths[index] + (extraWidth * flexibleWidth) / flexibleTotal;
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const columnWidths = getColumnWidths();
|
|
264
|
+
|
|
265
|
+
const getMaxLineCount = (cellLines) => Math.max(1, ...cellLines.map((lines) => lines.length));
|
|
266
|
+
|
|
267
|
+
const getRowHeight = (cellLines, rowLineHeight) =>
|
|
268
|
+
Math.max(minRowHeight, getMaxLineCount(cellLines) * rowLineHeight + cellPaddingY * 2);
|
|
269
|
+
|
|
270
|
+
const drawTableRow = (cellLines, rowHeight, options) => {
|
|
271
|
+
let x = margin;
|
|
272
|
+
|
|
273
|
+
cellLines.forEach((lines, index) => {
|
|
274
|
+
const width = columnWidths[index] || availableWidth;
|
|
275
|
+
page.drawRectangle({
|
|
276
|
+
x,
|
|
277
|
+
y: y - rowHeight,
|
|
278
|
+
width,
|
|
279
|
+
height: rowHeight,
|
|
280
|
+
color: options.backgroundColor,
|
|
281
|
+
borderColor: options.borderColor,
|
|
282
|
+
borderWidth: 0.5,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
lines.forEach((line, lineIndex) => {
|
|
286
|
+
page.drawText(line, {
|
|
287
|
+
x: x + cellPaddingX,
|
|
288
|
+
y: y - cellPaddingY - options.fontSize - lineIndex * options.lineHeight,
|
|
289
|
+
size: options.fontSize,
|
|
290
|
+
font: options.font,
|
|
291
|
+
color: options.textColor,
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
x += width;
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
y -= rowHeight;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const headerCellLines = headersToUse.map((header, index) =>
|
|
302
|
+
wrapText(header.label, columnWidths[index] - cellPaddingX * 2, boldFont, headerFontSize)
|
|
303
|
+
);
|
|
304
|
+
const headerRowHeight = getRowHeight(headerCellLines, headerLineHeight);
|
|
305
|
+
|
|
306
|
+
const drawHeader = () => {
|
|
307
|
+
drawTableRow(headerCellLines, headerRowHeight, {
|
|
308
|
+
backgroundColor: rgb(0.92, 0.94, 0.96),
|
|
309
|
+
borderColor: rgb(0.72, 0.75, 0.78),
|
|
310
|
+
font: boldFont,
|
|
311
|
+
fontSize: headerFontSize,
|
|
312
|
+
lineHeight: headerLineHeight,
|
|
313
|
+
textColor: rgb(0.1, 0.1, 0.1),
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const addPage = () => {
|
|
318
|
+
page = pdfDoc.addPage(pageSize);
|
|
319
|
+
y = pageSize[1] - margin;
|
|
320
|
+
|
|
321
|
+
page.drawText(title || 'Report', {
|
|
322
|
+
x: margin,
|
|
323
|
+
y,
|
|
324
|
+
size: 14,
|
|
325
|
+
font: boldFont,
|
|
326
|
+
color: rgb(0.1, 0.1, 0.1),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
y -= titleHeight;
|
|
330
|
+
drawHeader();
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const drawDataRow = (row) => {
|
|
334
|
+
const rowFont = row.isSummaryRow ? boldFont : font;
|
|
335
|
+
const cellLines = headersToUse.map((header, index) =>
|
|
336
|
+
wrapText(row[header.key], columnWidths[index] - cellPaddingX * 2, rowFont, fontSize)
|
|
337
|
+
);
|
|
338
|
+
let remainingCellLines = cellLines.map((lines) => (lines.length ? lines : ['']));
|
|
339
|
+
const fullPageDataHeight = pageSize[1] - margin * 2 - titleHeight - headerRowHeight;
|
|
340
|
+
|
|
341
|
+
while (remainingCellLines.some((lines) => lines.length)) {
|
|
342
|
+
const rowHeight = getRowHeight(remainingCellLines, lineHeight);
|
|
343
|
+
const availableHeight = y - margin;
|
|
344
|
+
|
|
345
|
+
if (rowHeight > availableHeight && rowHeight <= fullPageDataHeight) {
|
|
346
|
+
addPage();
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const maxLinesOnPage =
|
|
351
|
+
rowHeight > availableHeight
|
|
352
|
+
? Math.max(1, Math.floor((availableHeight - cellPaddingY * 2) / lineHeight))
|
|
353
|
+
: getMaxLineCount(remainingCellLines);
|
|
354
|
+
const segmentCellLines = remainingCellLines.map((lines) => lines.slice(0, maxLinesOnPage));
|
|
355
|
+
const segmentHeight = getRowHeight(segmentCellLines, lineHeight);
|
|
356
|
+
|
|
357
|
+
if (segmentHeight > availableHeight && availableHeight < fullPageDataHeight) {
|
|
358
|
+
addPage();
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
drawTableRow(segmentCellLines, segmentHeight, {
|
|
363
|
+
backgroundColor: row.isSummaryRow ? rgb(0.97, 0.97, 0.97) : rgb(1, 1, 1),
|
|
364
|
+
borderColor: rgb(0.82, 0.84, 0.86),
|
|
365
|
+
font: rowFont,
|
|
366
|
+
fontSize,
|
|
367
|
+
lineHeight,
|
|
368
|
+
textColor: rgb(0.12, 0.12, 0.12),
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
remainingCellLines = remainingCellLines.map((lines) => lines.slice(maxLinesOnPage));
|
|
372
|
+
if (remainingCellLines.some((lines) => lines.length)) addPage();
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
addPage();
|
|
377
|
+
|
|
378
|
+
(csvData || []).forEach((row) => {
|
|
379
|
+
drawDataRow(row);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
if (!csvData?.length) {
|
|
383
|
+
page.drawText('No records found', {
|
|
384
|
+
x: margin,
|
|
385
|
+
y: y + 1,
|
|
386
|
+
size: fontSize,
|
|
387
|
+
font,
|
|
388
|
+
color: rgb(0.12, 0.12, 0.12),
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const pdfBytes = await pdfDoc.save();
|
|
393
|
+
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
|
|
394
|
+
const flName = downloadFileName || pdfFileName || fileName || 'Report.pdf';
|
|
395
|
+
saveAs(blob, flName);
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const handleDownloadMenuClick = ({ key }) => {
|
|
399
|
+
if (key === 'excel') {
|
|
400
|
+
exportToExcel(fileName);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (key === 'pdf') {
|
|
405
|
+
exportToPdf(pdfFileName);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (key === 'mail') {
|
|
410
|
+
setMailModalVisible(true);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const handleMailModalClose = () => {
|
|
415
|
+
setMailModalVisible(false);
|
|
416
|
+
mailForm.resetFields();
|
|
417
|
+
setSelectedFormat('excel');
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const handleFormatToggle = (value) => {
|
|
421
|
+
const nextValue = selectedFormat === value ? null : value;
|
|
422
|
+
setSelectedFormat(nextValue);
|
|
423
|
+
mailForm.setFieldsValue({ format: nextValue });
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
const downloadMenu = (
|
|
428
|
+
<Menu className="export-download-menu" onClick={handleDownloadMenuClick}>
|
|
429
|
+
<Menu.Item
|
|
430
|
+
key="excel"
|
|
431
|
+
icon={<FileExcelFilled className="export-download-menu__icon export-download-menu__icon--excel" />}
|
|
432
|
+
>
|
|
433
|
+
Download Excel Sheet
|
|
434
|
+
</Menu.Item>
|
|
435
|
+
<Menu.Item
|
|
436
|
+
key="pdf"
|
|
437
|
+
icon={<FilePdfFilled className="export-download-menu__icon export-download-menu__icon--pdf" />}
|
|
438
|
+
>
|
|
439
|
+
Download PDF
|
|
440
|
+
</Menu.Item>
|
|
441
|
+
</Menu>
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
const isPdf = format === 'pdf';
|
|
445
|
+
const handleExportButtonClick = () => {
|
|
446
|
+
if (isPdf) {
|
|
447
|
+
exportToPdf();
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
exportToExcel();
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
if (dropdown) {
|
|
455
|
+
return (
|
|
456
|
+
<>
|
|
457
|
+
<Dropdown overlay={downloadMenu} trigger={['click']} placement="bottomRight">
|
|
458
|
+
<Button type="primary" size="small" className="export-download-button">
|
|
459
|
+
<Trans i18nKey={buttonText || 'Download'} /> <DownloadOutlined className="export-download-button__icon" />
|
|
460
|
+
</Button>
|
|
461
|
+
</Dropdown>
|
|
462
|
+
</>
|
|
463
|
+
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
82
467
|
// Render an Ant Design Button that triggers the Excel export
|
|
83
468
|
return (
|
|
84
|
-
<Button type="secondary" size="small" onClick={
|
|
85
|
-
<Trans i18nKey=
|
|
469
|
+
<Button type="secondary" size="small" onClick={handleExportButtonClick} className="export-download-button">
|
|
470
|
+
<Trans i18nKey={buttonText || (isPdf ? 'PDF Download' : 'Download')} /> <DownloadOutlined className="export-download-button__icon" />
|
|
86
471
|
</Button>
|
|
87
472
|
);
|
|
88
473
|
};
|
|
@@ -32,3 +32,37 @@
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
.export-download-button {
|
|
37
|
+
&.ant-btn-primary {
|
|
38
|
+
background: #2f80ed;
|
|
39
|
+
border-color: #2f80ed;
|
|
40
|
+
color: #fff;
|
|
41
|
+
|
|
42
|
+
&:hover,
|
|
43
|
+
&:focus {
|
|
44
|
+
background: #1c7ed6;
|
|
45
|
+
border-color: #1c7ed6;
|
|
46
|
+
color: #fff;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.export-download-menu {
|
|
52
|
+
&__icon {
|
|
53
|
+
font-size: 16px;
|
|
54
|
+
vertical-align: -0.15em;
|
|
55
|
+
|
|
56
|
+
&--excel {
|
|
57
|
+
color: #18a058;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&--pdf {
|
|
61
|
+
color: #e03131;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&--mail {
|
|
65
|
+
color: #1c7ed6;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -80,6 +80,14 @@ class CoreScript extends Base {
|
|
|
80
80
|
});
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
+
// sentToReportMail = (formBody) => {
|
|
84
|
+
// return ApiUtils.post({
|
|
85
|
+
// baseUrl: 'http://localhost:8002/dev/',
|
|
86
|
+
// url: `core-scripts/send-to-report-mail`,
|
|
87
|
+
// formBody,
|
|
88
|
+
// });
|
|
89
|
+
// };
|
|
90
|
+
|
|
83
91
|
/**
|
|
84
92
|
*Updating user details
|
|
85
93
|
* @returns
|
|
@@ -70,6 +70,10 @@ export default function ReportingDashboard({
|
|
|
70
70
|
|
|
71
71
|
const [formContents, setformContents] = useState({});
|
|
72
72
|
const [liveFormContents, setLiveFormContents] = useState({});
|
|
73
|
+
const [reportMailRequest, setReportMailRequest] = useState({
|
|
74
|
+
scriptId: null,
|
|
75
|
+
inputParameters: {},
|
|
76
|
+
});
|
|
73
77
|
|
|
74
78
|
const scriptId = useRef(null);
|
|
75
79
|
|
|
@@ -300,6 +304,11 @@ export default function ReportingDashboard({
|
|
|
300
304
|
formBody = { body: { ...scope, ...paginationData } };
|
|
301
305
|
}
|
|
302
306
|
|
|
307
|
+
setReportMailRequest({
|
|
308
|
+
scriptId: coreScriptId,
|
|
309
|
+
inputParameters: formBody,
|
|
310
|
+
});
|
|
311
|
+
|
|
303
312
|
// Fetch result
|
|
304
313
|
const result = await CoreScripts.getReportingLisitng(coreScriptId, formBody, dbPtr);
|
|
305
314
|
|
|
@@ -668,6 +677,8 @@ export default function ReportingDashboard({
|
|
|
668
677
|
selectedSearchFields={selectedSearchFields}
|
|
669
678
|
handleRemoveSearchField={handleRemoveSearchField}
|
|
670
679
|
fetchReportData={(paginationUpdate) => fetchReportData(id, formContents, dbPtr, paginationUpdate || pagination)}
|
|
680
|
+
reportScriptId={reportMailRequest.scriptId}
|
|
681
|
+
reportInputParameters={reportMailRequest.inputParameters}
|
|
671
682
|
/>
|
|
672
683
|
{/** GuestList component end*/}
|
|
673
684
|
</>
|
|
@@ -676,6 +687,29 @@ export default function ReportingDashboard({
|
|
|
676
687
|
);
|
|
677
688
|
}
|
|
678
689
|
|
|
690
|
+
/**
|
|
691
|
+
* Filters report records using the same search text that drives the table.
|
|
692
|
+
*
|
|
693
|
+
* @param {Array<Object>} records - Report rows.
|
|
694
|
+
* @param {string} searchText - Current table search text.
|
|
695
|
+
* @returns {Array<Object>} Rows matching the search text.
|
|
696
|
+
*/
|
|
697
|
+
function filterReportRecords(records, searchText) {
|
|
698
|
+
if (!Array.isArray(records)) return [];
|
|
699
|
+
|
|
700
|
+
const query = String(searchText || '').trim().toLowerCase();
|
|
701
|
+
|
|
702
|
+
if (!query) return records;
|
|
703
|
+
|
|
704
|
+
return records.filter((record) =>
|
|
705
|
+
Object.values(record).some((value) => {
|
|
706
|
+
if (value === undefined || value === null || typeof value === 'object') return false;
|
|
707
|
+
|
|
708
|
+
return String(value).toLowerCase().indexOf(query) !== -1;
|
|
709
|
+
})
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
|
|
679
713
|
/**
|
|
680
714
|
*
|
|
681
715
|
* @param root0
|
|
@@ -703,6 +737,8 @@ function GuestList({
|
|
|
703
737
|
selectedSearchFields,
|
|
704
738
|
handleRemoveSearchField,
|
|
705
739
|
fetchReportData,
|
|
740
|
+
reportScriptId,
|
|
741
|
+
reportInputParameters,
|
|
706
742
|
}) {
|
|
707
743
|
/**
|
|
708
744
|
* @param {*} propValues
|
|
@@ -791,13 +827,13 @@ function GuestList({
|
|
|
791
827
|
return col;
|
|
792
828
|
});
|
|
793
829
|
const summaryCols = columns.filter((col) => col.enable_summary);
|
|
794
|
-
let dataToExport = [...patients];
|
|
830
|
+
let dataToExport = [...filterReportRecords(patients, query)];
|
|
795
831
|
|
|
796
832
|
if (summaryCols.length > 0) {
|
|
797
833
|
// Build one synthetic row for CSV export that mirrors the table layout:
|
|
798
834
|
// numeric summary cells are populated from `calculateSummaryValues`, while
|
|
799
835
|
// non-summary columns stay blank unless a configured caption should be shown.
|
|
800
|
-
const summaryValues = calculateSummaryValues(summaryCols,
|
|
836
|
+
const summaryValues = calculateSummaryValues(summaryCols, dataToExport);
|
|
801
837
|
const summaryRow = { isSummaryRow: true };
|
|
802
838
|
|
|
803
839
|
cols.forEach((col, index) => {
|
|
@@ -824,37 +860,15 @@ function GuestList({
|
|
|
824
860
|
}
|
|
825
861
|
let exportDatas = getExportData(dataToExport, exportCols);
|
|
826
862
|
|
|
827
|
-
if (exportDatas.
|
|
863
|
+
if (exportDatas.exportDataHeaders.length) {
|
|
828
864
|
setExportData({ exportDatas });
|
|
865
|
+
} else {
|
|
866
|
+
setExportData({});
|
|
829
867
|
}
|
|
830
868
|
}
|
|
831
|
-
}, [patients, columns]);
|
|
832
|
-
|
|
833
|
-
let filtered;
|
|
834
|
-
|
|
835
|
-
if (patients) {
|
|
836
|
-
filtered = patients.filter((record) => {
|
|
837
|
-
if (query) {
|
|
838
|
-
// Keys
|
|
839
|
-
let keys = Object.keys(record);
|
|
840
|
-
|
|
841
|
-
let flag = false;
|
|
869
|
+
}, [patients, columns, query]);
|
|
842
870
|
|
|
843
|
-
|
|
844
|
-
let ele = record[key];
|
|
845
|
-
|
|
846
|
-
if (ele && typeof ele === 'string' && ele.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
|
|
847
|
-
flag = true;
|
|
848
|
-
}
|
|
849
|
-
});
|
|
850
|
-
|
|
851
|
-
/**Will return flag */
|
|
852
|
-
return flag;
|
|
853
|
-
} else {
|
|
854
|
-
return true;
|
|
855
|
-
}
|
|
856
|
-
});
|
|
857
|
-
}
|
|
871
|
+
let filtered = filterReportRecords(patients, query);
|
|
858
872
|
|
|
859
873
|
/**
|
|
860
874
|
* Checks for a match in the filtered patient list based on a scanned code,
|
|
@@ -977,11 +991,18 @@ function GuestList({
|
|
|
977
991
|
<Search className="table-search-input" placeholder="Enter Search Value" allowClear onChange={onSearch} />
|
|
978
992
|
<div className="table-export-button">
|
|
979
993
|
{exportData.exportDatas && (
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
994
|
+
<>
|
|
995
|
+
<ExportReactCSV
|
|
996
|
+
title={config.caption}
|
|
997
|
+
headers={exportData.exportDatas.exportDataHeaders}
|
|
998
|
+
csvData={exportData.exportDatas.exportDataColumns}
|
|
999
|
+
fileName={`${config.caption || 'Report'}.xlsx`}
|
|
1000
|
+
pdfFileName={`${config.caption || 'Report'}.pdf`}
|
|
1001
|
+
scriptId={reportScriptId}
|
|
1002
|
+
inputParameters={reportInputParameters}
|
|
1003
|
+
dropdown
|
|
1004
|
+
/>
|
|
1005
|
+
</>
|
|
985
1006
|
)}
|
|
986
1007
|
</div>
|
|
987
1008
|
|
|
@@ -1036,7 +1057,7 @@ function GuestList({
|
|
|
1036
1057
|
dataSource={filtered ? filtered : patients} // In case if there is no filtered values we can use patient data
|
|
1037
1058
|
columns={cols}
|
|
1038
1059
|
sticky
|
|
1039
|
-
pagination={
|
|
1060
|
+
pagination={true}
|
|
1040
1061
|
summary={(pageData) => {
|
|
1041
1062
|
const summaryCols = columns.filter((col) => col.enable_summary);
|
|
1042
1063
|
if (!summaryCols.length) return null;
|