UNPKG

@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
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(()