UNPKG

ng-prime-tools

Version:

An advanced PrimeNG table for Angular

1 lines 412 kB
{"version":3,"file":"ng-prime-tools.mjs","sources":["../../../projects/ng-prime-tools/src/lib/utils/text.util.ts","../../../projects/ng-prime-tools/src/lib/pipes/custom-currency.ts","../../../projects/ng-prime-tools/src/lib/pipes/custom-date.pipe.ts","../../../projects/ng-prime-tools/src/lib/pt-advanced-prime-table/pt-advanced-prime-table.component.ts","../../../projects/ng-prime-tools/src/lib/pt-advanced-prime-table/pt-advanced-prime-table.component.html","../../../projects/ng-prime-tools/src/lib/pt-multi-select/pt-multi-select.component.ts","../../../projects/ng-prime-tools/src/lib/pt-multi-select/pt-multi-select.component.html","../../../projects/ng-prime-tools/src/lib/pt-multi-select/pt-multi-select.module.ts","../../../projects/ng-prime-tools/src/lib/pt-advanced-prime-table/pt-advanced-prime-table.module.ts","../../../projects/ng-prime-tools/src/lib/pt-advanced-prime-table/date-utility.service.ts","../../../projects/ng-prime-tools/src/lib/enums/table-type.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/search-criteria-type.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/form-input-type.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/button-color.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/input-validation.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/badge-type.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/severity.enum.ts","../../../projects/ng-prime-tools/src/lib/enums/public-api.ts","../../../projects/ng-prime-tools/src/lib/multi-search-criteria/multi-search-criteria.component.ts","../../../projects/ng-prime-tools/src/lib/multi-search-criteria/multi-search-criteria.component.html","../../../projects/ng-prime-tools/src/lib/multi-search-criteria/multi-search-criteria.module.ts","../../../projects/ng-prime-tools/src/lib/pt-check-box-input/pt-check-box-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-check-box-input/pt-check-box-input.component.html","../../../projects/ng-prime-tools/src/lib/utils/date.util.ts","../../../projects/ng-prime-tools/src/lib/pt-date-input/pt-date.service.ts","../../../projects/ng-prime-tools/src/lib/pt-date-input/pt-date-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-date-input/pt-date-input.component.html","../../../projects/ng-prime-tools/src/lib/pt-number-input/pt-number-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-number-input/pt-number-input.component.html","../../../projects/ng-prime-tools/src/lib/pt-switch-input/pt-switch-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-switch-input/pt-switch-input.component.html","../../../projects/ng-prime-tools/src/lib/pt-text-area-input/pt-text-area-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-text-area-input/pt-text-area-input.component.html","../../../projects/ng-prime-tools/src/lib/pt-text-input/pt-text-input.component.ts","../../../projects/ng-prime-tools/src/lib/pt-text-input/pt-text-input.component.html","../../../projects/ng-prime-tools/src/lib/pt-dropdown/pt-dropdown.component.ts","../../../projects/ng-prime-tools/src/lib/pt-dropdown/pt-dropdown.component.html","../../../projects/ng-prime-tools/src/lib/pt-form-builder/pt-dynamic-form-field/pt-dynamic-form-field.component.ts","../../../projects/ng-prime-tools/src/lib/pt-form-builder/pt-dynamic-form-field/pt-dynamic-form-field.component.html","../../../projects/ng-prime-tools/src/lib/pt-form-builder/pt-form-builder.component.ts","../../../projects/ng-prime-tools/src/lib/pt-form-builder/pt-form-builder.component.html","../../../projects/ng-prime-tools/src/lib/pt-check-box-input/pt-check-box-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-number-input/pt-number-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-switch-input/pt-switch-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-text-input/pt-text-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-text-area-input/pt-text-area-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-date-input/pt-date-input.module.ts","../../../projects/ng-prime-tools/src/lib/pt-dropdown/pt-dropdown.module.ts","../../../projects/ng-prime-tools/src/lib/pt-form-builder/pt-dynamic-form-field/pt-dynamic-form-field-module.ts","../../../projects/ng-prime-tools/src/lib/pt-form-builder/pt-form-builder.module.ts","../../../projects/ng-prime-tools/src/lib/pt-metric-card/pt-metric-card.component.ts","../../../projects/ng-prime-tools/src/lib/pt-metric-card/pt-metric-card.component.html","../../../projects/ng-prime-tools/src/lib/pt-metric-card/pt-metric-card.module.ts","../../../projects/ng-prime-tools/src/lib/pt-metric-card-group/pt-metric-card-group.component.ts","../../../projects/ng-prime-tools/src/lib/pt-metric-card-group/pt-metric-card-group.component.html","../../../projects/ng-prime-tools/src/lib/pt-metric-card-group/pt-metric-card-group.module.ts","../../../projects/ng-prime-tools/src/lib/pt-chart/pt-chart.component.ts","../../../projects/ng-prime-tools/src/lib/pt-chart/pt-chart.component.html","../../../projects/ng-prime-tools/src/lib/pt-chart/pt-chart.module.ts","../../../projects/ng-prime-tools/src/lib/pt-menu/pt-menu.component.ts","../../../projects/ng-prime-tools/src/lib/pt-menu/pt-menu.component.html","../../../projects/ng-prime-tools/src/lib/pt-card/pt-card.component.ts","../../../projects/ng-prime-tools/src/lib/pt-card/pt-card.component.html","../../../projects/ng-prime-tools/src/lib/pt-menu/pt-menu.module.ts","../../../projects/ng-prime-tools/src/lib/pt-card/pt-card.module.ts","../../../projects/ng-prime-tools/src/lib/pt-menu-fancy/pt-menu-fancy.component.ts","../../../projects/ng-prime-tools/src/lib/pt-menu-fancy/pt-menu-fancy.component.html","../../../projects/ng-prime-tools/src/lib/pt-nav-bar-menu/pt-nav-bar-menu.component.ts","../../../projects/ng-prime-tools/src/lib/pt-nav-bar-menu/pt-nav-bar-menu.component.html","../../../projects/ng-prime-tools/src/lib/pt-menu-fancy/pt-menu-fancy.module.ts","../../../projects/ng-prime-tools/src/lib/pt-nav-bar-menu/pt-nav-bar-menu.module.ts","../../../projects/ng-prime-tools/src/lib/pt-side-bar-menu/pt-side-bar-menu.component.ts","../../../projects/ng-prime-tools/src/lib/pt-side-bar-menu/pt-side-bar-menu.component.html","../../../projects/ng-prime-tools/src/lib/pt-side-bar-menu/pt-side-bar-menu.module.ts","../../../projects/ng-prime-tools/src/lib/pt-footer/pt-footer.component.ts","../../../projects/ng-prime-tools/src/lib/pt-footer/pt-footer.component.html","../../../projects/ng-prime-tools/src/lib/pt-bread-crumb/pt-bread-crumb.service.ts","../../../projects/ng-prime-tools/src/lib/pt-bread-crumb/pt-bread-crumb.component.ts","../../../projects/ng-prime-tools/src/lib/pt-bread-crumb/pt-bread-crumb.component.html","../../../projects/ng-prime-tools/src/lib/pt-page-skeleton/pt-page-skeleton.component.ts","../../../projects/ng-prime-tools/src/lib/pt-page-skeleton/pt-page-skeleton.component.html","../../../projects/ng-prime-tools/src/lib/pt-footer/pt-footer.module.ts","../../../projects/ng-prime-tools/src/lib/pt-bread-crumb/pt-bread-crumb.module.ts","../../../projects/ng-prime-tools/src/lib/pt-page-skeleton/pt-page-skeleton.module.ts","../../../projects/ng-prime-tools/src/lib/pt-button/pt-button.component.ts","../../../projects/ng-prime-tools/src/lib/pt-button/pt-button.component.html","../../../projects/ng-prime-tools/src/lib/pt-button/pt-button.module.ts","../../../projects/ng-prime-tools/src/lib/pt-login-page/pt-login-card/pt-login-card.component.ts","../../../projects/ng-prime-tools/src/lib/pt-login-page/pt-login-card/pt-login-card.component.html","../../../projects/ng-prime-tools/src/lib/pt-login-page/pt-login-page.component.ts","../../../projects/ng-prime-tools/src/lib/pt-login-page/pt-login-page.component.html","../../../projects/ng-prime-tools/src/lib/pt-login-page/pt-login-page.module.ts","../../../projects/ng-prime-tools/src/lib/pt-dialog/pt-dialog.component.ts","../../../projects/ng-prime-tools/src/lib/pt-dialog/pt-dialog.component.html","../../../projects/ng-prime-tools/src/lib/pt-dialog/pt-dialog.module.ts","../../../projects/ng-prime-tools/src/lib/pt-toast-notifier/pt-toast-notifier.component.ts","../../../projects/ng-prime-tools/src/lib/pt-toast-notifier/pt-toast-notifier.component.html","../../../projects/ng-prime-tools/src/lib/pt-toast-notifier/pt-toast-notifier.module.ts","../../../projects/ng-prime-tools/src/lib/ng-prime-tools.module.ts","../../../projects/ng-prime-tools/src/lib/pt-metric-panel/pt-metric-panel.component.ts","../../../projects/ng-prime-tools/src/lib/pt-metric-panel/pt-metric-panel.component.html","../../../projects/ng-prime-tools/src/lib/pt-metric-panel/pt-metric-panel.module.ts","../../../projects/ng-prime-tools/src/lib/pt-chart-comparison/pt-chart-comparison.component.ts","../../../projects/ng-prime-tools/src/lib/pt-chart-comparison/pt-chart-comparison.component.html","../../../projects/ng-prime-tools/src/lib/pt-chart-comparison/pt-chart-comparison.module.ts","../../../projects/ng-prime-tools/src/lib/pt-line-chart/pt-line-chart.component.ts","../../../projects/ng-prime-tools/src/lib/pt-line-chart/pt-line-chart.component.html","../../../projects/ng-prime-tools/src/lib/pt-line-chart/pt-line-chart.module.ts","../../../projects/ng-prime-tools/src/lib/models/public-api.ts","../../../projects/ng-prime-tools/src/lib/pt-group/pt-group.component.ts","../../../projects/ng-prime-tools/src/lib/pt-group/pt-group.component.html","../../../projects/ng-prime-tools/src/lib/pt-group/pt-group.module.ts","../../../projects/ng-prime-tools/src/ng-prime-tools.ts"],"sourcesContent":["import { TableColumn } from '../models';\n\n/**\n * Calculates the width required for a column based on the header text (column title).\n * It uses the Canvas API to measure text width dynamically.\n *\n * @param {TableColumn} col - The column metadata containing the header title and code.\n * @param {string} [font='16px Arial'] - The font to use for measurement (defaults to '16px Arial').\n * @returns {number} - The calculated width of the column in pixels.\n */\nexport function calculateTextWidth(\n col: TableColumn,\n font: string = '16px Arial'\n): number {\n // Create a canvas context for measuring text\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n\n if (!context) {\n return 100; // Fallback width if canvas context is not available\n }\n\n // Set the font to match the provided font or the document body font\n context.font = font || getComputedStyle(document.body).font;\n\n // Measure the header text width\n const headerWidth = context.measureText(col.title).width;\n\n // Return the width with some padding\n return Math.ceil(headerWidth + 20); // Add padding for extra space\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n name: 'customCurrency',\n standalone: true,\n})\nexport class CustomCurrencyPipe implements PipeTransform {\n transform(\n value: number,\n currency?: string,\n decimalPlaces?: number,\n thousandSeparator: 'comma' | 'space' = 'comma',\n decimalSeparator: 'comma' | 'dot' = 'dot'\n ): string {\n let formattedValue: string;\n\n if (decimalPlaces !== undefined) {\n formattedValue = value.toFixed(decimalPlaces);\n } else {\n formattedValue = value.toString();\n }\n\n const thousandSeparatorChar = thousandSeparator === 'space' ? ' ' : ',';\n const decimalSeparatorChar = decimalSeparator === 'comma' ? ',' : '.';\n\n formattedValue = formattedValue.replace(\n /\\B(?=(\\d{3})+(?!\\d))/g,\n thousandSeparatorChar\n );\n\n if (decimalSeparatorChar === ',') {\n formattedValue = formattedValue.replace('.', ',');\n }\n\n if (currency) {\n let formattedCurrency: string;\n\n switch (currency) {\n case 'MAD':\n formattedCurrency = `${formattedValue} DH`;\n break;\n case 'USD':\n formattedCurrency = `$${formattedValue}`;\n break;\n default:\n formattedCurrency = `${formattedValue} ${currency}`;\n }\n\n return formattedCurrency;\n }\n\n return formattedValue;\n }\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n name: 'customDate',\n})\nexport class CustomDatePipe implements PipeTransform {\n transform(value: any, format: string = 'dd/MM/yyyy'): string | null {\n if (!value) return null;\n\n if (typeof value === 'string') {\n const parts = value.split('/');\n if (parts.length === 3) {\n const day = parseInt(parts[0], 10);\n const month = parseInt(parts[1], 10) - 1;\n const year = parseInt(parts[2], 10);\n const date = new Date(year, month, day);\n\n if (isNaN(date.getTime())) return null;\n\n const options: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n };\n return new Intl.DateTimeFormat('en-GB', options).format(date);\n }\n } else if (value instanceof Date) {\n // If the value is already a Date object\n const options: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n };\n return new Intl.DateTimeFormat('en-GB', options).format(value);\n }\n\n return null;\n }\n}\n","import { Component, Input, OnInit, Output, ViewChild } from '@angular/core';\nimport { EventEmitter } from '@angular/core';\nimport { Table } from 'primeng/table';\nimport {\n ImageStyle,\n TableColumn,\n TableTypeEnum,\n TitleStyle,\n} from '../../public-api';\nimport { calculateTextWidth } from '../utils/text.util';\n\n@Component({\n selector: 'pt-advanced-prime-table',\n templateUrl: './pt-advanced-prime-table.component.html',\n styleUrl: './pt-advanced-prime-table.component.css',\n})\nexport class PTAdvancedPrimeTableComponent implements OnInit {\n // Inputs\n @Input() data: any[] = [];\n @Input() columns: TableColumn[] = [];\n @Input() totalRecords: number = 0;\n @Input() rowsPerPage: number[] = [];\n @Input() hasSearchFilter: boolean = false;\n @Input() hasExportExcel: boolean = false;\n @Input() hasExportPDF: boolean = false;\n @Input() hasColumnFilter: boolean = false;\n @Input() isPaginated: boolean = false;\n @Input() actions: any[] = [];\n @Input() isSortable: boolean = false;\n @Input() loading: boolean = false;\n @Input() maxHeight: string | null = null;\n\n // Outputs: Events emitted to the parent component\n @Output() filter = new EventEmitter();\n @Output() search = new EventEmitter<any>();\n @Output() exportExcelEvent = new EventEmitter<void>();\n @Output() exportPdfEvent = new EventEmitter<void>();\n\n @ViewChild('dt', { static: false }) dt!: Table;\n\n public TableTypeEnum = TableTypeEnum;\n searchValue: string = '';\n\n public filters: { [key: string]: any } = {};\n\n private validCurrencyCodes = ['USD', 'EUR', 'MAD'];\n iconWidth = 77;\n\n // Component state properties\n isDelete: boolean = false;\n isEdit: boolean = false;\n rows: number = 0;\n\n // Data management properties\n dataMap = new Map();\n map = new Map();\n optionEntries = new Map<string, any[]>();\n optionValues: any[] = [];\n globalFilterFields: string[] = [];\n\n // CRUD operation handlers\n Delete: (value: any) => void = () => {};\n initEditableRow: (data: any) => void = () => {};\n saveEditableRow: (data: any) => void = () => {};\n cancelEditableRow: (item: any) => void = () => {};\n hasGroupedColumns: boolean = false;\n constructor() {}\n\n ngOnInit(): void {\n this.hasGroupedColumns = this.columns.some(\n (col) => col.children && col.children.length > 0\n );\n\n this.globalFilterFields = this.columns\n .filter((col) => col.code !== undefined && col.isFilter !== false)\n .map((col) => col.code as string);\n this.initializePagination();\n this.initializeActions();\n\n // Set default value for isSortable\n this.columns.forEach((col) => {\n if (col.type === TableTypeEnum.ACTION) {\n col.isEditable = false;\n col.isFilter = false;\n col.isSortable = false;\n }\n\n if (col.type === TableTypeEnum.COMPOSED) {\n this.initializeComposedFilters(col);\n }\n\n if (col.isSortable === undefined) {\n col.isSortable = true;\n }\n\n if (col.isEditable === undefined) {\n col.isEditable = true;\n }\n\n if (col.isFilter !== false && col.code !== undefined) {\n this.globalFilterFields.push(col.code);\n }\n\n if (!col.width) {\n col.width = this.calculateColumnWidth(col);\n }\n });\n }\n\n // Initialize filters for composed columns\n private initializeComposedFilters(col: TableColumn): void {\n col.composedNames?.forEach((composedName) => {\n this.globalFilterFields.push(col.code + '.' + composedName);\n this.filters[composedName] = {\n options: col.filterOptions,\n value: [],\n label: 'Filter by ' + composedName,\n placeholder: 'Select options for ' + composedName,\n };\n });\n }\n\n // Get the column type for composed fields (STRING, IMAGE, etc.)\n getComposedFieldType(\n col: TableColumn,\n composedName: string\n ): TableTypeEnum | undefined {\n // Ensure that col.composedNames and col.composedTypes are valid arrays\n if (col.composedNames && col.composedTypes) {\n const index = col.composedNames.indexOf(composedName);\n\n // Check if index is a valid number (not -1) and within bounds of composedTypes array\n if (index >= 0 && index < col.composedTypes.length) {\n return col.composedTypes[index]; // Safe access of composedTypes array\n }\n }\n\n return undefined; // Return undefined if no valid index is found or composedNames/composedTypes are not valid\n }\n\n onComposedFilterChange(composedName: string, selectedValues: any): void {\n console.log('Selected Values:', selectedValues);\n console.log('Data Before Filtering:', this.data);\n\n // Update the filter value with the selected values\n this.filters[composedName].value = selectedValues;\n\n // Emit the filter event to notify the parent component (if needed)\n this.filter.emit(this.filters);\n\n // Get the filter value (joining array into a string)\n const filterValue = selectedValues.join(',');\n console.log('Filter Values to be Applied:', filterValue);\n\n // Apply global filter using PrimeNG's filterGlobal method\n this.dt.filterGlobal(filterValue, 'contains');\n }\n\n onFilter(event: any): void {\n this.totalRecords = event.filteredValue?.length || 0;\n }\n\n onCalendarFilterChange(\n event: any,\n columnCode: string,\n filterCallback: any\n ): void {\n // Log the data's date format for debugging\n console.log(\n 'Data Before Filtering:',\n this.data.map((item) => item[columnCode])\n );\n\n console.log('event : ' + event);\n\n // Convert the event value to a string (in the desired date format)\n\n const filterValue = event ? new Date(event) : null;\n\n console.log('filterValue : ' + filterValue);\n // If the filterValue is empty, do not trigger filterCallback\n if (!filterValue) {\n return;\n }\n\n // Manually trigger the filterCallback after updating the value (passing the string value)\n filterCallback(filterValue);\n\n const filterValueString = event ? this.formatDate(event) : '';\n // Call the onFilter event to update totalRecords\n this.onFilter({\n filteredValue: this.data.filter((item) => {\n const columnValue = item[columnCode];\n\n // If the column value is a string, use it as is for comparison\n if (columnValue) {\n // Convert the item value to a string (in the same format)\n const itemDateString = this.formatDate(new Date(columnValue));\n return itemDateString === filterValueString; // Compare the string dates\n }\n return false;\n }),\n });\n }\n\n // Filter logic for composed columns (to check against multi-select values)\n filterComposedData(item: any, composedName: string, value: any): boolean {\n if (Array.isArray(value) && value.length > 0) {\n return value.some((filterValue: string) =>\n item[composedName]?.toLowerCase().includes(filterValue.toLowerCase())\n );\n }\n return true;\n }\n\n // Function to calculate column width based on text in header and data\n private calculateColumnWidth(col: TableColumn): string {\n const calculatedWidth = calculateTextWidth(col, col.title);\n const totalWidth = calculatedWidth + this.iconWidth + 20;\n return `${totalWidth}px`;\n }\n\n getHeaderWidth(col: TableColumn): string {\n // Remove 'px' from col.width and convert it to a number\n const widthWithoutPx = parseInt(col.width?.replace('px', '') || '0', 10);\n\n // Add 20 to the calculated width\n const headerWidth = widthWithoutPx + 20;\n\n // Return the new width in 'px'\n return `${headerWidth}px`;\n }\n\n clear(table: Table) {\n table.clear();\n this.searchValue = '';\n }\n\n private parseDate(dateString: string): Date | null {\n const parts = dateString.split('/');\n if (parts.length === 3) {\n // Assuming date format is DD/MM/YYYY\n const day = parseInt(parts[0], 10);\n const month = parseInt(parts[1], 10) - 1;\n const year = parseInt(parts[2], 10);\n const date = new Date(year, month, day);\n return isNaN(date.getTime()) ? null : date;\n }\n return null;\n }\n\n private initializePagination(): void {\n if (this.isPaginated) {\n // Check if rowsPerPage is undefined or an empty array\n if (!this.rowsPerPage || this.rowsPerPage.length === 0) {\n this.rowsPerPage = [20, 30, 40];\n }\n this.rows = this.rowsPerPage[0];\n }\n }\n\n private initializeActions(): void {\n if (this.actions) {\n this.actions.forEach((action) => {\n switch (action.code) {\n case 'delete':\n this.isDelete = true;\n this.Delete = (value: any) => action.action(value);\n break;\n case 'edit':\n this.initializeEditActions(action);\n break;\n default:\n this.isDelete = false;\n this.isEdit = false;\n }\n });\n }\n }\n\n private initializeEditActions(action: any): void {\n this.isEdit = true;\n this.initEditableRow = (data: any) => action.action.init(data);\n this.saveEditableRow = (data: any) => {\n const record = this.map.get(data.id);\n action.action.save(data, record);\n this.dataMap.clear();\n };\n this.cancelEditableRow = (item: any) => console.log(item);\n }\n\n onChange(event: Event, id: number, key: any) {\n const target = event.target as HTMLInputElement;\n this.changeHandler(id, key, target.value);\n }\n\n changeHandler(id: number, key: any, value: any) {\n let column = this.columns.find((item) => item.code === key);\n if (!this.map.get(id)) {\n if (column?.type === TableTypeEnum.DATE) {\n let date = this.parseDate(value);\n this.dataMap.set(key, date);\n } else {\n this.dataMap.set(key, value);\n }\n this.map.set(id, new Map(this.dataMap));\n } else {\n let mapItem = this.map.get(id);\n if (column?.type === TableTypeEnum.DATE) {\n let date = this.parseDate(value);\n mapItem.set(key, date);\n } else {\n mapItem.set(key, value);\n }\n }\n }\n\n getColumnFilterType(column: TableColumn): string {\n switch (column.type) {\n case TableTypeEnum.STRING:\n return 'text';\n case TableTypeEnum.AMOUNT:\n case TableTypeEnum.NUMBER:\n return 'numeric';\n case TableTypeEnum.DATE:\n return 'date';\n case TableTypeEnum.MULTISELECT:\n return 'multiSelect';\n case TableTypeEnum.BOOLEAN:\n return 'boolean';\n case TableTypeEnum.COMPOSED:\n return 'composed';\n default:\n return 'text';\n }\n }\n\n // State Check Methods\n isEditable(key: string): boolean {\n let column = this.columns.find((item) => item.code === key);\n return column?.isEditable !== false;\n }\n\n isMultiSelect(key: any): boolean {\n let column = this.columns.find((item) => item.code === key);\n if (\n column?.type === TableTypeEnum.MULTISELECT &&\n column.options &&\n column.code !== undefined\n ) {\n this.optionEntries = new Map([\n [column.code, Object.values(column.options)],\n ]);\n this.optionValues = this.optionEntries.get(key) || [];\n return true;\n }\n return false;\n }\n\n isDatePicker(key: any): boolean {\n return (\n this.columns.find((item) => item.code === key)?.type ===\n TableTypeEnum.DATE\n );\n }\n\n // Utility Methods\n dateConverter(value: any): string {\n return new Date(value).toLocaleDateString('en-US');\n }\n\n getCurrencySymbol(column: TableColumn): string | undefined {\n return column.type === TableTypeEnum.AMOUNT &&\n column.currency &&\n this.isValidCurrencyCode(column.currency)\n ? column.currency\n : undefined;\n }\n\n private isValidCurrencyCode(currencyCode: string): boolean {\n return this.validCurrencyCodes.includes(currencyCode);\n }\n\n filterGlobal(event: Event): void {\n const target = event.target as HTMLInputElement;\n const value = target.value.toLowerCase();\n\n // Create a new filtered dataset\n const filteredData = this.data.filter((item) => {\n return this.globalFilterFields.some((field) => {\n const column = this.columns.find((col) => col.code === field);\n if (!column) {\n return false;\n }\n\n // Handle different column types\n if (column.type === TableTypeEnum.DATE) {\n const itemDate = this.formatDate(item[field]);\n return itemDate && itemDate.includes(value);\n } else if (\n column.type === TableTypeEnum.AMOUNT ||\n column.type === TableTypeEnum.NUMBER\n ) {\n return (\n item[field] && item[field].toString().toLowerCase().includes(value)\n );\n } else if (column.type === TableTypeEnum.COMPOSED) {\n // Handle composed type by searching for text in the composed cells\n return this.filterComposedColumn(item[field], value);\n } else {\n return (\n item[field] && item[field].toString().toLowerCase().includes(value)\n );\n }\n });\n });\n\n // Update the table's value\n this.dt.value = filteredData;\n\n // After filtering, update the totalRecords\n this.totalRecords = filteredData.length ?? 0;\n }\n\n private filterComposedColumn(composedData: any, value: string): boolean {\n if (composedData) {\n // Iterate over composed keys and check if any key's value contains the search text\n return Object.keys(composedData).some((key) => {\n const cellValue = composedData[key];\n if (typeof cellValue === 'string') {\n return cellValue.toLowerCase().includes(value);\n }\n return false;\n });\n }\n return false;\n }\n\n formatDate(date: any): string {\n if (!date) return '';\n if (date instanceof Date) {\n // Handle Date object\n const day = date.getDate().toString().padStart(2, '0');\n const month = (date.getMonth() + 1).toString().padStart(2, '0');\n const year = date.getFullYear().toString();\n return `${day}/${month}/${year}`;\n } else if (typeof date === 'string') {\n // Handle string date\n const parts = date.split('/');\n if (parts.length === 3) {\n return `${parts[0]}/${parts[1]}/${parts[2]}`;\n } else {\n // Handle partial dates\n return date;\n }\n }\n return '';\n }\n\n exportExcel() {\n this.exportExcelEvent.emit();\n }\n\n exportPdf() {\n this.exportPdfEvent.emit();\n }\n\n getImageStyle(style: TitleStyle | ImageStyle | undefined): {\n [key: string]: string;\n } {\n if (style) {\n const imageStyle: { [key: string]: string } = {\n width: (style as ImageStyle).width || 'auto',\n height: (style as ImageStyle).height || 'auto',\n };\n\n // Fallback to empty string if margin, marginLeft, or marginRight are undefined\n if ((style as ImageStyle).margin) {\n imageStyle.margin = (style as ImageStyle).margin!;\n }\n\n if ((style as ImageStyle).marginLeft) {\n imageStyle.marginLeft = (style as ImageStyle).marginLeft!;\n }\n\n if ((style as ImageStyle).marginRight) {\n imageStyle.marginRight = (style as ImageStyle).marginRight!;\n }\n\n if ((style as ImageStyle).marginTop) {\n imageStyle.marginTop = (style as ImageStyle).marginTop!;\n }\n\n if ((style as ImageStyle).marginBottom) {\n imageStyle.marginBottom = (style as ImageStyle).marginBottom!;\n }\n\n return imageStyle;\n }\n\n return {};\n }\n\n getTitleStyle(style: TitleStyle | ImageStyle | undefined): {\n [key: string]: string;\n } {\n if (style) {\n return {\n color: style.color || 'inherit',\n fontSize: (style as TitleStyle).fontSize || 'inherit',\n textAlign: (style as TitleStyle).position || 'left',\n };\n }\n return {};\n }\n\n formatNumber(\n value: number,\n decimalPlaces?: number,\n thousandSeparator: 'comma' | 'space' = 'comma',\n decimalSeparator: 'comma' | 'dot' = 'dot'\n ): string {\n if (value === null || value === undefined || isNaN(value)) return '';\n\n // Convert the number to a string with full precision if decimalPlaces is undefined\n let formattedNumber =\n decimalPlaces !== undefined\n ? value.toFixed(decimalPlaces)\n : value.toString();\n\n // Replace decimal separator (default is \"dot\")\n if (decimalSeparator === 'comma') {\n formattedNumber = formattedNumber.replace('.', ',');\n }\n\n // Apply thousand separator only if the number is >= 1000\n if (thousandSeparator && Math.abs(value) >= 1000) {\n const parts = formattedNumber.split(\n decimalSeparator === 'comma' ? ',' : '.'\n );\n parts[0] = parts[0].replace(\n /\\B(?=(\\d{3})+(?!\\d))/g,\n thousandSeparator === 'comma' ? ',' : ' '\n );\n formattedNumber = parts.join(decimalSeparator === 'comma' ? ',' : '.');\n }\n\n return formattedNumber;\n }\n}\n","<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.