tailjng 0.0.55 → 0.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/settings/components-list.js +4 -0
- package/cli/settings/header-generator.js +1 -1
- package/fesm2022/tailjng.mjs.map +1 -1
- package/lib/interfaces/crud/crud.interface.d.ts +5 -2
- package/lib/interfaces/crud/filter.interface.d.ts +4 -0
- package/package.json +1 -1
- package/src/lib/components/card/card-complete/complete-card.component.html +104 -0
- package/src/lib/components/card/card-complete/complete-card.component.scss +35 -0
- package/src/lib/components/card/card-complete/complete-card.component.ts +652 -0
- package/src/lib/components/filter/filter-complete/complete-filter.component.html +194 -178
- package/src/lib/components/filter/filter-complete/complete-filter.component.ts +11 -1
- package/src/lib/components/select/select-dropdown/dropdown-select.component.html +4 -3
- package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +3 -0
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +3 -1
- package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +7 -1
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
import { Component, EventEmitter, Input, OnInit, Output, TemplateRef } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
import { LucideAngularModule } from 'lucide-angular';
|
|
5
|
+
import { Params } from '@angular/router';
|
|
6
|
+
import { animate, state, style, transition, trigger } from '@angular/animations';
|
|
7
|
+
import { EnableBoolean, FilterButton, FilterSelect, JAlertToastService, JConverterCrudService, JGenericCrudService, JIconsService, LoadingState, LoadingStates, OptionsTable, SortDirection, TableColumn } from 'tailjng';
|
|
8
|
+
import { JCompletePaginatorComponent } from '../../paginator/paginator-complete/complete-paginator.component';
|
|
9
|
+
import { JCompleteFilterComponent } from '../../filter/filter-complete/complete-filter.component';
|
|
10
|
+
|
|
11
|
+
@Component({
|
|
12
|
+
selector: 'JCompleteCard',
|
|
13
|
+
standalone: true,
|
|
14
|
+
imports: [CommonModule, FormsModule, JCompletePaginatorComponent, JCompleteFilterComponent, LucideAngularModule],
|
|
15
|
+
templateUrl: './complete-card.component.html',
|
|
16
|
+
styleUrl: './complete-card.component.scss',
|
|
17
|
+
animations: [
|
|
18
|
+
trigger('slideToggle', [
|
|
19
|
+
state('collapsed', style({
|
|
20
|
+
height: '0',
|
|
21
|
+
opacity: 0,
|
|
22
|
+
overflow: 'hidden',
|
|
23
|
+
})),
|
|
24
|
+
state('expanded', style({
|
|
25
|
+
height: '*',
|
|
26
|
+
opacity: 1,
|
|
27
|
+
})),
|
|
28
|
+
transition('collapsed <=> expanded', [
|
|
29
|
+
animate('0.3s ease-in-out')
|
|
30
|
+
])
|
|
31
|
+
])
|
|
32
|
+
]
|
|
33
|
+
})
|
|
34
|
+
export class JCompleteCardComponent implements OnInit {
|
|
35
|
+
|
|
36
|
+
Math = Math;
|
|
37
|
+
|
|
38
|
+
// Loading states
|
|
39
|
+
@Output() dataLoaded = new EventEmitter<void>();
|
|
40
|
+
|
|
41
|
+
loadingStates: LoadingStates = {
|
|
42
|
+
initialLoad: 'idle',
|
|
43
|
+
search: 'idle',
|
|
44
|
+
itemsPerPage: 'idle',
|
|
45
|
+
pagination: 'idle',
|
|
46
|
+
sort: 'idle',
|
|
47
|
+
aditionalButtons: {},
|
|
48
|
+
checked: 'idle',
|
|
49
|
+
action: 'idle',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
@Input() endpoint!: string;
|
|
53
|
+
mainEndpoint!: string;
|
|
54
|
+
@Input() columns: TableColumn<any>[] = [];
|
|
55
|
+
@Input() defaultFilters: { [key: string]: any } = {};
|
|
56
|
+
@Input() isPaginator = true;
|
|
57
|
+
@Input() isSearch = true;
|
|
58
|
+
|
|
59
|
+
data: any[] = [];
|
|
60
|
+
@Input() itemTemplate!: any;
|
|
61
|
+
|
|
62
|
+
// Expansion
|
|
63
|
+
expandedRows: Set<any> = new Set();
|
|
64
|
+
|
|
65
|
+
// Paginator
|
|
66
|
+
currentPage = 1;
|
|
67
|
+
@Input() itemsPerPageOptions = [12, 36, 60, 120];
|
|
68
|
+
itemsPerPage = this.itemsPerPageOptions[0];
|
|
69
|
+
totalItems = 0;
|
|
70
|
+
|
|
71
|
+
// Sorting
|
|
72
|
+
sortColumn: string | null = null;
|
|
73
|
+
sortDirection: SortDirection = 'none';
|
|
74
|
+
sortingColumn: string | null = null;
|
|
75
|
+
|
|
76
|
+
// Search
|
|
77
|
+
searchQuery = '';
|
|
78
|
+
@Input() searchPlaceholder = 'Buscar...';
|
|
79
|
+
|
|
80
|
+
// Filters
|
|
81
|
+
filters: any = {};
|
|
82
|
+
|
|
83
|
+
// Data filtered and paginated
|
|
84
|
+
displayData: any[] = [];
|
|
85
|
+
|
|
86
|
+
// Pages display
|
|
87
|
+
pages: number[] = [];
|
|
88
|
+
|
|
89
|
+
// Properties for visualization
|
|
90
|
+
get startIndex(): number {
|
|
91
|
+
return (this.currentPage - 1) * this.itemsPerPage;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get totalPages(): number {
|
|
95
|
+
return Math.ceil(this.totalItems / this.itemsPerPage);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get params(): Params {
|
|
99
|
+
return this.getQueryParams();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@Input() filtersButton: FilterButton[] = [];
|
|
103
|
+
@Input() filtersSelect: FilterSelect[] = [];
|
|
104
|
+
|
|
105
|
+
// Check
|
|
106
|
+
@Input() checked: boolean = false;
|
|
107
|
+
@Input() checkedColumn: string = 'id_status';
|
|
108
|
+
@Input() checkedValues: any[][] = [[true], [false]];
|
|
109
|
+
@Input() checkedTitles: string[] = ["Activos", "Inactivos"];
|
|
110
|
+
isChecked!: boolean;
|
|
111
|
+
titleChecked!: string;
|
|
112
|
+
|
|
113
|
+
constructor(
|
|
114
|
+
public readonly iconsService: JIconsService,
|
|
115
|
+
private readonly genericService: JGenericCrudService,
|
|
116
|
+
private readonly alertToastService: JAlertToastService,
|
|
117
|
+
private readonly converterService: JConverterCrudService,
|
|
118
|
+
) { }
|
|
119
|
+
|
|
120
|
+
ngOnInit() {
|
|
121
|
+
this.mainEndpoint = this.endpoint.split('/')[0] ?? this.endpoint;
|
|
122
|
+
this.isChecked = this.checkedValues[0][0];
|
|
123
|
+
this.titleChecked = this.checkedTitles[0];
|
|
124
|
+
this.columnDefaults();
|
|
125
|
+
this.loadData();
|
|
126
|
+
this.overrideFilterEvents();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
overrideFilterEvents() {
|
|
130
|
+
for (const filter of this.filtersSelect) {
|
|
131
|
+
if (filter.type === 'dropdown' || filter.type === 'searchable') {
|
|
132
|
+
const key = filter.optionValue ?? 'value';
|
|
133
|
+
const deepKey = filter.deep ? `${filter.deep}.${key}` : key;
|
|
134
|
+
|
|
135
|
+
const originalOnSelected = filter.onSelected;
|
|
136
|
+
|
|
137
|
+
filter.onSelected = (value: any) => {
|
|
138
|
+
const selectedValue = value?.[key] ?? value;
|
|
139
|
+
|
|
140
|
+
if (selectedValue === null || selectedValue === undefined) {
|
|
141
|
+
delete this.filters[deepKey];
|
|
142
|
+
} else {
|
|
143
|
+
this.filters[deepKey] = selectedValue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (typeof originalOnSelected === 'function') {
|
|
147
|
+
originalOnSelected(value);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.loadData('search');
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// =====================================================
|
|
157
|
+
// Get data
|
|
158
|
+
// =====================================================
|
|
159
|
+
|
|
160
|
+
// Load data from the server
|
|
161
|
+
loadData(loadingType: keyof LoadingStates = 'initialLoad', onFinally?: () => void) {
|
|
162
|
+
this.setLoadingState(loadingType, 'loading');
|
|
163
|
+
|
|
164
|
+
const params = this.getQueryParams();
|
|
165
|
+
|
|
166
|
+
// Simulating API wait
|
|
167
|
+
// setTimeout(() => {
|
|
168
|
+
this.genericService.findAll<any>({ endpoint: this.endpoint, params }).subscribe({
|
|
169
|
+
next: (response) => {
|
|
170
|
+
this.data = response.data[this.mainEndpoint] ?? [];
|
|
171
|
+
|
|
172
|
+
if (response.meta?.page) {
|
|
173
|
+
this.totalItems = response.meta.page.totalRecords;
|
|
174
|
+
this.currentPage = response.meta.page.currentPage;
|
|
175
|
+
} else {
|
|
176
|
+
this.totalItems = this.data.length;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (response.meta?.sort) {
|
|
180
|
+
this.sortColumn = response.meta.sort.by;
|
|
181
|
+
this.sortDirection = response.meta.sort.order.toLowerCase() as SortDirection;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.updateDisplayData();
|
|
185
|
+
this.generatePagination();
|
|
186
|
+
this.setLoadingState(loadingType, 'success');
|
|
187
|
+
},
|
|
188
|
+
error: (error) => {
|
|
189
|
+
console.error('Error fetching data:', error);
|
|
190
|
+
this.setLoadingState(loadingType, 'error');
|
|
191
|
+
}
|
|
192
|
+
}).add(() => {
|
|
193
|
+
this.dataLoaded.emit();
|
|
194
|
+
if (loadingType === 'sort') {
|
|
195
|
+
this.sortingColumn = null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (onFinally) {
|
|
199
|
+
onFinally();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
// }, 2000);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Update the data displayed in the table
|
|
206
|
+
updateDisplayData() {
|
|
207
|
+
this.displayData = this.data;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// =====================================================
|
|
211
|
+
// Change state in boolean fields
|
|
212
|
+
// =====================================================
|
|
213
|
+
|
|
214
|
+
// MMethod to change the state of a checkbox
|
|
215
|
+
onCheckboxChange(item: any, column: TableColumn<any>) {
|
|
216
|
+
// Get the ID field name based on dataProperty
|
|
217
|
+
const idField = `id_${this.mainEndpoint}`;
|
|
218
|
+
|
|
219
|
+
// Get the record ID
|
|
220
|
+
const recordId = item[idField];
|
|
221
|
+
|
|
222
|
+
// Get the current boolean value
|
|
223
|
+
const currentValue = this.getValue(item, column);
|
|
224
|
+
|
|
225
|
+
const formData: EnableBoolean<any> = {
|
|
226
|
+
key: idField,
|
|
227
|
+
value: currentValue,
|
|
228
|
+
values: [currentValue, !currentValue]
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Update status
|
|
232
|
+
this.genericService.toggle<any>({ endpoint: this.mainEndpoint, id: recordId, data: formData }).subscribe({
|
|
233
|
+
next: (response) => {
|
|
234
|
+
item[column.key] = !currentValue;
|
|
235
|
+
|
|
236
|
+
this.alertToastService.AlertToast({
|
|
237
|
+
type: "success",
|
|
238
|
+
title: "Registro actualizado!",
|
|
239
|
+
description: response.message,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Change active or inactive
|
|
246
|
+
checkActiveInactive(isChecked: boolean): void {
|
|
247
|
+
this.isChecked = !isChecked;
|
|
248
|
+
|
|
249
|
+
const index = this.isChecked ? 0 : 1;
|
|
250
|
+
this.titleChecked = this.checkedTitles[index];
|
|
251
|
+
|
|
252
|
+
// ONLY update the id_status property without touching other filters
|
|
253
|
+
this.filters[this.checkedColumn] = this.checkedValues[index];
|
|
254
|
+
|
|
255
|
+
this.filtersSelect = this.filtersSelect.map(filter => {
|
|
256
|
+
if ('optionValue' in filter && filter.optionValue === this.checkedColumn) {
|
|
257
|
+
return {
|
|
258
|
+
...filter,
|
|
259
|
+
defaultFilters: {
|
|
260
|
+
...(filter.hasOwnProperty('defaultFilters') ? (filter as any).defaultFilters : {}),
|
|
261
|
+
[this.checkedColumn]: this.filters[this.checkedColumn]
|
|
262
|
+
},
|
|
263
|
+
selected: null
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return filter;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
this.currentPage = 1;
|
|
270
|
+
this.loadData('checked');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Remove filters
|
|
274
|
+
onClearFilters(buttonType: string): void {
|
|
275
|
+
this.setAditionalButtonLoading(buttonType);
|
|
276
|
+
|
|
277
|
+
this.loadData('initialLoad', () => {
|
|
278
|
+
this.clearAditionalButtonLoading(buttonType);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// =====================================================
|
|
283
|
+
// Columns
|
|
284
|
+
// =====================================================
|
|
285
|
+
|
|
286
|
+
// Default column values
|
|
287
|
+
columnDefaults() {
|
|
288
|
+
this.columns.forEach(column => {
|
|
289
|
+
column.visible ??= true;
|
|
290
|
+
column.sortable ??= true;
|
|
291
|
+
column.isSearchable ??= true;
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Get the count of visible columns for colspan in empty state
|
|
296
|
+
getVisibleColumnsCount(): number {
|
|
297
|
+
return this.columns.filter(col => col.visible).length;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// =====================================================
|
|
301
|
+
// Data Processing
|
|
302
|
+
// =====================================================
|
|
303
|
+
|
|
304
|
+
// Get the cell values to identify a boolean field
|
|
305
|
+
isBoolean(value: any): boolean {
|
|
306
|
+
return typeof value === 'boolean';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// MMethod to get the cell values dynamically
|
|
310
|
+
getValue(item: any, column: TableColumn<any>): any {
|
|
311
|
+
let value: any;
|
|
312
|
+
|
|
313
|
+
// If a valueGetter exists, use it directly
|
|
314
|
+
if (typeof column.valueGetter === 'function') {
|
|
315
|
+
value = column.valueGetter(item);
|
|
316
|
+
} else {
|
|
317
|
+
// If not, look for the value by key (with support for nested keys)
|
|
318
|
+
const keys = column.key.split('.');
|
|
319
|
+
value = item;
|
|
320
|
+
|
|
321
|
+
for (const key of keys) {
|
|
322
|
+
if (value != null) {
|
|
323
|
+
value = value[key];
|
|
324
|
+
} else {
|
|
325
|
+
value = null;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// If value is null or undefined, return 'S/N'
|
|
332
|
+
if (value === null || value === undefined) {
|
|
333
|
+
return 'S/N';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Apply formatting according to column configuration
|
|
337
|
+
const formatted = this.converterService.formatData(value, column);
|
|
338
|
+
|
|
339
|
+
// If formatData returns nothing (unformatted value), return the original value
|
|
340
|
+
return formatted ?? value;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// =====================================================
|
|
344
|
+
// Search Parameters
|
|
345
|
+
// =====================================================
|
|
346
|
+
|
|
347
|
+
// Get the query parameters for the data request
|
|
348
|
+
getQueryParams(): Params {
|
|
349
|
+
const params: Params = this.genericService.params({
|
|
350
|
+
page: this.currentPage,
|
|
351
|
+
limit: this.itemsPerPage,
|
|
352
|
+
sort: {
|
|
353
|
+
column: this.sortColumn,
|
|
354
|
+
direction: this.sortDirection,
|
|
355
|
+
},
|
|
356
|
+
filters: this.filters,
|
|
357
|
+
defaultFilters: this.defaultFilters,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
if (this.searchQuery && this.searchQuery.trim() !== '') {
|
|
361
|
+
const baseSearchKeys = this.columns
|
|
362
|
+
.filter(col => col.isSearchable)
|
|
363
|
+
.map(col => col.key);
|
|
364
|
+
|
|
365
|
+
const extraSearchKeys = this.columns
|
|
366
|
+
.flatMap(col => col.extraSearchFields || []);
|
|
367
|
+
|
|
368
|
+
const allSearchKeys = [...baseSearchKeys, ...extraSearchKeys];
|
|
369
|
+
|
|
370
|
+
params['search'] = this.searchQuery;
|
|
371
|
+
params['searchFields'] = allSearchKeys;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return params;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// =====================================================
|
|
378
|
+
// Sorting
|
|
379
|
+
// =====================================================
|
|
380
|
+
|
|
381
|
+
// Get the row number
|
|
382
|
+
getRowNumber(index: number): number {
|
|
383
|
+
return this.startIndex + index + 1;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Column sorting
|
|
387
|
+
onSort(column: TableColumn<any>) {
|
|
388
|
+
// Prevent multiple simultaneous sort requests
|
|
389
|
+
if (this.isLoading('sort')) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!column.sortable) return;
|
|
394
|
+
|
|
395
|
+
// Guard the column that is being sorted
|
|
396
|
+
this.sortingColumn = column.key;
|
|
397
|
+
|
|
398
|
+
// Check if we are dealing with the same column as the last sort
|
|
399
|
+
const currentSortKey = this.converterService.getSortKey(this.sortColumn);
|
|
400
|
+
const columnSortKey = this.converterService.getSortKey(column.key);
|
|
401
|
+
|
|
402
|
+
// Sort direction logic
|
|
403
|
+
if (currentSortKey === columnSortKey) {
|
|
404
|
+
// Toggle sort direction
|
|
405
|
+
if (this.sortDirection === 'asc') {
|
|
406
|
+
this.sortDirection = 'desc';
|
|
407
|
+
} else if (this.sortDirection === 'desc') {
|
|
408
|
+
this.sortDirection = 'none';
|
|
409
|
+
} else {
|
|
410
|
+
this.sortDirection = 'asc';
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
// If a new column is selected for sorting, start with ascending order
|
|
414
|
+
this.sortColumn = column.key;
|
|
415
|
+
this.sortDirection = 'asc';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
this.loadData('sort');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Get the sort key
|
|
422
|
+
getSortKey(value: any): string {
|
|
423
|
+
return this.converterService.getSortKey(value);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Handle the key press event for sorting
|
|
427
|
+
onSortKeyPress(event: KeyboardEvent, column: TableColumn<any>) {
|
|
428
|
+
// If a sort is already in progress, do not allow another
|
|
429
|
+
if (this.isLoading('sort')) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
434
|
+
event.preventDefault();
|
|
435
|
+
this.onSort(column);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// =====================================================
|
|
440
|
+
// Search
|
|
441
|
+
// =====================================================
|
|
442
|
+
|
|
443
|
+
// Search functionality
|
|
444
|
+
onSearch() {
|
|
445
|
+
this.currentPage = 1;
|
|
446
|
+
this.loadData('search');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Selector de items por página
|
|
450
|
+
onItemsPerPageChange() {
|
|
451
|
+
this.currentPage = 1;
|
|
452
|
+
this.loadData('itemsPerPage');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// =====================================================
|
|
456
|
+
// Pagination
|
|
457
|
+
// =====================================================
|
|
458
|
+
|
|
459
|
+
// Generate pagination numbers
|
|
460
|
+
generatePagination() {
|
|
461
|
+
const totalPages = this.totalPages;
|
|
462
|
+
const currentPage = this.currentPage;
|
|
463
|
+
|
|
464
|
+
// Show 3 pagination numbers
|
|
465
|
+
const maxPagesToShow = 3;
|
|
466
|
+
let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2));
|
|
467
|
+
let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1);
|
|
468
|
+
|
|
469
|
+
// Adjust if we're near the end
|
|
470
|
+
if (endPage - startPage + 1 < maxPagesToShow) {
|
|
471
|
+
startPage = Math.max(1, endPage - maxPagesToShow + 1);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
this.pages = Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Pagination
|
|
478
|
+
handlePageChange(page: number) {
|
|
479
|
+
this.currentPage = page;
|
|
480
|
+
this.loadData('pagination');
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// =====================================================
|
|
484
|
+
// Options
|
|
485
|
+
// =====================================================
|
|
486
|
+
|
|
487
|
+
// MMethod to handle button click
|
|
488
|
+
onButtonClick(button: OptionsTable, element: any): void {
|
|
489
|
+
// Executes the parent's action if defined
|
|
490
|
+
if (button.clicked) {
|
|
491
|
+
button.clicked(element);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// MMethod to get a tooltip
|
|
496
|
+
getTooltip(tooltip: string | ((data?: any) => string), data: any): string {
|
|
497
|
+
if (typeof tooltip === 'function') {
|
|
498
|
+
return tooltip(data);
|
|
499
|
+
}
|
|
500
|
+
return tooltip ?? '';
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// MMethod to get an icon
|
|
504
|
+
getIcon(icon: ((data?: any) => any), data: any): any {
|
|
505
|
+
if (typeof icon === 'function') {
|
|
506
|
+
return icon(data);
|
|
507
|
+
}
|
|
508
|
+
return icon;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Evaluate if a button is disabled
|
|
512
|
+
getDisabled(option: OptionsTable, data: any): boolean {
|
|
513
|
+
if (typeof option.disabled === 'function') {
|
|
514
|
+
return option.disabled(data);
|
|
515
|
+
}
|
|
516
|
+
return !!option.disabled;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Evaluate if a button is visible
|
|
520
|
+
getIsVisible(option: OptionsTable, data: any): boolean {
|
|
521
|
+
if (typeof option.isVisible === 'function') {
|
|
522
|
+
return option.isVisible(data);
|
|
523
|
+
}
|
|
524
|
+
// If not defined, default is visible
|
|
525
|
+
return option.isVisible !== false;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// MMethod to get a button's CSS class
|
|
529
|
+
mergeNgClasses(optionNgClass: ((data?: any) => any), data: any): any {
|
|
530
|
+
const baseClass = {
|
|
531
|
+
'min-w-auto p-1! pl-2! pr-2!': true
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
let dynamicClass: any = {};
|
|
535
|
+
if (typeof optionNgClass === 'function') {
|
|
536
|
+
dynamicClass = optionNgClass(data);
|
|
537
|
+
} else {
|
|
538
|
+
dynamicClass = optionNgClass ?? {};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
...baseClass,
|
|
543
|
+
...(typeof dynamicClass === 'string' ? { [dynamicClass]: true } : dynamicClass)
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// ====================================================
|
|
548
|
+
// Loading parameters
|
|
549
|
+
// =====================================================
|
|
550
|
+
|
|
551
|
+
// MMethod to check if a specific state is loading
|
|
552
|
+
isLoading(state: keyof LoadingStates): boolean {
|
|
553
|
+
return this.loadingStates[state] === 'loading';
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// MMethod to check if any state is loading
|
|
557
|
+
isAnyLoading(): boolean {
|
|
558
|
+
return Object.values(this.loadingStates).some(state => state === 'loading');
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Method to update a loading state
|
|
562
|
+
setLoadingState(state: keyof LoadingStates, value: LoadingState | { [buttonType: string]: LoadingState }): void {
|
|
563
|
+
if (state === 'aditionalButtons') {
|
|
564
|
+
if (typeof value === 'string') {
|
|
565
|
+
// Prevent assigning a string directly if an object is expected
|
|
566
|
+
console.warn(`No puedes asignar '${value}' directamente a aditionalButtons. Usa setAditionalButtonLoading en su lugar.`);
|
|
567
|
+
} else {
|
|
568
|
+
this.loadingStates.aditionalButtons = value;
|
|
569
|
+
}
|
|
570
|
+
} else {
|
|
571
|
+
this.loadingStates[state] = value as LoadingState;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Activate loading
|
|
576
|
+
setAditionalButtonLoading(buttonType: string, id?: number | string): void {
|
|
577
|
+
const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
|
|
578
|
+
this.loadingStates.aditionalButtons[key] = 'loading';
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Clear loading
|
|
582
|
+
clearAditionalButtonLoading(buttonType: string, id?: number | string): void {
|
|
583
|
+
const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
|
|
584
|
+
this.loadingStates.aditionalButtons[key] = 'idle';
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Verify loading
|
|
588
|
+
isAditionalButtonLoading(buttonType: string, id?: number | string): boolean {
|
|
589
|
+
const key = id !== undefined ? `${buttonType}_${id}` : buttonType;
|
|
590
|
+
return this.loadingStates.aditionalButtons[key] === 'loading';
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ==================================================
|
|
594
|
+
// HTML
|
|
595
|
+
// ==================================================
|
|
596
|
+
|
|
597
|
+
getTemplate(row: any): string {
|
|
598
|
+
const templateColumn = this.columns.find(col => typeof col.expandTemplate === 'function');
|
|
599
|
+
return templateColumn?.expandTemplate?.(row) ?? '';
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// ==================================================
|
|
603
|
+
// Expand rows
|
|
604
|
+
// ==================================================
|
|
605
|
+
|
|
606
|
+
@Input() expandTemplate?: TemplateRef<any>;
|
|
607
|
+
@Input() onExpandData?: (item: any) => Promise<any>;
|
|
608
|
+
|
|
609
|
+
expandedItems: { [key: string]: boolean } = {};
|
|
610
|
+
expandData: { [key: string]: any } = {};
|
|
611
|
+
expandLoading: { [key: string]: boolean } = {};
|
|
612
|
+
|
|
613
|
+
// MMethod to expand or collapse a row
|
|
614
|
+
toggleExpanded(item: any) {
|
|
615
|
+
const id = item?.id || item?.id_voucher || item?.id_card;
|
|
616
|
+
const isOpen = this.expandedItems[id];
|
|
617
|
+
|
|
618
|
+
if (!isOpen) {
|
|
619
|
+
this.expandLoading[id] = true;
|
|
620
|
+
|
|
621
|
+
this.expandedItems[id] = true;
|
|
622
|
+
if (this.onExpandData) {
|
|
623
|
+
this.onExpandData(item).then(data => {
|
|
624
|
+
this.expandData[id] = data;
|
|
625
|
+
this.expandLoading[id] = false;
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
} else {
|
|
629
|
+
this.expandedItems[id] = false;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
isExpanded(item: any): boolean {
|
|
634
|
+
const id = item?.id || item?.id_voucher || item?.id_card;
|
|
635
|
+
return !!this.expandedItems[id];
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
getExpandData(item: any): any {
|
|
639
|
+
const id = item?.id || item?.id_voucher || item?.id_card;
|
|
640
|
+
return this.expandData[id];
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
isLoadingExpand(item: any): boolean {
|
|
644
|
+
const id = item?.id || item?.id_voucher || item?.id_card;
|
|
645
|
+
return !!this.expandLoading[id];
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
trackById(index: number, item: any): any {
|
|
649
|
+
const id = `${item['id_' + this.mainEndpoint]}-${index}`;
|
|
650
|
+
return id ? id : index;
|
|
651
|
+
}
|
|
652
|
+
}
|