@ddiazr/data-table 0.0.1
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/README.md +169 -0
- package/fesm2022/ddiazr-data-table.mjs +234 -0
- package/fesm2022/ddiazr-data-table.mjs.map +1 -0
- package/package.json +34 -0
- package/types/ddiazr-data-table.d.ts +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Generic Table
|
|
2
|
+
|
|
3
|
+
Tabla Dinamica con Angular 21+
|
|
4
|
+
|
|
5
|
+
## Instalación
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @ddiazr/data-table
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Peer Dependencies
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i bootstrap @fortawesome/fontawesome-free
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## TableColumn && TableAction
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
interface TableAction<T> {
|
|
21
|
+
label: string;
|
|
22
|
+
icon?: string;
|
|
23
|
+
styleClass?: string; // Clases CSS (ej: Bootstrap)
|
|
24
|
+
callback: (row: T) => void;
|
|
25
|
+
isVisible?: (row: T) => boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface TableColumn<T> {
|
|
29
|
+
key: keyof T | 'actions' | 'select';
|
|
30
|
+
label: string;
|
|
31
|
+
isSortable?: boolean;
|
|
32
|
+
templateRef?: any;
|
|
33
|
+
dataType?: DataType;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Campos
|
|
38
|
+
|
|
39
|
+
| Propiedad | Tipo | Obligatorio | Descripción |
|
|
40
|
+
| --------------- | ------------------ | ----------- | ----------------------------------------------------- |
|
|
41
|
+
| `data` | `any[]` | ✅ Sí | Datos de entrada segun el jsonen la tabla |
|
|
42
|
+
| `columns` | `ColumnConfig[]` | ✅ Sí | Datos de las columnas que quieras que tenga la tabla |
|
|
43
|
+
| `filterPlaceholder` | `string` | ❌ No | Si necesita cambiar al buscador el placeholder |
|
|
44
|
+
| `showExportButtons` | `boolean` | ❌ No | Todos los botnes de exportar (EXCEL, PDF O PRINT) |
|
|
45
|
+
| `actions` | `TableAction<T>[]` | ❌ No | Funcion que devuelve la accion segun el columnsConfig |
|
|
46
|
+
|
|
47
|
+
## Uso básico
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { DataTable } from '@ddiazr/utilt-service';
|
|
51
|
+
|
|
52
|
+
interface TableAction<T> {
|
|
53
|
+
label: string;
|
|
54
|
+
icon?: string;
|
|
55
|
+
styleClass?: string; // Clases CSS (ej: Bootstrap)
|
|
56
|
+
callback: (row: T) => void;
|
|
57
|
+
isVisible?: (row: T) => boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface TableColumn<T> {
|
|
61
|
+
key: keyof T | 'actions' | 'select';
|
|
62
|
+
label: string;
|
|
63
|
+
isSortable?: boolean;
|
|
64
|
+
templateRef?: any;
|
|
65
|
+
dataType?: DataType;
|
|
66
|
+
}
|
|
67
|
+
@Component({
|
|
68
|
+
imports: [DataTable],
|
|
69
|
+
template: `
|
|
70
|
+
<tbl-generic-drag-and-drop
|
|
71
|
+
[data]="data()"
|
|
72
|
+
[columns]="columns()"
|
|
73
|
+
[actions]="actions()"
|
|
74
|
+
filterPlaceholder='BUSCAR POR'
|
|
75
|
+
[showExportButtons]="true"
|
|
76
|
+
/>
|
|
77
|
+
`,
|
|
78
|
+
})
|
|
79
|
+
export class AppComponent {
|
|
80
|
+
|
|
81
|
+
data = signal<any[]>([
|
|
82
|
+
{
|
|
83
|
+
id: 1,
|
|
84
|
+
nombre: 'Juan Perez',
|
|
85
|
+
edad: 36,
|
|
86
|
+
salario: 1000,
|
|
87
|
+
fechanac: '21/07/1700',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 2,
|
|
91
|
+
nombre: 'Juana Perez',
|
|
92
|
+
edad: 14,
|
|
93
|
+
salario: 2000,
|
|
94
|
+
fechanac: '21/07/1700',
|
|
95
|
+
},
|
|
96
|
+
]);
|
|
97
|
+
//DECALRAMOS LAS COLUMNAS QUE VA A CONTENER LA TABLA
|
|
98
|
+
columns = signal<TableColumn[]>([
|
|
99
|
+
{
|
|
100
|
+
key: 'id',
|
|
101
|
+
label: 'ID',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
key: 'nombre',
|
|
105
|
+
label: 'Nombre',
|
|
106
|
+
isSortable: true,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: 'edad',
|
|
110
|
+
label: 'Edad',
|
|
111
|
+
isSortable: true,
|
|
112
|
+
dataType: 'number',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
key: 'salario',
|
|
116
|
+
label: 'Salario',
|
|
117
|
+
isSortable: true,
|
|
118
|
+
dataType: 'currency',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
key: 'fechanac',
|
|
122
|
+
label: 'F. Nac',
|
|
123
|
+
isSortable: true,
|
|
124
|
+
dataType: 'date',
|
|
125
|
+
},
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
// SI SE NECESITA QUE LA TABLA TENGA BOTONES PARA N ACCIONES
|
|
129
|
+
// SE MUESTRA ESTE EJEMPLO QUE TIENE DOS ACCIONES
|
|
130
|
+
// 1. MUESTRA EL BOTON EDIT SI LA EDAD ES > 18
|
|
131
|
+
// 2. MUESTRA EL BOTON SIN RESTRICCION
|
|
132
|
+
actions = signal<TableAction<any>[]>([
|
|
133
|
+
{
|
|
134
|
+
label: 'Edit',
|
|
135
|
+
icon: 'fa fa-edit',
|
|
136
|
+
styleClass: 'btn-warning',
|
|
137
|
+
showIf: (row) => row.edad > 18,
|
|
138
|
+
callback: (row) => this.edit(row),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
label: 'VER',
|
|
142
|
+
icon: 'fa fa-edit',
|
|
143
|
+
styleClass: 'btn-warning',
|
|
144
|
+
callback: (row) => this.ver(row.id),
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Configuración de estilos
|
|
151
|
+
|
|
152
|
+
En tu `angular.json`:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
"styles": [
|
|
156
|
+
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
|
157
|
+
"node_modules/@fortawesome/fontawesome-free/css/all.min.css"
|
|
158
|
+
]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Licencia
|
|
162
|
+
|
|
163
|
+
MIT
|
|
164
|
+
|
|
165
|
+
# Source files
|
|
166
|
+
|
|
167
|
+
src/
|
|
168
|
+
_.ts
|
|
169
|
+
!_.d.ts
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import * as i1 from '@angular/common';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { Pipe, input, output, signal, computed, Component } from '@angular/core';
|
|
5
|
+
import * as i2 from '@angular/forms';
|
|
6
|
+
import { FormsModule } from '@angular/forms';
|
|
7
|
+
|
|
8
|
+
class SafeDatePipe {
|
|
9
|
+
transform(value) {
|
|
10
|
+
if (!value)
|
|
11
|
+
return "";
|
|
12
|
+
const date = new Date(value);
|
|
13
|
+
if (isNaN(date.getTime()))
|
|
14
|
+
return value;
|
|
15
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
16
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
17
|
+
const year = date.getFullYear();
|
|
18
|
+
return `${day}/${month}/${year}`;
|
|
19
|
+
}
|
|
20
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SafeDatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
21
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.2", ngImport: i0, type: SafeDatePipe, isStandalone: true, name: "safeDate" });
|
|
22
|
+
}
|
|
23
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SafeDatePipe, decorators: [{
|
|
24
|
+
type: Pipe,
|
|
25
|
+
args: [{
|
|
26
|
+
name: "safeDate",
|
|
27
|
+
standalone: true,
|
|
28
|
+
pure: true, // ✅ Muy importante para performance
|
|
29
|
+
}]
|
|
30
|
+
}] });
|
|
31
|
+
|
|
32
|
+
class SafeCurrencyPipe {
|
|
33
|
+
transform(value) {
|
|
34
|
+
if (value == null || value === '')
|
|
35
|
+
return '';
|
|
36
|
+
const num = Number(value);
|
|
37
|
+
if (isNaN(num))
|
|
38
|
+
return value;
|
|
39
|
+
return 'Q' + num.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
40
|
+
}
|
|
41
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SafeCurrencyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
42
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.2", ngImport: i0, type: SafeCurrencyPipe, isStandalone: true, name: "safeCurrency" });
|
|
43
|
+
}
|
|
44
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SafeCurrencyPipe, decorators: [{
|
|
45
|
+
type: Pipe,
|
|
46
|
+
args: [{
|
|
47
|
+
name: 'safeCurrency',
|
|
48
|
+
standalone: true,
|
|
49
|
+
pure: true
|
|
50
|
+
}]
|
|
51
|
+
}] });
|
|
52
|
+
|
|
53
|
+
class SafeNumberPipe {
|
|
54
|
+
transform(value) {
|
|
55
|
+
if (value == null || value === "")
|
|
56
|
+
return "";
|
|
57
|
+
const num = Number(value);
|
|
58
|
+
if (isNaN(num))
|
|
59
|
+
return value;
|
|
60
|
+
return num.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
61
|
+
}
|
|
62
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SafeNumberPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
63
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.2", ngImport: i0, type: SafeNumberPipe, isStandalone: true, name: "safeNumber" });
|
|
64
|
+
}
|
|
65
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: SafeNumberPipe, decorators: [{
|
|
66
|
+
type: Pipe,
|
|
67
|
+
args: [{
|
|
68
|
+
name: "safeNumber",
|
|
69
|
+
standalone: true,
|
|
70
|
+
pure: true,
|
|
71
|
+
}]
|
|
72
|
+
}] });
|
|
73
|
+
|
|
74
|
+
class DataTable {
|
|
75
|
+
// ------------------------------------
|
|
76
|
+
// I. INPUT SIGNALS
|
|
77
|
+
// ------------------------------------
|
|
78
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
79
|
+
columns = input.required(...(ngDevMode ? [{ debugName: "columns" }] : []));
|
|
80
|
+
actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : []));
|
|
81
|
+
filterPlaceholder = input('Buscar...', ...(ngDevMode ? [{ debugName: "filterPlaceholder" }] : []));
|
|
82
|
+
showExportButtons = input(true, ...(ngDevMode ? [{ debugName: "showExportButtons" }] : []));
|
|
83
|
+
// ------------------------------------
|
|
84
|
+
// II. OUTPUTS (Usando la función output())
|
|
85
|
+
// ------------------------------------
|
|
86
|
+
actionClicked = output();
|
|
87
|
+
exportRequested = output();
|
|
88
|
+
// ------------------------------------
|
|
89
|
+
// III. ESTADO INTERNO (Signals para Filtrado)
|
|
90
|
+
// ------------------------------------
|
|
91
|
+
filterTerm = signal('', ...(ngDevMode ? [{ debugName: "filterTerm" }] : []));
|
|
92
|
+
sortState = signal({
|
|
93
|
+
column: null,
|
|
94
|
+
direction: null,
|
|
95
|
+
}, ...(ngDevMode ? [{ debugName: "sortState" }] : []));
|
|
96
|
+
currentPage = signal(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : [])); // Página actual, inicia en 1
|
|
97
|
+
pageSize = signal(10, ...(ngDevMode ? [{ debugName: "pageSize" }] : [])); // Elementos por página
|
|
98
|
+
// 4. Datos filtrados (Computed Signal - LÓGICA DE BÚSQUEDA)
|
|
99
|
+
filteredData = computed(() => {
|
|
100
|
+
const term = this.filterTerm().toLowerCase();
|
|
101
|
+
const currentData = this.data();
|
|
102
|
+
if (!term)
|
|
103
|
+
return currentData;
|
|
104
|
+
return currentData.filter((item) => Object.values(item).some((value) => String(value).toLowerCase().includes(term)));
|
|
105
|
+
}, ...(ngDevMode ? [{ debugName: "filteredData" }] : []));
|
|
106
|
+
sortedAndFilteredData = computed(() => {
|
|
107
|
+
const data = [...this.filteredData()]; // Copia para ordenar
|
|
108
|
+
const state = this.sortState();
|
|
109
|
+
if (!state.column || !state.direction) {
|
|
110
|
+
return data; // Sin ordenamiento
|
|
111
|
+
}
|
|
112
|
+
const directionMultiplier = state.direction === 'asc' ? 1 : -1;
|
|
113
|
+
const columnKey = state.column;
|
|
114
|
+
return data.sort((a, b) => {
|
|
115
|
+
// Usamos localeCompare para ordenar strings correctamente, si no, comparación simple
|
|
116
|
+
const aValue = a[columnKey];
|
|
117
|
+
const bValue = b[columnKey];
|
|
118
|
+
if (typeof aValue === 'string' && typeof bValue === 'string') {
|
|
119
|
+
return aValue.localeCompare(bValue) * directionMultiplier;
|
|
120
|
+
}
|
|
121
|
+
if (aValue < bValue) {
|
|
122
|
+
return -1 * directionMultiplier;
|
|
123
|
+
}
|
|
124
|
+
if (aValue > bValue) {
|
|
125
|
+
return 1 * directionMultiplier;
|
|
126
|
+
}
|
|
127
|
+
return 0;
|
|
128
|
+
});
|
|
129
|
+
}, ...(ngDevMode ? [{ debugName: "sortedAndFilteredData" }] : []));
|
|
130
|
+
paginatedData = computed(() => {
|
|
131
|
+
const data = this.sortedAndFilteredData();
|
|
132
|
+
const size = this.pageSize();
|
|
133
|
+
const page = this.currentPage();
|
|
134
|
+
const start = (page - 1) * size;
|
|
135
|
+
const end = start + size;
|
|
136
|
+
// Retorna solo el slice (rebanada) de datos de la página actual
|
|
137
|
+
return data.slice(start, end);
|
|
138
|
+
}, ...(ngDevMode ? [{ debugName: "paginatedData" }] : []));
|
|
139
|
+
totalPages = computed(() => {
|
|
140
|
+
const totalItems = this.sortedAndFilteredData().length;
|
|
141
|
+
const size = this.pageSize();
|
|
142
|
+
return Math.ceil(totalItems / size);
|
|
143
|
+
}, ...(ngDevMode ? [{ debugName: "totalPages" }] : []));
|
|
144
|
+
pagesToShow = computed(() => {
|
|
145
|
+
const total = this.totalPages();
|
|
146
|
+
const current = this.currentPage();
|
|
147
|
+
const maxPagesToShow = 5; // Máximo de botones numéricos a mostrar
|
|
148
|
+
const pages = [];
|
|
149
|
+
// Lógica simple para centrar los botones en la página actual
|
|
150
|
+
let start = Math.max(1, current - Math.floor(maxPagesToShow / 2));
|
|
151
|
+
let end = Math.min(total, start + maxPagesToShow - 1);
|
|
152
|
+
// Ajuste si la tabla es pequeña
|
|
153
|
+
if (end - start + 1 < maxPagesToShow) {
|
|
154
|
+
start = Math.max(1, end - maxPagesToShow + 1);
|
|
155
|
+
}
|
|
156
|
+
for (let i = start; i <= end; i++) {
|
|
157
|
+
pages.push(i);
|
|
158
|
+
}
|
|
159
|
+
return pages;
|
|
160
|
+
}, ...(ngDevMode ? [{ debugName: "pagesToShow" }] : []));
|
|
161
|
+
// ------------------------------------
|
|
162
|
+
// IV. FUNCIONES
|
|
163
|
+
// ------------------------------------
|
|
164
|
+
onSort(columnKey) {
|
|
165
|
+
// Solo permitir ordenar si no es una columna de acción/selección
|
|
166
|
+
if (columnKey === 'actions' || columnKey === 'select')
|
|
167
|
+
return;
|
|
168
|
+
this.sortState.update((current) => {
|
|
169
|
+
let newDirection = 'asc';
|
|
170
|
+
if (current.column === columnKey) {
|
|
171
|
+
// Mismo column, se alterna la dirección
|
|
172
|
+
newDirection = current.direction === 'asc' ? 'desc' : 'asc';
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
column: columnKey,
|
|
176
|
+
direction: newDirection,
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* FUNCIÓN DE BÚSQUEDA
|
|
182
|
+
* Actualiza el WritableSignal interno.
|
|
183
|
+
*/
|
|
184
|
+
onSearchChange(term) {
|
|
185
|
+
this.filterTerm.set(term);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* FUNCIÓN DE ACCIONES
|
|
189
|
+
* Emite el evento usando .emit() del Output Signal.
|
|
190
|
+
*/
|
|
191
|
+
onActionClick(action, rowData) {
|
|
192
|
+
this.actionClicked.emit({ action, rowData });
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* FUNCIÓN DE EXPORTACIÓN
|
|
196
|
+
* Emite el evento de salida, delegando la lógica al padre.
|
|
197
|
+
*/
|
|
198
|
+
exportData(format) {
|
|
199
|
+
this.exportRequested.emit(format);
|
|
200
|
+
}
|
|
201
|
+
getConfig(key) {
|
|
202
|
+
return this.columns().find((c) => c.key === key);
|
|
203
|
+
}
|
|
204
|
+
goToPage(page) {
|
|
205
|
+
const total = this.totalPages();
|
|
206
|
+
if (page >= 1 && page <= total) {
|
|
207
|
+
this.currentPage.set(page);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
onPageSizeChange(event) {
|
|
211
|
+
const target = event.target;
|
|
212
|
+
const newSize = Number(target.value);
|
|
213
|
+
this.pageSize.set(newSize);
|
|
214
|
+
// Reiniciar a la página 1 después de cambiar el tamaño para evitar errores de índice
|
|
215
|
+
this.currentPage.set(1);
|
|
216
|
+
}
|
|
217
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: DataTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
218
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: DataTable, isStandalone: true, selector: "dtbl-data-table", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, filterPlaceholder: { classPropertyName: "filterPlaceholder", publicName: "filterPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, showExportButtons: { classPropertyName: "showExportButtons", publicName: "showExportButtons", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionClicked: "actionClicked", exportRequested: "exportRequested" }, ngImport: i0, template: "<div class=\"container-fluid pt-3\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <input\r\n type=\"text\"\r\n class=\"form-control w-25\"\r\n [placeholder]=\"filterPlaceholder()\"\r\n [ngModel]=\"filterTerm()\"\r\n (ngModelChange)=\"onSearchChange($event)\"\r\n />\r\n\r\n @if (showExportButtons()) {\r\n <div class=\"export-buttons btn-group\" role=\"group\">\r\n <button class=\"btn btn-outline-success\" (click)=\"exportData('excel')\">\r\n <i class=\"bi bi-file-earmark-excel\"></i> Exportar a Excel\r\n </button>\r\n <button class=\"btn btn-outline-danger\" (click)=\"exportData('pdf')\">\r\n <i class=\"bi bi-file-earmark-pdf\"></i> Exportar a PDF\r\n </button>\r\n <button class=\"btn btn-outline-secondary\" (click)=\"exportData('print')\">\r\n <i class=\"bi bi-printer\"></i> Imprimir\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"table-responsive\">\r\n <table class=\"table table-hover table-bordered caption-top\">\r\n <caption>\r\n Mostrando\r\n {{\r\n paginatedData().length\r\n }}\r\n de\r\n {{\r\n sortedAndFilteredData().length\r\n }}\r\n resultados (Total:\r\n {{\r\n data().length\r\n }}).\r\n </caption>\r\n\r\n <thead>\r\n <tr>\r\n @for (col of columns(); track col.key) {\r\n <th\r\n [class.sortable]=\"col.isSortable\"\r\n (click)=\"col.isSortable && onSort(col.key)\"\r\n scope=\"col\"\r\n class=\"text-nowrap\"\r\n >\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span class=\"fw-bold\">{{ col.label }}</span>\r\n\r\n @if (col.isSortable) {\r\n @if (col.key === sortState().column) {\r\n <i\r\n class=\"fa-solid ms-1\"\r\n [class.fa-sort-up]=\"sortState().direction === 'asc'\"\r\n [class.fa-sort-down]=\"sortState().direction === 'desc'\"\r\n >\r\n </i>\r\n } @else {\r\n <i class=\"fa-solid fa-sort text-muted ms-1\" style=\"opacity: 0.5\"></i>\r\n }\r\n }\r\n </div>\r\n </th>\r\n }\r\n @if (actions().length > 0) {\r\n <th class=\"bg-dark text-center text-white\">Acciones</th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n @for (row of paginatedData(); track row.id || $index) {\r\n <tr>\r\n @for (col of columns(); track col.key) {\r\n <td class=\"text-nowrap\">\r\n @if (col.templateRef) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n col.templateRef;\r\n context: { $implicit: row, column: col.key }\r\n \"\r\n ></ng-container>\r\n } @else {\r\n <!-- {{ formatCellValue(row, col) }} -->\r\n @let config = getConfig(col.key.toString());\r\n @switch (config?.dataType) {\r\n @case ('date') {\r\n {{ $any(row)[col.key] | safeDate }}\r\n }\r\n @case ('currency') {\r\n {{ $any(row)[col.key] | safeCurrency }}\r\n }\r\n @case ('number') {\r\n {{ $any(row)[col.key] | safeNumber }}\r\n }\r\n @default {\r\n {{ $any(row)[col.key] }}\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n @if (actions().length > 0) {\r\n <td class=\"text-center\">\r\n @for (action of actions(); track action.label) {\r\n @if (!action.isVisible || action.isVisible(row)) {\r\n <button\r\n [class]=\"'btn btn-sm mx-1 ' + action.styleClass\"\r\n (click)=\"action.callback(row)\"\r\n >\r\n <i [class]=\"action.icon\"></i>\r\n </button>\r\n }\r\n }\r\n </td>\r\n }\r\n </tr>\r\n } @empty {\r\n <tr>\r\n <td [attr.colspan]=\"columns().length\" class=\"text-center\">\r\n No hay datos disponibles para mostrar.\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-3\">\r\n <div class=\"d-flex align-items-center\">\r\n <label for=\"pageSizeSelect\" class=\"me-2 text-nowrap\">Art\u00EDculos por p\u00E1gina: </label>\r\n <select\r\n id=\"pageSizeSelect\"\r\n class=\"form-select form-select-sm w-auto\"\r\n (change)=\"onPageSizeChange($event)\"\r\n >\r\n <option value=\"10\" [selected]=\"pageSize() === 10\">10</option>\r\n <option value=\"25\" [selected]=\"pageSize() === 25\">25</option>\r\n <option value=\"50\" [selected]=\"pageSize() === 50\">50</option>\r\n <option value=\"100\" [selected]=\"pageSize() === 100\">100</option>\r\n </select>\r\n </div>\r\n\r\n <nav aria-label=\"Paginaci\u00F3n de tabla\">\r\n <ul class=\"pagination pagination-md mb-0\">\r\n <li class=\"page-item\" [class.disabled]=\"currentPage() === 1\">\r\n <button type=\"button\" class=\"page-link\" (click)=\"goToPage(currentPage() - 1)\"><<</button>\r\n </li>\r\n\r\n @for (pageNumber of pagesToShow(); track pageNumber) {\r\n <li class=\"page-item\" [class.active]=\"currentPage() === pageNumber\">\r\n <button type=\"button\" class=\"page-link\" (click)=\"goToPage(pageNumber)\">\r\n {{ pageNumber }}\r\n </button>\r\n </li>\r\n }\r\n\r\n <li class=\"page-item\" [class.disabled]=\"currentPage() === totalPages()\">\r\n <button type=\"button\" class=\"page-link\" (click)=\"goToPage(currentPage() + 1)\">>></button>\r\n </li>\r\n </ul>\r\n </nav>\r\n </div>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: SafeDatePipe, name: "safeDate" }, { kind: "pipe", type: SafeCurrencyPipe, name: "safeCurrency" }, { kind: "pipe", type: SafeNumberPipe, name: "safeNumber" }] });
|
|
219
|
+
}
|
|
220
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: DataTable, decorators: [{
|
|
221
|
+
type: Component,
|
|
222
|
+
args: [{ selector: 'dtbl-data-table', imports: [CommonModule, FormsModule, SafeDatePipe, SafeCurrencyPipe, SafeNumberPipe], template: "<div class=\"container-fluid pt-3\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <input\r\n type=\"text\"\r\n class=\"form-control w-25\"\r\n [placeholder]=\"filterPlaceholder()\"\r\n [ngModel]=\"filterTerm()\"\r\n (ngModelChange)=\"onSearchChange($event)\"\r\n />\r\n\r\n @if (showExportButtons()) {\r\n <div class=\"export-buttons btn-group\" role=\"group\">\r\n <button class=\"btn btn-outline-success\" (click)=\"exportData('excel')\">\r\n <i class=\"bi bi-file-earmark-excel\"></i> Exportar a Excel\r\n </button>\r\n <button class=\"btn btn-outline-danger\" (click)=\"exportData('pdf')\">\r\n <i class=\"bi bi-file-earmark-pdf\"></i> Exportar a PDF\r\n </button>\r\n <button class=\"btn btn-outline-secondary\" (click)=\"exportData('print')\">\r\n <i class=\"bi bi-printer\"></i> Imprimir\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"table-responsive\">\r\n <table class=\"table table-hover table-bordered caption-top\">\r\n <caption>\r\n Mostrando\r\n {{\r\n paginatedData().length\r\n }}\r\n de\r\n {{\r\n sortedAndFilteredData().length\r\n }}\r\n resultados (Total:\r\n {{\r\n data().length\r\n }}).\r\n </caption>\r\n\r\n <thead>\r\n <tr>\r\n @for (col of columns(); track col.key) {\r\n <th\r\n [class.sortable]=\"col.isSortable\"\r\n (click)=\"col.isSortable && onSort(col.key)\"\r\n scope=\"col\"\r\n class=\"text-nowrap\"\r\n >\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span class=\"fw-bold\">{{ col.label }}</span>\r\n\r\n @if (col.isSortable) {\r\n @if (col.key === sortState().column) {\r\n <i\r\n class=\"fa-solid ms-1\"\r\n [class.fa-sort-up]=\"sortState().direction === 'asc'\"\r\n [class.fa-sort-down]=\"sortState().direction === 'desc'\"\r\n >\r\n </i>\r\n } @else {\r\n <i class=\"fa-solid fa-sort text-muted ms-1\" style=\"opacity: 0.5\"></i>\r\n }\r\n }\r\n </div>\r\n </th>\r\n }\r\n @if (actions().length > 0) {\r\n <th class=\"bg-dark text-center text-white\">Acciones</th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n @for (row of paginatedData(); track row.id || $index) {\r\n <tr>\r\n @for (col of columns(); track col.key) {\r\n <td class=\"text-nowrap\">\r\n @if (col.templateRef) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n col.templateRef;\r\n context: { $implicit: row, column: col.key }\r\n \"\r\n ></ng-container>\r\n } @else {\r\n <!-- {{ formatCellValue(row, col) }} -->\r\n @let config = getConfig(col.key.toString());\r\n @switch (config?.dataType) {\r\n @case ('date') {\r\n {{ $any(row)[col.key] | safeDate }}\r\n }\r\n @case ('currency') {\r\n {{ $any(row)[col.key] | safeCurrency }}\r\n }\r\n @case ('number') {\r\n {{ $any(row)[col.key] | safeNumber }}\r\n }\r\n @default {\r\n {{ $any(row)[col.key] }}\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n @if (actions().length > 0) {\r\n <td class=\"text-center\">\r\n @for (action of actions(); track action.label) {\r\n @if (!action.isVisible || action.isVisible(row)) {\r\n <button\r\n [class]=\"'btn btn-sm mx-1 ' + action.styleClass\"\r\n (click)=\"action.callback(row)\"\r\n >\r\n <i [class]=\"action.icon\"></i>\r\n </button>\r\n }\r\n }\r\n </td>\r\n }\r\n </tr>\r\n } @empty {\r\n <tr>\r\n <td [attr.colspan]=\"columns().length\" class=\"text-center\">\r\n No hay datos disponibles para mostrar.\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-3\">\r\n <div class=\"d-flex align-items-center\">\r\n <label for=\"pageSizeSelect\" class=\"me-2 text-nowrap\">Art\u00EDculos por p\u00E1gina: </label>\r\n <select\r\n id=\"pageSizeSelect\"\r\n class=\"form-select form-select-sm w-auto\"\r\n (change)=\"onPageSizeChange($event)\"\r\n >\r\n <option value=\"10\" [selected]=\"pageSize() === 10\">10</option>\r\n <option value=\"25\" [selected]=\"pageSize() === 25\">25</option>\r\n <option value=\"50\" [selected]=\"pageSize() === 50\">50</option>\r\n <option value=\"100\" [selected]=\"pageSize() === 100\">100</option>\r\n </select>\r\n </div>\r\n\r\n <nav aria-label=\"Paginaci\u00F3n de tabla\">\r\n <ul class=\"pagination pagination-md mb-0\">\r\n <li class=\"page-item\" [class.disabled]=\"currentPage() === 1\">\r\n <button type=\"button\" class=\"page-link\" (click)=\"goToPage(currentPage() - 1)\"><<</button>\r\n </li>\r\n\r\n @for (pageNumber of pagesToShow(); track pageNumber) {\r\n <li class=\"page-item\" [class.active]=\"currentPage() === pageNumber\">\r\n <button type=\"button\" class=\"page-link\" (click)=\"goToPage(pageNumber)\">\r\n {{ pageNumber }}\r\n </button>\r\n </li>\r\n }\r\n\r\n <li class=\"page-item\" [class.disabled]=\"currentPage() === totalPages()\">\r\n <button type=\"button\" class=\"page-link\" (click)=\"goToPage(currentPage() + 1)\">>></button>\r\n </li>\r\n </ul>\r\n </nav>\r\n </div>\r\n</div>\r\n" }]
|
|
223
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], filterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPlaceholder", required: false }] }], showExportButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExportButtons", required: false }] }], actionClicked: [{ type: i0.Output, args: ["actionClicked"] }], exportRequested: [{ type: i0.Output, args: ["exportRequested"] }] } });
|
|
224
|
+
|
|
225
|
+
/*
|
|
226
|
+
* Public API Surface of data-table
|
|
227
|
+
*/
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Generated bundle index. Do not edit.
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
export { DataTable };
|
|
234
|
+
//# sourceMappingURL=ddiazr-data-table.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ddiazr-data-table.mjs","sources":["../../../projects/data-table/src/lib/pipes/safe-date.pipe.ts","../../../projects/data-table/src/lib/pipes/safe-currency.pipe.ts","../../../projects/data-table/src/lib/pipes/safe-number.pipe.ts","../../../projects/data-table/src/lib/data-table.ts","../../../projects/data-table/src/lib/data-table.html","../../../projects/data-table/src/public-api.ts","../../../projects/data-table/src/ddiazr-data-table.ts"],"sourcesContent":["import { Pipe, PipeTransform } from \"@angular/core\";\r\n\r\n@Pipe({\r\n name: \"safeDate\",\r\n standalone: true,\r\n pure: true, // ✅ Muy importante para performance\r\n})\r\nexport class SafeDatePipe implements PipeTransform {\r\n transform(value: any): string {\r\n if (!value) return \"\";\r\n const date = new Date(value);\r\n if (isNaN(date.getTime())) return value;\r\n\r\n const day = String(date.getDate()).padStart(2, \"0\");\r\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\r\n const year = date.getFullYear();\r\n\r\n return `${day}/${month}/${year}`;\r\n }\r\n}\r\n","import { Pipe, PipeTransform } from \"@angular/core\";\r\n\r\n@Pipe({\r\n name: 'safeCurrency',\r\n standalone: true,\r\n pure: true\r\n})\r\nexport class SafeCurrencyPipe implements PipeTransform {\r\n transform(value: any): string {\r\n if (value == null || value === '') return '';\r\n const num = Number(value);\r\n if (isNaN(num)) return value;\r\n \r\n return 'Q' + num.toFixed(2).replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\r\n }\r\n}","import { Pipe, PipeTransform } from \"@angular/core\";\r\n\r\n@Pipe({\r\n name: \"safeNumber\",\r\n standalone: true,\r\n pure: true,\r\n})\r\nexport class SafeNumberPipe implements PipeTransform {\r\n transform(value: any): string {\r\n if (value == null || value === \"\") return \"\";\r\n const num = Number(value);\r\n if (isNaN(num)) return value;\r\n\r\n return num.toFixed(2).replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n }\r\n}\r\n","import { CommonModule } from '@angular/common';\r\nimport { Component, computed, input, output, signal, WritableSignal } from '@angular/core';\r\nimport { FormsModule } from '@angular/forms';\r\nimport { SortDirection, TableColumn, SortState, TableAction } from './interfaces/table.interface';\r\nimport { SafeDatePipe } from './pipes/safe-date.pipe';\r\nimport { SafeCurrencyPipe } from './pipes/safe-currency.pipe';\r\nimport { SafeNumberPipe } from './pipes/safe-number.pipe';\r\n@Component({\r\n selector: 'dtbl-data-table',\r\n imports: [CommonModule, FormsModule, SafeDatePipe, SafeCurrencyPipe, SafeNumberPipe],\r\n templateUrl: './data-table.html',\r\n styles: ``,\r\n})\r\nexport class DataTable<T extends { id?: any }> {\r\n // ------------------------------------\r\n // I. INPUT SIGNALS\r\n // ------------------------------------\r\n public data = input.required<T[]>();\r\n public columns = input.required<TableColumn<T>[]>();\r\n public actions = input<TableAction<T>[]>([]);\r\n public filterPlaceholder = input<string>('Buscar...');\r\n public showExportButtons = input<boolean>(true);\r\n\r\n // ------------------------------------\r\n // II. OUTPUTS (Usando la función output())\r\n // ------------------------------------\r\n public actionClicked = output<{ action: string; rowData: T }>();\r\n public exportRequested = output<'pdf' | 'excel' | 'print'>();\r\n\r\n // ------------------------------------\r\n // III. ESTADO INTERNO (Signals para Filtrado)\r\n // ------------------------------------\r\n\r\n public filterTerm: WritableSignal<string> = signal('');\r\n public sortState: WritableSignal<SortState<T>> = signal({\r\n column: null,\r\n direction: null,\r\n });\r\n\r\n public currentPage: WritableSignal<number> = signal(1); // Página actual, inicia en 1\r\n public pageSize: WritableSignal<number> = signal(10); // Elementos por página\r\n\r\n // 4. Datos filtrados (Computed Signal - LÓGICA DE BÚSQUEDA)\r\n public filteredData = computed(() => {\r\n const term = this.filterTerm().toLowerCase();\r\n const currentData = this.data();\r\n\r\n if (!term) return currentData;\r\n\r\n return currentData.filter((item) =>\r\n Object.values(item as object).some((value) => String(value).toLowerCase().includes(term)),\r\n );\r\n });\r\n\r\n public sortedAndFilteredData = computed(() => {\r\n const data = [...this.filteredData()]; // Copia para ordenar\r\n const state = this.sortState();\r\n\r\n if (!state.column || !state.direction) {\r\n return data; // Sin ordenamiento\r\n }\r\n\r\n const directionMultiplier = state.direction === 'asc' ? 1 : -1;\r\n const columnKey = state.column as keyof T;\r\n\r\n return data.sort((a, b) => {\r\n // Usamos localeCompare para ordenar strings correctamente, si no, comparación simple\r\n const aValue = a[columnKey];\r\n const bValue = b[columnKey];\r\n\r\n if (typeof aValue === 'string' && typeof bValue === 'string') {\r\n return aValue.localeCompare(bValue) * directionMultiplier;\r\n }\r\n\r\n if (aValue < bValue) {\r\n return -1 * directionMultiplier;\r\n }\r\n if (aValue > bValue) {\r\n return 1 * directionMultiplier;\r\n }\r\n return 0;\r\n });\r\n });\r\n\r\n public paginatedData = computed(() => {\r\n const data = this.sortedAndFilteredData();\r\n const size = this.pageSize();\r\n const page = this.currentPage();\r\n\r\n const start = (page - 1) * size;\r\n const end = start + size;\r\n\r\n // Retorna solo el slice (rebanada) de datos de la página actual\r\n return data.slice(start, end);\r\n });\r\n\r\n public totalPages = computed(() => {\r\n const totalItems = this.sortedAndFilteredData().length;\r\n const size = this.pageSize();\r\n return Math.ceil(totalItems / size);\r\n });\r\n\r\n public pagesToShow = computed<number[]>(() => {\r\n const total = this.totalPages();\r\n const current = this.currentPage();\r\n const maxPagesToShow = 5; // Máximo de botones numéricos a mostrar\r\n const pages: number[] = [];\r\n\r\n // Lógica simple para centrar los botones en la página actual\r\n let start = Math.max(1, current - Math.floor(maxPagesToShow / 2));\r\n let end = Math.min(total, start + maxPagesToShow - 1);\r\n\r\n // Ajuste si la tabla es pequeña\r\n if (end - start + 1 < maxPagesToShow) {\r\n start = Math.max(1, end - maxPagesToShow + 1);\r\n }\r\n\r\n for (let i = start; i <= end; i++) {\r\n pages.push(i);\r\n }\r\n return pages;\r\n });\r\n\r\n // ------------------------------------\r\n // IV. FUNCIONES\r\n // ------------------------------------\r\n onSort(columnKey: keyof T | 'actions' | 'select'): void {\r\n // Solo permitir ordenar si no es una columna de acción/selección\r\n if (columnKey === 'actions' || columnKey === 'select') return;\r\n\r\n this.sortState.update((current) => {\r\n let newDirection: SortDirection = 'asc';\r\n\r\n if (current.column === columnKey) {\r\n // Mismo column, se alterna la dirección\r\n newDirection = current.direction === 'asc' ? 'desc' : 'asc';\r\n }\r\n\r\n return {\r\n column: columnKey as keyof T,\r\n direction: newDirection,\r\n };\r\n });\r\n }\r\n\r\n /**\r\n * FUNCIÓN DE BÚSQUEDA\r\n * Actualiza el WritableSignal interno.\r\n */\r\n onSearchChange(term: string): void {\r\n this.filterTerm.set(term);\r\n }\r\n\r\n /**\r\n * FUNCIÓN DE ACCIONES\r\n * Emite el evento usando .emit() del Output Signal.\r\n */\r\n onActionClick(action: string, rowData: T): void {\r\n this.actionClicked.emit({ action, rowData });\r\n }\r\n\r\n /**\r\n * FUNCIÓN DE EXPORTACIÓN\r\n * Emite el evento de salida, delegando la lógica al padre.\r\n */\r\n exportData(format: 'pdf' | 'excel' | 'print'): void {\r\n this.exportRequested.emit(format);\r\n }\r\n\r\n getConfig(key: string): TableColumn<T> | undefined {\r\n return this.columns().find((c) => c.key === key);\r\n }\r\n\r\n goToPage(page: number): void {\r\n const total = this.totalPages();\r\n if (page >= 1 && page <= total) {\r\n this.currentPage.set(page);\r\n }\r\n }\r\n\r\n onPageSizeChange(event: Event): void {\r\n const target = event.target as HTMLSelectElement;\r\n const newSize = Number(target.value);\r\n\r\n this.pageSize.set(newSize);\r\n // Reiniciar a la página 1 después de cambiar el tamaño para evitar errores de índice\r\n this.currentPage.set(1);\r\n }\r\n}\r\n","<div class=\"container-fluid pt-3\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <input\r\n type=\"text\"\r\n class=\"form-control w-25\"\r\n [placeholder]=\"filterPlaceholder()\"\r\n [ngModel]=\"filterTerm()\"\r\n (ngModelChange)=\"onSearchChange($event)\"\r\n />\r\n\r\n @if (showExportButtons()) {\r\n <div class=\"export-buttons btn-group\" role=\"group\">\r\n <button class=\"btn btn-outline-success\" (click)=\"exportData('excel')\">\r\n <i class=\"bi bi-file-earmark-excel\"></i> Exportar a Excel\r\n </button>\r\n <button class=\"btn btn-outline-danger\" (click)=\"exportData('pdf')\">\r\n <i class=\"bi bi-file-earmark-pdf\"></i> Exportar a PDF\r\n </button>\r\n <button class=\"btn btn-outline-secondary\" (click)=\"exportData('print')\">\r\n <i class=\"bi bi-printer\"></i> Imprimir\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"table-responsive\">\r\n <table class=\"table table-hover table-bordered caption-top\">\r\n <caption>\r\n Mostrando\r\n {{\r\n paginatedData().length\r\n }}\r\n de\r\n {{\r\n sortedAndFilteredData().length\r\n }}\r\n resultados (Total:\r\n {{\r\n data().length\r\n }}).\r\n </caption>\r\n\r\n <thead>\r\n <tr>\r\n @for (col of columns(); track col.key) {\r\n <th\r\n [class.sortable]=\"col.isSortable\"\r\n (click)=\"col.isSortable && onSort(col.key)\"\r\n scope=\"col\"\r\n class=\"text-nowrap\"\r\n >\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n <span class=\"fw-bold\">{{ col.label }}</span>\r\n\r\n @if (col.isSortable) {\r\n @if (col.key === sortState().column) {\r\n <i\r\n class=\"fa-solid ms-1\"\r\n [class.fa-sort-up]=\"sortState().direction === 'asc'\"\r\n [class.fa-sort-down]=\"sortState().direction === 'desc'\"\r\n >\r\n </i>\r\n } @else {\r\n <i class=\"fa-solid fa-sort text-muted ms-1\" style=\"opacity: 0.5\"></i>\r\n }\r\n }\r\n </div>\r\n </th>\r\n }\r\n @if (actions().length > 0) {\r\n <th class=\"bg-dark text-center text-white\">Acciones</th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n @for (row of paginatedData(); track row.id || $index) {\r\n <tr>\r\n @for (col of columns(); track col.key) {\r\n <td class=\"text-nowrap\">\r\n @if (col.templateRef) {\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n col.templateRef;\r\n context: { $implicit: row, column: col.key }\r\n \"\r\n ></ng-container>\r\n } @else {\r\n <!-- {{ formatCellValue(row, col) }} -->\r\n @let config = getConfig(col.key.toString());\r\n @switch (config?.dataType) {\r\n @case ('date') {\r\n {{ $any(row)[col.key] | safeDate }}\r\n }\r\n @case ('currency') {\r\n {{ $any(row)[col.key] | safeCurrency }}\r\n }\r\n @case ('number') {\r\n {{ $any(row)[col.key] | safeNumber }}\r\n }\r\n @default {\r\n {{ $any(row)[col.key] }}\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n @if (actions().length > 0) {\r\n <td class=\"text-center\">\r\n @for (action of actions(); track action.label) {\r\n @if (!action.isVisible || action.isVisible(row)) {\r\n <button\r\n [class]=\"'btn btn-sm mx-1 ' + action.styleClass\"\r\n (click)=\"action.callback(row)\"\r\n >\r\n <i [class]=\"action.icon\"></i>\r\n </button>\r\n }\r\n }\r\n </td>\r\n }\r\n </tr>\r\n } @empty {\r\n <tr>\r\n <td [attr.colspan]=\"columns().length\" class=\"text-center\">\r\n No hay datos disponibles para mostrar.\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-3\">\r\n <div class=\"d-flex align-items-center\">\r\n <label for=\"pageSizeSelect\" class=\"me-2 text-nowrap\">Artículos por página: </label>\r\n <select\r\n id=\"pageSizeSelect\"\r\n class=\"form-select form-select-sm w-auto\"\r\n (change)=\"onPageSizeChange($event)\"\r\n >\r\n <option value=\"10\" [selected]=\"pageSize() === 10\">10</option>\r\n <option value=\"25\" [selected]=\"pageSize() === 25\">25</option>\r\n <option value=\"50\" [selected]=\"pageSize() === 50\">50</option>\r\n <option value=\"100\" [selected]=\"pageSize() === 100\">100</option>\r\n </select>\r\n </div>\r\n\r\n <nav aria-label=\"Paginación de tabla\">\r\n <ul class=\"pagination pagination-md mb-0\">\r\n <li class=\"page-item\" [class.disabled]=\"currentPage() === 1\">\r\n <button type=\"button\" class=\"page-link\" (click)=\"goToPage(currentPage() - 1)\"><<</button>\r\n </li>\r\n\r\n @for (pageNumber of pagesToShow(); track pageNumber) {\r\n <li class=\"page-item\" [class.active]=\"currentPage() === pageNumber\">\r\n <button type=\"button\" class=\"page-link\" (click)=\"goToPage(pageNumber)\">\r\n {{ pageNumber }}\r\n </button>\r\n </li>\r\n }\r\n\r\n <li class=\"page-item\" [class.disabled]=\"currentPage() === totalPages()\">\r\n <button type=\"button\" class=\"page-link\" (click)=\"goToPage(currentPage() + 1)\">>></button>\r\n </li>\r\n </ul>\r\n </nav>\r\n </div>\r\n</div>\r\n","/*\r\n * Public API Surface of data-table\r\n */\r\n\r\nexport * from './lib/data-table';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ddiazr/data-table",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Tabla generica sirve para acciones, export PDF,EXCEl",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Dany Díaz",
|
|
7
|
+
"email": "danylen1@hotmail.com"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"@angular/common": "^21.0.0",
|
|
12
|
+
"@angular/core": "^21.0.0",
|
|
13
|
+
"bootstrap": "^5.3.8",
|
|
14
|
+
"@fortawesome/fontawesome-free": "^7.1.0",
|
|
15
|
+
"jspdf": "^4.0.0",
|
|
16
|
+
"jspdf-autotable": "^5.0.7",
|
|
17
|
+
"xlsx": "^0.18.5"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"tslib": "^2.3.0"
|
|
21
|
+
},
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"module": "fesm2022/ddiazr-data-table.mjs",
|
|
24
|
+
"typings": "types/ddiazr-data-table.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
"./package.json": {
|
|
27
|
+
"default": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./types/ddiazr-data-table.d.ts",
|
|
31
|
+
"default": "./fesm2022/ddiazr-data-table.mjs"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { WritableSignal } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
type DataType = 'string' | 'number' | 'currency' | 'date' | 'boolean';
|
|
5
|
+
type SortDirection = 'asc' | 'desc' | null;
|
|
6
|
+
/**
|
|
7
|
+
* Define la configuración de una columna en la tabla genérica.
|
|
8
|
+
* @template T El tipo de dato de la fila (ej: User, Product).
|
|
9
|
+
*/
|
|
10
|
+
interface TableColumn<T> {
|
|
11
|
+
key: keyof T | 'actions' | 'select';
|
|
12
|
+
label: string;
|
|
13
|
+
isSortable?: boolean;
|
|
14
|
+
templateRef?: any;
|
|
15
|
+
dataType?: DataType;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Define el estado actual de ordenamiento de la tabla.
|
|
19
|
+
* @template T El tipo de dato de la fila.
|
|
20
|
+
*/
|
|
21
|
+
interface SortState<T> {
|
|
22
|
+
column: keyof T | null;
|
|
23
|
+
direction: SortDirection;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Define un botón o acción específica dentro de la columna 'actions'.
|
|
27
|
+
*/
|
|
28
|
+
interface TableAction<T> {
|
|
29
|
+
label: string;
|
|
30
|
+
icon?: string;
|
|
31
|
+
styleClass?: string;
|
|
32
|
+
callback: (row: T) => void;
|
|
33
|
+
isVisible?: (row: T) => boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare class DataTable<T extends {
|
|
37
|
+
id?: any;
|
|
38
|
+
}> {
|
|
39
|
+
data: _angular_core.InputSignal<T[]>;
|
|
40
|
+
columns: _angular_core.InputSignal<TableColumn<T>[]>;
|
|
41
|
+
actions: _angular_core.InputSignal<TableAction<T>[]>;
|
|
42
|
+
filterPlaceholder: _angular_core.InputSignal<string>;
|
|
43
|
+
showExportButtons: _angular_core.InputSignal<boolean>;
|
|
44
|
+
actionClicked: _angular_core.OutputEmitterRef<{
|
|
45
|
+
action: string;
|
|
46
|
+
rowData: T;
|
|
47
|
+
}>;
|
|
48
|
+
exportRequested: _angular_core.OutputEmitterRef<"pdf" | "excel" | "print">;
|
|
49
|
+
filterTerm: WritableSignal<string>;
|
|
50
|
+
sortState: WritableSignal<SortState<T>>;
|
|
51
|
+
currentPage: WritableSignal<number>;
|
|
52
|
+
pageSize: WritableSignal<number>;
|
|
53
|
+
filteredData: _angular_core.Signal<T[]>;
|
|
54
|
+
sortedAndFilteredData: _angular_core.Signal<T[]>;
|
|
55
|
+
paginatedData: _angular_core.Signal<T[]>;
|
|
56
|
+
totalPages: _angular_core.Signal<number>;
|
|
57
|
+
pagesToShow: _angular_core.Signal<number[]>;
|
|
58
|
+
onSort(columnKey: keyof T | 'actions' | 'select'): void;
|
|
59
|
+
/**
|
|
60
|
+
* FUNCIÓN DE BÚSQUEDA
|
|
61
|
+
* Actualiza el WritableSignal interno.
|
|
62
|
+
*/
|
|
63
|
+
onSearchChange(term: string): void;
|
|
64
|
+
/**
|
|
65
|
+
* FUNCIÓN DE ACCIONES
|
|
66
|
+
* Emite el evento usando .emit() del Output Signal.
|
|
67
|
+
*/
|
|
68
|
+
onActionClick(action: string, rowData: T): void;
|
|
69
|
+
/**
|
|
70
|
+
* FUNCIÓN DE EXPORTACIÓN
|
|
71
|
+
* Emite el evento de salida, delegando la lógica al padre.
|
|
72
|
+
*/
|
|
73
|
+
exportData(format: 'pdf' | 'excel' | 'print'): void;
|
|
74
|
+
getConfig(key: string): TableColumn<T> | undefined;
|
|
75
|
+
goToPage(page: number): void;
|
|
76
|
+
onPageSizeChange(event: Event): void;
|
|
77
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DataTable<any>, never>;
|
|
78
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<DataTable<any>, "dtbl-data-table", never, { "data": { "alias": "data"; "required": true; "isSignal": true; }; "columns": { "alias": "columns"; "required": true; "isSignal": true; }; "actions": { "alias": "actions"; "required": false; "isSignal": true; }; "filterPlaceholder": { "alias": "filterPlaceholder"; "required": false; "isSignal": true; }; "showExportButtons": { "alias": "showExportButtons"; "required": false; "isSignal": true; }; }, { "actionClicked": "actionClicked"; "exportRequested": "exportRequested"; }, never, never, true, never>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { DataTable };
|