UNPKG

ng-prime-tools

Version:

An advanced PrimeNG table for Angular

1,191 lines (1,187 loc) 726 kB
import * as i0 from '@angular/core'; import { Pipe, EventEmitter, ViewChild, Output, Input, Component, NgModule, Injectable, Inject, HostListener, ContentChild, HostBinding } from '@angular/core'; import * as i1 from '@angular/common'; import { CommonModule, DOCUMENT } from '@angular/common'; import * as i2 from '@angular/forms'; import { FormGroup, FormControl, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms'; import * as i3 from 'primeng/table'; import { TableModule } from 'primeng/table'; import * as i1$1 from 'primeng/api'; import { ConfirmationService, MessageService } from 'primeng/api'; import * as i5 from 'primeng/inputtext'; import { InputTextModule } from 'primeng/inputtext'; import * as i6 from 'primeng/button'; import { ButtonModule } from 'primeng/button'; import * as i7 from 'primeng/datepicker'; import { DatePickerModule } from 'primeng/datepicker'; import * as i8 from 'primeng/multiselect'; import { MultiSelectModule } from 'primeng/multiselect'; import * as i9 from 'primeng/tag'; import { TagModule } from 'primeng/tag'; import * as i10 from 'primeng/iconfield'; import { IconFieldModule } from 'primeng/iconfield'; import * as i11 from 'primeng/inputicon'; import { InputIconModule } from 'primeng/inputicon'; import * as i12 from 'primeng/tooltip'; import { TooltipModule } from 'primeng/tooltip'; import * as i13 from 'primeng/progressbar'; import { ProgressBarModule } from 'primeng/progressbar'; import { ProgressSpinnerModule } from 'primeng/progressspinner'; import * as i8$1 from 'primeng/inputnumber'; import { InputNumberModule } from 'primeng/inputnumber'; import * as i9$1 from 'primeng/panel'; import { PanelModule } from 'primeng/panel'; import * as i3$1 from 'primeng/checkbox'; import { CheckboxModule } from 'primeng/checkbox'; import * as i3$2 from 'primeng/inputgroup'; import { InputGroupModule } from 'primeng/inputgroup'; import * as i4 from 'primeng/inputgroupaddon'; import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; import * as i3$3 from 'primeng/toggleswitch'; import { ToggleSwitchModule } from 'primeng/toggleswitch'; import * as i3$4 from 'primeng/textarea'; import { TextareaModule } from 'primeng/textarea'; import * as i6$1 from 'primeng/password'; import { PasswordModule } from 'primeng/password'; import * as i3$5 from 'primeng/select'; import { SelectModule } from 'primeng/select'; import * as i3$6 from 'primeng/inputotp'; import { InputOtpModule } from 'primeng/inputotp'; import { Chart, registerables } from 'chart.js'; import ChartDataLabels from 'chartjs-plugin-datalabels'; import { Subject, interval, BehaviorSubject } from 'rxjs'; import { takeUntil, filter } from 'rxjs/operators'; import * as i1$2 from '@angular/router'; import { RouterModule, NavigationEnd } from '@angular/router'; import * as i2$1 from 'primeng/breadcrumb'; import { BreadcrumbModule } from 'primeng/breadcrumb'; import * as i3$7 from 'primeng/confirmdialog'; import { ConfirmDialogModule } from 'primeng/confirmdialog'; import * as i2$2 from 'primeng/toast'; import { ToastModule } from 'primeng/toast'; import * as i2$3 from 'primeng/dialog'; import { DialogModule } from 'primeng/dialog'; /** * Calculates the width required for a column based on the header text (column title). * It uses the Canvas API to measure text width dynamically. * * @param {TableColumn} col - The column metadata containing the header title and code. * @param {string} [font='16px Arial'] - The font to use for measurement (defaults to '16px Arial'). * @returns {number} - The calculated width of the column in pixels. */ function calculateTextWidth(col, font = '16px Arial') { // Create a canvas context for measuring text const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); if (!context) { return 100; // Fallback width if canvas context is not available } // Set the font to match the provided font or the document body font context.font = font || getComputedStyle(document.body).font; // Measure the header text width const headerWidth = context.measureText(col.title).width; // Return the width with some padding return Math.ceil(headerWidth + 20); // Add padding for extra space } class CustomCurrencyPipe { transform(value, currency, decimalPlaces, thousandSeparator = 'comma', decimalSeparator = 'dot') { let formattedValue; if (decimalPlaces !== undefined) { formattedValue = value.toFixed(decimalPlaces); } else { formattedValue = value.toString(); } const thousandSeparatorChar = thousandSeparator === 'space' ? ' ' : ','; const decimalSeparatorChar = decimalSeparator === 'comma' ? ',' : '.'; formattedValue = formattedValue.replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparatorChar); if (decimalSeparatorChar === ',') { formattedValue = formattedValue.replace('.', ','); } if (currency) { let formattedCurrency; switch (currency) { case 'MAD': formattedCurrency = `${formattedValue} DH`; break; case 'USD': formattedCurrency = `$${formattedValue}`; break; default: formattedCurrency = `${formattedValue} ${currency}`; } return formattedCurrency; } return formattedValue; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CustomCurrencyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.14", ngImport: i0, type: CustomCurrencyPipe, isStandalone: true, name: "customCurrency" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CustomCurrencyPipe, decorators: [{ type: Pipe, args: [{ name: 'customCurrency', standalone: true, }] }] }); class CustomDatePipe { transform(value, format = 'dd/MM/yyyy') { if (!value) return null; if (typeof value === 'string') { const parts = value.split('/'); if (parts.length === 3) { const day = parseInt(parts[0], 10); const month = parseInt(parts[1], 10) - 1; const year = parseInt(parts[2], 10); const date = new Date(year, month, day); if (isNaN(date.getTime())) return null; const options = { year: 'numeric', month: '2-digit', day: '2-digit', }; return new Intl.DateTimeFormat('en-GB', options).format(date); } } else if (value instanceof Date) { // If the value is already a Date object const options = { year: 'numeric', month: '2-digit', day: '2-digit', }; return new Intl.DateTimeFormat('en-GB', options).format(value); } return null; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CustomDatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.14", ngImport: i0, type: CustomDatePipe, isStandalone: false, name: "customDate" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CustomDatePipe, decorators: [{ type: Pipe, args: [{ name: 'customDate', standalone: false }] }] }); class PTAdvancedPrimeTableComponent { constructor() { this.data = []; this.columns = []; this.totalRecords = 0; this.rowsPerPage = [10, 20, 30]; this.hasSearchFilter = false; this.hasExportExcel = false; this.hasExportPDF = false; this.hasColumnFilter = false; this.cellPadding = '0.5rem 0.75rem'; this.isPaginated = true; this.isLazy = false; this.actions = []; this.isSortable = false; this.loading = false; this.maxHeight = null; this.isRowReorderable = false; this.rowReorderIdField = 'id'; this.rowOrderStartAt = 1; this.selectionDataKey = 'id'; this.selectionMode = null; this.selection = null; this.cellHeight = null; this.selectionChange = new EventEmitter(); this.rowSelect = new EventEmitter(); this.rowUnselect = new EventEmitter(); this.lazyLoad = new EventEmitter(); this.search = new EventEmitter(); this.exportExcelEvent = new EventEmitter(); this.exportPdfEvent = new EventEmitter(); this.onPageChange = new EventEmitter(); this.onSortColumn = new EventEmitter(); this.onFilterColumn = new EventEmitter(); this.rowReorderChange = new EventEmitter(); this.filteredData = new EventEmitter(); this.TableTypeEnum = TableTypeEnum; this.AlignEnum = AlignEnum; this.SeverityEnum = SeverityEnum; this.searchValue = ''; this.filters = {}; this.latestFilterValues = {}; this.clearedFields = new Set(); this.validCurrencyCodes = ['USD', 'EUR', 'MAD']; this.iconWidth = 77; this.rows = 0; this.first = 0; this.currentPage = 0; this.currentSortField = null; this.currentSortOrder = null; this.hasGroupedColumns = false; this.isDelete = false; this.isEdit = false; this.Delete = () => { }; this.initEditableRow = () => { }; this.saveEditableRow = () => { }; this.cancelEditableRow = () => { }; this.customActions = []; this.dataMap = new Map(); this.map = new Map(); this.optionEntries = new Map(); this.optionValues = []; this.globalFilterFields = []; } ngOnInit() { this.hasGroupedColumns = this.columns.some((col) => col.children && col.children.length > 0); this.globalFilterFields = this.columns .filter((col) => col.code !== undefined && col.isFilter !== false) .map((col) => col.code); this.initializePagination(); this.initializeActions(); this.columns.forEach((col) => { if (col.type === TableTypeEnum.ACTION) { col.isEditable = false; col.isFilter = false; col.isSortable = false; } if (col.type === TableTypeEnum.TAG || col.type === TableTypeEnum.PROGRESS) { col.isEditable = false; } if (col.type === TableTypeEnum.COMPOSED) { this.initializeComposedFilters(col); } if (col.isSortable === undefined) col.isSortable = true; if (col.isEditable === undefined) col.isEditable = true; if (col.isFilter !== false && col.code !== undefined) { if (!this.globalFilterFields.includes(col.code)) { this.globalFilterFields.push(col.code); } } if (!col.width) col.width = this.calculateColumnWidth(col); }); if (!this.isLazy) { this.totalRecords = this.data?.length ?? 0; } } ngOnChanges(changes) { if (changes['data'] && !this.isLazy) { this.totalRecords = this.data?.length ?? 0; if (this.dt) this.dt.first = 0; } } emitLazyLoad() { const payload = { page: this.currentPage, rows: this.rows, first: this.first, search: this.searchValue?.trim() || undefined, sortField: this.currentSortField, sortOrder: this.currentSortOrder, filters: { ...this.latestFilterValues }, }; this.lazyLoad.emit(payload); } resetToFirstPage() { this.currentPage = 0; this.first = 0; if (this.dt) { this.dt.first = 0; } } canUseRowReorder() { return this.isRowReorderable && !this.isLazy; } buildRowOrdering(data) { const idField = this.rowReorderIdField || 'id'; return (data ?? []).map((row, index) => ({ id: row?.[idField], order: this.rowOrderStartAt + index, })); } onRowReorder(event) { const oldIndex = event.dragIndex ?? -1; const newIndex = event.dropIndex ?? -1; if (oldIndex < 0 || newIndex < 0) { return; } const reorderedData = [...(this.data ?? [])]; if (!reorderedData.length || newIndex >= reorderedData.length) { return; } const movedRow = reorderedData[newIndex] ?? null; this.data = reorderedData; if (!this.isLazy) { this.totalRecords = this.data.length; } this.rowReorderChange.emit({ oldIndex, newIndex, movedRow, data: [...this.data], ordering: this.buildRowOrdering(this.data), }); } getHeaderTitleClass(col) { const align = col.headerAlign ?? AlignEnum.LEFT; switch (align) { case AlignEnum.CENTER: return 'header-title-center'; case AlignEnum.RIGHT: return 'header-title-right'; default: return 'header-title-left'; } } getHeaderAlignClass(col) { const align = col.headerAlign ?? AlignEnum.LEFT; switch (align) { case AlignEnum.CENTER: return 'header-align-center'; case AlignEnum.RIGHT: return 'header-align-right'; default: return 'header-align-left'; } } getDataAlignClass(col) { const effectiveAlign = col.dataAlign ?? (col.type === TableTypeEnum.NUMBER || col.type === TableTypeEnum.AMOUNT ? AlignEnum.RIGHT : AlignEnum.LEFT); switch (effectiveAlign) { case AlignEnum.CENTER: return 'cell-align-center'; case AlignEnum.RIGHT: return 'cell-align-right'; default: return 'cell-align-left'; } } getCellInnerAlignClass(col) { const effectiveAlign = col.dataAlign ?? (col.type === TableTypeEnum.NUMBER || col.type === TableTypeEnum.AMOUNT ? AlignEnum.RIGHT : AlignEnum.LEFT); switch (effectiveAlign) { case AlignEnum.CENTER: return 'cell-inner-center'; case AlignEnum.RIGHT: return 'cell-inner-right'; default: return 'cell-inner-left'; } } initializeActions() { this.isDelete = false; this.isEdit = false; this.Delete = () => { }; this.initEditableRow = () => { }; this.saveEditableRow = () => { }; this.cancelEditableRow = () => { }; this.customActions = []; if (!this.actions || this.actions.length === 0) return; this.actions.forEach((action) => { switch (action.code) { case 'delete': this.isDelete = true; this.Delete = (row) => action.action(row); break; case 'edit': this.initializeEditActions(action); break; default: this.customActions.push(action); break; } }); } initializeEditActions(action) { this.isEdit = true; this.initEditableRow = (data) => action.action.init(data); this.saveEditableRow = (data) => { const record = this.map.get(data.id); action.action.save(data, record); this.dataMap.clear(); }; this.cancelEditableRow = (item) => console.log(item); } onCustomActionClick(action, row) { if (!this.isActionVisible(action, row)) return; if (this.isActionDisabled(action, row)) return; if (action && typeof action.action === 'function') { setTimeout(() => action.action(row), 0); } } initializeComposedFilters(col) { col.composedNames?.forEach((composedName) => { const code = col.code || ''; const composedCode = code + '.' + composedName; if (!this.globalFilterFields.includes(composedCode)) { this.globalFilterFields.push(composedCode); } this.filters[composedName] = { options: col.filterOptions, value: [], label: 'Filter by ' + composedName, placeholder: 'Select option', }; }); } getComposedFieldType(col, composedName) { if (col.composedNames && col.composedTypes) { const index = col.composedNames.indexOf(composedName); if (index >= 0 && index < col.composedTypes.length) { return col.composedTypes[index]; } } return undefined; } onComposedColumnClear(col) { if (!col.code || !col.composedNames) return; col.composedNames.forEach((name) => { const key = `${col.code}.${name}`; delete this.latestFilterValues[key]; if (this.filters[name]) { this.filters[name].value = []; } this.clearedFields.add(key); }); } onComposedFilterValueChange(col, composedName, value, filterModel) { const key = `${col.code}.${composedName}`; if (filterModel) { filterModel.value = value; if (Array.isArray(filterModel.constraints) && filterModel.constraints.length > 0) { filterModel.constraints[0].value = value; } } if (!this.filters[composedName]) { this.filters[composedName] = { options: col.filterOptions, value: [], placeholder: 'Select option', }; } this.filters[composedName].value = value || []; const isEmpty = !value || (Array.isArray(value) && value.length === 0); if (isEmpty) { delete this.latestFilterValues[key]; } else { this.latestFilterValues[key] = value; } } onFilterClear(field) { if (!field) return; delete this.latestFilterValues[field]; this.clearedFields.add(field); } onFilterValueChange(field, filterModel, value) { if (!field) return; const isEmpty = value === null || value === undefined || value === '' || (Array.isArray(value) && value.length === 0); if (isEmpty) { delete this.latestFilterValues[field]; } else { this.latestFilterValues[field] = value; } if (filterModel) { filterModel.value = value; if (Array.isArray(filterModel.constraints) && filterModel.constraints.length > 0) { filterModel.constraints[0].value = value; } } } onNumberFilterChange(field, value) { if (!field) return; const isEmpty = value === null || value === undefined || value === '' || (Array.isArray(value) && value.length === 0); if (isEmpty) { delete this.latestFilterValues[field]; } else { this.latestFilterValues[field] = value; } } findColumnByField(field) { return this.columns.find((c) => c.code === field || (c.code && field.startsWith(c.code + '.'))); } filterColumn(event) { const filters = event?.filters; if (!filters) { if (this.isLazy) { this.resetToFirstPage(); this.onFilterColumn.emit(event); this.emitLazyLoad(); } return; } const isNullish = (v) => v === null || v === undefined || v === ''; const nextFilterValues = { ...this.latestFilterValues, }; Object.keys(filters).forEach((field) => { const meta = filters[field]; const normalizeMeta = (m) => Array.isArray(m) && m.length > 0 ? m[0] : m; const m = normalizeMeta(meta); const col = this.findColumnByField(field); const wasCleared = this.clearedFields.has(field); if (!m && !col) return; if (wasCleared) { delete nextFilterValues[field]; if (col?.type === TableTypeEnum.COMPOSED && col.code && col.composedNames) { col.composedNames.forEach((name) => { delete nextFilterValues[`${col.code}.${name}`]; }); } if (m) { m.value = null; if (Array.isArray(m.constraints) && m.constraints.length > 0) { m.constraints.forEach((c) => (c.value = null)); } } this.clearedFields.delete(field); return; } if (col && col.type === TableTypeEnum.COMPOSED) { const composedValues = {}; col.composedNames?.forEach((name) => { const key = `${col.code}.${name}`; if (this.clearedFields.has(key)) { delete nextFilterValues[key]; this.clearedFields.delete(key); return; } const val = nextFilterValues[key]; const empty = isNullish(val) || (Array.isArray(val) && val.length === 0); if (!empty) { composedValues[name] = val; } }); if (Object.keys(composedValues).length === 0) { if (m) { m.value = null; if (Array.isArray(m.constraints)) { m.constraints.forEach((c) => (c.value = null)); } } } else { m.value = composedValues; } return; } if (!m) return; let value = m.value; if (Array.isArray(m.constraints) && m.constraints.length > 0) { const cVal = m.constraints[0].value; if (!isNullish(cVal)) { value = cVal; } } const cached = nextFilterValues[field]; const hasCached = !isNullish(cached) && (!Array.isArray(cached) || cached.length > 0); if ((isNullish(value) || (Array.isArray(value) && value.length === 0)) && hasCached) { value = cached; } const isEmpty = isNullish(value) || (Array.isArray(value) && value.length === 0); if (isEmpty) { delete nextFilterValues[field]; m.value = null; if (Array.isArray(m.constraints) && m.constraints.length > 0) { m.constraints[0].value = null; } return; } let emitValue = value; if (col && (col.type === TableTypeEnum.DATE || col.type === TableTypeEnum.DATETIME)) { const d = this.parseAnyDate(value); emitValue = col.type === TableTypeEnum.DATE ? this.formatDateWithColumn(d, col) : this.formatDateTimeWithColumn(d, col); } nextFilterValues[field] = emitValue; m.value = emitValue; if (Array.isArray(m.constraints) && m.constraints.length > 0) { m.constraints[0].value = emitValue; } }); this.latestFilterValues = nextFilterValues; if (this.isLazy) { this.resetToFirstPage(); this.onFilterColumn.emit(event); this.emitLazyLoad(); } else if (this.dt) { const current = (this.dt.filteredValue ?? this.dt.value ?? []); this.totalRecords = current.length; this.dt.first = 0; this.filteredData.emit([...current]); } } changePage(event) { const page = event.page ?? Math.floor((event.first || 0) / event.rows); const rows = event.rows; const first = event.first ?? page * rows; this.rows = rows; this.currentPage = page; this.first = first; if (this.isLazy) { this.onPageChange.emit({ page, rows }); this.emitLazyLoad(); } } sortColumn(event) { if (!this.isLazy) return; let field = event.field; const col = this.columns.find((c) => c.code === field); if (col && col.type === TableTypeEnum.COMPOSED) { let textProp; if (col.composedNames && col.composedTypes) { const idx = col.composedTypes.findIndex((t) => t === TableTypeEnum.STRING); if (idx >= 0 && idx < col.composedNames.length) { textProp = col.composedNames[idx]; } } if (!textProp && col.composedNames?.length) { textProp = col.composedNames[0]; } if (textProp) { field = `${field}.${textProp}`; } } this.currentSortField = field ?? null; this.currentSortOrder = event.order ?? null; this.resetToFirstPage(); this.onSortColumn.emit({ ...event, field }); this.emitLazyLoad(); } parseDate_ddMMyyyy(dateString) { const parts = dateString.split('/'); if (parts.length === 3) { const day = parseInt(parts[0], 10); const month = parseInt(parts[1], 10) - 1; const year = parseInt(parts[2], 10); const date = new Date(year, month, day); return isNaN(date.getTime()) ? null : date; } return null; } onChange(event, id, key) { const target = event.target; this.changeHandler(id, key, target.value); } changeHandler(id, key, value) { const column = this.columns.find((item) => item.code === key); if (!this.map.get(id)) { if (column?.type === TableTypeEnum.DATE) { this.dataMap.set(key, this.parseDate_ddMMyyyy(value)); } else { this.dataMap.set(key, value); } this.map.set(id, new Map(this.dataMap)); } else { const mapItem = this.map.get(id); if (column?.type === TableTypeEnum.DATE) { mapItem.set(key, this.parseDate_ddMMyyyy(value)); } else { mapItem.set(key, value); } } } getColumnFilterType(column) { switch (column.type) { case TableTypeEnum.STRING: return 'text'; case TableTypeEnum.AMOUNT: case TableTypeEnum.NUMBER: return 'numeric'; case TableTypeEnum.DATE: case TableTypeEnum.DATETIME: return 'date'; case TableTypeEnum.MULTISELECT: return 'multiSelect'; case TableTypeEnum.BOOLEAN: return 'boolean'; case TableTypeEnum.COMPOSED: return 'composed'; default: return 'text'; } } isEditable(key) { if (!key) return false; const column = this.columns.find((item) => item.code === key); return column?.isEditable !== false; } isMultiSelect(key) { const column = this.columns.find((item) => item.code === key); if (column?.type === TableTypeEnum.MULTISELECT && column.options && column.code !== undefined) { this.optionEntries = new Map([ [column.code, Object.values(column.options)], ]); this.optionValues = this.optionEntries.get(key) || []; return true; } return false; } isDatePicker(key) { const t = this.columns.find((item) => item.code === key)?.type; return t === TableTypeEnum.DATE || t === TableTypeEnum.DATETIME; } isDateTimePicker(key) { const t = this.columns.find((item) => item.code === key)?.type; return t === TableTypeEnum.DATETIME; } filterGlobal(event) { const target = event.target; const value = (target.value || '').toLowerCase(); if (this.isLazy) { this.searchValue = value; this.resetToFirstPage(); this.search.emit(value); this.emitLazyLoad(); return; } if (!value) { const allData = [...(this.data ?? [])]; this.dt.value = allData; this.totalRecords = allData.length; this.dt.first = 0; this.filteredData.emit(allData); return; } const filteredData = (this.data ?? []).filter((item) => (this.globalFilterFields ?? []).some((field) => { const column = this.columns?.find((col) => col.code === field); if (!column) return false; const cell = item?.[field]; if (column.type === TableTypeEnum.DATE) { return this.formatDateWithColumn(this.parseAnyDate(cell), column) .toLowerCase() .includes(value); } if (column.type === TableTypeEnum.DATETIME) { return this.formatDateTimeWithColumn(this.parseAnyDate(cell), column) .toLowerCase() .includes(value); } if (column.type === TableTypeEnum.AMOUNT || column.type === TableTypeEnum.NUMBER) { return String(cell ?? '') .toLowerCase() .includes(value); } if (column.type === TableTypeEnum.COMPOSED) { return this.filterComposedColumn(cell, value); } return String(cell ?? '') .toLowerCase() .includes(value); })); this.dt.value = filteredData; this.totalRecords = filteredData.length; this.dt.first = 0; this.filteredData.emit([...filteredData]); } filterComposedColumn(composedData, value) { if (!composedData) return false; return Object.keys(composedData).some((key) => { const cellValue = composedData[key]; return typeof cellValue === 'string' ? cellValue.toLowerCase().includes(value) : false; }); } parseAnyDate(input) { if (input === null || input === undefined || input === '') return null; if (input instanceof Date) return isNaN(input.getTime()) ? null : input; if (typeof input === 'number') { const d = new Date(input); return isNaN(d.getTime()) ? null : d; } if (typeof input === 'string') { const s = input.trim(); const isoTry = new Date(s); if (!isNaN(isoTry.getTime())) return isoTry; if (s.includes('/')) { const parts = s.split(' ')[0].split('/'); if (parts.length === 3) { const day = parseInt(parts[0], 10); const month = parseInt(parts[1], 10) - 1; const year = parseInt(parts[2], 10); const d = new Date(year, month, day); return isNaN(d.getTime()) ? null : d; } } } return null; } formatWithPattern(d, pattern) { if (!d) return ''; const pad2 = (n) => String(n).padStart(2, '0'); const map = { dd: pad2(d.getDate()), MM: pad2(d.getMonth() + 1), yyyy: String(d.getFullYear()), HH: pad2(d.getHours()), mm: pad2(d.getMinutes()), ss: pad2(d.getSeconds()), }; return pattern .replace(/yyyy/g, map.yyyy) .replace(/dd/g, map.dd) .replace(/MM/g, map.MM) .replace(/HH/g, map.HH) .replace(/mm/g, map.mm) .replace(/ss/g, map.ss); } formatDate(date) { const d = this.parseAnyDate(date); return this.formatWithPattern(d, 'dd/MM/yyyy'); } formatDateWithColumn(d, col) { const fmt = col.dateFormat?.trim() || 'dd/MM/yyyy'; return this.formatWithPattern(d, fmt); } formatDateTimeWithColumn(d, col) { const fmt = col.dateTimeFormat?.trim() || 'dd/MM/yyyy HH:mm:ss'; return this.formatWithPattern(d, fmt); } calculateColumnWidth(col) { const calculatedWidth = calculateTextWidth(col, col.title); const totalWidth = calculatedWidth + this.iconWidth + 20; return `${totalWidth}px`; } getHeaderWidth(col) { return col.width ? col.width : this.calculateColumnWidth(col); } clear(table) { table.clear(); this.searchValue = ''; this.latestFilterValues = {}; this.clearedFields.clear(); this.currentSortField = null; this.currentSortOrder = null; Object.keys(this.filters).forEach((key) => { if (this.filters[key]) this.filters[key].value = []; }); if (this.isLazy) { this.resetToFirstPage(); this.onFilterColumn.emit({ cleared: true }); this.emitLazyLoad(); return; } if (this.dt) { const allData = [...(this.data ?? [])]; this.dt.value = allData; this.totalRecords = allData.length; this.dt.first = 0; this.filteredData.emit(allData); } } initializePagination() { if (this.isPaginated) { if (!this.rowsPerPage || this.rowsPerPage.length === 0) { this.rowsPerPage = [10, 20, 30]; } this.rows = this.rowsPerPage[0]; this.currentPage = 0; this.first = 0; } } getCurrencySymbol(column) { return column.type === TableTypeEnum.AMOUNT && column.currency && this.isValidCurrencyCode(column.currency) ? column.currency : undefined; } isValidCurrencyCode(currencyCode) { return this.validCurrencyCodes.includes(currencyCode); } exportExcel() { this.exportExcelEvent.emit(); } exportPdf() { this.exportPdfEvent.emit(); } getImageStyle(style) { if (!style) return {}; const imageStyle = { width: style.width || 'auto', height: style.height || 'auto', }; if (style.margin) { imageStyle.margin = style.margin; } if (style.marginLeft) { imageStyle.marginLeft = style.marginLeft; } if (style.marginRight) { imageStyle.marginRight = style.marginRight; } if (style.marginTop) { imageStyle.marginTop = style.marginTop; } if (style.marginBottom) { imageStyle.marginBottom = style.marginBottom; } return imageStyle; } getTitleStyle(style) { if (!style) return {}; return { color: style.color || 'inherit', fontSize: style.fontSize || 'inherit', textAlign: style.position || 'left', }; } formatNumber(value, decimalPlaces, thousandSeparator = 'comma', decimalSeparator = 'dot') { if (value === null || value === undefined || isNaN(value)) return ''; let formattedNumber = decimalPlaces !== undefined ? value.toFixed(decimalPlaces) : value.toString(); if (decimalSeparator === 'comma') { formattedNumber = formattedNumber.replace('.', ','); } if (thousandSeparator && Math.abs(value) >= 1000) { const parts = formattedNumber.split(decimalSeparator === 'comma' ? ',' : '.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator === 'comma' ? ',' : ' '); formattedNumber = parts.join(decimalSeparator === 'comma' ? ',' : '.'); } return formattedNumber; } isActionVisible(action, row) { const v = action?.visible; if (v === undefined || v === null) return true; return typeof v === 'function' ? !!v(row) : !!v; } isActionDisabled(action, row) { const d = action.disabled; if (d === undefined || d === null) return false; return typeof d === 'function' ? !!d(row) : !!d; } getComposedCellStyle(col, composedName, row) { const extendedCol = col; const styleConfig = extendedCol?.composedCellStyles?.[composedName]; if (!styleConfig) { return {}; } return typeof styleConfig === 'function' ? (styleConfig(row) ?? {}) : styleConfig; } mergeStyles(...styles) { return styles.reduce((acc, style) => { if (!style) { return acc; } return { ...acc, ...style, }; }, {}); } getMergedComposedTextStyle(col, composedName, row) { return this.mergeStyles(this.getTitleStyle(col.composedStyles?.[composedName]), this.getComposedCellStyle(col, composedName, row)); } getMergedComposedImageStyle(col, composedName, row) { return this.mergeStyles(this.getImageStyle(col.composedStyles?.[composedName]), this.getComposedCellStyle(col, composedName, row)); } getTagValue(col, row) { const extendedCol = col; const tagValue = extendedCol.tagValue; if (typeof tagValue === 'function') { return tagValue(row) ?? ''; } if (tagValue !== undefined && tagValue !== null) { return String(tagValue); } return String(col.code ? row?.[col.code] : ''); } getTagSeverity(col, row) { const extendedCol = col; const tagSeverity = extendedCol.tagSeverity; if (typeof tagSeverity === 'function') { return tagSeverity(row) ?? SeverityEnum.INFO; } return tagSeverity ?? SeverityEnum.INFO; } getTagIcon(col, row) { const extendedCol = col; const tagIcon = extendedCol.tagIcon; if (typeof tagIcon === 'function') { return tagIcon(row) || undefined; } return tagIcon || undefined; } isTagRounded(col) { return col.tagRounded !== false; } getActionTooltip(action, row) { if (!action?.tooltip) { return ''; } return action.tooltip; } getDefaultActionTooltip(code) { switch (code) { case 'delete': return 'Supprimer'; case 'edit': return 'Modifier'; case 'save': return 'Enregistrer'; case 'cancel': return 'Annuler'; default: return ''; } } getProgressValue(col, row) { const extendedCol = col; const progressValue = extendedCol.progressValue; let value; if (typeof progressValue === 'function') { value = Number(progressValue(row) ?? 0); } else if (progressValue !== undefined && progressValue !== null) { value = Number(progressValue); } else { value = Number(col.code ? row?.[col.code] : 0); } if (!Number.isFinite(value)) { return 0; } return Math.max(0, Math.min(100, value)); } isProgressShowValue(col) { return col.progressShowValue !== false; } getProgressUnit(col) { return col.progressUnit ?? '%'; } getProgressSeverity(col, row) { const extendedCol = col; const severity = extendedCol.progressSeverity; if (typeof severity === 'function') { return severity(row) ?? SeverityEnum.INFO; } return severity ?? SeverityEnum.INFO; } getMultiSelectValues(value) { if (Array.isArray(value)) { return value; } if (value === null || value === undefined || value === '') { return []; } return [value]; } getCellHeight() { return this.cellHeight?.trim() || null; } getColumnCellPadding(col) { return col.cellPadding?.trim() || this.cellPadding?.trim() || '0px'; } getColumnCellMargin(col) { return col.cellMargin?.trim() || '0px'; } getCellInnerStyle(col, defaultMargin = '0px') { return { margin: col.cellMargin?.trim() || defaultMargin, minHeight: '0', boxSizing: 'border-box', }; } getCellStyle(col, row) { const styles = {}; if (col.cellStyle) { const customStyle = typeof col.cellStyle === 'function' ? col.cellStyle(row) : col.cellStyle; Object.assign(styles, customStyle); } const height = this.getCellHeight(); if (height) { styles['height'] = height; styles['max-height'] = height; styles['line-height'] = height; } styles['padding'] = this.getColumnCellPadding(col); styles['box-sizing'] = 'border-box'; return styles; } getHeaderCellStyle(col) { const styles = { padding: this.getColumnCellPadding(col), boxSizing: 'border-box', }; const height = this.getCellHeight(); if (height) { styles['height'] = height; styles['max-height'] = height; styles['line-height'] = height; } return styles; } getStaticCellStyle() { const styles = { padding: this.cellPadding?.trim() || '0px', boxSizing: 'border-box', }; const height = this.getCellHeight(); if (height) { styles['height'] = height; styles['max-height'] = height; styles['line-height'] = height; } return styles; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PTAdvancedPrimeTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PTAdvancedPrimeTableComponent, isStandalone: false, selector: "pt-advanced-prime-table", inputs: { data: "data", columns: "columns", totalRecords: "totalRecords", rowsPerPage: "rowsPerPage", hasSearchFilter: "hasSearchFilter", hasExportExcel: "hasExportExcel", hasExportPDF: "hasExportPDF", hasColumnFilter: "hasColumnFilter", cellPadding: "cellPadding", isPaginated: "isPaginated", isLazy: "isLazy", actions: "actions", isSortable: "isSortable", loading: "loading", maxHeight: "maxHeight", isRowReorderable: "isRowReorderable", rowReorderIdField: "rowReorderIdField", rowOrderStartAt: "rowOrderStartAt", selectionDataKey: "selectionDataKey", selectionMode: "selectionMode", selection: "selection", cellHeight: "cellHeight" }, outputs: { selectionChange: "selectionChange", rowSelect: "rowSelect", rowUnselect: "rowUnselect", lazyLoad: "lazyLoad", search: "search", exportExcelEvent: "exportExcelEvent", exportPdfEvent: "exportPdfEvent", onPageChange: "onPageChange", onSortColumn: "onSortColumn", onFilterColumn: "onFilterColumn", rowReorderChange: "rowReorderChange", filteredData: "filteredData" }, viewQueries: [{ propertyName: "dt", first: true, predicate: ["dt"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"pt-advanced-prime-table table-container\">\n <p-table\n #dt\n [value]=\"data\"\n [loading]=\"loading\"\n [rows]=\"rows\"\n [paginator]=\"isPaginated\"\n [globalFilterFields]=\"globalFilterFields\"\n [rowsPerPageOptions]=\"rowsPerPage\"\n [totalRecords]=\"totalRecords\"\n [lazy]=\"isLazy\"\n [filterDelay]=\"0\"\n [dataKey]=\"selectionDataKey || rowReorderIdField || 'id'\"\n styleClass=\"p-datatable-gridlines p-datatable-striped\"\n [scrollable]=\"true\"\n [scrollHeight]=\"maxHeight !== null ? maxHeight : undefined\"\n (onRowReorder)=\"onRowReorder($event)\"\n (onPage)=\"changePage($event)\"\n (onSort)=\"sortColumn($event)\"\n (onFilter)=\"filterColumn($event)\"\n [selectionMode]=\"selectionMode\"\n [(selection)]=\"selection\"\n (selectionChange)=\"selectionChange.emit($event)\"\n (onRowSelect)=\"rowSelect.emit($event.data)\"\n (onRowUnselect)=\"rowUnselect.emit($event.data)\"\n >\n <ng-template pTemplate=\"colgroup\" let-columns>\n <colgroup>\n @if (canUseRowReorder()) {\n <col style=\"width: 3rem\" />\n }\n\n @for (col of columns; track col) {\n <col [style.width]=\"col.width || getHeaderWidth(col)\" />\n }\n </colgroup>\n </ng-template>\n\n <ng-template pTemplate=\"caption\">\n <div class=\"flex\">\n <div>\n <h3>Total: {{ totalRecords }}</h3>\n </div>\n\n <div>\n @if (hasSearchFilter) {\n <button\n pButton\n icon=\"pi pi-filter-slash\"\n class=\"p-button-rounded p-button-text\"\n (click)=\"clear(dt)\"\n title=\"Clear filters\"\n ></button>\n }\n\n @if (hasExportExcel) {\n <button\n pButton\n icon=\"pi pi-file-excel\"\n class=\"p-button-rounded p-button-text\"\n (click)=\"exportExcel()\"\n title=\"Export to Excel\"\n ></button>\n }\n\n @if (hasExportPDF) {\n <button\n pButton\n icon=\"pi pi-file-pdf\"\n class=\"p-button-rounded p-button-text\"\n (click)=\"exportPdf()\"\n title=\"Export to PDF\"\n ></button>\n }\n </div>\n\n @if (hasSearchFilter) {\n <div class=\"ml-auto\">\n <p-iconField iconPosition=\"left\" class=\"ml-auto\">\n <p-inputIcon>\n <i class=\"pi pi-search\"></i>\n </p-inputIcon>\n\n <input\n pInputText\n type=\"text\"\n [(ngModel)]=\"searchValue\"\n (input)=\"filterGlobal($event)\"\n placeholder=\"Search keyword\"\n />\n </p-iconField>\n </div>\n }\n </div>\n </ng-template>\n\n <ng-template pTemplate=\"header\">\n <tr class=\"sticky-header\">\n @if (canUseRowReorder()) {\n <th [style.width]=\"'3rem'\" [ngStyle]=\"getStaticCellStyle()\"></th>\n }\n\n @for (col of columns; track col) {\n @if (!col.children) {\n <th\n [pSortableColumn]=\"col.code\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngStyle]=\"getHeaderCellStyle(col)\"\n [ngClass]=\"[\n getHeaderAlignClass(col),\n col.type === TableTypeEnum.ACTION ? 'action-column' : '',\n ]\"\n colspan=\"1\"\n >\n @if (isSortable && col.isSortable !== false) {\n <div\n class=\"header-container d-flex align-items-center justify-content-between\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngStyle]=\"getCellInnerStyle(col, '10px')\"\n >\n <span [ngClass]=\"getHeaderTitleClass(col)\">\n {{ col.title }}\n </span>\n\n <div\n class=\"icons d-flex align-items-center\"\n [style.width]=\"'77px'\"\n >\n <p-sortIcon [field]=\"col.code\" />\n\n @if (hasColumnFilter && col.isFilter !== false) {\n @if (col.type === TableTypeEnum.COMPOSED) {\n <p-columnFilter\n display=\"menu\"\n [field]=\"col.code\"\n type=\"text\"\n [showApplyButton]=\"true\"\n [showClea