ms-data-grid 0.0.16 → 0.0.17

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/DOCUMENTATION.md +243 -0
  3. package/ng-package.json +10 -0
  4. package/package.json +33 -45
  5. package/src/lib/css/bootstrap.css +12043 -0
  6. package/src/lib/data-grid/data-grid.component.html +4806 -0
  7. package/src/lib/data-grid/data-grid.component.scss +1502 -0
  8. package/src/lib/data-grid/data-grid.component.spec.ts +28 -0
  9. package/src/lib/data-grid/data-grid.component.ts +4216 -0
  10. package/src/lib/data-grid/statuses.ts +47 -0
  11. package/src/lib/data-grid.module.ts +20 -0
  12. package/src/lib/data-grid.service.spec.ts +16 -0
  13. package/src/lib/data-grid.service.ts +9 -0
  14. package/src/lib/directives/draggable-header.directive.spec.ts +11 -0
  15. package/src/lib/directives/draggable-header.directive.ts +172 -0
  16. package/src/lib/pipes/filter.pipe.spec.ts +11 -0
  17. package/src/lib/pipes/filter.pipe.ts +16 -0
  18. package/src/lib/services/cell-selection.service.spec.ts +16 -0
  19. package/src/lib/services/cell-selection.service.ts +234 -0
  20. package/src/lib/services/common.service.spec.ts +16 -0
  21. package/src/lib/services/common.service.ts +239 -0
  22. package/src/lib/services/copy-service.service.spec.ts +16 -0
  23. package/src/lib/services/copy-service.service.ts +251 -0
  24. package/src/lib/services/drag-drp.service.spec.ts +16 -0
  25. package/src/lib/services/drag-drp.service.ts +58 -0
  26. package/src/lib/services/split-columns.service.spec.ts +16 -0
  27. package/src/lib/services/split-columns.service.ts +148 -0
  28. package/src/lib/services/swap-columns.service.spec.ts +16 -0
  29. package/src/lib/services/swap-columns.service.ts +162 -0
  30. package/{public-api.d.ts → src/public-api.ts} +8 -4
  31. package/tsconfig.lib.json +16 -0
  32. package/tsconfig.lib.prod.json +10 -0
  33. package/tsconfig.spec.json +14 -0
  34. package/esm2022/lib/data-grid/data-grid.component.mjs +0 -3623
  35. package/esm2022/lib/data-grid/statuses.mjs +0 -44
  36. package/esm2022/lib/data-grid.module.mjs +0 -26
  37. package/esm2022/lib/data-grid.service.mjs +0 -14
  38. package/esm2022/lib/directives/draggable-header.directive.mjs +0 -145
  39. package/esm2022/lib/pipes/filter.pipe.mjs +0 -22
  40. package/esm2022/lib/services/common.service.mjs +0 -206
  41. package/esm2022/lib/services/copy-service.service.mjs +0 -221
  42. package/esm2022/lib/services/split-columns.service.mjs +0 -143
  43. package/esm2022/lib/services/swap-columns.service.mjs +0 -118
  44. package/esm2022/ms-data-grid.mjs +0 -5
  45. package/esm2022/public-api.mjs +0 -8
  46. package/fesm2022/ms-data-grid.mjs +0 -4546
  47. package/fesm2022/ms-data-grid.mjs.map +0 -1
  48. package/index.d.ts +0 -5
  49. package/lib/data-grid/data-grid.component.d.ts +0 -468
  50. package/lib/data-grid/statuses.d.ts +0 -3
  51. package/lib/data-grid.module.d.ts +0 -14
  52. package/lib/data-grid.service.d.ts +0 -6
  53. package/lib/directives/draggable-header.directive.d.ts +0 -31
  54. package/lib/pipes/filter.pipe.d.ts +0 -7
  55. package/lib/services/common.service.d.ts +0 -17
  56. package/lib/services/copy-service.service.d.ts +0 -14
  57. package/lib/services/split-columns.service.d.ts +0 -9
  58. package/lib/services/swap-columns.service.d.ts +0 -19
@@ -0,0 +1,4216 @@
1
+ import {
2
+ Component,
3
+ OnInit,
4
+ Input,
5
+ Output,
6
+ OnChanges,
7
+ SimpleChanges,
8
+ ElementRef,
9
+ ViewChild,
10
+ AfterViewInit,
11
+ HostListener,
12
+ ChangeDetectorRef,
13
+ EventEmitter,
14
+ NgZone,
15
+ ChangeDetectionStrategy,
16
+ } from '@angular/core';
17
+ import { SplitColumnsService } from '../services/split-columns.service';
18
+ import { CommonService } from '../services/common.service';
19
+ import { SwapColumnsService } from '../services/swap-columns.service';
20
+ import {
21
+ CdkDrag,
22
+ CdkDragDrop,
23
+ CdkDragEnd,
24
+ CdkDragEnter,
25
+ CdkDragExit,
26
+ CdkDragMove,
27
+ CdkDragSortEvent,
28
+ CdkDragStart,
29
+ DragDrop,
30
+ moveItemInArray,
31
+ } from '@angular/cdk/drag-drop';
32
+ import {
33
+ trigger,
34
+ state,
35
+ style,
36
+ transition,
37
+ animate,
38
+ query,
39
+ stagger,
40
+ } from '@angular/animations';
41
+ import { CdkDropList } from '@angular/cdk/drag-drop';
42
+ import { STATUSES_BADGE_MAP } from './statuses';
43
+ import { CopyServiceService } from '../services/copy-service.service';
44
+
45
+ interface CellPosition {
46
+ rowIndex: number;
47
+ colIndex: number;
48
+ subColIndex: number;
49
+ field: string;
50
+ key: string;
51
+ }
52
+ export const sortingAnimation = trigger('listSort', [
53
+ transition('* => *', [
54
+ // Query all elements entering or leaving
55
+ query(':enter, :leave', style({ position: 'absolute', width: '100%' }), { optional: true }),
56
+
57
+ // Animate leaving elements upward with fade out
58
+ query(':leave', [
59
+ animate('300ms ease-in', style({ transform: 'translateY(-20px)', opacity: 0 }))
60
+ ], { optional: true }),
61
+
62
+ // Stagger the entering elements for a smoother effect
63
+ query(':enter', [
64
+ style({ transform: 'translateY(20px)', opacity: 0 }),
65
+ stagger(50, [
66
+ animate('300ms ease-out', style({ transform: 'translateY(0)', opacity: 1 }))
67
+ ])
68
+ ], { optional: true })
69
+ ])
70
+ ]);
71
+ @Component({
72
+ selector: 'data-grid',
73
+ templateUrl: './data-grid.component.html',
74
+ styleUrls: ['./data-grid.component.scss', '../css/bootstrap.css'],
75
+ animations: [
76
+ trigger('accordionToggle', [
77
+ state(
78
+ 'collapsed',
79
+ style({ height: '0px', overflow: 'unset' })
80
+ ),
81
+ state('expanded', style({ height: '*', overflow: 'unset' })),
82
+ transition('collapsed <=> expanded', animate('300ms ease-in')),
83
+ ]),
84
+ trigger('slideToggle', [
85
+ transition(':enter', [
86
+ style({ height: '0px', opacity: 0, overflow: 'hidden' }),
87
+ animate('300ms ease-out', style({ height: '*', opacity: 1 }))
88
+ ]),
89
+ transition(':leave', [
90
+ style({ height: '*', opacity: 1, overflow: 'hidden' }),
91
+ animate('300ms ease-in', style({ height: '0px', opacity: 0 }))
92
+ ])
93
+ ]),
94
+ trigger('slideUp', [
95
+ state('void', style({ transform: 'translateY(100%)', opacity: 0 })),
96
+ state('*', style({ transform: 'translateY(0)', opacity: 1 })),
97
+ transition('void => *', animate('300ms ease-out')),
98
+ transition('* => void', animate('300ms ease-in')),
99
+ ])
100
+ ],
101
+ })
102
+ export class DataGridComponent implements OnChanges, AfterViewInit {
103
+ // **************************************//
104
+ // **************************************//
105
+ // ********** Input Goes Here *********** //
106
+ // **************************************//
107
+ // **************************************//
108
+
109
+ // Pagination Config Store Here
110
+ @Input() paginationConfig: any = {};
111
+
112
+ // The dataset store here;
113
+ @Input() dataSet: any[] = [];
114
+
115
+ // The columns Store Here
116
+ @Input() columns: any[] = [];
117
+
118
+ // Row Height;
119
+ @Input() rowHeight: number = 44;
120
+
121
+ // Header Row Height;
122
+ @Input() headerRowHeight: number = 44;
123
+
124
+ // Show Vertical Borders;
125
+ @Input() showVerticalBorder: boolean = true;
126
+
127
+ // Even Rows Background Color;
128
+ @Input() evenRowsBackgroundColor: string | undefined = undefined;
129
+
130
+ // Even Rows Background Color;
131
+ @Input() oddRowsBackgroundColor: string | undefined = undefined;
132
+
133
+ // Header Rows Background Color;
134
+ @Input() headerBackgroundColor: string = '#eaeaea';
135
+
136
+ // Header Rows Background Color;
137
+ @Input() checkboxesBackgroundColor: string = '#eaeaea';
138
+
139
+ // Show Columns Grouping;
140
+ @Input() showColumnsGrouping: boolean = false;
141
+
142
+ // Row Hovered Background color;
143
+ @Input() rowHoverColor: string | undefined = 'rgba(0, 123, 255, 0.1)';
144
+
145
+ // Left PinnedBackground color;
146
+ @Input() leftPinnedBackgroundColor: string | undefined = '#000';
147
+
148
+ // Body Background color;
149
+ @Input() bodyBackgroundColor: string | undefined = '#fff';
150
+
151
+ // Right Pinned Background color;
152
+ @Input() rightPinnedBackgroundColor: string | undefined = '#000';
153
+
154
+ // Side Menu Background color;
155
+ @Input() sidemenuBackgroundColor: string | undefined = '#000';
156
+
157
+ // Body text color;
158
+ @Input() bodyTextColor: string | undefined = '#000';
159
+
160
+ // Header text color;
161
+ @Input() headerTextColor: string | undefined = '#000';
162
+
163
+ // Header text size;
164
+ @Input() headerTextFontsSize: number | undefined = 16;
165
+
166
+ // Body text color;
167
+ @Input() bodyTextFontsSize: number | undefined = 16;
168
+
169
+ // Header font weight;
170
+ @Input() headerFontWeight: number | undefined = 500;
171
+
172
+ // Body Font Weight;
173
+ @Input() bodyFontWeight: number | undefined = 400;
174
+
175
+ // Checked Row Background Color;
176
+ @Input() checkedRowBackgroundColor: string | undefined = '';
177
+
178
+ // dropdowns Background Color;
179
+ @Input() dropdownsBackgroundColor: string | undefined = '';
180
+
181
+ // Footer row Height;
182
+ @Input() footerRowHeight: number = 46;
183
+
184
+ // Footer row Height;
185
+ @Input() topGroupedBadgesBackgroundColor: string | undefined = '';
186
+
187
+ // Show Row wise grouping;
188
+ @Input() showRowsGrouping: boolean | undefined = false;
189
+
190
+ // Show Row wise grouping;
191
+ @Input() showFilterRow: boolean | undefined = false;
192
+
193
+ // Show Row wise grouping;
194
+ @Input() fontFaimly: string | undefined = 'sans-serif';
195
+
196
+ // Show SideColumn;
197
+ @Input() showSideMenu: boolean = false;
198
+
199
+ // Footer Padding
200
+ @Input() footerPadding: number = 3
201
+
202
+
203
+ // Footer Padding
204
+ @Input() topFilterRowHeight: number = 50;
205
+
206
+ // Show Rows shading
207
+ @Input() rowShadingEnabled: boolean = false;
208
+
209
+ // Show Rows shading
210
+ @Input() showSerialNumber: boolean = false;
211
+
212
+ // Single Spa Url Attach to icons
213
+ @Input() singleSpaAssetsPath: string = 'assets/';
214
+
215
+
216
+ // Applied Filters
217
+ @Input() filtersConfig: any[] = [];
218
+
219
+
220
+ // Applied Filters
221
+ @Input() loading: boolean = false;
222
+
223
+ //Vertical Scrollbar style
224
+ @Input() verticalScrollbarWidth: 'auto' | 'thin' = 'auto';
225
+
226
+ //Horizintal Scrollbar style
227
+ @Input() horizintalScrollbarWidth: 'auto' | 'thin' = 'auto';
228
+
229
+ // Show Cell details box
230
+ @Input() showCellDetailsBox: boolean = false;
231
+
232
+ //Date format
233
+
234
+ @Input() dateFormat: string = 'dd/MM/yyyy'
235
+
236
+ //Date format
237
+ @Input() tableSearch: string = '';
238
+
239
+
240
+ //Date format
241
+ @Input() actions: any[] = ['edit', 'delete'];
242
+
243
+
244
+ // Table Config for paginations is here
245
+ @Input() config: any;
246
+
247
+
248
+ // Selection task bar
249
+ @Input() showTaskbar = false
250
+
251
+ // Table Name for state manage
252
+ @Input() tableName = true
253
+
254
+
255
+ // Listing type
256
+ @Input() listingType: string | boolean = '';
257
+
258
+ // Listing type
259
+
260
+ @Input() checkboxState: { reset: boolean } = {
261
+ reset: true
262
+ }
263
+
264
+ // Taskbar actions
265
+ @Input() taskbarActions: any[] = []
266
+
267
+
268
+ // Sorting Config to show sort icons
269
+ @Input() sortingConfig: { field: string, order_by: string } | null = null;
270
+
271
+
272
+ @Input() tableFilterViewId: any = '';
273
+
274
+
275
+ @Input() selectedTableLayout: any = 'medium'
276
+
277
+ @Input() closeDropdown: { preset: { closed: boolean, loading: boolean } } = { preset: { closed: false, loading: false } };
278
+
279
+ // Table View
280
+
281
+ // GlobalSearch
282
+ @Input() globalSearchText: string = '';
283
+
284
+ // Nested Table Row Fontsize
285
+ @Input() nestedTablerowFontsize = 14;
286
+
287
+ // Nested table row Header row Height
288
+ @Input() nestedTableHeaderRowHeight = 40;
289
+
290
+ // Nested Table row height
291
+ @Input() nestedTablerowHeight = 36;
292
+
293
+
294
+ @Input() gridType: string = '';
295
+
296
+ @Input() currencySymbol: string = '$';
297
+
298
+
299
+ @Input() leftPinnedBoxshadow: string = '#4b4b4b 1px 1px 5px 0px';
300
+
301
+ @Input() rightPinnedBoxshadow: string = '#4b4b4b 4px 1px 6px 0px';
302
+
303
+ // GlobalSearch
304
+ @Input() selectedRowsBackgroundColor: string = '#8ac5ff';
305
+
306
+ // GlobalSearch
307
+ @Input() nestedTableHeaderBAckgroundColor: string = '#eaeaea';
308
+
309
+ @Input() tableView: any[] = [];
310
+
311
+ @Input() keepMultipleExpandedDetails = false;
312
+
313
+
314
+ @Input() showTotalAmountRow = false;
315
+
316
+ @Input() tableType = '';
317
+
318
+ @Input() columnThreedotsMunuConfig = {
319
+ showPinleft: true,
320
+ showPinright: true,
321
+ showAscending: true,
322
+ showDescending: true,
323
+ showFilter: true,
324
+ showRowsGrouping: this.showRowsGrouping,
325
+ showAutosizeAllColumns: false,
326
+ showAutosizeThisColumn: false,
327
+ showChoseColumns: false,
328
+ showResetColumns: false,
329
+ };
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+
342
+
343
+
344
+ // ///////////////////////////////////////////////////////////////////////////////////////////
345
+ // ///////////////////////////////////////////////////////////////////////////////////////////
346
+ // Out Put Events
347
+ // ///////////////////////////////////////////////////////////////////////////////////////////
348
+ // ///////////////////////////////////////////////////////////////////////////////////////////
349
+
350
+
351
+
352
+ //Change Table Layout
353
+ @Output() public changeLayout = new EventEmitter<any>();
354
+
355
+ // Filter Apply event;
356
+ @Output() public filterOptions = new EventEmitter<any>();
357
+
358
+ @Output() genericEvent: EventEmitter<any> = new EventEmitter();
359
+
360
+ @Output() tablePresetConfig: EventEmitter<any> = new EventEmitter();
361
+
362
+ @Output() sortingOrderOptions: EventEmitter<any> = new EventEmitter<any>();
363
+
364
+ @Output() createUpdateConfigListing: EventEmitter<any> = new EventEmitter<any>();
365
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
373
+
374
+ groupedColumns: any[] = [];
375
+ activeCol: any = null;
376
+ activeFilterCell: any = null;
377
+ showActionsDropDown = false;
378
+ sideMenuVisible = false;
379
+ pivotMode: boolean = false;
380
+ columnSearch: string = '';
381
+ expandAllAccordians = true;
382
+ currentOpenedSideMenue: 'cols' | 'filtrs' | null = null;
383
+ originalColumns: any[] = [];
384
+ originalDataSet: any[] = [];
385
+ activeTopButton: string | null = '';
386
+ filterColumnsList: any[] = [];
387
+ groupBoxPadding = 200;
388
+ presetName: string = '';
389
+ presetFilter: any = '';
390
+ searchTextPresetTable = '';
391
+ addFilterColumnInput = '';
392
+ searchInDropdown = '';
393
+ addFilterDropdownSearch = ''
394
+ topShowHideColumns = '';
395
+ choseColumnsSearch = ''
396
+ editinDropdownSearch = '';
397
+ isThreeDotsFilterOpen = false;
398
+ confirmDelete = false;
399
+ fontFamilies: string[] = [
400
+ // Common Excel fonts
401
+ 'Arial',
402
+ 'Arial Black',
403
+ 'Bahnschrift',
404
+ 'Calibri',
405
+ 'Cambria',
406
+ 'Cambria Math',
407
+ 'Candara',
408
+ 'Comic Sans MS',
409
+ 'Consolas',
410
+ 'Constantia',
411
+ 'Corbel',
412
+ 'Courier New',
413
+ 'Ebrima',
414
+ 'Franklin Gothic Medium',
415
+ 'Gabriola',
416
+ 'Gadugi',
417
+ 'Georgia',
418
+ 'Impact',
419
+ 'Ink Free',
420
+ 'Javanese Text',
421
+ 'Leelawadee UI',
422
+ 'Lucida Console',
423
+ 'Lucida Sans Unicode',
424
+ 'Malgun Gothic',
425
+ 'Microsoft Himalaya',
426
+ 'Microsoft JhengHei',
427
+ 'Microsoft New Tai Lue',
428
+ 'Microsoft PhagsPa',
429
+ 'Microsoft Sans Serif',
430
+ 'Microsoft Tai Le',
431
+ 'Microsoft YaHei',
432
+ 'Microsoft Yi Baiti',
433
+ 'Mongolian Baiti',
434
+ 'MS Gothic',
435
+ 'MS PGothic',
436
+ 'MS UI Gothic',
437
+ 'MV Boli',
438
+ 'Nirmala UI',
439
+ 'Palatino Linotype',
440
+ 'Segoe Print',
441
+ 'Segoe Script',
442
+ 'Segoe UI',
443
+ 'Segoe UI Historic',
444
+ 'Segoe UI Emoji',
445
+ 'Segoe UI Symbol',
446
+ 'SimSun',
447
+ 'Sitka Small',
448
+ 'Sylfaen',
449
+ 'Symbol',
450
+ 'Tahoma',
451
+ 'Times New Roman',
452
+ 'Trebuchet MS',
453
+ 'Verdana',
454
+ 'Webdings',
455
+ 'Wingdings',
456
+ 'Yu Gothic',
457
+ 'sans-serif',
458
+ 'serif',
459
+ 'monospace'
460
+ ];
461
+
462
+ fontSizes: string[] = [
463
+ '8', '9', '10', '11', '12', '14', '16', '18', '20', '24', '28', '32'
464
+ ];
465
+
466
+
467
+
468
+
469
+ constructor(
470
+ private columnService: SplitColumnsService,
471
+ private cdr: ChangeDetectorRef,
472
+ public commonSevice: CommonService,
473
+ private swapColumnService: SwapColumnsService,
474
+ private elementRef: ElementRef,
475
+ private ngZone: NgZone,
476
+ private copyService: CopyServiceService
477
+ ) { }
478
+
479
+ @ViewChild('cellText', { static: false }) cellText!: ElementRef;
480
+ async ngAfterViewInit() {
481
+ setTimeout(async () => {
482
+ // this.updateFlattenedData();
483
+ // this.computeViewportRows();
484
+ // this.updateVisibleRows(0);
485
+ // this.cdr.detectChanges();
486
+ await this.SetColumnsDefaultWidth();
487
+ await this.refreshHeaders()
488
+ this.updateFlattenedData();
489
+ this.computeViewportRows();
490
+ this.updateVisibleRows(0);
491
+ if (this.cellText) {
492
+ const observer = new ResizeObserver(() => {
493
+ this.cdr.detectChanges();
494
+ });
495
+ observer.observe(this.cellText.nativeElement);
496
+ }
497
+ this.cdr.detectChanges();
498
+ }, 300);
499
+
500
+
501
+ }
502
+
503
+ async ngOnChanges(changes: SimpleChanges) {
504
+ if (changes['filtersConfig']) {
505
+ await this.applyFilteroptionList();
506
+ this.checkFilterChangesEffect();
507
+ }
508
+ if (changes['columns']?.currentValue?.length) {
509
+ // this.columns = this.columnService.setColumnsQuery(this.columns);
510
+ this.originalColumns = structuredClone(this.columns);
511
+ if (this.dataGridContainer?.nativeElement?.offsetWidth) {
512
+ await this.SetColumnsDefaultWidth();
513
+ await this.updateColumnWidthsAndGroups();
514
+ await this.refreshPreviewColumns();
515
+ this.setSectionsWidth();
516
+ this.cdr.detectChanges();
517
+ }
518
+ this.generateDropListIds();
519
+ this.cdr.detectChanges();
520
+
521
+ requestAnimationFrame(() => {
522
+
523
+ this.cdr.detectChanges();
524
+ });
525
+ }
526
+ if (changes['dataSet']) {
527
+ this.dataSet = this.dataSet?.map((row, i) => ({
528
+ ...row,
529
+ __virtualIndex: i + 1
530
+ }));
531
+ this.originalDataSet = structuredClone(this.dataSet);
532
+ this.expandedCells.clear();
533
+ this.updateFlattenedData();
534
+ this.cdr.detectChanges();
535
+ setTimeout(() => {
536
+ if (this.mainScroll?.nativeElement) {
537
+ this.computeViewportRows();
538
+ this.updateVisibleRows(0);
539
+ }
540
+ }, 100);
541
+ if (this.centerPinnedHeader?.nativeElement) {
542
+ void this.centerPinnedHeader.nativeElement.offsetWidth;
543
+ if (this.centerScrollableBody?.nativeElement) {
544
+ this.centerScrollableBody.nativeElement.scrollLeft = this.centerPinnedHeader?.nativeElement?.scrollLeft;
545
+ }
546
+ this.cdr.detectChanges();
547
+ }
548
+
549
+ }
550
+ if (changes['checkboxState']?.currentValue?.reset) {
551
+ this.selectedRows.clear();
552
+ this.cdr.detectChanges();
553
+ }
554
+ if (changes['closeDropdown']) {
555
+ if (this.closeDropdown.preset.closed) {
556
+ this.activeTopButton = null;
557
+ }
558
+ }
559
+ if (changes['oddRowsBackgroundColor']) {
560
+ this.rowShadingEnabled = this.oddRowsBackgroundColor ? this.rowShadingEnabled = true : false;
561
+ }
562
+ }
563
+
564
+
565
+
566
+ async applyFilteroptionList() {
567
+ const updatedColumns = await this.commonSevice.applyFiltersToColumns(this.columns, this.filtersConfig);
568
+ this.columns = [...updatedColumns];
569
+ this.cdr.detectChanges();
570
+ }
571
+ leftPinnedColumns: any[] = [];
572
+ centerColumns: any[] = [];
573
+ rightPinnedColumns: any[] = [];
574
+
575
+ previewLeftPinnedColumns: any[] = [];
576
+ previewCenterColumns: any[] = [];
577
+ previewRightPinnedColumns: any[] = [];
578
+
579
+ // Main Container Template References
580
+ @ViewChild('dataGridContainer')
581
+ dataGridContainer!: ElementRef<HTMLDivElement>;
582
+
583
+ // Body Template References
584
+ @ViewChild('leftPinnedBody')
585
+ leftPinnedBody!: ElementRef<HTMLDivElement>;
586
+ @ViewChild('centerPinnedBody')
587
+ centerPinnedBody!: ElementRef<HTMLDivElement>;
588
+ @ViewChild('rightPinnedBody')
589
+ rightPinnedBody!: ElementRef<HTMLDivElement>;
590
+
591
+ // Headers Template References
592
+ @ViewChild('leftPinnedHeader')
593
+ leftPinnedHeader!: ElementRef<HTMLDivElement>;
594
+ @ViewChild('centerPinnedHeader')
595
+ centerPinnedHeader!: ElementRef<HTMLDivElement>;
596
+ @ViewChild('rightPinnedHeader')
597
+ rightPinnedHeader!: ElementRef<HTMLDivElement>;
598
+
599
+ @ViewChild('columnsGroupedBox')
600
+ columnsGroupedBox!: ElementRef<HTMLDivElement>;
601
+
602
+ // Center Fake scrollbard
603
+ @ViewChild('centerFakeScrollbar')
604
+ centerFakeScrollbar!: ElementRef<HTMLDivElement>;
605
+ async updateColumnWidthsAndGroups(columns: any = null) {
606
+ if (!this.dataGridContainer) return;
607
+
608
+ const containerWidth = this.dataGridContainer.nativeElement.offsetWidth;
609
+
610
+ // Wrap in a promise so we can await
611
+ const { left, center, right }: any = await new Promise(resolve => {
612
+ const prepared = this.columnService.prepareColumns(
613
+ columns ? columns : this.columns,
614
+ containerWidth
615
+ );
616
+ resolve(prepared);
617
+ });
618
+
619
+ this.leftPinnedColumns = left;
620
+ this.centerColumns = center;
621
+ this.rightPinnedColumns = right;
622
+ await new Promise(r => setTimeout(r, 0));
623
+ this.cdr.detectChanges();
624
+ }
625
+
626
+ async refreshPreviewColumns(columns: any = null) {
627
+ if (!this.dataGridContainer) return;
628
+
629
+ const containerWidth = this.dataGridContainer.nativeElement.offsetWidth;
630
+
631
+ // Wrap prepareColumns in a Promise to make it awaitable
632
+ const { left, center, right }: any = await new Promise(resolve => {
633
+ const prepared = this.columnService.prepareColumns(
634
+ columns ? columns : this.columns,
635
+ containerWidth
636
+ );
637
+ resolve(prepared);
638
+ });
639
+
640
+ this.previewLeftPinnedColumns = left;
641
+ this.previewCenterColumns = center;
642
+ this.previewRightPinnedColumns = right;
643
+ await new Promise(r => setTimeout(r, 10));
644
+ this.cdr.detectChanges();
645
+ }
646
+
647
+
648
+ async SetColumnsDefaultWidth() {
649
+ const containerWidth = this.dataGridContainer?.nativeElement.offsetWidth;
650
+ this.columns = this.columnService.assignDefaultWidths(
651
+ this.columns,
652
+ containerWidth
653
+ );
654
+ await new Promise(resolve => setTimeout(resolve, 0));
655
+ this.cdr.detectChanges();
656
+ }
657
+
658
+
659
+ setSectionsWidth() {
660
+ const left = document.querySelector('.left-pinned-body') as HTMLElement;
661
+ const center = document.querySelector(
662
+ '.center-scrollable-body'
663
+ ) as HTMLElement;
664
+ const right = document.querySelector('.right-pinned-body') as HTMLElement;
665
+ if (left) {
666
+ left.style.minWidth = `${this.leftPinnedHeader.nativeElement.offsetWidth}`;
667
+ }
668
+ if (center) {
669
+ left.style.minWidth = `${this.centerPinnedHeader.nativeElement.offsetWidth}`;
670
+ }
671
+ if (right) {
672
+ left.style.minWidth = `${this.rightPinnedHeader.nativeElement.offsetWidth}`;
673
+ }
674
+ }
675
+
676
+ // onCenterBodyScroll(): void {
677
+ // const scrollTop = this.centerPinnedBody.nativeElement.scrollTop;
678
+ // const scrollLeft = this.centerPinnedBody.nativeElement.scrollLeft;
679
+
680
+ // // Sync vertical scroll with left & right pinned bodies
681
+ // this.leftPinnedBody.nativeElement.scrollTop = scrollTop;
682
+ // this.rightPinnedBody.nativeElement.scrollTop = scrollTop;
683
+
684
+ // // Sync horizontal scroll with center header
685
+ // this.centerPinnedHeader.nativeElement.scrollLeft = scrollLeft;
686
+ // this.centerFakeScrollbar.nativeElement.scrollLeft = scrollLeft;
687
+ // }
688
+
689
+ onLeftBodyScroll(): void {
690
+ const scrollTop = this.leftPinnedBody.nativeElement.scrollTop;
691
+ this.centerPinnedBody.nativeElement.scrollTop = scrollTop;
692
+ this.rightPinnedBody.nativeElement.scrollTop = scrollTop;
693
+ }
694
+ onRightBodyScroll(): void {
695
+ const scrollTop = this.rightPinnedBody.nativeElement.scrollTop;
696
+ this.centerPinnedBody.nativeElement.scrollTop = scrollTop;
697
+ this.leftPinnedBody.nativeElement.scrollTop = scrollTop;
698
+ }
699
+
700
+ fakeScrollbarScrollLeft = 0;
701
+ onFakeScroll(event: Event) {
702
+ const target = event.target as HTMLElement;
703
+ this.fakeScrollbarScrollLeft = target.scrollLeft;
704
+ this.centerPinnedBody.nativeElement.scrollLeft = target.scrollLeft;
705
+ this.centerPinnedHeader.nativeElement.scrollLeft = target.scrollLeft;
706
+ }
707
+
708
+
709
+
710
+
711
+ getNestedValue(obj: any, field: string): any {
712
+ return field.split('.').reduce((acc, part) => acc && acc[part], obj);
713
+ }
714
+
715
+ isNestedValueArray(obj: any, field: string): boolean {
716
+ if (!obj || !field) return false;
717
+ const value = field
718
+ .split('.')
719
+ .reduce((acc, part) => (acc && acc[part] !== undefined ? acc[part] : undefined), obj);
720
+ return Array.isArray(value);
721
+ }
722
+
723
+ onResizeGroup(event: MouseEvent, col: any, isRightPinned?: boolean): void {
724
+ event.preventDefault();
725
+ event.stopPropagation();
726
+
727
+ const startX = event.clientX;
728
+ const children = col.children || [];
729
+ if (!children.length) return;
730
+
731
+ const childWidths: { field: string; width: number }[] = children.map(
732
+ (child: any) => {
733
+ const el = document.querySelector(
734
+ `[field="${child.field}"]`
735
+ ) as HTMLElement;
736
+ return {
737
+ field: child.field,
738
+ width: el?.offsetWidth || 0,
739
+ };
740
+ }
741
+ );
742
+
743
+ const totalInitialWidth = childWidths.reduce(
744
+ (sum, col) => sum + col.width,
745
+ 0
746
+ );
747
+
748
+ const onMouseMove = (moveEvent: MouseEvent) => {
749
+ let deltaX = moveEvent.clientX - startX;
750
+
751
+ if (isRightPinned) {
752
+ deltaX = -deltaX;
753
+ }
754
+
755
+ // Prevent shrinking too small
756
+ if (totalInitialWidth + deltaX < children.length * 80) return;
757
+
758
+ let totalNewWidth = 0;
759
+
760
+ childWidths.forEach((child) => {
761
+ const ratio = child.width / totalInitialWidth;
762
+ const newWidth = Math.max(child.width + deltaX * ratio, 80);
763
+ totalNewWidth += newWidth;
764
+
765
+ const childEls = document.querySelectorAll(`[field="${child.field}"]`);
766
+ childEls.forEach((el: Element) => {
767
+ const elHtml = el as HTMLElement;
768
+ elHtml.style.minWidth = `${newWidth}px`;
769
+ elHtml.style.width = `${newWidth}px`;
770
+ });
771
+
772
+ this.updateColumnWidthInSourceByField(child.field, newWidth);
773
+ });
774
+
775
+ // ✅ Update group header width in DOM
776
+ const groupHeaderEl = document.querySelector(
777
+ `[group="${col.header}"]`
778
+ ) as HTMLElement;
779
+ if (groupHeaderEl) {
780
+ groupHeaderEl.style.width = `${totalNewWidth}px`;
781
+ this.cdr.detectChanges();
782
+ }
783
+ };
784
+
785
+ const onMouseUp = () => {
786
+ if (this.tableFilterViewId) {
787
+ this.savePreset('mouseUp');
788
+ }
789
+ this.refreshHeaders();
790
+ window.removeEventListener('mousemove', onMouseMove);
791
+ window.removeEventListener('mouseup', onMouseUp);
792
+ const event = {
793
+ columns: this.columns,
794
+ type: 'createUpdateConfigListing',
795
+ }
796
+ this.createUpdateConfigListing.emit(event);
797
+ };
798
+
799
+ window.addEventListener('mousemove', onMouseMove);
800
+ window.addEventListener('mouseup', onMouseUp);
801
+ }
802
+
803
+ private updateColumnWidthInSourceByField(field: string, width: number): void {
804
+ const update = (columns: any[]) => {
805
+ for (const col of columns) {
806
+ if (col.children?.length) {
807
+ update(col.children);
808
+ } else if (col.field === field) {
809
+ col.width = width;
810
+ }
811
+ }
812
+ };
813
+
814
+ update(this.columns);
815
+ this.cdr.detectChanges();
816
+ }
817
+
818
+ onResizeColumn(event: MouseEvent, col: any): void {
819
+ event.preventDefault();
820
+ event.stopPropagation();
821
+
822
+ const startX = event.clientX;
823
+
824
+ const targetEl = document.querySelector(
825
+ `[field="${col.field}"]`
826
+ ) as HTMLElement;
827
+ const initialWidth = targetEl?.offsetWidth || 150;
828
+
829
+ const matchingEls = document.querySelectorAll(`[field="${col.field}"]`);
830
+
831
+ // 👉 Add highlight while resizing
832
+ if (col?.pinned == 'right') {
833
+ matchingEls.forEach((el: Element) => {
834
+ (el as HTMLElement).classList.add('resizing-highlight-right');
835
+ });
836
+ } else {
837
+ matchingEls.forEach((el: Element) => {
838
+ (el as HTMLElement).classList.add('resizing-highlight');
839
+ });
840
+ }
841
+
842
+ const onMouseMove = (moveEvent: MouseEvent) => {
843
+ let deltaX = moveEvent.clientX - startX;
844
+
845
+ // 👉 Reverse if the column is pinned to the right
846
+ if (col.pinned === 'right') {
847
+ deltaX = -deltaX;
848
+ }
849
+
850
+ let newWidth = initialWidth + deltaX;
851
+ if (newWidth < 80) return;
852
+
853
+ matchingEls.forEach((el: Element) => {
854
+ const element = el as HTMLElement;
855
+ element.style.minWidth = `${newWidth}px`;
856
+ element.style.width = `${newWidth}px`;
857
+ });
858
+
859
+ this.updateColumnWidthInSourceByField(col.field, newWidth);
860
+ col.width = newWidth;
861
+ };
862
+
863
+ const onMouseUp = () => {
864
+ if (this.tableFilterViewId) {
865
+ this.savePreset('mouseUp');
866
+ }
867
+ this.refreshHeaders();
868
+ const event = {
869
+ columns: this.columns,
870
+ type: 'createUpdateConfigListing',
871
+ }
872
+ this.createUpdateConfigListing.emit(event);
873
+
874
+ matchingEls.forEach((el: Element) => {
875
+ (el as HTMLElement).classList.remove('resizing-highlight');
876
+ (el as HTMLElement).classList.remove('resizing-highlight-right');
877
+ });
878
+
879
+ window.removeEventListener('mousemove', onMouseMove);
880
+ window.removeEventListener('mouseup', onMouseUp);
881
+ };
882
+
883
+ window.addEventListener('mousemove', onMouseMove);
884
+ window.addEventListener('mouseup', onMouseUp);
885
+ }
886
+
887
+
888
+
889
+ onResizeGroupBox(event: MouseEvent): void {
890
+ event.preventDefault();
891
+ event.stopPropagation();
892
+
893
+ const startX = event.clientX;
894
+
895
+ const targetEl = document.getElementById('groupBoxHeaderDiv');
896
+ const initialWidth = targetEl?.offsetWidth || 150;
897
+
898
+ const onMouseMove = (moveEvent: MouseEvent) => {
899
+ const deltaX = moveEvent.clientX - startX;
900
+
901
+ let newWidth = initialWidth + deltaX;
902
+ if (newWidth < 100) newWidth = 100;
903
+
904
+ if (targetEl) {
905
+ targetEl.style.width = `${newWidth}px`;
906
+ targetEl.style.minWidth = `${newWidth}px`;
907
+ this.groupBoxPadding = newWidth;
908
+ }
909
+ };
910
+
911
+ const onMouseUp = () => {
912
+ window.removeEventListener('mousemove', onMouseMove);
913
+ window.removeEventListener('mouseup', onMouseUp);
914
+ };
915
+
916
+ window.addEventListener('mousemove', onMouseMove);
917
+ window.addEventListener('mouseup', onMouseUp);
918
+ }
919
+
920
+
921
+ onFilterChange(col: any): void {
922
+ const filterValue = col.filterValue?.toLowerCase() || '';
923
+ }
924
+
925
+ // Get Body Height
926
+ get bodyWrapperHeight(): string {
927
+ const rows = this.showColumnsGrouping && this.showFilterRow ? 3 : (this.showColumnsGrouping || this.showFilterRow ? 2 : 1);
928
+ const offset = this.rowHeight * rows + 17;
929
+ return `calc(100% - ${offset}px)`;
930
+ }
931
+
932
+ // Row Hover Logic Here
933
+ hoveredRowId: string | number | null = null;
934
+ onRowHover(row: any): void {
935
+ this.hoveredRowId = row._id || row.id;
936
+ }
937
+
938
+ onRowLeave(): void {
939
+ this.hoveredRowId = null;
940
+ }
941
+
942
+ @HostListener('document:click', ['$event'])
943
+ onDocumentClick(event: MouseEvent): void {
944
+ const target = event.target as HTMLElement;
945
+ this.isVisible = false;
946
+ if (
947
+ !this.hasParentWithClass(target, [
948
+ 'three-dots',
949
+ 'filter-icon-wrapper',
950
+ 'column-menu',
951
+ 'filter-menu',
952
+ ])
953
+ ) {
954
+ this.activeCol = null;
955
+ this.activeFilterCell = null;
956
+ }
957
+ if (!this.elementRef.nativeElement.contains(event.target)) {
958
+ this.showActionsDropDown = false;
959
+ }
960
+ const insideTopFilterRow = target.closest('.filter-tags') || target.closest('.add-filter-button-menu') ||
961
+ target.closest('.table-right-top-actions') || target.closest('.table-layout') || target.closest('.filter-row-filter-wrapper');
962
+ const isClickedInsideThreeDotsFilter = target.closest('.three-dots-filter') || target.closest('.filter-menu-container');
963
+ if (!isClickedInsideThreeDotsFilter) {
964
+ this.closeThreeDotsMenuFilter();
965
+ }
966
+ if (!insideTopFilterRow) {
967
+ this.closeFilterDropdowns(event);
968
+ }
969
+ if (!target.closest('.set-default-preset')) {
970
+ document.querySelector('.set-default-preset')?.classList?.remove('show');
971
+ }
972
+
973
+ if (!target.closest('.data-grid-table-wrapper')) {
974
+ this.selectedKeys.clear();
975
+ this.activeCell = '';
976
+ }
977
+ }
978
+
979
+
980
+ closeThreeDotsMenuFilter() {
981
+ this.isThreeDotsFilterOpen = false;
982
+ }
983
+
984
+
985
+ closeFilterDropdowns(event: MouseEvent) {
986
+ this.isFilterOpen = false;
987
+ this.isActiveFilterOpen = false;
988
+ this.activeTopButton = '';
989
+ this.activeFilterCell = null;
990
+ }
991
+
992
+ private hasParentWithClass(
993
+ element: HTMLElement,
994
+ classList: string[]
995
+ ): boolean {
996
+ let el: HTMLElement | null = element;
997
+
998
+ while (el !== null) {
999
+ if (
1000
+ el.classList &&
1001
+ classList.some((className) => el!.classList.contains(className))
1002
+ ) {
1003
+ return true;
1004
+ }
1005
+ el = el.parentElement;
1006
+ }
1007
+
1008
+ return false;
1009
+ }
1010
+
1011
+ isMenueHidden = false;
1012
+ openThreeDotsMenu(event: MouseEvent, child: any) {
1013
+ event.preventDefault();
1014
+ event.stopPropagation();
1015
+ this.isMenueHidden = true;
1016
+ const containerWidth = this.dataGridContainer?.nativeElement?.offsetWidth;
1017
+ this.activeCol = child;
1018
+ this.activeFilterCell = null;
1019
+ const x = event.clientX;
1020
+ setTimeout(() => {
1021
+ const element = document.querySelector('.column-menu') as HTMLElement;
1022
+ if (element) {
1023
+ if (x > (containerWidth / 2)) {
1024
+ element.style.left = `${x - 240}px`;
1025
+ } else {
1026
+ element!.style.left = `${x}px`;
1027
+ }
1028
+ }
1029
+ this.isMenueHidden = false;
1030
+ this.cdr.detectChanges();
1031
+ }, 0);
1032
+ }
1033
+ // ////////////////////////////////////////////////////////////////////////////////////////
1034
+ // ////////////////////////////////////////////////////////////////////////////////////////
1035
+ // Column Three Dots Actions
1036
+ // //////////////////////////////////////////////////////////////////////////////////////
1037
+ // //////////////////////////////////////////////////////////////////////////////////////
1038
+ sortAsc(col: any) {
1039
+ if (!col.is_sortable) return;
1040
+ col!.order_by = 'asc';
1041
+ const event = {
1042
+ order: 'asc',
1043
+ column: col
1044
+ }
1045
+ this.cdr.detectChanges();
1046
+ this.sortingOrderOptions.emit(event);
1047
+ }
1048
+
1049
+ sortDesc(col: any) {
1050
+ if (!col.is_sortable) return;
1051
+ col!.order_by = 'desc';
1052
+ const event = {
1053
+ order: 'desc',
1054
+ column: col
1055
+ }
1056
+ this.cdr.detectChanges();
1057
+ this.sortingOrderOptions.emit(event);
1058
+ }
1059
+
1060
+ resetSort(col: any) {
1061
+ this.sortingConfig = { field: '', order_by: '' };
1062
+ const event = { field: null, order_by: null }
1063
+ this.cdr.detectChanges();
1064
+ this.sortingOrderOptions.emit(event);
1065
+ }
1066
+
1067
+
1068
+ async updateColumnPinInSourceByField(
1069
+ column: any,
1070
+ pinned: 'left' | 'right' | null
1071
+ ) {
1072
+ const update = (columns: any[]) => {
1073
+ for (const col of columns) {
1074
+ if (col.children?.length) {
1075
+ update(col.children);
1076
+ } else if (col.field === column.field) {
1077
+ col.pinned = pinned;
1078
+ }
1079
+ }
1080
+ };
1081
+ this.activeCol = null;
1082
+ update(this.columns);
1083
+ this.updateColumnWidthsAndGroups();
1084
+ this.refreshPreviewColumns();
1085
+ this.cdr.detectChanges();
1086
+ const event = {
1087
+ columns: this.columns,
1088
+ type: 'createUpdateConfigListing',
1089
+ }
1090
+ this.createUpdateConfigListing.emit(event);
1091
+ }
1092
+
1093
+ autosizeColumn(cols: any | any[]): void {
1094
+ this.activeCol = null;
1095
+
1096
+ // Normalize input to an array
1097
+ const columnsToResize = Array.isArray(cols) ? cols : [cols];
1098
+
1099
+ // Helper: Flatten all visible leaf columns
1100
+ const getVisibleLeafColumns = (columns: any[]): any[] => {
1101
+ const result: any[] = [];
1102
+ for (const column of columns) {
1103
+ if (column.children && Array.isArray(column.children)) {
1104
+ result.push(...getVisibleLeafColumns(column.children));
1105
+ } else if (column.is_visible) {
1106
+ result.push(column);
1107
+ }
1108
+ }
1109
+ return result;
1110
+ };
1111
+
1112
+ const visibleColumns = getVisibleLeafColumns(this.columns);
1113
+ const visibleCount = visibleColumns.length;
1114
+
1115
+ if (visibleCount === 0) return;
1116
+
1117
+ const containerWidth =
1118
+ this.dataGridContainer?.nativeElement?.offsetWidth ?? 0;
1119
+ const equalWidth = Math.floor(containerWidth / visibleCount);
1120
+
1121
+ // Resize each passed column
1122
+ for (const col of columnsToResize) {
1123
+ if (col && col.field) {
1124
+ col.width = equalWidth;
1125
+ this.updateColumnWidthInSourceByField(col.field, equalWidth);
1126
+ }
1127
+ }
1128
+
1129
+ this.updateColumnWidthsAndGroups();
1130
+
1131
+ setTimeout(() => {
1132
+ const sendData = {
1133
+ columns: this.columns,
1134
+ filters: this.filtersConfig,
1135
+ no_of_records: this.paginationConfig.pageSize,
1136
+ type: this.tableType,
1137
+ };
1138
+ this.createUpdateConfigListing.emit(sendData);
1139
+ }, 1000);
1140
+ this.refreshPreviewColumns();
1141
+ }
1142
+
1143
+ getGroupWidth(group: any): number {
1144
+ return (
1145
+ group?.children
1146
+ ?.filter((col: any) => col?.is_visible)
1147
+ ?.reduce((acc: number, col: any) => acc + (col?.width || 0), 0) || 0
1148
+ );
1149
+ }
1150
+
1151
+ autosizeAllColumns(): void {
1152
+ this.activeCol = null;
1153
+
1154
+ const getVisibleLeafColumns = (columns: any[]): any[] => {
1155
+ const result: any[] = [];
1156
+
1157
+ for (const column of columns) {
1158
+ if (column.children && Array.isArray(column.children)) {
1159
+ result.push(...getVisibleLeafColumns(column.children));
1160
+ } else if (column.is_visible !== false) {
1161
+ result.push(column);
1162
+ }
1163
+ }
1164
+
1165
+ return result;
1166
+ };
1167
+
1168
+ const visibleColumns = getVisibleLeafColumns(this.columns);
1169
+ const visibleCount = visibleColumns.length;
1170
+
1171
+ if (visibleCount === 0) return;
1172
+
1173
+ const containerWidth =
1174
+ this.dataGridContainer?.nativeElement?.offsetWidth ?? 0;
1175
+ const equalWidth = Math.max(Math.floor(containerWidth / visibleCount), 150);
1176
+
1177
+ // Update widths for all visible columns
1178
+ visibleColumns.forEach((col) => {
1179
+ col.width = equalWidth;
1180
+ this.updateColumnWidthInSourceByField(col.field, equalWidth);
1181
+ });
1182
+
1183
+ this.refreshHeaders();
1184
+ this.cdr.detectChanges();
1185
+ setTimeout(() => {
1186
+ const sendData = {
1187
+ columns: this.columns,
1188
+ filters: this.filtersConfig,
1189
+ no_of_records: this.paginationConfig.pageSize,
1190
+ type: this.tableType,
1191
+ };
1192
+ this.createUpdateConfigListing.emit(sendData);
1193
+ }, 1000);
1194
+ }
1195
+
1196
+ private markColumnAsGrouped(columns: any[], field: string): void {
1197
+ for (const col of columns) {
1198
+ if (col.field === field) {
1199
+ col.isRowGrouped = true;
1200
+ return;
1201
+ }
1202
+ if (col.children?.length) {
1203
+ this.markColumnAsGrouped(col.children, field);
1204
+ }
1205
+ }
1206
+ }
1207
+
1208
+ async groupBy(col: any) {
1209
+ this.activeCol = null;
1210
+ if (!col) return;
1211
+ this.loading = true;
1212
+ this.cdr.detectChanges();
1213
+ if (col?.isGroupable) {
1214
+ this.groupedColumns.push(col);
1215
+ this.markColumnAsGrouped(this.columns, col.field);
1216
+ }
1217
+ const fields = this.groupedColumns.map((item) => item.field);
1218
+ this.dataSet = await this.groupDataAsync(this.originalDataSet, fields);
1219
+ setTimeout(() => {
1220
+ this.updateFlattenedData();
1221
+ this.updateColumnWidthsAndGroups();
1222
+ this.refreshPreviewColumns();
1223
+ this.updateVisibleRows(0);
1224
+ this.loading = false;
1225
+ this.cdr.detectChanges();
1226
+ }, 100);
1227
+
1228
+ setTimeout(() => {
1229
+ this.groupBoxPadding = this.columnsGroupedBox?.nativeElement.offsetWidth;
1230
+ this.cdr.detectChanges()
1231
+ }, 10);
1232
+ }
1233
+
1234
+ async groupDataAsync(data: any[], groupFields: string[]): Promise<any[]> {
1235
+ return new Promise(resolve => {
1236
+ setTimeout(() => {
1237
+ const result = this.groupData(data, groupFields);
1238
+ resolve(result);
1239
+ }, 0);
1240
+ });
1241
+ }
1242
+
1243
+
1244
+
1245
+
1246
+ chooseColumns() {
1247
+ this.activeCol = null;
1248
+ this.showColumnPanel = true;
1249
+ }
1250
+
1251
+ resetColumns() {
1252
+ this.activeCol = null;
1253
+
1254
+ this.columns = structuredClone(this.originalColumns);
1255
+ this.SetColumnsDefaultWidth();
1256
+ setTimeout(() => {
1257
+ this.updateColumnWidthsAndGroups();
1258
+ this.refreshPreviewColumns();
1259
+ this.cdr.detectChanges();
1260
+ }, 100);
1261
+ }
1262
+
1263
+ clearFilter(col: any) {
1264
+ col.firstValue = '';
1265
+ col.secondValue = '';
1266
+ col.firstCondition = 'contains';
1267
+ col.secondCondition = 'contains';
1268
+ col.logic = 'AND';
1269
+ col.selectAll = false;
1270
+ col.selectedOptions = {};
1271
+ col.searchValue = '';
1272
+
1273
+ }
1274
+
1275
+
1276
+ applyFilter(col: any) {
1277
+ this.filterColumnsList.push(col);
1278
+ }
1279
+
1280
+ // Rows Selection Logic Goes Here
1281
+
1282
+ selectedRows: Set<string> = new Set();
1283
+
1284
+ /**
1285
+ * Get normalized ID from row.
1286
+ */
1287
+ private getRowId(row: any): string {
1288
+ return row.id ?? row._id;
1289
+ }
1290
+
1291
+ /**
1292
+ * Toggle a single row selection.
1293
+ */
1294
+ toggleRowSelection(row: any): void {
1295
+ const id = this.getRowId(row);
1296
+ if (this.selectedRows.has(id)) {
1297
+ this.selectedRows.delete(id);
1298
+ } else {
1299
+ this.selectedRows.add(id);
1300
+ }
1301
+
1302
+ const ids = Array.from(this.selectedRows);
1303
+ const event = {
1304
+ data: {
1305
+ listingType: this.listingType,
1306
+ obj: ids
1307
+ },
1308
+ eventType: "onSelectRow",
1309
+ }
1310
+ this.genericEvent.emit(event);
1311
+ }
1312
+
1313
+
1314
+ toggleSelectAll(data: any[]): void {
1315
+ const allIds = this.originalDataSet.map((row) => this.getRowId(row));
1316
+ const allSelected = allIds.every((id) => this.selectedRows.has(id));
1317
+
1318
+ if (allSelected) {
1319
+ allIds.forEach((id) => this.selectedRows.delete(id));
1320
+ } else {
1321
+ allIds.forEach((id) => this.selectedRows.add(id));
1322
+ }
1323
+ this.cdr.detectChanges();
1324
+ const ids = Array.from(this.selectedRows);
1325
+ const event = {
1326
+ data: {
1327
+ listingType: this.listingType,
1328
+ obj: ids
1329
+ },
1330
+ eventType: "onSelectRow",
1331
+ }
1332
+ this.genericEvent.emit(event);
1333
+ }
1334
+
1335
+ isRowSelected(row: any): boolean {
1336
+ return this.selectedRows.has(this.getRowId(row));
1337
+ }
1338
+
1339
+ isAllSelected(data: any[]): boolean {
1340
+ return (data?.every((row) => this.selectedRows.has(this.getRowId(row))) && (this.dataSet?.length > 0));
1341
+ }
1342
+
1343
+ isIndeterminateState(data: any[]): boolean {
1344
+ if (!data || data.length === 0) return false;
1345
+ const selectedCount = data.filter(row => this.selectedRows.has(this.getRowId(row))).length;
1346
+ return selectedCount > 0 && selectedCount < data.length;
1347
+ }
1348
+
1349
+ // Side Menu Working Goes Here
1350
+ toggleSideMenu(clickedOn: 'cols' | 'filtrs') {
1351
+ if (this.currentOpenedSideMenue == clickedOn) {
1352
+ this.currentOpenedSideMenue = clickedOn;
1353
+ this.sideMenuVisible = !this.sideMenuVisible;
1354
+ } else if (
1355
+ this.sideMenuVisible &&
1356
+ clickedOn != this.currentOpenedSideMenue
1357
+ ) {
1358
+ this.currentOpenedSideMenue = clickedOn;
1359
+ return;
1360
+ } else {
1361
+ this.currentOpenedSideMenue = clickedOn;
1362
+ this.sideMenuVisible = !this.sideMenuVisible;
1363
+ }
1364
+ }
1365
+
1366
+ toggleGroupVisibility(col: any) {
1367
+ const allVisible = col.children.every((child: any) => child.is_visible);
1368
+ col.children.forEach((child: any) => (child.is_visible = !allVisible));
1369
+ const event = {
1370
+ columns: this.columns,
1371
+ type: 'createUpdateConfigListing',
1372
+ }
1373
+ this.createUpdateConfigListing.emit(event);
1374
+ setTimeout(() => {
1375
+ this.updateColumnWidthsAndGroups();
1376
+ this.refreshPreviewColumns();
1377
+ this.SetColumnsDefaultWidth();
1378
+ this.cdr.detectChanges();
1379
+ }, 10);
1380
+ }
1381
+
1382
+ isColumnVisible(columns: any) {
1383
+ if (columns?.children) {
1384
+ return columns.children.every((element: any) => element.is_visible);
1385
+ }
1386
+
1387
+ return columns?.is_visible;
1388
+ }
1389
+
1390
+ allColumnsSelected(): boolean {
1391
+ const flatten = this.flattenColumns(this.columns);
1392
+ return flatten.every((col) => col.is_visible);
1393
+ }
1394
+
1395
+
1396
+ private findColumnByField(field: string, cols: any[]): any | null {
1397
+ for (const col of cols) {
1398
+ if (col.field === field) {
1399
+ return col;
1400
+ }
1401
+ if (col.children && Array.isArray(col.children)) {
1402
+ const found = this.findColumnByField(field, col.children);
1403
+ if (found) return found;
1404
+ }
1405
+ }
1406
+ return null;
1407
+ }
1408
+
1409
+
1410
+
1411
+ async toggleColumnVisibility(column: any, isVisible: boolean) {
1412
+ const col = this.findColumnByField(column.field, this.columns);
1413
+ if (col) {
1414
+ col.is_visible = isVisible;
1415
+ }
1416
+ await this.refreshHeaders();
1417
+ this.cdr.detectChanges();
1418
+
1419
+ const event = {
1420
+ columns: this.columns,
1421
+ type: 'createUpdateConfigListing',
1422
+ };
1423
+ this.createUpdateConfigListing.emit(event);
1424
+ if (this.tableFilterViewId) {
1425
+ this.savePreset('mouseUp');
1426
+ }
1427
+ }
1428
+
1429
+
1430
+ toggleAllColumnsVisibility(): void {
1431
+ const flatten = this.flattenColumns(this.columns);
1432
+ const allSelected = flatten.every((col: any) => col.is_visible);
1433
+ flatten.forEach((col: any) => (col.is_visible = !allSelected));
1434
+ setTimeout(() => {
1435
+ this.updateColumnWidthsAndGroups();
1436
+ setTimeout(() => {
1437
+ this.refreshPreviewColumns();
1438
+ this.cdr.detectChanges();
1439
+ }, 100);
1440
+ }, 100);
1441
+ }
1442
+
1443
+ flattenColumns(cols: any[]): any[] {
1444
+ return cols.reduce((acc: any[], col: any) => {
1445
+ if (col?.children && col.children.length) {
1446
+ // recursively flatten children
1447
+ return acc.concat(this.flattenColumns(col.children));
1448
+ } else {
1449
+ // leaf column
1450
+ return acc.concat(col);
1451
+ }
1452
+ }, []);
1453
+ }
1454
+
1455
+
1456
+ filteredColumns(cols: any[]): any[] {
1457
+ const search = this.columnSearch.toLowerCase();
1458
+ return cols
1459
+ .map((col) => {
1460
+ if (col.children) {
1461
+ const filteredChildren = this.filteredColumns(col.children);
1462
+ if (
1463
+ col.header.toLowerCase().includes(search) ||
1464
+ filteredChildren.length
1465
+ ) {
1466
+ return { ...col, children: filteredChildren };
1467
+ }
1468
+ return null;
1469
+ } else {
1470
+ return col.header.toLowerCase().includes(search) ? col : null;
1471
+ }
1472
+ })
1473
+ .filter(Boolean);
1474
+ }
1475
+
1476
+ get accordionState(): 'all' | 'none' | 'some' {
1477
+ const groups = this.getAllGroupColumns(this.columns);
1478
+ const expandedCount = groups.filter((col) => col.expanded).length;
1479
+
1480
+ if (expandedCount === 0) return 'none';
1481
+ if (expandedCount === groups.length) return 'all';
1482
+ return 'some';
1483
+ }
1484
+
1485
+ get filterAccordionState(): 'all' | 'none' | 'some' {
1486
+ const groups = this.getAllGroupColumns(this.columns);
1487
+ const expandedCount = groups.filter((col) => col.expandedFilter).length;
1488
+
1489
+ if (expandedCount === 0) return 'none';
1490
+ if (expandedCount === groups.length) return 'all';
1491
+ return 'some';
1492
+ }
1493
+
1494
+ // Recursively collect all group columns
1495
+ private getAllGroupColumns(cols: any[]): any[] {
1496
+ let groups: any[] = [];
1497
+ for (const col of cols) {
1498
+ if (col.children?.length) {
1499
+ groups.push(col);
1500
+ groups = groups.concat(this.getAllGroupColumns(col.children));
1501
+ }
1502
+ }
1503
+ return groups;
1504
+ }
1505
+
1506
+ // Toggle based on current state
1507
+ toggleAllAccordions(): void {
1508
+ const nextState = this.accordionState === 'all' ? false : true;
1509
+ this.setAccordionState(this.columns, nextState);
1510
+ }
1511
+
1512
+ toggleAllFilterAccordions(): void {
1513
+ const nextState = this.filterAccordionState === 'all' ? false : true;
1514
+ this.setFilterAccordionState(this.columns, nextState);
1515
+ }
1516
+
1517
+ private setAccordionState(cols: any[], state: boolean): void {
1518
+ for (let col of cols) {
1519
+ if (col.children?.length) {
1520
+ col.expanded = state;
1521
+ this.setAccordionState(col.children, state); // Recursively handle nested
1522
+ }
1523
+ }
1524
+ this.cdr.detectChanges();
1525
+ }
1526
+ private setFilterAccordionState(cols: any[], state: boolean): void {
1527
+ for (let col of cols) {
1528
+ if (col.children?.length) {
1529
+ col.expandedFilter = state;
1530
+ this.setFilterAccordionState(col.children, state); // Recursively handle nested
1531
+ }
1532
+ }
1533
+ }
1534
+
1535
+ showColumnPanel = false;
1536
+
1537
+ closeModalColumnPanel() {
1538
+ this.showColumnPanel = false;
1539
+ }
1540
+
1541
+ @HostListener('document:keydown.escape', ['$event'])
1542
+ onEscape(event: KeyboardEvent) {
1543
+ this.closeModalColumnPanel();
1544
+ }
1545
+
1546
+ // Check If Any column have right pinned
1547
+ get hasRightPinnedColumns() {
1548
+ return this.commonSevice.gethasRightPinnedColumns(this.columns);
1549
+ }
1550
+
1551
+ get hasLeftPinnedColumns() {
1552
+ return this.commonSevice.gethasLeftPinnedColumns(this.columns);
1553
+ }
1554
+
1555
+ get dataSetLength() {
1556
+ return this.commonSevice.getExpandedRowCount(this.dataSet);
1557
+ }
1558
+
1559
+
1560
+
1561
+ @HostListener('window:resize', ['$event'])
1562
+ onResize(event: UIEvent) {
1563
+ this.autosizeAllColumns();
1564
+ const h = this.mainScroll?.nativeElement?.clientHeight ?? 0;
1565
+ this.viewportRows = Math.max(1, Math.ceil(h / this.rowHeight));
1566
+ this.updateVisibleRows(this.mainScroll?.nativeElement?.scrollTop);
1567
+ this.cdr.detectChanges();
1568
+ setTimeout(() => { }, 100);
1569
+ }
1570
+
1571
+ async refreshHeaders() {
1572
+ this.ngZone.runOutsideAngular(async () => {
1573
+ await new Promise(r => setTimeout(r, 0));
1574
+ await this.updateColumnWidthsAndGroups();
1575
+ await this.refreshPreviewColumns();
1576
+
1577
+ this.ngZone.run(() => this.cdr.detectChanges());
1578
+ });
1579
+ }
1580
+
1581
+ get hasAnyVisibleColumn() {
1582
+ return this.commonSevice.gethasVisibleColumns(this.columns);
1583
+ }
1584
+
1585
+ get hasAnyInVisibleColumn() {
1586
+ return this.commonSevice.gethasInVisibleColumns(this.columns);
1587
+ }
1588
+
1589
+ get columnsCount() {
1590
+ return this.commonSevice.getTotalColumnsLength(this.columns);
1591
+ }
1592
+
1593
+ tableHeaderAndBodyHeight() {
1594
+ return `calc(100% - ${this.footerRowHeight})`;
1595
+ }
1596
+
1597
+ draggingInGroupArea = false;
1598
+
1599
+ @ViewChild('centerScroll') centerScroll!: ElementRef<HTMLDivElement>;
1600
+ visibleRowCount = 25;
1601
+ startIndex = 0;
1602
+ visibleRows: any[] = [];
1603
+
1604
+ private flattenGroupedRows(
1605
+ rows: any[] = [],
1606
+ level = 0,
1607
+ result: any[] = []
1608
+ ): any[] {
1609
+ for (const row of rows) {
1610
+ result.push({ ...row, __depth: level });
1611
+ if (row.isGroup && row.isExpand && row.children?.length) {
1612
+ this.flattenGroupedRows(row.children, level + 1, []);
1613
+ }
1614
+ }
1615
+ this.assignVirtualIndexes(this.visibleRows);
1616
+ return result;
1617
+ }
1618
+
1619
+ assignVirtualIndexes(data: any[]): any[] {
1620
+ let currentIndex = 1;
1621
+ const walk = (items: any[]): void => {
1622
+ for (const item of items) {
1623
+ item.__virtualIndex = currentIndex++;
1624
+ if (item?.isGroup) {
1625
+ const group = item;
1626
+ if (group.isExpand && Array.isArray(group.children)) {
1627
+ walk(group.children);
1628
+ }
1629
+ }
1630
+ }
1631
+ };
1632
+ walk(data);
1633
+ console.log('This dataset: ', this.visibleRows)
1634
+ return data;
1635
+ }
1636
+
1637
+
1638
+ updateVisibleRows(scrollTop: number) {
1639
+
1640
+ if (this.groupedColumns?.length) {
1641
+ this.visibleRows = this.dataSet;
1642
+ return;
1643
+ }
1644
+ const flatData = this.flattenGroupedRows(this.dataSet);
1645
+
1646
+ if (this.dataSet?.length <= 15) {
1647
+ this.visibleRows = flatData;
1648
+ this.firstIndex = 0;
1649
+ this.startIndex = 0;
1650
+ this.renderStart = 0;
1651
+ this.cdr.detectChanges();
1652
+ return;
1653
+ }
1654
+ const total = flatData.length;
1655
+ const maxFirst = Math.max(0, total - this.viewportRows);
1656
+ const first = Math.min(Math.floor(scrollTop / this.rowHeight), maxFirst);
1657
+ const start = Math.max(0, first);
1658
+ const end = Math.min(total, first + this.viewportRows);
1659
+
1660
+ this.firstIndex = first;
1661
+ this.startIndex = first;
1662
+ this.renderStart = start;
1663
+ this.visibleRows = flatData.slice(start, end);
1664
+ this.assignVirtualIndexes(this.visibleRows);
1665
+ this.cdr.detectChanges();
1666
+ }
1667
+
1668
+
1669
+ trackByRenderedIndex = (i: number, _row: any) => this.renderStart + i;
1670
+
1671
+ private isSyncingFromMain = false;
1672
+ private isSyncingFromFake = false;
1673
+ private mainScrollRaf: number | null = null;
1674
+
1675
+ trackById(index: number, item: any) {
1676
+ return item.id || item._id;
1677
+ }
1678
+
1679
+ flattenedData: any[] = [];
1680
+
1681
+ updateFlattenedData(): void {
1682
+ setTimeout(() => {
1683
+ this.flattenedData = this.flattenGroupedRows(this.dataSet);
1684
+ this.cdr.detectChanges();
1685
+ }, 10);
1686
+ }
1687
+
1688
+ // requestAnimationFrame IDs
1689
+ private fakeScrollRaf: number | null = null;
1690
+
1691
+
1692
+ // onMainScroll(event: Event) {
1693
+ // this.expandedCells.clear();
1694
+ // const scrollTop = (event.target as HTMLElement).scrollTop;
1695
+ // if (this.groupedColumns?.length > 0) {
1696
+ // this.startIndex = 0;
1697
+ // this.cdr.detectChanges();
1698
+ // return;
1699
+ // }
1700
+ // const overscan = this.overscan * 2;
1701
+ // this.startIndex = Math.floor(scrollTop / this.rowHeight);
1702
+ // const start = Math.max(0, this.startIndex - overscan);
1703
+ // const end = Math.min(this.flattenedData.length, this.startIndex + this.viewportRows + overscan);
1704
+ // this.visibleRows = this.flattenedData.slice(start, end);
1705
+ // this.cdr.detectChanges();
1706
+ // }
1707
+ translateY = 0;
1708
+
1709
+ lastScrollTop = 0;
1710
+ onMainScroll(event: Event): void {
1711
+ this.expandedCells.clear();
1712
+ if (this.isSyncingFromFake) {
1713
+ this.isSyncingFromFake = false;
1714
+ return;
1715
+ }
1716
+ const scrollTop = (event.target as HTMLElement).scrollTop;
1717
+ if (Math.abs(scrollTop - this.lastScrollTop) < this.rowHeight * 3) {
1718
+ return;
1719
+ }
1720
+ this.lastScrollTop = scrollTop;
1721
+
1722
+ if (this.fakeScrollRaf) cancelAnimationFrame(this.fakeScrollRaf);
1723
+
1724
+ this.fakeScrollRaf = requestAnimationFrame(() => {
1725
+ if (this.groupedColumns?.length > 0) {
1726
+ this.startIndex = 0;
1727
+ this.cdr.detectChanges();
1728
+ return;
1729
+ }
1730
+
1731
+ const overscan = 15;
1732
+ const newStartIndex = Math.floor(scrollTop / this.rowHeight);
1733
+ const start = Math.max(0, newStartIndex - overscan);
1734
+ const end = Math.min(
1735
+ this.flattenedData.length,
1736
+ newStartIndex + this.viewportRows + overscan
1737
+ );
1738
+
1739
+ this.translateY = start * (this.rowHeight - 1);
1740
+ this.startIndex = start;
1741
+ this.visibleRows = this.dataSet.slice(start, end);
1742
+ this.cdr.detectChanges();
1743
+ });
1744
+ }
1745
+
1746
+
1747
+ get isScrollbarVisible(): boolean {
1748
+ if (!this.mainScroll) return false;
1749
+ const el = this.mainScroll.nativeElement;
1750
+ return el.scrollHeight > el.clientHeight;
1751
+ }
1752
+
1753
+ toggleExpand(row: any) {
1754
+ if (row.details) {
1755
+ row.isDetailsExpand = !row.isDetailsExpand;
1756
+ } else {
1757
+ row.isExpand = !row.isExpand;
1758
+ }
1759
+ this.rowSelectedIndexes.clear();
1760
+ this.updateFlattenedData();
1761
+ this.cdr.detectChanges();
1762
+ // setTimeout(() => {
1763
+ // this.cdr.detectChanges();
1764
+ // }, 100);
1765
+ }
1766
+
1767
+ onMainFakeScroll(event: Event) {
1768
+ if (this.isSyncingFromMain) {
1769
+ this.isSyncingFromMain = false;
1770
+ return;
1771
+ }
1772
+ if (this.groupedColumns?.length > 0) {
1773
+ this.startIndex = 0;
1774
+ this.cdr.detectChanges();
1775
+ return;
1776
+ }
1777
+ const scrollTop = (event.target as HTMLElement).scrollTop;
1778
+ if (this.mainScrollRaf) cancelAnimationFrame(this.mainScrollRaf);
1779
+ this.mainScrollRaf = requestAnimationFrame(() => {
1780
+ this.isSyncingFromFake = true;
1781
+ this.mainScroll.nativeElement.scrollTop = scrollTop;
1782
+ const overscan = this.overscan ?? 0;
1783
+ this.startIndex = Math.floor(scrollTop / this.rowHeight);
1784
+ const start = Math.max(0, this.startIndex - overscan);
1785
+ const end = Math.min(
1786
+ this.flattenedData.length,
1787
+ this.startIndex + this.viewportRows + overscan
1788
+ );
1789
+ this.visibleRows = this.flattenedData.slice(start, end);
1790
+ this.cdr.detectChanges();
1791
+ });
1792
+ }
1793
+
1794
+
1795
+
1796
+
1797
+
1798
+ viewportRows = 0; // how many rows fit in viewport (computed)
1799
+ firstIndex = 0; // index of the first visible row (clamped)
1800
+ renderStart = 0; // where the slice actually starts (firstIndex - overscan, clamped)
1801
+
1802
+ private scrollRaf: number | null = null;
1803
+ private pendingScrollTop = 0;
1804
+
1805
+ @ViewChild('mainScroll') mainScroll!: ElementRef<HTMLDivElement>;
1806
+ @ViewChild('fakeScroll') fakeScroll!: ElementRef<HTMLDivElement>;
1807
+ @ViewChild('horizintalFakeScroll')
1808
+ horizintalFakeScroll!: ElementRef<HTMLDivElement>;
1809
+ @ViewChild('centerScrollableBody')
1810
+ centerScrollableBody!: ElementRef<any>;
1811
+ private overscan = 10; // buffer rows above and below
1812
+
1813
+ computeViewportRows() {
1814
+ if (this.fakeScroll?.nativeElement) {
1815
+ this.fakeScroll.nativeElement.scrollTop = 0;
1816
+ }
1817
+
1818
+ if (this.mainScroll?.nativeElement) {
1819
+ this.mainScroll.nativeElement.scrollTop = 0;
1820
+ const h = this.mainScroll.nativeElement.clientHeight ?? 0;
1821
+
1822
+ // rows visible in viewport
1823
+ const rowsInViewport = Math.max(1, Math.ceil(h / this.rowHeight));
1824
+
1825
+ // add buffer rows (overscan)
1826
+ this.viewportRows = rowsInViewport + this.overscan * 2;
1827
+
1828
+ this.cdr.detectChanges();
1829
+ }
1830
+ }
1831
+
1832
+
1833
+ onHorizintalFakeScroll(event: Event) {
1834
+ const scrollLeft = (event.target as HTMLElement).scrollLeft;
1835
+ this.centerPinnedHeader.nativeElement.scrollLeft = scrollLeft;
1836
+ this.centerScrollableBody.nativeElement.scrollLeft = scrollLeft;
1837
+ }
1838
+
1839
+ onCenterBodyScroll(event: Event) {
1840
+ const target = event.target as HTMLElement;
1841
+ this.horizintalFakeScroll.nativeElement.scrollLeft = target.scrollLeft;
1842
+ if (this.centerPinnedHeader) {
1843
+ this.centerPinnedHeader.nativeElement.scrollLeft = target.scrollLeft;
1844
+ }
1845
+ }
1846
+
1847
+ onCenterHeaderScroll(event: Event) {
1848
+ const target = event.target as HTMLElement;
1849
+ if (this.horizintalFakeScroll?.nativeElement) {
1850
+ this.horizintalFakeScroll.nativeElement.scrollLeft = target.scrollLeft;
1851
+ }
1852
+ if (this.centerScrollableBody?.nativeElement) {
1853
+ this.centerScrollableBody.nativeElement.scrollLeft = target.scrollLeft;
1854
+ }
1855
+ }
1856
+
1857
+ // Dragging Logic is implemented here
1858
+ // private initialIndex: number | null = null;
1859
+
1860
+ draggingColumn: any;
1861
+ dragStartIndex: any = 0;
1862
+ // onDragStart(data: any, index: number) {
1863
+ // this.draggingColumn = data.column;
1864
+ // this.dragStartIndex = index;
1865
+ // }
1866
+ // onDragMove(data: any) {
1867
+ // const { clientX, clientY } = data.event;
1868
+ // const headers = Array.from(
1869
+ // document.querySelectorAll('.one-row-header-cells')
1870
+ // ) as HTMLElement[];
1871
+
1872
+ // headers.forEach((headerEl, idx) => {
1873
+ // const rect = headerEl.getBoundingClientRect();
1874
+ // if (
1875
+ // idx !== this.dragStartIndex &&
1876
+ // clientX > rect.left &&
1877
+ // clientX < rect.right &&
1878
+ // clientY > rect.top &&
1879
+ // clientY < rect.bottom
1880
+ // ) {
1881
+ // const otherIndex = idx;
1882
+ // console.log(`${this.dragStartIndex} --> ${otherIndex}`);
1883
+ // this.swapColumn(this.dragStartIndex, otherIndex);
1884
+ // console.log('Updated Columns: ', this.columns);
1885
+ // this.dragStartIndex = otherIndex; // update index
1886
+ // }
1887
+ // });
1888
+ // }
1889
+
1890
+ // swapColumn(previusIndex: number, currentIndex: number) {
1891
+ // const columns = structuredClone(this.columns);
1892
+ // const flattenColumns = this.flattenColumns(columns);
1893
+
1894
+ // const visibleFlattenColumns = flattenColumns.filter(
1895
+ // (col) => col.is_visible
1896
+ // );
1897
+ // const previusColumn = visibleFlattenColumns[previusIndex];
1898
+ // const currentColumn = visibleFlattenColumns[currentIndex];
1899
+
1900
+ // console.log('Previus Column: ', previusColumn);
1901
+ // console.log('current clumn: ', currentColumn);
1902
+ // }
1903
+
1904
+ // onDragEnd(data: any) {
1905
+ // this.draggingColumn = null;
1906
+ // this.dragStartIndex = null;
1907
+ // }
1908
+
1909
+ // CDK DRAG DROP LOGIC GOES HERE
1910
+
1911
+
1912
+ canEnterToRowsGrouping = (drag: CdkDrag<any>, drop: CdkDropList<any>) => {
1913
+ // Example: Block if already pinned left
1914
+ const data = drag.data;
1915
+ if (data?.children && data?.children.length) {
1916
+ return data.children.some(
1917
+ (col: any) => col.is_visible && col.isGroupable
1918
+ );
1919
+ }
1920
+ return data.is_visible && data.isGroupable;
1921
+ };
1922
+
1923
+ onDragMoved(event: CdkDragMove<any>) {
1924
+ const pointerX = event.pointerPosition.x;
1925
+ const pointerY = event.pointerPosition.y;
1926
+
1927
+ const targetElement = document.getElementById(
1928
+ 'rows-grouping-top-container'
1929
+ );
1930
+
1931
+ if (targetElement) {
1932
+ const rect = targetElement.getBoundingClientRect();
1933
+
1934
+ const isOverTarget =
1935
+ pointerX >= rect.left &&
1936
+ pointerX <= rect.right &&
1937
+ pointerY >= rect.top &&
1938
+ pointerY <= rect.bottom;
1939
+
1940
+ if (isOverTarget) {
1941
+ this.draggingInGroupArea = true;
1942
+ } else {
1943
+ this.draggingInGroupArea = false;
1944
+ }
1945
+ }
1946
+ }
1947
+
1948
+ enterToTopRowGrouping(dropList: CdkDragEnter<any>) {
1949
+ const draggingData = dropList.item.data;
1950
+ this.draggingInGroupArea = true;
1951
+
1952
+ if (Array.isArray(draggingData?.children)) {
1953
+ const targetGroup = this.columns.find(
1954
+ (col) => col.header === draggingData.header
1955
+ );
1956
+ if (targetGroup) {
1957
+ draggingData.children.forEach((draggedChild: any) => {
1958
+ const matchingChild = targetGroup.children.find(
1959
+ (child: any) => child.field === draggedChild.field
1960
+ );
1961
+ if (matchingChild) {
1962
+ matchingChild.isRowGrouped = !!matchingChild.isGroupable;
1963
+ }
1964
+ });
1965
+ }
1966
+ }
1967
+ else {
1968
+ // Try finding it in top-level columns first
1969
+ let targetColumn = this.columns.find(
1970
+ (col: any) => col.field === draggingData.field
1971
+ );
1972
+
1973
+ if (targetColumn) {
1974
+ targetColumn.isRowGrouped = !!targetColumn.isGroupable;
1975
+ } else {
1976
+ for (let group of this.columns) {
1977
+ if (Array.isArray(group.children)) {
1978
+ const matchingChild = group.children.find(
1979
+ (child: any) => child.field === draggingData.field
1980
+ );
1981
+ if (matchingChild) {
1982
+ matchingChild.isRowGrouped = !!matchingChild.isGroupable;
1983
+ break;
1984
+ }
1985
+ }
1986
+ }
1987
+ }
1988
+ }
1989
+
1990
+ // ✅ Refresh UI
1991
+ this.refreshPreviewColumns();
1992
+ this.cdr.detectChanges();
1993
+ }
1994
+
1995
+ onDropListEnter(
1996
+ dropList: CdkDragEnter<any>,
1997
+ section: 'left' | 'center' | 'right'
1998
+ ) {
1999
+ const draggingData = dropList.item.data;
2000
+
2001
+ const targetColumn: any = this.columns.find(
2002
+ (col) => col.header == draggingData.header
2003
+ );
2004
+
2005
+ if (!targetColumn) return;
2006
+
2007
+ if (
2008
+ Array.isArray(draggingData?.children) &&
2009
+ Array.isArray(targetColumn?.children)
2010
+ ) {
2011
+ draggingData.children.forEach((draggedChild: any) => {
2012
+ const matchingChild = targetColumn.children.find(
2013
+ (child: any) => child.field === draggedChild.field
2014
+ );
2015
+ if (matchingChild) {
2016
+ matchingChild.pinned = section === 'center' ? null : section;
2017
+ matchingChild['isRowGrouped'] = false;
2018
+ }
2019
+ });
2020
+ } else {
2021
+ targetColumn.pinned = section === 'center' ? null : section;
2022
+ targetColumn['isRowGrouped'] = false;
2023
+ }
2024
+
2025
+ this.refreshPreviewColumns();
2026
+ this.cdr.detectChanges();
2027
+ }
2028
+
2029
+ enterToTopGroupingRow(dropList: CdkDragEnter<any>) {
2030
+ this.shouldDisableDroplistSorting = this.isDisableColumnGrouping;
2031
+ this.draggingInGroupArea = true;
2032
+ // const index = this.groupedColumns.findIndex(
2033
+ // (col) => col.field === column.field
2034
+ // );
2035
+ // if (index !== -1) {
2036
+ // this.groupedColumns.splice(index, 1);
2037
+ // }
2038
+
2039
+ // // 2. Traverse this.columns to find and update the matching column
2040
+ // this.columns.forEach((group) => {
2041
+ // if (group?.children && Array.isArray(group.children)) {
2042
+ // group.children.forEach((child) => {
2043
+ // if (child.field === column.field) {
2044
+ // child.isRowGrouped = false;
2045
+ // }
2046
+ // });
2047
+ // } else if (group.field === column.field) {
2048
+ // group.isRowGrouped = false;
2049
+ // }
2050
+ // });
2051
+ this.cdr.detectChanges();
2052
+ }
2053
+
2054
+ exitedFromTheTopRow(dropList: CdkDragExit<any>) {
2055
+ this.draggingInGroupArea = false;
2056
+ this.isDisableColumnGrouping = false;
2057
+ this.shouldDisableDroplistSorting = false;
2058
+ const draggingData = dropList.item.data;
2059
+ if (Array.isArray(draggingData?.children)) {
2060
+ const targetGroup = this.columns.find(
2061
+ (col) => col.header === draggingData.header
2062
+ );
2063
+ if (targetGroup) {
2064
+ draggingData.children.forEach((draggedChild: any) => {
2065
+ const matchingChild = targetGroup.children.find(
2066
+ (child: any) => child.field === draggedChild.field
2067
+ );
2068
+ if (matchingChild) {
2069
+ matchingChild.isRowGrouped = false;
2070
+ }
2071
+ });
2072
+ }
2073
+ } else {
2074
+ let targetColumn = this.columns.find(
2075
+ (col: any) => col.field === draggingData.field
2076
+ );
2077
+
2078
+ if (targetColumn) {
2079
+ targetColumn.isRowGrouped = false;
2080
+ } else {
2081
+ for (let group of this.columns) {
2082
+ if (Array.isArray(group.children)) {
2083
+ const matchingChild = group.children.find(
2084
+ (child: any) => child.field === draggingData.field
2085
+ );
2086
+ if (matchingChild) {
2087
+ matchingChild.isRowGrouped = false;
2088
+ break;
2089
+ }
2090
+ }
2091
+ }
2092
+ }
2093
+ }
2094
+ this.refreshPreviewColumns();
2095
+ this.cdr.detectChanges();
2096
+ }
2097
+
2098
+ shouldDisableDroplistSorting = false;
2099
+ isDisableColumnGrouping = false;
2100
+ checkColumnGroupingStatus(col: any) {
2101
+ if (col?.children && Array.isArray(col.children)) {
2102
+ const allChildrenDisabled = col.children.every(
2103
+ (child: any) => child.isGroupable === false
2104
+ );
2105
+ this.isDisableColumnGrouping = allChildrenDisabled;
2106
+ } else {
2107
+ this.isDisableColumnGrouping = col.isGroupable === false;
2108
+ }
2109
+ this.cdr.detectChanges();
2110
+ }
2111
+
2112
+ currentDraggingColumn: any = null;
2113
+ dragStartOnGroup(col: any) {
2114
+ this.currentDraggingColumn = col;
2115
+ }
2116
+
2117
+ onSortGroup = async (event: CdkDragSortEvent<any>, section: string) => {
2118
+ const columns = (this as any)[section];
2119
+ if (event.previousIndex === event.currentIndex) return;
2120
+ const visibleColumns = columns.filter((col: any) => {
2121
+ if (col?.children && Array.isArray(col.children)) {
2122
+ return col.children.some((child: any) => child.is_visible);
2123
+ }
2124
+ return col.is_visible;
2125
+ });
2126
+ const previousHeader = visibleColumns[event.previousIndex].header;
2127
+ const currentHeader = visibleColumns[event.currentIndex].header;
2128
+ const visiblePrevIndex = visibleColumns.findIndex(
2129
+ (col: any) => col.header === previousHeader
2130
+ );
2131
+ const visibleCurrIndex = visibleColumns.findIndex(
2132
+ (col: any) => col.header === currentHeader
2133
+ );
2134
+ const getFields = (item: any): string[] => {
2135
+ if (item?.children && Array.isArray(item.children)) {
2136
+ return item.children.map((child: any) => child.field);
2137
+ }
2138
+ return [item.field];
2139
+ };
2140
+ const prevFields = getFields(visibleColumns[visiblePrevIndex]);
2141
+ const currFields = getFields(visibleColumns[visibleCurrIndex]);
2142
+ const allFields = [...prevFields, ...currFields];
2143
+ const cells = allFields.length
2144
+ ? ([] as HTMLElement[]).concat(
2145
+ ...allFields.map((field) =>
2146
+ Array.from(document.querySelectorAll(`[field="${field}"]`)) as HTMLElement[]
2147
+ )
2148
+ )
2149
+ : [];
2150
+ const firstPositions = new Map<HTMLElement, number>();
2151
+ cells.forEach((el) => firstPositions.set(el, el.getBoundingClientRect().left));
2152
+ console.log("this .coluns", this.columns);
2153
+ moveItemInArray((this as any)[section], visiblePrevIndex, visibleCurrIndex);
2154
+
2155
+ const prevColIndexInColumns = this.columns.findIndex(item => item.header === previousHeader);
2156
+ const currColIndexInColumns = this.columns.findIndex(item => item.header === currentHeader);
2157
+ moveItemInArray(this.columns, prevColIndexInColumns, currColIndexInColumns);
2158
+ // await this.refreshPreviewColumns();
2159
+ this.cdr.detectChanges();
2160
+ allFields.forEach((field) => {
2161
+ const updatedCells = Array.from(
2162
+ document.querySelectorAll(`[field="${field}"]`)
2163
+ ) as HTMLElement[];
2164
+
2165
+ updatedCells.forEach((el) => {
2166
+ const newLeft = el.getBoundingClientRect().left;
2167
+ const oldLeft = firstPositions.get(el)!;
2168
+ const deltaX = oldLeft - newLeft;
2169
+ if (deltaX !== 0) {
2170
+ el.style.willChange = 'transform';
2171
+ el.style.transition = 'none';
2172
+ el.style.transform = `translateX(${deltaX}px)`;
2173
+ void el.offsetWidth;
2174
+ el.style.transition = 'transform 400ms cubic-bezier(0.4, 0, 0.2, 1)';
2175
+ el.style.transform = 'translateX(0)';
2176
+ const handle = () => {
2177
+ el.style.transition = '';
2178
+ el.style.transform = '';
2179
+ el.style.willChange = '';
2180
+ el.removeEventListener('transitionend', handle);
2181
+ };
2182
+ el.addEventListener('transitionend', handle);
2183
+ }
2184
+ });
2185
+ });
2186
+ }
2187
+
2188
+
2189
+ onDropGroup() {
2190
+ this.leftPinnedColumns = structuredClone(this.previewLeftPinnedColumns);
2191
+ this.centerColumns = structuredClone(this.previewCenterColumns);
2192
+ this.rightPinnedColumns = structuredClone(this.previewRightPinnedColumns);
2193
+ const event = {
2194
+ columns: this.columns,
2195
+ type: 'createUpdateConfigListing',
2196
+ }
2197
+ this.createUpdateConfigListing.emit(event);
2198
+
2199
+ this.cdr.detectChanges();
2200
+ }
2201
+
2202
+ async onDropTopGroup(event: CdkDragDrop<any>) {
2203
+ this.loading = true;
2204
+ this.cdr.detectChanges();
2205
+ const draggedData = event.item.data;
2206
+ if (draggedData?.children && Array.isArray(draggedData.children)) {
2207
+ draggedData.children.forEach((col: any) => {
2208
+ if (col.is_visible && col.isGroupable) {
2209
+ this.groupedColumns.push(col);
2210
+ }
2211
+ });
2212
+ } else {
2213
+ if (draggedData.is_visible && draggedData.isGroupable) {
2214
+ this.groupedColumns.push(draggedData);
2215
+ }
2216
+ }
2217
+ this.draggingInGroupArea = false;
2218
+ const fields = this.groupedColumns.map((item) => item.field);
2219
+ this.dataSet = await this.groupDataAsync(this.originalDataSet, fields);
2220
+ setTimeout(() => {
2221
+ this.updateColumnWidthsAndGroups();
2222
+ this.refreshPreviewColumns();
2223
+ this.updateVisibleRows(0);
2224
+ this.loading = false;
2225
+ this.cdr.detectChanges();
2226
+ }, 100);
2227
+ }
2228
+
2229
+ async ungroupColumn(column: any) {
2230
+ try {
2231
+ this.loading = true;
2232
+ this.cdr.detectChanges();
2233
+ const index = this.groupedColumns.findIndex(
2234
+ (col) => col.field === column.field
2235
+ );
2236
+ if (index !== -1) {
2237
+ this.groupedColumns.splice(index, 1);
2238
+ }
2239
+ const fields = this.groupedColumns.map((item) => item.field);
2240
+ this.dataSet = await this.groupDataAsync(this.originalDataSet, fields);
2241
+ this.columns.forEach((group) => {
2242
+ if (group?.children && Array.isArray(group.children)) {
2243
+ group.children.forEach((child: any) => {
2244
+ if (child.field === column.field) {
2245
+ child.isRowGrouped = false;
2246
+ }
2247
+ });
2248
+ } else if (group.field === column.field) {
2249
+ group.isRowGrouped = false;
2250
+ }
2251
+ });
2252
+ setTimeout(() => {
2253
+ this.updateFlattenedData();
2254
+ this.updateColumnWidthsAndGroups();
2255
+ this.refreshPreviewColumns();
2256
+ this.updateVisibleRows(0);
2257
+ this.loading = false;
2258
+ this.cdr.detectChanges();
2259
+ }, 100);
2260
+
2261
+ } catch (err) {
2262
+ console.error("Error in ungroupColumn:", err);
2263
+ this.loading = false;
2264
+ this.cdr.detectChanges();
2265
+ }
2266
+ }
2267
+
2268
+
2269
+ shouldTheGroupHeaderShow(group: any) {
2270
+ if (group?.children && group.children.length) {
2271
+ return group.children.some(
2272
+ (col: any) => col.is_visible && !col?.isRowGrouped
2273
+ );
2274
+ }
2275
+ return group?.is_visible && !group?.isRowGrouped;
2276
+ }
2277
+
2278
+
2279
+ onChildDragStart() {
2280
+ debugger;
2281
+ }
2282
+
2283
+ dropListIds: string[] = [];
2284
+
2285
+ generateDropListIds() {
2286
+ this.dropListIds = [];
2287
+
2288
+ const sectionKeys = [
2289
+ 'leftPinnedColumns',
2290
+ 'centerColumns',
2291
+ 'rightPinnedColumns',
2292
+ ];
2293
+
2294
+ sectionKeys.forEach((sectionKey) => {
2295
+ const sectionCols = (this as any)[sectionKey];
2296
+
2297
+ this.columns.forEach((col: any, i: number) => {
2298
+ if (col?.children?.length > 0) {
2299
+ col.children.forEach((child: any, childIndex: number) => {
2300
+ if (child?.is_visible && !child.isRowGrouped) {
2301
+ const id = `${sectionKey}${i}`;
2302
+ this.dropListIds.push(id);
2303
+ }
2304
+ });
2305
+ } else if (col?.is_visible && !col.isRowGrouped) {
2306
+ const id = `${sectionKey}${i}`;
2307
+ this.dropListIds.push(id);
2308
+ }
2309
+ });
2310
+ });
2311
+ }
2312
+
2313
+ onChildDroplistSorted = async (event: CdkDragSortEvent<any>, section: string) => {
2314
+ if (event.previousIndex === event.currentIndex) return;
2315
+
2316
+ const pinned =
2317
+ section == 'previewLeftPinnedColumns'
2318
+ ? 'left'
2319
+ : section == 'previewRightPinnedColumns'
2320
+ ? 'right'
2321
+ : "";
2322
+
2323
+ const column = event.item.data;
2324
+ let group = this.columns.find(
2325
+ (col: any) =>
2326
+ Array.isArray(col.children) &&
2327
+ col.children.some((child: any) => child?.field === column?.field)
2328
+ );
2329
+ const groupIndex = this.columns.findIndex(
2330
+ (col) => col.header === group.header
2331
+ );
2332
+ const filteredGroup = group?.children.filter((col: any) => {
2333
+ const isPinnedMatch =
2334
+ (pinned === "" && (!col?.pinned || col?.pinned === "")) || col?.pinned === pinned;
2335
+ return isPinnedMatch && col?.is_visible;
2336
+ });
2337
+
2338
+ const previousField = filteredGroup[event.previousIndex].field;
2339
+ const currentField = filteredGroup[event.currentIndex].field;
2340
+ const visiblePrevIndex = group.children.findIndex(
2341
+ (col: any) => col.field == previousField
2342
+ );
2343
+ const visibleCurrIndex = group.children.findIndex(
2344
+ (col: any) => col.field == currentField
2345
+ );
2346
+ const allFields = [previousField, currentField];
2347
+ const cells = allFields.length
2348
+ ? ([] as HTMLElement[]).concat(
2349
+ ...allFields.map((field) =>
2350
+ Array.from(document.querySelectorAll(`[field="${field}"]`)) as HTMLElement[]
2351
+ )
2352
+ )
2353
+ : [];
2354
+
2355
+ const groupInSection = (this as any)[section].find(
2356
+ (col: any) =>
2357
+ Array.isArray(col.children) &&
2358
+ col.children.some((child: any) => child?.field === column?.field)
2359
+ );
2360
+
2361
+ const filterGroupInSection = (this as any)[section].find(
2362
+ (col: any) =>
2363
+ Array.isArray(col.children) &&
2364
+ col.children.some((child: any) => child?.field === column?.field)
2365
+ );
2366
+
2367
+ const groupInSectionIndex = (this as any)[section].findIndex(
2368
+ (col: any) => col.header === groupInSection.header
2369
+ );
2370
+
2371
+ const firstPositions = new Map<HTMLElement, number>();
2372
+ cells.forEach((el) => firstPositions.set(el, el.getBoundingClientRect().left));
2373
+ moveItemInArray(
2374
+ this.columns[groupIndex]?.children,
2375
+ visiblePrevIndex,
2376
+ visibleCurrIndex
2377
+ );
2378
+
2379
+ moveItemInArray(
2380
+ (this as any)[section]?.[groupInSectionIndex].children,
2381
+ event.previousIndex,
2382
+ event.currentIndex
2383
+ );
2384
+
2385
+ // await this.refreshPreviewColumns();
2386
+ this.cdr.detectChanges();
2387
+ allFields.forEach((field) => {
2388
+ const updatedCells = Array.from(
2389
+ document.querySelectorAll(`[field="${field}"]`)
2390
+ ) as HTMLElement[];
2391
+ updatedCells.forEach((el) => {
2392
+ const newLeft = el.getBoundingClientRect().left;
2393
+ const oldLeft = firstPositions.get(el)!;
2394
+ const deltaX = oldLeft - newLeft;
2395
+ if (deltaX !== 0) {
2396
+ el.style.willChange = 'transform';
2397
+ el.style.transition = 'none';
2398
+ el.style.transform = `translateX(${deltaX}px)`;
2399
+ void el.offsetWidth;
2400
+ el.style.transition = 'transform 400ms cubic-bezier(0.4, 0, 0.2, 1)';
2401
+ el.style.transform = 'translateX(0)';
2402
+
2403
+ const handle = () => {
2404
+ el.style.transition = '';
2405
+ el.style.transform = '';
2406
+ el.style.willChange = '';
2407
+ el.removeEventListener('transitionend', handle);
2408
+ };
2409
+ el.addEventListener('transitionend', handle);
2410
+ }
2411
+ });
2412
+ });
2413
+ console.group('Group: ', group);
2414
+ }
2415
+
2416
+
2417
+ onChildDroplistDroped(cdkDragDropevent: CdkDragDrop<any>) {
2418
+ this.updateColumnWidthsAndGroups();
2419
+ const event = {
2420
+ columns: this.columns,
2421
+ type: 'createUpdateConfigListing',
2422
+ }
2423
+ this.createUpdateConfigListing.emit(event);
2424
+
2425
+ setTimeout(() => {
2426
+ this.cdr.detectChanges();
2427
+ }, 2);
2428
+ }
2429
+
2430
+ // Rows Grouping Logic Goes Here
2431
+
2432
+ groupData(data: any[], groupFields: string[]): any[] {
2433
+ let dataSet = structuredClone(data);
2434
+ if (!groupFields.length) return data;
2435
+ const [currentField, ...restFields] = groupFields;
2436
+ const groupedMap = new Map<string, any[]>();
2437
+ for (const item of dataSet) {
2438
+ let keyValue = currentField.split('.').reduce((obj, k) => obj?.[k], item);
2439
+ let groupKey: string;
2440
+ if (Array.isArray(keyValue)) {
2441
+ if (keyValue.length === 0) {
2442
+ groupKey = "_Blank";
2443
+ } else if (typeof keyValue[0] === "object") {
2444
+ groupKey = keyValue
2445
+ .map(obj => (obj?.department_name || obj.roleName) ?? "_Blank")
2446
+ .join(",");
2447
+ } else {
2448
+ groupKey = keyValue
2449
+ .map(val => val ?? "_Blank")
2450
+ .join(",");
2451
+ }
2452
+ } else {
2453
+ groupKey = keyValue ?? "_Blank";
2454
+ }
2455
+ if (!groupedMap.has(groupKey)) groupedMap.set(groupKey, []);
2456
+ groupedMap.get(groupKey)!.push(item);
2457
+ }
2458
+ return Array.from(groupedMap.entries()).map(([groupValue, groupItems]) => ({
2459
+ isGroup: true,
2460
+ groupField: currentField,
2461
+ groupValue,
2462
+ children: this.groupData(groupItems, restFields),
2463
+ isExpand: false,
2464
+ }));
2465
+ }
2466
+
2467
+
2468
+ countLeafRows(group: any): number {
2469
+ if (!group.children || !group.children.length) return 0;
2470
+
2471
+ return group.children.reduce((count: number, child: any) => {
2472
+ return count + (child.isGroup ? this.countLeafRows(child) : 1);
2473
+ }, 0);
2474
+ }
2475
+
2476
+ isActiveFilterOpen = false;
2477
+ toggleActiveFilter() {
2478
+ this.isActiveFilterOpen = !this.isActiveFilterOpen;
2479
+ }
2480
+
2481
+ toggleActions(type: string) {
2482
+ if (type === this.activeTopButton) this.activeTopButton = type;
2483
+ else this.activeTopButton = type;
2484
+ this.activeSubButton = '';
2485
+ this.isFilterOpen = false;
2486
+ this.cdr.detectChanges();
2487
+ }
2488
+
2489
+ activeSubButton = '';
2490
+ toggleSubActions(type: string) {
2491
+ if (type === this.activeSubButton) this.activeSubButton = '';
2492
+ else this.activeSubButton = type;
2493
+ this.cdr.detectChanges();
2494
+ }
2495
+
2496
+ toggleActionsDropdown() {
2497
+ this.showActionsDropDown = !this.showActionsDropDown;
2498
+ this.cdr.detectChanges();
2499
+ }
2500
+
2501
+
2502
+
2503
+ async changeTableLayout(event: Event, layoutType: string) {
2504
+ let target = event.target as HTMLInputElement;
2505
+
2506
+ if (target.checked) {
2507
+ this.selectedTableLayout = layoutType;
2508
+ if (layoutType === 'small') {
2509
+ this.rowHeight = 36;
2510
+ this.headerRowHeight = 40;
2511
+ this.bodyTextFontsSize = 10;
2512
+ this.headerTextFontsSize = 10;
2513
+
2514
+ } else if (layoutType === 'medium') {
2515
+ this.rowHeight = 44;
2516
+ this.headerRowHeight = 44;
2517
+ this.bodyTextFontsSize = 14;
2518
+ this.headerTextFontsSize = 14;
2519
+
2520
+ } else {
2521
+ this.rowHeight = 60;
2522
+ this.headerRowHeight = 52;
2523
+ this.bodyTextFontsSize = 16;
2524
+ this.headerTextFontsSize = 16;
2525
+ }
2526
+ this.updateVisibleRows(this.mainScroll?.nativeElement?.scrollTop);
2527
+ await this.refreshHeaders();
2528
+ }
2529
+ if (this.tableFilterViewId) {
2530
+ this.savePreset('mouseUp');
2531
+ }
2532
+ this.onFontChange()
2533
+ }
2534
+
2535
+
2536
+ pageSizeOptions = [25, 50, 75, 100, 150, 200, 250, 300, 500];
2537
+ get startIndexData() {
2538
+ return (this.paginationConfig.page - 1) * this.paginationConfig.limit;
2539
+ }
2540
+
2541
+ get endIndex() {
2542
+ return Math.min(this.startIndex + this.paginationConfig.limit, this.paginationConfig.totalResults);
2543
+ }
2544
+
2545
+ get visiblePages(): (number | string)[] {
2546
+ const pages: (number | string)[] = [];
2547
+
2548
+ if (this.paginationConfig.totalPages <= 7) {
2549
+ for (let i = 1; i <= this.paginationConfig.totalPages; i++) {
2550
+ pages.push(i);
2551
+ }
2552
+ } else {
2553
+ pages.push(1);
2554
+ if (this.paginationConfig.page > 3) {
2555
+ pages.push('...');
2556
+ }
2557
+ let start = Math.max(2, this.paginationConfig.page - 1);
2558
+ let end = Math.min(this.paginationConfig.totalPages - 1, this.paginationConfig.page + 1);
2559
+
2560
+ for (let i = start; i <= end; i++) {
2561
+ pages.push(i);
2562
+ }
2563
+
2564
+ if (this.paginationConfig.page < this.paginationConfig.totalPages - 2) {
2565
+ pages.push('...');
2566
+ }
2567
+ pages.push(this.paginationConfig.totalPages);
2568
+ }
2569
+
2570
+ return pages;
2571
+ }
2572
+
2573
+
2574
+ searchTextForFilterDropDown = '';
2575
+ toggleColumnInFilterDropdown(col: any) {
2576
+
2577
+ }
2578
+
2579
+ removeColumnFromFilter(option: any) {
2580
+
2581
+ }
2582
+
2583
+ handleBackspace(event: Event) {
2584
+ // If search is empty, remove last tag
2585
+ if (!this.searchTextForFilterDropDown && this.selectedFilterOptions.length) {
2586
+ const last = this.selectedFilterOptions[this.selectedFilterOptions.length - 1];
2587
+ this.toggleSelectionInFilter(last);
2588
+ }
2589
+ }
2590
+
2591
+ @ViewChild('filterMenueSearchInput') filterMenueSearchInput!: ElementRef<HTMLInputElement>
2592
+ @ViewChild('filterMenueTextchInput') filterMenueTextchInput!: ElementRef<HTMLInputElement>
2593
+
2594
+ openFilteronThreeDotsClick(col: any) {
2595
+ if (col.type == 'image') return;
2596
+ this.selectedColumnForFilter = col;
2597
+ this.isThreeDotsFilterOpen = true;
2598
+ if (col.type === 'dropdown') {
2599
+ this.currentFilterSelectedIds.clear();
2600
+ const savedIds = col?.query?._ids || [];
2601
+ savedIds.forEach((id: string) => this.currentFilterSelectedIds.add(id));
2602
+
2603
+ this.selectedFilterOptions = this.selectedColumnForFilter?.column_dropdown_value.filter(
2604
+ (option: any) => this.currentFilterSelectedIds.has(option?.id || option?._id || option)
2605
+ );
2606
+ setTimeout(() => {
2607
+ this.filterMenueSearchInput.nativeElement.focus();
2608
+ }, 100);
2609
+ } else if (['string', 'number', 'date'].includes(col.type)) {
2610
+ this.firstCondition = col.type == 'date' ? 'equal' : 'contain';
2611
+ this.firstValue = col?.query?.first_value || '';
2612
+ this.condition = col?.query?.condition || 'none';
2613
+ this.secondCondition = (col?.query?.second_condition) || (col.type == 'date' ? 'equal' : 'contain');
2614
+ this.secondValue = col?.query?.second_value || '';
2615
+ setTimeout(() => {
2616
+ this.filterMenueTextchInput.nativeElement.focus();
2617
+ }, 100);
2618
+ }
2619
+ }
2620
+
2621
+ isFilterOpen = false
2622
+ selectedColumnForFilter: any;
2623
+ showFilters = this.showSideMenu ? false : false;
2624
+
2625
+ openFilter(col: any) {
2626
+ this.activeFilterCell = col;
2627
+ this.activeCol = null
2628
+ this.isFilterOpen = true;
2629
+ this.selectedColumnForFilter = col;
2630
+
2631
+ if (col.type === 'dropdown') {
2632
+ this.currentFilterSelectedIds.clear();
2633
+ const savedIds = col?.query?._ids || [];
2634
+ savedIds.forEach((id: string) => this.currentFilterSelectedIds.add(id));
2635
+
2636
+ this.selectedFilterOptions = this.selectedColumnForFilter?.column_dropdown_value?.filter(
2637
+ (option: any) => this.currentFilterSelectedIds.has(option?.id || option?._id || option)
2638
+ );
2639
+ } else if (['string', 'number', 'date'].includes(col.type)) {
2640
+ this.firstCondition = col.type == 'date' ? 'equal' : 'contain';
2641
+ this.firstValue = col?.query?.first_value || '';
2642
+ this.condition = col?.query?.condition || 'none';
2643
+ this.secondCondition = col?.query?.second_condition || 'contain';
2644
+ this.secondValue = col?.query?.second_value || '';
2645
+ }
2646
+ this.cdr.detectChanges();
2647
+ }
2648
+
2649
+
2650
+ firstValue: any = '';
2651
+ firstCondition = '';
2652
+ secondValue: any = '';
2653
+ secondCondition = '';
2654
+ condition = '';
2655
+
2656
+ resetTextFilterChanges() {
2657
+ this.firstCondition = 'contain';
2658
+ this.firstValue = '';
2659
+ this.condition = 'none';
2660
+ this.secondCondition = '';
2661
+ this.secondValue = '';
2662
+ this.isFilterOpen = false;
2663
+ this.isActiveFilterOpen = false;
2664
+ this.activeTopButton = '';
2665
+ }
2666
+
2667
+ toggleAllValusSelectionInDropdownFilter(column: any) {
2668
+
2669
+ }
2670
+
2671
+
2672
+ selectedFilterOptions: any[] = []
2673
+
2674
+ currentFilterSelectedIds = new Set<string>();
2675
+
2676
+ toggleSelectionInFilter(option: any) {
2677
+ debugger
2678
+ const id = option.id || option._id || option;
2679
+ if (this.currentFilterSelectedIds.has(id)) {
2680
+ this.currentFilterSelectedIds.delete(id);
2681
+ } else {
2682
+ this.currentFilterSelectedIds.add(id);
2683
+ }
2684
+ this.selectedFilterOptions = this.selectedColumnForFilter?.column_dropdown_value.filter((option: any) => this.currentFilterSelectedIds.has(option.id || option._id || option))
2685
+ this.cdr.detectChanges();
2686
+ }
2687
+
2688
+ resetFilterChanges() {
2689
+ this.isFilterOpen = false;
2690
+ this.currentFilterSelectedIds.clear();
2691
+ this.selectedFilterOptions!.length = 0;
2692
+ this.isActiveFilterOpen = false;
2693
+ this.activeTopButton = '';
2694
+ // const filteredColumns = this.commonSevice.getFiltersFromColumns(this.columns);
2695
+ // console.log("Filtered Columns Are: ", filteredColumns);
2696
+ }
2697
+
2698
+ applyDropdownFilter() {
2699
+ const findColumn = (columns: any[], field: string): any => {
2700
+ for (const col of columns) {
2701
+ if (col.field === field) return col;
2702
+ if (col.children && col.children.length) {
2703
+ const found = findColumn(col.children, field);
2704
+ if (found) return found;
2705
+ }
2706
+ }
2707
+ return null;
2708
+ };
2709
+
2710
+ const column = findColumn(this.columns, this.selectedColumnForFilter.field);
2711
+ if (column) {
2712
+ if (column.type === 'dropdown') {
2713
+ column.query = column.query || {};
2714
+ column.query._ids = column.query._ids || [];
2715
+
2716
+ // Remove stale IDs (not present in Set)
2717
+ column.query._ids = column.query._ids.filter(
2718
+ (id: any) => this.currentFilterSelectedIds.has(id) // <-- use .has()
2719
+ );
2720
+
2721
+ // Add new ones if missing
2722
+ this.currentFilterSelectedIds.forEach(id => {
2723
+ if (!column.query._ids.includes(id)) {
2724
+ column.query._ids.push(id);
2725
+ }
2726
+ });
2727
+
2728
+ } else if (['string', 'number', 'date'].includes(column.type)) {
2729
+ column.query = {
2730
+ first_condition: this.firstCondition || 'contain',
2731
+ first_value: this.firstValue || null,
2732
+ condition: this.condition || 'none',
2733
+ second_condition: this.secondCondition || null,
2734
+ second_value: this.secondValue || null,
2735
+ };
2736
+ }
2737
+ }
2738
+
2739
+ const filter = {
2740
+ field: column.field,
2741
+ search: column.search,
2742
+ query: column.query,
2743
+ type: column.type,
2744
+ _ids: column._ids
2745
+ };
2746
+
2747
+ this.filtersConfig.push(filter);
2748
+
2749
+ this.isFilterOpen = false;
2750
+ this.isActiveFilterOpen = false;
2751
+ this.activeTopButton = '';
2752
+ this.activeFilterCell = null;
2753
+ this.isThreeDotsFilterOpen = false;
2754
+
2755
+ const filteredColumns = this.commonSevice.getFiltersFromColumns(this.columns, this.filtersConfig);
2756
+ this.filterOptions.emit(filteredColumns);
2757
+ }
2758
+
2759
+ // applyFilterFromFilterRow(column: any) {
2760
+ // const filter = {
2761
+ // field: column.field,
2762
+ // search: column.search,
2763
+ // query: column.query,
2764
+ // type: column.type,
2765
+ // _ids: column._ids
2766
+ // };
2767
+ // this.filtersConfig.push(filter);
2768
+ // this.isFilterOpen = false;
2769
+ // this.isActiveFilterOpen = false;
2770
+ // this.activeTopButton = '';
2771
+ // this.activeFilterCell = null;
2772
+ // this.isThreeDotsFilterOpen = false;
2773
+ // const filteredColumns = this.commonSevice.getFiltersFromColumns(this.columns, this.filtersConfig);
2774
+ // this.filterOptions.emit(filteredColumns);
2775
+ // }
2776
+
2777
+ isFilterAppliedOnColumn(column: any) {
2778
+ return this.filtersConfig.some(col => col.field === column.field);
2779
+ }
2780
+
2781
+ // Side Filters Logic is here
2782
+
2783
+ toggleSelectAllSideFilters(col: any, event: Event) {
2784
+ const checked = (event?.target as HTMLInputElement)?.checked;
2785
+
2786
+ if (checked) {
2787
+ col.query._ids = col.column_dropdown_value.map(
2788
+ (opt: any) => opt.id || opt._id || opt
2789
+ );
2790
+ this.currentFilterSelectedIds = new Set<string>(col.query._ids);
2791
+ } else {
2792
+ col.query._ids = [];
2793
+ this.currentFilterSelectedIds.clear();
2794
+ }
2795
+ }
2796
+
2797
+
2798
+ isAllSideFilterOptionsSelected(col: any): boolean {
2799
+ if (!col?.query?._ids || !Array.isArray(col.column_dropdown_value)) return false;
2800
+ return col.query._ids.length === col.column_dropdown_value.length;
2801
+ }
2802
+
2803
+ onOptionToggle(col: any, option: any) {
2804
+ const optionId = option.id || option._id || option;
2805
+ if (!col.query._ids) {
2806
+ col.query._ids = [];
2807
+ }
2808
+ const idx = col.query._ids.indexOf(optionId);
2809
+ if (idx > -1) {
2810
+ col.query._ids.splice(idx, 1);
2811
+ } else {
2812
+ col.query._ids.push(optionId);
2813
+ }
2814
+ this.cdr.detectChanges();
2815
+ }
2816
+
2817
+
2818
+
2819
+
2820
+ resetSideFilter(col: any) {
2821
+ const resetColumnRecursively = (columns: any[]): boolean => {
2822
+ for (const column of columns) {
2823
+ if (column.field === col.field) {
2824
+ column.query = {
2825
+ _ids: [],
2826
+ first_condition: 'contain',
2827
+ first_value: null,
2828
+ condition: 'none',
2829
+ second_condition: 'contain',
2830
+ second_value: null
2831
+ };
2832
+ return true;
2833
+ }
2834
+ if (Array.isArray(column?.children) && column?.children?.length) {
2835
+ if (resetColumnRecursively(column.children)) {
2836
+ return true;
2837
+ }
2838
+ }
2839
+ }
2840
+ return false;
2841
+ };
2842
+ resetColumnRecursively(this.columns);
2843
+ this.activeFilterCell = null;
2844
+ this.cdr.detectChanges();
2845
+ const filteredColumns = this.commonSevice.getFiltersFromColumns(this.columns, this.filtersConfig);
2846
+ this.filterOptions.emit(filteredColumns);
2847
+ }
2848
+
2849
+
2850
+ clearAllFilters() {
2851
+ this.tableView?.forEach((ele) => { ele.is_temp = false });
2852
+ const resetAllRecursively = (columns: any[]) => {
2853
+ for (const column of columns) {
2854
+ if (column.query) {
2855
+ column.query = {
2856
+ _ids: [],
2857
+ first_condition: 'contain',
2858
+ first_value: null,
2859
+ condition: 'none',
2860
+ second_condition: 'contain',
2861
+ second_value: null
2862
+ };
2863
+ }
2864
+ if (Array.isArray(column?.children) && column.children.length) {
2865
+ resetAllRecursively(column.children);
2866
+ }
2867
+ }
2868
+ };
2869
+ resetAllRecursively(this.columns);
2870
+ this.activeFilterCell = null;
2871
+ this.refreshPreviewColumns();
2872
+ this.cdr.detectChanges();
2873
+ const event = { eventType: 'reset' };
2874
+ this.genericEvent.emit(event)
2875
+ }
2876
+
2877
+
2878
+ applySideFilter() {
2879
+ const filteredColumns = this.commonSevice.getFiltersFromColumns(this.columns, this.filtersConfig);
2880
+ this.filterOptions.emit(filteredColumns);
2881
+ }
2882
+
2883
+
2884
+ trackByField(index: number, col: any): string {
2885
+ return col?.field;
2886
+ }
2887
+
2888
+ get activeFilteredColumns(): any[] {
2889
+ const collectFiltered = (columns: any[]): any[] => {
2890
+ let result: any[] = [];
2891
+
2892
+ columns.forEach(col => {
2893
+ if (col.children && col.children.length) {
2894
+ result = result.concat(collectFiltered(col.children));
2895
+ }
2896
+
2897
+ if (col.query) {
2898
+ const hasDropdownFilter =
2899
+ Array.isArray(col.query._ids) && col.query._ids.length > 0 && this.filtersConfig.some((ele) => ele.field == col.field);
2900
+
2901
+ const hasValueFilter =
2902
+ col.query.first_value !== null && col.query.first_value !== undefined && col.query.first_value !== '' && this.filtersConfig.some((ele) => ele.field == col.field);
2903
+
2904
+ if (hasDropdownFilter || hasValueFilter) {
2905
+ result.push(col);
2906
+ }
2907
+ }
2908
+ });
2909
+
2910
+ return result;
2911
+ };
2912
+ return [...collectFiltered(this.columns)];
2913
+ }
2914
+
2915
+
2916
+
2917
+ toggleOpenFilter() {
2918
+ this.showFilters = !this.showFilters;
2919
+ this.cdr.detectChanges();
2920
+ }
2921
+
2922
+
2923
+
2924
+
2925
+ // Cell Editing Work start here
2926
+
2927
+ editingKey: string | null = null;
2928
+ activeCell: string | null = null;
2929
+
2930
+ setActiveCell(row: any, column: any) {
2931
+ this.activeCell = ((row.id || row._id) + '-' + column.field);
2932
+ this.cdr.detectChanges();
2933
+ }
2934
+
2935
+ isActiveCell(row: any, col: any): boolean {
2936
+ return this.activeCell === ((row.id || row._id) + '-' + col.field);
2937
+ }
2938
+
2939
+ enableEdit(row: any, column: any) {
2940
+ this.editinDropdownSearch = '';
2941
+ if (row?.__virtualIndex == 0) return;
2942
+ if (column.type == 'image' || !column?.is_editable) return;
2943
+ this.editingKey = ((row.id || row._id) + '-' + column.field);
2944
+
2945
+ if (column.type === 'dropdown') {
2946
+ setTimeout(() => {
2947
+ const dropdownMenu = document.querySelector('.cell-editing-dropdown-menu') as HTMLElement;
2948
+ if (dropdownMenu) {
2949
+ const rect = dropdownMenu.getBoundingClientRect();
2950
+ const windowHeight = this.dataGridContainer.nativeElement?.offsetHeight;
2951
+
2952
+ if (rect.bottom > windowHeight) {
2953
+ dropdownMenu.style.top = `-${rect.height}px`;
2954
+ } else {
2955
+ dropdownMenu.style.top = '100%';
2956
+ }
2957
+ }
2958
+ });
2959
+ }
2960
+
2961
+ this.cdr.detectChanges();
2962
+ }
2963
+
2964
+
2965
+ disableEdit(row: any, column: any, control?: any) {
2966
+ this.rowSelectedIndexes.clear();
2967
+ if (!this.editingKey) return
2968
+ this.checkRowEditAndEmitValue(row, column, row[column.field])
2969
+ this.editingKey = null;
2970
+ this.cdr.detectChanges();
2971
+ const current = this.getNestedValue(row, column?.field);
2972
+ if (!current || (column.type == 'email' && !this.validateEmail(current))) {
2973
+ const originalRow = this.originalDataSet.find(item => item.id == row.id || item.id == row._id);
2974
+ const original = this.getNestedValue(originalRow, column?.field);
2975
+ this.setNestedValue(row, column, original);
2976
+ this.cdr.detectChanges();
2977
+ }
2978
+ }
2979
+
2980
+ emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
2981
+ validateEmail(value: string): boolean {
2982
+ return this.emailRegex.test(value);
2983
+ }
2984
+ allowOnlyNumbers(event: KeyboardEvent) {
2985
+ const charCode = event.which ? event.which : event.keyCode;
2986
+ if (charCode < 48 || charCode > 57) {
2987
+ event.preventDefault();
2988
+ }
2989
+ }
2990
+
2991
+ checkRowEditAndEmitValue(row: any, column: any, value?: any) {
2992
+ // setTimeout(() => {
2993
+ const originalRow = this.originalDataSet?.find(
2994
+ (r: any) => r.id === row.id || r._id === row._id
2995
+ );
2996
+ if (!originalRow) return;
2997
+ // const clean = (obj: any) => JSON.parse(JSON.stringify(obj));
2998
+ // const currentValue = this.getNestedValue(row, column.field);
2999
+ // const originalValue = this.getNestedValue(originalRow, column.field);
3000
+ const originValue = this.dataSet.find((r: any) => r.id === row.id);
3001
+ const changedValue = originValue.verified == value;
3002
+ // if (changedValue) return;
3003
+ // const hasChanged = this.commonSevice.hasFieldChanged(currentValue, originalValue, column.type);
3004
+ // if (!hasChanged) return;
3005
+ // this.setNestedValue(originalRow, column, value);
3006
+
3007
+ const sendObj = {
3008
+ data: { ...originValue },
3009
+ eventType: 'onCellEdit',
3010
+ value: value,
3011
+ column: column
3012
+ };
3013
+ return this.genericEvent.emit(sendObj)
3014
+ // }, 100);
3015
+ }
3016
+
3017
+
3018
+ isEditing(row: any, col: any): boolean {
3019
+ return this.editingKey === ((row.id || row._id) + '-' + col.field);
3020
+ }
3021
+
3022
+ setNestedValue(obj: any, column: any, option: any, calledFromInput = false): void {
3023
+ const path = column.field;
3024
+ const keys = path.split('.');
3025
+ const lastKey = keys.pop();
3026
+ const parent = keys.reduce((acc: { [x: string]: {}; }, key: string | number) => acc[key] ??= {}, obj);
3027
+
3028
+ if (parent && lastKey) {
3029
+ if (typeof option === 'object' && option !== null) {
3030
+ // case: dropdown object {id, value}
3031
+ parent.id = option.id;
3032
+ parent[lastKey] = option.value;
3033
+ } else {
3034
+ // case: plain string or number
3035
+ parent[lastKey] = option;
3036
+ }
3037
+ }
3038
+
3039
+ const sendObj = {
3040
+ data: { ...obj },
3041
+ eventType: 'onCellEdit',
3042
+ };
3043
+
3044
+ if (calledFromInput) {
3045
+ this.checkRowEditAndEmitValue(obj, column, option);
3046
+ }
3047
+ this.cdr.detectChanges();
3048
+ }
3049
+
3050
+
3051
+
3052
+
3053
+
3054
+
3055
+ goToPage(page: any) {
3056
+ if (page < 1 || page > this.paginationConfig.totalPages) return;
3057
+ this.paginationConfig.page = page;
3058
+ const event = {
3059
+ obj: {
3060
+ limit: this.paginationConfig.limit,
3061
+ page: page,
3062
+ },
3063
+ eventType: 'pageChange',
3064
+ };
3065
+ this.genericEvent.emit(event)
3066
+ }
3067
+
3068
+ onPageSizeChange() {
3069
+ this.paginationConfig.page = 1;
3070
+ const event = {
3071
+ obj: {
3072
+ limit: this.paginationConfig.limit,
3073
+ page: this.paginationConfig.page,
3074
+ },
3075
+ eventType: 'pageChange',
3076
+ };
3077
+ this.genericEvent.emit(event)
3078
+ }
3079
+
3080
+ actionPreset(data: any, type: any) {
3081
+ this.tableView?.forEach((ele) => { ele.is_temp = false });
3082
+ const event = {
3083
+ obj: {
3084
+ data: data
3085
+ },
3086
+ eventType: type
3087
+ }
3088
+ this.genericEvent.emit(event)
3089
+ }
3090
+
3091
+ selectFilter(data: any) {
3092
+ this.tableView?.forEach((ele) => { ele.is_temp = false });
3093
+ this.tableView = this.tableView.map(item => ({
3094
+ ...item,
3095
+ is_temp: item.id == data?.id
3096
+ }));
3097
+ if (data?.is_deafult) {
3098
+ return this.actionPreset(data, 'setPreset')
3099
+ }
3100
+ const event = {
3101
+ obj: {
3102
+ data: data
3103
+ },
3104
+ eventType: 'select'
3105
+ }
3106
+ this.genericEvent.emit(event)
3107
+ }
3108
+
3109
+ savePreset(control?: any) {
3110
+ if (control?.invalid) {
3111
+ control?.control.markAsTouched();
3112
+ return;
3113
+ }
3114
+ this.presetFilter = this.tableFilterViewId ? true : this.presetFilter;
3115
+ let selectedData: any = this.tableView.find((ele) => ele.id == this.tableFilterViewId);
3116
+ let config = {
3117
+ fontFaimly: this.fontFaimly,
3118
+ bodyTextFontsSize: this.bodyTextFontsSize,
3119
+ headerTextFontsSize: this.headerTextFontsSize,
3120
+ oddRowsBackgroundColor: this.rowShadingEnabled ? this.oddRowsBackgroundColor : undefined,
3121
+ showVerticalBorder: this.showVerticalBorder,
3122
+ selectedTableLayout: this.selectedTableLayout,
3123
+ globalSearch: this.tableSearch
3124
+
3125
+ }
3126
+ const event = {
3127
+ obj: {
3128
+ name: this.tableFilterViewId ? selectedData?.name : this.presetName,
3129
+ activeFilters: this.presetFilter,
3130
+ columns: this.columns,
3131
+ filters: this.presetFilter ? this.filtersConfig : [],
3132
+ config: config,
3133
+ id: this.tableFilterViewId,
3134
+ loadingCall: control == 'mouseUp' ? false : true
3135
+ },
3136
+ eventType: this.tableFilterViewId ? 'updatePreset' : 'createPreset'
3137
+ }
3138
+ this.genericEvent.emit(event)
3139
+ }
3140
+
3141
+
3142
+ toggleRowShading() {
3143
+ this.oddRowsBackgroundColor = this.rowShadingEnabled ? '#f1f1f1' : undefined;
3144
+ this.onFontChange();
3145
+ }
3146
+
3147
+ trackByTable(index: number): number {
3148
+ return index;
3149
+ }
3150
+ activeRow: any
3151
+ activeRowCol: any;
3152
+
3153
+ // Track expanded cells as a Set of unique row+col identifiers
3154
+
3155
+ getCellKey(row: any, col: any): string {
3156
+ return `${this.getRowId(row)}_${col?.field}`;
3157
+ }
3158
+
3159
+ expandedCells = new Map<string, number>();
3160
+ private zCounter = 21;
3161
+
3162
+ toggleExpandOfLongCellText(
3163
+ row: any,
3164
+ col: any,
3165
+ columns: any[],
3166
+ expandWholeRow = false
3167
+ ) {
3168
+ const expandCells = (targetRow: any) => {
3169
+ columns.forEach(c => {
3170
+ if (c.type === 'image') return;
3171
+ const text = targetRow[c.field];
3172
+ if (typeof text === 'string' && text.length > 50) {
3173
+ const key = this.getRowId(targetRow) + '-' + c.field;
3174
+ this.zCounter++;
3175
+ this.expandedCells.set(key, this.zCounter);
3176
+ }
3177
+ });
3178
+ };
3179
+
3180
+ const collectKeysForRow = (targetRow: any) => {
3181
+ const keys: string[] = [];
3182
+ columns.forEach(c => {
3183
+ if (c.type === 'image') return;
3184
+
3185
+ const text = targetRow[c.field];
3186
+ if (typeof text === 'string' && text.length > 50) {
3187
+ keys.push(this.getRowId(targetRow) + '-' + c.field);
3188
+ }
3189
+ });
3190
+ return keys;
3191
+ };
3192
+
3193
+ if (expandWholeRow) {
3194
+ let rowKeys: string[] = [];
3195
+ if (Array.isArray(row.children) && row.children.length > 0) {
3196
+ rowKeys = collectKeysForRow(row);
3197
+ row.children.forEach((child: any) => {
3198
+ rowKeys.push(...collectKeysForRow(child));
3199
+ });
3200
+ } else {
3201
+ rowKeys = collectKeysForRow(row);
3202
+ }
3203
+ const allExpanded = rowKeys.every(k => this.expandedCells.has(k));
3204
+ this.expandedCells.clear();
3205
+ if (!allExpanded) {
3206
+ if (Array.isArray(row.children) && row.children.length > 0) {
3207
+ expandCells(row);
3208
+ row.children.forEach((child: any) => expandCells(child));
3209
+ } else {
3210
+ expandCells(row);
3211
+ }
3212
+ }
3213
+ } else {
3214
+ const key = this.getRowId(row) + '-' + col.field;
3215
+ if (col.type === 'image') return;
3216
+
3217
+ if (!this.expandedCells.has(key)) {
3218
+ this.expandedCells.clear();
3219
+ this.zCounter++;
3220
+ this.expandedCells.set(key, this.zCounter);
3221
+ } else {
3222
+ this.expandedCells.delete(key);
3223
+ }
3224
+ }
3225
+ this.cdr.detectChanges();
3226
+ }
3227
+
3228
+
3229
+
3230
+ isExpanded(row: any, col: any): boolean {
3231
+ return this.expandedCells.has(this.getRowId(row) + '-' + col.field);
3232
+ }
3233
+
3234
+ getZIndex(row: any, col: any): number {
3235
+ return this.expandedCells.get(this.getRowId(row) + '-' + col.field) ?? 21;
3236
+ }
3237
+
3238
+ isOverflowing(element: HTMLElement | null): boolean {
3239
+ if (!element) return false;
3240
+ return element.scrollWidth > element.clientWidth;
3241
+ }
3242
+
3243
+
3244
+ // Picture cell Logic
3245
+ colorCombination = ['pic-comb1', 'pic-comb2', 'pic-comb4'];
3246
+ getDynamicClass(name: string): string {
3247
+ if (!name) return this.colorCombination[0];
3248
+ const hash = Array.from(name).reduce((acc, char) => acc + char.charCodeAt(0), 0);
3249
+ const index = hash % this.colorCombination?.length;
3250
+ return this.colorCombination[index];
3251
+ }
3252
+
3253
+ getInitials(name: string): string {
3254
+ if (!name) return 'NA';
3255
+ const parts = name.trim().split(' ');
3256
+ return (parts[0][0] + (parts[1]?.[0] || '')).toUpperCase();
3257
+ }
3258
+
3259
+
3260
+ actionHide: boolean = true;
3261
+ xPos = 0;
3262
+ yPos = 0;
3263
+ isVisible = false;
3264
+ deatilsList: any;
3265
+ openExpendIndex: any;
3266
+ positionedYet = false;
3267
+
3268
+ onRightClick(event: MouseEvent | TouchEvent | any, deatilsList: any): boolean {
3269
+ if (!(event instanceof MouseEvent)) {
3270
+ event.preventDefault();
3271
+ }
3272
+ if (deatilsList.__virtualIndex == 0) return false;
3273
+ event.preventDefault();
3274
+ this.xPos = (event instanceof MouseEvent) ? event.clientX : event.touches[0].clientX;
3275
+ this.yPos = (event instanceof MouseEvent) ? event.clientY : event.touches[0].clientY;
3276
+ // this.xPos = event.clientX;
3277
+ // this.yPos = event.clientY;
3278
+ this.isVisible = true;
3279
+ this.positionedYet = false;
3280
+ this.deatilsList = deatilsList;
3281
+ setTimeout(() => {
3282
+ const menuElement = document.querySelector('.context-menu') as HTMLElement;
3283
+ if (!menuElement) return;
3284
+ const menuWidth = menuElement.offsetWidth;
3285
+ const menuHeight = menuElement.offsetHeight;
3286
+ const viewportWidth = window.innerWidth;
3287
+ const viewportHeight = window.innerHeight;
3288
+ let x = (event instanceof MouseEvent) ? event.clientX : event.touches[0].clientX; //event.clientX;
3289
+ let y = (event instanceof MouseEvent) ? event.clientY : event.touches[0].clientY; //event.clientY;
3290
+ if (x + menuWidth > viewportWidth) {
3291
+ x = viewportWidth - menuWidth - 10;
3292
+ }
3293
+ if (y + menuHeight > viewportHeight) {
3294
+ y = viewportHeight - menuHeight - 10;
3295
+ }
3296
+ this.xPos = x;
3297
+ this.yPos = y;
3298
+ this.isVisible = true;
3299
+
3300
+ setTimeout(() => {
3301
+ this.positionedYet = true;
3302
+ this.cdr.detectChanges();
3303
+ });
3304
+
3305
+ this.cdr.detectChanges();
3306
+ });
3307
+ this.cdr.detectChanges()
3308
+ return false;
3309
+ }
3310
+
3311
+ onActionClick(action: string) {
3312
+ const sendObj = {
3313
+ obj: this.deatilsList,
3314
+ eventType: action,
3315
+ };
3316
+ this.positionedYet = false;
3317
+ this.cdr.detectChanges();
3318
+ this.genericEvent.emit(sendObj);
3319
+ }
3320
+
3321
+
3322
+ onVerifyClick(type: string) {
3323
+ const idsArray = Array.from(this.selectedRows);
3324
+ const arrayOfObjectsWithTableType: any[] = [];
3325
+ this.selectedRows.forEach(id => {
3326
+ arrayOfObjectsWithTableType.push({
3327
+ id: id,
3328
+ listingType: this.listingType,
3329
+ });
3330
+ });
3331
+ const sendObj = {
3332
+ data: {
3333
+ obj: idsArray,
3334
+ tableName: this.tableName,
3335
+ listingType: this.listingType,
3336
+ },
3337
+ eventType: type,
3338
+ };
3339
+ this.genericEvent.emit(sendObj)
3340
+ }
3341
+
3342
+
3343
+
3344
+ getCellClasses(column: any, value: any): string {
3345
+ if (!value) return '';
3346
+ let val: any;
3347
+ if (typeof value === 'object') {
3348
+ val = value?.value;
3349
+ } else {
3350
+ val = value;
3351
+ }
3352
+ const field = column?.field?.toLowerCase();
3353
+ const status = val?.toString().toLowerCase();
3354
+ if (field === 'status' || field === 'account_status' || field === 'availStatus' || field === 'is_custom_grade') {
3355
+ return STATUSES_BADGE_MAP[status] || 'badge badge-secondary';
3356
+ }
3357
+ return '';
3358
+ }
3359
+
3360
+ removeColumnFilterFromColumn(column: any) {
3361
+ if (!column) return
3362
+ if (column.type === 'dropdown') {
3363
+ column.query._ids = [];
3364
+ } else if (['string', 'number', 'date'].includes(column.type)) {
3365
+ column.query = {
3366
+ first_condition: 'contain',
3367
+ first_value: null,
3368
+ condition: 'none',
3369
+ second_condition: null,
3370
+ second_value: null,
3371
+ }
3372
+ }
3373
+ const filteredColumns = this.commonSevice.getFiltersFromColumns(this.columns, this.filtersConfig);
3374
+ this.filterOptions.emit(filteredColumns);
3375
+ }
3376
+
3377
+ onSideMenuColumnsVisibilityChange() {
3378
+ this.refreshHeaders();
3379
+ const event = {
3380
+ columns: this.columns,
3381
+ type: 'createUpdateConfigListing',
3382
+ }
3383
+ this.createUpdateConfigListing.emit(event);
3384
+ }
3385
+
3386
+ downloadCsv() {
3387
+ const event = {
3388
+ obj: {
3389
+ columns: this.columns,
3390
+ filters: []
3391
+ },
3392
+ eventType: 'downloadCsv'
3393
+ }
3394
+ this.genericEvent.emit(event)
3395
+
3396
+ }
3397
+
3398
+ onFontChange() {
3399
+ this.headerTextFontsSize = this.bodyTextFontsSize;
3400
+ const config = {
3401
+ fontFaimly: this.fontFaimly,
3402
+ bodyTextFontsSize: this.bodyTextFontsSize,
3403
+ headerTextFontsSize: this.headerTextFontsSize,
3404
+ oddRowsBackgroundColor: this.rowShadingEnabled ? this.oddRowsBackgroundColor : undefined,
3405
+ showVerticalBorder: this.showVerticalBorder,
3406
+ selectedTableLayout: this.selectedTableLayout,
3407
+ globalSearch: this.tableSearch
3408
+ }
3409
+ const event = {
3410
+ obj: {
3411
+ data: config
3412
+ },
3413
+ eventType: 'config'
3414
+ }
3415
+ this.genericEvent.emit(event)
3416
+ }
3417
+
3418
+ onGlobalSearch() {
3419
+ const event = {
3420
+ obj: {
3421
+ tableSearch: this.tableSearch
3422
+ },
3423
+ eventType: 'search'
3424
+ }
3425
+ this.genericEvent.emit(event)
3426
+ if (this.tableFilterViewId) {
3427
+ this.savePreset('mouseUp');
3428
+ }
3429
+ }
3430
+
3431
+ checkFilterChangesEffect(): any {
3432
+ if (!this.tableFilterViewId) return true
3433
+ const findDefaultFilter = this.tableView?.find((ele: any) => ele?.id == this.tableFilterViewId);
3434
+ const normalizeFilters = (filters: any[] | undefined): any[] => {
3435
+ if (!Array.isArray(filters)) return [];
3436
+ const cloned = filters.map(f => {
3437
+ const item = JSON.parse(JSON.stringify(f ?? {}));
3438
+ if (!Array.isArray(item._ids)) item._ids = [];
3439
+ item._ids = item._ids.slice().sort((a: any, b: any) => {
3440
+ if (typeof a === 'number' && typeof b === 'number') return a - b;
3441
+ return ('' + a).localeCompare('' + b);
3442
+ });
3443
+
3444
+ if (item.query === null) {
3445
+ } else if (typeof item.query === 'object') {
3446
+ if (!Array.isArray(item.query._ids)) item.query._ids = [];
3447
+ item.query._ids = item.query._ids.slice().sort((a: any, b: any) => {
3448
+ if (typeof a === 'number' && typeof b === 'number') return a - b;
3449
+ return ('' + a).localeCompare('' + b);
3450
+ });
3451
+ }
3452
+
3453
+ return item;
3454
+ });
3455
+ cloned.sort((a: any, b: any) => {
3456
+ const fa = (a.field || '') + '|' + (a.type || '');
3457
+ const fb = (b.field || '') + '|' + (b.type || '');
3458
+ return fa.localeCompare(fb);
3459
+ });
3460
+
3461
+ return cloned;
3462
+ };
3463
+
3464
+ const deepEqual = (a: any, b: any): boolean => {
3465
+ if (a === b) return true;
3466
+
3467
+ if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
3468
+ return a === b;
3469
+ }
3470
+
3471
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
3472
+
3473
+ if (Array.isArray(a)) {
3474
+ if (a.length !== b.length) return false;
3475
+ for (let i = 0; i < a.length; i++) {
3476
+ if (!deepEqual(a[i], b[i])) return false;
3477
+ }
3478
+ return true;
3479
+ }
3480
+
3481
+ const keysA = Object.keys(a);
3482
+ const keysB = Object.keys(b);
3483
+ if (keysA.length !== keysB.length) return false;
3484
+
3485
+ for (const key of keysA) {
3486
+ if (!keysB.includes(key)) return false;
3487
+ if (!deepEqual(a[key], b[key])) return false;
3488
+ }
3489
+ return true;
3490
+ };
3491
+ const normalizedA = normalizeFilters(findDefaultFilter?.filters);
3492
+ const normalizedB = normalizeFilters(this.filtersConfig);
3493
+ const isSame = deepEqual(normalizedA, normalizedB);
3494
+ return isSame;
3495
+ }
3496
+
3497
+
3498
+
3499
+ // *************************************************************************
3500
+ // *************************************************************************
3501
+ // *************************************************************************
3502
+ // *************************************************************************
3503
+ // Cell Selection Implemented Here Implemented Here
3504
+ // *************************************************************************
3505
+ // *************************************************************************
3506
+ // *************************************************************************
3507
+ // *************************************************************************
3508
+
3509
+ selectedCells: any[] = [];
3510
+ selectedKeys: Set<string> = new Set();
3511
+ selectionStart: any = null;
3512
+ isSelecting = false;
3513
+ startSelection(
3514
+ rowIndex: number,
3515
+ colIndex: number,
3516
+ subColIndex: number | null,
3517
+ field: string,
3518
+ event: MouseEvent,
3519
+ section: string
3520
+ ) {
3521
+ // event.preventDefault();
3522
+ this.rowSelectedIndexes.clear();
3523
+ const safeSub = subColIndex ?? 0;
3524
+ const key = `${rowIndex}-${colIndex}-${safeSub}-${field}`;
3525
+
3526
+ this.selectionStart = { rowIndex, colIndex, subColIndex: safeSub, field, key };
3527
+ this.isSelecting = true;
3528
+ this.selectedKeys.clear();
3529
+ this.selectedKeys.add(key);
3530
+ this.selectedCells = [this.selectionStart];
3531
+ const mouseUpHandler = () => {
3532
+ this.endSelection();
3533
+ document.removeEventListener('mouseup', mouseUpHandler);
3534
+ };
3535
+ document.addEventListener('mouseup', mouseUpHandler);
3536
+ this.updateSelectionBoundaries()
3537
+ }
3538
+
3539
+
3540
+ extendSelection(
3541
+ rowIndex: number,
3542
+ colIndex: number,
3543
+ subColIndex: number | null,
3544
+ field: string,
3545
+ event: MouseEvent,
3546
+ section: string
3547
+ ) {
3548
+ if (!this.isSelecting || !this.selectionStart) return;
3549
+
3550
+ const start = this.selectionStart;
3551
+ const targetSubColIndex = subColIndex ?? 0;
3552
+ const newKeys: Set<string> = new Set();
3553
+ const newCells: any[] = [];
3554
+ const minRow = Math.min(start.rowIndex, rowIndex);
3555
+ const maxRow = Math.max(start.rowIndex, rowIndex);
3556
+ const columnsToSelect = this.getColumnSelectionRange(start.colIndex, colIndex, start.subColIndex, targetSubColIndex);
3557
+
3558
+ for (let r = minRow; r <= maxRow; r++) {
3559
+ for (const colInfo of columnsToSelect) {
3560
+ const k = `${r}-${colInfo.colIndex}-${colInfo.subColIndex}-${section}`;
3561
+ newKeys.add(k);
3562
+ newCells.push({
3563
+ rowIndex: r,
3564
+ colIndex: colInfo.colIndex,
3565
+ subColIndex: colInfo.subColIndex,
3566
+ field,
3567
+ key: k
3568
+ });
3569
+ }
3570
+ }
3571
+
3572
+ this.selectedKeys = newKeys;
3573
+ this.selectedCells = newCells;
3574
+ this.updateSelectionBoundaries();
3575
+
3576
+ // Add auto-scrolling logic
3577
+ this.handleCellAutoScroll(event);
3578
+ }
3579
+
3580
+
3581
+
3582
+
3583
+ getColumnSelectionRange(
3584
+ startCol: number,
3585
+ endCol: number,
3586
+ startSub: number,
3587
+ endSub: number
3588
+ ): { colIndex: number; subColIndex: number }[] {
3589
+ const result: { colIndex: number; subColIndex: number }[] = [];
3590
+ const minCol = Math.min(startCol, endCol);
3591
+ const maxCol = Math.max(startCol, endCol);
3592
+
3593
+ const isLeftToRight = startCol <= endCol;
3594
+ for (let c = minCol; c <= maxCol; c++) {
3595
+ const subColCount = this.getSubColumnCount(c);
3596
+
3597
+ if (c === startCol && c === endCol) {
3598
+ const minSub = Math.min(startSub, endSub);
3599
+ const maxSub = Math.max(startSub, endSub);
3600
+ for (let s = minSub; s <= maxSub && s < subColCount; s++) {
3601
+ result.push({ colIndex: c, subColIndex: s });
3602
+ }
3603
+ }
3604
+ else if (c === startCol) {
3605
+ if (isLeftToRight) {
3606
+ for (let s = startSub; s < subColCount; s++) {
3607
+ result.push({ colIndex: c, subColIndex: s });
3608
+ }
3609
+ } else {
3610
+ for (let s = 0; s <= startSub && s < subColCount; s++) {
3611
+ result.push({ colIndex: c, subColIndex: s });
3612
+ }
3613
+ }
3614
+ }
3615
+ else if (c === endCol) {
3616
+ if (isLeftToRight) {
3617
+ for (let s = 0; s <= endSub && s < subColCount; s++) {
3618
+ result.push({ colIndex: c, subColIndex: s });
3619
+ }
3620
+ } else {
3621
+ for (let s = endSub; s < subColCount; s++) {
3622
+ result.push({ colIndex: c, subColIndex: s });
3623
+ }
3624
+ }
3625
+ }
3626
+ else {
3627
+ for (let s = 0; s < subColCount; s++) {
3628
+ result.push({ colIndex: c, subColIndex: s });
3629
+ }
3630
+ }
3631
+ }
3632
+
3633
+ return result;
3634
+ }
3635
+
3636
+ getSubColumnCount(colIndex: number): number {
3637
+ if (!this.columns || colIndex < 0 || colIndex >= this.columns.length) {
3638
+ return 1;
3639
+ }
3640
+ const column = this.columns[colIndex];
3641
+ if (column.children && Array.isArray(column.children)) {
3642
+ return column.children.length;
3643
+ }
3644
+ return 1;
3645
+ }
3646
+ isGroupColumn(colIndex: number): boolean {
3647
+ return this.getSubColumnCount(colIndex) > 1;
3648
+ }
3649
+ endSelection() {
3650
+ this.isSelecting = false;
3651
+ this.stopAutoScroll();
3652
+ this.updateSelectionBoundaries();
3653
+ }
3654
+ isSelected(
3655
+ rowIndex: number,
3656
+ colIndex: number,
3657
+ subColIndex: number | null,
3658
+ field: string,
3659
+ section: string
3660
+ ) {
3661
+ const safeSub = subColIndex ?? 0;
3662
+ const key = `${rowIndex}-${colIndex}-${safeSub}-${section}`;
3663
+ return this.selectedKeys.has(key);
3664
+ }
3665
+
3666
+
3667
+ private selectionBounds: {
3668
+ top: number | null;
3669
+ bottom: number | null;
3670
+ left: { colIndex: number; subColIndex: number } | null;
3671
+ right: { colIndex: number; subColIndex: number } | null;
3672
+ } = { top: null, bottom: null, left: null, right: null };
3673
+
3674
+ updateSelectionBoundaries() {
3675
+ if (this.selectedCells.length === 0) {
3676
+ this.selectionBounds = { top: null, bottom: null, left: null, right: null };
3677
+ return;
3678
+ }
3679
+
3680
+ // Get all unique row indices and find min/max
3681
+ const rowIndices = [...new Set(this.selectedCells.map(cell => cell.rowIndex))];
3682
+ this.selectionBounds.top = Math.min(...rowIndices);
3683
+ this.selectionBounds.bottom = Math.max(...rowIndices);
3684
+
3685
+ // For left/right boundaries, we need to consider both colIndex and subColIndex
3686
+ const leftMost = this.findLeftMostCell();
3687
+ const rightMost = this.findRightMostCell();
3688
+
3689
+ this.selectionBounds.left = leftMost;
3690
+ this.selectionBounds.right = rightMost;
3691
+ }
3692
+ findLeftMostCell(): { colIndex: number; subColIndex: number } | null {
3693
+ if (this.selectedCells.length === 0) return null;
3694
+
3695
+ const minColIndex = Math.min(...this.selectedCells.map(cell => cell.colIndex));
3696
+ const leftColCells = this.selectedCells.filter(cell => cell.colIndex === minColIndex);
3697
+ const minSubColIndex = Math.min(...leftColCells.map(cell => cell.subColIndex));
3698
+
3699
+ return { colIndex: minColIndex, subColIndex: minSubColIndex };
3700
+ }
3701
+ findRightMostCell(): { colIndex: number; subColIndex: number } | null {
3702
+ if (this.selectedCells.length === 0) return null;
3703
+
3704
+ const maxColIndex = Math.max(...this.selectedCells.map(cell => cell.colIndex));
3705
+ const rightColCells = this.selectedCells.filter(cell => cell.colIndex === maxColIndex);
3706
+ const maxSubColIndex = Math.max(...rightColCells.map(cell => cell.subColIndex));
3707
+
3708
+ return { colIndex: maxColIndex, subColIndex: maxSubColIndex };
3709
+ }
3710
+ isTopBorder(rowIndex: number, colIndex: number, subColIndex: number | null, section: string): boolean {
3711
+ const safeSub = subColIndex ?? 0;
3712
+ const isSelected = this.selectedKeys.has(`${rowIndex}-${colIndex}-${safeSub}-${section}`);
3713
+ return isSelected && this.selectionBounds.top !== null && rowIndex === this.selectionBounds.top;
3714
+ }
3715
+ isBottomBorder(rowIndex: number, colIndex: number, subColIndex: number | null, section: string): boolean {
3716
+ const safeSub = subColIndex ?? 0;
3717
+ const isSelected = this.selectedKeys.has(`${rowIndex}-${colIndex}-${safeSub}-${section}`);
3718
+ return isSelected && this.selectionBounds.bottom !== null && rowIndex === this.selectionBounds.bottom;
3719
+ }
3720
+ isLeftBorder(rowIndex: number, colIndex: number, subColIndex: number | null, section: string): boolean {
3721
+ const safeSub = subColIndex ?? 0;
3722
+ const isSelected = this.selectedKeys.has(`${rowIndex}-${colIndex}-${safeSub}-${section}`);
3723
+ return isSelected && this.selectionBounds.left !== null &&
3724
+ colIndex === this.selectionBounds.left.colIndex &&
3725
+ safeSub === this.selectionBounds.left.subColIndex;
3726
+ }
3727
+
3728
+ isRightBorder(rowIndex: number, colIndex: number, subColIndex: number | null, section: string): boolean {
3729
+ const safeSub = subColIndex ?? 0;
3730
+ const isSelected = this.selectedKeys.has(`${rowIndex}-${colIndex}-${safeSub}-${section}`);
3731
+ return isSelected && this.selectionBounds.right !== null &&
3732
+ colIndex === this.selectionBounds.right.colIndex &&
3733
+ safeSub === this.selectionBounds.right.subColIndex;
3734
+ }
3735
+
3736
+ isTopLeftCorner(rowIndex: number, colIndex: number, subColIndex: number | null, section: string): boolean {
3737
+ return this.isTopBorder(rowIndex, colIndex, subColIndex, section) && this.isLeftBorder(rowIndex, colIndex, subColIndex, section);
3738
+ }
3739
+
3740
+ isTopRightCorner(rowIndex: number, colIndex: number, subColIndex: number | null, section: string): boolean {
3741
+ return this.isTopBorder(rowIndex, colIndex, subColIndex, section) && this.isRightBorder(rowIndex, colIndex, subColIndex, section);
3742
+ }
3743
+
3744
+ isBottomLeftCorner(rowIndex: number, colIndex: number, subColIndex: number | null, section: string): boolean {
3745
+ return this.isBottomBorder(rowIndex, colIndex, subColIndex, section) && this.isLeftBorder(rowIndex, colIndex, subColIndex, section);
3746
+ }
3747
+
3748
+ isBottomRightCorner(rowIndex: number, colIndex: number, subColIndex: number | null, section: string): boolean {
3749
+ return this.isBottomBorder(rowIndex, colIndex, subColIndex, section) && this.isRightBorder(rowIndex, colIndex, subColIndex, section);
3750
+ }
3751
+
3752
+
3753
+ cellSelectionAutoScrollInterval: any = null;
3754
+ private scrollSpeed = 100;
3755
+ private scrollMargin = 150;
3756
+ handleCellAutoScroll(event: MouseEvent) {
3757
+ if (this.cellSelectionAutoScrollInterval) {
3758
+ clearInterval(this.cellSelectionAutoScrollInterval);
3759
+ this.cellSelectionAutoScrollInterval = null;
3760
+ }
3761
+ const horizontalContainer = this.centerScrollableBody.nativeElement;
3762
+ const verticalContainer = this.mainScroll.nativeElement;
3763
+ if (!horizontalContainer || !verticalContainer) return;
3764
+ const horizontalRect = horizontalContainer.getBoundingClientRect();
3765
+ const mouseX = event.clientX;
3766
+ const verticalRect = verticalContainer.getBoundingClientRect();
3767
+ const mouseY = event.clientY;
3768
+ const nearTop = mouseY - verticalRect.top < this.scrollMargin;
3769
+ const nearBottom = verticalRect.bottom - mouseY < this.scrollMargin;
3770
+ const nearLeft = mouseX - horizontalRect.left < this.scrollMargin;
3771
+ const nearRight = horizontalRect.right - mouseX < this.scrollMargin;
3772
+ let scrollX = 0;
3773
+ let scrollY = 0;
3774
+ if (nearTop) scrollY = -this.scrollSpeed;
3775
+ if (nearBottom) scrollY = this.scrollSpeed;
3776
+ if (nearLeft) scrollX = -this.scrollSpeed;
3777
+ if (nearRight) scrollX = this.scrollSpeed;
3778
+ if (scrollX !== 0 || scrollY !== 0) {
3779
+ this.cellSelectionAutoScrollInterval = setInterval(() => {
3780
+ if (scrollX !== 0) {
3781
+ horizontalContainer.scrollBy(scrollX, 0);
3782
+ }
3783
+ if (scrollY !== 0) {
3784
+ verticalContainer.scrollBy(0, scrollY);
3785
+ }
3786
+ }, 16);
3787
+ }
3788
+ }
3789
+
3790
+ stopAutoScroll() {
3791
+ if (this.cellSelectionAutoScrollInterval) {
3792
+ clearInterval(this.cellSelectionAutoScrollInterval);
3793
+ this.cellSelectionAutoScrollInterval = null;
3794
+ }
3795
+ }
3796
+
3797
+ // *************************************************************************
3798
+ // *************************************************************************
3799
+ // *************************************************************************
3800
+ // *************************************************************************
3801
+ // Row Selection from the Indexing Implemented Here
3802
+ // *************************************************************************
3803
+ // *************************************************************************
3804
+ // *************************************************************************
3805
+ // *************************************************************************
3806
+
3807
+ rowSelectedIndexes: Set<number> = new Set();
3808
+ rowSelecting = false;
3809
+ rowSelectionStartIndex: number | null = null;
3810
+ autoScrollInterval: any = null;
3811
+
3812
+ get firstSelectedRow(): number | null {
3813
+ if (this.rowSelectedIndexes.size === 0) return null;
3814
+ return Math.min(...this.rowSelectedIndexes);
3815
+ }
3816
+
3817
+ get lastSelectedRow(): number | null {
3818
+ if (this.rowSelectedIndexes.size === 0) return null;
3819
+ return Math.max(...this.rowSelectedIndexes);
3820
+ }
3821
+
3822
+
3823
+ onRowMouseDown(index: number, event: MouseEvent) {
3824
+ event.preventDefault();
3825
+ this.rowSelecting = true;
3826
+ this.selectedKeys.clear();
3827
+ this.rowSelectionStartIndex = index;
3828
+ this.rowSelectedIndexes.clear();
3829
+ this.rowSelectedIndexes.add(index);
3830
+ this.activeCell = '';
3831
+ this.cdr.detectChanges();
3832
+
3833
+ const mouseMoveHandler = (e: MouseEvent) => this.handleAutoScroll(e);
3834
+ const mouseUpHandler = () => {
3835
+ this.onRowMouseUp();
3836
+ document.removeEventListener('mouseup', mouseUpHandler);
3837
+ document.removeEventListener('mousemove', mouseMoveHandler);
3838
+ if (this.autoScrollInterval) {
3839
+ cancelAnimationFrame(this.autoScrollInterval);
3840
+ this.autoScrollInterval = null;
3841
+ }
3842
+ };
3843
+
3844
+ document.addEventListener('mouseup', mouseUpHandler);
3845
+ document.addEventListener('mousemove', mouseMoveHandler);
3846
+ }
3847
+
3848
+ onRowMouseOver(index: number, event: MouseEvent) {
3849
+ if (!this.rowSelecting || this.rowSelectionStartIndex === null) return;
3850
+ this.rowSelectedIndexes.clear();
3851
+ const start = Math.min(this.rowSelectionStartIndex, index);
3852
+ const end = Math.max(this.rowSelectionStartIndex, index);
3853
+ for (let i = start; i <= end; i++) {
3854
+ this.rowSelectedIndexes.add(i);
3855
+ }
3856
+ console.log([...this.rowSelectedIndexes]);
3857
+ }
3858
+
3859
+ onRowMouseUp() {
3860
+ this.rowSelecting = false;
3861
+ this.rowSelectionStartIndex = null;
3862
+ }
3863
+ handleAutoScroll(e: MouseEvent) {
3864
+ const container = this.mainScroll?.nativeElement as HTMLElement;
3865
+ if (!container) return;
3866
+
3867
+ const rect = container.getBoundingClientRect();
3868
+ const edgeSize = 40
3869
+ let scrollAmount = 0;
3870
+
3871
+ if (e.clientY < rect.top + edgeSize) {
3872
+ scrollAmount = -150;
3873
+ } else if (e.clientY > rect.bottom - edgeSize) {
3874
+ scrollAmount = 150;
3875
+ }
3876
+
3877
+ if (scrollAmount !== 0) {
3878
+ if (!this.autoScrollInterval) {
3879
+ const step = () => {
3880
+ container.scrollTop += scrollAmount;
3881
+ this.autoScrollInterval = requestAnimationFrame(step);
3882
+ };
3883
+ this.autoScrollInterval = requestAnimationFrame(step);
3884
+ }
3885
+ } else {
3886
+ if (this.autoScrollInterval) {
3887
+ cancelAnimationFrame(this.autoScrollInterval);
3888
+ this.autoScrollInterval = null;
3889
+ }
3890
+ }
3891
+ }
3892
+
3893
+
3894
+ // getSelectedDataForCopy(): any[][] {
3895
+ // const copiedRows: any[][] = [];
3896
+ // const findRowByVirtualIndex = (vIndex: number) =>
3897
+ // this.dataSet.find((r: any) => r.__virtualIndex === vIndex);
3898
+ // if (this.rowSelectedIndexes && this.rowSelectedIndexes.size > 0) {
3899
+ // const sortedIndexes = [...this.rowSelectedIndexes].sort((a, b) => a - b);
3900
+ // for (const vIndex of sortedIndexes) {
3901
+ // const row = findRowByVirtualIndex(vIndex);
3902
+ // if (!row) continue;
3903
+ // const rowValues = this.columns.flatMap(col =>
3904
+ // col.children && Array.isArray(col.children)
3905
+ // ? col.children.map((c: { field: string }) =>
3906
+ // this.getNestedValue(row, c.field) ?? ''
3907
+ // )
3908
+ // : this.getNestedValue(row, col.field) ?? ''
3909
+ // );
3910
+ // copiedRows.push(rowValues);
3911
+ // }
3912
+ // }
3913
+
3914
+ // if (this.selectedCells && this.selectedCells.length > 0) {
3915
+ // const rowsMap = new Map<number, any[]>();
3916
+ // for (const cell of this.selectedCells) {
3917
+ // const row = findRowByVirtualIndex(cell.rowIndex);
3918
+ // if (!row) continue;
3919
+
3920
+ // const col = this.columns[cell.colIndex];
3921
+ // let fieldName = col?.field;
3922
+ // if (col?.children && col.children[cell.subColIndex]) {
3923
+ // fieldName = col.children[cell.subColIndex].field;
3924
+ // }
3925
+ // const value = this.getNestedValue(row, fieldName) ?? '';
3926
+
3927
+ // if (!rowsMap.has(cell.rowIndex)) rowsMap.set(cell.rowIndex, []);
3928
+ // rowsMap.get(cell.rowIndex)!.push(value);
3929
+ // }
3930
+
3931
+ // const sortedCells = [...rowsMap.entries()]
3932
+ // .sort(([a], [b]) => a - b)
3933
+ // .map(([_, v]) => v);
3934
+
3935
+ // copiedRows.push(...sortedCells);
3936
+ // }
3937
+
3938
+ // if (copiedRows.length === 0) {
3939
+ // const activeCell = document.querySelector('.active-cell');
3940
+ // if (activeCell) return [[activeCell.textContent?.trim() || '']];
3941
+ // }
3942
+ // const maxCols = copiedRows.reduce((max, row) => Math.max(max, row.length), 0);
3943
+ // const normalized = copiedRows.map(row => {
3944
+ // const newRow = [...row];
3945
+ // while (newRow.length < maxCols) newRow.push('');
3946
+ // return newRow;
3947
+ // });
3948
+ // return normalized;
3949
+ // }
3950
+
3951
+
3952
+ undoStack: any[][] = [];
3953
+ redoStack: any[][] = [];
3954
+
3955
+ private cloneData(data: any[]): any[] {
3956
+ return JSON.parse(JSON.stringify(data));
3957
+ }
3958
+
3959
+ performCut(selectedData: any[][]) {
3960
+ // Save current state for undo
3961
+ this.undoStack.push(this.cloneData(this.visibleRows));
3962
+ this.redoStack = []; // Clear redo stack on new action
3963
+
3964
+ // Perform cut
3965
+ this.copyService.cutWithAnimation(selectedData);
3966
+ const { updatedRows } = this.copyService.cutSelectedSelectedData(
3967
+ this.visibleRows,
3968
+ this.rowSelectedIndexes,
3969
+ this.selectedCells,
3970
+ this.columns,
3971
+ this.setNestedValue.bind(this)
3972
+ );
3973
+
3974
+ console.log('Updated Rows: ', updatedRows);
3975
+ }
3976
+
3977
+
3978
+
3979
+ getSelectedDataForCopy(): any[][] {
3980
+ return this.copyService.getSelectedDataForCopy(
3981
+ this.visibleRows,
3982
+ this.columns,
3983
+ this.rowSelectedIndexes,
3984
+ this.selectedCells,
3985
+ this.getNestedValue.bind(this)
3986
+ );
3987
+ }
3988
+
3989
+ @HostListener('document:keydown', ['$event'])
3990
+ onKeyDown(event: KeyboardEvent) {
3991
+ const target = event.target as HTMLElement;
3992
+ const isFormField =
3993
+ target.tagName === 'INPUT' ||
3994
+ target.tagName === 'TEXTAREA' ||
3995
+ target.isContentEditable;
3996
+ if (isFormField) return;
3997
+
3998
+ const tableContainer = document.querySelector('.data-grid-body-wrapper'); // use your table class/id
3999
+ if (tableContainer && !tableContainer.contains(target)) return;
4000
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'c') {
4001
+ if (!this.selectedCells?.length) return
4002
+ const selectedData = this.getSelectedDataForCopy();
4003
+ event.preventDefault();
4004
+ this.copyService.copyToClipboard(selectedData);
4005
+
4006
+ } else if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'x') {
4007
+ if (!this.selectedCells?.length) return
4008
+ const selectedData = this.getSelectedDataForCopy();
4009
+ event.preventDefault();
4010
+ this.performCut(selectedData);
4011
+
4012
+ } else if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'z') {
4013
+ if (!this.selectedCells?.length) return
4014
+ event.preventDefault();
4015
+ this.undo();
4016
+
4017
+ } else if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'y') {
4018
+ if (!this.selectedCells?.length) return
4019
+ event.preventDefault();
4020
+ this.redo();
4021
+ }
4022
+ }
4023
+
4024
+ undo() {
4025
+ if (this.undoStack.length > 0) {
4026
+ const prevState = this.undoStack.pop()!;
4027
+ this.redoStack.push(this.cloneData(this.visibleRows));
4028
+ this.visibleRows = this.cloneData(prevState);
4029
+ }
4030
+ }
4031
+
4032
+ redo() {
4033
+ if (this.redoStack.length > 0) {
4034
+ const nextState = this.redoStack.pop()!;
4035
+ this.undoStack.push(this.cloneData(this.visibleRows));
4036
+ this.visibleRows = this.cloneData(nextState);
4037
+ }
4038
+ }
4039
+
4040
+
4041
+
4042
+ @HostListener('document:paste', ['$event'])
4043
+ onPaste = async (event: ClipboardEvent) => {
4044
+ if (!this.selectedCells?.length || !this.activeCell) return
4045
+ event.preventDefault();
4046
+ const text = event.clipboardData?.getData('text');
4047
+ if (!text) return;
4048
+ const startCell = this.selectedCells?.[0];
4049
+ if (!startCell) return;
4050
+ const updatedRows = await this.copyService.pasteFromClipboardText(
4051
+ text,
4052
+ this.visibleRows,
4053
+ this.columns,
4054
+ startCell.rowIndex,
4055
+ startCell.colIndex,
4056
+ startCell.subColIndex
4057
+ );
4058
+ console.log('Updated Rows:', updatedRows);
4059
+ if (updatedRows?.updateRows?.length) {
4060
+ this.genericEvent.emit({
4061
+ data: updatedRows?.updateRows,
4062
+ eventType: 'onCellEdit',
4063
+ column: updatedRows?.currentColums
4064
+ });
4065
+ }
4066
+
4067
+ this.cdr.detectChanges();
4068
+ };
4069
+
4070
+
4071
+
4072
+ isDate(value: any): boolean {
4073
+ return !isNaN(new Date(value)?.getTime());
4074
+ }
4075
+
4076
+
4077
+ // Hold expanded IDs (array for multiple, single value for single)
4078
+ expandededDetailRows: (string | number)[] | string | number = '';
4079
+
4080
+ toggleDetailRowExpand(row: any) {
4081
+ const id = row?.id || row?._id;
4082
+
4083
+ if (this.keepMultipleExpandedDetails) {
4084
+ if (!Array.isArray(this.expandededDetailRows)) {
4085
+ this.expandededDetailRows = [];
4086
+ }
4087
+ const expanded = this.expandededDetailRows as (string | number)[];
4088
+ const index = expanded.indexOf(id);
4089
+
4090
+ if (index > -1) {
4091
+ expanded.splice(index, 1);
4092
+ } else {
4093
+ expanded.push(id);
4094
+ }
4095
+ } else {
4096
+ if (this.expandededDetailRows === id) {
4097
+ this.expandededDetailRows = '';
4098
+ } else {
4099
+ this.expandededDetailRows = id;
4100
+ }
4101
+ }
4102
+
4103
+ setTimeout(() => {
4104
+ void this.rightPinnedHeader?.nativeElement.offsetWidth;
4105
+ this.centerPinnedBody?.nativeElement.offsetWidth;
4106
+ this.rightPinnedBody?.nativeElement.offsetWidth;
4107
+ this.cdr.detectChanges();
4108
+ }, 300)
4109
+ }
4110
+
4111
+ isDetailsExpanded(row: any): boolean {
4112
+ const id = row?.id || row?._id;
4113
+
4114
+ if (this.keepMultipleExpandedDetails) {
4115
+ return Array.isArray(this.expandededDetailRows) &&
4116
+ this.expandededDetailRows.includes(id);
4117
+ } else {
4118
+ return this.expandededDetailRows === id;
4119
+ }
4120
+ }
4121
+
4122
+ getFilterValue(option: any): any {
4123
+ return option?.value ?? option;
4124
+ }
4125
+
4126
+
4127
+ openIndex: number | null = null;
4128
+ toggleMenu(i: number, event?: MouseEvent) {
4129
+ if (event) { event.stopPropagation(); }
4130
+ this.openIndex = this.openIndex === i ? null : i;
4131
+ }
4132
+
4133
+ @ViewChild('nestedTable')
4134
+ nestedTable!: ElementRef<HTMLDivElement>;
4135
+ get hasVerticalScroll(): boolean {
4136
+ if (!this.nestedTable) return false;
4137
+ return this.nestedTable.nativeElement.scrollHeight > this.nestedTable.nativeElement.clientHeight;
4138
+ }
4139
+
4140
+
4141
+ getTotalAmount(column: any) {
4142
+ if (!column?.is_amount) return;
4143
+ const total = this.originalDataSet.reduce((sum, row) => {
4144
+ const value = row[column.field] || 0;
4145
+ return sum + value;
4146
+ }, 0);
4147
+
4148
+ return `${this.currencySymbol}${total}`;
4149
+ }
4150
+
4151
+ dropColumn(event: CdkDragDrop<any[]>, row: any) {
4152
+ // Reorder the columns array
4153
+ moveItemInArray(row.detail.columns, event.previousIndex, event.currentIndex);
4154
+ }
4155
+
4156
+ currentSubSortColumn: string | null = null; // active column
4157
+ currentSortDirection: 'asc' | 'desc' = 'asc'; // active direction
4158
+
4159
+ sortNestedCol(col: any, dataSet: any[] = []) {
4160
+ if (this.currentSubSortColumn === col.field) {
4161
+ this.currentSortDirection = this.currentSortDirection === 'asc' ? 'desc' : 'asc';
4162
+ } else {
4163
+ this.currentSubSortColumn = col.field;
4164
+ this.currentSortDirection = 'asc';
4165
+ }
4166
+ col.order_by = this.currentSortDirection;
4167
+ dataSet.sort((a, b) => {
4168
+ let aVal = a[col.field];
4169
+ let bVal = b[col.field];
4170
+ aVal = aVal == null ? '' : aVal;
4171
+ bVal = bVal == null ? '' : bVal;
4172
+ if (col.type === 'date') {
4173
+ aVal = new Date(aVal).getTime();
4174
+ bVal = new Date(bVal).getTime();
4175
+ }
4176
+ const aNum = parseFloat(aVal);
4177
+ const bNum = parseFloat(bVal);
4178
+ const bothNumbers = !isNaN(aNum) && !isNaN(bNum);
4179
+
4180
+ if (bothNumbers) {
4181
+ return this.currentSortDirection === 'asc' ? aNum - bNum : bNum - aNum;
4182
+ } else {
4183
+ const comp = String(aVal).localeCompare(String(bVal));
4184
+ return this.currentSortDirection === 'asc' ? comp : -comp;
4185
+ }
4186
+ });
4187
+ }
4188
+
4189
+ getColumnWidthPx(row: any, col: any): string {
4190
+ if (col.width) return col.width + 'px';
4191
+ const pixels = this.centerScrollableBody?.nativeElement?.offsetWidth / row.detail?.columns.length;
4192
+ return pixels + 'px';
4193
+ }
4194
+
4195
+ fullscreenImage: string | null = null;
4196
+
4197
+ openFullScreenImage(src: string) {
4198
+ this.fullscreenImage = src;
4199
+ }
4200
+
4201
+ getVisibleColumnCount(columns: any[]): number {
4202
+ let count = 0;
4203
+ for (const col of columns) {
4204
+ if (col.children && col.children.length > 0) {
4205
+ count += this.getVisibleColumnCount(col.children);
4206
+ } else {
4207
+ if (col.is_visible) {
4208
+ count++;
4209
+ }
4210
+ }
4211
+ }
4212
+ return count;
4213
+ }
4214
+
4215
+
4216
+ }