@c80/ui 1.0.58 → 1.0.63
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/esm2022/index.js +1 -0
- package/esm2022/index.js.map +1 -1
- package/esm2022/lib/action-list/action-list.types.js.map +1 -1
- package/esm2022/lib/card-level/card-level.component.js +1 -3
- package/esm2022/lib/card-level/card-level.component.js.map +1 -1
- package/esm2022/lib/card-level/card-level.interface.js.map +1 -1
- package/esm2022/lib/card-level/index.js.map +1 -1
- package/esm2022/lib/error-notification/error-notification.component.js +41 -0
- package/esm2022/lib/error-notification/error-notification.component.js.map +1 -0
- package/esm2022/lib/error-notification/error-notification.types.js +2 -0
- package/esm2022/lib/error-notification/error-notification.types.js.map +1 -0
- package/esm2022/lib/error-notification/index.js +3 -0
- package/esm2022/lib/error-notification/index.js.map +1 -0
- package/esm2022/lib/header/header.component.js +2 -2
- package/esm2022/lib/header/header.component.js.map +1 -1
- package/esm2022/lib/icon/icon.component.js +1 -3
- package/esm2022/lib/icon/icon.component.js.map +1 -1
- package/esm2022/lib/icon/icon.definitions.js +74 -1
- package/esm2022/lib/icon/icon.definitions.js.map +1 -1
- package/esm2022/lib/icon/theme.service.js +3 -0
- package/esm2022/lib/icon/theme.service.js.map +1 -1
- package/esm2022/lib/info-list/info-list.component.js +2 -2
- package/esm2022/lib/modal/index.js.map +1 -1
- package/esm2022/lib/modal/modal.component.js +2 -2
- package/esm2022/lib/modal/modal.component.js.map +1 -1
- package/esm2022/lib/modal/modal.service.js.map +1 -1
- package/esm2022/lib/modal/modal.types.js +2 -0
- package/esm2022/lib/modal/modal.types.js.map +1 -0
- package/esm2022/lib/profile-stats/profile-stats.component.js +2 -2
- package/esm2022/lib/select/index.js +1 -1
- package/esm2022/lib/select/index.js.map +1 -1
- package/esm2022/lib/select/select.component.js +4 -1
- package/esm2022/lib/select/select.component.js.map +1 -1
- package/esm2022/lib/select/select.types.js +2 -0
- package/esm2022/lib/select/select.types.js.map +1 -0
- package/esm2022/lib/snackbar/index.js.map +1 -1
- package/esm2022/lib/snackbar/snackbar.component.js +2 -2
- package/esm2022/lib/snackbar/snackbar.component.js.map +1 -1
- package/esm2022/lib/snackbar/snackbar.service.js.map +1 -1
- package/esm2022/lib/snackbar/snackbar.types.js +2 -0
- package/esm2022/lib/snackbar/{snackbar.model.js.map → snackbar.types.js.map} +1 -1
- package/esm2022/lib/stat-card/index.js.map +1 -1
- package/esm2022/lib/stat-card/stat-card.component.js.map +1 -1
- package/esm2022/lib/stat-card/stat-card.types.js +2 -0
- package/esm2022/lib/stat-card/stat-card.types.js.map +1 -0
- package/esm2022/lib/tab/c80-tab.types.js +2 -0
- package/esm2022/lib/tab/c80-tab.types.js.map +1 -0
- package/esm2022/lib/tab/index.js.map +1 -1
- package/esm2022/lib/table/index.js +2 -0
- package/esm2022/lib/table/index.js.map +1 -1
- package/esm2022/lib/table/table-data-utils.service.js +1 -3
- package/esm2022/lib/table/table-data-utils.service.js.map +1 -1
- package/esm2022/lib/table/table-dto-mapper.service.js +98 -0
- package/esm2022/lib/table/table-dto-mapper.service.js.map +1 -0
- package/esm2022/lib/table/table-pagination.service.js +79 -0
- package/esm2022/lib/table/table-pagination.service.js.map +1 -0
- package/esm2022/lib/table/table.component.js +66 -6
- package/esm2022/lib/table/table.component.js.map +1 -1
- package/esm2022/lib/table/table.types.js.map +1 -1
- package/esm2022/lib/table/table.utils.js +3 -2
- package/esm2022/lib/table/table.utils.js.map +1 -1
- package/index.d.ts +1 -0
- package/lib/action-list/action-list.types.d.ts +1 -1
- package/lib/error-notification/error-notification.component.d.ts +20 -0
- package/lib/error-notification/error-notification.types.d.ts +4 -0
- package/lib/error-notification/index.d.ts +2 -0
- package/lib/icon/icon.component.d.ts +1 -1
- package/lib/modal/index.d.ts +1 -0
- package/lib/modal/modal.component.d.ts +1 -16
- package/lib/modal/modal.service.d.ts +1 -1
- package/lib/modal/modal.types.d.ts +15 -0
- package/lib/select/index.d.ts +1 -1
- package/lib/select/select.component.d.ts +2 -1
- package/lib/snackbar/index.d.ts +1 -1
- package/lib/snackbar/snackbar.component.d.ts +1 -1
- package/lib/snackbar/snackbar.service.d.ts +1 -1
- package/lib/stat-card/index.d.ts +1 -0
- package/lib/stat-card/stat-card.component.d.ts +1 -7
- package/lib/stat-card/stat-card.types.d.ts +7 -0
- package/lib/tab/index.d.ts +1 -1
- package/lib/table/index.d.ts +2 -0
- package/lib/table/table-dto-mapper.service.d.ts +34 -0
- package/lib/table/table-pagination.service.d.ts +41 -0
- package/lib/table/table.component.d.ts +24 -3
- package/lib/table/table.types.d.ts +16 -1
- package/lib/table/table.utils.d.ts +2 -1
- package/package.json +1 -1
- package/esm2022/lib/select/select.model.js +0 -2
- package/esm2022/lib/select/select.model.js.map +0 -1
- package/esm2022/lib/snackbar/snackbar.model.js +0 -2
- package/esm2022/lib/tab/c80-tab.model.js +0 -2
- package/esm2022/lib/tab/c80-tab.model.js.map +0 -1
- /package/lib/select/{select.model.d.ts → select.types.d.ts} +0 -0
- /package/lib/snackbar/{snackbar.model.d.ts → snackbar.types.d.ts} +0 -0
- /package/lib/tab/{c80-tab.model.d.ts → c80-tab.types.d.ts} +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Servicio para mapear automáticamente datos de tabla a DTOs
|
|
5
|
+
* basándose en la configuración de TableColDef.
|
|
6
|
+
*
|
|
7
|
+
* Reglas de exclusión:
|
|
8
|
+
* - readOnly: true → campo NO se incluye
|
|
9
|
+
* - accessor: 'id' → campo NO se incluye (IDs van en URL, no en body)
|
|
10
|
+
* - readOnly: false o undefined → campo se incluye
|
|
11
|
+
*/
|
|
12
|
+
export class TableDtoMapperService {
|
|
13
|
+
/**
|
|
14
|
+
* Mapea un objeto de fila a DTO, excluyendo campos readOnly e id
|
|
15
|
+
* @param row - Datos de la fila
|
|
16
|
+
* @param columns - Definiciones de columnas
|
|
17
|
+
* @param additionalFields - Campos adicionales a agregar al DTO (ej: boxId)
|
|
18
|
+
* @returns DTO con solo campos editables (sin id)
|
|
19
|
+
*/
|
|
20
|
+
mapToDto(row, columns, additionalFields) {
|
|
21
|
+
const dto = {};
|
|
22
|
+
// Mapear solo columnas NO readOnly y NO id
|
|
23
|
+
const editableColumns = columns.filter(col => !col.readOnly && col.accessor !== 'id');
|
|
24
|
+
for (const col of editableColumns) {
|
|
25
|
+
const value = row[col.accessor];
|
|
26
|
+
// Solo incluir si el valor existe
|
|
27
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
28
|
+
dto[col.accessor] = this.convertValue(value, col);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Agregar campos adicionales (ej: boxId, profileId)
|
|
32
|
+
if (additionalFields) {
|
|
33
|
+
Object.assign(dto, additionalFields);
|
|
34
|
+
}
|
|
35
|
+
return dto;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Convierte un valor según el tipo de columna
|
|
39
|
+
* @param value - Valor a convertir
|
|
40
|
+
* @param col - Definición de columna
|
|
41
|
+
* @returns Valor convertido
|
|
42
|
+
*/
|
|
43
|
+
convertValue(value, col) {
|
|
44
|
+
switch (col.type) {
|
|
45
|
+
case 'integer':
|
|
46
|
+
return this.toInteger(value, col);
|
|
47
|
+
case 'number':
|
|
48
|
+
return this.toNumber(value);
|
|
49
|
+
case 'boolean':
|
|
50
|
+
return this.toBoolean(value);
|
|
51
|
+
case 'string':
|
|
52
|
+
case 'password':
|
|
53
|
+
case 'enum':
|
|
54
|
+
return this.toString(value);
|
|
55
|
+
default:
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
toInteger(value, col) {
|
|
60
|
+
if (typeof value !== 'number')
|
|
61
|
+
return undefined;
|
|
62
|
+
let result = Math.floor(value);
|
|
63
|
+
// Aplicar min/max si existen
|
|
64
|
+
if (col.min !== undefined)
|
|
65
|
+
result = Math.max(result, col.min);
|
|
66
|
+
if (col.max !== undefined)
|
|
67
|
+
result = Math.min(result, col.max);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
toNumber(value) {
|
|
71
|
+
if (typeof value === 'number')
|
|
72
|
+
return value;
|
|
73
|
+
if (typeof value === 'string') {
|
|
74
|
+
const num = Number(value);
|
|
75
|
+
return Number.isNaN(num) ? undefined : num;
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
toBoolean(value) {
|
|
80
|
+
if (typeof value === 'boolean')
|
|
81
|
+
return value;
|
|
82
|
+
if (typeof value === 'string')
|
|
83
|
+
return value.toLowerCase() === 'true';
|
|
84
|
+
return Boolean(value);
|
|
85
|
+
}
|
|
86
|
+
toString(value) {
|
|
87
|
+
return String(value);
|
|
88
|
+
}
|
|
89
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TableDtoMapperService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
90
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TableDtoMapperService, providedIn: 'root' });
|
|
91
|
+
}
|
|
92
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TableDtoMapperService, decorators: [{
|
|
93
|
+
type: Injectable,
|
|
94
|
+
args: [{
|
|
95
|
+
providedIn: 'root'
|
|
96
|
+
}]
|
|
97
|
+
}] });
|
|
98
|
+
//# sourceMappingURL=table-dto-mapper.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-dto-mapper.service.js","sourceRoot":"","sources":["../../../../../libs/ui/src/lib/table/table-dto-mapper.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport type { TableColDef } from './table.types';\n\n/**\n * Servicio para mapear automáticamente datos de tabla a DTOs\n * basándose en la configuración de TableColDef.\n * \n * Reglas de exclusión:\n * - readOnly: true → campo NO se incluye\n * - accessor: 'id' → campo NO se incluye (IDs van en URL, no en body)\n * - readOnly: false o undefined → campo se incluye\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class TableDtoMapperService {\n\n /**\n * Mapea un objeto de fila a DTO, excluyendo campos readOnly e id\n * @param row - Datos de la fila\n * @param columns - Definiciones de columnas\n * @param additionalFields - Campos adicionales a agregar al DTO (ej: boxId)\n * @returns DTO con solo campos editables (sin id)\n */\n mapToDto(\n row: Record<string, unknown>,\n columns: TableColDef[],\n additionalFields?: Record<string, unknown>\n ): Record<string, unknown> {\n const dto: Record<string, unknown> = {};\n\n // Mapear solo columnas NO readOnly y NO id\n const editableColumns = columns.filter(col =>\n !col.readOnly && col.accessor !== 'id'\n );\n\n for (const col of editableColumns) {\n const value = row[col.accessor];\n\n // Solo incluir si el valor existe\n if (value !== undefined && value !== null && value !== '') {\n dto[col.accessor] = this.convertValue(value, col);\n }\n }\n\n // Agregar campos adicionales (ej: boxId, profileId)\n if (additionalFields) {\n Object.assign(dto, additionalFields);\n }\n\n return dto;\n }\n\n /**\n * Convierte un valor según el tipo de columna\n * @param value - Valor a convertir\n * @param col - Definición de columna\n * @returns Valor convertido\n */\n private convertValue(value: unknown, col: TableColDef): unknown {\n switch (col.type) {\n case 'integer':\n return this.toInteger(value, col);\n case 'number':\n return this.toNumber(value);\n case 'boolean':\n return this.toBoolean(value);\n case 'string':\n case 'password':\n case 'enum':\n return this.toString(value);\n default:\n return value;\n }\n }\n\n private toInteger(value: unknown, col: TableColDef): number | undefined {\n if (typeof value !== 'number') return undefined;\n\n let result = Math.floor(value);\n\n // Aplicar min/max si existen\n if (col.min !== undefined) result = Math.max(result, col.min);\n if (col.max !== undefined) result = Math.min(result, col.max);\n\n return result;\n }\n\n private toNumber(value: unknown): number | undefined {\n if (typeof value === 'number') return value;\n if (typeof value === 'string') {\n const num = Number(value);\n return Number.isNaN(num) ? undefined : num;\n }\n return undefined;\n }\n\n private toBoolean(value: unknown): boolean {\n if (typeof value === 'boolean') return value;\n if (typeof value === 'string') return value.toLowerCase() === 'true';\n return Boolean(value);\n }\n\n private toString(value: unknown): string {\n return String(value);\n }\n}\n"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Injectable, signal, computed } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Servicio para gestionar el estado de paginación en tablas C80
|
|
5
|
+
*
|
|
6
|
+
* Maneja:
|
|
7
|
+
* - Estado de paginación (página actual, tamaño, total)
|
|
8
|
+
* - Cálculo automático de offset para API (limit/offset)
|
|
9
|
+
* - Navegación (siguiente, anterior)
|
|
10
|
+
* - Validaciones de límites
|
|
11
|
+
*/
|
|
12
|
+
export class TablePaginationService {
|
|
13
|
+
/**
|
|
14
|
+
* Inicializa el estado de paginación para una nueva tabla
|
|
15
|
+
* @param initialPageSize - Tamaño de página inicial (default: 10)
|
|
16
|
+
* @returns Objeto con signals y métodos de paginación
|
|
17
|
+
*/
|
|
18
|
+
createPaginationState(initialPageSize = 10) {
|
|
19
|
+
const currentPage = signal(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
|
|
20
|
+
const pageSize = signal(initialPageSize, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
|
|
21
|
+
const totalItems = signal(0, ...(ngDevMode ? [{ debugName: "totalItems" }] : []));
|
|
22
|
+
const totalPages = computed(() => {
|
|
23
|
+
const total = totalItems();
|
|
24
|
+
const size = pageSize();
|
|
25
|
+
return Math.ceil(total / size) || 1;
|
|
26
|
+
}, ...(ngDevMode ? [{ debugName: "totalPages" }] : []));
|
|
27
|
+
const hasNextPage = computed(() => currentPage() < totalPages(), ...(ngDevMode ? [{ debugName: "hasNextPage" }] : []));
|
|
28
|
+
const hasPrevPage = computed(() => currentPage() > 1, ...(ngDevMode ? [{ debugName: "hasPrevPage" }] : []));
|
|
29
|
+
const offset = computed(() => (currentPage() - 1) * pageSize(), ...(ngDevMode ? [{ debugName: "offset" }] : []));
|
|
30
|
+
return {
|
|
31
|
+
currentPage,
|
|
32
|
+
pageSize,
|
|
33
|
+
totalItems,
|
|
34
|
+
totalPages,
|
|
35
|
+
hasNextPage,
|
|
36
|
+
hasPrevPage,
|
|
37
|
+
offset,
|
|
38
|
+
setPage: (page) => this.setPage(currentPage, page, totalPages),
|
|
39
|
+
setPageSize: (size) => this.setPageSize(pageSize, currentPage, size),
|
|
40
|
+
setTotalItems: (total) => totalItems.set(total),
|
|
41
|
+
nextPage: () => this.nextPage(currentPage, totalPages),
|
|
42
|
+
prevPage: () => this.prevPage(currentPage),
|
|
43
|
+
reset: () => this.reset(currentPage, totalItems)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
setPage(currentPageSignal, page, totalPages) {
|
|
47
|
+
const validPage = Math.max(1, Math.min(page, totalPages()));
|
|
48
|
+
currentPageSignal.set(validPage);
|
|
49
|
+
}
|
|
50
|
+
setPageSize(pageSizeSignal, currentPageSignal, size) {
|
|
51
|
+
pageSizeSignal.set(Math.max(1, size));
|
|
52
|
+
currentPageSignal.set(1);
|
|
53
|
+
}
|
|
54
|
+
nextPage(currentPageSignal, totalPages) {
|
|
55
|
+
const current = currentPageSignal();
|
|
56
|
+
if (current < totalPages()) {
|
|
57
|
+
currentPageSignal.set(current + 1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
prevPage(currentPageSignal) {
|
|
61
|
+
const current = currentPageSignal();
|
|
62
|
+
if (current > 1) {
|
|
63
|
+
currentPageSignal.set(current - 1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
reset(currentPageSignal, totalItemsSignal) {
|
|
67
|
+
currentPageSignal.set(1);
|
|
68
|
+
totalItemsSignal.set(0);
|
|
69
|
+
}
|
|
70
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TablePaginationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
71
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TablePaginationService, providedIn: 'root' });
|
|
72
|
+
}
|
|
73
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TablePaginationService, decorators: [{
|
|
74
|
+
type: Injectable,
|
|
75
|
+
args: [{
|
|
76
|
+
providedIn: 'root'
|
|
77
|
+
}]
|
|
78
|
+
}] });
|
|
79
|
+
//# sourceMappingURL=table-pagination.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-pagination.service.js","sourceRoot":"","sources":["../../../../../libs/ui/src/lib/table/table-pagination.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, signal, computed, type Signal } from '@angular/core';\n\nexport interface TablePaginationState {\n currentPage: Signal<number>;\n pageSize: Signal<number>;\n totalItems: Signal<number>;\n totalPages: Signal<number>;\n hasNextPage: Signal<boolean>;\n hasPrevPage: Signal<boolean>;\n offset: Signal<number>;\n setPage: (page: number) => void;\n setPageSize: (size: number) => void;\n setTotalItems: (total: number) => void;\n nextPage: () => void;\n prevPage: () => void;\n reset: () => void;\n}\n\n/**\n * Servicio para gestionar el estado de paginación en tablas C80\n *\n * Maneja:\n * - Estado de paginación (página actual, tamaño, total)\n * - Cálculo automático de offset para API (limit/offset)\n * - Navegación (siguiente, anterior)\n * - Validaciones de límites\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class TablePaginationService {\n\n /**\n * Inicializa el estado de paginación para una nueva tabla\n * @param initialPageSize - Tamaño de página inicial (default: 10)\n * @returns Objeto con signals y métodos de paginación\n */\n createPaginationState(initialPageSize = 10): TablePaginationState {\n const currentPage = signal(1);\n const pageSize = signal(initialPageSize);\n const totalItems = signal(0);\n\n const totalPages = computed(() => {\n const total = totalItems();\n const size = pageSize();\n return Math.ceil(total / size) || 1;\n });\n\n const hasNextPage = computed(() => currentPage() < totalPages());\n const hasPrevPage = computed(() => currentPage() > 1);\n const offset = computed(() => (currentPage() - 1) * pageSize());\n\n return {\n currentPage,\n pageSize,\n totalItems,\n totalPages,\n hasNextPage,\n hasPrevPage,\n offset,\n setPage: (page: number) => this.setPage(currentPage, page, totalPages),\n setPageSize: (size: number) => this.setPageSize(pageSize, currentPage, size),\n setTotalItems: (total: number) => totalItems.set(total),\n nextPage: () => this.nextPage(currentPage, totalPages),\n prevPage: () => this.prevPage(currentPage),\n reset: () => this.reset(currentPage, totalItems)\n };\n }\n\n private setPage(\n currentPageSignal: ReturnType<typeof signal<number>>,\n page: number,\n totalPages: Signal<number>\n ): void {\n const validPage = Math.max(1, Math.min(page, totalPages()));\n currentPageSignal.set(validPage);\n }\n\n private setPageSize(\n pageSizeSignal: ReturnType<typeof signal<number>>,\n currentPageSignal: ReturnType<typeof signal<number>>,\n size: number\n ): void {\n pageSizeSignal.set(Math.max(1, size));\n currentPageSignal.set(1);\n }\n\n private nextPage(\n currentPageSignal: ReturnType<typeof signal<number>>,\n totalPages: Signal<number>\n ): void {\n const current = currentPageSignal();\n if (current < totalPages()) {\n currentPageSignal.set(current + 1);\n }\n }\n\n private prevPage(\n currentPageSignal: ReturnType<typeof signal<number>>\n ): void {\n const current = currentPageSignal();\n if (current > 1) {\n currentPageSignal.set(current - 1);\n }\n }\n\n private reset(\n currentPageSignal: ReturnType<typeof signal<number>>,\n totalItemsSignal: ReturnType<typeof signal<number>>\n ): void {\n currentPageSignal.set(1);\n totalItemsSignal.set(0);\n }\n}\n"]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Component, ChangeDetectionStrategy, input, signal, computed, output, inject, } from '@angular/core';
|
|
1
|
+
import { Component, ChangeDetectionStrategy, input, signal, computed, output, inject, effect, } from '@angular/core';
|
|
2
2
|
import { IconComponent } from '../icon';
|
|
3
3
|
import { TableColumnVisibilityService } from './table-column-visibility.service';
|
|
4
4
|
import { TableDataUtilsService } from './table-data-utils.service';
|
|
5
5
|
import { TableDataConverterService } from './table-data-converter.service';
|
|
6
6
|
import { TableSelectionService } from './table-selection.service';
|
|
7
7
|
import { TableCrudStateService } from './table-crud-state.service';
|
|
8
|
+
import { TablePaginationService } from './table-pagination.service';
|
|
8
9
|
import { ModalComponent, ModalService } from '../modal';
|
|
9
10
|
import { booleanAttribute, getErrorMessage, getInputValue, trackById, shouldShowAction, getActionTooltip, } from './table.utils';
|
|
10
11
|
import * as i0 from "@angular/core";
|
|
@@ -132,13 +133,14 @@ import * as i0 from "@angular/core";
|
|
|
132
133
|
* - customActions con custom → Botón personalizado emite evento
|
|
133
134
|
*/ export class TableComponent {
|
|
134
135
|
// Servicios inyectados
|
|
135
|
-
/* v8 ignore next
|
|
136
|
+
/* v8 ignore next 7 */
|
|
136
137
|
modalService = inject(ModalService);
|
|
137
138
|
visibilityService = inject(TableColumnVisibilityService);
|
|
138
139
|
dataUtils = inject(TableDataUtilsService);
|
|
139
140
|
dataConverter = inject(TableDataConverterService);
|
|
140
141
|
selectionService = inject(TableSelectionService);
|
|
141
142
|
crudService = inject(TableCrudStateService);
|
|
143
|
+
paginationService = inject(TablePaginationService);
|
|
142
144
|
// Inputs
|
|
143
145
|
/* v8 ignore next */
|
|
144
146
|
data$ = input.required(...(ngDevMode ? [{ debugName: "data$" }] : []));
|
|
@@ -158,14 +160,19 @@ import * as i0 from "@angular/core";
|
|
|
158
160
|
allowSelection = input(false, ...(ngDevMode ? [{ debugName: "allowSelection", transform: booleanAttribute }] : [{ transform: booleanAttribute }])); // Si es true, permite selección de filas
|
|
159
161
|
/* v8 ignore next */
|
|
160
162
|
noConfirm = input(false, ...(ngDevMode ? [{ debugName: "noConfirm", transform: booleanAttribute }] : [{ transform: booleanAttribute }])); // Si es true, no muestra confirmaciones modales
|
|
163
|
+
/* v8 ignore next */
|
|
164
|
+
paginated = input(false, ...(ngDevMode ? [{ debugName: "paginated", transform: booleanAttribute }] : [{ transform: booleanAttribute }])); // Si es true, habilita paginación con limit/offset
|
|
165
|
+
/* v8 ignore next */
|
|
166
|
+
paginationConfig = input({ pageSize: 10 }, ...(ngDevMode ? [{ debugName: "paginationConfig" }] : [])); // Configuración de paginación (tamaño de página, total items)
|
|
161
167
|
// Outputs - Acciones unificadas (Angular 18+ output API)
|
|
162
168
|
/* v8 ignore next */
|
|
163
169
|
actionClick = output(); // Output unificado para TODAS las acciones (CRUD + custom)
|
|
164
170
|
// Outputs - Utilidades
|
|
165
|
-
/* v8 ignore next
|
|
171
|
+
/* v8 ignore next 4 */
|
|
166
172
|
searchTerm = output();
|
|
167
173
|
errorEvent = output();
|
|
168
174
|
selectable = output();
|
|
175
|
+
paginationChange = output(); // Output para cambios de paginación (page, limit, offset)
|
|
169
176
|
// Estado principal
|
|
170
177
|
/* v8 ignore next 2 */
|
|
171
178
|
data = signal([], ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
@@ -183,6 +190,14 @@ import * as i0 from "@angular/core";
|
|
|
183
190
|
newRow = this.crudState.newRow;
|
|
184
191
|
editing = this.crudState.editing;
|
|
185
192
|
editRow = this.crudState.editRow;
|
|
193
|
+
// Estado de paginación (delegado a servicio)
|
|
194
|
+
/* v8 ignore next 7 */
|
|
195
|
+
paginationState = this.paginationService.createPaginationState(this.paginationConfig().pageSize ?? 10);
|
|
196
|
+
currentPage = this.paginationState.currentPage;
|
|
197
|
+
pageSize = this.paginationState.pageSize;
|
|
198
|
+
totalPages = this.paginationState.totalPages;
|
|
199
|
+
hasNextPage = this.paginationState.hasNextPage;
|
|
200
|
+
hasPrevPage = this.paginationState.hasPrevPage;
|
|
186
201
|
// Keys visibles computed automáticamente
|
|
187
202
|
/* v8 ignore next 7 */
|
|
188
203
|
keys = computed(() => this.visibilityService.updateVisibleKeys(this.columns(), { data: this.data(), creating: this.creating(), editing: this.editing() }), ...(ngDevMode ? [{ debugName: "keys" }] : []));
|
|
@@ -193,12 +208,29 @@ import * as i0 from "@angular/core";
|
|
|
193
208
|
hasCrudUpdate = computed(() => this.customActions().some(a => a.name === 'update'), ...(ngDevMode ? [{ debugName: "hasCrudUpdate" }] : []));
|
|
194
209
|
hasCrudDelete = computed(() => this.customActions().some(a => a.name === 'delete'), ...(ngDevMode ? [{ debugName: "hasCrudDelete" }] : []));
|
|
195
210
|
hasCrudCancel = computed(() => this.customActions().some(a => a.name === 'cancel'), ...(ngDevMode ? [{ debugName: "hasCrudCancel" }] : []));
|
|
211
|
+
/* v8 ignore next 5 */
|
|
212
|
+
shouldShowCreateButton = computed(() => {
|
|
213
|
+
const createAction = this.customActions().find(a => a.name === 'create');
|
|
214
|
+
if (!createAction)
|
|
215
|
+
return false;
|
|
216
|
+
return !createAction.condition || createAction.condition({}, this.data());
|
|
217
|
+
}, ...(ngDevMode ? [{ debugName: "shouldShowCreateButton" }] : []));
|
|
196
218
|
// Table max height computed
|
|
197
219
|
/* v8 ignore next 3 */
|
|
198
220
|
tableMaxHeight = computed(() => this.dataUtils.getTableMaxHeight(this.size()), ...(ngDevMode ? [{ debugName: "tableMaxHeight" }] : []));
|
|
199
221
|
/* v8 ignore next 2 */
|
|
200
222
|
dataSub;
|
|
201
223
|
inputValuesSub;
|
|
224
|
+
constructor() {
|
|
225
|
+
// Sincronizar totalItems del paginationConfig con el estado de paginación
|
|
226
|
+
/* v8 ignore next 4 */
|
|
227
|
+
effect(() => {
|
|
228
|
+
const config = this.paginationConfig();
|
|
229
|
+
if (config.totalItems !== undefined && this.paginated()) {
|
|
230
|
+
this.paginationState.setTotalItems(config.totalItems);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
202
234
|
/**
|
|
203
235
|
* Maneja input de creación/edición de forma unificada
|
|
204
236
|
*/
|
|
@@ -464,6 +496,34 @@ import * as i0 from "@angular/core";
|
|
|
464
496
|
isColumnVisibleForRow(column, row) {
|
|
465
497
|
return this.visibilityService.isColumnVisibleForRow(column, { row, data: this.data(), editing: this.editing() });
|
|
466
498
|
}
|
|
499
|
+
// Métodos de paginación
|
|
500
|
+
onPageChange(page) {
|
|
501
|
+
this.paginationState.setPage(page);
|
|
502
|
+
this.emitPaginationChange();
|
|
503
|
+
}
|
|
504
|
+
onNextPage() {
|
|
505
|
+
this.paginationState.nextPage();
|
|
506
|
+
this.emitPaginationChange();
|
|
507
|
+
}
|
|
508
|
+
onPrevPage() {
|
|
509
|
+
this.paginationState.prevPage();
|
|
510
|
+
this.emitPaginationChange();
|
|
511
|
+
}
|
|
512
|
+
emitPaginationChange() {
|
|
513
|
+
const event = {
|
|
514
|
+
page: this.currentPage(),
|
|
515
|
+
limit: this.pageSize(),
|
|
516
|
+
offset: this.paginationState.offset()
|
|
517
|
+
};
|
|
518
|
+
this.paginationChange.emit(event);
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Actualiza el total de items para calcular páginas
|
|
522
|
+
* Debe ser llamado por el componente padre después de recibir datos
|
|
523
|
+
*/
|
|
524
|
+
updateTotalItems(total) {
|
|
525
|
+
this.paginationState.setTotalItems(total);
|
|
526
|
+
}
|
|
467
527
|
/**
|
|
468
528
|
* Obtiene el color de una acción considerando el tema actual.
|
|
469
529
|
* Si la acción tiene color explícito, lo usa. Si no, aplica color por tema.
|
|
@@ -481,10 +541,10 @@ import * as i0 from "@angular/core";
|
|
|
481
541
|
return '#e9ecef';
|
|
482
542
|
}
|
|
483
543
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
484
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.10", type: TableComponent, isStandalone: true, selector: "c80-table", inputs: { data$: { classPropertyName: "data$", publicName: "data$", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, inputValues$: { classPropertyName: "inputValues$", publicName: "inputValues$", isSignal: true, isRequired: false, transformFunction: null }, customActions: { classPropertyName: "customActions", publicName: "customActions", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, allowSelection: { classPropertyName: "allowSelection", publicName: "allowSelection", isSignal: true, isRequired: false, transformFunction: null }, noConfirm: { classPropertyName: "noConfirm", publicName: "noConfirm", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionClick: "actionClick", searchTerm: "searchTerm", errorEvent: "errorEvent", selectable: "selectable" }, ngImport: i0, template: "<!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n<div class=\"table-responsive\" [style.max-height]=\"tableMaxHeight()\" [style.overflow-y]=\"size() > 0 ? 'auto' : 'visible'\">\n <!-- Search Bar -->\n @if (searchable()) {\n <div class=\"search-container\">\n <div class=\"search-input-wrapper\">\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <c80-icon icon=\"search\" [size]=\".65\" [button]=\"false\" />\n </span>\n <input type=\"text\" class=\"form-control search-input\" placeholder=\"Buscar...\" [value]=\"searchValue()\" (input)=\"onSearchInput($event)\" aria-label=\"Buscar en la tabla\" />\n @if (searchValue()) {\n <button class=\"btn btn-outline-secondary btn-borrar\" type=\"button\" (click)=\"clearSearch()\" title=\"Limpiar b\u00FAsqueda\">\n <c80-icon icon=\"cancel\" [size]=\".6\" />\n </button>\n }\n </div>\n </div>\n </div>\n }\n\n <table class=\"table table-bordered table-hover align-middle\">\n <thead class=\"thead table-light sticky-header\">\n <tr>\n @if (allowSelection() && data().length !== 0) {\n <th class=\"text-center selection-column\">\n @if (multiple()) {\n <input type=\"checkbox\" [checked]=\"selectAllChecked()\" [indeterminate]=\"selectAllIndeterminate()\" (change)=\"toggleSelectAll()\" aria-label=\"Seleccionar todo\" />\n }\n </th>\n }\n @for (col of columns(); track col) {\n @if (isColumnVisibleInHeader(col)) {\n @if (col.type === 'boolean') {\n <th class=\"text-center boolean-column\">{{ col.label }}</th>\n }\n @else if (col.type === 'number' || col.type === 'integer') {\n <th class=\"text-center number-column\">{{ col.label }}</th>\n }\n @else {\n <th>{{ col.label }}</th>\n }\n }\n }\n @if (hasAnyActions()) {\n <th class=\"table-actions-header\">\n <div class=\"actions-wrapper\">\n <span>Actions</span>\n @if (hasCrudCreate()) {\n <c80-icon button icon=\"add\" [disabled]=\"creating()\" title=\"Agregar\" [size]=\".6\" (iconClick)=\"startCreate()\" />\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of data(); track trackById(i, row); let i = $index) {\n <tr>\n @if (allowSelection() && data().length !== 0) {\n <td class=\"text-center selection-column\">\n <input type=\"checkbox\" [checked]=\"isItemSelected(row)\" (change)=\"toggleItemSelection(row)\" [attr.aria-label]=\"'Seleccionar fila ' + (i + 1)\" />\n </td>\n }\n @for (col of columns(); track col) {\n @if (isColumnVisibleForRow(col, row)) {\n @if (col.type === 'boolean') {\n <td class=\"text-center boolean-column\">\n @if (editing() === row['id'] && !col.readOnly) {\n <input type=\"checkbox\" [checked]=\"!!editRow()?.[col.accessor]\" (change)=\"onEditInput($event, col.accessor, col)\" [attr.aria-label]=\"col.label\" />\n }\n @else {\n @if (getCellValue(row, col.accessor) === true) {\n <c80-icon icon=\"check\" [size]=\".7\" />\n <br />\n }\n @else if (getCellValue(row, col.accessor) === false) {\n <c80-icon icon=\"cancel\" [size]=\".7\" />\n <br />\n }\n }\n </td>\n }\n @else if (col.type === 'number' || col.type === 'integer') {\n <td class=\"text-center number-column\">\n @if (editing() === row['id'] && !col.readOnly) {\n <input class=\"form-control form-control-sm\" type=\"number\" [value]=\"editRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" [min]=\"col.min\" [max]=\"col.max\" [step]=\"col.type === 'integer' ? '1' : 'any'\"\n (input)=\"onEditInput($event, col.accessor, col)\" />\n }\n @else {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{ getDisplayValue(getCellValue(row,\n col.accessor), col) }}</span>\n }\n </td>\n }\n @else {\n <td>\n @if (editing() === row['id'] && !col.readOnly) {\n @if (col.type === 'enum') {\n <select class=\"form-control form-control-sm\" [value]=\"editRow()?.[col.accessor] ?? ''\" (change)=\"onEditInput($event, col.accessor, col)\">\n <option value=\"\">{{ col.label }}</option>\n @for (option of getEnumOptions(col); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n }\n @else {\n <input class=\"form-control form-control-sm\" [type]=\"col.type === 'password' ? 'password' : 'text'\" [value]=\"editRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" (input)=\"onEditInput($event, col.accessor, col)\" />\n }\n }\n @else {\n @if (col.type === 'password') {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">******</span>\n }\n @else if (col.type === 'enum') {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{\n getEnumDisplayValue(getCellValue(row, col.accessor), col)\n }}</span>\n }\n @else if (getCellValue(row, col.accessor) === true) {\n <c80-icon icon=\"check\" [size]=\".7\" />\n }\n @else if (getCellValue(row, col.accessor) === false) {\n <c80-icon icon=\"cancel\" [size]=\".7\" />\n }\n @else {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{ getDisplayValue(getCellValue(row,\n col.accessor), col) }}</span>\n }\n }\n </td>\n }\n }\n }\n @if (hasAnyActions()) {\n <td class=\"text-center actions-cell\">\n <div class=\"actions-container\">\n @if (editing() === row['id']) {\n <!-- Modo edici\u00F3n: mostrar guardar y cancelar -->\n @if (hasCrudUpdate()) {\n <c80-icon button icon=\"check\" title=\"Guardar\" (iconClick)=\"saveEdit(row)\" [size]=\".7\" />\n }\n <c80-icon button icon=\"cancel\" color=\"warn\" title=\"Cancelar\" (iconClick)=\"cancelEdit()\" [size]=\".7\" />\n }\n @else {\n @for (action of customActions(); track action.name) {\n @if (shouldShowAction(action, row)) {\n <c80-icon button [icon]=\"action.icon\" [customColor]=\"getActionColor(action)\" [color]=\"action.name === 'delete' ? 'warn' : 'primary'\" [title]=\"getActionTooltip(action)\" (iconClick)=\"onDynamicAction(action, row)\"\n [size]=\".7\" />\n }\n }\n }\n </div>\n </td>\n }\n </tr>\n }\n @if (creating() && hasCrudCreate()) {\n <tr>\n @if (allowSelection() && data().length !== 0) {\n <td class=\"text-center selection-column\">\n <!-- Empty cell for alignment -->\n </td>\n }\n @for (col of columns(); track col) {\n @if (isColumnVisible(col)) {\n @if (col.type === 'boolean') {\n <td class=\"text-center\">\n @if (!col.readOnly) {\n <input type=\"checkbox\" [checked]=\"!!newRow()?.[col.accessor]\" (change)=\"onInput($event, col.accessor, col)\" [attr.aria-label]=\"col.label\" />\n }\n @else {\n <!-- ReadOnly boolean column in create mode shows empty -->\n <span class=\"text-muted\">-</span>\n }\n </td>\n } @else if (col.type === 'number' || col.type === 'integer') {\n <td class=\"text-center number-column\">\n @if (!col.readOnly) {\n <input class=\"form-control form-control-sm\" type=\"number\" [value]=\"newRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" [min]=\"col.min\" [max]=\"col.max\" [step]=\"col.type === 'integer' ? '1' : 'any'\"\n (input)=\"onInput($event, col.accessor, col)\" />\n }\n @else {\n <!-- ReadOnly number column in create mode shows empty -->\n <span class=\"text-muted\">-</span>\n }\n </td>\n } @else {\n <td>\n @if (!col.readOnly) {\n @if (col.type === 'enum') {\n <select class=\"form-control form-control-sm\" [value]=\"newRow()?.[col.accessor] ?? ''\" (change)=\"onInput($event, col.accessor, col)\">\n <option value=\"\">{{ col.label }}</option>\n @for (option of getEnumOptions(col); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n }\n @else {\n <input class=\"form-control form-control-sm\" type=\"text\" [value]=\"newRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" (input)=\"onInput($event, col.accessor, col)\" />\n }\n }\n @else {\n <!-- ReadOnly column in create mode shows empty -->\n <span class=\"text-muted\">-</span>\n }\n </td>\n }\n }\n }\n <td class=\"text-center actions-cell\">\n <div class=\"actions-container\">\n @if (hasCrudCreate()) {\n <c80-icon button icon=\"check\" title=\"Guardar\" (iconClick)=\"saveCreate()\" [size]=\".7\" />\n }\n <c80-icon button icon=\"cancel\" color=\"warn\" title=\"Cancelar\" (iconClick)=\"cancelCreate()\" [size]=\".7\" />\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n @if (data().length === 0 && !creating()) {\n <div class=\"text-center text-muted py-3 small\">\n No hay datos para mostrar.\n </div>\n }\n</div>\n\n<c80-modal />", styles: ["@charset \"UTF-8\";input[type=checkbox]{width:1.3rem!important;height:1.3rem!important;accent-color:var(--color-icon-danger);background:var(--color-bg-primary);margin:0 .25rem;vertical-align:middle;cursor:pointer}.table-responsive{width:100%;overflow-x:auto}.table-responsive .search-container{margin-bottom:-.06rem}.table-responsive .search-container .search-input-wrapper .input-group .btn-borrar{border-bottom-right-radius:0;border-color:var(--color-border-default);height:32px}.table-responsive .search-container .search-input-wrapper .input-group .input-group-text{background-color:var(--color-bg-secondary);border-color:var(--color-border-default);border-bottom-left-radius:0;color:var(--color-text-secondary);width:56.1px;height:32px;padding:.25rem .5rem}.table-responsive .search-container .search-input-wrapper .input-group .input-group-text c80-icon{display:flex;align-items:center;justify-content:center}.table-responsive .search-container .search-input-wrapper .input-group .search-input{border-color:var(--color-border-default);border-bottom-right-radius:0;font-size:.75rem;height:32px;padding:.25rem .5rem;outline:none!important;background-color:var(--color-bg-primary);color:var(--color-text-primary)}.table-responsive .search-container .search-input-wrapper .input-group .search-input:focus{outline:none!important}.table-responsive .search-container .search-input-wrapper .input-group .search-input::placeholder{color:var(--color-text-muted);font-style:italic}.table-responsive .table{min-width:0px;margin-bottom:.5rem;background-color:var(--color-bg-primary);color:var(--color-text-primary);border-color:var(--color-border-default)}.table-responsive .table.table-bordered,.table-responsive .table.table-bordered th,.table-responsive .table.table-bordered td{border-color:var(--color-border-default)}.table-responsive .table.table-hover tbody tr:hover{background-color:var(--color-bg-hover)!important;color:var(--color-text-primary)}.table-responsive .table.table-hover tbody tr:hover td{background-color:var(--color-bg-hover)}.table-responsive .table .sticky-header{position:sticky;top:0;z-index:10;background-color:var(--color-bg-secondary)!important}.table-responsive .table .sticky-header .table-actions-header{display:table-cell;vertical-align:middle;padding:.2rem .6rem!important;background-color:var(--color-bg-secondary)!important}.table-responsive .table .sticky-header .actions-wrapper{display:flex;align-items:center;justify-content:center;gap:.5rem;height:100%}.table-responsive .table .sticky-header th{max-height:31px!important;vertical-align:middle!important;padding:.2rem .6rem!important;font-size:small!important;background-color:var(--color-bg-secondary)!important;border-bottom:2px solid var(--color-border-default);color:var(--color-text-primary)}.table-responsive .table tbody{background-color:var(--color-bg-primary)}.table-responsive .table tbody td{height:35px!important;min-height:35px!important;max-height:35px!important;vertical-align:middle!important;padding:.2rem .8rem!important;font-size:small;color:var(--color-text-primary);background-color:var(--color-bg-primary);border-color:var(--color-border-default)}.table-responsive .table tbody tr{height:35px!important;min-height:35px!important;max-height:35px!important;cursor:pointer;background-color:var(--color-bg-primary)}.table-responsive .table tbody tr:hover{background-color:var(--color-bg-hover)!important}.table-responsive .table tbody tr:hover td{background-color:var(--color-bg-hover)}.table-responsive .table tbody .text-muted{color:var(--color-text-muted)!important}.table-responsive .table tbody input,.table-responsive .table tbody select{border:1px solid var(--color-border-default);height:100%!important;font-size:smaller!important;background-color:var(--color-bg-primary);color:var(--color-text-primary)}.table-responsive .table tbody input:focus,.table-responsive .table tbody select:focus{border-color:var(--color-border-focus);box-shadow:var(--shadow-focus)}.table-responsive .table tbody input::placeholder,.table-responsive .table tbody select::placeholder{color:var(--color-text-muted)}.table-responsive .table tbody input[type=text],.table-responsive .table tbody input:not([type]){width:100%!important}.table-responsive .table tbody input[type=number]{min-width:80px!important;width:80px!important;text-align:center}.table-responsive .table tbody select{width:100%!important;padding:.25rem .5rem}.table-responsive .table thead th.boolean-column,.table-responsive .table tbody td.boolean-column,.table-responsive .table thead th.selection-column,.table-responsive .table tbody td.selection-column,.table-responsive .table thead th.table-actions-header,.table-responsive .table tbody td.table-actions-header{width:1%;white-space:nowrap}.table-responsive .table thead th.number-column,.table-responsive .table tbody td.number-column{width:1%;white-space:nowrap;text-align:center}.table-responsive .table thead th.number-column input[type=number],.table-responsive .table tbody td.number-column input[type=number]{min-width:80px!important;width:80px!important;text-align:center}.table-responsive .table .actions-cell{white-space:nowrap;padding:0!important}.table-responsive .table .actions-container{display:flex;flex-direction:row;justify-content:center;align-items:center;width:100%;height:100%}.table-responsive .text-center.text-muted{color:var(--color-text-muted)!important}.table-responsive .btn-outline-secondary{background-color:var(--color-bg-primary);color:var(--color-text-primary);border-color:var(--color-border-default)}.table-responsive .btn-outline-secondary:hover{background-color:var(--color-bg-hover);border-color:var(--color-border-medium)}.table-responsive .btn-outline-secondary:focus{box-shadow:var(--shadow-focus)}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "c80-icon", inputs: ["icon", "color", "customColor", "disabled", "size", "button", "border", "type", "textLeft", "textRight", "dark"], outputs: ["iconClick"] }, { kind: "component", type: ModalComponent, selector: "c80-modal" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
544
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.10", type: TableComponent, isStandalone: true, selector: "c80-table", inputs: { data$: { classPropertyName: "data$", publicName: "data$", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, inputValues$: { classPropertyName: "inputValues$", publicName: "inputValues$", isSignal: true, isRequired: false, transformFunction: null }, customActions: { classPropertyName: "customActions", publicName: "customActions", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, allowSelection: { classPropertyName: "allowSelection", publicName: "allowSelection", isSignal: true, isRequired: false, transformFunction: null }, noConfirm: { classPropertyName: "noConfirm", publicName: "noConfirm", isSignal: true, isRequired: false, transformFunction: null }, paginated: { classPropertyName: "paginated", publicName: "paginated", isSignal: true, isRequired: false, transformFunction: null }, paginationConfig: { classPropertyName: "paginationConfig", publicName: "paginationConfig", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionClick: "actionClick", searchTerm: "searchTerm", errorEvent: "errorEvent", selectable: "selectable", paginationChange: "paginationChange" }, ngImport: i0, template: "<!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n<div class=\"table-responsive\" [style.max-height]=\"tableMaxHeight()\" [style.overflow-y]=\"size() > 0 ? 'auto' : 'visible'\">\n <!-- Search Bar -->\n @if (searchable()) {\n <div class=\"search-container\">\n <div class=\"search-input-wrapper\">\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <c80-icon icon=\"search\" [size]=\".65\" [button]=\"false\" />\n </span>\n <input type=\"text\" class=\"form-control search-input\" placeholder=\"Buscar...\" [value]=\"searchValue()\" (input)=\"onSearchInput($event)\" aria-label=\"Buscar en la tabla\" />\n @if (searchValue()) {\n <button class=\"btn btn-outline-secondary btn-borrar\" type=\"button\" (click)=\"clearSearch()\" title=\"Limpiar b\u00FAsqueda\">\n <c80-icon icon=\"cancel\" [size]=\".6\" />\n </button>\n }\n </div>\n </div>\n </div>\n }\n\n <table class=\"table table-bordered table-hover align-middle\">\n <thead class=\"thead table-light sticky-header\">\n <tr>\n @if (allowSelection() && data().length !== 0) {\n <th class=\"text-center selection-column\">\n @if (multiple()) {\n <input type=\"checkbox\" [checked]=\"selectAllChecked()\" [indeterminate]=\"selectAllIndeterminate()\" (change)=\"toggleSelectAll()\" aria-label=\"Seleccionar todo\" />\n }\n </th>\n }\n @for (col of columns(); track col) {\n @if (isColumnVisibleInHeader(col)) {\n @if (col.type === 'boolean') {\n <th class=\"text-center boolean-column\">{{ col.label }}</th>\n }\n @else if (col.type === 'number' || col.type === 'integer') {\n <th class=\"text-center number-column\">{{ col.label }}</th>\n }\n @else if (col.type === 'date') {\n <th class=\"text-center date-column\">{{ col.label }}</th>\n }\n @else {\n <th>{{ col.label }}</th>\n }\n }\n }\n @if (hasAnyActions()) {\n <th class=\"table-actions-header\">\n <div class=\"actions-wrapper\">\n <span>Actions</span>\n @if (shouldShowCreateButton()) {\n <c80-icon button icon=\"add\" [disabled]=\"creating()\" title=\"Agregar\" [size]=\".6\" (iconClick)=\"startCreate()\" />\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of data(); track trackById(i, row); let i = $index) {\n <tr>\n @if (allowSelection() && data().length !== 0) {\n <td class=\"text-center selection-column\">\n <input type=\"checkbox\" [checked]=\"isItemSelected(row)\" (change)=\"toggleItemSelection(row)\" [attr.aria-label]=\"'Seleccionar fila ' + (i + 1)\" />\n </td>\n }\n @for (col of columns(); track col) {\n @if (isColumnVisibleForRow(col, row)) {\n @if (col.type === 'boolean') {\n <td class=\"text-center boolean-column\">\n @if (editing() === row['id'] && !col.readOnly) {\n <input type=\"checkbox\" [checked]=\"!!editRow()?.[col.accessor]\" (change)=\"onEditInput($event, col.accessor, col)\" [attr.aria-label]=\"col.label\" />\n }\n @else {\n @if (getCellValue(row, col.accessor) === true) {\n <c80-icon icon=\"check\" [size]=\".7\" />\n <br />\n }\n @else if (getCellValue(row, col.accessor) === false) {\n <c80-icon icon=\"cancel\" [size]=\".7\" />\n <br />\n }\n }\n </td>\n }\n @else if (col.type === 'number' || col.type === 'integer') {\n <td class=\"text-center number-column\">\n @if (editing() === row['id'] && !col.readOnly) {\n <input class=\"form-control form-control-sm\" type=\"number\" [value]=\"editRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" [min]=\"col.min\" [max]=\"col.max\" [step]=\"col.type === 'integer' ? '1' : 'any'\"\n (input)=\"onEditInput($event, col.accessor, col)\" />\n }\n @else {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{ getDisplayValue(getCellValue(row,\n col.accessor), col) }}</span>\n }\n </td>\n }\n @else if (col.type === 'date') {\n <td class=\"text-center date-column\">\n @if (editing() === row['id'] && !col.readOnly) {\n <input class=\"form-control form-control-sm\" type=\"datetime-local\" [value]=\"editRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" (input)=\"onEditInput($event, col.accessor, col)\" />\n }\n @else {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{ getDisplayValue(getCellValue(row,\n col.accessor), col) }}</span>\n }\n </td>\n }\n @else {\n <td>\n @if (editing() === row['id'] && !col.readOnly) {\n @if (col.type === 'enum') {\n <select class=\"form-control form-control-sm\" [value]=\"editRow()?.[col.accessor] ?? ''\" (change)=\"onEditInput($event, col.accessor, col)\">\n <option value=\"\">{{ col.label }}</option>\n @for (option of getEnumOptions(col); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n }\n @else {\n <input class=\"form-control form-control-sm\" [type]=\"col.type === 'password' ? 'password' : 'text'\" [value]=\"editRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" (input)=\"onEditInput($event, col.accessor, col)\" />\n }\n }\n @else {\n @if (col.type === 'password') {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">******</span>\n }\n @else if (col.type === 'enum') {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{\n getEnumDisplayValue(getCellValue(row, col.accessor), col)\n }}</span>\n }\n @else if (getCellValue(row, col.accessor) === true) {\n <c80-icon icon=\"check\" [size]=\".7\" />\n }\n @else if (getCellValue(row, col.accessor) === false) {\n <c80-icon icon=\"cancel\" [size]=\".7\" />\n }\n @else {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{ getDisplayValue(getCellValue(row,\n col.accessor), col) }}</span>\n }\n }\n </td>\n }\n }\n }\n @if (hasAnyActions()) {\n <td class=\"text-end actions-cell\">\n <div class=\"actions-container\" [class.centered-actions]=\"editing() === row['id']\">\n @if (editing() === row['id']) {\n <!-- Modo edici\u00F3n: mostrar guardar y cancelar -->\n @if (hasCrudUpdate()) {\n <c80-icon button icon=\"check\" title=\"Guardar\" (iconClick)=\"saveEdit(row)\" [size]=\".7\" />\n }\n <c80-icon button icon=\"cancel\" color=\"warn\" title=\"Cancelar\" (iconClick)=\"cancelEdit()\" [size]=\".7\" />\n }\n @else {\n @for (action of customActions(); track action.name) {\n @if (shouldShowAction(action, row, data())) {\n <c80-icon button [icon]=\"action.icon\" [customColor]=\"getActionColor(action)\" [color]=\"action.name === 'delete' ? 'warn' : 'primary'\" [title]=\"getActionTooltip(action)\" (iconClick)=\"onDynamicAction(action, row)\" [size]=\".7\" />\n }\n }\n }\n </div>\n </td>\n }\n </tr>\n }\n @if (creating() && hasCrudCreate()) {\n <tr>\n @if (allowSelection() && data().length !== 0) {\n <td class=\"text-center selection-column\">\n <!-- Empty cell for alignment -->\n </td>\n }\n @for (col of columns(); track col) {\n @if (isColumnVisible(col)) {\n @if (col.type === 'boolean') {\n <td class=\"text-center\">\n @if (!col.readOnly) {\n <input type=\"checkbox\" [checked]=\"!!newRow()?.[col.accessor]\" (change)=\"onInput($event, col.accessor, col)\" [attr.aria-label]=\"col.label\" />\n }\n @else {\n <!-- ReadOnly boolean column in create mode shows empty -->\n <span class=\"text-muted\">-</span>\n }\n </td>\n } @else if (col.type === 'number' || col.type === 'integer') {\n <td class=\"text-center number-column\">\n @if (!col.readOnly) {\n <input class=\"form-control form-control-sm\" type=\"number\" [value]=\"newRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" [min]=\"col.min\" [max]=\"col.max\" [step]=\"col.type === 'integer' ? '1' : 'any'\"\n (input)=\"onInput($event, col.accessor, col)\" />\n }\n @else {\n <!-- ReadOnly number column in create mode shows empty -->\n <span class=\"text-muted\">-</span>\n }\n </td>\n } @else {\n <td>\n @if (!col.readOnly) {\n @if (col.type === 'enum') {\n <select class=\"form-control form-control-sm\" [value]=\"newRow()?.[col.accessor] ?? ''\" (change)=\"onInput($event, col.accessor, col)\">\n <option value=\"\">{{ col.label }}</option>\n @for (option of getEnumOptions(col); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n }\n @else {\n <input class=\"form-control form-control-sm\" type=\"text\" [value]=\"newRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" (input)=\"onInput($event, col.accessor, col)\" />\n }\n }\n @else {\n <!-- ReadOnly column in create mode shows empty -->\n <span class=\"text-muted\">-</span>\n }\n </td>\n }\n }\n }\n <td class=\"text-end actions-cell\">\n <div class=\"actions-container centered-actions\">\n @if (hasCrudCreate()) {\n <c80-icon button icon=\"check\" title=\"Guardar\" (iconClick)=\"saveCreate()\" [size]=\".7\" />\n }\n <c80-icon button icon=\"cancel\" color=\"warn\" title=\"Cancelar\" (iconClick)=\"cancelCreate()\" [size]=\".7\" />\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n @if (data().length === 0 && !creating()) {\n <div class=\"text-center text-muted py-3 small\">\n No hay datos para mostrar.\n </div>\n }\n\n <!-- Pagination Controls -->\n @if (paginated() && data().length > 0) {\n <div class=\"pagination-container\">\n <div class=\"pagination-info\">\n P\u00E1gina {{ currentPage() }} de {{ totalPages() }}\n </div>\n <div class=\"pagination-controls\">\n <button type=\"button\" class=\"btn btn-sm\" [disabled]=\"!hasPrevPage()\" (click)=\"onPrevPage()\" title=\"P\u00E1gina anterior\">\n <c80-icon icon=\"arrowLeft\" [size]=\".6\" [button]=\"false\" />\n </button>\n <button type=\"button\" class=\"btn btn-sm\" [disabled]=\"!hasNextPage()\" (click)=\"onNextPage()\" title=\"P\u00E1gina siguiente\">\n <c80-icon icon=\"arrowRight\" [size]=\".6\" [button]=\"false\" />\n </button>\n </div>\n </div>\n }\n</div>\n\n<c80-modal />", styles: ["@charset \"UTF-8\";input[type=checkbox]{width:1.3rem!important;height:1.3rem!important;accent-color:var(--color-icon-danger);background:var(--color-bg-primary);margin:0 .25rem;vertical-align:middle;cursor:pointer}.table-responsive{width:100%;overflow-x:auto}.table-responsive .search-container{margin-bottom:-.06rem}.table-responsive .search-container .search-input-wrapper .input-group .btn-borrar{border-bottom-right-radius:0;border-color:var(--color-border-default);height:32px}.table-responsive .search-container .search-input-wrapper .input-group .input-group-text{background-color:var(--color-bg-secondary);border-color:var(--color-border-default);border-bottom-left-radius:0;color:var(--color-text-secondary);width:56.1px;height:32px;padding:.25rem .5rem}.table-responsive .search-container .search-input-wrapper .input-group .input-group-text c80-icon{display:flex;align-items:center;justify-content:center}.table-responsive .search-container .search-input-wrapper .input-group .search-input{border-color:var(--color-border-default);border-bottom-right-radius:0;font-size:.75rem;height:32px;padding:.25rem .5rem;outline:none!important;background-color:var(--color-bg-primary);color:var(--color-text-primary)}.table-responsive .search-container .search-input-wrapper .input-group .search-input:focus{outline:none!important}.table-responsive .search-container .search-input-wrapper .input-group .search-input::placeholder{color:var(--color-text-muted);font-style:italic}.table-responsive .table{min-width:0px;margin-bottom:0;background-color:var(--color-bg-primary);color:var(--color-text-primary);border-color:var(--color-border-default)}.table-responsive .table.table-bordered,.table-responsive .table.table-bordered th,.table-responsive .table.table-bordered td{border-color:var(--color-border-default)}.table-responsive .table.table-hover tbody tr:hover{background-color:var(--color-bg-hover)!important;color:var(--color-text-primary)}.table-responsive .table.table-hover tbody tr:hover td{background-color:var(--color-bg-hover)}.table-responsive .table .sticky-header{position:sticky;top:0;z-index:10;background-color:var(--color-bg-secondary)!important}.table-responsive .table .sticky-header .table-actions-header{display:table-cell;vertical-align:middle;padding:.2rem .6rem!important;background-color:var(--color-bg-secondary)!important}.table-responsive .table .sticky-header .actions-wrapper{display:flex;align-items:center;justify-content:center;gap:.5rem;height:100%}.table-responsive .table .sticky-header th{max-height:31px!important;vertical-align:middle!important;padding:.2rem .6rem!important;font-size:small!important;background-color:var(--color-bg-secondary)!important;border-bottom:2px solid var(--color-border-default);color:var(--color-text-primary)}.table-responsive .table tbody{background-color:var(--color-bg-primary)}.table-responsive .table tbody td{height:35px!important;min-height:35px!important;max-height:35px!important;vertical-align:middle!important;padding:.2rem .8rem!important;font-size:small;color:var(--color-text-primary);background-color:var(--color-bg-primary);border-color:var(--color-border-default)}.table-responsive .table tbody tr{height:35px!important;min-height:35px!important;max-height:35px!important;cursor:pointer;background-color:var(--color-bg-primary)}.table-responsive .table tbody tr:hover{background-color:var(--color-bg-hover)!important}.table-responsive .table tbody tr:hover td{background-color:var(--color-bg-hover)}.table-responsive .table tbody .text-muted{color:var(--color-text-muted)!important}.table-responsive .table tbody input,.table-responsive .table tbody select{border:1px solid var(--color-border-default);height:100%!important;font-size:smaller!important;background-color:var(--color-bg-primary);color:var(--color-text-primary)}.table-responsive .table tbody input:focus,.table-responsive .table tbody select:focus{border-color:var(--color-border-focus);box-shadow:var(--shadow-focus)}.table-responsive .table tbody input::placeholder,.table-responsive .table tbody select::placeholder{color:var(--color-text-muted)}.table-responsive .table tbody input[type=text],.table-responsive .table tbody input:not([type]){width:100%!important}.table-responsive .table tbody input[type=number]{min-width:80px!important;width:80px!important;text-align:center}.table-responsive .table tbody select{width:100%!important;padding:.25rem .5rem}.table-responsive .table thead th.boolean-column,.table-responsive .table tbody td.boolean-column,.table-responsive .table thead th.selection-column,.table-responsive .table tbody td.selection-column,.table-responsive .table thead th.table-actions-header,.table-responsive .table tbody td.table-actions-header{width:1%;white-space:nowrap}.table-responsive .table thead th.number-column,.table-responsive .table tbody td.number-column{width:1%;white-space:nowrap;text-align:center}.table-responsive .table thead th.number-column input[type=number],.table-responsive .table tbody td.number-column input[type=number]{min-width:80px!important;width:80px!important;text-align:center}.table-responsive .table thead th.date-column,.table-responsive .table tbody td.date-column{width:1%;white-space:nowrap;text-align:center}.table-responsive .table thead th.date-column input[type=datetime-local],.table-responsive .table tbody td.date-column input[type=datetime-local]{min-width:200px!important;width:200px!important;text-align:center}.table-responsive .table .actions-cell{white-space:nowrap;padding:0!important}.table-responsive .table .actions-container{display:flex;flex-direction:row;justify-content:flex-end;align-items:center;width:100%;height:100%}.table-responsive .table .actions-container.centered-actions{justify-content:center}.table-responsive .text-center.text-muted{color:var(--color-text-muted)!important}.table-responsive .btn-outline-secondary{background-color:var(--color-bg-primary);color:var(--color-text-primary);border-color:var(--color-border-default)}.table-responsive .btn-outline-secondary:hover{background-color:var(--color-bg-hover);border-color:var(--color-border-medium)}.table-responsive .btn-outline-secondary:focus{box-shadow:var(--shadow-focus)}.table-responsive .pagination-container{display:flex;justify-content:space-between;align-items:center;padding:.2rem .5rem;background-color:var(--color-bg-secondary);border:1px solid var(--color-border-default);border-top:none;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.table-responsive .pagination-container .pagination-info{font-size:.875rem;color:var(--color-text-secondary);font-weight:500}.table-responsive .pagination-container .pagination-controls{display:flex;gap:.5rem}.table-responsive .pagination-container .pagination-controls button{min-width:36px;height:32px;padding:.25rem .5rem;display:flex;align-items:center;justify-content:center;background-color:var(--color-bg-primary);color:var(--color-text-primary);border:none}.table-responsive .pagination-container .pagination-controls button:hover:not(:disabled){background-color:var(--color-bg-hover);border:none}.table-responsive .pagination-container .pagination-controls button:disabled{opacity:.5;cursor:auto;background-color:var(--color-bg-primary);border:none}\n"], dependencies: [{ kind: "component", type: IconComponent, selector: "c80-icon", inputs: ["icon", "color", "customColor", "disabled", "size", "button", "border", "type", "textLeft", "textRight", "dark"], outputs: ["iconClick"] }, { kind: "component", type: ModalComponent, selector: "c80-modal" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
485
545
|
}
|
|
486
546
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TableComponent, decorators: [{
|
|
487
547
|
type: Component,
|
|
488
|
-
args: [{ selector: 'c80-table', standalone: true, imports: [IconComponent, ModalComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n<div class=\"table-responsive\" [style.max-height]=\"tableMaxHeight()\" [style.overflow-y]=\"size() > 0 ? 'auto' : 'visible'\">\n <!-- Search Bar -->\n @if (searchable()) {\n <div class=\"search-container\">\n <div class=\"search-input-wrapper\">\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <c80-icon icon=\"search\" [size]=\".65\" [button]=\"false\" />\n </span>\n <input type=\"text\" class=\"form-control search-input\" placeholder=\"Buscar...\" [value]=\"searchValue()\" (input)=\"onSearchInput($event)\" aria-label=\"Buscar en la tabla\" />\n @if (searchValue()) {\n <button class=\"btn btn-outline-secondary btn-borrar\" type=\"button\" (click)=\"clearSearch()\" title=\"Limpiar b\u00FAsqueda\">\n <c80-icon icon=\"cancel\" [size]=\".6\" />\n </button>\n }\n </div>\n </div>\n </div>\n }\n\n <table class=\"table table-bordered table-hover align-middle\">\n <thead class=\"thead table-light sticky-header\">\n <tr>\n @if (allowSelection() && data().length !== 0) {\n <th class=\"text-center selection-column\">\n @if (multiple()) {\n <input type=\"checkbox\" [checked]=\"selectAllChecked()\" [indeterminate]=\"selectAllIndeterminate()\" (change)=\"toggleSelectAll()\" aria-label=\"Seleccionar todo\" />\n }\n </th>\n }\n @for (col of columns(); track col) {\n @if (isColumnVisibleInHeader(col)) {\n @if (col.type === 'boolean') {\n <th class=\"text-center boolean-column\">{{ col.label }}</th>\n }\n @else if (col.type === 'number' || col.type === 'integer') {\n <th class=\"text-center number-column\">{{ col.label }}</th>\n }\n @else {\n <th>{{ col.label }}</th>\n }\n }\n }\n @if (hasAnyActions()) {\n <th class=\"table-actions-header\">\n <div class=\"actions-wrapper\">\n <span>Actions</span>\n @if (hasCrudCreate()) {\n <c80-icon button icon=\"add\" [disabled]=\"creating()\" title=\"Agregar\" [size]=\".6\" (iconClick)=\"startCreate()\" />\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of data(); track trackById(i, row); let i = $index) {\n <tr>\n @if (allowSelection() && data().length !== 0) {\n <td class=\"text-center selection-column\">\n <input type=\"checkbox\" [checked]=\"isItemSelected(row)\" (change)=\"toggleItemSelection(row)\" [attr.aria-label]=\"'Seleccionar fila ' + (i + 1)\" />\n </td>\n }\n @for (col of columns(); track col) {\n @if (isColumnVisibleForRow(col, row)) {\n @if (col.type === 'boolean') {\n <td class=\"text-center boolean-column\">\n @if (editing() === row['id'] && !col.readOnly) {\n <input type=\"checkbox\" [checked]=\"!!editRow()?.[col.accessor]\" (change)=\"onEditInput($event, col.accessor, col)\" [attr.aria-label]=\"col.label\" />\n }\n @else {\n @if (getCellValue(row, col.accessor) === true) {\n <c80-icon icon=\"check\" [size]=\".7\" />\n <br />\n }\n @else if (getCellValue(row, col.accessor) === false) {\n <c80-icon icon=\"cancel\" [size]=\".7\" />\n <br />\n }\n }\n </td>\n }\n @else if (col.type === 'number' || col.type === 'integer') {\n <td class=\"text-center number-column\">\n @if (editing() === row['id'] && !col.readOnly) {\n <input class=\"form-control form-control-sm\" type=\"number\" [value]=\"editRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" [min]=\"col.min\" [max]=\"col.max\" [step]=\"col.type === 'integer' ? '1' : 'any'\"\n (input)=\"onEditInput($event, col.accessor, col)\" />\n }\n @else {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{ getDisplayValue(getCellValue(row,\n col.accessor), col) }}</span>\n }\n </td>\n }\n @else {\n <td>\n @if (editing() === row['id'] && !col.readOnly) {\n @if (col.type === 'enum') {\n <select class=\"form-control form-control-sm\" [value]=\"editRow()?.[col.accessor] ?? ''\" (change)=\"onEditInput($event, col.accessor, col)\">\n <option value=\"\">{{ col.label }}</option>\n @for (option of getEnumOptions(col); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n }\n @else {\n <input class=\"form-control form-control-sm\" [type]=\"col.type === 'password' ? 'password' : 'text'\" [value]=\"editRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" (input)=\"onEditInput($event, col.accessor, col)\" />\n }\n }\n @else {\n @if (col.type === 'password') {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">******</span>\n }\n @else if (col.type === 'enum') {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{\n getEnumDisplayValue(getCellValue(row, col.accessor), col)\n }}</span>\n }\n @else if (getCellValue(row, col.accessor) === true) {\n <c80-icon icon=\"check\" [size]=\".7\" />\n }\n @else if (getCellValue(row, col.accessor) === false) {\n <c80-icon icon=\"cancel\" [size]=\".7\" />\n }\n @else {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{ getDisplayValue(getCellValue(row,\n col.accessor), col) }}</span>\n }\n }\n </td>\n }\n }\n }\n @if (hasAnyActions()) {\n <td class=\"text-center actions-cell\">\n <div class=\"actions-container\">\n @if (editing() === row['id']) {\n <!-- Modo edici\u00F3n: mostrar guardar y cancelar -->\n @if (hasCrudUpdate()) {\n <c80-icon button icon=\"check\" title=\"Guardar\" (iconClick)=\"saveEdit(row)\" [size]=\".7\" />\n }\n <c80-icon button icon=\"cancel\" color=\"warn\" title=\"Cancelar\" (iconClick)=\"cancelEdit()\" [size]=\".7\" />\n }\n @else {\n @for (action of customActions(); track action.name) {\n @if (shouldShowAction(action, row)) {\n <c80-icon button [icon]=\"action.icon\" [customColor]=\"getActionColor(action)\" [color]=\"action.name === 'delete' ? 'warn' : 'primary'\" [title]=\"getActionTooltip(action)\" (iconClick)=\"onDynamicAction(action, row)\"\n [size]=\".7\" />\n }\n }\n }\n </div>\n </td>\n }\n </tr>\n }\n @if (creating() && hasCrudCreate()) {\n <tr>\n @if (allowSelection() && data().length !== 0) {\n <td class=\"text-center selection-column\">\n <!-- Empty cell for alignment -->\n </td>\n }\n @for (col of columns(); track col) {\n @if (isColumnVisible(col)) {\n @if (col.type === 'boolean') {\n <td class=\"text-center\">\n @if (!col.readOnly) {\n <input type=\"checkbox\" [checked]=\"!!newRow()?.[col.accessor]\" (change)=\"onInput($event, col.accessor, col)\" [attr.aria-label]=\"col.label\" />\n }\n @else {\n <!-- ReadOnly boolean column in create mode shows empty -->\n <span class=\"text-muted\">-</span>\n }\n </td>\n } @else if (col.type === 'number' || col.type === 'integer') {\n <td class=\"text-center number-column\">\n @if (!col.readOnly) {\n <input class=\"form-control form-control-sm\" type=\"number\" [value]=\"newRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" [min]=\"col.min\" [max]=\"col.max\" [step]=\"col.type === 'integer' ? '1' : 'any'\"\n (input)=\"onInput($event, col.accessor, col)\" />\n }\n @else {\n <!-- ReadOnly number column in create mode shows empty -->\n <span class=\"text-muted\">-</span>\n }\n </td>\n } @else {\n <td>\n @if (!col.readOnly) {\n @if (col.type === 'enum') {\n <select class=\"form-control form-control-sm\" [value]=\"newRow()?.[col.accessor] ?? ''\" (change)=\"onInput($event, col.accessor, col)\">\n <option value=\"\">{{ col.label }}</option>\n @for (option of getEnumOptions(col); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n }\n @else {\n <input class=\"form-control form-control-sm\" type=\"text\" [value]=\"newRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" (input)=\"onInput($event, col.accessor, col)\" />\n }\n }\n @else {\n <!-- ReadOnly column in create mode shows empty -->\n <span class=\"text-muted\">-</span>\n }\n </td>\n }\n }\n }\n <td class=\"text-center actions-cell\">\n <div class=\"actions-container\">\n @if (hasCrudCreate()) {\n <c80-icon button icon=\"check\" title=\"Guardar\" (iconClick)=\"saveCreate()\" [size]=\".7\" />\n }\n <c80-icon button icon=\"cancel\" color=\"warn\" title=\"Cancelar\" (iconClick)=\"cancelCreate()\" [size]=\".7\" />\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n @if (data().length === 0 && !creating()) {\n <div class=\"text-center text-muted py-3 small\">\n No hay datos para mostrar.\n </div>\n }\n</div>\n\n<c80-modal />", styles: ["@charset \"UTF-8\";input[type=checkbox]{width:1.3rem!important;height:1.3rem!important;accent-color:var(--color-icon-danger);background:var(--color-bg-primary);margin:0 .25rem;vertical-align:middle;cursor:pointer}.table-responsive{width:100%;overflow-x:auto}.table-responsive .search-container{margin-bottom:-.06rem}.table-responsive .search-container .search-input-wrapper .input-group .btn-borrar{border-bottom-right-radius:0;border-color:var(--color-border-default);height:32px}.table-responsive .search-container .search-input-wrapper .input-group .input-group-text{background-color:var(--color-bg-secondary);border-color:var(--color-border-default);border-bottom-left-radius:0;color:var(--color-text-secondary);width:56.1px;height:32px;padding:.25rem .5rem}.table-responsive .search-container .search-input-wrapper .input-group .input-group-text c80-icon{display:flex;align-items:center;justify-content:center}.table-responsive .search-container .search-input-wrapper .input-group .search-input{border-color:var(--color-border-default);border-bottom-right-radius:0;font-size:.75rem;height:32px;padding:.25rem .5rem;outline:none!important;background-color:var(--color-bg-primary);color:var(--color-text-primary)}.table-responsive .search-container .search-input-wrapper .input-group .search-input:focus{outline:none!important}.table-responsive .search-container .search-input-wrapper .input-group .search-input::placeholder{color:var(--color-text-muted);font-style:italic}.table-responsive .table{min-width:0px;margin-bottom:.5rem;background-color:var(--color-bg-primary);color:var(--color-text-primary);border-color:var(--color-border-default)}.table-responsive .table.table-bordered,.table-responsive .table.table-bordered th,.table-responsive .table.table-bordered td{border-color:var(--color-border-default)}.table-responsive .table.table-hover tbody tr:hover{background-color:var(--color-bg-hover)!important;color:var(--color-text-primary)}.table-responsive .table.table-hover tbody tr:hover td{background-color:var(--color-bg-hover)}.table-responsive .table .sticky-header{position:sticky;top:0;z-index:10;background-color:var(--color-bg-secondary)!important}.table-responsive .table .sticky-header .table-actions-header{display:table-cell;vertical-align:middle;padding:.2rem .6rem!important;background-color:var(--color-bg-secondary)!important}.table-responsive .table .sticky-header .actions-wrapper{display:flex;align-items:center;justify-content:center;gap:.5rem;height:100%}.table-responsive .table .sticky-header th{max-height:31px!important;vertical-align:middle!important;padding:.2rem .6rem!important;font-size:small!important;background-color:var(--color-bg-secondary)!important;border-bottom:2px solid var(--color-border-default);color:var(--color-text-primary)}.table-responsive .table tbody{background-color:var(--color-bg-primary)}.table-responsive .table tbody td{height:35px!important;min-height:35px!important;max-height:35px!important;vertical-align:middle!important;padding:.2rem .8rem!important;font-size:small;color:var(--color-text-primary);background-color:var(--color-bg-primary);border-color:var(--color-border-default)}.table-responsive .table tbody tr{height:35px!important;min-height:35px!important;max-height:35px!important;cursor:pointer;background-color:var(--color-bg-primary)}.table-responsive .table tbody tr:hover{background-color:var(--color-bg-hover)!important}.table-responsive .table tbody tr:hover td{background-color:var(--color-bg-hover)}.table-responsive .table tbody .text-muted{color:var(--color-text-muted)!important}.table-responsive .table tbody input,.table-responsive .table tbody select{border:1px solid var(--color-border-default);height:100%!important;font-size:smaller!important;background-color:var(--color-bg-primary);color:var(--color-text-primary)}.table-responsive .table tbody input:focus,.table-responsive .table tbody select:focus{border-color:var(--color-border-focus);box-shadow:var(--shadow-focus)}.table-responsive .table tbody input::placeholder,.table-responsive .table tbody select::placeholder{color:var(--color-text-muted)}.table-responsive .table tbody input[type=text],.table-responsive .table tbody input:not([type]){width:100%!important}.table-responsive .table tbody input[type=number]{min-width:80px!important;width:80px!important;text-align:center}.table-responsive .table tbody select{width:100%!important;padding:.25rem .5rem}.table-responsive .table thead th.boolean-column,.table-responsive .table tbody td.boolean-column,.table-responsive .table thead th.selection-column,.table-responsive .table tbody td.selection-column,.table-responsive .table thead th.table-actions-header,.table-responsive .table tbody td.table-actions-header{width:1%;white-space:nowrap}.table-responsive .table thead th.number-column,.table-responsive .table tbody td.number-column{width:1%;white-space:nowrap;text-align:center}.table-responsive .table thead th.number-column input[type=number],.table-responsive .table tbody td.number-column input[type=number]{min-width:80px!important;width:80px!important;text-align:center}.table-responsive .table .actions-cell{white-space:nowrap;padding:0!important}.table-responsive .table .actions-container{display:flex;flex-direction:row;justify-content:center;align-items:center;width:100%;height:100%}.table-responsive .text-center.text-muted{color:var(--color-text-muted)!important}.table-responsive .btn-outline-secondary{background-color:var(--color-bg-primary);color:var(--color-text-primary);border-color:var(--color-border-default)}.table-responsive .btn-outline-secondary:hover{background-color:var(--color-bg-hover);border-color:var(--color-border-medium)}.table-responsive .btn-outline-secondary:focus{box-shadow:var(--shadow-focus)}\n"] }]
|
|
489
|
-
}], propDecorators: { data$: [{ type: i0.Input, args: [{ isSignal: true, alias: "data$", required: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], inputValues$: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputValues$", required: false }] }], customActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "customActions", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], allowSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowSelection", required: false }] }], noConfirm: [{ type: i0.Input, args: [{ isSignal: true, alias: "noConfirm", required: false }] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }], searchTerm: [{ type: i0.Output, args: ["searchTerm"] }], errorEvent: [{ type: i0.Output, args: ["errorEvent"] }], selectable: [{ type: i0.Output, args: ["selectable"] }] } });
|
|
548
|
+
args: [{ selector: 'c80-table', standalone: true, imports: [IconComponent, ModalComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n<div class=\"table-responsive\" [style.max-height]=\"tableMaxHeight()\" [style.overflow-y]=\"size() > 0 ? 'auto' : 'visible'\">\n <!-- Search Bar -->\n @if (searchable()) {\n <div class=\"search-container\">\n <div class=\"search-input-wrapper\">\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <c80-icon icon=\"search\" [size]=\".65\" [button]=\"false\" />\n </span>\n <input type=\"text\" class=\"form-control search-input\" placeholder=\"Buscar...\" [value]=\"searchValue()\" (input)=\"onSearchInput($event)\" aria-label=\"Buscar en la tabla\" />\n @if (searchValue()) {\n <button class=\"btn btn-outline-secondary btn-borrar\" type=\"button\" (click)=\"clearSearch()\" title=\"Limpiar b\u00FAsqueda\">\n <c80-icon icon=\"cancel\" [size]=\".6\" />\n </button>\n }\n </div>\n </div>\n </div>\n }\n\n <table class=\"table table-bordered table-hover align-middle\">\n <thead class=\"thead table-light sticky-header\">\n <tr>\n @if (allowSelection() && data().length !== 0) {\n <th class=\"text-center selection-column\">\n @if (multiple()) {\n <input type=\"checkbox\" [checked]=\"selectAllChecked()\" [indeterminate]=\"selectAllIndeterminate()\" (change)=\"toggleSelectAll()\" aria-label=\"Seleccionar todo\" />\n }\n </th>\n }\n @for (col of columns(); track col) {\n @if (isColumnVisibleInHeader(col)) {\n @if (col.type === 'boolean') {\n <th class=\"text-center boolean-column\">{{ col.label }}</th>\n }\n @else if (col.type === 'number' || col.type === 'integer') {\n <th class=\"text-center number-column\">{{ col.label }}</th>\n }\n @else if (col.type === 'date') {\n <th class=\"text-center date-column\">{{ col.label }}</th>\n }\n @else {\n <th>{{ col.label }}</th>\n }\n }\n }\n @if (hasAnyActions()) {\n <th class=\"table-actions-header\">\n <div class=\"actions-wrapper\">\n <span>Actions</span>\n @if (shouldShowCreateButton()) {\n <c80-icon button icon=\"add\" [disabled]=\"creating()\" title=\"Agregar\" [size]=\".6\" (iconClick)=\"startCreate()\" />\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of data(); track trackById(i, row); let i = $index) {\n <tr>\n @if (allowSelection() && data().length !== 0) {\n <td class=\"text-center selection-column\">\n <input type=\"checkbox\" [checked]=\"isItemSelected(row)\" (change)=\"toggleItemSelection(row)\" [attr.aria-label]=\"'Seleccionar fila ' + (i + 1)\" />\n </td>\n }\n @for (col of columns(); track col) {\n @if (isColumnVisibleForRow(col, row)) {\n @if (col.type === 'boolean') {\n <td class=\"text-center boolean-column\">\n @if (editing() === row['id'] && !col.readOnly) {\n <input type=\"checkbox\" [checked]=\"!!editRow()?.[col.accessor]\" (change)=\"onEditInput($event, col.accessor, col)\" [attr.aria-label]=\"col.label\" />\n }\n @else {\n @if (getCellValue(row, col.accessor) === true) {\n <c80-icon icon=\"check\" [size]=\".7\" />\n <br />\n }\n @else if (getCellValue(row, col.accessor) === false) {\n <c80-icon icon=\"cancel\" [size]=\".7\" />\n <br />\n }\n }\n </td>\n }\n @else if (col.type === 'number' || col.type === 'integer') {\n <td class=\"text-center number-column\">\n @if (editing() === row['id'] && !col.readOnly) {\n <input class=\"form-control form-control-sm\" type=\"number\" [value]=\"editRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" [min]=\"col.min\" [max]=\"col.max\" [step]=\"col.type === 'integer' ? '1' : 'any'\"\n (input)=\"onEditInput($event, col.accessor, col)\" />\n }\n @else {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{ getDisplayValue(getCellValue(row,\n col.accessor), col) }}</span>\n }\n </td>\n }\n @else if (col.type === 'date') {\n <td class=\"text-center date-column\">\n @if (editing() === row['id'] && !col.readOnly) {\n <input class=\"form-control form-control-sm\" type=\"datetime-local\" [value]=\"editRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" (input)=\"onEditInput($event, col.accessor, col)\" />\n }\n @else {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{ getDisplayValue(getCellValue(row,\n col.accessor), col) }}</span>\n }\n </td>\n }\n @else {\n <td>\n @if (editing() === row['id'] && !col.readOnly) {\n @if (col.type === 'enum') {\n <select class=\"form-control form-control-sm\" [value]=\"editRow()?.[col.accessor] ?? ''\" (change)=\"onEditInput($event, col.accessor, col)\">\n <option value=\"\">{{ col.label }}</option>\n @for (option of getEnumOptions(col); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n }\n @else {\n <input class=\"form-control form-control-sm\" [type]=\"col.type === 'password' ? 'password' : 'text'\" [value]=\"editRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" (input)=\"onEditInput($event, col.accessor, col)\" />\n }\n }\n @else {\n @if (col.type === 'password') {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">******</span>\n }\n @else if (col.type === 'enum') {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{\n getEnumDisplayValue(getCellValue(row, col.accessor), col)\n }}</span>\n }\n @else if (getCellValue(row, col.accessor) === true) {\n <c80-icon icon=\"check\" [size]=\".7\" />\n }\n @else if (getCellValue(row, col.accessor) === false) {\n <c80-icon icon=\"cancel\" [size]=\".7\" />\n }\n @else {\n <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->\n <span [style.color]=\"getCellColor(getCellValue(row, col.accessor), col)\">{{ getDisplayValue(getCellValue(row,\n col.accessor), col) }}</span>\n }\n }\n </td>\n }\n }\n }\n @if (hasAnyActions()) {\n <td class=\"text-end actions-cell\">\n <div class=\"actions-container\" [class.centered-actions]=\"editing() === row['id']\">\n @if (editing() === row['id']) {\n <!-- Modo edici\u00F3n: mostrar guardar y cancelar -->\n @if (hasCrudUpdate()) {\n <c80-icon button icon=\"check\" title=\"Guardar\" (iconClick)=\"saveEdit(row)\" [size]=\".7\" />\n }\n <c80-icon button icon=\"cancel\" color=\"warn\" title=\"Cancelar\" (iconClick)=\"cancelEdit()\" [size]=\".7\" />\n }\n @else {\n @for (action of customActions(); track action.name) {\n @if (shouldShowAction(action, row, data())) {\n <c80-icon button [icon]=\"action.icon\" [customColor]=\"getActionColor(action)\" [color]=\"action.name === 'delete' ? 'warn' : 'primary'\" [title]=\"getActionTooltip(action)\" (iconClick)=\"onDynamicAction(action, row)\" [size]=\".7\" />\n }\n }\n }\n </div>\n </td>\n }\n </tr>\n }\n @if (creating() && hasCrudCreate()) {\n <tr>\n @if (allowSelection() && data().length !== 0) {\n <td class=\"text-center selection-column\">\n <!-- Empty cell for alignment -->\n </td>\n }\n @for (col of columns(); track col) {\n @if (isColumnVisible(col)) {\n @if (col.type === 'boolean') {\n <td class=\"text-center\">\n @if (!col.readOnly) {\n <input type=\"checkbox\" [checked]=\"!!newRow()?.[col.accessor]\" (change)=\"onInput($event, col.accessor, col)\" [attr.aria-label]=\"col.label\" />\n }\n @else {\n <!-- ReadOnly boolean column in create mode shows empty -->\n <span class=\"text-muted\">-</span>\n }\n </td>\n } @else if (col.type === 'number' || col.type === 'integer') {\n <td class=\"text-center number-column\">\n @if (!col.readOnly) {\n <input class=\"form-control form-control-sm\" type=\"number\" [value]=\"newRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" [min]=\"col.min\" [max]=\"col.max\" [step]=\"col.type === 'integer' ? '1' : 'any'\"\n (input)=\"onInput($event, col.accessor, col)\" />\n }\n @else {\n <!-- ReadOnly number column in create mode shows empty -->\n <span class=\"text-muted\">-</span>\n }\n </td>\n } @else {\n <td>\n @if (!col.readOnly) {\n @if (col.type === 'enum') {\n <select class=\"form-control form-control-sm\" [value]=\"newRow()?.[col.accessor] ?? ''\" (change)=\"onInput($event, col.accessor, col)\">\n <option value=\"\">{{ col.label }}</option>\n @for (option of getEnumOptions(col); track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n }\n @else {\n <input class=\"form-control form-control-sm\" type=\"text\" [value]=\"newRow()?.[col.accessor] ?? ''\" [placeholder]=\"col.label\" (input)=\"onInput($event, col.accessor, col)\" />\n }\n }\n @else {\n <!-- ReadOnly column in create mode shows empty -->\n <span class=\"text-muted\">-</span>\n }\n </td>\n }\n }\n }\n <td class=\"text-end actions-cell\">\n <div class=\"actions-container centered-actions\">\n @if (hasCrudCreate()) {\n <c80-icon button icon=\"check\" title=\"Guardar\" (iconClick)=\"saveCreate()\" [size]=\".7\" />\n }\n <c80-icon button icon=\"cancel\" color=\"warn\" title=\"Cancelar\" (iconClick)=\"cancelCreate()\" [size]=\".7\" />\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n @if (data().length === 0 && !creating()) {\n <div class=\"text-center text-muted py-3 small\">\n No hay datos para mostrar.\n </div>\n }\n\n <!-- Pagination Controls -->\n @if (paginated() && data().length > 0) {\n <div class=\"pagination-container\">\n <div class=\"pagination-info\">\n P\u00E1gina {{ currentPage() }} de {{ totalPages() }}\n </div>\n <div class=\"pagination-controls\">\n <button type=\"button\" class=\"btn btn-sm\" [disabled]=\"!hasPrevPage()\" (click)=\"onPrevPage()\" title=\"P\u00E1gina anterior\">\n <c80-icon icon=\"arrowLeft\" [size]=\".6\" [button]=\"false\" />\n </button>\n <button type=\"button\" class=\"btn btn-sm\" [disabled]=\"!hasNextPage()\" (click)=\"onNextPage()\" title=\"P\u00E1gina siguiente\">\n <c80-icon icon=\"arrowRight\" [size]=\".6\" [button]=\"false\" />\n </button>\n </div>\n </div>\n }\n</div>\n\n<c80-modal />", styles: ["@charset \"UTF-8\";input[type=checkbox]{width:1.3rem!important;height:1.3rem!important;accent-color:var(--color-icon-danger);background:var(--color-bg-primary);margin:0 .25rem;vertical-align:middle;cursor:pointer}.table-responsive{width:100%;overflow-x:auto}.table-responsive .search-container{margin-bottom:-.06rem}.table-responsive .search-container .search-input-wrapper .input-group .btn-borrar{border-bottom-right-radius:0;border-color:var(--color-border-default);height:32px}.table-responsive .search-container .search-input-wrapper .input-group .input-group-text{background-color:var(--color-bg-secondary);border-color:var(--color-border-default);border-bottom-left-radius:0;color:var(--color-text-secondary);width:56.1px;height:32px;padding:.25rem .5rem}.table-responsive .search-container .search-input-wrapper .input-group .input-group-text c80-icon{display:flex;align-items:center;justify-content:center}.table-responsive .search-container .search-input-wrapper .input-group .search-input{border-color:var(--color-border-default);border-bottom-right-radius:0;font-size:.75rem;height:32px;padding:.25rem .5rem;outline:none!important;background-color:var(--color-bg-primary);color:var(--color-text-primary)}.table-responsive .search-container .search-input-wrapper .input-group .search-input:focus{outline:none!important}.table-responsive .search-container .search-input-wrapper .input-group .search-input::placeholder{color:var(--color-text-muted);font-style:italic}.table-responsive .table{min-width:0px;margin-bottom:0;background-color:var(--color-bg-primary);color:var(--color-text-primary);border-color:var(--color-border-default)}.table-responsive .table.table-bordered,.table-responsive .table.table-bordered th,.table-responsive .table.table-bordered td{border-color:var(--color-border-default)}.table-responsive .table.table-hover tbody tr:hover{background-color:var(--color-bg-hover)!important;color:var(--color-text-primary)}.table-responsive .table.table-hover tbody tr:hover td{background-color:var(--color-bg-hover)}.table-responsive .table .sticky-header{position:sticky;top:0;z-index:10;background-color:var(--color-bg-secondary)!important}.table-responsive .table .sticky-header .table-actions-header{display:table-cell;vertical-align:middle;padding:.2rem .6rem!important;background-color:var(--color-bg-secondary)!important}.table-responsive .table .sticky-header .actions-wrapper{display:flex;align-items:center;justify-content:center;gap:.5rem;height:100%}.table-responsive .table .sticky-header th{max-height:31px!important;vertical-align:middle!important;padding:.2rem .6rem!important;font-size:small!important;background-color:var(--color-bg-secondary)!important;border-bottom:2px solid var(--color-border-default);color:var(--color-text-primary)}.table-responsive .table tbody{background-color:var(--color-bg-primary)}.table-responsive .table tbody td{height:35px!important;min-height:35px!important;max-height:35px!important;vertical-align:middle!important;padding:.2rem .8rem!important;font-size:small;color:var(--color-text-primary);background-color:var(--color-bg-primary);border-color:var(--color-border-default)}.table-responsive .table tbody tr{height:35px!important;min-height:35px!important;max-height:35px!important;cursor:pointer;background-color:var(--color-bg-primary)}.table-responsive .table tbody tr:hover{background-color:var(--color-bg-hover)!important}.table-responsive .table tbody tr:hover td{background-color:var(--color-bg-hover)}.table-responsive .table tbody .text-muted{color:var(--color-text-muted)!important}.table-responsive .table tbody input,.table-responsive .table tbody select{border:1px solid var(--color-border-default);height:100%!important;font-size:smaller!important;background-color:var(--color-bg-primary);color:var(--color-text-primary)}.table-responsive .table tbody input:focus,.table-responsive .table tbody select:focus{border-color:var(--color-border-focus);box-shadow:var(--shadow-focus)}.table-responsive .table tbody input::placeholder,.table-responsive .table tbody select::placeholder{color:var(--color-text-muted)}.table-responsive .table tbody input[type=text],.table-responsive .table tbody input:not([type]){width:100%!important}.table-responsive .table tbody input[type=number]{min-width:80px!important;width:80px!important;text-align:center}.table-responsive .table tbody select{width:100%!important;padding:.25rem .5rem}.table-responsive .table thead th.boolean-column,.table-responsive .table tbody td.boolean-column,.table-responsive .table thead th.selection-column,.table-responsive .table tbody td.selection-column,.table-responsive .table thead th.table-actions-header,.table-responsive .table tbody td.table-actions-header{width:1%;white-space:nowrap}.table-responsive .table thead th.number-column,.table-responsive .table tbody td.number-column{width:1%;white-space:nowrap;text-align:center}.table-responsive .table thead th.number-column input[type=number],.table-responsive .table tbody td.number-column input[type=number]{min-width:80px!important;width:80px!important;text-align:center}.table-responsive .table thead th.date-column,.table-responsive .table tbody td.date-column{width:1%;white-space:nowrap;text-align:center}.table-responsive .table thead th.date-column input[type=datetime-local],.table-responsive .table tbody td.date-column input[type=datetime-local]{min-width:200px!important;width:200px!important;text-align:center}.table-responsive .table .actions-cell{white-space:nowrap;padding:0!important}.table-responsive .table .actions-container{display:flex;flex-direction:row;justify-content:flex-end;align-items:center;width:100%;height:100%}.table-responsive .table .actions-container.centered-actions{justify-content:center}.table-responsive .text-center.text-muted{color:var(--color-text-muted)!important}.table-responsive .btn-outline-secondary{background-color:var(--color-bg-primary);color:var(--color-text-primary);border-color:var(--color-border-default)}.table-responsive .btn-outline-secondary:hover{background-color:var(--color-bg-hover);border-color:var(--color-border-medium)}.table-responsive .btn-outline-secondary:focus{box-shadow:var(--shadow-focus)}.table-responsive .pagination-container{display:flex;justify-content:space-between;align-items:center;padding:.2rem .5rem;background-color:var(--color-bg-secondary);border:1px solid var(--color-border-default);border-top:none;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.table-responsive .pagination-container .pagination-info{font-size:.875rem;color:var(--color-text-secondary);font-weight:500}.table-responsive .pagination-container .pagination-controls{display:flex;gap:.5rem}.table-responsive .pagination-container .pagination-controls button{min-width:36px;height:32px;padding:.25rem .5rem;display:flex;align-items:center;justify-content:center;background-color:var(--color-bg-primary);color:var(--color-text-primary);border:none}.table-responsive .pagination-container .pagination-controls button:hover:not(:disabled){background-color:var(--color-bg-hover);border:none}.table-responsive .pagination-container .pagination-controls button:disabled{opacity:.5;cursor:auto;background-color:var(--color-bg-primary);border:none}\n"] }]
|
|
549
|
+
}], ctorParameters: () => [], propDecorators: { data$: [{ type: i0.Input, args: [{ isSignal: true, alias: "data$", required: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], inputValues$: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputValues$", required: false }] }], customActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "customActions", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], allowSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowSelection", required: false }] }], noConfirm: [{ type: i0.Input, args: [{ isSignal: true, alias: "noConfirm", required: false }] }], paginated: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginated", required: false }] }], paginationConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginationConfig", required: false }] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }], searchTerm: [{ type: i0.Output, args: ["searchTerm"] }], errorEvent: [{ type: i0.Output, args: ["errorEvent"] }], selectable: [{ type: i0.Output, args: ["selectable"] }], paginationChange: [{ type: i0.Output, args: ["paginationChange"] }] } });
|
|
490
550
|
//# sourceMappingURL=table.component.js.map
|