robobyte-front-builder 1.0.16 → 1.0.19
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/lib/providers/RoboByteFrontBuilderProvider.jsx +26 -9
- package/src/pages/_app.js +0 -8
- package/src/pages/reportModule/reportBuilder/index.js +719 -561
- package/src/pages/reportModule/reportBuilder/reportViewer/index.js +135 -80
- package/src/pages/reportModule/reportBuilder/reports/index.js +51 -11
- package/src/pages/reportModule/reportBuilder/reportsPermissions/index.js +127 -0
- package/src/services/helper/multiSelectEditorByBuilder.js +245 -0
- package/src/services/helper/reportSessionHelper.js +83 -0
- package/src/views/genericTable/ColumnConfiguratorDialog.js +762 -0
- package/src/views/genericTable/FormattingSettingsDialog.js +546 -0
- package/src/views/genericTable/ReportSettingsDialog.js +151 -0
- package/src/views/genericTable/SGrid.js +872 -159
- package/src/views/genericTable/TAGGrid.js +83 -69
- package/src/views/genericTable/convertStringFunctions.js +200 -0
- package/src/views/genericTable/updateRefHelpers.js +421 -0
- package/src/views/rolePermissions/UpdateReportPermissionDialog.js +315 -0
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* - Adds search term filters based on searchTermRef and searchFieldsRef metadata.
|
|
27
27
|
* - Translates group/pivot/value columns, sort model, and pagination into the expected backend format.
|
|
28
28
|
* 3) Data is requested via ReportBuilderEndpoints.Post.GenericGet. Responses may contain rows and aggregations.
|
|
29
|
-
* 4) Grid updates, status bar shows counts/aggregations, and export functions can read current
|
|
29
|
+
* 4) Grid updates, status bar shows counts/aggregations, and export functions can read current view state.
|
|
30
30
|
*
|
|
31
31
|
* Quick search behavior (important)
|
|
32
32
|
* - searchTermRef holds the current string. If it parses to a finite number, we search numeric (Int) fields with a numeric value.
|
|
@@ -59,6 +59,24 @@ import Grid from '@mui/material/Grid'
|
|
|
59
59
|
// ** Store Imports
|
|
60
60
|
// ** Custom Components Imports
|
|
61
61
|
import {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'
|
|
62
|
+
|
|
63
|
+
// Global styles for flashing effect
|
|
64
|
+
if (typeof document !== 'undefined') {
|
|
65
|
+
const styleId = 'ag-grid-flashing-effect';
|
|
66
|
+
if (!document.getElementById(styleId)) {
|
|
67
|
+
const style = document.createElement('style');
|
|
68
|
+
style.id = styleId;
|
|
69
|
+
style.innerHTML = `
|
|
70
|
+
@keyframes flashing-bg {
|
|
71
|
+
0% { opacity: 1; }
|
|
72
|
+
50% { opacity: 0.5; }
|
|
73
|
+
100% { opacity: 1; }
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
document.head.appendChild(style);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
62
80
|
import {Endpoints, Services} from 'src/services/Endpoints'
|
|
63
81
|
import {
|
|
64
82
|
Autocomplete, Checkbox,
|
|
@@ -76,7 +94,8 @@ import {
|
|
|
76
94
|
Paper,
|
|
77
95
|
List,
|
|
78
96
|
ListItemButton,
|
|
79
|
-
ClickAwayListener
|
|
97
|
+
ClickAwayListener,
|
|
98
|
+
Menu
|
|
80
99
|
} from '@mui/material'
|
|
81
100
|
import {AgGridReact} from "ag-grid-react";
|
|
82
101
|
import {Edit, FilterAlt, PrintOutlined, RefreshOutlined, Save, SaveAs, Close} from "@mui/icons-material";
|
|
@@ -101,6 +120,22 @@ import jsPDF from "jspdf";
|
|
|
101
120
|
import "jspdf-autotable";
|
|
102
121
|
import arabicFontBase64 from "../../../public/fonts/font";
|
|
103
122
|
import {FilterFormat} from "services/helper/FilterFormat";
|
|
123
|
+
import {saveReportSession} from 'src/services/helper/reportSessionHelper'
|
|
124
|
+
import {
|
|
125
|
+
setUpdateRefValue,
|
|
126
|
+
setUpdateRefRow,
|
|
127
|
+
getUpdateRefValue,
|
|
128
|
+
hasUpdateRefValue,
|
|
129
|
+
clearUpdateRefRow,
|
|
130
|
+
clearAllUpdateRef,
|
|
131
|
+
getAllUpdates,
|
|
132
|
+
removeUpdateRefByRowId,
|
|
133
|
+
getRowId as getRowIdFromSettings,
|
|
134
|
+
normalizeUpdateRef,
|
|
135
|
+
cloneUpdateRefToOriginal,
|
|
136
|
+
restoreUpdateRefFromOriginal
|
|
137
|
+
} from './updateRefHelpers'
|
|
138
|
+
import {processColumnDefinitions, processColumnsConfig} from './convertStringFunctions'
|
|
104
139
|
|
|
105
140
|
// ** Utils Import
|
|
106
141
|
|
|
@@ -137,6 +172,7 @@ const SGrid = props => {
|
|
|
137
172
|
setOutGridApi,
|
|
138
173
|
setEventRowSelected,
|
|
139
174
|
pageName,
|
|
175
|
+
pageId,
|
|
140
176
|
paramsPage,
|
|
141
177
|
fixedTIncludes,
|
|
142
178
|
groupBy,
|
|
@@ -144,14 +180,17 @@ const SGrid = props => {
|
|
|
144
180
|
reportTitle,
|
|
145
181
|
uniqueIdPath,
|
|
146
182
|
columnsConfig,
|
|
147
|
-
dataAsObject = false
|
|
183
|
+
dataAsObject = false,
|
|
184
|
+
isRerender = false,
|
|
185
|
+
updateRef
|
|
148
186
|
} = props
|
|
149
187
|
const groupEndPoint = ReportBuilderEndpoints.Post.GenericGet;
|
|
150
188
|
const streamEndPoint = ReportBuilderEndpoints.Post.GenericGet;
|
|
151
189
|
const pagedEndPoint = ReportBuilderEndpoints.Post.GenericGet;
|
|
152
190
|
const [responseType, setResponseType] = useState()
|
|
153
191
|
const [pagedAgg, setPagedAgg] = useState()
|
|
154
|
-
const
|
|
192
|
+
const colDefs = useRef([])
|
|
193
|
+
const originalRefData = useRef([])
|
|
155
194
|
const [gridApi, setGridApi] = useState(null)
|
|
156
195
|
const [includes, setIncludes] = useState([])
|
|
157
196
|
const [isPagination, setIsPagination] = useState(true)
|
|
@@ -163,6 +202,7 @@ const SGrid = props => {
|
|
|
163
202
|
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
164
203
|
const router = useRouter()
|
|
165
204
|
const authValues = useContext(AuthContext)
|
|
205
|
+
const contextValues = useContext(SystemContext)
|
|
166
206
|
const [isTemplateEditing, setIsTemplateEditing] = useState(false)
|
|
167
207
|
const [isDownloading, setIsDownloading] = useState(false)
|
|
168
208
|
const [finalRequestObject, setFinalRequestObject] = useState(defaultFinalRequest)
|
|
@@ -191,6 +231,87 @@ const SGrid = props => {
|
|
|
191
231
|
Tfilter: [],
|
|
192
232
|
LocalTfilter: [],
|
|
193
233
|
})
|
|
234
|
+
|
|
235
|
+
// Persistence Key helper
|
|
236
|
+
const getSessionKey = useCallback(() => {
|
|
237
|
+
const reportId = builderData?.id || builderModel?.id;
|
|
238
|
+
const pId = pageId || router.query?.pageId;
|
|
239
|
+
if (pId) return `RB_SESSION_FILTERS_PAGE_${pId}`;
|
|
240
|
+
if (reportId) return `RB_SESSION_FILTERS_REP_${reportId}`;
|
|
241
|
+
return null;
|
|
242
|
+
}, [builderData?.id, builderModel?.id, pageId, router.query?.pageId]);
|
|
243
|
+
|
|
244
|
+
// Clone updateRef to originalRefData when it first gets data
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
if (updateRef && updateRef.current && Array.isArray(updateRef.current) && updateRef.current.length > 0) {
|
|
247
|
+
// Only clone if originalRefData is empty (first time updateRef has data)
|
|
248
|
+
if (!originalRefData.current || originalRefData.current.length === 0) {
|
|
249
|
+
cloneUpdateRefToOriginal(updateRef, originalRefData);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}, [updateRef?.current?.length]); // Watch for when updateRef gets data
|
|
253
|
+
|
|
254
|
+
// Load filters from sessionStorage on mount
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
const key = getSessionKey();
|
|
257
|
+
if (!key) return;
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
// Check if it's a refresh
|
|
261
|
+
let isRefresh = false;
|
|
262
|
+
try {
|
|
263
|
+
const navEntries = performance.getEntriesByType('navigation');
|
|
264
|
+
isRefresh = navEntries.length > 0 && navEntries[0].type === 'reload';
|
|
265
|
+
} catch (e) {
|
|
266
|
+
console.warn('SGrid: performance.getEntriesByType not supported', e);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (isRefresh) {
|
|
270
|
+
sessionStorage.removeItem(key);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const stored = sessionStorage.getItem(key);
|
|
275
|
+
if (stored) {
|
|
276
|
+
// If externalFilter is provided via props (routing payload), it should probably take precedence
|
|
277
|
+
// but let's see if it's actually passed. In ReportViewer, it's passed as 'filter' prop.
|
|
278
|
+
if (externalFilter) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const parsed = JSON.parse(stored);
|
|
283
|
+
|
|
284
|
+
if (parsed.Filter) setFilter(parsed.Filter);
|
|
285
|
+
if (parsed.selectedSearchObjects) setSelectedSearchObjects(parsed.selectedSearchObjects);
|
|
286
|
+
if (parsed.selectParams) setSelectParams(parsed.selectParams);
|
|
287
|
+
}
|
|
288
|
+
} catch (e) {
|
|
289
|
+
console.error('SGrid: Failed to restore session filters', e);
|
|
290
|
+
}
|
|
291
|
+
}, [getSessionKey, externalFilter]);
|
|
292
|
+
|
|
293
|
+
// Save filters to sessionStorage whenever they change
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
const key = getSessionKey();
|
|
296
|
+
if (!key) return;
|
|
297
|
+
|
|
298
|
+
const timeout = setTimeout(() => {
|
|
299
|
+
try {
|
|
300
|
+
const toStore = {
|
|
301
|
+
Filter,
|
|
302
|
+
selectedSearchObjects,
|
|
303
|
+
selectParams
|
|
304
|
+
};
|
|
305
|
+
// Always store current state. If user cleared all filters, we want to remember it's empty in this session.
|
|
306
|
+
// We only avoid storing if the state is exactly the default/initial state and we haven't modified it yet.
|
|
307
|
+
sessionStorage.setItem(key, JSON.stringify(toStore));
|
|
308
|
+
} catch (e) {
|
|
309
|
+
console.error('SGrid: Failed to save session filters', e);
|
|
310
|
+
}
|
|
311
|
+
}, 1000); // debounce saving
|
|
312
|
+
|
|
313
|
+
return () => clearTimeout(timeout);
|
|
314
|
+
}, [Filter, selectedSearchObjects, selectParams, getSessionKey]);
|
|
194
315
|
const [openDialogs, setOpenDialogs] = useState({
|
|
195
316
|
addTemplate: false,
|
|
196
317
|
CustomFilter: false,
|
|
@@ -277,7 +398,7 @@ const SGrid = props => {
|
|
|
277
398
|
if (c.colId === "IZ_groupCount") {
|
|
278
399
|
return {field: "IZ_groupCount", headerName: "Group Count"};
|
|
279
400
|
}
|
|
280
|
-
return colDefs.find(cd => cd.field === c.colId);
|
|
401
|
+
return colDefs.current.find(cd => cd.field === c.colId);
|
|
281
402
|
})
|
|
282
403
|
.filter(Boolean);
|
|
283
404
|
|
|
@@ -552,7 +673,15 @@ const SGrid = props => {
|
|
|
552
673
|
async function handleResetColsToStudio() {
|
|
553
674
|
try {
|
|
554
675
|
setBuilderTFilter(builderData?.filter?.Tfilter ?? [])
|
|
555
|
-
|
|
676
|
+
setFilter(pre => ({
|
|
677
|
+
...pre,
|
|
678
|
+
LocalTfilter: [
|
|
679
|
+
...(builderData?.filter?.fixedTFilter || []).map(f => ({...f})),
|
|
680
|
+
...(builderData?.filter?.LocalTfilter || []).map(f => ({...f}))
|
|
681
|
+
],
|
|
682
|
+
Tfilter: builderData?.filter?.Tfilter || [],
|
|
683
|
+
customFilterCode: builderData?.filter?.customFilterCode || ''
|
|
684
|
+
}))
|
|
556
685
|
const allowedAggFuncs = ["sum", "avg", "min", "max"];
|
|
557
686
|
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
558
687
|
let defaultCols = getDefaultColDefs()
|
|
@@ -567,7 +696,8 @@ const SGrid = props => {
|
|
|
567
696
|
const propertyType = col.propertyType ?? col.field?.propertyType;
|
|
568
697
|
const propPath = col.headerName ?? col.path;
|
|
569
698
|
const fieldName = col.headerName ?? col.path.replace(/\./g, '_');
|
|
570
|
-
const
|
|
699
|
+
const processedColumnsConfig = processColumnsConfig(columnsConfig ?? []);
|
|
700
|
+
const externalColConfig = processedColumnsConfig.find(x => x.field === fieldName)?.config ?? {}
|
|
571
701
|
const headerName = col.title ?? col.headerName ?? col.path;
|
|
572
702
|
const hasRouting = routingSettings.some(r => r.fieldName === fieldName);
|
|
573
703
|
let valueFormatter = null;
|
|
@@ -583,8 +713,15 @@ const SGrid = props => {
|
|
|
583
713
|
}
|
|
584
714
|
|
|
585
715
|
if (propertyType === 'Double') {
|
|
716
|
+
const decimals = col.formatting?.decimals ?? 0;
|
|
717
|
+
valueFormatter =
|
|
718
|
+
`return params?.value != null ? numeral(params.value.toFixed(${decimals})).format('0,0.${'0'.repeat(decimals)}') : ''`;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (propertyType === 'Int' && col.formatting?.decimals > 0) {
|
|
722
|
+
const decimals = col.formatting.decimals;
|
|
586
723
|
valueFormatter =
|
|
587
|
-
`return params?.value != null ? numeral(params.value.toFixed(
|
|
724
|
+
`return params?.value != null ? numeral(params.value.toFixed(${decimals})).format('0,0.${'0'.repeat(decimals)}') : ''`;
|
|
588
725
|
}
|
|
589
726
|
|
|
590
727
|
if (propertyType === 'Enum') {
|
|
@@ -593,7 +730,8 @@ const SGrid = props => {
|
|
|
593
730
|
`return params?.value != null ? enumTrans('${enumName}', params.value) : ''`;
|
|
594
731
|
}
|
|
595
732
|
|
|
596
|
-
const
|
|
733
|
+
const isNumeric = propertyType === 'Int' || propertyType === 'Double';
|
|
734
|
+
const isNumericAgg = isNumeric &&
|
|
597
735
|
!col.field?.path?.toLowerCase().endsWith('id');
|
|
598
736
|
|
|
599
737
|
const isImage = col.image === true;
|
|
@@ -608,7 +746,173 @@ const SGrid = props => {
|
|
|
608
746
|
? "agCheckboxCellRenderer"
|
|
609
747
|
: null)),
|
|
610
748
|
width: isImage ? 90 : undefined,
|
|
611
|
-
cellStyle:
|
|
749
|
+
cellStyle: (params) => {
|
|
750
|
+
let style = isImage ? {display: 'flex', alignItems: 'center', justifyContent: 'center'} : {};
|
|
751
|
+
const formatting = col.formatting;
|
|
752
|
+
if (formatting) {
|
|
753
|
+
if (formatting.fixedStyle) {
|
|
754
|
+
style = {...style, ...formatting.fixedStyle};
|
|
755
|
+
if (formatting.fixedStyle.flashing) {
|
|
756
|
+
style.animation = 'flashing-bg 1s infinite';
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (formatting.conditions && formatting.conditions.length > 0) {
|
|
760
|
+
const val = params.value;
|
|
761
|
+
for (const cond of formatting.conditions) {
|
|
762
|
+
let match = false;
|
|
763
|
+
const baseField = cond.baseField || 'self';
|
|
764
|
+
let currentVal;
|
|
765
|
+
if (baseField === 'self') {
|
|
766
|
+
currentVal = val;
|
|
767
|
+
} else {
|
|
768
|
+
// Resolve field path from data
|
|
769
|
+
// First try as is
|
|
770
|
+
currentVal = params.node.data[baseField];
|
|
771
|
+
|
|
772
|
+
// If not found and contains dots, try nested path
|
|
773
|
+
if ((currentVal === undefined || currentVal === null) && baseField.includes('.')) {
|
|
774
|
+
currentVal = baseField.split('.').reduce((obj, key) => (obj && obj[key] !== undefined) ? obj[key] : undefined, params.node.data);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const isNumericValue = typeof currentVal === 'number' || (!isNaN(parseFloat(currentVal)) && isFinite(currentVal));
|
|
779
|
+
let condVal;
|
|
780
|
+
if (cond.valueType === 'field' && cond.compareField) {
|
|
781
|
+
const compareField = cond.compareField;
|
|
782
|
+
// Resolve field path from data
|
|
783
|
+
// First try as is
|
|
784
|
+
condVal = params.node.data[compareField];
|
|
785
|
+
|
|
786
|
+
// If not found and contains dots, try nested path
|
|
787
|
+
if ((condVal === undefined || condVal === null) && compareField.includes('.')) {
|
|
788
|
+
condVal = compareField.split('.').reduce((obj, key) => (obj && obj[key] !== undefined) ? obj[key] : undefined, params.node.data);
|
|
789
|
+
}
|
|
790
|
+
} else if (cond.valueType === 'expression' && cond.expression) {
|
|
791
|
+
try {
|
|
792
|
+
// Math Expression evaluation
|
|
793
|
+
// Replace "FieldPath" with actual values from row data
|
|
794
|
+
let expr = cond.expression;
|
|
795
|
+
const fieldRegex = /"([^"]+)"/g;
|
|
796
|
+
let match;
|
|
797
|
+
let hasMissingValue = false;
|
|
798
|
+
const fieldValues = {};
|
|
799
|
+
|
|
800
|
+
// Find all fields in quotes
|
|
801
|
+
while ((match = fieldRegex.exec(cond.expression)) !== null) {
|
|
802
|
+
const fullMatch = match[0];
|
|
803
|
+
const path = match[1];
|
|
804
|
+
let val;
|
|
805
|
+
let fieldFound = false;
|
|
806
|
+
|
|
807
|
+
if (path === 'self') {
|
|
808
|
+
val = params.value;
|
|
809
|
+
fieldFound = true;
|
|
810
|
+
} else {
|
|
811
|
+
// Resolve field path from data
|
|
812
|
+
// First try as is (handles fieldName with underscores)
|
|
813
|
+
val = params.node.data[path];
|
|
814
|
+
|
|
815
|
+
if (val !== undefined) {
|
|
816
|
+
fieldFound = true;
|
|
817
|
+
} else if (path.includes('.')) {
|
|
818
|
+
// If not found and contains dots, try nested path
|
|
819
|
+
val = path.split('.').reduce((obj, key) => (obj && obj[key] !== undefined) ? obj[key] : undefined, params.node.data);
|
|
820
|
+
if (val !== undefined) {
|
|
821
|
+
fieldFound = true;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (fieldFound) {
|
|
827
|
+
// If it's a string that is not a number, wrap it in single quotes to make it a string literal in the expression
|
|
828
|
+
// unless it's already a number
|
|
829
|
+
if (typeof val === 'string' && isNaN(parseFloat(val))) {
|
|
830
|
+
fieldValues[fullMatch] = `'${val.replace(/'/g, "\\'")}'`;
|
|
831
|
+
} else if (typeof val === 'number') {
|
|
832
|
+
fieldValues[fullMatch] = val;
|
|
833
|
+
} else if (!isNaN(parseFloat(val)) && isFinite(val)) {
|
|
834
|
+
fieldValues[fullMatch] = parseFloat(val);
|
|
835
|
+
} else if (val === null) {
|
|
836
|
+
fieldValues[fullMatch] = 'null';
|
|
837
|
+
} else if (typeof val === 'boolean') {
|
|
838
|
+
fieldValues[fullMatch] = val;
|
|
839
|
+
} else {
|
|
840
|
+
// Default to string if we can't be sure
|
|
841
|
+
fieldValues[fullMatch] = `'${String(val).replace(/'/g, "\\'")}'`;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// Replace fields with their values
|
|
847
|
+
// Sort keys by length descending to avoid partial replacements (e.g., "Field" before "FieldLonger")
|
|
848
|
+
const sortedKeys = Object.keys(fieldValues).sort((a, b) => b.length - a.length);
|
|
849
|
+
sortedKeys.forEach(key => {
|
|
850
|
+
// Escape special characters in key for RegExp
|
|
851
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
852
|
+
expr = expr.replace(new RegExp(escapedKey, 'g'), fieldValues[key]);
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
// Use Function constructor for evaluation
|
|
856
|
+
// Strip potentially dangerous characters while allowing math, comparison, ternary, and basic logical operators
|
|
857
|
+
// We allow: numbers, operators (+-*/), comparison (><=!|&), ternary (?:), parens, dots, spaces, commas, and some math words
|
|
858
|
+
// Updated to allow keywords for if and switch statements
|
|
859
|
+
// Also added ' for string literals
|
|
860
|
+
const safeExpr = expr.replace(/[^0-9+\-*/().\s><=!|&?:,;{}'a-zA-Z\\]/g, '');
|
|
861
|
+
// Check if it already contains 'return', otherwise wrap it if it looks like a simple expression
|
|
862
|
+
const finalCode = safeExpr.trim().startsWith('if') || safeExpr.trim().startsWith('switch') || safeExpr.includes('return')
|
|
863
|
+
? safeExpr
|
|
864
|
+
: `return ${safeExpr}`;
|
|
865
|
+
condVal = new Function(finalCode)();
|
|
866
|
+
} catch (e) {
|
|
867
|
+
console.error("Math expression evaluation failed:", e);
|
|
868
|
+
condVal = undefined;
|
|
869
|
+
}
|
|
870
|
+
} else {
|
|
871
|
+
condVal = cond.value;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const isCondNumeric = typeof condVal === 'number' || (!isNaN(parseFloat(condVal)) && isFinite(condVal));
|
|
875
|
+
const finalCondVal = isCondNumeric ? parseFloat(condVal) : condVal;
|
|
876
|
+
const compareVal = isNumericValue ? parseFloat(currentVal) : currentVal;
|
|
877
|
+
|
|
878
|
+
if (cond.valueType === 'expression') {
|
|
879
|
+
match = !!condVal;
|
|
880
|
+
} else {
|
|
881
|
+
switch (cond.operator) {
|
|
882
|
+
case '==':
|
|
883
|
+
match = compareVal == finalCondVal;
|
|
884
|
+
break;
|
|
885
|
+
case '!=':
|
|
886
|
+
match = compareVal != finalCondVal;
|
|
887
|
+
break;
|
|
888
|
+
case '>':
|
|
889
|
+
match = compareVal > finalCondVal;
|
|
890
|
+
break;
|
|
891
|
+
case '<':
|
|
892
|
+
match = compareVal < finalCondVal;
|
|
893
|
+
break;
|
|
894
|
+
case '>=':
|
|
895
|
+
match = compareVal >= finalCondVal;
|
|
896
|
+
break;
|
|
897
|
+
case '<=':
|
|
898
|
+
match = compareVal <= finalCondVal;
|
|
899
|
+
break;
|
|
900
|
+
case 'contains':
|
|
901
|
+
match = String(compareVal).toLowerCase().includes(String(finalCondVal).toLowerCase());
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
if (match) {
|
|
906
|
+
style = {...style, ...cond.style};
|
|
907
|
+
if (cond.style.flashing) {
|
|
908
|
+
style.animation = 'flashing-bg 1s infinite';
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return style;
|
|
915
|
+
},
|
|
612
916
|
allowedAggFuncs: isNumericAgg ? allowedAggFuncs : [],
|
|
613
917
|
valueFormatter: valueFormatter
|
|
614
918
|
? applyValueFormatter(valueFormatter)
|
|
@@ -655,10 +959,11 @@ const SGrid = props => {
|
|
|
655
959
|
}))
|
|
656
960
|
setSelectParams(localSelectParams);
|
|
657
961
|
if (extraCols != null) {
|
|
658
|
-
|
|
962
|
+
const processedExtraCols = processColumnDefinitions(extraCols);
|
|
963
|
+
localColDefs = [...processedExtraCols, ...localColDefs];
|
|
659
964
|
}
|
|
660
965
|
localColDefs = [...localColDefs, ...defaultCols];
|
|
661
|
-
|
|
966
|
+
colDefs.current = ([
|
|
662
967
|
{
|
|
663
968
|
field: 'IZ_groupCount',
|
|
664
969
|
headerName: 'Group Count',
|
|
@@ -705,7 +1010,8 @@ const SGrid = props => {
|
|
|
705
1010
|
tableCols = processColumns(tableCols)
|
|
706
1011
|
let localColDefs = [...tableCols, ...defaultCols]
|
|
707
1012
|
if (extraCols != null) {
|
|
708
|
-
|
|
1013
|
+
const processedExtraCols = processColumnDefinitions(extraCols);
|
|
1014
|
+
localColDefs = [...processedExtraCols, ...localColDefs]
|
|
709
1015
|
}
|
|
710
1016
|
const localSelectTFilter = response.data.map(x => ({
|
|
711
1017
|
friendlyName: x.fieldName,
|
|
@@ -714,9 +1020,9 @@ const SGrid = props => {
|
|
|
714
1020
|
propertyType: x.type,
|
|
715
1021
|
}))
|
|
716
1022
|
setSelectTFilter(localSelectTFilter)
|
|
717
|
-
|
|
1023
|
+
colDefs.current = localColDefs
|
|
718
1024
|
} else {
|
|
719
|
-
|
|
1025
|
+
colDefs.current = []
|
|
720
1026
|
}
|
|
721
1027
|
} catch (error) {
|
|
722
1028
|
|
|
@@ -753,92 +1059,161 @@ const SGrid = props => {
|
|
|
753
1059
|
const {tRouting} = context;
|
|
754
1060
|
const fieldName = colDef.field;
|
|
755
1061
|
|
|
756
|
-
const
|
|
757
|
-
|
|
1062
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
1063
|
+
const open = Boolean(anchorEl);
|
|
758
1064
|
|
|
759
|
-
|
|
760
|
-
|
|
1065
|
+
const routes = tRouting?.filter(r => r.fieldName === fieldName || r.headerName === fieldName) || [];
|
|
1066
|
+
if (routes.length === 0) return <span>{value}</span>;
|
|
761
1067
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
try {
|
|
767
|
-
const fn = new Function("row", routing.script);
|
|
768
|
-
finalRoute = fn(data);
|
|
769
|
-
} catch (err) {
|
|
770
|
-
console.error("Routing script error:", err);
|
|
771
|
-
finalRoute = "#";
|
|
772
|
-
}
|
|
773
|
-
} else if (routing.pattern) {
|
|
774
|
-
finalRoute = routing.pattern.replace(/\{\{(.*?)\}\}/g, (_, key) => {
|
|
775
|
-
const val = data?.[key];
|
|
776
|
-
return val != null ? val : `:${key}`;
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
}
|
|
1068
|
+
const handleRouteClick = (routing) => {
|
|
1069
|
+
setAnchorEl(null);
|
|
1070
|
+
// Determine behavior based on routing.type
|
|
1071
|
+
const type = routing.type || 'Route';
|
|
780
1072
|
|
|
781
|
-
const handleClick = () => {
|
|
782
1073
|
if (type === 'Filter') {
|
|
783
1074
|
try {
|
|
784
1075
|
// Prepare payload to pass via window.name (works across new tabs without session)
|
|
785
|
-
const raw = routing.externalFilterJson || '';
|
|
786
|
-
const replaced = raw.replace(/\{\{(.*?)\}\}/g, (_, key) => {
|
|
787
|
-
const v = data?.[key];
|
|
788
|
-
return v != null ? String(v) : '';
|
|
789
|
-
});
|
|
790
1076
|
let externalFilter = null;
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
1077
|
+
|
|
1078
|
+
if (routing.customFilterCode) {
|
|
1079
|
+
try {
|
|
1080
|
+
// Create function with access to authContext (if needed) and row data
|
|
1081
|
+
const executeCode = new Function('context', 'authContext', 'row', `
|
|
1082
|
+
let customFilter = [];
|
|
1083
|
+
try {
|
|
1084
|
+
${routing.customFilterCode}
|
|
1085
|
+
} catch (e) {
|
|
1086
|
+
console.error('Inner error in routing customFilterCode:', e);
|
|
1087
|
+
}
|
|
1088
|
+
return customFilter;
|
|
1089
|
+
`);
|
|
1090
|
+
const customFilterResult = executeCode(contextValues, authValues, data);
|
|
1091
|
+
|
|
1092
|
+
if (customFilterResult && Array.isArray(customFilterResult)) {
|
|
1093
|
+
externalFilter = {Tfilter: FilterFormat(customFilterResult)};
|
|
1094
|
+
} else if (customFilterResult && typeof customFilterResult === 'object') {
|
|
1095
|
+
// If it returned { Tfilter: [...] } directly
|
|
1096
|
+
externalFilter = customFilterResult;
|
|
1097
|
+
}
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
console.error('Error executing routing custom filter code:', error);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
if (!externalFilter) {
|
|
1104
|
+
const raw = routing.externalFilterJson || '';
|
|
1105
|
+
const replaced = raw.replace(/\{\{(.*?)\}\}/g, (_, key) => {
|
|
1106
|
+
const v = data?.[key];
|
|
1107
|
+
return v != null ? String(v) : '';
|
|
1108
|
+
});
|
|
1109
|
+
try {
|
|
1110
|
+
externalFilter = replaced ? JSON.parse(replaced) : null;
|
|
1111
|
+
} catch (e) {
|
|
1112
|
+
console.error('Invalid externalFilterJson after replacement', e);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
if (externalFilter && routing.customFilterCode) {
|
|
1117
|
+
externalFilter.customFilterCode = routing.customFilterCode;
|
|
795
1118
|
}
|
|
796
1119
|
|
|
797
1120
|
const builderId = context?.builderId;
|
|
1121
|
+
const targetPageId = routing.pageId;
|
|
798
1122
|
const payload = {
|
|
799
|
-
pageId:
|
|
1123
|
+
pageId: targetPageId ?? null,
|
|
800
1124
|
externalFilter,
|
|
801
|
-
id: builderId ?? null
|
|
1125
|
+
id: targetPageId ? null : (builderId ?? null)
|
|
802
1126
|
};
|
|
803
1127
|
|
|
804
1128
|
const base = '/reportBuilder/reportViewer';
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
if (
|
|
809
|
-
|
|
1129
|
+
let url = base;
|
|
1130
|
+
if (targetPageId) {
|
|
1131
|
+
url += `?pageId=${encodeURIComponent(targetPageId)}`;
|
|
1132
|
+
} else if (builderId) {
|
|
1133
|
+
url += `?id=${encodeURIComponent(builderId)}`;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
try {
|
|
1137
|
+
const sessionId = saveReportSession(payload);
|
|
1138
|
+
if (sessionId) {
|
|
1139
|
+
url += (url.includes('?') ? '&' : '?') + `sessionId=${sessionId}`;
|
|
1140
|
+
} else {
|
|
1141
|
+
// fallback to session storage if localStorage fails
|
|
810
1142
|
const json = JSON.stringify(payload);
|
|
811
|
-
|
|
812
|
-
child.name = `RB:${b64}`;
|
|
813
|
-
} catch (e) {
|
|
814
|
-
// as a fallback, put minimal info in URL (may be long for large filters)
|
|
815
|
-
try {
|
|
816
|
-
const fallback = encodeURIComponent(JSON.stringify(payload));
|
|
817
|
-
child.location.href = builderId ? `${base}?id=${encodeURIComponent(builderId)}&payload=${fallback}` : `${base}?payload=${fallback}`;
|
|
818
|
-
} catch {}
|
|
1143
|
+
sessionStorage.setItem('RB_PAYLOAD', json);
|
|
819
1144
|
}
|
|
1145
|
+
} catch (e) {
|
|
1146
|
+
console.error('Failed to set session payload', e);
|
|
820
1147
|
}
|
|
1148
|
+
router.push(url).then(() => {
|
|
1149
|
+
});
|
|
821
1150
|
} catch (e) {
|
|
822
1151
|
console.error('Filter routing error', e);
|
|
823
1152
|
}
|
|
824
1153
|
} else {
|
|
1154
|
+
// Build finalRoute for Route type
|
|
1155
|
+
let finalRoute = null;
|
|
1156
|
+
if (routing.script) {
|
|
1157
|
+
try {
|
|
1158
|
+
const fn = new Function("row", routing.script);
|
|
1159
|
+
finalRoute = fn(data);
|
|
1160
|
+
} catch (err) {
|
|
1161
|
+
console.error("Routing script error:", err);
|
|
1162
|
+
finalRoute = "#";
|
|
1163
|
+
}
|
|
1164
|
+
} else if (routing.pattern) {
|
|
1165
|
+
finalRoute = routing.pattern.replace(/\{\{(.*?)\}\}/g, (_, key) => {
|
|
1166
|
+
const val = data?.[key];
|
|
1167
|
+
return val != null ? val : `:${key}`;
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
|
|
825
1171
|
if (finalRoute && finalRoute !== '#') {
|
|
826
|
-
|
|
1172
|
+
const target = routing.target || '_self';
|
|
1173
|
+
|
|
1174
|
+
if (target === '_blank' || target === '_parent' || target === '_top') {
|
|
1175
|
+
// Open in new tab/window or specific frame
|
|
1176
|
+
window.open(finalRoute, target);
|
|
1177
|
+
} else {
|
|
1178
|
+
// Default: navigate in same tab using Next.js router
|
|
1179
|
+
router.push(finalRoute);
|
|
1180
|
+
}
|
|
827
1181
|
}
|
|
828
1182
|
}
|
|
829
1183
|
};
|
|
830
1184
|
|
|
1185
|
+
const handleClick = (event) => {
|
|
1186
|
+
if (routes.length > 1) {
|
|
1187
|
+
setAnchorEl(event.currentTarget);
|
|
1188
|
+
} else {
|
|
1189
|
+
handleRouteClick(routes[0]);
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
|
|
831
1193
|
return (
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1194
|
+
<>
|
|
1195
|
+
<span
|
|
1196
|
+
style={{
|
|
1197
|
+
color: '#1976d2',
|
|
1198
|
+
fontWeight: 600,
|
|
1199
|
+
cursor: 'pointer',
|
|
1200
|
+
}}
|
|
1201
|
+
onClick={handleClick}
|
|
1202
|
+
>
|
|
1203
|
+
{value}
|
|
1204
|
+
</span>
|
|
1205
|
+
<Menu
|
|
1206
|
+
anchorEl={anchorEl}
|
|
1207
|
+
open={open}
|
|
1208
|
+
onClose={() => setAnchorEl(null)}
|
|
1209
|
+
>
|
|
1210
|
+
{routes.map((r, idx) => (
|
|
1211
|
+
<MenuItem key={idx} onClick={() => handleRouteClick(r)}>
|
|
1212
|
+
{r.name || `Route ${idx + 1}`}
|
|
1213
|
+
</MenuItem>
|
|
1214
|
+
))}
|
|
1215
|
+
</Menu>
|
|
1216
|
+
</>
|
|
842
1217
|
);
|
|
843
1218
|
};
|
|
844
1219
|
|
|
@@ -1059,13 +1434,30 @@ const SGrid = props => {
|
|
|
1059
1434
|
setFinalRequestObject(defaultFinalRequest)
|
|
1060
1435
|
const fixedTFilter = externalFilter?.fixedTFilter ?? externalFilter?.fixedTfilter ?? []
|
|
1061
1436
|
const localFixedTFilter = Filter?.fixedTFilter ?? []
|
|
1062
|
-
let localFilter = externalFilter ? {...externalFilter} : {}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1437
|
+
let localFilter = externalFilter && !Array.isArray(externalFilter) ? {...externalFilter} : {}
|
|
1438
|
+
// Merge with Filter but be careful not to overwrite Tfilter/TFilter with empty ones
|
|
1439
|
+
const {Tfilter: _tf, TFilter: _tF, customFilterCode: _cfc, ...otherInternalFilter} = Filter || {}
|
|
1440
|
+
localFilter = {...localFilter, ...otherInternalFilter}
|
|
1441
|
+
|
|
1442
|
+
let localTFilter = []
|
|
1443
|
+
if (externalFilter?.Tfilter) localTFilter.push(...externalFilter.Tfilter)
|
|
1444
|
+
if (externalFilter?.TFilter) localTFilter.push(...externalFilter.TFilter)
|
|
1445
|
+
if (Filter?.Tfilter) localTFilter.push(...Filter.Tfilter)
|
|
1446
|
+
if (Filter?.TFilter) localTFilter.push(...Filter.TFilter)
|
|
1447
|
+
|
|
1448
|
+
// Also support LocalTfilter (this is where CustomFilterDialog often stores its filters)
|
|
1449
|
+
if (Filter?.LocalTfilter) localTFilter.push(...Filter.LocalTfilter)
|
|
1450
|
+
if (externalFilter?.LocalTfilter) localTFilter.push(...externalFilter.LocalTfilter)
|
|
1451
|
+
|
|
1452
|
+
// Support for routing-passed externalFilter that might be directly inside externalFilter (not under Tfilter)
|
|
1453
|
+
if (Array.isArray(externalFilter) && localTFilter.length === 0) {
|
|
1454
|
+
localTFilter = [...externalFilter];
|
|
1455
|
+
}
|
|
1067
1456
|
localTFilter = [...localTFilter, ...fixedTFilter, ...localFixedTFilter, ...builderTFilter]
|
|
1068
1457
|
|
|
1458
|
+
// Extract customFilterCode from externalFilter if present (e.g. from routing)
|
|
1459
|
+
let currentCustomFilterCode = Filter?.customFilterCode || externalFilter?.customFilterCode || "";
|
|
1460
|
+
|
|
1069
1461
|
// Apply explicit user-selected search filters (selectedSearchObjects), grouped by path
|
|
1070
1462
|
if ((selectedSearchObjects?.length ?? 0) > 0) {
|
|
1071
1463
|
const byPath = selectedSearchObjects.reduce((acc, item) => {
|
|
@@ -1099,8 +1491,6 @@ const SGrid = props => {
|
|
|
1099
1491
|
localTFilter = [...localTFilter, ...grouped];
|
|
1100
1492
|
}
|
|
1101
1493
|
|
|
1102
|
-
localTFilter = localTFilter.filter(x => x.value !== null && x.value !== undefined && x.value !== '')
|
|
1103
|
-
|
|
1104
1494
|
delete localFilter.Tfilter
|
|
1105
1495
|
delete localFilter.fixedTfilter
|
|
1106
1496
|
delete localFilter.fixedTFilter
|
|
@@ -1112,8 +1502,8 @@ const SGrid = props => {
|
|
|
1112
1502
|
const pageSize = request.endRow - request.startRow // Calculate the page size
|
|
1113
1503
|
let aggregators = request.valueCols.filter(x => x.id != 'groupCount').map(agg => handleGetAggregationObject(agg))
|
|
1114
1504
|
let updateAggregation = true
|
|
1115
|
-
if (colDefs && gridApi) {
|
|
1116
|
-
let resultColDef = [...colDefs]
|
|
1505
|
+
if (colDefs.current && gridApi) {
|
|
1506
|
+
let resultColDef = [...colDefs.current]
|
|
1117
1507
|
if (request.rowGroupCols.length > 0) {
|
|
1118
1508
|
}
|
|
1119
1509
|
// gridApi.setGridOption('columnDefs', resultColDef)
|
|
@@ -1121,7 +1511,7 @@ const SGrid = props => {
|
|
|
1121
1511
|
}
|
|
1122
1512
|
// Build tFilter safely: remove fixed from preformatted lists and merge a single fixed source
|
|
1123
1513
|
// 1) Strip fixed items from local Tfilter/TFilter (they may already be formatted by CustomFilterDialog)
|
|
1124
|
-
let nonFixedLocal = Array.isArray(localTFilter) ? localTFilter.filter(it =>
|
|
1514
|
+
let nonFixedLocal = Array.isArray(localTFilter) ? localTFilter.filter(it => it != null && !(it.fixed === true && it.value === null)) : []
|
|
1125
1515
|
|
|
1126
1516
|
// 2) Choose ONE fixed source by priority: component state -> external -> builder
|
|
1127
1517
|
const extFixed = Array.isArray(externalFilter?.fixedTFilter)
|
|
@@ -1129,12 +1519,80 @@ const SGrid = props => {
|
|
|
1129
1519
|
: (Array.isArray(externalFilter?.fixedTfilter) ? externalFilter.fixedTfilter : [])
|
|
1130
1520
|
const fixedSource = (Array.isArray(Filter?.fixedTFilter) && Filter.fixedTFilter.length > 0)
|
|
1131
1521
|
? Filter.fixedTFilter
|
|
1132
|
-
: (extFixed.length > 0 ? extFixed : (Array.isArray(builderTFilter) ? builderTFilter : []))
|
|
1522
|
+
: (extFixed.length > 0 ? extFixed : (Array.isArray(builderTFilter) && builderTFilter.length > 0 ? builderTFilter : []))
|
|
1133
1523
|
|
|
1134
1524
|
// 3) Merge and format once
|
|
1135
|
-
const mergedForFormat = [...nonFixedLocal]
|
|
1136
|
-
|
|
1137
|
-
|
|
1525
|
+
const mergedForFormat = [...nonFixedLocal, ...fixedSource]
|
|
1526
|
+
let formattedT = FilterFormat(mergedForFormat).filter(x => !x?.isCustom)
|
|
1527
|
+
|
|
1528
|
+
// Dynamic placeholder replacement for authValues
|
|
1529
|
+
if (formattedT && Array.isArray(formattedT)) {
|
|
1530
|
+
formattedT = formattedT.map(f => {
|
|
1531
|
+
if (typeof f.value === 'string' && f.value.includes('{{auth.')) {
|
|
1532
|
+
const replaced = String(f.value).replace(/\{\{auth\.(.*?)\}\}/g, (_, path) => {
|
|
1533
|
+
return path.split('.').reduce((acc, key) => acc && acc[key], authValues) ?? '';
|
|
1534
|
+
});
|
|
1535
|
+
return {...f, value: replaced};
|
|
1536
|
+
}
|
|
1537
|
+
return f;
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
// Dynamic placeholder replacement for authValues
|
|
1542
|
+
if (formattedT && Array.isArray(formattedT)) {
|
|
1543
|
+
formattedT = formattedT.map(f => {
|
|
1544
|
+
if (typeof f.value === 'string' && f.value.includes('{{context.')) {
|
|
1545
|
+
const replaced = String(f.value).replace(/\{\{context\.(.*?)\}\}/g, (_, path) => {
|
|
1546
|
+
return path.split('.').reduce((acc, key) => acc && acc[key], contextValues) ?? '';
|
|
1547
|
+
});
|
|
1548
|
+
return {...f, value: replaced};
|
|
1549
|
+
}
|
|
1550
|
+
return f;
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// 4) Execute custom filter code if present
|
|
1555
|
+
let customFilterResult = null
|
|
1556
|
+
if (currentCustomFilterCode && currentCustomFilterCode.trim()) {
|
|
1557
|
+
try {
|
|
1558
|
+
// Wrap code in a function that provides contextValues
|
|
1559
|
+
const executeCode = new Function('context', 'authContext', 'filters', 'localFilter', `
|
|
1560
|
+
let customFilter = [];
|
|
1561
|
+
try {
|
|
1562
|
+
${currentCustomFilterCode}
|
|
1563
|
+
} catch (e) {
|
|
1564
|
+
console.error('Inner error in customFilterCode:', e);
|
|
1565
|
+
}
|
|
1566
|
+
return customFilter;
|
|
1567
|
+
`);
|
|
1568
|
+
customFilterResult = executeCode(contextValues, authValues, formattedT, localFilter)
|
|
1569
|
+
|
|
1570
|
+
// Validate that result is an array
|
|
1571
|
+
if (customFilterResult && !Array.isArray(customFilterResult)) {
|
|
1572
|
+
console.error('Custom filter must return an array, got:', typeof customFilterResult)
|
|
1573
|
+
customFilterResult = null
|
|
1574
|
+
}
|
|
1575
|
+
} catch (error) {
|
|
1576
|
+
console.error('Error executing custom filter code in SGrid:', error)
|
|
1577
|
+
customFilterResult = null
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// Merge custom filter array with formattedT if present
|
|
1582
|
+
if (customFilterResult && Array.isArray(customFilterResult)) {
|
|
1583
|
+
formattedT = [...formattedT, ...customFilterResult]
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// Final Deduplication after all merges and custom code execution
|
|
1587
|
+
if (Array.isArray(formattedT)) {
|
|
1588
|
+
formattedT = formattedT.filter((f, idx, self) =>
|
|
1589
|
+
idx === self.findIndex(t => (
|
|
1590
|
+
t.path === f.path &&
|
|
1591
|
+
t.method === f.method &&
|
|
1592
|
+
JSON.stringify(t.value) === JSON.stringify(f.value)
|
|
1593
|
+
))
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1138
1596
|
|
|
1139
1597
|
let data = {
|
|
1140
1598
|
...localFilter,
|
|
@@ -1144,7 +1602,7 @@ const SGrid = props => {
|
|
|
1144
1602
|
}
|
|
1145
1603
|
// Exclude any tFilter entries with null value before sending the request (per requirement)
|
|
1146
1604
|
if (Array.isArray(data.tFilter)) {
|
|
1147
|
-
data.tFilter = data.tFilter.filter(f => f == null ? false : f.value !== null);
|
|
1605
|
+
data.tFilter = data.tFilter.filter(f => f == null ? false : (f.value !== null && !f.isCustom));
|
|
1148
1606
|
}
|
|
1149
1607
|
try {
|
|
1150
1608
|
if (request.rowGroupCols.length !== request.groupKeys.length) {
|
|
@@ -1175,6 +1633,33 @@ const SGrid = props => {
|
|
|
1175
1633
|
}));
|
|
1176
1634
|
const aggIds = request.valueCols.map(vc => vc.id);
|
|
1177
1635
|
|
|
1636
|
+
// Dynamic placeholder replacement for authValues in selectParams
|
|
1637
|
+
let dynamicSelectParams = selectParams;
|
|
1638
|
+
if (Array.isArray(dynamicSelectParams)) {
|
|
1639
|
+
dynamicSelectParams = dynamicSelectParams.map(p => {
|
|
1640
|
+
if (typeof p.value === 'string' && p.value.includes('{{auth.')) {
|
|
1641
|
+
const replaced = p.value.replace(/\{\{auth\.(.*?)\}\}/g, (_, path) => {
|
|
1642
|
+
return path.split('.').reduce((acc, key) => acc && acc[key], authValues) ?? '';
|
|
1643
|
+
});
|
|
1644
|
+
return {...p, value: replaced};
|
|
1645
|
+
}
|
|
1646
|
+
return p;
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// Dynamic placeholder replacement for contextValues in selectParams
|
|
1651
|
+
if (Array.isArray(dynamicSelectParams)) {
|
|
1652
|
+
dynamicSelectParams = dynamicSelectParams.map(p => {
|
|
1653
|
+
if (typeof p.value === 'string' && p.value.includes('{{context.')) {
|
|
1654
|
+
const replaced = p.value.replace(/\{\{context\.(.*?)\}\}/g, (_, path) => {
|
|
1655
|
+
return path.split('.').reduce((acc, key) => acc && acc[key], contextValues) ?? '';
|
|
1656
|
+
});
|
|
1657
|
+
return {...p, value: replaced};
|
|
1658
|
+
}
|
|
1659
|
+
return p;
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1178
1663
|
const groupOrderColdIds = request.sortModel.filter(x => groupOrderCols.includes(x.colId) || ((x.colId == "IZ_groupCount" || x.colId.endsWith("groupCount") || aggIds.some(id => x.colId.toLowerCase().endsWith(id.toLowerCase()))) && isGroupCountOrder))
|
|
1179
1664
|
let orderBy = groupOrderColdIds.map(sort => handleGetSortObject(sort.colId, sort.sort, true, aggIds.some(id => sort.colId.toLowerCase().endsWith(id.toLowerCase()))))
|
|
1180
1665
|
data = {
|
|
@@ -1183,11 +1668,22 @@ const SGrid = props => {
|
|
|
1183
1668
|
orderBy: orderBy
|
|
1184
1669
|
}
|
|
1185
1670
|
|
|
1671
|
+
// Final deduplication for group keys vs base filters
|
|
1672
|
+
if (Array.isArray(data.tFilter)) {
|
|
1673
|
+
data.tFilter = data.tFilter.filter((f, idx, self) =>
|
|
1674
|
+
idx === self.findIndex(t => (
|
|
1675
|
+
t.path === f.path &&
|
|
1676
|
+
t.method === f.method &&
|
|
1677
|
+
JSON.stringify(t.value) === JSON.stringify(f.value)
|
|
1678
|
+
))
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1186
1682
|
// if (updateAggregation !== true) {
|
|
1187
1683
|
// data.aggregators = []
|
|
1188
1684
|
// }
|
|
1189
1685
|
data.selectFields = selectedFields
|
|
1190
|
-
data.selectParams =
|
|
1686
|
+
data.selectParams = dynamicSelectParams
|
|
1191
1687
|
data.rawSql = builderData.rawSql
|
|
1192
1688
|
|
|
1193
1689
|
const response = await Services.PostService(groupEndPoint, false, data, {
|
|
@@ -1259,8 +1755,46 @@ const SGrid = props => {
|
|
|
1259
1755
|
...data,
|
|
1260
1756
|
tFilter: [...tFilters, ...data.tFilter]
|
|
1261
1757
|
}
|
|
1758
|
+
|
|
1759
|
+
// Final deduplication for group keys vs base filters
|
|
1760
|
+
if (Array.isArray(data.tFilter)) {
|
|
1761
|
+
data.tFilter = data.tFilter.filter((f, idx, self) =>
|
|
1762
|
+
idx === self.findIndex(t => (
|
|
1763
|
+
t.path === f.path &&
|
|
1764
|
+
t.method === f.method &&
|
|
1765
|
+
JSON.stringify(t.value) === JSON.stringify(f.value)
|
|
1766
|
+
))
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1262
1769
|
}
|
|
1263
1770
|
let orderBy = request.sortModel.filter(x => !x.colId.endsWith("IZ_groupCount")).map(sort => handleGetSortObject(sort.colId, sort.sort, false))
|
|
1771
|
+
|
|
1772
|
+
// Dynamic placeholder replacement for authValues in selectParams
|
|
1773
|
+
let dynamicSelectParams = selectParams;
|
|
1774
|
+
if (Array.isArray(dynamicSelectParams)) {
|
|
1775
|
+
dynamicSelectParams = dynamicSelectParams.map(p => {
|
|
1776
|
+
if (typeof p.value === 'string' && p.value.includes('{{auth.')) {
|
|
1777
|
+
const replaced = p.value.replace(/\{\{auth\.(.*?)\}\}/g, (_, path) => {
|
|
1778
|
+
return path.split('.').reduce((acc, key) => acc && acc[key], authValues) ?? '';
|
|
1779
|
+
});
|
|
1780
|
+
return {...p, value: replaced};
|
|
1781
|
+
}
|
|
1782
|
+
return p;
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
if (Array.isArray(dynamicSelectParams)) {
|
|
1787
|
+
dynamicSelectParams = dynamicSelectParams.map(p => {
|
|
1788
|
+
if (typeof p.value === 'string' && p.value.includes('{{context.')) {
|
|
1789
|
+
const replaced = p.value.replace(/\{\{context\.(.*?)\}\}/g, (_, path) => {
|
|
1790
|
+
return path.split('.').reduce((acc, key) => acc && acc[key], contextValues) ?? '';
|
|
1791
|
+
});
|
|
1792
|
+
return {...p, value: replaced};
|
|
1793
|
+
}
|
|
1794
|
+
return p;
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1264
1798
|
if (data.tIncludes != null)
|
|
1265
1799
|
data = {
|
|
1266
1800
|
...data,
|
|
@@ -1281,7 +1815,7 @@ const SGrid = props => {
|
|
|
1281
1815
|
data.aggregators = []
|
|
1282
1816
|
}
|
|
1283
1817
|
data.selectFields = selectedFields
|
|
1284
|
-
data.selectParams =
|
|
1818
|
+
data.selectParams = dynamicSelectParams
|
|
1285
1819
|
data.lastRowParams = lastRowRef.current[pageNumber];
|
|
1286
1820
|
|
|
1287
1821
|
data.isPaginationByFilter = builderData?.isRaw === false
|
|
@@ -1292,7 +1826,6 @@ const SGrid = props => {
|
|
|
1292
1826
|
rowCount: undefined // <== This keeps the grid in "loading" state
|
|
1293
1827
|
}
|
|
1294
1828
|
data.rawSql = builderData?.rawSql
|
|
1295
|
-
console.log(builderData)
|
|
1296
1829
|
const response = await Services.PostService(pagedEndPoint, false, data, {
|
|
1297
1830
|
...paramsPage,
|
|
1298
1831
|
page: isSingle ? 1 : pageNumber,
|
|
@@ -1341,7 +1874,7 @@ const SGrid = props => {
|
|
|
1341
1874
|
return {success: false}
|
|
1342
1875
|
}
|
|
1343
1876
|
},
|
|
1344
|
-
[externalFilter, includes, paramsPage, Filter, selectedFields, isPagination, selectedSearchObjects, builderData]
|
|
1877
|
+
[externalFilter, includes, paramsPage, Filter, selectedFields, isPagination, selectedSearchObjects, builderData, authValues, contextValues]
|
|
1345
1878
|
)
|
|
1346
1879
|
const datasource = useMemo(() => createServerSideDatasource(getRowsFromApi), [getRowsFromApi]);
|
|
1347
1880
|
|
|
@@ -1416,7 +1949,19 @@ const SGrid = props => {
|
|
|
1416
1949
|
}
|
|
1417
1950
|
|
|
1418
1951
|
function handleFilterChange(field, value) {
|
|
1419
|
-
|
|
1952
|
+
if (field === 'LocalTfilter') {
|
|
1953
|
+
// When updating LocalTfilter, preserve main filters from builderModel
|
|
1954
|
+
setFilter(preValue => {
|
|
1955
|
+
const mainFilters = (preValue.LocalTfilter || []).filter(f => f.isMainFilter === true)
|
|
1956
|
+
const newFilters = Array.isArray(value) ? value : []
|
|
1957
|
+
return {
|
|
1958
|
+
...preValue,
|
|
1959
|
+
LocalTfilter: [...mainFilters, ...newFilters]
|
|
1960
|
+
}
|
|
1961
|
+
})
|
|
1962
|
+
} else {
|
|
1963
|
+
handleChange(setFilter, field, value)
|
|
1964
|
+
}
|
|
1420
1965
|
}
|
|
1421
1966
|
|
|
1422
1967
|
async function handleGetTemplates() {
|
|
@@ -1524,14 +2069,53 @@ const SGrid = props => {
|
|
|
1524
2069
|
}
|
|
1525
2070
|
|
|
1526
2071
|
const restoreState = () => {
|
|
1527
|
-
|
|
2072
|
+
let builderMainFilter = builderModel?.filter?.LocalTfilter ?? []
|
|
2073
|
+
|
|
2074
|
+
if (gridApi !== null && colDefs.current !== null && selectedTemplate != null) {
|
|
2075
|
+
console.log(selectedTemplate)
|
|
1528
2076
|
gridApi.applyColumnState({
|
|
1529
2077
|
state: selectedTemplate.value,
|
|
1530
2078
|
applyOrder: true,
|
|
1531
2079
|
});
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
2080
|
+
if (selectedTemplate.filterValue != null) {
|
|
2081
|
+
let fixedLocalFilters = builderModel?.filter?.fixedTFilter ?? []
|
|
2082
|
+
fixedLocalFilters = fixedLocalFilters.filter(x => x.fixed === true)
|
|
2083
|
+
fixedLocalFilters = fixedLocalFilters.map(x => ({
|
|
2084
|
+
...x,
|
|
2085
|
+
isMainFilter: x.fixed === true ? false : x.isMainFilter
|
|
2086
|
+
}))
|
|
2087
|
+
let templateFilter = selectedTemplate?.filterValue?.LocalTfilter ?? []
|
|
2088
|
+
let nonExistsFilter = fixedLocalFilters.filter(x => !templateFilter.some(y => y.path === x.path && y.method === x.method && y.isOrList === x.isOrList))
|
|
2089
|
+
let finalLocalFilter = [...templateFilter, ...nonExistsFilter]
|
|
2090
|
+
|
|
2091
|
+
// Handle Tfilter from template
|
|
2092
|
+
const templateTfilter = selectedTemplate?.filterValue?.Tfilter ?? []
|
|
2093
|
+
const templateCustomFilterCode = selectedTemplate?.filterValue?.customFilterCode ?? ''
|
|
2094
|
+
|
|
2095
|
+
setFilter(preValue => ({
|
|
2096
|
+
...preValue,
|
|
2097
|
+
LocalTfilter: [...finalLocalFilter, ...builderMainFilter],
|
|
2098
|
+
Tfilter: templateTfilter,
|
|
2099
|
+
customFilterCode: templateCustomFilterCode
|
|
2100
|
+
}));
|
|
2101
|
+
}
|
|
2102
|
+
} else {
|
|
2103
|
+
let fixedLocalFilters = builderModel?.filter?.fixedTFilter ?? []
|
|
2104
|
+
fixedLocalFilters = fixedLocalFilters.filter(x => x.fixed === true)
|
|
2105
|
+
|
|
2106
|
+
let finalLocalFilter = [...fixedLocalFilters]
|
|
2107
|
+
|
|
2108
|
+
// When no template, use builderModel filters
|
|
2109
|
+
const builderTfilter = builderModel?.filter?.Tfilter ?? []
|
|
2110
|
+
const builderCustomFilterCode = builderModel?.filter?.customFilterCode ?? ''
|
|
2111
|
+
|
|
2112
|
+
setFilter(preValue => ({
|
|
2113
|
+
...preValue,
|
|
2114
|
+
LocalTfilter: [...finalLocalFilter, ...builderMainFilter],
|
|
2115
|
+
Tfilter: builderTfilter,
|
|
2116
|
+
customFilterCode: builderCustomFilterCode
|
|
2117
|
+
}));
|
|
2118
|
+
|
|
1535
2119
|
}
|
|
1536
2120
|
};
|
|
1537
2121
|
|
|
@@ -1566,7 +2150,6 @@ const SGrid = props => {
|
|
|
1566
2150
|
};
|
|
1567
2151
|
|
|
1568
2152
|
setSelectedSearchObjects(prev => [...prev, newObj]);
|
|
1569
|
-
console.log(newObj)
|
|
1570
2153
|
setSearchTerm('');
|
|
1571
2154
|
setSearchPopoverOpen(false);
|
|
1572
2155
|
debouncedRefresh();
|
|
@@ -1575,18 +2158,16 @@ const SGrid = props => {
|
|
|
1575
2158
|
|
|
1576
2159
|
useEffect(() => {
|
|
1577
2160
|
if (builderData) {
|
|
1578
|
-
|
|
1579
2161
|
if (builderData.isRaw) {
|
|
1580
2162
|
handleGetRawColumns(builderData.rawSql)
|
|
1581
2163
|
} else {
|
|
1582
2164
|
handleResetColsToStudio()
|
|
1583
2165
|
}
|
|
1584
2166
|
}
|
|
1585
|
-
}, [builderData]);
|
|
2167
|
+
}, [builderData, isRerender]);
|
|
1586
2168
|
|
|
1587
2169
|
useEffect(() => {
|
|
1588
2170
|
if (builderModel != null) {
|
|
1589
|
-
console.log(builderModel)
|
|
1590
2171
|
let rawSql = builderModel.rawSql;
|
|
1591
2172
|
if (builderModel.isRaw === true) {
|
|
1592
2173
|
const localSelectParams = builderModel.selectionParams.map((x, index) => ({
|
|
@@ -1607,11 +2188,44 @@ const SGrid = props => {
|
|
|
1607
2188
|
return param.value;
|
|
1608
2189
|
});
|
|
1609
2190
|
}
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
2191
|
+
|
|
2192
|
+
setBuilderData(prev => {
|
|
2193
|
+
// Only update if builderModel data changed or rawSql changed
|
|
2194
|
+
const isSameModel = prev && JSON.stringify(prev) === JSON.stringify({...builderModel, rawSql});
|
|
2195
|
+
if (isSameModel) return prev;
|
|
2196
|
+
return {...builderModel, rawSql: rawSql};
|
|
2197
|
+
});
|
|
2198
|
+
|
|
2199
|
+
// We explicitly skip updating setFilter if builderModel triggered it but we just restored from session.
|
|
2200
|
+
// We rely on the fact that if a session restore happened, Filter.LocalTfilter will likely be populated.
|
|
2201
|
+
|
|
2202
|
+
setFilter(preValue => {
|
|
2203
|
+
const fixedTFilter = builderModel?.filter?.fixedTFilter || [];
|
|
2204
|
+
const localTFilter = builderModel?.filter?.LocalTfilter || [];
|
|
2205
|
+
const tFilter = builderModel?.filter?.Tfilter || [];
|
|
2206
|
+
const customFilterCode = builderModel?.filter?.customFilterCode || '';
|
|
2207
|
+
|
|
2208
|
+
// If we already have filters (possibly restored from session), don't overwrite with just fixed filters
|
|
2209
|
+
// unless it's a completely fresh state or fixed filters changed.
|
|
2210
|
+
const isSameFilter = JSON.stringify(preValue.LocalTfilter) === JSON.stringify([...fixedTFilter, ...localTFilter]);
|
|
2211
|
+
const isSameCode = preValue.customFilterCode === customFilterCode;
|
|
2212
|
+
|
|
2213
|
+
if (preValue.LocalTfilter.length > (fixedTFilter.length + localTFilter.length) && isSameCode) {
|
|
2214
|
+
return preValue;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
if (isSameFilter && isSameCode) return preValue;
|
|
2218
|
+
|
|
2219
|
+
return {
|
|
2220
|
+
...preValue,
|
|
2221
|
+
Tfilter: tFilter,
|
|
2222
|
+
LocalTfilter: [
|
|
2223
|
+
...fixedTFilter.map(f => ({...f})),
|
|
2224
|
+
...localTFilter.map(f => ({...f}))
|
|
2225
|
+
],
|
|
2226
|
+
customFilterCode: customFilterCode
|
|
2227
|
+
};
|
|
2228
|
+
})
|
|
1615
2229
|
}
|
|
1616
2230
|
}, [builderModel]);
|
|
1617
2231
|
|
|
@@ -1640,39 +2254,38 @@ const SGrid = props => {
|
|
|
1640
2254
|
}, [selectParams, builderModel?.isRaw]);
|
|
1641
2255
|
|
|
1642
2256
|
useEffect(() => {
|
|
1643
|
-
|
|
1644
2257
|
if (gridApi !== null) {
|
|
1645
2258
|
gridApi.refreshServerSide({purge: !noPurge})
|
|
1646
2259
|
lastRowRef.current = {}
|
|
1647
2260
|
}
|
|
1648
|
-
}, [refresh, externalFilter, localRefresh, Filter]);
|
|
2261
|
+
}, [refresh, externalFilter, localRefresh, Filter.Tfilter, Filter.LocalTfilter, Filter.customFilterCode, externalFilter?.customFilterCode]);
|
|
1649
2262
|
|
|
1650
2263
|
useEffect(() => {
|
|
1651
|
-
if (gridApi != null && colDefs != null)
|
|
2264
|
+
if (gridApi != null && colDefs.current != null)
|
|
1652
2265
|
handleGetTemplates()
|
|
1653
|
-
}, [gridApi, colDefs])
|
|
2266
|
+
}, [gridApi, colDefs.current])
|
|
1654
2267
|
|
|
1655
2268
|
useEffect(() => {
|
|
1656
|
-
if (colDefs.length > 0
|
|
2269
|
+
if (colDefs.current.length > 0) {
|
|
1657
2270
|
restoreState()
|
|
1658
2271
|
}
|
|
1659
2272
|
}, [selectedTemplate])
|
|
1660
|
-
useEffect(() => {
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
}, [
|
|
2273
|
+
// useEffect(() => {
|
|
2274
|
+
// if (templates.length > 0 && colDefs.length > 0) {
|
|
2275
|
+
// const userDefined = templates.find(x => x.isDefault === true)
|
|
2276
|
+
// if (userDefined != null)
|
|
2277
|
+
// setSelectedTemplate(userDefined)
|
|
2278
|
+
// else {
|
|
2279
|
+
// const systemDefined = templates.find(x => x.type === "UserPredefined")
|
|
2280
|
+
// if (systemDefined != null)
|
|
2281
|
+
// setSelectedTemplate(systemDefined)
|
|
2282
|
+
// else {
|
|
2283
|
+
// const anyTemplate = templates[0]
|
|
2284
|
+
// setSelectedTemplate(anyTemplate)
|
|
2285
|
+
// }
|
|
2286
|
+
// }
|
|
2287
|
+
// }
|
|
2288
|
+
// }, [])
|
|
1676
2289
|
|
|
1677
2290
|
useEffect(() => {
|
|
1678
2291
|
if (!timerValue || isNaN(timerValue) || timerValue == 0) {
|
|
@@ -1705,11 +2318,10 @@ const SGrid = props => {
|
|
|
1705
2318
|
}, [externalTimer]); // re// n after each refresh or change in value
|
|
1706
2319
|
|
|
1707
2320
|
return (
|
|
1708
|
-
<Grid
|
|
1709
|
-
<Grid container
|
|
2321
|
+
<Grid container size={{ xs: 12 }}>
|
|
2322
|
+
<Grid container
|
|
1710
2323
|
sx={{backgroundColor: '#fafafb', justifyContent: 'space-between'}} padding={2}
|
|
1711
|
-
|
|
1712
|
-
>
|
|
2324
|
+
size={{ xs: 12 }}>
|
|
1713
2325
|
<Box sx={{display: 'flex'}}>
|
|
1714
2326
|
{(builderData?.id != null && minimized !== true) &&
|
|
1715
2327
|
<Box sx={{minWidth: '250px'}}>
|
|
@@ -1719,12 +2331,11 @@ const SGrid = props => {
|
|
|
1719
2331
|
size={'small'}
|
|
1720
2332
|
value={selectedTemplate}
|
|
1721
2333
|
fullWidth
|
|
1722
|
-
disableClearable
|
|
1723
2334
|
options={templates}
|
|
1724
2335
|
onChange={(e, value) => {
|
|
1725
2336
|
setSelectedTemplate(value)
|
|
1726
|
-
}
|
|
1727
|
-
|
|
2337
|
+
}}
|
|
2338
|
+
|
|
1728
2339
|
getOptionLabel={option => option.name}
|
|
1729
2340
|
renderInput={params => (
|
|
1730
2341
|
<TextField
|
|
@@ -1745,7 +2356,7 @@ const SGrid = props => {
|
|
|
1745
2356
|
defaultValue={0} size={'small'}
|
|
1746
2357
|
color='primary' sx={{width: '60px'}}>
|
|
1747
2358
|
<MenuItem key={1} value={0}>No</MenuItem>
|
|
1748
|
-
|
|
2359
|
+
{/*<MenuItem key={2} value={0.1667}>10s</MenuItem>*/}
|
|
1749
2360
|
<MenuItem key={2} value={0.5}>30s</MenuItem>
|
|
1750
2361
|
<MenuItem key={2} value={1}>1m</MenuItem>
|
|
1751
2362
|
<MenuItem key={2} value={2}>2m</MenuItem>
|
|
@@ -1973,7 +2584,7 @@ const SGrid = props => {
|
|
|
1973
2584
|
columnHoverHighlight={true}
|
|
1974
2585
|
theme={agTheme}
|
|
1975
2586
|
enableRtl={true}
|
|
1976
|
-
columnDefs={colDefs}
|
|
2587
|
+
columnDefs={colDefs.current}
|
|
1977
2588
|
rowModelType={"serverSide"}
|
|
1978
2589
|
onGridReady={onGridReady}
|
|
1979
2590
|
maxConcurrentDatasourceRequests={0}
|
|
@@ -1981,10 +2592,95 @@ const SGrid = props => {
|
|
|
1981
2592
|
onRowSelected={onRowSelected}
|
|
1982
2593
|
// getChildCount={getChildCount}
|
|
1983
2594
|
sideBar={sideBarConfig}
|
|
1984
|
-
context={{
|
|
2595
|
+
context={{
|
|
2596
|
+
tRouting,
|
|
2597
|
+
builderId: builderData?.id,
|
|
2598
|
+
updateRef: updateRef,
|
|
2599
|
+
settings: builderModel?.settings
|
|
2600
|
+
}} // pass routing, builder id, updateRef and settings
|
|
1985
2601
|
gridOptions={{
|
|
1986
2602
|
enableRangeSelection: true,
|
|
1987
2603
|
enableCharts: true,
|
|
2604
|
+
getContextMenuItems: (params) => {
|
|
2605
|
+
const rowUniqueId = builderModel?.settings?.rowUniqueId
|
|
2606
|
+
const rowData = params.node?.data
|
|
2607
|
+
const defaultItems = params.defaultItems || []
|
|
2608
|
+
|
|
2609
|
+
if (!updateRef || !rowUniqueId || !rowData) {
|
|
2610
|
+
return defaultItems
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
const rowIdValue = getRowIdFromSettings(rowData, rowUniqueId)
|
|
2614
|
+
const hasUpdate = updateRef.current && Array.isArray(updateRef.current) &&
|
|
2615
|
+
updateRef.current.some(item => item.rowId === rowIdValue)
|
|
2616
|
+
|
|
2617
|
+
const customItems = []
|
|
2618
|
+
|
|
2619
|
+
if (hasUpdate) {
|
|
2620
|
+
customItems.push({
|
|
2621
|
+
name: 'Remove Row from Update Ref',
|
|
2622
|
+
icon: '<span class="ag-icon ag-icon-cancel" unselectable="on" role="presentation"></span>',
|
|
2623
|
+
action: () => {
|
|
2624
|
+
removeUpdateRefByRowId(updateRef, rowData, rowUniqueId)
|
|
2625
|
+
params.api.refreshCells({force: true})
|
|
2626
|
+
}
|
|
2627
|
+
})
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
customItems.push({
|
|
2631
|
+
name: 'Clone Update Ref (Backup)',
|
|
2632
|
+
icon: '<span class="ag-icon ag-icon-copy" unselectable="on" role="presentation"></span>',
|
|
2633
|
+
action: () => {
|
|
2634
|
+
cloneUpdateRefToOriginal(updateRef, originalRefData)
|
|
2635
|
+
params.api.refreshCells({force: true})
|
|
2636
|
+
},
|
|
2637
|
+
disabled: !updateRef.current || updateRef.current.length === 0
|
|
2638
|
+
})
|
|
2639
|
+
|
|
2640
|
+
customItems.push({
|
|
2641
|
+
name: 'Restore Update Ref (from Backup)',
|
|
2642
|
+
icon: '<span class="ag-icon ag-icon-undo" unselectable="on" role="presentation"></span>',
|
|
2643
|
+
action: () => {
|
|
2644
|
+
restoreUpdateRefFromOriginal(updateRef, originalRefData)
|
|
2645
|
+
params.api.refreshCells({force: true})
|
|
2646
|
+
},
|
|
2647
|
+
disabled: !originalRefData.current || originalRefData.current.length === 0
|
|
2648
|
+
})
|
|
2649
|
+
|
|
2650
|
+
customItems.push({
|
|
2651
|
+
name: 'Normalize Update Ref (Merge Duplicates)',
|
|
2652
|
+
icon: '<span class="ag-icon ag-icon-columns" unselectable="on" role="presentation"></span>',
|
|
2653
|
+
action: () => {
|
|
2654
|
+
normalizeUpdateRef(updateRef)
|
|
2655
|
+
params.api.refreshCells({force: true})
|
|
2656
|
+
},
|
|
2657
|
+
disabled: !updateRef.current || updateRef.current.length === 0
|
|
2658
|
+
})
|
|
2659
|
+
|
|
2660
|
+
customItems.push({
|
|
2661
|
+
name: 'Clear All Update Ref',
|
|
2662
|
+
icon: '<span class="ag-icon ag-icon-cancel" unselectable="on" role="presentation"></span>',
|
|
2663
|
+
action: () => {
|
|
2664
|
+
if (updateRef.current && Array.isArray(updateRef.current)) {
|
|
2665
|
+
const count = updateRef.current.length
|
|
2666
|
+
clearAllUpdateRef(updateRef)
|
|
2667
|
+
params.api.refreshCells({force: true})
|
|
2668
|
+
console.log(`Cleared ${count} rows from updateRef`)
|
|
2669
|
+
}
|
|
2670
|
+
},
|
|
2671
|
+
disabled: !updateRef.current || updateRef.current.length === 0
|
|
2672
|
+
})
|
|
2673
|
+
|
|
2674
|
+
if (customItems.length > 0) {
|
|
2675
|
+
return [
|
|
2676
|
+
...customItems,
|
|
2677
|
+
'separator',
|
|
2678
|
+
...defaultItems
|
|
2679
|
+
]
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
return defaultItems
|
|
2683
|
+
}
|
|
1988
2684
|
}}
|
|
1989
2685
|
rowSelection="multiple"
|
|
1990
2686
|
statusBar={{
|
|
@@ -2073,29 +2769,34 @@ const SGrid = props => {
|
|
|
2073
2769
|
scroll='body'
|
|
2074
2770
|
// onClose={() => handleToggleDialogs('CustomFilter')}
|
|
2075
2771
|
>
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
const
|
|
2093
|
-
|
|
2772
|
+
{openDialogs.CustomFilter && (
|
|
2773
|
+
<CustomFilterDialog
|
|
2774
|
+
handleToggleDialogs={handleToggleDialogs}
|
|
2775
|
+
Filter={(Filter?.LocalTfilter || []).filter(f => (f.isMainFilter !== true))}
|
|
2776
|
+
customFilterCode={Filter?.customFilterCode}
|
|
2777
|
+
handleFilterChange={handleFilterChange}
|
|
2778
|
+
className={builderData?.reportSource?.fullName}
|
|
2779
|
+
LocalFilter={false}
|
|
2780
|
+
|
|
2781
|
+
isViewer={router.pathname.includes('reportViewer')}
|
|
2782
|
+
selectTFilter={selectTFilter}
|
|
2783
|
+
selectParamsMeta={builderData?.selectionParams || []}
|
|
2784
|
+
selectParamsValues={selectParams}
|
|
2785
|
+
onSelectParamsSave={(list) => {
|
|
2786
|
+
// list is array of { index, value, type }
|
|
2787
|
+
setSelectParams(prev => {
|
|
2788
|
+
const arr = [...(prev || [])];
|
|
2789
|
+
(list || []).forEach(item => {
|
|
2790
|
+
const i = item.index;
|
|
2791
|
+
const type = item.type;
|
|
2792
|
+
const current = arr[i] || {id: i, PropertyType: type, value: null};
|
|
2793
|
+
arr[i] = {...current, PropertyType: current.PropertyType || type, value: item.value};
|
|
2794
|
+
});
|
|
2795
|
+
return arr;
|
|
2094
2796
|
});
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
/>
|
|
2797
|
+
}}
|
|
2798
|
+
/>
|
|
2799
|
+
)}
|
|
2099
2800
|
</Dialog>
|
|
2100
2801
|
|
|
2101
2802
|
|
|
@@ -2166,3 +2867,15 @@ function toNestedByUnderscore(input) {
|
|
|
2166
2867
|
return result;
|
|
2167
2868
|
}
|
|
2168
2869
|
|
|
2870
|
+
// Export helper functions for use in column configurations
|
|
2871
|
+
export {
|
|
2872
|
+
setUpdateRefValue,
|
|
2873
|
+
setUpdateRefRow,
|
|
2874
|
+
getUpdateRefValue,
|
|
2875
|
+
hasUpdateRefValue,
|
|
2876
|
+
clearUpdateRefRow,
|
|
2877
|
+
clearAllUpdateRef,
|
|
2878
|
+
getAllUpdates,
|
|
2879
|
+
removeUpdateRefByRowId
|
|
2880
|
+
}
|
|
2881
|
+
|