@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.
@@ -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: 'Group your data by a specific field for better organization',
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(25); // Default to 25 for better UX
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 = grouping?.enabled || selectedTemplate?.grouping?.enabled;
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 = customUrls.executeUrl || selectedTemplate?.executeUrl || executeUrl;
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: selectedTemplate?.id || 'utilization_planning',
2285
- filters: []
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('❌ Grouped report execution error:', result.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 = customUrls.executeUrl || selectedTemplate?.executeUrl || executeUrl;
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('🎯 [DEBUG] Initial fetchedDataCount:', fetchedDataCount);
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('🏗️ [DEBUG] Created dynamic template for custom grouping:', dynamicTemplate);
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('Please select a field to group by before executing the report.');
2497
- console.log('⚠️ [DEBUG] Custom grouping enabled but no groupByField selected');
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('📋 [DEBUG] Using template grouping:', effectiveGrouping);
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('🎯 [DEBUG] groupedResult.data:', groupedResult.data);
2518
- console.log('🎯 [DEBUG] groupedResult.data.grouped:', groupedResult.data?.grouped);
2519
- console.log('🎯 [DEBUG] groupedResult.data.groupedData:', groupedResult.data?.groupedData);
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 (groupedResult.data?.grouped && groupedResult.data?.groupedData?.groups) {
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('🎯 [DEBUG] Before assignment - groupedResult.data.groupedData.totalRecords:', groupedResult.data.groupedData?.totalRecords);
2527
- fetchedDataCount = groupedResult.data.groupedData?.totalRecords || 0;
2528
- console.log('🎯 [DEBUG] After assignment - fetchedDataCount:', fetchedDataCount);
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: groupedResult.data.groupedData?.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('🎯 [DEBUG] Fallback data length:', fallbackData.length);
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 firstPageResult = await dataSource({
2545
- skip: 0,
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 (firstPageResult.data && firstPageResult.data.length > 0) {
2552
- setPreviewData(firstPageResult.data);
2553
- fetchedDataCount = firstPageResult.data.length;
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
- // Find the column metadata from selected columns to get type information
2566
- const columnMetadata = selectedColumns.find((col) => {
2567
- // Handle both direct column names and aliased names
2568
- return (
2569
- col.column === key ||
2570
- col.displayName === key ||
2571
- key.includes(col.column) ||
2572
- col.column.includes(key)
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
- // Check if any value in this column is JSON
2577
- const hasJsonValues = dataToProcess.some((row) => {
2578
- return isJsonValue(row[key]);
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
- // Try to find column type from table columns with improved detection
2582
- let columnType = 'varchar'; // default
2583
- if (columnMetadata) {
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 === columnMetadata.column
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
- // Enhanced column type detection for status fields
2608
- if (!columnMetadata || columnType === 'varchar') {
2609
- // Check if this looks like a status field based on column name
2610
- const lowerKey = key.toLowerCase();
2611
- if (
2612
- lowerKey.includes('status') ||
2613
- lowerKey.includes('active') ||
2614
- lowerKey.includes('enabled') ||
2615
- lowerKey.includes('disabled')
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
- // For JSON content, wrap in a container that allows full height expansion
2653
- if (isJsonValue(value)) {
2654
- return (
2655
- <div
2656
- style={{
2657
- whiteSpace: 'normal',
2658
- wordWrap: 'break-word',
2659
- lineHeight: '1.4',
2660
- padding: '4px 0',
2661
- width: '100%',
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
- return formattedValue;
2670
- },
2671
- };
2672
- });
2673
- setGridColumns(dynamicColumns);
2674
- } else {
2675
- // Fallback to selected columns if no data
2676
- const columns = selectedColumns.map((col) => {
2677
- // Find column type from table columns with enhanced detection
2678
- let columnType = 'varchar';
2679
- const tableColumn = tableColumns.find(
2680
- (tc) => tc.name === col.column
2681
- );
2682
- if (tableColumn) {
2683
- columnType = tableColumn.type;
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
- // For JSON content, wrap in a container that allows full height expansion
2724
- if (isJsonValue(value)) {
2725
- return (
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
- // Show success message with available data count
2753
- console.log('🎯 [DEBUG] Final fetchedDataCount before toast:', fetchedDataCount);
2754
- console.log('🎯 [DEBUG] Type of fetchedDataCount:', typeof fetchedDataCount);
2755
- toast.success(`Found ${fetchedDataCount} records`);
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: config.grouping.groupHeaderStyle || 'primary',
2857
- emptyRowStyle: config.grouping.emptyRowStyle || 'light'
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 filter criteria
2970
- if (template.quickFilters) {
2971
- const defaultFilters = template.quickFilters
2972
- .filter(
2973
- (filter) =>
2974
- filter.value && filter.value !== 'current_user'
2975
- ) // Skip user-specific filters for now
2976
- .map((filter) => ({
2977
- id: filter.id,
2978
- field: filter.field,
2979
- operator: filter.operator || '=',
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
- if (defaultFilters.length > 0) {
2985
- setFilterCriteria({
2986
- operator: 'AND',
2987
- groups: [
2988
- {
2989
- operator: 'AND',
2990
- filters: defaultFilters,
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: template.grouping.groupHeaderStyle || 'primary',
3010
- emptyRowStyle: template.grouping.emptyRowStyle || 'light'
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
- // Apply template-based quick filter
4645
- const applyTemplateQuickFilter = (quickFilter) => {
4646
- let filterValue = quickFilter.value;
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
- filterValue = {
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
- filterValue = {
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
- filterValue = {
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
- filterValue = {
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
- filterValue = {
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
- break;
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 = customUrls.exportUrl || selectedTemplate?.exportUrl || exportUrl;
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: groupedData && groupedData.groups ? groupedData.groups.length : 'undefined',
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: !!(groupedData && groupedData.groupedData),
6573
- hasGroups: !!(groupedData && groupedData.groupedData && groupedData.groupedData.groups),
6574
- groupsLength: actualGroups ? actualGroups.length : 'undefined'
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(col => col.displayName || col.column);
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 (column.displayName && row[`\`${column.displayName}\``] !== undefined) {
6741
+ if (
6742
+ column.displayName &&
6743
+ row[`\`${column.displayName}\``] !== undefined
6744
+ ) {
6599
6745
  value = row[`\`${column.displayName}\``];
6600
- } else if (column.displayName && row[column.displayName] !== undefined) {
6746
+ } else if (
6747
+ column.displayName &&
6748
+ row[column.displayName] !== undefined
6749
+ ) {
6601
6750
  value = row[column.displayName];
6602
- } else if (column.table && row[`${column.table}_${column.column}`] !== undefined) {
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 (column.table && row[`\`${column.table}_${column.column}\``] !== undefined) {
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.replace(/• /g, '').replace(/<[^>]*>/g, '').trim();
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 ((column.displayName && column.displayName.toLowerCase().includes('abn')) ||
6633
- (column.column && column.column.toLowerCase().includes('abn'))) {
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,2)} ${cleanABN.slice(2,5)} ${cleanABN.slice(5,8)} ${cleanABN.slice(8,11)}`;
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 ((column.displayName && column.displayName.toLowerCase().includes('acn')) ||
6642
- (column.column && column.column.toLowerCase().includes('acn'))) {
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,3)} ${cleanACN.slice(3,6)} ${cleanACN.slice(6,9)}`;
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 = selectedTemplate?.grouping?.groupDisplayName || 'Group';
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 => getCellValue(row, 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': return { wch: 40 };
6854
+ case 'Comments':
6855
+ return { wch: 40 };
6680
6856
  case 'Project':
6681
- case 'Project Name': return { wch: 25 };
6857
+ case 'Project Name':
6858
+ return { wch: 25 };
6682
6859
  case 'User':
6683
- case 'Assigned User': return { wch: 20 };
6860
+ case 'Assigned User':
6861
+ return { wch: 20 };
6684
6862
  case 'Start Date':
6685
6863
  case 'End Date':
6686
- case 'Database Updated': return { wch: 15 };
6687
- default: return { wch: 18 };
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(/[^a-zA-Z0-9]/g, '_')}_grouped_report_${new Date().toISOString().split('T')[0]}.xlsx`;
6699
- const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
6700
- const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
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 ? groupingConfig : null,
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) => setGroupingConfig({
6855
- ...groupingConfig,
6856
- enabled: e.target.checked,
6857
- // Reset fields when disabled
6858
- ...(e.target.checked ? {} : {
6859
- groupByField: '',
6860
- groupDisplayName: ''
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}>Enable Grouping</span>
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 sections (e.g., "Projects by Facility")
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}>Group By Field *</label>
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 = e.target.selectedOptions[0];
6882
- const fieldDisplayName = selectedOption?.text || '';
7078
+ const selectedOption =
7079
+ e.target.selectedOptions[0];
7080
+ const fieldDisplayName =
7081
+ selectedOption?.text || '';
6883
7082
  // Extract table name for display
6884
- const tableMatch = fieldDisplayName.match(/^([^.]+)\./);
6885
- const tableName = tableMatch ? tableMatch[1] : 'Group';
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: groupingConfig.groupDisplayName || tableName,
6891
- sortBy: e.target.value // Auto-set sort field
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="">Select field to group by...</option>
6897
- {getAvailableFilterColumns().map((col, index) => {
6898
- const fieldValue = `${col.table}.${col.column}`;
6899
- const displayName = `${col.tableName}.${col.columnName}`;
6900
- return (
6901
- <option key={index} value={fieldValue}>
6902
- {displayName}
6903
- </option>
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 categories you want to group by
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}>Group Display Name</label>
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) => setGroupingConfig({
6919
- ...groupingConfig,
6920
- groupDisplayName: e.target.value
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., "Facility: Main Building")
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}>Advanced Options</h4>
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}>Rows Per Group</label>
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) => setGroupingConfig({
6943
- ...groupingConfig,
6944
- rowsPerGroup: parseInt(e.target.value) || 10
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}>Group Header Style</label>
7179
+ <label className={styles.formLabel}>
7180
+ Group Header Style
7181
+ </label>
6952
7182
  <select
6953
- value={groupingConfig.groupHeaderStyle}
6954
- onChange={(e) => setGroupingConfig({
6955
- ...groupingConfig,
6956
- groupHeaderStyle: e.target.value
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">Primary (Blue)</option>
6961
- <option value="secondary">Secondary (Gray)</option>
6962
- <option value="success">Success (Green)</option>
6963
- <option value="warning">Warning (Orange)</option>
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}>Sort Direction</label>
7211
+ <label className={styles.formLabel}>
7212
+ Sort Direction
7213
+ </label>
6969
7214
  <select
6970
7215
  value={groupingConfig.sortDirection}
6971
- onChange={(e) => setGroupingConfig({
6972
- ...groupingConfig,
6973
- sortDirection: e.target.value
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">Ascending (A-Z)</option>
6978
- <option value="desc">Descending (Z-A)</option>
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={groupingConfig.showEmptyRows}
6988
- onChange={(e) => setGroupingConfig({
6989
- ...groupingConfig,
6990
- showEmptyRows: e.target.checked
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}>Show Empty Rows</span>
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={groupingConfig.showGroupTotals}
7000
- onChange={(e) => setGroupingConfig({
7001
- ...groupingConfig,
7002
- showGroupTotals: e.target.checked
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}>Show Group Totals</span>
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) => setGroupingConfig({
7013
- ...groupingConfig,
7014
- expandable: e.target.checked
7015
- })}
7278
+ onChange={(e) =>
7279
+ setGroupingConfig({
7280
+ ...groupingConfig,
7281
+ expandable:
7282
+ e.target.checked,
7283
+ })
7284
+ }
7016
7285
  />
7017
- <span className={styles.checkboxText}>Collapsible Groups</span>
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}>Configuration Preview</h4>
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>Options:</strong>
7032
- {groupingConfig.showEmptyRows && ' Empty Rows'}
7033
- {groupingConfig.showGroupTotals && ' Group Totals'}
7034
- {groupingConfig.expandable && ' Collapsible'}
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
- <div className={styles.customUrlsSection}>
7044
- <h4 className={styles.sectionTitle}>Custom API Endpoints (Optional)</h4>
7045
- <p className={styles.helpText}>
7046
- Override default API endpoints for specialized report handling
7047
- </p>
7048
-
7049
- <div className={styles.optionsGrid}>
7050
- <div className={styles.formGroup}>
7051
- <label className={styles.formLabel}>Custom Execute URL</label>
7052
- <input
7053
- type="text"
7054
- value={customUrls.executeUrl}
7055
- onChange={(e) => setCustomUrls({
7056
- ...customUrls,
7057
- executeUrl: e.target.value
7058
- })}
7059
- placeholder="/api/custom-reports/execute"
7060
- className={styles.formInput}
7061
- />
7062
- <p className={styles.fieldHelp}>
7063
- Custom endpoint for report execution (overrides template and default URLs)
7064
- </p>
7065
- </div>
7066
-
7067
- <div className={styles.formGroup}>
7068
- <label className={styles.formLabel}>Custom Export URL</label>
7069
- <input
7070
- type="text"
7071
- value={customUrls.exportUrl}
7072
- onChange={(e) => setCustomUrls({
7073
- ...customUrls,
7074
- exportUrl: e.target.value
7075
- })}
7076
- placeholder="/api/custom-reports/export"
7077
- className={styles.formInput}
7078
- />
7079
- <p className={styles.fieldHelp}>
7080
- Custom endpoint for report exports (for server-side exports)
7081
- </p>
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
- </div>
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 || selectedTemplate?.grouping?.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} ${styles.btnSecondary} ${
7113
- groupedData ? styles.active : ''
7114
- }`}
7417
+ className={`${styles.btn} ${
7418
+ styles.btnSecondary
7419
+ } ${groupedData ? styles.active : ''}`}
7115
7420
  onClick={() => {
7116
- if (!groupedData && previewData.length > 0) {
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} ${styles.btnSecondary} ${
7128
- !groupedData ? styles.active : ''
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('🎯 [DEBUG] Render check - groupedData:', groupedData);
7151
- console.log('🎯 [DEBUG] Render check - groupedData?.grouped:', groupedData?.grouped);
7152
- console.log('🎯 [DEBUG] Render check - selectedColumns:', selectedColumns);
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">Report Results</h3>
7183
- <span className="group-stats">({previewData.length} records)</span>
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={() => handleRegularExport()}
7189
- disabled={isLoading || previewData.length === 0}
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 = previewData.length > 0 ? Object.keys(previewData[0]).map(key => ({name: key, header: key.replace(/\./g, ' ')})) : gridColumns;
7202
- return headerColumns.map((col, index) => (
7203
- <th key={index} className="table-header">
7204
- {col.header || col.name}
7205
- </th>
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(`🎯 [DEBUG] Rendering ${previewData.length} table rows`);
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 = previewData.length > 0 ? Object.keys(previewData[0]).map(key => ({name: key, header: key.replace(/\./g, ' ')})) : gridColumns;
7215
- console.log(`🎯 [DEBUG] Using columns:`, actualColumns);
7216
- return previewData.map((row, rowIndex) => (
7217
- <tr key={rowIndex} className="data-row">
7218
- {actualColumns.map((col, colIndex) => (
7219
- <td key={colIndex} className="table-cell">
7220
- {(() => {
7221
- const value = row[col.name];
7222
-
7223
- // Handle null/undefined
7224
- if (value === null || value === undefined) {
7225
- return <span style={{color: '#6c757d', fontStyle: 'italic'}}>—</span>;
7226
- }
7227
-
7228
- // Handle JSON strings
7229
- if (typeof value === 'string' && value.startsWith('{') && value.endsWith('}')) {
7230
- try {
7231
- const parsed = JSON.parse(value);
7232
- return (
7233
- <div style={{fontSize: '0.85em', lineHeight: '1.3'}}>
7234
- {Object.entries(parsed).map(([key, val]) => (
7235
- <div key={key} style={{marginBottom: '2px'}}>
7236
- <strong>{key.replace(/_/g, ' ')}:</strong> {val || '—'}
7237
- </div>
7238
- ))}
7239
- </div>
7240
- );
7241
- } catch (e) {
7242
- return value;
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
- // Handle other boolean values
7276
- if (typeof value === 'boolean') {
7277
- return value ?
7278
- <span style={{color: '#28a745', fontWeight: '500'}}>Yes</span> :
7279
- <span style={{color: '#6c757d', fontWeight: '500'}}>No</span>;
7280
- }
7281
-
7282
- // Handle numeric values
7283
- if (typeof value === 'number' && !col.name.toLowerCase().includes('status')) {
7284
- return value.toLocaleString();
7285
- }
7286
-
7287
- // Default string handling
7288
- return value;
7289
- })()}
7290
- </td>
7291
- ))}
7292
- </tr>
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
  };