@visns-studio/visns-components 5.9.9 → 5.9.10
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { toast } from 'react-toastify';
|
|
4
4
|
import moment from 'moment';
|
|
@@ -13,16 +13,15 @@ import {
|
|
|
13
13
|
Info,
|
|
14
14
|
CircleCheck,
|
|
15
15
|
CirclePlus,
|
|
16
|
-
BookOpen,
|
|
17
16
|
Data,
|
|
18
17
|
File,
|
|
19
|
-
SettingsVertical,
|
|
20
18
|
Person,
|
|
21
19
|
Calendar,
|
|
22
20
|
Map,
|
|
23
21
|
Tag,
|
|
24
22
|
Money,
|
|
25
23
|
ShippingBoxV1,
|
|
24
|
+
SettingsVertical,
|
|
26
25
|
Cart,
|
|
27
26
|
Newspaper,
|
|
28
27
|
} from 'akar-icons';
|
|
@@ -30,7 +29,6 @@ import CustomFetch from '../Fetch';
|
|
|
30
29
|
import Download from '../Download';
|
|
31
30
|
import { saveAs } from 'file-saver';
|
|
32
31
|
import Swal from 'sweetalert2';
|
|
33
|
-
import GenericReport from './GenericReport';
|
|
34
32
|
import styles from './styles/GenericReportImproved.module.scss';
|
|
35
33
|
import './styles/SweetAlert.module.css';
|
|
36
34
|
|
|
@@ -737,21 +735,12 @@ const wizardSteps = [
|
|
|
737
735
|
},
|
|
738
736
|
];
|
|
739
737
|
|
|
740
|
-
// Legacy template reports (no longer used in UI but kept for backward compatibility)
|
|
741
|
-
const reportTemplates = [];
|
|
742
|
-
|
|
743
738
|
const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
744
739
|
// Extract settings with defaults
|
|
745
740
|
const { tableUrl, columnUrl } = setting;
|
|
746
741
|
|
|
747
|
-
// Ref for tooltip element
|
|
748
|
-
const tooltipRef = React.useRef(null);
|
|
749
|
-
|
|
750
742
|
// UI State
|
|
751
|
-
const [beginnerMode, setBeginnerMode] = useState(true);
|
|
752
743
|
const [currentWizardStep, setCurrentWizardStep] = useState(0);
|
|
753
|
-
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
|
|
754
|
-
// Removed selectedTemplate state as we now use saved reports instead of templates
|
|
755
744
|
const [showTemplates, setShowTemplates] = useState(true);
|
|
756
745
|
|
|
757
746
|
// State for database schema
|
|
@@ -784,7 +773,6 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
784
773
|
|
|
785
774
|
// State for saved reports
|
|
786
775
|
const [savedReports, setSavedReports] = useState([]);
|
|
787
|
-
const [selectedReport, setSelectedReport] = useState(null);
|
|
788
776
|
const [loadedReportId, setLoadedReportId] = useState(null); // Track loaded report for overwrite functionality
|
|
789
777
|
const [isLoadingReports, setIsLoadingReports] = useState(false);
|
|
790
778
|
|
|
@@ -792,15 +780,10 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
792
780
|
const [suggestedJoins, setSuggestedJoins] = useState({});
|
|
793
781
|
const [isLoadingSuggestedJoins, setIsLoadingSuggestedJoins] = useState({});
|
|
794
782
|
|
|
795
|
-
// State for toggling visibility of sections
|
|
796
|
-
const [showSuggestedRelationships, setShowSuggestedRelationships] =
|
|
797
|
-
useState({});
|
|
798
|
-
const [showJoinTables, setShowJoinTables] = useState(false);
|
|
799
|
-
const [showSortingSection, setShowSortingSection] = useState(false);
|
|
800
|
-
const [showFilteringSection, setShowFilteringSection] = useState(false);
|
|
801
|
-
|
|
802
783
|
// State for sorting and filtering
|
|
803
784
|
const [sortCriteria, setSortCriteria] = useState([]);
|
|
785
|
+
const [showSortingSection, setShowSortingSection] = useState(false);
|
|
786
|
+
const [showFilteringSection, setShowFilteringSection] = useState(false);
|
|
804
787
|
|
|
805
788
|
// Enhanced filter criteria with visual builder support
|
|
806
789
|
const [filterCriteria, setFilterCriteria] = useState({
|
|
@@ -815,30 +798,18 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
815
798
|
|
|
816
799
|
// State for report execution
|
|
817
800
|
const [totalResults, setTotalResults] = useState(0);
|
|
818
|
-
const [executedSql, setExecutedSql] = useState('');
|
|
819
|
-
const [showSqlPreview, setShowSqlPreview] = useState(false);
|
|
820
801
|
|
|
821
802
|
// State for report preview
|
|
822
803
|
const [previewData, setPreviewData] = useState([]);
|
|
823
804
|
const [gridColumns, setGridColumns] = useState([]);
|
|
824
805
|
const [isLoading, setIsLoading] = useState(false);
|
|
806
|
+
const [hasAutoExecuted, setHasAutoExecuted] = useState(false);
|
|
825
807
|
|
|
826
|
-
// State for pagination - now using
|
|
808
|
+
// State for pagination - now using server-side pagination
|
|
827
809
|
const [currentPage, setCurrentPage] = useState(1);
|
|
828
810
|
const [pageSize, setPageSize] = useState(25); // Default to 25 for better UX
|
|
829
811
|
const [totalPages, setTotalPages] = useState(0);
|
|
830
|
-
const [
|
|
831
|
-
|
|
832
|
-
// State for JSON field keys
|
|
833
|
-
const [jsonFieldKeys, setJsonFieldKeys] = useState({});
|
|
834
|
-
const [loadingJsonKeys, setLoadingJsonKeys] = useState({});
|
|
835
|
-
const [showJsonKeySelector, setShowJsonKeySelector] = useState({});
|
|
836
|
-
|
|
837
|
-
// State for suggested filters
|
|
838
|
-
const [suggestedFilters, setSuggestedFilters] = useState([]);
|
|
839
|
-
const [isGeneratingSuggestions, setIsGeneratingSuggestions] =
|
|
840
|
-
useState(false);
|
|
841
|
-
const [showSuggestedFilters, setShowSuggestedFilters] = useState(false);
|
|
812
|
+
const [totalCount, setTotalCount] = useState(0); // Total records from server
|
|
842
813
|
|
|
843
814
|
// State for showing hidden fields
|
|
844
815
|
const [showHiddenFields, setShowHiddenFields] = useState(false);
|
|
@@ -850,7 +821,6 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
850
821
|
reportsUrl = '/ajax/reportBuilder/reports',
|
|
851
822
|
executeUrl = '/ajax/reportBuilder/execute',
|
|
852
823
|
suggestedJoinsUrl = '/ajax/reportBuilder/getSuggestedJoins',
|
|
853
|
-
jsonFieldKeysUrl = '/ajax/reportBuilder/getJsonFieldKeys',
|
|
854
824
|
exportUrl = '/ajax/reportBuilder/export',
|
|
855
825
|
} = setting;
|
|
856
826
|
|
|
@@ -862,21 +832,21 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
862
832
|
<div style="text-align: left; font-size: 14px; color: #4b5563;">
|
|
863
833
|
<div style="margin-bottom: 20px;">
|
|
864
834
|
<h4 style="color: #1f2937; margin-bottom: 10px;">Choose Your Path:</h4>
|
|
865
|
-
|
|
835
|
+
|
|
866
836
|
<div style="background: #f0f9ff; border: 1px solid #3b82f6; border-radius: 8px; padding: 12px; margin-bottom: 12px;">
|
|
867
837
|
<h5 style="color: #1e40af; margin: 0 0 8px 0;">🚀 Quick Start (Recommended)</h5>
|
|
868
|
-
<p style="margin: 0;">Use a
|
|
838
|
+
<p style="margin: 0;">Use a saved report to get started immediately. Perfect for common reports like client lists or recent invoices.</p>
|
|
869
839
|
</div>
|
|
870
|
-
|
|
840
|
+
|
|
871
841
|
<div style="background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px;">
|
|
872
842
|
<h5 style="color: #374151; margin: 0 0 8px 0;">✨ Custom Report</h5>
|
|
873
843
|
<p style="margin: 0;">Build your own report from scratch with our step-by-step wizard. We'll guide you through each step!</p>
|
|
874
844
|
</div>
|
|
875
845
|
</div>
|
|
876
|
-
|
|
846
|
+
|
|
877
847
|
<div style="background: #fef3c7; border: 1px solid #f59e0b; border-radius: 8px; padding: 12px;">
|
|
878
848
|
<p style="margin: 0; color: #92400e;">
|
|
879
|
-
<strong>💡 Tip:</strong>
|
|
849
|
+
<strong>💡 Tip:</strong> Follow the wizard steps to create professional reports quickly and easily.
|
|
880
850
|
</p>
|
|
881
851
|
</div>
|
|
882
852
|
</div>
|
|
@@ -901,7 +871,7 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
901
871
|
|
|
902
872
|
// Show help on first load
|
|
903
873
|
const hasSeenHelp = localStorage.getItem('reportBuilder_hasSeenHelp');
|
|
904
|
-
if (!hasSeenHelp
|
|
874
|
+
if (!hasSeenHelp) {
|
|
905
875
|
setTimeout(() => {
|
|
906
876
|
showGuidedHelp();
|
|
907
877
|
localStorage.setItem('reportBuilder_hasSeenHelp', 'true');
|
|
@@ -920,7 +890,6 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
920
890
|
// Auto-select common columns when user reaches the columns step
|
|
921
891
|
useEffect(() => {
|
|
922
892
|
if (
|
|
923
|
-
beginnerMode &&
|
|
924
893
|
selectedColumns.length === 0 &&
|
|
925
894
|
tableColumns.length > 0 &&
|
|
926
895
|
currentWizardStep === 2 // Step 3 is index 2 (columns step)
|
|
@@ -958,7 +927,6 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
958
927
|
}, [
|
|
959
928
|
tableColumns,
|
|
960
929
|
selectedColumns.length,
|
|
961
|
-
beginnerMode,
|
|
962
930
|
selectedTable,
|
|
963
931
|
currentWizardStep,
|
|
964
932
|
]);
|
|
@@ -966,22 +934,23 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
966
934
|
// Auto-execute report when user reaches step 5 (preview step)
|
|
967
935
|
useEffect(() => {
|
|
968
936
|
if (
|
|
969
|
-
beginnerMode &&
|
|
970
937
|
currentWizardStep === 4 && // Step 5 is index 4 (preview step)
|
|
971
938
|
selectedTable &&
|
|
972
939
|
selectedColumns.length > 0 &&
|
|
973
940
|
previewData.length === 0 && // Only if no data loaded yet
|
|
974
|
-
!isLoading
|
|
941
|
+
!isLoading &&
|
|
942
|
+
!hasAutoExecuted // Prevent infinite loops
|
|
975
943
|
) {
|
|
944
|
+
setHasAutoExecuted(true);
|
|
976
945
|
executeReport();
|
|
977
946
|
}
|
|
978
947
|
}, [
|
|
979
948
|
currentWizardStep,
|
|
980
|
-
beginnerMode,
|
|
981
949
|
selectedTable,
|
|
982
950
|
selectedColumns.length,
|
|
983
951
|
previewData.length,
|
|
984
952
|
isLoading,
|
|
953
|
+
hasAutoExecuted,
|
|
985
954
|
]);
|
|
986
955
|
|
|
987
956
|
// Delete saved report
|
|
@@ -1041,6 +1010,8 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1041
1010
|
],
|
|
1042
1011
|
});
|
|
1043
1012
|
setSortCriteria([]);
|
|
1013
|
+
setShowSortingSection(false);
|
|
1014
|
+
setShowFilteringSection(false);
|
|
1044
1015
|
setPreviewData([]);
|
|
1045
1016
|
setGridColumns([]);
|
|
1046
1017
|
setReportName('');
|
|
@@ -1050,6 +1021,7 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1050
1021
|
setShowTemplates(true);
|
|
1051
1022
|
setTableColumns([]);
|
|
1052
1023
|
setAvailableTables([]);
|
|
1024
|
+
setHasAutoExecuted(false); // Reset auto-execute flag
|
|
1053
1025
|
|
|
1054
1026
|
toast.info(
|
|
1055
1027
|
'Report builder reset. You can start creating a new report.'
|
|
@@ -1459,14 +1431,36 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1459
1431
|
if (!tableName || !columnUrl) return;
|
|
1460
1432
|
|
|
1461
1433
|
try {
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1434
|
+
// Use the same API format as fetchTableColumns
|
|
1435
|
+
const result = await CustomFetch(columnUrl, 'POST', {
|
|
1436
|
+
table: tableName,
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
console.log(
|
|
1440
|
+
`Manual join columns API response for ${tableName}:`,
|
|
1441
|
+
result
|
|
1465
1442
|
);
|
|
1466
|
-
|
|
1443
|
+
|
|
1444
|
+
if (result.error) {
|
|
1445
|
+
console.error(`Error fetching ${type} columns:`, result.error);
|
|
1446
|
+
} else {
|
|
1447
|
+
// Handle the response format similar to fetchTableColumns
|
|
1448
|
+
let columnsData = [];
|
|
1449
|
+
if (result.data?.success && result.data.data) {
|
|
1450
|
+
columnsData = result.data.data;
|
|
1451
|
+
} else if (Array.isArray(result.data)) {
|
|
1452
|
+
columnsData = result.data;
|
|
1453
|
+
} else if (Array.isArray(result)) {
|
|
1454
|
+
columnsData = result;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
console.log(
|
|
1458
|
+
`Loaded ${columnsData.length} columns for ${type} table ${tableName}`
|
|
1459
|
+
);
|
|
1460
|
+
|
|
1467
1461
|
setManualJoinColumns((prev) => ({
|
|
1468
1462
|
...prev,
|
|
1469
|
-
[type]:
|
|
1463
|
+
[type]: columnsData,
|
|
1470
1464
|
}));
|
|
1471
1465
|
}
|
|
1472
1466
|
} catch (error) {
|
|
@@ -1521,9 +1515,7 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1521
1515
|
fetchTableColumns(tableName);
|
|
1522
1516
|
fetchSuggestedJoins(tableName);
|
|
1523
1517
|
|
|
1524
|
-
|
|
1525
|
-
goToNextStep(); // Now goes to relationships step
|
|
1526
|
-
}
|
|
1518
|
+
goToNextStep(); // Go to relationships step
|
|
1527
1519
|
};
|
|
1528
1520
|
|
|
1529
1521
|
// Column selection handler
|
|
@@ -1551,13 +1543,101 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1551
1543
|
}
|
|
1552
1544
|
};
|
|
1553
1545
|
|
|
1554
|
-
//
|
|
1546
|
+
// Create dataSource function for server-side pagination (similar to DataGrid)
|
|
1547
|
+
const dataSource = useCallback(
|
|
1548
|
+
async ({ skip, limit, sortInfo, filterValue }) => {
|
|
1549
|
+
if (!selectedTable || selectedColumns.length === 0) {
|
|
1550
|
+
return { data: [], count: 0 };
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
try {
|
|
1554
|
+
const page = Math.floor(skip / limit) + 1;
|
|
1555
|
+
|
|
1556
|
+
// Use the same payload format as GenericReport (with query wrapper)
|
|
1557
|
+
const queryConfig = {
|
|
1558
|
+
mainTable: selectedTable,
|
|
1559
|
+
columns: selectedColumns.map((col) => ({
|
|
1560
|
+
table: col.table,
|
|
1561
|
+
column: col.column,
|
|
1562
|
+
alias: col.alias || col.displayName,
|
|
1563
|
+
})),
|
|
1564
|
+
joins: joins.map((join) => ({
|
|
1565
|
+
joinType: join.joinType,
|
|
1566
|
+
targetTable: join.targetTable,
|
|
1567
|
+
sourceColumn: join.sourceColumn,
|
|
1568
|
+
targetColumn: join.targetColumn,
|
|
1569
|
+
})),
|
|
1570
|
+
filters: flattenFilterCriteria(filterCriteria),
|
|
1571
|
+
sorting: sortCriteria,
|
|
1572
|
+
};
|
|
1573
|
+
|
|
1574
|
+
// Add sorting from DataGrid if provided
|
|
1575
|
+
if (sortInfo) {
|
|
1576
|
+
queryConfig.sorting = [
|
|
1577
|
+
{
|
|
1578
|
+
column: sortInfo.name,
|
|
1579
|
+
direction: sortInfo.dir === 1 ? 'asc' : 'desc',
|
|
1580
|
+
},
|
|
1581
|
+
];
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
const reportConfig = {
|
|
1585
|
+
query: queryConfig,
|
|
1586
|
+
limit: limit,
|
|
1587
|
+
offset: skip,
|
|
1588
|
+
page: page,
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
const result = await CustomFetch(
|
|
1592
|
+
executeUrl,
|
|
1593
|
+
'POST',
|
|
1594
|
+
reportConfig
|
|
1595
|
+
);
|
|
1596
|
+
|
|
1597
|
+
if (result.error) {
|
|
1598
|
+
console.error('Report execution error:', result.error);
|
|
1599
|
+
return { data: [], count: 0 };
|
|
1600
|
+
} else if (result.data?.success) {
|
|
1601
|
+
const fetchedData = result.data.data || [];
|
|
1602
|
+
const totalRecords =
|
|
1603
|
+
result.data.total || fetchedData.length;
|
|
1604
|
+
|
|
1605
|
+
// Update total count and results for display
|
|
1606
|
+
setTotalCount(totalRecords);
|
|
1607
|
+
setTotalResults(totalRecords);
|
|
1608
|
+
|
|
1609
|
+
console.info(
|
|
1610
|
+
'Fetched data:',
|
|
1611
|
+
fetchedData.length,
|
|
1612
|
+
'of',
|
|
1613
|
+
totalRecords
|
|
1614
|
+
);
|
|
1615
|
+
|
|
1616
|
+
return { data: fetchedData, count: totalRecords };
|
|
1617
|
+
} else {
|
|
1618
|
+
console.error('Unexpected response format:', result);
|
|
1619
|
+
return { data: [], count: 0 };
|
|
1620
|
+
}
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
console.error('Error in dataSource:', error);
|
|
1623
|
+
return { data: [], count: 0 };
|
|
1624
|
+
}
|
|
1625
|
+
},
|
|
1626
|
+
[
|
|
1627
|
+
selectedTable,
|
|
1628
|
+
selectedColumns,
|
|
1629
|
+
joins,
|
|
1630
|
+
filterCriteria,
|
|
1631
|
+
sortCriteria,
|
|
1632
|
+
executeUrl,
|
|
1633
|
+
]
|
|
1634
|
+
);
|
|
1635
|
+
|
|
1636
|
+
// Execute report function for manual execution (creates grid columns)
|
|
1555
1637
|
const executeReport = async () => {
|
|
1556
1638
|
if (!selectedTable || selectedColumns.length === 0) {
|
|
1557
1639
|
toast.warning(
|
|
1558
|
-
|
|
1559
|
-
? 'Please select at least one field to include in your report.'
|
|
1560
|
-
: 'Please select a table and at least one column.'
|
|
1640
|
+
'Please select at least one field to include in your report.'
|
|
1561
1641
|
);
|
|
1562
1642
|
return;
|
|
1563
1643
|
}
|
|
@@ -1565,147 +1645,41 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1565
1645
|
setIsLoading(true);
|
|
1566
1646
|
|
|
1567
1647
|
try {
|
|
1568
|
-
//
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
})),
|
|
1576
|
-
joins: joins.map((join) => ({
|
|
1577
|
-
joinType: join.joinType,
|
|
1578
|
-
targetTable: join.targetTable,
|
|
1579
|
-
sourceColumn: join.sourceColumn,
|
|
1580
|
-
targetColumn: join.targetColumn,
|
|
1581
|
-
})),
|
|
1582
|
-
filters: flattenFilterCriteria(filterCriteria),
|
|
1583
|
-
sorting: sortCriteria,
|
|
1584
|
-
};
|
|
1585
|
-
|
|
1586
|
-
const reportConfig = {
|
|
1587
|
-
query: queryConfig,
|
|
1588
|
-
limit: 1000, // Fetch up to 1000 records for local pagination
|
|
1589
|
-
offset: 0, // Always start from beginning
|
|
1590
|
-
};
|
|
1591
|
-
|
|
1592
|
-
const result = await CustomFetch(executeUrl, 'POST', reportConfig);
|
|
1593
|
-
|
|
1594
|
-
if (result.error) {
|
|
1595
|
-
toast.error(result.error);
|
|
1596
|
-
} else if (result.data?.success) {
|
|
1597
|
-
// Store all fetched data for local pagination
|
|
1598
|
-
const allFetchedData = result.data.data || [];
|
|
1599
|
-
setAllData(allFetchedData);
|
|
1600
|
-
console.info('Fetched data:', allFetchedData.length);
|
|
1601
|
-
|
|
1602
|
-
setTotalResults(allFetchedData.length);
|
|
1603
|
-
setExecutedSql(result.data.sql || '');
|
|
1604
|
-
|
|
1605
|
-
// Calculate total pages for local pagination
|
|
1606
|
-
const calculatedTotalPages = Math.ceil(
|
|
1607
|
-
allFetchedData.length / pageSize
|
|
1608
|
-
);
|
|
1609
|
-
|
|
1610
|
-
setTotalPages(calculatedTotalPages);
|
|
1611
|
-
console.info('Total pages:', calculatedTotalPages);
|
|
1612
|
-
|
|
1613
|
-
setCurrentPage(1); // Reset to first page
|
|
1648
|
+
// Fetch first page to get data structure for grid columns
|
|
1649
|
+
const firstPageResult = await dataSource({
|
|
1650
|
+
skip: 0,
|
|
1651
|
+
limit: pageSize,
|
|
1652
|
+
sortInfo: null,
|
|
1653
|
+
filterValue: null,
|
|
1654
|
+
});
|
|
1614
1655
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
const endIndex = Math.min(pageSize, allFetchedData.length);
|
|
1618
|
-
setPreviewData(allFetchedData.slice(startIndex, endIndex));
|
|
1656
|
+
if (firstPageResult.data && firstPageResult.data.length > 0) {
|
|
1657
|
+
setPreviewData(firstPageResult.data);
|
|
1619
1658
|
|
|
1620
1659
|
// Create grid columns dynamically from actual data with smart formatting
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
});
|
|
1634
|
-
|
|
1635
|
-
// Check if any value in this column is JSON
|
|
1636
|
-
const hasJsonValues = result.data.data.some((row) => {
|
|
1637
|
-
return isJsonValue(row[key]);
|
|
1638
|
-
});
|
|
1639
|
-
|
|
1640
|
-
// Try to find column type from table columns
|
|
1641
|
-
let columnType = 'varchar'; // default
|
|
1642
|
-
if (columnMetadata) {
|
|
1643
|
-
const tableColumn = tableColumns.find(
|
|
1644
|
-
(tc) => tc.name === columnMetadata.column
|
|
1645
|
-
);
|
|
1646
|
-
if (tableColumn) {
|
|
1647
|
-
columnType = tableColumn.type;
|
|
1648
|
-
} else {
|
|
1649
|
-
// Check in joined table columns
|
|
1650
|
-
joins.forEach((join) => {
|
|
1651
|
-
if (join.availableColumns) {
|
|
1652
|
-
const joinedColumn =
|
|
1653
|
-
join.availableColumns.find(
|
|
1654
|
-
(jc) =>
|
|
1655
|
-
jc.name ===
|
|
1656
|
-
columnMetadata.column
|
|
1657
|
-
);
|
|
1658
|
-
if (joinedColumn) {
|
|
1659
|
-
columnType = joinedColumn.type;
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
});
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
return {
|
|
1667
|
-
name: key,
|
|
1668
|
-
header: formatName(key), // Use formatted name as header
|
|
1669
|
-
defaultFlex: 1,
|
|
1670
|
-
minWidth: hasJsonValues ? 250 : 100, // Wider columns for JSON data
|
|
1671
|
-
maxWidth: hasJsonValues ? 400 : undefined, // Max width for JSON columns
|
|
1672
|
-
render: ({ value }) => {
|
|
1673
|
-
// Use smart formatting for the cell value
|
|
1674
|
-
const formattedValue = formatSmartValue(
|
|
1675
|
-
value,
|
|
1676
|
-
key,
|
|
1677
|
-
columnType
|
|
1678
|
-
);
|
|
1679
|
-
|
|
1680
|
-
// For JSON content, wrap in a container that allows full height expansion
|
|
1681
|
-
if (isJsonValue(value)) {
|
|
1682
|
-
return (
|
|
1683
|
-
<div
|
|
1684
|
-
style={{
|
|
1685
|
-
whiteSpace: 'normal',
|
|
1686
|
-
wordWrap: 'break-word',
|
|
1687
|
-
lineHeight: '1.4',
|
|
1688
|
-
padding: '4px 0',
|
|
1689
|
-
width: '100%',
|
|
1690
|
-
}}
|
|
1691
|
-
>
|
|
1692
|
-
{formattedValue}
|
|
1693
|
-
</div>
|
|
1694
|
-
);
|
|
1695
|
-
}
|
|
1660
|
+
const firstRow = firstPageResult.data[0];
|
|
1661
|
+
const dynamicColumns = Object.keys(firstRow).map((key) => {
|
|
1662
|
+
// Find the column metadata from selected columns to get type information
|
|
1663
|
+
const columnMetadata = selectedColumns.find((col) => {
|
|
1664
|
+
// Handle both direct column names and aliased names
|
|
1665
|
+
return (
|
|
1666
|
+
col.column === key ||
|
|
1667
|
+
col.displayName === key ||
|
|
1668
|
+
key.includes(col.column) ||
|
|
1669
|
+
col.column.includes(key)
|
|
1670
|
+
);
|
|
1671
|
+
});
|
|
1696
1672
|
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1673
|
+
// Check if any value in this column is JSON
|
|
1674
|
+
const hasJsonValues = firstPageResult.data.some((row) => {
|
|
1675
|
+
return isJsonValue(row[key]);
|
|
1700
1676
|
});
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
// Find column type from table columns
|
|
1706
|
-
let columnType = 'varchar';
|
|
1677
|
+
|
|
1678
|
+
// Try to find column type from table columns with improved detection
|
|
1679
|
+
let columnType = 'varchar'; // default
|
|
1680
|
+
if (columnMetadata) {
|
|
1707
1681
|
const tableColumn = tableColumns.find(
|
|
1708
|
-
(tc) => tc.name ===
|
|
1682
|
+
(tc) => tc.name === columnMetadata.column
|
|
1709
1683
|
);
|
|
1710
1684
|
if (tableColumn) {
|
|
1711
1685
|
columnType = tableColumn.type;
|
|
@@ -1715,7 +1689,9 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1715
1689
|
if (join.availableColumns) {
|
|
1716
1690
|
const joinedColumn =
|
|
1717
1691
|
join.availableColumns.find(
|
|
1718
|
-
(jc) =>
|
|
1692
|
+
(jc) =>
|
|
1693
|
+
jc.name ===
|
|
1694
|
+
columnMetadata.column
|
|
1719
1695
|
);
|
|
1720
1696
|
if (joinedColumn) {
|
|
1721
1697
|
columnType = joinedColumn.type;
|
|
@@ -1723,67 +1699,154 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1723
1699
|
}
|
|
1724
1700
|
});
|
|
1725
1701
|
}
|
|
1702
|
+
}
|
|
1726
1703
|
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1704
|
+
// Enhanced column type detection for status fields
|
|
1705
|
+
if (!columnMetadata || columnType === 'varchar') {
|
|
1706
|
+
// Check if this looks like a status field based on column name
|
|
1707
|
+
const lowerKey = key.toLowerCase();
|
|
1708
|
+
if (
|
|
1709
|
+
lowerKey.includes('status') ||
|
|
1710
|
+
lowerKey.includes('active') ||
|
|
1711
|
+
lowerKey.includes('enabled') ||
|
|
1712
|
+
lowerKey.includes('disabled')
|
|
1713
|
+
) {
|
|
1714
|
+
// Check if values look like boolean/status values
|
|
1715
|
+
const sampleValues = firstPageResult.data
|
|
1716
|
+
.slice(0, 5)
|
|
1717
|
+
.map((row) => row[key]);
|
|
1718
|
+
const hasStatusValues = sampleValues.some(
|
|
1719
|
+
(val) =>
|
|
1720
|
+
val === 0 ||
|
|
1721
|
+
val === 1 ||
|
|
1722
|
+
val === '0' ||
|
|
1723
|
+
val === '1' ||
|
|
1724
|
+
val === true ||
|
|
1725
|
+
val === false ||
|
|
1726
|
+
val === 'true' ||
|
|
1727
|
+
val === 'false'
|
|
1728
|
+
);
|
|
1729
|
+
if (hasStatusValues) {
|
|
1730
|
+
columnType = 'tinyint'; // This will trigger status formatting
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
return {
|
|
1736
|
+
name: key,
|
|
1737
|
+
header: formatName(key), // Use formatted name as header
|
|
1738
|
+
defaultFlex: 1,
|
|
1739
|
+
minWidth: hasJsonValues ? 250 : 100, // Wider columns for JSON data
|
|
1740
|
+
maxWidth: hasJsonValues ? 400 : undefined, // Max width for JSON columns
|
|
1741
|
+
render: ({ value }) => {
|
|
1742
|
+
// Use smart formatting for the cell value
|
|
1743
|
+
const formattedValue = formatSmartValue(
|
|
1744
|
+
value,
|
|
1745
|
+
key,
|
|
1746
|
+
columnType
|
|
1747
|
+
);
|
|
1748
|
+
|
|
1749
|
+
// For JSON content, wrap in a container that allows full height expansion
|
|
1750
|
+
if (isJsonValue(value)) {
|
|
1751
|
+
return (
|
|
1752
|
+
<div
|
|
1753
|
+
style={{
|
|
1754
|
+
whiteSpace: 'normal',
|
|
1755
|
+
wordWrap: 'break-word',
|
|
1756
|
+
lineHeight: '1.4',
|
|
1757
|
+
padding: '4px 0',
|
|
1758
|
+
width: '100%',
|
|
1759
|
+
}}
|
|
1760
|
+
>
|
|
1761
|
+
{formattedValue}
|
|
1762
|
+
</div>
|
|
1737
1763
|
);
|
|
1764
|
+
}
|
|
1738
1765
|
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1766
|
+
return formattedValue;
|
|
1767
|
+
},
|
|
1768
|
+
};
|
|
1769
|
+
});
|
|
1770
|
+
setGridColumns(dynamicColumns);
|
|
1771
|
+
} else {
|
|
1772
|
+
// Fallback to selected columns if no data
|
|
1773
|
+
const columns = selectedColumns.map((col) => {
|
|
1774
|
+
// Find column type from table columns with enhanced detection
|
|
1775
|
+
let columnType = 'varchar';
|
|
1776
|
+
const tableColumn = tableColumns.find(
|
|
1777
|
+
(tc) => tc.name === col.column
|
|
1778
|
+
);
|
|
1779
|
+
if (tableColumn) {
|
|
1780
|
+
columnType = tableColumn.type;
|
|
1781
|
+
} else {
|
|
1782
|
+
// Check in joined table columns
|
|
1783
|
+
joins.forEach((join) => {
|
|
1784
|
+
if (join.availableColumns) {
|
|
1785
|
+
const joinedColumn = join.availableColumns.find(
|
|
1786
|
+
(jc) => jc.name === col.column
|
|
1787
|
+
);
|
|
1788
|
+
if (joinedColumn) {
|
|
1789
|
+
columnType = joinedColumn.type;
|
|
1754
1790
|
}
|
|
1791
|
+
}
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1755
1794
|
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1795
|
+
// Enhanced column type detection for status fields
|
|
1796
|
+
if (columnType === 'varchar') {
|
|
1797
|
+
const lowerColumn = col.column.toLowerCase();
|
|
1798
|
+
if (
|
|
1799
|
+
lowerColumn.includes('status') ||
|
|
1800
|
+
lowerColumn.includes('active') ||
|
|
1801
|
+
lowerColumn.includes('enabled') ||
|
|
1802
|
+
lowerColumn.includes('disabled')
|
|
1803
|
+
) {
|
|
1804
|
+
columnType = 'tinyint'; // This will trigger status formatting
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1762
1807
|
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1808
|
+
return {
|
|
1809
|
+
name: col.displayName || formatName(col.column),
|
|
1810
|
+
header: col.displayName || formatName(col.column),
|
|
1811
|
+
defaultFlex: 1,
|
|
1812
|
+
minWidth: 100,
|
|
1813
|
+
render: ({ value }) => {
|
|
1814
|
+
const formattedValue = formatSmartValue(
|
|
1815
|
+
value,
|
|
1816
|
+
col.column,
|
|
1817
|
+
columnType
|
|
1818
|
+
);
|
|
1819
|
+
|
|
1820
|
+
// For JSON content, wrap in a container that allows full height expansion
|
|
1821
|
+
if (isJsonValue(value)) {
|
|
1822
|
+
return (
|
|
1823
|
+
<div
|
|
1824
|
+
style={{
|
|
1825
|
+
whiteSpace: 'normal',
|
|
1826
|
+
wordWrap: 'break-word',
|
|
1827
|
+
lineHeight: '1.4',
|
|
1828
|
+
padding: '4px 0',
|
|
1829
|
+
width: '100%',
|
|
1830
|
+
}}
|
|
1831
|
+
>
|
|
1832
|
+
{formattedValue}
|
|
1833
|
+
</div>
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1769
1836
|
|
|
1770
|
-
|
|
1771
|
-
|
|
1837
|
+
return formattedValue;
|
|
1838
|
+
},
|
|
1839
|
+
};
|
|
1840
|
+
});
|
|
1841
|
+
setGridColumns(columns);
|
|
1842
|
+
}
|
|
1772
1843
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
toast.warning(
|
|
1776
|
-
'Showing first 1000 records. Consider adding filters to narrow your results.'
|
|
1777
|
-
);
|
|
1778
|
-
}
|
|
1779
|
-
} else {
|
|
1780
|
-
toast.error(
|
|
1781
|
-
'Failed to execute report: ' +
|
|
1782
|
-
(result.data?.message ||
|
|
1783
|
-
result.message ||
|
|
1784
|
-
'Unknown error')
|
|
1785
|
-
);
|
|
1844
|
+
if (currentWizardStep < wizardSteps.length - 1) {
|
|
1845
|
+
setCurrentWizardStep(wizardSteps.length - 1);
|
|
1786
1846
|
}
|
|
1847
|
+
|
|
1848
|
+
// Show success message with total records found
|
|
1849
|
+
toast.success(`Found ${firstPageResult.count} records`);
|
|
1787
1850
|
} catch (error) {
|
|
1788
1851
|
toast.error('Failed to execute report');
|
|
1789
1852
|
console.error('Error executing report:', error);
|
|
@@ -1792,60 +1855,22 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1792
1855
|
}
|
|
1793
1856
|
};
|
|
1794
1857
|
|
|
1795
|
-
// Local pagination handlers
|
|
1796
|
-
const handlePageChange = (newPage) => {
|
|
1797
|
-
if (
|
|
1798
|
-
newPage >= 1 &&
|
|
1799
|
-
newPage <= totalPages &&
|
|
1800
|
-
newPage !== currentPage &&
|
|
1801
|
-
allData.length > 0
|
|
1802
|
-
) {
|
|
1803
|
-
setCurrentPage(newPage);
|
|
1804
|
-
|
|
1805
|
-
// Calculate slice indices for local pagination
|
|
1806
|
-
const startIndex = (newPage - 1) * pageSize;
|
|
1807
|
-
const endIndex = Math.min(startIndex + pageSize, allData.length);
|
|
1808
|
-
setPreviewData(allData.slice(startIndex, endIndex));
|
|
1809
|
-
}
|
|
1810
|
-
};
|
|
1811
|
-
|
|
1812
|
-
const handlePageSizeChange = (newPageSize) => {
|
|
1813
|
-
if (newPageSize !== pageSize && allData.length > 0) {
|
|
1814
|
-
setPageSize(newPageSize);
|
|
1815
|
-
setCurrentPage(1); // Reset to first page
|
|
1816
|
-
|
|
1817
|
-
// Recalculate total pages with new page size
|
|
1818
|
-
const calculatedTotalPages = Math.ceil(
|
|
1819
|
-
allData.length / newPageSize
|
|
1820
|
-
);
|
|
1821
|
-
setTotalPages(calculatedTotalPages);
|
|
1822
|
-
|
|
1823
|
-
// Update preview data for first page with new page size
|
|
1824
|
-
const endIndex = Math.min(newPageSize, allData.length);
|
|
1825
|
-
setPreviewData(allData.slice(0, endIndex));
|
|
1826
|
-
}
|
|
1827
|
-
};
|
|
1828
|
-
|
|
1829
1858
|
// Reset pagination when starting a new report
|
|
1830
1859
|
const resetPagination = () => {
|
|
1831
1860
|
setCurrentPage(1);
|
|
1832
1861
|
setTotalPages(0);
|
|
1833
1862
|
setPreviewData([]);
|
|
1834
1863
|
setTotalResults(0);
|
|
1835
|
-
|
|
1864
|
+
setTotalCount(0);
|
|
1836
1865
|
};
|
|
1837
1866
|
|
|
1838
|
-
// Render the appropriate content
|
|
1867
|
+
// Render the appropriate content
|
|
1839
1868
|
const renderContent = () => {
|
|
1840
|
-
if (
|
|
1869
|
+
if (showTemplates) {
|
|
1841
1870
|
return renderTemplateSelection();
|
|
1842
1871
|
}
|
|
1843
1872
|
|
|
1844
|
-
|
|
1845
|
-
return renderWizardMode();
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
return renderAdvancedMode();
|
|
1873
|
+
return renderWizardMode();
|
|
1849
1874
|
};
|
|
1850
1875
|
|
|
1851
1876
|
// Load saved report configuration
|
|
@@ -1869,13 +1894,16 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1869
1894
|
|
|
1870
1895
|
if (config.joins) {
|
|
1871
1896
|
setJoins(config.joins);
|
|
1872
|
-
|
|
1897
|
+
// Fetch columns for each joined table
|
|
1898
|
+
config.joins.forEach((join, index) => {
|
|
1873
1899
|
if (!availableTables.includes(join.targetTable)) {
|
|
1874
1900
|
setAvailableTables((prev) => [
|
|
1875
1901
|
...prev,
|
|
1876
1902
|
join.targetTable,
|
|
1877
1903
|
]);
|
|
1878
1904
|
}
|
|
1905
|
+
// Fetch columns for the joined table to populate availableColumns
|
|
1906
|
+
fetchJoinColumns(join.targetTable, index);
|
|
1879
1907
|
});
|
|
1880
1908
|
}
|
|
1881
1909
|
|
|
@@ -1885,13 +1913,18 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1885
1913
|
|
|
1886
1914
|
if (config.sorting) {
|
|
1887
1915
|
setSortCriteria(config.sorting);
|
|
1916
|
+
// Show sorting section if there are sort criteria
|
|
1917
|
+
if (config.sorting.length > 0) {
|
|
1918
|
+
setShowSortingSection(true);
|
|
1919
|
+
}
|
|
1888
1920
|
}
|
|
1889
1921
|
|
|
1890
1922
|
setReportName(report.label);
|
|
1891
1923
|
setIsPublic(report.is_public || false);
|
|
1892
1924
|
setLoadedReportId(report.id); // Track the loaded report ID for overwrite functionality
|
|
1893
1925
|
setShowTemplates(false);
|
|
1894
|
-
|
|
1926
|
+
// Set to the preview step (index 4) since the report is already configured
|
|
1927
|
+
setCurrentWizardStep(4);
|
|
1895
1928
|
|
|
1896
1929
|
toast.success(`Loaded report: ${report.label}`);
|
|
1897
1930
|
} catch (error) {
|
|
@@ -2142,13 +2175,21 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
2142
2175
|
|
|
2143
2176
|
{/* Navigation buttons */}
|
|
2144
2177
|
<div className={styles.wizardNavigation}>
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2178
|
+
{currentWizardStep === 0 ? (
|
|
2179
|
+
<button
|
|
2180
|
+
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
2181
|
+
onClick={startAgain}
|
|
2182
|
+
>
|
|
2183
|
+
Cancel
|
|
2184
|
+
</button>
|
|
2185
|
+
) : (
|
|
2186
|
+
<button
|
|
2187
|
+
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
2188
|
+
onClick={goToPreviousStep}
|
|
2189
|
+
>
|
|
2190
|
+
Previous
|
|
2191
|
+
</button>
|
|
2192
|
+
)}
|
|
2152
2193
|
|
|
2153
2194
|
{currentWizardStep === wizardSteps.length - 1 ? (
|
|
2154
2195
|
<button
|
|
@@ -2879,19 +2920,20 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
2879
2920
|
}
|
|
2880
2921
|
>
|
|
2881
2922
|
<option value="">Select Table</option>
|
|
2882
|
-
{availableTables
|
|
2923
|
+
{/* Use all tables, not just availableTables */}
|
|
2924
|
+
{tables
|
|
2883
2925
|
.filter(
|
|
2884
2926
|
(table) =>
|
|
2885
|
-
table !==
|
|
2927
|
+
table.name !==
|
|
2886
2928
|
manualJoin.sourceTable
|
|
2887
2929
|
)
|
|
2888
2930
|
.map((table) => (
|
|
2889
2931
|
<option
|
|
2890
|
-
key={table}
|
|
2891
|
-
value={table}
|
|
2932
|
+
key={table.name}
|
|
2933
|
+
value={table.name}
|
|
2892
2934
|
>
|
|
2893
2935
|
{getTableDisplayName(
|
|
2894
|
-
table,
|
|
2936
|
+
table.name,
|
|
2895
2937
|
definition
|
|
2896
2938
|
)}
|
|
2897
2939
|
</option>
|
|
@@ -3306,14 +3348,299 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3306
3348
|
|
|
3307
3349
|
return (
|
|
3308
3350
|
<div className={styles.filtersSection}>
|
|
3351
|
+
{/* Sorting Section */}
|
|
3352
|
+
<div className={styles.reportSection}>
|
|
3353
|
+
<div
|
|
3354
|
+
className={styles.sectionHeader}
|
|
3355
|
+
title={
|
|
3356
|
+
showSortingSection
|
|
3357
|
+
? 'Hide sorting options'
|
|
3358
|
+
: 'Show sorting options'
|
|
3359
|
+
}
|
|
3360
|
+
>
|
|
3361
|
+
<h3
|
|
3362
|
+
onClick={() =>
|
|
3363
|
+
setShowSortingSection(!showSortingSection)
|
|
3364
|
+
}
|
|
3365
|
+
style={{ cursor: 'pointer' }}
|
|
3366
|
+
>
|
|
3367
|
+
Sorting
|
|
3368
|
+
</h3>
|
|
3369
|
+
<div className={styles.sectionHeaderActions}>
|
|
3370
|
+
<button
|
|
3371
|
+
className={styles.btn}
|
|
3372
|
+
onClick={handleAddSortCriterion}
|
|
3373
|
+
disabled={selectedColumns.length === 0}
|
|
3374
|
+
>
|
|
3375
|
+
Add Sort Criterion
|
|
3376
|
+
</button>
|
|
3377
|
+
<div
|
|
3378
|
+
className={`${styles.toggleBtn} ${
|
|
3379
|
+
showSortingSection
|
|
3380
|
+
? styles.toggleBtnActive
|
|
3381
|
+
: ''
|
|
3382
|
+
}`}
|
|
3383
|
+
onClick={() =>
|
|
3384
|
+
setShowSortingSection(!showSortingSection)
|
|
3385
|
+
}
|
|
3386
|
+
style={{ cursor: 'pointer' }}
|
|
3387
|
+
>
|
|
3388
|
+
{showSortingSection ? '▲' : '▼'}
|
|
3389
|
+
</div>
|
|
3390
|
+
</div>
|
|
3391
|
+
</div>
|
|
3392
|
+
|
|
3393
|
+
{showSortingSection && (
|
|
3394
|
+
<>
|
|
3395
|
+
{sortCriteria.length > 0 && (
|
|
3396
|
+
<div className={styles.filterGroup}>
|
|
3397
|
+
<div className={styles.joinTablesContainer}>
|
|
3398
|
+
{sortCriteria.map(
|
|
3399
|
+
(criterion, index) => (
|
|
3400
|
+
<div
|
|
3401
|
+
key={index}
|
|
3402
|
+
className={
|
|
3403
|
+
styles.joinSection
|
|
3404
|
+
}
|
|
3405
|
+
data-sort-index={index + 1}
|
|
3406
|
+
>
|
|
3407
|
+
<div
|
|
3408
|
+
className={
|
|
3409
|
+
styles.joinHeader
|
|
3410
|
+
}
|
|
3411
|
+
>
|
|
3412
|
+
<h4
|
|
3413
|
+
data-sort-index={
|
|
3414
|
+
index + 1
|
|
3415
|
+
}
|
|
3416
|
+
>
|
|
3417
|
+
by{' '}
|
|
3418
|
+
{criterion.table &&
|
|
3419
|
+
criterion.column
|
|
3420
|
+
? `${formatName(
|
|
3421
|
+
criterion.table
|
|
3422
|
+
)}.${formatName(
|
|
3423
|
+
criterion.column
|
|
3424
|
+
)}`
|
|
3425
|
+
: `Criterion #${
|
|
3426
|
+
index + 1
|
|
3427
|
+
}`}
|
|
3428
|
+
</h4>
|
|
3429
|
+
<button
|
|
3430
|
+
className={
|
|
3431
|
+
styles.removeJoinBtn
|
|
3432
|
+
}
|
|
3433
|
+
onClick={() =>
|
|
3434
|
+
handleRemoveSortCriterion(
|
|
3435
|
+
index
|
|
3436
|
+
)
|
|
3437
|
+
}
|
|
3438
|
+
title="Remove Sort Criterion"
|
|
3439
|
+
>
|
|
3440
|
+
×
|
|
3441
|
+
</button>
|
|
3442
|
+
</div>
|
|
3443
|
+
|
|
3444
|
+
<div
|
|
3445
|
+
className={
|
|
3446
|
+
styles.joinGrid
|
|
3447
|
+
}
|
|
3448
|
+
>
|
|
3449
|
+
<div
|
|
3450
|
+
className={
|
|
3451
|
+
styles.joinGridColumn
|
|
3452
|
+
}
|
|
3453
|
+
>
|
|
3454
|
+
<div
|
|
3455
|
+
className={
|
|
3456
|
+
styles.joinField
|
|
3457
|
+
}
|
|
3458
|
+
>
|
|
3459
|
+
<label>
|
|
3460
|
+
Column:
|
|
3461
|
+
</label>
|
|
3462
|
+
<select
|
|
3463
|
+
className={
|
|
3464
|
+
styles.selectControl
|
|
3465
|
+
}
|
|
3466
|
+
value={
|
|
3467
|
+
criterion.table &&
|
|
3468
|
+
criterion.column
|
|
3469
|
+
? `${criterion.table}.${criterion.column}`
|
|
3470
|
+
: ''
|
|
3471
|
+
}
|
|
3472
|
+
onChange={(
|
|
3473
|
+
e
|
|
3474
|
+
) => {
|
|
3475
|
+
if (
|
|
3476
|
+
e
|
|
3477
|
+
.target
|
|
3478
|
+
.value
|
|
3479
|
+
) {
|
|
3480
|
+
const dotIndex =
|
|
3481
|
+
e.target.value.indexOf(
|
|
3482
|
+
'.'
|
|
3483
|
+
);
|
|
3484
|
+
if (
|
|
3485
|
+
dotIndex !==
|
|
3486
|
+
-1
|
|
3487
|
+
) {
|
|
3488
|
+
const table =
|
|
3489
|
+
e.target.value.substring(
|
|
3490
|
+
0,
|
|
3491
|
+
dotIndex
|
|
3492
|
+
);
|
|
3493
|
+
const column =
|
|
3494
|
+
e.target.value.substring(
|
|
3495
|
+
dotIndex +
|
|
3496
|
+
1
|
|
3497
|
+
);
|
|
3498
|
+
|
|
3499
|
+
handleSortCriterionChange(
|
|
3500
|
+
index,
|
|
3501
|
+
'column',
|
|
3502
|
+
''
|
|
3503
|
+
);
|
|
3504
|
+
handleSortCriterionChange(
|
|
3505
|
+
index,
|
|
3506
|
+
'table',
|
|
3507
|
+
table
|
|
3508
|
+
);
|
|
3509
|
+
handleSortCriterionChange(
|
|
3510
|
+
index,
|
|
3511
|
+
'column',
|
|
3512
|
+
column
|
|
3513
|
+
);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
}}
|
|
3517
|
+
>
|
|
3518
|
+
<option value="">
|
|
3519
|
+
Select
|
|
3520
|
+
Column
|
|
3521
|
+
</option>
|
|
3522
|
+
{/* Main table columns */}
|
|
3523
|
+
<optgroup
|
|
3524
|
+
label={`${formatName(
|
|
3525
|
+
selectedTable
|
|
3526
|
+
)} Columns`}
|
|
3527
|
+
>
|
|
3528
|
+
{tableColumns.map(
|
|
3529
|
+
(
|
|
3530
|
+
column
|
|
3531
|
+
) => (
|
|
3532
|
+
<option
|
|
3533
|
+
key={`${selectedTable}.${column.name}`}
|
|
3534
|
+
value={`${selectedTable}.${column.name}`}
|
|
3535
|
+
>
|
|
3536
|
+
{formatName(
|
|
3537
|
+
column.name
|
|
3538
|
+
)}
|
|
3539
|
+
</option>
|
|
3540
|
+
)
|
|
3541
|
+
)}
|
|
3542
|
+
</optgroup>
|
|
3543
|
+
|
|
3544
|
+
{/* Joined tables columns */}
|
|
3545
|
+
{joins.map(
|
|
3546
|
+
(
|
|
3547
|
+
join,
|
|
3548
|
+
joinIndex
|
|
3549
|
+
) =>
|
|
3550
|
+
join.targetTable &&
|
|
3551
|
+
join.availableColumns &&
|
|
3552
|
+
join
|
|
3553
|
+
.availableColumns
|
|
3554
|
+
.length >
|
|
3555
|
+
0 ? (
|
|
3556
|
+
<optgroup
|
|
3557
|
+
key={`join-${joinIndex}`}
|
|
3558
|
+
label={`${formatName(
|
|
3559
|
+
join.targetTable
|
|
3560
|
+
)} Columns`}
|
|
3561
|
+
>
|
|
3562
|
+
{join.availableColumns.map(
|
|
3563
|
+
(
|
|
3564
|
+
column
|
|
3565
|
+
) => (
|
|
3566
|
+
<option
|
|
3567
|
+
key={`${join.targetTable}.${column.name}`}
|
|
3568
|
+
value={`${join.targetTable}.${column.name}`}
|
|
3569
|
+
>
|
|
3570
|
+
{formatName(
|
|
3571
|
+
column.name
|
|
3572
|
+
)}
|
|
3573
|
+
</option>
|
|
3574
|
+
)
|
|
3575
|
+
)}
|
|
3576
|
+
</optgroup>
|
|
3577
|
+
) : null
|
|
3578
|
+
)}
|
|
3579
|
+
</select>
|
|
3580
|
+
</div>
|
|
3581
|
+
</div>
|
|
3582
|
+
|
|
3583
|
+
<div
|
|
3584
|
+
className={
|
|
3585
|
+
styles.joinGridColumn
|
|
3586
|
+
}
|
|
3587
|
+
>
|
|
3588
|
+
<div
|
|
3589
|
+
className={
|
|
3590
|
+
styles.joinField
|
|
3591
|
+
}
|
|
3592
|
+
>
|
|
3593
|
+
<label>
|
|
3594
|
+
Direction:
|
|
3595
|
+
</label>
|
|
3596
|
+
<select
|
|
3597
|
+
className={
|
|
3598
|
+
styles.selectControl
|
|
3599
|
+
}
|
|
3600
|
+
value={
|
|
3601
|
+
criterion.direction
|
|
3602
|
+
}
|
|
3603
|
+
onChange={(
|
|
3604
|
+
e
|
|
3605
|
+
) =>
|
|
3606
|
+
handleSortCriterionChange(
|
|
3607
|
+
index,
|
|
3608
|
+
'direction',
|
|
3609
|
+
e
|
|
3610
|
+
.target
|
|
3611
|
+
.value
|
|
3612
|
+
)
|
|
3613
|
+
}
|
|
3614
|
+
>
|
|
3615
|
+
<option value="ASC">
|
|
3616
|
+
Ascending
|
|
3617
|
+
</option>
|
|
3618
|
+
<option value="DESC">
|
|
3619
|
+
Descending
|
|
3620
|
+
</option>
|
|
3621
|
+
</select>
|
|
3622
|
+
</div>
|
|
3623
|
+
</div>
|
|
3624
|
+
</div>
|
|
3625
|
+
</div>
|
|
3626
|
+
)
|
|
3627
|
+
)}
|
|
3628
|
+
</div>
|
|
3629
|
+
</div>
|
|
3630
|
+
)}
|
|
3631
|
+
</>
|
|
3632
|
+
)}
|
|
3633
|
+
</div>
|
|
3634
|
+
|
|
3635
|
+
{/* Filtering Section */}
|
|
3309
3636
|
<div className={styles.helpBox}>
|
|
3310
3637
|
<Info size={20} />
|
|
3311
3638
|
<div>
|
|
3312
|
-
<h4>
|
|
3639
|
+
<h4>Filter Builder</h4>
|
|
3313
3640
|
<p>
|
|
3314
|
-
Create
|
|
3315
|
-
|
|
3316
|
-
|
|
3641
|
+
Create filters to narrow down your results. Use
|
|
3642
|
+
filter groups to create complex conditions like "(A
|
|
3643
|
+
AND B) OR (C AND D)".
|
|
3317
3644
|
</p>
|
|
3318
3645
|
</div>
|
|
3319
3646
|
</div>
|
|
@@ -3685,6 +4012,48 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3685
4012
|
);
|
|
3686
4013
|
};
|
|
3687
4014
|
|
|
4015
|
+
// Sorting functions (from original GenericReport)
|
|
4016
|
+
// Add a new sort criterion
|
|
4017
|
+
const handleAddSortCriterion = () => {
|
|
4018
|
+
// Default to the first selected column if available
|
|
4019
|
+
const defaultColumn =
|
|
4020
|
+
selectedColumns.length > 0
|
|
4021
|
+
? {
|
|
4022
|
+
table: selectedColumns[0].table,
|
|
4023
|
+
column: selectedColumns[0].column,
|
|
4024
|
+
displayName: selectedColumns[0].displayName,
|
|
4025
|
+
}
|
|
4026
|
+
: null;
|
|
4027
|
+
|
|
4028
|
+
const newSortCriterion = {
|
|
4029
|
+
table: defaultColumn?.table || '',
|
|
4030
|
+
column: defaultColumn?.column || '',
|
|
4031
|
+
direction: 'ASC',
|
|
4032
|
+
};
|
|
4033
|
+
|
|
4034
|
+
setSortCriteria([...sortCriteria, newSortCriterion]);
|
|
4035
|
+
setShowSortingSection(true);
|
|
4036
|
+
};
|
|
4037
|
+
|
|
4038
|
+
// Update a sort criterion
|
|
4039
|
+
const handleSortCriterionChange = (index, field, value) => {
|
|
4040
|
+
const updatedSortCriteria = [...sortCriteria];
|
|
4041
|
+
updatedSortCriteria[index][field] = value;
|
|
4042
|
+
setSortCriteria(updatedSortCriteria);
|
|
4043
|
+
};
|
|
4044
|
+
|
|
4045
|
+
// Remove a sort criterion
|
|
4046
|
+
const handleRemoveSortCriterion = (index) => {
|
|
4047
|
+
const updatedSortCriteria = [...sortCriteria];
|
|
4048
|
+
updatedSortCriteria.splice(index, 1);
|
|
4049
|
+
setSortCriteria(updatedSortCriteria);
|
|
4050
|
+
|
|
4051
|
+
// Hide the section if no sort criteria remain
|
|
4052
|
+
if (updatedSortCriteria.length === 0) {
|
|
4053
|
+
setShowSortingSection(false);
|
|
4054
|
+
}
|
|
4055
|
+
};
|
|
4056
|
+
|
|
3688
4057
|
// Export report in different formats (replicating GenericReport pattern)
|
|
3689
4058
|
const exportReport = async (format) => {
|
|
3690
4059
|
if (!selectedTable || selectedColumns.length === 0) {
|
|
@@ -3882,6 +4251,7 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3882
4251
|
className={`${styles.btn} ${styles.btnPrimary}`}
|
|
3883
4252
|
onClick={() => {
|
|
3884
4253
|
resetPagination();
|
|
4254
|
+
setHasAutoExecuted(false); // Reset flag for manual execution
|
|
3885
4255
|
executeReport();
|
|
3886
4256
|
}}
|
|
3887
4257
|
disabled={isLoading}
|
|
@@ -3895,19 +4265,12 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3895
4265
|
<div className={styles.gridContainer}>
|
|
3896
4266
|
<ReactDataGrid
|
|
3897
4267
|
columns={gridColumns}
|
|
3898
|
-
dataSource={
|
|
4268
|
+
dataSource={dataSource}
|
|
3899
4269
|
style={{ height: 800 }}
|
|
3900
|
-
pagination
|
|
4270
|
+
pagination
|
|
3901
4271
|
limit={pageSize}
|
|
3902
|
-
skip={(currentPage - 1) * pageSize}
|
|
3903
|
-
count={totalResults}
|
|
3904
4272
|
onLimitChange={(newLimit) => {
|
|
3905
|
-
|
|
3906
|
-
}}
|
|
3907
|
-
onSkipChange={(newSkip) => {
|
|
3908
|
-
const newPage =
|
|
3909
|
-
Math.floor(newSkip / pageSize) + 1;
|
|
3910
|
-
handlePageChange(newPage);
|
|
4273
|
+
setPageSize(newLimit);
|
|
3911
4274
|
}}
|
|
3912
4275
|
pageSizes={[10, 15, 25, 50]}
|
|
3913
4276
|
defaultLimit={25}
|
|
@@ -3936,6 +4299,7 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3936
4299
|
<button
|
|
3937
4300
|
className={`${styles.btn} ${styles.btnSuccess}`}
|
|
3938
4301
|
onClick={() => saveReport(false)}
|
|
4302
|
+
style={{ marginRight: '10px' }}
|
|
3939
4303
|
>
|
|
3940
4304
|
<Save size={16} />
|
|
3941
4305
|
{loadedReportId
|
|
@@ -4021,15 +4385,6 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4021
4385
|
);
|
|
4022
4386
|
};
|
|
4023
4387
|
|
|
4024
|
-
// Render advanced mode (using original GenericReport)
|
|
4025
|
-
const renderAdvancedMode = () => {
|
|
4026
|
-
return (
|
|
4027
|
-
<div className={styles.advancedMode}>
|
|
4028
|
-
<GenericReport setting={setting} definition={definition} />
|
|
4029
|
-
</div>
|
|
4030
|
-
);
|
|
4031
|
-
};
|
|
4032
|
-
|
|
4033
4388
|
return (
|
|
4034
4389
|
<div className={styles.reportBuilder}>
|
|
4035
4390
|
{/* Header */}
|
|
@@ -4044,29 +4399,6 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4044
4399
|
Help
|
|
4045
4400
|
</button>
|
|
4046
4401
|
</div>
|
|
4047
|
-
|
|
4048
|
-
<div className={styles.headerRight}>
|
|
4049
|
-
<div className={styles.modeToggle}>
|
|
4050
|
-
<span className={beginnerMode ? styles.active : ''}>
|
|
4051
|
-
<BookOpen size={16} />
|
|
4052
|
-
Beginner
|
|
4053
|
-
</span>
|
|
4054
|
-
<label className={styles.switch}>
|
|
4055
|
-
<input
|
|
4056
|
-
type="checkbox"
|
|
4057
|
-
checked={!beginnerMode}
|
|
4058
|
-
onChange={(e) =>
|
|
4059
|
-
setBeginnerMode(!e.target.checked)
|
|
4060
|
-
}
|
|
4061
|
-
/>
|
|
4062
|
-
<span className={styles.slider}></span>
|
|
4063
|
-
</label>
|
|
4064
|
-
<span className={!beginnerMode ? styles.active : ''}>
|
|
4065
|
-
<SettingsVertical size={16} />
|
|
4066
|
-
Advanced
|
|
4067
|
-
</span>
|
|
4068
|
-
</div>
|
|
4069
|
-
</div>
|
|
4070
4402
|
</div>
|
|
4071
4403
|
|
|
4072
4404
|
{/* Main content */}
|