@jcbautistam/custom-table
Version:
Librería de tabla reutilizable con acciones, filtros, exportación y más.
1 lines • 181 kB
Source Map (JSON)
{"version":3,"file":"jcbautistam-custom-table.mjs","sources":["../../../projects/custom-table/src/lib/Models/ParametrosGenerales.ts","../../../projects/custom-table/src/lib/Services/DateTime.service.ts","../../../projects/custom-table/src/lib/Services/ExportTableExcel.service.ts","../../../projects/custom-table/src/lib/Services/loading.service.ts","../../../projects/custom-table/src/lib/Services/exportNotification.service.ts","../../../projects/custom-table/src/lib/Components/ColumnVisibility/column-visibility-modal2.component.ts","../../../projects/custom-table/src/lib/Components/ColumnVisibility/column-visibility-modal.component.html","../../../projects/custom-table/src/lib/Directives/ToUpperCase.directive.ts","../../../projects/custom-table/src/lib/Services/formatNumber.Service.ts","../../../projects/custom-table/src/lib/Directives/OnlyDigit.directive.ts","../../../projects/custom-table/src/lib/pipes/onlyDigit.pipe.ts","../../../projects/custom-table/src/lib/pipes/string-truncate.pipe.ts","../../../projects/custom-table/src/lib/pipes/image.pipe.ts","../../../projects/custom-table/src/lib/Components/CustomTable/custom-table.component.ts","../../../projects/custom-table/src/lib/Components/CustomTable/custom-table.component.html","../../../projects/custom-table/src/lib/pipes/booleanToText.pipe.ts","../../../projects/custom-table/src/lib/pipes/espaciado.pipe.ts","../../../projects/custom-table/src/lib/Directives/ValidateSpecialChars.Directive.ts","../../../projects/custom-table/src/lib/Directives/TimeInput.directive.ts","../../../projects/custom-table/src/lib/Directives/AutoSelectOption.directive.ts","../../../projects/custom-table/src/lib/Directives/FomartDateTime.directive.ts","../../../projects/custom-table/src/lib/Directives/ReadOnlySelector.directive.ts","../../../projects/custom-table/src/lib/Directives/PhoneNumber.directive.ts","../../../projects/custom-table/src/lib/Directives/MaxLength.directive.ts","../../../projects/custom-table/src/lib/Directives/OnlyDigitBase.directive.ts","../../../projects/custom-table/src/lib/Loaders/material-icons-loader.ts","../../../projects/custom-table/src/lib/custom-table.module.ts","../../../projects/custom-table/src/public-api.ts","../../../projects/custom-table/src/jcbautistam-custom-table.ts"],"sourcesContent":["import { HttpParams } from '@angular/common/http';\r\nimport { Injectable } from '@angular/core';\r\n\r\nexport interface IParametrosGenerales {\r\n ordenarPor?: string;\r\n descending?: boolean;\r\n noPagina?: number;\r\n tamanoPagina?: number;\r\n activos?: boolean;\r\n filtrosIniciales?: Record<string, string>;\r\n filtrosPorColumna?: Record<string, string>;\r\n multiIds?: string;\r\n actionMulti?: string;\r\n idCompania?: number;\r\n}\r\n\r\nexport class ParametrosGenerales implements IParametrosGenerales {\r\n ordenarPor: string = 'FechaCreacion';\r\n descending: boolean = false;\r\n noPagina: number = 1;\r\n tamanoPagina: number = 10;\r\n activos: boolean = true;\r\n filtrosIniciales: Record<string, string> = {};\r\n filtrosPorColumna: Record<string, string> = {};\r\n\r\n rangoFechas: string = '';\r\n\r\n multiIds: string = '';\r\n actionMulti: string = '';\r\n idCompania: number = 0;\r\n\r\n constructor(init?: Partial<IParametrosGenerales>) {\r\n if (init) {\r\n Object.assign(this, init);\r\n }\r\n }\r\n}\r\n\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\nexport class ConfiguracionParametros {\r\n configurar(\r\n parametros: ParametrosGenerales,\r\n extraParams?: Record<string, any>\r\n ): HttpParams {\r\n let params = new HttpParams();\r\n parametros.idCompania = Number(localStorage.getItem('CompaniaSelect')) || 0;\r\n\r\n // Agregar valores básicos del objeto de parámetros\r\n Object.entries(parametros).forEach(([key, value]) => {\r\n if (value !== undefined && value !== null) {\r\n params = params.set(key, value.toString());\r\n }\r\n });\r\n\r\n // Agregar filtros iniciales\r\n params = this.agregarFiltros(\r\n params,\r\n 'filtrosIniciales',\r\n parametros.filtrosIniciales\r\n );\r\n\r\n // Agregar filtros por columna\r\n params = this.agregarFiltros(\r\n params,\r\n 'filtrosPorColumna',\r\n parametros.filtrosPorColumna\r\n );\r\n\r\n // Agregar parámetros adicionales\r\n if (extraParams) {\r\n Object.entries(extraParams).forEach(([key, value]) => {\r\n if (value !== undefined && value !== null) {\r\n params = params.set(key, value.toString());\r\n }\r\n });\r\n }\r\n\r\n return params;\r\n }\r\n\r\n private agregarFiltros(\r\n params: HttpParams,\r\n prefix: string,\r\n filtros?: Record<string, string>\r\n ): HttpParams {\r\n // Añadir filtros si existen\r\n if (filtros) {\r\n Object.keys(filtros).forEach((key) => {\r\n const filtro = filtros![key];\r\n if (filtro) {\r\n params = params.set(`${prefix}[${key}]`, filtro);\r\n }\r\n });\r\n }\r\n\r\n return params;\r\n }\r\n}\r\n","import { Injectable } from '@angular/core';\r\nimport { formatInTimeZone, toDate } from 'date-fns-tz';\r\nimport { formatDate } from '@angular/common';\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\nexport class DateTimeService {\r\n // Obtener la zona horaria del dispositivo del usuario\r\n userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;\r\n\r\n constructor() {}\r\n\r\n toTimeZone(date: Date = new Date()): string {\r\n // Formatear la fecha en la zona horaria del usuario y en formato ISO 8601\r\n const isoString = formatInTimeZone(\r\n date,\r\n this.userTimeZone,\r\n \"yyyy-MM-dd'T'HH:mm:ssXXX\"\r\n );\r\n return isoString;\r\n }\r\n\r\n toUTCDate(dateInput: Date = new Date()) {\r\n const date = new Date(dateInput);\r\n // Obtener los componentes de la fecha\r\n const dayName = date.toLocaleString('en-US', { weekday: 'short' });\r\n const monthName = date.toLocaleString('en-US', { month: 'short' });\r\n const day = date.getDate();\r\n const year = date.getFullYear();\r\n const hours = date.getHours().toString().padStart(2, '0');\r\n const minutes = date.getMinutes().toString().padStart(2, '0');\r\n const seconds = date.getSeconds().toString().padStart(2, '0');\r\n\r\n // Obtener el offset de la zona horaria en horas y minutos\r\n const timezoneOffset = -date.getTimezoneOffset();\r\n const timezoneHours = Math.floor(timezoneOffset / 60)\r\n .toString()\r\n .padStart(2, '0');\r\n const timezoneMinutes = (timezoneOffset % 60).toString().padStart(2, '0');\r\n const timezoneSign = timezoneOffset >= 0 ? '+' : '-';\r\n\r\n return new Date(\r\n `${dayName} ${monthName} ${day} ${year} ${hours}:${minutes}:${seconds} GMT${timezoneSign}${timezoneHours}${timezoneMinutes}`\r\n );\r\n }\r\n toTime(date: Date | null = new Date()): string {\r\n if (!date) {\r\n date = new Date();\r\n }\r\n // Formatear la fecha en la zona horaria del usuario y en formato ISO 8601\r\n const isoString = formatInTimeZone(date, this.userTimeZone, 'HH:mm:ssXXX');\r\n return isoString;\r\n }\r\n\r\n stringToDate(fechaString: string): Date {\r\n const convertToDate = toDate(fechaString, { timeZone: this.userTimeZone });\r\n return convertToDate;\r\n }\r\n\r\n getDateTimeNow() {\r\n let date = new Date();\r\n const year = date.getFullYear();\r\n const month = String(date.getMonth() + 1).padStart(2, '0'); // Los meses son de 0 a 11\r\n const day = String(date.getDate()).padStart(2, '0');\r\n const hours = String(date.getHours()).padStart(2, '0');\r\n const minutes = String(date.getMinutes()).padStart(2, '0');\r\n const seconds = String(date.getSeconds()).padStart(2, '0');\r\n\r\n return new Date(`${year}-${month}-${day}T${hours}:${minutes}:${seconds}`);\r\n }\r\n\r\n getformatDate(date: Date = new Date(), format:string = 'dd/MM/yyyy'): string{\r\n\r\n return formatDate(date , format,'en-US')\r\n }\r\n\r\n getTextDateLiq(fecha: Date): string {\r\n const opciones: Intl.DateTimeFormatOptions = {\r\n day: 'numeric', // Día del mes\r\n month: 'long', // Nombre completo del mes\r\n year: 'numeric', // Año\r\n };\r\n\r\n const formateador = new Intl.DateTimeFormat('es-ES', opciones);\r\n return formateador.format(new Date(fecha)).replace(',', '').toUpperCase();\r\n }\r\n\r\n combinarFechaYHora(fecha: string, hora: string): string {\r\n // Crea un objeto de fecha a partir de la fecha ingresada\r\n const fechaObj = new Date(fecha);\r\n const [horas, minutos] = hora.split(':').map(Number);\r\n\r\n // Ajusta la hora y los minutos\r\n fechaObj.setHours(horas, minutos, 0);\r\n\r\n // Construye la cadena en formato 'YYYY-MM-DDTHH:mm:ss'\r\n const año = fechaObj.getFullYear();\r\n const mes = String(fechaObj.getMonth() + 1).padStart(2, '0'); // Los meses van de 0 a 11\r\n const día = String(fechaObj.getDate()).padStart(2, '0');\r\n const horasLocal = String(fechaObj.getHours()).padStart(2, '0');\r\n const minutosLocal = String(fechaObj.getMinutes()).padStart(2, '0');\r\n const segundosLocal = String(fechaObj.getSeconds()).padStart(2, '0');\r\n\r\n // Devuelve la fecha combinada\r\n return `${año}-${mes}-${día}T${horasLocal}:${minutosLocal}:${segundosLocal}`;\r\n }\r\n}\r\n","import { Injectable } from '@angular/core';\r\nimport * as XLSX from 'xlsx';\r\nimport { DateTimeService } from './DateTime.service';\r\n\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\nexport class ExportTableExcelService {\r\n constructor(private dateTimeService: DateTimeService) {}\r\n\r\n exportToExcel(tableId: string, name?: string, dataList?: any[], columnsDate?: string[]) {\r\n try {\r\n let timeSpan = this.dateTimeService.getformatDate(new Date(), 'yyyyMMdd');\r\n let fileName = `${name || 'Export'}_${timeSpan}.xlsx`;\r\n\r\n if (dataList && dataList.length > 0) {\r\n // Convertir valores booleanos de la columna \"activo\" a \"activo\" o \"inactivo\"\r\n const modifiedDataList = dataList.map(item => {\r\n if (item.hasOwnProperty('Activo')) {\r\n console.log('column', item);\r\n return { ...item, Activo: item.Activo ? 'activo' : 'inactivo' };\r\n }\r\n return item;\r\n });\r\n\r\n const workSheet = XLSX.utils.json_to_sheet(modifiedDataList);\r\n\r\n // Ajustar el ancho de las columnas\r\n const columnWidths = modifiedDataList[0] ? Object.keys(modifiedDataList[0]).map(() => ({ wpx: 100 })) : [];\r\n workSheet['!cols'] = columnWidths;\r\n\r\n const workBook: XLSX.WorkBook = XLSX.utils.book_new();\r\n XLSX.utils.book_append_sheet(\r\n workBook,\r\n workSheet,\r\n name?.substring(0, 31) || 'Sheet1'\r\n );\r\n XLSX.writeFile(workBook, fileName);\r\n console.log(`Archivo ${fileName} generado exitosamente.`);\r\n } else {\r\n throw new Error('No hay datos para exportar.');\r\n }\r\n } catch (error) {\r\n console.error('Error al generar el archivo Excel:', error);\r\n }\r\n }\r\n}\r\n","import { Injectable } from '@angular/core';\r\nimport { NgxSpinnerService } from 'ngx-spinner';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class LoadingService {\r\n\r\n constructor(private spinner: NgxSpinnerService) {}\r\n\r\n // Método para abrir el spinner\r\n open(): void {\r\n this.spinner.show(); \r\n }\r\n\r\n // Método para cerrar el spinner\r\n close(): void {\r\n this.spinner.hide();\r\n }\r\n\r\n // Método para abrir y cerrar el spinner después de un tiempo\r\n delay(seconds: number): void {\r\n this.open();\r\n setTimeout(() => {\r\n this.close();\r\n }, seconds * 1000);\r\n }\r\n}\r\n","import { Injectable } from '@angular/core';\r\nimport { BehaviorSubject, Observable, interval, Subscription } from 'rxjs';\r\nimport { take, map } from 'rxjs/operators';\r\n\r\nexport interface ExportJob {\r\n id: string;\r\n fileName: string;\r\n progress: number;\r\n isCompleted: boolean;\r\n error: string | null;\r\n timestamp: number;\r\n progressAnimation?: Subscription | null;\r\n}\r\n\r\nexport interface ExportStatus {\r\n isExporting: boolean;\r\n activeJobs: ExportJob[];\r\n showNotification: boolean;\r\n}\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class ExportNotificationService {\r\n private defaultStatus: ExportStatus = {\r\n isExporting: false,\r\n activeJobs: [],\r\n showNotification: false\r\n };\r\n\r\n private exportStatusSubject = new BehaviorSubject<ExportStatus>(this.defaultStatus);\r\n public exportStatus$: Observable<ExportStatus> = this.exportStatusSubject.asObservable();\r\n\r\n constructor() { }\r\n\r\n /**\r\n * Inicia una nueva exportación y retorna su ID\r\n */\r\n startExport(fileName: string): string {\r\n const jobId = this.generateJobId();\r\n const currentStatus = this.exportStatusSubject.value;\r\n \r\n const newJob: ExportJob = {\r\n id: jobId,\r\n fileName,\r\n progress: 0,\r\n isCompleted: false,\r\n error: null,\r\n timestamp: Date.now(),\r\n progressAnimation: null\r\n };\r\n \r\n // Añadir el nuevo trabajo a la lista de trabajos activos\r\n const updatedJobs = [...currentStatus.activeJobs, newJob];\r\n \r\n this.exportStatusSubject.next({\r\n isExporting: true,\r\n activeJobs: updatedJobs,\r\n showNotification: true\r\n });\r\n \r\n // Iniciar la animación de progreso hasta el 10%\r\n this.animateProgressTo(jobId, 0, 10, 800);\r\n \r\n return jobId;\r\n }\r\n\r\n /**\r\n * Actualiza el progreso de una exportación específica\r\n */\r\n updateProgress(jobId: string, targetProgress: number): void {\r\n const currentStatus = this.exportStatusSubject.value;\r\n const job = currentStatus.activeJobs.find(j => j.id === jobId);\r\n \r\n if (job) {\r\n // Detener cualquier animación previa\r\n this.stopAnimation(job);\r\n \r\n // Animar desde el progreso actual hasta el objetivo\r\n const currentProgress = job.progress;\r\n this.animateProgressTo(jobId, currentProgress, targetProgress, 1000);\r\n }\r\n }\r\n\r\n /**\r\n * Detiene de forma segura una animación\r\n */\r\n private stopAnimation(job: ExportJob): void {\r\n if (job && job.progressAnimation) {\r\n try {\r\n job.progressAnimation.unsubscribe();\r\n job.progressAnimation = null;\r\n } catch (error) {\r\n console.error('Error al detener animación:', error);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Anima el progreso de una forma más realista\r\n */\r\n private animateProgressTo(jobId: string, fromProgress: number, toProgress: number, duration: number): void {\r\n // Obtener estado actual\r\n const currentStatus = this.exportStatusSubject.value;\r\n const jobIndex = currentStatus.activeJobs.findIndex(j => j.id === jobId);\r\n \r\n if (jobIndex === -1) return;\r\n \r\n const job = currentStatus.activeJobs[jobIndex];\r\n \r\n // Detener animación previa si existe\r\n this.stopAnimation(job);\r\n \r\n // Calcular cuántos pasos necesitamos\r\n const steps = Math.max(Math.ceil(duration / 50), 5); // Al menos 5 pasos\r\n const stepValue = (toProgress - fromProgress) / steps;\r\n \r\n // Crear una animación con interval\r\n const animation = interval(duration / steps).pipe(\r\n take(steps),\r\n map(step => fromProgress + stepValue * (step + 1))\r\n ).subscribe({\r\n next: (progress) => {\r\n // Obtener el estado actual\r\n const updatedStatus = this.exportStatusSubject.value;\r\n const currentJobIndex = updatedStatus.activeJobs.findIndex(j => j.id === jobId);\r\n \r\n if (currentJobIndex !== -1) {\r\n // Crear una copia de los trabajos\r\n const updatedJobs = [...updatedStatus.activeJobs];\r\n \r\n // Actualizar el progreso del trabajo específico\r\n updatedJobs[currentJobIndex] = {\r\n ...updatedJobs[currentJobIndex],\r\n progress: progress\r\n };\r\n \r\n // Actualizar el estado\r\n this.exportStatusSubject.next({\r\n ...updatedStatus,\r\n activeJobs: updatedJobs\r\n });\r\n \r\n // Limpiar la suscripción cuando llegamos al último paso\r\n if (progress >= toProgress - stepValue/2) {\r\n this.stopAnimation(updatedJobs[currentJobIndex]);\r\n }\r\n }\r\n },\r\n error: (err) => {\r\n console.error('Error en animación:', err);\r\n },\r\n complete: () => {\r\n // Completar la animación\r\n }\r\n });\r\n \r\n // Guardar la animación en el job\r\n const updatedJobs = [...currentStatus.activeJobs];\r\n updatedJobs[jobIndex] = {\r\n ...updatedJobs[jobIndex],\r\n progressAnimation: animation\r\n };\r\n \r\n this.exportStatusSubject.next({\r\n ...currentStatus,\r\n activeJobs: updatedJobs\r\n });\r\n }\r\n\r\n /**\r\n * Marca una exportación como completada\r\n */\r\n completeExport(jobId: string): void {\r\n const currentStatus = this.exportStatusSubject.value;\r\n const jobIndex = currentStatus.activeJobs.findIndex(j => j.id === jobId);\r\n \r\n if (jobIndex !== -1) {\r\n const job = currentStatus.activeJobs[jobIndex];\r\n \r\n // Detener cualquier animación en curso\r\n this.stopAnimation(job);\r\n \r\n // Animar al 100% para completar\r\n this.animateProgressTo(jobId, job.progress, 100, 500);\r\n \r\n // Actualizar el estado a completado\r\n const updatedJobs = currentStatus.activeJobs.map(job => {\r\n if (job.id === jobId) {\r\n return {\r\n ...job,\r\n isCompleted: true\r\n };\r\n }\r\n return job;\r\n });\r\n \r\n this.exportStatusSubject.next({\r\n ...currentStatus,\r\n activeJobs: updatedJobs,\r\n isExporting: updatedJobs.some(job => !job.isCompleted && !job.error)\r\n });\r\n \r\n // Programar la eliminación del trabajo completado después de cierto tiempo\r\n setTimeout(() => {\r\n this.removeJob(jobId);\r\n }, 5000);\r\n }\r\n }\r\n\r\n /**\r\n * Marca una exportación como fallida\r\n */\r\n errorExport(jobId: string, error: any): void {\r\n console.error('Error en la exportación:', error);\r\n \r\n const currentStatus = this.exportStatusSubject.value;\r\n const jobIndex = currentStatus.activeJobs.findIndex(j => j.id === jobId);\r\n \r\n if (jobIndex !== -1) {\r\n const job = currentStatus.activeJobs[jobIndex];\r\n \r\n // Detener cualquier animación en curso\r\n this.stopAnimation(job);\r\n \r\n const updatedJobs = currentStatus.activeJobs.map(job => {\r\n if (job.id === jobId) {\r\n return {\r\n ...job,\r\n error: error.message || 'Error al exportar',\r\n isCompleted: true\r\n };\r\n }\r\n return job;\r\n });\r\n \r\n this.exportStatusSubject.next({\r\n ...currentStatus,\r\n activeJobs: updatedJobs,\r\n isExporting: updatedJobs.some(job => !job.isCompleted && !job.error)\r\n });\r\n \r\n // Programar la eliminación del trabajo fallido después de cierto tiempo\r\n setTimeout(() => {\r\n this.removeJob(jobId);\r\n }, 5000);\r\n }\r\n }\r\n\r\n /**\r\n * Elimina un trabajo de la lista de trabajos activos\r\n */\r\n private removeJob(jobId: string): void {\r\n const currentStatus = this.exportStatusSubject.value;\r\n const jobIndex = currentStatus.activeJobs.findIndex(j => j.id === jobId);\r\n \r\n if (jobIndex !== -1) {\r\n const job = currentStatus.activeJobs[jobIndex];\r\n \r\n // Detener cualquier animación en curso\r\n this.stopAnimation(job);\r\n \r\n const updatedJobs = currentStatus.activeJobs.filter(job => job.id !== jobId);\r\n \r\n if (updatedJobs.length === 0) {\r\n // Si no hay más trabajos, ocultar la notificación\r\n this.exportStatusSubject.next({\r\n isExporting: false,\r\n activeJobs: [],\r\n showNotification: false\r\n });\r\n } else {\r\n // Si aún hay trabajos, actualizar la lista\r\n this.exportStatusSubject.next({\r\n ...currentStatus,\r\n activeJobs: updatedJobs,\r\n isExporting: updatedJobs.some(job => !job.isCompleted && !job.error)\r\n });\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Oculta la notificación manualmente\r\n */\r\n hideNotification(): void {\r\n const currentStatus = this.exportStatusSubject.value;\r\n \r\n // Detener todas las animaciones en curso\r\n currentStatus.activeJobs.forEach(job => {\r\n this.stopAnimation(job);\r\n });\r\n \r\n this.exportStatusSubject.next({\r\n ...currentStatus,\r\n showNotification: false\r\n });\r\n }\r\n\r\n /**\r\n * Muestra la notificación si estaba oculta\r\n */\r\n showNotification(): void {\r\n const currentStatus = this.exportStatusSubject.value;\r\n if (currentStatus.activeJobs.length > 0) {\r\n this.exportStatusSubject.next({\r\n ...currentStatus,\r\n showNotification: true\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Genera un ID único para cada trabajo\r\n */\r\n private generateJobId(): string {\r\n return 'export-' + Date.now() + '-' + Math.floor(Math.random() * 1000);\r\n }\r\n} ","//column-visibility-modal.component.ts\r\n\r\nimport { Component, Inject } from '@angular/core';\r\nimport { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';\r\n\r\n@Component({\r\n \r\n selector: 'app-column-visibility-modal',\r\n templateUrl: './column-visibility-modal.component.html',\r\n styleUrls: ['./column-visibility-modal.component.css']\r\n})\r\nexport class ColumnVisibilityModal2Component {\r\n columns: any[];\r\n columnsReqMin: number;\r\n\r\n constructor(\r\n public dialogRef: MatDialogRef<ColumnVisibilityModal2Component>,\r\n @Inject(MAT_DIALOG_DATA) public data: { columns: any[], columnsReqMin: number }\r\n ) {\r\n this.columns = JSON.parse(JSON.stringify(data.columns)); // Clonamos para evitar modificar la referencia original\r\n this.columnsReqMin = data.columnsReqMin; // Recibimos el mínimo de columnas desde el componente padre\r\n }\r\n\r\n // Cuenta cuántas columnas están seleccionadas\r\n getSelectedCount(): number {\r\n return this.columns.filter(col => col.visible).length;\r\n }\r\n\r\n // Devuelve `true` si el checkbox debe estar deshabilitado\r\n isCheckboxDisabled(column: any): boolean {\r\n return this.getSelectedCount() <= this.columnsReqMin && column.visible;\r\n } \r\n}\r\n\r\n","<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","import { Directive, HostListener } from '@angular/core';\r\nimport { NgControl } from '@angular/forms';\r\n\r\n@Directive({\r\n selector: '[toUpperCase]'\r\n})\r\nexport class ToUpperCaseDirective {\r\n\r\n constructor(private ngControl: NgControl) { }\r\n\r\n @HostListener('input', ['$event.target.value']) onInputChange(value: string) {\r\n this.ngControl?.control?.setValue(value.toUpperCase(), { emitEvent: false });\r\n }\r\n\r\n // @HostListener('input', ['$event']) onInput(event: Event) {\r\n // const input = event.target as HTMLInputElement;\r\n // const valorMayusculas = input.value.toUpperCase();\r\n // input.value = valorMayusculas; // Convierte el valor del input a mayúsculas\r\n // }\r\n}","import { Injectable } from '@angular/core';\r\n\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\nexport class FormatNumberService {\r\n constructor() {}\r\n\r\n formatNumber(value: any, char?: string) {\r\n\r\n if (value == null || value == undefined ) \r\n return;\r\n\r\n // Convertir a string si es un número\r\n if (typeof value === 'number') {\r\n value = value.toString();\r\n }\r\n\r\n value = value.replace(/[^0-9.-]*/g, '');\r\n const parts = value.split('.');\r\n\r\n //Agrega una coma cada 3 cifras\r\n parts[0] = parts[0].replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\r\n value = parts.join('.');\r\n\r\n //Solo acepta un punto decimal\r\n if (parts.length > 2) {\r\n value = parts[0] + '.' + parts.slice(1).join('');\r\n }\r\n\r\n // Solo acepta dos decimales y completa con ceros si es necesario\r\n if (parts.length > 1) {\r\n let decimalPart = parts[1].slice(0, 4);\r\n // if (decimalPart.length < 4) {\r\n // decimalPart = decimalPart.padEnd(4, '0');\r\n // }\r\n value = `${parts[0]}.${decimalPart}`;\r\n } \r\n // else {\r\n // value = `${parts[0]}.0000`;\r\n // }\r\n\r\n if(char != null)\r\n value = char + value;\r\n\r\n return value;\r\n }\r\n\r\n convertToNumber(value: any): number {\r\n if (value == null || value === '0') return 0;\r\n\r\n if (typeof value === 'number') {\r\n return isNaN(value) ? 0 : value;\r\n }\r\n\r\n value = value.replace(/[^0-9.]*/g, '');\r\n const numberValue = Number(value);\r\n\r\n return isNaN(numberValue) ? 0 : numberValue;\r\n }\r\n\r\n // convertToNumber(value: any): number {\r\n // console.log('convertToNumber',value);\r\n // if (value == null || value == '0') return 0;\r\n\r\n // if (typeof value === 'number')\r\n // return value;\r\n \r\n // value = value.replace(/[^0-9.-]*/g, '');\r\n\r\n // return Number(value);\r\n // }\r\n\r\n textNumber(num: number) {\r\n\r\n console.log('textNumber', num);\r\n let numWord: string = '';\r\n let numUnidad: number = 0;\r\n let numResult: number = num;\r\n let and: string = ' ';\r\n\r\n const integerPart = Math.floor(num);\r\n const decimalPart = Math.round((num - integerPart) * 100);\r\n\r\n const decimalPartInWords = decimalPart.toString().padStart(2, '0');\r\n\r\n numUnidad = Math.floor(numResult / 1000);\r\n if (numUnidad > 0) {\r\n if (numUnidad > 1)\r\n numWord = numWord + this.numberToWords(numUnidad) + ' MIL';\r\n else numWord = 'MIL ';\r\n numResult = numResult - numUnidad * 1000;\r\n }\r\n\r\n numUnidad = Math.floor(numResult / 100) * 100;\r\n if (numUnidad > 0) {\r\n numWord = numWord + ' ' + this.numberToWords(numUnidad);\r\n numResult = numResult - numUnidad;\r\n }\r\n\r\n numUnidad = Math.floor(numResult / 10) * 10;\r\n if (numUnidad > 0) {\r\n numWord = numWord + ' ' + this.numberToWords(numUnidad);\r\n numResult = numResult - numUnidad;\r\n and = ' Y ';\r\n }\r\n\r\n numUnidad = Math.floor(numResult);\r\n if (numUnidad > 0) {\r\n numWord = numWord + and + this.numberToWords(numUnidad);\r\n numResult = numResult - numUnidad;\r\n }\r\n return `${numWord} ${decimalPartInWords}/100 M.N.`;\r\n }\r\n\r\n numberToWords(num: number): string {\r\n const units = [\r\n '',\r\n 'UNO',\r\n 'DOS',\r\n 'TRES',\r\n 'CUATRO',\r\n 'CINCO',\r\n 'SEIS',\r\n 'SIETE',\r\n 'OCHO',\r\n 'NUEVE',\r\n ];\r\n const tens = [\r\n '',\r\n 'DIEZ',\r\n 'VEINTE',\r\n 'TREINTA',\r\n 'CUARENTA',\r\n 'CINCUENTA',\r\n 'SESENTA',\r\n 'SETENTA',\r\n 'OCHENTA',\r\n 'NOVENTA',\r\n ];\r\n const teens = [\r\n 'ONCE',\r\n 'DOCE',\r\n 'TRECE',\r\n 'CATORCE',\r\n 'QUINCE',\r\n 'DIECISEIS',\r\n 'DIECISIETE',\r\n 'DIECIOCHO',\r\n 'DIECINUEVE',\r\n ];\r\n const hundreds = [\r\n '',\r\n 'CIENTO',\r\n 'DOSCIENTOS',\r\n 'TRESCIENTOS',\r\n 'CUATROCIENTOS',\r\n 'QUINIENTOS',\r\n 'SEISCIENTOS',\r\n 'SETECIENTOS',\r\n 'OCHOCIENTOS',\r\n 'NOVECIENTOS',\r\n ];\r\n\r\n if (num === 0) return 'CERO';\r\n\r\n if (num === 100) return 'CIEN';\r\n\r\n let word = '';\r\n\r\n if (Math.floor(num / 100) > 0) {\r\n word += hundreds[Math.floor(num / 100)] + ' ';\r\n }\r\n\r\n let remainder = num % 100;\r\n\r\n if (remainder >= 11 && remainder <= 19) {\r\n word += teens[remainder - 11];\r\n } else {\r\n if (Math.floor(remainder / 10) > 0) {\r\n word += tens[Math.floor(remainder / 10)] + ' ';\r\n }\r\n remainder = remainder % 10;\r\n if (remainder > 0) {\r\n word += units[remainder];\r\n }\r\n }\r\n\r\n return word.trim();\r\n }\r\n}\r\n","import {\r\n Directive,\r\n ElementRef,\r\n HostListener,\r\n Input,\r\n OnInit,\r\n OnChanges,\r\n SimpleChanges,\r\n forwardRef,\r\n Renderer2\r\n} from '@angular/core';\r\nimport { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';\r\nimport { FormatNumberService } from '../Services/formatNumber.Service';\r\n\r\n@Directive({\r\n selector: '[onlyDigit]',\r\n providers: [\r\n {\r\n provide: NG_VALUE_ACCESSOR,\r\n useExisting: forwardRef(() => OnlyDigitDirective),\r\n multi: true\r\n }\r\n ]\r\n})\r\nexport class OnlyDigitDirective implements OnInit, OnChanges, ControlValueAccessor {\r\n private _format: string = '';\r\n private _showZero: boolean = true;\r\n\r\n @Input('onlyDigit')\r\n set format(value: string) {\r\n this._format = value?.toLowerCase() || '';\r\n }\r\n\r\n get format(): string {\r\n return this._format;\r\n }\r\n\r\n @Input()\r\n set showZero(value: string) {\r\n this._showZero = (value == 'true') ? true : false;\r\n }\r\n\r\n get showZero(): boolean {\r\n return this._showZero;\r\n }\r\n\r\n private onChange: (value: any) => void = () => {};\r\n private onTouched: () => void = () => {};\r\n\r\n constructor(private el: ElementRef, private renderer: Renderer2, private formatNumberService: FormatNumberService) {\r\n this.renderer.addClass(this.el.nativeElement, 'text-end');\r\n }\r\n\r\n ngOnInit() {\r\n this.writeValue(this.el.nativeElement.value); // Establecer el valor inicial con el formato\r\n }\r\n\r\n ngOnChanges(changes: SimpleChanges) {\r\n\r\n if (changes['format']) {\r\n this.writeValue(this.el.nativeElement.value); // Actualizar el valor con el nuevo formato\r\n }\r\n }\r\n\r\n writeValue(value: any): void {\r\n if (value == null || isNaN(value) || value === \"\") return;\r\n\r\n let formattedValue = value;\r\n \r\n if (this.format != 'id') {\r\n \r\n formattedValue = this.formatNumberService.formatNumber(value, this.format);\r\n } \r\n\r\n this.renderer.setProperty(this.el.nativeElement, 'value', formattedValue); // Asignar el valor formateado al campo\r\n }\r\n\r\n registerOnChange(fn: any): void {\r\n this.onChange = fn;\r\n }\r\n\r\n registerOnTouched(fn: any): void {\r\n this.onTouched = fn;\r\n }\r\n\r\n setDisabledState?(isDisabled: boolean): void {\r\n this.renderer.setProperty(this.el.nativeElement, 'disabled', isDisabled);\r\n }\r\n\r\n @HostListener('input', ['$event.target.value'])\r\n onInput(value: string) {\r\n\r\n if (value == null) return;\r\n\r\n let formattedValue = value;\r\n\r\n if (this.format != 'id') {\r\n formattedValue = this.formatNumberService.formatNumber(formattedValue, this.format);\r\n }else{\r\n formattedValue = formattedValue.replace(/[^0-9-]*/g, '');\r\n }\r\n\r\n this.renderer.setProperty(this.el.nativeElement, 'value', formattedValue);\r\n\r\n if (this.onChange) {\r\n const numericValue = parseFloat(formattedValue.replace(/[^0-9.-]*/g, ''));\r\n this.onChange(isNaN(numericValue) ? null : numericValue);\r\n }\r\n }\r\n\r\n @HostListener('blur')\r\n onBlur() {\r\n if (this.onTouched) {\r\n this.onTouched();\r\n }\r\n }\r\n\r\n}","import { Pipe, PipeTransform } from '@angular/core';\r\nimport { FormatNumberService } from '../Services/formatNumber.Service';\r\n\r\n@Pipe({\r\n name: 'onlyDigit',\r\n})\r\nexport class OnlyDigitPipe implements PipeTransform {\r\n\r\n constructor(private numServ: FormatNumberService) {}\r\n\r\n transform(value: number, char?: string): string {\r\n \r\n char = char ?? '';\r\n let stringValue = '';\r\n if (value == null || value == undefined) value = 0;\r\n\r\n // Convertir a string si es un número\r\n if (typeof value === 'number') {\r\n stringValue = value.toString();\r\n }\r\n\r\n if (char.toLowerCase() != 'id') {\r\n if (!stringValue || stringValue === '') {\r\n stringValue = '0';\r\n } else if (char == '%') {\r\n stringValue = stringValue + char;\r\n } else {\r\n\r\n stringValue = this.numServ.formatNumber(stringValue, char);\r\n \r\n }\r\n }\r\n\r\n return stringValue;\r\n }\r\n}\r\n","import { Pipe, PipeTransform } from '@angular/core';\r\n\r\n@Pipe({\r\n name: 'truncate'\r\n})\r\nexport class StringTruncatePipe implements PipeTransform {\r\n transform(value: string, limit: number = 25, completeWords: boolean = false, ellipsis: string = '...'): string {\r\n if (completeWords) {\r\n limit = value.substr(0, limit).lastIndexOf(' ');\r\n }\r\n\r\n if (value == null) \r\n return '';\r\n\r\n return value.length > limit ? value.substr(0, limit) + ellipsis : value;\r\n }\r\n}","import { Pipe, PipeTransform } from '@angular/core';\r\n\r\n@Pipe({\r\n name: 'image'\r\n})\r\nexport class ImagePipe implements PipeTransform {\r\n\r\n transform(imageHex: string): unknown {\r\n if (imageHex && imageHex.trim() !== '') {\r\n try {\r\n const cleanHex = imageHex.startsWith('0x') ? imageHex.slice(2) : imageHex;\r\n const hexPairs = cleanHex.match(/[\\dA-F]{2}/gi) || [];\r\n const bytes = hexPairs.map(pair => parseInt(pair, 16));\r\n const byteArray = new Uint8Array(bytes);\r\n const blob = new Blob([byteArray], { type: 'image/jpeg' });\r\n return URL.createObjectURL(blob);\r\n } catch (error) {\r\n console.error('Error al convertir hex a imagen:', error);\r\n return '';\r\n }\r\n }\r\n return '';\r\n }\r\n\r\n}\r\n","import {\r\n Component, Input, Output, OnInit, OnChanges, SimpleChanges, EventEmitter, ViewChild,\r\n AfterViewInit, ElementRef, QueryList, ViewChildren, OnDestroy,\r\n ViewEncapsulation\r\n} from '@angular/core';\r\nimport { BsDatepickerConfig, BsDaterangepickerConfig } from 'ngx-bootstrap/datepicker';\r\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\r\nimport { MatTableDataSource } from '@angular/material/table';\r\nimport { MatDialog } from '@angular/material/dialog';\r\nimport { Observable } from 'rxjs';\r\nimport { HttpParams } from '@angular/common/http';\r\nimport { fromEvent } from 'rxjs';\r\nimport { debounceTime, takeUntil } from 'rxjs/operators';\r\nimport { Subscription } from 'rxjs';\r\nimport { TableAction } from '../../Interfaces/TableAction';\r\nimport { ConfiguracionParametros, ParametrosGenerales } from '../../Models/ParametrosGenerales';\r\nimport { ExportTableExcelService } from '../../Services/ExportTableExcel.service';\r\nimport { LoadingService } from '../../Services/loading.service';\r\nimport { ExportNotificationService } from '../../Services/exportNotification.service';\r\nimport { ColumnConfig } from '../../Interfaces/ColumnConfig';\r\nimport { ColumnVisibilityModal2Component } from '../ColumnVisibility/column-visibility-modal2.component';\r\n\r\n\r\n@Component({\r\n selector: 'custom-table',\r\n templateUrl: './custom-table.component.html',\r\n styleUrls: ['./custom-table.component.css'],\r\n encapsulation: ViewEncapsulation.None \r\n})\r\n\r\nexport class CustomTableComponent\r\n implements OnInit, AfterViewInit, OnDestroy, OnChanges {\r\n [x: string]: any;\r\n\r\n // Objeto para manejar la cancelación de suscripciones de Observable\r\n private destroy$ = new EventEmitter<void>();\r\n\r\n // Variables para el control de la exportación\r\n private exportSubscription: Subscription | null = null;\r\n isExportInProgress: boolean = false;\r\n bsConfigGeneral: Partial<BsDatepickerConfig> | undefined;\r\n maxDate: Date = new Date(); // Fecha máxima hoy\r\n minDate: Date = new Date(); // Fecha mínima 30 días atrás\r\n rowSeleccionada: any;\r\n totalRecords: number = 0;\r\n formularioModificado: boolean = false;\r\n isSelectAll: boolean = false; \r\n originalData: any[] = []; // Una copia de los datos originales para restablecer después del filtrado\r\n dataActives: any[] = [];\r\n\r\n // Esto almacenará el filtro actual para cada columna.\r\n filters: { [key: string]: string | boolean | null } = {};\r\n\r\n dataSource: MatTableDataSource<any> = new MatTableDataSource<any>();\r\n displayedColumns: string[] = [];\r\n public styleRow: { [clave: string]: string } = {};\r\n public isToggleChecked: boolean = true;\r\n\r\n @ViewChildren('filterInput') filterInputs!: QueryList<ElementRef>;\r\n @ViewChild(MatPaginator) paginator!: MatPaginator;\r\n @ViewChild('matTableWrapper') matTableWrapper!: ElementRef;\r\n\r\n get columnKeys() {\r\n return Object.keys(this.columnConfigs);\r\n }\r\n\r\n //Se reciben los datos que mostrará la tabla\r\n @Input() data: any[] = [];\r\n //Se reciben los datos que mostrará en campo editable select\r\n // @Input() dataSelect: any[] = [];\r\n @Input() dataSelect: any[][] = [];\r\n //Se reciben las columnas que debe renderizar la tabla\r\n /* @Input() columns: string[] = []; */\r\n //Se reciben las configuraciones de las columnas que se mostrarán\r\n @Input() columnConfigs: any;\r\n @Input() withHeader: boolean = true;\r\n //Se reciben configuraciones generales de la tabla como datos de paginacion y fontsize\r\n @Input() tableConfigs: any = {};\r\n //Se recibe el nombre del menu para aplicar el correcto permiso por usuario\r\n @Input() nombreMenu: string = '';\r\n //Se recibe el titulo de la tabla\r\n @Input() nombreTabla: string = '';\r\n //Se recibe la ruta a la que navegara cuando se use el boton [crear] propio de la tabla\r\n @Input() crearRoute: string = '';\r\n //Se recibe la indicacion sobre si se debe mostrar el boton [crear] normal\r\n @Input() showCreateButton = false;\r\n //Se recibe la indicacion sobre si se debe mostrar el boton [crear] normal\r\n @Input() showCreateButtonModal = false;\r\n //Se recibe la indicacion sobre si se debe mostrar el boton [exportar a excel] que descarga un archivo excel\r\n @Input() showExportarButton: boolean = true;\r\n //Se recibe el nombre con el que se debe guardar el archivo excel al exportar la tabla desde el boton [exportar a excel]\r\n @Input() excelFileName: string = '';\r\n //Indica si esta \"cargando\" datos, y asi manejar la visualizacion del indicador loader\r\n isLoading: boolean = false;\r\n //Se recibe la indicacion sobre si se debe mostrar el boton [crear] cuando es un catalogoGeneral\r\n @Input() showCreateButtonDetGral = false;\r\n //Se recibe la indicacion sobre si se debe mostrar el boton [refrescar datos] que refresca los datos de la tabla\r\n @Input() showRefreshButton: boolean = true;\r\n @Input() showAddButton: boolean = false;\r\n //Se recibe la indicacion sobre si se debe mostrar el boton [configuracion de columnas] que abre el modal para elegir que columnas visualizar\r\n @Input() showConfColumnsButton: boolean = true;\r\n //Se recibe la indicacion sobre si se debe mostrar el boton [incluir inactivos] que incluye los registros que estan en estatus de inactivo\r\n @Input() showFilterInactivos = true;\r\n //Se recibe un arreglo de configuracion de boton de accion, permite saber que botons de acciones debe renderizar\r\n @Input() actions: TableAction[] = [];\r\n @Input() resaltarSeleccion: boolean = false;\r\n @Input() mostrarFiltroGeneralFechas: boolean = false;\r\n @Input() showSelectAll: boolean = false;\r\n // Aquí defines el mínimo de columnas requeridas\r\n @Input() columnsReqMin: number = 3;\r\n @Input() isReport = false; \r\n @Input() fetchDataFunction!: ( params: HttpParams, extraParam?: any ) => Observable<any>; // extraParam es opcional\r\n @Input() parametros: ParametrosGenerales = new ParametrosGenerales();\r\n @Input() extraParams: { [key: string]: any } = {}; // Objeto con parámetros dinámicos\r\n //Nueva propiedad para centrar la tabla automáticamente\r\n @Input() centerTable: boolean = true;\r\n //Nueva propiedad para controlar la altura de la tabla\r\n @Input() tableHeight: string = 'auto'; // Opción para forzar una altura específica si se necesita\r\n //Propiedad para controlar la visibilidad del gran total\r\n @Input() showGranTotal: boolean = false;\r\n @Input() widthColumn: string = '20%';\r\n\r\n //Outputs para emitir eventos al padre\r\n @Output() onSelectAll: EventEmitter<void> = new EventEmitter();\r\n @Output() cambiarEstatusEvent = new EventEmitter<any>();\r\n @Output() createEvent = new EventEmitter<void>();\r\n @Output() refreshEvent = new EventEmitter<void>();\r\n @Output() AddEvent = new EventEmitter<void>();\r\n @Output() onCreate: EventEmitter<void> = new EventEmitter();\r\n @Output() enviarItemEvent = new EventEmitter<void>();\r\n @Output() filtroFechaGeneralEvent = new EventEmitter<(Date | undefined)[]>();\r\n @Output() changeData = new EventEmitter<any>();\r\n @Output() filtroFechaReportEvent = new EventEmitter<Date | undefined>();\r\n\r\n constructor(\r\n private excelService: ExportTableExcelService,\r\n public dialog: MatDialog,\r\n private configParams: ConfiguracionParametros,\r\n public loadingService: LoadingService,\r\n private exportNotificationService: ExportNotificationService\r\n ) {\r\n this.minDate.setDate(this.minDate.getDate() - 30);\r\n\r\n this.bsConfigGeneral = {\r\n minDate: this.minDate,\r\n maxDate: this.maxDate,\r\n containerClass: 'theme-orange',\r\n dateInputFormat: 'DD/MM/YYYY',\r\n isAnimated: true,\r\n };\r\n }\r\n\r\n // En tu componente o servicio\r\n getColumnConfigWithDefault(columnKey: string): ColumnConfig {\r\n const defaultConfig: ColumnConfig = {\r\n displayName: '', // Definir el nombre adecuado si es necesario\r\n type: 'default', // Puedes ajustar el tipo predeterminado según tus necesidades\r\n showFilter: true, // Opciones predeterminadas para otros atributos si es necesario\r\n visible: true,\r\n };\r\n\r\n const columnConfig = this.columnConfigs[columnKey];\r\n if (columnConfig) {\r\n // Combinar la configuración de columna existente con la configuración predeterminada\r\n return { ...defaultConfig, ...columnConfig };\r\n } else {\r\n // Si la configuración de columna no está definida, devolver la configuración predeterminada\r\n return defaultConfig;\r\n }\r\n }\r\n\r\n // Función para determinar si se debe bloquear el renglón\r\n shouldBlockRow(element: any): boolean {\r\n for (const columnKey of Object.keys(this.columnConfigs)) {\r\n const columnConfig = this.getColumnConfigWithDefault(columnKey);\r\n if (\r\n columnConfig.bloquearSeleccion &&\r\n columnConfig.bloquearSeleccion(element)\r\n ) {\r\n return true; // Se bloquea el renglón si se cumple la condición en alguna columna\r\n }\r\n }\r\n return false; // No se bloquea el renglón si no se encuentra ninguna condición de bloqueo\r\n }\r\n\r\n // Definir los rangos de fechas\r\n predefinedRanges: BsCustomDates[] = [\r\n { label: 'Hoy', value: [new Date(), new Date()] },\r\n {\r\n label: 'Últimos 7 Días',\r\n value: [\r\n new Date(new Date().setDate(new Date().getDate() - 6)),\r\n new Date(),\r\n ],\r\n },\r\n ];\r\n\r\n //Definir configuracion inicial para DateRangePicker\r\n bsConfig: Partial<BsDaterangepickerConfig> = {\r\n containerClass: 'theme-orange',\r\n dateInputFormat: 'DD/MM/YYYY',\r\n isAnimated: true,\r\n };\r\n\r\n //Abre modal para configurar que columnas configure para renderizar\r\n openColumnVisibilityDialog(): void {\r\n const columnsArray = Object.keys(this.columnConfigs)\r\n .filter((key) => key !== 'activo')\r\n .map((key) => ({\r\n key: key,\r\n ...this.columnConfigs[key],\r\n }));\r\n\r\n const dialogRef = this.dialog.open(ColumnVisibilityModal2Component, {\r\n width: '250px',\r\n data: { columns: columnsArray, columnsReqMin: this.columnsReqMin }, // Pasamos el mínimo de columnas\r\n });\r\n\r\n dialogRef.afterClosed().subscribe((result) => {\r\n if (result) {\r\n // Actualizar columnConfigs con los resultados\r\n result.forEach((column: { key: string | number; visible: any }) => {\r\n this.columnConfigs[column.key].visible = column.visible;\r\n });\r\n this.updateDisplayedColumns();\r\n }\r\n });\r\n }\r\n\r\n //Actualiza el arreglo de columnas a renderizar\r\n updateDisplayedColumns() {\r\n /* if (this.columnConfigs.activo !== undefined)\r\n this.columnConfigs.activo.visible = false; */\r\n\r\n const visibleColumns = Object.keys(this.columnConfigs).filter(\r\n (key) => this.columnConfigs[key].visible\r\n );\r\n\r\n if (this.actions.length === 0) {\r\n this.displayedColumns = [...visibleColumns];\r\n } else {\r\n this.displayedColumns = [...visibleColumns, 'acciones'];\r\n }\r\n }\r\n\r\n onValueChangeDateGeneral(value?: (Date | undefined)[]): void {\r\n if (\r\n value &&\r\n value.length === 2 &&\r\n value[0] instanceof Date &&\r\n value[1] instanceof Date\r\n ) {\r\n this.filtroFechaGeneralEvent.emit(value);\r\n }\r\n\r\n if (value && value[0] instanceof Date && value[1] instanceof Date) {\r\n this.parametros.rangoFechas =\r\n this.fechaToString(value[0]) + '-' + this.fechaToString(value[1]);\r\n }\r\n\r\n this.parametros['noPagina'] = 1;\r\n this.parametros['tamanoPagina'] = 10;\r\n\r\n this.loadData();\r\n\r\n // Asegurar que el paginador se sincroniza\r\n this.paginator.pageIndex = 0;\r\n this.paginator.length = this.totalRecords;\r\n }\r\n\r\n //Ejecuta al cambiar el valor del DataRangePicker\r\n onValueChangeDate(\r\n value?: (Date | undefined)[],\r\n column: string = 'fechaCreacion'\r\n ): void {\r\n if (value == undefined) {\r\n this.resetDateFilter(column);\r\n }\r\n\r\n console.log('fecha ingresasa: ', value, column);\r\n\r\n if (\r\n value &&\r\n value.length === 2 &&\r\n value[0] instanceof Date &&\r\n value[1] instanceof Date\r\n ) {\r\n this.applyDateFilter(value[0], value[1], column);\r\n } else {\r\n this.resetDateFilter(column);\r\n }\r\n }\r\n\r\n ngAfterViewInit() {\r\n this.formularioModificado = true;\r\n\r\n // Cargar datos iniciales\r\n this.loadData();\r\n\r\n // Ajustar el tamaño de la tabla al espacio disponible\r\n this.adjustTableHeight();\r\n\r\n // Observar cambios en el tamaño de la ventana\r\n fromEvent(window, 'resize')\r\n .pipe(debounceTime(200), takeUntil(this.destroy$))\r\n .subscribe(() => {\r\n this.checkForHorizontalOverflow();\r\n this.adjustTableHeight();\r\n });\r\n\r\n // Verificar si hay overflow horizontal\r\n this.checkForHorizontalOverflow();\r\n\r\n // Detectar cuando hay espacio vacío después del último registro\r\n this.checkEmptySpace();\r\n\r\n // Observar cambios en los datos para actualizar el estado de espacio vacío\r\n this.dataSource.connect().subscribe(() => {\r\n this.checkEmptySpace();\r\n });\r\n }\r\n\r\n ngOnInit() {\r\n this.updateDisplayedColumns();\r\n\r\n // Inicialización de filtros\r\n this.filters = {};\r\n for (const column of Object.keys(this.columnConfigs)) {\r\n this.filters[column] = '';\r\n }\r\n\r\n // Suscribirse al estado de exportación\r\n this.exportSubscription = this.exportNotificationService.exportStatus$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(status => {\r\n this.isExportInProgress = status.isExporting;\r\n });\r\n }\r\n\r\n ngOnChanges(changes: SimpleChanges) {\r\n if (\r\n (changes['extraParams'] && this.extraParams) ||\r\n (changes['parametros'] && this.parametros)\r\n ) {\r\n // Cuando itemDefault cambia, asignamos sus valores al form\r\n this.loadData();\r\n }\r\n }\r\n\r\n\r\n\r\n applyFilter = (event: any, column: string) => {\r\n this.parametros.filtrosPorColumna = {\r\n ...this.parametros.filtrosPorColumna,\r\n [column]: event.target.value,\r\n };\r\n\r\n this.parametros['noPagina'] = 1;\r\n this.parametros['tamanoPagina'] = 10;\r\n\r\n this.loadData();\r\n\r\n // Asegurar que el paginador se sincroniza\r\n this.paginator.pageIndex = 0;\r\n this.paginator.length = this.totalRecords;\r\n };\r\n\r\n columnMatches(row: any): boolean {\r\n for (const column of Object.keys(this.columnConfigs)) {\r\n const columnName = column;\r\n let filter = this.filters[columnName];\r\n if (filter === null) {\r\n contin