@visns-studio/visns-components 6.1.4 → 6.1.6
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/package.json +1 -1
- package/src/components/generic/GenericReport.jsx +793 -750
- package/src/components/generic/GroupedReportRenderer.jsx +21 -24
- package/src/components/generic/SectionGroupedReport.jsx +54 -94
- package/src/components/generic/groupedReport.css +168 -124
- package/src/components/styles/GenericDetail.module.scss +76 -32
- package/src/components/styles/GenericFormBuilder.module.scss +12 -11
- package/src/components/styles/GenericIndex.module.scss +93 -23
- package/src/components/styles/GenericReport.module.scss +938 -1018
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { toast } from 'react-toastify';
|
|
4
4
|
import moment from 'moment';
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from 'lucide-react';
|
|
35
35
|
import CustomFetch from '../Fetch';
|
|
36
36
|
import Download from '../Download';
|
|
37
|
+
import debugLog from '../utils/debugLog';
|
|
37
38
|
import { saveAs } from 'file-saver';
|
|
38
39
|
import * as XLSX from 'xlsx';
|
|
39
40
|
import Swal from 'sweetalert2';
|
|
@@ -287,6 +288,31 @@ const formatName = (name) => {
|
|
|
287
288
|
.join(' ');
|
|
288
289
|
};
|
|
289
290
|
|
|
291
|
+
// A saved report's `detail` arrives as either a JSON string or an object,
|
|
292
|
+
// and older rows can hold malformed JSON — never let that reach the renderer.
|
|
293
|
+
const parseReportDetail = (detail) => {
|
|
294
|
+
if (!detail) return null;
|
|
295
|
+
if (typeof detail !== 'string') {
|
|
296
|
+
return typeof detail === 'object' ? detail : null;
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
const parsed = JSON.parse(detail);
|
|
300
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
debugLog('Could not parse saved report detail:', error);
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// Escape values interpolated into SweetAlert HTML strings
|
|
308
|
+
const escapeHtml = (value) =>
|
|
309
|
+
String(value ?? '')
|
|
310
|
+
.replace(/&/g, '&')
|
|
311
|
+
.replace(/</g, '<')
|
|
312
|
+
.replace(/>/g, '>')
|
|
313
|
+
.replace(/"/g, '"')
|
|
314
|
+
.replace(/'/g, ''');
|
|
315
|
+
|
|
290
316
|
// Enhanced function to format pivot field names with context from end table
|
|
291
317
|
const formatPivotFieldName = (fieldName, pivotTableName, joins, definition = null) => {
|
|
292
318
|
if (!fieldName || !pivotTableName) return formatName(fieldName);
|
|
@@ -747,8 +773,9 @@ const formatSmartValue = (value, columnName, columnType) => {
|
|
|
747
773
|
|
|
748
774
|
// Smart field filtering - hide technical fields by default
|
|
749
775
|
const shouldHideField = (fieldName, fieldType) => {
|
|
750
|
-
|
|
751
|
-
const
|
|
776
|
+
if (!fieldName) return false;
|
|
777
|
+
const name = String(fieldName).toLowerCase();
|
|
778
|
+
const type = String(fieldType || '').toLowerCase();
|
|
752
779
|
|
|
753
780
|
// Hide foreign keys and primary key IDs (confusing for non-dev users)
|
|
754
781
|
if (name.endsWith('_id') || name === 'id') return true;
|
|
@@ -828,9 +855,10 @@ const categorizeFields = (columns, showHiddenFields = false) => {
|
|
|
828
855
|
other: [],
|
|
829
856
|
};
|
|
830
857
|
|
|
831
|
-
columns.forEach((column) => {
|
|
832
|
-
|
|
833
|
-
const
|
|
858
|
+
(columns || []).forEach((column) => {
|
|
859
|
+
if (!column || !column.name) return;
|
|
860
|
+
const name = String(column.name).toLowerCase();
|
|
861
|
+
const type = String(column.type || '').toLowerCase();
|
|
834
862
|
|
|
835
863
|
// Check if field should be hidden
|
|
836
864
|
if (shouldHideField(column.name, column.type)) {
|
|
@@ -1022,8 +1050,14 @@ const GenericReportImproved = ({
|
|
|
1022
1050
|
const [selectedTable, setSelectedTable] = useState(null);
|
|
1023
1051
|
const [tableColumns, setTableColumns] = useState([]);
|
|
1024
1052
|
const [isLoadingTables, setIsLoadingTables] = useState(false);
|
|
1053
|
+
const [isLoadingColumns, setIsLoadingColumns] = useState(false);
|
|
1025
1054
|
const [isLoadingJoinColumns, setIsLoadingJoinColumns] = useState(false);
|
|
1026
1055
|
|
|
1056
|
+
// Human-readable failure messages per fetch step (null = no failure)
|
|
1057
|
+
const [tablesError, setTablesError] = useState(null);
|
|
1058
|
+
const [columnsError, setColumnsError] = useState(null);
|
|
1059
|
+
const [reportsError, setReportsError] = useState(null);
|
|
1060
|
+
|
|
1027
1061
|
// State for report configuration
|
|
1028
1062
|
const [selectedColumns, setSelectedColumns] = useState([]);
|
|
1029
1063
|
const [availableTables, setAvailableTables] = useState([]);
|
|
@@ -1126,11 +1160,7 @@ const GenericReportImproved = ({
|
|
|
1126
1160
|
enabled: false,
|
|
1127
1161
|
selectedField: '',
|
|
1128
1162
|
});
|
|
1129
|
-
|
|
1130
|
-
// Debug logging for uniqueFieldConfig changes
|
|
1131
|
-
useEffect(() => {
|
|
1132
|
-
console.log('🔍 [EFFECT] uniqueFieldConfig changed:', uniqueFieldConfig);
|
|
1133
|
-
}, [uniqueFieldConfig]);
|
|
1163
|
+
|
|
1134
1164
|
const [excludedRows, setExcludedRows] = useState(new Set());
|
|
1135
1165
|
const [showRowExclusionModal, setShowRowExclusionModal] = useState(false);
|
|
1136
1166
|
const [exportType, setExportType] = useState('regular');
|
|
@@ -1509,11 +1539,46 @@ const GenericReportImproved = ({
|
|
|
1509
1539
|
hasAutoExecuted,
|
|
1510
1540
|
]);
|
|
1511
1541
|
|
|
1542
|
+
// Escape closes whichever modal is open
|
|
1543
|
+
useEffect(() => {
|
|
1544
|
+
if (!showCalculatedFieldModal && !showRowExclusionModal) return undefined;
|
|
1545
|
+
|
|
1546
|
+
const handleKeyDown = (event) => {
|
|
1547
|
+
if (event.key !== 'Escape') return;
|
|
1548
|
+
if (showCalculatedFieldModal) {
|
|
1549
|
+
handleCalculatedFieldCancel();
|
|
1550
|
+
} else {
|
|
1551
|
+
setShowRowExclusionModal(false);
|
|
1552
|
+
}
|
|
1553
|
+
};
|
|
1554
|
+
|
|
1555
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
1556
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
1557
|
+
}, [showCalculatedFieldModal, showRowExclusionModal]);
|
|
1558
|
+
|
|
1559
|
+
// Move focus into a modal when it opens so keyboard users start inside it
|
|
1560
|
+
const calculatedFieldModalRef = React.useRef(null);
|
|
1561
|
+
const rowExclusionModalRef = React.useRef(null);
|
|
1562
|
+
|
|
1563
|
+
useEffect(() => {
|
|
1564
|
+
if (showCalculatedFieldModal) {
|
|
1565
|
+
calculatedFieldModalRef.current?.focus();
|
|
1566
|
+
}
|
|
1567
|
+
}, [showCalculatedFieldModal]);
|
|
1568
|
+
|
|
1569
|
+
useEffect(() => {
|
|
1570
|
+
if (showRowExclusionModal) {
|
|
1571
|
+
rowExclusionModalRef.current?.focus();
|
|
1572
|
+
}
|
|
1573
|
+
}, [showRowExclusionModal]);
|
|
1574
|
+
|
|
1512
1575
|
// Delete saved report
|
|
1513
1576
|
const deleteReport = async (reportId, reportLabel) => {
|
|
1514
1577
|
const result = await Swal.fire({
|
|
1515
1578
|
title: 'Delete Report',
|
|
1516
|
-
html: `Are you sure you want to delete "<strong>${
|
|
1579
|
+
html: `Are you sure you want to delete "<strong>${escapeHtml(
|
|
1580
|
+
reportLabel
|
|
1581
|
+
)}</strong>"?<br><br>This action cannot be undone.`,
|
|
1517
1582
|
icon: 'warning',
|
|
1518
1583
|
showCancelButton: true,
|
|
1519
1584
|
confirmButtonText: 'Yes, Delete',
|
|
@@ -1545,18 +1610,13 @@ const GenericReportImproved = ({
|
|
|
1545
1610
|
}
|
|
1546
1611
|
} catch (error) {
|
|
1547
1612
|
toast.error('Failed to delete report');
|
|
1548
|
-
|
|
1613
|
+
debugLog('Delete error:', error);
|
|
1549
1614
|
}
|
|
1550
1615
|
}
|
|
1551
1616
|
};
|
|
1552
1617
|
|
|
1553
1618
|
// Start again function to reset wizard to home
|
|
1554
1619
|
const startAgain = () => {
|
|
1555
|
-
console.log('🔄 [DEBUG] StartAgain called - BEFORE reset:', {
|
|
1556
|
-
selectedTemplate: selectedTemplate?.id,
|
|
1557
|
-
customUrls: customUrls,
|
|
1558
|
-
groupingConfig: groupingConfig,
|
|
1559
|
-
});
|
|
1560
1620
|
|
|
1561
1621
|
// Reset wizard state
|
|
1562
1622
|
setCurrentWizardStep(0);
|
|
@@ -1664,12 +1724,6 @@ const GenericReportImproved = ({
|
|
|
1664
1724
|
// Reset UI state
|
|
1665
1725
|
setShowHiddenFields(false);
|
|
1666
1726
|
|
|
1667
|
-
console.log('✅ [DEBUG] StartAgain completed - AFTER reset:', {
|
|
1668
|
-
selectedTemplate: null,
|
|
1669
|
-
customUrls: { executeUrl: '', exportUrl: '' },
|
|
1670
|
-
groupingConfig: { enabled: false },
|
|
1671
|
-
});
|
|
1672
|
-
|
|
1673
1727
|
toast.info(
|
|
1674
1728
|
'Report builder reset. You can start creating a new report.'
|
|
1675
1729
|
);
|
|
@@ -1711,12 +1765,16 @@ const GenericReportImproved = ({
|
|
|
1711
1765
|
// Fetch database tables from the API
|
|
1712
1766
|
const fetchDatabaseTables = async () => {
|
|
1713
1767
|
if (!tableUrl) {
|
|
1714
|
-
|
|
1768
|
+
debugLog('No tableUrl provided to fetch database tables');
|
|
1715
1769
|
setIsLoadingTables(false);
|
|
1770
|
+
setTablesError(
|
|
1771
|
+
'No data source endpoint is configured for this report builder.'
|
|
1772
|
+
);
|
|
1716
1773
|
return;
|
|
1717
1774
|
}
|
|
1718
1775
|
|
|
1719
1776
|
setIsLoadingTables(true);
|
|
1777
|
+
setTablesError(null);
|
|
1720
1778
|
|
|
1721
1779
|
// List of tables to hide from the report builder
|
|
1722
1780
|
const hiddenTables = [
|
|
@@ -1738,6 +1796,7 @@ const GenericReportImproved = ({
|
|
|
1738
1796
|
const result = await CustomFetch(tableUrl, 'POST', {});
|
|
1739
1797
|
|
|
1740
1798
|
if (result.error) {
|
|
1799
|
+
setTablesError(result.error);
|
|
1741
1800
|
toast.error(result.error);
|
|
1742
1801
|
} else {
|
|
1743
1802
|
// Handle response format like the working advanced mode
|
|
@@ -1746,7 +1805,10 @@ const GenericReportImproved = ({
|
|
|
1746
1805
|
|
|
1747
1806
|
// Ensure tableData is an array
|
|
1748
1807
|
if (!Array.isArray(tableData)) {
|
|
1749
|
-
|
|
1808
|
+
debugLog('Unexpected table data format:', tableData);
|
|
1809
|
+
setTablesError(
|
|
1810
|
+
'The data source list came back in an unexpected format.'
|
|
1811
|
+
);
|
|
1750
1812
|
toast.error('Unexpected response format from table API');
|
|
1751
1813
|
return;
|
|
1752
1814
|
}
|
|
@@ -1755,8 +1817,9 @@ const GenericReportImproved = ({
|
|
|
1755
1817
|
let filteredTables = tableData
|
|
1756
1818
|
.filter((table) => {
|
|
1757
1819
|
const tableName =
|
|
1758
|
-
typeof table === 'string' ? table : table
|
|
1820
|
+
typeof table === 'string' ? table : table?.name;
|
|
1759
1821
|
return (
|
|
1822
|
+
typeof tableName === 'string' &&
|
|
1760
1823
|
!hiddenTables.includes(tableName) &&
|
|
1761
1824
|
!tableName.includes('_')
|
|
1762
1825
|
);
|
|
@@ -1786,16 +1849,18 @@ const GenericReportImproved = ({
|
|
|
1786
1849
|
// Sort alphabetically
|
|
1787
1850
|
filteredTables.sort((a, b) => a.name.localeCompare(b.name));
|
|
1788
1851
|
|
|
1789
|
-
console.log('Loaded tables:', filteredTables);
|
|
1790
1852
|
setTables(filteredTables);
|
|
1791
1853
|
|
|
1792
1854
|
if (filteredTables.length === 0) {
|
|
1793
|
-
|
|
1855
|
+
debugLog('No tables available after filtering');
|
|
1794
1856
|
}
|
|
1795
1857
|
}
|
|
1796
1858
|
} catch (error) {
|
|
1859
|
+
setTablesError(
|
|
1860
|
+
'We could not load the available data sources. Please try again.'
|
|
1861
|
+
);
|
|
1797
1862
|
toast.error('Failed to load database tables');
|
|
1798
|
-
|
|
1863
|
+
debugLog('Error fetching tables:', error);
|
|
1799
1864
|
} finally {
|
|
1800
1865
|
setIsLoadingTables(false);
|
|
1801
1866
|
}
|
|
@@ -1805,16 +1870,16 @@ const GenericReportImproved = ({
|
|
|
1805
1870
|
const fetchTableColumns = async (tableName) => {
|
|
1806
1871
|
if (!tableName || !columnUrl) return;
|
|
1807
1872
|
|
|
1808
|
-
|
|
1873
|
+
setIsLoadingColumns(true);
|
|
1874
|
+
setColumnsError(null);
|
|
1809
1875
|
|
|
1810
1876
|
try {
|
|
1811
1877
|
const result = await CustomFetch(columnUrl, 'POST', {
|
|
1812
1878
|
table: tableName,
|
|
1813
1879
|
});
|
|
1814
1880
|
|
|
1815
|
-
console.log('Column API Response:', result); // Debug log
|
|
1816
|
-
|
|
1817
1881
|
if (result.error) {
|
|
1882
|
+
setColumnsError(result.error);
|
|
1818
1883
|
toast.error(result.error);
|
|
1819
1884
|
} else {
|
|
1820
1885
|
// Handle the specific response format from getTableColumns (like GenericReport)
|
|
@@ -1839,23 +1904,28 @@ const GenericReportImproved = ({
|
|
|
1839
1904
|
|
|
1840
1905
|
if (Array.isArray(columnsData) && columnsData.length > 0) {
|
|
1841
1906
|
setTableColumns(columnsData);
|
|
1842
|
-
console.log('Loaded columns:', columnsData);
|
|
1843
1907
|
|
|
1844
1908
|
// Note: Auto-selection of columns removed since user should choose relationships first,
|
|
1845
1909
|
// then select columns in step 3
|
|
1846
1910
|
} else {
|
|
1847
|
-
|
|
1911
|
+
debugLog('Invalid columns data format:', result);
|
|
1848
1912
|
setTableColumns([]);
|
|
1913
|
+
setColumnsError(
|
|
1914
|
+
'No fields were returned for this data source.'
|
|
1915
|
+
);
|
|
1849
1916
|
toast.error(
|
|
1850
1917
|
'Failed to load columns. Please check the table configuration.'
|
|
1851
1918
|
);
|
|
1852
1919
|
}
|
|
1853
1920
|
}
|
|
1854
1921
|
} catch (error) {
|
|
1922
|
+
setColumnsError(
|
|
1923
|
+
'We could not load the fields for this data source. Please try again.'
|
|
1924
|
+
);
|
|
1855
1925
|
toast.error('Failed to load table columns');
|
|
1856
|
-
|
|
1926
|
+
debugLog('Error fetching columns:', error);
|
|
1857
1927
|
} finally {
|
|
1858
|
-
|
|
1928
|
+
setIsLoadingColumns(false);
|
|
1859
1929
|
}
|
|
1860
1930
|
};
|
|
1861
1931
|
|
|
@@ -1864,22 +1934,29 @@ const GenericReportImproved = ({
|
|
|
1864
1934
|
if (!reportsUrl) return;
|
|
1865
1935
|
|
|
1866
1936
|
setIsLoadingReports(true);
|
|
1937
|
+
setReportsError(null);
|
|
1867
1938
|
|
|
1868
1939
|
try {
|
|
1869
1940
|
const result = await CustomFetch(reportsUrl, 'GET');
|
|
1870
1941
|
|
|
1871
1942
|
if (result.error) {
|
|
1943
|
+
setReportsError(result.error);
|
|
1872
1944
|
toast.error(result.error);
|
|
1873
|
-
|
|
1874
|
-
setSavedReports(result.data.data);
|
|
1875
|
-
} else if (result.reports) {
|
|
1876
|
-
setSavedReports(result.reports);
|
|
1877
|
-
} else if (result.data) {
|
|
1878
|
-
setSavedReports(result.data);
|
|
1945
|
+
return;
|
|
1879
1946
|
}
|
|
1947
|
+
|
|
1948
|
+
const reportList =
|
|
1949
|
+
(result.data?.success && result.data?.data) ||
|
|
1950
|
+
result.reports ||
|
|
1951
|
+
result.data;
|
|
1952
|
+
|
|
1953
|
+
setSavedReports(Array.isArray(reportList) ? reportList : []);
|
|
1880
1954
|
} catch (error) {
|
|
1955
|
+
setReportsError(
|
|
1956
|
+
'We could not load your saved reports. Please try again.'
|
|
1957
|
+
);
|
|
1881
1958
|
toast.error('Failed to load saved reports');
|
|
1882
|
-
|
|
1959
|
+
debugLog('Error fetching reports:', error);
|
|
1883
1960
|
} finally {
|
|
1884
1961
|
setIsLoadingReports(false);
|
|
1885
1962
|
}
|
|
@@ -1888,23 +1965,20 @@ const GenericReportImproved = ({
|
|
|
1888
1965
|
// Fetch detected joins for a table
|
|
1889
1966
|
const fetchDetectedJoins = async (tableName) => {
|
|
1890
1967
|
if (!tableName || !detectedJoinsUrl) {
|
|
1891
|
-
|
|
1968
|
+
debugLog(
|
|
1892
1969
|
'Missing tableName or detectedJoinsUrl for fetchDetectedJoins'
|
|
1893
1970
|
);
|
|
1894
1971
|
return;
|
|
1895
1972
|
}
|
|
1896
1973
|
|
|
1897
|
-
setIsLoadingDetectedJoins({
|
|
1898
|
-
...isLoadingDetectedJoins,
|
|
1899
|
-
[tableName]: true,
|
|
1900
|
-
});
|
|
1974
|
+
setIsLoadingDetectedJoins((prev) => ({ ...prev, [tableName]: true }));
|
|
1901
1975
|
|
|
1902
1976
|
try {
|
|
1903
1977
|
const result = await CustomFetch(detectedJoinsUrl, 'POST', {
|
|
1904
1978
|
table: tableName,
|
|
1905
1979
|
});
|
|
1906
1980
|
|
|
1907
|
-
|
|
1981
|
+
// Debug log
|
|
1908
1982
|
|
|
1909
1983
|
if (result.error) {
|
|
1910
1984
|
toast.error(result.error);
|
|
@@ -1944,37 +2018,26 @@ const GenericReportImproved = ({
|
|
|
1944
2018
|
);
|
|
1945
2019
|
});
|
|
1946
2020
|
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
filteredJoins
|
|
1950
|
-
);
|
|
1951
|
-
|
|
1952
|
-
setDetectedJoins({
|
|
1953
|
-
...detectedJoins,
|
|
2021
|
+
setDetectedJoins((prev) => ({
|
|
2022
|
+
...prev,
|
|
1954
2023
|
[tableName]: filteredJoins,
|
|
1955
|
-
});
|
|
2024
|
+
}));
|
|
1956
2025
|
} else {
|
|
1957
|
-
|
|
2026
|
+
debugLog(
|
|
1958
2027
|
'No valid detected joins found or invalid response format:',
|
|
1959
2028
|
result
|
|
1960
2029
|
);
|
|
1961
|
-
setDetectedJoins({
|
|
1962
|
-
...detectedJoins,
|
|
1963
|
-
[tableName]: [],
|
|
1964
|
-
});
|
|
2030
|
+
setDetectedJoins((prev) => ({ ...prev, [tableName]: [] }));
|
|
1965
2031
|
}
|
|
1966
2032
|
}
|
|
1967
2033
|
} catch (error) {
|
|
1968
|
-
|
|
1969
|
-
setDetectedJoins({
|
|
1970
|
-
...detectedJoins,
|
|
1971
|
-
[tableName]: [],
|
|
1972
|
-
});
|
|
2034
|
+
debugLog('Error fetching detected joins:', error);
|
|
2035
|
+
setDetectedJoins((prev) => ({ ...prev, [tableName]: [] }));
|
|
1973
2036
|
} finally {
|
|
1974
|
-
setIsLoadingDetectedJoins({
|
|
1975
|
-
...
|
|
2037
|
+
setIsLoadingDetectedJoins((prev) => ({
|
|
2038
|
+
...prev,
|
|
1976
2039
|
[tableName]: false,
|
|
1977
|
-
});
|
|
2040
|
+
}));
|
|
1978
2041
|
}
|
|
1979
2042
|
};
|
|
1980
2043
|
|
|
@@ -2014,7 +2077,7 @@ const GenericReportImproved = ({
|
|
|
2014
2077
|
// Fetch columns for a joined table (similar to GenericReport)
|
|
2015
2078
|
const fetchJoinColumns = async (tableName, joinIndex) => {
|
|
2016
2079
|
if (!tableName || !columnUrl) {
|
|
2017
|
-
|
|
2080
|
+
debugLog('Missing tableName or columnUrl for fetchJoinColumns');
|
|
2018
2081
|
return;
|
|
2019
2082
|
}
|
|
2020
2083
|
|
|
@@ -2055,11 +2118,8 @@ const GenericReportImproved = ({
|
|
|
2055
2118
|
return updatedJoins;
|
|
2056
2119
|
});
|
|
2057
2120
|
|
|
2058
|
-
console.log(
|
|
2059
|
-
`Loaded ${columnsData.length} columns for joined table ${tableName}`
|
|
2060
|
-
);
|
|
2061
2121
|
} else {
|
|
2062
|
-
|
|
2122
|
+
debugLog(
|
|
2063
2123
|
'Invalid columns data format for joined table:',
|
|
2064
2124
|
result
|
|
2065
2125
|
);
|
|
@@ -2070,7 +2130,7 @@ const GenericReportImproved = ({
|
|
|
2070
2130
|
}
|
|
2071
2131
|
} catch (error) {
|
|
2072
2132
|
toast.error(`Failed to load columns for joined table ${tableName}`);
|
|
2073
|
-
|
|
2133
|
+
debugLog('Error fetching join columns:', error);
|
|
2074
2134
|
} finally {
|
|
2075
2135
|
setIsLoadingJoinColumns(false);
|
|
2076
2136
|
}
|
|
@@ -2106,13 +2166,8 @@ const GenericReportImproved = ({
|
|
|
2106
2166
|
table: tableName,
|
|
2107
2167
|
});
|
|
2108
2168
|
|
|
2109
|
-
console.log(
|
|
2110
|
-
`Manual join columns API response for ${tableName}:`,
|
|
2111
|
-
result
|
|
2112
|
-
);
|
|
2113
|
-
|
|
2114
2169
|
if (result.error) {
|
|
2115
|
-
|
|
2170
|
+
debugLog(`Error fetching ${type} columns:`, result.error);
|
|
2116
2171
|
} else {
|
|
2117
2172
|
// Handle the response format similar to fetchTableColumns
|
|
2118
2173
|
let columnsData = [];
|
|
@@ -2143,24 +2198,20 @@ const GenericReportImproved = ({
|
|
|
2143
2198
|
|
|
2144
2199
|
// Ensure columnsData is always an array
|
|
2145
2200
|
if (!Array.isArray(columnsData)) {
|
|
2146
|
-
|
|
2201
|
+
debugLog(
|
|
2147
2202
|
`Invalid columns data format for ${type} table:`,
|
|
2148
2203
|
result
|
|
2149
2204
|
);
|
|
2150
2205
|
columnsData = [];
|
|
2151
2206
|
}
|
|
2152
2207
|
|
|
2153
|
-
console.log(
|
|
2154
|
-
`Loaded ${columnsData.length} columns for ${type} table ${tableName}`
|
|
2155
|
-
);
|
|
2156
|
-
|
|
2157
2208
|
setManualJoinColumns((prev) => ({
|
|
2158
2209
|
...prev,
|
|
2159
2210
|
[type]: columnsData,
|
|
2160
2211
|
}));
|
|
2161
2212
|
}
|
|
2162
2213
|
} catch (error) {
|
|
2163
|
-
|
|
2214
|
+
debugLog(`Error fetching ${type} columns:`, error);
|
|
2164
2215
|
// Ensure state remains consistent even on error
|
|
2165
2216
|
setManualJoinColumns((prev) => ({
|
|
2166
2217
|
...prev,
|
|
@@ -2220,8 +2271,8 @@ const GenericReportImproved = ({
|
|
|
2220
2271
|
setSelectedColumns([]);
|
|
2221
2272
|
setJoins([]);
|
|
2222
2273
|
setAvailableTables([tableName]);
|
|
2223
|
-
|
|
2224
|
-
|
|
2274
|
+
// Columns and suggested joins are fetched by the selectedTable effect —
|
|
2275
|
+
// calling them here as well doubled every request.
|
|
2225
2276
|
|
|
2226
2277
|
goToNextStep(); // Go to relationships step
|
|
2227
2278
|
};
|
|
@@ -2390,25 +2441,24 @@ const GenericReportImproved = ({
|
|
|
2390
2441
|
};
|
|
2391
2442
|
|
|
2392
2443
|
setFilterCriteria((prev) => {
|
|
2393
|
-
const
|
|
2444
|
+
const groups = prev?.groups?.length
|
|
2445
|
+
? prev.groups
|
|
2446
|
+
: [{ operator: 'AND', filters: [] }];
|
|
2394
2447
|
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
updatedCriteria.groups[0].filters.push(newFilterCriterion);
|
|
2410
|
-
|
|
2411
|
-
return updatedCriteria;
|
|
2448
|
+
return {
|
|
2449
|
+
operator: prev?.operator || 'AND',
|
|
2450
|
+
groups: groups.map((group, index) =>
|
|
2451
|
+
index === 0
|
|
2452
|
+
? {
|
|
2453
|
+
...group,
|
|
2454
|
+
filters: [
|
|
2455
|
+
...(group.filters || []),
|
|
2456
|
+
newFilterCriterion,
|
|
2457
|
+
],
|
|
2458
|
+
}
|
|
2459
|
+
: group
|
|
2460
|
+
),
|
|
2461
|
+
};
|
|
2412
2462
|
});
|
|
2413
2463
|
|
|
2414
2464
|
toast.success(`Applied filter: ${suggestion.title}`);
|
|
@@ -2437,22 +2487,6 @@ const GenericReportImproved = ({
|
|
|
2437
2487
|
selectedTemplate?.executeUrl ||
|
|
2438
2488
|
executeUrl;
|
|
2439
2489
|
|
|
2440
|
-
// Debug logging
|
|
2441
|
-
console.log('🔍 [DEBUG] Grouped Report Detected:', {
|
|
2442
|
-
selectedTemplate: selectedTemplate?.id,
|
|
2443
|
-
isDynamicTemplate,
|
|
2444
|
-
template,
|
|
2445
|
-
customExecuteUrl: customUrls.executeUrl,
|
|
2446
|
-
templateExecuteUrl: selectedTemplate?.executeUrl,
|
|
2447
|
-
defaultExecuteUrl: executeUrl,
|
|
2448
|
-
finalExecuteUrl: templateExecuteUrl,
|
|
2449
|
-
isGroupedReport,
|
|
2450
|
-
templateGroupingConfig: selectedTemplate?.grouping,
|
|
2451
|
-
customGroupingConfig: groupingConfig,
|
|
2452
|
-
usingCustomUrl: !!customUrls.executeUrl,
|
|
2453
|
-
usingTemplateExecuteUrl: !!selectedTemplate?.executeUrl,
|
|
2454
|
-
});
|
|
2455
|
-
|
|
2456
2490
|
let payload;
|
|
2457
2491
|
|
|
2458
2492
|
if (isDynamicTemplate) {
|
|
@@ -2479,20 +2513,8 @@ const GenericReportImproved = ({
|
|
|
2479
2513
|
unique: groupedUniqueConfig,
|
|
2480
2514
|
};
|
|
2481
2515
|
|
|
2482
|
-
// Debug log for grouped reports
|
|
2483
|
-
if (uniqueFieldConfig.enabled) {
|
|
2484
|
-
console.group('🔍 [DEBUG] Grouped Report - Unique Field Configuration');
|
|
2485
|
-
console.log('Grouped unique config:', groupedUniqueConfig);
|
|
2486
|
-
console.groupEnd();
|
|
2487
|
-
}
|
|
2488
2516
|
} else {
|
|
2489
2517
|
// For predefined templates, use the existing approach
|
|
2490
|
-
console.log('🚨 [DEBUG] Using predefined template approach:', {
|
|
2491
|
-
selectedTemplate: selectedTemplate,
|
|
2492
|
-
selectedTemplateId: selectedTemplate?.id,
|
|
2493
|
-
fallbackId: 'utilization_planning',
|
|
2494
|
-
finalTemplateId: selectedTemplate?.id || 'utilization_planning',
|
|
2495
|
-
});
|
|
2496
2518
|
|
|
2497
2519
|
payload = {
|
|
2498
2520
|
template_id:
|
|
@@ -2501,23 +2523,15 @@ const GenericReportImproved = ({
|
|
|
2501
2523
|
};
|
|
2502
2524
|
}
|
|
2503
2525
|
|
|
2504
|
-
console.log('🚀 [DEBUG] Sending grouped report request:', {
|
|
2505
|
-
url: templateExecuteUrl,
|
|
2506
|
-
method: 'POST',
|
|
2507
|
-
payload,
|
|
2508
|
-
});
|
|
2509
|
-
|
|
2510
2526
|
const result = await CustomFetch(
|
|
2511
2527
|
templateExecuteUrl,
|
|
2512
2528
|
'POST',
|
|
2513
2529
|
payload
|
|
2514
2530
|
);
|
|
2515
2531
|
|
|
2516
|
-
console.log('📊 [DEBUG] Grouped report response:', result);
|
|
2517
|
-
|
|
2518
2532
|
if (result.error) {
|
|
2519
|
-
|
|
2520
|
-
'
|
|
2533
|
+
debugLog(
|
|
2534
|
+
'Grouped report execution error:',
|
|
2521
2535
|
result.error
|
|
2522
2536
|
);
|
|
2523
2537
|
return { data: [], count: 0 };
|
|
@@ -2529,18 +2543,6 @@ const GenericReportImproved = ({
|
|
|
2529
2543
|
const page = Math.floor(skip / limit) + 1;
|
|
2530
2544
|
|
|
2531
2545
|
// Use the same payload format as GenericReport (with query wrapper)
|
|
2532
|
-
// Debug the unique field state at query time
|
|
2533
|
-
console.group('🔍 [DEBUG] DataSource - Unique Field State Check');
|
|
2534
|
-
console.log('uniqueFieldConfig:', uniqueFieldConfig);
|
|
2535
|
-
console.log('uniqueFieldConfig.enabled:', uniqueFieldConfig.enabled);
|
|
2536
|
-
console.log('uniqueFieldConfig.selectedField:', uniqueFieldConfig.selectedField);
|
|
2537
|
-
console.log('Condition evaluation:', uniqueFieldConfig.enabled && uniqueFieldConfig.selectedField);
|
|
2538
|
-
|
|
2539
|
-
// Check DOM state vs React state
|
|
2540
|
-
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
|
|
2541
|
-
console.log('🔍 [DEBUG] All checkboxes in DOM:', Array.from(checkboxes).map((cb, i) => `${i}: ${cb.checked}`));
|
|
2542
|
-
console.groupEnd();
|
|
2543
|
-
|
|
2544
2546
|
const uniqueConfig = uniqueFieldConfig.enabled && uniqueFieldConfig.selectedField ? {
|
|
2545
2547
|
enabled: true,
|
|
2546
2548
|
field: uniqueFieldConfig.selectedField,
|
|
@@ -2564,13 +2566,6 @@ const GenericReportImproved = ({
|
|
|
2564
2566
|
type: col.type,
|
|
2565
2567
|
};
|
|
2566
2568
|
|
|
2567
|
-
// Log calculated fields specifically
|
|
2568
|
-
if (col.isCalculated) {
|
|
2569
|
-
console.log(
|
|
2570
|
-
'Frontend sending calculated field:',
|
|
2571
|
-
column
|
|
2572
|
-
);
|
|
2573
|
-
}
|
|
2574
2569
|
|
|
2575
2570
|
return column;
|
|
2576
2571
|
}),
|
|
@@ -2586,16 +2581,6 @@ const GenericReportImproved = ({
|
|
|
2586
2581
|
unique: uniqueConfig,
|
|
2587
2582
|
};
|
|
2588
2583
|
|
|
2589
|
-
// Debug log for unique field configuration
|
|
2590
|
-
if (uniqueFieldConfig.enabled) {
|
|
2591
|
-
console.group('🔍 [DEBUG] Unique Field Configuration');
|
|
2592
|
-
console.log('Unique config enabled:', uniqueFieldConfig.enabled);
|
|
2593
|
-
console.log('Selected field:', uniqueFieldConfig.selectedField);
|
|
2594
|
-
console.log('Query unique config:', uniqueConfig);
|
|
2595
|
-
console.log('Expected SQL: SELECT DISTINCT', uniqueFieldConfig.selectedField, '...');
|
|
2596
|
-
console.groupEnd();
|
|
2597
|
-
}
|
|
2598
|
-
|
|
2599
2584
|
// Add sorting from DataGrid if provided
|
|
2600
2585
|
if (sortInfo) {
|
|
2601
2586
|
queryConfig.sorting = [
|
|
@@ -2619,16 +2604,6 @@ const GenericReportImproved = ({
|
|
|
2619
2604
|
selectedTemplate?.executeUrl ||
|
|
2620
2605
|
executeUrl;
|
|
2621
2606
|
|
|
2622
|
-
console.log('🔍 [DEBUG] Regular Report API Call:', {
|
|
2623
|
-
selectedTemplate: selectedTemplate?.id,
|
|
2624
|
-
customExecuteUrl: customUrls.executeUrl,
|
|
2625
|
-
templateExecuteUrl: selectedTemplate?.executeUrl,
|
|
2626
|
-
defaultExecuteUrl: executeUrl,
|
|
2627
|
-
finalExecuteUrl,
|
|
2628
|
-
usingCustomUrl: !!customUrls.executeUrl,
|
|
2629
|
-
usingTemplateExecuteUrl: !!selectedTemplate?.executeUrl,
|
|
2630
|
-
});
|
|
2631
|
-
|
|
2632
2607
|
const result = await CustomFetch(
|
|
2633
2608
|
finalExecuteUrl,
|
|
2634
2609
|
'POST',
|
|
@@ -2636,7 +2611,7 @@ const GenericReportImproved = ({
|
|
|
2636
2611
|
);
|
|
2637
2612
|
|
|
2638
2613
|
if (result.error) {
|
|
2639
|
-
|
|
2614
|
+
debugLog('Report execution error:', result.error);
|
|
2640
2615
|
return { data: [], count: 0 };
|
|
2641
2616
|
} else if (result.data?.success) {
|
|
2642
2617
|
const fetchedData = result.data.data || [];
|
|
@@ -2647,20 +2622,13 @@ const GenericReportImproved = ({
|
|
|
2647
2622
|
setTotalCount(totalRecords);
|
|
2648
2623
|
setTotalResults(totalRecords);
|
|
2649
2624
|
|
|
2650
|
-
console.info(
|
|
2651
|
-
'Fetched data:',
|
|
2652
|
-
fetchedData.length,
|
|
2653
|
-
'of',
|
|
2654
|
-
totalRecords
|
|
2655
|
-
);
|
|
2656
|
-
|
|
2657
2625
|
return { data: fetchedData, count: totalRecords };
|
|
2658
2626
|
} else {
|
|
2659
|
-
|
|
2627
|
+
debugLog('Unexpected response format:', result);
|
|
2660
2628
|
return { data: [], count: 0 };
|
|
2661
2629
|
}
|
|
2662
2630
|
} catch (error) {
|
|
2663
|
-
|
|
2631
|
+
debugLog('Error in dataSource:', error);
|
|
2664
2632
|
return { data: [], count: 0 };
|
|
2665
2633
|
}
|
|
2666
2634
|
},
|
|
@@ -2678,20 +2646,11 @@ const GenericReportImproved = ({
|
|
|
2678
2646
|
);
|
|
2679
2647
|
|
|
2680
2648
|
// Execute report function for manual execution (creates grid columns)
|
|
2681
|
-
const executeReport = async () => {
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
hasGrouping: selectedTemplate?.grouping?.enabled,
|
|
2687
|
-
executeUrl,
|
|
2688
|
-
setting,
|
|
2689
|
-
});
|
|
2690
|
-
|
|
2691
|
-
// CRITICAL DEBUG: Check uniqueFieldConfig state at execute time
|
|
2692
|
-
console.log('🚨 [CRITICAL] uniqueFieldConfig at executeReport start:', uniqueFieldConfig);
|
|
2693
|
-
console.log('🚨 [CRITICAL] uniqueFieldConfig.enabled:', uniqueFieldConfig.enabled);
|
|
2694
|
-
console.log('🚨 [CRITICAL] uniqueFieldConfig.selectedField:', uniqueFieldConfig.selectedField);
|
|
2649
|
+
const executeReport = async ({ page, size } = {}) => {
|
|
2650
|
+
// Pagination handlers pass the page/size they just set: reading them from
|
|
2651
|
+
// state here would use the value from the render that queued the update.
|
|
2652
|
+
const pageToLoad = page || currentPage;
|
|
2653
|
+
const sizeToLoad = size || pageSize;
|
|
2695
2654
|
|
|
2696
2655
|
if (!selectedTable || selectedColumns.length === 0) {
|
|
2697
2656
|
toast.warning(
|
|
@@ -2708,20 +2667,7 @@ const GenericReportImproved = ({
|
|
|
2708
2667
|
const hasTemplateGrouping = selectedTemplate?.grouping?.enabled;
|
|
2709
2668
|
const hasCustomGrouping = groupingConfig.enabled;
|
|
2710
2669
|
const hasGrouping = hasTemplateGrouping || hasCustomGrouping;
|
|
2711
|
-
let
|
|
2712
|
-
|
|
2713
|
-
console.log('📋 [DEBUG] Template analysis:', {
|
|
2714
|
-
selectedTemplate,
|
|
2715
|
-
hasTemplateGrouping,
|
|
2716
|
-
hasCustomGrouping,
|
|
2717
|
-
hasGrouping,
|
|
2718
|
-
customGroupingConfig: groupingConfig,
|
|
2719
|
-
templateGroupingConfig: selectedTemplate?.grouping,
|
|
2720
|
-
});
|
|
2721
|
-
console.log(
|
|
2722
|
-
'🎯 [DEBUG] Initial fetchedDataCount:',
|
|
2723
|
-
fetchedDataCount
|
|
2724
|
-
);
|
|
2670
|
+
let fetchedRows = [];
|
|
2725
2671
|
|
|
2726
2672
|
if (hasGrouping) {
|
|
2727
2673
|
// Determine which grouping configuration to use
|
|
@@ -2753,27 +2699,16 @@ const GenericReportImproved = ({
|
|
|
2753
2699
|
};
|
|
2754
2700
|
effectiveGrouping = dynamicTemplate.grouping;
|
|
2755
2701
|
|
|
2756
|
-
console.log(
|
|
2757
|
-
'🏗️ [DEBUG] Created dynamic template for custom grouping:',
|
|
2758
|
-
dynamicTemplate
|
|
2759
|
-
);
|
|
2760
2702
|
} else if (hasCustomGrouping && !groupingConfig.groupByField) {
|
|
2761
2703
|
// Custom grouping enabled but no field selected - show error and fallback to regular report
|
|
2762
2704
|
toast.error(
|
|
2763
2705
|
'Please select a field to group by before executing the report.'
|
|
2764
2706
|
);
|
|
2765
|
-
console.log(
|
|
2766
|
-
'⚠️ [DEBUG] Custom grouping enabled but no groupByField selected'
|
|
2767
|
-
);
|
|
2768
2707
|
setIsLoading(false);
|
|
2769
2708
|
return;
|
|
2770
2709
|
} else {
|
|
2771
2710
|
// Use existing template grouping
|
|
2772
2711
|
effectiveGrouping = selectedTemplate.grouping;
|
|
2773
|
-
console.log(
|
|
2774
|
-
'📋 [DEBUG] Using template grouping:',
|
|
2775
|
-
effectiveGrouping
|
|
2776
|
-
);
|
|
2777
2712
|
}
|
|
2778
2713
|
|
|
2779
2714
|
// For grouped reports, fetch data with reasonable limit
|
|
@@ -2786,76 +2721,48 @@ const GenericReportImproved = ({
|
|
|
2786
2721
|
template: dynamicTemplate, // Pass dynamic template if created
|
|
2787
2722
|
});
|
|
2788
2723
|
|
|
2789
|
-
console.log('🎯 [DEBUG] Full API Response:', groupedResult);
|
|
2790
|
-
console.log(
|
|
2791
|
-
'🎯 [DEBUG] groupedResult.data:',
|
|
2792
|
-
groupedResult.data
|
|
2793
|
-
);
|
|
2794
|
-
console.log(
|
|
2795
|
-
'🎯 [DEBUG] groupedResult.data.grouped:',
|
|
2796
|
-
groupedResult.data?.grouped
|
|
2797
|
-
);
|
|
2798
|
-
console.log(
|
|
2799
|
-
'🎯 [DEBUG] groupedResult.data.groupedData:',
|
|
2800
|
-
groupedResult.data?.groupedData
|
|
2801
|
-
);
|
|
2802
|
-
|
|
2803
2724
|
if (
|
|
2804
2725
|
groupedResult.data?.grouped &&
|
|
2805
2726
|
groupedResult.data?.groupedData?.groups
|
|
2806
2727
|
) {
|
|
2807
|
-
console.log('✅ Processing grouped report');
|
|
2808
2728
|
setGroupedData(groupedResult.data); // Store entire response with grouped flag
|
|
2809
2729
|
setPreviewData([]); // Clear regular preview data
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
groupedResult.data.groupedData?.totalRecords
|
|
2814
|
-
);
|
|
2815
|
-
fetchedDataCount =
|
|
2730
|
+
|
|
2731
|
+
// Keep the record counters in step with the grouped result
|
|
2732
|
+
const groupedTotal =
|
|
2816
2733
|
groupedResult.data.groupedData?.totalRecords || 0;
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
fetchedDataCount
|
|
2820
|
-
);
|
|
2821
|
-
console.log('🔢 [DEBUG] Grouped data count:', {
|
|
2822
|
-
totalRecords:
|
|
2823
|
-
groupedResult.data.groupedData?.totalRecords,
|
|
2824
|
-
fetchedDataCount,
|
|
2825
|
-
hasGroups: !!groupedResult.data.groupedData?.groups,
|
|
2826
|
-
});
|
|
2734
|
+
setTotalResults(groupedTotal);
|
|
2735
|
+
setTotalCount(groupedTotal);
|
|
2827
2736
|
} else {
|
|
2828
2737
|
// Fallback to regular display if server doesn't support grouping
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
fallbackData.length
|
|
2834
|
-
);
|
|
2738
|
+
const fallbackData = Array.isArray(groupedResult.data?.data)
|
|
2739
|
+
? groupedResult.data.data
|
|
2740
|
+
: [];
|
|
2741
|
+
fetchedRows = fallbackData;
|
|
2835
2742
|
setPreviewData(fallbackData);
|
|
2836
|
-
|
|
2743
|
+
setTotalResults(fallbackData.length);
|
|
2744
|
+
setTotalCount(fallbackData.length);
|
|
2837
2745
|
}
|
|
2838
2746
|
} else {
|
|
2839
2747
|
// Regular non-grouped report
|
|
2840
2748
|
const currentPageResult = await dataSource({
|
|
2841
|
-
skip: (
|
|
2842
|
-
limit:
|
|
2749
|
+
skip: (pageToLoad - 1) * sizeToLoad,
|
|
2750
|
+
limit: sizeToLoad,
|
|
2843
2751
|
sortInfo: null,
|
|
2844
2752
|
filterValue: null,
|
|
2845
2753
|
});
|
|
2846
2754
|
|
|
2847
|
-
|
|
2848
|
-
currentPageResult.data
|
|
2849
|
-
|
|
2850
|
-
)
|
|
2851
|
-
setPreviewData(currentPageResult.data);
|
|
2852
|
-
fetchedDataCount = currentPageResult.data.length;
|
|
2853
|
-
}
|
|
2755
|
+
fetchedRows = Array.isArray(currentPageResult.data)
|
|
2756
|
+
? currentPageResult.data
|
|
2757
|
+
: [];
|
|
2758
|
+
setPreviewData(fetchedRows);
|
|
2854
2759
|
}
|
|
2855
2760
|
|
|
2856
2761
|
// Create grid columns for regular reports only
|
|
2857
2762
|
if (!hasGrouping) {
|
|
2858
|
-
|
|
2763
|
+
// Use the rows we just fetched — previewData still holds the
|
|
2764
|
+
// previous render's value at this point.
|
|
2765
|
+
const dataToProcess = fetchedRows;
|
|
2859
2766
|
|
|
2860
2767
|
if (dataToProcess.length > 0) {
|
|
2861
2768
|
// Create grid columns dynamically from actual data with smart formatting
|
|
@@ -2970,15 +2877,17 @@ const GenericReportImproved = ({
|
|
|
2970
2877
|
};
|
|
2971
2878
|
});
|
|
2972
2879
|
setGridColumns(dynamicColumns);
|
|
2880
|
+
} else if (
|
|
2881
|
+
!Array.isArray(selectedColumns) ||
|
|
2882
|
+
selectedColumns.length === 0
|
|
2883
|
+
) {
|
|
2884
|
+
debugLog(
|
|
2885
|
+
'selectedColumns is not a valid array:',
|
|
2886
|
+
selectedColumns
|
|
2887
|
+
);
|
|
2888
|
+
setGridColumns([]);
|
|
2973
2889
|
} else {
|
|
2974
2890
|
// Fallback to selected columns if no data
|
|
2975
|
-
console.log('🔧 [DEBUG] Creating fallback columns from selectedColumns:', selectedColumns);
|
|
2976
|
-
if (!Array.isArray(selectedColumns) || selectedColumns.length === 0) {
|
|
2977
|
-
console.warn('⚠️ selectedColumns is not a valid array:', selectedColumns);
|
|
2978
|
-
setGridColumns([]);
|
|
2979
|
-
return;
|
|
2980
|
-
}
|
|
2981
|
-
|
|
2982
2891
|
const columns = selectedColumns.map((col, index) => {
|
|
2983
2892
|
try {
|
|
2984
2893
|
// Find column type from table columns with enhanced detection
|
|
@@ -3049,13 +2958,23 @@ const GenericReportImproved = ({
|
|
|
3049
2958
|
|
|
3050
2959
|
return formattedValue;
|
|
3051
2960
|
} catch (renderError) {
|
|
3052
|
-
|
|
2961
|
+
debugLog(
|
|
2962
|
+
'Cell render error:',
|
|
2963
|
+
renderError,
|
|
2964
|
+
'for value:',
|
|
2965
|
+
value
|
|
2966
|
+
);
|
|
3053
2967
|
return String(value || '');
|
|
3054
2968
|
}
|
|
3055
2969
|
},
|
|
3056
2970
|
};
|
|
3057
2971
|
} catch (colError) {
|
|
3058
|
-
|
|
2972
|
+
debugLog(
|
|
2973
|
+
'Column mapping error:',
|
|
2974
|
+
colError,
|
|
2975
|
+
'for column:',
|
|
2976
|
+
col
|
|
2977
|
+
);
|
|
3059
2978
|
return {
|
|
3060
2979
|
name: `error_col_${index}`,
|
|
3061
2980
|
header: 'Error Column',
|
|
@@ -3074,25 +2993,8 @@ const GenericReportImproved = ({
|
|
|
3074
2993
|
}
|
|
3075
2994
|
|
|
3076
2995
|
// Report executed successfully
|
|
3077
|
-
console.log('🎯 [DEBUG] Final fetchedDataCount:', fetchedDataCount);
|
|
3078
|
-
console.log(
|
|
3079
|
-
'🎯 [DEBUG] Type of fetchedDataCount:',
|
|
3080
|
-
typeof fetchedDataCount
|
|
3081
|
-
);
|
|
3082
2996
|
} catch (error) {
|
|
3083
|
-
|
|
3084
|
-
console.error('Error executing report:', error);
|
|
3085
|
-
console.log('Error name:', error.name);
|
|
3086
|
-
console.log('Error message:', error.message);
|
|
3087
|
-
console.log('Error stack:', error.stack);
|
|
3088
|
-
console.log('Current state at error:', {
|
|
3089
|
-
selectedTable,
|
|
3090
|
-
selectedColumns: selectedColumns.length,
|
|
3091
|
-
hasGrouping: selectedTemplate?.grouping?.enabled || groupingConfig.enabled,
|
|
3092
|
-
currentPage,
|
|
3093
|
-
pageSize,
|
|
3094
|
-
});
|
|
3095
|
-
console.groupEnd();
|
|
2997
|
+
debugLog('Error executing report:', error);
|
|
3096
2998
|
|
|
3097
2999
|
// Only show toast error for actual failures, not for successful pagination
|
|
3098
3000
|
if (!error.message?.includes('successful')) {
|
|
@@ -3124,23 +3026,24 @@ const GenericReportImproved = ({
|
|
|
3124
3026
|
// Load saved report configuration
|
|
3125
3027
|
const loadSavedReport = (report) => {
|
|
3126
3028
|
try {
|
|
3127
|
-
const config =
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3029
|
+
const config = parseReportDetail(report?.detail);
|
|
3030
|
+
|
|
3031
|
+
if (!config) {
|
|
3032
|
+
toast.error('This report has no saved configuration.');
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3131
3035
|
|
|
3132
3036
|
if (config.mainTable) {
|
|
3133
3037
|
setSelectedTable(config.mainTable);
|
|
3134
3038
|
setAvailableTables([config.mainTable]);
|
|
3135
|
-
|
|
3136
|
-
fetchDetectedJoins(config.mainTable);
|
|
3039
|
+
// The selectedTable effect fetches columns and suggested joins.
|
|
3137
3040
|
}
|
|
3138
3041
|
|
|
3139
|
-
if (config.columns) {
|
|
3042
|
+
if (Array.isArray(config.columns)) {
|
|
3140
3043
|
setSelectedColumns(config.columns);
|
|
3141
3044
|
}
|
|
3142
3045
|
|
|
3143
|
-
if (config.joins) {
|
|
3046
|
+
if (Array.isArray(config.joins)) {
|
|
3144
3047
|
setJoins(config.joins);
|
|
3145
3048
|
// Fetch columns for each joined table
|
|
3146
3049
|
config.joins.forEach((join, index) => {
|
|
@@ -3157,7 +3060,6 @@ const GenericReportImproved = ({
|
|
|
3157
3060
|
|
|
3158
3061
|
// Restore unique field configuration if it exists
|
|
3159
3062
|
if (config.unique) {
|
|
3160
|
-
console.log('🔄 [RESTORE] Restoring unique field config:', config.unique);
|
|
3161
3063
|
setUniqueFieldConfig(config.unique);
|
|
3162
3064
|
}
|
|
3163
3065
|
|
|
@@ -3242,19 +3144,13 @@ const GenericReportImproved = ({
|
|
|
3242
3144
|
toast.success(`Loaded report: ${report.label}`);
|
|
3243
3145
|
} catch (error) {
|
|
3244
3146
|
toast.error('Failed to load report configuration');
|
|
3245
|
-
|
|
3147
|
+
debugLog('Error loading report:', error);
|
|
3246
3148
|
}
|
|
3247
3149
|
};
|
|
3248
3150
|
|
|
3249
3151
|
// Load business template configuration
|
|
3250
3152
|
const loadBusinessTemplate = async (template) => {
|
|
3251
3153
|
try {
|
|
3252
|
-
console.log('🏗️ [DEBUG] Loading business template:', {
|
|
3253
|
-
templateId: template.id,
|
|
3254
|
-
templateName: template.name,
|
|
3255
|
-
hasGrouping: template.grouping?.enabled,
|
|
3256
|
-
groupingConfig: template.grouping,
|
|
3257
|
-
});
|
|
3258
3154
|
|
|
3259
3155
|
setSelectedTemplate(template);
|
|
3260
3156
|
|
|
@@ -3385,11 +3281,6 @@ const GenericReportImproved = ({
|
|
|
3385
3281
|
emptyRowStyle: template.grouping.emptyRowStyle || 'light',
|
|
3386
3282
|
});
|
|
3387
3283
|
|
|
3388
|
-
console.log('🏗️ [DEBUG] Applied template grouping config:', {
|
|
3389
|
-
enabled: template.grouping.enabled,
|
|
3390
|
-
groupByField: template.grouping.groupByField,
|
|
3391
|
-
groupDisplayName: template.grouping.groupDisplayName,
|
|
3392
|
-
});
|
|
3393
3284
|
} else {
|
|
3394
3285
|
// Reset grouping config if template doesn't have grouping
|
|
3395
3286
|
setGroupingConfig({
|
|
@@ -3418,7 +3309,7 @@ const GenericReportImproved = ({
|
|
|
3418
3309
|
toast.success(`Loaded template: ${template.name}`);
|
|
3419
3310
|
} catch (error) {
|
|
3420
3311
|
toast.error('Failed to load template configuration');
|
|
3421
|
-
|
|
3312
|
+
debugLog('Error loading template:', error);
|
|
3422
3313
|
}
|
|
3423
3314
|
};
|
|
3424
3315
|
|
|
@@ -3450,69 +3341,109 @@ const GenericReportImproved = ({
|
|
|
3450
3341
|
</div>
|
|
3451
3342
|
</div>
|
|
3452
3343
|
|
|
3453
|
-
{/* Business Templates Section
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
<
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
)
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3344
|
+
{/* Business Templates Section — hidden when the host app
|
|
3345
|
+
ships no templates, so the panel is never empty */}
|
|
3346
|
+
{businessTemplates.length > 0 && (
|
|
3347
|
+
<div className={styles.businessTemplatesSection}>
|
|
3348
|
+
<h3>Business Report Templates</h3>
|
|
3349
|
+
<p>
|
|
3350
|
+
Pre-configured reports for common business
|
|
3351
|
+
scenarios
|
|
3352
|
+
</p>
|
|
3353
|
+
<div className={styles.templatesGrid}>
|
|
3354
|
+
{businessTemplates.map((template) => {
|
|
3355
|
+
const TemplateIcon = getTemplateIcon(
|
|
3356
|
+
template.icon
|
|
3357
|
+
);
|
|
3358
|
+
const tags = Array.isArray(template.tags)
|
|
3359
|
+
? template.tags
|
|
3360
|
+
: [];
|
|
3361
|
+
return (
|
|
3362
|
+
<div
|
|
3363
|
+
key={template.id}
|
|
3364
|
+
className={styles.templateCard}
|
|
3365
|
+
onClick={() =>
|
|
3366
|
+
loadBusinessTemplate(template)
|
|
3367
|
+
}
|
|
3368
|
+
>
|
|
3478
3369
|
<div
|
|
3479
|
-
className={styles.
|
|
3370
|
+
className={styles.templateIcon}
|
|
3480
3371
|
>
|
|
3481
|
-
{
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3372
|
+
<TemplateIcon size={24} />
|
|
3373
|
+
</div>
|
|
3374
|
+
<div
|
|
3375
|
+
className={
|
|
3376
|
+
styles.templateContent
|
|
3377
|
+
}
|
|
3378
|
+
>
|
|
3379
|
+
<h4>{template.name}</h4>
|
|
3380
|
+
<p>{template.description}</p>
|
|
3381
|
+
{tags.length > 0 && (
|
|
3382
|
+
<div
|
|
3383
|
+
className={
|
|
3384
|
+
styles.templateTags
|
|
3385
|
+
}
|
|
3386
|
+
>
|
|
3387
|
+
{tags.map(
|
|
3388
|
+
(tag, index) => (
|
|
3389
|
+
<span
|
|
3390
|
+
key={index}
|
|
3391
|
+
className={
|
|
3392
|
+
styles.templateTag
|
|
3393
|
+
}
|
|
3394
|
+
>
|
|
3395
|
+
{tag}
|
|
3396
|
+
</span>
|
|
3397
|
+
)
|
|
3398
|
+
)}
|
|
3399
|
+
</div>
|
|
3492
3400
|
)}
|
|
3493
3401
|
</div>
|
|
3402
|
+
{template.category && (
|
|
3403
|
+
<div
|
|
3404
|
+
className={
|
|
3405
|
+
styles.templateCategory
|
|
3406
|
+
}
|
|
3407
|
+
>
|
|
3408
|
+
{template.category}
|
|
3409
|
+
</div>
|
|
3410
|
+
)}
|
|
3494
3411
|
</div>
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
{template.category}
|
|
3499
|
-
</div>
|
|
3500
|
-
</div>
|
|
3501
|
-
);
|
|
3502
|
-
})}
|
|
3412
|
+
);
|
|
3413
|
+
})}
|
|
3414
|
+
</div>
|
|
3503
3415
|
</div>
|
|
3504
|
-
|
|
3416
|
+
)}
|
|
3505
3417
|
|
|
3506
3418
|
{/* Saved Reports List */}
|
|
3507
3419
|
{isLoadingReports ? (
|
|
3508
3420
|
<div className={styles.loading}>
|
|
3509
3421
|
Loading your saved reports...
|
|
3510
3422
|
</div>
|
|
3423
|
+
) : reportsError ? (
|
|
3424
|
+
<div className={styles.errorPanel} role="alert">
|
|
3425
|
+
<TriangleAlert size={20} />
|
|
3426
|
+
<div>
|
|
3427
|
+
<h3>Saved reports unavailable</h3>
|
|
3428
|
+
<p>{reportsError}</p>
|
|
3429
|
+
</div>
|
|
3430
|
+
<button
|
|
3431
|
+
type="button"
|
|
3432
|
+
className={styles.retryButton}
|
|
3433
|
+
onClick={fetchSavedReports}
|
|
3434
|
+
>
|
|
3435
|
+
Try again
|
|
3436
|
+
</button>
|
|
3437
|
+
</div>
|
|
3511
3438
|
) : savedReports.length > 0 ? (
|
|
3512
3439
|
<div className={styles.savedReportsSection}>
|
|
3513
3440
|
<h3>Your Saved Reports ({savedReports.length})</h3>
|
|
3514
3441
|
<div className={styles.reportsGrid}>
|
|
3515
|
-
{savedReports.map((report) =>
|
|
3442
|
+
{savedReports.map((report) => {
|
|
3443
|
+
const reportMainTable =
|
|
3444
|
+
parseReportDetail(report.detail)
|
|
3445
|
+
?.mainTable || '';
|
|
3446
|
+
return (
|
|
3516
3447
|
<div
|
|
3517
3448
|
key={report.id}
|
|
3518
3449
|
className={styles.reportCard}
|
|
@@ -3566,7 +3497,7 @@ const GenericReportImproved = ({
|
|
|
3566
3497
|
</span>
|
|
3567
3498
|
</div>
|
|
3568
3499
|
</div>
|
|
3569
|
-
{
|
|
3500
|
+
{reportMainTable && (
|
|
3570
3501
|
<div
|
|
3571
3502
|
className={
|
|
3572
3503
|
styles.reportTableInfo
|
|
@@ -3587,13 +3518,7 @@ const GenericReportImproved = ({
|
|
|
3587
3518
|
}}
|
|
3588
3519
|
/>
|
|
3589
3520
|
{formatName(
|
|
3590
|
-
|
|
3591
|
-
'string'
|
|
3592
|
-
? JSON.parse(
|
|
3593
|
-
report.detail
|
|
3594
|
-
).mainTable
|
|
3595
|
-
: report.detail
|
|
3596
|
-
.mainTable
|
|
3521
|
+
reportMainTable
|
|
3597
3522
|
)}
|
|
3598
3523
|
</span>
|
|
3599
3524
|
</div>
|
|
@@ -3608,6 +3533,7 @@ const GenericReportImproved = ({
|
|
|
3608
3533
|
</div>
|
|
3609
3534
|
<div className={styles.reportActions}>
|
|
3610
3535
|
<button
|
|
3536
|
+
type="button"
|
|
3611
3537
|
className={
|
|
3612
3538
|
styles.deleteReportBtn
|
|
3613
3539
|
}
|
|
@@ -3624,7 +3550,8 @@ const GenericReportImproved = ({
|
|
|
3624
3550
|
</button>
|
|
3625
3551
|
</div>
|
|
3626
3552
|
</div>
|
|
3627
|
-
|
|
3553
|
+
);
|
|
3554
|
+
})}
|
|
3628
3555
|
</div>
|
|
3629
3556
|
</div>
|
|
3630
3557
|
) : (
|
|
@@ -3664,10 +3591,24 @@ const GenericReportImproved = ({
|
|
|
3664
3591
|
className={`${styles.wizardStep} ${
|
|
3665
3592
|
isActive ? styles.active : ''
|
|
3666
3593
|
} ${isComplete ? styles.complete : ''}`}
|
|
3594
|
+
role="button"
|
|
3595
|
+
tabIndex={index <= currentWizardStep ? 0 : -1}
|
|
3596
|
+
aria-current={isActive ? 'step' : undefined}
|
|
3597
|
+
aria-disabled={index > currentWizardStep}
|
|
3667
3598
|
onClick={() =>
|
|
3668
3599
|
index <= currentWizardStep &&
|
|
3669
3600
|
setCurrentWizardStep(index)
|
|
3670
3601
|
}
|
|
3602
|
+
onKeyDown={(event) => {
|
|
3603
|
+
if (
|
|
3604
|
+
(event.key === 'Enter' ||
|
|
3605
|
+
event.key === ' ') &&
|
|
3606
|
+
index <= currentWizardStep
|
|
3607
|
+
) {
|
|
3608
|
+
event.preventDefault();
|
|
3609
|
+
setCurrentWizardStep(index);
|
|
3610
|
+
}
|
|
3611
|
+
}}
|
|
3671
3612
|
>
|
|
3672
3613
|
<div className={styles.stepIcon}>
|
|
3673
3614
|
{isComplete && !isActive ? (
|
|
@@ -3739,6 +3680,7 @@ const GenericReportImproved = ({
|
|
|
3739
3680
|
<div className={styles.wizardNavigation}>
|
|
3740
3681
|
{currentWizardStep === 0 ? (
|
|
3741
3682
|
<button
|
|
3683
|
+
type="button"
|
|
3742
3684
|
className={`${styles.btn} ${styles.btnCancel}`}
|
|
3743
3685
|
onClick={startAgain}
|
|
3744
3686
|
>
|
|
@@ -3746,6 +3688,7 @@ const GenericReportImproved = ({
|
|
|
3746
3688
|
</button>
|
|
3747
3689
|
) : (
|
|
3748
3690
|
<button
|
|
3691
|
+
type="button"
|
|
3749
3692
|
className={`${styles.btn} ${styles.btnPrevious}`}
|
|
3750
3693
|
onClick={goToPreviousStep}
|
|
3751
3694
|
>
|
|
@@ -3755,6 +3698,7 @@ const GenericReportImproved = ({
|
|
|
3755
3698
|
|
|
3756
3699
|
{currentWizardStep === wizardSteps.length - 1 ? (
|
|
3757
3700
|
<button
|
|
3701
|
+
type="button"
|
|
3758
3702
|
className={`${styles.btn} ${styles.btnStartAgain}`}
|
|
3759
3703
|
onClick={startAgain}
|
|
3760
3704
|
>
|
|
@@ -3762,6 +3706,7 @@ const GenericReportImproved = ({
|
|
|
3762
3706
|
</button>
|
|
3763
3707
|
) : (
|
|
3764
3708
|
<button
|
|
3709
|
+
type="button"
|
|
3765
3710
|
className={`${styles.btn} ${styles.btnPrimary}`}
|
|
3766
3711
|
onClick={goToNextStep}
|
|
3767
3712
|
disabled={!isStepComplete(currentStep.id)}
|
|
@@ -3803,6 +3748,21 @@ const GenericReportImproved = ({
|
|
|
3803
3748
|
<div className={styles.loading}>
|
|
3804
3749
|
Loading available tables...
|
|
3805
3750
|
</div>
|
|
3751
|
+
) : tablesError ? (
|
|
3752
|
+
<div className={styles.errorPanel} role="alert">
|
|
3753
|
+
<TriangleAlert size={20} />
|
|
3754
|
+
<div>
|
|
3755
|
+
<h3>Data sources unavailable</h3>
|
|
3756
|
+
<p>{tablesError}</p>
|
|
3757
|
+
</div>
|
|
3758
|
+
<button
|
|
3759
|
+
type="button"
|
|
3760
|
+
className={styles.retryButton}
|
|
3761
|
+
onClick={fetchDatabaseTables}
|
|
3762
|
+
>
|
|
3763
|
+
Try again
|
|
3764
|
+
</button>
|
|
3765
|
+
</div>
|
|
3806
3766
|
) : tables.length === 0 ? (
|
|
3807
3767
|
<div className={styles.noTables}>
|
|
3808
3768
|
<Data size={48} />
|
|
@@ -3834,9 +3794,21 @@ const GenericReportImproved = ({
|
|
|
3834
3794
|
className={`${styles.tableCard} ${
|
|
3835
3795
|
isSelected ? styles.selected : ''
|
|
3836
3796
|
}`}
|
|
3797
|
+
role="button"
|
|
3798
|
+
tabIndex={0}
|
|
3799
|
+
aria-pressed={isSelected}
|
|
3837
3800
|
onClick={() =>
|
|
3838
3801
|
handleTableSelect(table.name)
|
|
3839
3802
|
}
|
|
3803
|
+
onKeyDown={(event) => {
|
|
3804
|
+
if (
|
|
3805
|
+
event.key === 'Enter' ||
|
|
3806
|
+
event.key === ' '
|
|
3807
|
+
) {
|
|
3808
|
+
event.preventDefault();
|
|
3809
|
+
handleTableSelect(table.name);
|
|
3810
|
+
}
|
|
3811
|
+
}}
|
|
3840
3812
|
>
|
|
3841
3813
|
<div className={styles.tableCardIcon}>
|
|
3842
3814
|
<TableIcon size={24} />
|
|
@@ -3877,44 +3849,23 @@ const GenericReportImproved = ({
|
|
|
3877
3849
|
);
|
|
3878
3850
|
};
|
|
3879
3851
|
|
|
3880
|
-
//
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
if (
|
|
3884
|
-
return (
|
|
3885
|
-
<div className={styles.loading}>
|
|
3886
|
-
Loading table columns
|
|
3887
|
-
{isLoadingJoinColumns ? ' from joined tables' : ''}...
|
|
3888
|
-
</div>
|
|
3889
|
-
);
|
|
3890
|
-
}
|
|
3891
|
-
|
|
3892
|
-
if (tableColumns.length === 0) {
|
|
3893
|
-
return (
|
|
3894
|
-
<div className={styles.noColumns}>
|
|
3895
|
-
<Data size={48} />
|
|
3896
|
-
<h3>No Columns Available</h3>
|
|
3897
|
-
<p>
|
|
3898
|
-
No columns found for the selected table. Please try
|
|
3899
|
-
selecting a different table or check your configuration.
|
|
3900
|
-
</p>
|
|
3901
|
-
</div>
|
|
3902
|
-
);
|
|
3903
|
-
}
|
|
3852
|
+
// Field pickers for the column step. Memoised because the step re-renders on
|
|
3853
|
+
// every checkbox toggle and these lists only change when the schema does.
|
|
3854
|
+
const allAvailableColumns = useMemo(() => {
|
|
3855
|
+
if (!selectedTable) return [];
|
|
3904
3856
|
|
|
3905
|
-
//
|
|
3857
|
+
// Table names used for display disambiguation
|
|
3906
3858
|
const allAvailableTables = [
|
|
3907
3859
|
selectedTable,
|
|
3908
3860
|
...joins.map((join) => join.targetTable),
|
|
3909
|
-
];
|
|
3861
|
+
].map((name) => ({ name }));
|
|
3910
3862
|
|
|
3911
|
-
|
|
3912
|
-
const allAvailableColumns = [
|
|
3863
|
+
const sections = [
|
|
3913
3864
|
{
|
|
3914
3865
|
tableName: selectedTable,
|
|
3915
3866
|
displayName: getUniqueTableDisplayName(
|
|
3916
3867
|
selectedTable,
|
|
3917
|
-
allAvailableTables
|
|
3868
|
+
allAvailableTables,
|
|
3918
3869
|
definition
|
|
3919
3870
|
),
|
|
3920
3871
|
columns: tableColumns,
|
|
@@ -3924,7 +3875,7 @@ const GenericReportImproved = ({
|
|
|
3924
3875
|
tableName: join.targetTable,
|
|
3925
3876
|
displayName: getUniqueTableDisplayName(
|
|
3926
3877
|
join.targetTable,
|
|
3927
|
-
allAvailableTables
|
|
3878
|
+
allAvailableTables,
|
|
3928
3879
|
definition
|
|
3929
3880
|
),
|
|
3930
3881
|
columns: join.availableColumns || [],
|
|
@@ -3932,18 +3883,13 @@ const GenericReportImproved = ({
|
|
|
3932
3883
|
})),
|
|
3933
3884
|
];
|
|
3934
3885
|
|
|
3935
|
-
// Add calculated fields if template is selected or custom fields exist
|
|
3936
|
-
const templateCalculatedFields =
|
|
3937
|
-
selectedTemplate && selectedTemplate.calculatedFields
|
|
3938
|
-
? selectedTemplate.calculatedFields
|
|
3939
|
-
: [];
|
|
3940
3886
|
const allCalculatedFields = [
|
|
3941
|
-
...
|
|
3887
|
+
...(selectedTemplate?.calculatedFields || []),
|
|
3942
3888
|
...customCalculatedFields,
|
|
3943
3889
|
];
|
|
3944
3890
|
|
|
3945
3891
|
if (allCalculatedFields.length > 0) {
|
|
3946
|
-
|
|
3892
|
+
sections.push({
|
|
3947
3893
|
tableName: 'calculated',
|
|
3948
3894
|
displayName: 'Calculated Fields',
|
|
3949
3895
|
columns: allCalculatedFields.map((calc) => ({
|
|
@@ -3954,16 +3900,77 @@ const GenericReportImproved = ({
|
|
|
3954
3900
|
isCalculated: true,
|
|
3955
3901
|
})),
|
|
3956
3902
|
isCalculated: true,
|
|
3957
|
-
};
|
|
3958
|
-
allAvailableColumns.push(calculatedFields);
|
|
3903
|
+
});
|
|
3959
3904
|
}
|
|
3960
3905
|
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3906
|
+
return sections;
|
|
3907
|
+
}, [
|
|
3908
|
+
selectedTable,
|
|
3909
|
+
tableColumns,
|
|
3910
|
+
joins,
|
|
3911
|
+
definition,
|
|
3912
|
+
selectedTemplate,
|
|
3913
|
+
customCalculatedFields,
|
|
3914
|
+
]);
|
|
3915
|
+
|
|
3916
|
+
const mainTableCategories = useMemo(
|
|
3917
|
+
() => categorizeFields(tableColumns, showHiddenFields),
|
|
3918
|
+
[tableColumns, showHiddenFields]
|
|
3919
|
+
);
|
|
3920
|
+
|
|
3921
|
+
// categorizeFields only collects hidden fields when they are being shown,
|
|
3922
|
+
// so the toggle's counter has to be derived from the raw column list.
|
|
3923
|
+
const mainHiddenCount = useMemo(
|
|
3924
|
+
() =>
|
|
3925
|
+
(tableColumns || []).filter((col) =>
|
|
3926
|
+
shouldHideField(col?.name, col?.type)
|
|
3927
|
+
).length,
|
|
3928
|
+
[tableColumns]
|
|
3929
|
+
);
|
|
3930
|
+
|
|
3931
|
+
// Render column selection
|
|
3932
|
+
const renderColumnSelection = () => {
|
|
3933
|
+
// Add loading check at the beginning
|
|
3934
|
+
if (isLoadingColumns || isLoadingJoinColumns) {
|
|
3935
|
+
return (
|
|
3936
|
+
<div className={styles.loading}>
|
|
3937
|
+
Loading table columns
|
|
3938
|
+
{isLoadingJoinColumns ? ' from joined tables' : ''}...
|
|
3939
|
+
</div>
|
|
3940
|
+
);
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3943
|
+
if (columnsError) {
|
|
3944
|
+
return (
|
|
3945
|
+
<div className={styles.errorPanel} role="alert">
|
|
3946
|
+
<TriangleAlert size={20} />
|
|
3947
|
+
<div>
|
|
3948
|
+
<h3>Fields unavailable</h3>
|
|
3949
|
+
<p>{columnsError}</p>
|
|
3950
|
+
</div>
|
|
3951
|
+
<button
|
|
3952
|
+
type="button"
|
|
3953
|
+
className={styles.retryButton}
|
|
3954
|
+
onClick={() => fetchTableColumns(selectedTable)}
|
|
3955
|
+
>
|
|
3956
|
+
Try again
|
|
3957
|
+
</button>
|
|
3958
|
+
</div>
|
|
3959
|
+
);
|
|
3960
|
+
}
|
|
3961
|
+
|
|
3962
|
+
if (tableColumns.length === 0) {
|
|
3963
|
+
return (
|
|
3964
|
+
<div className={styles.noColumns}>
|
|
3965
|
+
<Data size={48} />
|
|
3966
|
+
<h3>No Columns Available</h3>
|
|
3967
|
+
<p>
|
|
3968
|
+
No columns found for the selected table. Please try
|
|
3969
|
+
selecting a different table or check your configuration.
|
|
3970
|
+
</p>
|
|
3971
|
+
</div>
|
|
3972
|
+
);
|
|
3973
|
+
}
|
|
3967
3974
|
|
|
3968
3975
|
const categoryInfo = {
|
|
3969
3976
|
essential: {
|
|
@@ -4025,6 +4032,7 @@ const GenericReportImproved = ({
|
|
|
4025
4032
|
</p>
|
|
4026
4033
|
<div className={styles.quickActions}>
|
|
4027
4034
|
<button
|
|
4035
|
+
type="button"
|
|
4028
4036
|
className={styles.linkButton}
|
|
4029
4037
|
onClick={() => {
|
|
4030
4038
|
const allVisibleColumns = [];
|
|
@@ -4073,6 +4081,7 @@ const GenericReportImproved = ({
|
|
|
4073
4081
|
Select All Visible
|
|
4074
4082
|
</button>
|
|
4075
4083
|
<button
|
|
4084
|
+
type="button"
|
|
4076
4085
|
className={styles.linkButton}
|
|
4077
4086
|
onClick={() => setSelectedColumns([])}
|
|
4078
4087
|
>
|
|
@@ -4080,6 +4089,7 @@ const GenericReportImproved = ({
|
|
|
4080
4089
|
</button>
|
|
4081
4090
|
{mainHiddenCount > 0 && (
|
|
4082
4091
|
<button
|
|
4092
|
+
type="button"
|
|
4083
4093
|
className={styles.linkButton}
|
|
4084
4094
|
onClick={() =>
|
|
4085
4095
|
setShowHiddenFields(!showHiddenFields)
|
|
@@ -4484,19 +4494,12 @@ const GenericReportImproved = ({
|
|
|
4484
4494
|
type="checkbox"
|
|
4485
4495
|
checked={uniqueFieldConfig.enabled}
|
|
4486
4496
|
onChange={(e) => {
|
|
4487
|
-
console.log('🔍 [DEBUG] Unique field toggled:', e.target.checked);
|
|
4488
|
-
console.log('🔍 [DEBUG] Current uniqueFieldConfig before change:', uniqueFieldConfig);
|
|
4489
4497
|
const newConfig = {
|
|
4490
4498
|
...uniqueFieldConfig,
|
|
4491
4499
|
enabled: e.target.checked,
|
|
4492
4500
|
selectedField: e.target.checked ? uniqueFieldConfig.selectedField : ''
|
|
4493
4501
|
};
|
|
4494
|
-
console.log('🔍 [DEBUG] New unique field config:', newConfig);
|
|
4495
4502
|
setUniqueFieldConfig(newConfig);
|
|
4496
|
-
// Log immediately after setState
|
|
4497
|
-
setTimeout(() => {
|
|
4498
|
-
console.log('🔍 [DEBUG] State after setTimeout:', uniqueFieldConfig);
|
|
4499
|
-
}, 100);
|
|
4500
4503
|
}}
|
|
4501
4504
|
/>
|
|
4502
4505
|
<span className={styles.checkboxText}>
|
|
@@ -4512,27 +4515,21 @@ const GenericReportImproved = ({
|
|
|
4512
4515
|
|
|
4513
4516
|
{uniqueFieldConfig.enabled && (
|
|
4514
4517
|
<div className={styles.uniqueFieldSelector}>
|
|
4515
|
-
<label
|
|
4518
|
+
<label
|
|
4519
|
+
className={styles.fieldLabel}
|
|
4520
|
+
htmlFor="unique-field-select"
|
|
4521
|
+
>
|
|
4516
4522
|
Select field to make unique:
|
|
4517
4523
|
</label>
|
|
4518
4524
|
<select
|
|
4525
|
+
id="unique-field-select"
|
|
4519
4526
|
value={uniqueFieldConfig.selectedField}
|
|
4520
4527
|
onChange={(e) => {
|
|
4521
|
-
console.log('🔍 [DEBUG] Unique field selected:', e.target.value);
|
|
4522
|
-
console.log('🔍 [DEBUG] Available dropdown options:', Array.from(e.target.options).map(opt => ({value: opt.value, text: opt.text})));
|
|
4523
|
-
console.log('🔍 [DEBUG] Selected option text:', e.target.options[e.target.selectedIndex]?.text);
|
|
4524
|
-
console.log('🔍 [DEBUG] Current uniqueFieldConfig before field change:', uniqueFieldConfig);
|
|
4525
|
-
console.log('🔍 [DEBUG] Current selectedColumns:', selectedColumns);
|
|
4526
4528
|
const newConfig = {
|
|
4527
4529
|
...uniqueFieldConfig,
|
|
4528
4530
|
selectedField: e.target.value
|
|
4529
4531
|
};
|
|
4530
|
-
console.log('🔍 [DEBUG] Updated unique field config with selected field:', newConfig);
|
|
4531
4532
|
setUniqueFieldConfig(newConfig);
|
|
4532
|
-
// Log immediately after setState
|
|
4533
|
-
setTimeout(() => {
|
|
4534
|
-
console.log('🔍 [DEBUG] Field state after setTimeout:', uniqueFieldConfig);
|
|
4535
|
-
}, 100);
|
|
4536
4533
|
}}
|
|
4537
4534
|
className={styles.fieldSelect}
|
|
4538
4535
|
required
|
|
@@ -4564,7 +4561,9 @@ const GenericReportImproved = ({
|
|
|
4564
4561
|
// Render relationships (simplified)
|
|
4565
4562
|
const renderRelationships = () => {
|
|
4566
4563
|
const tableSuggestions = detectedJoins[selectedTable] || [];
|
|
4567
|
-
|
|
4564
|
+
// Named apart from the report-execution `isLoading` state it shadowed
|
|
4565
|
+
const isLoadingSuggestions =
|
|
4566
|
+
isLoadingDetectedJoins[selectedTable] || false;
|
|
4568
4567
|
|
|
4569
4568
|
return (
|
|
4570
4569
|
<div className={styles.relationshipsSection}>
|
|
@@ -4605,6 +4604,7 @@ const GenericReportImproved = ({
|
|
|
4605
4604
|
</span>
|
|
4606
4605
|
</div>
|
|
4607
4606
|
<button
|
|
4607
|
+
type="button"
|
|
4608
4608
|
className={styles.removeJoinBtn}
|
|
4609
4609
|
onClick={() => removeJoin(index)}
|
|
4610
4610
|
>
|
|
@@ -4615,8 +4615,14 @@ const GenericReportImproved = ({
|
|
|
4615
4615
|
</div>
|
|
4616
4616
|
)}
|
|
4617
4617
|
|
|
4618
|
+
{isLoadingSuggestions && (
|
|
4619
|
+
<div className={styles.loading}>
|
|
4620
|
+
Finding possible connections...
|
|
4621
|
+
</div>
|
|
4622
|
+
)}
|
|
4623
|
+
|
|
4618
4624
|
{/* Detected Relationships */}
|
|
4619
|
-
{tableSuggestions.length > 0 && (
|
|
4625
|
+
{!isLoadingSuggestions && tableSuggestions.length > 0 && (
|
|
4620
4626
|
<div className={styles.detectedJoins}>
|
|
4621
4627
|
<h3>Detected Connections</h3>
|
|
4622
4628
|
<p>
|
|
@@ -4639,18 +4645,21 @@ const GenericReportImproved = ({
|
|
|
4639
4645
|
definition
|
|
4640
4646
|
)}
|
|
4641
4647
|
</span>
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4648
|
+
{suggestion.confidence != null && (
|
|
4649
|
+
<span
|
|
4650
|
+
className={
|
|
4651
|
+
styles.confidenceRating
|
|
4652
|
+
}
|
|
4653
|
+
title={`Confidence: ${Math.round(
|
|
4654
|
+
suggestion.confidence
|
|
4655
|
+
)}%`}
|
|
4656
|
+
>
|
|
4657
|
+
{Math.round(
|
|
4658
|
+
suggestion.confidence
|
|
4659
|
+
)}
|
|
4660
|
+
% match
|
|
4661
|
+
</span>
|
|
4662
|
+
)}
|
|
4654
4663
|
</div>
|
|
4655
4664
|
<p
|
|
4656
4665
|
className={
|
|
@@ -4668,6 +4677,7 @@ const GenericReportImproved = ({
|
|
|
4668
4677
|
</p>
|
|
4669
4678
|
</div>
|
|
4670
4679
|
<button
|
|
4680
|
+
type="button"
|
|
4671
4681
|
className={`${styles.connectIconBtn} ${
|
|
4672
4682
|
joins.some(
|
|
4673
4683
|
(j) =>
|
|
@@ -4712,9 +4722,13 @@ const GenericReportImproved = ({
|
|
|
4712
4722
|
</div>
|
|
4713
4723
|
)}
|
|
4714
4724
|
|
|
4715
|
-
{
|
|
4716
|
-
<div className={styles.
|
|
4717
|
-
|
|
4725
|
+
{!isLoadingSuggestions && tableSuggestions.length === 0 && (
|
|
4726
|
+
<div className={styles.emptyState}>
|
|
4727
|
+
<p>
|
|
4728
|
+
No connections were detected for{' '}
|
|
4729
|
+
{getTableDisplayName(selectedTable, definition)}.
|
|
4730
|
+
You can add one manually below, or skip this step.
|
|
4731
|
+
</p>
|
|
4718
4732
|
</div>
|
|
4719
4733
|
)}
|
|
4720
4734
|
|
|
@@ -4738,6 +4752,7 @@ const GenericReportImproved = ({
|
|
|
4738
4752
|
|
|
4739
4753
|
{!showManualJoinForm ? (
|
|
4740
4754
|
<button
|
|
4755
|
+
type="button"
|
|
4741
4756
|
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
4742
4757
|
onClick={() => {
|
|
4743
4758
|
setShowManualJoinForm(true);
|
|
@@ -4915,6 +4930,7 @@ const GenericReportImproved = ({
|
|
|
4915
4930
|
|
|
4916
4931
|
<div className={styles.manualJoinActions}>
|
|
4917
4932
|
<button
|
|
4933
|
+
type="button"
|
|
4918
4934
|
className={`${styles.btn} ${styles.btnPrimary}`}
|
|
4919
4935
|
onClick={addManualJoin}
|
|
4920
4936
|
disabled={
|
|
@@ -4927,6 +4943,7 @@ const GenericReportImproved = ({
|
|
|
4927
4943
|
Add Relationship
|
|
4928
4944
|
</button>
|
|
4929
4945
|
<button
|
|
4946
|
+
type="button"
|
|
4930
4947
|
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
4931
4948
|
onClick={() => {
|
|
4932
4949
|
setShowManualJoinForm(false);
|
|
@@ -4957,6 +4974,7 @@ const GenericReportImproved = ({
|
|
|
4957
4974
|
{getTableDisplayName(selectedTable, definition)} table.
|
|
4958
4975
|
</p>
|
|
4959
4976
|
<button
|
|
4977
|
+
type="button"
|
|
4960
4978
|
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
4961
4979
|
onClick={goToNextStep}
|
|
4962
4980
|
>
|
|
@@ -5107,9 +5125,23 @@ const GenericReportImproved = ({
|
|
|
5107
5125
|
|
|
5108
5126
|
// Legacy functions for backward compatibility
|
|
5109
5127
|
const addFilter = (filter) => {
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5128
|
+
setFilterCriteria((prev) => {
|
|
5129
|
+
const groups = prev?.groups?.length
|
|
5130
|
+
? prev.groups
|
|
5131
|
+
: [{ operator: 'AND', filters: [] }];
|
|
5132
|
+
|
|
5133
|
+
return {
|
|
5134
|
+
operator: prev?.operator || 'AND',
|
|
5135
|
+
groups: groups.map((group, index) =>
|
|
5136
|
+
index === 0
|
|
5137
|
+
? {
|
|
5138
|
+
...group,
|
|
5139
|
+
filters: [...(group.filters || []), filter],
|
|
5140
|
+
}
|
|
5141
|
+
: group
|
|
5142
|
+
),
|
|
5143
|
+
};
|
|
5144
|
+
});
|
|
5113
5145
|
};
|
|
5114
5146
|
|
|
5115
5147
|
const removeFilter = (groupIndex, filterIndex) => {
|
|
@@ -5241,6 +5273,7 @@ const GenericReportImproved = ({
|
|
|
5241
5273
|
<div className={styles.quickFiltersGrid}>
|
|
5242
5274
|
{selectedTemplate.quickFilters.map((quickFilter) => (
|
|
5243
5275
|
<button
|
|
5276
|
+
type="button"
|
|
5244
5277
|
key={quickFilter.id}
|
|
5245
5278
|
className={styles.quickFilterBtn}
|
|
5246
5279
|
onClick={() =>
|
|
@@ -5389,13 +5422,27 @@ const GenericReportImproved = ({
|
|
|
5389
5422
|
|
|
5390
5423
|
// Update filter value
|
|
5391
5424
|
const updateFilter = (groupIndex, filterIndex, field, value) => {
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5425
|
+
setFilterCriteria((prev) => ({
|
|
5426
|
+
...prev,
|
|
5427
|
+
groups: (prev.groups || []).map((group, gIndex) =>
|
|
5428
|
+
gIndex === groupIndex
|
|
5429
|
+
? {
|
|
5430
|
+
...group,
|
|
5431
|
+
filters: (group.filters || []).map(
|
|
5432
|
+
(filter, fIndex) =>
|
|
5433
|
+
fIndex === filterIndex
|
|
5434
|
+
? { ...filter, [field]: value }
|
|
5435
|
+
: filter
|
|
5436
|
+
),
|
|
5437
|
+
}
|
|
5438
|
+
: group
|
|
5439
|
+
),
|
|
5440
|
+
}));
|
|
5395
5441
|
};
|
|
5396
5442
|
|
|
5397
|
-
// Get available columns for filters
|
|
5398
|
-
|
|
5443
|
+
// Get available columns for filters. Memoised: the filter step, the sort
|
|
5444
|
+
// step and the grouping step each ask for this list on every render.
|
|
5445
|
+
const availableFilterColumns = useMemo(() => {
|
|
5399
5446
|
const allColumns = [];
|
|
5400
5447
|
|
|
5401
5448
|
// Add main table columns
|
|
@@ -5437,13 +5484,15 @@ const GenericReportImproved = ({
|
|
|
5437
5484
|
});
|
|
5438
5485
|
|
|
5439
5486
|
return allColumns;
|
|
5440
|
-
};
|
|
5487
|
+
}, [tableColumns, joins, selectedTable, definition]);
|
|
5488
|
+
|
|
5489
|
+
const getAvailableFilterColumns = () => availableFilterColumns;
|
|
5441
5490
|
|
|
5442
5491
|
// Advanced filter rendering (from original GenericReport)
|
|
5443
5492
|
const renderFilters = () => {
|
|
5444
5493
|
// Ensure filterCriteria has proper structure
|
|
5445
5494
|
if (!filterCriteria || !filterCriteria.groups) {
|
|
5446
|
-
|
|
5495
|
+
debugLog(
|
|
5447
5496
|
'FilterCriteria is not properly structured, reinitializing...'
|
|
5448
5497
|
);
|
|
5449
5498
|
setFilterCriteria({
|
|
@@ -6017,14 +6066,6 @@ const GenericReportImproved = ({
|
|
|
6017
6066
|
|
|
6018
6067
|
const smartSuggestions = getSmartSuggestions();
|
|
6019
6068
|
|
|
6020
|
-
// Debug logging to check if suggestions are generated
|
|
6021
|
-
console.log('Smart suggestions debug:', {
|
|
6022
|
-
selectedTable,
|
|
6023
|
-
tableColumns: tableColumns?.length,
|
|
6024
|
-
smartSuggestions: smartSuggestions?.length,
|
|
6025
|
-
suggestions: smartSuggestions,
|
|
6026
|
-
});
|
|
6027
|
-
|
|
6028
6069
|
return (
|
|
6029
6070
|
<div className={styles.filtersSection}>
|
|
6030
6071
|
{/* Template Quick Filters */}
|
|
@@ -6089,6 +6130,7 @@ const GenericReportImproved = ({
|
|
|
6089
6130
|
className={styles.suggestionActions}
|
|
6090
6131
|
>
|
|
6091
6132
|
<button
|
|
6133
|
+
type="button"
|
|
6092
6134
|
className={`${styles.btn} ${styles.btnPrimary}`}
|
|
6093
6135
|
onClick={() =>
|
|
6094
6136
|
applySuggestion(suggestion)
|
|
@@ -6137,6 +6179,7 @@ const GenericReportImproved = ({
|
|
|
6137
6179
|
</h3>
|
|
6138
6180
|
<div className={styles.sectionHeaderActions}>
|
|
6139
6181
|
<button
|
|
6182
|
+
type="button"
|
|
6140
6183
|
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
6141
6184
|
onClick={(e) => {
|
|
6142
6185
|
e.stopPropagation();
|
|
@@ -6181,6 +6224,7 @@ const GenericReportImproved = ({
|
|
|
6181
6224
|
: 'Configure column'}
|
|
6182
6225
|
</h4>
|
|
6183
6226
|
<button
|
|
6227
|
+
type="button"
|
|
6184
6228
|
className={
|
|
6185
6229
|
styles.removeSortBtn
|
|
6186
6230
|
}
|
|
@@ -6205,8 +6249,13 @@ const GenericReportImproved = ({
|
|
|
6205
6249
|
styles.sortingField
|
|
6206
6250
|
}
|
|
6207
6251
|
>
|
|
6208
|
-
<label
|
|
6252
|
+
<label
|
|
6253
|
+
htmlFor={`sort-column-${index}`}
|
|
6254
|
+
>
|
|
6255
|
+
Column:
|
|
6256
|
+
</label>
|
|
6209
6257
|
<select
|
|
6258
|
+
id={`sort-column-${index}`}
|
|
6210
6259
|
className={
|
|
6211
6260
|
styles.sortingSelect
|
|
6212
6261
|
}
|
|
@@ -6321,8 +6370,13 @@ const GenericReportImproved = ({
|
|
|
6321
6370
|
styles.sortingField
|
|
6322
6371
|
}
|
|
6323
6372
|
>
|
|
6324
|
-
<label
|
|
6373
|
+
<label
|
|
6374
|
+
htmlFor={`sort-direction-${index}`}
|
|
6375
|
+
>
|
|
6376
|
+
Direction:
|
|
6377
|
+
</label>
|
|
6325
6378
|
<select
|
|
6379
|
+
id={`sort-direction-${index}`}
|
|
6326
6380
|
className={
|
|
6327
6381
|
styles.sortingSelect
|
|
6328
6382
|
}
|
|
@@ -6407,6 +6461,7 @@ const GenericReportImproved = ({
|
|
|
6407
6461
|
|
|
6408
6462
|
<div className={styles.filterActionButtons}>
|
|
6409
6463
|
<button
|
|
6464
|
+
type="button"
|
|
6410
6465
|
className={`${styles.btn} ${styles.btnPrimary}`}
|
|
6411
6466
|
onClick={handleAddFilterGroup}
|
|
6412
6467
|
disabled={selectedColumns.length === 0}
|
|
@@ -6475,6 +6530,7 @@ const GenericReportImproved = ({
|
|
|
6475
6530
|
|
|
6476
6531
|
<div className={styles.filterGroupActions}>
|
|
6477
6532
|
<button
|
|
6533
|
+
type="button"
|
|
6478
6534
|
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
6479
6535
|
onClick={() =>
|
|
6480
6536
|
handleAddFilterCriterion(groupIndex)
|
|
@@ -6486,6 +6542,7 @@ const GenericReportImproved = ({
|
|
|
6486
6542
|
{filterCriteria.groups &&
|
|
6487
6543
|
filterCriteria.groups.length > 1 && (
|
|
6488
6544
|
<button
|
|
6545
|
+
type="button"
|
|
6489
6546
|
className={styles.removeJoinBtn}
|
|
6490
6547
|
onClick={() =>
|
|
6491
6548
|
handleRemoveFilterGroup(
|
|
@@ -6532,6 +6589,7 @@ const GenericReportImproved = ({
|
|
|
6532
6589
|
}`}
|
|
6533
6590
|
</h4>
|
|
6534
6591
|
<button
|
|
6592
|
+
type="button"
|
|
6535
6593
|
className={
|
|
6536
6594
|
styles.removeJoinBtn
|
|
6537
6595
|
}
|
|
@@ -6558,8 +6616,13 @@ const GenericReportImproved = ({
|
|
|
6558
6616
|
styles.filterField
|
|
6559
6617
|
}
|
|
6560
6618
|
>
|
|
6561
|
-
<label
|
|
6619
|
+
<label
|
|
6620
|
+
htmlFor={`filter-column-${groupIndex}-${filterIndex}`}
|
|
6621
|
+
>
|
|
6622
|
+
Column:
|
|
6623
|
+
</label>
|
|
6562
6624
|
<select
|
|
6625
|
+
id={`filter-column-${groupIndex}-${filterIndex}`}
|
|
6563
6626
|
className={
|
|
6564
6627
|
styles.filterSelect
|
|
6565
6628
|
}
|
|
@@ -6637,8 +6700,13 @@ const GenericReportImproved = ({
|
|
|
6637
6700
|
styles.filterField
|
|
6638
6701
|
}
|
|
6639
6702
|
>
|
|
6640
|
-
<label
|
|
6703
|
+
<label
|
|
6704
|
+
htmlFor={`filter-operator-${groupIndex}-${filterIndex}`}
|
|
6705
|
+
>
|
|
6706
|
+
Operator:
|
|
6707
|
+
</label>
|
|
6641
6708
|
<select
|
|
6709
|
+
id={`filter-operator-${groupIndex}-${filterIndex}`}
|
|
6642
6710
|
className={
|
|
6643
6711
|
styles.filterSelect
|
|
6644
6712
|
}
|
|
@@ -6711,10 +6779,13 @@ const GenericReportImproved = ({
|
|
|
6711
6779
|
styles.filterField
|
|
6712
6780
|
}
|
|
6713
6781
|
>
|
|
6714
|
-
<label
|
|
6782
|
+
<label
|
|
6783
|
+
htmlFor={`filter-value-${groupIndex}-${filterIndex}`}
|
|
6784
|
+
>
|
|
6715
6785
|
Value:
|
|
6716
6786
|
</label>
|
|
6717
6787
|
<input
|
|
6788
|
+
id={`filter-value-${groupIndex}-${filterIndex}`}
|
|
6718
6789
|
type="text"
|
|
6719
6790
|
className={
|
|
6720
6791
|
styles.filterInput
|
|
@@ -6768,6 +6839,7 @@ const GenericReportImproved = ({
|
|
|
6768
6839
|
records.
|
|
6769
6840
|
</p>
|
|
6770
6841
|
<button
|
|
6842
|
+
type="button"
|
|
6771
6843
|
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
6772
6844
|
onClick={goToNextStep}
|
|
6773
6845
|
>
|
|
@@ -6906,41 +6978,11 @@ const GenericReportImproved = ({
|
|
|
6906
6978
|
excludedRows: Array.from(excludedRows),
|
|
6907
6979
|
};
|
|
6908
6980
|
|
|
6909
|
-
// Debug log for exports
|
|
6910
|
-
if (uniqueFieldConfig.enabled) {
|
|
6911
|
-
console.group('🔍 [DEBUG] Export - Unique Field Configuration');
|
|
6912
|
-
console.log('Export format:', format);
|
|
6913
|
-
console.log('Export unique config:', exportUniqueConfig);
|
|
6914
|
-
console.log('Query config:', queryConfig);
|
|
6915
|
-
console.groupEnd();
|
|
6916
|
-
}
|
|
6917
|
-
|
|
6918
6981
|
const exportData = {
|
|
6919
6982
|
query: queryConfig,
|
|
6920
6983
|
format: format === 'excel' ? 'xlsx' : format, // Convert excel to xlsx
|
|
6921
6984
|
};
|
|
6922
6985
|
|
|
6923
|
-
// DEBUG: Log the complete request payload
|
|
6924
|
-
console.group('🐛 PDF Export Debug');
|
|
6925
|
-
console.log('Export Request Details:');
|
|
6926
|
-
console.log('URL:', exportUrl);
|
|
6927
|
-
console.log('Format:', format);
|
|
6928
|
-
console.log('Report Name:', reportName);
|
|
6929
|
-
console.log('Selected Table:', selectedTable);
|
|
6930
|
-
console.log('Selected Columns:', selectedColumns);
|
|
6931
|
-
console.log('Joins:', joins);
|
|
6932
|
-
console.log('Filter Criteria:', filterCriteria);
|
|
6933
|
-
console.log(
|
|
6934
|
-
'Flattened Filters:',
|
|
6935
|
-
flattenFilterCriteria(filterCriteria)
|
|
6936
|
-
);
|
|
6937
|
-
console.log('Sort Criteria:', sortCriteria);
|
|
6938
|
-
console.log(
|
|
6939
|
-
'🚀 Final Payload:',
|
|
6940
|
-
JSON.stringify(exportData, null, 2)
|
|
6941
|
-
);
|
|
6942
|
-
console.groupEnd();
|
|
6943
|
-
|
|
6944
6986
|
// Generate filename with timestamp
|
|
6945
6987
|
const timestamp = moment().format('YYYY-MM-DD_HH-mm-ss');
|
|
6946
6988
|
const fileName = `${reportName.replace(
|
|
@@ -6948,109 +6990,57 @@ const GenericReportImproved = ({
|
|
|
6948
6990
|
'_'
|
|
6949
6991
|
)}_${timestamp}.${format === 'excel' ? 'xlsx' : format}`;
|
|
6950
6992
|
|
|
6951
|
-
console.log('📁 Generated filename:', fileName);
|
|
6952
|
-
|
|
6953
6993
|
// Priority: custom export URL > template export URL > default export URL
|
|
6954
6994
|
const finalExportUrl =
|
|
6955
6995
|
customUrls.exportUrl ||
|
|
6956
6996
|
selectedTemplate?.exportUrl ||
|
|
6957
6997
|
exportUrl;
|
|
6958
6998
|
|
|
6959
|
-
console.log('🔍 [DEBUG] Export API Call:', {
|
|
6960
|
-
selectedTemplate: selectedTemplate?.id,
|
|
6961
|
-
customExportUrl: customUrls.exportUrl,
|
|
6962
|
-
templateExportUrl: selectedTemplate?.exportUrl,
|
|
6963
|
-
defaultExportUrl: exportUrl,
|
|
6964
|
-
finalExportUrl,
|
|
6965
|
-
usingCustomExportUrl: !!customUrls.exportUrl,
|
|
6966
|
-
usingTemplateExportUrl: !!selectedTemplate?.exportUrl,
|
|
6967
|
-
});
|
|
6968
|
-
|
|
6969
6999
|
// Use Download component which handles error checking and blob download properly
|
|
6970
|
-
console.log('📡 Making request to:', finalExportUrl);
|
|
6971
7000
|
const result = await Download(finalExportUrl, 'POST', exportData);
|
|
6972
7001
|
|
|
6973
|
-
console.log('Download component returned result:', result);
|
|
6974
|
-
|
|
6975
7002
|
if (result && result.data) {
|
|
6976
|
-
console.log('Result has data, triggering download');
|
|
6977
|
-
console.log('Result data type:', typeof result.data);
|
|
6978
|
-
console.log(
|
|
6979
|
-
'Result data instanceof Blob:',
|
|
6980
|
-
result.data instanceof Blob
|
|
6981
|
-
);
|
|
6982
|
-
|
|
6983
|
-
// Use saveAs to trigger download
|
|
6984
7003
|
saveAs(result.data, fileName);
|
|
6985
7004
|
toast.success(`Report exported as ${format.toUpperCase()}`);
|
|
6986
7005
|
} else {
|
|
6987
|
-
|
|
6988
|
-
console.log('Full result object:', result);
|
|
7006
|
+
debugLog('Export returned no data', result);
|
|
6989
7007
|
toast.error(
|
|
6990
7008
|
`Failed to export report as ${format.toUpperCase()}`
|
|
6991
7009
|
);
|
|
6992
7010
|
}
|
|
6993
7011
|
} catch (error) {
|
|
6994
|
-
//
|
|
6995
|
-
console.group('❌ Export Error Debug');
|
|
6996
|
-
console.error('Error object:', error);
|
|
6997
|
-
console.log('Error name:', error.name);
|
|
6998
|
-
console.log('Error message:', error.message);
|
|
6999
|
-
console.log('Error stack:', error.stack);
|
|
7000
|
-
|
|
7012
|
+
// Errors may arrive as binary blobs — unwrap them for a readable message
|
|
7001
7013
|
let errorMessage = `Failed to export report as ${format.toUpperCase()}`;
|
|
7002
7014
|
|
|
7003
7015
|
if (error.response) {
|
|
7004
|
-
console.log('📡 Error response received');
|
|
7005
|
-
console.log('Response status:', error.response.status);
|
|
7006
|
-
console.log('Response statusText:', error.response.statusText);
|
|
7007
|
-
console.log('Response headers:', error.response.headers);
|
|
7008
|
-
console.log('Response data type:', typeof error.response.data);
|
|
7009
|
-
console.log(
|
|
7010
|
-
'Response data instanceof Blob:',
|
|
7011
|
-
error.response.data instanceof Blob
|
|
7012
|
-
);
|
|
7013
|
-
console.log('Raw response data:', error.response.data);
|
|
7014
|
-
|
|
7015
7016
|
if (error.response.data instanceof Blob) {
|
|
7016
7017
|
// If error response is a blob, try to read it as text
|
|
7017
7018
|
try {
|
|
7018
|
-
console.log('Attempting to parse blob error response');
|
|
7019
7019
|
const errorText = await error.response.data.text();
|
|
7020
|
-
console.log('Blob content as text:', errorText);
|
|
7021
7020
|
|
|
7022
7021
|
try {
|
|
7023
7022
|
const errorData = JSON.parse(errorText);
|
|
7024
|
-
console.log('Parsed JSON error data:', errorData);
|
|
7025
7023
|
errorMessage =
|
|
7026
7024
|
errorData.message ||
|
|
7027
7025
|
errorData.error ||
|
|
7028
7026
|
errorMessage;
|
|
7029
7027
|
} catch (jsonError) {
|
|
7030
|
-
|
|
7031
|
-
'
|
|
7028
|
+
debugLog(
|
|
7029
|
+
'Export error blob was not JSON:',
|
|
7032
7030
|
jsonError
|
|
7033
7031
|
);
|
|
7034
7032
|
errorMessage = errorText || errorMessage;
|
|
7035
7033
|
}
|
|
7036
7034
|
} catch (parseError) {
|
|
7037
7035
|
// If parsing fails, use default message
|
|
7038
|
-
|
|
7039
|
-
'
|
|
7036
|
+
debugLog(
|
|
7037
|
+
'Could not read export error response:',
|
|
7040
7038
|
parseError
|
|
7041
7039
|
);
|
|
7042
7040
|
}
|
|
7043
7041
|
} else if (error.response.data && error.response.data.message) {
|
|
7044
|
-
console.log(
|
|
7045
|
-
'Using error.response.data.message:',
|
|
7046
|
-
error.response.data.message
|
|
7047
|
-
);
|
|
7048
7042
|
errorMessage = error.response.data.message;
|
|
7049
7043
|
} else if (error.response.data && error.response.data.error) {
|
|
7050
|
-
console.log(
|
|
7051
|
-
'Using error.response.data.error:',
|
|
7052
|
-
error.response.data.error
|
|
7053
|
-
);
|
|
7054
7044
|
errorMessage = error.response.data.error;
|
|
7055
7045
|
}
|
|
7056
7046
|
|
|
@@ -7061,27 +7051,29 @@ const GenericReportImproved = ({
|
|
|
7061
7051
|
• Memory limitations on the server
|
|
7062
7052
|
• Try reducing the dataset or using Excel/CSV format`;
|
|
7063
7053
|
}
|
|
7064
|
-
} else {
|
|
7065
|
-
console.log('❌ No error.response available');
|
|
7066
7054
|
}
|
|
7067
7055
|
|
|
7068
|
-
console.log('🔔 Final error message:', errorMessage);
|
|
7069
|
-
console.groupEnd();
|
|
7070
|
-
|
|
7071
7056
|
toast.error(errorMessage);
|
|
7072
|
-
|
|
7057
|
+
debugLog('Export error:', error);
|
|
7073
7058
|
}
|
|
7074
7059
|
};
|
|
7075
7060
|
|
|
7076
7061
|
// Handle row exclusion modal
|
|
7077
|
-
const handleExportWithExclusion = (
|
|
7078
|
-
|
|
7062
|
+
const handleExportWithExclusion = (requestedType = 'regular') => {
|
|
7063
|
+
const hasRows =
|
|
7064
|
+
requestedType === 'grouped'
|
|
7065
|
+
? (groupedData?.groupedData?.groups || []).some(
|
|
7066
|
+
(group) => (group.rows || []).length > 0
|
|
7067
|
+
)
|
|
7068
|
+
: previewData.length > 0;
|
|
7069
|
+
|
|
7070
|
+
if (!hasRows) {
|
|
7079
7071
|
toast.error('No data available to export');
|
|
7080
7072
|
return;
|
|
7081
7073
|
}
|
|
7082
7074
|
|
|
7083
7075
|
setShowRowExclusionModal(true);
|
|
7084
|
-
setExportType(
|
|
7076
|
+
setExportType(requestedType);
|
|
7085
7077
|
};
|
|
7086
7078
|
|
|
7087
7079
|
const confirmExportWithExclusion = async () => {
|
|
@@ -7121,21 +7113,12 @@ const GenericReportImproved = ({
|
|
|
7121
7113
|
}
|
|
7122
7114
|
|
|
7123
7115
|
try {
|
|
7124
|
-
//
|
|
7125
|
-
|
|
7126
|
-
setPreviewData(filteredData);
|
|
7127
|
-
|
|
7128
|
-
// Use the existing client-side Excel export functionality
|
|
7116
|
+
// Excluded rows are sent to the server as part of the query payload
|
|
7117
|
+
// (see queryConfig.excludedRows), so the preview state stays untouched.
|
|
7129
7118
|
await exportReport('excel');
|
|
7130
|
-
|
|
7131
|
-
// Restore original data
|
|
7132
|
-
setPreviewData(originalPreviewData);
|
|
7133
7119
|
} catch (error) {
|
|
7134
|
-
|
|
7120
|
+
debugLog('Regular export failed:', error);
|
|
7135
7121
|
toast.error('Export failed. Please try again.');
|
|
7136
|
-
|
|
7137
|
-
// Restore original data on error as well
|
|
7138
|
-
setPreviewData(previewData);
|
|
7139
7122
|
}
|
|
7140
7123
|
};
|
|
7141
7124
|
|
|
@@ -7145,44 +7128,25 @@ const GenericReportImproved = ({
|
|
|
7145
7128
|
|
|
7146
7129
|
// Perform the actual grouped export
|
|
7147
7130
|
const performGroupedExport = async () => {
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7131
|
+
// A grouped report can come from a business template or from the
|
|
7132
|
+
// wizard's own grouping step, so fall back through both.
|
|
7133
|
+
const exportTitle =
|
|
7134
|
+
selectedTemplate?.name ||
|
|
7135
|
+
reportName ||
|
|
7136
|
+
(selectedTable ? `${formatName(selectedTable)} Report` : 'Report');
|
|
7152
7137
|
|
|
7153
|
-
// Debug logging to understand the groupedData state
|
|
7154
|
-
console.log('🔍 [DEBUG] Export function called with:', {
|
|
7155
|
-
groupedData: groupedData,
|
|
7156
|
-
hasGroupedData: !!groupedData,
|
|
7157
|
-
hasGroups: !!(groupedData && groupedData.groups),
|
|
7158
|
-
groupsLength:
|
|
7159
|
-
groupedData && groupedData.groups
|
|
7160
|
-
? groupedData.groups.length
|
|
7161
|
-
: 'undefined',
|
|
7162
|
-
selectedColumns: selectedColumns,
|
|
7163
|
-
selectedTemplate: selectedTemplate?.name,
|
|
7164
|
-
});
|
|
7165
|
-
|
|
7166
|
-
// Check the correct path: groupedData.groupedData.groups
|
|
7167
7138
|
const actualGroups = groupedData?.groupedData?.groups;
|
|
7168
|
-
if (!
|
|
7169
|
-
|
|
7170
|
-
groupedData: groupedData,
|
|
7171
|
-
hasGroupedData: !!groupedData,
|
|
7172
|
-
hasGroupedDataProperty: !!(
|
|
7173
|
-
groupedData && groupedData.groupedData
|
|
7174
|
-
),
|
|
7175
|
-
hasGroups: !!(
|
|
7176
|
-
groupedData &&
|
|
7177
|
-
groupedData.groupedData &&
|
|
7178
|
-
groupedData.groupedData.groups
|
|
7179
|
-
),
|
|
7180
|
-
groupsLength: actualGroups ? actualGroups.length : 'undefined',
|
|
7181
|
-
});
|
|
7139
|
+
if (!Array.isArray(actualGroups) || actualGroups.length === 0) {
|
|
7140
|
+
debugLog('Grouped export skipped — no grouped data:', groupedData);
|
|
7182
7141
|
toast.error('No grouped data available for export');
|
|
7183
7142
|
return;
|
|
7184
7143
|
}
|
|
7185
7144
|
|
|
7145
|
+
if (!Array.isArray(selectedColumns) || selectedColumns.length === 0) {
|
|
7146
|
+
toast.error('No fields selected to export');
|
|
7147
|
+
return;
|
|
7148
|
+
}
|
|
7149
|
+
|
|
7186
7150
|
try {
|
|
7187
7151
|
toast.info('Generating Excel export...');
|
|
7188
7152
|
|
|
@@ -7245,7 +7209,7 @@ const GenericReportImproved = ({
|
|
|
7245
7209
|
|
|
7246
7210
|
// Handle Comments field - strip HTML and formatting
|
|
7247
7211
|
if (column.displayName === 'Comments' && value) {
|
|
7248
|
-
return value
|
|
7212
|
+
return String(value)
|
|
7249
7213
|
.replace(/• /g, '')
|
|
7250
7214
|
.replace(/<[^>]*>/g, '')
|
|
7251
7215
|
.trim();
|
|
@@ -7290,13 +7254,18 @@ const GenericReportImproved = ({
|
|
|
7290
7254
|
actualGroups.forEach((group, groupIndex) => {
|
|
7291
7255
|
// Add group header row
|
|
7292
7256
|
const groupDisplayName =
|
|
7293
|
-
selectedTemplate?.grouping?.groupDisplayName ||
|
|
7257
|
+
selectedTemplate?.grouping?.groupDisplayName ||
|
|
7258
|
+
groupingConfig.groupDisplayName ||
|
|
7259
|
+
'Group';
|
|
7294
7260
|
const groupHeaderRow = Array(headers.length).fill('');
|
|
7295
7261
|
groupHeaderRow[0] = `${groupDisplayName}: ${group.groupDisplayName}`;
|
|
7296
7262
|
worksheetData.push(groupHeaderRow);
|
|
7297
7263
|
|
|
7298
7264
|
// Add data rows for this group (excluding excluded rows)
|
|
7299
|
-
const allRows = [
|
|
7265
|
+
const allRows = [
|
|
7266
|
+
...(group.rows || []),
|
|
7267
|
+
...(group.emptyRows || []),
|
|
7268
|
+
];
|
|
7300
7269
|
allRows.forEach((row, rowIndex) => {
|
|
7301
7270
|
// Create a unique identifier for the row
|
|
7302
7271
|
const rowId = row.id || `group-${groupIndex}-row-${rowIndex}`;
|
|
@@ -7343,14 +7312,17 @@ const GenericReportImproved = ({
|
|
|
7343
7312
|
worksheet['!cols'] = colWidths;
|
|
7344
7313
|
|
|
7345
7314
|
// Add worksheet to workbook
|
|
7346
|
-
|
|
7315
|
+
// Excel sheet names are capped at 31 chars and reject [ ] : * ? / \\
|
|
7316
|
+
const sheetName =
|
|
7317
|
+
exportTitle.replace(/[[\]:*?/\\]/g, ' ').slice(0, 31) ||
|
|
7318
|
+
'Grouped Report';
|
|
7347
7319
|
XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
|
|
7348
7320
|
|
|
7349
7321
|
// Generate file and download
|
|
7350
|
-
const filename = `${
|
|
7322
|
+
const filename = `${exportTitle.replace(
|
|
7351
7323
|
/[^a-zA-Z0-9]/g,
|
|
7352
7324
|
'_'
|
|
7353
|
-
)}_grouped_report_${
|
|
7325
|
+
)}_grouped_report_${moment().format('YYYY-MM-DD')}.xlsx`;
|
|
7354
7326
|
const excelBuffer = XLSX.write(workbook, {
|
|
7355
7327
|
bookType: 'xlsx',
|
|
7356
7328
|
type: 'array',
|
|
@@ -7362,7 +7334,7 @@ const GenericReportImproved = ({
|
|
|
7362
7334
|
saveAs(blob, filename);
|
|
7363
7335
|
toast.success('Excel export completed successfully!');
|
|
7364
7336
|
} catch (error) {
|
|
7365
|
-
|
|
7337
|
+
debugLog('Export error:', error);
|
|
7366
7338
|
toast.error(`Export failed: ${error.message}`);
|
|
7367
7339
|
}
|
|
7368
7340
|
};
|
|
@@ -7391,9 +7363,9 @@ const GenericReportImproved = ({
|
|
|
7391
7363
|
<div style="text-align: left;">
|
|
7392
7364
|
${
|
|
7393
7365
|
isExistingReport
|
|
7394
|
-
? `<div style="background: #
|
|
7395
|
-
<strong
|
|
7396
|
-
"${reportName}"
|
|
7366
|
+
? `<div style="background: var(--bg-color, #f7f7f8); border: 1px solid var(--border-color, #e1e1e1); border-radius: var(--br, 5px); padding: 12px; margin-bottom: 16px;">
|
|
7367
|
+
<strong>This will overwrite the existing report:</strong><br>
|
|
7368
|
+
"${escapeHtml(reportName)}"
|
|
7397
7369
|
</div>`
|
|
7398
7370
|
: ''
|
|
7399
7371
|
}
|
|
@@ -7401,14 +7373,16 @@ const GenericReportImproved = ({
|
|
|
7401
7373
|
<input
|
|
7402
7374
|
id="report-name"
|
|
7403
7375
|
type="text"
|
|
7404
|
-
value="${
|
|
7376
|
+
value="${escapeHtml(
|
|
7405
7377
|
saveAs
|
|
7406
7378
|
? `${reportName} (Copy)`
|
|
7407
7379
|
: reportName ||
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7380
|
+
`${formatName(
|
|
7381
|
+
selectedTable
|
|
7382
|
+
)} Report - ${moment().format(
|
|
7383
|
+
'YYYY-MM-DD'
|
|
7384
|
+
)}`
|
|
7385
|
+
)}"
|
|
7412
7386
|
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 16px;"
|
|
7413
7387
|
/>
|
|
7414
7388
|
<label style="display: flex; align-items: center; gap: 8px;">
|
|
@@ -7426,9 +7400,16 @@ const GenericReportImproved = ({
|
|
|
7426
7400
|
preConfirm: () => {
|
|
7427
7401
|
const reportNameInput = document.getElementById('report-name');
|
|
7428
7402
|
const isPublicInput = document.getElementById('is-public');
|
|
7403
|
+
const name = (reportNameInput?.value || '').trim();
|
|
7404
|
+
|
|
7405
|
+
if (!name) {
|
|
7406
|
+
Swal.showValidationMessage('Please enter a report name');
|
|
7407
|
+
return false;
|
|
7408
|
+
}
|
|
7409
|
+
|
|
7429
7410
|
return {
|
|
7430
|
-
name
|
|
7431
|
-
isPublic: isPublicInput
|
|
7411
|
+
name,
|
|
7412
|
+
isPublic: !!isPublicInput?.checked,
|
|
7432
7413
|
};
|
|
7433
7414
|
},
|
|
7434
7415
|
});
|
|
@@ -7498,7 +7479,7 @@ const GenericReportImproved = ({
|
|
|
7498
7479
|
}
|
|
7499
7480
|
} catch (error) {
|
|
7500
7481
|
toast.error('Failed to save report');
|
|
7501
|
-
|
|
7482
|
+
debugLog('Save error:', error);
|
|
7502
7483
|
}
|
|
7503
7484
|
}
|
|
7504
7485
|
};
|
|
@@ -7543,10 +7524,14 @@ const GenericReportImproved = ({
|
|
|
7543
7524
|
<div className={styles.groupingOptions}>
|
|
7544
7525
|
{/* Group By Field Selection */}
|
|
7545
7526
|
<div className={styles.formGroup}>
|
|
7546
|
-
<label
|
|
7527
|
+
<label
|
|
7528
|
+
className={styles.formLabel}
|
|
7529
|
+
htmlFor="grouping-field"
|
|
7530
|
+
>
|
|
7547
7531
|
Group By Field *
|
|
7548
7532
|
</label>
|
|
7549
7533
|
<select
|
|
7534
|
+
id="grouping-field"
|
|
7550
7535
|
value={groupingConfig.groupByField}
|
|
7551
7536
|
onChange={(e) => {
|
|
7552
7537
|
const selectedOption =
|
|
@@ -7599,10 +7584,14 @@ const GenericReportImproved = ({
|
|
|
7599
7584
|
|
|
7600
7585
|
{/* Group Display Name */}
|
|
7601
7586
|
<div className={styles.formGroup}>
|
|
7602
|
-
<label
|
|
7587
|
+
<label
|
|
7588
|
+
className={styles.formLabel}
|
|
7589
|
+
htmlFor="grouping-display-name"
|
|
7590
|
+
>
|
|
7603
7591
|
Group Display Name
|
|
7604
7592
|
</label>
|
|
7605
7593
|
<input
|
|
7594
|
+
id="grouping-display-name"
|
|
7606
7595
|
type="text"
|
|
7607
7596
|
value={groupingConfig.groupDisplayName}
|
|
7608
7597
|
onChange={(e) =>
|
|
@@ -7628,10 +7617,14 @@ const GenericReportImproved = ({
|
|
|
7628
7617
|
|
|
7629
7618
|
<div className={styles.optionsGrid}>
|
|
7630
7619
|
<div className={styles.formGroup}>
|
|
7631
|
-
<label
|
|
7620
|
+
<label
|
|
7621
|
+
className={styles.formLabel}
|
|
7622
|
+
htmlFor="grouping-rows-per-group"
|
|
7623
|
+
>
|
|
7632
7624
|
Rows Per Group
|
|
7633
7625
|
</label>
|
|
7634
7626
|
<input
|
|
7627
|
+
id="grouping-rows-per-group"
|
|
7635
7628
|
type="number"
|
|
7636
7629
|
min="1"
|
|
7637
7630
|
max="100"
|
|
@@ -7650,10 +7643,14 @@ const GenericReportImproved = ({
|
|
|
7650
7643
|
</div>
|
|
7651
7644
|
|
|
7652
7645
|
<div className={styles.formGroup}>
|
|
7653
|
-
<label
|
|
7646
|
+
<label
|
|
7647
|
+
className={styles.formLabel}
|
|
7648
|
+
htmlFor="grouping-header-style"
|
|
7649
|
+
>
|
|
7654
7650
|
Group Header Style
|
|
7655
7651
|
</label>
|
|
7656
7652
|
<select
|
|
7653
|
+
id="grouping-header-style"
|
|
7657
7654
|
value={
|
|
7658
7655
|
groupingConfig.groupHeaderStyle
|
|
7659
7656
|
}
|
|
@@ -7682,10 +7679,14 @@ const GenericReportImproved = ({
|
|
|
7682
7679
|
</div>
|
|
7683
7680
|
|
|
7684
7681
|
<div className={styles.formGroup}>
|
|
7685
|
-
<label
|
|
7682
|
+
<label
|
|
7683
|
+
className={styles.formLabel}
|
|
7684
|
+
htmlFor="grouping-sort-direction"
|
|
7685
|
+
>
|
|
7686
7686
|
Sort Direction
|
|
7687
7687
|
</label>
|
|
7688
7688
|
<select
|
|
7689
|
+
id="grouping-sort-direction"
|
|
7689
7690
|
value={groupingConfig.sortDirection}
|
|
7690
7691
|
onChange={(e) =>
|
|
7691
7692
|
setGroupingConfig({
|
|
@@ -7815,10 +7816,14 @@ const GenericReportImproved = ({
|
|
|
7815
7816
|
|
|
7816
7817
|
<div className={styles.optionsGrid}>
|
|
7817
7818
|
<div className={styles.formGroup}>
|
|
7818
|
-
<label
|
|
7819
|
+
<label
|
|
7820
|
+
className={styles.formLabel}
|
|
7821
|
+
htmlFor="custom-execute-url"
|
|
7822
|
+
>
|
|
7819
7823
|
Custom Execute URL
|
|
7820
7824
|
</label>
|
|
7821
7825
|
<input
|
|
7826
|
+
id="custom-execute-url"
|
|
7822
7827
|
type="text"
|
|
7823
7828
|
value={customUrls.executeUrl}
|
|
7824
7829
|
onChange={(e) =>
|
|
@@ -7837,10 +7842,14 @@ const GenericReportImproved = ({
|
|
|
7837
7842
|
</div>
|
|
7838
7843
|
|
|
7839
7844
|
<div className={styles.formGroup}>
|
|
7840
|
-
<label
|
|
7845
|
+
<label
|
|
7846
|
+
className={styles.formLabel}
|
|
7847
|
+
htmlFor="custom-export-url"
|
|
7848
|
+
>
|
|
7841
7849
|
Custom Export URL
|
|
7842
7850
|
</label>
|
|
7843
7851
|
<input
|
|
7852
|
+
id="custom-export-url"
|
|
7844
7853
|
type="text"
|
|
7845
7854
|
value={customUrls.exportUrl}
|
|
7846
7855
|
onChange={(e) =>
|
|
@@ -7865,17 +7874,30 @@ const GenericReportImproved = ({
|
|
|
7865
7874
|
);
|
|
7866
7875
|
};
|
|
7867
7876
|
|
|
7877
|
+
// Column set for the preview table. The response keys are authoritative —
|
|
7878
|
+
// gridColumns is only a fallback for the "no rows yet" case.
|
|
7879
|
+
const previewColumns = useMemo(() => {
|
|
7880
|
+
if (previewData.length > 0 && previewData[0]) {
|
|
7881
|
+
return Object.keys(previewData[0]).map((key) => ({
|
|
7882
|
+
name: key,
|
|
7883
|
+
header: key.replace(/\./g, ' '),
|
|
7884
|
+
}));
|
|
7885
|
+
}
|
|
7886
|
+
return Array.isArray(gridColumns) ? gridColumns : [];
|
|
7887
|
+
}, [previewData, gridColumns]);
|
|
7888
|
+
|
|
7868
7889
|
// Render preview
|
|
7869
7890
|
const renderPreview = () => {
|
|
7870
7891
|
return (
|
|
7871
7892
|
<div className={styles.previewSection}>
|
|
7872
7893
|
<div className={styles.previewActions}>
|
|
7873
7894
|
<button
|
|
7895
|
+
type="button"
|
|
7874
7896
|
className={`${styles.btn} ${styles.btnPrimary}`}
|
|
7875
7897
|
onClick={() => {
|
|
7876
7898
|
resetPagination();
|
|
7877
7899
|
// Don't reset hasAutoExecuted to avoid triggering useEffect
|
|
7878
|
-
executeReport();
|
|
7900
|
+
executeReport({ page: 1 });
|
|
7879
7901
|
}}
|
|
7880
7902
|
disabled={isLoading}
|
|
7881
7903
|
>
|
|
@@ -7888,6 +7910,7 @@ const GenericReportImproved = ({
|
|
|
7888
7910
|
<div className={styles.viewToggle}>
|
|
7889
7911
|
<span className={styles.viewLabel}>View:</span>
|
|
7890
7912
|
<button
|
|
7913
|
+
type="button"
|
|
7891
7914
|
className={`${styles.btn} ${
|
|
7892
7915
|
styles.btnSecondary
|
|
7893
7916
|
} ${groupedData ? styles.active : ''}`}
|
|
@@ -7903,9 +7926,10 @@ const GenericReportImproved = ({
|
|
|
7903
7926
|
disabled={isLoading}
|
|
7904
7927
|
title="Show grouped report"
|
|
7905
7928
|
>
|
|
7906
|
-
|
|
7929
|
+
Grouped
|
|
7907
7930
|
</button>
|
|
7908
7931
|
<button
|
|
7932
|
+
type="button"
|
|
7909
7933
|
className={`${styles.btn} ${
|
|
7910
7934
|
styles.btnSecondary
|
|
7911
7935
|
} ${!groupedData ? styles.active : ''}`}
|
|
@@ -7921,28 +7945,20 @@ const GenericReportImproved = ({
|
|
|
7921
7945
|
disabled={isLoading}
|
|
7922
7946
|
title="Show regular table"
|
|
7923
7947
|
>
|
|
7924
|
-
|
|
7948
|
+
Table
|
|
7925
7949
|
</button>
|
|
7926
7950
|
</div>
|
|
7927
7951
|
)}
|
|
7928
7952
|
</div>
|
|
7929
7953
|
|
|
7954
|
+
{isLoading && (
|
|
7955
|
+
<div className={styles.loading}>
|
|
7956
|
+
Running your report...
|
|
7957
|
+
</div>
|
|
7958
|
+
)}
|
|
7959
|
+
|
|
7930
7960
|
{/* Render grouped report if available */}
|
|
7931
|
-
{
|
|
7932
|
-
console.log(
|
|
7933
|
-
'🎯 [DEBUG] Render check - groupedData:',
|
|
7934
|
-
groupedData
|
|
7935
|
-
);
|
|
7936
|
-
console.log(
|
|
7937
|
-
'🎯 [DEBUG] Render check - groupedData?.grouped:',
|
|
7938
|
-
groupedData?.grouped
|
|
7939
|
-
);
|
|
7940
|
-
console.log(
|
|
7941
|
-
'🎯 [DEBUG] Render check - selectedColumns:',
|
|
7942
|
-
selectedColumns
|
|
7943
|
-
);
|
|
7944
|
-
return groupedData && groupedData.grouped;
|
|
7945
|
-
})() && (
|
|
7961
|
+
{!isLoading && groupedData?.grouped && groupedData.groupedData && (
|
|
7946
7962
|
<div className={styles.groupedReportContainer}>
|
|
7947
7963
|
<GroupedReportRenderer
|
|
7948
7964
|
data={{
|
|
@@ -7955,15 +7971,13 @@ const GenericReportImproved = ({
|
|
|
7955
7971
|
: selectedTemplate?.grouping || {}
|
|
7956
7972
|
}
|
|
7957
7973
|
columns={selectedColumns}
|
|
7958
|
-
onExport={
|
|
7959
|
-
handleGroupedExport();
|
|
7960
|
-
}}
|
|
7974
|
+
onExport={handleGroupedExport}
|
|
7961
7975
|
/>
|
|
7962
7976
|
</div>
|
|
7963
7977
|
)}
|
|
7964
7978
|
|
|
7965
7979
|
{/* Render regular grid if no grouping */}
|
|
7966
|
-
{!groupedData && previewData.length > 0 && (
|
|
7980
|
+
{!isLoading && !groupedData && previewData.length > 0 && (
|
|
7967
7981
|
<>
|
|
7968
7982
|
<div className={styles.gridContainer}>
|
|
7969
7983
|
<div className="grouped-report">
|
|
@@ -7979,6 +7993,7 @@ const GenericReportImproved = ({
|
|
|
7979
7993
|
</div>
|
|
7980
7994
|
<div className="grouped-report-actions">
|
|
7981
7995
|
<button
|
|
7996
|
+
type="button"
|
|
7982
7997
|
className="btn btn-export"
|
|
7983
7998
|
onClick={() =>
|
|
7984
7999
|
handleRegularExport()
|
|
@@ -7997,67 +8012,27 @@ const GenericReportImproved = ({
|
|
|
7997
8012
|
<table className="grouped-table">
|
|
7998
8013
|
<thead>
|
|
7999
8014
|
<tr>
|
|
8000
|
-
{(
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
header: key.replace(
|
|
8012
|
-
/\./g,
|
|
8013
|
-
' '
|
|
8014
|
-
),
|
|
8015
|
-
})
|
|
8016
|
-
)
|
|
8017
|
-
: gridColumns;
|
|
8018
|
-
return headerColumns.map(
|
|
8019
|
-
(col, index) => (
|
|
8020
|
-
<th
|
|
8021
|
-
key={index}
|
|
8022
|
-
className="table-header"
|
|
8023
|
-
>
|
|
8024
|
-
{col.header ||
|
|
8025
|
-
col.name}
|
|
8026
|
-
</th>
|
|
8027
|
-
)
|
|
8028
|
-
);
|
|
8029
|
-
})()}
|
|
8015
|
+
{previewColumns.map(
|
|
8016
|
+
(col, index) => (
|
|
8017
|
+
<th
|
|
8018
|
+
key={index}
|
|
8019
|
+
className="table-header"
|
|
8020
|
+
>
|
|
8021
|
+
{col.header ||
|
|
8022
|
+
col.name}
|
|
8023
|
+
</th>
|
|
8024
|
+
)
|
|
8025
|
+
)}
|
|
8030
8026
|
</tr>
|
|
8031
8027
|
</thead>
|
|
8032
8028
|
<tbody>
|
|
8033
|
-
{(
|
|
8034
|
-
|
|
8035
|
-
`🎯 [DEBUG] Rendering ${previewData.length} table rows`
|
|
8036
|
-
);
|
|
8037
|
-
// Always use response keys as fallback instead of gridColumns that might have wrong names
|
|
8038
|
-
const actualColumns =
|
|
8039
|
-
previewData.length > 0
|
|
8040
|
-
? Object.keys(
|
|
8041
|
-
previewData[0]
|
|
8042
|
-
).map((key) => ({
|
|
8043
|
-
name: key,
|
|
8044
|
-
header: key.replace(
|
|
8045
|
-
/\./g,
|
|
8046
|
-
' '
|
|
8047
|
-
),
|
|
8048
|
-
}))
|
|
8049
|
-
: gridColumns;
|
|
8050
|
-
console.log(
|
|
8051
|
-
`🎯 [DEBUG] Using columns:`,
|
|
8052
|
-
actualColumns
|
|
8053
|
-
);
|
|
8054
|
-
return previewData.map(
|
|
8055
|
-
(row, rowIndex) => (
|
|
8029
|
+
{previewData.map(
|
|
8030
|
+
(row, rowIndex) => (
|
|
8056
8031
|
<tr
|
|
8057
8032
|
key={rowIndex}
|
|
8058
8033
|
className="data-row"
|
|
8059
8034
|
>
|
|
8060
|
-
{
|
|
8035
|
+
{previewColumns.map(
|
|
8061
8036
|
(
|
|
8062
8037
|
col,
|
|
8063
8038
|
colIndex
|
|
@@ -8522,8 +8497,7 @@ const GenericReportImproved = ({
|
|
|
8522
8497
|
)}
|
|
8523
8498
|
</tr>
|
|
8524
8499
|
)
|
|
8525
|
-
|
|
8526
|
-
})()}
|
|
8500
|
+
)}
|
|
8527
8501
|
</tbody>
|
|
8528
8502
|
</table>
|
|
8529
8503
|
</div>
|
|
@@ -8566,8 +8540,11 @@ const GenericReportImproved = ({
|
|
|
8566
8540
|
styles.pageSizeSelector
|
|
8567
8541
|
}
|
|
8568
8542
|
>
|
|
8569
|
-
<label>
|
|
8543
|
+
<label htmlFor="preview-page-size">
|
|
8544
|
+
Show:
|
|
8545
|
+
</label>
|
|
8570
8546
|
<select
|
|
8547
|
+
id="preview-page-size"
|
|
8571
8548
|
value={pageSize}
|
|
8572
8549
|
onChange={(e) => {
|
|
8573
8550
|
const newPageSize =
|
|
@@ -8578,13 +8555,15 @@ const GenericReportImproved = ({
|
|
|
8578
8555
|
setPageSize(
|
|
8579
8556
|
newPageSize
|
|
8580
8557
|
);
|
|
8581
|
-
setCurrentPage(1);
|
|
8582
|
-
// Re-execute the report with new page size
|
|
8558
|
+
setCurrentPage(1);
|
|
8583
8559
|
if (
|
|
8584
8560
|
previewData.length >
|
|
8585
8561
|
0
|
|
8586
8562
|
) {
|
|
8587
|
-
executeReport(
|
|
8563
|
+
executeReport({
|
|
8564
|
+
page: 1,
|
|
8565
|
+
size: newPageSize,
|
|
8566
|
+
});
|
|
8588
8567
|
}
|
|
8589
8568
|
}}
|
|
8590
8569
|
className={
|
|
@@ -8616,6 +8595,7 @@ const GenericReportImproved = ({
|
|
|
8616
8595
|
}
|
|
8617
8596
|
>
|
|
8618
8597
|
<button
|
|
8598
|
+
type="button"
|
|
8619
8599
|
onClick={() => {
|
|
8620
8600
|
if (
|
|
8621
8601
|
currentPage > 1
|
|
@@ -8626,7 +8606,9 @@ const GenericReportImproved = ({
|
|
|
8626
8606
|
setCurrentPage(
|
|
8627
8607
|
newPage
|
|
8628
8608
|
);
|
|
8629
|
-
executeReport(
|
|
8609
|
+
executeReport({
|
|
8610
|
+
page: newPage,
|
|
8611
|
+
});
|
|
8630
8612
|
}
|
|
8631
8613
|
}}
|
|
8632
8614
|
disabled={
|
|
@@ -8694,12 +8676,17 @@ const GenericReportImproved = ({
|
|
|
8694
8676
|
if (startPage > 1) {
|
|
8695
8677
|
pages.push(
|
|
8696
8678
|
<button
|
|
8679
|
+
type="button"
|
|
8697
8680
|
key={1}
|
|
8698
8681
|
onClick={() => {
|
|
8699
8682
|
setCurrentPage(
|
|
8700
8683
|
1
|
|
8701
8684
|
);
|
|
8702
|
-
executeReport(
|
|
8685
|
+
executeReport(
|
|
8686
|
+
{
|
|
8687
|
+
page: 1,
|
|
8688
|
+
}
|
|
8689
|
+
);
|
|
8703
8690
|
}}
|
|
8704
8691
|
className={`${
|
|
8705
8692
|
styles.pageBtn
|
|
@@ -8739,12 +8726,17 @@ const GenericReportImproved = ({
|
|
|
8739
8726
|
) {
|
|
8740
8727
|
pages.push(
|
|
8741
8728
|
<button
|
|
8729
|
+
type="button"
|
|
8742
8730
|
key={i}
|
|
8743
8731
|
onClick={() => {
|
|
8744
8732
|
setCurrentPage(
|
|
8745
8733
|
i
|
|
8746
8734
|
);
|
|
8747
|
-
executeReport(
|
|
8735
|
+
executeReport(
|
|
8736
|
+
{
|
|
8737
|
+
page: i,
|
|
8738
|
+
}
|
|
8739
|
+
);
|
|
8748
8740
|
}}
|
|
8749
8741
|
className={`${
|
|
8750
8742
|
styles.pageBtn
|
|
@@ -8783,6 +8775,7 @@ const GenericReportImproved = ({
|
|
|
8783
8775
|
}
|
|
8784
8776
|
pages.push(
|
|
8785
8777
|
<button
|
|
8778
|
+
type="button"
|
|
8786
8779
|
key={
|
|
8787
8780
|
totalPages
|
|
8788
8781
|
}
|
|
@@ -8790,7 +8783,11 @@ const GenericReportImproved = ({
|
|
|
8790
8783
|
setCurrentPage(
|
|
8791
8784
|
totalPages
|
|
8792
8785
|
);
|
|
8793
|
-
executeReport(
|
|
8786
|
+
executeReport(
|
|
8787
|
+
{
|
|
8788
|
+
page: totalPages,
|
|
8789
|
+
}
|
|
8790
|
+
);
|
|
8794
8791
|
}}
|
|
8795
8792
|
className={`${
|
|
8796
8793
|
styles.pageBtn
|
|
@@ -8813,6 +8810,7 @@ const GenericReportImproved = ({
|
|
|
8813
8810
|
</div>
|
|
8814
8811
|
|
|
8815
8812
|
<button
|
|
8813
|
+
type="button"
|
|
8816
8814
|
onClick={() => {
|
|
8817
8815
|
const totalPages =
|
|
8818
8816
|
Math.ceil(
|
|
@@ -8829,7 +8827,9 @@ const GenericReportImproved = ({
|
|
|
8829
8827
|
setCurrentPage(
|
|
8830
8828
|
newPage
|
|
8831
8829
|
);
|
|
8832
|
-
executeReport(
|
|
8830
|
+
executeReport({
|
|
8831
|
+
page: newPage,
|
|
8832
|
+
});
|
|
8833
8833
|
}
|
|
8834
8834
|
}}
|
|
8835
8835
|
disabled={
|
|
@@ -8870,6 +8870,7 @@ const GenericReportImproved = ({
|
|
|
8870
8870
|
</p>
|
|
8871
8871
|
<div className={styles.saveButtons}>
|
|
8872
8872
|
<button
|
|
8873
|
+
type="button"
|
|
8873
8874
|
className={`${styles.btn} ${styles.btnSuccess}`}
|
|
8874
8875
|
onClick={() => saveReport(false)}
|
|
8875
8876
|
style={{ marginRight: '10px' }}
|
|
@@ -8881,6 +8882,7 @@ const GenericReportImproved = ({
|
|
|
8881
8882
|
</button>
|
|
8882
8883
|
{loadedReportId && (
|
|
8883
8884
|
<button
|
|
8885
|
+
type="button"
|
|
8884
8886
|
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
8885
8887
|
onClick={() => saveReport(true)}
|
|
8886
8888
|
>
|
|
@@ -8939,6 +8941,7 @@ const GenericReportImproved = ({
|
|
|
8939
8941
|
<div className={styles.headerLeft}>
|
|
8940
8942
|
<h1>Report Builder</h1>
|
|
8941
8943
|
<button
|
|
8944
|
+
type="button"
|
|
8942
8945
|
className={styles.helpButton}
|
|
8943
8946
|
onClick={showGuidedHelp}
|
|
8944
8947
|
>
|
|
@@ -8953,13 +8956,31 @@ const GenericReportImproved = ({
|
|
|
8953
8956
|
|
|
8954
8957
|
{/* Calculated Field Modal */}
|
|
8955
8958
|
{showCalculatedFieldModal && (
|
|
8956
|
-
<div
|
|
8957
|
-
|
|
8959
|
+
<div
|
|
8960
|
+
className={styles.modalOverlay}
|
|
8961
|
+
onMouseDown={(e) => {
|
|
8962
|
+
if (e.target === e.currentTarget) {
|
|
8963
|
+
handleCalculatedFieldCancel();
|
|
8964
|
+
}
|
|
8965
|
+
}}
|
|
8966
|
+
>
|
|
8967
|
+
<div
|
|
8968
|
+
className={styles.calculatedFieldModal}
|
|
8969
|
+
role="dialog"
|
|
8970
|
+
aria-modal="true"
|
|
8971
|
+
aria-labelledby="calculated-field-modal-title"
|
|
8972
|
+
ref={calculatedFieldModalRef}
|
|
8973
|
+
tabIndex={-1}
|
|
8974
|
+
>
|
|
8958
8975
|
<div className={styles.modalHeader}>
|
|
8959
|
-
<h3
|
|
8976
|
+
<h3 id="calculated-field-modal-title">
|
|
8977
|
+
Add Calculated Field
|
|
8978
|
+
</h3>
|
|
8960
8979
|
<button
|
|
8980
|
+
type="button"
|
|
8961
8981
|
className={styles.modalCloseButton}
|
|
8962
8982
|
onClick={handleCalculatedFieldCancel}
|
|
8983
|
+
aria-label="Close"
|
|
8963
8984
|
>
|
|
8964
8985
|
✕
|
|
8965
8986
|
</button>
|
|
@@ -9056,12 +9077,14 @@ const GenericReportImproved = ({
|
|
|
9056
9077
|
|
|
9057
9078
|
<div className={styles.modalFooter}>
|
|
9058
9079
|
<button
|
|
9080
|
+
type="button"
|
|
9059
9081
|
className={styles.cancelButton}
|
|
9060
9082
|
onClick={handleCalculatedFieldCancel}
|
|
9061
9083
|
>
|
|
9062
9084
|
Cancel
|
|
9063
9085
|
</button>
|
|
9064
9086
|
<button
|
|
9087
|
+
type="button"
|
|
9065
9088
|
className={styles.addButton}
|
|
9066
9089
|
onClick={handleCalculatedFieldSubmit}
|
|
9067
9090
|
>
|
|
@@ -9074,14 +9097,32 @@ const GenericReportImproved = ({
|
|
|
9074
9097
|
|
|
9075
9098
|
{/* Row Exclusion Modal */}
|
|
9076
9099
|
{showRowExclusionModal && (
|
|
9077
|
-
<div
|
|
9078
|
-
|
|
9100
|
+
<div
|
|
9101
|
+
className={styles.modalOverlay}
|
|
9102
|
+
onMouseDown={(e) => {
|
|
9103
|
+
if (e.target === e.currentTarget) {
|
|
9104
|
+
setShowRowExclusionModal(false);
|
|
9105
|
+
}
|
|
9106
|
+
}}
|
|
9107
|
+
>
|
|
9108
|
+
<div
|
|
9109
|
+
className={styles.rowExclusionModal}
|
|
9110
|
+
role="dialog"
|
|
9111
|
+
aria-modal="true"
|
|
9112
|
+
aria-labelledby="row-exclusion-modal-title"
|
|
9113
|
+
ref={rowExclusionModalRef}
|
|
9114
|
+
tabIndex={-1}
|
|
9115
|
+
>
|
|
9079
9116
|
<div className={styles.modalHeader}>
|
|
9080
|
-
<h3
|
|
9117
|
+
<h3 id="row-exclusion-modal-title">
|
|
9118
|
+
Select Rows to Exclude
|
|
9119
|
+
</h3>
|
|
9081
9120
|
<p>Choose which rows you don't want to include in the export</p>
|
|
9082
9121
|
<button
|
|
9122
|
+
type="button"
|
|
9083
9123
|
className={styles.modalCloseButton}
|
|
9084
9124
|
onClick={() => setShowRowExclusionModal(false)}
|
|
9125
|
+
aria-label="Close"
|
|
9085
9126
|
>
|
|
9086
9127
|
✕
|
|
9087
9128
|
</button>
|
|
@@ -9171,12 +9212,14 @@ const GenericReportImproved = ({
|
|
|
9171
9212
|
|
|
9172
9213
|
<div className={styles.modalFooter}>
|
|
9173
9214
|
<button
|
|
9215
|
+
type="button"
|
|
9174
9216
|
className={styles.cancelButton}
|
|
9175
9217
|
onClick={() => setShowRowExclusionModal(false)}
|
|
9176
9218
|
>
|
|
9177
9219
|
Cancel
|
|
9178
9220
|
</button>
|
|
9179
9221
|
<button
|
|
9222
|
+
type="button"
|
|
9180
9223
|
className={styles.confirmButton}
|
|
9181
9224
|
onClick={confirmExportWithExclusion}
|
|
9182
9225
|
>
|