ng-prime-tools
Version:
An advanced PrimeNG table for Angular
448 lines • 161 kB
JavaScript
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