ng-firebase-table-kxp 1.0.1 → 1.0.2

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 (37) hide show
  1. package/esm2020/lib/components/table/table.component.mjs +776 -0
  2. package/esm2020/lib/firebase-table-kxp-lib.component.mjs +19 -0
  3. package/esm2020/lib/firebase-table-kxp-lib.module.mjs +74 -0
  4. package/esm2020/lib/firebase-table-kxp-lib.service.mjs +14 -0
  5. package/esm2020/lib/services/table.service.mjs +908 -0
  6. package/esm2020/lib/types/Table.mjs +2 -0
  7. package/esm2020/ng-firebase-table-kxp.mjs +5 -0
  8. package/esm2020/public-api.mjs +15 -0
  9. package/fesm2015/ng-firebase-table-kxp.mjs +1811 -0
  10. package/fesm2015/ng-firebase-table-kxp.mjs.map +1 -0
  11. package/fesm2020/ng-firebase-table-kxp.mjs +1784 -0
  12. package/fesm2020/ng-firebase-table-kxp.mjs.map +1 -0
  13. package/index.d.ts +5 -0
  14. package/lib/components/table/table.component.d.ts +111 -0
  15. package/lib/firebase-table-kxp-lib.component.d.ts +5 -0
  16. package/lib/firebase-table-kxp-lib.module.d.ts +21 -0
  17. package/lib/firebase-table-kxp-lib.service.d.ts +6 -0
  18. package/lib/services/table.service.d.ts +70 -0
  19. package/{src/lib/types/Table.ts → lib/types/Table.d.ts} +28 -31
  20. package/package.json +22 -2
  21. package/{src/public-api.ts → public-api.d.ts} +0 -13
  22. package/CHANGELOG.md +0 -88
  23. package/ng-package.json +0 -7
  24. package/src/lib/components/table/table.component.html +0 -555
  25. package/src/lib/components/table/table.component.scss +0 -22
  26. package/src/lib/components/table/table.component.spec.ts +0 -24
  27. package/src/lib/components/table/table.component.ts +0 -917
  28. package/src/lib/firebase-table-kxp-lib.component.spec.ts +0 -23
  29. package/src/lib/firebase-table-kxp-lib.component.ts +0 -15
  30. package/src/lib/firebase-table-kxp-lib.module.ts +0 -45
  31. package/src/lib/firebase-table-kxp-lib.service.spec.ts +0 -16
  32. package/src/lib/firebase-table-kxp-lib.service.ts +0 -9
  33. package/src/lib/services/table.service.spec.ts +0 -16
  34. package/src/lib/services/table.service.ts +0 -1235
  35. package/tsconfig.lib.json +0 -14
  36. package/tsconfig.lib.prod.json +0 -10
  37. package/tsconfig.spec.json +0 -14
@@ -1,917 +0,0 @@
1
- import { Arrange, Condition, Tab, TabData, TableData } from '../../types/Table';
2
- import { Component, Input, OnInit, ViewChild } from '@angular/core';
3
- import {
4
- AngularFirestore,
5
- QueryDocumentSnapshot,
6
- } from '@angular/fire/compat/firestore';
7
- import { MatPaginator, PageEvent } from '@angular/material/paginator';
8
- import { MatSort } from '@angular/material/sort';
9
- import { MatTableDataSource } from '@angular/material/table';
10
- import { Router } from '@angular/router';
11
- import { TableService } from '../../services/table.service';
12
- import { debounceTime, firstValueFrom, Subject } from 'rxjs';
13
- import { OrderByDirection } from 'firebase/firestore';
14
- import firebase from 'firebase/compat';
15
- import WhereFilterOp = firebase.firestore.WhereFilterOp;
16
- import firestore = firebase.firestore;
17
- import { FormControl, FormArray, FormGroup } from '@angular/forms';
18
-
19
- @Component({
20
- selector: 'lib-table',
21
- templateUrl: './table.component.html',
22
- styleUrls: ['./table.component.scss'],
23
- })
24
- export class TableComponent implements OnInit {
25
- // INPUTS
26
- @Input() data!: TableData;
27
- @Input() downloadTable?: (arrange: Arrange, conditions: Condition[]) => void;
28
- arrange: Arrange | null = null;
29
- // VARIABLES
30
- dataSource!: MatTableDataSource<any>;
31
- currentPageNumber = 1;
32
- currentClientPageIndex = 0;
33
-
34
- public items: any[] = [];
35
- public isLoading = false;
36
- private lastDoc: QueryDocumentSnapshot<any> | null = null;
37
- private firstDoc: QueryDocumentSnapshot<any> | null = null;
38
- private sortBy: { field: string; order: OrderByDirection } = {
39
- field: 'createdAt',
40
- order: 'desc',
41
- };
42
- public columnProperties: string[] = [];
43
-
44
- filtersForm: FormArray;
45
-
46
- selectSort: FormControl = new FormControl('');
47
- currentArrange = '';
48
-
49
- hasNextPage: boolean = false;
50
-
51
- dropdownItems: any[] = [];
52
- sortableDropdownItems: any[] = [];
53
- pageSize = 25;
54
- totalItems = 0;
55
- pageEvent?: PageEvent;
56
- filterValue: string | null = null;
57
-
58
- hasFilterableColumn: boolean = false;
59
- hasSortableColumn: boolean = false;
60
- filterPredicate: ((data: any, filter: string) => boolean) | undefined;
61
- private filterSubject = new Subject<string>();
62
- private readonly debounceTimeMs = 500;
63
-
64
- selectedTab: number = 0;
65
-
66
- @ViewChild(MatPaginator) paginator!: MatPaginator;
67
- @ViewChild(MatSort) sort!: MatSort;
68
-
69
- // Propriedades para controle do tooltip
70
- hoveredCell: { row: any; col: any } | null = null;
71
- showTooltip: boolean = false;
72
- tooltipTimeout: any;
73
- tooltipContent: string = '';
74
- tooltipPosition: { x: number; y: number } = { x: 0, y: 0 };
75
-
76
- // CONSTRUCTOR
77
- constructor(
78
- private router: Router,
79
- private tableService: TableService,
80
- private firestore: AngularFirestore
81
- ) {
82
- this.filtersForm = new FormArray([this.createFilterGroup()]);
83
- }
84
-
85
- createFilterGroup(): FormGroup {
86
- return new FormGroup({
87
- selectFilter: new FormControl(''),
88
- typeFilter: new FormControl(''),
89
- selectItem: new FormControl(''),
90
- initialDate: new FormControl('', this.tableService.dateFormatValidator()),
91
- finalDate: new FormControl('', this.tableService.dateFormatValidator()),
92
- });
93
- }
94
-
95
- addFilter(filterData?: {
96
- selectFilter: any;
97
- typeFilter?: string;
98
- selectItem?: any[];
99
- initialDate?: string;
100
- finalDate?: string;
101
- }): void {
102
- const newFilterGroup = this.createFilterGroup();
103
-
104
- if (filterData) {
105
- if (filterData.selectFilter) {
106
- newFilterGroup.get('selectFilter')?.setValue(filterData.selectFilter);
107
- }
108
- if (filterData.typeFilter) {
109
- newFilterGroup.get('typeFilter')?.setValue(filterData.typeFilter);
110
- }
111
- if (filterData.selectItem) {
112
- newFilterGroup.get('selectItem')?.setValue(filterData.selectItem);
113
- }
114
- if (filterData.initialDate) {
115
- newFilterGroup.get('initialDate')?.setValue(filterData.initialDate);
116
- }
117
- if (filterData.finalDate) {
118
- newFilterGroup.get('finalDate')?.setValue(filterData.finalDate);
119
- }
120
- }
121
-
122
- this.filtersForm.push(newFilterGroup);
123
- }
124
-
125
- onSelectFilterChange() {
126
- const lastIndex = this.filtersForm.length - 1;
127
- const lastFilter = this.filtersForm.at(lastIndex);
128
- if (lastFilter.get('selectFilter')?.value) {
129
- this.addFilter();
130
- }
131
- }
132
-
133
- removeFilter(index: number): void {
134
- this.filtersForm.removeAt(index);
135
- if (this.filtersForm.length === 0) {
136
- this.addFilter();
137
- }
138
- }
139
-
140
- removeAllFilters(): void {
141
- this.filtersForm.clear();
142
- this.addFilter();
143
- this.resetFilter();
144
- }
145
-
146
- // METHODS
147
- public async ngOnInit() {
148
- if (!this.data.color)
149
- this.data.color = { bg: 'bg-primary', text: 'text-black' };
150
- this.columnProperties = this.data.displayedColumns.map((column) => {
151
- return column.property;
152
- });
153
-
154
- if (this.data.actionButton && !this.data.actionButton.condition) {
155
- this.data.actionButton.condition = (_row?: any) => true;
156
- }
157
-
158
- if (this.data.pagination) {
159
- this.data.displayedColumns.forEach((col) => {
160
- if (col.isFilterable) {
161
- if (this.hasFilterableColumn === false)
162
- this.hasFilterableColumn = true;
163
-
164
- this.dropdownItems.push({
165
- ...col,
166
- arrange: 'filter',
167
- title: col.title,
168
- });
169
- }
170
- if (col.isSortable) {
171
- if (this.hasSortableColumn === false) this.hasSortableColumn = true;
172
- this.sortableDropdownItems.push({
173
- ...col,
174
- arrange: 'ascending',
175
- title: col.title + ': crescente',
176
- });
177
- this.sortableDropdownItems.push({
178
- ...col,
179
- arrange: 'descending',
180
- title: col.title + ': decrescente',
181
- });
182
- }
183
- if (col.isFilterableByDate) {
184
- this.dropdownItems.push({
185
- ...col,
186
- arrange: 'filterByDate',
187
- title: col.title + ': filtro por data',
188
- });
189
- }
190
- });
191
- if (this.data.filterableOptions) {
192
- this.data.filterableOptions.forEach((option) =>
193
- this.dropdownItems.push({ ...option, arrange: 'equals' })
194
- );
195
- }
196
- }
197
-
198
- // Sem paginação
199
- if (this.data.pagination === false) {
200
- await this.loadItems();
201
- }
202
- // Com paginação
203
- if (this.data.pagination === true) {
204
- if (this.data.sortBy)
205
- this.sortBy = {
206
- field: this.data.sortBy.field,
207
- order: this.data.sortBy.order,
208
- };
209
- this.filterSubject
210
- .pipe(debounceTime(this.debounceTimeMs))
211
- .subscribe(() => {
212
- this.loadItemsPaginated('forward', true);
213
- });
214
-
215
- this.isLoading = true;
216
- await this.loadItemsPaginated('forward', true);
217
-
218
- this.sort.active = 'createdAt';
219
- this.sort.direction = 'desc';
220
- this.dataSource.paginator = this.paginator;
221
- this.dataSource.sort = this.sort;
222
- this.totalItems = 0;
223
- if (this.data.totalRef) {
224
- for (const totalRef of this.data.totalRef) {
225
- const totalRefDoc = await totalRef.ref.get();
226
- const docData = totalRefDoc.data() as any;
227
- if (docData && docData[totalRef.field])
228
- this.totalItems = (this.totalItems +
229
- docData[totalRef.field]) as number;
230
- }
231
- }
232
- this.isLoading = false;
233
- }
234
- }
235
-
236
- getDisplayValue(col: any, row: any, withinLimit: boolean = false): string {
237
- let value: string;
238
- if (row[col.property] === null || row[col.property] === undefined) {
239
- return '';
240
- }
241
-
242
- if (col.calculateValue) {
243
- value = String(col.calculateValue(row));
244
- } else {
245
- value = this.getNestedValue(row, col.property);
246
- }
247
-
248
- // Verifica se é um array e tem arrayField definido
249
- if (Array.isArray(value) && col.arrayField) {
250
- value = this.formatArrayValue(value, col.arrayField);
251
- }
252
-
253
- if (col.queryLength && row[col.property]) {
254
- value = row[col.property];
255
- }
256
- value = col.pipe ? col.pipe.transform(value) : value;
257
- return withinLimit ? value : value.substring(0, col.charLimit) + '...';
258
- }
259
-
260
- getNestedValue(obj: any, path: string): any {
261
- if (!path) return undefined;
262
- const properties = path.split('.');
263
- return properties.reduce(
264
- (acc, currentPart) => acc && acc[currentPart],
265
- obj
266
- );
267
- }
268
-
269
- private formatArrayValue(array: any[], field: string): string {
270
- if (!Array.isArray(array) || array.length === 0) {
271
- return '';
272
- }
273
-
274
- const values = array
275
- .map((item) => {
276
- if (typeof item === 'object' && item !== null) {
277
- return item[field] || '';
278
- }
279
- return String(item);
280
- })
281
- .filter((value) => value !== '' && value !== null && value !== undefined);
282
-
283
- return values.join(', ');
284
- }
285
-
286
- // Métodos sem paginação
287
- private async loadItems() {
288
- this.items = await this.tableService.getItems(this.data.collectionRef);
289
- if (this.data.conditions) {
290
- this.filterItems();
291
- }
292
- await this.loadRelations();
293
- await this.loadQueryLengths();
294
- this.totalItems = this.items.length;
295
- this.dataSource = new MatTableDataSource(this.items);
296
- this.dataSource.paginator = this.paginator;
297
- this.dataSource.sort = this.sort;
298
- if (this.data.sortBy) {
299
- this.dataSource.sort.active = this.data.sortBy.field;
300
- this.dataSource.sort.direction = this.data.sortBy.order;
301
- this.dataSource.sort.sortChange.emit();
302
- }
303
-
304
- this.dataSource.filterPredicate = (data, filter: string) => {
305
- return this.data.displayedColumns.some((col): boolean => {
306
- if (col.filterPredicates) {
307
- return col.filterPredicates.some((predicate): boolean => {
308
- const propertyValue = data[col.property];
309
- if (!propertyValue || typeof propertyValue !== 'object') {
310
- return false;
311
- }
312
-
313
- const predicateValue = propertyValue[predicate];
314
- if (predicateValue === null || predicateValue === undefined) {
315
- return false;
316
- }
317
-
318
- return String(predicateValue)
319
- .trim()
320
- .toLocaleLowerCase()
321
- .includes(filter);
322
- });
323
- }
324
-
325
- if (col.property && col.isFilterable) {
326
- const propertyValue = data[col.property];
327
- if (propertyValue === null || propertyValue === undefined) {
328
- return false;
329
- }
330
-
331
- return String(propertyValue)
332
- .trim()
333
- .toLocaleLowerCase()
334
- .includes(filter);
335
- }
336
- return false;
337
- });
338
- };
339
- this.filterPredicate = this.dataSource.filterPredicate;
340
- }
341
-
342
- // Métodos com paginação
343
- private async loadItemsPaginated(
344
- navigation: string = 'reload',
345
- reset: boolean = false
346
- ) {
347
- if (reset && ['forward', 'reload'].includes(navigation)) {
348
- this.lastDoc = null;
349
- }
350
- if (reset && ['backward', 'reload'].includes(navigation)) {
351
- this.firstDoc = null;
352
- }
353
-
354
- const activeFilters = this.filtersForm.controls
355
- .flatMap((control) => {
356
- const group = control as FormGroup;
357
- const selectedFilter = group.get('selectFilter')?.value;
358
- if (!selectedFilter) return [];
359
-
360
- const arrange = selectedFilter.arrange;
361
-
362
- if (arrange === 'filter') {
363
- const filterValue = group.get('typeFilter')?.value;
364
- if (!filterValue) return [];
365
- return {
366
- arrange,
367
- filter: {
368
- property: selectedFilter.property,
369
- filtering: filterValue,
370
- },
371
- dateFilter: undefined,
372
- };
373
- }
374
-
375
- if (arrange === 'filterByDate') {
376
- const initial = group.get('initialDate')?.value;
377
- const final = group.get('finalDate')?.value;
378
- if (initial && final) {
379
- const [dayI, monthI, yearI] = initial.split('/');
380
- const initialDate = new Date(`${monthI}/${dayI}/${yearI}`);
381
- const [dayF, monthF, yearF] = final.split('/');
382
- const finalDate = new Date(`${monthF}/${dayF}/${yearF}`);
383
- finalDate.setHours(23, 59, 59);
384
- return {
385
- arrange,
386
- filter: undefined,
387
- dateFilter: {
388
- initial: initialDate,
389
- final: finalDate,
390
- },
391
- };
392
- }
393
- return [];
394
- }
395
-
396
- if (selectedFilter.hasOwnProperty('items') && arrange === 'equals') {
397
- const selectedItems = group.get('selectItem')?.value;
398
- if (Array.isArray(selectedItems) && selectedItems.length > 0) {
399
- return selectedItems.map((item) => ({
400
- arrange,
401
- filter: {
402
- property: item.property,
403
- filtering: item.value,
404
- },
405
- dateFilter: undefined,
406
- }));
407
- }
408
- }
409
-
410
- return [];
411
- })
412
- .filter((f) => f && (f.filter?.filtering !== undefined || f.dateFilter));
413
-
414
- this.arrange = {
415
- filters: activeFilters,
416
- sortBy: this.sortBy,
417
- };
418
-
419
- const paginated: any = {
420
- batchSize: this.pageSize,
421
- collection: this.data.collection,
422
- doc: { lastDoc: this.lastDoc, firstDoc: this.firstDoc },
423
- navigation,
424
- arrange: this.arrange,
425
- conditions: this.data.conditions,
426
- size: this.totalItems,
427
- filterFn: this.data.filterFn,
428
- clientPageIndex: this.currentClientPageIndex,
429
- };
430
-
431
- const result = await this.tableService.getPaginated(paginated);
432
- this.items = result.items;
433
- await this.loadRelations();
434
- await this.loadQueryLengths();
435
- this.lastDoc = result.lastDoc as any;
436
- this.firstDoc = result.firstDoc as any;
437
-
438
- // Atualizar currentClientPageIndex se retornado pelo fallback
439
- if (result.currentClientPageIndex !== undefined) {
440
- this.currentClientPageIndex = result.currentClientPageIndex;
441
- }
442
-
443
- let sum = 0;
444
- if (this.data.totalRef) {
445
- for (const totalRef of this.data.totalRef) {
446
- const totalRefDoc = await totalRef.ref.get();
447
- const docData = totalRefDoc.data() as any;
448
- if (docData || result.filterLength) {
449
- sum =
450
- result.filterLength ??
451
- ((sum + (docData ? docData[totalRef.field] : 0)) as number);
452
- }
453
- }
454
- this.totalItems = sum;
455
- }
456
-
457
- this.hasNextPage = result.hasNextPage;
458
- this.dataSource = new MatTableDataSource(this.items);
459
- this.filterPredicate = this.dataSource.filterPredicate;
460
- }
461
-
462
- async onPageChange(event?: PageEvent) {
463
- if (this.data.pagination === true && event) {
464
- this.isLoading = true;
465
-
466
- const previousPageIndex = event.previousPageIndex ?? 0;
467
- const pageIndex = event.pageIndex;
468
- const currentComponentPageSize = this.pageSize;
469
- const eventPageSize = event.pageSize;
470
- const totalItems = this.totalItems;
471
-
472
- let navigationDirection: string;
473
- let resetDocs = false;
474
- let originalPageSize: number | null = null;
475
-
476
- const lastPageIndex = Math.max(
477
- 0,
478
- Math.ceil(totalItems / eventPageSize) - 1
479
- );
480
-
481
- // Atualizar currentClientPageIndex sempre para o fallback
482
- this.currentClientPageIndex = pageIndex;
483
-
484
- if (previousPageIndex !== undefined && pageIndex > previousPageIndex) {
485
- this.currentPageNumber++;
486
- } else if (
487
- previousPageIndex !== undefined &&
488
- pageIndex < previousPageIndex
489
- ) {
490
- this.currentPageNumber = Math.max(1, this.currentPageNumber - 1);
491
- }
492
-
493
- if (eventPageSize !== currentComponentPageSize) {
494
- console.log('Alterou a quantidade de elementos exibidos por página');
495
- this.pageSize = eventPageSize;
496
- navigationDirection = 'forward';
497
- resetDocs = true;
498
- this.currentClientPageIndex = 0;
499
- } else if (
500
- pageIndex === 0 &&
501
- previousPageIndex !== undefined &&
502
- pageIndex < previousPageIndex
503
- ) {
504
- console.log('Pulou para a primeira página');
505
- navigationDirection = 'forward';
506
- this.currentPageNumber = 1;
507
- this.currentClientPageIndex = 0;
508
- resetDocs = true;
509
- } else if (
510
- pageIndex === lastPageIndex &&
511
- previousPageIndex !== undefined &&
512
- pageIndex > previousPageIndex &&
513
- pageIndex - previousPageIndex > 1
514
- ) {
515
- console.log('Pulou para a ultima página');
516
- navigationDirection = 'backward';
517
- resetDocs = true;
518
-
519
- const itemsExpectedInLastPage =
520
- totalItems - lastPageIndex * eventPageSize;
521
-
522
- if (
523
- itemsExpectedInLastPage > 0 &&
524
- itemsExpectedInLastPage < eventPageSize
525
- ) {
526
- originalPageSize = this.pageSize;
527
- this.pageSize = itemsExpectedInLastPage;
528
- }
529
- } else if (
530
- previousPageIndex !== undefined &&
531
- pageIndex > previousPageIndex
532
- ) {
533
- console.log('Procedendo');
534
- navigationDirection = 'forward';
535
- resetDocs = false;
536
- } else if (
537
- previousPageIndex !== undefined &&
538
- pageIndex < previousPageIndex
539
- ) {
540
- console.log('Retrocedendo.');
541
- navigationDirection = 'backward';
542
- resetDocs = false;
543
- } else if (
544
- previousPageIndex !== undefined &&
545
- pageIndex === previousPageIndex
546
- ) {
547
- console.log('Recarregando.');
548
- navigationDirection = 'reload';
549
- resetDocs = false;
550
- } else if (previousPageIndex === undefined && pageIndex === 0) {
551
- console.log(
552
- 'Evento inicial do paginador para pág 0. ngOnInit carregou.'
553
- );
554
- this.isLoading = false;
555
- if (event) this.pageEvent = event;
556
- return;
557
- } else {
558
- console.warn('INESPERADO! Condição de navegação não tratada:', event);
559
- this.isLoading = false;
560
- if (event) this.pageEvent = event;
561
- return;
562
- }
563
-
564
- if (navigationDirection) {
565
- try {
566
- await this.loadItemsPaginated(navigationDirection, resetDocs);
567
- } catch (error) {
568
- console.error('Erro ao carregar itens paginados:', error);
569
- } finally {
570
- if (originalPageSize !== null) {
571
- this.pageSize = originalPageSize;
572
- }
573
- }
574
- }
575
-
576
- if (event) this.pageEvent = event;
577
- this.isLoading = false;
578
- }
579
- }
580
-
581
- // Outros métodos
582
-
583
- applyFilter(value: string) {
584
- // Sem paginação
585
- if (this.data.pagination === false) {
586
- this.dataSource.filter = String(value).trim().toLowerCase();
587
- }
588
- // Com paginação
589
- if (this.data.pagination === true) {
590
- this.filterValue = value;
591
- this.filterSubject.next(this.filterValue);
592
- }
593
- }
594
-
595
- goToDetails(row: any): void {
596
- if (this.data.isNotClickable) {
597
- return;
598
- }
599
- const urlPath = this.data.url || this.data.name;
600
- const url = this.router.serializeUrl(
601
- this.router.createUrlTree([`/${urlPath}`, row.id])
602
- );
603
- window.open(url, '_blank');
604
- }
605
-
606
- async getRelation(params: {
607
- id: any;
608
- collection: string;
609
- newProperty: string;
610
- }): Promise<string> {
611
- try {
612
- let snapshot: firestore.DocumentSnapshot<unknown> | undefined;
613
- if (
614
- params.id !== '' &&
615
- params.id !== undefined &&
616
- params.collection !== undefined &&
617
- params.collection !== ''
618
- ) {
619
- snapshot = await firstValueFrom(
620
- this.firestore.collection(params.collection).doc(params.id).get()
621
- );
622
- }
623
- if (snapshot && snapshot.exists) {
624
- const data = snapshot.data() as any;
625
- return data?.[params.newProperty] ?? '';
626
- }
627
- return '';
628
- } catch (e) {
629
- console.log(e);
630
- return '';
631
- }
632
- }
633
-
634
- private async loadRelations() {
635
- const relationPromises = this.data.displayedColumns
636
- .filter((col) => col.relation)
637
- .flatMap((col) =>
638
- this.items.map(async (item) => {
639
- if (col.relation) {
640
- item[col.property] = await this.getRelation({
641
- id: item[col.relation.property],
642
- collection: col.relation.collection,
643
- newProperty: col.relation.newProperty,
644
- });
645
- }
646
- })
647
- );
648
-
649
- await Promise.all(relationPromises);
650
- }
651
-
652
- async getQueryLength(params: {
653
- item: any;
654
- relation: {
655
- collection: string;
656
- property: string;
657
- operator: WhereFilterOp;
658
- value: string;
659
- };
660
- }) {
661
- const snapshot = await this.firestore
662
- .collection(params.relation.collection)
663
- .ref.where(
664
- params.relation.property,
665
- params.relation.operator,
666
- params.item[params.relation.value]
667
- )
668
- .get();
669
- return snapshot.size;
670
- }
671
-
672
- private async loadQueryLengths() {
673
- const lengthPromises = this.data.displayedColumns
674
- .filter((col) => col.queryLength)
675
- .flatMap((col) =>
676
- this.items.map(async (item) => {
677
- if (col.queryLength) {
678
- item[col.property] = await this.getQueryLength({
679
- item: item,
680
- relation: col.queryLength,
681
- });
682
- }
683
- })
684
- );
685
- await Promise.all(lengthPromises);
686
- }
687
-
688
- private filterItems() {
689
- if (this.data.conditions) {
690
- this.data.conditions.forEach((cond) => {
691
- this.items = this.items.filter((item) => {
692
- const operatorFunction = (this.tableService.operators as any)[
693
- cond.operator
694
- ];
695
- if (operatorFunction) {
696
- return operatorFunction(
697
- item[cond.firestoreProperty],
698
- cond.dashProperty
699
- );
700
- }
701
- return false;
702
- });
703
- });
704
- }
705
- }
706
-
707
- // Filtro de data
708
- async search() {
709
- if (this.selectSort.value) {
710
- if (this.selectSort.value.arrange === 'ascending') {
711
- this.sortBy = {
712
- field: this.selectSort.value.property,
713
- order: 'asc',
714
- };
715
- }
716
- if (this.selectSort.value.arrange === 'descending') {
717
- this.sortBy = {
718
- field: this.selectSort.value.property,
719
- order: 'desc',
720
- };
721
- }
722
- }
723
-
724
- await this.loadItemsPaginated('reload', true);
725
- this.currentArrange =
726
- this.filtersForm.length > 0
727
- ? this.filtersForm.at(0).get('selectFilter')?.value?.arrange ?? ''
728
- : '';
729
- this.paginator.firstPage();
730
- }
731
-
732
- async resetFilter() {
733
- if (!this.data.pagination) {
734
- this.dataSource.filter = '';
735
- if (this.filterPredicate) {
736
- this.dataSource.filterPredicate = this.filterPredicate;
737
- }
738
- } else {
739
- this.dataSource.filter = '';
740
- if (this.filterPredicate) {
741
- this.dataSource.filterPredicate = this.filterPredicate;
742
- }
743
- }
744
- this.filtersForm.clear();
745
- this.addFilter();
746
- this.selectSort.patchValue('');
747
- this.sortBy = {
748
- order: this.data.sortBy ? this.data.sortBy.order : 'desc',
749
- field: this.data.sortBy ? this.data.sortBy.field : 'createdAt',
750
- };
751
-
752
- await this.loadItemsPaginated('reload', true);
753
- this.currentArrange = '';
754
- this.paginator.firstPage();
755
- }
756
-
757
- // Método público para recarregar a tabela
758
- async reloadTable() {
759
- if (this.data.pagination) {
760
- await this.loadItemsPaginated('reload', true);
761
- this.paginator.firstPage();
762
- } else {
763
- await this.loadItems();
764
- }
765
- }
766
-
767
- updateDisplayedColumns() {
768
- if (this.dataSource) {
769
- this.dataSource = new MatTableDataSource<any>([]);
770
- }
771
-
772
- this.columnProperties = this.data.displayedColumns.map((column) => {
773
- return column.property;
774
- });
775
-
776
- this.dropdownItems = [];
777
- this.sortableDropdownItems = [];
778
-
779
- this.data.displayedColumns.forEach((col) => {
780
- if (col.isFilterable) {
781
- this.dropdownItems.push({
782
- ...col,
783
- arrange: 'filter',
784
- title: col.title,
785
- });
786
- }
787
- if (col.isSortable) {
788
- this.sortableDropdownItems.push({
789
- ...col,
790
- arrange: 'ascending',
791
- title: col.title + ': crescente',
792
- });
793
- this.sortableDropdownItems.push({
794
- ...col,
795
- arrange: 'descending',
796
- title: col.title + ': decrescente',
797
- });
798
- }
799
- if (col.isFilterableByDate) {
800
- this.dropdownItems.push({
801
- ...col,
802
- arrange: 'filterByDate',
803
- title: col.title + ': filtro por data',
804
- });
805
- }
806
- });
807
-
808
- if (this.data.filterableOptions) {
809
- this.data.filterableOptions.forEach((option) =>
810
- this.dropdownItems.push({ ...option, arrange: 'equals' })
811
- );
812
- }
813
- }
814
-
815
- isString(value: any): boolean {
816
- return typeof value === 'string';
817
- }
818
-
819
- // Métodos para controle do tooltip
820
- onCellMouseEnter(event: MouseEvent, row: any, col: any): void {
821
- // Só mostrar tooltip se a coluna tiver charLimit definido
822
- if (!col.charLimit) {
823
- return;
824
- }
825
-
826
- const fullValue = this.getDisplayValue(col, row, true);
827
-
828
- // Só mostrar tooltip se o valor completo for maior que o limite
829
- if (fullValue.length <= col.charLimit) {
830
- return;
831
- }
832
-
833
- this.hoveredCell = { row, col };
834
- this.tooltipContent = fullValue;
835
-
836
- // Definir posição do tooltip
837
- this.tooltipPosition = {
838
- x: event.clientX + 10,
839
- y: event.clientY - 10,
840
- };
841
-
842
- // Timeout para mostrar o tooltip
843
- this.tooltipTimeout = setTimeout(() => {
844
- if (
845
- this.hoveredCell &&
846
- this.hoveredCell.row === row &&
847
- this.hoveredCell.col === col
848
- ) {
849
- this.showTooltip = true;
850
- }
851
- }, 500);
852
- }
853
-
854
- onCellMouseLeave(): void {
855
- if (this.tooltipTimeout) {
856
- clearTimeout(this.tooltipTimeout);
857
- this.tooltipTimeout = null;
858
- }
859
-
860
- this.showTooltip = false;
861
- this.hoveredCell = null;
862
- this.tooltipContent = '';
863
- }
864
-
865
- onCellMouseMove(event: MouseEvent): void {
866
- if (this.showTooltip) {
867
- this.tooltipPosition = {
868
- x: event.clientX + 10,
869
- y: event.clientY - 10,
870
- };
871
- }
872
- }
873
-
874
- // Métodos para inversão vertical dos tabs
875
- getTabGroups(tabs: any[]): number[] {
876
- if (!tabs || tabs.length === 0) return [];
877
-
878
- const totalGroups = Math.ceil(tabs.length / 6);
879
- const groups: number[] = [];
880
-
881
- // Criar array de índices invertidos (último grupo primeiro)
882
- for (let i = totalGroups - 1; i >= 0; i--) {
883
- groups.push(i);
884
- }
885
-
886
- return groups;
887
- }
888
-
889
- getTabGroup(tabs: any[], groupIndex: number): any[] {
890
- if (!tabs || tabs.length === 0) return [];
891
-
892
- const startIndex = groupIndex * 6;
893
- const endIndex = Math.min(startIndex + 6, tabs.length);
894
-
895
- return tabs.slice(startIndex, endIndex);
896
- }
897
-
898
- getRealTabIndex(groupIndex: number, tabIndexInGroup: number): number {
899
- if (!this.data.tabs?.tabsData) return 0;
900
- const totalGroups = Math.ceil(this.data.tabs.tabsData.length / 6);
901
- const realGroupIndex = totalGroups - 1 - groupIndex;
902
- return realGroupIndex * 6 + tabIndexInGroup;
903
- }
904
-
905
- onTableSelected(i: number, j: number) {
906
- if (!this.data.tabs?.tabsData || !this.data.tabs.method) return;
907
- this.selectedTab = this.getRealTabIndex(i, j);
908
- const tab = this.data.tabs.tabsData[this.selectedTab];
909
- if (tab) {
910
- this.data.tabs.method(tab, this.selectedTab);
911
- }
912
- }
913
-
914
- isTabSelected(originalIndex: number): boolean {
915
- return this.selectedTab === originalIndex;
916
- }
917
- }