robobyte-front-builder 1.0.17 → 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.
@@ -1,4 +1,4 @@
1
- import BlankLayout from 'src/@core/layouts/BlankLayout'
1
+ import BlankLayout from "src/@core/layouts/BlankLayout";
2
2
  import {
3
3
  Autocomplete,
4
4
  Box,
@@ -16,68 +16,71 @@ import {
16
16
  Link,
17
17
  Chip,
18
18
  Button,
19
+ DialogActions,
20
+ MenuItem,
21
+ Select,
22
+ InputLabel,
23
+ FormControl,
24
+ DialogContent,
25
+ DialogTitle,
19
26
  Dialog,
20
27
  Tooltip,
21
- Accordion,
22
- AccordionSummary,
23
- AccordionDetails,
24
- FormControlLabel
25
- } from '@mui/material'
26
- import { useEffect, useRef, useState } from 'react'
27
- import { Endpoints, Services } from 'services/Endpoints'
28
+ Checkbox, Accordion, AccordionSummary, AccordionDetails, FormControlLabel,
29
+ } from '@mui/material';
30
+ import {useEffect, useRef, useState} from "react";
31
+ import {Endpoints, Services} from "services/Endpoints";
28
32
  import {
29
33
  AddBox,
34
+ ArrowBackOutlined,
30
35
  ArrowForwardSharp,
31
36
  AssessmentOutlined,
32
- Expand,
33
- FilterAlt,
37
+ BackupOutlined,
38
+ Expand, FilterAlt, FilterBAndW, FilterVintageOutlined,
39
+ KeyboardReturnOutlined,
34
40
  PreviewOutlined,
35
41
  SaveAsOutlined,
36
- SaveOutlined,
37
- Search,
38
- Visibility,
39
- VisibilityOff,
40
- Image as ImageIcon,
42
+ SaveOutlined, Search, Visibility, VisibilityOff, Image as ImageIcon,
41
43
  CloudDownloadOutlined,
42
44
  CloudUploadOutlined
43
- } from '@mui/icons-material'
44
- import dayjs from 'dayjs'
45
- import { BackspaceOutline, CheckCircleOutline, DeleteOutline, Filter, PencilOutline } from 'mdi-material-ui'
46
- import SGrid from 'views/genericTable/SGrid'
47
- import { Controller, useForm } from 'react-hook-form'
48
- import ComputedTextEditor from 'views/genericTable/RegexTextEditor'
49
- import ExpressionEditor from 'views/genericTable/RegexTextEditor'
50
- import BuilderExpressionParams from 'views/genericTable/BuilderExpressionParams'
51
- import handleChange from 'services/helper/handleChange'
52
- import DynamicValueList from 'views/genericTable/BuilderExpressionParams'
53
- import SqlEditor from 'views/genericTable/QueryEditor'
54
- import Switch from '@mui/material/Switch'
55
- import { EditorState } from 'draft-js'
56
- import PermissionsSubjects from 'src/configs/Permissions/PermissionsSubjects.json'
57
- import ReportBuilderSaveForm from 'views/genericTable/ReportBuilderSaveDialog'
58
- import { useRouter } from 'next/router'
59
- import CustomFilterDialog from 'views/customFilter/CustomFilterDialog'
60
-
61
- import RoutingSettingDialog from 'views/genericTable/RoutingSettingDialog'
62
- import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'
63
- import FixedFilterDialog from 'views/genericTable/FixedFilterDialog'
64
- import fetchReportData from 'services/reportData/fetchReportData'
45
+ } from "@mui/icons-material";
46
+ import dayjs from 'dayjs';
47
+ import {BackspaceOutline, CheckCircleOutline, DeleteOutline, Filter, PencilOutline} from "mdi-material-ui";
48
+ import SGrid from "views/genericTable/SGrid";
49
+ import {Controller, useForm} from "react-hook-form";
50
+ import ComputedTextEditor from "views/genericTable/RegexTextEditor";
51
+ import ExpressionEditor from "views/genericTable/RegexTextEditor";
52
+ import BuilderExpressionParams from "views/genericTable/BuilderExpressionParams";
53
+ import handleChange from "services/helper/handleChange";
54
+ import DynamicValueList from "views/genericTable/BuilderExpressionParams";
55
+ import SqlEditor from "views/genericTable/QueryEditor";
56
+ import Switch from "@mui/material/Switch";
57
+ import {EditorState} from "draft-js";
58
+ import PermissionsSubjects from "src/configs/Permissions/PermissionsSubjects.json";
59
+ import ReportBuilderSaveForm from "views/genericTable/ReportBuilderSaveDialog";
60
+ import {useRouter} from "next/router";
61
+ import CustomFilterDialog from "views/customFilter/CustomFilterDialog";
62
+ import FixedFilterDialog from "views/customFilter/FixedFilterDialog";
63
+ import RoutingSettingDialog from "views/genericTable/RoutingSettingDialog";
64
+ import FormattingSettingsDialog from "views/genericTable/FormattingSettingsDialog";
65
+ import ColumnConfiguratorDialog from "views/genericTable/ColumnConfiguratorDialog";
66
+ import ReportSettingsDialog from "views/genericTable/ReportSettingsDialog";
67
+ import {DragDropContext, Droppable, Draggable} from 'react-beautiful-dnd';
65
68
 
66
69
  const ReportBuilder = () => {
67
- const [models, setModels] = useState([])
68
- const [modelFields, setModelFields] = useState([])
69
- const [searchQuery, setSearchQuery] = useState('')
70
- const [selectedCollection, setSelectedCollection] = useState()
71
- const [editingHeaderPath, setEditingHeaderPath] = useState(null)
72
- const [isLoading, setIsLoading] = useState(false)
70
+ const [models, setModels] = useState([]);
71
+ const [modelFields, setModelFields] = useState([]);
72
+ const [searchQuery, setSearchQuery] = useState('');
73
+ const [selectedCollection, setSelectedCollection] = useState();
74
+ const [editingHeaderPath, setEditingHeaderPath] = useState(null);
75
+ const [isLoading, setIsLoading] = useState(false);
73
76
  const router = useRouter()
74
- const { id } = router.query
77
+ const {id} = router.query
75
78
  const [builderMetadata, setBuilderMetadata] = useState({
76
79
  id: 0,
77
80
  name: '',
78
81
  description: '',
79
- isNew: true
80
- })
82
+ isNew: true,
83
+ });
81
84
  const [builderModel, setBuilderModel] = useState({
82
85
  reportSource: null,
83
86
  selectedFields: [],
@@ -85,14 +88,17 @@ const ReportBuilder = () => {
85
88
  isRaw: false,
86
89
  filter: {
87
90
  Tfilter: [],
88
- LocalTfilter: []
91
+ LocalTfilter: [],
89
92
  },
90
93
  searchFilter: [],
91
94
  rawSql: '',
92
- routingSettings: []
93
- })
94
- const [breadcrumbs, setBreadcrumbs] = useState([])
95
- const [isPreview, setIsPreview] = useState(false)
95
+ routingSettings: [],
96
+ settings: {
97
+ rowUniqueId: []
98
+ }
99
+ });
100
+ const [breadcrumbs, setBreadcrumbs] = useState([]);
101
+ const [isPreview, setIsPreview] = useState(false);
96
102
  const [openDialogs, setOpenDialogs] = useState({
97
103
  expression: false,
98
104
  params: false,
@@ -100,12 +106,19 @@ const ReportBuilder = () => {
100
106
  filter: false,
101
107
  searchFilter: false,
102
108
  routing: false,
103
- fixedFilter: false
104
- })
105
- const [selectedRow, setSelectedRow] = useState(null)
109
+ fixedFilter: false,
110
+ formatting: false,
111
+ columnConfig: false,
112
+ settings: false
113
+ });
114
+ const [selectedRow, setSelectedRow] = useState(null);
115
+ const [columnConfigResult, setColumnConfigResult] = useState(null);
106
116
 
107
117
  // File input ref for uploading builder model JSON
108
- const fileInputRef = useRef(null)
118
+ const fileInputRef = useRef(null);
119
+
120
+ // UpdateRef for tracking field changes in the grid (array structure)
121
+ const updateRef = useRef([]);
109
122
 
110
123
  // Download current builderModel as JSON file
111
124
  const handleDownloadModel = () => {
@@ -113,121 +126,122 @@ const ReportBuilder = () => {
113
126
  const replacer = (key, value) => {
114
127
  // Convert Dayjs instances to Date so JSON.stringify uses ISO via toJSON
115
128
  if (value && typeof value === 'object') {
116
- if (dayjs.isDayjs(value)) return value.toDate()
117
- if (value instanceof Date) return value // keep as Date
129
+ if (dayjs.isDayjs(value)) return value.toDate();
130
+ if (value instanceof Date) return value; // keep as Date
118
131
  }
119
- return value
120
- }
121
- const json = JSON.stringify(builderModel, replacer, 2)
122
- const blob = new Blob([json], { type: 'application/json' })
123
- const url = URL.createObjectURL(blob)
124
- const a = document.createElement('a')
125
- const ts = new Date().toISOString().replace(/[:.]/g, '-')
126
- a.href = url
127
- a.download = `builderModel-${ts}.json`
128
- document.body.appendChild(a)
129
- a.click()
130
- document.body.removeChild(a)
131
- URL.revokeObjectURL(url)
132
+ return value;
133
+ };
134
+ const json = JSON.stringify(builderModel, replacer, 2);
135
+ const blob = new Blob([json], {type: 'application/json'});
136
+ const url = URL.createObjectURL(blob);
137
+ const a = document.createElement('a');
138
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
139
+ a.href = url;
140
+ a.download = `builderModel-${ts}.json`;
141
+ document.body.appendChild(a);
142
+ a.click();
143
+ document.body.removeChild(a);
144
+ URL.revokeObjectURL(url);
132
145
  } catch (err) {
133
- console.error('Failed to download builder model:', err)
146
+ console.error('Failed to download builder model:', err);
134
147
  }
135
- }
148
+ };
136
149
 
137
150
  // Trigger hidden file input
138
151
  const handleUploadClick = () => {
139
152
  try {
140
- fileInputRef.current?.click()
153
+ fileInputRef.current?.click();
141
154
  } catch (e) {
142
- console.error(e)
155
+ console.error(e);
143
156
  }
144
- }
157
+ };
145
158
 
146
159
  // Handle JSON file selection and load into builderModel
147
- const handleFileChange = e => {
148
- const file = e?.target?.files?.[0]
149
- if (!file) return
150
- const reader = new FileReader()
160
+ const handleFileChange = (e) => {
161
+ const file = e?.target?.files?.[0];
162
+ if (!file) return;
163
+ const reader = new FileReader();
151
164
  reader.onload = () => {
152
165
  try {
153
- const text = reader.result || ''
154
- const obj = JSON.parse(text)
166
+ const text = reader.result || '';
167
+ const obj = JSON.parse(text);
155
168
  // Basic validation: ensure it's an object
156
169
  if (obj && typeof obj === 'object') {
157
- setBuilderModel(obj)
170
+ setBuilderModel(obj);
158
171
  } else {
159
- console.error('Uploaded file is not a valid builder model object')
172
+ console.error('Uploaded file is not a valid builder model object');
160
173
  }
161
174
  } catch (err) {
162
- console.error('Failed to parse uploaded JSON:', err)
175
+ console.error('Failed to parse uploaded JSON:', err);
163
176
  } finally {
164
- if (e?.target) e.target.value = ''
177
+ if (e?.target) e.target.value = '';
165
178
  }
166
- }
167
- reader.readAsText(file)
168
- }
179
+ };
180
+ reader.readAsText(file);
181
+ };
169
182
 
170
- const { control, handleSubmit, reset } = useForm({
183
+ const {control, handleSubmit, reset} = useForm({
171
184
  defaultValues: {
172
185
  name: '',
173
186
  type: '',
174
- expression: ''
175
- }
176
- })
187
+ expression: '',
188
+ },
189
+ });
177
190
 
178
191
  const handleToggleDialogs = dialog => {
179
192
  handleChange(setOpenDialogs, dialog, !openDialogs[dialog])
180
193
  }
181
194
 
182
- const isInSearchFilter = fullPath => (builderModel.searchFilter ?? []).some(f => f.fullPath === fullPath)
195
+ const isInSearchFilter = (fullPath) =>
196
+ (builderModel.searchFilter ?? []).some(f => f.fullPath === fullPath);
183
197
 
184
- const toggleSearchFilterByFullPath = payload => {
198
+ const toggleSearchFilterByFullPath = (payload) => {
185
199
  setBuilderModel(prev => {
186
- const exists = (prev.searchFilter ?? []).some(f => f.fullPath === payload.fullPath)
200
+ const exists = (prev.searchFilter ?? []).some(f => f.fullPath === payload.fullPath);
187
201
  const updated = exists
188
202
  ? prev.searchFilter.filter(f => f.fullPath !== payload.fullPath)
189
- : [...(prev.searchFilter ?? []), payload]
203
+ : [...(prev.searchFilter ?? []), payload];
190
204
 
191
205
  return {
192
206
  ...prev,
193
207
  searchFilter: updated
194
- }
195
- })
196
- }
208
+ };
209
+ });
210
+ };
197
211
 
198
212
  const handleGetModels = async () => {
199
213
  try {
200
- setIsLoading(true)
201
- const response = await Services.PostService(Endpoints.ReportBuilder.Post.GetModels, false, {})
214
+ setIsLoading(true);
215
+ const response = await Services.PostService(Endpoints.ReportBuilder.Post.GetModels, false, {});
202
216
  if (response) {
203
- setModels(response)
217
+ setModels(response);
204
218
  }
205
219
  } catch (error) {
206
- console.error(error)
220
+ console.error(error);
207
221
  } finally {
208
- setIsLoading(false)
222
+ setIsLoading(false);
209
223
  }
210
- }
224
+ };
211
225
 
212
226
  const handleGetFields = async (modelFullName, friendlyName = null, propertyType, isPrevious) => {
213
227
  try {
214
- setIsLoading(true)
228
+ setIsLoading(true);
215
229
  const response = await Services.PostService(
216
230
  Endpoints.ReportBuilder.Post.GetModelFields,
217
231
  false,
218
232
  {},
219
- { modelFullName }
220
- )
233
+ {modelFullName}
234
+ );
221
235
  if (response) {
222
- setModelFields(response.data)
223
- const modelObject = models.find(m => m.fullName === modelFullName)
236
+ setModelFields(response.data);
237
+ const modelObject = models.find(m => m.fullName === modelFullName);
224
238
 
225
- console.log(modelObject)
239
+ console.log(modelObject);
226
240
  setBreadcrumbs(prev => {
227
- const exists = prev.find(b => b.fullName === modelFullName)
241
+ const exists = prev.find(b => b.fullName === modelFullName);
228
242
  if (exists && isPrevious) {
229
- const index = prev.findIndex(b => b.fullName === modelFullName)
230
- return prev.slice(0, index + 1)
243
+ const index = prev.findIndex(b => b.fullName === modelFullName);
244
+ return prev.slice(0, index + 1);
231
245
  }
232
246
  console.log(prev)
233
247
  return [
@@ -235,69 +249,73 @@ const ReportBuilder = () => {
235
249
  {
236
250
  fullName: modelFullName,
237
251
  friendlyName: friendlyName ?? modelObject?.friendlyName ?? modelFullName,
238
- propertyType: propertyType ?? modelObject?.propertyType ?? null
239
- }
240
- ]
241
- })
252
+ propertyType: propertyType ?? modelObject?.propertyType ?? null,
253
+ },
254
+ ];
255
+ });
242
256
  }
243
257
  } catch (error) {
244
- console.error(error)
258
+ console.error(error);
245
259
  } finally {
246
- setIsLoading(false)
260
+ setIsLoading(false);
247
261
  }
248
- }
262
+ };
249
263
 
250
264
  // 1) Trim a full field‐path for *leaf* fields:
251
- // • always drop the root segment
252
- // • if there are any collections, drop everything up through the *last* collection
265
+ // • always drop the root segment
266
+ // • if there are any collections, drop everything up through the *last* collection
253
267
  const getLeafTrimmedPath = (fullPath, collectionBreadcrumbs) => {
254
- const parts = fullPath.split('.')
255
- const afterRoot = parts.slice(1)
268
+ const parts = fullPath.split('.');
269
+ const afterRoot = parts.slice(1);
256
270
  if (!collectionBreadcrumbs.length) {
257
- return afterRoot.join('.')
271
+ return afterRoot.join('.');
258
272
  }
259
- const lastColl = collectionBreadcrumbs[collectionBreadcrumbs.length - 1].friendlyName
260
- const idx = afterRoot.lastIndexOf(lastColl)
261
- return idx >= 0 ? afterRoot.slice(idx + 1).join('.') : afterRoot.join('.')
262
- }
263
-
264
- // 2) Trim a *collection* node’s path:
265
- // • drop the root segment
266
- // then drop *exactly* the first collection (if present), keep everything else
273
+ const lastColl = collectionBreadcrumbs[collectionBreadcrumbs.length - 1].friendlyName;
274
+ const idx = afterRoot.lastIndexOf(lastColl);
275
+ return idx >= 0
276
+ ? afterRoot.slice(idx + 1).join('.')
277
+ : afterRoot.join('.');
278
+ };
279
+
280
+ // 2) Trim a *collection* node’s path:
281
+ // • drop the root segment
282
+ // • then drop *exactly* the first collection (if present), keep everything else
267
283
  const getCollectionTrimmedPath = (fullCollectionPath, collectionBreadcrumbs) => {
268
- const parts = fullCollectionPath.split('.')
269
- const afterRoot = parts.slice(1)
284
+ const parts = fullCollectionPath.split('.');
285
+ const afterRoot = parts.slice(1);
270
286
  if (!collectionBreadcrumbs.length) {
271
- return afterRoot.join('.')
287
+ return afterRoot.join('.');
272
288
  }
273
- const firstColl = collectionBreadcrumbs[0].friendlyName
289
+ const firstColl = collectionBreadcrumbs[0].friendlyName;
274
290
  // if the very first segment after root is our first collection, drop it
275
291
  if (afterRoot[0] === firstColl) {
276
- const rest = afterRoot.slice(1)
292
+ const rest = afterRoot.slice(1);
277
293
  // if that was the only segment, fall back to the collection name
278
- return rest.length ? rest.join('.') : firstColl
294
+ return rest.length ? rest.join('.') : firstColl;
279
295
  }
280
- return afterRoot.join('.')
281
- }
296
+ return afterRoot.join('.');
297
+ };
282
298
 
283
- const handleAddStaticField = field => {
299
+ const handleAddStaticField = (field) => {
284
300
  console.log(builderModel.selectedFields)
285
301
  // build fullPath & identify collection crumbs
286
- const fullPathParts = [...breadcrumbs.map(b => b.friendlyName), field.friendlyName]
287
- const fullPath = fullPathParts.join('.')
302
+ const fullPathParts = [
303
+ ...breadcrumbs.map(b => b.friendlyName),
304
+ field.friendlyName
305
+ ];
306
+ const fullPath = fullPathParts.join('.');
288
307
  const collectionBreadcrumbs = breadcrumbs
289
308
  .map((b, i) => ({
290
309
  ...b,
291
- fullPath: breadcrumbs
292
- .slice(0, i + 1)
310
+ fullPath: breadcrumbs.slice(0, i + 1)
293
311
  .map(x => x.friendlyName)
294
312
  .join('.')
295
313
  }))
296
- .filter(b => b.propertyType === 'Collection')
314
+ .filter(b => b.propertyType === 'Collection');
297
315
 
298
316
  // compute leaf path & header
299
- const trimmedPath = getLeafTrimmedPath(fullPath, collectionBreadcrumbs)
300
- const headerName = trimmedPath.replace(/\./g, '_')
317
+ const trimmedPath = getLeafTrimmedPath(fullPath, collectionBreadcrumbs);
318
+ const headerName = trimmedPath.replace(/\./g, '_');
301
319
  console.log(field)
302
320
  const newLeafField = {
303
321
  fullPath: fullPath,
@@ -309,19 +327,21 @@ const ReportBuilder = () => {
309
327
  propertyType: field.propertyType,
310
328
  enumName: field.enumName,
311
329
  hidden: false,
312
- image: false
313
- }
330
+ image: false,
331
+ };
314
332
 
315
333
  setBuilderModel(prev => {
316
- const clone = [...prev.selectedFields]
334
+ const clone = [...prev.selectedFields];
317
335
 
318
336
  const insertIntoCollectionsOnly = (nodes, crumbs, depth = 0) => {
319
- const crumb = crumbs[depth]
320
- const collFull = crumb.fullPath
321
- const collTrimmed = getCollectionTrimmedPath(collFull, crumbs)
322
- const collHeader = collTrimmed.replace(/\./g, '_')
323
-
324
- let node = nodes.find(n => n.propertyType === 'Collection' && n.path === collTrimmed)
337
+ const crumb = crumbs[depth];
338
+ const collFull = crumb.fullPath;
339
+ const collTrimmed = getCollectionTrimmedPath(collFull, crumbs);
340
+ const collHeader = collTrimmed.replace(/\./g, '_');
341
+
342
+ let node = nodes.find(n =>
343
+ n.propertyType === 'Collection' && n.path === collTrimmed
344
+ );
325
345
  if (!node) {
326
346
  node = {
327
347
  fullPath: fullPath,
@@ -329,33 +349,34 @@ const ReportBuilder = () => {
329
349
  headerName: collHeader,
330
350
  propertyType: 'Collection',
331
351
  children: []
332
- }
333
- nodes.push(node)
352
+ };
353
+ nodes.push(node);
334
354
  }
335
355
 
336
- const isLast = depth === crumbs.length - 1
356
+ const isLast = depth === crumbs.length - 1;
337
357
  if (isLast) {
338
358
  if (!node.children.some(f => f.path === trimmedPath)) {
339
- node.children.push(newLeafField)
359
+ node.children.push(newLeafField);
340
360
  }
341
361
  } else {
342
- insertIntoCollectionsOnly(node.children, crumbs, depth + 1)
362
+ insertIntoCollectionsOnly(node.children, crumbs, depth + 1);
343
363
  }
344
- }
364
+ };
345
365
 
346
366
  if (!collectionBreadcrumbs.length) {
347
367
  // no collections ⇒ flat insert
348
368
  if (!clone.some(f => f.path === trimmedPath)) {
349
- return { ...prev, selectedFields: [...clone, newLeafField] }
369
+ return {...prev, selectedFields: [...clone, newLeafField]};
350
370
  }
351
- return prev
371
+ return prev;
352
372
  }
353
373
 
354
374
  // otherwise build the nested collection node(s)
355
- insertIntoCollectionsOnly(clone, collectionBreadcrumbs)
356
- return { ...prev, selectedFields: clone }
357
- })
358
- }
375
+ insertIntoCollectionsOnly(clone, collectionBreadcrumbs);
376
+ return {...prev, selectedFields: clone};
377
+ });
378
+ };
379
+
359
380
 
360
381
  // (1) Helper: insert a node into the right collection, by `collectionPath`
361
382
  function insertIntoCollection(nodes, collectionPath, newField) {
@@ -366,19 +387,19 @@ const ReportBuilder = () => {
366
387
  return {
367
388
  ...node,
368
389
  children: [...(node.children || []), newField]
369
- }
390
+ };
370
391
  }
371
392
  // otherwise recurse
372
393
  return {
373
394
  ...node,
374
395
  children: insertIntoCollection(node.children || [], collectionPath, newField)
375
- }
396
+ };
376
397
  }
377
- return node
378
- })
398
+ return node;
399
+ });
379
400
  }
380
401
 
381
- // (2) Add computed field — either top‐level or inside the selectedCollection
402
+ // (2) Add computed field — either top‐level or inside the selectedCollection
382
403
  const handleAddComputedField = (expression, headerName, propertyType, title) => {
383
404
  const newField = {
384
405
  path: headerName,
@@ -388,221 +409,230 @@ const ReportBuilder = () => {
388
409
  expression,
389
410
  title,
390
411
  hidden: false,
391
- image: false
392
- }
412
+ image: false,
413
+ };
393
414
 
394
415
  setBuilderModel(prev => {
395
- const { selectedFields } = prev
416
+ const {selectedFields} = prev;
396
417
  // if no collection selected, append at root
397
418
  if (!selectedCollection) {
398
419
  return {
399
420
  ...prev,
400
421
  selectedFields: [...selectedFields, newField]
401
- }
422
+ };
402
423
  }
403
424
  // otherwise insert under the chosen collection
404
- const updated = insertIntoCollection(selectedFields, selectedCollection.path, newField)
405
- return { ...prev, selectedFields: updated }
406
- })
407
- setSelectedCollection(null)
408
- }
409
-
410
- // (3) Update computed field — likewise drill into the right spot
411
- const handleUpdateComputedField = (oldHeaderName, expression, headerName, propertyType, title) => {
425
+ const updated = insertIntoCollection(
426
+ selectedFields,
427
+ selectedCollection.path,
428
+ newField
429
+ );
430
+ return {...prev, selectedFields: updated};
431
+ });
432
+ setSelectedCollection(null);
433
+
434
+ };
435
+
436
+ // (3) Update computed field — likewise drill into the right spot
437
+ const handleUpdateComputedField = (
438
+ oldHeaderName,
439
+ expression,
440
+ headerName,
441
+ propertyType,
442
+ title
443
+ ) => {
412
444
  setBuilderModel(prev => {
413
445
  const updateNode = node => {
414
446
  if (node.type === 'computed' && node.headerName === oldHeaderName) {
415
- return { ...node, headerName, expression, propertyType, title, path: headerName }
447
+ return {...node, headerName, expression, propertyType, title, path: headerName};
416
448
  }
417
449
  if (node.propertyType === 'Collection') {
418
450
  return {
419
451
  ...node,
420
452
  children: (node.children || []).map(updateNode)
421
- }
453
+ };
422
454
  }
423
- return node
424
- }
455
+ return node;
456
+ };
425
457
 
426
458
  return {
427
459
  ...prev,
428
460
  selectedFields: prev.selectedFields.map(updateNode)
429
- }
430
- })
431
- setSelectedCollection(null)
432
- }
461
+ };
462
+ });
463
+ setSelectedCollection(null);
464
+
465
+ };
466
+
433
467
 
434
468
  function removeFieldByPath(fields, pathToRemove) {
435
469
  return fields.reduce((acc, f) => {
436
470
  if (f.propertyType === 'Collection') {
437
471
  // first, recurse into its children
438
- const filteredChildren = removeFieldByPath(f.children || [], pathToRemove)
472
+ const filteredChildren = removeFieldByPath(f.children || [], pathToRemove);
439
473
  // only keep this collection if it still has children
440
474
  if (filteredChildren.length > 0) {
441
- acc.push({ ...f, children: filteredChildren })
475
+ acc.push({...f, children: filteredChildren});
442
476
  }
443
477
  } else {
444
478
  // leaf field: only keep it if paths don't match
445
479
  if (f.path !== pathToRemove) {
446
- acc.push(f)
480
+ acc.push(f);
447
481
  }
448
482
  }
449
- return acc
450
- }, [])
483
+ return acc;
484
+ }, []);
451
485
  }
452
486
 
453
487
  function handleFilterChange(field, value) {
454
- console.log(field, value)
488
+ console.log(field, value);
455
489
  setBuilderModel(prev => ({
456
490
  ...prev,
457
491
  filter: {
458
492
  ...prev.filter,
459
- [field]: value // <-- computed key on the nested object
460
- }
461
- }))
493
+ [field]: value, // <-- computed key on the nested object
494
+ },
495
+ }));
462
496
  }
463
497
 
464
- const handleDragEnd = result => {
465
- const { source, destination } = result || {}
466
- if (!destination) return
467
- if (source.droppableId !== destination.droppableId) return
468
- const droppableId = source.droppableId
498
+
499
+ const handleDragEnd = (result) => {
500
+ const {source, destination} = result || {};
501
+ if (!destination) return;
502
+ if (source.droppableId !== destination.droppableId) return;
503
+ const droppableId = source.droppableId;
469
504
 
470
505
  setBuilderModel(prev => {
471
506
  const reorderLeaves = (arr, from, to) => {
472
- const leaves = arr.filter(n => !n.children)
473
- const collections = arr.filter(n => n.children)
474
- if (from < 0 || to < 0 || from >= leaves.length || to >= leaves.length) return arr
475
- const leafMap = Object.fromEntries(leaves.map(n => [n.path, n]))
476
- const order = leaves.map(n => n.path)
477
- const [moved] = order.splice(from, 1)
478
- order.splice(to, 0, moved)
479
- const orderQueue = order.slice()
507
+ const leaves = arr.filter(n => !n.children);
508
+ const collections = arr.filter(n => n.children);
509
+ if (from < 0 || to < 0 || from >= leaves.length || to >= leaves.length) return arr;
510
+ const leafMap = Object.fromEntries(leaves.map(n => [n.path, n]));
511
+ const order = leaves.map(n => n.path);
512
+ const [moved] = order.splice(from, 1);
513
+ order.splice(to, 0, moved);
514
+ const orderQueue = order.slice();
480
515
  // rebuild original array: keep collections in place, leaves in new order
481
516
  return arr.map(node => {
482
- if (node.children) return node
483
- const nextPath = orderQueue.shift()
484
- return leafMap[nextPath] || node
485
- })
486
- }
487
-
488
- const updateCollections = nodes =>
489
- nodes.map(n => {
490
- if (n.propertyType === 'Collection') {
491
- if (n.path === droppableId) {
492
- const newChildren = reorderLeaves(n.children || [], source.index, destination.index)
493
- return { ...n, children: newChildren }
494
- }
495
- return { ...n, children: updateCollections(n.children || []) }
517
+ if (node.children) return node;
518
+ const nextPath = orderQueue.shift();
519
+ return leafMap[nextPath] || node;
520
+ });
521
+ };
522
+
523
+ const updateCollections = (nodes) => nodes.map(n => {
524
+ if (n.propertyType === 'Collection') {
525
+ if (n.path === droppableId) {
526
+ const newChildren = reorderLeaves(n.children || [], source.index, destination.index);
527
+ return {...n, children: newChildren};
496
528
  }
497
- return n
498
- })
529
+ return {...n, children: updateCollections(n.children || [])};
530
+ }
531
+ return n;
532
+ });
499
533
 
500
534
  if (droppableId === 'root') {
501
- return { ...prev, selectedFields: reorderLeaves(prev.selectedFields, source.index, destination.index) }
535
+ return {...prev, selectedFields: reorderLeaves(prev.selectedFields, source.index, destination.index)};
502
536
  } else {
503
- return { ...prev, selectedFields: updateCollections(prev.selectedFields) }
537
+ return {...prev, selectedFields: updateCollections(prev.selectedFields)};
504
538
  }
505
- })
506
- }
539
+ });
540
+ };
507
541
 
508
542
  const renderSelectedFields = (nodes, level = 0, droppableId = 'root') => {
509
543
  // Count leaf-only index for Draggable indices
510
- let leafIndexCounter = 0
544
+ let leafIndexCounter = 0;
511
545
  return (
512
546
  <Droppable droppableId={droppableId}>
513
- {provided => (
514
- <List dense disablePadding sx={{ pl: level * 2 }} ref={provided.innerRef} {...provided.droppableProps}>
515
- {nodes.map(item =>
547
+ {(provided) => (
548
+ <List dense disablePadding sx={{pl: level * 2}} ref={provided.innerRef} {...provided.droppableProps}>
549
+ {nodes.map((item) => (
516
550
  item.children ? (
517
- <Accordion key={item.headerName} disableGutters sx={{ mb: 1 }}>
518
- <AccordionSummary expandIcon={<Expand />}>
551
+ <Accordion key={item.headerName} disableGutters sx={{mb: 1}}>
552
+ <AccordionSummary expandIcon={<Expand/>}>
519
553
  <Box
520
554
  sx={{
521
555
  display: 'flex',
522
556
  alignItems: 'center',
523
557
  justifyContent: 'space-between',
524
- width: '100%'
558
+ width: '100%',
525
559
  }}
526
560
  >
527
- <Typography variant='subtitle2' fontWeight='bold'>
561
+ <Typography variant="subtitle2" fontWeight="bold">
528
562
  {item.headerName ?? item.path}
529
563
  </Typography>
530
564
  <IconButton
531
- size='small'
532
- onClick={e => {
533
- e.stopPropagation()
534
- setSelectedCollection(item)
535
- setSelectedRow(null)
536
- setOpenDialogs(prev => ({ ...prev, expression: true }))
565
+ size="small"
566
+ onClick={(e) => {
567
+ e.stopPropagation();
568
+ setSelectedCollection(item);
569
+ setSelectedRow(null);
570
+ setOpenDialogs(prev => ({...prev, expression: true}));
537
571
  }}
538
- title='Add computed field to this collection'
572
+ title="Add computed field to this collection"
539
573
  >
540
- <AddBox fontSize='small' />
574
+ <AddBox fontSize="small"/>
541
575
  </IconButton>
542
576
  </Box>
543
577
  </AccordionSummary>
544
- <AccordionDetails>{renderSelectedFields(item.children, level + 1, item.path)}</AccordionDetails>
578
+ <AccordionDetails>
579
+ {renderSelectedFields(item.children, level + 1, item.path)}
580
+ </AccordionDetails>
545
581
  </Accordion>
546
582
  ) : (
547
- <Draggable
548
- key={`${droppableId}::${item.path}`}
549
- draggableId={`${droppableId}::${item.path}`}
550
- index={leafIndexCounter++}
551
- >
552
- {providedDrag => (
583
+ <Draggable key={`${droppableId}::${item.path}`} draggableId={`${droppableId}::${item.path}`}
584
+ index={leafIndexCounter++}>
585
+ {(providedDrag) => (
553
586
  <ListItem
554
- ref={providedDrag.innerRef}
555
- {...providedDrag.draggableProps}
556
- {...providedDrag.dragHandleProps}
557
- divider
558
- >
587
+ ref={providedDrag.innerRef} {...providedDrag.draggableProps} {...providedDrag.dragHandleProps}
588
+ divider>
559
589
  <ListItemText
560
590
  primary={
561
591
  editingHeaderPath === item.path ? (
562
592
  <TextField
563
- size='small'
593
+ size="small"
564
594
  autoFocus
565
- variant='standard'
595
+ variant="standard"
566
596
  value={item.title}
567
- onChange={e => {
568
- const newHeaderName = e.target.value
597
+ onChange={(e) => {
598
+ const newHeaderName = e.target.value;
569
599
 
570
600
  setBuilderModel(prev => {
571
- const updateNode = node => {
601
+ const updateNode = (node) => {
572
602
  if (node.path === item.path && !node.children) {
573
- return { ...node, title: newHeaderName }
603
+ return {...node, title: newHeaderName};
574
604
  }
575
605
  if (node.propertyType === 'Collection') {
576
- return { ...node, children: (node.children || []).map(updateNode) }
606
+ return {...node, children: (node.children || []).map(updateNode)};
577
607
  }
578
- return node
579
- }
608
+ return node;
609
+ };
580
610
 
581
611
  return {
582
612
  ...prev,
583
613
  selectedFields: prev.selectedFields.map(updateNode)
584
- }
585
- })
614
+ };
615
+ });
586
616
  }}
587
617
  onBlur={() => setEditingHeaderPath(null)}
588
- onKeyDown={e => {
618
+ onKeyDown={(e) => {
589
619
  if (e.key === 'Enter' || e.key === 'Escape') {
590
- setEditingHeaderPath(null)
620
+ setEditingHeaderPath(null);
591
621
  }
592
622
  }}
593
623
  inputProps={{
594
624
  style: {
595
625
  fontWeight: 'bold',
596
- fontSize: '0.875rem'
626
+ fontSize: '0.875rem',
597
627
  }
598
628
  }}
599
629
  />
600
630
  ) : (
601
631
  <Typography
602
- variant='subtitle2'
603
- fontWeight='bold'
632
+ variant="subtitle2"
633
+ fontWeight="bold"
604
634
  onDoubleClick={() => setEditingHeaderPath(item.path)}
605
- sx={{ cursor: 'pointer' }}
635
+ sx={{cursor: 'pointer'}}
606
636
  >
607
637
  {item.title ?? item.headerName}
608
638
  </Typography>
@@ -610,153 +640,175 @@ const ReportBuilder = () => {
610
640
  }
611
641
  secondary={item.path}
612
642
  />
613
- <Box sx={{ display: 'flex', gap: 1 }}>
643
+ <Box sx={{display: 'flex', gap: 1}}>
614
644
  {item.type === 'computed' && (
615
645
  <IconButton
616
- size='small'
646
+ size="small"
617
647
  onClick={() => {
618
- setSelectedRow(item)
619
- setOpenDialogs(prev => ({ ...prev, expression: true }))
648
+ setSelectedRow(item);
649
+ setOpenDialogs(prev => ({...prev, expression: true}));
620
650
  }}
621
- title='Edit computed field'
651
+ title="Edit computed field"
622
652
  >
623
- <PencilOutline fontSize='small' />
653
+ <PencilOutline fontSize="small"/>
624
654
  </IconButton>
625
655
  )}
626
656
 
627
- {(item?.field?.propertyType === 'String' ||
628
- item?.field?.propertyType === 'Int' ||
629
- item?.field?.propertyType === 'Guid') &&
630
- item?.type === 'static' && (
631
- <IconButton
632
- size='small'
633
- onClick={() => {
634
- console.log(item, level)
635
- toggleSearchFilterByFullPath({
636
- fullPath: item.fullPath,
637
- path: item.path || item.headerName || item.fullPath,
638
- propertyType: item.propertyType,
639
- isWithinCollection: level !== 0
640
- })
641
- }}
642
- title={
643
- isInSearchFilter(item.fullPath) ? 'Remove from search filter' : 'Add to search filter'
644
- }
645
- sx={{
646
- color: isInSearchFilter(item.fullPath) ? 'primary.main' : 'text.secondary',
647
- '&:hover': {
648
- backgroundColor: 'transparent',
649
- color: isInSearchFilter(item.fullPath) ? 'primary.main' : 'info.main'
650
- }
651
- }}
652
- >
653
- <Search fontSize='small' />
654
- </IconButton>
655
- )}
657
+ {((item?.field?.propertyType === 'String' || item?.field?.propertyType === 'Int' || item?.field?.propertyType === 'Guid') && item?.type === 'static') && (
658
+ <IconButton
659
+ size="small"
660
+ onClick={() => {
661
+ console.log(item, level)
662
+ toggleSearchFilterByFullPath({
663
+ fullPath: item.fullPath,
664
+ path: item.path || item.headerName || item.fullPath,
665
+ propertyType: item.propertyType,
666
+ isWithinCollection: level !== 0
667
+ });
668
+ }}
669
+ title={
670
+ isInSearchFilter(item.fullPath)
671
+ ? 'Remove from search filter'
672
+ : 'Add to search filter'
673
+ }
674
+ sx={{
675
+ color: isInSearchFilter(item.fullPath) ? 'primary.main' : 'text.secondary',
676
+ '&:hover': {
677
+ backgroundColor: 'transparent',
678
+ color: isInSearchFilter(item.fullPath) ? 'primary.main' : 'info.main',
679
+ },
680
+ }}
681
+ >
682
+ <Search fontSize="small"/>
683
+ </IconButton>
684
+ )}
656
685
  <IconButton
657
- size='small'
686
+ size="small"
658
687
  onClick={() => {
659
688
  setBuilderModel(prev => {
660
- const toggleHidden = node => {
689
+ const toggleHidden = (node) => {
661
690
  if (node.propertyType === 'Collection') {
662
691
  return {
663
692
  ...node,
664
693
  children: (node.children || []).map(toggleHidden)
665
- }
694
+ };
666
695
  }
667
696
  if (node.path === item.path) {
668
- return { ...node, hidden: !node.hidden }
697
+ return {...node, hidden: !node.hidden};
669
698
  }
670
- return node
671
- }
699
+ return node;
700
+ };
672
701
  return {
673
702
  ...prev,
674
703
  selectedFields: prev.selectedFields.map(toggleHidden)
675
- }
676
- })
704
+ };
705
+ });
677
706
  }}
678
- title={item.hidden ? 'Show column' : 'Hide column'}
707
+ title={item.hidden ? "Show column" : "Hide column"}
679
708
  sx={{
680
709
  color: item.hidden ? 'warning.main' : 'text.secondary',
681
710
  '&:hover': {
682
711
  backgroundColor: 'transparent',
683
- color: item.hidden ? 'warning.main' : 'info.main'
684
- }
712
+ color: item.hidden ? 'warning.main' : 'info.main',
713
+ },
685
714
  }}
686
715
  >
687
- {item.hidden ? <VisibilityOff fontSize='small' /> : <Visibility fontSize='small' />}
716
+ {item.hidden ? <VisibilityOff fontSize="small"/> : <Visibility fontSize="small"/>}
688
717
  </IconButton>
689
718
  <IconButton
690
- size='small'
719
+ size="small"
691
720
  onClick={() => {
692
721
  setBuilderModel(prev => {
693
- const toggleImage = node => {
722
+ const toggleImage = (node) => {
694
723
  if (node.propertyType === 'Collection') {
695
- return { ...node, children: (node.children || []).map(toggleImage) }
724
+ return {...node, children: (node.children || []).map(toggleImage)};
696
725
  }
697
726
  if (node.path === item.path) {
698
- return { ...node, image: !node.image }
727
+ return {...node, image: !node.image};
699
728
  }
700
- return node
701
- }
729
+ return node;
730
+ };
702
731
  return {
703
732
  ...prev,
704
733
  selectedFields: prev.selectedFields.map(toggleImage)
705
- }
706
- })
734
+ };
735
+ });
707
736
  }}
708
- title={item.image ? 'Unset image' : 'Render as image'}
737
+ title={item.image ? "Unset image" : "Render as image"}
709
738
  sx={{
710
739
  color: item.image ? 'success.main' : 'text.secondary',
711
740
  '&:hover': {
712
741
  backgroundColor: 'transparent',
713
- color: item.image ? 'success.main' : 'info.main'
714
- }
742
+ color: item.image ? 'success.main' : 'info.main',
743
+ },
744
+ }}
745
+ >
746
+ <ImageIcon fontSize="small"/>
747
+ </IconButton>
748
+ <IconButton
749
+ size="small"
750
+ onClick={() => {
751
+ setSelectedRow(item);
752
+ setOpenDialogs(prev => ({...prev, formatting: true}));
753
+ }}
754
+ title="Formatting Settings"
755
+ sx={{
756
+ color: item.formatting ? 'primary.main' : 'text.secondary',
757
+ '&:hover': {
758
+ backgroundColor: 'transparent',
759
+ color: item.formatting ? 'primary.main' : 'info.main',
760
+ },
715
761
  }}
716
762
  >
717
- <ImageIcon fontSize='small' />
763
+ <FilterVintageOutlined fontSize="small"/>
718
764
  </IconButton>
719
765
  <IconButton
720
- size='small'
766
+ size="small"
721
767
  onClick={() =>
722
768
  setBuilderModel(prev => ({
723
769
  ...prev,
724
770
  selectedFields: removeFieldByPath(prev.selectedFields, item.path)
725
771
  }))
726
772
  }
727
- title='Remove field'
773
+ title="Remove field"
728
774
  >
729
- <DeleteOutline fontSize='small' />
775
+ <DeleteOutline fontSize="small"/>
730
776
  </IconButton>
731
777
  </Box>
732
778
  </ListItem>
733
779
  )}
734
780
  </Draggable>
735
781
  )
736
- )}
782
+ ))}
737
783
  {provided.placeholder}
738
784
  </List>
739
785
  )}
740
786
  </Droppable>
741
- )
742
- }
787
+ );
788
+ };
743
789
 
744
790
  const isFullPathSelected = (nodes, targetFullPath) => {
745
791
  for (const node of nodes) {
746
- if (node.fullPath === targetFullPath) return true
792
+ if (node.fullPath === targetFullPath) return true;
747
793
  if (node.children && isFullPathSelected(node.children, targetFullPath)) {
748
- return true
794
+ return true;
749
795
  }
750
796
  }
751
- return false
752
- }
797
+ return false;
798
+ };
753
799
 
754
- const handleGetReport = async id => {
800
+
801
+ const handleGetReport = async (id) => {
755
802
  try {
756
- setIsLoading(true)
757
- const response = await Services.PostService(Endpoints.ReportBuilder.Post.GetReportDetails, false, {}, { id: id })
803
+ setIsLoading(true);
804
+ const response = await Services.PostService(
805
+ Endpoints.ReportBuilder.Post.GetReportDetails,
806
+ false,
807
+ {},
808
+ {id: id}
809
+ )
758
810
  if (response) {
759
- setBuilderModel(JSON.parse(response.data.value))
811
+ setBuilderModel(JSON.parse(response.data.value));
760
812
  setBuilderMetadata({
761
813
  id: response.data.id,
762
814
  name: response.data.name,
@@ -769,22 +821,24 @@ const ReportBuilder = () => {
769
821
  } catch (error) {
770
822
  console.error(error)
771
823
  } finally {
772
- setIsLoading(false)
824
+ setIsLoading(false);
773
825
  }
774
826
  }
775
827
 
776
828
 
777
829
  useEffect(() => {
778
- handleGetModels()
779
- }, [])
830
+ handleGetModels();
831
+ }, []);
780
832
  useEffect(() => {
781
833
  if (id != null) {
782
- handleGetReport(id)
834
+
835
+ handleGetReport(id);
783
836
  }
784
- }, [id])
837
+ }, [id]);
838
+
785
839
 
786
840
  return (
787
- <div dir='ltr' style={{ direction: 'ltr', textAlign: 'left', width: '100%', height: '100%' }}>
841
+ <div dir="ltr" style={{direction: 'ltr', textAlign: 'left', width: '100%', height: '100%'}}>
788
842
  <style>
789
843
  {`
790
844
  .MuiListItemText-primary,
@@ -796,8 +850,8 @@ const ReportBuilder = () => {
796
850
  `}
797
851
  </style>
798
852
 
799
- <CssBaseline />
800
- <Box sx={{ display: 'flex', height: '100vh', overflow: 'hidden' }}>
853
+ <CssBaseline/>
854
+ <Box sx={{display: 'flex', height: '100vh', overflow: 'hidden'}}>
801
855
  <Paper
802
856
  elevation={3}
803
857
  sx={{
@@ -807,84 +861,86 @@ const ReportBuilder = () => {
807
861
  borderRight: '1px solid #eee',
808
862
  borderRadius: '0px',
809
863
  p: 2,
810
- bgcolor: 'white'
864
+ bgcolor: 'white',
811
865
  }}
812
866
  >
813
- <Box display='flex' alignItems='center' gap={1} justifyContent='space-between'>
814
- <Box display='flex' alignItems='center' gap={1}>
815
- <Typography variant='h6'>Report Configuration</Typography>
816
- <Chip variant={'filled'} label='Beta' color='warning' size='small' />
867
+ <Box display="flex" alignItems="center" gap={1} justifyContent="space-between">
868
+ <Box display="flex" alignItems="center" gap={1}>
869
+ <Typography variant="h6">
870
+ Report Configuration
871
+ </Typography>
872
+ <Chip variant={'filled'} label="Beta" color="warning" size="small"/>
817
873
  </Box>
818
874
  <Box>
819
- <IconButton
820
- onClick={() => {
821
- setBuilderMetadata(preValue => ({
822
- ...preValue,
823
- isNew: id == null
824
- }))
825
- handleToggleDialogs('save')
826
- }}
827
- >
828
- <SaveAsOutlined color={'primary'} />
875
+ <IconButton onClick={() => {
876
+ setBuilderMetadata(preValue => ({
877
+ ...preValue,
878
+ isNew: (id == null),
879
+ }))
880
+ handleToggleDialogs('save')
881
+ }}>
882
+ <SaveAsOutlined color={'primary'}/>
829
883
  </IconButton>
830
- {builderMetadata.id !== 0 && (
831
- <IconButton
832
- onClick={() => {
833
- setBuilderMetadata(preValue => ({
834
- ...preValue,
835
- isNew: false
836
- }))
837
- handleToggleDialogs('save')
838
- }}
839
- >
840
- <SaveOutlined color={'primary'} />
841
- </IconButton>
842
- )}
843
-
844
- <IconButton
845
- onClick={() => {
846
- router.push('/reportModule/reportBuilder/reports')
847
- }}
848
- >
849
- <AssessmentOutlined color={'primary'} />
884
+ {builderMetadata.id !== 0 && <IconButton onClick={() => {
885
+ setBuilderMetadata(preValue => ({
886
+ ...preValue,
887
+ isNew: false,
888
+ }))
889
+ handleToggleDialogs('save')
890
+ }}><SaveOutlined color={'primary'}/></IconButton>}
891
+
892
+ <IconButton onClick={() => {
893
+ router.push('reportBuilder/reports')
894
+ }}>
895
+ <AssessmentOutlined color={'primary'}/>
850
896
  </IconButton>
851
897
  </Box>
898
+
852
899
  </Box>
853
900
  <Autocomplete
854
- size='small'
901
+ size="small"
855
902
  value={builderModel.reportSource}
856
903
  options={models}
857
904
  getOptionLabel={option => option.tableName || ''}
858
905
  onChange={(e, value) => {
859
906
  if (value) {
860
- setBuilderModel(preValue => ({ ...preValue, reportSource: value }))
861
- setBreadcrumbs([])
862
- handleGetFields(value.fullName, value.friendlyName, value.propertyType)
907
+ setBuilderModel(preValue => ({...preValue, reportSource: value}))
908
+ setBreadcrumbs([]);
909
+ handleGetFields(value.fullName, value.friendlyName, value.propertyType);
863
910
  }
864
911
  }}
865
912
  renderOption={(props, option) => (
866
- <Box sx={{ direction: 'rtl' }} component='li' {...props}>
913
+ <Box sx={{direction: 'rtl'}} component="li" {...props}>
867
914
  <Box>
868
- <Typography sx={{ color: 'primary.dark' }}>{option.tableName}</Typography>
869
- <Typography variant='body2' color='text.secondary'>
915
+ <Typography sx={{color: 'primary.dark'}}>
916
+ {option.tableName}
917
+ </Typography>
918
+ <Typography variant="body2" color="text.secondary">
870
919
  {option.friendlyName}
871
920
  </Typography>
872
921
  </Box>
873
922
  </Box>
874
923
  )}
875
- renderInput={params => <TextField {...params} variant='outlined' label='Select Source Model' fullWidth />}
876
- sx={{ mb: 2, mt: 2 }}
924
+ renderInput={(params) => (
925
+ <TextField
926
+ {...params}
927
+ variant="outlined"
928
+ label="Select Source Model"
929
+ fullWidth
930
+ />
931
+ )}
932
+ sx={{mb: 2, mt: 2}}
877
933
  />
878
934
 
879
935
  {breadcrumbs.length > 0 && (
880
- <Breadcrumbs separator='' sx={{ mb: 2 }} aria-label='breadcrumb'>
936
+ <Breadcrumbs separator="" sx={{mb: 2}} aria-label="breadcrumb">
881
937
  {breadcrumbs.map((crumb, i) => (
882
938
  <Link
883
939
  key={crumb.fullName}
884
- underline='hover'
940
+ underline="hover"
885
941
  color={i === breadcrumbs.length - 1 ? 'primary.main' : 'inherit'}
886
942
  onClick={() => handleGetFields(crumb.fullName, crumb.friendlyName, crumb.propertyType, true)}
887
- sx={{ cursor: 'pointer', fontSize: '0.875rem' }}
943
+ sx={{cursor: 'pointer', fontSize: '0.875rem'}}
888
944
  >
889
945
  {crumb.friendlyName}
890
946
  </Link>
@@ -892,45 +948,51 @@ const ReportBuilder = () => {
892
948
  </Breadcrumbs>
893
949
  )}
894
950
 
895
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mb: 2 }}>
896
- <Typography variant='subtitle1' gutterBottom>
951
+ <Box sx={{display: 'flex', flexDirection: 'column', gap: 1, mb: 2}}>
952
+ <Typography variant="subtitle1" gutterBottom>
897
953
  Available Fields
898
954
  </Typography>
899
955
  <TextField
900
- variant='outlined'
901
- size='small'
902
- placeholder='Search fields...'
956
+ variant="outlined"
957
+ size="small"
958
+ placeholder="Search fields..."
903
959
  fullWidth
904
960
  value={searchQuery}
905
- onChange={e => setSearchQuery(e.target.value)}
961
+ onChange={(e) => setSearchQuery(e.target.value)}
906
962
  />
907
963
  </Box>
908
964
 
909
- <Box sx={{ flexGrow: 1, overflowY: 'auto' }}>
965
+ <Box sx={{flexGrow: 1, overflowY: 'auto'}}>
910
966
  <List dense disablePadding>
911
967
  {modelFields
912
- .filter(
913
- field =>
914
- field.friendlyName?.toLowerCase().includes(searchQuery.toLowerCase()) ||
915
- field.path?.toLowerCase().includes(searchQuery.toLowerCase())
968
+ .filter(field =>
969
+ field.friendlyName?.toLowerCase().includes(searchQuery.toLowerCase()) ||
970
+ field.path?.toLowerCase().includes(searchQuery.toLowerCase())
916
971
  )
917
972
  .map((field, index) => {
973
+
918
974
  // Now check against your selectedFields by their stored 'path'
919
- const fullPath = [...breadcrumbs.map(b => b.friendlyName), field.friendlyName].join('.')
975
+ const fullPath = [
976
+ ...breadcrumbs.map(b => b.friendlyName),
977
+ field.friendlyName
978
+ ].join('.');
920
979
 
921
980
  // now check *that*:
922
- const isSelected = isFullPathSelected(builderModel.selectedFields, fullPath)
981
+ const isSelected = isFullPathSelected(
982
+ builderModel.selectedFields,
983
+ fullPath
984
+ );
923
985
  return (
924
986
  <ListItem key={index} divider>
925
987
  <ListItemText
926
988
  primary={
927
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
989
+ <Box sx={{display: 'flex', alignItems: 'center', gap: 1}}>
928
990
  {field.friendlyName}
929
991
  {isSelected && (
930
992
  <CheckCircleOutline
931
- fontSize='small'
932
- color='success'
933
- titleAccess='Field already selected'
993
+ fontSize="small"
994
+ color="success"
995
+ titleAccess="Field already selected"
934
996
  />
935
997
  )}
936
998
  </Box>
@@ -940,7 +1002,7 @@ const ReportBuilder = () => {
940
1002
  sx: {
941
1003
  textAlign: 'left',
942
1004
  direction: 'ltr',
943
- display: 'block'
1005
+ display: 'block',
944
1006
  }
945
1007
  }}
946
1008
  secondaryTypographyProps={{
@@ -949,7 +1011,7 @@ const ReportBuilder = () => {
949
1011
  color: 'red',
950
1012
  textAlign: 'left',
951
1013
  direction: 'ltr',
952
- display: 'block'
1014
+ display: 'block',
953
1015
  }
954
1016
  }}
955
1017
  />
@@ -960,12 +1022,12 @@ const ReportBuilder = () => {
960
1022
  alignItems: 'center',
961
1023
  gap: 1,
962
1024
  ml: 1,
963
- direction: 'ltr'
1025
+ direction: 'ltr',
964
1026
  }}
965
1027
  >
966
1028
  {(field.propertyType === 'Class' || field.propertyType === 'Collection') && (
967
1029
  <IconButton
968
- size='small'
1030
+ size="small"
969
1031
  onClick={() => {
970
1032
  handleGetFields(field.modelClassName, field.friendlyName, field.propertyType)
971
1033
  setSearchQuery('')
@@ -974,90 +1036,98 @@ const ReportBuilder = () => {
974
1036
  color: 'text.secondary',
975
1037
  '&:hover': {
976
1038
  backgroundColor: 'transparent',
977
- color: 'success.main'
978
- }
1039
+ color: 'success.main',
1040
+ },
979
1041
  }}
980
- title='Navigate into nested fields'
1042
+ title="Navigate into nested fields"
981
1043
  >
982
- <ArrowForwardSharp fontSize='inherit' />
1044
+ <ArrowForwardSharp fontSize="inherit"/>
983
1045
  </IconButton>
984
1046
  )}
985
1047
 
1048
+
986
1049
  {field.propertyType !== 'Class' && field.propertyType !== 'Collection' && (
987
1050
  <IconButton
988
1051
  onClick={() => {
989
1052
  handleAddStaticField(field)
990
1053
  }}
991
- size='small'
1054
+ size="small"
992
1055
  sx={{
993
1056
  color: 'text.secondary',
994
1057
  '&:hover': {
995
1058
  backgroundColor: 'transparent',
996
- color: 'primary.main'
997
- }
1059
+ color: 'primary.main',
1060
+ },
998
1061
  }}
999
- title='Add field to report'
1062
+ title="Add field to report"
1000
1063
  >
1001
- <AddBox fontSize='inherit' />
1064
+ <AddBox fontSize="inherit"/>
1002
1065
  </IconButton>
1003
1066
  )}
1067
+
1004
1068
  </Box>
1005
1069
  </ListItem>
1006
- )
1070
+ );
1007
1071
  })}
1008
1072
  </List>
1073
+
1009
1074
  </Box>
1010
1075
 
1011
1076
  {isLoading && (
1012
- <Box sx={{ display: 'flex', justifyContent: 'center', mt: 2 }}>
1013
- <CircularProgress size={24} />
1077
+ <Box sx={{display: 'flex', justifyContent: 'center', mt: 2}}>
1078
+ <CircularProgress size={24}/>
1014
1079
  </Box>
1015
1080
  )}
1016
1081
  </Paper>
1017
1082
 
1018
- <Box sx={{ flexGrow: 1, p: 3, overflowY: 'auto' }}>
1083
+ <Box sx={{flexGrow: 1, p: 3, overflowY: 'auto'}}>
1084
+
1019
1085
  <Grid container spacing={2}>
1020
- <Box sx={{ flexGrow: 1, p: 3, overflowY: 'auto' }}>
1021
- <Box sx={{ mb: 3 }}>
1086
+ <Box sx={{flexGrow: 1, p: 3, overflowY: 'auto'}}>
1087
+ <Box sx={{mb: 3}}>
1022
1088
  {/* Top row: Title (left) and Preview icon (right) */}
1023
1089
  <Box
1024
1090
  sx={{
1025
1091
  display: 'flex',
1026
1092
  justifyContent: 'space-between',
1027
1093
  alignItems: 'center',
1028
- mb: 2
1094
+ mb: 2,
1029
1095
  }}
1030
1096
  >
1031
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
1032
- <Typography variant='h5' sx={{ fontWeight: 600 }}>
1097
+ <Box sx={{display: 'flex', alignItems: 'center', gap: 2}}>
1098
+ <Typography variant="h5" sx={{fontWeight: 600}}>
1033
1099
  Builder
1034
1100
  </Typography>
1035
1101
 
1036
1102
  <FormControlLabel
1037
1103
  control={
1038
1104
  <Switch
1039
- size='small'
1105
+ size="small"
1040
1106
  checked={builderModel.isRaw}
1041
- onChange={() => setBuilderModel(prev => ({ ...prev, isRaw: !prev.isRaw }))}
1042
- color='primary'
1107
+ onChange={() =>
1108
+ setBuilderModel(prev => ({...prev, isRaw: !prev.isRaw}))
1109
+ }
1110
+ color="primary"
1043
1111
  />
1044
1112
  }
1045
- label='Raw'
1113
+ label="Raw"
1046
1114
  />
1047
1115
  </Box>
1048
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
1116
+ <Box sx={{display: 'flex', alignItems: 'center', gap: 2}}>
1049
1117
  <Tooltip title={'تصفية رئيسية'}>
1118
+
1050
1119
  <IconButton
1051
1120
  sx={{
1052
1121
  color: isPreview ? 'primary.main' : 'text.secondary',
1053
1122
  '&:hover': {
1054
1123
  backgroundColor: 'transparent',
1055
- color: 'primary.main'
1056
- }
1124
+ color: 'primary.main',
1125
+ },
1057
1126
  }}
1058
- onClick={() => handleToggleDialogs('filter')}
1127
+ onClick={() => handleToggleDialogs('filter')
1128
+ }
1059
1129
  >
1060
- <FilterAlt />
1130
+ <FilterAlt/>
1061
1131
  </IconButton>
1062
1132
  </Tooltip>
1063
1133
  <IconButton
@@ -1065,14 +1135,15 @@ const ReportBuilder = () => {
1065
1135
  color: isPreview ? 'primary.main' : 'text.secondary',
1066
1136
  '&:hover': {
1067
1137
  backgroundColor: 'transparent',
1068
- color: 'primary.main'
1069
- }
1138
+ color: 'primary.main',
1139
+ },
1070
1140
  }}
1071
1141
  onClick={() => setIsPreview(prev => !prev)}
1072
1142
  >
1073
- <PreviewOutlined />
1143
+ <PreviewOutlined/>
1074
1144
  </IconButton>
1075
1145
  </Box>
1146
+
1076
1147
  </Box>
1077
1148
 
1078
1149
  {/* Actions row: Just the Add Computed Field button aligned right */}
@@ -1085,22 +1156,22 @@ const ReportBuilder = () => {
1085
1156
  borderRadius: '8px',
1086
1157
  display: 'flex',
1087
1158
  justifyContent: 'space-between',
1088
- backgroundColor: 'background.paper'
1159
+ backgroundColor: 'background.paper',
1089
1160
  }}
1090
1161
  >
1091
1162
  <Button
1092
- variant='outlined'
1093
- color='primary'
1163
+ variant="outlined"
1164
+ color="primary"
1094
1165
  onClick={() => {
1095
- console.log(breadcrumbs)
1166
+ console.log(breadcrumbs);
1096
1167
  handleToggleDialogs('expression')
1097
1168
  }}
1098
1169
  >
1099
1170
  Add Computed Field
1100
1171
  </Button>
1101
1172
  <Button
1102
- variant='outlined'
1103
- color='primary'
1173
+ variant="outlined"
1174
+ color="primary"
1104
1175
  onClick={() => {
1105
1176
  setSelectedRow(null) // reset edit
1106
1177
  handleToggleDialogs('routing')
@@ -1109,38 +1180,56 @@ const ReportBuilder = () => {
1109
1180
  Add Routing
1110
1181
  </Button>
1111
1182
  <Button
1112
- variant='outlined'
1113
- color='primary'
1183
+ variant="outlined"
1184
+ color="primary"
1114
1185
  onClick={() => {
1115
- console.log(breadcrumbs)
1186
+ console.log(breadcrumbs);
1116
1187
  handleToggleDialogs('params')
1117
1188
  }}
1118
1189
  >
1119
1190
  Global Parameters
1120
1191
  </Button>
1121
- <Button variant='outlined' color='secondary' onClick={() => handleToggleDialogs('fixedFilter')}>
1192
+ <Button
1193
+ variant="outlined"
1194
+ color="secondary"
1195
+ onClick={() => handleToggleDialogs('fixedFilter')}
1196
+ >
1122
1197
  Fixed Filter
1123
1198
  </Button>
1199
+ <Button
1200
+ variant="outlined"
1201
+ color="info"
1202
+ onClick={() => handleToggleDialogs('columnConfig')}
1203
+ >
1204
+ Column Configurator
1205
+ </Button>
1206
+ <Button
1207
+ variant="outlined"
1208
+ color="warning"
1209
+ onClick={() => handleToggleDialogs('settings')}
1210
+ >
1211
+ Report Settings
1212
+ </Button>
1124
1213
  {/* Hidden input for uploading builder model */}
1125
1214
  <input
1126
- type='file'
1127
- accept='application/json,.json'
1215
+ type="file"
1216
+ accept="application/json,.json"
1128
1217
  ref={fileInputRef}
1129
- style={{ display: 'none' }}
1218
+ style={{display: 'none'}}
1130
1219
  onChange={handleFileChange}
1131
1220
  />
1132
1221
  <Button
1133
- variant='outlined'
1134
- color='secondary'
1135
- startIcon={<CloudDownloadOutlined />}
1222
+ variant="outlined"
1223
+ color="secondary"
1224
+ startIcon={<CloudDownloadOutlined/>}
1136
1225
  onClick={handleDownloadModel}
1137
1226
  >
1138
1227
  Download Model
1139
1228
  </Button>
1140
1229
  <Button
1141
- variant='outlined'
1142
- color='secondary'
1143
- startIcon={<CloudUploadOutlined />}
1230
+ variant="outlined"
1231
+ color="secondary"
1232
+ startIcon={<CloudUploadOutlined/>}
1144
1233
  onClick={handleUploadClick}
1145
1234
  >
1146
1235
  Upload Model
@@ -1148,35 +1237,31 @@ const ReportBuilder = () => {
1148
1237
  </Box>
1149
1238
  </Box>
1150
1239
 
1151
- {!isPreview ? (
1152
- builderModel.isRaw ? (
1153
- <Box>
1154
- <SqlEditor builderModel={builderModel} setBuilderModel={setBuilderModel} />
1155
- </Box>
1156
- ) : (
1157
- <Box>
1158
- <DragDropContext onDragEnd={handleDragEnd}>
1159
- {renderSelectedFields(builderModel.selectedFields)}
1160
- </DragDropContext>
1161
- </Box>
1162
- )
1163
- ) : (
1240
+ {!isPreview ? builderModel.isRaw ?
1241
+ <Box><SqlEditor builderModel={builderModel} setBuilderModel={setBuilderModel}/></Box> :
1164
1242
  <Box>
1165
- <SGrid builderModel={builderModel} />
1166
- </Box>
1167
- )}
1243
+ <DragDropContext onDragEnd={handleDragEnd}>
1244
+ {renderSelectedFields(builderModel.selectedFields)}
1245
+ </DragDropContext>
1246
+ </Box> : <Box>
1247
+ <SGrid
1248
+ builderModel={builderModel}
1249
+ filter={builderModel.filter}
1250
+ extraCols={columnConfigResult?.extraCols ?? []}
1251
+ columnsConfig={columnConfigResult?.columnsConfig ?? []}
1252
+ updateRef={updateRef}
1253
+ />
1254
+ </Box>}
1168
1255
  </Box>
1169
1256
  </Grid>
1170
- {!isPreview && (
1171
- <Box
1172
- sx={{ mt: 4, backgroundColor: '#fff', borderRadius: '12px', p: '10px', border: '1px solid lightgrey' }}
1173
- >
1174
- <Typography variant='h6' gutterBottom>
1257
+ {!isPreview &&
1258
+ <Box sx={{mt: 4, backgroundColor: '#fff', borderRadius: '12px', p: '10px', border: '1px solid lightgrey'}}>
1259
+ <Typography variant="h6" gutterBottom>
1175
1260
  Routing Settings
1176
1261
  </Typography>
1177
1262
 
1178
1263
  {builderModel.routingSettings?.length === 0 && (
1179
- <Typography variant='body2' color='text.secondary'>
1264
+ <Typography variant="body2" color="text.secondary">
1180
1265
  No routing settings defined yet.
1181
1266
  </Typography>
1182
1267
  )}
@@ -1189,37 +1274,37 @@ const ReportBuilder = () => {
1189
1274
  display: 'flex',
1190
1275
  alignItems: 'center',
1191
1276
  justifyContent: 'space-between',
1192
- width: '100%'
1277
+ width: '100%',
1193
1278
  }}
1194
1279
  >
1195
1280
  {/* Left side: routing info */}
1196
1281
  <Box>
1197
- <Typography variant='body1'>
1198
- {route.fieldName} → {route.pattern}
1282
+ <Typography variant="body1">
1283
+ {route.name ? `${route.name} (${route.fieldName})` : route.fieldName} → {route.type === 'Filter' ? `Page: ${route.pageId}` : route.pattern}
1199
1284
  </Typography>
1200
- <Typography variant='body2' color='text.secondary'>
1201
- Params: {route.params.join(', ')}
1285
+ <Typography variant="body2" color="text.secondary">
1286
+ Params: {(route.params || []).join(', ')}
1202
1287
  </Typography>
1203
1288
  </Box>
1204
1289
 
1205
1290
  {/* Right side: action buttons */}
1206
- <Box sx={{ display: 'flex', gap: 1 }}>
1291
+ <Box sx={{display: 'flex', gap: 1}}>
1207
1292
  <Button
1208
- size='small'
1293
+ size="small"
1209
1294
  onClick={() => {
1210
- setSelectedRow({ ...route, index: idx })
1295
+ setSelectedRow({...route, index: idx})
1211
1296
  handleToggleDialogs('routing')
1212
1297
  }}
1213
1298
  >
1214
1299
  Update
1215
1300
  </Button>
1216
1301
  <Button
1217
- size='small'
1218
- color='error'
1302
+ size="small"
1303
+ color="error"
1219
1304
  onClick={() =>
1220
- setBuilderModel(prev => ({
1305
+ setBuilderModel((prev) => ({
1221
1306
  ...prev,
1222
- routingSettings: prev.routingSettings.filter((_, i) => i !== idx)
1307
+ routingSettings: prev.routingSettings.filter((_, i) => i !== idx),
1223
1308
  }))
1224
1309
  }
1225
1310
  >
@@ -1230,9 +1315,40 @@ const ReportBuilder = () => {
1230
1315
  </ListItem>
1231
1316
  ))}
1232
1317
  </List>
1233
- </Box>
1234
- )}
1318
+
1319
+ </Box>}
1320
+
1321
+ {!isPreview && columnConfigResult &&
1322
+ <Box sx={{mt: 4, backgroundColor: '#fff', borderRadius: '12px', p: '10px', border: '1px solid lightgrey'}}>
1323
+ <Box sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2}}>
1324
+ <Typography variant="h6">
1325
+ Column Configurator Result (Testing)
1326
+ </Typography>
1327
+ <Button
1328
+ size="small"
1329
+ color="error"
1330
+ onClick={() => setColumnConfigResult(null)}
1331
+ >
1332
+ Clear
1333
+ </Button>
1334
+ </Box>
1335
+ <Box
1336
+ component="pre"
1337
+ sx={{
1338
+ p: 2,
1339
+ bgcolor: 'grey.900',
1340
+ color: 'grey.100',
1341
+ borderRadius: 1,
1342
+ overflow: 'auto',
1343
+ maxHeight: 400,
1344
+ fontSize: '0.875rem'
1345
+ }}
1346
+ >
1347
+ {JSON.stringify(columnConfigResult, null, 2)}
1348
+ </Box>
1349
+ </Box>}
1235
1350
  </Box>
1351
+
1236
1352
  </Box>
1237
1353
 
1238
1354
  <Dialog
@@ -1240,46 +1356,35 @@ const ReportBuilder = () => {
1240
1356
  onClose={() => {
1241
1357
  handleToggleDialogs('expression')
1242
1358
  setSelectedRow(null)
1243
- setSelectedCollection(null)
1244
- }}
1245
- maxWidth='lg'
1246
- fullWidth
1247
- >
1359
+ setSelectedCollection(null);
1360
+ }} maxWidth="lg" fullWidth>
1248
1361
  <ExpressionEditor
1249
1362
  oldValue={selectedRow}
1250
1363
  addComputedField={handleAddComputedField}
1251
1364
  handleUpdateComputedField={handleUpdateComputedField}
1252
1365
  selectionParams={builderModel.selectionParams}
1253
- setSelectionParams={params => setBuilderModel(prev => ({ ...prev, selectionParams: params }))}
1366
+ setSelectionParams={(params) =>
1367
+ setBuilderModel(prev => ({...prev, selectionParams: params}))
1368
+ }
1254
1369
  />
1255
1370
  </Dialog>
1256
- <Dialog
1257
- open={openDialogs.params}
1258
- onClose={() => {
1259
- handleToggleDialogs('params')
1260
- setSelectedRow(null)
1261
- }}
1262
- maxWidth='lg'
1263
- fullWidth
1264
- >
1371
+ <Dialog open={openDialogs.params} onClose={() => {
1372
+ handleToggleDialogs('params')
1373
+ setSelectedRow(null)
1374
+ }} maxWidth="lg" fullWidth>
1265
1375
  <DynamicValueList
1266
1376
  items={builderModel.selectionParams}
1267
1377
  onChange={newParams => {
1268
1378
  setBuilderModel(prev => ({
1269
1379
  ...prev,
1270
1380
  selectionParams: newParams
1271
- }))
1381
+ }));
1272
1382
  }}
1273
1383
  />
1274
1384
  </Dialog>
1275
- <Dialog
1276
- open={openDialogs.save}
1277
- onClose={() => {
1278
- handleToggleDialogs('save')
1279
- }}
1280
- maxWidth='lg'
1281
- fullWidth
1282
- >
1385
+ <Dialog open={openDialogs.save} onClose={() => {
1386
+ handleToggleDialogs('save')
1387
+ }} maxWidth="lg" fullWidth>
1283
1388
  <ReportBuilderSaveForm
1284
1389
  builderMetadata={builderMetadata}
1285
1390
  setBuilderMetadata={setBuilderMetadata}
@@ -1296,15 +1401,18 @@ const ReportBuilder = () => {
1296
1401
  scroll='body'
1297
1402
  // onClose={() => handleToggleDialogs('CustomFilter')}
1298
1403
  >
1299
- <CustomFilterDialog
1300
- handleToggleDialogs={handleToggleDialogs}
1301
- Filter={[...(builderModel.filter?.LocalTfilter || [])]}
1302
- handleFilterChange={handleFilterChange}
1303
- className={builderModel?.reportSource?.fullName}
1304
- LocalFilter={false}
1305
- selectTFilter={[]}
1306
- selectParamsMeta={builderModel.selectionParams}
1307
- />
1404
+ {openDialogs.filter && builderModel.reportSource && (
1405
+ <CustomFilterDialog
1406
+ handleToggleDialogs={handleToggleDialogs}
1407
+ Filter={builderModel.filter?.LocalTfilter || []}
1408
+ customFilterCode={builderModel.filter?.customFilterCode}
1409
+ handleFilterChange={handleFilterChange}
1410
+ className={builderModel?.reportSource?.fullName}
1411
+ LocalFilter={false}
1412
+ selectTFilter={[]}
1413
+ // selectParamsMeta={builderModel.selectionParams}
1414
+ />
1415
+ )}
1308
1416
  </Dialog>
1309
1417
  <Dialog
1310
1418
  fullWidth
@@ -1316,7 +1424,7 @@ const ReportBuilder = () => {
1316
1424
  <FixedFilterDialog
1317
1425
  className={builderModel?.reportSource?.fullName}
1318
1426
  value={builderModel?.filter?.fixedTFilter || []}
1319
- onSave={list => {
1427
+ onSave={(list) => {
1320
1428
  setBuilderModel(prev => ({
1321
1429
  ...prev,
1322
1430
  filter: {
@@ -1328,11 +1436,16 @@ const ReportBuilder = () => {
1328
1436
  onClose={() => handleToggleDialogs('fixedFilter')}
1329
1437
  />
1330
1438
  </Dialog>
1331
- <Dialog open={openDialogs.routing} onClose={() => handleToggleDialogs('routing')} maxWidth='sm' fullWidth>
1439
+ <Dialog
1440
+ open={openDialogs.routing}
1441
+ onClose={() => handleToggleDialogs('routing')}
1442
+ maxWidth="sm"
1443
+ fullWidth
1444
+ >
1332
1445
  <RoutingSettingDialog
1333
- value={selectedRow || { fieldName: '', pattern: '', params: [] }}
1334
- onChange={newValue => {
1335
- setBuilderModel(prev => {
1446
+ value={selectedRow || {fieldName: '', pattern: '', params: []}}
1447
+ onChange={(newValue) => {
1448
+ setBuilderModel((prev) => {
1336
1449
  const clone = [...(prev.routingSettings || [])]
1337
1450
  if (selectedRow?.index != null) {
1338
1451
  // Update existing
@@ -1341,21 +1454,66 @@ const ReportBuilder = () => {
1341
1454
  // Add new
1342
1455
  clone.push(newValue)
1343
1456
  }
1344
- return { ...prev, routingSettings: clone }
1457
+ return {...prev, routingSettings: clone}
1345
1458
  })
1346
1459
  }}
1347
1460
  onClose={() => handleToggleDialogs('routing')}
1348
1461
  />
1349
1462
  </Dialog>
1463
+ <FormattingSettingsDialog
1464
+ open={openDialogs.formatting}
1465
+ onClose={() => setOpenDialogs(prev => ({...prev, formatting: false}))}
1466
+ field={selectedRow ? {
1467
+ ...selectedRow,
1468
+ parentFields: builderModel.selectedFields.filter(f => f.path !== selectedRow.path && !f.children)
1469
+ } : null}
1470
+ onSave={(formatting) => {
1471
+ setBuilderModel(prev => {
1472
+ const updateNode = (node) => {
1473
+ if (node.propertyType === 'Collection') {
1474
+ return {...node, children: (node.children || []).map(updateNode)};
1475
+ }
1476
+ if (node.path === selectedRow.path) {
1477
+ return {...node, formatting};
1478
+ }
1479
+ return node;
1480
+ };
1481
+ return {
1482
+ ...prev,
1483
+ selectedFields: prev.selectedFields.map(updateNode)
1484
+ };
1485
+ });
1486
+ }}
1487
+ />
1488
+ <ColumnConfiguratorDialog
1489
+ open={openDialogs.columnConfig}
1490
+ onClose={() => handleToggleDialogs('columnConfig')}
1491
+ onResult={(result) => {
1492
+ console.log('Column Configuration Result:', result);
1493
+ setColumnConfigResult(result);
1494
+ handleToggleDialogs('columnConfig');
1495
+ }}
1496
+ />
1497
+ <ReportSettingsDialog
1498
+ open={openDialogs.settings}
1499
+ onClose={() => handleToggleDialogs('settings')}
1500
+ settings={builderModel.settings}
1501
+ onSave={(newSettings) => {
1502
+ setBuilderModel(prev => ({
1503
+ ...prev,
1504
+ settings: newSettings
1505
+ }));
1506
+ }}
1507
+ />
1350
1508
  </div>
1351
1509
  )
1352
- }
1510
+ };
1353
1511
 
1354
1512
  ReportBuilder.acl = {
1355
1513
  action: 'view',
1356
- subject: PermissionsSubjects.ReportBuilderPage
1357
- }
1514
+ subject: PermissionsSubjects.ReportBuilder
1515
+ };
1358
1516
 
1359
- ReportBuilder.getLayout = page => <BlankLayout>{page}</BlankLayout>
1517
+ ReportBuilder.getLayout = (page) => <BlankLayout>{page}</BlankLayout>;
1360
1518
 
1361
- export default ReportBuilder
1519
+ export default ReportBuilder;