UNPKG

ng-prime-tools

Version:

An advanced PrimeNG table for Angular

448 lines 161 kB
import { Component, Input, Output, ViewChild } from '@angular/core'; import { EventEmitter } from '@angular/core'; import { TableTypeEnum, } from '../../public-api'; import { calculateTextWidth } from '../utils/text.util'; import * as i0 from "@angular/core"; import * as i1 from "@angular/common"; import * as i2 from "primeng/table"; import * as i3 from "primeng/api"; import * as i4 from "primeng/inputtext"; import * as i5 from "primeng/button"; import * as i6 from "primeng/calendar"; import * as i7 from "@angular/forms"; import * as i8 from "primeng/multiselect"; import * as i9 from "primeng/tag"; import * as i10 from "primeng/iconfield"; import * as i11 from "primeng/inputicon"; import * as i12 from "../pipes/custom-currency"; import * as i13 from "../pipes/custom-date.pipe"; export class PTAdvancedPrimeTableComponent { constructor() { // Inputs this.data = []; this.columns = []; this.totalRecords = 0; this.rowsPerPage = []; this.hasSearchFilter = false; this.hasExportExcel = false; this.hasExportPDF = false; this.hasColumnFilter = false; this.isPaginated = false; this.actions = []; this.isSortable = false; this.loading = false; this.maxHeight = null; // Outputs: Events emitted to the parent component this.filter = new EventEmitter(); this.search = new EventEmitter(); this.exportExcelEvent = new EventEmitter(); this.exportPdfEvent = new EventEmitter(); this.TableTypeEnum = TableTypeEnum; this.searchValue = ''; this.filters = {}; this.validCurrencyCodes = ['USD', 'EUR', 'MAD']; this.iconWidth = 77; // Component state properties this.isDelete = false; this.isEdit = false; this.rows = 0; // Data management properties this.dataMap = new Map(); this.map = new Map(); this.optionEntries = new Map(); this.optionValues = []; this.globalFilterFields = []; // CRUD operation handlers this.Delete = () => { }; this.initEditableRow = () => { }; this.saveEditableRow = () => { }; this.cancelEditableRow = () => { }; this.hasGroupedColumns = false; } 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(); // Set default value for isSortable this.columns.forEach((col) => { if (col.type === TableTypeEnum.ACTION) { col.isEditable = false; col.isFilter = false; col.isSortable = 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) { this.globalFilterFields.push(col.code); } if (!col.width) { col.width = this.calculateColumnWidth(col); } }); } // Initialize filters for composed columns initializeComposedFilters(col) { col.composedNames?.forEach((composedName) => { this.globalFilterFields.push(col.code + '.' + composedName); this.filters[composedName] = { options: col.filterOptions, value: [], label: 'Filter by ' + composedName, placeholder: 'Select options for ' + composedName, }; }); } // Get the column type for composed fields (STRING, IMAGE, etc.) getComposedFieldType(col, composedName) { // Ensure that col.composedNames and col.composedTypes are valid arrays if (col.composedNames && col.composedTypes) { const index = col.composedNames.indexOf(composedName); // Check if index is a valid number (not -1) and within bounds of composedTypes array if (index >= 0 && index < col.composedTypes.length) { return col.composedTypes[index]; // Safe access of composedTypes array } } return undefined; // Return undefined if no valid index is found or composedNames/composedTypes are not valid } onComposedFilterChange(composedName, selectedValues) { console.log('Selected Values:', selectedValues); console.log('Data Before Filtering:', this.data); // Update the filter value with the selected values this.filters[composedName].value = selectedValues; // Emit the filter event to notify the parent component (if needed) this.filter.emit(this.filters); // Get the filter value (joining array into a string) const filterValue = selectedValues.join(','); console.log('Filter Values to be Applied:', filterValue); // Apply global filter using PrimeNG's filterGlobal method this.dt.filterGlobal(filterValue, 'contains'); } onFilter(event) { this.totalRecords = event.filteredValue?.length || 0; } onCalendarFilterChange(event, columnCode, filterCallback) { // Log the data's date format for debugging console.log('Data Before Filtering:', this.data.map((item) => item[columnCode])); console.log('event : ' + event); // Convert the event value to a string (in the desired date format) const filterValue = event ? new Date(event) : null; console.log('filterValue : ' + filterValue); // If the filterValue is empty, do not trigger filterCallback if (!filterValue) { return; } // Manually trigger the filterCallback after updating the value (passing the string value) filterCallback(filterValue); const filterValueString = event ? this.formatDate(event) : ''; // Call the onFilter event to update totalRecords this.onFilter({ filteredValue: this.data.filter((item) => { const columnValue = item[columnCode]; // If the column value is a string, use it as is for comparison if (columnValue) { // Convert the item value to a string (in the same format) const itemDateString = this.formatDate(new Date(columnValue)); return itemDateString === filterValueString; // Compare the string dates } return false; }), }); } // Filter logic for composed columns (to check against multi-select values) filterComposedData(item, composedName, value) { if (Array.isArray(value) && value.length > 0) { return value.some((filterValue) => item[composedName]?.toLowerCase().includes(filterValue.toLowerCase())); } return true; } // Function to calculate column width based on text in header and data calculateColumnWidth(col) { const calculatedWidth = calculateTextWidth(col, col.title); const totalWidth = calculatedWidth + this.iconWidth + 20; return `${totalWidth}px`; } getHeaderWidth(col) { // Remove 'px' from col.width and convert it to a number const widthWithoutPx = parseInt(col.width?.replace('px', '') || '0', 10); // Add 20 to the calculated width const headerWidth = widthWithoutPx + 20; // Return the new width in 'px' return `${headerWidth}px`; } clear(table) { table.clear(); this.searchValue = ''; } parseDate(dateString) { const parts = dateString.split('/'); if (parts.length === 3) { // Assuming date format is DD/MM/YYYY 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; } initializePagination() { if (this.isPaginated) { // Check if rowsPerPage is undefined or an empty array if (!this.rowsPerPage || this.rowsPerPage.length === 0) { this.rowsPerPage = [20, 30, 40]; } this.rows = this.rowsPerPage[0]; } } initializeActions() { if (this.actions) { this.actions.forEach((action) => { switch (action.code) { case 'delete': this.isDelete = true; this.Delete = (value) => action.action(value); break; case 'edit': this.initializeEditActions(action); break; default: this.isDelete = false; this.isEdit = false; } }); } } 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); } onChange(event, id, key) { const target = event.target; this.changeHandler(id, key, target.value); } changeHandler(id, key, value) { let column = this.columns.find((item) => item.code === key); if (!this.map.get(id)) { if (column?.type === TableTypeEnum.DATE) { let date = this.parseDate(value); this.dataMap.set(key, date); } else { this.dataMap.set(key, value); } this.map.set(id, new Map(this.dataMap)); } else { let mapItem = this.map.get(id); if (column?.type === TableTypeEnum.DATE) { let date = this.parseDate(value); mapItem.set(key, date); } 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: return 'date'; case TableTypeEnum.MULTISELECT: return 'multiSelect'; case TableTypeEnum.BOOLEAN: return 'boolean'; case TableTypeEnum.COMPOSED: return 'composed'; default: return 'text'; } } // State Check Methods isEditable(key) { let column = this.columns.find((item) => item.code === key); return column?.isEditable !== false; } isMultiSelect(key) { let 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) { return (this.columns.find((item) => item.code === key)?.type === TableTypeEnum.DATE); } // Utility Methods dateConverter(value) { return new Date(value).toLocaleDateString('en-US'); } getCurrencySymbol(column) { return column.type === TableTypeEnum.AMOUNT && column.currency && this.isValidCurrencyCode(column.currency) ? column.currency : undefined; } isValidCurrencyCode(currencyCode) { return this.validCurrencyCodes.includes(currencyCode); } filterGlobal(event) { const target = event.target; const value = target.value.toLowerCase(); // Create a new filtered dataset const filteredData = this.data.filter((item) => { return this.globalFilterFields.some((field) => { const column = this.columns.find((col) => col.code === field); if (!column) { return false; } // Handle different column types if (column.type === TableTypeEnum.DATE) { const itemDate = this.formatDate(item[field]); return itemDate && itemDate.includes(value); } else if (column.type === TableTypeEnum.AMOUNT || column.type === TableTypeEnum.NUMBER) { return (item[field] && item[field].toString().toLowerCase().includes(value)); } else if (column.type === TableTypeEnum.COMPOSED) { // Handle composed type by searching for text in the composed cells return this.filterComposedColumn(item[field], value); } else { return (item[field] && item[field].toString().toLowerCase().includes(value)); } }); }); // Update the table's value this.dt.value = filteredData; // After filtering, update the totalRecords this.totalRecords = filteredData.length ?? 0; } filterComposedColumn(composedData, value) { if (composedData) { // Iterate over composed keys and check if any key's value contains the search text return Object.keys(composedData).some((key) => { const cellValue = composedData[key]; if (typeof cellValue === 'string') { return cellValue.toLowerCase().includes(value); } return false; }); } return false; } formatDate(date) { if (!date) return ''; if (date instanceof Date) { // Handle Date object const day = date.getDate().toString().padStart(2, '0'); const month = (date.getMonth() + 1).toString().padStart(2, '0'); const year = date.getFullYear().toString(); return `${day}/${month}/${year}`; } else if (typeof date === 'string') { // Handle string date const parts = date.split('/'); if (parts.length === 3) { return `${parts[0]}/${parts[1]}/${parts[2]}`; } else { // Handle partial dates return date; } } return ''; } exportExcel() { this.exportExcelEvent.emit(); } exportPdf() { this.exportPdfEvent.emit(); } getImageStyle(style) { if (style) { const imageStyle = { width: style.width || 'auto', height: style.height || 'auto', }; // Fallback to empty string if margin, marginLeft, or marginRight are undefined 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; } return {}; } getTitleStyle(style) { if (style) { return { color: style.color || 'inherit', fontSize: style.fontSize || 'inherit', textAlign: style.position || 'left', }; } return {}; } formatNumber(value, decimalPlaces, thousandSeparator = 'comma', decimalSeparator = 'dot') { if (value === null || value === undefined || isNaN(value)) return ''; // Convert the number to a string with full precision if decimalPlaces is undefined let formattedNumber = decimalPlaces !== undefined ? value.toFixed(decimalPlaces) : value.toString(); // Replace decimal separator (default is "dot") if (decimalSeparator === 'comma') { formattedNumber = formattedNumber.replace('.', ','); } // Apply thousand separator only if the number is >= 1000 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; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: PTAdvancedPrimeTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.11", type: PTAdvancedPrimeTableComponent, selector: "pt-advanced-prime-table", inputs: { data: "data", columns: "columns", totalRecords: "totalRecords", rowsPerPage: "rowsPerPage", hasSearchFilter: "hasSearchFilter", hasExportExcel: "hasExportExcel", hasExportPDF: "hasExportPDF", hasColumnFilter: "hasColumnFilter", isPaginated: "isPaginated", actions: "actions", isSortable: "isSortable", loading: "loading", maxHeight: "maxHeight" }, outputs: { filter: "filter", search: "search", exportExcelEvent: "exportExcelEvent", exportPdfEvent: "exportPdfEvent" }, viewQueries: [{ propertyName: "dt", first: true, predicate: ["dt"], descendants: 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 dataKey=\"id\"\n styleClass=\"p-datatable-gridlines\"\n styleClass=\"p-datatable-striped\"\n editMode=\"row\"\n [scrollable]=\"true\"\n [scrollHeight]=\"maxHeight !== null ? maxHeight : undefined\"\n (onFilter)=\"onFilter($event)\"\n >\n <ng-template pTemplate=\"caption\">\n <div class=\"flex\">\n <div>\n <h3>Total: {{ totalRecords }}</h3>\n </div>\n\n <div>\n <!-- Clear filters -->\n <button\n *ngIf=\"hasSearchFilter\"\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 <!-- Export to Excel Button -->\n <button\n *ngIf=\"hasExportExcel\"\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 <!-- Export to PDF Button -->\n <button\n *ngIf=\"hasExportPDF\"\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 </div>\n <div class=\"ml-auto\" *ngIf=\"hasSearchFilter\">\n <!-- Add this wrapper div with ml-auto class -->\n <p-iconField iconPosition=\"left\" class=\"ml-auto\">\n <p-inputIcon>\n <i class=\"pi pi-search\"></i>\n </p-inputIcon>\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 </div>\n </ng-template>\n\n <ng-template pTemplate=\"header\">\n <tr class=\"sticky-header\">\n <ng-container *ngFor=\"let col of columns\">\n <th\n *ngIf=\"!col.children; else groupHeader\"\n [style.width]=\"getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n colspan=\"1\"\n >\n <ng-container\n *ngIf=\"isSortable && col.isSortable !== false; else noSortHeader\"\n >\n <th\n pSortableColumn=\"{{ col.code }}\"\n [style.width]=\"getHeaderWidth(col)\"\n >\n <div\n class=\"header-container d-flex align-items-center justify-content-between\"\n [style.width]=\"col.width\"\n [style.padding]=\"'0px'\"\n [style.margin]=\"'10px'\"\n >\n <span>{{ col.title }}</span>\n <div\n class=\"icons d-flex align-items-center\"\n [style.width]=\"'77px'\"\n >\n <p-sortIcon field=\"{{ col.code }}\" />\n <ng-container *ngIf=\"col.isFilter !== false\">\n <p-columnFilter\n display=\"menu\"\n [field]=\"col.code\"\n display=\"menu\"\n [type]=\"getColumnFilterType(col)\"\n *ngIf=\"col.type === TableTypeEnum.COMPOSED\"\n showClearButton=\"false\"\n showApplyButton=\"false\"\n >\n <!-- TableTypeEnum.COMPOSED -->\n <ng-template\n pTemplate=\"filter\"\n let-value\n let-filterCallback=\"filterCallback\"\n (onFilter)=\"onFilter($event)\"\n >\n <div *ngFor=\"let composedName of col.composedNames\">\n <ng-container\n *ngIf=\"\n getComposedFieldType(col, composedName) ===\n TableTypeEnum.STRING\n \"\n >\n <p-multiSelect\n [ngModel]=\"filters[composedName]?.value\"\n [options]=\"filters[composedName]?.options\"\n (onChange)=\"\n onComposedFilterChange(\n composedName,\n $event.value\n )\n \"\n [placeholder]=\"\n filters[composedName]?.placeholder\n \"\n [display]=\"'chip'\"\n >\n <ng-template let-item pTemplate=\"item\">\n <div class=\"custom-multiselect-item\">\n <img\n *ngIf=\"item.image\"\n [src]=\"item.image\"\n alt=\"icon\"\n class=\"filter-image\"\n />\n <span>{{ item.label }}</span>\n </div>\n </ng-template>\n </p-multiSelect>\n </ng-container>\n </div>\n\n <!-- Define itemTemplate here -->\n <ng-template let-item pTemplate=\"item\">\n <div class=\"custom-multiselect-item\">\n <img\n *ngIf=\"item.image\"\n [src]=\"item.image\"\n alt=\"icon\"\n class=\"filter-image\"\n />\n <span>{{ item.label }}</span>\n </div>\n </ng-template>\n </ng-template>\n </p-columnFilter>\n\n <!-- other TableTypeEnum.XXX -->\n <p-columnFilter\n display=\"menu\"\n [field]=\"col.code\"\n display=\"menu\"\n [type]=\"getColumnFilterType(col)\"\n *ngIf=\"col.type !== TableTypeEnum.COMPOSED\"\n hideOnClear=\"true\"\n >\n <!-- TableTypeEnum.NUMBER -->\n <ng-template\n pTemplate=\"filter\"\n let-value\n let-filterCallback=\"filterCallback\"\n *ngIf=\"col.type === TableTypeEnum.NUMBER\"\n (onFilter)=\"onFilter($event)\"\n >\n <input\n pInputText\n type=\"number\"\n [step]=\"\n col.decimalPlaces\n ? '0.' + '1'.padEnd(col.decimalPlaces, '0')\n : 'any'\n \"\n [ngModel]=\"value\"\n (ngModelChange)=\"filterCallback($event)\"\n [placeholder]=\"'Enter a number'\"\n />\n </ng-template>\n\n <!-- TableTypeEnum.DATE -->\n <ng-template\n pTemplate=\"filter\"\n let-value\n let-filterCallback=\"filterCallback\"\n *ngIf=\"col.type === TableTypeEnum.DATE\"\n (onFilter)=\"onFilter($event)\"\n >\n <p-calendar\n [ngModel]=\"value\"\n (ngModelChange)=\"\n onCalendarFilterChange(\n $event,\n col.code!,\n filterCallback\n )\n \"\n [dateFormat]=\"'dd/mm/yy'\"\n [placeholder]=\"'Choose a date'\"\n ></p-calendar>\n </ng-template>\n\n <!-- TableTypeEnum.MULTISELECT -->\n <ng-template\n pTemplate=\"filter\"\n let-value\n let-filterCallback=\"filterCallback\"\n *ngIf=\"col.type === TableTypeEnum.MULTISELECT\"\n (onFilter)=\"onFilter($event)\"\n >\n <p-multiSelect\n [options]=\"col.filterOptions\"\n [ngModel]=\"value\"\n (ngModelChange)=\"filterCallback($event)\"\n [display]=\"'chip'\"\n [placeholder]=\"'Choose option'\"\n class=\"custom-multiselect\"\n ></p-multiSelect>\n </ng-template>\n </p-columnFilter>\n </ng-container>\n </div>\n </div>\n </th>\n </ng-container>\n <ng-template #noSortHeader>\n <th>\n <div class=\"header-container\">\n <span>{{ col.title }}</span>\n <ng-container *ngIf=\"col.isFilter !== false\">\n <p-columnFilter\n *ngIf=\"col.type === 'AMOUNT'\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [currency]=\"getCurrencySymbol(col)\"\n ></p-columnFilter>\n\n <p-columnFilter\n *ngIf=\"col.type !== 'AMOUNT'\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n >\n <ng-template\n pTemplate=\"filter\"\n let-value\n let-filterCallback=\"filterCallback\"\n *ngIf=\"getColumnFilterType(col) === 'date'\"\n >\n <p-calendar\n [ngModel]=\"value\"\n (ngModelChange)=\"filterCallback($event)\"\n [dateFormat]=\"'dd/mm/yy'\"\n ></p-calendar>\n </ng-template>\n\n <ng-template\n pTemplate=\"filter\"\n let-value\n let-filterCallback=\"filterCallback\"\n *ngIf=\"getColumnFilterType(col) === 'multiSelect'\"\n >\n <p-multiSelect\n [options]=\"col.filterOptions\"\n [ngModel]=\"value\"\n (ngModelChange)=\"filterCallback($event)\"\n [display]=\"'chip'\"\n placeholder=\"Select\"\n class=\"custom-multiselect\"\n ></p-multiSelect>\n </ng-template>\n </p-columnFilter>\n </ng-container>\n </div>\n </th>\n </ng-template>\n </th>\n <!-- Grouped headers -->\n <ng-template #groupHeader>\n <th\n [attr.colspan]=\"col.children?.length\"\n [style.width]=\"getHeaderWidth(col)\"\n [style.text-align]=\"'center'\"\n >\n <span>{{ col.title }}</span>\n </th>\n </ng-template>\n </ng-container>\n </tr>\n <!-- Child headers (Second Row) -->\n <tr *ngIf=\"hasGroupedColumns\">\n <ng-container *ngFor=\"let col of columns\">\n <ng-container *ngIf=\"col.children\">\n <th\n *ngFor=\"let child of col.children\"\n [style.width]=\"getHeaderWidth(child)\"\n [style.padding]=\"'0px'\"\n >\n <!-- Sortable/Filterable header logic for child columns -->\n </th>\n </ng-container>\n </ng-container>\n </tr>\n </ng-template>\n\n <!-- Empty message template -->\n <ng-template pTemplate=\"emptymessage\">\n <div class=\"empty-message\">\n <i class=\"pi pi-info-circle\"></i>\n <p>No records available to display.</p>\n </div>\n </ng-template>\n\n <!-- Body -->\n <ng-template\n pTemplate=\"body\"\n let-data\n let-editing=\"editing\"\n let-ri=\"rowIndex\"\n >\n <!-- Render a table row and make it editable if `isEdit` is true -->\n <tr *ngIf=\"!loading\" [pEditableRow]=\"isEdit ? data : null\">\n <!-- Loop through each column -->\n <ng-container *ngFor=\"let col of columns\">\n <!-- Check if the column has children -->\n <ng-container *ngIf=\"!col.children; else childColumns\">\n <!-- Render a single cell for columns without children -->\n <ng-container\n *ngIf=\"col.code !== undefined && data[col.code] !== undefined\"\n >\n <td\n *ngIf=\"isEditable(col.code); else normalTD\"\n [style.width]=\"getHeaderWidth(col)\"\n >\n <!-- Editable input for the column -->\n <ng-container *ngIf=\"isMultiSelect(col.code); else datePicker\">\n <p-cellEditor>\n <ng-template pTemplate=\"input\">\n <p-multiSelect\n appendTo=\"body\"\n [ngModel]=\"data[col.code]\"\n [style]=\"{ width: '100%' }\"\n (ngModelChange)=\"\n changeHandler(data.id, col.code, $event)\n \"\n [options]=\"optionValues\"\n ></p-multiSelect>\n </ng-template>\n <ng-template pTemplate=\"output\">\n <div class=\"multi-select-container\">\n <ng-container *ngFor=\"let rec of data[col.code]\">\n <p-tag [value]=\"rec\"></p-tag>\n </ng-container>\n </div>\n </ng-template>\n </p-cellEditor>\n </ng-container>\n\n <ng-template #datePicker>\n <ng-container\n *ngIf=\"isDatePicker(col.code); else normalInput\"\n >\n <p-cellEditor>\n <ng-template pTemplate=\"input\">\n <p-calendar\n [inputId]=\"data[col.code]\"\n [ngModel]=\"data[col.code]\"\n (ngModelChange)=\"\n changeHandler(data.id, col.code, $event)\n \"\n [dateFormat]=\"'dd/mm/yy'\"\n ></p-calendar>\n </ng-template>\n <ng-template pTemplate=\"output\">\n {{ data[col.code] | customDate }}\n </ng-template>\n </p-cellEditor>\n </ng-container>\n </ng-template>\n\n <ng-template #normalInput>\n <p-cellEditor>\n <ng-template pTemplate=\"input\">\n <input\n pInputText\n type=\"text\"\n [ngModel]=\"data[col.code]\"\n (change)=\"onChange($event, data.id, col.code)\"\n />\n </ng-template>\n <ng-template pTemplate=\"output\">\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.AMOUNT;\n else normalOutput\n \"\n >\n {{\n data[col.code]\n | customCurrency\n : getCurrencySymbol(col)\n : col.decimalPlaces\n : col.thousandSeparator\n : col.decimalSeparator\n }}\n </ng-container>\n <ng-template #normalOutput>\n {{ data[col.code] }}\n </ng-template>\n </ng-template>\n </p-cellEditor>\n </ng-template>\n </td>\n\n <ng-template #normalTD>\n <td [style.width]=\"getHeaderWidth(col)\">\n <!-- COMPOSED -->\n <ng-container *ngIf=\"col.type === TableTypeEnum.COMPOSED\">\n <div class=\"composed-cell\">\n <ng-container\n *ngFor=\"\n let composedName of col.composedNames;\n let i = index\n \"\n >\n <!-- Check if the composedType is IMAGE -->\n <ng-container\n *ngIf=\"\n col.composedTypes &&\n col.composedTypes[i] === TableTypeEnum.IMAGE\n \"\n >\n <img\n [src]=\"data[col.code][composedName]\"\n alt=\"composed-img\"\n class=\"composed-image\"\n [ngStyle]=\"getImageStyle(col.composedStyles?.[composedName])\"\n />\n </ng-container>\n\n <!-- Check if the composedType is STRING -->\n <ng-container\n *ngIf=\"\n col.composedTypes &&\n col.composedTypes[i] === TableTypeEnum.STRING\n \"\n >\n <span\n class=\"composed-text\"\n [ngStyle]=\"getTitleStyle(col.composedStyles?.[composedName])\"\n >\n {{ data[col.code][composedName] }}\n </span>\n </ng-container>\n </ng-container>\n </div>\n </ng-container>\n\n <!-- AMOUNT-->\n <ng-container *ngIf=\"col.type === TableTypeEnum.AMOUNT\">\n {{\n data[col.code]\n | customCurrency\n : getCurrencySymbol(col)\n : col.decimalPlaces\n : col.thousandSeparator\n : col.decimalSeparator\n }}\n </ng-container>\n\n <!-- NUMBER-->\n <ng-container *ngIf=\"col.type === TableTypeEnum.NUMBER\">\n {{\n formatNumber(\n data[col.code],\n col.decimalPlaces,\n col.thousandSeparator,\n col.decimalSeparator\n )\n }}\n </ng-container>\n\n <!-- DATE -->\n <ng-container *ngIf=\"col.type === TableTypeEnum.DATE\">\n <!-- Format the date using your formatDate method -->\n {{ formatDate(data[col.code]) }}\n </ng-container>\n\n <!-- STRING, MULTISELECT-->\n <ng-container\n *ngIf=\"\n [\n TableTypeEnum.STRING,\n TableTypeEnum.MULTISELECT\n ].includes(col.type!)\n \"\n >\n {{ data[col.code] }}\n </ng-container>\n </td>\n </ng-template>\n </ng-container>\n </ng-container>\n\n <!-- Render child columns if the column has children -->\n <ng-template #childColumns>\n <ng-container *ngFor=\"let child of col.children\">\n <ng-container\n *ngIf=\"\n child.code !== undefined && data[child.code] !== undefined\n \"\n >\n <td [style.width]=\"getHeaderWidth(child)\">\n <!-- Render editable or normal cells for child columns -->\n <ng-container\n *ngIf=\"isEditable(child.code); else childNormalTD\"\n >\n <p-cellEditor>\n <ng-template pTemplate=\"input\">\n <input\n pInputText\n type=\"text\"\n [ngModel]=\"data[child.code]\"\n (change)=\"onChange($event, data.id, child.code)\"\n />\n </ng-template>\n <ng-template pTemplate=\"output\">\n {{ data[child.code] }}\n </ng-template>\n </p-cellEditor>\n </ng-container>\n\n <ng-template #childNormalTD>\n {{ data[child.code] }}\n </ng-template>\n </td>\n </ng-container>\n </ng-container>\n </ng-template>\n </ng-container>\n\n <!-- Render action buttons if there are any actions defined -->\n <td *ngIf=\"actions?.length\">\n <div class=\"action-buttons-container\">\n <div *ngIf=\"isDelete\">\n <button\n pButton\n pRipple\n type=\"button\"\n icon=\"pi pi-trash\"\n (click)=\"Delete(data.id)\"\n class=\"p-button-rounded p-button-text\"\n ></button>\n </div>\n <div>\n <button\n pInitEditableRow\n *ngIf=\"!editing\"\n pButton\n pRipple\n type=\"button\"\n icon=\"pi pi-pencil\"\n (click)=\"initEditableRow(data)\"\n class=\"p-button-rounded p-button-text\"\n ></button>\n <button\n *ngIf=\"editing\"\n pSaveEditableRow\n pButton\n pRipple\n type=\"button\"\n icon=\"pi pi-check\"\n (click)=\"saveEditableRow(data)\"\n class=\"p-button-rounded p-button-text\"\n ></button>\n <button\n *ngIf=\"editing\"\n pCancelEditableRow\n pButton\n pRipple\n type=\"button\"\n icon=\"pi pi-times\"\n (click)=\"cancelEditableRow(data)\"\n class=\"p-button-rounded p-button-text\"\n ></button>\n </div>\n </div>\n </td>\n </tr>\n </ng-template>\n </p-table>\n</div>\n", styles: [".pt-advanced-prime-table .bread-crumb{margin-bottom:15px}.pt-advanced-prime-table .date{width:100%;height:5rem;display:grid;justify-items:start;align-items:center}.pt-advanced-prime-table .filter-container{width:100%;display:flex;justify-content:space-between;align-items:center}.pt-advanced-prime-table .settings{display:flex;gap:1rem}.pt-advanced-prime-table .multi-select-container{display:flex;justify-content:center;align-items:center;gap:.3rem}.pt-advanced-prime-table ::ng-deep p-table{min-width:50rem}.pt-advanced-prime-table ::ng-deep .custom-multiselect .p-hidden-accessible input{display:none}.pt-advanced-prime-table ::ng-deep .p-datatable .p-sortable-column.p-highlight:hover{background:none}.pt-advanced-prime-table ::ng-deep .p-datatable .p-sortable-column:focus{box-shadow:none;outline:0 none}.pt-advanced-prime-table ::ng-deep .header-container{display:flex;justify-content:space-between;align-items:center;width:100%}.pt-advanced-prime-table ::ng-deep p-columnfilter.p-element.ng-star-inserted{margin-top:4px}.pt-advanced-prime-table .flex{display:flex;justify-content:space-between;align-items:center}.pt-advanced-prime-table .ml-auto{margin-left:auto}.pt-advanced-prime-table ::ng-deep p-inputicon{margin-right:-1.5rem;z-index:2;position:relative}.pt-advanced-prime-table ::ng-deep .p-inputtext{padding-left:1.7rem}.pt-advanced-prime-table ::ng-deep .bt-filter-btn button{cursor:pointer;margin-left:1rem}.pt-advanced-prime-table ::ng-deep .p-icon-field-left .p-input-icon:first-of-type{left:-1rem}.pt-advanced-prime-table .table-row{text-align:center;display:flex;gap:1rem;justify-content:center}.pt-advanced-prime-table ::ng-deep span.p-button-icon.pi.pi-file-excel{font-size:1.25em;color:green}.pt-advanced-prime-table ::ng-deep span.p-button-icon.pi.pi-file-pdf{font-size:1.25em;color:red}.pt-advanced-prime-table .table-container{display:flex;flex-direction:column;height:100%;overflow:hidden}.pt-advanced-prime-table ::ng-deep .p-datatable{display:flex;flex-direction:column;width:100%;border-collapse:collapse;table-layout:fixed}.pt-advanced-prime-table ::ng-deep .p-datatable thead{display:table;width:100%;table-layout:fixed;background:#fff;z-index:2}.pt-advanced-prime-table ::ng-deep .p-datatable tfoot{display:table;width:100%;table-layout:fixed;background:#fff;z-index:2}.pt-advanced-prime-table ::ng-deep .p-datatable tbody{display:block;overflow-y:auto;overflow-x:hidden}.pt-advanced-prime-table ::ng-deep .p-datatable tbody tr{display:table;width:100%;table-layout:fixed}.pt-advanced-prime-table .empty-message{text-align:center;padding:2rem;color:#888;font-size:1.2rem}.pt-advanced-prime-table .empty-message i{display:block;font-size:2rem;margin-bottom:.5rem}.pt-advanced-prime-table th{white-space:normal;word-wrap:break-word}.filter-image{width:22px;height:14px;margin-right:5px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i2.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "style", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "scrollDirection", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "responsive", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "autoLayout", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll", "virtualRowHeight"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i2.SortableColumn, selector: "[pSortableColumn]", inputs: ["pSortableColumn", "pSortableColumnDisabled"] }, { kind: "component", type: i2.CellEditor, selector: "p-cellEditor" }, { kind: "component", type: i2.SortIcon, selector: "p-sortIcon", inputs: ["field"] }, { kind: "directive", type: i2.EditableRow, selector: "[pEditableRow]", inputs: ["pEditableRow", "pEditableRowDisabled"] }, { kind: "directive", type: i2.InitEditableRow, selector: "[pInitEditableRow]" }, { kind: "directive", type: i2.SaveEditableRow, selector: "[pSaveEditableRow]" }, { kind: "directive", type: i2.CancelEditableRow, selector: "[pCancelEditableRow]" }, { kind: "component", type: i2.ColumnFilter, selector: "p-columnFilter", inputs: ["field", "type", "display", "showMenu", "matchMode", "operator", "showOperator", "showClearButton", "showApplyButton", "showMatchModes", "showAddButton", "hideOnClear", "placeholder", "matchModeOptions", "maxConstraints", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "locale", "localeMatcher", "currency", "currencyDisplay", "useGrouping", "showButtons", "ariaLabel"], outputs: ["onShow", "onHide"] }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["variant"] }, { kind: "directive", type: i5.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "lo