@jcbautistam/custom-table
Version:
Librería de tabla reutilizable con acciones, filtros, exportación y más.
1,100 lines (1,087 loc) • 195 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Component, Inject, Directive, HostListener, forwardRef, Input, Pipe, EventEmitter, ViewEncapsulation, ViewChildren, ViewChild, Output, NgModule } from '@angular/core';
import * as i6 from '@angular/common';
import { formatDate, CommonModule } from '@angular/common';
import * as i16 from '@angular/material/tooltip';
import { MatTooltipModule } from '@angular/material/tooltip';
import * as i1$2 from '@angular/forms';
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
import * as i8 from '@angular/router';
import { RouterModule } from '@angular/router';
import * as i5 from '@angular/material/checkbox';
import { MatCheckboxModule } from '@angular/material/checkbox';
import * as i15 from '@angular/material/slide-toggle';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import * as i17 from '@angular/material/progress-spinner';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import * as i1$1 from '@angular/material/dialog';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import * as i9 from '@angular/material/paginator';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import * as i10 from '@angular/material/table';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import * as i4 from '@angular/material/button';
import { MatButtonModule } from '@angular/material/button';
import * as i12 from '@angular/material/icon';
import { MatIconModule } from '@angular/material/icon';
import * as i13 from 'ngx-bootstrap/datepicker';
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
import { BehaviorSubject, interval, fromEvent } from 'rxjs';
import { take, map, debounceTime, takeUntil } from 'rxjs/operators';
import { HttpParams } from '@angular/common/http';
import * as XLSX from 'xlsx';
import { formatInTimeZone, toDate } from 'date-fns-tz';
import * as i1 from 'ngx-spinner';
import * as i1$3 from '@angular/material/select';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
class ParametrosGenerales {
constructor(init) {
this.ordenarPor = 'FechaCreacion';
this.descending = false;
this.noPagina = 1;
this.tamanoPagina = 10;
this.activos = true;
this.filtrosIniciales = {};
this.filtrosPorColumna = {};
this.rangoFechas = '';
this.multiIds = '';
this.actionMulti = '';
this.idCompania = 0;
if (init) {
Object.assign(this, init);
}
}
}
class ConfiguracionParametros {
configurar(parametros, extraParams) {
let params = new HttpParams();
parametros.idCompania = Number(localStorage.getItem('CompaniaSelect')) || 0;
// Agregar valores básicos del objeto de parámetros
Object.entries(parametros).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
params = params.set(key, value.toString());
}
});
// Agregar filtros iniciales
params = this.agregarFiltros(params, 'filtrosIniciales', parametros.filtrosIniciales);
// Agregar filtros por columna
params = this.agregarFiltros(params, 'filtrosPorColumna', parametros.filtrosPorColumna);
// Agregar parámetros adicionales
if (extraParams) {
Object.entries(extraParams).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
params = params.set(key, value.toString());
}
});
}
return params;
}
agregarFiltros(params, prefix, filtros) {
// Añadir filtros si existen
if (filtros) {
Object.keys(filtros).forEach((key) => {
const filtro = filtros[key];
if (filtro) {
params = params.set(`${prefix}[${key}]`, filtro);
}
});
}
return params;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ConfiguracionParametros, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ConfiguracionParametros, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ConfiguracionParametros, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
class DateTimeService {
constructor() {
// Obtener la zona horaria del dispositivo del usuario
this.userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
}
toTimeZone(date = new Date()) {
// Formatear la fecha en la zona horaria del usuario y en formato ISO 8601
const isoString = formatInTimeZone(date, this.userTimeZone, "yyyy-MM-dd'T'HH:mm:ssXXX");
return isoString;
}
toUTCDate(dateInput = new Date()) {
const date = new Date(dateInput);
// Obtener los componentes de la fecha
const dayName = date.toLocaleString('en-US', { weekday: 'short' });
const monthName = date.toLocaleString('en-US', { month: 'short' });
const day = date.getDate();
const year = date.getFullYear();
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
// Obtener el offset de la zona horaria en horas y minutos
const timezoneOffset = -date.getTimezoneOffset();
const timezoneHours = Math.floor(timezoneOffset / 60)
.toString()
.padStart(2, '0');
const timezoneMinutes = (timezoneOffset % 60).toString().padStart(2, '0');
const timezoneSign = timezoneOffset >= 0 ? '+' : '-';
return new Date(`${dayName} ${monthName} ${day} ${year} ${hours}:${minutes}:${seconds} GMT${timezoneSign}${timezoneHours}${timezoneMinutes}`);
}
toTime(date = new Date()) {
if (!date) {
date = new Date();
}
// Formatear la fecha en la zona horaria del usuario y en formato ISO 8601
const isoString = formatInTimeZone(date, this.userTimeZone, 'HH:mm:ssXXX');
return isoString;
}
stringToDate(fechaString) {
const convertToDate = toDate(fechaString, { timeZone: this.userTimeZone });
return convertToDate;
}
getDateTimeNow() {
let date = new Date();
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); // Los meses son de 0 a 11
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return new Date(`${year}-${month}-${day}T${hours}:${minutes}:${seconds}`);
}
getformatDate(date = new Date(), format = 'dd/MM/yyyy') {
return formatDate(date, format, 'en-US');
}
getTextDateLiq(fecha) {
const opciones = {
day: 'numeric',
month: 'long',
year: 'numeric', // Año
};
const formateador = new Intl.DateTimeFormat('es-ES', opciones);
return formateador.format(new Date(fecha)).replace(',', '').toUpperCase();
}
combinarFechaYHora(fecha, hora) {
// Crea un objeto de fecha a partir de la fecha ingresada
const fechaObj = new Date(fecha);
const [horas, minutos] = hora.split(':').map(Number);
// Ajusta la hora y los minutos
fechaObj.setHours(horas, minutos, 0);
// Construye la cadena en formato 'YYYY-MM-DDTHH:mm:ss'
const año = fechaObj.getFullYear();
const mes = String(fechaObj.getMonth() + 1).padStart(2, '0'); // Los meses van de 0 a 11
const día = String(fechaObj.getDate()).padStart(2, '0');
const horasLocal = String(fechaObj.getHours()).padStart(2, '0');
const minutosLocal = String(fechaObj.getMinutes()).padStart(2, '0');
const segundosLocal = String(fechaObj.getSeconds()).padStart(2, '0');
// Devuelve la fecha combinada
return `${año}-${mes}-${día}T${horasLocal}:${minutosLocal}:${segundosLocal}`;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: DateTimeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: DateTimeService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: DateTimeService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return []; } });
class ExportTableExcelService {
constructor(dateTimeService) {
this.dateTimeService = dateTimeService;
}
exportToExcel(tableId, name, dataList, columnsDate) {
try {
let timeSpan = this.dateTimeService.getformatDate(new Date(), 'yyyyMMdd');
let fileName = `${name || 'Export'}_${timeSpan}.xlsx`;
if (dataList && dataList.length > 0) {
// Convertir valores booleanos de la columna "activo" a "activo" o "inactivo"
const modifiedDataList = dataList.map(item => {
if (item.hasOwnProperty('Activo')) {
console.log('column', item);
return { ...item, Activo: item.Activo ? 'activo' : 'inactivo' };
}
return item;
});
const workSheet = XLSX.utils.json_to_sheet(modifiedDataList);
// Ajustar el ancho de las columnas
const columnWidths = modifiedDataList[0] ? Object.keys(modifiedDataList[0]).map(() => ({ wpx: 100 })) : [];
workSheet['!cols'] = columnWidths;
const workBook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workBook, workSheet, name?.substring(0, 31) || 'Sheet1');
XLSX.writeFile(workBook, fileName);
console.log(`Archivo ${fileName} generado exitosamente.`);
}
else {
throw new Error('No hay datos para exportar.');
}
}
catch (error) {
console.error('Error al generar el archivo Excel:', error);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ExportTableExcelService, deps: [{ token: DateTimeService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ExportTableExcelService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ExportTableExcelService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: DateTimeService }]; } });
class LoadingService {
constructor(spinner) {
this.spinner = spinner;
}
// Método para abrir el spinner
open() {
this.spinner.show();
}
// Método para cerrar el spinner
close() {
this.spinner.hide();
}
// Método para abrir y cerrar el spinner después de un tiempo
delay(seconds) {
this.open();
setTimeout(() => {
this.close();
}, seconds * 1000);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: LoadingService, deps: [{ token: i1.NgxSpinnerService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: LoadingService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: LoadingService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: function () { return [{ type: i1.NgxSpinnerService }]; } });
class ExportNotificationService {
constructor() {
this.defaultStatus = {
isExporting: false,
activeJobs: [],
showNotification: false
};
this.exportStatusSubject = new BehaviorSubject(this.defaultStatus);
this.exportStatus$ = this.exportStatusSubject.asObservable();
}
/**
* Inicia una nueva exportación y retorna su ID
*/
startExport(fileName) {
const jobId = this.generateJobId();
const currentStatus = this.exportStatusSubject.value;
const newJob = {
id: jobId,
fileName,
progress: 0,
isCompleted: false,
error: null,
timestamp: Date.now(),
progressAnimation: null
};
// Añadir el nuevo trabajo a la lista de trabajos activos
const updatedJobs = [...currentStatus.activeJobs, newJob];
this.exportStatusSubject.next({
isExporting: true,
activeJobs: updatedJobs,
showNotification: true
});
// Iniciar la animación de progreso hasta el 10%
this.animateProgressTo(jobId, 0, 10, 800);
return jobId;
}
/**
* Actualiza el progreso de una exportación específica
*/
updateProgress(jobId, targetProgress) {
const currentStatus = this.exportStatusSubject.value;
const job = currentStatus.activeJobs.find(j => j.id === jobId);
if (job) {
// Detener cualquier animación previa
this.stopAnimation(job);
// Animar desde el progreso actual hasta el objetivo
const currentProgress = job.progress;
this.animateProgressTo(jobId, currentProgress, targetProgress, 1000);
}
}
/**
* Detiene de forma segura una animación
*/
stopAnimation(job) {
if (job && job.progressAnimation) {
try {
job.progressAnimation.unsubscribe();
job.progressAnimation = null;
}
catch (error) {
console.error('Error al detener animación:', error);
}
}
}
/**
* Anima el progreso de una forma más realista
*/
animateProgressTo(jobId, fromProgress, toProgress, duration) {
// Obtener estado actual
const currentStatus = this.exportStatusSubject.value;
const jobIndex = currentStatus.activeJobs.findIndex(j => j.id === jobId);
if (jobIndex === -1)
return;
const job = currentStatus.activeJobs[jobIndex];
// Detener animación previa si existe
this.stopAnimation(job);
// Calcular cuántos pasos necesitamos
const steps = Math.max(Math.ceil(duration / 50), 5); // Al menos 5 pasos
const stepValue = (toProgress - fromProgress) / steps;
// Crear una animación con interval
const animation = interval(duration / steps).pipe(take(steps), map(step => fromProgress + stepValue * (step + 1))).subscribe({
next: (progress) => {
// Obtener el estado actual
const updatedStatus = this.exportStatusSubject.value;
const currentJobIndex = updatedStatus.activeJobs.findIndex(j => j.id === jobId);
if (currentJobIndex !== -1) {
// Crear una copia de los trabajos
const updatedJobs = [...updatedStatus.activeJobs];
// Actualizar el progreso del trabajo específico
updatedJobs[currentJobIndex] = {
...updatedJobs[currentJobIndex],
progress: progress
};
// Actualizar el estado
this.exportStatusSubject.next({
...updatedStatus,
activeJobs: updatedJobs
});
// Limpiar la suscripción cuando llegamos al último paso
if (progress >= toProgress - stepValue / 2) {
this.stopAnimation(updatedJobs[currentJobIndex]);
}
}
},
error: (err) => {
console.error('Error en animación:', err);
},
complete: () => {
// Completar la animación
}
});
// Guardar la animación en el job
const updatedJobs = [...currentStatus.activeJobs];
updatedJobs[jobIndex] = {
...updatedJobs[jobIndex],
progressAnimation: animation
};
this.exportStatusSubject.next({
...currentStatus,
activeJobs: updatedJobs
});
}
/**
* Marca una exportación como completada
*/
completeExport(jobId) {
const currentStatus = this.exportStatusSubject.value;
const jobIndex = currentStatus.activeJobs.findIndex(j => j.id === jobId);
if (jobIndex !== -1) {
const job = currentStatus.activeJobs[jobIndex];
// Detener cualquier animación en curso
this.stopAnimation(job);
// Animar al 100% para completar
this.animateProgressTo(jobId, job.progress, 100, 500);
// Actualizar el estado a completado
const updatedJobs = currentStatus.activeJobs.map(job => {
if (job.id === jobId) {
return {
...job,
isCompleted: true
};
}
return job;
});
this.exportStatusSubject.next({
...currentStatus,
activeJobs: updatedJobs,
isExporting: updatedJobs.some(job => !job.isCompleted && !job.error)
});
// Programar la eliminación del trabajo completado después de cierto tiempo
setTimeout(() => {
this.removeJob(jobId);
}, 5000);
}
}
/**
* Marca una exportación como fallida
*/
errorExport(jobId, error) {
console.error('Error en la exportación:', error);
const currentStatus = this.exportStatusSubject.value;
const jobIndex = currentStatus.activeJobs.findIndex(j => j.id === jobId);
if (jobIndex !== -1) {
const job = currentStatus.activeJobs[jobIndex];
// Detener cualquier animación en curso
this.stopAnimation(job);
const updatedJobs = currentStatus.activeJobs.map(job => {
if (job.id === jobId) {
return {
...job,
error: error.message || 'Error al exportar',
isCompleted: true
};
}
return job;
});
this.exportStatusSubject.next({
...currentStatus,
activeJobs: updatedJobs,
isExporting: updatedJobs.some(job => !job.isCompleted && !job.error)
});
// Programar la eliminación del trabajo fallido después de cierto tiempo
setTimeout(() => {
this.removeJob(jobId);
}, 5000);
}
}
/**
* Elimina un trabajo de la lista de trabajos activos
*/
removeJob(jobId) {
const currentStatus = this.exportStatusSubject.value;
const jobIndex = currentStatus.activeJobs.findIndex(j => j.id === jobId);
if (jobIndex !== -1) {
const job = currentStatus.activeJobs[jobIndex];
// Detener cualquier animación en curso
this.stopAnimation(job);
const updatedJobs = currentStatus.activeJobs.filter(job => job.id !== jobId);
if (updatedJobs.length === 0) {
// Si no hay más trabajos, ocultar la notificación
this.exportStatusSubject.next({
isExporting: false,
activeJobs: [],
showNotification: false
});
}
else {
// Si aún hay trabajos, actualizar la lista
this.exportStatusSubject.next({
...currentStatus,
activeJobs: updatedJobs,
isExporting: updatedJobs.some(job => !job.isCompleted && !job.error)
});
}
}
}
/**
* Oculta la notificación manualmente
*/
hideNotification() {
const currentStatus = this.exportStatusSubject.value;
// Detener todas las animaciones en curso
currentStatus.activeJobs.forEach(job => {
this.stopAnimation(job);
});
this.exportStatusSubject.next({
...currentStatus,
showNotification: false
});
}
/**
* Muestra la notificación si estaba oculta
*/
showNotification() {
const currentStatus = this.exportStatusSubject.value;
if (currentStatus.activeJobs.length > 0) {
this.exportStatusSubject.next({
...currentStatus,
showNotification: true
});
}
}
/**
* Genera un ID único para cada trabajo
*/
generateJobId() {
return 'export-' + Date.now() + '-' + Math.floor(Math.random() * 1000);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ExportNotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ExportNotificationService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ExportNotificationService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: function () { return []; } });
//column-visibility-modal.component.ts
class ColumnVisibilityModal2Component {
constructor(dialogRef, data) {
this.dialogRef = dialogRef;
this.data = data;
this.columns = JSON.parse(JSON.stringify(data.columns)); // Clonamos para evitar modificar la referencia original
this.columnsReqMin = data.columnsReqMin; // Recibimos el mínimo de columnas desde el componente padre
}
// Cuenta cuántas columnas están seleccionadas
getSelectedCount() {
return this.columns.filter(col => col.visible).length;
}
// Devuelve `true` si el checkbox debe estar deshabilitado
isCheckboxDisabled(column) {
return this.getSelectedCount() <= this.columnsReqMin && column.visible;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ColumnVisibilityModal2Component, deps: [{ token: i1$1.MatDialogRef }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.2", type: ColumnVisibilityModal2Component, selector: "app-column-visibility-modal", ngImport: i0, template: "<h2 mat-dialog-title>Configurar columnas</h2>\r\n<mat-dialog-content>\r\n <div *ngFor=\"let column of columns\">\r\n <mat-checkbox \r\n [(ngModel)]=\"column.visible\"\r\n [disabled]=\"isCheckboxDisabled(column)\"\r\n >\r\n {{ column.displayName }}\r\n </mat-checkbox>\r\n </div>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button (click)=\"dialogRef.close()\">Cancelar</button>\r\n <button mat-button [disabled]=\"getSelectedCount() < columnsReqMin\" (click)=\"dialogRef.close(columns)\">Aceptar</button>\r\n</mat-dialog-actions>\r\n", styles: [""], dependencies: [{ kind: "directive", type: i6.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1$1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i1$1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "component", type: i4.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { kind: "component", type: i5.MatCheckbox, selector: "mat-checkbox", inputs: ["disableRipple", "color", "tabIndex"], exportAs: ["matCheckbox"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ColumnVisibilityModal2Component, decorators: [{
type: Component,
args: [{ selector: 'app-column-visibility-modal', template: "<h2 mat-dialog-title>Configurar columnas</h2>\r\n<mat-dialog-content>\r\n <div *ngFor=\"let column of columns\">\r\n <mat-checkbox \r\n [(ngModel)]=\"column.visible\"\r\n [disabled]=\"isCheckboxDisabled(column)\"\r\n >\r\n {{ column.displayName }}\r\n </mat-checkbox>\r\n </div>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button (click)=\"dialogRef.close()\">Cancelar</button>\r\n <button mat-button [disabled]=\"getSelectedCount() < columnsReqMin\" (click)=\"dialogRef.close(columns)\">Aceptar</button>\r\n</mat-dialog-actions>\r\n" }]
}], ctorParameters: function () { return [{ type: i1$1.MatDialogRef }, { type: undefined, decorators: [{
type: Inject,
args: [MAT_DIALOG_DATA]
}] }]; } });
class ToUpperCaseDirective {
constructor(ngControl) {
this.ngControl = ngControl;
}
onInputChange(value) {
this.ngControl?.control?.setValue(value.toUpperCase(), { emitEvent: false });
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ToUpperCaseDirective, deps: [{ token: i1$2.NgControl }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.2", type: ToUpperCaseDirective, selector: "[toUpperCase]", host: { listeners: { "input": "onInputChange($event.target.value)" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ToUpperCaseDirective, decorators: [{
type: Directive,
args: [{
selector: '[toUpperCase]'
}]
}], ctorParameters: function () { return [{ type: i1$2.NgControl }]; }, propDecorators: { onInputChange: [{
type: HostListener,
args: ['input', ['$event.target.value']]
}] } });
class FormatNumberService {
constructor() { }
formatNumber(value, char) {
if (value == null || value == undefined)
return;
// Convertir a string si es un número
if (typeof value === 'number') {
value = value.toString();
}
value = value.replace(/[^0-9.-]*/g, '');
const parts = value.split('.');
//Agrega una coma cada 3 cifras
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
value = parts.join('.');
//Solo acepta un punto decimal
if (parts.length > 2) {
value = parts[0] + '.' + parts.slice(1).join('');
}
// Solo acepta dos decimales y completa con ceros si es necesario
if (parts.length > 1) {
let decimalPart = parts[1].slice(0, 4);
// if (decimalPart.length < 4) {
// decimalPart = decimalPart.padEnd(4, '0');
// }
value = `${parts[0]}.${decimalPart}`;
}
// else {
// value = `${parts[0]}.0000`;
// }
if (char != null)
value = char + value;
return value;
}
convertToNumber(value) {
if (value == null || value === '0')
return 0;
if (typeof value === 'number') {
return isNaN(value) ? 0 : value;
}
value = value.replace(/[^0-9.]*/g, '');
const numberValue = Number(value);
return isNaN(numberValue) ? 0 : numberValue;
}
// convertToNumber(value: any): number {
// console.log('convertToNumber',value);
// if (value == null || value == '0') return 0;
// if (typeof value === 'number')
// return value;
// value = value.replace(/[^0-9.-]*/g, '');
// return Number(value);
// }
textNumber(num) {
console.log('textNumber', num);
let numWord = '';
let numUnidad = 0;
let numResult = num;
let and = ' ';
const integerPart = Math.floor(num);
const decimalPart = Math.round((num - integerPart) * 100);
const decimalPartInWords = decimalPart.toString().padStart(2, '0');
numUnidad = Math.floor(numResult / 1000);
if (numUnidad > 0) {
if (numUnidad > 1)
numWord = numWord + this.numberToWords(numUnidad) + ' MIL';
else
numWord = 'MIL ';
numResult = numResult - numUnidad * 1000;
}
numUnidad = Math.floor(numResult / 100) * 100;
if (numUnidad > 0) {
numWord = numWord + ' ' + this.numberToWords(numUnidad);
numResult = numResult - numUnidad;
}
numUnidad = Math.floor(numResult / 10) * 10;
if (numUnidad > 0) {
numWord = numWord + ' ' + this.numberToWords(numUnidad);
numResult = numResult - numUnidad;
and = ' Y ';
}
numUnidad = Math.floor(numResult);
if (numUnidad > 0) {
numWord = numWord + and + this.numberToWords(numUnidad);
numResult = numResult - numUnidad;
}
return `${numWord} ${decimalPartInWords}/100 M.N.`;
}
numberToWords(num) {
const units = [
'',
'UNO',
'DOS',
'TRES',
'CUATRO',
'CINCO',
'SEIS',
'SIETE',
'OCHO',
'NUEVE',
];
const tens = [
'',
'DIEZ',
'VEINTE',
'TREINTA',
'CUARENTA',
'CINCUENTA',
'SESENTA',
'SETENTA',
'OCHENTA',
'NOVENTA',
];
const teens = [
'ONCE',
'DOCE',
'TRECE',
'CATORCE',
'QUINCE',
'DIECISEIS',
'DIECISIETE',
'DIECIOCHO',
'DIECINUEVE',
];
const hundreds = [
'',
'CIENTO',
'DOSCIENTOS',
'TRESCIENTOS',
'CUATROCIENTOS',
'QUINIENTOS',
'SEISCIENTOS',
'SETECIENTOS',
'OCHOCIENTOS',
'NOVECIENTOS',
];
if (num === 0)
return 'CERO';
if (num === 100)
return 'CIEN';
let word = '';
if (Math.floor(num / 100) > 0) {
word += hundreds[Math.floor(num / 100)] + ' ';
}
let remainder = num % 100;
if (remainder >= 11 && remainder <= 19) {
word += teens[remainder - 11];
}
else {
if (Math.floor(remainder / 10) > 0) {
word += tens[Math.floor(remainder / 10)] + ' ';
}
remainder = remainder % 10;
if (remainder > 0) {
word += units[remainder];
}
}
return word.trim();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: FormatNumberService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: FormatNumberService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: FormatNumberService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return []; } });
class OnlyDigitDirective {
set format(value) {
this._format = value?.toLowerCase() || '';
}
get format() {
return this._format;
}
set showZero(value) {
this._showZero = (value == 'true') ? true : false;
}
get showZero() {
return this._showZero;
}
constructor(el, renderer, formatNumberService) {
this.el = el;
this.renderer = renderer;
this.formatNumberService = formatNumberService;
this._format = '';
this._showZero = true;
this.onChange = () => { };
this.onTouched = () => { };
this.renderer.addClass(this.el.nativeElement, 'text-end');
}
ngOnInit() {
this.writeValue(this.el.nativeElement.value); // Establecer el valor inicial con el formato
}
ngOnChanges(changes) {
if (changes['format']) {
this.writeValue(this.el.nativeElement.value); // Actualizar el valor con el nuevo formato
}
}
writeValue(value) {
if (value == null || isNaN(value) || value === "")
return;
let formattedValue = value;
if (this.format != 'id') {
formattedValue = this.formatNumberService.formatNumber(value, this.format);
}
this.renderer.setProperty(this.el.nativeElement, 'value', formattedValue); // Asignar el valor formateado al campo
}
registerOnChange(fn) {
this.onChange = fn;
}
registerOnTouched(fn) {
this.onTouched = fn;
}
setDisabledState(isDisabled) {
this.renderer.setProperty(this.el.nativeElement, 'disabled', isDisabled);
}
onInput(value) {
if (value == null)
return;
let formattedValue = value;
if (this.format != 'id') {
formattedValue = this.formatNumberService.formatNumber(formattedValue, this.format);
}
else {
formattedValue = formattedValue.replace(/[^0-9-]*/g, '');
}
this.renderer.setProperty(this.el.nativeElement, 'value', formattedValue);
if (this.onChange) {
const numericValue = parseFloat(formattedValue.replace(/[^0-9.-]*/g, ''));
this.onChange(isNaN(numericValue) ? null : numericValue);
}
}
onBlur() {
if (this.onTouched) {
this.onTouched();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: OnlyDigitDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: FormatNumberService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.2", type: OnlyDigitDirective, selector: "[onlyDigit]", inputs: { format: ["onlyDigit", "format"], showZero: "showZero" }, host: { listeners: { "input": "onInput($event.target.value)", "blur": "onBlur()" } }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => OnlyDigitDirective),
multi: true
}
], usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: OnlyDigitDirective, decorators: [{
type: Directive,
args: [{
selector: '[onlyDigit]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => OnlyDigitDirective),
multi: true
}
]
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: FormatNumberService }]; }, propDecorators: { format: [{
type: Input,
args: ['onlyDigit']
}], showZero: [{
type: Input
}], onInput: [{
type: HostListener,
args: ['input', ['$event.target.value']]
}], onBlur: [{
type: HostListener,
args: ['blur']
}] } });
class OnlyDigitPipe {
constructor(numServ) {
this.numServ = numServ;
}
transform(value, char) {
char = char ?? '';
let stringValue = '';
if (value == null || value == undefined)
value = 0;
// Convertir a string si es un número
if (typeof value === 'number') {
stringValue = value.toString();
}
if (char.toLowerCase() != 'id') {
if (!stringValue || stringValue === '') {
stringValue = '0';
}
else if (char == '%') {
stringValue = stringValue + char;
}
else {
stringValue = this.numServ.formatNumber(stringValue, char);
}
}
return stringValue;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: OnlyDigitPipe, deps: [{ token: FormatNumberService }], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "16.2.2", ngImport: i0, type: OnlyDigitPipe, name: "onlyDigit" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: OnlyDigitPipe, decorators: [{
type: Pipe,
args: [{
name: 'onlyDigit',
}]
}], ctorParameters: function () { return [{ type: FormatNumberService }]; } });
class StringTruncatePipe {
transform(value, limit = 25, completeWords = false, ellipsis = '...') {
if (completeWords) {
limit = value.substr(0, limit).lastIndexOf(' ');
}
if (value == null)
return '';
return value.length > limit ? value.substr(0, limit) + ellipsis : value;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: StringTruncatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "16.2.2", ngImport: i0, type: StringTruncatePipe, name: "truncate" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: StringTruncatePipe, decorators: [{
type: Pipe,
args: [{
name: 'truncate'
}]
}] });
class ImagePipe {
transform(imageHex) {
if (imageHex && imageHex.trim() !== '') {
try {
const cleanHex = imageHex.startsWith('0x') ? imageHex.slice(2) : imageHex;
const hexPairs = cleanHex.match(/[\dA-F]{2}/gi) || [];
const bytes = hexPairs.map(pair => parseInt(pair, 16));
const byteArray = new Uint8Array(bytes);
const blob = new Blob([byteArray], { type: 'image/jpeg' });
return URL.createObjectURL(blob);
}
catch (error) {
console.error('Error al convertir hex a imagen:', error);
return '';
}
}
return '';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ImagePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "16.2.2", ngImport: i0, type: ImagePipe, name: "image" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: ImagePipe, decorators: [{
type: Pipe,
args: [{
name: 'image'
}]
}] });
class CustomTableComponent {
get columnKeys() {
return Object.keys(this.columnConfigs);
}
constructor(excelService, dialog, configParams, loadingService, exportNotificationService) {
this.excelService = excelService;
this.dialog = dialog;
this.configParams = configParams;
this.loadingService = loadingService;
this.exportNotificationService = exportNotificationService;
// Objeto para manejar la cancelación de suscripciones de Observable
this.destroy$ = new EventEmitter();
// Variables para el control de la exportación
this.exportSubscription = null;
this.isExportInProgress = false;
this.maxDate = new Date(); // Fecha máxima hoy
this.minDate = new Date(); // Fecha mínima 30 días atrás
this.totalRecords = 0;
this.formularioModificado = false;
this.isSelectAll = false;
this.originalData = []; // Una copia de los datos originales para restablecer después del filtrado
this.dataActives = [];
// Esto almacenará el filtro actual para cada columna.
this.filters = {};
this.dataSource = new MatTableDataSource();
this.displayedColumns = [];
this.styleRow = {};
this.isToggleChecked = true;
//Se reciben los datos que mostrará la tabla
this.data = [];
//Se reciben los datos que mostrará en campo editable select
// @Input() dataSelect: any[] = [];
this.dataSelect = [];
this.withHeader = true;
//Se reciben configuraciones generales de la tabla como datos de paginacion y fontsize
this.tableConfigs = {};
//Se recibe el nombre del menu para aplicar el correcto permiso por usuario
this.nombreMenu = '';
//Se recibe el titulo de la tabla
this.nombreTabla = '';
//Se recibe la ruta a la que navegara cuando se use el boton [crear] propio de la tabla
this.crearRoute = '';
//Se recibe la indicacion sobre si se debe mostrar el boton [crear] normal
this.showCreateButton = false;
//Se recibe la indicacion sobre si se debe mostrar el boton [crear] normal
this.showCreateButtonModal = false;
//Se recibe la indicacion sobre si se debe mostrar el boton [exportar a excel] que descarga un archivo excel
this.showExportarButton = true;
//Se recibe el nombre con el que se debe guardar el archivo excel al exportar la tabla desde el boton [exportar a excel]
this.excelFileName = '';
//Indica si esta "cargando" datos, y asi manejar la visualizacion del indicador loader
this.isLoading = false;
//Se recibe la indicacion sobre si se debe mostrar el boton [crear] cuando es un catalogoGeneral
this.showCreateButtonDetGral = false;
//Se recibe la indicacion sobre si se debe mostrar el boton [refrescar datos] que refresca los datos de la tabla
this.showRefreshButton = true;
this.showAddButton = false;
//Se recibe la indicacion sobre si se debe mostrar el boton [configuracion de columnas] que abre el modal para elegir que columnas visualizar
this.showConfColumnsButton = true;
//Se recibe la indicacion sobre si se debe mostrar el boton [incluir inactivos] que incluye los registros que estan en estatus de inactivo
this.showFilterInactivos = true;
//Se recibe un arreglo de configuracion de boton de accion, permite saber que botons de acciones debe renderizar
this.actions = [];
this.resaltarSeleccion = false;
this.mostrarFiltroGeneralFechas = false;
this.showSelectAll = false;
// Aquí defines el mínimo de columnas requeridas
this.columnsReqMin = 3;
this.isReport = false;
this.parametros = new ParametrosGenerales();
this.extraParams = {}; // Objeto con parámetros dinámicos
//Nueva propiedad para centrar la tabla automáticamente
this.centerTable = true;
//Nueva propiedad para controlar la altura de la tabla
this.tableHeight = 'auto'; // Opción para forzar una altura específica si se necesita
//Propiedad para controlar la visibilidad del gran total
this.showGranTotal = false;
this.widthColumn = '20%';
//Outputs para emitir eventos al padre
this.onSelectAll = new EventEmitter();
this.cambiarEstatusEvent = new EventEmitter();
this.createEvent = new EventEmitter();
this.refreshEvent = new EventEmitter();
this.AddEvent = new EventEmitter();
this.onCreate = new EventEmitter();
this.enviarItemEvent = new EventEmitter();
this.filtroFechaGeneralEvent = new EventEmitter();
this.changeData = new EventEmitter();
this.filtroFechaReportEvent = new EventEmitter();
// Definir los rangos de fechas
this.predefinedRanges = [
{ label: 'Hoy', value: [new Date(), new Date()] },
{
label: 'Últimos 7 Días',
value: [
new Date(new Date().setDate(new Date().getDate() - 6)),
new Date(),
],
},
];
//Definir configuracion inicial para DateRangePicker
this.bsConfig = {
containerClass: 'theme-orange',
dateInputFormat: 'DD/MM/YYYY',
isAnimated: true,
};
this.applyFilter = (event, column) => {
this.parametros.filtrosPorColumna = {
...this.parametros.filtrosPorColumna,
[column]: event.target.value,
};
this.parametros['noPagina'] = 1;
this.parametros['tamanoPagina'] = 10;
this.loadData();
// Asegurar que el paginador se sincroniza
this.paginator.pageIndex = 0;
this.paginator.length = this.totalRecords;
};
this.applyFilterActives = (value) => {
this.parametros.activos = !value;
this.parametros['noPagina'] = 1;
this.parametros['tamanoPagina'] = 10;
this.loadData();
// Asegurar que el paginador se sincroniza
this.paginator.pageIndex = 0;
this.paginator.length = this.totalRecords;
};
this.updateData = () => {
this.isLoading = true;
this.loadData();
};
this.refreshTable = () => {
this.filterInputs.forEach((input) => {
input.nativeElement.value = '';
});
this.parametros.filtrosPorColumna = {};
this.parametros['noPagina'] = 1;
this.parametros['tamanoPagina'] = 10;
this.loadData();
// Asegurar que el paginador se sincroniza
this.paginator.pageIndex = 0;
this.paginator.length = this.totalRecords;
// Verificar overflow después de que los datos se actualizan
setTimeout(() => this.checkForHorizontalOverflow(), 300);
};
this.loadData = () => {
if (this.formularioModificado) {
this.originalData = [];
this.dataSource.data = [];
this.isLoading = true;
const params = this.configParams.configurar(this.parametros);
this.fetchDataFunction(params, this.extraParams).subscribe((data) => {
const items = data.items;
this.data = items;
this.totalRecords = data.totalRecords;
this.dataSource.data = this.data;
this.isLoading = false;
// Estilos predeterminados o configuraciones
this.styleRow = {
height: this.tableConfigs.heightRow,
};
// Inicializa originalData de forma segura
if (Array.isArray(this.data)) {
this.originalData = [...this.data];
}
else {
this.originalData = []; // O maneja como veas conveniente
}
if (this.isReport) {
this.bsConfigGeneral = {
containerClass: 'theme-orange',
dateInputFormat: 'DD/MM/YYYY',
isAnimated: true,
maxDate: this.maxDate,
};
}
// Recalcular dimensiones después de cargar datos
setTimeout(()