ng-firebase-table-kxp 1.0.10 → 1.0.12

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.
@@ -36,2111 +36,2273 @@ import * as i1 from '@angular/fire/compat/firestore';
36
36
  import * as i3 from 'ngx-toastr';
37
37
  import * as i12 from '@angular/material/core';
38
38
 
39
- class NgFirebaseTableKxpComponent {
40
- }
41
- NgFirebaseTableKxpComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
42
- NgFirebaseTableKxpComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: NgFirebaseTableKxpComponent, selector: "lib-ng-firebase-table-kxp", ngImport: i0, template: ` <p>ng-firebase-table-kxp works!</p> `, isInline: true });
43
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpComponent, decorators: [{
44
- type: Component,
45
- args: [{ selector: 'lib-ng-firebase-table-kxp', template: ` <p>ng-firebase-table-kxp works!</p> ` }]
39
+ class NgFirebaseTableKxpComponent {
40
+ }
41
+ NgFirebaseTableKxpComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
42
+ NgFirebaseTableKxpComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: NgFirebaseTableKxpComponent, selector: "lib-ng-firebase-table-kxp", ngImport: i0, template: ` <p>ng-firebase-table-kxp works!</p> `, isInline: true });
43
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpComponent, decorators: [{
44
+ type: Component,
45
+ args: [{ selector: 'lib-ng-firebase-table-kxp', template: ` <p>ng-firebase-table-kxp works!</p> ` }]
46
46
  }] });
47
47
 
48
- class TableService {
49
- constructor(ngFire, dialog, toastr) {
50
- this.ngFire = ngFire;
51
- this.dialog = dialog;
52
- this.toastr = toastr;
53
- this.operators = {
54
- '==': (a, b) => a === b,
55
- '!=': (a, b) => a !== b,
56
- '>': (a, b) => a > b,
57
- '<': (a, b) => a < b,
58
- '>=': (a, b) => a >= b,
59
- '<=': (a, b) => a <= b,
60
- in: (a, b) => Array.isArray(b) && b.includes(a),
61
- 'not-in': (a, b) => Array.isArray(b) && !b.includes(a),
62
- 'array-contains': (a, b) => Array.isArray(a) && a.includes(b),
63
- 'array-contains-any': (a, b) => Array.isArray(a) &&
64
- Array.isArray(b) &&
65
- b.some((item) => a.includes(item)),
66
- includes: (a, b) => a.includes(b), // Para strings ou arrays
67
- };
68
- }
69
- getItems(collection) {
70
- return __awaiter(this, void 0, void 0, function* () {
71
- try {
72
- const querySnapshot = yield collection.get();
73
- return querySnapshot.docs.map((doc) => {
74
- return Object.assign(Object.assign({}, doc.data()), { id: doc.id });
75
- });
76
- }
77
- catch (error) {
78
- console.warn('Collection não encontrada:', error);
79
- return [];
80
- }
81
- });
82
- }
83
- executeQuery(params) {
84
- return __awaiter(this, void 0, void 0, function* () {
85
- if (params.filterFn) {
86
- // Lógica com filtro no cliente (filterFn)
87
- const BATCH_FETCH_SIZE = params.batchSize;
88
- const GOAL_SIZE = params.batchSize + 1;
89
- if (params.navigation === 'forward' || params.navigation === 'reload') {
90
- if (params.navigation === 'reload' && params.doc) {
91
- params.doc.lastDoc = null;
92
- }
93
- let lastDocCursor = params.doc ? params.doc.lastDoc : null;
94
- let pageResults = [];
95
- let allFetchedDocs = [];
96
- let hasMoreDocsInDb = true;
97
- while (pageResults.length < GOAL_SIZE && hasMoreDocsInDb) {
98
- let query = this.ngFire.collection(params.collection).ref;
99
- query = this.applyFilters(query, params.arrange, params.conditions);
100
- if (lastDocCursor) {
101
- query = query.startAfter(lastDocCursor);
102
- }
103
- query = query.limit(BATCH_FETCH_SIZE);
104
- const snapshot = yield query.get();
105
- if (snapshot.empty) {
106
- hasMoreDocsInDb = false;
107
- break;
108
- }
109
- lastDocCursor = snapshot.docs[snapshot.docs.length - 1];
110
- allFetchedDocs.push(...snapshot.docs);
111
- const batchUsers = snapshot.docs
112
- .map((doc) => (Object.assign({ id: doc.id }, doc.data())))
113
- .filter(params.filterFn);
114
- pageResults.push(...batchUsers);
115
- if (snapshot.size < BATCH_FETCH_SIZE) {
116
- hasMoreDocsInDb = false;
117
- }
118
- }
119
- const hasNextPage = pageResults.length > params.batchSize;
120
- const finalItems = pageResults.slice(0, params.batchSize);
121
- const firstDocOfPage = allFetchedDocs.find((doc) => { var _a; return doc.id === ((_a = finalItems[0]) === null || _a === void 0 ? void 0 : _a.id); }) || null;
122
- const lastDocOfPage = allFetchedDocs.find((doc) => { var _a; return doc.id === ((_a = finalItems[finalItems.length - 1]) === null || _a === void 0 ? void 0 : _a.id); }) || null;
123
- return {
124
- items: finalItems,
125
- filterLength: null,
126
- firstDoc: firstDocOfPage,
127
- lastDoc: lastDocOfPage,
128
- hasNextPage: hasNextPage,
129
- hasPreviousPage: !!(params.doc && params.doc.lastDoc) &&
130
- params.navigation !== 'reload',
131
- currentClientPageIndex: undefined,
132
- };
133
- }
134
- // Lógica para trás (backward)
135
- else if (params.navigation === 'backward') {
136
- if (!params.doc || !params.doc.firstDoc) {
137
- return {
138
- items: [],
139
- filterLength: null,
140
- firstDoc: null,
141
- lastDoc: null,
142
- hasNextPage: true,
143
- hasPreviousPage: false,
144
- currentClientPageIndex: undefined,
145
- };
146
- }
147
- let pageResults = [];
148
- let allFetchedDocs = [];
149
- let hasMoreDocsInDb = true;
150
- let boundaryDoc = params.doc.firstDoc;
151
- while (pageResults.length < GOAL_SIZE && hasMoreDocsInDb) {
152
- let query = this.ngFire.collection(params.collection).ref;
153
- query = this.applyFilters(query, params.arrange, params.conditions);
154
- query = query.endBefore(boundaryDoc);
155
- query = query.limitToLast(BATCH_FETCH_SIZE);
156
- const snapshot = yield query.get();
157
- if (snapshot.empty) {
158
- hasMoreDocsInDb = false;
159
- break;
160
- }
161
- boundaryDoc = snapshot.docs[0];
162
- allFetchedDocs = [...snapshot.docs, ...allFetchedDocs];
163
- const batchUsers = snapshot.docs
164
- .map((doc) => (Object.assign({ id: doc.id }, doc.data())))
165
- .filter(params.filterFn);
166
- pageResults = [...batchUsers, ...pageResults];
167
- if (snapshot.size < BATCH_FETCH_SIZE) {
168
- hasMoreDocsInDb = false;
169
- }
170
- }
171
- const finalItems = pageResults.slice(0, params.batchSize);
172
- const firstDocOfPage = allFetchedDocs.find((doc) => { var _a; return doc.id === ((_a = finalItems[0]) === null || _a === void 0 ? void 0 : _a.id); }) || null;
173
- const lastDocOfPage = allFetchedDocs.find((doc) => { var _a; return doc.id === ((_a = finalItems[finalItems.length - 1]) === null || _a === void 0 ? void 0 : _a.id); }) || null;
174
- return {
175
- items: finalItems,
176
- filterLength: null,
177
- firstDoc: firstDocOfPage,
178
- lastDoc: lastDocOfPage,
179
- hasNextPage: true,
180
- currentClientPageIndex: undefined,
181
- };
182
- }
183
- }
184
- else {
185
- let items = [];
186
- let docs = [];
187
- let hasNextPage = false;
188
- let filterLength = null;
189
- let query = this.ngFire.collection(params.collection).ref;
190
- if (params.conditions) {
191
- params.conditions.forEach((c) => {
192
- if (c.operator === '!=') {
193
- query = query.orderBy(c.firestoreProperty);
194
- }
195
- });
196
- }
197
- query = this.applyFilters(query, params.arrange, params.conditions);
198
- if (params.navigation === 'reload') {
199
- query = query.limit(params.batchSize + 1);
200
- if (params.doc && params.doc.firstDoc) {
201
- query = query.startAt(params.doc.firstDoc);
202
- }
203
- }
204
- else if (params.navigation === 'forward') {
205
- query = query.limit(params.batchSize + 1);
206
- if (params.doc && params.doc.lastDoc) {
207
- query = query.startAfter(params.doc.lastDoc);
208
- }
209
- }
210
- else {
211
- // backward
212
- query = query.limitToLast(params.batchSize + 1);
213
- if (params.doc && params.doc.firstDoc) {
214
- query = query.endBefore(params.doc.firstDoc);
215
- }
216
- }
217
- const itemCol = yield query.get();
218
- itemCol.docs.forEach((doc) => docs.push(doc));
219
- const itemPromises = docs.map((item) => __awaiter(this, void 0, void 0, function* () {
220
- const itemData = item.data();
221
- items.push(Object.assign({ id: item.id }, itemData));
222
- }));
223
- let lastDoc = docs[docs.length - 1] || null;
224
- let firstDoc = docs[0];
225
- if ((items.length > params.batchSize && params.navigation === 'forward') ||
226
- (params.navigation === 'reload' && items.length > params.batchSize)) {
227
- lastDoc = docs[docs.length - 2] || null;
228
- items.pop();
229
- hasNextPage = true;
230
- }
231
- if (items.length > params.batchSize && params.navigation === 'backward') {
232
- firstDoc = docs[1];
233
- items.shift();
234
- hasNextPage = true;
235
- }
236
- yield Promise.all(itemPromises);
237
- return {
238
- items,
239
- filterLength,
240
- lastDoc,
241
- firstDoc,
242
- hasNextPage,
243
- currentClientPageIndex: undefined,
244
- };
245
- }
246
- // Fallback para garantir que sempre retornamos algo
247
- return {
248
- items: [],
249
- filterLength: null,
250
- firstDoc: null,
251
- lastDoc: null,
252
- hasNextPage: false,
253
- currentClientPageIndex: undefined,
254
- };
255
- });
256
- }
257
- applyFilters(query, arrange, conditions) {
258
- if (conditions) {
259
- conditions.map((cond) => {
260
- query = query.where(cond.firestoreProperty, cond.operator, cond.dashProperty);
261
- });
262
- }
263
- let hasFilterSpecificOrderBy = false;
264
- let appliedOrderByField = null;
265
- const equalsFilters = arrange.filters.filter((f) => f.arrange === 'equals' && f.filter);
266
- const otherFilters = arrange.filters.filter((f) => f.arrange !== 'equals');
267
- const equalsGroupedByProperty = equalsFilters.reduce((acc, current) => {
268
- const prop = current.filter.property;
269
- if (!acc[prop]) {
270
- acc[prop] = [];
271
- }
272
- acc[prop].push(current.filter.filtering);
273
- return acc;
274
- }, {});
275
- for (const prop in equalsGroupedByProperty) {
276
- const values = equalsGroupedByProperty[prop];
277
- if (values.length > 0) {
278
- query = query.where(prop, 'in', values);
279
- }
280
- }
281
- otherFilters.forEach((filterItem) => {
282
- var _a, _b;
283
- // Aplicar filtragem por busca
284
- if (((_a = filterItem.filter) === null || _a === void 0 ? void 0 : _a.filtering) &&
285
- ((_b = filterItem.filter) === null || _b === void 0 ? void 0 : _b.property) !== '' &&
286
- filterItem.arrange === 'filter') {
287
- query = query
288
- .where(filterItem.filter.property, '>=', filterItem.filter.filtering.trim().toUpperCase())
289
- .where(filterItem.filter.property, '<=', filterItem.filter.filtering.trim().toUpperCase() + '\uf8ff');
290
- if (!hasFilterSpecificOrderBy) {
291
- query = query.orderBy(filterItem.filter.property);
292
- hasFilterSpecificOrderBy = true;
293
- appliedOrderByField = filterItem.filter.property;
294
- }
295
- }
296
- // Aplicar filtro do tipo "filterByDate"
297
- if (filterItem.dateFilter && filterItem.arrange === 'filterByDate') {
298
- query = query
299
- .where(arrange.sortBy.field, '>=', filterItem.dateFilter.initial)
300
- .where(arrange.sortBy.field, '<=', filterItem.dateFilter.final);
301
- if (!hasFilterSpecificOrderBy) {
302
- query = query.orderBy(arrange.sortBy.field);
303
- hasFilterSpecificOrderBy = true;
304
- appliedOrderByField = arrange.sortBy.field;
305
- }
306
- }
307
- });
308
- // Aplicar sortBy
309
- if (arrange.sortBy && arrange.sortBy.field && arrange.sortBy.order) {
310
- if (appliedOrderByField !== arrange.sortBy.field) {
311
- query = query.orderBy(arrange.sortBy.field, arrange.sortBy.order);
312
- }
313
- }
314
- return query;
315
- }
316
- getIdFilter(params) {
317
- var _a, _b, _c;
318
- if (!((_a = params.arrange) === null || _a === void 0 ? void 0 : _a.filters))
319
- return null;
320
- const idFilter = params.arrange.filters.find((f) => {
321
- var _a, _b;
322
- return f.arrange === 'filter' &&
323
- ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.property) === 'id' &&
324
- ((_b = f.filter) === null || _b === void 0 ? void 0 : _b.filtering);
325
- });
326
- return ((_c = (_b = idFilter === null || idFilter === void 0 ? void 0 : idFilter.filter) === null || _b === void 0 ? void 0 : _b.filtering) === null || _c === void 0 ? void 0 : _c.trim()) || null;
327
- }
328
- getDocumentById(collection, docId) {
329
- return __awaiter(this, void 0, void 0, function* () {
330
- try {
331
- const docRef = this.ngFire.collection(collection).doc(docId);
332
- const docSnapshot = yield docRef.get().toPromise();
333
- if (docSnapshot && docSnapshot.exists) {
334
- return Object.assign({ id: docSnapshot.id }, docSnapshot.data());
335
- }
336
- return null;
337
- }
338
- catch (error) {
339
- console.warn('Erro ao buscar documento por ID:', error);
340
- return null;
341
- }
342
- });
343
- }
344
- searchByIdPartial(params, searchTerm) {
345
- var _a, _b, _c, _d;
346
- return __awaiter(this, void 0, void 0, function* () {
347
- const exactMatch = yield this.getDocumentById(params.collection, searchTerm);
348
- if (exactMatch) {
349
- if (params.conditions) {
350
- const operators = this.operators;
351
- const passesConditions = params.conditions.every((cond) => {
352
- const operatorFn = operators[cond.operator];
353
- return operatorFn
354
- ? operatorFn(exactMatch[cond.firestoreProperty], cond.dashProperty)
355
- : false;
356
- });
357
- if (!passesConditions) {
358
- return {
359
- items: [],
360
- filterLength: 0,
361
- firstDoc: null,
362
- lastDoc: null,
363
- hasNextPage: false,
364
- hasPreviousPage: false,
365
- currentClientPageIndex: 0,
366
- totalPages: 0,
367
- };
368
- }
369
- }
370
- if (params.filterFn && !params.filterFn(exactMatch)) {
371
- return {
372
- items: [],
373
- filterLength: 0,
374
- firstDoc: null,
375
- lastDoc: null,
376
- hasNextPage: false,
377
- hasPreviousPage: false,
378
- currentClientPageIndex: 0,
379
- totalPages: 0,
380
- };
381
- }
382
- return {
383
- items: [exactMatch],
384
- filterLength: 1,
385
- firstDoc: null,
386
- lastDoc: null,
387
- hasNextPage: false,
388
- hasPreviousPage: false,
389
- currentClientPageIndex: 0,
390
- totalPages: 1,
391
- };
392
- }
393
- const searchTermLower = searchTerm.toLowerCase();
394
- const paramsWithoutIdFilter = Object.assign(Object.assign({}, params), { arrange: Object.assign(Object.assign({}, params.arrange), { filters: params.arrange.filters.filter((f) => { var _a; return !(f.arrange === 'filter' && ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.property) === 'id'); }) }) });
395
- let query = this.ngFire.collection(params.collection).ref;
396
- // Aplicar conditions
397
- if (params.conditions) {
398
- params.conditions.forEach((cond) => {
399
- query = query.where(cond.firestoreProperty, cond.operator, cond.dashProperty);
400
- });
401
- }
402
- // Aplicar sortBy
403
- if (((_b = (_a = params.arrange) === null || _a === void 0 ? void 0 : _a.sortBy) === null || _b === void 0 ? void 0 : _b.field) && ((_d = (_c = params.arrange) === null || _c === void 0 ? void 0 : _c.sortBy) === null || _d === void 0 ? void 0 : _d.order)) {
404
- query = query.orderBy(params.arrange.sortBy.field, params.arrange.sortBy.order);
405
- }
406
- const snapshot = yield query.get();
407
- let items = snapshot.docs
408
- .map((doc) => (Object.assign({ id: doc.id }, doc.data())))
409
- .filter((item) => item.id.toLowerCase().includes(searchTermLower));
410
- // Separar equals filters e outros filtros
411
- const equalsFilters = paramsWithoutIdFilter.arrange.filters.filter((f) => f.arrange === 'equals' && f.filter);
412
- const otherFilters = paramsWithoutIdFilter.arrange.filters.filter((f) => {
413
- var _a;
414
- return f.arrange !== 'equals' &&
415
- (f.arrange !== 'filter' || ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.property) !== 'id');
416
- });
417
- // Aplicar equals filters com lógica OR dentro de cada propriedade
418
- if (equalsFilters.length > 0) {
419
- // Agrupar por propriedade para aplicar OR
420
- const groupedByProperty = equalsFilters.reduce((acc, f) => {
421
- const prop = f.filter.property;
422
- if (!acc[prop]) {
423
- acc[prop] = [];
424
- }
425
- acc[prop].push(f.filter.filtering);
426
- return acc;
427
- }, {});
428
- // Filtrar: item deve ter pelo menos um valor de CADA propriedade
429
- items = items.filter((item) => {
430
- return Object.entries(groupedByProperty).every(([prop, values]) => {
431
- return values.includes(item[prop]);
432
- });
433
- });
434
- }
435
- // Aplicar outros filtros
436
- otherFilters.forEach((filterItem) => {
437
- var _a, _b;
438
- if (filterItem.arrange === 'filter' &&
439
- ((_a = filterItem.filter) === null || _a === void 0 ? void 0 : _a.filtering) &&
440
- ((_b = filterItem.filter) === null || _b === void 0 ? void 0 : _b.property)) {
441
- const filterValue = String(filterItem.filter.filtering)
442
- .trim()
443
- .toLowerCase();
444
- items = items.filter((item) => {
445
- const itemValue = String(item[filterItem.filter.property]).toLowerCase();
446
- return itemValue.includes(filterValue);
447
- });
448
- }
449
- });
450
- // Aplicar filterFn se existir
451
- if (params.filterFn) {
452
- items = items.filter(params.filterFn);
453
- }
454
- // Paginação
455
- const pageSize = params.batchSize;
456
- let currentClientPageIndex = 0;
457
- if (params.navigation === 'reload') {
458
- currentClientPageIndex = 0;
459
- }
460
- else {
461
- // Usar o índice passado pelo componente sem incrementar/decrementar
462
- currentClientPageIndex = params.clientPageIndex || 0;
463
- }
464
- const startIndex = currentClientPageIndex * pageSize;
465
- const endIndex = startIndex + pageSize;
466
- const paginatedItems = items.slice(startIndex, endIndex);
467
- const totalPages = Math.ceil(items.length / pageSize);
468
- const hasNextPage = currentClientPageIndex < totalPages - 1;
469
- const hasPreviousPage = currentClientPageIndex > 0;
470
- return {
471
- items: paginatedItems,
472
- filterLength: items.length,
473
- firstDoc: null,
474
- lastDoc: null,
475
- hasNextPage,
476
- hasPreviousPage,
477
- currentClientPageIndex,
478
- totalPages,
479
- };
480
- });
481
- }
482
- shouldUseClientSideFallback(params) {
483
- var _a, _b, _c;
484
- const hasConditions = params.conditions && params.conditions.length > 0;
485
- const hasArrangeFilters = ((_a = params.arrange) === null || _a === void 0 ? void 0 : _a.filters) && params.arrange.filters.length > 0;
486
- const hasSortBy = (_c = (_b = params.arrange) === null || _b === void 0 ? void 0 : _b.sortBy) === null || _c === void 0 ? void 0 : _c.field;
487
- if (params.filterFn) {
488
- return false;
489
- }
490
- if (hasArrangeFilters) {
491
- const equalsFilters = params.arrange.filters.filter((f) => f.arrange === 'equals' && f.filter);
492
- if (equalsFilters.length > 0) {
493
- const propertiesSet = new Set(equalsFilters.map((f) => f.filter.property));
494
- if (propertiesSet.size > 1) {
495
- return true;
496
- }
497
- }
498
- }
499
- if (hasConditions && hasArrangeFilters && hasSortBy) {
500
- return true;
501
- }
502
- if (hasConditions && hasArrangeFilters) {
503
- return true;
504
- }
505
- if (hasArrangeFilters && params.arrange.filters.length > 1 && hasSortBy) {
506
- const equalsFilters = params.arrange.filters.filter((f) => f.arrange === 'equals' && f.filter);
507
- if (equalsFilters.length > 0) {
508
- const propertiesSet = new Set(equalsFilters.map((f) => f.filter.property));
509
- if (propertiesSet.size === 1 &&
510
- equalsFilters.length === params.arrange.filters.length) {
511
- return false;
512
- }
513
- }
514
- return true;
515
- }
516
- return false;
517
- }
518
- getPaginated(params) {
519
- return __awaiter(this, void 0, void 0, function* () {
520
- const idFilterValue = this.getIdFilter(params);
521
- if (idFilterValue) {
522
- const result = yield this.searchByIdPartial(params, idFilterValue);
523
- return result;
524
- }
525
- // Detectar preventivamente se deve usar fallback
526
- if (this.shouldUseClientSideFallback(params)) {
527
- yield this.trackMissingIndexPreventive(params.collection, params.arrange, params.conditions);
528
- const result = yield this.executeClientSideQuery(params);
529
- console.log('📊 [TABLE] Resultados paginados via fallback client-side:', {
530
- totalItems: result.filterLength,
531
- returnedItems: result.items.length,
532
- hasNextPage: result.hasNextPage,
533
- currentPage: (result.currentClientPageIndex || 0) + 1,
534
- });
535
- return result;
536
- }
537
- try {
538
- const result = yield this.executeQuery(params);
539
- console.log('📊 [TABLE] Resultados paginados via Firestore:', {
540
- totalItems: result.filterLength || 'N/A',
541
- returnedItems: result.items.length,
542
- hasNextPage: result.hasNextPage,
543
- });
544
- return result;
545
- }
546
- catch (error) {
547
- if (error && error.code === 'failed-precondition') {
548
- yield this.trackMissingIndex(error, params.collection, params.arrange, params.conditions);
549
- const result = yield this.executeClientSideQuery(params);
550
- console.log('📊 [TABLE] Resultados paginados via fallback (erro de index):', {
551
- totalItems: result.filterLength,
552
- returnedItems: result.items.length,
553
- hasNextPage: result.hasNextPage,
554
- currentPage: (result.currentClientPageIndex || 0) + 1,
555
- });
556
- return result;
557
- }
558
- else if (error && error.code === 'invalid-argument') {
559
- yield this.trackMissingIndex(error, params.collection, params.arrange, params.conditions);
560
- const result = yield this.executeClientSideQuery(params);
561
- console.log('📊 [TABLE] Resultados paginados via fallback (argumento inválido):', {
562
- totalItems: result.filterLength,
563
- returnedItems: result.items.length,
564
- hasNextPage: result.hasNextPage,
565
- currentPage: (result.currentClientPageIndex || 0) + 1,
566
- });
567
- return result;
568
- }
569
- else {
570
- throw error;
571
- }
572
- }
573
- });
574
- }
575
- executeClientSideQuery(params) {
576
- var _a, _b, _c;
577
- return __awaiter(this, void 0, void 0, function* () {
578
- // Otimizar usando pelo menos uma cláusula .where() quando possível
579
- let query = this.ngFire.collection(params.collection).ref;
580
- let appliedCondition = null;
581
- let hasAppliedWhereClause = false;
582
- // Primeiro, tenta aplicar condições simples
583
- if (params.conditions && params.conditions.length > 0) {
584
- const simpleCondition = params.conditions.find((cond) => ['==', '>', '<', '>=', '<=', 'in', 'array-contains'].includes(cond.operator));
585
- if (simpleCondition) {
586
- query = query.where(simpleCondition.firestoreProperty, simpleCondition.operator, simpleCondition.dashProperty);
587
- appliedCondition = simpleCondition;
588
- hasAppliedWhereClause = true;
589
- }
590
- }
591
- // Se não há condições disponíveis, tenta aplicar filtros do arrange
592
- let appliedFirestoreFilter = null;
593
- if (!hasAppliedWhereClause && ((_a = params.arrange) === null || _a === void 0 ? void 0 : _a.filters)) {
594
- // Agrupar equals filters por propriedade
595
- const equalsFilters = params.arrange.filters.filter((f) => f.arrange === 'equals' && f.filter);
596
- const equalsGroupedByProperty = equalsFilters.reduce((acc, current) => {
597
- const prop = current.filter.property;
598
- if (!acc[prop]) {
599
- acc[prop] = [];
600
- }
601
- acc[prop].push(current.filter.filtering);
602
- return acc;
603
- }, {});
604
- // Se há apenas UMA propriedade com múltiplos valores, aplicar no Firestore com 'in'
605
- const properties = Object.keys(equalsGroupedByProperty);
606
- if (properties.length === 1 && equalsFilters.length > 0) {
607
- const prop = properties[0];
608
- const values = equalsGroupedByProperty[prop];
609
- query = query.where(prop, 'in', values);
610
- hasAppliedWhereClause = true;
611
- // Marcar TODOS os equals filters dessa propriedade como aplicados
612
- appliedFirestoreFilter = 'all-equals';
613
- }
614
- else if (properties.length === 0) {
615
- const otherFilter = params.arrange.filters.find((f) => {
616
- var _a, _b, _c, _d;
617
- return (f.arrange === 'filter' &&
618
- ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.filtering) &&
619
- ((_b = f.filter) === null || _b === void 0 ? void 0 : _b.property)) ||
620
- (f.arrange === 'filterByDate' &&
621
- ((_c = f.dateFilter) === null || _c === void 0 ? void 0 : _c.initial) &&
622
- ((_d = f.dateFilter) === null || _d === void 0 ? void 0 : _d.final));
623
- });
624
- if (otherFilter) {
625
- if (otherFilter.arrange === 'filter' && otherFilter.filter) {
626
- const filterValue = otherFilter.filter.filtering
627
- .trim()
628
- .toUpperCase();
629
- query = query
630
- .where(otherFilter.filter.property, '>=', filterValue)
631
- .where(otherFilter.filter.property, '<=', filterValue + '\uf8ff');
632
- hasAppliedWhereClause = true;
633
- appliedFirestoreFilter = otherFilter;
634
- }
635
- else if (otherFilter.arrange === 'filterByDate' &&
636
- otherFilter.dateFilter &&
637
- ((_b = params.arrange.sortBy) === null || _b === void 0 ? void 0 : _b.field)) {
638
- query = query
639
- .where(params.arrange.sortBy.field, '>=', otherFilter.dateFilter.initial)
640
- .where(params.arrange.sortBy.field, '<=', otherFilter.dateFilter.final);
641
- hasAppliedWhereClause = true;
642
- appliedFirestoreFilter = otherFilter;
643
- }
644
- }
645
- }
646
- }
647
- const allDocsSnapshot = yield query.get();
648
- let items = allDocsSnapshot.docs.map((doc) => (Object.assign({ id: doc.id }, doc.data())));
649
- // Aplicar condições restantes
650
- if (params.conditions) {
651
- const remainingConditions = params.conditions.filter((cond) => cond !== appliedCondition);
652
- if (remainingConditions.length > 0) {
653
- const operators = this.operators;
654
- items = items.filter((item) => {
655
- return remainingConditions.every((cond) => {
656
- const operatorFn = operators[cond.operator];
657
- return operatorFn
658
- ? operatorFn(item[cond.firestoreProperty], cond.dashProperty)
659
- : false;
660
- });
661
- });
662
- }
663
- }
664
- const { filters, sortBy } = params.arrange;
665
- // Track which filter was already applied in Firestore to avoid double filtering
666
- if (hasAppliedWhereClause && !appliedCondition && ((_c = params.arrange) === null || _c === void 0 ? void 0 : _c.filters)) {
667
- const equalsFilter = params.arrange.filters.find((f) => { var _a; return f.arrange === 'equals' && ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.filtering); });
668
- if (equalsFilter) {
669
- appliedFirestoreFilter = equalsFilter;
670
- }
671
- else {
672
- appliedFirestoreFilter = params.arrange.filters.find((f) => {
673
- var _a, _b, _c, _d;
674
- return (f.arrange === 'filter' &&
675
- ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.filtering) &&
676
- ((_b = f.filter) === null || _b === void 0 ? void 0 : _b.property)) ||
677
- (f.arrange === 'filterByDate' &&
678
- ((_c = f.dateFilter) === null || _c === void 0 ? void 0 : _c.initial) &&
679
- ((_d = f.dateFilter) === null || _d === void 0 ? void 0 : _d.final));
680
- });
681
- }
682
- }
683
- const equalsFilters = filters.filter((f) => f.arrange === 'equals');
684
- const otherFilters = filters.filter((f) => f.arrange !== 'equals');
685
- // Aplicar equals filters no client-side apenas se não foram aplicados no Firestore
686
- if (appliedFirestoreFilter !== 'all-equals' && equalsFilters.length > 0) {
687
- // Agrupar por propriedade para aplicar OR dentro de cada propriedade
688
- const groupedByProperty = equalsFilters.reduce((acc, f) => {
689
- const prop = f.filter.property;
690
- if (!acc[prop]) {
691
- acc[prop] = [];
692
- }
693
- acc[prop].push(f.filter.filtering);
694
- return acc;
695
- }, {});
696
- // Filtrar: item deve ter pelo menos um valor de CADA propriedade
697
- // (AND entre propriedades, OR dentro de cada propriedade)
698
- items = items.filter((item) => {
699
- return Object.entries(groupedByProperty).every(([prop, values]) => {
700
- return values.includes(item[prop]);
701
- });
702
- });
703
- }
704
- otherFilters.forEach((filterItem) => {
705
- var _a, _b, _c, _d;
706
- if (appliedFirestoreFilter === filterItem) {
707
- return;
708
- }
709
- if (filterItem.arrange === 'filter' &&
710
- ((_a = filterItem.filter) === null || _a === void 0 ? void 0 : _a.filtering) &&
711
- ((_b = filterItem.filter) === null || _b === void 0 ? void 0 : _b.property)) {
712
- const filterValue = String(filterItem.filter.filtering)
713
- .trim()
714
- .toLowerCase();
715
- items = items.filter((item) => {
716
- const itemValue = String(item[filterItem.filter.property]).toLowerCase();
717
- return itemValue.includes(filterValue);
718
- });
719
- }
720
- if (filterItem.arrange === 'filterByDate' &&
721
- ((_c = filterItem.dateFilter) === null || _c === void 0 ? void 0 : _c.initial) &&
722
- ((_d = filterItem.dateFilter) === null || _d === void 0 ? void 0 : _d.final) &&
723
- sortBy.field) {
724
- items = items.filter((item) => {
725
- try {
726
- const fieldValue = item[sortBy.field];
727
- if (!fieldValue) {
728
- return false;
729
- }
730
- let itemDate;
731
- if (typeof fieldValue.toDate === 'function') {
732
- itemDate = fieldValue.toDate();
733
- }
734
- else if (fieldValue instanceof Date) {
735
- itemDate = fieldValue;
736
- }
737
- else if (typeof fieldValue === 'string') {
738
- itemDate = new Date(fieldValue);
739
- if (isNaN(itemDate.getTime())) {
740
- return false;
741
- }
742
- }
743
- else if (typeof fieldValue === 'number') {
744
- itemDate = new Date(fieldValue);
745
- }
746
- else {
747
- return false;
748
- }
749
- return (itemDate >= filterItem.dateFilter.initial &&
750
- itemDate <= filterItem.dateFilter.final);
751
- }
752
- catch (error) {
753
- console.warn('Erro ao processar filtro de data para o item:', item.id, error);
754
- return false;
755
- }
756
- });
757
- }
758
- });
759
- // Aplicar filterFn se existir
760
- if (params.filterFn) {
761
- items = items.filter(params.filterFn);
762
- }
763
- if (sortBy && sortBy.field && sortBy.order) {
764
- items.sort((a, b) => {
765
- const valA = a[sortBy.field];
766
- const valB = b[sortBy.field];
767
- if (valA < valB) {
768
- return sortBy.order === 'asc' ? -1 : 1;
769
- }
770
- if (valA > valB) {
771
- return sortBy.order === 'asc' ? 1 : -1;
772
- }
773
- return 0;
774
- });
775
- }
776
- // Implementação adequada da paginação
777
- let currentClientPageIndex = 0;
778
- // Determinar a página atual baseada na navegação
779
- if (params.navigation === 'reload') {
780
- currentClientPageIndex = 0;
781
- }
782
- else {
783
- currentClientPageIndex = params.clientPageIndex || 0;
784
- }
785
- const pageSize = params.batchSize;
786
- const startIndex = currentClientPageIndex * pageSize;
787
- const endIndex = startIndex + pageSize;
788
- const paginatedItems = items.slice(startIndex, endIndex);
789
- const totalPages = Math.ceil(items.length / pageSize);
790
- const hasNextPage = currentClientPageIndex < totalPages - 1;
791
- const hasPreviousPage = currentClientPageIndex > 0;
792
- return {
793
- items: paginatedItems,
794
- filterLength: items.length,
795
- lastDoc: null,
796
- firstDoc: null,
797
- hasNextPage: hasNextPage,
798
- hasPreviousPage: hasPreviousPage,
799
- currentClientPageIndex: currentClientPageIndex,
800
- totalPages: totalPages,
801
- };
802
- });
803
- }
804
- getItemsData(collection, arrange, conditions = undefined) {
805
- return __awaiter(this, void 0, void 0, function* () {
806
- try {
807
- let query = this.ngFire.collection(collection).ref;
808
- query = this.applyFilters(query, arrange, conditions);
809
- const snapshot = yield query.get();
810
- return yield Promise.all(snapshot.docs.map((doc) => __awaiter(this, void 0, void 0, function* () {
811
- const data = doc.data();
812
- const id = doc.id;
813
- return Object.assign({ id }, data);
814
- })));
815
- }
816
- catch (e) {
817
- throw e;
818
- }
819
- });
820
- }
821
- deleteIndex(id, col) {
822
- return __awaiter(this, void 0, void 0, function* () {
823
- try {
824
- const batch = this.ngFire.firestore.batch();
825
- const docRef = this.ngFire.collection(col).doc(id);
826
- const docSnapshot = (yield firstValueFrom(docRef.get()));
827
- const doc = docSnapshot.data();
828
- batch.delete(docRef.ref);
829
- if (doc && typeof doc.index === 'number') {
830
- yield this.reindex(doc.index, col, batch);
831
- }
832
- yield batch.commit();
833
- this.toastr.success('Item excluído com sucesso!');
834
- return true;
835
- }
836
- catch (e) {
837
- const error = e;
838
- console.error('Erro ao deletar item:', error);
839
- this.toastr.error('Erro ao deletar item.');
840
- return false;
841
- }
842
- });
843
- }
844
- reindex(index, col, batch) {
845
- return __awaiter(this, void 0, void 0, function* () {
846
- try {
847
- const snapshot = (yield firstValueFrom(this.ngFire.collection(col).get()));
848
- const docs = snapshot.docs;
849
- for (let doc of docs) {
850
- const data = doc.data();
851
- if (data && typeof data.index === 'number' && data.index > index) {
852
- data.index--;
853
- const docRef = this.ngFire.collection(col).doc(doc.id).ref;
854
- batch.update(docRef, data);
855
- }
856
- }
857
- }
858
- catch (error) {
859
- console.error('Erro ao reindexar:', error);
860
- }
861
- return;
862
- });
863
- }
864
- dateFormatValidator() {
865
- return (control) => {
866
- if (!control.value) {
867
- return null;
868
- }
869
- const dateStr = control.value.trim();
870
- const datePattern = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/;
871
- if (!datePattern.test(dateStr)) {
872
- return { invalidFormat: true };
873
- }
874
- const parts = dateStr.split('/');
875
- const day = parts[0].padStart(2, '0');
876
- const month = parts[1].padStart(2, '0');
877
- const year = parts[2];
878
- const normalizedDate = `${day}/${month}/${year}`;
879
- const date = moment(normalizedDate, 'DD/MM/YYYY', true);
880
- if (!date.isValid()) {
881
- return { invalidDate: true };
882
- }
883
- return null;
884
- };
885
- }
886
- updateIndex(index, id, col) {
887
- return __awaiter(this, void 0, void 0, function* () {
888
- yield this.ngFire.collection(col).doc(id).update({ index });
889
- });
890
- }
891
- /**
892
- * Extrai o link de criação de índice da mensagem de erro do Firestore
893
- */
894
- extractIndexLink(error) {
895
- if (!error || !error.message)
896
- return null;
897
- const linkMatch = error.message.match(/(https:\/\/console\.firebase\.google\.com\/[^\s]+)/);
898
- return linkMatch ? linkMatch[1] : null;
899
- }
900
- /**
901
- * Rastreia índices ausentes ao usar fallback preventivo
902
- */
903
- trackMissingIndexPreventive(collection, arrange, conditions = undefined) {
904
- var _a;
905
- return __awaiter(this, void 0, void 0, function* () {
906
- try {
907
- const querySignature = this.generateQuerySignature(collection, arrange, conditions);
908
- const docId = `${collection}_${querySignature}`;
909
- const indexLink = this.generateIndexLink(collection, arrange, conditions);
910
- const indexInstructions = this.generateIndexInstructions(collection, arrange, conditions);
911
- const trackingData = {
912
- collection,
913
- indexLink,
914
- indexInstructions,
915
- arrange: {
916
- sortBy: arrange.sortBy,
917
- filters: ((_a = arrange.filters) === null || _a === void 0 ? void 0 : _a.map((f) => {
918
- var _a, _b;
919
- return ({
920
- arrange: f.arrange,
921
- property: ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.property) || null,
922
- dateField: f.arrange === 'filterByDate' ? (_b = arrange.sortBy) === null || _b === void 0 ? void 0 : _b.field : null,
923
- });
924
- })) || [],
925
- },
926
- conditions: (conditions === null || conditions === void 0 ? void 0 : conditions.map((c) => ({
927
- property: c.firestoreProperty,
928
- operator: c.operator,
929
- }))) || [],
930
- errorMessage: `Fallback preventivo usado para a collection ${collection}. A query exigiria índice composto.`,
931
- updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
932
- };
933
- console.log('📄 [INDEX LINK] Dados que serão salvos no documento:', {
934
- docId,
935
- collection: trackingData.collection,
936
- indexLink: trackingData.indexLink,
937
- arrange: trackingData.arrange,
938
- conditions: trackingData.conditions,
939
- errorMessage: trackingData.errorMessage,
940
- });
941
- const docRef = this.ngFire.collection('missingIndexes').doc(docId);
942
- const doc = yield docRef.get().toPromise();
943
- if (doc && doc.exists) {
944
- yield docRef.update({
945
- count: firebase.firestore.FieldValue.increment(1),
946
- updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
947
- lastError: trackingData.errorMessage,
948
- });
949
- }
950
- else {
951
- yield docRef.set(Object.assign(Object.assign({}, trackingData), { count: 1, createdAt: firebase.firestore.FieldValue.serverTimestamp() }));
952
- }
953
- }
954
- catch (trackingError) {
955
- console.warn('Falha ao rastrear fallback preventivo:', trackingError);
956
- }
957
- });
958
- }
959
- /**
960
- * Gera uma assinatura única para uma query
961
- */
962
- generateQuerySignature(collection, arrange, conditions = undefined) {
963
- var _a;
964
- const signature = {
965
- collection,
966
- sortBy: arrange.sortBy,
967
- filters: ((_a = arrange.filters) === null || _a === void 0 ? void 0 : _a.map((f) => {
968
- var _a;
969
- return ({
970
- arrange: f.arrange,
971
- property: ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.property) || null,
972
- });
973
- })) || [],
974
- conditions: (conditions === null || conditions === void 0 ? void 0 : conditions.map((c) => ({
975
- property: c.firestoreProperty,
976
- operator: c.operator,
977
- }))) || [],
978
- };
979
- return btoa(JSON.stringify(signature))
980
- .replace(/[^a-zA-Z0-9]/g, '')
981
- .substring(0, 20);
982
- }
983
- /**
984
- * Gera instruções claras para criar o índice manualmente
985
- */
986
- generateIndexInstructions(collection, arrange, conditions = undefined) {
987
- var _a;
988
- const instructions = {
989
- summary: '',
990
- collection: collection,
991
- fields: [],
992
- queryExample: '',
993
- stepByStep: [],
994
- notes: [],
995
- };
996
- const fields = [];
997
- if (conditions && conditions.length > 0) {
998
- conditions.forEach((condition) => {
999
- if (condition.firestoreProperty) {
1000
- fields.push({
1001
- field: condition.firestoreProperty,
1002
- order: 'Ascending',
1003
- type: 'WHERE clause',
1004
- operator: condition.operator,
1005
- description: `Filtrar por ${condition.firestoreProperty} usando operador ${condition.operator}`,
1006
- });
1007
- }
1008
- });
1009
- }
1010
- if (arrange.filters && arrange.filters.length > 0) {
1011
- arrange.filters.forEach((filter) => {
1012
- var _a;
1013
- if ((_a = filter.filter) === null || _a === void 0 ? void 0 : _a.property) {
1014
- fields.push({
1015
- field: filter.filter.property,
1016
- order: 'Ascending',
1017
- type: 'WHERE clause (filter)',
1018
- operator: filter.arrange === 'filter' ? 'CONTAINS' : 'RANGE',
1019
- description: `Filtrar por ${filter.filter.property} usando filtro ${filter.arrange}`,
1020
- });
1021
- }
1022
- });
1023
- }
1024
- if ((_a = arrange.sortBy) === null || _a === void 0 ? void 0 : _a.field) {
1025
- fields.push({
1026
- field: arrange.sortBy.field,
1027
- order: arrange.sortBy.order === 'desc' ? 'Descending' : 'Ascending',
1028
- type: 'ORDER BY clause',
1029
- operator: 'N/A',
1030
- description: `Ordenar resultados por ${arrange.sortBy.field} em ordem ${arrange.sortBy.order}`,
1031
- });
1032
- }
1033
- instructions.fields = fields;
1034
- const fieldNames = fields.map((f) => f.field).join(' + ');
1035
- instructions.summary = `Criar índice composto para ${collection}: ${fieldNames}`;
1036
- let queryExample = `db.collection('${collection}')`;
1037
- fields.forEach((field, index) => {
1038
- if (field.type.includes('WHERE')) {
1039
- if (field.operator === '==') {
1040
- queryExample += `\n .where('${field.field}', '==', 'value')`;
1041
- }
1042
- else if (field.operator === 'CONTAINS') {
1043
- queryExample += `\n .where('${field.field}', '>=', 'searchText')`;
1044
- }
1045
- else {
1046
- queryExample += `\n .where('${field.field}', '${field.operator}', 'value')`;
1047
- }
1048
- }
1049
- });
1050
- const orderByField = fields.find((f) => f.type.includes('ORDER BY'));
1051
- if (orderByField) {
1052
- queryExample += `\n .orderBy('${orderByField.field}', '${orderByField.order.toLowerCase()}')`;
1053
- }
1054
- instructions.queryExample = queryExample;
1055
- instructions.stepByStep = [
1056
- '1. Ir para Firebase Console → Firestore → Indexes',
1057
- '2. Clicar em "Create Index"',
1058
- `3. Definir Collection ID: ${collection}`,
1059
- '4. Configurar campos nesta ORDEM EXATA:',
1060
- ...fields.map((field, index) => ` ${index + 1}. Campo: ${field.field}, Order: ${field.order}, Array: No`),
1061
- '5. Definir Query scopes: Collection',
1062
- '6. Clicar em "Create" e aguardar conclusão',
1063
- ];
1064
- instructions.notes = [
1065
- '⚠️ A ordem dos campos é CRÍTICA - deve corresponder exatamente à ordem da query',
1066
- '⚠️ As cláusulas WHERE devem vir ANTES do campo ORDER BY',
1067
- '⚠️ Este índice só funcionará para queries com esta combinação EXATA de campos',
1068
- '⚠️ A criação do índice pode levar vários minutos',
1069
- ];
1070
- return instructions;
1071
- }
1072
- /**
1073
- * Gera um link de índice baseado na estrutura da query
1074
- */
1075
- generateIndexLink(collection, arrange, conditions = undefined) {
1076
- var _a;
1077
- try {
1078
- const indexFields = [];
1079
- if (conditions && conditions.length > 0) {
1080
- conditions.forEach((condition) => {
1081
- if (condition.firestoreProperty) {
1082
- indexFields.push(condition.firestoreProperty);
1083
- }
1084
- });
1085
- }
1086
- if (arrange.filters && arrange.filters.length > 0) {
1087
- arrange.filters.forEach((filter) => {
1088
- var _a;
1089
- if ((_a = filter.filter) === null || _a === void 0 ? void 0 : _a.property) {
1090
- indexFields.push(filter.filter.property);
1091
- }
1092
- });
1093
- }
1094
- if ((_a = arrange.sortBy) === null || _a === void 0 ? void 0 : _a.field) {
1095
- indexFields.push(arrange.sortBy.field);
1096
- }
1097
- if (indexFields.length > 1) {
1098
- const baseUrl = 'https://console.firebase.google.com/project/toppayy-dev/firestore/indexes';
1099
- const queryParams = new URLSearchParams({
1100
- create_composite: `collection=${collection}&fields=${indexFields.join(',')}`,
1101
- });
1102
- const finalLink = `${baseUrl}?${queryParams.toString()}`;
1103
- return finalLink;
1104
- }
1105
- return null;
1106
- }
1107
- catch (error) {
1108
- console.warn('Falha ao gerar link de índice:', error);
1109
- return null;
1110
- }
1111
- }
1112
- trackMissingIndex(error, collection, arrange, conditions = undefined) {
1113
- var _a;
1114
- return __awaiter(this, void 0, void 0, function* () {
1115
- try {
1116
- const indexLink = this.extractIndexLink(error);
1117
- if (!indexLink)
1118
- return;
1119
- const linkHash = btoa(indexLink)
1120
- .replace(/[^a-zA-Z0-9]/g, '')
1121
- .substring(0, 20);
1122
- const docId = `${collection}_${linkHash}`;
1123
- const indexInstructions = this.generateIndexInstructions(collection, arrange, conditions);
1124
- const trackingData = {
1125
- collection,
1126
- indexLink,
1127
- indexInstructions,
1128
- arrange: {
1129
- sortBy: arrange.sortBy,
1130
- filters: ((_a = arrange.filters) === null || _a === void 0 ? void 0 : _a.map((f) => {
1131
- var _a, _b;
1132
- return ({
1133
- arrange: f.arrange,
1134
- property: ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.property) || null,
1135
- dateField: f.arrange === 'filterByDate' ? (_b = arrange.sortBy) === null || _b === void 0 ? void 0 : _b.field : null,
1136
- });
1137
- })) || [],
1138
- },
1139
- conditions: (conditions === null || conditions === void 0 ? void 0 : conditions.map((c) => ({
1140
- property: c.firestoreProperty,
1141
- operator: c.operator,
1142
- }))) || [],
1143
- errorMessage: error.message,
1144
- updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
1145
- };
1146
- const docRef = this.ngFire.collection('missingIndexes').doc(docId);
1147
- const doc = yield docRef.get().toPromise();
1148
- if (doc && doc.exists) {
1149
- yield docRef.update({
1150
- count: firebase.firestore.FieldValue.increment(1),
1151
- updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
1152
- lastError: error.message,
1153
- });
1154
- }
1155
- else {
1156
- yield docRef.set(Object.assign(Object.assign({}, trackingData), { count: 1, createdAt: firebase.firestore.FieldValue.serverTimestamp() }));
1157
- }
1158
- }
1159
- catch (trackingError) {
1160
- console.warn('Falha ao rastrear índice ausente:', trackingError);
1161
- }
1162
- });
1163
- }
1164
- }
1165
- TableService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: TableService, deps: [{ token: i1.AngularFirestore, optional: true }, { token: i2.MatDialog, optional: true }, { token: i3.ToastrService, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
1166
- TableService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: TableService, providedIn: 'root' });
1167
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: TableService, decorators: [{
1168
- type: Injectable,
1169
- args: [{
1170
- providedIn: 'root',
1171
- }]
1172
- }], ctorParameters: function () {
1173
- return [{ type: i1.AngularFirestore, decorators: [{
1174
- type: Optional
1175
- }] }, { type: i2.MatDialog, decorators: [{
1176
- type: Optional
1177
- }] }, { type: i3.ToastrService, decorators: [{
1178
- type: Optional
1179
- }] }];
48
+ class TableService {
49
+ constructor(ngFire, dialog, toastr) {
50
+ this.ngFire = ngFire;
51
+ this.dialog = dialog;
52
+ this.toastr = toastr;
53
+ this.operators = {
54
+ '==': (a, b) => a === b,
55
+ '!=': (a, b) => a !== b,
56
+ '>': (a, b) => a > b,
57
+ '<': (a, b) => a < b,
58
+ '>=': (a, b) => a >= b,
59
+ '<=': (a, b) => a <= b,
60
+ in: (a, b) => Array.isArray(b) && b.includes(a),
61
+ 'not-in': (a, b) => Array.isArray(b) && !b.includes(a),
62
+ 'array-contains': (a, b) => Array.isArray(a) && a.includes(b),
63
+ 'array-contains-any': (a, b) => Array.isArray(a) &&
64
+ Array.isArray(b) &&
65
+ b.some((item) => a.includes(item)),
66
+ includes: (a, b) => a.includes(b), // Para strings ou arrays
67
+ };
68
+ }
69
+ getItems(collection) {
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ try {
72
+ const querySnapshot = yield collection.get();
73
+ return querySnapshot.docs.map((doc) => {
74
+ return Object.assign(Object.assign({}, doc.data()), { id: doc.id });
75
+ });
76
+ }
77
+ catch (error) {
78
+ console.warn('Collection não encontrada:', error);
79
+ return [];
80
+ }
81
+ });
82
+ }
83
+ executeQuery(params) {
84
+ return __awaiter(this, void 0, void 0, function* () {
85
+ if (params.filterFn) {
86
+ // Lógica com filtro no cliente (filterFn)
87
+ const BATCH_FETCH_SIZE = params.batchSize;
88
+ const GOAL_SIZE = params.batchSize + 1;
89
+ if (params.navigation === 'forward' || params.navigation === 'reload') {
90
+ if (params.navigation === 'reload' && params.doc) {
91
+ params.doc.lastDoc = null;
92
+ }
93
+ let lastDocCursor = params.doc ? params.doc.lastDoc : null;
94
+ let pageResults = [];
95
+ let allFetchedDocs = [];
96
+ let hasMoreDocsInDb = true;
97
+ while (pageResults.length < GOAL_SIZE && hasMoreDocsInDb) {
98
+ let query = this.ngFire.collection(params.collection).ref;
99
+ query = this.applyFilters(query, params.arrange, params.conditions);
100
+ if (lastDocCursor) {
101
+ query = query.startAfter(lastDocCursor);
102
+ }
103
+ query = query.limit(BATCH_FETCH_SIZE);
104
+ const snapshot = yield query.get();
105
+ if (snapshot.empty) {
106
+ hasMoreDocsInDb = false;
107
+ break;
108
+ }
109
+ lastDocCursor = snapshot.docs[snapshot.docs.length - 1];
110
+ allFetchedDocs.push(...snapshot.docs);
111
+ const batchUsers = snapshot.docs
112
+ .map((doc) => (Object.assign({ id: doc.id }, doc.data())))
113
+ .filter(params.filterFn);
114
+ pageResults.push(...batchUsers);
115
+ if (snapshot.size < BATCH_FETCH_SIZE) {
116
+ hasMoreDocsInDb = false;
117
+ }
118
+ }
119
+ const hasNextPage = pageResults.length > params.batchSize;
120
+ const finalItems = pageResults.slice(0, params.batchSize);
121
+ const firstDocOfPage = allFetchedDocs.find((doc) => { var _a; return doc.id === ((_a = finalItems[0]) === null || _a === void 0 ? void 0 : _a.id); }) || null;
122
+ const lastDocOfPage = allFetchedDocs.find((doc) => { var _a; return doc.id === ((_a = finalItems[finalItems.length - 1]) === null || _a === void 0 ? void 0 : _a.id); }) || null;
123
+ return {
124
+ items: finalItems,
125
+ filterLength: null,
126
+ firstDoc: firstDocOfPage,
127
+ lastDoc: lastDocOfPage,
128
+ hasNextPage: hasNextPage,
129
+ hasPreviousPage: !!(params.doc && params.doc.lastDoc) &&
130
+ params.navigation !== 'reload',
131
+ currentClientPageIndex: undefined,
132
+ };
133
+ }
134
+ // Lógica para trás (backward)
135
+ else if (params.navigation === 'backward') {
136
+ if (!params.doc || !params.doc.firstDoc) {
137
+ return {
138
+ items: [],
139
+ filterLength: null,
140
+ firstDoc: null,
141
+ lastDoc: null,
142
+ hasNextPage: true,
143
+ hasPreviousPage: false,
144
+ currentClientPageIndex: undefined,
145
+ };
146
+ }
147
+ let pageResults = [];
148
+ let allFetchedDocs = [];
149
+ let hasMoreDocsInDb = true;
150
+ let boundaryDoc = params.doc.firstDoc;
151
+ while (pageResults.length < GOAL_SIZE && hasMoreDocsInDb) {
152
+ let query = this.ngFire.collection(params.collection).ref;
153
+ query = this.applyFilters(query, params.arrange, params.conditions);
154
+ query = query.endBefore(boundaryDoc);
155
+ query = query.limitToLast(BATCH_FETCH_SIZE);
156
+ const snapshot = yield query.get();
157
+ if (snapshot.empty) {
158
+ hasMoreDocsInDb = false;
159
+ break;
160
+ }
161
+ boundaryDoc = snapshot.docs[0];
162
+ allFetchedDocs = [...snapshot.docs, ...allFetchedDocs];
163
+ const batchUsers = snapshot.docs
164
+ .map((doc) => (Object.assign({ id: doc.id }, doc.data())))
165
+ .filter(params.filterFn);
166
+ pageResults = [...batchUsers, ...pageResults];
167
+ if (snapshot.size < BATCH_FETCH_SIZE) {
168
+ hasMoreDocsInDb = false;
169
+ }
170
+ }
171
+ const finalItems = pageResults.slice(0, params.batchSize);
172
+ const firstDocOfPage = allFetchedDocs.find((doc) => { var _a; return doc.id === ((_a = finalItems[0]) === null || _a === void 0 ? void 0 : _a.id); }) || null;
173
+ const lastDocOfPage = allFetchedDocs.find((doc) => { var _a; return doc.id === ((_a = finalItems[finalItems.length - 1]) === null || _a === void 0 ? void 0 : _a.id); }) || null;
174
+ return {
175
+ items: finalItems,
176
+ filterLength: null,
177
+ firstDoc: firstDocOfPage,
178
+ lastDoc: lastDocOfPage,
179
+ hasNextPage: true,
180
+ currentClientPageIndex: undefined,
181
+ };
182
+ }
183
+ }
184
+ else {
185
+ let items = [];
186
+ let docs = [];
187
+ let hasNextPage = false;
188
+ let filterLength = null;
189
+ let query = this.ngFire.collection(params.collection).ref;
190
+ if (params.conditions) {
191
+ params.conditions.forEach((c) => {
192
+ if (c.operator === '!=') {
193
+ query = query.orderBy(c.firestoreProperty);
194
+ }
195
+ });
196
+ }
197
+ query = this.applyFilters(query, params.arrange, params.conditions);
198
+ if (params.navigation === 'reload') {
199
+ query = query.limit(params.batchSize + 1);
200
+ if (params.doc && params.doc.firstDoc) {
201
+ query = query.startAt(params.doc.firstDoc);
202
+ }
203
+ }
204
+ else if (params.navigation === 'forward') {
205
+ query = query.limit(params.batchSize + 1);
206
+ if (params.doc && params.doc.lastDoc) {
207
+ query = query.startAfter(params.doc.lastDoc);
208
+ }
209
+ }
210
+ else {
211
+ // backward
212
+ query = query.limitToLast(params.batchSize + 1);
213
+ if (params.doc && params.doc.firstDoc) {
214
+ query = query.endBefore(params.doc.firstDoc);
215
+ }
216
+ }
217
+ const itemCol = yield query.get();
218
+ itemCol.docs.forEach((doc) => docs.push(doc));
219
+ const itemPromises = docs.map((item) => __awaiter(this, void 0, void 0, function* () {
220
+ const itemData = item.data();
221
+ items.push(Object.assign({ id: item.id }, itemData));
222
+ }));
223
+ let lastDoc = docs[docs.length - 1] || null;
224
+ let firstDoc = docs[0];
225
+ if ((items.length > params.batchSize && params.navigation === 'forward') ||
226
+ (params.navigation === 'reload' && items.length > params.batchSize)) {
227
+ lastDoc = docs[docs.length - 2] || null;
228
+ items.pop();
229
+ hasNextPage = true;
230
+ }
231
+ if (items.length > params.batchSize && params.navigation === 'backward') {
232
+ firstDoc = docs[1];
233
+ items.shift();
234
+ hasNextPage = true;
235
+ }
236
+ yield Promise.all(itemPromises);
237
+ return {
238
+ items,
239
+ filterLength,
240
+ lastDoc,
241
+ firstDoc,
242
+ hasNextPage,
243
+ currentClientPageIndex: undefined,
244
+ };
245
+ }
246
+ // Fallback para garantir que sempre retornamos algo
247
+ return {
248
+ items: [],
249
+ filterLength: null,
250
+ firstDoc: null,
251
+ lastDoc: null,
252
+ hasNextPage: false,
253
+ currentClientPageIndex: undefined,
254
+ };
255
+ });
256
+ }
257
+ applyFilters(query, arrange, conditions) {
258
+ if (conditions) {
259
+ conditions.map((cond) => {
260
+ query = query.where(cond.firestoreProperty, cond.operator, cond.dashProperty);
261
+ });
262
+ }
263
+ let hasFilterSpecificOrderBy = false;
264
+ let appliedOrderByField = null;
265
+ const equalsFilters = arrange.filters.filter((f) => f.arrange === 'equals' && f.filter);
266
+ const otherFilters = arrange.filters.filter((f) => f.arrange !== 'equals');
267
+ const equalsGroupedByProperty = equalsFilters.reduce((acc, current) => {
268
+ const prop = current.filter.property;
269
+ if (!acc[prop]) {
270
+ acc[prop] = [];
271
+ }
272
+ acc[prop].push(current.filter.filtering);
273
+ return acc;
274
+ }, {});
275
+ for (const prop in equalsGroupedByProperty) {
276
+ const values = equalsGroupedByProperty[prop];
277
+ if (values.length > 0) {
278
+ query = query.where(prop, 'in', values);
279
+ }
280
+ }
281
+ otherFilters.forEach((filterItem) => {
282
+ var _a, _b;
283
+ // Aplicar filtragem por busca
284
+ if (((_a = filterItem.filter) === null || _a === void 0 ? void 0 : _a.filtering) &&
285
+ ((_b = filterItem.filter) === null || _b === void 0 ? void 0 : _b.property) !== '' &&
286
+ filterItem.arrange === 'filter') {
287
+ query = query
288
+ .where(filterItem.filter.property, '>=', filterItem.filter.filtering.trim().toUpperCase())
289
+ .where(filterItem.filter.property, '<=', filterItem.filter.filtering.trim().toUpperCase() + '\uf8ff');
290
+ if (!hasFilterSpecificOrderBy) {
291
+ query = query.orderBy(filterItem.filter.property);
292
+ hasFilterSpecificOrderBy = true;
293
+ appliedOrderByField = filterItem.filter.property;
294
+ }
295
+ }
296
+ // Aplicar filtro do tipo "filterByDate"
297
+ if (filterItem.dateFilter && filterItem.arrange === 'filterByDate') {
298
+ query = query
299
+ .where(arrange.sortBy.field, '>=', filterItem.dateFilter.initial)
300
+ .where(arrange.sortBy.field, '<=', filterItem.dateFilter.final);
301
+ if (!hasFilterSpecificOrderBy) {
302
+ query = query.orderBy(arrange.sortBy.field);
303
+ hasFilterSpecificOrderBy = true;
304
+ appliedOrderByField = arrange.sortBy.field;
305
+ }
306
+ }
307
+ });
308
+ // Aplicar sortBy
309
+ if (arrange.sortBy && arrange.sortBy.field && arrange.sortBy.order) {
310
+ if (appliedOrderByField !== arrange.sortBy.field) {
311
+ query = query.orderBy(arrange.sortBy.field, arrange.sortBy.order);
312
+ }
313
+ }
314
+ return query;
315
+ }
316
+ getIdFilter(params) {
317
+ var _a, _b, _c;
318
+ if (!((_a = params.arrange) === null || _a === void 0 ? void 0 : _a.filters))
319
+ return null;
320
+ const idFilter = params.arrange.filters.find((f) => {
321
+ var _a, _b;
322
+ return f.arrange === 'filter' &&
323
+ ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.property) === 'id' &&
324
+ ((_b = f.filter) === null || _b === void 0 ? void 0 : _b.filtering);
325
+ });
326
+ return ((_c = (_b = idFilter === null || idFilter === void 0 ? void 0 : idFilter.filter) === null || _b === void 0 ? void 0 : _b.filtering) === null || _c === void 0 ? void 0 : _c.trim()) || null;
327
+ }
328
+ getDocumentById(collection, docId) {
329
+ return __awaiter(this, void 0, void 0, function* () {
330
+ try {
331
+ const docRef = this.ngFire.collection(collection).doc(docId);
332
+ const docSnapshot = yield docRef.get().toPromise();
333
+ if (docSnapshot && docSnapshot.exists) {
334
+ return Object.assign({ id: docSnapshot.id }, docSnapshot.data());
335
+ }
336
+ return null;
337
+ }
338
+ catch (error) {
339
+ console.warn('Erro ao buscar documento por ID:', error);
340
+ return null;
341
+ }
342
+ });
343
+ }
344
+ searchByIdPartial(params, searchTerm) {
345
+ var _a, _b, _c, _d;
346
+ return __awaiter(this, void 0, void 0, function* () {
347
+ const exactMatch = yield this.getDocumentById(params.collection, searchTerm);
348
+ if (exactMatch) {
349
+ if (params.conditions) {
350
+ const operators = this.operators;
351
+ const passesConditions = params.conditions.every((cond) => {
352
+ const operatorFn = operators[cond.operator];
353
+ return operatorFn
354
+ ? operatorFn(exactMatch[cond.firestoreProperty], cond.dashProperty)
355
+ : false;
356
+ });
357
+ if (!passesConditions) {
358
+ return {
359
+ items: [],
360
+ filterLength: 0,
361
+ firstDoc: null,
362
+ lastDoc: null,
363
+ hasNextPage: false,
364
+ hasPreviousPage: false,
365
+ currentClientPageIndex: 0,
366
+ totalPages: 0,
367
+ };
368
+ }
369
+ }
370
+ if (params.filterFn && !params.filterFn(exactMatch)) {
371
+ return {
372
+ items: [],
373
+ filterLength: 0,
374
+ firstDoc: null,
375
+ lastDoc: null,
376
+ hasNextPage: false,
377
+ hasPreviousPage: false,
378
+ currentClientPageIndex: 0,
379
+ totalPages: 0,
380
+ };
381
+ }
382
+ return {
383
+ items: [exactMatch],
384
+ filterLength: 1,
385
+ firstDoc: null,
386
+ lastDoc: null,
387
+ hasNextPage: false,
388
+ hasPreviousPage: false,
389
+ currentClientPageIndex: 0,
390
+ totalPages: 1,
391
+ };
392
+ }
393
+ const searchTermLower = searchTerm.toLowerCase();
394
+ const paramsWithoutIdFilter = Object.assign(Object.assign({}, params), { arrange: Object.assign(Object.assign({}, params.arrange), { filters: params.arrange.filters.filter((f) => { var _a; return !(f.arrange === 'filter' && ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.property) === 'id'); }) }) });
395
+ let query = this.ngFire.collection(params.collection).ref;
396
+ // Aplicar conditions
397
+ if (params.conditions) {
398
+ params.conditions.forEach((cond) => {
399
+ query = query.where(cond.firestoreProperty, cond.operator, cond.dashProperty);
400
+ });
401
+ }
402
+ // Aplicar sortBy
403
+ if (((_b = (_a = params.arrange) === null || _a === void 0 ? void 0 : _a.sortBy) === null || _b === void 0 ? void 0 : _b.field) && ((_d = (_c = params.arrange) === null || _c === void 0 ? void 0 : _c.sortBy) === null || _d === void 0 ? void 0 : _d.order)) {
404
+ query = query.orderBy(params.arrange.sortBy.field, params.arrange.sortBy.order);
405
+ }
406
+ const snapshot = yield query.get();
407
+ let items = snapshot.docs
408
+ .map((doc) => (Object.assign({ id: doc.id }, doc.data())))
409
+ .filter((item) => item.id.toLowerCase().includes(searchTermLower));
410
+ // Separar equals filters e outros filtros
411
+ const equalsFilters = paramsWithoutIdFilter.arrange.filters.filter((f) => f.arrange === 'equals' && f.filter);
412
+ const otherFilters = paramsWithoutIdFilter.arrange.filters.filter((f) => {
413
+ var _a;
414
+ return f.arrange !== 'equals' &&
415
+ (f.arrange !== 'filter' || ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.property) !== 'id');
416
+ });
417
+ // Aplicar equals filters com lógica OR dentro de cada propriedade
418
+ if (equalsFilters.length > 0) {
419
+ // Agrupar por propriedade para aplicar OR
420
+ const groupedByProperty = equalsFilters.reduce((acc, f) => {
421
+ const prop = f.filter.property;
422
+ if (!acc[prop]) {
423
+ acc[prop] = [];
424
+ }
425
+ acc[prop].push(f.filter.filtering);
426
+ return acc;
427
+ }, {});
428
+ // Filtrar: item deve ter pelo menos um valor de CADA propriedade
429
+ items = items.filter((item) => {
430
+ return Object.entries(groupedByProperty).every(([prop, values]) => {
431
+ return values.includes(item[prop]);
432
+ });
433
+ });
434
+ }
435
+ // Aplicar outros filtros
436
+ otherFilters.forEach((filterItem) => {
437
+ var _a, _b;
438
+ if (filterItem.arrange === 'filter' &&
439
+ ((_a = filterItem.filter) === null || _a === void 0 ? void 0 : _a.filtering) &&
440
+ ((_b = filterItem.filter) === null || _b === void 0 ? void 0 : _b.property)) {
441
+ const filterValue = String(filterItem.filter.filtering)
442
+ .trim()
443
+ .toLowerCase();
444
+ items = items.filter((item) => {
445
+ const itemValue = String(item[filterItem.filter.property]).toLowerCase();
446
+ return itemValue.includes(filterValue);
447
+ });
448
+ }
449
+ });
450
+ // Aplicar filterFn se existir
451
+ if (params.filterFn) {
452
+ items = items.filter(params.filterFn);
453
+ }
454
+ // Paginação
455
+ const pageSize = params.batchSize;
456
+ let currentClientPageIndex = 0;
457
+ if (params.navigation === 'reload') {
458
+ currentClientPageIndex = 0;
459
+ }
460
+ else {
461
+ // Usar o índice passado pelo componente sem incrementar/decrementar
462
+ currentClientPageIndex = params.clientPageIndex || 0;
463
+ }
464
+ const startIndex = currentClientPageIndex * pageSize;
465
+ const endIndex = startIndex + pageSize;
466
+ const paginatedItems = items.slice(startIndex, endIndex);
467
+ const totalPages = Math.ceil(items.length / pageSize);
468
+ const hasNextPage = currentClientPageIndex < totalPages - 1;
469
+ const hasPreviousPage = currentClientPageIndex > 0;
470
+ return {
471
+ items: paginatedItems,
472
+ filterLength: items.length,
473
+ firstDoc: null,
474
+ lastDoc: null,
475
+ hasNextPage,
476
+ hasPreviousPage,
477
+ currentClientPageIndex,
478
+ totalPages,
479
+ };
480
+ });
481
+ }
482
+ shouldUseClientSideFallback(params) {
483
+ var _a, _b, _c;
484
+ const hasConditions = params.conditions && params.conditions.length > 0;
485
+ const hasArrangeFilters = ((_a = params.arrange) === null || _a === void 0 ? void 0 : _a.filters) && params.arrange.filters.length > 0;
486
+ const hasSortBy = (_c = (_b = params.arrange) === null || _b === void 0 ? void 0 : _b.sortBy) === null || _c === void 0 ? void 0 : _c.field;
487
+ if (params.filterFn) {
488
+ return false;
489
+ }
490
+ if (hasArrangeFilters) {
491
+ const equalsFilters = params.arrange.filters.filter((f) => f.arrange === 'equals' && f.filter);
492
+ if (equalsFilters.length > 0) {
493
+ const propertiesSet = new Set(equalsFilters.map((f) => f.filter.property));
494
+ if (propertiesSet.size > 1) {
495
+ return true;
496
+ }
497
+ }
498
+ }
499
+ if (hasConditions && hasArrangeFilters && hasSortBy) {
500
+ return true;
501
+ }
502
+ if (hasConditions && hasArrangeFilters) {
503
+ return true;
504
+ }
505
+ if (hasArrangeFilters && params.arrange.filters.length > 1 && hasSortBy) {
506
+ const equalsFilters = params.arrange.filters.filter((f) => f.arrange === 'equals' && f.filter);
507
+ if (equalsFilters.length > 0) {
508
+ const propertiesSet = new Set(equalsFilters.map((f) => f.filter.property));
509
+ if (propertiesSet.size === 1 &&
510
+ equalsFilters.length === params.arrange.filters.length) {
511
+ return false;
512
+ }
513
+ }
514
+ return true;
515
+ }
516
+ return false;
517
+ }
518
+ getPaginated(params) {
519
+ return __awaiter(this, void 0, void 0, function* () {
520
+ const idFilterValue = this.getIdFilter(params);
521
+ if (idFilterValue) {
522
+ const result = yield this.searchByIdPartial(params, idFilterValue);
523
+ return result;
524
+ }
525
+ // Detectar preventivamente se deve usar fallback
526
+ if (this.shouldUseClientSideFallback(params)) {
527
+ yield this.trackMissingIndexPreventive(params.collection, params.arrange, params.conditions);
528
+ const result = yield this.executeClientSideQuery(params);
529
+ console.log('📊 [TABLE] Resultados paginados via fallback client-side:', {
530
+ totalItems: result.filterLength,
531
+ returnedItems: result.items.length,
532
+ hasNextPage: result.hasNextPage,
533
+ currentPage: (result.currentClientPageIndex || 0) + 1,
534
+ });
535
+ return result;
536
+ }
537
+ try {
538
+ const result = yield this.executeQuery(params);
539
+ console.log('📊 [TABLE] Resultados paginados via Firestore:', {
540
+ totalItems: result.filterLength || 'N/A',
541
+ returnedItems: result.items.length,
542
+ hasNextPage: result.hasNextPage,
543
+ });
544
+ return result;
545
+ }
546
+ catch (error) {
547
+ if (error && error.code === 'failed-precondition') {
548
+ yield this.trackMissingIndex(error, params.collection, params.arrange, params.conditions);
549
+ const result = yield this.executeClientSideQuery(params);
550
+ console.log('📊 [TABLE] Resultados paginados via fallback (erro de index):', {
551
+ totalItems: result.filterLength,
552
+ returnedItems: result.items.length,
553
+ hasNextPage: result.hasNextPage,
554
+ currentPage: (result.currentClientPageIndex || 0) + 1,
555
+ });
556
+ return result;
557
+ }
558
+ else if (error && error.code === 'invalid-argument') {
559
+ yield this.trackMissingIndex(error, params.collection, params.arrange, params.conditions);
560
+ const result = yield this.executeClientSideQuery(params);
561
+ console.log('📊 [TABLE] Resultados paginados via fallback (argumento inválido):', {
562
+ totalItems: result.filterLength,
563
+ returnedItems: result.items.length,
564
+ hasNextPage: result.hasNextPage,
565
+ currentPage: (result.currentClientPageIndex || 0) + 1,
566
+ });
567
+ return result;
568
+ }
569
+ else {
570
+ throw error;
571
+ }
572
+ }
573
+ });
574
+ }
575
+ executeClientSideQuery(params) {
576
+ var _a, _b, _c;
577
+ return __awaiter(this, void 0, void 0, function* () {
578
+ // Otimizar usando pelo menos uma cláusula .where() quando possível
579
+ let query = this.ngFire.collection(params.collection).ref;
580
+ let appliedCondition = null;
581
+ let hasAppliedWhereClause = false;
582
+ // Primeiro, tenta aplicar condições simples
583
+ if (params.conditions && params.conditions.length > 0) {
584
+ const simpleCondition = params.conditions.find((cond) => ['==', '>', '<', '>=', '<=', 'in', 'array-contains'].includes(cond.operator));
585
+ if (simpleCondition) {
586
+ query = query.where(simpleCondition.firestoreProperty, simpleCondition.operator, simpleCondition.dashProperty);
587
+ appliedCondition = simpleCondition;
588
+ hasAppliedWhereClause = true;
589
+ }
590
+ }
591
+ // Se não há condições disponíveis, tenta aplicar filtros do arrange
592
+ let appliedFirestoreFilter = null;
593
+ if (!hasAppliedWhereClause && ((_a = params.arrange) === null || _a === void 0 ? void 0 : _a.filters)) {
594
+ // Agrupar equals filters por propriedade
595
+ const equalsFilters = params.arrange.filters.filter((f) => f.arrange === 'equals' && f.filter);
596
+ const equalsGroupedByProperty = equalsFilters.reduce((acc, current) => {
597
+ const prop = current.filter.property;
598
+ if (!acc[prop]) {
599
+ acc[prop] = [];
600
+ }
601
+ acc[prop].push(current.filter.filtering);
602
+ return acc;
603
+ }, {});
604
+ // Se há apenas UMA propriedade com múltiplos valores, aplicar no Firestore com 'in'
605
+ const properties = Object.keys(equalsGroupedByProperty);
606
+ if (properties.length === 1 && equalsFilters.length > 0) {
607
+ const prop = properties[0];
608
+ const values = equalsGroupedByProperty[prop];
609
+ query = query.where(prop, 'in', values);
610
+ hasAppliedWhereClause = true;
611
+ // Marcar TODOS os equals filters dessa propriedade como aplicados
612
+ appliedFirestoreFilter = 'all-equals';
613
+ }
614
+ else if (properties.length === 0) {
615
+ const otherFilter = params.arrange.filters.find((f) => {
616
+ var _a, _b, _c, _d;
617
+ return (f.arrange === 'filter' &&
618
+ ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.filtering) &&
619
+ ((_b = f.filter) === null || _b === void 0 ? void 0 : _b.property)) ||
620
+ (f.arrange === 'filterByDate' &&
621
+ ((_c = f.dateFilter) === null || _c === void 0 ? void 0 : _c.initial) &&
622
+ ((_d = f.dateFilter) === null || _d === void 0 ? void 0 : _d.final));
623
+ });
624
+ if (otherFilter) {
625
+ if (otherFilter.arrange === 'filter' && otherFilter.filter) {
626
+ const filterValue = otherFilter.filter.filtering
627
+ .trim()
628
+ .toUpperCase();
629
+ query = query
630
+ .where(otherFilter.filter.property, '>=', filterValue)
631
+ .where(otherFilter.filter.property, '<=', filterValue + '\uf8ff');
632
+ hasAppliedWhereClause = true;
633
+ appliedFirestoreFilter = otherFilter;
634
+ }
635
+ else if (otherFilter.arrange === 'filterByDate' &&
636
+ otherFilter.dateFilter &&
637
+ ((_b = params.arrange.sortBy) === null || _b === void 0 ? void 0 : _b.field)) {
638
+ query = query
639
+ .where(params.arrange.sortBy.field, '>=', otherFilter.dateFilter.initial)
640
+ .where(params.arrange.sortBy.field, '<=', otherFilter.dateFilter.final);
641
+ hasAppliedWhereClause = true;
642
+ appliedFirestoreFilter = otherFilter;
643
+ }
644
+ }
645
+ }
646
+ }
647
+ const allDocsSnapshot = yield query.get();
648
+ let items = allDocsSnapshot.docs.map((doc) => (Object.assign({ id: doc.id }, doc.data())));
649
+ // Aplicar condições restantes
650
+ if (params.conditions) {
651
+ const remainingConditions = params.conditions.filter((cond) => cond !== appliedCondition);
652
+ if (remainingConditions.length > 0) {
653
+ const operators = this.operators;
654
+ items = items.filter((item) => {
655
+ return remainingConditions.every((cond) => {
656
+ const operatorFn = operators[cond.operator];
657
+ return operatorFn
658
+ ? operatorFn(item[cond.firestoreProperty], cond.dashProperty)
659
+ : false;
660
+ });
661
+ });
662
+ }
663
+ }
664
+ const { filters, sortBy } = params.arrange;
665
+ // Track which filter was already applied in Firestore to avoid double filtering
666
+ if (hasAppliedWhereClause && !appliedCondition && ((_c = params.arrange) === null || _c === void 0 ? void 0 : _c.filters)) {
667
+ const equalsFilter = params.arrange.filters.find((f) => { var _a; return f.arrange === 'equals' && ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.filtering); });
668
+ if (equalsFilter) {
669
+ appliedFirestoreFilter = equalsFilter;
670
+ }
671
+ else {
672
+ appliedFirestoreFilter = params.arrange.filters.find((f) => {
673
+ var _a, _b, _c, _d;
674
+ return (f.arrange === 'filter' &&
675
+ ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.filtering) &&
676
+ ((_b = f.filter) === null || _b === void 0 ? void 0 : _b.property)) ||
677
+ (f.arrange === 'filterByDate' &&
678
+ ((_c = f.dateFilter) === null || _c === void 0 ? void 0 : _c.initial) &&
679
+ ((_d = f.dateFilter) === null || _d === void 0 ? void 0 : _d.final));
680
+ });
681
+ }
682
+ }
683
+ const equalsFilters = filters.filter((f) => f.arrange === 'equals');
684
+ const otherFilters = filters.filter((f) => f.arrange !== 'equals');
685
+ // Aplicar equals filters no client-side apenas se não foram aplicados no Firestore
686
+ if (appliedFirestoreFilter !== 'all-equals' && equalsFilters.length > 0) {
687
+ // Agrupar por propriedade para aplicar OR dentro de cada propriedade
688
+ const groupedByProperty = equalsFilters.reduce((acc, f) => {
689
+ const prop = f.filter.property;
690
+ if (!acc[prop]) {
691
+ acc[prop] = [];
692
+ }
693
+ acc[prop].push(f.filter.filtering);
694
+ return acc;
695
+ }, {});
696
+ // Filtrar: item deve ter pelo menos um valor de CADA propriedade
697
+ // (AND entre propriedades, OR dentro de cada propriedade)
698
+ items = items.filter((item) => {
699
+ return Object.entries(groupedByProperty).every(([prop, values]) => {
700
+ return values.includes(item[prop]);
701
+ });
702
+ });
703
+ }
704
+ otherFilters.forEach((filterItem) => {
705
+ var _a, _b, _c, _d;
706
+ if (appliedFirestoreFilter === filterItem) {
707
+ return;
708
+ }
709
+ if (filterItem.arrange === 'filter' &&
710
+ ((_a = filterItem.filter) === null || _a === void 0 ? void 0 : _a.filtering) &&
711
+ ((_b = filterItem.filter) === null || _b === void 0 ? void 0 : _b.property)) {
712
+ const filterValue = String(filterItem.filter.filtering)
713
+ .trim()
714
+ .toLowerCase();
715
+ items = items.filter((item) => {
716
+ const itemValue = String(item[filterItem.filter.property]).toLowerCase();
717
+ return itemValue.includes(filterValue);
718
+ });
719
+ }
720
+ if (filterItem.arrange === 'filterByDate' &&
721
+ ((_c = filterItem.dateFilter) === null || _c === void 0 ? void 0 : _c.initial) &&
722
+ ((_d = filterItem.dateFilter) === null || _d === void 0 ? void 0 : _d.final) &&
723
+ sortBy.field) {
724
+ items = items.filter((item) => {
725
+ try {
726
+ const fieldValue = item[sortBy.field];
727
+ if (!fieldValue) {
728
+ return false;
729
+ }
730
+ let itemDate;
731
+ if (typeof fieldValue.toDate === 'function') {
732
+ itemDate = fieldValue.toDate();
733
+ }
734
+ else if (fieldValue instanceof Date) {
735
+ itemDate = fieldValue;
736
+ }
737
+ else if (typeof fieldValue === 'string') {
738
+ itemDate = new Date(fieldValue);
739
+ if (isNaN(itemDate.getTime())) {
740
+ return false;
741
+ }
742
+ }
743
+ else if (typeof fieldValue === 'number') {
744
+ itemDate = new Date(fieldValue);
745
+ }
746
+ else {
747
+ return false;
748
+ }
749
+ return (itemDate >= filterItem.dateFilter.initial &&
750
+ itemDate <= filterItem.dateFilter.final);
751
+ }
752
+ catch (error) {
753
+ console.warn('Erro ao processar filtro de data para o item:', item.id, error);
754
+ return false;
755
+ }
756
+ });
757
+ }
758
+ });
759
+ // Aplicar filterFn se existir
760
+ if (params.filterFn) {
761
+ items = items.filter(params.filterFn);
762
+ }
763
+ if (sortBy && sortBy.field && sortBy.order) {
764
+ items.sort((a, b) => {
765
+ const valA = a[sortBy.field];
766
+ const valB = b[sortBy.field];
767
+ if (valA < valB) {
768
+ return sortBy.order === 'asc' ? -1 : 1;
769
+ }
770
+ if (valA > valB) {
771
+ return sortBy.order === 'asc' ? 1 : -1;
772
+ }
773
+ return 0;
774
+ });
775
+ }
776
+ // Implementação adequada da paginação
777
+ let currentClientPageIndex = 0;
778
+ // Determinar a página atual baseada na navegação
779
+ if (params.navigation === 'reload') {
780
+ currentClientPageIndex = 0;
781
+ }
782
+ else {
783
+ currentClientPageIndex = params.clientPageIndex || 0;
784
+ }
785
+ const pageSize = params.batchSize;
786
+ const startIndex = currentClientPageIndex * pageSize;
787
+ const endIndex = startIndex + pageSize;
788
+ const paginatedItems = items.slice(startIndex, endIndex);
789
+ const totalPages = Math.ceil(items.length / pageSize);
790
+ const hasNextPage = currentClientPageIndex < totalPages - 1;
791
+ const hasPreviousPage = currentClientPageIndex > 0;
792
+ return {
793
+ items: paginatedItems,
794
+ filterLength: items.length,
795
+ lastDoc: null,
796
+ firstDoc: null,
797
+ hasNextPage: hasNextPage,
798
+ hasPreviousPage: hasPreviousPage,
799
+ currentClientPageIndex: currentClientPageIndex,
800
+ totalPages: totalPages,
801
+ };
802
+ });
803
+ }
804
+ getItemsData(collection, arrange, conditions = undefined) {
805
+ return __awaiter(this, void 0, void 0, function* () {
806
+ try {
807
+ let query = this.ngFire.collection(collection).ref;
808
+ query = this.applyFilters(query, arrange, conditions);
809
+ const snapshot = yield query.get();
810
+ return yield Promise.all(snapshot.docs.map((doc) => __awaiter(this, void 0, void 0, function* () {
811
+ const data = doc.data();
812
+ const id = doc.id;
813
+ return Object.assign({ id }, data);
814
+ })));
815
+ }
816
+ catch (e) {
817
+ throw e;
818
+ }
819
+ });
820
+ }
821
+ deleteIndex(id, col) {
822
+ return __awaiter(this, void 0, void 0, function* () {
823
+ try {
824
+ const batch = this.ngFire.firestore.batch();
825
+ const docRef = this.ngFire.collection(col).doc(id);
826
+ const docSnapshot = (yield firstValueFrom(docRef.get()));
827
+ const doc = docSnapshot.data();
828
+ batch.delete(docRef.ref);
829
+ if (doc && typeof doc.index === 'number') {
830
+ yield this.reindex(doc.index, col, batch);
831
+ }
832
+ yield batch.commit();
833
+ this.toastr.success('Item excluído com sucesso!');
834
+ return true;
835
+ }
836
+ catch (e) {
837
+ const error = e;
838
+ console.error('Erro ao deletar item:', error);
839
+ this.toastr.error('Erro ao deletar item.');
840
+ return false;
841
+ }
842
+ });
843
+ }
844
+ reindex(index, col, batch) {
845
+ return __awaiter(this, void 0, void 0, function* () {
846
+ try {
847
+ const snapshot = (yield firstValueFrom(this.ngFire.collection(col).get()));
848
+ const docs = snapshot.docs;
849
+ for (let doc of docs) {
850
+ const data = doc.data();
851
+ if (data && typeof data.index === 'number' && data.index > index) {
852
+ data.index--;
853
+ const docRef = this.ngFire.collection(col).doc(doc.id).ref;
854
+ batch.update(docRef, data);
855
+ }
856
+ }
857
+ }
858
+ catch (error) {
859
+ console.error('Erro ao reindexar:', error);
860
+ }
861
+ return;
862
+ });
863
+ }
864
+ dateFormatValidator() {
865
+ return (control) => {
866
+ if (!control.value) {
867
+ return null;
868
+ }
869
+ const dateStr = control.value.trim();
870
+ const datePattern = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/;
871
+ if (!datePattern.test(dateStr)) {
872
+ return { invalidFormat: true };
873
+ }
874
+ const parts = dateStr.split('/');
875
+ const day = parts[0].padStart(2, '0');
876
+ const month = parts[1].padStart(2, '0');
877
+ const year = parts[2];
878
+ const normalizedDate = `${day}/${month}/${year}`;
879
+ const date = moment(normalizedDate, 'DD/MM/YYYY', true);
880
+ if (!date.isValid()) {
881
+ return { invalidDate: true };
882
+ }
883
+ return null;
884
+ };
885
+ }
886
+ updateIndex(index, id, col) {
887
+ return __awaiter(this, void 0, void 0, function* () {
888
+ yield this.ngFire.collection(col).doc(id).update({ index });
889
+ });
890
+ }
891
+ /**
892
+ * Extrai o link de criação de índice da mensagem de erro do Firestore
893
+ */
894
+ extractIndexLink(error) {
895
+ if (!error || !error.message)
896
+ return null;
897
+ const linkMatch = error.message.match(/(https:\/\/console\.firebase\.google\.com\/[^\s]+)/);
898
+ return linkMatch ? linkMatch[1] : null;
899
+ }
900
+ /**
901
+ * Rastreia índices ausentes ao usar fallback preventivo
902
+ */
903
+ trackMissingIndexPreventive(collection, arrange, conditions = undefined) {
904
+ var _a;
905
+ return __awaiter(this, void 0, void 0, function* () {
906
+ try {
907
+ const querySignature = this.generateQuerySignature(collection, arrange, conditions);
908
+ const docId = `${collection}_${querySignature}`;
909
+ const indexLink = this.generateIndexLink(collection, arrange, conditions);
910
+ const indexInstructions = this.generateIndexInstructions(collection, arrange, conditions);
911
+ const trackingData = {
912
+ collection,
913
+ indexLink,
914
+ indexInstructions,
915
+ arrange: {
916
+ sortBy: arrange.sortBy,
917
+ filters: ((_a = arrange.filters) === null || _a === void 0 ? void 0 : _a.map((f) => {
918
+ var _a, _b;
919
+ return ({
920
+ arrange: f.arrange,
921
+ property: ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.property) || null,
922
+ dateField: f.arrange === 'filterByDate' ? (_b = arrange.sortBy) === null || _b === void 0 ? void 0 : _b.field : null,
923
+ });
924
+ })) || [],
925
+ },
926
+ conditions: (conditions === null || conditions === void 0 ? void 0 : conditions.map((c) => ({
927
+ property: c.firestoreProperty,
928
+ operator: c.operator,
929
+ }))) || [],
930
+ errorMessage: `Fallback preventivo usado para a collection ${collection}. A query exigiria índice composto.`,
931
+ updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
932
+ };
933
+ console.log('📄 [INDEX LINK] Dados que serão salvos no documento:', {
934
+ docId,
935
+ collection: trackingData.collection,
936
+ indexLink: trackingData.indexLink,
937
+ arrange: trackingData.arrange,
938
+ conditions: trackingData.conditions,
939
+ errorMessage: trackingData.errorMessage,
940
+ });
941
+ const docRef = this.ngFire.collection('missingIndexes').doc(docId);
942
+ const doc = yield docRef.get().toPromise();
943
+ if (doc && doc.exists) {
944
+ yield docRef.update({
945
+ count: firebase.firestore.FieldValue.increment(1),
946
+ updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
947
+ lastError: trackingData.errorMessage,
948
+ });
949
+ }
950
+ else {
951
+ yield docRef.set(Object.assign(Object.assign({}, trackingData), { count: 1, createdAt: firebase.firestore.FieldValue.serverTimestamp() }));
952
+ }
953
+ }
954
+ catch (trackingError) {
955
+ console.warn('Falha ao rastrear fallback preventivo:', trackingError);
956
+ }
957
+ });
958
+ }
959
+ /**
960
+ * Gera uma assinatura única para uma query
961
+ */
962
+ generateQuerySignature(collection, arrange, conditions = undefined) {
963
+ var _a;
964
+ const signature = {
965
+ collection,
966
+ sortBy: arrange.sortBy,
967
+ filters: ((_a = arrange.filters) === null || _a === void 0 ? void 0 : _a.map((f) => {
968
+ var _a;
969
+ return ({
970
+ arrange: f.arrange,
971
+ property: ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.property) || null,
972
+ });
973
+ })) || [],
974
+ conditions: (conditions === null || conditions === void 0 ? void 0 : conditions.map((c) => ({
975
+ property: c.firestoreProperty,
976
+ operator: c.operator,
977
+ }))) || [],
978
+ };
979
+ return btoa(JSON.stringify(signature))
980
+ .replace(/[^a-zA-Z0-9]/g, '')
981
+ .substring(0, 20);
982
+ }
983
+ /**
984
+ * Gera instruções claras para criar o índice manualmente
985
+ */
986
+ generateIndexInstructions(collection, arrange, conditions = undefined) {
987
+ var _a;
988
+ const instructions = {
989
+ summary: '',
990
+ collection: collection,
991
+ fields: [],
992
+ queryExample: '',
993
+ stepByStep: [],
994
+ notes: [],
995
+ };
996
+ const fields = [];
997
+ if (conditions && conditions.length > 0) {
998
+ conditions.forEach((condition) => {
999
+ if (condition.firestoreProperty) {
1000
+ fields.push({
1001
+ field: condition.firestoreProperty,
1002
+ order: 'Ascending',
1003
+ type: 'WHERE clause',
1004
+ operator: condition.operator,
1005
+ description: `Filtrar por ${condition.firestoreProperty} usando operador ${condition.operator}`,
1006
+ });
1007
+ }
1008
+ });
1009
+ }
1010
+ if (arrange.filters && arrange.filters.length > 0) {
1011
+ arrange.filters.forEach((filter) => {
1012
+ var _a;
1013
+ if ((_a = filter.filter) === null || _a === void 0 ? void 0 : _a.property) {
1014
+ fields.push({
1015
+ field: filter.filter.property,
1016
+ order: 'Ascending',
1017
+ type: 'WHERE clause (filter)',
1018
+ operator: filter.arrange === 'filter' ? 'CONTAINS' : 'RANGE',
1019
+ description: `Filtrar por ${filter.filter.property} usando filtro ${filter.arrange}`,
1020
+ });
1021
+ }
1022
+ });
1023
+ }
1024
+ if ((_a = arrange.sortBy) === null || _a === void 0 ? void 0 : _a.field) {
1025
+ fields.push({
1026
+ field: arrange.sortBy.field,
1027
+ order: arrange.sortBy.order === 'desc' ? 'Descending' : 'Ascending',
1028
+ type: 'ORDER BY clause',
1029
+ operator: 'N/A',
1030
+ description: `Ordenar resultados por ${arrange.sortBy.field} em ordem ${arrange.sortBy.order}`,
1031
+ });
1032
+ }
1033
+ instructions.fields = fields;
1034
+ const fieldNames = fields.map((f) => f.field).join(' + ');
1035
+ instructions.summary = `Criar índice composto para ${collection}: ${fieldNames}`;
1036
+ let queryExample = `db.collection('${collection}')`;
1037
+ fields.forEach((field, index) => {
1038
+ if (field.type.includes('WHERE')) {
1039
+ if (field.operator === '==') {
1040
+ queryExample += `\n .where('${field.field}', '==', 'value')`;
1041
+ }
1042
+ else if (field.operator === 'CONTAINS') {
1043
+ queryExample += `\n .where('${field.field}', '>=', 'searchText')`;
1044
+ }
1045
+ else {
1046
+ queryExample += `\n .where('${field.field}', '${field.operator}', 'value')`;
1047
+ }
1048
+ }
1049
+ });
1050
+ const orderByField = fields.find((f) => f.type.includes('ORDER BY'));
1051
+ if (orderByField) {
1052
+ queryExample += `\n .orderBy('${orderByField.field}', '${orderByField.order.toLowerCase()}')`;
1053
+ }
1054
+ instructions.queryExample = queryExample;
1055
+ instructions.stepByStep = [
1056
+ '1. Ir para Firebase Console → Firestore → Indexes',
1057
+ '2. Clicar em "Create Index"',
1058
+ `3. Definir Collection ID: ${collection}`,
1059
+ '4. Configurar campos nesta ORDEM EXATA:',
1060
+ ...fields.map((field, index) => ` ${index + 1}. Campo: ${field.field}, Order: ${field.order}, Array: No`),
1061
+ '5. Definir Query scopes: Collection',
1062
+ '6. Clicar em "Create" e aguardar conclusão',
1063
+ ];
1064
+ instructions.notes = [
1065
+ '⚠️ A ordem dos campos é CRÍTICA - deve corresponder exatamente à ordem da query',
1066
+ '⚠️ As cláusulas WHERE devem vir ANTES do campo ORDER BY',
1067
+ '⚠️ Este índice só funcionará para queries com esta combinação EXATA de campos',
1068
+ '⚠️ A criação do índice pode levar vários minutos',
1069
+ ];
1070
+ return instructions;
1071
+ }
1072
+ /**
1073
+ * Gera um link de índice baseado na estrutura da query
1074
+ */
1075
+ generateIndexLink(collection, arrange, conditions = undefined) {
1076
+ var _a;
1077
+ try {
1078
+ const indexFields = [];
1079
+ if (conditions && conditions.length > 0) {
1080
+ conditions.forEach((condition) => {
1081
+ if (condition.firestoreProperty) {
1082
+ indexFields.push(condition.firestoreProperty);
1083
+ }
1084
+ });
1085
+ }
1086
+ if (arrange.filters && arrange.filters.length > 0) {
1087
+ arrange.filters.forEach((filter) => {
1088
+ var _a;
1089
+ if ((_a = filter.filter) === null || _a === void 0 ? void 0 : _a.property) {
1090
+ indexFields.push(filter.filter.property);
1091
+ }
1092
+ });
1093
+ }
1094
+ if ((_a = arrange.sortBy) === null || _a === void 0 ? void 0 : _a.field) {
1095
+ indexFields.push(arrange.sortBy.field);
1096
+ }
1097
+ if (indexFields.length > 1) {
1098
+ const baseUrl = 'https://console.firebase.google.com/project/toppayy-dev/firestore/indexes';
1099
+ const queryParams = new URLSearchParams({
1100
+ create_composite: `collection=${collection}&fields=${indexFields.join(',')}`,
1101
+ });
1102
+ const finalLink = `${baseUrl}?${queryParams.toString()}`;
1103
+ return finalLink;
1104
+ }
1105
+ return null;
1106
+ }
1107
+ catch (error) {
1108
+ console.warn('Falha ao gerar link de índice:', error);
1109
+ return null;
1110
+ }
1111
+ }
1112
+ trackMissingIndex(error, collection, arrange, conditions = undefined) {
1113
+ var _a;
1114
+ return __awaiter(this, void 0, void 0, function* () {
1115
+ try {
1116
+ const indexLink = this.extractIndexLink(error);
1117
+ if (!indexLink)
1118
+ return;
1119
+ const linkHash = btoa(indexLink)
1120
+ .replace(/[^a-zA-Z0-9]/g, '')
1121
+ .substring(0, 20);
1122
+ const docId = `${collection}_${linkHash}`;
1123
+ const indexInstructions = this.generateIndexInstructions(collection, arrange, conditions);
1124
+ const trackingData = {
1125
+ collection,
1126
+ indexLink,
1127
+ indexInstructions,
1128
+ arrange: {
1129
+ sortBy: arrange.sortBy,
1130
+ filters: ((_a = arrange.filters) === null || _a === void 0 ? void 0 : _a.map((f) => {
1131
+ var _a, _b;
1132
+ return ({
1133
+ arrange: f.arrange,
1134
+ property: ((_a = f.filter) === null || _a === void 0 ? void 0 : _a.property) || null,
1135
+ dateField: f.arrange === 'filterByDate' ? (_b = arrange.sortBy) === null || _b === void 0 ? void 0 : _b.field : null,
1136
+ });
1137
+ })) || [],
1138
+ },
1139
+ conditions: (conditions === null || conditions === void 0 ? void 0 : conditions.map((c) => ({
1140
+ property: c.firestoreProperty,
1141
+ operator: c.operator,
1142
+ }))) || [],
1143
+ errorMessage: error.message,
1144
+ updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
1145
+ };
1146
+ const docRef = this.ngFire.collection('missingIndexes').doc(docId);
1147
+ const doc = yield docRef.get().toPromise();
1148
+ if (doc && doc.exists) {
1149
+ yield docRef.update({
1150
+ count: firebase.firestore.FieldValue.increment(1),
1151
+ updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
1152
+ lastError: error.message,
1153
+ });
1154
+ }
1155
+ else {
1156
+ yield docRef.set(Object.assign(Object.assign({}, trackingData), { count: 1, createdAt: firebase.firestore.FieldValue.serverTimestamp() }));
1157
+ }
1158
+ }
1159
+ catch (trackingError) {
1160
+ console.warn('Falha ao rastrear índice ausente:', trackingError);
1161
+ }
1162
+ });
1163
+ }
1164
+ }
1165
+ TableService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: TableService, deps: [{ token: i1.AngularFirestore, optional: true }, { token: i2.MatDialog, optional: true }, { token: i3.ToastrService, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
1166
+ TableService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: TableService, providedIn: 'root' });
1167
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: TableService, decorators: [{
1168
+ type: Injectable,
1169
+ args: [{
1170
+ providedIn: 'root',
1171
+ }]
1172
+ }], ctorParameters: function () {
1173
+ return [{ type: i1.AngularFirestore, decorators: [{
1174
+ type: Optional
1175
+ }] }, { type: i2.MatDialog, decorators: [{
1176
+ type: Optional
1177
+ }] }, { type: i3.ToastrService, decorators: [{
1178
+ type: Optional
1179
+ }] }];
1180
1180
  } });
1181
1181
 
1182
- class TableComponent {
1183
- // CONSTRUCTOR
1184
- constructor(router, tableService, firestore) {
1185
- this.router = router;
1186
- this.tableService = tableService;
1187
- this.firestore = firestore;
1188
- this.arrange = null;
1189
- this.currentPageNumber = 1;
1190
- this.currentClientPageIndex = 0;
1191
- this.items = [];
1192
- this.isLoading = false;
1193
- this.lastDoc = null;
1194
- this.firstDoc = null;
1195
- this.sortBy = {
1196
- field: 'createdAt',
1197
- order: 'desc',
1198
- };
1199
- this.columnProperties = [];
1200
- this.selectSort = new FormControl('');
1201
- this.currentArrange = '';
1202
- this.hasNextPage = false;
1203
- this.dropdownItems = [];
1204
- this.sortableDropdownItems = [];
1205
- this.pageSize = 25;
1206
- this.totalItems = 0;
1207
- this.filterValue = null;
1208
- this.hasFilterableColumn = false;
1209
- this.hasSortableColumn = false;
1210
- this.filterSubject = new Subject();
1211
- this.debounceTimeMs = 500;
1212
- this.selectedTab = 0;
1213
- // Propriedades para controle do tooltip
1214
- this.hoveredCell = null;
1215
- this.showTooltip = false;
1216
- this.tooltipContent = '';
1217
- this.tooltipPosition = { x: 0, y: 0 };
1218
- this.filtersForm = new FormArray([this.createFilterGroup()]);
1219
- }
1220
- createFilterGroup() {
1221
- return new FormGroup({
1222
- selectFilter: new FormControl(''),
1223
- typeFilter: new FormControl(''),
1224
- selectItem: new FormControl(''),
1225
- initialDate: new FormControl('', this.tableService.dateFormatValidator()),
1226
- finalDate: new FormControl('', this.tableService.dateFormatValidator()),
1227
- });
1228
- }
1229
- addFilter(filterData) {
1230
- var _a, _b, _c, _d, _e;
1231
- const newFilterGroup = this.createFilterGroup();
1232
- if (filterData) {
1233
- if (filterData.selectFilter) {
1234
- (_a = newFilterGroup.get('selectFilter')) === null || _a === void 0 ? void 0 : _a.setValue(filterData.selectFilter);
1235
- }
1236
- if (filterData.typeFilter) {
1237
- (_b = newFilterGroup.get('typeFilter')) === null || _b === void 0 ? void 0 : _b.setValue(filterData.typeFilter);
1238
- }
1239
- if (filterData.selectItem) {
1240
- (_c = newFilterGroup.get('selectItem')) === null || _c === void 0 ? void 0 : _c.setValue(filterData.selectItem);
1241
- }
1242
- if (filterData.initialDate) {
1243
- (_d = newFilterGroup.get('initialDate')) === null || _d === void 0 ? void 0 : _d.setValue(filterData.initialDate);
1244
- }
1245
- if (filterData.finalDate) {
1246
- (_e = newFilterGroup.get('finalDate')) === null || _e === void 0 ? void 0 : _e.setValue(filterData.finalDate);
1247
- }
1248
- }
1249
- this.filtersForm.push(newFilterGroup);
1250
- }
1251
- onSelectFilterChange() {
1252
- var _a;
1253
- const lastIndex = this.filtersForm.length - 1;
1254
- const lastFilter = this.filtersForm.at(lastIndex);
1255
- if ((_a = lastFilter.get('selectFilter')) === null || _a === void 0 ? void 0 : _a.value) {
1256
- this.addFilter();
1257
- }
1258
- }
1259
- removeFilter(index) {
1260
- this.filtersForm.removeAt(index);
1261
- if (this.filtersForm.length === 0) {
1262
- this.addFilter();
1263
- }
1264
- }
1265
- removeAllFilters() {
1266
- this.filtersForm.clear();
1267
- this.addFilter();
1268
- this.resetFilter();
1269
- }
1270
- // METHODS
1271
- ngOnInit() {
1272
- return __awaiter(this, void 0, void 0, function* () {
1273
- if (!this.data.color)
1274
- this.data.color = { bg: 'bg-primary', text: 'text-black' };
1275
- this.columnProperties = this.data.displayedColumns.map((column) => {
1276
- return column.property;
1277
- });
1278
- if (this.data.actionButton && !this.data.actionButton.condition) {
1279
- this.data.actionButton.condition = (_row) => true;
1280
- }
1281
- this.data.displayedColumns.forEach((col) => {
1282
- if (col.isFilterable) {
1283
- if (this.hasFilterableColumn === false)
1284
- this.hasFilterableColumn = true;
1285
- this.dropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'filter', title: col.title }));
1286
- }
1287
- if (col.isSortable) {
1288
- if (this.hasSortableColumn === false)
1289
- this.hasSortableColumn = true;
1290
- this.sortableDropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'ascending', title: col.title + ': crescente' }));
1291
- this.sortableDropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'descending', title: col.title + ': decrescente' }));
1292
- }
1293
- if (col.isFilterableByDate) {
1294
- this.dropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'filterByDate', title: col.title + ': filtro por data' }));
1295
- }
1296
- });
1297
- if (this.data.filterableOptions &&
1298
- Array.isArray(this.data.filterableOptions) &&
1299
- this.data.filterableOptions.length > 0) {
1300
- this.data.filterableOptions.forEach((option) => this.dropdownItems.push(Object.assign(Object.assign({}, option), { arrange: 'equals' })));
1301
- }
1302
- // Sem paginação
1303
- if (this.data.pagination === false) {
1304
- yield this.loadItems();
1305
- }
1306
- // Com paginação
1307
- if (this.data.pagination === true) {
1308
- if (this.data.sortBy)
1309
- this.sortBy = {
1310
- field: this.data.sortBy.field,
1311
- order: this.data.sortBy.order,
1312
- };
1313
- this.filterSubject
1314
- .pipe(debounceTime(this.debounceTimeMs))
1315
- .subscribe(() => {
1316
- this.loadItemsPaginated('reload', true);
1317
- });
1318
- this.isLoading = true;
1319
- yield this.loadItemsPaginated('reload', true);
1320
- this.sort.active = 'createdAt';
1321
- this.sort.direction = 'desc';
1322
- this.dataSource.paginator = this.paginator;
1323
- this.dataSource.sort = this.sort;
1324
- this.totalItems = 0;
1325
- if (this.data.totalRef) {
1326
- for (const totalRef of this.data.totalRef) {
1327
- const totalRefDoc = yield totalRef.ref.get();
1328
- const docData = totalRefDoc.data();
1329
- if (docData && docData[totalRef.field])
1330
- this.totalItems = (this.totalItems +
1331
- docData[totalRef.field]);
1332
- }
1333
- }
1334
- this.isLoading = false;
1335
- }
1336
- });
1337
- }
1338
- getDisplayValue(col, row, withinLimit = false) {
1339
- let value;
1340
- if (col.calculateValue) {
1341
- value = col.calculateValue(row);
1342
- }
1343
- else {
1344
- value = this.getNestedValue(row, col.property);
1345
- }
1346
- if (Array.isArray(value) && col.arrayField) {
1347
- value = this.formatArrayValue(value, col.arrayField);
1348
- }
1349
- if (col.queryLength && row[col.property]) {
1350
- value = row[col.property];
1351
- }
1352
- value = col.pipe ? col.pipe.transform(value) : value;
1353
- if (value === null || value === undefined) {
1354
- value = '';
1355
- }
1356
- else {
1357
- value = String(value);
1358
- }
1359
- if (typeof value !== 'string') {
1360
- value = '';
1361
- }
1362
- if (withinLimit || !col.charLimit) {
1363
- return value;
1364
- }
1365
- return value.substring(0, col.charLimit) + '...';
1366
- }
1367
- getNestedValue(obj, path) {
1368
- if (!path)
1369
- return undefined;
1370
- const properties = path.split('.');
1371
- return properties.reduce((acc, currentPart) => acc && acc[currentPart], obj);
1372
- }
1373
- formatArrayValue(array, field) {
1374
- if (!Array.isArray(array) || array.length === 0) {
1375
- return '';
1376
- }
1377
- const values = array
1378
- .map((item) => {
1379
- if (typeof item === 'object' && item !== null) {
1380
- return item[field] || '';
1381
- }
1382
- return String(item);
1383
- })
1384
- .filter((value) => value !== '' && value !== null && value !== undefined);
1385
- return values.join(', ');
1386
- }
1387
- // Métodos sem paginação
1388
- loadItems() {
1389
- return __awaiter(this, void 0, void 0, function* () {
1390
- this.items = yield this.tableService.getItems(this.data.collectionRef);
1391
- if (this.data.conditions) {
1392
- this.filterItems();
1393
- }
1394
- yield this.loadRelations();
1395
- yield this.loadQueryLengths();
1396
- this.totalItems = this.items.length;
1397
- // Aplicar filtros client-side se existirem
1398
- let itemsToDisplay = [...this.items];
1399
- itemsToDisplay = this.applyClientSideFilters(itemsToDisplay);
1400
- this.dataSource = new MatTableDataSource(itemsToDisplay);
1401
- this.dataSource.paginator = this.paginator;
1402
- this.dataSource.sort = this.sort;
1403
- if (this.data.sortBy) {
1404
- this.dataSource.sort.active = this.data.sortBy.field;
1405
- this.dataSource.sort.direction = this.data.sortBy.order;
1406
- this.dataSource.sort.sortChange.emit();
1407
- }
1408
- this.dataSource.filterPredicate = (data, filter) => {
1409
- return this.data.displayedColumns.some((col) => {
1410
- if (col.filterPredicates) {
1411
- return col.filterPredicates.some((predicate) => {
1412
- const propertyValue = data[col.property];
1413
- if (!propertyValue || typeof propertyValue !== 'object') {
1414
- return false;
1415
- }
1416
- const predicateValue = propertyValue[predicate];
1417
- if (predicateValue === null || predicateValue === undefined) {
1418
- return false;
1419
- }
1420
- return String(predicateValue)
1421
- .trim()
1422
- .toLocaleLowerCase()
1423
- .includes(filter);
1424
- });
1425
- }
1426
- if (col.property && col.isFilterable) {
1427
- const propertyValue = data[col.property];
1428
- if (propertyValue === null || propertyValue === undefined) {
1429
- return false;
1430
- }
1431
- return String(propertyValue)
1432
- .trim()
1433
- .toLocaleLowerCase()
1434
- .includes(filter);
1435
- }
1436
- return false;
1437
- });
1438
- };
1439
- this.filterPredicate = this.dataSource.filterPredicate;
1440
- });
1441
- }
1442
- // Aplicar filtros client-side (filtros por data)
1443
- applyClientSideFilters(items) {
1444
- let filteredItems = [...items];
1445
- // Processar filtros do filtersForm
1446
- this.filtersForm.controls.forEach((control) => {
1447
- var _a, _b, _c;
1448
- const group = control;
1449
- const selectedFilter = (_a = group.get('selectFilter')) === null || _a === void 0 ? void 0 : _a.value;
1450
- if (!selectedFilter)
1451
- return;
1452
- const arrange = selectedFilter.arrange;
1453
- if (arrange === 'filterByDate') {
1454
- const initial = (_b = group.get('initialDate')) === null || _b === void 0 ? void 0 : _b.value;
1455
- const final = (_c = group.get('finalDate')) === null || _c === void 0 ? void 0 : _c.value;
1456
- // aplicar filtro se ambas as datas estiverem preenchidas
1457
- if (initial && final && initial.trim() && final.trim()) {
1458
- try {
1459
- // Validar formato da data (DD/MM/AAAA)
1460
- const datePattern = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/;
1461
- if (!datePattern.test(initial) || !datePattern.test(final)) {
1462
- return; // Ignorar se formato inválido
1463
- }
1464
- const [dayI, monthI, yearI] = initial.split('/');
1465
- const initialDate = new Date(`${monthI}/${dayI}/${yearI}`);
1466
- const [dayF, monthF, yearF] = final.split('/');
1467
- const finalDate = new Date(`${monthF}/${dayF}/${yearF}`);
1468
- finalDate.setHours(23, 59, 59);
1469
- // Validar se as datas são válidas
1470
- if (isNaN(initialDate.getTime()) || isNaN(finalDate.getTime())) {
1471
- return; // Ignorar se datas inválidas
1472
- }
1473
- // Usar o campo da coluna ou o sortBy.field como padrão
1474
- const dateField = selectedFilter.property || this.sortBy.field;
1475
- filteredItems = filteredItems.filter((item) => {
1476
- try {
1477
- const fieldValue = item[dateField];
1478
- if (!fieldValue) {
1479
- return false;
1480
- }
1481
- let itemDate;
1482
- if (typeof fieldValue.toDate === 'function') {
1483
- itemDate = fieldValue.toDate();
1484
- }
1485
- else if (fieldValue instanceof Date) {
1486
- itemDate = fieldValue;
1487
- }
1488
- else if (typeof fieldValue === 'string') {
1489
- itemDate = new Date(fieldValue);
1490
- if (isNaN(itemDate.getTime())) {
1491
- return false;
1492
- }
1493
- }
1494
- else if (typeof fieldValue === 'number') {
1495
- itemDate = new Date(fieldValue);
1496
- }
1497
- else {
1498
- return false;
1499
- }
1500
- return itemDate >= initialDate && itemDate <= finalDate;
1501
- }
1502
- catch (error) {
1503
- console.warn('Erro ao processar filtro de data para o item:', item.id, error);
1504
- return false;
1505
- }
1506
- });
1507
- }
1508
- catch (error) {
1509
- console.warn('Erro ao processar datas do filtro:', error);
1510
- }
1511
- }
1512
- // Se as datas não estiverem preenchidas, não aplicar filtro (retornar todos os itens)
1513
- }
1514
- });
1515
- return filteredItems;
1516
- }
1517
- // Handler para mudanças de data no filtro
1518
- onDateFilterChange() {
1519
- if (this.data.pagination === false) {
1520
- // Aplicar filtros (ou remover se campos estiverem vazios)
1521
- this.applyFiltersToDataSource();
1522
- }
1523
- }
1524
- // Aplicar filtros ao dataSource quando não há paginação
1525
- applyFiltersToDataSource() {
1526
- if (!this.dataSource)
1527
- return;
1528
- let filteredItems = this.applyClientSideFilters([...this.items]);
1529
- this.dataSource.data = filteredItems;
1530
- this.totalItems = filteredItems.length;
1531
- }
1532
- // Métodos com paginação
1533
- loadItemsPaginated(navigation = 'reload', reset = false) {
1534
- var _a;
1535
- return __awaiter(this, void 0, void 0, function* () {
1536
- if (reset && ['forward', 'reload'].includes(navigation)) {
1537
- this.lastDoc = null;
1538
- this.currentClientPageIndex = 0;
1539
- }
1540
- if (reset && ['backward', 'reload'].includes(navigation)) {
1541
- this.firstDoc = null;
1542
- this.currentClientPageIndex = 0;
1543
- }
1544
- const activeFilters = this.filtersForm.controls
1545
- .flatMap((control) => {
1546
- var _a, _b, _c, _d, _e;
1547
- const group = control;
1548
- const selectedFilter = (_a = group.get('selectFilter')) === null || _a === void 0 ? void 0 : _a.value;
1549
- if (!selectedFilter)
1550
- return [];
1551
- const arrange = selectedFilter.arrange;
1552
- if (arrange === 'filter') {
1553
- const filterValue = (_b = group.get('typeFilter')) === null || _b === void 0 ? void 0 : _b.value;
1554
- if (!filterValue)
1555
- return [];
1556
- return {
1557
- arrange,
1558
- filter: {
1559
- property: selectedFilter.property,
1560
- filtering: filterValue,
1561
- },
1562
- dateFilter: undefined,
1563
- };
1564
- }
1565
- if (arrange === 'filterByDate') {
1566
- const initial = (_c = group.get('initialDate')) === null || _c === void 0 ? void 0 : _c.value;
1567
- const final = (_d = group.get('finalDate')) === null || _d === void 0 ? void 0 : _d.value;
1568
- if (initial && final) {
1569
- const [dayI, monthI, yearI] = initial.split('/');
1570
- const initialDate = new Date(`${monthI}/${dayI}/${yearI}`);
1571
- const [dayF, monthF, yearF] = final.split('/');
1572
- const finalDate = new Date(`${monthF}/${dayF}/${yearF}`);
1573
- finalDate.setHours(23, 59, 59);
1574
- return {
1575
- arrange,
1576
- filter: undefined,
1577
- dateFilter: {
1578
- initial: initialDate,
1579
- final: finalDate,
1580
- },
1581
- };
1582
- }
1583
- return [];
1584
- }
1585
- if (selectedFilter.hasOwnProperty('items') && arrange === 'equals') {
1586
- const selectedItems = (_e = group.get('selectItem')) === null || _e === void 0 ? void 0 : _e.value;
1587
- if (Array.isArray(selectedItems) && selectedItems.length > 0) {
1588
- return selectedItems.map((item) => ({
1589
- arrange,
1590
- filter: {
1591
- property: item.property,
1592
- filtering: item.value,
1593
- },
1594
- dateFilter: undefined,
1595
- }));
1596
- }
1597
- }
1598
- return [];
1599
- })
1600
- .filter((f) => { var _a; return f && (((_a = f.filter) === null || _a === void 0 ? void 0 : _a.filtering) !== undefined || f.dateFilter); });
1601
- this.arrange = {
1602
- filters: activeFilters,
1603
- sortBy: this.sortBy,
1604
- };
1605
- const paginated = {
1606
- batchSize: this.pageSize,
1607
- collection: this.data.collection,
1608
- doc: { lastDoc: this.lastDoc, firstDoc: this.firstDoc },
1609
- navigation,
1610
- arrange: this.arrange,
1611
- conditions: this.data.conditions,
1612
- size: this.totalItems,
1613
- filterFn: this.data.filterFn,
1614
- clientPageIndex: this.currentClientPageIndex,
1615
- };
1616
- const result = yield this.tableService.getPaginated(paginated);
1617
- this.items = result.items;
1618
- yield this.loadRelations();
1619
- yield this.loadQueryLengths();
1620
- this.lastDoc = result.lastDoc;
1621
- this.firstDoc = result.firstDoc;
1622
- // Atualizar currentClientPageIndex se retornado pelo fallback
1623
- if (result.currentClientPageIndex !== undefined) {
1624
- this.currentClientPageIndex = result.currentClientPageIndex;
1625
- }
1626
- let sum = 0;
1627
- if (this.data.totalRef) {
1628
- for (const totalRef of this.data.totalRef) {
1629
- const totalRefDoc = yield totalRef.ref.get();
1630
- const docData = totalRefDoc.data();
1631
- if (docData || result.filterLength) {
1632
- sum =
1633
- (_a = result.filterLength) !== null && _a !== void 0 ? _a : (sum + (docData ? docData[totalRef.field] : 0));
1634
- }
1635
- }
1636
- this.totalItems = sum;
1637
- }
1638
- this.hasNextPage = result.hasNextPage;
1639
- this.dataSource = new MatTableDataSource(this.items);
1640
- this.filterPredicate = this.dataSource.filterPredicate;
1641
- });
1642
- }
1643
- onPageChange(event) {
1644
- var _a;
1645
- return __awaiter(this, void 0, void 0, function* () {
1646
- if (this.data.pagination === true && event) {
1647
- this.isLoading = true;
1648
- const previousPageIndex = (_a = event.previousPageIndex) !== null && _a !== void 0 ? _a : 0;
1649
- const pageIndex = event.pageIndex;
1650
- const currentComponentPageSize = this.pageSize;
1651
- const eventPageSize = event.pageSize;
1652
- const totalItems = this.totalItems;
1653
- let navigationDirection;
1654
- let resetDocs = false;
1655
- let originalPageSize = null;
1656
- const lastPageIndex = Math.max(0, Math.ceil(totalItems / eventPageSize) - 1);
1657
- // Atualizar currentClientPageIndex sempre para o fallback
1658
- this.currentClientPageIndex = pageIndex;
1659
- if (previousPageIndex !== undefined && pageIndex > previousPageIndex) {
1660
- this.currentPageNumber++;
1661
- }
1662
- else if (previousPageIndex !== undefined &&
1663
- pageIndex < previousPageIndex) {
1664
- this.currentPageNumber = Math.max(1, this.currentPageNumber - 1);
1665
- }
1666
- if (eventPageSize !== currentComponentPageSize) {
1667
- console.log('Alterou a quantidade de elementos exibidos por página');
1668
- this.pageSize = eventPageSize;
1669
- navigationDirection = 'forward';
1670
- resetDocs = true;
1671
- this.currentClientPageIndex = 0;
1672
- }
1673
- else if (pageIndex === 0 &&
1674
- previousPageIndex !== undefined &&
1675
- pageIndex < previousPageIndex) {
1676
- console.log('Pulou para a primeira página');
1677
- navigationDirection = 'forward';
1678
- this.currentPageNumber = 1;
1679
- this.currentClientPageIndex = 0;
1680
- resetDocs = true;
1681
- }
1682
- else if (pageIndex === lastPageIndex &&
1683
- previousPageIndex !== undefined &&
1684
- pageIndex > previousPageIndex &&
1685
- pageIndex - previousPageIndex > 1) {
1686
- console.log('Pulou para a ultima página');
1687
- navigationDirection = 'backward';
1688
- resetDocs = true;
1689
- const itemsExpectedInLastPage = totalItems - lastPageIndex * eventPageSize;
1690
- if (itemsExpectedInLastPage > 0 &&
1691
- itemsExpectedInLastPage < eventPageSize) {
1692
- originalPageSize = this.pageSize;
1693
- this.pageSize = itemsExpectedInLastPage;
1694
- }
1695
- }
1696
- else if (previousPageIndex !== undefined &&
1697
- pageIndex > previousPageIndex) {
1698
- console.log('Procedendo');
1699
- navigationDirection = 'forward';
1700
- resetDocs = false;
1701
- }
1702
- else if (previousPageIndex !== undefined &&
1703
- pageIndex < previousPageIndex) {
1704
- console.log('Retrocedendo.');
1705
- navigationDirection = 'backward';
1706
- resetDocs = false;
1707
- }
1708
- else if (previousPageIndex !== undefined &&
1709
- pageIndex === previousPageIndex) {
1710
- console.log('Recarregando.');
1711
- navigationDirection = 'reload';
1712
- resetDocs = false;
1713
- }
1714
- else if (previousPageIndex === undefined && pageIndex === 0) {
1715
- console.log('Evento inicial do paginador para pág 0. ngOnInit carregou.');
1716
- this.isLoading = false;
1717
- if (event)
1718
- this.pageEvent = event;
1719
- return;
1720
- }
1721
- else {
1722
- console.warn('INESPERADO! Condição de navegação não tratada:', event);
1723
- this.isLoading = false;
1724
- if (event)
1725
- this.pageEvent = event;
1726
- return;
1727
- }
1728
- if (navigationDirection) {
1729
- try {
1730
- yield this.loadItemsPaginated(navigationDirection, resetDocs);
1731
- }
1732
- catch (error) {
1733
- console.error('Erro ao carregar itens paginados:', error);
1734
- }
1735
- finally {
1736
- if (originalPageSize !== null) {
1737
- this.pageSize = originalPageSize;
1738
- }
1739
- }
1740
- }
1741
- if (event)
1742
- this.pageEvent = event;
1743
- this.isLoading = false;
1744
- }
1745
- });
1746
- }
1747
- // Outros métodos
1748
- applyFilter(value) {
1749
- // Sem paginação
1750
- if (this.data.pagination === false) {
1751
- this.dataSource.filter = String(value).trim().toLowerCase();
1752
- }
1753
- // Com paginação
1754
- if (this.data.pagination === true) {
1755
- this.filterValue = value;
1756
- this.filterSubject.next(this.filterValue);
1757
- }
1758
- }
1759
- goToDetails(row) {
1760
- if (this.data.isNotClickable) {
1761
- return;
1762
- }
1763
- const urlPath = this.data.url || this.data.name;
1764
- const url = this.router.serializeUrl(this.router.createUrlTree([`/${urlPath}`, row.id]));
1765
- window.open(url, '_blank');
1766
- }
1767
- getRelation(params) {
1768
- var _a;
1769
- return __awaiter(this, void 0, void 0, function* () {
1770
- try {
1771
- let snapshot;
1772
- if (params.id !== '' &&
1773
- params.id !== undefined &&
1774
- params.collection !== undefined &&
1775
- params.collection !== '') {
1776
- snapshot = yield firstValueFrom(this.firestore.collection(params.collection).doc(params.id).get());
1777
- }
1778
- if (snapshot && snapshot.exists) {
1779
- const data = snapshot.data();
1780
- return (_a = data === null || data === void 0 ? void 0 : data[params.newProperty]) !== null && _a !== void 0 ? _a : '';
1781
- }
1782
- return '';
1783
- }
1784
- catch (e) {
1785
- console.log(e);
1786
- return '';
1787
- }
1788
- });
1789
- }
1790
- loadRelations() {
1791
- return __awaiter(this, void 0, void 0, function* () {
1792
- const relationPromises = this.data.displayedColumns
1793
- .filter((col) => col.relation)
1794
- .flatMap((col) => this.items.map((item) => __awaiter(this, void 0, void 0, function* () {
1795
- if (col.relation) {
1796
- item[col.property] = yield this.getRelation({
1797
- id: item[col.relation.property],
1798
- collection: col.relation.collection,
1799
- newProperty: col.relation.newProperty,
1800
- });
1801
- }
1802
- })));
1803
- yield Promise.all(relationPromises);
1804
- });
1805
- }
1806
- getQueryLength(params) {
1807
- return __awaiter(this, void 0, void 0, function* () {
1808
- const snapshot = yield this.firestore
1809
- .collection(params.relation.collection)
1810
- .ref.where(params.relation.property, params.relation.operator, params.item[params.relation.value])
1811
- .get();
1812
- return snapshot.size;
1813
- });
1814
- }
1815
- loadQueryLengths() {
1816
- return __awaiter(this, void 0, void 0, function* () {
1817
- const lengthPromises = this.data.displayedColumns
1818
- .filter((col) => col.queryLength)
1819
- .flatMap((col) => this.items.map((item) => __awaiter(this, void 0, void 0, function* () {
1820
- if (col.queryLength) {
1821
- item[col.property] = yield this.getQueryLength({
1822
- item: item,
1823
- relation: col.queryLength,
1824
- });
1825
- }
1826
- })));
1827
- yield Promise.all(lengthPromises);
1828
- });
1829
- }
1830
- filterItems() {
1831
- if (this.data.conditions) {
1832
- this.data.conditions.forEach((cond) => {
1833
- this.items = this.items.filter((item) => {
1834
- const operatorFunction = this.tableService.operators[cond.operator];
1835
- if (operatorFunction) {
1836
- return operatorFunction(item[cond.firestoreProperty], cond.dashProperty);
1837
- }
1838
- return false;
1839
- });
1840
- });
1841
- }
1842
- }
1843
- // Filtro de data
1844
- search() {
1845
- var _a, _b, _c, _d, _e, _f;
1846
- return __awaiter(this, void 0, void 0, function* () {
1847
- if (this.selectSort.value) {
1848
- if (this.selectSort.value.arrange === 'ascending') {
1849
- this.sortBy = {
1850
- field: this.selectSort.value.property,
1851
- order: 'asc',
1852
- };
1853
- }
1854
- if (this.selectSort.value.arrange === 'descending') {
1855
- this.sortBy = {
1856
- field: this.selectSort.value.property,
1857
- order: 'desc',
1858
- };
1859
- }
1860
- }
1861
- // Sem paginação: aplicar filtros client-side
1862
- if (this.data.pagination === false) {
1863
- this.applyFiltersToDataSource();
1864
- this.currentArrange =
1865
- this.filtersForm.length > 0
1866
- ? (_c = (_b = (_a = this.filtersForm.at(0).get('selectFilter')) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.arrange) !== null && _c !== void 0 ? _c : ''
1867
- : '';
1868
- }
1869
- else {
1870
- // Com paginação: comportamento original
1871
- yield this.loadItemsPaginated('reload', true);
1872
- this.currentArrange =
1873
- this.filtersForm.length > 0
1874
- ? (_f = (_e = (_d = this.filtersForm.at(0).get('selectFilter')) === null || _d === void 0 ? void 0 : _d.value) === null || _e === void 0 ? void 0 : _e.arrange) !== null && _f !== void 0 ? _f : ''
1875
- : '';
1876
- this.paginator.firstPage();
1877
- }
1878
- });
1879
- }
1880
- resetFilter() {
1881
- return __awaiter(this, void 0, void 0, function* () {
1882
- this.dataSource.filter = '';
1883
- if (this.filterPredicate) {
1884
- this.dataSource.filterPredicate = this.filterPredicate;
1885
- }
1886
- this.filtersForm.clear();
1887
- this.addFilter();
1888
- this.selectSort.patchValue('');
1889
- this.sortBy = {
1890
- order: this.data.sortBy ? this.data.sortBy.order : 'desc',
1891
- field: this.data.sortBy ? this.data.sortBy.field : 'createdAt',
1892
- };
1893
- // Sem paginação: recarregar itens originais
1894
- if (this.data.pagination === false) {
1895
- this.dataSource.data = [...this.items];
1896
- this.totalItems = this.items.length;
1897
- this.currentArrange = '';
1898
- }
1899
- else {
1900
- // Com paginação: comportamento original
1901
- yield this.loadItemsPaginated('reload', true);
1902
- this.currentArrange = '';
1903
- this.paginator.firstPage();
1904
- }
1905
- });
1906
- }
1907
- // Método público para recarregar a tabela
1908
- reloadTable() {
1909
- return __awaiter(this, void 0, void 0, function* () {
1910
- if (this.data.pagination) {
1911
- yield this.loadItemsPaginated('reload', true);
1912
- this.paginator.firstPage();
1913
- }
1914
- else {
1915
- yield this.loadItems();
1916
- }
1917
- });
1918
- }
1919
- updateDisplayedColumns() {
1920
- if (this.dataSource) {
1921
- this.dataSource = new MatTableDataSource([]);
1922
- }
1923
- this.columnProperties = this.data.displayedColumns.map((column) => {
1924
- return column.property;
1925
- });
1926
- this.dropdownItems = [];
1927
- this.sortableDropdownItems = [];
1928
- this.data.displayedColumns.forEach((col) => {
1929
- if (col.isFilterable) {
1930
- this.dropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'filter', title: col.title }));
1931
- }
1932
- if (col.isSortable) {
1933
- this.sortableDropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'ascending', title: col.title + ': crescente' }));
1934
- this.sortableDropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'descending', title: col.title + ': decrescente' }));
1935
- }
1936
- if (col.isFilterableByDate) {
1937
- this.dropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'filterByDate', title: col.title + ': filtro por data' }));
1938
- }
1939
- });
1940
- if (this.data.filterableOptions &&
1941
- Array.isArray(this.data.filterableOptions)) {
1942
- this.data.filterableOptions.forEach((option) => this.dropdownItems.push(Object.assign(Object.assign({}, option), { arrange: 'equals' })));
1943
- }
1944
- }
1945
- isString(value) {
1946
- return typeof value === 'string';
1947
- }
1948
- // Métodos para controle do tooltip
1949
- onCellMouseEnter(event, row, col) {
1950
- // Só mostrar tooltip se a coluna tiver charLimit definido
1951
- if (!col.charLimit) {
1952
- return;
1953
- }
1954
- const fullValue = this.getDisplayValue(col, row, true);
1955
- // mostrar tooltip se o valor completo for maior que o limite
1956
- if (fullValue.length <= col.charLimit) {
1957
- return;
1958
- }
1959
- this.hoveredCell = { row, col };
1960
- this.tooltipContent = fullValue;
1961
- // Definir posição do tooltip
1962
- this.tooltipPosition = {
1963
- x: event.clientX + 10,
1964
- y: event.clientY - 10,
1965
- };
1966
- // Timeout para mostrar o tooltip
1967
- this.tooltipTimeout = setTimeout(() => {
1968
- if (this.hoveredCell &&
1969
- this.hoveredCell.row === row &&
1970
- this.hoveredCell.col === col) {
1971
- this.showTooltip = true;
1972
- }
1973
- }, 500);
1974
- }
1975
- onCellMouseLeave() {
1976
- if (this.tooltipTimeout) {
1977
- clearTimeout(this.tooltipTimeout);
1978
- this.tooltipTimeout = null;
1979
- }
1980
- this.showTooltip = false;
1981
- this.hoveredCell = null;
1982
- this.tooltipContent = '';
1983
- }
1984
- onCellMouseMove(event) {
1985
- if (this.showTooltip) {
1986
- this.tooltipPosition = {
1987
- x: event.clientX + 10,
1988
- y: event.clientY - 10,
1989
- };
1990
- }
1991
- }
1992
- // Métodos para inversão vertical dos tabs
1993
- getTabGroups(tabs) {
1994
- if (!tabs || tabs.length === 0)
1995
- return [];
1996
- const totalGroups = Math.ceil(tabs.length / 6);
1997
- const groups = [];
1998
- // Criar array de índices invertidos (último grupo primeiro)
1999
- for (let i = totalGroups - 1; i >= 0; i--) {
2000
- groups.push(i);
2001
- }
2002
- return groups;
2003
- }
2004
- getTabGroup(tabs, groupIndex) {
2005
- if (!tabs || tabs.length === 0)
2006
- return [];
2007
- const startIndex = groupIndex * 6;
2008
- const endIndex = Math.min(startIndex + 6, tabs.length);
2009
- return tabs.slice(startIndex, endIndex);
2010
- }
2011
- getRealTabIndex(groupIndex, tabIndexInGroup) {
2012
- var _a;
2013
- if (!((_a = this.data.tabs) === null || _a === void 0 ? void 0 : _a.tabsData))
2014
- return 0;
2015
- const totalGroups = Math.ceil(this.data.tabs.tabsData.length / 6);
2016
- const realGroupIndex = totalGroups - 1 - groupIndex;
2017
- return realGroupIndex * 6 + tabIndexInGroup;
2018
- }
2019
- onTableSelected(i, j) {
2020
- var _a;
2021
- if (!((_a = this.data.tabs) === null || _a === void 0 ? void 0 : _a.tabsData) || !this.data.tabs.method)
2022
- return;
2023
- this.selectedTab = this.getRealTabIndex(i, j);
2024
- const tab = this.data.tabs.tabsData[this.selectedTab];
2025
- if (tab) {
2026
- this.data.tabs.method(tab, this.selectedTab);
2027
- }
2028
- }
2029
- isTabSelected(originalIndex) {
2030
- return this.selectedTab === originalIndex;
2031
- }
2032
- shouldShowActionButton() {
2033
- var _a, _b;
2034
- if (!((_a = this.data) === null || _a === void 0 ? void 0 : _a.actionButton)) {
2035
- return false;
2036
- }
2037
- if (!this.data.actionButton.condition) {
2038
- return true;
2039
- }
2040
- try {
2041
- return (_b = this.data.actionButton.condition(null)) !== null && _b !== void 0 ? _b : true;
2042
- }
2043
- catch (_c) {
2044
- return true;
2045
- }
2046
- }
2047
- }
2048
- TableComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: TableComponent, deps: [{ token: i1$1.Router }, { token: TableService }, { token: i1.AngularFirestore }], target: i0.ɵɵFactoryTarget.Component });
2049
- TableComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: TableComponent, selector: "lib-table", inputs: { data: "data", downloadTable: "downloadTable" }, viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0, template: "<div *ngIf=\"data\" class=\"card-body\">\r\n <div class=\"flex flex-col justify-between gap-6\">\r\n <!-- UNIFIED CONTROL PANEL: FILTERS, SORT & ACTIONS -->\r\n <div\r\n class=\"rounded-xl border border-gray-200 bg-white p-4 shadow-lg\"\r\n *ngIf=\"\r\n data.pagination === true &&\r\n (dropdownItems.length > 0 ||\r\n sortableDropdownItems.length > 0 ||\r\n data.actionButton)\r\n \"\r\n >\r\n <!-- PANEL HEADER: Title, Custom Action, and Global Actions -->\r\n <div\r\n class=\"mb-4 flex flex-col items-start justify-between gap-4 border-b-2 border-gray-200 pb-4 md:flex-row md:items-center\"\r\n >\r\n <!-- Left Side: Title & Main Action Button -->\r\n <div class=\"flex flex-wrap items-center gap-4\">\r\n <div class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-filter text-xl text-blue-500\"></i>\r\n <span class=\"text-lg font-semibold text-gray-700\"\r\n >Filtros e A\u00E7\u00F5es</span\r\n >\r\n </div>\r\n <button\r\n *ngIf=\"data.actionButton && data.actionButton.condition\"\r\n [ngClass]=\"\r\n (data.actionButton.colorClass || 'bg-blue-500') +\r\n ' flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white hover:opacity-70'\r\n \"\r\n [routerLink]=\"data.actionButton.routerLink\"\r\n (click)=\"\r\n data.actionButton.method ? data.actionButton.method($event) : null\r\n \"\r\n >\r\n <i\r\n *ngIf=\"data.actionButton.icon\"\r\n [class]=\"data.actionButton.icon\"\r\n ></i>\r\n {{ data.actionButton.label }}\r\n </button>\r\n </div>\r\n\r\n <!-- Right Side: Search, Reset, Export -->\r\n <div\r\n class=\"flex flex-wrap gap-3\"\r\n *ngIf=\"\r\n this.hasFilterableColumn === true || this.hasSortableColumn === true\r\n \"\r\n >\r\n <button\r\n (click)=\"search()\"\r\n type=\"button\"\r\n class=\"flex items-center gap-2 rounded-lg bg-green-600 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-green-700\"\r\n matTooltip=\"Aplicar filtros\"\r\n >\r\n <i class=\"fa fa-search\"></i>\r\n Pesquisar\r\n </button>\r\n\r\n <button\r\n (click)=\"resetFilter()\"\r\n class=\"flex items-center gap-2 rounded-lg bg-red-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-red-600\"\r\n matTooltip=\"Limpar filtros\"\r\n >\r\n <i class=\"fas fa-redo-alt\"></i>\r\n Resetar\r\n </button>\r\n\r\n <button\r\n *ngIf=\"data.download !== false && downloadTable\"\r\n class=\"flex items-center gap-2 rounded-lg bg-orange-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-orange-600\"\r\n matTooltipPosition=\"above\"\r\n matTooltip=\"Exportar Tabela\"\r\n [disabled]=\"\r\n this.dataSource && this.dataSource.filteredData.length <= 0\r\n \"\r\n (click)=\"\r\n $any(arrange) && downloadTable !== undefined\r\n ? downloadTable($any(arrange), data.conditions || [])\r\n : null\r\n \"\r\n >\r\n <i class=\"fa fa-download\"></i>\r\n Exportar\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- FILTERS CONTENT (WITH REFINEMENTS) -->\r\n <div class=\"mb-4 space-y-3\" *ngIf=\"filtersForm.controls.length > 0\">\r\n <div\r\n [formGroup]=\"$any(filterGroup)\"\r\n *ngFor=\"let filterGroup of filtersForm.controls; let i = index\"\r\n class=\"flex flex-wrap items-center gap-3 rounded-lg border border-gray-200 p-2\"\r\n >\r\n <!-- FILTER TYPE SELECTOR -->\r\n <div class=\"min-w-[200px] flex-1\" *ngIf=\"dropdownItems.length > 0\">\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label>Tipo de filtro</mat-label>\r\n <mat-select\r\n placeholder=\"Selecione o tipo...\"\r\n formControlName=\"selectFilter\"\r\n (selectionChange)=\"onSelectFilterChange()\"\r\n >\r\n <mat-option *ngFor=\"let item of dropdownItems\" [value]=\"item\">\r\n <div class=\"flex items-center gap-2\">\r\n <i\r\n [class]=\"item.icon || 'fa fa-filter'\"\r\n class=\"text-sm text-blue-500\"\r\n ></i>\r\n <span>{{ item.title }}</span>\r\n </div>\r\n </mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- TEXT FILTER -->\r\n <div\r\n class=\"min-w-[200px] flex-1\"\r\n *ngIf=\"\r\n $any(filterGroup).get('selectFilter')?.value?.arrange === 'filter'\r\n \"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-search text-gray-400\"></i>\r\n <span>{{\r\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\r\n \"Filtrar\"\r\n }}</span>\r\n </mat-label>\r\n <input\r\n (keyup.enter)=\"search()\"\r\n formControlName=\"typeFilter\"\r\n matInput\r\n placeholder=\"Digite para filtrar...\"\r\n #input\r\n />\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- DROPDOWN FILTER -->\r\n <div\r\n class=\"min-w-[200px] flex-1\"\r\n *ngIf=\"\r\n $any(filterGroup).get('selectFilter')?.value &&\r\n $any(filterGroup)\r\n .get('selectFilter')\r\n ?.value.hasOwnProperty('items')\r\n \"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label>{{\r\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\r\n \"Selecione\"\r\n }}</mat-label>\r\n <mat-select\r\n placeholder=\"Selecione...\"\r\n formControlName=\"selectItem\"\r\n multiple\r\n >\r\n <mat-option\r\n *ngFor=\"\r\n let item of $any(filterGroup).get('selectFilter')?.value\r\n .items\r\n \"\r\n [value]=\"item\"\r\n >\r\n {{ item.label }}\r\n </mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- DATE FILTER -->\r\n <div\r\n class=\"min-w-[340px] flex-auto\"\r\n *ngIf=\"\r\n $any(filterGroup).get('selectFilter')?.value?.arrange ===\r\n 'filterByDate'\r\n \"\r\n >\r\n <div\r\n class=\"flex flex-col items-stretch gap-3 sm:flex-row sm:items-center\"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-calendar text-gray-400\"></i>\r\n <span>Data Inicial</span>\r\n </mat-label>\r\n <input\r\n matInput\r\n (keyup.enter)=\"search()\"\r\n formControlName=\"initialDate\"\r\n [dropSpecialCharacters]=\"false\"\r\n mask=\"d0/M0/0000\"\r\n placeholder=\"DD/MM/AAAA\"\r\n maxlength=\"10\"\r\n />\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-calendar text-gray-400\"></i>\r\n <span>Data Final</span>\r\n </mat-label>\r\n <input\r\n (keyup.enter)=\"search()\"\r\n matInput\r\n formControlName=\"finalDate\"\r\n [dropSpecialCharacters]=\"false\"\r\n mask=\"d0/M0/0000\"\r\n placeholder=\"DD/MM/AAAA\"\r\n maxlength=\"10\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n\r\n <!-- REMOVE FILTER BUTTON -->\r\n <div *ngIf=\"filtersForm.length > 1\" class=\"ml-auto flex-shrink-0\">\r\n <button\r\n (click)=\"removeFilter(i)\"\r\n class=\"flex h-10 w-10 items-center justify-center rounded-full transition-colors duration-300 hover:bg-red-100\"\r\n matTooltip=\"Remover filtro\"\r\n >\r\n <i class=\"fa fa-trash text-red-500 hover:text-red-600\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- PANEL FOOTER: Add Filter & Sort -->\r\n <div\r\n class=\"-mb-2 flex flex-col items-center justify-between gap-4 border-t border-gray-200 pt-4 sm:flex-row\"\r\n >\r\n <!-- Add Filter Button -->\r\n <div *ngIf=\"dropdownItems.length > 0\">\r\n <button\r\n (click)=\"addFilter()\"\r\n class=\"transform rounded-full border-2 border-blue-300 bg-blue-50 px-6 py-2 text-sm font-medium text-blue-600 transition-all duration-300 hover:-translate-y-0.5 hover:border-blue-400 hover:bg-blue-100 hover:shadow-md\"\r\n matTooltip=\"Adicionar novo filtro\"\r\n >\r\n <i class=\"fa fa-plus mr-2\"></i>\r\n Adicionar Filtro\r\n </button>\r\n </div>\r\n\r\n <!-- Sort Dropdown -->\r\n <div\r\n class=\"w-full sm:w-auto sm:min-w-[250px]\"\r\n *ngIf=\"sortableDropdownItems.length > 0\"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label>Ordenar por</mat-label>\r\n <mat-select placeholder=\"Selecione...\" [formControl]=\"selectSort\">\r\n <mat-option\r\n *ngFor=\"let item of sortableDropdownItems\"\r\n [value]=\"item\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-sort-alpha-down text-cyan-600\"></i>\r\n <span>{{ item.title }}</span>\r\n </div>\r\n </mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- SIMPLE SEARCH (for non-paginated tables) -->\r\n <div\r\n class=\"rounded-xl border border-gray-200 bg-white p-4 shadow-lg\"\r\n *ngIf=\"data.pagination === false && hasFilterableColumn === true\"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-search text-blue-500\"></i>\r\n Buscar\r\n </mat-label>\r\n <input\r\n matInput\r\n (keyup.enter)=\"search()\"\r\n (keyup)=\"applyFilter(filterInput.value)\"\r\n placeholder=\"Digite para filtrar...\"\r\n #filterInput\r\n />\r\n <mat-icon matSuffix class=\"text-gray-500\">search</mat-icon>\r\n </mat-form-field>\r\n <button\r\n *ngIf=\"data.actionButton\"\r\n [ngClass]=\"\r\n (data.actionButton.colorClass || 'bg-blue-500') +\r\n ' float-right flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white hover:opacity-70'\r\n \"\r\n [routerLink]=\"data.actionButton.routerLink\"\r\n (click)=\"\r\n data.actionButton.method ? data.actionButton.method($event) : null\r\n \"\r\n >\r\n <i *ngIf=\"data.actionButton.icon\" [class]=\"data.actionButton.icon\"></i>\r\n {{ data.actionButton.label }}\r\n </button>\r\n </div>\r\n\r\n <!-- FILTERS PANEL (for non-paginated tables) -->\r\n <div\r\n class=\"rounded-xl border border-gray-200 bg-white p-4 shadow-lg\"\r\n *ngIf=\"data.pagination === false && dropdownItems.length > 0\"\r\n >\r\n <!-- FILTERS CONTENT -->\r\n <div class=\"mb-4 space-y-3\" *ngIf=\"filtersForm.controls.length > 0\">\r\n <div\r\n [formGroup]=\"$any(filterGroup)\"\r\n *ngFor=\"let filterGroup of filtersForm.controls; let i = index\"\r\n class=\"flex flex-wrap items-center gap-3 rounded-lg border border-gray-200 p-2\"\r\n >\r\n <!-- FILTER TYPE SELECTOR -->\r\n <div class=\"min-w-[200px] flex-1\" *ngIf=\"dropdownItems.length > 0\">\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label>Tipo de filtro</mat-label>\r\n <mat-select\r\n placeholder=\"Selecione o tipo...\"\r\n formControlName=\"selectFilter\"\r\n (selectionChange)=\"onSelectFilterChange()\"\r\n >\r\n <mat-option *ngFor=\"let item of dropdownItems\" [value]=\"item\">\r\n <div class=\"flex items-center gap-2\">\r\n <i\r\n [class]=\"item.icon || 'fa fa-filter'\"\r\n class=\"text-sm text-blue-500\"\r\n ></i>\r\n <span>{{ item.title }}</span>\r\n </div>\r\n </mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- TEXT FILTER -->\r\n <div\r\n class=\"min-w-[200px] flex-1\"\r\n *ngIf=\"\r\n $any(filterGroup).get('selectFilter')?.value?.arrange === 'filter'\r\n \"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-search text-gray-400\"></i>\r\n <span>{{\r\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\r\n \"Filtrar\"\r\n }}</span>\r\n </mat-label>\r\n <input\r\n (keyup.enter)=\"search()\"\r\n formControlName=\"typeFilter\"\r\n matInput\r\n placeholder=\"Digite para filtrar...\"\r\n #input\r\n />\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- DROPDOWN FILTER -->\r\n <div\r\n class=\"min-w-[200px] flex-1\"\r\n *ngIf=\"\r\n $any(filterGroup).get('selectFilter')?.value &&\r\n $any(filterGroup)\r\n .get('selectFilter')\r\n ?.value.hasOwnProperty('items')\r\n \"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label>{{\r\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\r\n \"Selecione\"\r\n }}</mat-label>\r\n <mat-select\r\n placeholder=\"Selecione...\"\r\n formControlName=\"selectItem\"\r\n multiple\r\n >\r\n <mat-option\r\n *ngFor=\"\r\n let item of $any(filterGroup).get('selectFilter')?.value\r\n .items\r\n \"\r\n [value]=\"item\"\r\n >\r\n {{ item.label }}\r\n </mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- DATE FILTER -->\r\n <div\r\n class=\"min-w-[340px] flex-auto\"\r\n *ngIf=\"\r\n $any(filterGroup).get('selectFilter')?.value?.arrange ===\r\n 'filterByDate'\r\n \"\r\n >\r\n <div\r\n class=\"flex flex-col items-stretch gap-3 sm:flex-row sm:items-center\"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-calendar text-gray-400\"></i>\r\n <span>Data Inicial</span>\r\n </mat-label>\r\n <input\r\n matInput\r\n (keyup.enter)=\"search()\"\r\n (blur)=\"onDateFilterChange()\"\r\n formControlName=\"initialDate\"\r\n [dropSpecialCharacters]=\"false\"\r\n mask=\"d0/M0/0000\"\r\n placeholder=\"DD/MM/AAAA\"\r\n maxlength=\"10\"\r\n />\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-calendar text-gray-400\"></i>\r\n <span>Data Final</span>\r\n </mat-label>\r\n <input\r\n (keyup.enter)=\"search()\"\r\n (blur)=\"onDateFilterChange()\"\r\n matInput\r\n formControlName=\"finalDate\"\r\n [dropSpecialCharacters]=\"false\"\r\n mask=\"d0/M0/0000\"\r\n placeholder=\"DD/MM/AAAA\"\r\n maxlength=\"10\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n\r\n <!-- REMOVE FILTER BUTTON -->\r\n <div *ngIf=\"filtersForm.length > 1\" class=\"ml-auto flex-shrink-0\">\r\n <button\r\n (click)=\"removeFilter(i)\"\r\n class=\"flex h-10 w-10 items-center justify-center rounded-full transition-colors duration-300 hover:bg-red-100\"\r\n matTooltip=\"Remover filtro\"\r\n >\r\n <i class=\"fa fa-trash text-red-500 hover:text-red-600\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- PANEL FOOTER: Add Filter & Actions -->\r\n <div\r\n class=\"-mb-2 flex flex-col items-center justify-between gap-4 border-t border-gray-200 pt-4 sm:flex-row\"\r\n >\r\n <!-- Add Filter Button -->\r\n <div *ngIf=\"dropdownItems.length > 0\">\r\n <button\r\n (click)=\"addFilter()\"\r\n class=\"transform rounded-full border-2 border-blue-300 bg-blue-50 px-6 py-2 text-sm font-medium text-blue-600 transition-all duration-300 hover:-translate-y-0.5 hover:border-blue-400 hover:bg-blue-100 hover:shadow-md\"\r\n matTooltip=\"Adicionar novo filtro\"\r\n >\r\n <i class=\"fa fa-plus mr-2\"></i>\r\n Adicionar Filtro\r\n </button>\r\n </div>\r\n\r\n <!-- Action Buttons -->\r\n <div class=\"flex flex-wrap gap-3\">\r\n <button\r\n (click)=\"search()\"\r\n type=\"button\"\r\n class=\"flex items-center gap-2 rounded-lg bg-green-600 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-green-700\"\r\n matTooltip=\"Aplicar filtros\"\r\n >\r\n <i class=\"fa fa-search\"></i>\r\n Aplicar\r\n </button>\r\n\r\n <button\r\n (click)=\"resetFilter()\"\r\n class=\"flex items-center gap-2 rounded-lg bg-red-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-red-600\"\r\n matTooltip=\"Limpar filtros\"\r\n >\r\n <i class=\"fas fa-redo-alt\"></i>\r\n Resetar\r\n </button>\r\n\r\n <button\r\n *ngIf=\"data.download !== false && downloadTable\"\r\n class=\"flex items-center gap-2 rounded-lg bg-orange-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-orange-600\"\r\n matTooltipPosition=\"above\"\r\n matTooltip=\"Exportar Tabela\"\r\n [disabled]=\"\r\n this.dataSource && this.dataSource.filteredData.length <= 0\r\n \"\r\n (click)=\"\r\n $any(arrange) && downloadTable !== undefined\r\n ? downloadTable($any(arrange), data.conditions || [])\r\n : null\r\n \"\r\n >\r\n <i class=\"fa fa-download\"></i>\r\n Exportar\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"flex flex-col\">\r\n <div\r\n class=\"mx-auto flex flex-col\"\r\n *ngIf=\"data.tabs && data.tabs.tabsData && data.tabs.tabsData.length > 0\"\r\n >\r\n <!-- Calcular quantos grupos de 6 tabs existem -->\r\n <ng-container\r\n *ngFor=\"\r\n let groupIndex of getTabGroups(data.tabs.tabsData);\r\n let i = index\r\n \"\r\n >\r\n <div class=\"mx-auto flex flex-row\">\r\n <ng-container\r\n *ngFor=\"\r\n let tab of getTabGroup(data.tabs.tabsData, groupIndex);\r\n let j = index\r\n \"\r\n >\r\n <button\r\n class=\"border-2 border-gray-300 bg-gray-200 px-4 py-2 font-medium transition hover:brightness-95\"\r\n [ngClass]=\"\r\n isTabSelected(getRealTabIndex(i, j))\r\n ? 'border-b-0 brightness-110'\r\n : ''\r\n \"\r\n (click)=\"onTableSelected(i, j)\"\r\n >\r\n {{ tab.label }}\r\n <span\r\n *ngIf=\"tab.counter !== undefined\"\r\n class=\"ml-2 text-xs font-bold\"\r\n [ngClass]=\"tab.counterClass\"\r\n >\r\n {{ tab.counter }}\r\n </span>\r\n </button>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n </div>\r\n <div class=\"mat-elevation-z8 w-full overflow-x-auto rounded-xl\">\r\n <table\r\n mat-table\r\n [dataSource]=\"dataSource\"\r\n matSort\r\n #sort=\"matSort\"\r\n matSortActive=\"createdAt\"\r\n matSortDirection=\"desc\"\r\n >\r\n <ng-container\r\n *ngFor=\"let col of data.displayedColumns\"\r\n matColumnDef=\"{{ col.property }}\"\r\n >\r\n <ng-container *matHeaderCellDef>\r\n <!-- IF THE COLUMN IS NOT SORTABLE, THEN DON'T SHOW THE SORT BUTTONS -->\r\n <th\r\n *ngIf=\"!col.isSortable || data.pagination === true\"\r\n mat-header-cell\r\n [ngClass]=\"\r\n (data.color?.bg ? ' ' + $any(data.color).bg : '') +\r\n (data.color?.text ? ' ' + $any(data.color).text : '')\r\n \"\r\n >\r\n {{ col.title }}\r\n </th>\r\n <!-- IF THE COLUMN IS SORTABLE, THEN SHOW THE SORT BUTTONS -->\r\n <th\r\n *ngIf=\"col.isSortable && data.pagination === false\"\r\n mat-header-cell\r\n mat-sort-header\r\n [ngClass]=\"\r\n (data.color?.bg ? ' ' + $any(data.color).bg : '') +\r\n (data.color?.text ? ' ' + $any(data.color).text : '')\r\n \"\r\n >\r\n {{ col.title }}\r\n </th>\r\n <td\r\n mat-cell\r\n *matCellDef=\"let row\"\r\n (click)=\"col.method ? col.method(row) : null\"\r\n (mouseenter)=\"onCellMouseEnter($event, row, col)\"\r\n (mouseleave)=\"onCellMouseLeave()\"\r\n (mousemove)=\"onCellMouseMove($event)\"\r\n >\r\n <!-- CHECK IF THE COLUMN MUST BE DISPLAYED -->\r\n <span *ngIf=\"!col.image && !col.iconClass && !col.method\">\r\n <ng-container>\r\n <span\r\n *ngIf=\"\r\n col.charLimit &&\r\n row[col.property] &&\r\n row[col.property].length > col.charLimit;\r\n else withinLimit\r\n \"\r\n >\r\n <a\r\n *ngIf=\"col.hasLink === true\"\r\n [href]=\"row[col.property]\"\r\n target=\"_blank\"\r\n >\r\n {{ getDisplayValue(col, row) }}\r\n </a>\r\n <a\r\n *ngIf=\"col.hasLink && isString(col.hasLink)\"\r\n [href]=\"col.hasLink\"\r\n target=\"_blank\"\r\n >\r\n {{ getDisplayValue(col, row) }}\r\n </a>\r\n <span\r\n *ngIf=\"col.hasLink !== true && !isString(col.hasLink)\"\r\n >\r\n {{ getDisplayValue(col, row) }}\r\n </span>\r\n </span>\r\n </ng-container>\r\n <ng-template #withinLimit>\r\n <a\r\n *ngIf=\"col.hasLink === true\"\r\n [href]=\"row[col.property]\"\r\n target=\"_blank\"\r\n >\r\n {{ getDisplayValue(col, row, true) }}\r\n </a>\r\n <a\r\n *ngIf=\"col.hasLink && isString(col.hasLink)\"\r\n [href]=\"col.hasLink\"\r\n target=\"_blank\"\r\n >\r\n {{ getDisplayValue(col, row, true) }}\r\n </a>\r\n <span\r\n *ngIf=\"col.hasLink !== true && !isString(col.hasLink)\"\r\n >\r\n {{ getDisplayValue(col, row, true) }}\r\n </span>\r\n </ng-template>\r\n </span>\r\n <!------------------- IMAGE ------------------>\r\n <img\r\n *ngIf=\"\r\n col.image && col.image.path && !col.iconClass && !col.method\r\n \"\r\n [src]=\"col.image.path + '/' + row[col.property]\"\r\n [ngClass]=\"col.image.class\"\r\n alt=\"Imagem\"\r\n />\r\n <img\r\n *ngIf=\"\r\n col.image && col.image.url && !col.iconClass && !col.method\r\n \"\r\n [src]=\"row[col.property]\"\r\n [ngClass]=\"col.image.class\"\r\n alt=\"Imagem\"\r\n />\r\n <ng-container *ngIf=\"col.iconClass\">\r\n <button\r\n *ngFor=\"let iconClass of col.iconClass\"\r\n (click)=\"\r\n iconClass.buttonMethod\r\n ? iconClass.buttonMethod(row, $event)\r\n : $event.stopPropagation()\r\n \"\r\n >\r\n <span\r\n [ngClass]=\"iconClass.class\"\r\n *ngIf=\"\r\n iconClass.condition === undefined ||\r\n (iconClass.condition !== undefined &&\r\n $any(iconClass.condition)(row))\r\n \"\r\n >{{ iconClass.text }}</span\r\n >\r\n </button>\r\n </ng-container>\r\n </td>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"columnProperties\"></tr>\r\n <tr\r\n [ngClass]=\"{\r\n 'example-element-row': data.isNotClickable === true,\r\n 'example-element-row cursor-pointer': !data.isNotClickable\r\n }\"\r\n mat-row\r\n *matRowDef=\"let row; columns: columnProperties\"\r\n (click)=\"goToDetails(row)\"\r\n ></tr>\r\n\r\n <!-- ROW SHOWN WHEN THERE IS NO MATCHING DATA. -->\r\n <tr class=\"mat-row\" *matNoDataRow>\r\n <td *ngIf=\"!isLoading\" class=\"mat-cell p-4\" colspan=\"4\">\r\n Nenhum resultado encontrado para a busca\r\n </td>\r\n </tr>\r\n </table>\r\n\r\n <div class=\"flex justify-center\" *ngIf=\"isLoading\">\r\n <mat-spinner></mat-spinner>\r\n </div>\r\n\r\n <div class=\"paginator-container\">\r\n <mat-paginator\r\n #paginator\r\n [pageSizeOptions]=\"[25, 50, 100]\"\r\n [pageSize]=\"pageSize\"\r\n [length]=\"totalItems\"\r\n showFirstLastButtons\r\n aria-label=\"Select page of periodic elements\"\r\n (page)=\"onPageChange($event)\"\r\n [ngClass]=\"{\r\n 'hide-length':\r\n ['filter', 'filterByDate', 'equals'].includes(\r\n this.currentArrange\r\n ) || this.data.filterFn,\r\n 'hide-next-button': !hasNextPage && data.pagination === true,\r\n 'hide-last-button':\r\n (!hasNextPage && data.pagination === true) || this.data.filterFn\r\n }\"\r\n >\r\n </mat-paginator>\r\n <div\r\n *ngIf=\"\r\n !isLoading &&\r\n dataSource?.data &&\r\n dataSource.data.length > 0 &&\r\n data?.filterFn\r\n \"\r\n class=\"page-number-display\"\r\n >\r\n {{ currentPageNumber }}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- TOOLTIP PERSONALIZADO -->\r\n <div\r\n *ngIf=\"showTooltip\"\r\n class=\"fixed z-50 max-w-md break-words rounded-lg bg-gray-800 px-3 py-2 text-sm text-white shadow-lg\"\r\n [style.left.px]=\"tooltipPosition.x\"\r\n [style.top.px]=\"tooltipPosition.y\"\r\n [style.pointer-events]=\"'none'\"\r\n >\r\n {{ tooltipContent }}\r\n </div>\r\n</div>\r\n", styles: ["::ng-deep .hide-length .mat-mdc-paginator-range-label{display:none}::ng-deep .hide-next-button .mat-mdc-tooltip-trigger.mat-mdc-paginator-navigation-next.mdc-icon-button.mat-mdc-icon-button.mat-unthemed.mat-mdc-button-base{visibility:hidden}::ng-deep .hide-next-button .mat-mdc-tooltip-trigger.mat-mdc-paginator-navigation-last.mdc-icon-button.mat-mdc-icon-button.mat-unthemed.mat-mdc-button-base.ng-star-inserted{visibility:hidden}::ng-deep .mat-mdc-text-field-wrapper.mdc-text-field.ng-tns-c162-1.mdc-text-field--filled{width:25dvw}::ng-deep .custom-filter .mat-mdc-text-field-wrapper{width:20dvw;max-width:300px}\n"], dependencies: [{ kind: "directive", type: i4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i5.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i5.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i5.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i5.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i5.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: i6.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i6.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i6.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i6.MatColumnDef, selector: "[matColumnDef]", inputs: ["sticky", "matColumnDef"] }, { kind: "directive", type: i6.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i6.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i6.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i6.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i6.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i6.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i6.MatNoDataRow, selector: "ng-template[matNoDataRow]" }, { kind: "component", type: i7.MatPaginator, selector: "mat-paginator", inputs: ["disabled"], exportAs: ["matPaginator"] }, { kind: "directive", type: i8.MatSort, selector: "[matSort]", inputs: ["matSortDisabled", "matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i8.MatSortHeader, selector: "[mat-sort-header]", inputs: ["disabled", "mat-sort-header", "arrowPosition", "start", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "component", type: i9.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i9.MatLabel, selector: "mat-label" }, { kind: "directive", type: i9.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i10.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i11.MatSelect, selector: "mat-select", inputs: ["disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator"], exportAs: ["matSelect"] }, { kind: "component", type: i12.MatOption, selector: "mat-option", exportAs: ["matOption"] }, { kind: "directive", type: i13.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }, { kind: "component", type: i14.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: i15.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i16.NgxMaskDirective, selector: "input[mask], textarea[mask]", inputs: ["mask", "specialCharacters", "patterns", "prefix", "suffix", "thousandSeparator", "decimalMarker", "dropSpecialCharacters", "hiddenInput", "showMaskTyped", "placeHolderCharacter", "shownMaskExpression", "showTemplate", "clearIfNotMatch", "validation", "separatorLimit", "allowNegativeNumbers", "leadZeroDateTime", "leadZero", "triggerOnMaskChange", "apm", "inputTransformFn", "outputTransformFn", "keepCharacterPositions"], outputs: ["maskFilled"], exportAs: ["mask", "ngxMask"] }] });
2050
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: TableComponent, decorators: [{
2051
- type: Component,
2052
- args: [{ selector: 'lib-table', template: "<div *ngIf=\"data\" class=\"card-body\">\r\n <div class=\"flex flex-col justify-between gap-6\">\r\n <!-- UNIFIED CONTROL PANEL: FILTERS, SORT & ACTIONS -->\r\n <div\r\n class=\"rounded-xl border border-gray-200 bg-white p-4 shadow-lg\"\r\n *ngIf=\"\r\n data.pagination === true &&\r\n (dropdownItems.length > 0 ||\r\n sortableDropdownItems.length > 0 ||\r\n data.actionButton)\r\n \"\r\n >\r\n <!-- PANEL HEADER: Title, Custom Action, and Global Actions -->\r\n <div\r\n class=\"mb-4 flex flex-col items-start justify-between gap-4 border-b-2 border-gray-200 pb-4 md:flex-row md:items-center\"\r\n >\r\n <!-- Left Side: Title & Main Action Button -->\r\n <div class=\"flex flex-wrap items-center gap-4\">\r\n <div class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-filter text-xl text-blue-500\"></i>\r\n <span class=\"text-lg font-semibold text-gray-700\"\r\n >Filtros e A\u00E7\u00F5es</span\r\n >\r\n </div>\r\n <button\r\n *ngIf=\"data.actionButton && data.actionButton.condition\"\r\n [ngClass]=\"\r\n (data.actionButton.colorClass || 'bg-blue-500') +\r\n ' flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white hover:opacity-70'\r\n \"\r\n [routerLink]=\"data.actionButton.routerLink\"\r\n (click)=\"\r\n data.actionButton.method ? data.actionButton.method($event) : null\r\n \"\r\n >\r\n <i\r\n *ngIf=\"data.actionButton.icon\"\r\n [class]=\"data.actionButton.icon\"\r\n ></i>\r\n {{ data.actionButton.label }}\r\n </button>\r\n </div>\r\n\r\n <!-- Right Side: Search, Reset, Export -->\r\n <div\r\n class=\"flex flex-wrap gap-3\"\r\n *ngIf=\"\r\n this.hasFilterableColumn === true || this.hasSortableColumn === true\r\n \"\r\n >\r\n <button\r\n (click)=\"search()\"\r\n type=\"button\"\r\n class=\"flex items-center gap-2 rounded-lg bg-green-600 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-green-700\"\r\n matTooltip=\"Aplicar filtros\"\r\n >\r\n <i class=\"fa fa-search\"></i>\r\n Pesquisar\r\n </button>\r\n\r\n <button\r\n (click)=\"resetFilter()\"\r\n class=\"flex items-center gap-2 rounded-lg bg-red-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-red-600\"\r\n matTooltip=\"Limpar filtros\"\r\n >\r\n <i class=\"fas fa-redo-alt\"></i>\r\n Resetar\r\n </button>\r\n\r\n <button\r\n *ngIf=\"data.download !== false && downloadTable\"\r\n class=\"flex items-center gap-2 rounded-lg bg-orange-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-orange-600\"\r\n matTooltipPosition=\"above\"\r\n matTooltip=\"Exportar Tabela\"\r\n [disabled]=\"\r\n this.dataSource && this.dataSource.filteredData.length <= 0\r\n \"\r\n (click)=\"\r\n $any(arrange) && downloadTable !== undefined\r\n ? downloadTable($any(arrange), data.conditions || [])\r\n : null\r\n \"\r\n >\r\n <i class=\"fa fa-download\"></i>\r\n Exportar\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- FILTERS CONTENT (WITH REFINEMENTS) -->\r\n <div class=\"mb-4 space-y-3\" *ngIf=\"filtersForm.controls.length > 0\">\r\n <div\r\n [formGroup]=\"$any(filterGroup)\"\r\n *ngFor=\"let filterGroup of filtersForm.controls; let i = index\"\r\n class=\"flex flex-wrap items-center gap-3 rounded-lg border border-gray-200 p-2\"\r\n >\r\n <!-- FILTER TYPE SELECTOR -->\r\n <div class=\"min-w-[200px] flex-1\" *ngIf=\"dropdownItems.length > 0\">\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label>Tipo de filtro</mat-label>\r\n <mat-select\r\n placeholder=\"Selecione o tipo...\"\r\n formControlName=\"selectFilter\"\r\n (selectionChange)=\"onSelectFilterChange()\"\r\n >\r\n <mat-option *ngFor=\"let item of dropdownItems\" [value]=\"item\">\r\n <div class=\"flex items-center gap-2\">\r\n <i\r\n [class]=\"item.icon || 'fa fa-filter'\"\r\n class=\"text-sm text-blue-500\"\r\n ></i>\r\n <span>{{ item.title }}</span>\r\n </div>\r\n </mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- TEXT FILTER -->\r\n <div\r\n class=\"min-w-[200px] flex-1\"\r\n *ngIf=\"\r\n $any(filterGroup).get('selectFilter')?.value?.arrange === 'filter'\r\n \"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-search text-gray-400\"></i>\r\n <span>{{\r\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\r\n \"Filtrar\"\r\n }}</span>\r\n </mat-label>\r\n <input\r\n (keyup.enter)=\"search()\"\r\n formControlName=\"typeFilter\"\r\n matInput\r\n placeholder=\"Digite para filtrar...\"\r\n #input\r\n />\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- DROPDOWN FILTER -->\r\n <div\r\n class=\"min-w-[200px] flex-1\"\r\n *ngIf=\"\r\n $any(filterGroup).get('selectFilter')?.value &&\r\n $any(filterGroup)\r\n .get('selectFilter')\r\n ?.value.hasOwnProperty('items')\r\n \"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label>{{\r\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\r\n \"Selecione\"\r\n }}</mat-label>\r\n <mat-select\r\n placeholder=\"Selecione...\"\r\n formControlName=\"selectItem\"\r\n multiple\r\n >\r\n <mat-option\r\n *ngFor=\"\r\n let item of $any(filterGroup).get('selectFilter')?.value\r\n .items\r\n \"\r\n [value]=\"item\"\r\n >\r\n {{ item.label }}\r\n </mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- DATE FILTER -->\r\n <div\r\n class=\"min-w-[340px] flex-auto\"\r\n *ngIf=\"\r\n $any(filterGroup).get('selectFilter')?.value?.arrange ===\r\n 'filterByDate'\r\n \"\r\n >\r\n <div\r\n class=\"flex flex-col items-stretch gap-3 sm:flex-row sm:items-center\"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-calendar text-gray-400\"></i>\r\n <span>Data Inicial</span>\r\n </mat-label>\r\n <input\r\n matInput\r\n (keyup.enter)=\"search()\"\r\n formControlName=\"initialDate\"\r\n [dropSpecialCharacters]=\"false\"\r\n mask=\"d0/M0/0000\"\r\n placeholder=\"DD/MM/AAAA\"\r\n maxlength=\"10\"\r\n />\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-calendar text-gray-400\"></i>\r\n <span>Data Final</span>\r\n </mat-label>\r\n <input\r\n (keyup.enter)=\"search()\"\r\n matInput\r\n formControlName=\"finalDate\"\r\n [dropSpecialCharacters]=\"false\"\r\n mask=\"d0/M0/0000\"\r\n placeholder=\"DD/MM/AAAA\"\r\n maxlength=\"10\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n\r\n <!-- REMOVE FILTER BUTTON -->\r\n <div *ngIf=\"filtersForm.length > 1\" class=\"ml-auto flex-shrink-0\">\r\n <button\r\n (click)=\"removeFilter(i)\"\r\n class=\"flex h-10 w-10 items-center justify-center rounded-full transition-colors duration-300 hover:bg-red-100\"\r\n matTooltip=\"Remover filtro\"\r\n >\r\n <i class=\"fa fa-trash text-red-500 hover:text-red-600\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- PANEL FOOTER: Add Filter & Sort -->\r\n <div\r\n class=\"-mb-2 flex flex-col items-center justify-between gap-4 border-t border-gray-200 pt-4 sm:flex-row\"\r\n >\r\n <!-- Add Filter Button -->\r\n <div *ngIf=\"dropdownItems.length > 0\">\r\n <button\r\n (click)=\"addFilter()\"\r\n class=\"transform rounded-full border-2 border-blue-300 bg-blue-50 px-6 py-2 text-sm font-medium text-blue-600 transition-all duration-300 hover:-translate-y-0.5 hover:border-blue-400 hover:bg-blue-100 hover:shadow-md\"\r\n matTooltip=\"Adicionar novo filtro\"\r\n >\r\n <i class=\"fa fa-plus mr-2\"></i>\r\n Adicionar Filtro\r\n </button>\r\n </div>\r\n\r\n <!-- Sort Dropdown -->\r\n <div\r\n class=\"w-full sm:w-auto sm:min-w-[250px]\"\r\n *ngIf=\"sortableDropdownItems.length > 0\"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label>Ordenar por</mat-label>\r\n <mat-select placeholder=\"Selecione...\" [formControl]=\"selectSort\">\r\n <mat-option\r\n *ngFor=\"let item of sortableDropdownItems\"\r\n [value]=\"item\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-sort-alpha-down text-cyan-600\"></i>\r\n <span>{{ item.title }}</span>\r\n </div>\r\n </mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- SIMPLE SEARCH (for non-paginated tables) -->\r\n <div\r\n class=\"rounded-xl border border-gray-200 bg-white p-4 shadow-lg\"\r\n *ngIf=\"data.pagination === false && hasFilterableColumn === true\"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-search text-blue-500\"></i>\r\n Buscar\r\n </mat-label>\r\n <input\r\n matInput\r\n (keyup.enter)=\"search()\"\r\n (keyup)=\"applyFilter(filterInput.value)\"\r\n placeholder=\"Digite para filtrar...\"\r\n #filterInput\r\n />\r\n <mat-icon matSuffix class=\"text-gray-500\">search</mat-icon>\r\n </mat-form-field>\r\n <button\r\n *ngIf=\"data.actionButton\"\r\n [ngClass]=\"\r\n (data.actionButton.colorClass || 'bg-blue-500') +\r\n ' float-right flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white hover:opacity-70'\r\n \"\r\n [routerLink]=\"data.actionButton.routerLink\"\r\n (click)=\"\r\n data.actionButton.method ? data.actionButton.method($event) : null\r\n \"\r\n >\r\n <i *ngIf=\"data.actionButton.icon\" [class]=\"data.actionButton.icon\"></i>\r\n {{ data.actionButton.label }}\r\n </button>\r\n </div>\r\n\r\n <!-- FILTERS PANEL (for non-paginated tables) -->\r\n <div\r\n class=\"rounded-xl border border-gray-200 bg-white p-4 shadow-lg\"\r\n *ngIf=\"data.pagination === false && dropdownItems.length > 0\"\r\n >\r\n <!-- FILTERS CONTENT -->\r\n <div class=\"mb-4 space-y-3\" *ngIf=\"filtersForm.controls.length > 0\">\r\n <div\r\n [formGroup]=\"$any(filterGroup)\"\r\n *ngFor=\"let filterGroup of filtersForm.controls; let i = index\"\r\n class=\"flex flex-wrap items-center gap-3 rounded-lg border border-gray-200 p-2\"\r\n >\r\n <!-- FILTER TYPE SELECTOR -->\r\n <div class=\"min-w-[200px] flex-1\" *ngIf=\"dropdownItems.length > 0\">\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label>Tipo de filtro</mat-label>\r\n <mat-select\r\n placeholder=\"Selecione o tipo...\"\r\n formControlName=\"selectFilter\"\r\n (selectionChange)=\"onSelectFilterChange()\"\r\n >\r\n <mat-option *ngFor=\"let item of dropdownItems\" [value]=\"item\">\r\n <div class=\"flex items-center gap-2\">\r\n <i\r\n [class]=\"item.icon || 'fa fa-filter'\"\r\n class=\"text-sm text-blue-500\"\r\n ></i>\r\n <span>{{ item.title }}</span>\r\n </div>\r\n </mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- TEXT FILTER -->\r\n <div\r\n class=\"min-w-[200px] flex-1\"\r\n *ngIf=\"\r\n $any(filterGroup).get('selectFilter')?.value?.arrange === 'filter'\r\n \"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-search text-gray-400\"></i>\r\n <span>{{\r\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\r\n \"Filtrar\"\r\n }}</span>\r\n </mat-label>\r\n <input\r\n (keyup.enter)=\"search()\"\r\n formControlName=\"typeFilter\"\r\n matInput\r\n placeholder=\"Digite para filtrar...\"\r\n #input\r\n />\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- DROPDOWN FILTER -->\r\n <div\r\n class=\"min-w-[200px] flex-1\"\r\n *ngIf=\"\r\n $any(filterGroup).get('selectFilter')?.value &&\r\n $any(filterGroup)\r\n .get('selectFilter')\r\n ?.value.hasOwnProperty('items')\r\n \"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"w-full\">\r\n <mat-label>{{\r\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\r\n \"Selecione\"\r\n }}</mat-label>\r\n <mat-select\r\n placeholder=\"Selecione...\"\r\n formControlName=\"selectItem\"\r\n multiple\r\n >\r\n <mat-option\r\n *ngFor=\"\r\n let item of $any(filterGroup).get('selectFilter')?.value\r\n .items\r\n \"\r\n [value]=\"item\"\r\n >\r\n {{ item.label }}\r\n </mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n </div>\r\n\r\n <!-- DATE FILTER -->\r\n <div\r\n class=\"min-w-[340px] flex-auto\"\r\n *ngIf=\"\r\n $any(filterGroup).get('selectFilter')?.value?.arrange ===\r\n 'filterByDate'\r\n \"\r\n >\r\n <div\r\n class=\"flex flex-col items-stretch gap-3 sm:flex-row sm:items-center\"\r\n >\r\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-calendar text-gray-400\"></i>\r\n <span>Data Inicial</span>\r\n </mat-label>\r\n <input\r\n matInput\r\n (keyup.enter)=\"search()\"\r\n (blur)=\"onDateFilterChange()\"\r\n formControlName=\"initialDate\"\r\n [dropSpecialCharacters]=\"false\"\r\n mask=\"d0/M0/0000\"\r\n placeholder=\"DD/MM/AAAA\"\r\n maxlength=\"10\"\r\n />\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\r\n <mat-label class=\"flex items-center gap-2\">\r\n <i class=\"fa fa-calendar text-gray-400\"></i>\r\n <span>Data Final</span>\r\n </mat-label>\r\n <input\r\n (keyup.enter)=\"search()\"\r\n (blur)=\"onDateFilterChange()\"\r\n matInput\r\n formControlName=\"finalDate\"\r\n [dropSpecialCharacters]=\"false\"\r\n mask=\"d0/M0/0000\"\r\n placeholder=\"DD/MM/AAAA\"\r\n maxlength=\"10\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n\r\n <!-- REMOVE FILTER BUTTON -->\r\n <div *ngIf=\"filtersForm.length > 1\" class=\"ml-auto flex-shrink-0\">\r\n <button\r\n (click)=\"removeFilter(i)\"\r\n class=\"flex h-10 w-10 items-center justify-center rounded-full transition-colors duration-300 hover:bg-red-100\"\r\n matTooltip=\"Remover filtro\"\r\n >\r\n <i class=\"fa fa-trash text-red-500 hover:text-red-600\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- PANEL FOOTER: Add Filter & Actions -->\r\n <div\r\n class=\"-mb-2 flex flex-col items-center justify-between gap-4 border-t border-gray-200 pt-4 sm:flex-row\"\r\n >\r\n <!-- Add Filter Button -->\r\n <div *ngIf=\"dropdownItems.length > 0\">\r\n <button\r\n (click)=\"addFilter()\"\r\n class=\"transform rounded-full border-2 border-blue-300 bg-blue-50 px-6 py-2 text-sm font-medium text-blue-600 transition-all duration-300 hover:-translate-y-0.5 hover:border-blue-400 hover:bg-blue-100 hover:shadow-md\"\r\n matTooltip=\"Adicionar novo filtro\"\r\n >\r\n <i class=\"fa fa-plus mr-2\"></i>\r\n Adicionar Filtro\r\n </button>\r\n </div>\r\n\r\n <!-- Action Buttons -->\r\n <div class=\"flex flex-wrap gap-3\">\r\n <button\r\n (click)=\"search()\"\r\n type=\"button\"\r\n class=\"flex items-center gap-2 rounded-lg bg-green-600 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-green-700\"\r\n matTooltip=\"Aplicar filtros\"\r\n >\r\n <i class=\"fa fa-search\"></i>\r\n Aplicar\r\n </button>\r\n\r\n <button\r\n (click)=\"resetFilter()\"\r\n class=\"flex items-center gap-2 rounded-lg bg-red-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-red-600\"\r\n matTooltip=\"Limpar filtros\"\r\n >\r\n <i class=\"fas fa-redo-alt\"></i>\r\n Resetar\r\n </button>\r\n\r\n <button\r\n *ngIf=\"data.download !== false && downloadTable\"\r\n class=\"flex items-center gap-2 rounded-lg bg-orange-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-orange-600\"\r\n matTooltipPosition=\"above\"\r\n matTooltip=\"Exportar Tabela\"\r\n [disabled]=\"\r\n this.dataSource && this.dataSource.filteredData.length <= 0\r\n \"\r\n (click)=\"\r\n $any(arrange) && downloadTable !== undefined\r\n ? downloadTable($any(arrange), data.conditions || [])\r\n : null\r\n \"\r\n >\r\n <i class=\"fa fa-download\"></i>\r\n Exportar\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"flex flex-col\">\r\n <div\r\n class=\"mx-auto flex flex-col\"\r\n *ngIf=\"data.tabs && data.tabs.tabsData && data.tabs.tabsData.length > 0\"\r\n >\r\n <!-- Calcular quantos grupos de 6 tabs existem -->\r\n <ng-container\r\n *ngFor=\"\r\n let groupIndex of getTabGroups(data.tabs.tabsData);\r\n let i = index\r\n \"\r\n >\r\n <div class=\"mx-auto flex flex-row\">\r\n <ng-container\r\n *ngFor=\"\r\n let tab of getTabGroup(data.tabs.tabsData, groupIndex);\r\n let j = index\r\n \"\r\n >\r\n <button\r\n class=\"border-2 border-gray-300 bg-gray-200 px-4 py-2 font-medium transition hover:brightness-95\"\r\n [ngClass]=\"\r\n isTabSelected(getRealTabIndex(i, j))\r\n ? 'border-b-0 brightness-110'\r\n : ''\r\n \"\r\n (click)=\"onTableSelected(i, j)\"\r\n >\r\n {{ tab.label }}\r\n <span\r\n *ngIf=\"tab.counter !== undefined\"\r\n class=\"ml-2 text-xs font-bold\"\r\n [ngClass]=\"tab.counterClass\"\r\n >\r\n {{ tab.counter }}\r\n </span>\r\n </button>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n </div>\r\n <div class=\"mat-elevation-z8 w-full overflow-x-auto rounded-xl\">\r\n <table\r\n mat-table\r\n [dataSource]=\"dataSource\"\r\n matSort\r\n #sort=\"matSort\"\r\n matSortActive=\"createdAt\"\r\n matSortDirection=\"desc\"\r\n >\r\n <ng-container\r\n *ngFor=\"let col of data.displayedColumns\"\r\n matColumnDef=\"{{ col.property }}\"\r\n >\r\n <ng-container *matHeaderCellDef>\r\n <!-- IF THE COLUMN IS NOT SORTABLE, THEN DON'T SHOW THE SORT BUTTONS -->\r\n <th\r\n *ngIf=\"!col.isSortable || data.pagination === true\"\r\n mat-header-cell\r\n [ngClass]=\"\r\n (data.color?.bg ? ' ' + $any(data.color).bg : '') +\r\n (data.color?.text ? ' ' + $any(data.color).text : '')\r\n \"\r\n >\r\n {{ col.title }}\r\n </th>\r\n <!-- IF THE COLUMN IS SORTABLE, THEN SHOW THE SORT BUTTONS -->\r\n <th\r\n *ngIf=\"col.isSortable && data.pagination === false\"\r\n mat-header-cell\r\n mat-sort-header\r\n [ngClass]=\"\r\n (data.color?.bg ? ' ' + $any(data.color).bg : '') +\r\n (data.color?.text ? ' ' + $any(data.color).text : '')\r\n \"\r\n >\r\n {{ col.title }}\r\n </th>\r\n <td\r\n mat-cell\r\n *matCellDef=\"let row\"\r\n (click)=\"col.method ? col.method(row) : null\"\r\n (mouseenter)=\"onCellMouseEnter($event, row, col)\"\r\n (mouseleave)=\"onCellMouseLeave()\"\r\n (mousemove)=\"onCellMouseMove($event)\"\r\n >\r\n <!-- CHECK IF THE COLUMN MUST BE DISPLAYED -->\r\n <span *ngIf=\"!col.image && !col.iconClass && !col.method\">\r\n <ng-container>\r\n <span\r\n *ngIf=\"\r\n col.charLimit &&\r\n row[col.property] &&\r\n row[col.property].length > col.charLimit;\r\n else withinLimit\r\n \"\r\n >\r\n <a\r\n *ngIf=\"col.hasLink === true\"\r\n [href]=\"row[col.property]\"\r\n target=\"_blank\"\r\n >\r\n {{ getDisplayValue(col, row) }}\r\n </a>\r\n <a\r\n *ngIf=\"col.hasLink && isString(col.hasLink)\"\r\n [href]=\"col.hasLink\"\r\n target=\"_blank\"\r\n >\r\n {{ getDisplayValue(col, row) }}\r\n </a>\r\n <span\r\n *ngIf=\"col.hasLink !== true && !isString(col.hasLink)\"\r\n >\r\n {{ getDisplayValue(col, row) }}\r\n </span>\r\n </span>\r\n </ng-container>\r\n <ng-template #withinLimit>\r\n <a\r\n *ngIf=\"col.hasLink === true\"\r\n [href]=\"row[col.property]\"\r\n target=\"_blank\"\r\n >\r\n {{ getDisplayValue(col, row, true) }}\r\n </a>\r\n <a\r\n *ngIf=\"col.hasLink && isString(col.hasLink)\"\r\n [href]=\"col.hasLink\"\r\n target=\"_blank\"\r\n >\r\n {{ getDisplayValue(col, row, true) }}\r\n </a>\r\n <span\r\n *ngIf=\"col.hasLink !== true && !isString(col.hasLink)\"\r\n >\r\n {{ getDisplayValue(col, row, true) }}\r\n </span>\r\n </ng-template>\r\n </span>\r\n <!------------------- IMAGE ------------------>\r\n <img\r\n *ngIf=\"\r\n col.image && col.image.path && !col.iconClass && !col.method\r\n \"\r\n [src]=\"col.image.path + '/' + row[col.property]\"\r\n [ngClass]=\"col.image.class\"\r\n alt=\"Imagem\"\r\n />\r\n <img\r\n *ngIf=\"\r\n col.image && col.image.url && !col.iconClass && !col.method\r\n \"\r\n [src]=\"row[col.property]\"\r\n [ngClass]=\"col.image.class\"\r\n alt=\"Imagem\"\r\n />\r\n <ng-container *ngIf=\"col.iconClass\">\r\n <button\r\n *ngFor=\"let iconClass of col.iconClass\"\r\n (click)=\"\r\n iconClass.buttonMethod\r\n ? iconClass.buttonMethod(row, $event)\r\n : $event.stopPropagation()\r\n \"\r\n >\r\n <span\r\n [ngClass]=\"iconClass.class\"\r\n *ngIf=\"\r\n iconClass.condition === undefined ||\r\n (iconClass.condition !== undefined &&\r\n $any(iconClass.condition)(row))\r\n \"\r\n >{{ iconClass.text }}</span\r\n >\r\n </button>\r\n </ng-container>\r\n </td>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"columnProperties\"></tr>\r\n <tr\r\n [ngClass]=\"{\r\n 'example-element-row': data.isNotClickable === true,\r\n 'example-element-row cursor-pointer': !data.isNotClickable\r\n }\"\r\n mat-row\r\n *matRowDef=\"let row; columns: columnProperties\"\r\n (click)=\"goToDetails(row)\"\r\n ></tr>\r\n\r\n <!-- ROW SHOWN WHEN THERE IS NO MATCHING DATA. -->\r\n <tr class=\"mat-row\" *matNoDataRow>\r\n <td *ngIf=\"!isLoading\" class=\"mat-cell p-4\" colspan=\"4\">\r\n Nenhum resultado encontrado para a busca\r\n </td>\r\n </tr>\r\n </table>\r\n\r\n <div class=\"flex justify-center\" *ngIf=\"isLoading\">\r\n <mat-spinner></mat-spinner>\r\n </div>\r\n\r\n <div class=\"paginator-container\">\r\n <mat-paginator\r\n #paginator\r\n [pageSizeOptions]=\"[25, 50, 100]\"\r\n [pageSize]=\"pageSize\"\r\n [length]=\"totalItems\"\r\n showFirstLastButtons\r\n aria-label=\"Select page of periodic elements\"\r\n (page)=\"onPageChange($event)\"\r\n [ngClass]=\"{\r\n 'hide-length':\r\n ['filter', 'filterByDate', 'equals'].includes(\r\n this.currentArrange\r\n ) || this.data.filterFn,\r\n 'hide-next-button': !hasNextPage && data.pagination === true,\r\n 'hide-last-button':\r\n (!hasNextPage && data.pagination === true) || this.data.filterFn\r\n }\"\r\n >\r\n </mat-paginator>\r\n <div\r\n *ngIf=\"\r\n !isLoading &&\r\n dataSource?.data &&\r\n dataSource.data.length > 0 &&\r\n data?.filterFn\r\n \"\r\n class=\"page-number-display\"\r\n >\r\n {{ currentPageNumber }}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- TOOLTIP PERSONALIZADO -->\r\n <div\r\n *ngIf=\"showTooltip\"\r\n class=\"fixed z-50 max-w-md break-words rounded-lg bg-gray-800 px-3 py-2 text-sm text-white shadow-lg\"\r\n [style.left.px]=\"tooltipPosition.x\"\r\n [style.top.px]=\"tooltipPosition.y\"\r\n [style.pointer-events]=\"'none'\"\r\n >\r\n {{ tooltipContent }}\r\n </div>\r\n</div>\r\n", styles: ["::ng-deep .hide-length .mat-mdc-paginator-range-label{display:none}::ng-deep .hide-next-button .mat-mdc-tooltip-trigger.mat-mdc-paginator-navigation-next.mdc-icon-button.mat-mdc-icon-button.mat-unthemed.mat-mdc-button-base{visibility:hidden}::ng-deep .hide-next-button .mat-mdc-tooltip-trigger.mat-mdc-paginator-navigation-last.mdc-icon-button.mat-mdc-icon-button.mat-unthemed.mat-mdc-button-base.ng-star-inserted{visibility:hidden}::ng-deep .mat-mdc-text-field-wrapper.mdc-text-field.ng-tns-c162-1.mdc-text-field--filled{width:25dvw}::ng-deep .custom-filter .mat-mdc-text-field-wrapper{width:20dvw;max-width:300px}\n"] }]
2053
- }], ctorParameters: function () { return [{ type: i1$1.Router }, { type: TableService }, { type: i1.AngularFirestore }]; }, propDecorators: { data: [{
2054
- type: Input
2055
- }], downloadTable: [{
2056
- type: Input
2057
- }], paginator: [{
2058
- type: ViewChild,
2059
- args: [MatPaginator]
2060
- }], sort: [{
2061
- type: ViewChild,
2062
- args: [MatSort]
1182
+ class TableComponent {
1183
+ // CONSTRUCTOR
1184
+ constructor(router, tableService, firestore) {
1185
+ this.router = router;
1186
+ this.tableService = tableService;
1187
+ this.firestore = firestore;
1188
+ this.arrange = null;
1189
+ this.currentPageNumber = 1;
1190
+ this.currentClientPageIndex = 0;
1191
+ this.items = [];
1192
+ this.filteredItems = []; // Dados filtrados para modo não paginado (público para acesso externo)
1193
+ this.isLoading = false;
1194
+ this.lastDoc = null;
1195
+ this.firstDoc = null;
1196
+ this.sortBy = {
1197
+ field: 'createdAt',
1198
+ order: 'desc',
1199
+ };
1200
+ this.columnProperties = [];
1201
+ this.selectSort = new FormControl('');
1202
+ this.currentArrange = '';
1203
+ this.hasNextPage = false;
1204
+ this.dropdownItems = [];
1205
+ this.sortableDropdownItems = [];
1206
+ this.pageSize = 25;
1207
+ this.totalItems = 0;
1208
+ this.filterValue = null;
1209
+ this.hasFilterableColumn = false;
1210
+ this.hasSortableColumn = false;
1211
+ this.filterSubject = new Subject();
1212
+ this.debounceTimeMs = 500;
1213
+ this.selectedTab = 0;
1214
+ // Propriedades para controle do tooltip
1215
+ this.hoveredCell = null;
1216
+ this.showTooltip = false;
1217
+ this.tooltipContent = '';
1218
+ this.tooltipPosition = { x: 0, y: 0 };
1219
+ this.filtersForm = new FormArray([this.createFilterGroup()]);
1220
+ }
1221
+ createFilterGroup() {
1222
+ return new FormGroup({
1223
+ selectFilter: new FormControl(''),
1224
+ typeFilter: new FormControl(''),
1225
+ selectItem: new FormControl(''),
1226
+ initialDate: new FormControl('', this.tableService.dateFormatValidator()),
1227
+ finalDate: new FormControl('', this.tableService.dateFormatValidator()),
1228
+ });
1229
+ }
1230
+ addFilter(filterData) {
1231
+ var _a, _b, _c, _d, _e;
1232
+ const newFilterGroup = this.createFilterGroup();
1233
+ if (filterData) {
1234
+ if (filterData.selectFilter) {
1235
+ (_a = newFilterGroup.get('selectFilter')) === null || _a === void 0 ? void 0 : _a.setValue(filterData.selectFilter);
1236
+ }
1237
+ if (filterData.typeFilter) {
1238
+ (_b = newFilterGroup.get('typeFilter')) === null || _b === void 0 ? void 0 : _b.setValue(filterData.typeFilter);
1239
+ }
1240
+ if (filterData.selectItem) {
1241
+ (_c = newFilterGroup.get('selectItem')) === null || _c === void 0 ? void 0 : _c.setValue(filterData.selectItem);
1242
+ }
1243
+ if (filterData.initialDate) {
1244
+ (_d = newFilterGroup.get('initialDate')) === null || _d === void 0 ? void 0 : _d.setValue(filterData.initialDate);
1245
+ }
1246
+ if (filterData.finalDate) {
1247
+ (_e = newFilterGroup.get('finalDate')) === null || _e === void 0 ? void 0 : _e.setValue(filterData.finalDate);
1248
+ }
1249
+ }
1250
+ this.filtersForm.push(newFilterGroup);
1251
+ }
1252
+ onSelectFilterChange() {
1253
+ var _a;
1254
+ const lastIndex = this.filtersForm.length - 1;
1255
+ const lastFilter = this.filtersForm.at(lastIndex);
1256
+ if ((_a = lastFilter.get('selectFilter')) === null || _a === void 0 ? void 0 : _a.value) {
1257
+ this.addFilter();
1258
+ }
1259
+ }
1260
+ removeFilter(index) {
1261
+ this.filtersForm.removeAt(index);
1262
+ if (this.filtersForm.length === 0) {
1263
+ this.addFilter();
1264
+ }
1265
+ }
1266
+ removeAllFilters() {
1267
+ this.filtersForm.clear();
1268
+ this.addFilter();
1269
+ this.resetFilter();
1270
+ }
1271
+ // METHODS
1272
+ ngOnInit() {
1273
+ return __awaiter(this, void 0, void 0, function* () {
1274
+ if (!this.data.color)
1275
+ this.data.color = { bg: 'bg-primary', text: 'text-black' };
1276
+ this.columnProperties = this.data.displayedColumns.map((column) => {
1277
+ return column.property;
1278
+ });
1279
+ if (this.data.actionButton && !this.data.actionButton.condition) {
1280
+ this.data.actionButton.condition = (_row) => true;
1281
+ }
1282
+ this.data.displayedColumns.forEach((col) => {
1283
+ if (col.isFilterable) {
1284
+ if (this.hasFilterableColumn === false)
1285
+ this.hasFilterableColumn = true;
1286
+ this.dropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'filter', title: col.title }));
1287
+ }
1288
+ if (col.isSortable) {
1289
+ if (this.hasSortableColumn === false)
1290
+ this.hasSortableColumn = true;
1291
+ this.sortableDropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'ascending', title: col.title + ': crescente' }));
1292
+ this.sortableDropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'descending', title: col.title + ': decrescente' }));
1293
+ }
1294
+ if (col.isFilterableByDate) {
1295
+ this.dropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'filterByDate', title: col.title + ': filtro por data' }));
1296
+ }
1297
+ });
1298
+ if (this.data.filterableOptions &&
1299
+ Array.isArray(this.data.filterableOptions) &&
1300
+ this.data.filterableOptions.length > 0) {
1301
+ this.data.filterableOptions.forEach((option) => this.dropdownItems.push(Object.assign(Object.assign({}, option), { arrange: 'equals' })));
1302
+ }
1303
+ // Sem paginação
1304
+ if (this.data.pagination === false) {
1305
+ yield this.loadItems();
1306
+ }
1307
+ // Com paginação
1308
+ if (this.data.pagination === true) {
1309
+ if (this.data.sortBy)
1310
+ this.sortBy = {
1311
+ field: this.data.sortBy.field,
1312
+ order: this.data.sortBy.order,
1313
+ };
1314
+ this.filterSubject
1315
+ .pipe(debounceTime(this.debounceTimeMs))
1316
+ .subscribe(() => {
1317
+ this.loadItemsPaginated('reload', true);
1318
+ });
1319
+ this.isLoading = true;
1320
+ yield this.loadItemsPaginated('reload', true);
1321
+ this.sort.active = 'createdAt';
1322
+ this.sort.direction = 'desc';
1323
+ this.dataSource.paginator = this.paginator;
1324
+ this.dataSource.sort = this.sort;
1325
+ this.totalItems = 0;
1326
+ if (this.data.totalRef) {
1327
+ for (const totalRef of this.data.totalRef) {
1328
+ const totalRefDoc = yield totalRef.ref.get();
1329
+ const docData = totalRefDoc.data();
1330
+ if (docData && docData[totalRef.field])
1331
+ this.totalItems = (this.totalItems +
1332
+ docData[totalRef.field]);
1333
+ }
1334
+ }
1335
+ this.isLoading = false;
1336
+ }
1337
+ });
1338
+ }
1339
+ getDisplayValue(col, row, withinLimit = false) {
1340
+ let value;
1341
+ if (col.calculateValue) {
1342
+ value = col.calculateValue(row);
1343
+ }
1344
+ else {
1345
+ value = this.getNestedValue(row, col.property);
1346
+ }
1347
+ if (Array.isArray(value) && col.arrayField) {
1348
+ value = this.formatArrayValue(value, col.arrayField);
1349
+ }
1350
+ if (col.queryLength && row[col.property]) {
1351
+ value = row[col.property];
1352
+ }
1353
+ value = col.pipe ? col.pipe.transform(value) : value;
1354
+ if (value === null || value === undefined) {
1355
+ value = '';
1356
+ }
1357
+ else {
1358
+ value = String(value);
1359
+ }
1360
+ if (typeof value !== 'string') {
1361
+ value = '';
1362
+ }
1363
+ if (withinLimit || !col.charLimit) {
1364
+ return value;
1365
+ }
1366
+ return value.substring(0, col.charLimit) + '...';
1367
+ }
1368
+ getNestedValue(obj, path) {
1369
+ if (!path)
1370
+ return undefined;
1371
+ const properties = path.split('.');
1372
+ return properties.reduce((acc, currentPart) => acc && acc[currentPart], obj);
1373
+ }
1374
+ formatArrayValue(array, field) {
1375
+ if (!Array.isArray(array) || array.length === 0) {
1376
+ return '';
1377
+ }
1378
+ const values = array
1379
+ .map((item) => {
1380
+ if (typeof item === 'object' && item !== null) {
1381
+ return item[field] || '';
1382
+ }
1383
+ return String(item);
1384
+ })
1385
+ .filter((value) => value !== '' && value !== null && value !== undefined);
1386
+ return values.join(', ');
1387
+ }
1388
+ // Métodos sem paginação
1389
+ loadItems() {
1390
+ return __awaiter(this, void 0, void 0, function* () {
1391
+ this.items = yield this.tableService.getItems(this.data.collectionRef);
1392
+ if (this.data.conditions) {
1393
+ this.filterItems();
1394
+ }
1395
+ yield this.loadRelations();
1396
+ yield this.loadQueryLengths();
1397
+ this.totalItems = this.items.length;
1398
+ // Inicializar arrange para tabelas não paginadas
1399
+ this.arrange = {
1400
+ filters: [],
1401
+ sortBy: this.data.sortBy || {
1402
+ field: 'createdAt',
1403
+ order: 'desc',
1404
+ },
1405
+ };
1406
+ this.sortBy = this.data.sortBy || {
1407
+ field: 'createdAt',
1408
+ order: 'desc',
1409
+ };
1410
+ // Aplicar filtros client-side se existirem
1411
+ let itemsToDisplay = [...this.items];
1412
+ itemsToDisplay = this.applyClientSideFilters(itemsToDisplay);
1413
+ this.filteredItems = itemsToDisplay; // Armazenar dados filtrados
1414
+ this.dataSource = new MatTableDataSource(itemsToDisplay);
1415
+ this.dataSource.paginator = this.paginator;
1416
+ this.dataSource.sort = this.sort;
1417
+ if (this.data.sortBy) {
1418
+ this.dataSource.sort.active = this.data.sortBy.field;
1419
+ this.dataSource.sort.direction = this.data.sortBy.order;
1420
+ this.dataSource.sort.sortChange.emit();
1421
+ }
1422
+ this.dataSource.filterPredicate = (data, filter) => {
1423
+ return this.data.displayedColumns.some((col) => {
1424
+ if (col.filterPredicates) {
1425
+ return col.filterPredicates.some((predicate) => {
1426
+ const propertyValue = data[col.property];
1427
+ if (!propertyValue || typeof propertyValue !== 'object') {
1428
+ return false;
1429
+ }
1430
+ const predicateValue = propertyValue[predicate];
1431
+ if (predicateValue === null || predicateValue === undefined) {
1432
+ return false;
1433
+ }
1434
+ return String(predicateValue)
1435
+ .trim()
1436
+ .toLocaleLowerCase()
1437
+ .includes(filter);
1438
+ });
1439
+ }
1440
+ if (col.property && col.isFilterable) {
1441
+ const propertyValue = data[col.property];
1442
+ if (propertyValue === null || propertyValue === undefined) {
1443
+ return false;
1444
+ }
1445
+ return String(propertyValue)
1446
+ .trim()
1447
+ .toLocaleLowerCase()
1448
+ .includes(filter);
1449
+ }
1450
+ return false;
1451
+ });
1452
+ };
1453
+ this.filterPredicate = this.dataSource.filterPredicate;
1454
+ });
1455
+ }
1456
+ // Aplicar filtros client-side (filtros por data)
1457
+ applyClientSideFilters(items) {
1458
+ let filteredItems = [...items];
1459
+ // Agrupar filtros de data por propriedade
1460
+ const dateFiltersByProperty = {};
1461
+ const otherFilters = [];
1462
+ // Primeira passada: separar filtros de data dos outros filtros
1463
+ this.filtersForm.controls.forEach((control) => {
1464
+ var _a, _b, _c;
1465
+ const group = control;
1466
+ const selectedFilter = (_a = group.get('selectFilter')) === null || _a === void 0 ? void 0 : _a.value;
1467
+ if (!selectedFilter)
1468
+ return;
1469
+ const arrange = selectedFilter.arrange;
1470
+ if (arrange === 'filterByDate') {
1471
+ const initial = (_b = group.get('initialDate')) === null || _b === void 0 ? void 0 : _b.value;
1472
+ const final = (_c = group.get('finalDate')) === null || _c === void 0 ? void 0 : _c.value;
1473
+ // aplicar filtro se ambas as datas estiverem preenchidas
1474
+ if (initial && final && initial.trim() && final.trim()) {
1475
+ try {
1476
+ // Validar formato da data (DD/MM/AAAA)
1477
+ const datePattern = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/;
1478
+ if (!datePattern.test(initial) || !datePattern.test(final)) {
1479
+ return; // Ignorar se formato inválido
1480
+ }
1481
+ const [dayI, monthI, yearI] = initial.split('/');
1482
+ const initialDate = new Date(`${monthI}/${dayI}/${yearI}`);
1483
+ const [dayF, monthF, yearF] = final.split('/');
1484
+ const finalDate = new Date(`${monthF}/${dayF}/${yearF}`);
1485
+ finalDate.setHours(23, 59, 59);
1486
+ // Validar se as datas são válidas
1487
+ if (isNaN(initialDate.getTime()) || isNaN(finalDate.getTime())) {
1488
+ return; // Ignorar se datas inválidas
1489
+ }
1490
+ // Usar o campo da coluna ou o sortBy.field como padrão
1491
+ const dateField = selectedFilter.property || this.sortBy.field;
1492
+ if (!dateFiltersByProperty[dateField]) {
1493
+ dateFiltersByProperty[dateField] = [];
1494
+ }
1495
+ dateFiltersByProperty[dateField].push({
1496
+ initial: initialDate,
1497
+ final: finalDate,
1498
+ });
1499
+ }
1500
+ catch (error) {
1501
+ console.warn('Erro ao processar datas do filtro:', error);
1502
+ }
1503
+ }
1504
+ }
1505
+ else {
1506
+ // Outros tipos de filtros (texto, equals, etc)
1507
+ otherFilters.push({ group, selectedFilter, arrange });
1508
+ }
1509
+ });
1510
+ // Aplicar filtros de data com OR lógico para o mesmo campo
1511
+ Object.keys(dateFiltersByProperty).forEach((dateField) => {
1512
+ const intervals = dateFiltersByProperty[dateField];
1513
+ filteredItems = filteredItems.filter((item) => {
1514
+ try {
1515
+ const fieldValue = item[dateField];
1516
+ if (!fieldValue) {
1517
+ return false;
1518
+ }
1519
+ let itemDate;
1520
+ if (typeof fieldValue.toDate === 'function') {
1521
+ itemDate = fieldValue.toDate();
1522
+ }
1523
+ else if (fieldValue instanceof Date) {
1524
+ itemDate = fieldValue;
1525
+ }
1526
+ else if (typeof fieldValue === 'string') {
1527
+ itemDate = new Date(fieldValue);
1528
+ if (isNaN(itemDate.getTime())) {
1529
+ return false;
1530
+ }
1531
+ }
1532
+ else if (typeof fieldValue === 'number') {
1533
+ itemDate = new Date(fieldValue);
1534
+ }
1535
+ else {
1536
+ return false;
1537
+ }
1538
+ // OR lógico: item deve estar em QUALQUER um dos intervalos
1539
+ return intervals.some((interval) => itemDate >= interval.initial && itemDate <= interval.final);
1540
+ }
1541
+ catch (error) {
1542
+ console.warn('Erro ao processar filtro de data para o item:', item.id, error);
1543
+ return false;
1544
+ }
1545
+ });
1546
+ });
1547
+ // Aplicar outros filtros (texto, equals, etc) - AND lógico entre diferentes tipos
1548
+ otherFilters.forEach(({ group, selectedFilter, arrange }) => {
1549
+ var _a, _b;
1550
+ if (arrange === 'filter') {
1551
+ const filterValue = (_a = group.get('typeFilter')) === null || _a === void 0 ? void 0 : _a.value;
1552
+ if (filterValue && filterValue.trim()) {
1553
+ const property = selectedFilter.property;
1554
+ filteredItems = filteredItems.filter((item) => {
1555
+ const value = String(item[property] || '')
1556
+ .trim()
1557
+ .toLocaleLowerCase();
1558
+ return value.includes(filterValue.trim().toLocaleLowerCase());
1559
+ });
1560
+ }
1561
+ }
1562
+ else if (selectedFilter.hasOwnProperty('items') &&
1563
+ arrange === 'equals') {
1564
+ const selectedItems = (_b = group.get('selectItem')) === null || _b === void 0 ? void 0 : _b.value;
1565
+ if (Array.isArray(selectedItems) && selectedItems.length > 0) {
1566
+ const values = selectedItems.map((item) => item.value);
1567
+ filteredItems = filteredItems.filter((item) => {
1568
+ return selectedItems.some((selectedItem) => {
1569
+ const itemValue = item[selectedItem.property];
1570
+ return itemValue === selectedItem.value;
1571
+ });
1572
+ });
1573
+ }
1574
+ }
1575
+ });
1576
+ return filteredItems;
1577
+ }
1578
+ // Handler para mudanças de data no filtro
1579
+ onDateFilterChange() {
1580
+ if (this.data.pagination === false) {
1581
+ // Atualizar arrange com os filtros ativos
1582
+ this.arrange = this.buildArrangeFromFilters();
1583
+ // Aplicar filtros (ou remover se campos estiverem vazios)
1584
+ this.applyFiltersToDataSource();
1585
+ }
1586
+ }
1587
+ // Aplicar filtros ao dataSource quando não há paginação
1588
+ applyFiltersToDataSource() {
1589
+ if (!this.dataSource)
1590
+ return;
1591
+ let filteredItems = this.applyClientSideFilters([...this.items]);
1592
+ this.dataSource.data = filteredItems;
1593
+ // Atualizar filteredItems com os dados filtrados (será atualizado novamente no handleDownload com filteredData)
1594
+ this.filteredItems = filteredItems;
1595
+ this.totalItems = filteredItems.length;
1596
+ }
1597
+ // Métodos com paginação
1598
+ loadItemsPaginated(navigation = 'reload', reset = false) {
1599
+ var _a;
1600
+ return __awaiter(this, void 0, void 0, function* () {
1601
+ if (reset && ['forward', 'reload'].includes(navigation)) {
1602
+ this.lastDoc = null;
1603
+ this.currentClientPageIndex = 0;
1604
+ }
1605
+ if (reset && ['backward', 'reload'].includes(navigation)) {
1606
+ this.firstDoc = null;
1607
+ this.currentClientPageIndex = 0;
1608
+ }
1609
+ const activeFilters = this.filtersForm.controls
1610
+ .flatMap((control) => {
1611
+ var _a, _b, _c, _d, _e;
1612
+ const group = control;
1613
+ const selectedFilter = (_a = group.get('selectFilter')) === null || _a === void 0 ? void 0 : _a.value;
1614
+ if (!selectedFilter)
1615
+ return [];
1616
+ const arrange = selectedFilter.arrange;
1617
+ if (arrange === 'filter') {
1618
+ const filterValue = (_b = group.get('typeFilter')) === null || _b === void 0 ? void 0 : _b.value;
1619
+ if (!filterValue)
1620
+ return [];
1621
+ return {
1622
+ arrange,
1623
+ filter: {
1624
+ property: selectedFilter.property,
1625
+ filtering: filterValue,
1626
+ },
1627
+ dateFilter: undefined,
1628
+ };
1629
+ }
1630
+ if (arrange === 'filterByDate') {
1631
+ const initial = (_c = group.get('initialDate')) === null || _c === void 0 ? void 0 : _c.value;
1632
+ const final = (_d = group.get('finalDate')) === null || _d === void 0 ? void 0 : _d.value;
1633
+ if (initial && final) {
1634
+ const [dayI, monthI, yearI] = initial.split('/');
1635
+ const initialDate = new Date(`${monthI}/${dayI}/${yearI}`);
1636
+ const [dayF, monthF, yearF] = final.split('/');
1637
+ const finalDate = new Date(`${monthF}/${dayF}/${yearF}`);
1638
+ finalDate.setHours(23, 59, 59);
1639
+ return {
1640
+ arrange,
1641
+ filter: undefined,
1642
+ dateFilter: {
1643
+ initial: initialDate,
1644
+ final: finalDate,
1645
+ },
1646
+ };
1647
+ }
1648
+ return [];
1649
+ }
1650
+ if (selectedFilter.hasOwnProperty('items') && arrange === 'equals') {
1651
+ const selectedItems = (_e = group.get('selectItem')) === null || _e === void 0 ? void 0 : _e.value;
1652
+ if (Array.isArray(selectedItems) && selectedItems.length > 0) {
1653
+ return selectedItems.map((item) => ({
1654
+ arrange,
1655
+ filter: {
1656
+ property: item.property,
1657
+ filtering: item.value,
1658
+ },
1659
+ dateFilter: undefined,
1660
+ }));
1661
+ }
1662
+ }
1663
+ return [];
1664
+ })
1665
+ .filter((f) => { var _a; return f && (((_a = f.filter) === null || _a === void 0 ? void 0 : _a.filtering) !== undefined || f.dateFilter); });
1666
+ this.arrange = {
1667
+ filters: activeFilters,
1668
+ sortBy: this.sortBy,
1669
+ };
1670
+ const paginated = {
1671
+ batchSize: this.pageSize,
1672
+ collection: this.data.collection,
1673
+ doc: { lastDoc: this.lastDoc, firstDoc: this.firstDoc },
1674
+ navigation,
1675
+ arrange: this.arrange,
1676
+ conditions: this.data.conditions,
1677
+ size: this.totalItems,
1678
+ filterFn: this.data.filterFn,
1679
+ clientPageIndex: this.currentClientPageIndex,
1680
+ };
1681
+ const result = yield this.tableService.getPaginated(paginated);
1682
+ this.items = result.items;
1683
+ yield this.loadRelations();
1684
+ yield this.loadQueryLengths();
1685
+ this.lastDoc = result.lastDoc;
1686
+ this.firstDoc = result.firstDoc;
1687
+ // Atualizar currentClientPageIndex se retornado pelo fallback
1688
+ if (result.currentClientPageIndex !== undefined) {
1689
+ this.currentClientPageIndex = result.currentClientPageIndex;
1690
+ }
1691
+ let sum = 0;
1692
+ if (this.data.totalRef) {
1693
+ for (const totalRef of this.data.totalRef) {
1694
+ const totalRefDoc = yield totalRef.ref.get();
1695
+ const docData = totalRefDoc.data();
1696
+ if (docData || result.filterLength) {
1697
+ sum =
1698
+ (_a = result.filterLength) !== null && _a !== void 0 ? _a : (sum + (docData ? docData[totalRef.field] : 0));
1699
+ }
1700
+ }
1701
+ this.totalItems = sum;
1702
+ }
1703
+ this.hasNextPage = result.hasNextPage;
1704
+ this.dataSource = new MatTableDataSource(this.items);
1705
+ this.filterPredicate = this.dataSource.filterPredicate;
1706
+ });
1707
+ }
1708
+ onPageChange(event) {
1709
+ var _a;
1710
+ return __awaiter(this, void 0, void 0, function* () {
1711
+ if (this.data.pagination === true && event) {
1712
+ this.isLoading = true;
1713
+ const previousPageIndex = (_a = event.previousPageIndex) !== null && _a !== void 0 ? _a : 0;
1714
+ const pageIndex = event.pageIndex;
1715
+ const currentComponentPageSize = this.pageSize;
1716
+ const eventPageSize = event.pageSize;
1717
+ const totalItems = this.totalItems;
1718
+ let navigationDirection;
1719
+ let resetDocs = false;
1720
+ let originalPageSize = null;
1721
+ const lastPageIndex = Math.max(0, Math.ceil(totalItems / eventPageSize) - 1);
1722
+ // Atualizar currentClientPageIndex sempre para o fallback
1723
+ this.currentClientPageIndex = pageIndex;
1724
+ if (previousPageIndex !== undefined && pageIndex > previousPageIndex) {
1725
+ this.currentPageNumber++;
1726
+ }
1727
+ else if (previousPageIndex !== undefined &&
1728
+ pageIndex < previousPageIndex) {
1729
+ this.currentPageNumber = Math.max(1, this.currentPageNumber - 1);
1730
+ }
1731
+ if (eventPageSize !== currentComponentPageSize) {
1732
+ console.log('Alterou a quantidade de elementos exibidos por página');
1733
+ this.pageSize = eventPageSize;
1734
+ navigationDirection = 'forward';
1735
+ resetDocs = true;
1736
+ this.currentClientPageIndex = 0;
1737
+ }
1738
+ else if (pageIndex === 0 &&
1739
+ previousPageIndex !== undefined &&
1740
+ pageIndex < previousPageIndex) {
1741
+ console.log('Pulou para a primeira página');
1742
+ navigationDirection = 'forward';
1743
+ this.currentPageNumber = 1;
1744
+ this.currentClientPageIndex = 0;
1745
+ resetDocs = true;
1746
+ }
1747
+ else if (pageIndex === lastPageIndex &&
1748
+ previousPageIndex !== undefined &&
1749
+ pageIndex > previousPageIndex &&
1750
+ pageIndex - previousPageIndex > 1) {
1751
+ console.log('Pulou para a ultima página');
1752
+ navigationDirection = 'backward';
1753
+ resetDocs = true;
1754
+ const itemsExpectedInLastPage = totalItems - lastPageIndex * eventPageSize;
1755
+ if (itemsExpectedInLastPage > 0 &&
1756
+ itemsExpectedInLastPage < eventPageSize) {
1757
+ originalPageSize = this.pageSize;
1758
+ this.pageSize = itemsExpectedInLastPage;
1759
+ }
1760
+ }
1761
+ else if (previousPageIndex !== undefined &&
1762
+ pageIndex > previousPageIndex) {
1763
+ console.log('Procedendo');
1764
+ navigationDirection = 'forward';
1765
+ resetDocs = false;
1766
+ }
1767
+ else if (previousPageIndex !== undefined &&
1768
+ pageIndex < previousPageIndex) {
1769
+ console.log('Retrocedendo.');
1770
+ navigationDirection = 'backward';
1771
+ resetDocs = false;
1772
+ }
1773
+ else if (previousPageIndex !== undefined &&
1774
+ pageIndex === previousPageIndex) {
1775
+ console.log('Recarregando.');
1776
+ navigationDirection = 'reload';
1777
+ resetDocs = false;
1778
+ }
1779
+ else if (previousPageIndex === undefined && pageIndex === 0) {
1780
+ console.log('Evento inicial do paginador para pág 0. ngOnInit carregou.');
1781
+ this.isLoading = false;
1782
+ if (event)
1783
+ this.pageEvent = event;
1784
+ return;
1785
+ }
1786
+ else {
1787
+ console.warn('INESPERADO! Condição de navegação não tratada:', event);
1788
+ this.isLoading = false;
1789
+ if (event)
1790
+ this.pageEvent = event;
1791
+ return;
1792
+ }
1793
+ if (navigationDirection) {
1794
+ try {
1795
+ yield this.loadItemsPaginated(navigationDirection, resetDocs);
1796
+ }
1797
+ catch (error) {
1798
+ console.error('Erro ao carregar itens paginados:', error);
1799
+ }
1800
+ finally {
1801
+ if (originalPageSize !== null) {
1802
+ this.pageSize = originalPageSize;
1803
+ }
1804
+ }
1805
+ }
1806
+ if (event)
1807
+ this.pageEvent = event;
1808
+ this.isLoading = false;
1809
+ }
1810
+ });
1811
+ }
1812
+ // Outros métodos
1813
+ applyFilter(value) {
1814
+ // Sem paginação
1815
+ if (this.data.pagination === false) {
1816
+ this.dataSource.filter = String(value).trim().toLowerCase();
1817
+ }
1818
+ // Com paginação
1819
+ if (this.data.pagination === true) {
1820
+ this.filterValue = value;
1821
+ this.filterSubject.next(this.filterValue);
1822
+ }
1823
+ }
1824
+ goToDetails(row) {
1825
+ if (this.data.isNotClickable) {
1826
+ return;
1827
+ }
1828
+ const urlPath = this.data.url || this.data.name;
1829
+ const url = this.router.serializeUrl(this.router.createUrlTree([`/${urlPath}`, row.id]));
1830
+ window.open(url, '_blank');
1831
+ }
1832
+ getRelation(params) {
1833
+ var _a;
1834
+ return __awaiter(this, void 0, void 0, function* () {
1835
+ try {
1836
+ let snapshot;
1837
+ if (params.id !== '' &&
1838
+ params.id !== undefined &&
1839
+ params.collection !== undefined &&
1840
+ params.collection !== '') {
1841
+ snapshot = yield firstValueFrom(this.firestore.collection(params.collection).doc(params.id).get());
1842
+ }
1843
+ if (snapshot && snapshot.exists) {
1844
+ const data = snapshot.data();
1845
+ return (_a = data === null || data === void 0 ? void 0 : data[params.newProperty]) !== null && _a !== void 0 ? _a : '';
1846
+ }
1847
+ return '';
1848
+ }
1849
+ catch (e) {
1850
+ console.log(e);
1851
+ return '';
1852
+ }
1853
+ });
1854
+ }
1855
+ loadRelations() {
1856
+ return __awaiter(this, void 0, void 0, function* () {
1857
+ const relationPromises = this.data.displayedColumns
1858
+ .filter((col) => col.relation)
1859
+ .flatMap((col) => this.items.map((item) => __awaiter(this, void 0, void 0, function* () {
1860
+ if (col.relation) {
1861
+ item[col.property] = yield this.getRelation({
1862
+ id: item[col.relation.property],
1863
+ collection: col.relation.collection,
1864
+ newProperty: col.relation.newProperty,
1865
+ });
1866
+ }
1867
+ })));
1868
+ yield Promise.all(relationPromises);
1869
+ });
1870
+ }
1871
+ getQueryLength(params) {
1872
+ return __awaiter(this, void 0, void 0, function* () {
1873
+ const snapshot = yield this.firestore
1874
+ .collection(params.relation.collection)
1875
+ .ref.where(params.relation.property, params.relation.operator, params.item[params.relation.value])
1876
+ .get();
1877
+ return snapshot.size;
1878
+ });
1879
+ }
1880
+ loadQueryLengths() {
1881
+ return __awaiter(this, void 0, void 0, function* () {
1882
+ const lengthPromises = this.data.displayedColumns
1883
+ .filter((col) => col.queryLength)
1884
+ .flatMap((col) => this.items.map((item) => __awaiter(this, void 0, void 0, function* () {
1885
+ if (col.queryLength) {
1886
+ item[col.property] = yield this.getQueryLength({
1887
+ item: item,
1888
+ relation: col.queryLength,
1889
+ });
1890
+ }
1891
+ })));
1892
+ yield Promise.all(lengthPromises);
1893
+ });
1894
+ }
1895
+ filterItems() {
1896
+ if (this.data.conditions) {
1897
+ this.data.conditions.forEach((cond) => {
1898
+ this.items = this.items.filter((item) => {
1899
+ const operatorFunction = this.tableService.operators[cond.operator];
1900
+ if (operatorFunction) {
1901
+ return operatorFunction(item[cond.firestoreProperty], cond.dashProperty);
1902
+ }
1903
+ return false;
1904
+ });
1905
+ });
1906
+ }
1907
+ }
1908
+ buildArrangeFromFilters() {
1909
+ const activeFilters = this.filtersForm.controls
1910
+ .flatMap((control) => {
1911
+ var _a, _b, _c, _d, _e;
1912
+ const group = control;
1913
+ const selectedFilter = (_a = group.get('selectFilter')) === null || _a === void 0 ? void 0 : _a.value;
1914
+ if (!selectedFilter)
1915
+ return [];
1916
+ const arrange = selectedFilter.arrange;
1917
+ if (arrange === 'filter') {
1918
+ const filterValue = (_b = group.get('typeFilter')) === null || _b === void 0 ? void 0 : _b.value;
1919
+ if (!filterValue)
1920
+ return [];
1921
+ return {
1922
+ arrange,
1923
+ filter: {
1924
+ property: selectedFilter.property,
1925
+ filtering: filterValue,
1926
+ },
1927
+ dateFilter: undefined,
1928
+ };
1929
+ }
1930
+ if (arrange === 'filterByDate') {
1931
+ const initial = (_c = group.get('initialDate')) === null || _c === void 0 ? void 0 : _c.value;
1932
+ const final = (_d = group.get('finalDate')) === null || _d === void 0 ? void 0 : _d.value;
1933
+ if (initial && final) {
1934
+ try {
1935
+ const [dayI, monthI, yearI] = initial.split('/');
1936
+ const initialDate = new Date(`${monthI}/${dayI}/${yearI}`);
1937
+ const [dayF, monthF, yearF] = final.split('/');
1938
+ const finalDate = new Date(`${monthF}/${dayF}/${yearF}`);
1939
+ finalDate.setHours(23, 59, 59);
1940
+ return {
1941
+ arrange,
1942
+ filter: undefined,
1943
+ dateFilter: {
1944
+ initial: initialDate,
1945
+ final: finalDate,
1946
+ },
1947
+ };
1948
+ }
1949
+ catch (error) {
1950
+ return [];
1951
+ }
1952
+ }
1953
+ return [];
1954
+ }
1955
+ if (selectedFilter.hasOwnProperty('items') && arrange === 'equals') {
1956
+ const selectedItems = (_e = group.get('selectItem')) === null || _e === void 0 ? void 0 : _e.value;
1957
+ if (Array.isArray(selectedItems) && selectedItems.length > 0) {
1958
+ return selectedItems.map((item) => ({
1959
+ arrange,
1960
+ filter: {
1961
+ property: item.property,
1962
+ filtering: item.value,
1963
+ },
1964
+ dateFilter: undefined,
1965
+ }));
1966
+ }
1967
+ }
1968
+ return [];
1969
+ })
1970
+ .filter((f) => { var _a; return f && (((_a = f.filter) === null || _a === void 0 ? void 0 : _a.filtering) !== undefined || f.dateFilter); });
1971
+ return {
1972
+ filters: activeFilters,
1973
+ sortBy: this.sortBy,
1974
+ };
1975
+ }
1976
+ // Filtro de data
1977
+ search() {
1978
+ var _a, _b, _c, _d, _e, _f;
1979
+ return __awaiter(this, void 0, void 0, function* () {
1980
+ if (this.selectSort.value) {
1981
+ if (this.selectSort.value.arrange === 'ascending') {
1982
+ this.sortBy = {
1983
+ field: this.selectSort.value.property,
1984
+ order: 'asc',
1985
+ };
1986
+ }
1987
+ if (this.selectSort.value.arrange === 'descending') {
1988
+ this.sortBy = {
1989
+ field: this.selectSort.value.property,
1990
+ order: 'desc',
1991
+ };
1992
+ }
1993
+ }
1994
+ // Sem paginação: aplicar filtros client-side
1995
+ if (this.data.pagination === false) {
1996
+ // Atualizar arrange com os filtros ativos
1997
+ this.arrange = this.buildArrangeFromFilters();
1998
+ this.applyFiltersToDataSource();
1999
+ this.currentArrange =
2000
+ this.filtersForm.length > 0
2001
+ ? (_c = (_b = (_a = this.filtersForm.at(0).get('selectFilter')) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.arrange) !== null && _c !== void 0 ? _c : ''
2002
+ : '';
2003
+ }
2004
+ else {
2005
+ // Com paginação: comportamento original
2006
+ yield this.loadItemsPaginated('reload', true);
2007
+ this.currentArrange =
2008
+ this.filtersForm.length > 0
2009
+ ? (_f = (_e = (_d = this.filtersForm.at(0).get('selectFilter')) === null || _d === void 0 ? void 0 : _d.value) === null || _e === void 0 ? void 0 : _e.arrange) !== null && _f !== void 0 ? _f : ''
2010
+ : '';
2011
+ this.paginator.firstPage();
2012
+ }
2013
+ });
2014
+ }
2015
+ resetFilter() {
2016
+ return __awaiter(this, void 0, void 0, function* () {
2017
+ this.dataSource.filter = '';
2018
+ if (this.filterPredicate) {
2019
+ this.dataSource.filterPredicate = this.filterPredicate;
2020
+ }
2021
+ this.filtersForm.clear();
2022
+ this.addFilter();
2023
+ this.selectSort.patchValue('');
2024
+ this.sortBy = {
2025
+ order: this.data.sortBy ? this.data.sortBy.order : 'desc',
2026
+ field: this.data.sortBy ? this.data.sortBy.field : 'createdAt',
2027
+ };
2028
+ // Sem paginação: recarregar itens originais
2029
+ if (this.data.pagination === false) {
2030
+ // Resetar arrange para valores padrão
2031
+ this.arrange = {
2032
+ filters: [],
2033
+ sortBy: this.sortBy,
2034
+ };
2035
+ this.dataSource.data = [...this.items];
2036
+ this.totalItems = this.items.length;
2037
+ this.currentArrange = '';
2038
+ }
2039
+ else {
2040
+ // Com paginação: comportamento original
2041
+ yield this.loadItemsPaginated('reload', true);
2042
+ this.currentArrange = '';
2043
+ this.paginator.firstPage();
2044
+ }
2045
+ });
2046
+ }
2047
+ // Método público para recarregar a tabela
2048
+ reloadTable() {
2049
+ return __awaiter(this, void 0, void 0, function* () {
2050
+ if (this.data.pagination) {
2051
+ yield this.loadItemsPaginated('reload', true);
2052
+ this.paginator.firstPage();
2053
+ }
2054
+ else {
2055
+ yield this.loadItems();
2056
+ }
2057
+ });
2058
+ }
2059
+ updateDisplayedColumns() {
2060
+ if (this.dataSource) {
2061
+ this.dataSource = new MatTableDataSource([]);
2062
+ }
2063
+ this.columnProperties = this.data.displayedColumns.map((column) => {
2064
+ return column.property;
2065
+ });
2066
+ this.dropdownItems = [];
2067
+ this.sortableDropdownItems = [];
2068
+ this.data.displayedColumns.forEach((col) => {
2069
+ if (col.isFilterable) {
2070
+ this.dropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'filter', title: col.title }));
2071
+ }
2072
+ if (col.isSortable) {
2073
+ this.sortableDropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'ascending', title: col.title + ': crescente' }));
2074
+ this.sortableDropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'descending', title: col.title + ': decrescente' }));
2075
+ }
2076
+ if (col.isFilterableByDate) {
2077
+ this.dropdownItems.push(Object.assign(Object.assign({}, col), { arrange: 'filterByDate', title: col.title + ': filtro por data' }));
2078
+ }
2079
+ });
2080
+ if (this.data.filterableOptions &&
2081
+ Array.isArray(this.data.filterableOptions)) {
2082
+ this.data.filterableOptions.forEach((option) => this.dropdownItems.push(Object.assign(Object.assign({}, option), { arrange: 'equals' })));
2083
+ }
2084
+ }
2085
+ isString(value) {
2086
+ return typeof value === 'string';
2087
+ }
2088
+ // Métodos para controle do tooltip
2089
+ onCellMouseEnter(event, row, col) {
2090
+ // Só mostrar tooltip se a coluna tiver charLimit definido
2091
+ if (!col.charLimit) {
2092
+ return;
2093
+ }
2094
+ const fullValue = this.getDisplayValue(col, row, true);
2095
+ // Só mostrar tooltip se o valor completo for maior que o limite
2096
+ if (fullValue.length <= col.charLimit) {
2097
+ return;
2098
+ }
2099
+ this.hoveredCell = { row, col };
2100
+ this.tooltipContent = fullValue;
2101
+ // Definir posição do tooltip
2102
+ this.tooltipPosition = {
2103
+ x: event.clientX + 10,
2104
+ y: event.clientY - 10,
2105
+ };
2106
+ // Timeout para mostrar o tooltip
2107
+ this.tooltipTimeout = setTimeout(() => {
2108
+ if (this.hoveredCell &&
2109
+ this.hoveredCell.row === row &&
2110
+ this.hoveredCell.col === col) {
2111
+ this.showTooltip = true;
2112
+ }
2113
+ }, 500);
2114
+ }
2115
+ onCellMouseLeave() {
2116
+ if (this.tooltipTimeout) {
2117
+ clearTimeout(this.tooltipTimeout);
2118
+ this.tooltipTimeout = null;
2119
+ }
2120
+ this.showTooltip = false;
2121
+ this.hoveredCell = null;
2122
+ this.tooltipContent = '';
2123
+ }
2124
+ onCellMouseMove(event) {
2125
+ if (this.showTooltip) {
2126
+ this.tooltipPosition = {
2127
+ x: event.clientX + 10,
2128
+ y: event.clientY - 10,
2129
+ };
2130
+ }
2131
+ }
2132
+ // Métodos para inversão vertical dos tabs
2133
+ getTabGroups(tabs) {
2134
+ if (!tabs || tabs.length === 0)
2135
+ return [];
2136
+ const totalGroups = Math.ceil(tabs.length / 6);
2137
+ const groups = [];
2138
+ // Criar array de índices invertidos (último grupo primeiro)
2139
+ for (let i = totalGroups - 1; i >= 0; i--) {
2140
+ groups.push(i);
2141
+ }
2142
+ return groups;
2143
+ }
2144
+ getTabGroup(tabs, groupIndex) {
2145
+ if (!tabs || tabs.length === 0)
2146
+ return [];
2147
+ const startIndex = groupIndex * 6;
2148
+ const endIndex = Math.min(startIndex + 6, tabs.length);
2149
+ return tabs.slice(startIndex, endIndex);
2150
+ }
2151
+ getRealTabIndex(groupIndex, tabIndexInGroup) {
2152
+ var _a;
2153
+ if (!((_a = this.data.tabs) === null || _a === void 0 ? void 0 : _a.tabsData))
2154
+ return 0;
2155
+ const totalGroups = Math.ceil(this.data.tabs.tabsData.length / 6);
2156
+ const realGroupIndex = totalGroups - 1 - groupIndex;
2157
+ return realGroupIndex * 6 + tabIndexInGroup;
2158
+ }
2159
+ onTableSelected(i, j) {
2160
+ var _a;
2161
+ if (!((_a = this.data.tabs) === null || _a === void 0 ? void 0 : _a.tabsData) || !this.data.tabs.method)
2162
+ return;
2163
+ this.selectedTab = this.getRealTabIndex(i, j);
2164
+ const tab = this.data.tabs.tabsData[this.selectedTab];
2165
+ if (tab) {
2166
+ this.data.tabs.method(tab, this.selectedTab);
2167
+ }
2168
+ }
2169
+ isTabSelected(originalIndex) {
2170
+ return this.selectedTab === originalIndex;
2171
+ }
2172
+ shouldShowActionButton() {
2173
+ var _a, _b;
2174
+ if (!((_a = this.data) === null || _a === void 0 ? void 0 : _a.actionButton)) {
2175
+ return false;
2176
+ }
2177
+ if (!this.data.actionButton.condition) {
2178
+ return true;
2179
+ }
2180
+ try {
2181
+ return (_b = this.data.actionButton.condition(null)) !== null && _b !== void 0 ? _b : true;
2182
+ }
2183
+ catch (_c) {
2184
+ return true;
2185
+ }
2186
+ }
2187
+ // Método para lidar com download (diferente para paginado e não paginado)
2188
+ handleDownload() {
2189
+ if (!this.downloadTable)
2190
+ return;
2191
+ // Se não há paginação, usar dados filtrados do dataSource
2192
+ if (this.data.pagination === false) {
2193
+ // Atualizar filteredItems com os dados filtrados do dataSource
2194
+ // (que já inclui filtro de texto aplicado via filterPredicate)
2195
+ if (this.dataSource && this.dataSource.filteredData) {
2196
+ this.filteredItems = [...this.dataSource.filteredData];
2197
+ }
2198
+ // Construir arrange com os filtros ativos (se houver)
2199
+ const arrange = this.buildArrangeFromFilters();
2200
+ this.downloadTable(arrange, this.data.conditions || []);
2201
+ }
2202
+ else {
2203
+ // Modo paginado: usar arrange existente (comportamento original)
2204
+ if (this.arrange) {
2205
+ this.downloadTable(this.arrange, this.data.conditions || []);
2206
+ }
2207
+ }
2208
+ }
2209
+ }
2210
+ TableComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: TableComponent, deps: [{ token: i1$1.Router }, { token: TableService }, { token: i1.AngularFirestore }], target: i0.ɵɵFactoryTarget.Component });
2211
+ TableComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: TableComponent, selector: "lib-table", inputs: { data: "data", downloadTable: "downloadTable" }, viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0, template: "<div *ngIf=\"data\" class=\"card-body\">\n <div class=\"flex flex-col justify-between gap-6\">\n <!-- UNIFIED CONTROL PANEL: FILTERS, SORT & ACTIONS -->\n <div\n class=\"rounded-xl border border-gray-200 bg-white p-4 shadow-lg\"\n *ngIf=\"\n data.pagination === true &&\n (dropdownItems.length > 0 ||\n sortableDropdownItems.length > 0 ||\n data.actionButton)\n \"\n >\n <!-- PANEL HEADER: Title, Custom Action, and Global Actions -->\n <div\n class=\"mb-4 flex flex-col items-start justify-between gap-4 border-b-2 border-gray-200 pb-4 md:flex-row md:items-center\"\n >\n <!-- Left Side: Title & Main Action Button -->\n <div class=\"flex flex-wrap items-center gap-4\">\n <div class=\"flex items-center gap-2\">\n <i class=\"fa fa-filter text-xl text-blue-500\"></i>\n <span class=\"text-lg font-semibold text-gray-700\"\n >Filtros e A\u00E7\u00F5es</span\n >\n </div>\n <button\n *ngIf=\"data.actionButton && data.actionButton.condition\"\n [ngClass]=\"\n (data.actionButton.colorClass || 'bg-blue-500') +\n ' flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white hover:opacity-70'\n \"\n [routerLink]=\"data.actionButton.routerLink\"\n (click)=\"\n data.actionButton.method ? data.actionButton.method($event) : null\n \"\n >\n <i\n *ngIf=\"data.actionButton.icon\"\n [class]=\"data.actionButton.icon\"\n ></i>\n {{ data.actionButton.label }}\n </button>\n </div>\n\n <!-- Right Side: Search, Reset, Export -->\n <div\n class=\"flex flex-wrap gap-3\"\n *ngIf=\"\n this.hasFilterableColumn === true || this.hasSortableColumn === true\n \"\n >\n <button\n (click)=\"search()\"\n type=\"button\"\n class=\"flex items-center gap-2 rounded-lg bg-green-600 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-green-700\"\n matTooltip=\"Aplicar filtros\"\n >\n <i class=\"fa fa-search\"></i>\n Pesquisar\n </button>\n\n <button\n (click)=\"resetFilter()\"\n class=\"flex items-center gap-2 rounded-lg bg-red-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-red-600\"\n matTooltip=\"Limpar filtros\"\n >\n <i class=\"fas fa-redo-alt\"></i>\n Resetar\n </button>\n\n <button\n *ngIf=\"data.download !== false && downloadTable\"\n class=\"flex items-center gap-2 rounded-lg bg-orange-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-orange-600\"\n matTooltipPosition=\"above\"\n matTooltip=\"Exportar Tabela\"\n [disabled]=\"\n this.dataSource && this.dataSource.filteredData.length <= 0\n \"\n (click)=\"\n $any(arrange) && downloadTable !== undefined\n ? downloadTable($any(arrange), data.conditions || [])\n : null\n \"\n >\n <i class=\"fa fa-download\"></i>\n Exportar\n </button>\n </div>\n </div>\n\n <!-- FILTERS CONTENT (WITH REFINEMENTS) -->\n <div class=\"mb-4 space-y-3\" *ngIf=\"filtersForm.controls.length > 0\">\n <div\n [formGroup]=\"$any(filterGroup)\"\n *ngFor=\"let filterGroup of filtersForm.controls; let i = index\"\n class=\"flex flex-wrap items-center gap-3 rounded-lg border border-gray-200 p-2\"\n >\n <!-- FILTER TYPE SELECTOR -->\n <div class=\"min-w-[200px] flex-1\" *ngIf=\"dropdownItems.length > 0\">\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Tipo de filtro</mat-label>\n <mat-select\n placeholder=\"Selecione o tipo...\"\n formControlName=\"selectFilter\"\n (selectionChange)=\"onSelectFilterChange()\"\n >\n <mat-option *ngFor=\"let item of dropdownItems\" [value]=\"item\">\n <div class=\"flex items-center gap-2\">\n <i\n [class]=\"item.icon || 'fa fa-filter'\"\n class=\"text-sm text-blue-500\"\n ></i>\n <span>{{ item.title }}</span>\n </div>\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- TEXT FILTER -->\n <div\n class=\"min-w-[200px] flex-1\"\n *ngIf=\"\n $any(filterGroup).get('selectFilter')?.value?.arrange === 'filter'\n \"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-search text-gray-400\"></i>\n <span>{{\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\n \"Filtrar\"\n }}</span>\n </mat-label>\n <input\n (keyup.enter)=\"search()\"\n formControlName=\"typeFilter\"\n matInput\n placeholder=\"Digite para filtrar...\"\n #input\n />\n </mat-form-field>\n </div>\n\n <!-- DROPDOWN FILTER -->\n <div\n class=\"min-w-[200px] flex-1\"\n *ngIf=\"\n $any(filterGroup).get('selectFilter')?.value &&\n $any(filterGroup)\n .get('selectFilter')\n ?.value.hasOwnProperty('items')\n \"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\n \"Selecione\"\n }}</mat-label>\n <mat-select\n placeholder=\"Selecione...\"\n formControlName=\"selectItem\"\n multiple\n >\n <mat-option\n *ngFor=\"\n let item of $any(filterGroup).get('selectFilter')?.value\n .items\n \"\n [value]=\"item\"\n >\n {{ item.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- DATE FILTER -->\n <div\n class=\"min-w-[340px] flex-auto\"\n *ngIf=\"\n $any(filterGroup).get('selectFilter')?.value?.arrange ===\n 'filterByDate'\n \"\n >\n <div\n class=\"flex flex-col items-stretch gap-3 sm:flex-row sm:items-center\"\n >\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-calendar text-gray-400\"></i>\n <span>Data Inicial</span>\n </mat-label>\n <input\n matInput\n (keyup.enter)=\"search()\"\n formControlName=\"initialDate\"\n [dropSpecialCharacters]=\"false\"\n mask=\"d0/M0/0000\"\n placeholder=\"DD/MM/AAAA\"\n maxlength=\"10\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-calendar text-gray-400\"></i>\n <span>Data Final</span>\n </mat-label>\n <input\n (keyup.enter)=\"search()\"\n matInput\n formControlName=\"finalDate\"\n [dropSpecialCharacters]=\"false\"\n mask=\"d0/M0/0000\"\n placeholder=\"DD/MM/AAAA\"\n maxlength=\"10\"\n />\n </mat-form-field>\n </div>\n </div>\n\n <!-- REMOVE FILTER BUTTON -->\n <div *ngIf=\"filtersForm.length > 1\" class=\"ml-auto flex-shrink-0\">\n <button\n (click)=\"removeFilter(i)\"\n class=\"flex h-10 w-10 items-center justify-center rounded-full transition-colors duration-300 hover:bg-red-100\"\n matTooltip=\"Remover filtro\"\n >\n <i class=\"fa fa-trash text-red-500 hover:text-red-600\"></i>\n </button>\n </div>\n </div>\n </div>\n\n <!-- PANEL FOOTER: Add Filter & Sort -->\n <div\n class=\"-mb-2 flex flex-col items-center justify-between gap-4 border-t border-gray-200 pt-4 sm:flex-row\"\n >\n <!-- Add Filter Button -->\n <div *ngIf=\"dropdownItems.length > 0\">\n <button\n (click)=\"addFilter()\"\n class=\"transform rounded-full border-2 border-blue-300 bg-blue-50 px-6 py-2 text-sm font-medium text-blue-600 transition-all duration-300 hover:-translate-y-0.5 hover:border-blue-400 hover:bg-blue-100 hover:shadow-md\"\n matTooltip=\"Adicionar novo filtro\"\n >\n <i class=\"fa fa-plus mr-2\"></i>\n Adicionar Filtro\n </button>\n </div>\n\n <!-- Sort Dropdown -->\n <div\n class=\"w-full sm:w-auto sm:min-w-[250px]\"\n *ngIf=\"sortableDropdownItems.length > 0\"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Ordenar por</mat-label>\n <mat-select placeholder=\"Selecione...\" [formControl]=\"selectSort\">\n <mat-option\n *ngFor=\"let item of sortableDropdownItems\"\n [value]=\"item\"\n >\n <div class=\"flex items-center gap-2\">\n <i class=\"fa fa-sort-alpha-down text-cyan-600\"></i>\n <span>{{ item.title }}</span>\n </div>\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n </div>\n\n <!-- SIMPLE SEARCH (for non-paginated tables) -->\n <div\n class=\"rounded-xl border border-gray-200 bg-white p-4 shadow-lg\"\n *ngIf=\"data.pagination === false && hasFilterableColumn === true\"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-search text-blue-500\"></i>\n Buscar\n </mat-label>\n <input\n matInput\n (keyup.enter)=\"search()\"\n (keyup)=\"applyFilter(filterInput.value)\"\n placeholder=\"Digite para filtrar...\"\n #filterInput\n />\n <mat-icon matSuffix class=\"text-gray-500\">search</mat-icon>\n </mat-form-field>\n <button\n *ngIf=\"data.actionButton\"\n [ngClass]=\"\n (data.actionButton.colorClass || 'bg-blue-500') +\n ' float-right flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white hover:opacity-70'\n \"\n [routerLink]=\"data.actionButton.routerLink\"\n (click)=\"\n data.actionButton.method ? data.actionButton.method($event) : null\n \"\n >\n <i *ngIf=\"data.actionButton.icon\" [class]=\"data.actionButton.icon\"></i>\n {{ data.actionButton.label }}\n </button>\n </div>\n\n <!-- FILTERS PANEL (for non-paginated tables) -->\n <div\n class=\"rounded-xl border border-gray-200 bg-white p-4 shadow-lg\"\n *ngIf=\"data.pagination === false && dropdownItems.length > 0\"\n >\n <!-- FILTERS CONTENT -->\n <div class=\"mb-4 space-y-3\" *ngIf=\"filtersForm.controls.length > 0\">\n <div\n [formGroup]=\"$any(filterGroup)\"\n *ngFor=\"let filterGroup of filtersForm.controls; let i = index\"\n class=\"flex flex-wrap items-center gap-3 rounded-lg border border-gray-200 p-2\"\n >\n <!-- FILTER TYPE SELECTOR -->\n <div class=\"min-w-[200px] flex-1\" *ngIf=\"dropdownItems.length > 0\">\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Tipo de filtro</mat-label>\n <mat-select\n placeholder=\"Selecione o tipo...\"\n formControlName=\"selectFilter\"\n (selectionChange)=\"onSelectFilterChange()\"\n >\n <mat-option *ngFor=\"let item of dropdownItems\" [value]=\"item\">\n <div class=\"flex items-center gap-2\">\n <i\n [class]=\"item.icon || 'fa fa-filter'\"\n class=\"text-sm text-blue-500\"\n ></i>\n <span>{{ item.title }}</span>\n </div>\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- TEXT FILTER -->\n <div\n class=\"min-w-[200px] flex-1\"\n *ngIf=\"\n $any(filterGroup).get('selectFilter')?.value?.arrange === 'filter'\n \"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-search text-gray-400\"></i>\n <span>{{\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\n \"Filtrar\"\n }}</span>\n </mat-label>\n <input\n (keyup.enter)=\"search()\"\n formControlName=\"typeFilter\"\n matInput\n placeholder=\"Digite para filtrar...\"\n #input\n />\n </mat-form-field>\n </div>\n\n <!-- DROPDOWN FILTER -->\n <div\n class=\"min-w-[200px] flex-1\"\n *ngIf=\"\n $any(filterGroup).get('selectFilter')?.value &&\n $any(filterGroup)\n .get('selectFilter')\n ?.value.hasOwnProperty('items')\n \"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\n \"Selecione\"\n }}</mat-label>\n <mat-select\n placeholder=\"Selecione...\"\n formControlName=\"selectItem\"\n multiple\n >\n <mat-option\n *ngFor=\"\n let item of $any(filterGroup).get('selectFilter')?.value\n .items\n \"\n [value]=\"item\"\n >\n {{ item.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- DATE FILTER -->\n <div\n class=\"min-w-[340px] flex-auto\"\n *ngIf=\"\n $any(filterGroup).get('selectFilter')?.value?.arrange ===\n 'filterByDate'\n \"\n >\n <div\n class=\"flex flex-col items-stretch gap-3 sm:flex-row sm:items-center\"\n >\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-calendar text-gray-400\"></i>\n <span>Data Inicial</span>\n </mat-label>\n <input\n matInput\n (keyup.enter)=\"search()\"\n (blur)=\"onDateFilterChange()\"\n formControlName=\"initialDate\"\n [dropSpecialCharacters]=\"false\"\n mask=\"d0/M0/0000\"\n placeholder=\"DD/MM/AAAA\"\n maxlength=\"10\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-calendar text-gray-400\"></i>\n <span>Data Final</span>\n </mat-label>\n <input\n (keyup.enter)=\"search()\"\n (blur)=\"onDateFilterChange()\"\n matInput\n formControlName=\"finalDate\"\n [dropSpecialCharacters]=\"false\"\n mask=\"d0/M0/0000\"\n placeholder=\"DD/MM/AAAA\"\n maxlength=\"10\"\n />\n </mat-form-field>\n </div>\n </div>\n\n <!-- REMOVE FILTER BUTTON -->\n <div *ngIf=\"filtersForm.length > 1\" class=\"ml-auto flex-shrink-0\">\n <button\n (click)=\"removeFilter(i)\"\n class=\"flex h-10 w-10 items-center justify-center rounded-full transition-colors duration-300 hover:bg-red-100\"\n matTooltip=\"Remover filtro\"\n >\n <i class=\"fa fa-trash text-red-500 hover:text-red-600\"></i>\n </button>\n </div>\n </div>\n </div>\n\n <!-- PANEL FOOTER: Add Filter & Actions -->\n <div\n class=\"-mb-2 flex flex-col items-center justify-between gap-4 border-t border-gray-200 pt-4 sm:flex-row\"\n >\n <!-- Add Filter Button -->\n <div *ngIf=\"dropdownItems.length > 0\">\n <button\n (click)=\"addFilter()\"\n class=\"transform rounded-full border-2 border-blue-300 bg-blue-50 px-6 py-2 text-sm font-medium text-blue-600 transition-all duration-300 hover:-translate-y-0.5 hover:border-blue-400 hover:bg-blue-100 hover:shadow-md\"\n matTooltip=\"Adicionar novo filtro\"\n >\n <i class=\"fa fa-plus mr-2\"></i>\n Adicionar Filtro\n </button>\n </div>\n\n <!-- Action Buttons -->\n <div class=\"flex flex-wrap gap-3\">\n <button\n (click)=\"search()\"\n type=\"button\"\n class=\"flex items-center gap-2 rounded-lg bg-green-600 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-green-700\"\n matTooltip=\"Aplicar filtros\"\n >\n <i class=\"fa fa-search\"></i>\n Aplicar\n </button>\n\n <button\n (click)=\"resetFilter()\"\n class=\"flex items-center gap-2 rounded-lg bg-red-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-red-600\"\n matTooltip=\"Limpar filtros\"\n >\n <i class=\"fas fa-redo-alt\"></i>\n Resetar\n </button>\n\n <button\n *ngIf=\"data.download !== false && downloadTable\"\n class=\"flex items-center gap-2 rounded-lg bg-orange-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-orange-600\"\n matTooltipPosition=\"above\"\n matTooltip=\"Exportar Tabela\"\n [disabled]=\"\n this.dataSource && this.dataSource.filteredData.length <= 0\n \"\n (click)=\"handleDownload()\"\n >\n <i class=\"fa fa-download\"></i>\n Exportar\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"flex flex-col\">\n <div\n class=\"mx-auto flex flex-col\"\n *ngIf=\"data.tabs && data.tabs.tabsData && data.tabs.tabsData.length > 0\"\n >\n <!-- Calcular quantos grupos de 6 tabs existem -->\n <ng-container\n *ngFor=\"\n let groupIndex of getTabGroups(data.tabs.tabsData);\n let i = index\n \"\n >\n <div class=\"mx-auto flex flex-row\">\n <ng-container\n *ngFor=\"\n let tab of getTabGroup(data.tabs.tabsData, groupIndex);\n let j = index\n \"\n >\n <button\n class=\"border-2 border-gray-300 bg-gray-200 px-4 py-2 font-medium transition hover:brightness-95\"\n [ngClass]=\"\n isTabSelected(getRealTabIndex(i, j))\n ? 'border-b-0 brightness-110'\n : ''\n \"\n (click)=\"onTableSelected(i, j)\"\n >\n {{ tab.label }}\n <span\n *ngIf=\"tab.counter !== undefined\"\n class=\"ml-2 text-xs font-bold\"\n [ngClass]=\"tab.counterClass\"\n >\n {{ tab.counter }}\n </span>\n </button>\n </ng-container>\n </div>\n </ng-container>\n </div>\n <div class=\"mat-elevation-z8 w-full overflow-x-auto rounded-xl\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n #sort=\"matSort\"\n matSortActive=\"createdAt\"\n matSortDirection=\"desc\"\n >\n <ng-container\n *ngFor=\"let col of data.displayedColumns\"\n matColumnDef=\"{{ col.property }}\"\n >\n <ng-container *matHeaderCellDef>\n <!-- IF THE COLUMN IS NOT SORTABLE, THEN DON'T SHOW THE SORT BUTTONS -->\n <th\n *ngIf=\"!col.isSortable || data.pagination === true\"\n mat-header-cell\n [ngClass]=\"\n (data.color?.bg ? ' ' + $any(data.color).bg : '') +\n (data.color?.text ? ' ' + $any(data.color).text : '')\n \"\n >\n {{ col.title }}\n </th>\n <!-- IF THE COLUMN IS SORTABLE, THEN SHOW THE SORT BUTTONS -->\n <th\n *ngIf=\"col.isSortable && data.pagination === false\"\n mat-header-cell\n mat-sort-header\n [ngClass]=\"\n (data.color?.bg ? ' ' + $any(data.color).bg : '') +\n (data.color?.text ? ' ' + $any(data.color).text : '')\n \"\n >\n {{ col.title }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let row\"\n (click)=\"col.method ? col.method(row) : null\"\n (mouseenter)=\"onCellMouseEnter($event, row, col)\"\n (mouseleave)=\"onCellMouseLeave()\"\n (mousemove)=\"onCellMouseMove($event)\"\n >\n <!-- CHECK IF THE COLUMN MUST BE DISPLAYED -->\n <span *ngIf=\"!col.image && !col.iconClass && !col.method\">\n <ng-container>\n <span\n *ngIf=\"\n col.charLimit &&\n row[col.property] &&\n row[col.property].length > col.charLimit;\n else withinLimit\n \"\n >\n <a\n *ngIf=\"col.hasLink === true\"\n [href]=\"row[col.property]\"\n target=\"_blank\"\n >\n {{ getDisplayValue(col, row) }}\n </a>\n <a\n *ngIf=\"col.hasLink && isString(col.hasLink)\"\n [href]=\"col.hasLink\"\n target=\"_blank\"\n >\n {{ getDisplayValue(col, row) }}\n </a>\n <span\n *ngIf=\"col.hasLink !== true && !isString(col.hasLink)\"\n >\n {{ getDisplayValue(col, row) }}\n </span>\n </span>\n </ng-container>\n <ng-template #withinLimit>\n <a\n *ngIf=\"col.hasLink === true\"\n [href]=\"row[col.property]\"\n target=\"_blank\"\n >\n {{ getDisplayValue(col, row, true) }}\n </a>\n <a\n *ngIf=\"col.hasLink && isString(col.hasLink)\"\n [href]=\"col.hasLink\"\n target=\"_blank\"\n >\n {{ getDisplayValue(col, row, true) }}\n </a>\n <span\n *ngIf=\"col.hasLink !== true && !isString(col.hasLink)\"\n >\n {{ getDisplayValue(col, row, true) }}\n </span>\n </ng-template>\n </span>\n <!------------------- IMAGE ------------------>\n <img\n *ngIf=\"\n col.image && col.image.path && !col.iconClass && !col.method\n \"\n [src]=\"col.image.path + '/' + row[col.property]\"\n [ngClass]=\"col.image.class\"\n alt=\"Imagem\"\n />\n <img\n *ngIf=\"\n col.image && col.image.url && !col.iconClass && !col.method\n \"\n [src]=\"row[col.property]\"\n [ngClass]=\"col.image.class\"\n alt=\"Imagem\"\n />\n <ng-container *ngIf=\"col.iconClass\">\n <button\n *ngFor=\"let iconClass of col.iconClass\"\n (click)=\"\n iconClass.buttonMethod\n ? iconClass.buttonMethod(row, $event)\n : $event.stopPropagation()\n \"\n >\n <span\n [ngClass]=\"iconClass.class\"\n *ngIf=\"\n iconClass.condition === undefined ||\n (iconClass.condition !== undefined &&\n $any(iconClass.condition)(row))\n \"\n >{{ iconClass.text }}</span\n >\n </button>\n </ng-container>\n </td>\n </ng-container>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"columnProperties\"></tr>\n <tr\n [ngClass]=\"{\n 'example-element-row': data.isNotClickable === true,\n 'example-element-row cursor-pointer': !data.isNotClickable\n }\"\n mat-row\n *matRowDef=\"let row; columns: columnProperties\"\n (click)=\"goToDetails(row)\"\n ></tr>\n\n <!-- ROW SHOWN WHEN THERE IS NO MATCHING DATA. -->\n <tr class=\"mat-row\" *matNoDataRow>\n <td *ngIf=\"!isLoading\" class=\"mat-cell p-4\" colspan=\"4\">\n Nenhum resultado encontrado para a busca\n </td>\n </tr>\n </table>\n\n <div class=\"flex justify-center\" *ngIf=\"isLoading\">\n <mat-spinner></mat-spinner>\n </div>\n\n <div class=\"paginator-container\">\n <mat-paginator\n #paginator\n [pageSizeOptions]=\"[25, 50, 100]\"\n [pageSize]=\"pageSize\"\n [length]=\"totalItems\"\n showFirstLastButtons\n aria-label=\"Select page of periodic elements\"\n (page)=\"onPageChange($event)\"\n [ngClass]=\"{\n 'hide-length':\n ['filter', 'filterByDate', 'equals'].includes(\n this.currentArrange\n ) || this.data.filterFn,\n 'hide-next-button': !hasNextPage && data.pagination === true,\n 'hide-last-button':\n (!hasNextPage && data.pagination === true) || this.data.filterFn\n }\"\n >\n </mat-paginator>\n <div\n *ngIf=\"\n !isLoading &&\n dataSource?.data &&\n dataSource.data.length > 0 &&\n data?.filterFn\n \"\n class=\"page-number-display\"\n >\n {{ currentPageNumber }}\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- TOOLTIP PERSONALIZADO -->\n <div\n *ngIf=\"showTooltip\"\n class=\"fixed z-50 max-w-md break-words rounded-lg bg-gray-800 px-3 py-2 text-sm text-white shadow-lg\"\n [style.left.px]=\"tooltipPosition.x\"\n [style.top.px]=\"tooltipPosition.y\"\n [style.pointer-events]=\"'none'\"\n >\n {{ tooltipContent }}\n </div>\n</div>\n", styles: ["::ng-deep .hide-length .mat-mdc-paginator-range-label{display:none}::ng-deep .hide-next-button .mat-mdc-tooltip-trigger.mat-mdc-paginator-navigation-next.mdc-icon-button.mat-mdc-icon-button.mat-unthemed.mat-mdc-button-base{visibility:hidden}::ng-deep .hide-next-button .mat-mdc-tooltip-trigger.mat-mdc-paginator-navigation-last.mdc-icon-button.mat-mdc-icon-button.mat-unthemed.mat-mdc-button-base.ng-star-inserted{visibility:hidden}::ng-deep .mat-mdc-text-field-wrapper.mdc-text-field.ng-tns-c162-1.mdc-text-field--filled{width:25dvw}::ng-deep .custom-filter .mat-mdc-text-field-wrapper{width:20dvw;max-width:300px}\n"], dependencies: [{ kind: "directive", type: i4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i5.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i5.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i5.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i5.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i5.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: i6.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i6.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i6.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i6.MatColumnDef, selector: "[matColumnDef]", inputs: ["sticky", "matColumnDef"] }, { kind: "directive", type: i6.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i6.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i6.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i6.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i6.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i6.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i6.MatNoDataRow, selector: "ng-template[matNoDataRow]" }, { kind: "component", type: i7.MatPaginator, selector: "mat-paginator", inputs: ["disabled"], exportAs: ["matPaginator"] }, { kind: "directive", type: i8.MatSort, selector: "[matSort]", inputs: ["matSortDisabled", "matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i8.MatSortHeader, selector: "[mat-sort-header]", inputs: ["disabled", "mat-sort-header", "arrowPosition", "start", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "component", type: i9.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i9.MatLabel, selector: "mat-label" }, { kind: "directive", type: i9.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i10.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i11.MatSelect, selector: "mat-select", inputs: ["disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator"], exportAs: ["matSelect"] }, { kind: "component", type: i12.MatOption, selector: "mat-option", exportAs: ["matOption"] }, { kind: "directive", type: i13.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }, { kind: "component", type: i14.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: i15.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i16.NgxMaskDirective, selector: "input[mask], textarea[mask]", inputs: ["mask", "specialCharacters", "patterns", "prefix", "suffix", "thousandSeparator", "decimalMarker", "dropSpecialCharacters", "hiddenInput", "showMaskTyped", "placeHolderCharacter", "shownMaskExpression", "showTemplate", "clearIfNotMatch", "validation", "separatorLimit", "allowNegativeNumbers", "leadZeroDateTime", "leadZero", "triggerOnMaskChange", "apm", "inputTransformFn", "outputTransformFn", "keepCharacterPositions"], outputs: ["maskFilled"], exportAs: ["mask", "ngxMask"] }] });
2212
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: TableComponent, decorators: [{
2213
+ type: Component,
2214
+ args: [{ selector: 'lib-table', template: "<div *ngIf=\"data\" class=\"card-body\">\n <div class=\"flex flex-col justify-between gap-6\">\n <!-- UNIFIED CONTROL PANEL: FILTERS, SORT & ACTIONS -->\n <div\n class=\"rounded-xl border border-gray-200 bg-white p-4 shadow-lg\"\n *ngIf=\"\n data.pagination === true &&\n (dropdownItems.length > 0 ||\n sortableDropdownItems.length > 0 ||\n data.actionButton)\n \"\n >\n <!-- PANEL HEADER: Title, Custom Action, and Global Actions -->\n <div\n class=\"mb-4 flex flex-col items-start justify-between gap-4 border-b-2 border-gray-200 pb-4 md:flex-row md:items-center\"\n >\n <!-- Left Side: Title & Main Action Button -->\n <div class=\"flex flex-wrap items-center gap-4\">\n <div class=\"flex items-center gap-2\">\n <i class=\"fa fa-filter text-xl text-blue-500\"></i>\n <span class=\"text-lg font-semibold text-gray-700\"\n >Filtros e A\u00E7\u00F5es</span\n >\n </div>\n <button\n *ngIf=\"data.actionButton && data.actionButton.condition\"\n [ngClass]=\"\n (data.actionButton.colorClass || 'bg-blue-500') +\n ' flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white hover:opacity-70'\n \"\n [routerLink]=\"data.actionButton.routerLink\"\n (click)=\"\n data.actionButton.method ? data.actionButton.method($event) : null\n \"\n >\n <i\n *ngIf=\"data.actionButton.icon\"\n [class]=\"data.actionButton.icon\"\n ></i>\n {{ data.actionButton.label }}\n </button>\n </div>\n\n <!-- Right Side: Search, Reset, Export -->\n <div\n class=\"flex flex-wrap gap-3\"\n *ngIf=\"\n this.hasFilterableColumn === true || this.hasSortableColumn === true\n \"\n >\n <button\n (click)=\"search()\"\n type=\"button\"\n class=\"flex items-center gap-2 rounded-lg bg-green-600 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-green-700\"\n matTooltip=\"Aplicar filtros\"\n >\n <i class=\"fa fa-search\"></i>\n Pesquisar\n </button>\n\n <button\n (click)=\"resetFilter()\"\n class=\"flex items-center gap-2 rounded-lg bg-red-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-red-600\"\n matTooltip=\"Limpar filtros\"\n >\n <i class=\"fas fa-redo-alt\"></i>\n Resetar\n </button>\n\n <button\n *ngIf=\"data.download !== false && downloadTable\"\n class=\"flex items-center gap-2 rounded-lg bg-orange-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-orange-600\"\n matTooltipPosition=\"above\"\n matTooltip=\"Exportar Tabela\"\n [disabled]=\"\n this.dataSource && this.dataSource.filteredData.length <= 0\n \"\n (click)=\"\n $any(arrange) && downloadTable !== undefined\n ? downloadTable($any(arrange), data.conditions || [])\n : null\n \"\n >\n <i class=\"fa fa-download\"></i>\n Exportar\n </button>\n </div>\n </div>\n\n <!-- FILTERS CONTENT (WITH REFINEMENTS) -->\n <div class=\"mb-4 space-y-3\" *ngIf=\"filtersForm.controls.length > 0\">\n <div\n [formGroup]=\"$any(filterGroup)\"\n *ngFor=\"let filterGroup of filtersForm.controls; let i = index\"\n class=\"flex flex-wrap items-center gap-3 rounded-lg border border-gray-200 p-2\"\n >\n <!-- FILTER TYPE SELECTOR -->\n <div class=\"min-w-[200px] flex-1\" *ngIf=\"dropdownItems.length > 0\">\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Tipo de filtro</mat-label>\n <mat-select\n placeholder=\"Selecione o tipo...\"\n formControlName=\"selectFilter\"\n (selectionChange)=\"onSelectFilterChange()\"\n >\n <mat-option *ngFor=\"let item of dropdownItems\" [value]=\"item\">\n <div class=\"flex items-center gap-2\">\n <i\n [class]=\"item.icon || 'fa fa-filter'\"\n class=\"text-sm text-blue-500\"\n ></i>\n <span>{{ item.title }}</span>\n </div>\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- TEXT FILTER -->\n <div\n class=\"min-w-[200px] flex-1\"\n *ngIf=\"\n $any(filterGroup).get('selectFilter')?.value?.arrange === 'filter'\n \"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-search text-gray-400\"></i>\n <span>{{\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\n \"Filtrar\"\n }}</span>\n </mat-label>\n <input\n (keyup.enter)=\"search()\"\n formControlName=\"typeFilter\"\n matInput\n placeholder=\"Digite para filtrar...\"\n #input\n />\n </mat-form-field>\n </div>\n\n <!-- DROPDOWN FILTER -->\n <div\n class=\"min-w-[200px] flex-1\"\n *ngIf=\"\n $any(filterGroup).get('selectFilter')?.value &&\n $any(filterGroup)\n .get('selectFilter')\n ?.value.hasOwnProperty('items')\n \"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\n \"Selecione\"\n }}</mat-label>\n <mat-select\n placeholder=\"Selecione...\"\n formControlName=\"selectItem\"\n multiple\n >\n <mat-option\n *ngFor=\"\n let item of $any(filterGroup).get('selectFilter')?.value\n .items\n \"\n [value]=\"item\"\n >\n {{ item.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- DATE FILTER -->\n <div\n class=\"min-w-[340px] flex-auto\"\n *ngIf=\"\n $any(filterGroup).get('selectFilter')?.value?.arrange ===\n 'filterByDate'\n \"\n >\n <div\n class=\"flex flex-col items-stretch gap-3 sm:flex-row sm:items-center\"\n >\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-calendar text-gray-400\"></i>\n <span>Data Inicial</span>\n </mat-label>\n <input\n matInput\n (keyup.enter)=\"search()\"\n formControlName=\"initialDate\"\n [dropSpecialCharacters]=\"false\"\n mask=\"d0/M0/0000\"\n placeholder=\"DD/MM/AAAA\"\n maxlength=\"10\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-calendar text-gray-400\"></i>\n <span>Data Final</span>\n </mat-label>\n <input\n (keyup.enter)=\"search()\"\n matInput\n formControlName=\"finalDate\"\n [dropSpecialCharacters]=\"false\"\n mask=\"d0/M0/0000\"\n placeholder=\"DD/MM/AAAA\"\n maxlength=\"10\"\n />\n </mat-form-field>\n </div>\n </div>\n\n <!-- REMOVE FILTER BUTTON -->\n <div *ngIf=\"filtersForm.length > 1\" class=\"ml-auto flex-shrink-0\">\n <button\n (click)=\"removeFilter(i)\"\n class=\"flex h-10 w-10 items-center justify-center rounded-full transition-colors duration-300 hover:bg-red-100\"\n matTooltip=\"Remover filtro\"\n >\n <i class=\"fa fa-trash text-red-500 hover:text-red-600\"></i>\n </button>\n </div>\n </div>\n </div>\n\n <!-- PANEL FOOTER: Add Filter & Sort -->\n <div\n class=\"-mb-2 flex flex-col items-center justify-between gap-4 border-t border-gray-200 pt-4 sm:flex-row\"\n >\n <!-- Add Filter Button -->\n <div *ngIf=\"dropdownItems.length > 0\">\n <button\n (click)=\"addFilter()\"\n class=\"transform rounded-full border-2 border-blue-300 bg-blue-50 px-6 py-2 text-sm font-medium text-blue-600 transition-all duration-300 hover:-translate-y-0.5 hover:border-blue-400 hover:bg-blue-100 hover:shadow-md\"\n matTooltip=\"Adicionar novo filtro\"\n >\n <i class=\"fa fa-plus mr-2\"></i>\n Adicionar Filtro\n </button>\n </div>\n\n <!-- Sort Dropdown -->\n <div\n class=\"w-full sm:w-auto sm:min-w-[250px]\"\n *ngIf=\"sortableDropdownItems.length > 0\"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Ordenar por</mat-label>\n <mat-select placeholder=\"Selecione...\" [formControl]=\"selectSort\">\n <mat-option\n *ngFor=\"let item of sortableDropdownItems\"\n [value]=\"item\"\n >\n <div class=\"flex items-center gap-2\">\n <i class=\"fa fa-sort-alpha-down text-cyan-600\"></i>\n <span>{{ item.title }}</span>\n </div>\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n </div>\n </div>\n\n <!-- SIMPLE SEARCH (for non-paginated tables) -->\n <div\n class=\"rounded-xl border border-gray-200 bg-white p-4 shadow-lg\"\n *ngIf=\"data.pagination === false && hasFilterableColumn === true\"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-search text-blue-500\"></i>\n Buscar\n </mat-label>\n <input\n matInput\n (keyup.enter)=\"search()\"\n (keyup)=\"applyFilter(filterInput.value)\"\n placeholder=\"Digite para filtrar...\"\n #filterInput\n />\n <mat-icon matSuffix class=\"text-gray-500\">search</mat-icon>\n </mat-form-field>\n <button\n *ngIf=\"data.actionButton\"\n [ngClass]=\"\n (data.actionButton.colorClass || 'bg-blue-500') +\n ' float-right flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-white hover:opacity-70'\n \"\n [routerLink]=\"data.actionButton.routerLink\"\n (click)=\"\n data.actionButton.method ? data.actionButton.method($event) : null\n \"\n >\n <i *ngIf=\"data.actionButton.icon\" [class]=\"data.actionButton.icon\"></i>\n {{ data.actionButton.label }}\n </button>\n </div>\n\n <!-- FILTERS PANEL (for non-paginated tables) -->\n <div\n class=\"rounded-xl border border-gray-200 bg-white p-4 shadow-lg\"\n *ngIf=\"data.pagination === false && dropdownItems.length > 0\"\n >\n <!-- FILTERS CONTENT -->\n <div class=\"mb-4 space-y-3\" *ngIf=\"filtersForm.controls.length > 0\">\n <div\n [formGroup]=\"$any(filterGroup)\"\n *ngFor=\"let filterGroup of filtersForm.controls; let i = index\"\n class=\"flex flex-wrap items-center gap-3 rounded-lg border border-gray-200 p-2\"\n >\n <!-- FILTER TYPE SELECTOR -->\n <div class=\"min-w-[200px] flex-1\" *ngIf=\"dropdownItems.length > 0\">\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>Tipo de filtro</mat-label>\n <mat-select\n placeholder=\"Selecione o tipo...\"\n formControlName=\"selectFilter\"\n (selectionChange)=\"onSelectFilterChange()\"\n >\n <mat-option *ngFor=\"let item of dropdownItems\" [value]=\"item\">\n <div class=\"flex items-center gap-2\">\n <i\n [class]=\"item.icon || 'fa fa-filter'\"\n class=\"text-sm text-blue-500\"\n ></i>\n <span>{{ item.title }}</span>\n </div>\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- TEXT FILTER -->\n <div\n class=\"min-w-[200px] flex-1\"\n *ngIf=\"\n $any(filterGroup).get('selectFilter')?.value?.arrange === 'filter'\n \"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-search text-gray-400\"></i>\n <span>{{\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\n \"Filtrar\"\n }}</span>\n </mat-label>\n <input\n (keyup.enter)=\"search()\"\n formControlName=\"typeFilter\"\n matInput\n placeholder=\"Digite para filtrar...\"\n #input\n />\n </mat-form-field>\n </div>\n\n <!-- DROPDOWN FILTER -->\n <div\n class=\"min-w-[200px] flex-1\"\n *ngIf=\"\n $any(filterGroup).get('selectFilter')?.value &&\n $any(filterGroup)\n .get('selectFilter')\n ?.value.hasOwnProperty('items')\n \"\n >\n <mat-form-field appearance=\"outline\" class=\"w-full\">\n <mat-label>{{\n $any(filterGroup).get(\"selectFilter\")?.value?.title ||\n \"Selecione\"\n }}</mat-label>\n <mat-select\n placeholder=\"Selecione...\"\n formControlName=\"selectItem\"\n multiple\n >\n <mat-option\n *ngFor=\"\n let item of $any(filterGroup).get('selectFilter')?.value\n .items\n \"\n [value]=\"item\"\n >\n {{ item.label }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- DATE FILTER -->\n <div\n class=\"min-w-[340px] flex-auto\"\n *ngIf=\"\n $any(filterGroup).get('selectFilter')?.value?.arrange ===\n 'filterByDate'\n \"\n >\n <div\n class=\"flex flex-col items-stretch gap-3 sm:flex-row sm:items-center\"\n >\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-calendar text-gray-400\"></i>\n <span>Data Inicial</span>\n </mat-label>\n <input\n matInput\n (keyup.enter)=\"search()\"\n (blur)=\"onDateFilterChange()\"\n formControlName=\"initialDate\"\n [dropSpecialCharacters]=\"false\"\n mask=\"d0/M0/0000\"\n placeholder=\"DD/MM/AAAA\"\n maxlength=\"10\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"flex-1\">\n <mat-label class=\"flex items-center gap-2\">\n <i class=\"fa fa-calendar text-gray-400\"></i>\n <span>Data Final</span>\n </mat-label>\n <input\n (keyup.enter)=\"search()\"\n (blur)=\"onDateFilterChange()\"\n matInput\n formControlName=\"finalDate\"\n [dropSpecialCharacters]=\"false\"\n mask=\"d0/M0/0000\"\n placeholder=\"DD/MM/AAAA\"\n maxlength=\"10\"\n />\n </mat-form-field>\n </div>\n </div>\n\n <!-- REMOVE FILTER BUTTON -->\n <div *ngIf=\"filtersForm.length > 1\" class=\"ml-auto flex-shrink-0\">\n <button\n (click)=\"removeFilter(i)\"\n class=\"flex h-10 w-10 items-center justify-center rounded-full transition-colors duration-300 hover:bg-red-100\"\n matTooltip=\"Remover filtro\"\n >\n <i class=\"fa fa-trash text-red-500 hover:text-red-600\"></i>\n </button>\n </div>\n </div>\n </div>\n\n <!-- PANEL FOOTER: Add Filter & Actions -->\n <div\n class=\"-mb-2 flex flex-col items-center justify-between gap-4 border-t border-gray-200 pt-4 sm:flex-row\"\n >\n <!-- Add Filter Button -->\n <div *ngIf=\"dropdownItems.length > 0\">\n <button\n (click)=\"addFilter()\"\n class=\"transform rounded-full border-2 border-blue-300 bg-blue-50 px-6 py-2 text-sm font-medium text-blue-600 transition-all duration-300 hover:-translate-y-0.5 hover:border-blue-400 hover:bg-blue-100 hover:shadow-md\"\n matTooltip=\"Adicionar novo filtro\"\n >\n <i class=\"fa fa-plus mr-2\"></i>\n Adicionar Filtro\n </button>\n </div>\n\n <!-- Action Buttons -->\n <div class=\"flex flex-wrap gap-3\">\n <button\n (click)=\"search()\"\n type=\"button\"\n class=\"flex items-center gap-2 rounded-lg bg-green-600 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-green-700\"\n matTooltip=\"Aplicar filtros\"\n >\n <i class=\"fa fa-search\"></i>\n Aplicar\n </button>\n\n <button\n (click)=\"resetFilter()\"\n class=\"flex items-center gap-2 rounded-lg bg-red-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-red-600\"\n matTooltip=\"Limpar filtros\"\n >\n <i class=\"fas fa-redo-alt\"></i>\n Resetar\n </button>\n\n <button\n *ngIf=\"data.download !== false && downloadTable\"\n class=\"flex items-center gap-2 rounded-lg bg-orange-500 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-orange-600\"\n matTooltipPosition=\"above\"\n matTooltip=\"Exportar Tabela\"\n [disabled]=\"\n this.dataSource && this.dataSource.filteredData.length <= 0\n \"\n (click)=\"handleDownload()\"\n >\n <i class=\"fa fa-download\"></i>\n Exportar\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"flex flex-col\">\n <div\n class=\"mx-auto flex flex-col\"\n *ngIf=\"data.tabs && data.tabs.tabsData && data.tabs.tabsData.length > 0\"\n >\n <!-- Calcular quantos grupos de 6 tabs existem -->\n <ng-container\n *ngFor=\"\n let groupIndex of getTabGroups(data.tabs.tabsData);\n let i = index\n \"\n >\n <div class=\"mx-auto flex flex-row\">\n <ng-container\n *ngFor=\"\n let tab of getTabGroup(data.tabs.tabsData, groupIndex);\n let j = index\n \"\n >\n <button\n class=\"border-2 border-gray-300 bg-gray-200 px-4 py-2 font-medium transition hover:brightness-95\"\n [ngClass]=\"\n isTabSelected(getRealTabIndex(i, j))\n ? 'border-b-0 brightness-110'\n : ''\n \"\n (click)=\"onTableSelected(i, j)\"\n >\n {{ tab.label }}\n <span\n *ngIf=\"tab.counter !== undefined\"\n class=\"ml-2 text-xs font-bold\"\n [ngClass]=\"tab.counterClass\"\n >\n {{ tab.counter }}\n </span>\n </button>\n </ng-container>\n </div>\n </ng-container>\n </div>\n <div class=\"mat-elevation-z8 w-full overflow-x-auto rounded-xl\">\n <table\n mat-table\n [dataSource]=\"dataSource\"\n matSort\n #sort=\"matSort\"\n matSortActive=\"createdAt\"\n matSortDirection=\"desc\"\n >\n <ng-container\n *ngFor=\"let col of data.displayedColumns\"\n matColumnDef=\"{{ col.property }}\"\n >\n <ng-container *matHeaderCellDef>\n <!-- IF THE COLUMN IS NOT SORTABLE, THEN DON'T SHOW THE SORT BUTTONS -->\n <th\n *ngIf=\"!col.isSortable || data.pagination === true\"\n mat-header-cell\n [ngClass]=\"\n (data.color?.bg ? ' ' + $any(data.color).bg : '') +\n (data.color?.text ? ' ' + $any(data.color).text : '')\n \"\n >\n {{ col.title }}\n </th>\n <!-- IF THE COLUMN IS SORTABLE, THEN SHOW THE SORT BUTTONS -->\n <th\n *ngIf=\"col.isSortable && data.pagination === false\"\n mat-header-cell\n mat-sort-header\n [ngClass]=\"\n (data.color?.bg ? ' ' + $any(data.color).bg : '') +\n (data.color?.text ? ' ' + $any(data.color).text : '')\n \"\n >\n {{ col.title }}\n </th>\n <td\n mat-cell\n *matCellDef=\"let row\"\n (click)=\"col.method ? col.method(row) : null\"\n (mouseenter)=\"onCellMouseEnter($event, row, col)\"\n (mouseleave)=\"onCellMouseLeave()\"\n (mousemove)=\"onCellMouseMove($event)\"\n >\n <!-- CHECK IF THE COLUMN MUST BE DISPLAYED -->\n <span *ngIf=\"!col.image && !col.iconClass && !col.method\">\n <ng-container>\n <span\n *ngIf=\"\n col.charLimit &&\n row[col.property] &&\n row[col.property].length > col.charLimit;\n else withinLimit\n \"\n >\n <a\n *ngIf=\"col.hasLink === true\"\n [href]=\"row[col.property]\"\n target=\"_blank\"\n >\n {{ getDisplayValue(col, row) }}\n </a>\n <a\n *ngIf=\"col.hasLink && isString(col.hasLink)\"\n [href]=\"col.hasLink\"\n target=\"_blank\"\n >\n {{ getDisplayValue(col, row) }}\n </a>\n <span\n *ngIf=\"col.hasLink !== true && !isString(col.hasLink)\"\n >\n {{ getDisplayValue(col, row) }}\n </span>\n </span>\n </ng-container>\n <ng-template #withinLimit>\n <a\n *ngIf=\"col.hasLink === true\"\n [href]=\"row[col.property]\"\n target=\"_blank\"\n >\n {{ getDisplayValue(col, row, true) }}\n </a>\n <a\n *ngIf=\"col.hasLink && isString(col.hasLink)\"\n [href]=\"col.hasLink\"\n target=\"_blank\"\n >\n {{ getDisplayValue(col, row, true) }}\n </a>\n <span\n *ngIf=\"col.hasLink !== true && !isString(col.hasLink)\"\n >\n {{ getDisplayValue(col, row, true) }}\n </span>\n </ng-template>\n </span>\n <!------------------- IMAGE ------------------>\n <img\n *ngIf=\"\n col.image && col.image.path && !col.iconClass && !col.method\n \"\n [src]=\"col.image.path + '/' + row[col.property]\"\n [ngClass]=\"col.image.class\"\n alt=\"Imagem\"\n />\n <img\n *ngIf=\"\n col.image && col.image.url && !col.iconClass && !col.method\n \"\n [src]=\"row[col.property]\"\n [ngClass]=\"col.image.class\"\n alt=\"Imagem\"\n />\n <ng-container *ngIf=\"col.iconClass\">\n <button\n *ngFor=\"let iconClass of col.iconClass\"\n (click)=\"\n iconClass.buttonMethod\n ? iconClass.buttonMethod(row, $event)\n : $event.stopPropagation()\n \"\n >\n <span\n [ngClass]=\"iconClass.class\"\n *ngIf=\"\n iconClass.condition === undefined ||\n (iconClass.condition !== undefined &&\n $any(iconClass.condition)(row))\n \"\n >{{ iconClass.text }}</span\n >\n </button>\n </ng-container>\n </td>\n </ng-container>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"columnProperties\"></tr>\n <tr\n [ngClass]=\"{\n 'example-element-row': data.isNotClickable === true,\n 'example-element-row cursor-pointer': !data.isNotClickable\n }\"\n mat-row\n *matRowDef=\"let row; columns: columnProperties\"\n (click)=\"goToDetails(row)\"\n ></tr>\n\n <!-- ROW SHOWN WHEN THERE IS NO MATCHING DATA. -->\n <tr class=\"mat-row\" *matNoDataRow>\n <td *ngIf=\"!isLoading\" class=\"mat-cell p-4\" colspan=\"4\">\n Nenhum resultado encontrado para a busca\n </td>\n </tr>\n </table>\n\n <div class=\"flex justify-center\" *ngIf=\"isLoading\">\n <mat-spinner></mat-spinner>\n </div>\n\n <div class=\"paginator-container\">\n <mat-paginator\n #paginator\n [pageSizeOptions]=\"[25, 50, 100]\"\n [pageSize]=\"pageSize\"\n [length]=\"totalItems\"\n showFirstLastButtons\n aria-label=\"Select page of periodic elements\"\n (page)=\"onPageChange($event)\"\n [ngClass]=\"{\n 'hide-length':\n ['filter', 'filterByDate', 'equals'].includes(\n this.currentArrange\n ) || this.data.filterFn,\n 'hide-next-button': !hasNextPage && data.pagination === true,\n 'hide-last-button':\n (!hasNextPage && data.pagination === true) || this.data.filterFn\n }\"\n >\n </mat-paginator>\n <div\n *ngIf=\"\n !isLoading &&\n dataSource?.data &&\n dataSource.data.length > 0 &&\n data?.filterFn\n \"\n class=\"page-number-display\"\n >\n {{ currentPageNumber }}\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- TOOLTIP PERSONALIZADO -->\n <div\n *ngIf=\"showTooltip\"\n class=\"fixed z-50 max-w-md break-words rounded-lg bg-gray-800 px-3 py-2 text-sm text-white shadow-lg\"\n [style.left.px]=\"tooltipPosition.x\"\n [style.top.px]=\"tooltipPosition.y\"\n [style.pointer-events]=\"'none'\"\n >\n {{ tooltipContent }}\n </div>\n</div>\n", styles: ["::ng-deep .hide-length .mat-mdc-paginator-range-label{display:none}::ng-deep .hide-next-button .mat-mdc-tooltip-trigger.mat-mdc-paginator-navigation-next.mdc-icon-button.mat-mdc-icon-button.mat-unthemed.mat-mdc-button-base{visibility:hidden}::ng-deep .hide-next-button .mat-mdc-tooltip-trigger.mat-mdc-paginator-navigation-last.mdc-icon-button.mat-mdc-icon-button.mat-unthemed.mat-mdc-button-base.ng-star-inserted{visibility:hidden}::ng-deep .mat-mdc-text-field-wrapper.mdc-text-field.ng-tns-c162-1.mdc-text-field--filled{width:25dvw}::ng-deep .custom-filter .mat-mdc-text-field-wrapper{width:20dvw;max-width:300px}\n"] }]
2215
+ }], ctorParameters: function () { return [{ type: i1$1.Router }, { type: TableService }, { type: i1.AngularFirestore }]; }, propDecorators: { data: [{
2216
+ type: Input
2217
+ }], downloadTable: [{
2218
+ type: Input
2219
+ }], paginator: [{
2220
+ type: ViewChild,
2221
+ args: [MatPaginator]
2222
+ }], sort: [{
2223
+ type: ViewChild,
2224
+ args: [MatSort]
2063
2225
  }] } });
2064
2226
 
2065
- class NgFirebaseTableKxpModule {
2066
- }
2067
- NgFirebaseTableKxpModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2068
- NgFirebaseTableKxpModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpModule, declarations: [NgFirebaseTableKxpComponent, TableComponent], imports: [CommonModule,
2069
- ReactiveFormsModule,
2070
- FormsModule,
2071
- RouterModule,
2072
- MatTableModule,
2073
- MatPaginatorModule,
2074
- MatSortModule,
2075
- MatFormFieldModule,
2076
- MatInputModule,
2077
- MatSelectModule,
2078
- MatTooltipModule,
2079
- MatProgressSpinnerModule,
2080
- MatIconModule,
2081
- MatDialogModule,
2082
- NgxMaskDirective,
2083
- NgxMaskPipe], exports: [NgFirebaseTableKxpComponent, TableComponent] });
2084
- NgFirebaseTableKxpModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpModule, providers: [provideNgxMask()], imports: [CommonModule,
2085
- ReactiveFormsModule,
2086
- FormsModule,
2087
- RouterModule,
2088
- MatTableModule,
2089
- MatPaginatorModule,
2090
- MatSortModule,
2091
- MatFormFieldModule,
2092
- MatInputModule,
2093
- MatSelectModule,
2094
- MatTooltipModule,
2095
- MatProgressSpinnerModule,
2096
- MatIconModule,
2097
- MatDialogModule] });
2098
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpModule, decorators: [{
2099
- type: NgModule,
2100
- args: [{
2101
- declarations: [NgFirebaseTableKxpComponent, TableComponent],
2102
- imports: [
2103
- CommonModule,
2104
- ReactiveFormsModule,
2105
- FormsModule,
2106
- RouterModule,
2107
- MatTableModule,
2108
- MatPaginatorModule,
2109
- MatSortModule,
2110
- MatFormFieldModule,
2111
- MatInputModule,
2112
- MatSelectModule,
2113
- MatTooltipModule,
2114
- MatProgressSpinnerModule,
2115
- MatIconModule,
2116
- MatDialogModule,
2117
- NgxMaskDirective,
2118
- NgxMaskPipe,
2119
- ],
2120
- exports: [NgFirebaseTableKxpComponent, TableComponent],
2121
- providers: [provideNgxMask()],
2122
- }]
2227
+ class NgFirebaseTableKxpModule {
2228
+ }
2229
+ NgFirebaseTableKxpModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2230
+ NgFirebaseTableKxpModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpModule, declarations: [NgFirebaseTableKxpComponent, TableComponent], imports: [CommonModule,
2231
+ ReactiveFormsModule,
2232
+ FormsModule,
2233
+ RouterModule,
2234
+ MatTableModule,
2235
+ MatPaginatorModule,
2236
+ MatSortModule,
2237
+ MatFormFieldModule,
2238
+ MatInputModule,
2239
+ MatSelectModule,
2240
+ MatTooltipModule,
2241
+ MatProgressSpinnerModule,
2242
+ MatIconModule,
2243
+ MatDialogModule,
2244
+ NgxMaskDirective,
2245
+ NgxMaskPipe], exports: [NgFirebaseTableKxpComponent, TableComponent] });
2246
+ NgFirebaseTableKxpModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpModule, providers: [provideNgxMask()], imports: [CommonModule,
2247
+ ReactiveFormsModule,
2248
+ FormsModule,
2249
+ RouterModule,
2250
+ MatTableModule,
2251
+ MatPaginatorModule,
2252
+ MatSortModule,
2253
+ MatFormFieldModule,
2254
+ MatInputModule,
2255
+ MatSelectModule,
2256
+ MatTooltipModule,
2257
+ MatProgressSpinnerModule,
2258
+ MatIconModule,
2259
+ MatDialogModule] });
2260
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpModule, decorators: [{
2261
+ type: NgModule,
2262
+ args: [{
2263
+ declarations: [NgFirebaseTableKxpComponent, TableComponent],
2264
+ imports: [
2265
+ CommonModule,
2266
+ ReactiveFormsModule,
2267
+ FormsModule,
2268
+ RouterModule,
2269
+ MatTableModule,
2270
+ MatPaginatorModule,
2271
+ MatSortModule,
2272
+ MatFormFieldModule,
2273
+ MatInputModule,
2274
+ MatSelectModule,
2275
+ MatTooltipModule,
2276
+ MatProgressSpinnerModule,
2277
+ MatIconModule,
2278
+ MatDialogModule,
2279
+ NgxMaskDirective,
2280
+ NgxMaskPipe,
2281
+ ],
2282
+ exports: [NgFirebaseTableKxpComponent, TableComponent],
2283
+ providers: [provideNgxMask()],
2284
+ }]
2123
2285
  }] });
2124
2286
 
2125
- class NgFirebaseTableKxpService {
2126
- constructor() { }
2127
- }
2128
- NgFirebaseTableKxpService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2129
- NgFirebaseTableKxpService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpService, providedIn: 'root' });
2130
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpService, decorators: [{
2131
- type: Injectable,
2132
- args: [{
2133
- providedIn: 'root',
2134
- }]
2287
+ class NgFirebaseTableKxpService {
2288
+ constructor() { }
2289
+ }
2290
+ NgFirebaseTableKxpService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2291
+ NgFirebaseTableKxpService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpService, providedIn: 'root' });
2292
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgFirebaseTableKxpService, decorators: [{
2293
+ type: Injectable,
2294
+ args: [{
2295
+ providedIn: 'root',
2296
+ }]
2135
2297
  }], ctorParameters: function () { return []; } });
2136
2298
 
2137
- /*
2138
- * Public API Surface of ng-firebase-table-kxp
2139
- */
2299
+ /*
2300
+ * Public API Surface of ng-firebase-table-kxp
2301
+ */
2140
2302
  // Main module
2141
2303
 
2142
- /**
2143
- * Generated bundle index. Do not edit.
2304
+ /**
2305
+ * Generated bundle index. Do not edit.
2144
2306
  */
2145
2307
 
2146
2308
  export { NgFirebaseTableKxpComponent, NgFirebaseTableKxpModule, NgFirebaseTableKxpService, TableComponent, TableService };