@visns-studio/visns-components 5.14.10 → 5.14.12
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/README.md +51 -7
- package/package.json +4 -4
- package/src/components/Field.jsx +15 -0
- package/src/components/Form.jsx +57 -8
- package/src/components/columns/ColumnRenderers.jsx +16 -6
- package/src/components/generic/GenericReport.jsx +1662 -540
- package/src/components/generic/StandardModal.jsx +62 -12
- package/src/components/generic/shared/formatters.js +152 -23
- package/src/components/styles/Field.module.scss +144 -48
- package/src/components/styles/GenericReport.module.scss +328 -0
|
@@ -2,8 +2,8 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { toast } from 'react-toastify';
|
|
4
4
|
import moment from 'moment';
|
|
5
|
-
import ReactDataGrid from '@visns-studio/visns-datagrid-enterprise';
|
|
6
5
|
import GroupedReportRenderer from './GroupedReportRenderer';
|
|
6
|
+
import { formatCellContent } from './shared/formatters';
|
|
7
7
|
import {
|
|
8
8
|
HelpCircle as Question,
|
|
9
9
|
Link as LinkChain,
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
Rss,
|
|
31
31
|
Globe,
|
|
32
32
|
Lock,
|
|
33
|
+
Database,
|
|
33
34
|
} from 'lucide-react';
|
|
34
35
|
import CustomFetch from '../Fetch';
|
|
35
36
|
import Download from '../Download';
|
|
@@ -916,7 +917,8 @@ const wizardSteps = [
|
|
|
916
917
|
icon: Data,
|
|
917
918
|
description: 'Organize data into groups (optional)',
|
|
918
919
|
guidance: {
|
|
919
|
-
summary:
|
|
920
|
+
summary:
|
|
921
|
+
'Group your data by a specific field for better organization',
|
|
920
922
|
howTo: [
|
|
921
923
|
'Enable grouping to organize data into sections',
|
|
922
924
|
'Choose which field to group by (e.g., Department, Category, Status)',
|
|
@@ -1042,7 +1044,7 @@ const GenericReportImproved = ({
|
|
|
1042
1044
|
|
|
1043
1045
|
// State for pagination - now using server-side pagination
|
|
1044
1046
|
const [currentPage, setCurrentPage] = useState(1);
|
|
1045
|
-
const [pageSize, setPageSize] = useState(
|
|
1047
|
+
const [pageSize, setPageSize] = useState(10); // Default to 10 to reduce scrolling
|
|
1046
1048
|
const [totalPages, setTotalPages] = useState(0);
|
|
1047
1049
|
const [totalCount, setTotalCount] = useState(0); // Total records from server
|
|
1048
1050
|
|
|
@@ -1071,13 +1073,13 @@ const GenericReportImproved = ({
|
|
|
1071
1073
|
showGroupTotals: true,
|
|
1072
1074
|
expandable: true,
|
|
1073
1075
|
groupHeaderStyle: 'primary',
|
|
1074
|
-
emptyRowStyle: 'light'
|
|
1076
|
+
emptyRowStyle: 'light',
|
|
1075
1077
|
});
|
|
1076
1078
|
|
|
1077
1079
|
// State for custom URLs
|
|
1078
1080
|
const [customUrls, setCustomUrls] = useState({
|
|
1079
1081
|
executeUrl: '',
|
|
1080
|
-
exportUrl: ''
|
|
1082
|
+
exportUrl: '',
|
|
1081
1083
|
});
|
|
1082
1084
|
|
|
1083
1085
|
// Note: Removed table search functionality to simplify user experience
|
|
@@ -1416,13 +1418,30 @@ const GenericReportImproved = ({
|
|
|
1416
1418
|
currentWizardStep,
|
|
1417
1419
|
]);
|
|
1418
1420
|
|
|
1421
|
+
// Reset auto-execute flag when user navigates away from preview step
|
|
1422
|
+
useEffect(() => {
|
|
1423
|
+
if (currentWizardStep !== 5) {
|
|
1424
|
+
setHasAutoExecuted(false);
|
|
1425
|
+
}
|
|
1426
|
+
}, [currentWizardStep]);
|
|
1427
|
+
|
|
1428
|
+
// Reset auto-execute flag when report configuration changes
|
|
1429
|
+
useEffect(() => {
|
|
1430
|
+
setHasAutoExecuted(false);
|
|
1431
|
+
}, [
|
|
1432
|
+
selectedTable,
|
|
1433
|
+
selectedColumns.length,
|
|
1434
|
+
filterCriteria,
|
|
1435
|
+
joins.length,
|
|
1436
|
+
groupingConfig,
|
|
1437
|
+
]);
|
|
1438
|
+
|
|
1419
1439
|
// Auto-execute report when user reaches step 5 (preview step)
|
|
1420
1440
|
useEffect(() => {
|
|
1421
1441
|
if (
|
|
1422
1442
|
currentWizardStep === 5 && // Step 6 is index 5 (preview step)
|
|
1423
1443
|
selectedTable &&
|
|
1424
1444
|
selectedColumns.length > 0 &&
|
|
1425
|
-
previewData.length === 0 && // Only if no data loaded yet
|
|
1426
1445
|
!isLoading &&
|
|
1427
1446
|
!hasAutoExecuted // Prevent infinite loops
|
|
1428
1447
|
) {
|
|
@@ -1433,7 +1452,6 @@ const GenericReportImproved = ({
|
|
|
1433
1452
|
currentWizardStep,
|
|
1434
1453
|
selectedTable,
|
|
1435
1454
|
selectedColumns.length,
|
|
1436
|
-
previewData.length,
|
|
1437
1455
|
isLoading,
|
|
1438
1456
|
hasAutoExecuted,
|
|
1439
1457
|
]);
|
|
@@ -2241,15 +2259,19 @@ const GenericReportImproved = ({
|
|
|
2241
2259
|
|
|
2242
2260
|
try {
|
|
2243
2261
|
// Check if this is a grouped report
|
|
2244
|
-
const isGroupedReport =
|
|
2245
|
-
|
|
2262
|
+
const isGroupedReport =
|
|
2263
|
+
grouping?.enabled || selectedTemplate?.grouping?.enabled;
|
|
2264
|
+
|
|
2246
2265
|
if (isGroupedReport) {
|
|
2247
2266
|
// Check if we're using a dynamic template (custom grouping) or predefined template
|
|
2248
2267
|
const isDynamicTemplate = !!template;
|
|
2249
|
-
|
|
2268
|
+
|
|
2250
2269
|
// Priority: custom URL > template URL > default URL
|
|
2251
|
-
const templateExecuteUrl =
|
|
2252
|
-
|
|
2270
|
+
const templateExecuteUrl =
|
|
2271
|
+
customUrls.executeUrl ||
|
|
2272
|
+
selectedTemplate?.executeUrl ||
|
|
2273
|
+
executeUrl;
|
|
2274
|
+
|
|
2253
2275
|
// Debug logging
|
|
2254
2276
|
console.log('🔍 [DEBUG] Grouped Report Detected:', {
|
|
2255
2277
|
selectedTemplate: selectedTemplate?.id,
|
|
@@ -2263,11 +2285,11 @@ const GenericReportImproved = ({
|
|
|
2263
2285
|
templateGroupingConfig: selectedTemplate?.grouping,
|
|
2264
2286
|
customGroupingConfig: groupingConfig,
|
|
2265
2287
|
usingCustomUrl: !!customUrls.executeUrl,
|
|
2266
|
-
usingTemplateExecuteUrl: !!selectedTemplate?.executeUrl
|
|
2288
|
+
usingTemplateExecuteUrl: !!selectedTemplate?.executeUrl,
|
|
2267
2289
|
});
|
|
2268
2290
|
|
|
2269
2291
|
let payload;
|
|
2270
|
-
|
|
2292
|
+
|
|
2271
2293
|
if (isDynamicTemplate) {
|
|
2272
2294
|
// For dynamic templates, send the complete template configuration
|
|
2273
2295
|
payload = {
|
|
@@ -2276,20 +2298,21 @@ const GenericReportImproved = ({
|
|
|
2276
2298
|
columns: selectedColumns,
|
|
2277
2299
|
joins: joins,
|
|
2278
2300
|
filters: flattenFilterCriteria(filterCriteria),
|
|
2279
|
-
sorting: sortCriteria
|
|
2301
|
+
sorting: sortCriteria,
|
|
2280
2302
|
};
|
|
2281
2303
|
} else {
|
|
2282
2304
|
// For predefined templates, use the existing approach
|
|
2283
2305
|
payload = {
|
|
2284
|
-
template_id:
|
|
2285
|
-
|
|
2306
|
+
template_id:
|
|
2307
|
+
selectedTemplate?.id || 'utilization_planning',
|
|
2308
|
+
filters: [],
|
|
2286
2309
|
};
|
|
2287
2310
|
}
|
|
2288
|
-
|
|
2311
|
+
|
|
2289
2312
|
console.log('🚀 [DEBUG] Sending grouped report request:', {
|
|
2290
2313
|
url: templateExecuteUrl,
|
|
2291
2314
|
method: 'POST',
|
|
2292
|
-
payload
|
|
2315
|
+
payload,
|
|
2293
2316
|
});
|
|
2294
2317
|
|
|
2295
2318
|
const result = await CustomFetch(
|
|
@@ -2301,7 +2324,10 @@ const GenericReportImproved = ({
|
|
|
2301
2324
|
console.log('📊 [DEBUG] Grouped report response:', result);
|
|
2302
2325
|
|
|
2303
2326
|
if (result.error) {
|
|
2304
|
-
console.error(
|
|
2327
|
+
console.error(
|
|
2328
|
+
'❌ Grouped report execution error:',
|
|
2329
|
+
result.error
|
|
2330
|
+
);
|
|
2305
2331
|
return { data: [], count: 0 };
|
|
2306
2332
|
}
|
|
2307
2333
|
|
|
@@ -2362,8 +2388,11 @@ const GenericReportImproved = ({
|
|
|
2362
2388
|
};
|
|
2363
2389
|
|
|
2364
2390
|
// Priority: custom URL > template URL > default URL
|
|
2365
|
-
const finalExecuteUrl =
|
|
2366
|
-
|
|
2391
|
+
const finalExecuteUrl =
|
|
2392
|
+
customUrls.executeUrl ||
|
|
2393
|
+
selectedTemplate?.executeUrl ||
|
|
2394
|
+
executeUrl;
|
|
2395
|
+
|
|
2367
2396
|
console.log('🔍 [DEBUG] Regular Report API Call:', {
|
|
2368
2397
|
selectedTemplate: selectedTemplate?.id,
|
|
2369
2398
|
customExecuteUrl: customUrls.executeUrl,
|
|
@@ -2371,7 +2400,7 @@ const GenericReportImproved = ({
|
|
|
2371
2400
|
defaultExecuteUrl: executeUrl,
|
|
2372
2401
|
finalExecuteUrl,
|
|
2373
2402
|
usingCustomUrl: !!customUrls.executeUrl,
|
|
2374
|
-
usingTemplateExecuteUrl: !!selectedTemplate?.executeUrl
|
|
2403
|
+
usingTemplateExecuteUrl: !!selectedTemplate?.executeUrl,
|
|
2375
2404
|
});
|
|
2376
2405
|
|
|
2377
2406
|
const result = await CustomFetch(
|
|
@@ -2430,7 +2459,7 @@ const GenericReportImproved = ({
|
|
|
2430
2459
|
selectedTemplate: selectedTemplate?.id,
|
|
2431
2460
|
hasGrouping: selectedTemplate?.grouping?.enabled,
|
|
2432
2461
|
executeUrl,
|
|
2433
|
-
setting
|
|
2462
|
+
setting,
|
|
2434
2463
|
});
|
|
2435
2464
|
|
|
2436
2465
|
if (!selectedTable || selectedColumns.length === 0) {
|
|
@@ -2449,22 +2478,25 @@ const GenericReportImproved = ({
|
|
|
2449
2478
|
const hasCustomGrouping = groupingConfig.enabled;
|
|
2450
2479
|
const hasGrouping = hasTemplateGrouping || hasCustomGrouping;
|
|
2451
2480
|
let fetchedDataCount = 0;
|
|
2452
|
-
|
|
2481
|
+
|
|
2453
2482
|
console.log('📋 [DEBUG] Template analysis:', {
|
|
2454
2483
|
selectedTemplate,
|
|
2455
2484
|
hasTemplateGrouping,
|
|
2456
2485
|
hasCustomGrouping,
|
|
2457
2486
|
hasGrouping,
|
|
2458
2487
|
customGroupingConfig: groupingConfig,
|
|
2459
|
-
templateGroupingConfig: selectedTemplate?.grouping
|
|
2488
|
+
templateGroupingConfig: selectedTemplate?.grouping,
|
|
2460
2489
|
});
|
|
2461
|
-
console.log(
|
|
2462
|
-
|
|
2490
|
+
console.log(
|
|
2491
|
+
'🎯 [DEBUG] Initial fetchedDataCount:',
|
|
2492
|
+
fetchedDataCount
|
|
2493
|
+
);
|
|
2494
|
+
|
|
2463
2495
|
if (hasGrouping) {
|
|
2464
2496
|
// Determine which grouping configuration to use
|
|
2465
2497
|
let effectiveGrouping;
|
|
2466
2498
|
let dynamicTemplate;
|
|
2467
|
-
|
|
2499
|
+
|
|
2468
2500
|
if (hasCustomGrouping && groupingConfig.groupByField) {
|
|
2469
2501
|
// Create dynamic template with custom grouping configuration
|
|
2470
2502
|
dynamicTemplate = {
|
|
@@ -2485,24 +2517,34 @@ const GenericReportImproved = ({
|
|
|
2485
2517
|
showGroupTotals: groupingConfig.showGroupTotals,
|
|
2486
2518
|
expandable: groupingConfig.expandable,
|
|
2487
2519
|
groupHeaderStyle: groupingConfig.groupHeaderStyle,
|
|
2488
|
-
emptyRowStyle: groupingConfig.emptyRowStyle
|
|
2489
|
-
}
|
|
2520
|
+
emptyRowStyle: groupingConfig.emptyRowStyle,
|
|
2521
|
+
},
|
|
2490
2522
|
};
|
|
2491
2523
|
effectiveGrouping = dynamicTemplate.grouping;
|
|
2492
|
-
|
|
2493
|
-
console.log(
|
|
2524
|
+
|
|
2525
|
+
console.log(
|
|
2526
|
+
'🏗️ [DEBUG] Created dynamic template for custom grouping:',
|
|
2527
|
+
dynamicTemplate
|
|
2528
|
+
);
|
|
2494
2529
|
} else if (hasCustomGrouping && !groupingConfig.groupByField) {
|
|
2495
2530
|
// Custom grouping enabled but no field selected - show error and fallback to regular report
|
|
2496
|
-
toast.error(
|
|
2497
|
-
|
|
2531
|
+
toast.error(
|
|
2532
|
+
'Please select a field to group by before executing the report.'
|
|
2533
|
+
);
|
|
2534
|
+
console.log(
|
|
2535
|
+
'⚠️ [DEBUG] Custom grouping enabled but no groupByField selected'
|
|
2536
|
+
);
|
|
2498
2537
|
setIsLoading(false);
|
|
2499
2538
|
return;
|
|
2500
2539
|
} else {
|
|
2501
2540
|
// Use existing template grouping
|
|
2502
2541
|
effectiveGrouping = selectedTemplate.grouping;
|
|
2503
|
-
console.log(
|
|
2542
|
+
console.log(
|
|
2543
|
+
'📋 [DEBUG] Using template grouping:',
|
|
2544
|
+
effectiveGrouping
|
|
2545
|
+
);
|
|
2504
2546
|
}
|
|
2505
|
-
|
|
2547
|
+
|
|
2506
2548
|
// For grouped reports, fetch data with reasonable limit
|
|
2507
2549
|
const groupedResult = await dataSource({
|
|
2508
2550
|
skip: 0,
|
|
@@ -2510,79 +2552,200 @@ const GenericReportImproved = ({
|
|
|
2510
2552
|
sortInfo: null,
|
|
2511
2553
|
filterValue: null,
|
|
2512
2554
|
grouping: effectiveGrouping,
|
|
2513
|
-
template: dynamicTemplate // Pass dynamic template if created
|
|
2555
|
+
template: dynamicTemplate, // Pass dynamic template if created
|
|
2514
2556
|
});
|
|
2515
|
-
|
|
2557
|
+
|
|
2516
2558
|
console.log('🎯 [DEBUG] Full API Response:', groupedResult);
|
|
2517
|
-
console.log(
|
|
2518
|
-
|
|
2519
|
-
|
|
2559
|
+
console.log(
|
|
2560
|
+
'🎯 [DEBUG] groupedResult.data:',
|
|
2561
|
+
groupedResult.data
|
|
2562
|
+
);
|
|
2563
|
+
console.log(
|
|
2564
|
+
'🎯 [DEBUG] groupedResult.data.grouped:',
|
|
2565
|
+
groupedResult.data?.grouped
|
|
2566
|
+
);
|
|
2567
|
+
console.log(
|
|
2568
|
+
'🎯 [DEBUG] groupedResult.data.groupedData:',
|
|
2569
|
+
groupedResult.data?.groupedData
|
|
2570
|
+
);
|
|
2520
2571
|
|
|
2521
|
-
if (
|
|
2572
|
+
if (
|
|
2573
|
+
groupedResult.data?.grouped &&
|
|
2574
|
+
groupedResult.data?.groupedData?.groups
|
|
2575
|
+
) {
|
|
2522
2576
|
console.log('✅ Processing grouped report');
|
|
2523
2577
|
setGroupedData(groupedResult.data); // Store entire response with grouped flag
|
|
2524
2578
|
setPreviewData([]); // Clear regular preview data
|
|
2525
2579
|
// Get the count from the fetched grouped data
|
|
2526
|
-
console.log(
|
|
2527
|
-
|
|
2528
|
-
|
|
2580
|
+
console.log(
|
|
2581
|
+
'🎯 [DEBUG] Before assignment - groupedResult.data.groupedData.totalRecords:',
|
|
2582
|
+
groupedResult.data.groupedData?.totalRecords
|
|
2583
|
+
);
|
|
2584
|
+
fetchedDataCount =
|
|
2585
|
+
groupedResult.data.groupedData?.totalRecords || 0;
|
|
2586
|
+
console.log(
|
|
2587
|
+
'🎯 [DEBUG] After assignment - fetchedDataCount:',
|
|
2588
|
+
fetchedDataCount
|
|
2589
|
+
);
|
|
2529
2590
|
console.log('🔢 [DEBUG] Grouped data count:', {
|
|
2530
|
-
totalRecords:
|
|
2591
|
+
totalRecords:
|
|
2592
|
+
groupedResult.data.groupedData?.totalRecords,
|
|
2531
2593
|
fetchedDataCount,
|
|
2532
|
-
hasGroups: !!groupedResult.data.groupedData?.groups
|
|
2594
|
+
hasGroups: !!groupedResult.data.groupedData?.groups,
|
|
2533
2595
|
});
|
|
2534
2596
|
} else {
|
|
2535
2597
|
// Fallback to regular display if server doesn't support grouping
|
|
2536
2598
|
console.log('⚠️ Fallback to regular display');
|
|
2537
2599
|
const fallbackData = groupedResult.data?.data || [];
|
|
2538
|
-
console.log(
|
|
2600
|
+
console.log(
|
|
2601
|
+
'🎯 [DEBUG] Fallback data length:',
|
|
2602
|
+
fallbackData.length
|
|
2603
|
+
);
|
|
2539
2604
|
setPreviewData(fallbackData);
|
|
2540
2605
|
fetchedDataCount = fallbackData.length;
|
|
2541
2606
|
}
|
|
2542
2607
|
} else {
|
|
2543
2608
|
// Regular non-grouped report
|
|
2544
|
-
const
|
|
2545
|
-
skip:
|
|
2609
|
+
const currentPageResult = await dataSource({
|
|
2610
|
+
skip: (currentPage - 1) * pageSize,
|
|
2546
2611
|
limit: pageSize,
|
|
2547
2612
|
sortInfo: null,
|
|
2548
2613
|
filterValue: null,
|
|
2549
2614
|
});
|
|
2550
2615
|
|
|
2551
|
-
if (
|
|
2552
|
-
|
|
2553
|
-
|
|
2616
|
+
if (
|
|
2617
|
+
currentPageResult.data &&
|
|
2618
|
+
currentPageResult.data.length > 0
|
|
2619
|
+
) {
|
|
2620
|
+
setPreviewData(currentPageResult.data);
|
|
2621
|
+
fetchedDataCount = currentPageResult.data.length;
|
|
2554
2622
|
}
|
|
2555
2623
|
}
|
|
2556
2624
|
|
|
2557
2625
|
// Create grid columns for regular reports only
|
|
2558
2626
|
if (!hasGrouping) {
|
|
2559
2627
|
const dataToProcess = previewData.length > 0 ? previewData : [];
|
|
2560
|
-
|
|
2628
|
+
|
|
2561
2629
|
if (dataToProcess.length > 0) {
|
|
2562
2630
|
// Create grid columns dynamically from actual data with smart formatting
|
|
2563
2631
|
const firstRow = dataToProcess[0];
|
|
2564
2632
|
const dynamicColumns = Object.keys(firstRow).map((key) => {
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2633
|
+
// Find the column metadata from selected columns to get type information
|
|
2634
|
+
const columnMetadata = selectedColumns.find((col) => {
|
|
2635
|
+
// Handle both direct column names and aliased names
|
|
2636
|
+
return (
|
|
2637
|
+
col.column === key ||
|
|
2638
|
+
col.displayName === key ||
|
|
2639
|
+
key.includes(col.column) ||
|
|
2640
|
+
col.column.includes(key)
|
|
2641
|
+
);
|
|
2642
|
+
});
|
|
2575
2643
|
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2644
|
+
// Check if any value in this column is JSON
|
|
2645
|
+
const hasJsonValues = dataToProcess.some((row) => {
|
|
2646
|
+
return isJsonValue(row[key]);
|
|
2647
|
+
});
|
|
2648
|
+
|
|
2649
|
+
// Try to find column type from table columns with improved detection
|
|
2650
|
+
let columnType = 'varchar'; // default
|
|
2651
|
+
if (columnMetadata) {
|
|
2652
|
+
const tableColumn = tableColumns.find(
|
|
2653
|
+
(tc) => tc.name === columnMetadata.column
|
|
2654
|
+
);
|
|
2655
|
+
if (tableColumn) {
|
|
2656
|
+
columnType = tableColumn.type;
|
|
2657
|
+
} else {
|
|
2658
|
+
// Check in joined table columns
|
|
2659
|
+
joins.forEach((join) => {
|
|
2660
|
+
if (join.availableColumns) {
|
|
2661
|
+
const joinedColumn =
|
|
2662
|
+
join.availableColumns.find(
|
|
2663
|
+
(jc) =>
|
|
2664
|
+
jc.name ===
|
|
2665
|
+
columnMetadata.column
|
|
2666
|
+
);
|
|
2667
|
+
if (joinedColumn) {
|
|
2668
|
+
columnType = joinedColumn.type;
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2580
2674
|
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2675
|
+
// Enhanced column type detection for status fields
|
|
2676
|
+
if (!columnMetadata || columnType === 'varchar') {
|
|
2677
|
+
// Check if this looks like a status field based on column name
|
|
2678
|
+
const lowerKey = key.toLowerCase();
|
|
2679
|
+
if (
|
|
2680
|
+
lowerKey.includes('status') ||
|
|
2681
|
+
lowerKey.includes('active') ||
|
|
2682
|
+
lowerKey.includes('enabled') ||
|
|
2683
|
+
lowerKey.includes('disabled')
|
|
2684
|
+
) {
|
|
2685
|
+
// Check if values look like boolean/status values
|
|
2686
|
+
const sampleValues = firstPageResult.data
|
|
2687
|
+
.slice(0, 5)
|
|
2688
|
+
.map((row) => row[key]);
|
|
2689
|
+
const hasStatusValues = sampleValues.some(
|
|
2690
|
+
(val) =>
|
|
2691
|
+
val === 0 ||
|
|
2692
|
+
val === 1 ||
|
|
2693
|
+
val === '0' ||
|
|
2694
|
+
val === '1' ||
|
|
2695
|
+
val === true ||
|
|
2696
|
+
val === false ||
|
|
2697
|
+
val === 'true' ||
|
|
2698
|
+
val === 'false'
|
|
2699
|
+
);
|
|
2700
|
+
if (hasStatusValues) {
|
|
2701
|
+
columnType = 'tinyint'; // This will trigger status formatting
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
return {
|
|
2707
|
+
name: key,
|
|
2708
|
+
header: formatName(key), // Use formatted name as header
|
|
2709
|
+
defaultFlex: 1,
|
|
2710
|
+
minWidth: hasJsonValues ? 250 : 100, // Wider columns for JSON data
|
|
2711
|
+
maxWidth: hasJsonValues ? 400 : undefined, // Max width for JSON columns
|
|
2712
|
+
render: ({ value }) => {
|
|
2713
|
+
// Use smart formatting for the cell value
|
|
2714
|
+
const formattedValue = formatSmartValue(
|
|
2715
|
+
value,
|
|
2716
|
+
key,
|
|
2717
|
+
columnType
|
|
2718
|
+
);
|
|
2719
|
+
|
|
2720
|
+
// For JSON content, wrap in a container that allows full height expansion
|
|
2721
|
+
if (isJsonValue(value)) {
|
|
2722
|
+
return (
|
|
2723
|
+
<div
|
|
2724
|
+
style={{
|
|
2725
|
+
whiteSpace: 'normal',
|
|
2726
|
+
wordWrap: 'break-word',
|
|
2727
|
+
lineHeight: '1.4',
|
|
2728
|
+
padding: '4px 0',
|
|
2729
|
+
width: '100%',
|
|
2730
|
+
}}
|
|
2731
|
+
>
|
|
2732
|
+
{formattedValue}
|
|
2733
|
+
</div>
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
return formattedValue;
|
|
2738
|
+
},
|
|
2739
|
+
};
|
|
2740
|
+
});
|
|
2741
|
+
setGridColumns(dynamicColumns);
|
|
2742
|
+
} else {
|
|
2743
|
+
// Fallback to selected columns if no data
|
|
2744
|
+
const columns = selectedColumns.map((col) => {
|
|
2745
|
+
// Find column type from table columns with enhanced detection
|
|
2746
|
+
let columnType = 'varchar';
|
|
2584
2747
|
const tableColumn = tableColumns.find(
|
|
2585
|
-
(tc) => tc.name ===
|
|
2748
|
+
(tc) => tc.name === col.column
|
|
2586
2749
|
);
|
|
2587
2750
|
if (tableColumn) {
|
|
2588
2751
|
columnType = tableColumn.type;
|
|
@@ -2592,9 +2755,7 @@ const GenericReportImproved = ({
|
|
|
2592
2755
|
if (join.availableColumns) {
|
|
2593
2756
|
const joinedColumn =
|
|
2594
2757
|
join.availableColumns.find(
|
|
2595
|
-
(jc) =>
|
|
2596
|
-
jc.name ===
|
|
2597
|
-
columnMetadata.column
|
|
2758
|
+
(jc) => jc.name === col.column
|
|
2598
2759
|
);
|
|
2599
2760
|
if (joinedColumn) {
|
|
2600
2761
|
columnType = joinedColumn.type;
|
|
@@ -2602,144 +2763,53 @@ const GenericReportImproved = ({
|
|
|
2602
2763
|
}
|
|
2603
2764
|
});
|
|
2604
2765
|
}
|
|
2605
|
-
}
|
|
2606
2766
|
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
) {
|
|
2617
|
-
// Check if values look like boolean/status values
|
|
2618
|
-
const sampleValues = firstPageResult.data
|
|
2619
|
-
.slice(0, 5)
|
|
2620
|
-
.map((row) => row[key]);
|
|
2621
|
-
const hasStatusValues = sampleValues.some(
|
|
2622
|
-
(val) =>
|
|
2623
|
-
val === 0 ||
|
|
2624
|
-
val === 1 ||
|
|
2625
|
-
val === '0' ||
|
|
2626
|
-
val === '1' ||
|
|
2627
|
-
val === true ||
|
|
2628
|
-
val === false ||
|
|
2629
|
-
val === 'true' ||
|
|
2630
|
-
val === 'false'
|
|
2631
|
-
);
|
|
2632
|
-
if (hasStatusValues) {
|
|
2767
|
+
// Enhanced column type detection for status fields
|
|
2768
|
+
if (columnType === 'varchar') {
|
|
2769
|
+
const lowerColumn = col.column.toLowerCase();
|
|
2770
|
+
if (
|
|
2771
|
+
lowerColumn.includes('status') ||
|
|
2772
|
+
lowerColumn.includes('active') ||
|
|
2773
|
+
lowerColumn.includes('enabled') ||
|
|
2774
|
+
lowerColumn.includes('disabled')
|
|
2775
|
+
) {
|
|
2633
2776
|
columnType = 'tinyint'; // This will trigger status formatting
|
|
2634
2777
|
}
|
|
2635
2778
|
}
|
|
2636
|
-
}
|
|
2637
|
-
|
|
2638
|
-
return {
|
|
2639
|
-
name: key,
|
|
2640
|
-
header: formatName(key), // Use formatted name as header
|
|
2641
|
-
defaultFlex: 1,
|
|
2642
|
-
minWidth: hasJsonValues ? 250 : 100, // Wider columns for JSON data
|
|
2643
|
-
maxWidth: hasJsonValues ? 400 : undefined, // Max width for JSON columns
|
|
2644
|
-
render: ({ value }) => {
|
|
2645
|
-
// Use smart formatting for the cell value
|
|
2646
|
-
const formattedValue = formatSmartValue(
|
|
2647
|
-
value,
|
|
2648
|
-
key,
|
|
2649
|
-
columnType
|
|
2650
|
-
);
|
|
2651
2779
|
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
>
|
|
2664
|
-
{formattedValue}
|
|
2665
|
-
</div>
|
|
2780
|
+
return {
|
|
2781
|
+
name:
|
|
2782
|
+
col.displayName || `${col.table}.${col.column}`,
|
|
2783
|
+
header: col.displayName || formatName(col.column),
|
|
2784
|
+
defaultFlex: 1,
|
|
2785
|
+
minWidth: 100,
|
|
2786
|
+
render: ({ value }) => {
|
|
2787
|
+
const formattedValue = formatSmartValue(
|
|
2788
|
+
value,
|
|
2789
|
+
col.column,
|
|
2790
|
+
columnType
|
|
2666
2791
|
);
|
|
2667
|
-
}
|
|
2668
2792
|
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
} else {
|
|
2685
|
-
// Check in joined table columns
|
|
2686
|
-
joins.forEach((join) => {
|
|
2687
|
-
if (join.availableColumns) {
|
|
2688
|
-
const joinedColumn = join.availableColumns.find(
|
|
2689
|
-
(jc) => jc.name === col.column
|
|
2690
|
-
);
|
|
2691
|
-
if (joinedColumn) {
|
|
2692
|
-
columnType = joinedColumn.type;
|
|
2793
|
+
// For JSON content, wrap in a container that allows full height expansion
|
|
2794
|
+
if (isJsonValue(value)) {
|
|
2795
|
+
return (
|
|
2796
|
+
<div
|
|
2797
|
+
style={{
|
|
2798
|
+
whiteSpace: 'normal',
|
|
2799
|
+
wordWrap: 'break-word',
|
|
2800
|
+
lineHeight: '1.4',
|
|
2801
|
+
padding: '4px 0',
|
|
2802
|
+
width: '100%',
|
|
2803
|
+
}}
|
|
2804
|
+
>
|
|
2805
|
+
{formattedValue}
|
|
2806
|
+
</div>
|
|
2807
|
+
);
|
|
2693
2808
|
}
|
|
2694
|
-
}
|
|
2695
|
-
});
|
|
2696
|
-
}
|
|
2697
|
-
|
|
2698
|
-
// Enhanced column type detection for status fields
|
|
2699
|
-
if (columnType === 'varchar') {
|
|
2700
|
-
const lowerColumn = col.column.toLowerCase();
|
|
2701
|
-
if (
|
|
2702
|
-
lowerColumn.includes('status') ||
|
|
2703
|
-
lowerColumn.includes('active') ||
|
|
2704
|
-
lowerColumn.includes('enabled') ||
|
|
2705
|
-
lowerColumn.includes('disabled')
|
|
2706
|
-
) {
|
|
2707
|
-
columnType = 'tinyint'; // This will trigger status formatting
|
|
2708
|
-
}
|
|
2709
|
-
}
|
|
2710
|
-
|
|
2711
|
-
return {
|
|
2712
|
-
name: col.displayName || `${col.table}.${col.column}`,
|
|
2713
|
-
header: col.displayName || formatName(col.column),
|
|
2714
|
-
defaultFlex: 1,
|
|
2715
|
-
minWidth: 100,
|
|
2716
|
-
render: ({ value }) => {
|
|
2717
|
-
const formattedValue = formatSmartValue(
|
|
2718
|
-
value,
|
|
2719
|
-
col.column,
|
|
2720
|
-
columnType
|
|
2721
|
-
);
|
|
2722
2809
|
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
<div
|
|
2727
|
-
style={{
|
|
2728
|
-
whiteSpace: 'normal',
|
|
2729
|
-
wordWrap: 'break-word',
|
|
2730
|
-
lineHeight: '1.4',
|
|
2731
|
-
padding: '4px 0',
|
|
2732
|
-
width: '100%',
|
|
2733
|
-
}}
|
|
2734
|
-
>
|
|
2735
|
-
{formattedValue}
|
|
2736
|
-
</div>
|
|
2737
|
-
);
|
|
2738
|
-
}
|
|
2739
|
-
|
|
2740
|
-
return formattedValue;
|
|
2741
|
-
},
|
|
2742
|
-
};
|
|
2810
|
+
return formattedValue;
|
|
2811
|
+
},
|
|
2812
|
+
};
|
|
2743
2813
|
});
|
|
2744
2814
|
setGridColumns(columns);
|
|
2745
2815
|
}
|
|
@@ -2749,10 +2819,12 @@ const GenericReportImproved = ({
|
|
|
2749
2819
|
setCurrentWizardStep(wizardSteps.length - 1);
|
|
2750
2820
|
}
|
|
2751
2821
|
|
|
2752
|
-
//
|
|
2753
|
-
console.log('🎯 [DEBUG] Final fetchedDataCount
|
|
2754
|
-
console.log(
|
|
2755
|
-
|
|
2822
|
+
// Report executed successfully
|
|
2823
|
+
console.log('🎯 [DEBUG] Final fetchedDataCount:', fetchedDataCount);
|
|
2824
|
+
console.log(
|
|
2825
|
+
'🎯 [DEBUG] Type of fetchedDataCount:',
|
|
2826
|
+
typeof fetchedDataCount
|
|
2827
|
+
);
|
|
2756
2828
|
} catch (error) {
|
|
2757
2829
|
toast.error('Failed to execute report');
|
|
2758
2830
|
console.error('Error executing report:', error);
|
|
@@ -2853,8 +2925,9 @@ const GenericReportImproved = ({
|
|
|
2853
2925
|
showEmptyRows: config.grouping.showEmptyRows || false,
|
|
2854
2926
|
showGroupTotals: config.grouping.showGroupTotals || true,
|
|
2855
2927
|
expandable: config.grouping.expandable || true,
|
|
2856
|
-
groupHeaderStyle:
|
|
2857
|
-
|
|
2928
|
+
groupHeaderStyle:
|
|
2929
|
+
config.grouping.groupHeaderStyle || 'primary',
|
|
2930
|
+
emptyRowStyle: config.grouping.emptyRowStyle || 'light',
|
|
2858
2931
|
});
|
|
2859
2932
|
} else {
|
|
2860
2933
|
// Reset grouping config if not present in saved report
|
|
@@ -2869,7 +2942,7 @@ const GenericReportImproved = ({
|
|
|
2869
2942
|
showGroupTotals: true,
|
|
2870
2943
|
expandable: true,
|
|
2871
2944
|
groupHeaderStyle: 'primary',
|
|
2872
|
-
emptyRowStyle: 'light'
|
|
2945
|
+
emptyRowStyle: 'light',
|
|
2873
2946
|
});
|
|
2874
2947
|
}
|
|
2875
2948
|
|
|
@@ -2904,9 +2977,9 @@ const GenericReportImproved = ({
|
|
|
2904
2977
|
templateId: template.id,
|
|
2905
2978
|
templateName: template.name,
|
|
2906
2979
|
hasGrouping: template.grouping?.enabled,
|
|
2907
|
-
groupingConfig: template.grouping
|
|
2980
|
+
groupingConfig: template.grouping,
|
|
2908
2981
|
});
|
|
2909
|
-
|
|
2982
|
+
|
|
2910
2983
|
setSelectedTemplate(template);
|
|
2911
2984
|
|
|
2912
2985
|
// Set main table
|
|
@@ -2966,32 +3039,57 @@ const GenericReportImproved = ({
|
|
|
2966
3039
|
setSelectedColumns(formattedColumns);
|
|
2967
3040
|
}
|
|
2968
3041
|
|
|
2969
|
-
// Apply quick filters as default
|
|
2970
|
-
if (template.quickFilters) {
|
|
2971
|
-
const
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
value: filter.value,
|
|
2981
|
-
label: filter.label,
|
|
2982
|
-
}));
|
|
3042
|
+
// Apply template quick filters as default filters
|
|
3043
|
+
if (template.quickFilters && template.quickFilters.length > 0) {
|
|
3044
|
+
const updatedFilterCriteria = {
|
|
3045
|
+
operator: 'AND',
|
|
3046
|
+
groups: [
|
|
3047
|
+
{
|
|
3048
|
+
operator: 'AND',
|
|
3049
|
+
filters: [],
|
|
3050
|
+
},
|
|
3051
|
+
],
|
|
3052
|
+
};
|
|
2983
3053
|
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
3054
|
+
template.quickFilters.forEach((quickFilter) => {
|
|
3055
|
+
let filterValue = convertDynamicDateValue(
|
|
3056
|
+
quickFilter.value
|
|
3057
|
+
);
|
|
3058
|
+
|
|
3059
|
+
// Parse the field to extract table and column
|
|
3060
|
+
const fieldParts = quickFilter.field.split('.');
|
|
3061
|
+
const table = fieldParts[0];
|
|
3062
|
+
const column = fieldParts.slice(1).join('.'); // Handle cases like "leads.user.name"
|
|
3063
|
+
|
|
3064
|
+
// Convert date range objects to comma-separated string for BETWEEN operator
|
|
3065
|
+
if (
|
|
3066
|
+
quickFilter.type === 'date_range' &&
|
|
3067
|
+
typeof filterValue === 'object' &&
|
|
3068
|
+
filterValue.start &&
|
|
3069
|
+
filterValue.end
|
|
3070
|
+
) {
|
|
3071
|
+
filterValue = `${filterValue.start},${filterValue.end}`;
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
const newFilter = {
|
|
3075
|
+
id: `template_${quickFilter.id}_${Date.now()}`,
|
|
3076
|
+
field: quickFilter.field,
|
|
3077
|
+
table: table,
|
|
3078
|
+
column: column,
|
|
3079
|
+
operator:
|
|
3080
|
+
quickFilter.operator ||
|
|
3081
|
+
(quickFilter.type === 'date_range'
|
|
3082
|
+
? 'BETWEEN'
|
|
3083
|
+
: '='),
|
|
3084
|
+
value: filterValue,
|
|
3085
|
+
label: quickFilter.label,
|
|
3086
|
+
type: quickFilter.type,
|
|
3087
|
+
};
|
|
3088
|
+
|
|
3089
|
+
updatedFilterCriteria.groups[0].filters.push(newFilter);
|
|
3090
|
+
});
|
|
3091
|
+
|
|
3092
|
+
setFilterCriteria(updatedFilterCriteria);
|
|
2995
3093
|
}
|
|
2996
3094
|
|
|
2997
3095
|
// Apply template grouping configuration
|
|
@@ -3006,14 +3104,15 @@ const GenericReportImproved = ({
|
|
|
3006
3104
|
showEmptyRows: template.grouping.showEmptyRows || false,
|
|
3007
3105
|
showGroupTotals: template.grouping.showGroupTotals || true,
|
|
3008
3106
|
expandable: template.grouping.expandable !== false, // Default true
|
|
3009
|
-
groupHeaderStyle:
|
|
3010
|
-
|
|
3107
|
+
groupHeaderStyle:
|
|
3108
|
+
template.grouping.groupHeaderStyle || 'primary',
|
|
3109
|
+
emptyRowStyle: template.grouping.emptyRowStyle || 'light',
|
|
3011
3110
|
});
|
|
3012
|
-
|
|
3111
|
+
|
|
3013
3112
|
console.log('🏗️ [DEBUG] Applied template grouping config:', {
|
|
3014
3113
|
enabled: template.grouping.enabled,
|
|
3015
3114
|
groupByField: template.grouping.groupByField,
|
|
3016
|
-
groupDisplayName: template.grouping.groupDisplayName
|
|
3115
|
+
groupDisplayName: template.grouping.groupDisplayName,
|
|
3017
3116
|
});
|
|
3018
3117
|
} else {
|
|
3019
3118
|
// Reset grouping config if template doesn't have grouping
|
|
@@ -3028,7 +3127,7 @@ const GenericReportImproved = ({
|
|
|
3028
3127
|
showGroupTotals: true,
|
|
3029
3128
|
expandable: true,
|
|
3030
3129
|
groupHeaderStyle: 'primary',
|
|
3031
|
-
emptyRowStyle: 'light'
|
|
3130
|
+
emptyRowStyle: 'light',
|
|
3032
3131
|
});
|
|
3033
3132
|
}
|
|
3034
3133
|
|
|
@@ -4641,32 +4740,26 @@ const GenericReportImproved = ({
|
|
|
4641
4740
|
handleRemoveFilterCriterion(groupIndex, filterIndex);
|
|
4642
4741
|
};
|
|
4643
4742
|
|
|
4644
|
-
//
|
|
4645
|
-
const
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
// Handle dynamic date ranges
|
|
4649
|
-
switch (quickFilter.value) {
|
|
4743
|
+
// Helper function to convert dynamic date values to actual date ranges
|
|
4744
|
+
const convertDynamicDateValue = (value) => {
|
|
4745
|
+
switch (value) {
|
|
4650
4746
|
case 'current_week':
|
|
4651
|
-
|
|
4747
|
+
return {
|
|
4652
4748
|
start: moment().startOf('week').format('YYYY-MM-DD'),
|
|
4653
4749
|
end: moment().endOf('week').format('YYYY-MM-DD'),
|
|
4654
4750
|
};
|
|
4655
|
-
break;
|
|
4656
4751
|
case 'next_30_days':
|
|
4657
|
-
|
|
4752
|
+
return {
|
|
4658
4753
|
start: moment().format('YYYY-MM-DD'),
|
|
4659
4754
|
end: moment().add(30, 'days').format('YYYY-MM-DD'),
|
|
4660
4755
|
};
|
|
4661
|
-
break;
|
|
4662
4756
|
case 'last_30_days':
|
|
4663
|
-
|
|
4757
|
+
return {
|
|
4664
4758
|
start: moment().subtract(30, 'days').format('YYYY-MM-DD'),
|
|
4665
4759
|
end: moment().format('YYYY-MM-DD'),
|
|
4666
4760
|
};
|
|
4667
|
-
break;
|
|
4668
4761
|
case 'last_quarter':
|
|
4669
|
-
|
|
4762
|
+
return {
|
|
4670
4763
|
start: moment()
|
|
4671
4764
|
.subtract(1, 'quarter')
|
|
4672
4765
|
.startOf('quarter')
|
|
@@ -4676,9 +4769,8 @@ const GenericReportImproved = ({
|
|
|
4676
4769
|
.endOf('quarter')
|
|
4677
4770
|
.format('YYYY-MM-DD'),
|
|
4678
4771
|
};
|
|
4679
|
-
break;
|
|
4680
4772
|
case 'last_year':
|
|
4681
|
-
|
|
4773
|
+
return {
|
|
4682
4774
|
start: moment()
|
|
4683
4775
|
.subtract(1, 'year')
|
|
4684
4776
|
.startOf('year')
|
|
@@ -4688,12 +4780,49 @@ const GenericReportImproved = ({
|
|
|
4688
4780
|
.endOf('year')
|
|
4689
4781
|
.format('YYYY-MM-DD'),
|
|
4690
4782
|
};
|
|
4691
|
-
|
|
4783
|
+
case 'today':
|
|
4784
|
+
return moment().format('YYYY-MM-DD');
|
|
4785
|
+
case 'yesterday':
|
|
4786
|
+
return moment().subtract(1, 'day').format('YYYY-MM-DD');
|
|
4787
|
+
case 'current_month':
|
|
4788
|
+
return {
|
|
4789
|
+
start: moment().startOf('month').format('YYYY-MM-DD'),
|
|
4790
|
+
end: moment().endOf('month').format('YYYY-MM-DD'),
|
|
4791
|
+
};
|
|
4792
|
+
case 'current_year':
|
|
4793
|
+
return {
|
|
4794
|
+
start: moment().startOf('year').format('YYYY-MM-DD'),
|
|
4795
|
+
end: moment().endOf('year').format('YYYY-MM-DD'),
|
|
4796
|
+
};
|
|
4797
|
+
default:
|
|
4798
|
+
return value; // Return original value if no conversion needed
|
|
4799
|
+
}
|
|
4800
|
+
};
|
|
4801
|
+
|
|
4802
|
+
// Apply template-based quick filter
|
|
4803
|
+
const applyTemplateQuickFilter = (quickFilter) => {
|
|
4804
|
+
let filterValue = convertDynamicDateValue(quickFilter.value);
|
|
4805
|
+
|
|
4806
|
+
// Parse the field to extract table and column
|
|
4807
|
+
const fieldParts = quickFilter.field.split('.');
|
|
4808
|
+
const table = fieldParts[0];
|
|
4809
|
+
const column = fieldParts.slice(1).join('.'); // Handle cases like "leads.user.name"
|
|
4810
|
+
|
|
4811
|
+
// Convert date range objects to comma-separated string for BETWEEN operator
|
|
4812
|
+
if (
|
|
4813
|
+
quickFilter.type === 'date_range' &&
|
|
4814
|
+
typeof filterValue === 'object' &&
|
|
4815
|
+
filterValue.start &&
|
|
4816
|
+
filterValue.end
|
|
4817
|
+
) {
|
|
4818
|
+
filterValue = `${filterValue.start},${filterValue.end}`;
|
|
4692
4819
|
}
|
|
4693
4820
|
|
|
4694
4821
|
const newFilter = {
|
|
4695
4822
|
id: `template_${quickFilter.id}_${Date.now()}`,
|
|
4696
4823
|
field: quickFilter.field,
|
|
4824
|
+
table: table,
|
|
4825
|
+
column: column,
|
|
4697
4826
|
operator:
|
|
4698
4827
|
quickFilter.operator ||
|
|
4699
4828
|
(quickFilter.type === 'date_range' ? 'BETWEEN' : '='),
|
|
@@ -6412,8 +6541,11 @@ const GenericReportImproved = ({
|
|
|
6412
6541
|
console.log('📁 Generated filename:', fileName);
|
|
6413
6542
|
|
|
6414
6543
|
// Priority: custom export URL > template export URL > default export URL
|
|
6415
|
-
const finalExportUrl =
|
|
6416
|
-
|
|
6544
|
+
const finalExportUrl =
|
|
6545
|
+
customUrls.exportUrl ||
|
|
6546
|
+
selectedTemplate?.exportUrl ||
|
|
6547
|
+
exportUrl;
|
|
6548
|
+
|
|
6417
6549
|
console.log('🔍 [DEBUG] Export API Call:', {
|
|
6418
6550
|
selectedTemplate: selectedTemplate?.id,
|
|
6419
6551
|
customExportUrl: customUrls.exportUrl,
|
|
@@ -6421,7 +6553,7 @@ const GenericReportImproved = ({
|
|
|
6421
6553
|
defaultExportUrl: exportUrl,
|
|
6422
6554
|
finalExportUrl,
|
|
6423
6555
|
usingCustomExportUrl: !!customUrls.exportUrl,
|
|
6424
|
-
usingTemplateExportUrl: !!selectedTemplate?.exportUrl
|
|
6556
|
+
usingTemplateExportUrl: !!selectedTemplate?.exportUrl,
|
|
6425
6557
|
});
|
|
6426
6558
|
|
|
6427
6559
|
// Use Download component which handles error checking and blob download properly
|
|
@@ -6558,9 +6690,12 @@ const GenericReportImproved = ({
|
|
|
6558
6690
|
groupedData: groupedData,
|
|
6559
6691
|
hasGroupedData: !!groupedData,
|
|
6560
6692
|
hasGroups: !!(groupedData && groupedData.groups),
|
|
6561
|
-
groupsLength:
|
|
6693
|
+
groupsLength:
|
|
6694
|
+
groupedData && groupedData.groups
|
|
6695
|
+
? groupedData.groups.length
|
|
6696
|
+
: 'undefined',
|
|
6562
6697
|
selectedColumns: selectedColumns,
|
|
6563
|
-
selectedTemplate: selectedTemplate?.name
|
|
6698
|
+
selectedTemplate: selectedTemplate?.name,
|
|
6564
6699
|
});
|
|
6565
6700
|
|
|
6566
6701
|
// Check the correct path: groupedData.groupedData.groups
|
|
@@ -6569,9 +6704,15 @@ const GenericReportImproved = ({
|
|
|
6569
6704
|
console.error('❌ [DEBUG] Export failed - no grouped data:', {
|
|
6570
6705
|
groupedData: groupedData,
|
|
6571
6706
|
hasGroupedData: !!groupedData,
|
|
6572
|
-
hasGroupedDataProperty: !!(
|
|
6573
|
-
|
|
6574
|
-
|
|
6707
|
+
hasGroupedDataProperty: !!(
|
|
6708
|
+
groupedData && groupedData.groupedData
|
|
6709
|
+
),
|
|
6710
|
+
hasGroups: !!(
|
|
6711
|
+
groupedData &&
|
|
6712
|
+
groupedData.groupedData &&
|
|
6713
|
+
groupedData.groupedData.groups
|
|
6714
|
+
),
|
|
6715
|
+
groupsLength: actualGroups ? actualGroups.length : 'undefined',
|
|
6575
6716
|
});
|
|
6576
6717
|
toast.error('No grouped data available for export');
|
|
6577
6718
|
return;
|
|
@@ -6585,7 +6726,9 @@ const GenericReportImproved = ({
|
|
|
6585
6726
|
const worksheetData = [];
|
|
6586
6727
|
|
|
6587
6728
|
// Add headers
|
|
6588
|
-
const headers = selectedColumns.map(
|
|
6729
|
+
const headers = selectedColumns.map(
|
|
6730
|
+
(col) => col.displayName || col.column
|
|
6731
|
+
);
|
|
6589
6732
|
worksheetData.push(headers);
|
|
6590
6733
|
|
|
6591
6734
|
// Helper function to get cell value (same logic as SectionGroupedReport)
|
|
@@ -6595,13 +6738,25 @@ const GenericReportImproved = ({
|
|
|
6595
6738
|
let value;
|
|
6596
6739
|
|
|
6597
6740
|
// Try multiple field access patterns
|
|
6598
|
-
if (
|
|
6741
|
+
if (
|
|
6742
|
+
column.displayName &&
|
|
6743
|
+
row[`\`${column.displayName}\``] !== undefined
|
|
6744
|
+
) {
|
|
6599
6745
|
value = row[`\`${column.displayName}\``];
|
|
6600
|
-
} else if (
|
|
6746
|
+
} else if (
|
|
6747
|
+
column.displayName &&
|
|
6748
|
+
row[column.displayName] !== undefined
|
|
6749
|
+
) {
|
|
6601
6750
|
value = row[column.displayName];
|
|
6602
|
-
} else if (
|
|
6751
|
+
} else if (
|
|
6752
|
+
column.table &&
|
|
6753
|
+
row[`${column.table}_${column.column}`] !== undefined
|
|
6754
|
+
) {
|
|
6603
6755
|
value = row[`${column.table}_${column.column}`];
|
|
6604
|
-
} else if (
|
|
6756
|
+
} else if (
|
|
6757
|
+
column.table &&
|
|
6758
|
+
row[`\`${column.table}_${column.column}\``] !== undefined
|
|
6759
|
+
) {
|
|
6605
6760
|
value = row[`\`${column.table}_${column.column}\``];
|
|
6606
6761
|
} else if (row[column.column] !== undefined) {
|
|
6607
6762
|
value = row[column.column];
|
|
@@ -6613,11 +6768,11 @@ const GenericReportImproved = ({
|
|
|
6613
6768
|
|
|
6614
6769
|
// Format the value
|
|
6615
6770
|
if (value === null || value === undefined) return '';
|
|
6616
|
-
|
|
6771
|
+
|
|
6617
6772
|
// Handle dates
|
|
6618
6773
|
if (column.type === 'date' || column.type === 'datetime') {
|
|
6619
6774
|
if (value && moment(value).isValid()) {
|
|
6620
|
-
return column.type === 'date'
|
|
6775
|
+
return column.type === 'date'
|
|
6621
6776
|
? moment(value).format('DD/MM/YYYY')
|
|
6622
6777
|
: moment(value).format('DD/MM/YYYY HH:mm:ss');
|
|
6623
6778
|
}
|
|
@@ -6625,24 +6780,41 @@ const GenericReportImproved = ({
|
|
|
6625
6780
|
|
|
6626
6781
|
// Handle Comments field - strip HTML and formatting
|
|
6627
6782
|
if (column.displayName === 'Comments' && value) {
|
|
6628
|
-
return value
|
|
6783
|
+
return value
|
|
6784
|
+
.replace(/• /g, '')
|
|
6785
|
+
.replace(/<[^>]*>/g, '')
|
|
6786
|
+
.trim();
|
|
6629
6787
|
}
|
|
6630
6788
|
|
|
6631
6789
|
// Handle ABN (Australian Business Number) columns for export
|
|
6632
|
-
if (
|
|
6633
|
-
(column.
|
|
6790
|
+
if (
|
|
6791
|
+
(column.displayName &&
|
|
6792
|
+
column.displayName.toLowerCase().includes('abn')) ||
|
|
6793
|
+
(column.column &&
|
|
6794
|
+
column.column.toLowerCase().includes('abn'))
|
|
6795
|
+
) {
|
|
6634
6796
|
const cleanABN = String(value).replace(/\s/g, '');
|
|
6635
6797
|
if (cleanABN.length === 11 && /^\d{11}$/.test(cleanABN)) {
|
|
6636
|
-
return `${cleanABN.slice(0,
|
|
6798
|
+
return `${cleanABN.slice(0, 2)} ${cleanABN.slice(
|
|
6799
|
+
2,
|
|
6800
|
+
5
|
|
6801
|
+
)} ${cleanABN.slice(5, 8)} ${cleanABN.slice(8, 11)}`;
|
|
6637
6802
|
}
|
|
6638
6803
|
}
|
|
6639
6804
|
|
|
6640
6805
|
// Handle ACN (Australian Company Number) columns for export
|
|
6641
|
-
if (
|
|
6642
|
-
(column.
|
|
6806
|
+
if (
|
|
6807
|
+
(column.displayName &&
|
|
6808
|
+
column.displayName.toLowerCase().includes('acn')) ||
|
|
6809
|
+
(column.column &&
|
|
6810
|
+
column.column.toLowerCase().includes('acn'))
|
|
6811
|
+
) {
|
|
6643
6812
|
const cleanACN = String(value).replace(/\s/g, '');
|
|
6644
6813
|
if (cleanACN.length === 9 && /^\d{9}$/.test(cleanACN)) {
|
|
6645
|
-
return `${cleanACN.slice(0,
|
|
6814
|
+
return `${cleanACN.slice(0, 3)} ${cleanACN.slice(
|
|
6815
|
+
3,
|
|
6816
|
+
6
|
|
6817
|
+
)} ${cleanACN.slice(6, 9)}`;
|
|
6646
6818
|
}
|
|
6647
6819
|
}
|
|
6648
6820
|
|
|
@@ -6652,15 +6824,18 @@ const GenericReportImproved = ({
|
|
|
6652
6824
|
// Add data for each group
|
|
6653
6825
|
actualGroups.forEach((group, groupIndex) => {
|
|
6654
6826
|
// Add group header row
|
|
6655
|
-
const groupDisplayName =
|
|
6827
|
+
const groupDisplayName =
|
|
6828
|
+
selectedTemplate?.grouping?.groupDisplayName || 'Group';
|
|
6656
6829
|
const groupHeaderRow = Array(headers.length).fill('');
|
|
6657
6830
|
groupHeaderRow[0] = `${groupDisplayName}: ${group.groupDisplayName}`;
|
|
6658
6831
|
worksheetData.push(groupHeaderRow);
|
|
6659
6832
|
|
|
6660
6833
|
// Add data rows for this group
|
|
6661
6834
|
const allRows = [...group.rows, ...(group.emptyRows || [])];
|
|
6662
|
-
allRows.forEach(row => {
|
|
6663
|
-
const dataRow = selectedColumns.map(column =>
|
|
6835
|
+
allRows.forEach((row) => {
|
|
6836
|
+
const dataRow = selectedColumns.map((column) =>
|
|
6837
|
+
getCellValue(row, column)
|
|
6838
|
+
);
|
|
6664
6839
|
worksheetData.push(dataRow);
|
|
6665
6840
|
});
|
|
6666
6841
|
|
|
@@ -6674,34 +6849,45 @@ const GenericReportImproved = ({
|
|
|
6674
6849
|
const worksheet = XLSX.utils.aoa_to_sheet(worksheetData);
|
|
6675
6850
|
|
|
6676
6851
|
// Set column widths based on content
|
|
6677
|
-
const colWidths = selectedColumns.map(column => {
|
|
6852
|
+
const colWidths = selectedColumns.map((column) => {
|
|
6678
6853
|
switch (column.displayName) {
|
|
6679
|
-
case 'Comments':
|
|
6854
|
+
case 'Comments':
|
|
6855
|
+
return { wch: 40 };
|
|
6680
6856
|
case 'Project':
|
|
6681
|
-
case 'Project Name':
|
|
6857
|
+
case 'Project Name':
|
|
6858
|
+
return { wch: 25 };
|
|
6682
6859
|
case 'User':
|
|
6683
|
-
case 'Assigned User':
|
|
6860
|
+
case 'Assigned User':
|
|
6861
|
+
return { wch: 20 };
|
|
6684
6862
|
case 'Start Date':
|
|
6685
6863
|
case 'End Date':
|
|
6686
|
-
case 'Database Updated':
|
|
6687
|
-
|
|
6864
|
+
case 'Database Updated':
|
|
6865
|
+
return { wch: 15 };
|
|
6866
|
+
default:
|
|
6867
|
+
return { wch: 18 };
|
|
6688
6868
|
}
|
|
6689
6869
|
});
|
|
6690
6870
|
worksheet['!cols'] = colWidths;
|
|
6691
6871
|
|
|
6692
|
-
|
|
6693
6872
|
// Add worksheet to workbook
|
|
6694
6873
|
const sheetName = selectedTemplate.name || 'Grouped Report';
|
|
6695
6874
|
XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
|
|
6696
6875
|
|
|
6697
6876
|
// Generate file and download
|
|
6698
|
-
const filename = `${selectedTemplate.name.replace(
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6877
|
+
const filename = `${selectedTemplate.name.replace(
|
|
6878
|
+
/[^a-zA-Z0-9]/g,
|
|
6879
|
+
'_'
|
|
6880
|
+
)}_grouped_report_${new Date().toISOString().split('T')[0]}.xlsx`;
|
|
6881
|
+
const excelBuffer = XLSX.write(workbook, {
|
|
6882
|
+
bookType: 'xlsx',
|
|
6883
|
+
type: 'array',
|
|
6884
|
+
});
|
|
6885
|
+
const blob = new Blob([excelBuffer], {
|
|
6886
|
+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
6887
|
+
});
|
|
6888
|
+
|
|
6702
6889
|
saveAs(blob, filename);
|
|
6703
6890
|
toast.success('Excel export completed successfully!');
|
|
6704
|
-
|
|
6705
6891
|
} catch (error) {
|
|
6706
6892
|
console.error('Export error:', error);
|
|
6707
6893
|
toast.error(`Export failed: ${error.message}`);
|
|
@@ -6785,7 +6971,9 @@ const GenericReportImproved = ({
|
|
|
6785
6971
|
joins: joins,
|
|
6786
6972
|
filters: filterCriteria,
|
|
6787
6973
|
sorting: sortCriteria,
|
|
6788
|
-
grouping: groupingConfig.enabled
|
|
6974
|
+
grouping: groupingConfig.enabled
|
|
6975
|
+
? groupingConfig
|
|
6976
|
+
: null,
|
|
6789
6977
|
isGrouped: groupingConfig.enabled,
|
|
6790
6978
|
customCalculatedFields: customCalculatedFields,
|
|
6791
6979
|
customUrls: customUrls,
|
|
@@ -6851,187 +7039,288 @@ const GenericReportImproved = ({
|
|
|
6851
7039
|
<input
|
|
6852
7040
|
type="checkbox"
|
|
6853
7041
|
checked={groupingConfig.enabled}
|
|
6854
|
-
onChange={(e) =>
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
7042
|
+
onChange={(e) =>
|
|
7043
|
+
setGroupingConfig({
|
|
7044
|
+
...groupingConfig,
|
|
7045
|
+
enabled: e.target.checked,
|
|
7046
|
+
// Reset fields when disabled
|
|
7047
|
+
...(e.target.checked
|
|
7048
|
+
? {}
|
|
7049
|
+
: {
|
|
7050
|
+
groupByField: '',
|
|
7051
|
+
groupDisplayName: '',
|
|
7052
|
+
}),
|
|
6861
7053
|
})
|
|
6862
|
-
}
|
|
7054
|
+
}
|
|
6863
7055
|
className={styles.toggleInput}
|
|
6864
7056
|
/>
|
|
6865
7057
|
<span className={styles.toggleCheckbox}></span>
|
|
6866
|
-
<span className={styles.toggleText}>
|
|
7058
|
+
<span className={styles.toggleText}>
|
|
7059
|
+
Enable Grouping
|
|
7060
|
+
</span>
|
|
6867
7061
|
</label>
|
|
6868
7062
|
<p className={styles.helpText}>
|
|
6869
|
-
Group data by a field to organize results into
|
|
7063
|
+
Group data by a field to organize results into
|
|
7064
|
+
sections (e.g., "Projects by Facility")
|
|
6870
7065
|
</p>
|
|
6871
7066
|
</div>
|
|
6872
|
-
|
|
7067
|
+
|
|
6873
7068
|
{groupingConfig.enabled && (
|
|
6874
7069
|
<div className={styles.groupingOptions}>
|
|
6875
7070
|
{/* Group By Field Selection */}
|
|
6876
7071
|
<div className={styles.formGroup}>
|
|
6877
|
-
<label className={styles.formLabel}>
|
|
7072
|
+
<label className={styles.formLabel}>
|
|
7073
|
+
Group By Field *
|
|
7074
|
+
</label>
|
|
6878
7075
|
<select
|
|
6879
7076
|
value={groupingConfig.groupByField}
|
|
6880
7077
|
onChange={(e) => {
|
|
6881
|
-
const selectedOption =
|
|
6882
|
-
|
|
7078
|
+
const selectedOption =
|
|
7079
|
+
e.target.selectedOptions[0];
|
|
7080
|
+
const fieldDisplayName =
|
|
7081
|
+
selectedOption?.text || '';
|
|
6883
7082
|
// Extract table name for display
|
|
6884
|
-
const tableMatch =
|
|
6885
|
-
|
|
6886
|
-
|
|
7083
|
+
const tableMatch =
|
|
7084
|
+
fieldDisplayName.match(
|
|
7085
|
+
/^([^.]+)\./
|
|
7086
|
+
);
|
|
7087
|
+
const tableName = tableMatch
|
|
7088
|
+
? tableMatch[1]
|
|
7089
|
+
: 'Group';
|
|
7090
|
+
|
|
6887
7091
|
setGroupingConfig({
|
|
6888
7092
|
...groupingConfig,
|
|
6889
7093
|
groupByField: e.target.value,
|
|
6890
|
-
groupDisplayName:
|
|
6891
|
-
|
|
7094
|
+
groupDisplayName:
|
|
7095
|
+
groupingConfig.groupDisplayName ||
|
|
7096
|
+
tableName,
|
|
7097
|
+
sortBy: e.target.value, // Auto-set sort field
|
|
6892
7098
|
});
|
|
6893
7099
|
}}
|
|
6894
7100
|
className={styles.formSelect}
|
|
6895
7101
|
>
|
|
6896
|
-
<option value="">
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
7102
|
+
<option value="">
|
|
7103
|
+
Select field to group by...
|
|
7104
|
+
</option>
|
|
7105
|
+
{getAvailableFilterColumns().map(
|
|
7106
|
+
(col, index) => {
|
|
7107
|
+
const fieldValue = `${col.table}.${col.column}`;
|
|
7108
|
+
const displayName = `${col.tableName}.${col.columnName}`;
|
|
7109
|
+
return (
|
|
7110
|
+
<option
|
|
7111
|
+
key={index}
|
|
7112
|
+
value={fieldValue}
|
|
7113
|
+
>
|
|
7114
|
+
{displayName}
|
|
7115
|
+
</option>
|
|
7116
|
+
);
|
|
7117
|
+
}
|
|
7118
|
+
)}
|
|
6906
7119
|
</select>
|
|
6907
7120
|
<p className={styles.fieldHelp}>
|
|
6908
|
-
Choose the field that contains the
|
|
7121
|
+
Choose the field that contains the
|
|
7122
|
+
categories you want to group by
|
|
6909
7123
|
</p>
|
|
6910
7124
|
</div>
|
|
6911
|
-
|
|
7125
|
+
|
|
6912
7126
|
{/* Group Display Name */}
|
|
6913
7127
|
<div className={styles.formGroup}>
|
|
6914
|
-
<label className={styles.formLabel}>
|
|
7128
|
+
<label className={styles.formLabel}>
|
|
7129
|
+
Group Display Name
|
|
7130
|
+
</label>
|
|
6915
7131
|
<input
|
|
6916
7132
|
type="text"
|
|
6917
7133
|
value={groupingConfig.groupDisplayName}
|
|
6918
|
-
onChange={(e) =>
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
7134
|
+
onChange={(e) =>
|
|
7135
|
+
setGroupingConfig({
|
|
7136
|
+
...groupingConfig,
|
|
7137
|
+
groupDisplayName: e.target.value,
|
|
7138
|
+
})
|
|
7139
|
+
}
|
|
6922
7140
|
placeholder="e.g., Facility, Department, Category"
|
|
6923
7141
|
className={styles.formInput}
|
|
6924
7142
|
/>
|
|
6925
7143
|
<p className={styles.fieldHelp}>
|
|
6926
|
-
What to call each group in the report (e.g.,
|
|
7144
|
+
What to call each group in the report (e.g.,
|
|
7145
|
+
"Facility: Main Building")
|
|
6927
7146
|
</p>
|
|
6928
7147
|
</div>
|
|
6929
|
-
|
|
7148
|
+
|
|
6930
7149
|
{/* Advanced Group Options */}
|
|
6931
7150
|
<div className={styles.advancedGroupOptions}>
|
|
6932
|
-
<h4 className={styles.sectionTitle}>
|
|
6933
|
-
|
|
7151
|
+
<h4 className={styles.sectionTitle}>
|
|
7152
|
+
Advanced Options
|
|
7153
|
+
</h4>
|
|
7154
|
+
|
|
6934
7155
|
<div className={styles.optionsGrid}>
|
|
6935
7156
|
<div className={styles.formGroup}>
|
|
6936
|
-
<label className={styles.formLabel}>
|
|
7157
|
+
<label className={styles.formLabel}>
|
|
7158
|
+
Rows Per Group
|
|
7159
|
+
</label>
|
|
6937
7160
|
<input
|
|
6938
7161
|
type="number"
|
|
6939
7162
|
min="1"
|
|
6940
7163
|
max="100"
|
|
6941
7164
|
value={groupingConfig.rowsPerGroup}
|
|
6942
|
-
onChange={(e) =>
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
7165
|
+
onChange={(e) =>
|
|
7166
|
+
setGroupingConfig({
|
|
7167
|
+
...groupingConfig,
|
|
7168
|
+
rowsPerGroup:
|
|
7169
|
+
parseInt(
|
|
7170
|
+
e.target.value
|
|
7171
|
+
) || 10,
|
|
7172
|
+
})
|
|
7173
|
+
}
|
|
6946
7174
|
className={styles.formInput}
|
|
6947
7175
|
/>
|
|
6948
7176
|
</div>
|
|
6949
|
-
|
|
7177
|
+
|
|
6950
7178
|
<div className={styles.formGroup}>
|
|
6951
|
-
<label className={styles.formLabel}>
|
|
7179
|
+
<label className={styles.formLabel}>
|
|
7180
|
+
Group Header Style
|
|
7181
|
+
</label>
|
|
6952
7182
|
<select
|
|
6953
|
-
value={
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
7183
|
+
value={
|
|
7184
|
+
groupingConfig.groupHeaderStyle
|
|
7185
|
+
}
|
|
7186
|
+
onChange={(e) =>
|
|
7187
|
+
setGroupingConfig({
|
|
7188
|
+
...groupingConfig,
|
|
7189
|
+
groupHeaderStyle:
|
|
7190
|
+
e.target.value,
|
|
7191
|
+
})
|
|
7192
|
+
}
|
|
6958
7193
|
className={styles.formSelect}
|
|
6959
7194
|
>
|
|
6960
|
-
<option value="primary">
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
<option value="
|
|
7195
|
+
<option value="primary">
|
|
7196
|
+
Primary (Blue)
|
|
7197
|
+
</option>
|
|
7198
|
+
<option value="secondary">
|
|
7199
|
+
Secondary (Gray)
|
|
7200
|
+
</option>
|
|
7201
|
+
<option value="success">
|
|
7202
|
+
Success (Green)
|
|
7203
|
+
</option>
|
|
7204
|
+
<option value="warning">
|
|
7205
|
+
Warning (Orange)
|
|
7206
|
+
</option>
|
|
6964
7207
|
</select>
|
|
6965
7208
|
</div>
|
|
6966
|
-
|
|
7209
|
+
|
|
6967
7210
|
<div className={styles.formGroup}>
|
|
6968
|
-
<label className={styles.formLabel}>
|
|
7211
|
+
<label className={styles.formLabel}>
|
|
7212
|
+
Sort Direction
|
|
7213
|
+
</label>
|
|
6969
7214
|
<select
|
|
6970
7215
|
value={groupingConfig.sortDirection}
|
|
6971
|
-
onChange={(e) =>
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
7216
|
+
onChange={(e) =>
|
|
7217
|
+
setGroupingConfig({
|
|
7218
|
+
...groupingConfig,
|
|
7219
|
+
sortDirection:
|
|
7220
|
+
e.target.value,
|
|
7221
|
+
})
|
|
7222
|
+
}
|
|
6975
7223
|
className={styles.formSelect}
|
|
6976
7224
|
>
|
|
6977
|
-
<option value="asc">
|
|
6978
|
-
|
|
7225
|
+
<option value="asc">
|
|
7226
|
+
Ascending (A-Z)
|
|
7227
|
+
</option>
|
|
7228
|
+
<option value="desc">
|
|
7229
|
+
Descending (Z-A)
|
|
7230
|
+
</option>
|
|
6979
7231
|
</select>
|
|
6980
7232
|
</div>
|
|
6981
7233
|
</div>
|
|
6982
|
-
|
|
7234
|
+
|
|
6983
7235
|
<div className={styles.checkboxGrid}>
|
|
6984
7236
|
<label className={styles.checkboxLabel}>
|
|
6985
7237
|
<input
|
|
6986
7238
|
type="checkbox"
|
|
6987
|
-
checked={
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
7239
|
+
checked={
|
|
7240
|
+
groupingConfig.showEmptyRows
|
|
7241
|
+
}
|
|
7242
|
+
onChange={(e) =>
|
|
7243
|
+
setGroupingConfig({
|
|
7244
|
+
...groupingConfig,
|
|
7245
|
+
showEmptyRows:
|
|
7246
|
+
e.target.checked,
|
|
7247
|
+
})
|
|
7248
|
+
}
|
|
6992
7249
|
/>
|
|
6993
|
-
<span className={styles.checkboxText}>
|
|
7250
|
+
<span className={styles.checkboxText}>
|
|
7251
|
+
Show Empty Rows
|
|
7252
|
+
</span>
|
|
6994
7253
|
</label>
|
|
6995
|
-
|
|
7254
|
+
|
|
6996
7255
|
<label className={styles.checkboxLabel}>
|
|
6997
7256
|
<input
|
|
6998
7257
|
type="checkbox"
|
|
6999
|
-
checked={
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7258
|
+
checked={
|
|
7259
|
+
groupingConfig.showGroupTotals
|
|
7260
|
+
}
|
|
7261
|
+
onChange={(e) =>
|
|
7262
|
+
setGroupingConfig({
|
|
7263
|
+
...groupingConfig,
|
|
7264
|
+
showGroupTotals:
|
|
7265
|
+
e.target.checked,
|
|
7266
|
+
})
|
|
7267
|
+
}
|
|
7004
7268
|
/>
|
|
7005
|
-
<span className={styles.checkboxText}>
|
|
7269
|
+
<span className={styles.checkboxText}>
|
|
7270
|
+
Show Group Totals
|
|
7271
|
+
</span>
|
|
7006
7272
|
</label>
|
|
7007
|
-
|
|
7273
|
+
|
|
7008
7274
|
<label className={styles.checkboxLabel}>
|
|
7009
7275
|
<input
|
|
7010
7276
|
type="checkbox"
|
|
7011
7277
|
checked={groupingConfig.expandable}
|
|
7012
|
-
onChange={(e) =>
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7278
|
+
onChange={(e) =>
|
|
7279
|
+
setGroupingConfig({
|
|
7280
|
+
...groupingConfig,
|
|
7281
|
+
expandable:
|
|
7282
|
+
e.target.checked,
|
|
7283
|
+
})
|
|
7284
|
+
}
|
|
7016
7285
|
/>
|
|
7017
|
-
<span className={styles.checkboxText}>
|
|
7286
|
+
<span className={styles.checkboxText}>
|
|
7287
|
+
Collapsible Groups
|
|
7288
|
+
</span>
|
|
7018
7289
|
</label>
|
|
7019
7290
|
</div>
|
|
7020
7291
|
</div>
|
|
7021
|
-
|
|
7292
|
+
|
|
7022
7293
|
{/* Grouping Preview */}
|
|
7023
7294
|
{groupingConfig.groupByField && (
|
|
7024
7295
|
<div className={styles.groupingPreview}>
|
|
7025
|
-
<h4 className={styles.sectionTitle}>
|
|
7296
|
+
<h4 className={styles.sectionTitle}>
|
|
7297
|
+
Configuration Preview
|
|
7298
|
+
</h4>
|
|
7026
7299
|
<div className={styles.previewBox}>
|
|
7027
|
-
<p><strong>Group By:</strong> {groupingConfig.groupByField}</p>
|
|
7028
|
-
<p><strong>Display Name:</strong> {groupingConfig.groupDisplayName || 'Group'}</p>
|
|
7029
|
-
<p><strong>Sort:</strong> {groupingConfig.sortDirection === 'asc' ? 'Ascending' : 'Descending'}</p>
|
|
7030
7300
|
<p>
|
|
7031
|
-
<strong>
|
|
7032
|
-
{groupingConfig.
|
|
7033
|
-
|
|
7034
|
-
|
|
7301
|
+
<strong>Group By:</strong>{' '}
|
|
7302
|
+
{groupingConfig.groupByField}
|
|
7303
|
+
</p>
|
|
7304
|
+
<p>
|
|
7305
|
+
<strong>Display Name:</strong>{' '}
|
|
7306
|
+
{groupingConfig.groupDisplayName ||
|
|
7307
|
+
'Group'}
|
|
7308
|
+
</p>
|
|
7309
|
+
<p>
|
|
7310
|
+
<strong>Sort:</strong>{' '}
|
|
7311
|
+
{groupingConfig.sortDirection ===
|
|
7312
|
+
'asc'
|
|
7313
|
+
? 'Ascending'
|
|
7314
|
+
: 'Descending'}
|
|
7315
|
+
</p>
|
|
7316
|
+
<p>
|
|
7317
|
+
<strong>Options:</strong>
|
|
7318
|
+
{groupingConfig.showEmptyRows &&
|
|
7319
|
+
' Empty Rows'}
|
|
7320
|
+
{groupingConfig.showGroupTotals &&
|
|
7321
|
+
' Group Totals'}
|
|
7322
|
+
{groupingConfig.expandable &&
|
|
7323
|
+
' Collapsible'}
|
|
7035
7324
|
</p>
|
|
7036
7325
|
</div>
|
|
7037
7326
|
</div>
|
|
@@ -7039,49 +7328,64 @@ const GenericReportImproved = ({
|
|
|
7039
7328
|
</div>
|
|
7040
7329
|
)}
|
|
7041
7330
|
|
|
7042
|
-
{/* Custom URLs Configuration */}
|
|
7043
|
-
|
|
7044
|
-
<
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
className={styles.
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7331
|
+
{/* Custom URLs Configuration - Only show when grouping is enabled */}
|
|
7332
|
+
{groupingConfig.enabled && (
|
|
7333
|
+
<div className={styles.customUrlsSection}>
|
|
7334
|
+
<h4 className={styles.sectionTitle}>
|
|
7335
|
+
Custom API Endpoints (Optional)
|
|
7336
|
+
</h4>
|
|
7337
|
+
<p className={styles.helpText}>
|
|
7338
|
+
Override default API endpoints for specialized
|
|
7339
|
+
report handling
|
|
7340
|
+
</p>
|
|
7341
|
+
|
|
7342
|
+
<div className={styles.optionsGrid}>
|
|
7343
|
+
<div className={styles.formGroup}>
|
|
7344
|
+
<label className={styles.formLabel}>
|
|
7345
|
+
Custom Execute URL
|
|
7346
|
+
</label>
|
|
7347
|
+
<input
|
|
7348
|
+
type="text"
|
|
7349
|
+
value={customUrls.executeUrl}
|
|
7350
|
+
onChange={(e) =>
|
|
7351
|
+
setCustomUrls({
|
|
7352
|
+
...customUrls,
|
|
7353
|
+
executeUrl: e.target.value,
|
|
7354
|
+
})
|
|
7355
|
+
}
|
|
7356
|
+
placeholder="/api/custom-reports/execute"
|
|
7357
|
+
className={styles.formInput}
|
|
7358
|
+
/>
|
|
7359
|
+
<p className={styles.fieldHelp}>
|
|
7360
|
+
Custom endpoint for report execution
|
|
7361
|
+
(overrides template and default URLs)
|
|
7362
|
+
</p>
|
|
7363
|
+
</div>
|
|
7364
|
+
|
|
7365
|
+
<div className={styles.formGroup}>
|
|
7366
|
+
<label className={styles.formLabel}>
|
|
7367
|
+
Custom Export URL
|
|
7368
|
+
</label>
|
|
7369
|
+
<input
|
|
7370
|
+
type="text"
|
|
7371
|
+
value={customUrls.exportUrl}
|
|
7372
|
+
onChange={(e) =>
|
|
7373
|
+
setCustomUrls({
|
|
7374
|
+
...customUrls,
|
|
7375
|
+
exportUrl: e.target.value,
|
|
7376
|
+
})
|
|
7377
|
+
}
|
|
7378
|
+
placeholder="/api/custom-reports/export"
|
|
7379
|
+
className={styles.formInput}
|
|
7380
|
+
/>
|
|
7381
|
+
<p className={styles.fieldHelp}>
|
|
7382
|
+
Custom endpoint for report exports (for
|
|
7383
|
+
server-side exports)
|
|
7384
|
+
</p>
|
|
7385
|
+
</div>
|
|
7082
7386
|
</div>
|
|
7083
7387
|
</div>
|
|
7084
|
-
|
|
7388
|
+
)}
|
|
7085
7389
|
</div>
|
|
7086
7390
|
</div>
|
|
7087
7391
|
);
|
|
@@ -7103,17 +7407,21 @@ const GenericReportImproved = ({
|
|
|
7103
7407
|
>
|
|
7104
7408
|
{isLoading ? 'Loading...' : 'Generate Report'}
|
|
7105
7409
|
</button>
|
|
7106
|
-
|
|
7410
|
+
|
|
7107
7411
|
{/* View Toggle Button - only show if both grouped and regular data are available */}
|
|
7108
|
-
{(groupingConfig.enabled ||
|
|
7412
|
+
{(groupingConfig.enabled ||
|
|
7413
|
+
selectedTemplate?.grouping?.enabled) && (
|
|
7109
7414
|
<div className={styles.viewToggle}>
|
|
7110
7415
|
<span className={styles.viewLabel}>View:</span>
|
|
7111
7416
|
<button
|
|
7112
|
-
className={`${styles.btn} ${
|
|
7113
|
-
|
|
7114
|
-
}`}
|
|
7417
|
+
className={`${styles.btn} ${
|
|
7418
|
+
styles.btnSecondary
|
|
7419
|
+
} ${groupedData ? styles.active : ''}`}
|
|
7115
7420
|
onClick={() => {
|
|
7116
|
-
if (
|
|
7421
|
+
if (
|
|
7422
|
+
!groupedData &&
|
|
7423
|
+
previewData.length > 0
|
|
7424
|
+
) {
|
|
7117
7425
|
// Switch to grouped view
|
|
7118
7426
|
executeReport();
|
|
7119
7427
|
}
|
|
@@ -7124,9 +7432,9 @@ const GenericReportImproved = ({
|
|
|
7124
7432
|
📊 Grouped
|
|
7125
7433
|
</button>
|
|
7126
7434
|
<button
|
|
7127
|
-
className={`${styles.btn} ${
|
|
7128
|
-
|
|
7129
|
-
}`}
|
|
7435
|
+
className={`${styles.btn} ${
|
|
7436
|
+
styles.btnSecondary
|
|
7437
|
+
} ${!groupedData ? styles.active : ''}`}
|
|
7130
7438
|
onClick={() => {
|
|
7131
7439
|
if (groupedData) {
|
|
7132
7440
|
// Switch to regular table view
|
|
@@ -7147,20 +7455,29 @@ const GenericReportImproved = ({
|
|
|
7147
7455
|
|
|
7148
7456
|
{/* Render grouped report if available */}
|
|
7149
7457
|
{(() => {
|
|
7150
|
-
console.log(
|
|
7151
|
-
|
|
7152
|
-
|
|
7458
|
+
console.log(
|
|
7459
|
+
'🎯 [DEBUG] Render check - groupedData:',
|
|
7460
|
+
groupedData
|
|
7461
|
+
);
|
|
7462
|
+
console.log(
|
|
7463
|
+
'🎯 [DEBUG] Render check - groupedData?.grouped:',
|
|
7464
|
+
groupedData?.grouped
|
|
7465
|
+
);
|
|
7466
|
+
console.log(
|
|
7467
|
+
'🎯 [DEBUG] Render check - selectedColumns:',
|
|
7468
|
+
selectedColumns
|
|
7469
|
+
);
|
|
7153
7470
|
return groupedData && groupedData.grouped;
|
|
7154
7471
|
})() && (
|
|
7155
7472
|
<div className={styles.groupedReportContainer}>
|
|
7156
|
-
<GroupedReportRenderer
|
|
7473
|
+
<GroupedReportRenderer
|
|
7157
7474
|
data={{
|
|
7158
7475
|
...groupedData.groupedData,
|
|
7159
|
-
grouped: true
|
|
7476
|
+
grouped: true,
|
|
7160
7477
|
}}
|
|
7161
7478
|
config={
|
|
7162
|
-
groupingConfig.enabled
|
|
7163
|
-
? groupingConfig
|
|
7479
|
+
groupingConfig.enabled
|
|
7480
|
+
? groupingConfig
|
|
7164
7481
|
: selectedTemplate?.grouping || {}
|
|
7165
7482
|
}
|
|
7166
7483
|
columns={selectedColumns}
|
|
@@ -7179,14 +7496,23 @@ const GenericReportImproved = ({
|
|
|
7179
7496
|
<div className="grouped-report-section">
|
|
7180
7497
|
<div className="grouped-report-header">
|
|
7181
7498
|
<div className="grouped-report-summary">
|
|
7182
|
-
<h3 className="group-title">
|
|
7183
|
-
|
|
7499
|
+
<h3 className="group-title">
|
|
7500
|
+
Report Results
|
|
7501
|
+
</h3>
|
|
7502
|
+
<span className="group-stats">
|
|
7503
|
+
({previewData.length} records)
|
|
7504
|
+
</span>
|
|
7184
7505
|
</div>
|
|
7185
7506
|
<div className="grouped-report-actions">
|
|
7186
|
-
<button
|
|
7507
|
+
<button
|
|
7187
7508
|
className="btn btn-export"
|
|
7188
|
-
onClick={() =>
|
|
7189
|
-
|
|
7509
|
+
onClick={() =>
|
|
7510
|
+
handleRegularExport()
|
|
7511
|
+
}
|
|
7512
|
+
disabled={
|
|
7513
|
+
isLoading ||
|
|
7514
|
+
previewData.length === 0
|
|
7515
|
+
}
|
|
7190
7516
|
title="Export report data as Excel file"
|
|
7191
7517
|
>
|
|
7192
7518
|
Export
|
|
@@ -7198,103 +7524,865 @@ const GenericReportImproved = ({
|
|
|
7198
7524
|
<thead>
|
|
7199
7525
|
<tr>
|
|
7200
7526
|
{(() => {
|
|
7201
|
-
const headerColumns =
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7527
|
+
const headerColumns =
|
|
7528
|
+
previewData.length >
|
|
7529
|
+
0
|
|
7530
|
+
? Object.keys(
|
|
7531
|
+
previewData[0]
|
|
7532
|
+
).map(
|
|
7533
|
+
(
|
|
7534
|
+
key
|
|
7535
|
+
) => ({
|
|
7536
|
+
name: key,
|
|
7537
|
+
header: key.replace(
|
|
7538
|
+
/\./g,
|
|
7539
|
+
' '
|
|
7540
|
+
),
|
|
7541
|
+
})
|
|
7542
|
+
)
|
|
7543
|
+
: gridColumns;
|
|
7544
|
+
return headerColumns.map(
|
|
7545
|
+
(col, index) => (
|
|
7546
|
+
<th
|
|
7547
|
+
key={index}
|
|
7548
|
+
className="table-header"
|
|
7549
|
+
>
|
|
7550
|
+
{col.header ||
|
|
7551
|
+
col.name}
|
|
7552
|
+
</th>
|
|
7553
|
+
)
|
|
7554
|
+
);
|
|
7207
7555
|
})()}
|
|
7208
7556
|
</tr>
|
|
7209
7557
|
</thead>
|
|
7210
7558
|
<tbody>
|
|
7211
7559
|
{(() => {
|
|
7212
|
-
console.log(
|
|
7560
|
+
console.log(
|
|
7561
|
+
`🎯 [DEBUG] Rendering ${previewData.length} table rows`
|
|
7562
|
+
);
|
|
7213
7563
|
// Always use response keys as fallback instead of gridColumns that might have wrong names
|
|
7214
|
-
const actualColumns =
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
}
|
|
7245
|
-
|
|
7246
|
-
// Handle ABN (Australian Business Number) columns
|
|
7247
|
-
if (col.name.toLowerCase().includes('abn') && value) {
|
|
7248
|
-
const cleanABN = String(value).replace(/\s/g, ''); // Remove spaces, convert to string
|
|
7249
|
-
if (cleanABN.length === 11 && /^\d{11}$/.test(cleanABN)) {
|
|
7250
|
-
// Format as XX XXX XXX XXX
|
|
7251
|
-
const formatted = `${cleanABN.slice(0,2)} ${cleanABN.slice(2,5)} ${cleanABN.slice(5,8)} ${cleanABN.slice(8,11)}`;
|
|
7252
|
-
return <span style={{fontFamily: 'monospace', fontWeight: '500', color: '#495057'}}>{formatted}</span>;
|
|
7253
|
-
}
|
|
7254
|
-
}
|
|
7255
|
-
|
|
7256
|
-
// Handle ACN (Australian Company Number) columns
|
|
7257
|
-
if (col.name.toLowerCase().includes('acn') && value) {
|
|
7258
|
-
const cleanACN = String(value).replace(/\s/g, ''); // Remove spaces, convert to string
|
|
7259
|
-
if (cleanACN.length === 9 && /^\d{9}$/.test(cleanACN)) {
|
|
7260
|
-
// Format as XXX XXX XXX
|
|
7261
|
-
const formatted = `${cleanACN.slice(0,3)} ${cleanACN.slice(3,6)} ${cleanACN.slice(6,9)}`;
|
|
7262
|
-
return <span style={{fontFamily: 'monospace', fontWeight: '500', color: '#495057'}}>{formatted}</span>;
|
|
7263
|
-
}
|
|
7264
|
-
}
|
|
7265
|
-
|
|
7266
|
-
// Handle status/boolean-like columns
|
|
7267
|
-
if (col.name.toLowerCase().includes('status')) {
|
|
7268
|
-
if (value === 1 || value === '1' || value === true) {
|
|
7269
|
-
return <span style={{color: '#28a745', fontWeight: '500'}}>Active</span>;
|
|
7270
|
-
} else if (value === 0 || value === '0' || value === false) {
|
|
7271
|
-
return <span style={{color: '#6c757d', fontWeight: '500'}}>Inactive</span>;
|
|
7564
|
+
const actualColumns =
|
|
7565
|
+
previewData.length > 0
|
|
7566
|
+
? Object.keys(
|
|
7567
|
+
previewData[0]
|
|
7568
|
+
).map((key) => ({
|
|
7569
|
+
name: key,
|
|
7570
|
+
header: key.replace(
|
|
7571
|
+
/\./g,
|
|
7572
|
+
' '
|
|
7573
|
+
),
|
|
7574
|
+
}))
|
|
7575
|
+
: gridColumns;
|
|
7576
|
+
console.log(
|
|
7577
|
+
`🎯 [DEBUG] Using columns:`,
|
|
7578
|
+
actualColumns
|
|
7579
|
+
);
|
|
7580
|
+
return previewData.map(
|
|
7581
|
+
(row, rowIndex) => (
|
|
7582
|
+
<tr
|
|
7583
|
+
key={rowIndex}
|
|
7584
|
+
className="data-row"
|
|
7585
|
+
>
|
|
7586
|
+
{actualColumns.map(
|
|
7587
|
+
(
|
|
7588
|
+
col,
|
|
7589
|
+
colIndex
|
|
7590
|
+
) => (
|
|
7591
|
+
<td
|
|
7592
|
+
key={
|
|
7593
|
+
colIndex
|
|
7272
7594
|
}
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7595
|
+
className="table-cell"
|
|
7596
|
+
>
|
|
7597
|
+
{(() => {
|
|
7598
|
+
const value =
|
|
7599
|
+
row[
|
|
7600
|
+
col
|
|
7601
|
+
.name
|
|
7602
|
+
];
|
|
7603
|
+
|
|
7604
|
+
// Handle null/undefined
|
|
7605
|
+
if (
|
|
7606
|
+
value ===
|
|
7607
|
+
null ||
|
|
7608
|
+
value ===
|
|
7609
|
+
undefined
|
|
7610
|
+
) {
|
|
7611
|
+
return (
|
|
7612
|
+
<span
|
|
7613
|
+
style={{
|
|
7614
|
+
color: '#6c757d',
|
|
7615
|
+
fontStyle:
|
|
7616
|
+
'italic',
|
|
7617
|
+
}}
|
|
7618
|
+
>
|
|
7619
|
+
—
|
|
7620
|
+
</span>
|
|
7621
|
+
);
|
|
7622
|
+
}
|
|
7623
|
+
|
|
7624
|
+
// Handle JSON strings
|
|
7625
|
+
if (
|
|
7626
|
+
typeof value ===
|
|
7627
|
+
'string' &&
|
|
7628
|
+
value.startsWith(
|
|
7629
|
+
'{'
|
|
7630
|
+
) &&
|
|
7631
|
+
value.endsWith(
|
|
7632
|
+
'}'
|
|
7633
|
+
)
|
|
7634
|
+
) {
|
|
7635
|
+
try {
|
|
7636
|
+
const parsed =
|
|
7637
|
+
JSON.parse(
|
|
7638
|
+
value
|
|
7639
|
+
);
|
|
7640
|
+
return (
|
|
7641
|
+
<div
|
|
7642
|
+
style={{
|
|
7643
|
+
fontSize:
|
|
7644
|
+
'0.85em',
|
|
7645
|
+
lineHeight:
|
|
7646
|
+
'1.3',
|
|
7647
|
+
}}
|
|
7648
|
+
>
|
|
7649
|
+
{Object.entries(
|
|
7650
|
+
parsed
|
|
7651
|
+
).map(
|
|
7652
|
+
([
|
|
7653
|
+
key,
|
|
7654
|
+
val,
|
|
7655
|
+
]) => (
|
|
7656
|
+
<div
|
|
7657
|
+
key={
|
|
7658
|
+
key
|
|
7659
|
+
}
|
|
7660
|
+
style={{
|
|
7661
|
+
marginBottom:
|
|
7662
|
+
'2px',
|
|
7663
|
+
}}
|
|
7664
|
+
>
|
|
7665
|
+
<strong>
|
|
7666
|
+
{key.replace(
|
|
7667
|
+
/_/g,
|
|
7668
|
+
' '
|
|
7669
|
+
)}
|
|
7670
|
+
:
|
|
7671
|
+
</strong>{' '}
|
|
7672
|
+
{val ||
|
|
7673
|
+
'—'}
|
|
7674
|
+
</div>
|
|
7675
|
+
)
|
|
7676
|
+
)}
|
|
7677
|
+
</div>
|
|
7678
|
+
);
|
|
7679
|
+
} catch (e) {
|
|
7680
|
+
return value;
|
|
7681
|
+
}
|
|
7682
|
+
}
|
|
7683
|
+
|
|
7684
|
+
// Handle ABN (Australian Business Number) columns
|
|
7685
|
+
if (
|
|
7686
|
+
col.name
|
|
7687
|
+
.toLowerCase()
|
|
7688
|
+
.includes(
|
|
7689
|
+
'abn'
|
|
7690
|
+
) &&
|
|
7691
|
+
value
|
|
7692
|
+
) {
|
|
7693
|
+
const cleanABN =
|
|
7694
|
+
String(
|
|
7695
|
+
value
|
|
7696
|
+
).replace(
|
|
7697
|
+
/\s/g,
|
|
7698
|
+
''
|
|
7699
|
+
); // Remove spaces, convert to string
|
|
7700
|
+
if (
|
|
7701
|
+
cleanABN.length ===
|
|
7702
|
+
11 &&
|
|
7703
|
+
/^\d{11}$/.test(
|
|
7704
|
+
cleanABN
|
|
7705
|
+
)
|
|
7706
|
+
) {
|
|
7707
|
+
// Format as XX XXX XXX XXX
|
|
7708
|
+
const formatted = `${cleanABN.slice(
|
|
7709
|
+
0,
|
|
7710
|
+
2
|
|
7711
|
+
)} ${cleanABN.slice(
|
|
7712
|
+
2,
|
|
7713
|
+
5
|
|
7714
|
+
)} ${cleanABN.slice(
|
|
7715
|
+
5,
|
|
7716
|
+
8
|
|
7717
|
+
)} ${cleanABN.slice(
|
|
7718
|
+
8,
|
|
7719
|
+
11
|
|
7720
|
+
)}`;
|
|
7721
|
+
return (
|
|
7722
|
+
<span
|
|
7723
|
+
style={{
|
|
7724
|
+
fontFamily:
|
|
7725
|
+
'monospace',
|
|
7726
|
+
fontWeight:
|
|
7727
|
+
'500',
|
|
7728
|
+
color: '#495057',
|
|
7729
|
+
}}
|
|
7730
|
+
>
|
|
7731
|
+
{
|
|
7732
|
+
formatted
|
|
7733
|
+
}
|
|
7734
|
+
</span>
|
|
7735
|
+
);
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
|
|
7739
|
+
// Handle ACN (Australian Company Number) columns
|
|
7740
|
+
if (
|
|
7741
|
+
col.name
|
|
7742
|
+
.toLowerCase()
|
|
7743
|
+
.includes(
|
|
7744
|
+
'acn'
|
|
7745
|
+
) &&
|
|
7746
|
+
value
|
|
7747
|
+
) {
|
|
7748
|
+
const cleanACN =
|
|
7749
|
+
String(
|
|
7750
|
+
value
|
|
7751
|
+
).replace(
|
|
7752
|
+
/\s/g,
|
|
7753
|
+
''
|
|
7754
|
+
); // Remove spaces, convert to string
|
|
7755
|
+
if (
|
|
7756
|
+
cleanACN.length ===
|
|
7757
|
+
9 &&
|
|
7758
|
+
/^\d{9}$/.test(
|
|
7759
|
+
cleanACN
|
|
7760
|
+
)
|
|
7761
|
+
) {
|
|
7762
|
+
// Format as XXX XXX XXX
|
|
7763
|
+
const formatted = `${cleanACN.slice(
|
|
7764
|
+
0,
|
|
7765
|
+
3
|
|
7766
|
+
)} ${cleanACN.slice(
|
|
7767
|
+
3,
|
|
7768
|
+
6
|
|
7769
|
+
)} ${cleanACN.slice(
|
|
7770
|
+
6,
|
|
7771
|
+
9
|
|
7772
|
+
)}`;
|
|
7773
|
+
return (
|
|
7774
|
+
<span
|
|
7775
|
+
style={{
|
|
7776
|
+
fontFamily:
|
|
7777
|
+
'monospace',
|
|
7778
|
+
fontWeight:
|
|
7779
|
+
'500',
|
|
7780
|
+
color: '#495057',
|
|
7781
|
+
}}
|
|
7782
|
+
>
|
|
7783
|
+
{
|
|
7784
|
+
formatted
|
|
7785
|
+
}
|
|
7786
|
+
</span>
|
|
7787
|
+
);
|
|
7788
|
+
}
|
|
7789
|
+
}
|
|
7790
|
+
|
|
7791
|
+
// Handle status/boolean-like columns
|
|
7792
|
+
if (
|
|
7793
|
+
col.name
|
|
7794
|
+
.toLowerCase()
|
|
7795
|
+
.includes(
|
|
7796
|
+
'status'
|
|
7797
|
+
)
|
|
7798
|
+
) {
|
|
7799
|
+
if (
|
|
7800
|
+
value ===
|
|
7801
|
+
1 ||
|
|
7802
|
+
value ===
|
|
7803
|
+
'1' ||
|
|
7804
|
+
value ===
|
|
7805
|
+
true
|
|
7806
|
+
) {
|
|
7807
|
+
return (
|
|
7808
|
+
<span
|
|
7809
|
+
style={{
|
|
7810
|
+
color: '#28a745',
|
|
7811
|
+
fontWeight:
|
|
7812
|
+
'500',
|
|
7813
|
+
}}
|
|
7814
|
+
>
|
|
7815
|
+
Active
|
|
7816
|
+
</span>
|
|
7817
|
+
);
|
|
7818
|
+
} else if (
|
|
7819
|
+
value ===
|
|
7820
|
+
0 ||
|
|
7821
|
+
value ===
|
|
7822
|
+
'0' ||
|
|
7823
|
+
value ===
|
|
7824
|
+
false
|
|
7825
|
+
) {
|
|
7826
|
+
return (
|
|
7827
|
+
<span
|
|
7828
|
+
style={{
|
|
7829
|
+
color: '#6c757d',
|
|
7830
|
+
fontWeight:
|
|
7831
|
+
'500',
|
|
7832
|
+
}}
|
|
7833
|
+
>
|
|
7834
|
+
Inactive
|
|
7835
|
+
</span>
|
|
7836
|
+
);
|
|
7837
|
+
}
|
|
7838
|
+
}
|
|
7839
|
+
|
|
7840
|
+
// Handle other boolean values
|
|
7841
|
+
if (
|
|
7842
|
+
typeof value ===
|
|
7843
|
+
'boolean'
|
|
7844
|
+
) {
|
|
7845
|
+
return value ? (
|
|
7846
|
+
<span
|
|
7847
|
+
style={{
|
|
7848
|
+
color: '#28a745',
|
|
7849
|
+
fontWeight:
|
|
7850
|
+
'500',
|
|
7851
|
+
}}
|
|
7852
|
+
>
|
|
7853
|
+
Yes
|
|
7854
|
+
</span>
|
|
7855
|
+
) : (
|
|
7856
|
+
<span
|
|
7857
|
+
style={{
|
|
7858
|
+
color: '#6c757d',
|
|
7859
|
+
fontWeight:
|
|
7860
|
+
'500',
|
|
7861
|
+
}}
|
|
7862
|
+
>
|
|
7863
|
+
No
|
|
7864
|
+
</span>
|
|
7865
|
+
);
|
|
7866
|
+
}
|
|
7867
|
+
|
|
7868
|
+
// Handle numeric values (but exclude potential boolean columns)
|
|
7869
|
+
if (
|
|
7870
|
+
typeof value ===
|
|
7871
|
+
'number' &&
|
|
7872
|
+
!col.name
|
|
7873
|
+
.toLowerCase()
|
|
7874
|
+
.includes(
|
|
7875
|
+
'status'
|
|
7876
|
+
)
|
|
7877
|
+
) {
|
|
7878
|
+
const columnName =
|
|
7879
|
+
col.name.toLowerCase();
|
|
7880
|
+
// Don't format as numbers if they might be boolean values
|
|
7881
|
+
const mightBeBoolean =
|
|
7882
|
+
(value ===
|
|
7883
|
+
0 ||
|
|
7884
|
+
value ===
|
|
7885
|
+
1) &&
|
|
7886
|
+
(columnName.includes(
|
|
7887
|
+
'is_'
|
|
7888
|
+
) ||
|
|
7889
|
+
columnName.includes(
|
|
7890
|
+
'has_'
|
|
7891
|
+
) ||
|
|
7892
|
+
columnName.includes(
|
|
7893
|
+
'confirmed'
|
|
7894
|
+
) ||
|
|
7895
|
+
columnName.includes(
|
|
7896
|
+
'active'
|
|
7897
|
+
) ||
|
|
7898
|
+
columnName.includes(
|
|
7899
|
+
'enabled'
|
|
7900
|
+
) ||
|
|
7901
|
+
columnName.includes(
|
|
7902
|
+
'completed'
|
|
7903
|
+
) ||
|
|
7904
|
+
columnName.includes(
|
|
7905
|
+
'stage'
|
|
7906
|
+
) ||
|
|
7907
|
+
columnName.includes(
|
|
7908
|
+
'final'
|
|
7909
|
+
) ||
|
|
7910
|
+
columnName.includes(
|
|
7911
|
+
'flag'
|
|
7912
|
+
) ||
|
|
7913
|
+
columnName.includes(
|
|
7914
|
+
'approved'
|
|
7915
|
+
) ||
|
|
7916
|
+
columnName.includes(
|
|
7917
|
+
'verified'
|
|
7918
|
+
) ||
|
|
7919
|
+
columnName.includes(
|
|
7920
|
+
'sent'
|
|
7921
|
+
) ||
|
|
7922
|
+
columnName.endsWith(
|
|
7923
|
+
'?'
|
|
7924
|
+
));
|
|
7925
|
+
|
|
7926
|
+
if (
|
|
7927
|
+
!mightBeBoolean
|
|
7928
|
+
) {
|
|
7929
|
+
return value.toLocaleString();
|
|
7930
|
+
}
|
|
7931
|
+
}
|
|
7932
|
+
|
|
7933
|
+
// Use formatCellContent for all other values including dates and smart boolean formatting
|
|
7934
|
+
const formattedValue =
|
|
7935
|
+
formatCellContent(
|
|
7936
|
+
value,
|
|
7937
|
+
{
|
|
7938
|
+
id: col.name,
|
|
7939
|
+
name: col.name,
|
|
7940
|
+
// Get type from template column definition if available, otherwise infer
|
|
7941
|
+
type: (() => {
|
|
7942
|
+
// First try to find type from template's defaultColumns
|
|
7943
|
+
if (
|
|
7944
|
+
selectedTemplate?.defaultColumns
|
|
7945
|
+
) {
|
|
7946
|
+
const templateColumn =
|
|
7947
|
+
selectedTemplate.defaultColumns.find(
|
|
7948
|
+
(
|
|
7949
|
+
tc
|
|
7950
|
+
) =>
|
|
7951
|
+
`${tc.table}.${tc.column}` ===
|
|
7952
|
+
col.name ||
|
|
7953
|
+
tc.column ===
|
|
7954
|
+
col.name
|
|
7955
|
+
);
|
|
7956
|
+
if (
|
|
7957
|
+
templateColumn?.type
|
|
7958
|
+
) {
|
|
7959
|
+
return templateColumn.type;
|
|
7960
|
+
}
|
|
7961
|
+
}
|
|
7962
|
+
|
|
7963
|
+
// Fallback to column name-based type inference
|
|
7964
|
+
const columnName =
|
|
7965
|
+
col.name.toLowerCase();
|
|
7966
|
+
// Date column detection
|
|
7967
|
+
if (
|
|
7968
|
+
columnName.includes(
|
|
7969
|
+
'date'
|
|
7970
|
+
) ||
|
|
7971
|
+
columnName.includes(
|
|
7972
|
+
'time'
|
|
7973
|
+
) ||
|
|
7974
|
+
columnName.includes(
|
|
7975
|
+
'created_at'
|
|
7976
|
+
) ||
|
|
7977
|
+
columnName.includes(
|
|
7978
|
+
'updated_at'
|
|
7979
|
+
)
|
|
7980
|
+
) {
|
|
7981
|
+
return 'date';
|
|
7982
|
+
}
|
|
7983
|
+
// Boolean column detection - enhanced logic
|
|
7984
|
+
if (
|
|
7985
|
+
columnName.includes(
|
|
7986
|
+
'is_'
|
|
7987
|
+
) ||
|
|
7988
|
+
columnName.includes(
|
|
7989
|
+
'has_'
|
|
7990
|
+
) ||
|
|
7991
|
+
columnName.includes(
|
|
7992
|
+
'confirmed'
|
|
7993
|
+
) ||
|
|
7994
|
+
columnName.includes(
|
|
7995
|
+
'active'
|
|
7996
|
+
) ||
|
|
7997
|
+
columnName.includes(
|
|
7998
|
+
'enabled'
|
|
7999
|
+
) ||
|
|
8000
|
+
columnName.includes(
|
|
8001
|
+
'completed'
|
|
8002
|
+
) ||
|
|
8003
|
+
columnName.includes(
|
|
8004
|
+
'stage'
|
|
8005
|
+
) ||
|
|
8006
|
+
columnName.includes(
|
|
8007
|
+
'final'
|
|
8008
|
+
) ||
|
|
8009
|
+
columnName.includes(
|
|
8010
|
+
'flag'
|
|
8011
|
+
) ||
|
|
8012
|
+
columnName.includes(
|
|
8013
|
+
'approved'
|
|
8014
|
+
) ||
|
|
8015
|
+
columnName.includes(
|
|
8016
|
+
'verified'
|
|
8017
|
+
) ||
|
|
8018
|
+
columnName.includes(
|
|
8019
|
+
'sent'
|
|
8020
|
+
) ||
|
|
8021
|
+
columnName.endsWith(
|
|
8022
|
+
'?'
|
|
8023
|
+
) || // Handle columns like "Confirmed?"
|
|
8024
|
+
(typeof value ===
|
|
8025
|
+
'number' &&
|
|
8026
|
+
(value ===
|
|
8027
|
+
0 ||
|
|
8028
|
+
value ===
|
|
8029
|
+
1)) ||
|
|
8030
|
+
(typeof value ===
|
|
8031
|
+
'string' &&
|
|
8032
|
+
(value ===
|
|
8033
|
+
'0' ||
|
|
8034
|
+
value ===
|
|
8035
|
+
'1'))
|
|
8036
|
+
) {
|
|
8037
|
+
return 'boolean';
|
|
8038
|
+
}
|
|
8039
|
+
return undefined; // Let formatCellContent auto-detect
|
|
8040
|
+
})(),
|
|
8041
|
+
}
|
|
8042
|
+
);
|
|
8043
|
+
|
|
8044
|
+
return formattedValue;
|
|
8045
|
+
})()}
|
|
8046
|
+
</td>
|
|
8047
|
+
)
|
|
8048
|
+
)}
|
|
8049
|
+
</tr>
|
|
8050
|
+
)
|
|
8051
|
+
);
|
|
7294
8052
|
})()}
|
|
7295
8053
|
</tbody>
|
|
7296
8054
|
</table>
|
|
7297
8055
|
</div>
|
|
8056
|
+
|
|
8057
|
+
{/* Pagination Controls */}
|
|
8058
|
+
{previewData.length > 0 && (
|
|
8059
|
+
<div
|
|
8060
|
+
className={
|
|
8061
|
+
styles.paginationContainer
|
|
8062
|
+
}
|
|
8063
|
+
>
|
|
8064
|
+
<div
|
|
8065
|
+
className={
|
|
8066
|
+
styles.paginationInfo
|
|
8067
|
+
}
|
|
8068
|
+
>
|
|
8069
|
+
{(() => {
|
|
8070
|
+
const startRecord =
|
|
8071
|
+
Math.min(
|
|
8072
|
+
(currentPage - 1) *
|
|
8073
|
+
pageSize +
|
|
8074
|
+
1,
|
|
8075
|
+
totalCount
|
|
8076
|
+
);
|
|
8077
|
+
const endRecord = Math.min(
|
|
8078
|
+
currentPage * pageSize,
|
|
8079
|
+
totalCount
|
|
8080
|
+
);
|
|
8081
|
+
return `Showing ${startRecord}-${endRecord} of ${totalCount} records`;
|
|
8082
|
+
})()}
|
|
8083
|
+
</div>
|
|
8084
|
+
|
|
8085
|
+
<div
|
|
8086
|
+
className={
|
|
8087
|
+
styles.paginationControls
|
|
8088
|
+
}
|
|
8089
|
+
>
|
|
8090
|
+
<div
|
|
8091
|
+
className={
|
|
8092
|
+
styles.pageSizeSelector
|
|
8093
|
+
}
|
|
8094
|
+
>
|
|
8095
|
+
<label>Show:</label>
|
|
8096
|
+
<select
|
|
8097
|
+
value={pageSize}
|
|
8098
|
+
onChange={(e) => {
|
|
8099
|
+
const newPageSize =
|
|
8100
|
+
parseInt(
|
|
8101
|
+
e.target
|
|
8102
|
+
.value
|
|
8103
|
+
);
|
|
8104
|
+
setPageSize(
|
|
8105
|
+
newPageSize
|
|
8106
|
+
);
|
|
8107
|
+
setCurrentPage(1); // Reset to first page
|
|
8108
|
+
// Re-execute the report with new page size
|
|
8109
|
+
if (
|
|
8110
|
+
previewData.length >
|
|
8111
|
+
0
|
|
8112
|
+
) {
|
|
8113
|
+
executeReport();
|
|
8114
|
+
}
|
|
8115
|
+
}}
|
|
8116
|
+
className={
|
|
8117
|
+
styles.pageSizeSelect
|
|
8118
|
+
}
|
|
8119
|
+
>
|
|
8120
|
+
<option value={10}>
|
|
8121
|
+
10
|
|
8122
|
+
</option>
|
|
8123
|
+
<option value={25}>
|
|
8124
|
+
25
|
|
8125
|
+
</option>
|
|
8126
|
+
<option value={50}>
|
|
8127
|
+
50
|
|
8128
|
+
</option>
|
|
8129
|
+
<option value={100}>
|
|
8130
|
+
100
|
|
8131
|
+
</option>
|
|
8132
|
+
<option value={250}>
|
|
8133
|
+
250
|
|
8134
|
+
</option>
|
|
8135
|
+
</select>
|
|
8136
|
+
<span>records</span>
|
|
8137
|
+
</div>
|
|
8138
|
+
|
|
8139
|
+
<div
|
|
8140
|
+
className={
|
|
8141
|
+
styles.pageNavigation
|
|
8142
|
+
}
|
|
8143
|
+
>
|
|
8144
|
+
<button
|
|
8145
|
+
onClick={() => {
|
|
8146
|
+
if (
|
|
8147
|
+
currentPage > 1
|
|
8148
|
+
) {
|
|
8149
|
+
const newPage =
|
|
8150
|
+
currentPage -
|
|
8151
|
+
1;
|
|
8152
|
+
setCurrentPage(
|
|
8153
|
+
newPage
|
|
8154
|
+
);
|
|
8155
|
+
executeReport();
|
|
8156
|
+
}
|
|
8157
|
+
}}
|
|
8158
|
+
disabled={
|
|
8159
|
+
currentPage <= 1
|
|
8160
|
+
}
|
|
8161
|
+
className={`${
|
|
8162
|
+
styles.pageBtn
|
|
8163
|
+
} ${
|
|
8164
|
+
currentPage <= 1
|
|
8165
|
+
? styles.disabled
|
|
8166
|
+
: ''
|
|
8167
|
+
}`}
|
|
8168
|
+
>
|
|
8169
|
+
‹ Previous
|
|
8170
|
+
</button>
|
|
8171
|
+
|
|
8172
|
+
<div
|
|
8173
|
+
className={
|
|
8174
|
+
styles.pageNumbers
|
|
8175
|
+
}
|
|
8176
|
+
>
|
|
8177
|
+
{(() => {
|
|
8178
|
+
const maxPagesToShow = 5;
|
|
8179
|
+
const totalPages =
|
|
8180
|
+
Math.ceil(
|
|
8181
|
+
totalCount /
|
|
8182
|
+
pageSize
|
|
8183
|
+
);
|
|
8184
|
+
let startPage =
|
|
8185
|
+
Math.max(
|
|
8186
|
+
1,
|
|
8187
|
+
currentPage -
|
|
8188
|
+
Math.floor(
|
|
8189
|
+
maxPagesToShow /
|
|
8190
|
+
2
|
|
8191
|
+
)
|
|
8192
|
+
);
|
|
8193
|
+
let endPage =
|
|
8194
|
+
Math.min(
|
|
8195
|
+
totalPages,
|
|
8196
|
+
startPage +
|
|
8197
|
+
maxPagesToShow -
|
|
8198
|
+
1
|
|
8199
|
+
);
|
|
8200
|
+
|
|
8201
|
+
// Adjust start if we're near the end
|
|
8202
|
+
if (
|
|
8203
|
+
endPage -
|
|
8204
|
+
startPage <
|
|
8205
|
+
maxPagesToShow -
|
|
8206
|
+
1
|
|
8207
|
+
) {
|
|
8208
|
+
startPage =
|
|
8209
|
+
Math.max(
|
|
8210
|
+
1,
|
|
8211
|
+
endPage -
|
|
8212
|
+
maxPagesToShow +
|
|
8213
|
+
1
|
|
8214
|
+
);
|
|
8215
|
+
}
|
|
8216
|
+
|
|
8217
|
+
const pages = [];
|
|
8218
|
+
|
|
8219
|
+
// Add first page and ellipsis if needed
|
|
8220
|
+
if (startPage > 1) {
|
|
8221
|
+
pages.push(
|
|
8222
|
+
<button
|
|
8223
|
+
key={1}
|
|
8224
|
+
onClick={() => {
|
|
8225
|
+
setCurrentPage(
|
|
8226
|
+
1
|
|
8227
|
+
);
|
|
8228
|
+
executeReport();
|
|
8229
|
+
}}
|
|
8230
|
+
className={`${
|
|
8231
|
+
styles.pageBtn
|
|
8232
|
+
} ${
|
|
8233
|
+
1 ===
|
|
8234
|
+
currentPage
|
|
8235
|
+
? styles.active
|
|
8236
|
+
: ''
|
|
8237
|
+
}`}
|
|
8238
|
+
>
|
|
8239
|
+
1
|
|
8240
|
+
</button>
|
|
8241
|
+
);
|
|
8242
|
+
if (
|
|
8243
|
+
startPage >
|
|
8244
|
+
2
|
|
8245
|
+
) {
|
|
8246
|
+
pages.push(
|
|
8247
|
+
<span
|
|
8248
|
+
key="start-ellipsis"
|
|
8249
|
+
className={
|
|
8250
|
+
styles.ellipsis
|
|
8251
|
+
}
|
|
8252
|
+
>
|
|
8253
|
+
...
|
|
8254
|
+
</span>
|
|
8255
|
+
);
|
|
8256
|
+
}
|
|
8257
|
+
}
|
|
8258
|
+
|
|
8259
|
+
// Add visible page numbers
|
|
8260
|
+
for (
|
|
8261
|
+
let i =
|
|
8262
|
+
startPage;
|
|
8263
|
+
i <= endPage;
|
|
8264
|
+
i++
|
|
8265
|
+
) {
|
|
8266
|
+
pages.push(
|
|
8267
|
+
<button
|
|
8268
|
+
key={i}
|
|
8269
|
+
onClick={() => {
|
|
8270
|
+
setCurrentPage(
|
|
8271
|
+
i
|
|
8272
|
+
);
|
|
8273
|
+
executeReport();
|
|
8274
|
+
}}
|
|
8275
|
+
className={`${
|
|
8276
|
+
styles.pageBtn
|
|
8277
|
+
} ${
|
|
8278
|
+
i ===
|
|
8279
|
+
currentPage
|
|
8280
|
+
? styles.active
|
|
8281
|
+
: ''
|
|
8282
|
+
}`}
|
|
8283
|
+
>
|
|
8284
|
+
{i}
|
|
8285
|
+
</button>
|
|
8286
|
+
);
|
|
8287
|
+
}
|
|
8288
|
+
|
|
8289
|
+
// Add ellipsis and last page if needed
|
|
8290
|
+
if (
|
|
8291
|
+
endPage <
|
|
8292
|
+
totalPages
|
|
8293
|
+
) {
|
|
8294
|
+
if (
|
|
8295
|
+
endPage <
|
|
8296
|
+
totalPages -
|
|
8297
|
+
1
|
|
8298
|
+
) {
|
|
8299
|
+
pages.push(
|
|
8300
|
+
<span
|
|
8301
|
+
key="end-ellipsis"
|
|
8302
|
+
className={
|
|
8303
|
+
styles.ellipsis
|
|
8304
|
+
}
|
|
8305
|
+
>
|
|
8306
|
+
...
|
|
8307
|
+
</span>
|
|
8308
|
+
);
|
|
8309
|
+
}
|
|
8310
|
+
pages.push(
|
|
8311
|
+
<button
|
|
8312
|
+
key={
|
|
8313
|
+
totalPages
|
|
8314
|
+
}
|
|
8315
|
+
onClick={() => {
|
|
8316
|
+
setCurrentPage(
|
|
8317
|
+
totalPages
|
|
8318
|
+
);
|
|
8319
|
+
executeReport();
|
|
8320
|
+
}}
|
|
8321
|
+
className={`${
|
|
8322
|
+
styles.pageBtn
|
|
8323
|
+
} ${
|
|
8324
|
+
totalPages ===
|
|
8325
|
+
currentPage
|
|
8326
|
+
? styles.active
|
|
8327
|
+
: ''
|
|
8328
|
+
}`}
|
|
8329
|
+
>
|
|
8330
|
+
{
|
|
8331
|
+
totalPages
|
|
8332
|
+
}
|
|
8333
|
+
</button>
|
|
8334
|
+
);
|
|
8335
|
+
}
|
|
8336
|
+
|
|
8337
|
+
return pages;
|
|
8338
|
+
})()}
|
|
8339
|
+
</div>
|
|
8340
|
+
|
|
8341
|
+
<button
|
|
8342
|
+
onClick={() => {
|
|
8343
|
+
const totalPages =
|
|
8344
|
+
Math.ceil(
|
|
8345
|
+
totalCount /
|
|
8346
|
+
pageSize
|
|
8347
|
+
);
|
|
8348
|
+
if (
|
|
8349
|
+
currentPage <
|
|
8350
|
+
totalPages
|
|
8351
|
+
) {
|
|
8352
|
+
const newPage =
|
|
8353
|
+
currentPage +
|
|
8354
|
+
1;
|
|
8355
|
+
setCurrentPage(
|
|
8356
|
+
newPage
|
|
8357
|
+
);
|
|
8358
|
+
executeReport();
|
|
8359
|
+
}
|
|
8360
|
+
}}
|
|
8361
|
+
disabled={
|
|
8362
|
+
currentPage >=
|
|
8363
|
+
Math.ceil(
|
|
8364
|
+
totalCount /
|
|
8365
|
+
pageSize
|
|
8366
|
+
)
|
|
8367
|
+
}
|
|
8368
|
+
className={`${
|
|
8369
|
+
styles.pageBtn
|
|
8370
|
+
} ${
|
|
8371
|
+
currentPage >=
|
|
8372
|
+
Math.ceil(
|
|
8373
|
+
totalCount /
|
|
8374
|
+
pageSize
|
|
8375
|
+
)
|
|
8376
|
+
? styles.disabled
|
|
8377
|
+
: ''
|
|
8378
|
+
}`}
|
|
8379
|
+
>
|
|
8380
|
+
Next ›
|
|
8381
|
+
</button>
|
|
8382
|
+
</div>
|
|
8383
|
+
</div>
|
|
8384
|
+
</div>
|
|
8385
|
+
)}
|
|
7298
8386
|
</div>
|
|
7299
8387
|
</div>
|
|
7300
8388
|
</div>
|
|
@@ -7328,10 +8416,44 @@ const GenericReportImproved = ({
|
|
|
7328
8416
|
)}
|
|
7329
8417
|
</div>
|
|
7330
8418
|
</div>
|
|
7331
|
-
|
|
7332
8419
|
</div>
|
|
7333
8420
|
</>
|
|
7334
8421
|
)}
|
|
8422
|
+
|
|
8423
|
+
{/* Show no data message when no results found */}
|
|
8424
|
+
{!groupedData &&
|
|
8425
|
+
previewData.length === 0 &&
|
|
8426
|
+
!isLoading &&
|
|
8427
|
+
selectedTable &&
|
|
8428
|
+
selectedColumns.length > 0 && (
|
|
8429
|
+
<div className={styles.noDataContainer}>
|
|
8430
|
+
<div className={styles.noDataMessage}>
|
|
8431
|
+
<div className={styles.noDataIcon}>
|
|
8432
|
+
<Database size={48} />
|
|
8433
|
+
</div>
|
|
8434
|
+
<h3 className={styles.noDataTitle}>
|
|
8435
|
+
No Data Found
|
|
8436
|
+
</h3>
|
|
8437
|
+
<p className={styles.noDataText}>
|
|
8438
|
+
No records match the selected filters and
|
|
8439
|
+
criteria.
|
|
8440
|
+
</p>
|
|
8441
|
+
<div className={styles.noDataSuggestions}>
|
|
8442
|
+
<p>
|
|
8443
|
+
<strong>Try:</strong>
|
|
8444
|
+
</p>
|
|
8445
|
+
<ul>
|
|
8446
|
+
<li>Adjusting or removing filters</li>
|
|
8447
|
+
<li>Selecting a broader date range</li>
|
|
8448
|
+
<li>
|
|
8449
|
+
Checking if data exists for the
|
|
8450
|
+
selected table
|
|
8451
|
+
</li>
|
|
8452
|
+
</ul>
|
|
8453
|
+
</div>
|
|
8454
|
+
</div>
|
|
8455
|
+
</div>
|
|
8456
|
+
)}
|
|
7335
8457
|
</div>
|
|
7336
8458
|
);
|
|
7337
8459
|
};
|