ng-prime-tools 1.0.43 → 1.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -163,21 +163,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImpo
163
163
 
164
164
  class PTAdvancedPrimeTableComponent {
165
165
  constructor() {
166
- // Inputs
167
166
  this.data = [];
168
167
  this.columns = [];
169
168
  this.totalRecords = 0;
170
- this.rowsPerPage = [];
169
+ this.rowsPerPage = [10, 20, 30];
171
170
  this.hasSearchFilter = false;
172
171
  this.hasExportExcel = false;
173
172
  this.hasExportPDF = false;
174
173
  this.hasColumnFilter = false;
175
- this.isPaginated = false;
174
+ /** paginator UI */
175
+ this.isPaginated = true;
176
+ /** ✅ server-side mode (PrimeNG lazy = true) */
177
+ this.isLazy = false;
176
178
  this.actions = [];
177
179
  this.isSortable = false;
178
180
  this.loading = false;
179
181
  this.maxHeight = null;
180
- // Outputs
181
182
  this.search = new EventEmitter();
182
183
  this.exportExcelEvent = new EventEmitter();
183
184
  this.exportPdfEvent = new EventEmitter();
@@ -187,17 +188,13 @@ class PTAdvancedPrimeTableComponent {
187
188
  this.TableTypeEnum = TableTypeEnum;
188
189
  this.AlignEnum = AlignEnum;
189
190
  this.searchValue = '';
190
- // used to store composed filter UI state (and options)
191
191
  this.filters = {};
192
- // store the last filter value per column field (for all filters)
193
192
  this.latestFilterValues = {};
194
193
  this.clearedFields = new Set();
195
194
  this.validCurrencyCodes = ['USD', 'EUR', 'MAD'];
196
195
  this.iconWidth = 77;
197
- // Component state
198
196
  this.rows = 0;
199
197
  this.hasGroupedColumns = false;
200
- // edit/delete flags
201
198
  this.isDelete = false;
202
199
  this.isEdit = false;
203
200
  this.Delete = () => { };
@@ -205,7 +202,6 @@ class PTAdvancedPrimeTableComponent {
205
202
  this.saveEditableRow = () => { };
206
203
  this.cancelEditableRow = () => { };
207
204
  this.customActions = [];
208
- // Data management
209
205
  this.dataMap = new Map();
210
206
  this.map = new Map();
211
207
  this.optionEntries = new Map();
@@ -228,19 +224,29 @@ class PTAdvancedPrimeTableComponent {
228
224
  if (col.type === TableTypeEnum.COMPOSED) {
229
225
  this.initializeComposedFilters(col);
230
226
  }
231
- if (col.isSortable === undefined) {
227
+ if (col.isSortable === undefined)
232
228
  col.isSortable = true;
233
- }
234
- if (col.isEditable === undefined) {
229
+ if (col.isEditable === undefined)
235
230
  col.isEditable = true;
236
- }
237
231
  if (col.isFilter !== false && col.code !== undefined) {
238
232
  this.globalFilterFields.push(col.code);
239
233
  }
240
- if (!col.width) {
234
+ if (!col.width)
241
235
  col.width = this.calculateColumnWidth(col);
242
- }
243
236
  });
237
+ // ✅ client-side mode: totalRecords = data.length by default
238
+ if (!this.isLazy) {
239
+ this.totalRecords = this.data?.length ?? 0;
240
+ }
241
+ }
242
+ ngOnChanges(changes) {
243
+ // ✅ when data changes in client-side mode, keep paginator consistent
244
+ if (changes['data'] && !this.isLazy) {
245
+ this.totalRecords = this.data?.length ?? 0;
246
+ // if we were on page > 1, go back to first page (avoids empty view)
247
+ if (this.dt)
248
+ this.dt.first = 0;
249
+ }
244
250
  }
245
251
  // ---------- HEADER + BODY ALIGNMENT HELPERS ----------
246
252
  getHeaderTitleClass(col) {
@@ -288,9 +294,8 @@ class PTAdvancedPrimeTableComponent {
288
294
  this.saveEditableRow = () => { };
289
295
  this.cancelEditableRow = () => { };
290
296
  this.customActions = [];
291
- if (!this.actions || this.actions.length === 0) {
297
+ if (!this.actions || this.actions.length === 0)
292
298
  return;
293
- }
294
299
  this.actions.forEach((action) => {
295
300
  switch (action.code) {
296
301
  case 'delete':
@@ -318,16 +323,10 @@ class PTAdvancedPrimeTableComponent {
318
323
  }
319
324
  onCustomActionClick(action, row) {
320
325
  if (action && typeof action.action === 'function') {
321
- setTimeout(() => {
322
- action.action(row);
323
- }, 0);
326
+ setTimeout(() => action.action(row), 0);
324
327
  }
325
328
  }
326
329
  // ---------- FILTERING / SORTING / PAGINATION ----------
327
- /**
328
- * For composed columns, we still register filters under filters[composedName]
329
- * to keep options & placeholders.
330
- */
331
330
  initializeComposedFilters(col) {
332
331
  col.composedNames?.forEach((composedName) => {
333
332
  const code = col.code || '';
@@ -350,23 +349,17 @@ class PTAdvancedPrimeTableComponent {
350
349
  return undefined;
351
350
  }
352
351
  onComposedColumnClear(col) {
353
- if (!col.code || !col.composedNames) {
352
+ if (!col.code || !col.composedNames)
354
353
  return;
355
- }
356
354
  col.composedNames.forEach((name) => {
357
355
  const key = `${col.code}.${name}`;
358
356
  delete this.latestFilterValues[key];
359
- if (this.filters[name]) {
357
+ if (this.filters[name])
360
358
  this.filters[name].value = [];
361
- }
362
359
  });
363
360
  }
364
- /**
365
- * COMPOSED: cache UI values only, real filter applied on Apply/Clear.
366
- */
367
361
  onComposedFilterValueChange(col, composedName, value, filterModel) {
368
362
  const key = `${col.code}.${composedName}`;
369
- // 1) sync with PrimeNG filter model so Apply/Clear work correctly
370
363
  if (filterModel) {
371
364
  filterModel.value = value;
372
365
  if (Array.isArray(filterModel.constraints) &&
@@ -374,7 +367,6 @@ class PTAdvancedPrimeTableComponent {
374
367
  filterModel.constraints[0].value = value;
375
368
  }
376
369
  }
377
- // 2) keep your local UI model
378
370
  if (!this.filters[composedName]) {
379
371
  this.filters[composedName] = {
380
372
  options: col.filterOptions,
@@ -383,40 +375,29 @@ class PTAdvancedPrimeTableComponent {
383
375
  };
384
376
  }
385
377
  this.filters[composedName].value = value || [];
386
- // 3) cache for server-side `filterColumn`
387
378
  const isEmpty = !value || (Array.isArray(value) && value.length === 0);
388
- if (isEmpty) {
379
+ if (isEmpty)
389
380
  delete this.latestFilterValues[key];
390
- }
391
- else {
381
+ else
392
382
  this.latestFilterValues[key] = value;
393
- }
394
383
  }
395
384
  onFilterClear(field) {
396
- if (!field) {
385
+ if (!field)
397
386
  return;
398
- }
399
- // forget cached value for this field
400
387
  delete this.latestFilterValues[field];
401
- // mark this field as explicitly cleared so filterColumn
402
- // doesn't reuse the cached value on the same event
403
388
  this.clearedFields.add(field);
404
389
  }
405
390
  onFilterValueChange(field, filterModel, value) {
406
- if (!field) {
391
+ if (!field)
407
392
  return;
408
- }
409
393
  const isEmpty = value === null ||
410
394
  value === undefined ||
411
395
  (Array.isArray(value) && value.length === 0);
412
- if (isEmpty) {
396
+ if (isEmpty)
413
397
  delete this.latestFilterValues[field];
414
- }
415
- else {
398
+ else
416
399
  this.latestFilterValues[field] = value;
417
- }
418
400
  if (filterModel) {
419
- // update both places so PrimeNG emits it correctly
420
401
  filterModel.value = value;
421
402
  if (Array.isArray(filterModel.constraints) &&
422
403
  filterModel.constraints.length > 0) {
@@ -425,29 +406,25 @@ class PTAdvancedPrimeTableComponent {
425
406
  }
426
407
  }
427
408
  onNumberFilterChange(field, value) {
428
- if (!field) {
409
+ if (!field)
429
410
  return;
430
- }
431
411
  const isEmpty = value === null ||
432
412
  value === undefined ||
433
413
  value === '' ||
434
414
  (Array.isArray(value) && value.length === 0);
435
- if (isEmpty) {
415
+ if (isEmpty)
436
416
  delete this.latestFilterValues[field];
437
- }
438
- else {
417
+ else
439
418
  this.latestFilterValues[field] = value;
440
- }
441
419
  }
442
- /** Helper: find column by field. Supports composed fields like "code.prop". */
443
420
  findColumnByField(field) {
444
421
  return this.columns.find((c) => c.code === field || (c.code && field.startsWith(c.code + '.')));
445
422
  }
446
- // Fired by PrimeNG when the user clicks Apply / Clear in any filter menu
447
423
  filterColumn(event) {
448
424
  const filters = event?.filters;
449
425
  if (!filters) {
450
- this.onFilterColumn.emit(event);
426
+ if (this.isLazy)
427
+ this.onFilterColumn.emit(event);
451
428
  return;
452
429
  }
453
430
  const isNullish = (v) => v === null || v === undefined || v === '';
@@ -457,46 +434,33 @@ class PTAdvancedPrimeTableComponent {
457
434
  const m = normalizeMeta(meta);
458
435
  const col = this.findColumnByField(field);
459
436
  const wasCleared = this.clearedFields.has(field);
460
- // nothing to do
461
- if (!m && !col) {
437
+ if (!m && !col)
462
438
  return;
463
- }
464
- // ----------------------------------------------------
465
- // 🔹 SPECIAL CASE: COMPOSED COLUMN
466
- // ----------------------------------------------------
439
+ // COMPOSED
467
440
  if (col && col.type === TableTypeEnum.COMPOSED) {
468
441
  const composedValues = {};
469
442
  col.composedNames?.forEach((name) => {
470
443
  const key = `${col.code}.${name}`;
471
444
  const val = this.latestFilterValues[key];
472
445
  const empty = isNullish(val) || (Array.isArray(val) && val.length === 0);
473
- if (!empty) {
446
+ if (!empty)
474
447
  composedValues[name] = val;
475
- }
476
448
  });
477
- // If nothing in cache, treat as "no filter"
478
449
  if (Object.keys(composedValues).length === 0) {
479
450
  if (m) {
480
451
  m.value = null;
481
- if (Array.isArray(m.constraints)) {
452
+ if (Array.isArray(m.constraints))
482
453
  m.constraints.forEach((c) => (c.value = null));
483
- }
484
454
  }
485
455
  }
486
456
  else {
487
- // send object { sub1: [...], sub2: [...] } to parent
488
457
  m.value = composedValues;
489
458
  }
490
- const wasCleared = this.clearedFields.has(field);
491
- return; // composed handled
492
- }
493
- // ----------------------------------------------------
494
- // 🔹 GENERIC CASE: NON-COMPOSED COLUMNS
495
- // ----------------------------------------------------
496
- if (!m) {
497
459
  return;
498
460
  }
499
- // If the user clicked Clear for this field, force it to empty
461
+ if (!m)
462
+ return;
463
+ // CLEAR
500
464
  if (wasCleared) {
501
465
  delete this.latestFilterValues[field];
502
466
  m.value = null;
@@ -507,14 +471,11 @@ class PTAdvancedPrimeTableComponent {
507
471
  return;
508
472
  }
509
473
  let value = m.value;
510
- // PrimeNG often stores the real value in constraints[0].value
511
474
  if (Array.isArray(m.constraints) && m.constraints.length > 0) {
512
475
  const cVal = m.constraints[0].value;
513
- if (!isNullish(cVal)) {
476
+ if (!isNullish(cVal))
514
477
  value = cVal;
515
- }
516
478
  }
517
- // 🔴 NEW: fall back to cached value typed in the input
518
479
  const cached = this.latestFilterValues[field];
519
480
  const hasCached = !isNullish(cached) && (!Array.isArray(cached) || cached.length > 0);
520
481
  if ((isNullish(value) || (Array.isArray(value) && value.length === 0)) &&
@@ -522,7 +483,6 @@ class PTAdvancedPrimeTableComponent {
522
483
  value = cached;
523
484
  }
524
485
  const isEmpty = isNullish(value) || (Array.isArray(value) && value.length === 0);
525
- // CLEAR => remove from cache, reset PrimeNG metadata
526
486
  if (isEmpty) {
527
487
  delete this.latestFilterValues[field];
528
488
  m.value = null;
@@ -531,115 +491,66 @@ class PTAdvancedPrimeTableComponent {
531
491
  }
532
492
  return;
533
493
  }
534
- // NON-EMPTY => cache and normalize
535
494
  this.latestFilterValues[field] = value;
536
495
  let emitValue = value;
537
- // Only DATE columns need "dd/MM/yyyy" format
538
- if (col && col.type === TableTypeEnum.DATE && value) {
539
- emitValue = this.formatDate(value instanceof Date ? value : new Date(value));
496
+ // DATE / DATETIME normalization for server-side filtering
497
+ if (col &&
498
+ (col.type === TableTypeEnum.DATE || col.type === TableTypeEnum.DATETIME)) {
499
+ const d = this.parseAnyDate(value);
500
+ emitValue =
501
+ col.type === TableTypeEnum.DATE
502
+ ? this.formatDateWithColumn(d, col)
503
+ : this.formatDateTimeWithColumn(d, col);
540
504
  }
541
505
  m.value = emitValue;
542
506
  if (Array.isArray(m.constraints) && m.constraints.length > 0) {
543
507
  m.constraints[0].value = emitValue;
544
508
  }
545
509
  });
546
- // send normalized filters to parent (server-side pagination)
547
- this.onFilterColumn.emit(event);
510
+ // emit only in server-side mode
511
+ if (this.isLazy) {
512
+ this.onFilterColumn.emit(event);
513
+ }
514
+ else {
515
+ // client-side: keep totalRecords in sync with filtered view
516
+ if (this.dt) {
517
+ const current = (this.dt.filteredValue ?? this.dt.value ?? []);
518
+ this.totalRecords = current?.length ?? 0;
519
+ this.dt.first = 0;
520
+ }
521
+ }
548
522
  }
549
523
  changePage(event) {
550
524
  const page = event.page ?? Math.floor((event.first || 0) / event.rows);
551
525
  const rows = event.rows;
552
526
  this.rows = rows;
553
- this.onPageChange.emit({ page, rows });
527
+ // ✅ emit only when server-side pagination
528
+ if (this.isLazy) {
529
+ this.onPageChange.emit({ page, rows });
530
+ }
554
531
  }
555
532
  sortColumn(event) {
556
- if (!this.isPaginated) {
533
+ // emit only when server-side sorting
534
+ if (!this.isLazy)
557
535
  return;
558
- }
559
536
  let field = event.field;
560
537
  const col = this.columns.find((c) => c.code === field);
561
538
  if (col && col.type === TableTypeEnum.COMPOSED) {
562
539
  let textProp;
563
540
  if (col.composedNames && col.composedTypes) {
564
541
  const idx = col.composedTypes.findIndex((t) => t === TableTypeEnum.STRING);
565
- if (idx >= 0 && idx < col.composedNames.length) {
542
+ if (idx >= 0 && idx < col.composedNames.length)
566
543
  textProp = col.composedNames[idx];
567
- }
568
544
  }
569
- if (!textProp && col.composedNames && col.composedNames.length > 0) {
545
+ if (!textProp && col.composedNames?.length)
570
546
  textProp = col.composedNames[0];
571
- }
572
- if (textProp) {
547
+ if (textProp)
573
548
  field = `${field}.${textProp}`;
574
- }
575
- }
576
- this.onSortColumn.emit({
577
- ...event,
578
- field,
579
- });
580
- }
581
- onCalendarFilterChange(event, columnCode, filterCallback) {
582
- const filterValue = event ? new Date(event) : null;
583
- if (!filterValue) {
584
- return;
585
- }
586
- filterCallback(filterValue);
587
- if (this.isPaginated) {
588
- this.onFilterColumn.emit({
589
- type: 'date',
590
- columnCode,
591
- value: filterValue,
592
- });
593
- return;
594
- }
595
- const filterValueString = event ? this.formatDate(event) : '';
596
- this.filterColumn({
597
- filteredValue: this.data.filter((item) => {
598
- const columnValue = item[columnCode];
599
- if (columnValue) {
600
- const itemDateString = this.formatDate(new Date(columnValue));
601
- return itemDateString === filterValueString;
602
- }
603
- return false;
604
- }),
605
- });
606
- }
607
- // ---------- OTHER HELPERS ----------
608
- filterComposedData(item, composedName, value) {
609
- if (Array.isArray(value) && value.length > 0) {
610
- return value.some((filterValue) => item[composedName]?.toLowerCase().includes(filterValue.toLowerCase()));
611
549
  }
612
- return true;
550
+ this.onSortColumn.emit({ ...event, field });
613
551
  }
614
- calculateColumnWidth(col) {
615
- const calculatedWidth = calculateTextWidth(col, col.title);
616
- const totalWidth = calculatedWidth + this.iconWidth + 20;
617
- return `${totalWidth}px`;
618
- }
619
- getHeaderWidth(col) {
620
- if (col.width) {
621
- return col.width;
622
- }
623
- return this.calculateColumnWidth(col);
624
- }
625
- clear(table) {
626
- // Clear PrimeNG internal filters + UI
627
- table.clear();
628
- // Clear global search
629
- this.searchValue = '';
630
- // Clear cached values for normal & composed fields
631
- this.latestFilterValues = {};
632
- // Clear local models for composed filters
633
- Object.keys(this.filters).forEach((key) => {
634
- if (this.filters[key]) {
635
- this.filters[key].value = [];
636
- }
637
- });
638
- if (this.isPaginated) {
639
- this.onFilterColumn.emit({ cleared: true });
640
- }
641
- }
642
- parseDate(dateString) {
552
+ // ---------- EDIT HELPERS ----------
553
+ parseDate_ddMMyyyy(dateString) {
643
554
  const parts = dateString.split('/');
644
555
  if (parts.length === 3) {
645
556
  const day = parseInt(parts[0], 10);
@@ -650,14 +561,6 @@ class PTAdvancedPrimeTableComponent {
650
561
  }
651
562
  return null;
652
563
  }
653
- initializePagination() {
654
- if (this.isPaginated) {
655
- if (!this.rowsPerPage || this.rowsPerPage.length === 0) {
656
- this.rowsPerPage = [20, 30, 40];
657
- }
658
- this.rows = this.rowsPerPage[0];
659
- }
660
- }
661
564
  onChange(event, id, key) {
662
565
  const target = event.target;
663
566
  this.changeHandler(id, key, target.value);
@@ -666,8 +569,7 @@ class PTAdvancedPrimeTableComponent {
666
569
  const column = this.columns.find((item) => item.code === key);
667
570
  if (!this.map.get(id)) {
668
571
  if (column?.type === TableTypeEnum.DATE) {
669
- const date = this.parseDate(value);
670
- this.dataMap.set(key, date);
572
+ this.dataMap.set(key, this.parseDate_ddMMyyyy(value));
671
573
  }
672
574
  else {
673
575
  this.dataMap.set(key, value);
@@ -677,14 +579,14 @@ class PTAdvancedPrimeTableComponent {
677
579
  else {
678
580
  const mapItem = this.map.get(id);
679
581
  if (column?.type === TableTypeEnum.DATE) {
680
- const date = this.parseDate(value);
681
- mapItem.set(key, date);
582
+ mapItem.set(key, this.parseDate_ddMMyyyy(value));
682
583
  }
683
584
  else {
684
585
  mapItem.set(key, value);
685
586
  }
686
587
  }
687
588
  }
589
+ // ---------- TYPES ----------
688
590
  getColumnFilterType(column) {
689
591
  switch (column.type) {
690
592
  case TableTypeEnum.STRING:
@@ -693,6 +595,7 @@ class PTAdvancedPrimeTableComponent {
693
595
  case TableTypeEnum.NUMBER:
694
596
  return 'numeric';
695
597
  case TableTypeEnum.DATE:
598
+ case TableTypeEnum.DATETIME:
696
599
  return 'date';
697
600
  case TableTypeEnum.MULTISELECT:
698
601
  return 'multiSelect';
@@ -724,84 +627,178 @@ class PTAdvancedPrimeTableComponent {
724
627
  return false;
725
628
  }
726
629
  isDatePicker(key) {
727
- return (this.columns.find((item) => item.code === key)?.type ===
728
- TableTypeEnum.DATE);
630
+ const t = this.columns.find((item) => item.code === key)?.type;
631
+ return t === TableTypeEnum.DATE || t === TableTypeEnum.DATETIME;
729
632
  }
730
- dateConverter(value) {
731
- return new Date(value).toLocaleDateString('en-US');
732
- }
733
- getCurrencySymbol(column) {
734
- return column.type === TableTypeEnum.AMOUNT &&
735
- column.currency &&
736
- this.isValidCurrencyCode(column.currency)
737
- ? column.currency
738
- : undefined;
739
- }
740
- isValidCurrencyCode(currencyCode) {
741
- return this.validCurrencyCodes.includes(currencyCode);
633
+ isDateTimePicker(key) {
634
+ const t = this.columns.find((item) => item.code === key)?.type;
635
+ return t === TableTypeEnum.DATETIME;
742
636
  }
637
+ // ---------- SEARCH ----------
743
638
  filterGlobal(event) {
744
639
  const target = event.target;
745
- const value = target.value.toLowerCase();
746
- if (this.isPaginated) {
640
+ const value = (target.value || '').toLowerCase();
641
+ // server-side mode
642
+ if (this.isLazy) {
747
643
  this.search.emit(value);
748
644
  return;
749
645
  }
750
- const filteredData = this.data.filter((item) => {
751
- return this.globalFilterFields.some((field) => {
752
- const column = this.columns.find((col) => col.code === field);
753
- if (!column) {
754
- return false;
755
- }
756
- if (column.type === TableTypeEnum.DATE) {
757
- const itemDate = this.formatDate(item[field]);
758
- return itemDate && itemDate.includes(value);
759
- }
760
- else if (column.type === TableTypeEnum.AMOUNT ||
761
- column.type === TableTypeEnum.NUMBER) {
762
- return (item[field] && item[field].toString().toLowerCase().includes(value));
763
- }
764
- else if (column.type === TableTypeEnum.COMPOSED) {
765
- return this.filterComposedColumn(item[field], value);
766
- }
767
- else {
768
- return (item[field] && item[field].toString().toLowerCase().includes(value));
769
- }
770
- });
771
- });
646
+ // client-side mode
647
+ if (!value) {
648
+ this.dt.value = [...(this.data ?? [])];
649
+ this.totalRecords = (this.dt.value ?? []).length;
650
+ this.dt.first = 0;
651
+ return;
652
+ }
653
+ const filteredData = (this.data ?? []).filter((item) => (this.globalFilterFields ?? []).some((field) => {
654
+ const column = this.columns?.find((col) => col.code === field);
655
+ if (!column)
656
+ return false;
657
+ const cell = item?.[field];
658
+ if (column.type === TableTypeEnum.DATE) {
659
+ return this.formatDateWithColumn(this.parseAnyDate(cell), column)
660
+ .toLowerCase()
661
+ .includes(value);
662
+ }
663
+ if (column.type === TableTypeEnum.DATETIME) {
664
+ return this.formatDateTimeWithColumn(this.parseAnyDate(cell), column)
665
+ .toLowerCase()
666
+ .includes(value);
667
+ }
668
+ if (column.type === TableTypeEnum.AMOUNT ||
669
+ column.type === TableTypeEnum.NUMBER) {
670
+ return String(cell ?? '')
671
+ .toLowerCase()
672
+ .includes(value);
673
+ }
674
+ if (column.type === TableTypeEnum.COMPOSED) {
675
+ return this.filterComposedColumn(cell, value);
676
+ }
677
+ return String(cell ?? '')
678
+ .toLowerCase()
679
+ .includes(value);
680
+ }));
772
681
  this.dt.value = filteredData;
682
+ this.totalRecords = filteredData.length;
683
+ this.dt.first = 0;
773
684
  }
774
685
  filterComposedColumn(composedData, value) {
775
686
  if (composedData) {
776
687
  return Object.keys(composedData).some((key) => {
777
688
  const cellValue = composedData[key];
778
- if (typeof cellValue === 'string') {
779
- return cellValue.toLowerCase().includes(value);
780
- }
781
- return false;
689
+ return typeof cellValue === 'string'
690
+ ? cellValue.toLowerCase().includes(value)
691
+ : false;
782
692
  });
783
693
  }
784
694
  return false;
785
695
  }
786
- formatDate(date) {
787
- if (!date)
788
- return '';
789
- if (date instanceof Date) {
790
- const day = date.getDate().toString().padStart(2, '0');
791
- const month = (date.getMonth() + 1).toString().padStart(2, '0');
792
- const year = date.getFullYear().toString();
793
- return `${day}/${month}/${year}`;
794
- }
795
- else if (typeof date === 'string') {
796
- const parts = date.split('/');
797
- if (parts.length === 3) {
798
- return `${parts[0]}/${parts[1]}/${parts[2]}`;
696
+ // ---------- ✅ DATE / DATETIME PARSING + FORMATTING ----------
697
+ parseAnyDate(input) {
698
+ if (input === null || input === undefined || input === '')
699
+ return null;
700
+ if (input instanceof Date)
701
+ return isNaN(input.getTime()) ? null : input;
702
+ if (typeof input === 'number') {
703
+ const d = new Date(input);
704
+ return isNaN(d.getTime()) ? null : d;
705
+ }
706
+ if (typeof input === 'string') {
707
+ const s = input.trim();
708
+ const isoTry = new Date(s);
709
+ if (!isNaN(isoTry.getTime()))
710
+ return isoTry;
711
+ if (s.includes('/')) {
712
+ const parts = s.split(' ')[0].split('/');
713
+ if (parts.length === 3) {
714
+ const day = parseInt(parts[0], 10);
715
+ const month = parseInt(parts[1], 10) - 1;
716
+ const year = parseInt(parts[2], 10);
717
+ const d = new Date(year, month, day);
718
+ return isNaN(d.getTime()) ? null : d;
719
+ }
799
720
  }
800
- else {
801
- return date;
721
+ }
722
+ return null;
723
+ }
724
+ formatWithPattern(d, pattern) {
725
+ if (!d)
726
+ return '';
727
+ const pad2 = (n) => String(n).padStart(2, '0');
728
+ const map = {
729
+ dd: pad2(d.getDate()),
730
+ MM: pad2(d.getMonth() + 1),
731
+ yyyy: String(d.getFullYear()),
732
+ HH: pad2(d.getHours()),
733
+ mm: pad2(d.getMinutes()),
734
+ ss: pad2(d.getSeconds()),
735
+ };
736
+ return pattern
737
+ .replace(/yyyy/g, map.yyyy)
738
+ .replace(/dd/g, map.dd)
739
+ .replace(/MM/g, map.MM)
740
+ .replace(/HH/g, map.HH)
741
+ .replace(/mm/g, map.mm)
742
+ .replace(/ss/g, map.ss);
743
+ }
744
+ formatDate(date) {
745
+ const d = this.parseAnyDate(date);
746
+ return this.formatWithPattern(d, 'dd/MM/yyyy');
747
+ }
748
+ formatDateWithColumn(d, col) {
749
+ const fmt = col.dateFormat?.trim() || 'dd/MM/yyyy';
750
+ return this.formatWithPattern(d, fmt);
751
+ }
752
+ formatDateTimeWithColumn(d, col) {
753
+ const fmt = col.dateTimeFormat?.trim() || 'dd/MM/yyyy HH:mm:ss';
754
+ return this.formatWithPattern(d, fmt);
755
+ }
756
+ // ---------- MISC ----------
757
+ calculateColumnWidth(col) {
758
+ const calculatedWidth = calculateTextWidth(col, col.title);
759
+ const totalWidth = calculatedWidth + this.iconWidth + 20;
760
+ return `${totalWidth}px`;
761
+ }
762
+ getHeaderWidth(col) {
763
+ return col.width ? col.width : this.calculateColumnWidth(col);
764
+ }
765
+ clear(table) {
766
+ table.clear();
767
+ this.searchValue = '';
768
+ this.latestFilterValues = {};
769
+ Object.keys(this.filters).forEach((key) => {
770
+ if (this.filters[key])
771
+ this.filters[key].value = [];
772
+ });
773
+ // ✅ server-side: parent must reload
774
+ if (this.isLazy) {
775
+ this.onFilterColumn.emit({ cleared: true });
776
+ return;
777
+ }
778
+ // ✅ client-side: reset paginator totals
779
+ if (this.dt) {
780
+ this.dt.value = [...(this.data ?? [])];
781
+ this.totalRecords = (this.dt.value ?? []).length;
782
+ this.dt.first = 0;
783
+ }
784
+ }
785
+ initializePagination() {
786
+ if (this.isPaginated) {
787
+ if (!this.rowsPerPage || this.rowsPerPage.length === 0) {
788
+ this.rowsPerPage = [10, 20, 30];
802
789
  }
790
+ this.rows = this.rowsPerPage[0];
803
791
  }
804
- return '';
792
+ }
793
+ getCurrencySymbol(column) {
794
+ return column.type === TableTypeEnum.AMOUNT &&
795
+ column.currency &&
796
+ this.isValidCurrencyCode(column.currency)
797
+ ? column.currency
798
+ : undefined;
799
+ }
800
+ isValidCurrencyCode(currencyCode) {
801
+ return this.validCurrencyCodes.includes(currencyCode);
805
802
  }
806
803
  exportExcel() {
807
804
  this.exportExcelEvent.emit();
@@ -810,39 +807,32 @@ class PTAdvancedPrimeTableComponent {
810
807
  this.exportPdfEvent.emit();
811
808
  }
812
809
  getImageStyle(style) {
813
- if (style) {
814
- const imageStyle = {
815
- width: style.width || 'auto',
816
- height: style.height || 'auto',
817
- };
818
- if (style.margin) {
819
- imageStyle.margin = style.margin;
820
- }
821
- if (style.marginLeft) {
822
- imageStyle.marginLeft = style.marginLeft;
823
- }
824
- if (style.marginRight) {
825
- imageStyle.marginRight = style.marginRight;
826
- }
827
- if (style.marginTop) {
828
- imageStyle.marginTop = style.marginTop;
829
- }
830
- if (style.marginBottom) {
831
- imageStyle.marginBottom = style.marginBottom;
832
- }
833
- return imageStyle;
834
- }
835
- return {};
810
+ if (!style)
811
+ return {};
812
+ const imageStyle = {
813
+ width: style.width || 'auto',
814
+ height: style.height || 'auto',
815
+ };
816
+ if (style.margin)
817
+ imageStyle.margin = style.margin;
818
+ if (style.marginLeft)
819
+ imageStyle.marginLeft = style.marginLeft;
820
+ if (style.marginRight)
821
+ imageStyle.marginRight = style.marginRight;
822
+ if (style.marginTop)
823
+ imageStyle.marginTop = style.marginTop;
824
+ if (style.marginBottom)
825
+ imageStyle.marginBottom = style.marginBottom;
826
+ return imageStyle;
836
827
  }
837
828
  getTitleStyle(style) {
838
- if (style) {
839
- return {
840
- color: style.color || 'inherit',
841
- fontSize: style.fontSize || 'inherit',
842
- textAlign: style.position || 'left',
843
- };
844
- }
845
- return {};
829
+ if (!style)
830
+ return {};
831
+ return {
832
+ color: style.color || 'inherit',
833
+ fontSize: style.fontSize || 'inherit',
834
+ textAlign: style.position || 'left',
835
+ };
846
836
  }
847
837
  formatNumber(value, decimalPlaces, thousandSeparator = 'comma', decimalSeparator = 'dot') {
848
838
  if (value === null || value === undefined || isNaN(value))
@@ -861,12 +851,12 @@ class PTAdvancedPrimeTableComponent {
861
851
  return formattedNumber;
862
852
  }
863
853
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: PTAdvancedPrimeTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
864
- 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: { search: "search", exportExcelEvent: "exportExcelEvent", exportPdfEvent: "exportPdfEvent", onPageChange: "onPageChange", onSortColumn: "onSortColumn", onFilterColumn: "onFilterColumn" }, 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 [totalRecords]=\"totalRecords\"\n [lazy]=\"isPaginated\"\n dataKey=\"id\"\n styleClass=\"p-datatable-gridlines p-datatable-striped\"\n [scrollable]=\"true\"\n [scrollHeight]=\"maxHeight !== null ? maxHeight : undefined\"\n (onFilter)=\"filterColumn($event)\"\n (onPage)=\"changePage($event)\"\n (onSort)=\"sortColumn($event)\"\n >\n <!-- COLGROUP: sync header/body widths -->\n <ng-template pTemplate=\"colgroup\" let-columns>\n <colgroup>\n <col\n *ngFor=\"let col of columns\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n />\n </colgroup>\n </ng-template>\n\n <!-- CAPTION -->\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\n <div class=\"ml-auto\" *ngIf=\"hasSearchFilter\">\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 <!-- HEADER -->\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 pSortableColumn=\"{{ col.code }}\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n [ngClass]=\"[\n getHeaderAlignClass(col),\n col.type === TableTypeEnum.ACTION ? 'action-column' : ''\n ]\"\n colspan=\"1\"\n >\n <!-- SORTABLE HEADER -->\n <ng-container\n *ngIf=\"isSortable && col.isSortable !== false; else noSortHeader\"\n >\n <div\n class=\"header-container d-flex align-items-center justify-content-between\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n [style.margin]=\"'10px'\"\n >\n <span [ngClass]=\"getHeaderTitleClass(col)\">\n {{ col.title }}\n </span>\n <div\n class=\"icons d-flex align-items-center\"\n [style.width]=\"'77px'\"\n >\n <p-sortIcon field=\"{{ col.code }}\" />\n\n <!-- COLUMN FILTERS -->\n <ng-container\n *ngIf=\"hasColumnFilter && col.isFilter !== false\"\n >\n <!-- COMPOSED FILTER (uses built-in Apply/Clear) -->\n <p-columnFilter\n *ngIf=\"col.type === TableTypeEnum.COMPOSED\"\n display=\"menu\"\n [field]=\"col.code\"\n type=\"text\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n (onClear)=\"onComposedColumnClear(col)\"\n >\n <ng-template\n pTemplate=\"filter\"\n let-filter=\"filterCallback\"\n let-filterModel=\"filterModel\"\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 [options]=\"filters[composedName]?.options\"\n [ngModel]=\"filters[composedName]?.value\"\n (ngModelChange)=\"\n onComposedFilterValueChange(\n col,\n composedName,\n $event,\n filterModel\n )\n \"\n [placeholder]=\"filters[composedName]?.placeholder\"\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 </ng-template>\n </p-columnFilter>\n\n <!-- OTHER TYPES -->\n <p-columnFilter\n *ngIf=\"col.type !== TableTypeEnum.COMPOSED\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n (onClear)=\"onFilterClear(col.code!)\"\n >\n <!-- NUMBER / AMOUNT: use text input so '.' works -->\n <ng-template\n pTemplate=\"filter\"\n *ngIf=\"\n col.type === TableTypeEnum.NUMBER ||\n col.type === TableTypeEnum.AMOUNT\n \"\n let-value\n >\n <input\n pInputText\n type=\"text\"\n inputmode=\"decimal\"\n [ngModel]=\"latestFilterValues[col.code!] ?? value\"\n (ngModelChange)=\"\n onNumberFilterChange(col.code!, $event)\n \"\n placeholder=\"Enter a number\"\n />\n </ng-template>\n\n <!-- DATE -->\n <ng-template\n pTemplate=\"filter\"\n *ngIf=\"col.type === TableTypeEnum.DATE\"\n let-filterModel=\"filterModel\"\n >\n <p-calendar\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\n dateFormat=\"dd/mm/yy\"\n placeholder=\"Choose a date\"\n ></p-calendar>\n </ng-template>\n\n <!-- MULTISELECT -->\n <ng-template\n pTemplate=\"filter\"\n *ngIf=\"\n col.type === TableTypeEnum.MULTISELECT &&\n col.filterOptions &&\n col.filterOptions.length > 0\n \"\n let-filterModel=\"filterModel\"\n >\n <p-multiSelect\n [options]=\"col.filterOptions\"\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\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 </ng-container>\n\n <!-- NON-SORTABLE HEADER -->\n <ng-template #noSortHeader>\n <div\n class=\"header-container d-flex align-items-center justify-content-between\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n [style.margin]=\"'10px'\"\n >\n <span [ngClass]=\"getHeaderTitleClass(col)\">\n {{ col.title }}\n </span>\n\n <ng-container *ngIf=\"hasColumnFilter && col.isFilter !== false\">\n <!-- AMOUNT behaves like NUMBER -->\n <p-columnFilter\n *ngIf=\"col.type === 'AMOUNT'\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [currency]=\"getCurrencySymbol(col)\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n >\n <ng-template\n pTemplate=\"filter\"\n let-filterModel=\"filterModel\"\n >\n <input\n pInputText\n type=\"text\"\n inputmode=\"decimal\"\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\n placeholder=\"Enter an amount\"\n />\n </ng-template>\n </p-columnFilter>\n\n <p-columnFilter\n *ngIf=\"col.type !== 'AMOUNT'\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n >\n <!-- DATE -->\n <ng-template\n pTemplate=\"filter\"\n let-filterModel=\"filterModel\"\n *ngIf=\"getColumnFilterType(col) === 'date'\"\n >\n <p-calendar\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\n dateFormat=\"dd/mm/yy\"\n ></p-calendar>\n </ng-template>\n\n <!-- MULTISELECT -->\n <ng-template\n pTemplate=\"filter\"\n let-filterModel=\"filterModel\"\n *ngIf=\"getColumnFilterType(col) === 'multiSelect'\"\n >\n <p-multiSelect\n [options]=\"col.filterOptions\"\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\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 </ng-template>\n </th>\n\n <!-- GROUPED HEADER -->\n <ng-template #groupHeader>\n <th\n [attr.colspan]=\"col.children?.length\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngClass]=\"getHeaderAlignClass(col)\"\n >\n <span>{{ col.title }}</span>\n </th>\n </ng-template>\n </ng-container>\n </tr>\n\n <!-- CHILD HEADERS -->\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]=\"child.width || getHeaderWidth(child)\"\n [style.padding]=\"'0px'\"\n ></th>\n </ng-container>\n </ng-container>\n </tr>\n </ng-template>\n\n <!-- EMPTY MESSAGE -->\n <ng-template pTemplate=\"emptymessage\">\n <tr class=\"p-datatable-emptymessage\">\n <!-- span all columns -->\n <td class=\"empty-message-cell\" [attr.colspan]=\"columns.length || 1\">\n <div class=\"empty-message-wrapper\">\n <div class=\"empty-message\">\n <i class=\"pi pi-info-circle\"></i>\n <p>No records available to display.</p>\n </div>\n </div>\n </td>\n </tr>\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 <tr *ngIf=\"!loading\" [pEditableRow]=\"isEdit ? data : null\">\n <ng-container *ngFor=\"let col of columns\">\n <!-- SIMPLE COLUMNS (no children) -->\n <ng-container *ngIf=\"!col.children; else childColumns\">\n <!-- EDITABLE CELL -->\n <td\n *ngIf=\"\n isEditable(col.code!) && col.type !== TableTypeEnum.ACTION;\n else normalTD\n \"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngClass]=\"getDataAlignClass(col)\"\n >\n <!-- Editable input for NUMBER/DATE/STRING/MULTISELECT -->\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)=\"changeHandler(data.id, col.code, $event)\"\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 *ngIf=\"isDatePicker(col.code); else normalInput\">\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 <!-- NON-EDITABLE CELL (ALWAYS RENDERED) -->\n <ng-template #normalTD>\n <td\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngClass]=\"[\n getDataAlignClass(col),\n col.type === TableTypeEnum.ACTION ? 'action-column' : ''\n ]\"\n >\n <!-- ACTION column: built-in edit/delete + custom actions -->\n <ng-container\n *ngIf=\"col.type === TableTypeEnum.ACTION; else nonActionCell\"\n >\n <div class=\"action-buttons-container\">\n <!-- built-in delete -->\n <button\n *ngIf=\"isDelete\"\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\n <!-- built-in inline edit -->\n <div *ngIf=\"isEdit\">\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\n <!-- custom actions -->\n <button\n *ngFor=\"let act of customActions\"\n pButton\n pRipple\n type=\"button\"\n class=\"p-button-rounded p-button-text\"\n [icon]=\"act.icon || 'pi pi-ellipsis-h'\"\n [ngClass]=\"act.styleClass\"\n (click)=\"onCustomActionClick(act, data)\"\n ></button>\n </div>\n </ng-container>\n\n <!-- NON-ACTION cells -->\n <ng-template #nonActionCell>\n <!-- COMPOSED -->\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.COMPOSED;\n else nonComposed\n \"\n >\n <div class=\"composed-cell\">\n <ng-container\n *ngFor=\"\n let composedName of col.composedNames;\n let i = index\n \"\n >\n <!-- 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]=\"\n getImageStyle(col.composedStyles?.[composedName])\n \"\n />\n </ng-container>\n\n <!-- 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]=\"\n getTitleStyle(\n col.composedStyles?.[composedName]\n )\n \"\n >\n {{ data[col.code!]?.[composedName] }}\n </span>\n </ng-container>\n </ng-container>\n </div>\n </ng-container>\n\n <ng-template #nonComposed>\n <!-- AMOUNT-->\n <ng-container\n *ngIf=\"col.type === TableTypeEnum.AMOUNT; else nonAmount\"\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\n <ng-template #nonAmount>\n <!-- NUMBER-->\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.NUMBER;\n else nonNumber\n \"\n >\n {{\n formatNumber(\n data[col.code!],\n col.decimalPlaces,\n col.thousandSeparator,\n col.decimalSeparator\n )\n }}\n </ng-container>\n\n <ng-template #nonNumber>\n <!-- DATE -->\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.DATE;\n else normalTypes\n \"\n >\n {{ formatDate(data[col.code!]) }}\n </ng-container>\n\n <ng-template #normalTypes>\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 </ng-template>\n </ng-template>\n </ng-template>\n </ng-template>\n </ng-template>\n </td>\n </ng-template>\n </ng-container>\n\n <!-- CHILD COLUMNS -->\n <ng-template #childColumns>\n <ng-container *ngFor=\"let child of col.children\">\n <td\n [style.width]=\"child.width || getHeaderWidth(child)\"\n [ngClass]=\"getDataAlignClass(child)\"\n >\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]=\"child.code ? data[child.code] : null\"\n (change)=\"onChange($event, data.id, child.code)\"\n />\n </ng-template>\n <ng-template pTemplate=\"output\">\n {{ child.code ? data[child.code] : \"\" }}\n </ng-template>\n </p-cellEditor>\n </ng-container>\n\n <ng-template #childNormalTD>\n {{ child.code ? data[child.code] : \"\" }}\n </ng-template>\n </td>\n </ng-container>\n </ng-template>\n </ng-container>\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 .header-title-left,.pt-advanced-prime-table ::ng-deep .header-title-center,.pt-advanced-prime-table ::ng-deep .header-title-right{flex:1}.pt-advanced-prime-table ::ng-deep .header-title-left{text-align:left}.pt-advanced-prime-table ::ng-deep .header-title-center{text-align:center}.pt-advanced-prime-table ::ng-deep .header-title-right{text-align:right}.pt-advanced-prime-table ::ng-deep .header-align-left{text-align:left}.pt-advanced-prime-table ::ng-deep .header-align-center{text-align:center}.pt-advanced-prime-table ::ng-deep .header-align-right{text-align:right}.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:block;width:100%}.pt-advanced-prime-table ::ng-deep .p-datatable-emptymessage>td.empty-message-cell{padding:0!important}.pt-advanced-prime-table .empty-message-wrapper{width:100%;height:100%;min-height:180px;display:flex;align-items:center;justify-content:center}.pt-advanced-prime-table .empty-message{text-align:center;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}.pt-advanced-prime-table ::ng-deep .p-datatable-tbody>tr>td.cell-align-left{text-align:left!important}.pt-advanced-prime-table ::ng-deep .p-datatable-tbody>tr>td.cell-align-center{text-align:center!important}.pt-advanced-prime-table ::ng-deep .p-datatable-tbody>tr>td.cell-align-right{text-align:right!important}.pt-advanced-prime-table ::ng-deep th.action-column,.pt-advanced-prime-table ::ng-deep td.action-column{text-align:center!important;justify-content:center;align-items:center;overflow:hidden;white-space:nowrap;padding:0}.pt-advanced-prime-table ::ng-deep .action-buttons-container{width:100%;display:flex;justify-content:center!important;align-items:center!important;gap:.35rem}.pt-advanced-prime-table ::ng-deep .action-buttons-container .p-button.p-button-rounded.p-button-text{padding:.25rem!important;min-width:22px!important;height:22px!important}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { 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: i1$1.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: i3.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain"] }, { kind: "component", type: i6.Calendar, selector: "p-calendar", inputs: ["iconDisplay", "style", "styleClass", "inputStyle", "inputId", "name", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "disabled", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "appendTo", "readonlyInput", "shortYearCutoff", "monthNavigator", "yearNavigator", "hourFormat", "timeOnly", "stepYearPicker", "stepHour", "stepMinute", "stepSecond", "showSeconds", "required", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "variant", "minDate", "maxDate", "disabledDates", "disabledDays", "yearRange", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "locale", "view", "defaultDate"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i8.MultiSelect, selector: "p-multiSelect", inputs: ["id", "ariaLabel", "style", "styleClass", "panelStyle", "panelStyleClass", "inputId", "disabled", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "variant", "appendTo", "dataKey", "name", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "defaultLabel", "placeholder", "options", "filterValue", "itemSize", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "component", type: i9.Tag, selector: "p-tag", inputs: ["style", "styleClass", "severity", "value", "icon", "rounded"] }, { kind: "component", type: i10.IconField, selector: "p-iconField", inputs: ["iconPosition"] }, { kind: "component", type: i11.InputIcon, selector: "p-inputIcon", inputs: ["styleClass"] }, { kind: "pipe", type: CustomCurrencyPipe, name: "customCurrency" }, { kind: "pipe", type: CustomDatePipe, name: "customDate" }] }); }
854
+ 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", isLazy: "isLazy", actions: "actions", isSortable: "isSortable", loading: "loading", maxHeight: "maxHeight" }, outputs: { search: "search", exportExcelEvent: "exportExcelEvent", exportPdfEvent: "exportPdfEvent", onPageChange: "onPageChange", onSortColumn: "onSortColumn", onFilterColumn: "onFilterColumn" }, viewQueries: [{ propertyName: "dt", first: true, predicate: ["dt"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"pt-advanced-prime-table table-container\">\n <p-table\n #dt\n [value]=\"data\"\n [loading]=\"loading\"\n [rows]=\"rows\"\n [paginator]=\"isPaginated\"\n [globalFilterFields]=\"globalFilterFields\"\n [rowsPerPageOptions]=\"rowsPerPage\"\n [totalRecords]=\"totalRecords\"\n [lazy]=\"isLazy\"\n dataKey=\"id\"\n styleClass=\"p-datatable-gridlines p-datatable-striped\"\n [scrollable]=\"true\"\n [scrollHeight]=\"maxHeight !== null ? maxHeight : undefined\"\n (onFilter)=\"filterColumn($event)\"\n (onPage)=\"changePage($event)\"\n (onSort)=\"sortColumn($event)\"\n >\n <!-- COLGROUP: sync header/body widths -->\n <ng-template pTemplate=\"colgroup\" let-columns>\n <colgroup>\n <col\n *ngFor=\"let col of columns\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n />\n </colgroup>\n </ng-template>\n\n <!-- CAPTION -->\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\n <div class=\"ml-auto\" *ngIf=\"hasSearchFilter\">\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 <!-- HEADER -->\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 pSortableColumn=\"{{ col.code }}\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n [ngClass]=\"[\n getHeaderAlignClass(col),\n col.type === TableTypeEnum.ACTION ? 'action-column' : ''\n ]\"\n colspan=\"1\"\n >\n <!-- SORTABLE HEADER -->\n <ng-container\n *ngIf=\"isSortable && col.isSortable !== false; else noSortHeader\"\n >\n <div\n class=\"header-container d-flex align-items-center justify-content-between\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n [style.margin]=\"'10px'\"\n >\n <span [ngClass]=\"getHeaderTitleClass(col)\">\n {{ col.title }}\n </span>\n <div\n class=\"icons d-flex align-items-center\"\n [style.width]=\"'77px'\"\n >\n <p-sortIcon field=\"{{ col.code }}\" />\n\n <!-- COLUMN FILTERS -->\n <ng-container\n *ngIf=\"hasColumnFilter && col.isFilter !== false\"\n >\n <!-- COMPOSED FILTER (uses built-in Apply/Clear) -->\n <p-columnFilter\n *ngIf=\"col.type === TableTypeEnum.COMPOSED\"\n display=\"menu\"\n [field]=\"col.code\"\n type=\"text\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n (onClear)=\"onComposedColumnClear(col)\"\n >\n <ng-template\n pTemplate=\"filter\"\n let-filter=\"filterCallback\"\n let-filterModel=\"filterModel\"\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 [options]=\"filters[composedName]?.options\"\n [ngModel]=\"filters[composedName]?.value\"\n (ngModelChange)=\"\n onComposedFilterValueChange(\n col,\n composedName,\n $event,\n filterModel\n )\n \"\n [placeholder]=\"filters[composedName]?.placeholder\"\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 </ng-template>\n </p-columnFilter>\n\n <!-- OTHER TYPES -->\n <p-columnFilter\n *ngIf=\"col.type !== TableTypeEnum.COMPOSED\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n (onClear)=\"onFilterClear(col.code!)\"\n >\n <!-- NUMBER / AMOUNT: use text input so '.' works -->\n <ng-template\n pTemplate=\"filter\"\n *ngIf=\"\n col.type === TableTypeEnum.NUMBER ||\n col.type === TableTypeEnum.AMOUNT\n \"\n let-value\n >\n <input\n pInputText\n type=\"text\"\n inputmode=\"decimal\"\n [ngModel]=\"latestFilterValues[col.code!] ?? value\"\n (ngModelChange)=\"\n onNumberFilterChange(col.code!, $event)\n \"\n placeholder=\"Enter a number\"\n />\n </ng-template>\n\n <!-- DATE -->\n <ng-template\n pTemplate=\"filter\"\n *ngIf=\"col.type === TableTypeEnum.DATE\"\n let-filterModel=\"filterModel\"\n >\n <p-calendar\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\n dateFormat=\"dd/mm/yy\"\n placeholder=\"Choose a date\"\n ></p-calendar>\n </ng-template>\n\n <!-- MULTISELECT -->\n <ng-template\n pTemplate=\"filter\"\n *ngIf=\"\n col.type === TableTypeEnum.MULTISELECT &&\n col.filterOptions &&\n col.filterOptions.length > 0\n \"\n let-filterModel=\"filterModel\"\n >\n <p-multiSelect\n [options]=\"col.filterOptions\"\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\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 </ng-container>\n\n <!-- NON-SORTABLE HEADER -->\n <ng-template #noSortHeader>\n <div\n class=\"header-container d-flex align-items-center justify-content-between\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n [style.margin]=\"'10px'\"\n >\n <span [ngClass]=\"getHeaderTitleClass(col)\">\n {{ col.title }}\n </span>\n\n <ng-container *ngIf=\"hasColumnFilter && col.isFilter !== false\">\n <!-- AMOUNT behaves like NUMBER -->\n <p-columnFilter\n *ngIf=\"col.type === 'AMOUNT'\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [currency]=\"getCurrencySymbol(col)\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n >\n <ng-template\n pTemplate=\"filter\"\n let-filterModel=\"filterModel\"\n >\n <input\n pInputText\n type=\"text\"\n inputmode=\"decimal\"\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\n placeholder=\"Enter an amount\"\n />\n </ng-template>\n </p-columnFilter>\n\n <p-columnFilter\n *ngIf=\"col.type !== 'AMOUNT'\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n >\n <!-- DATE -->\n <ng-template\n pTemplate=\"filter\"\n let-filterModel=\"filterModel\"\n *ngIf=\"getColumnFilterType(col) === 'date'\"\n >\n <p-calendar\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\n dateFormat=\"dd/mm/yy\"\n ></p-calendar>\n </ng-template>\n\n <!-- MULTISELECT -->\n <ng-template\n pTemplate=\"filter\"\n let-filterModel=\"filterModel\"\n *ngIf=\"getColumnFilterType(col) === 'multiSelect'\"\n >\n <p-multiSelect\n [options]=\"col.filterOptions\"\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\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 </ng-template>\n </th>\n\n <!-- GROUPED HEADER -->\n <ng-template #groupHeader>\n <th\n [attr.colspan]=\"col.children?.length\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngClass]=\"getHeaderAlignClass(col)\"\n >\n <span>{{ col.title }}</span>\n </th>\n </ng-template>\n </ng-container>\n </tr>\n\n <!-- CHILD HEADERS -->\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]=\"child.width || getHeaderWidth(child)\"\n [style.padding]=\"'0px'\"\n ></th>\n </ng-container>\n </ng-container>\n </tr>\n </ng-template>\n\n <!-- EMPTY MESSAGE -->\n <ng-template pTemplate=\"emptymessage\">\n <tr class=\"p-datatable-emptymessage\">\n <!-- span all columns -->\n <td class=\"empty-message-cell\" [attr.colspan]=\"columns.length || 1\">\n <div class=\"empty-message-wrapper\">\n <div class=\"empty-message\">\n <i class=\"pi pi-info-circle\"></i>\n <p>No records available to display.</p>\n </div>\n </div>\n </td>\n </tr>\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 <tr *ngIf=\"!loading\" [pEditableRow]=\"isEdit ? data : null\">\n <ng-container *ngFor=\"let col of columns\">\n <!-- SIMPLE COLUMNS (no children) -->\n <ng-container *ngIf=\"!col.children; else childColumns\">\n <!-- EDITABLE CELL -->\n <td\n *ngIf=\"\n isEditable(col.code!) && col.type !== TableTypeEnum.ACTION;\n else normalTD\n \"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngClass]=\"getDataAlignClass(col)\"\n >\n <!-- Editable input for NUMBER/DATE/STRING/MULTISELECT -->\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)=\"changeHandler(data.id, col.code, $event)\"\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 *ngIf=\"isDatePicker(col.code); else normalInput\">\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 <!-- NON-EDITABLE CELL (ALWAYS RENDERED) -->\n <ng-template #normalTD>\n <td\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngClass]=\"[\n getDataAlignClass(col),\n col.type === TableTypeEnum.ACTION ? 'action-column' : ''\n ]\"\n >\n <!-- ACTION column: built-in edit/delete + custom actions -->\n <ng-container\n *ngIf=\"col.type === TableTypeEnum.ACTION; else nonActionCell\"\n >\n <div class=\"action-buttons-container\">\n <!-- built-in delete -->\n <button\n *ngIf=\"isDelete\"\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\n <!-- built-in inline edit -->\n <div *ngIf=\"isEdit\">\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\n <!-- custom actions -->\n <button\n *ngFor=\"let act of customActions\"\n pButton\n pRipple\n type=\"button\"\n class=\"p-button-rounded p-button-text\"\n [icon]=\"act.icon || 'pi pi-ellipsis-h'\"\n [ngClass]=\"act.styleClass\"\n (click)=\"onCustomActionClick(act, data)\"\n ></button>\n </div>\n </ng-container>\n\n <!-- NON-ACTION cells -->\n <ng-template #nonActionCell>\n <!-- COMPOSED -->\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.COMPOSED;\n else nonComposed\n \"\n >\n <div class=\"composed-cell\">\n <ng-container\n *ngFor=\"\n let composedName of col.composedNames;\n let i = index\n \"\n >\n <!-- 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]=\"\n getImageStyle(col.composedStyles?.[composedName])\n \"\n />\n </ng-container>\n\n <!-- 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]=\"\n getTitleStyle(\n col.composedStyles?.[composedName]\n )\n \"\n >\n {{ data[col.code!]?.[composedName] }}\n </span>\n </ng-container>\n </ng-container>\n </div>\n </ng-container>\n\n <ng-template #nonComposed>\n <!-- AMOUNT-->\n <ng-container\n *ngIf=\"col.type === TableTypeEnum.AMOUNT; else nonAmount\"\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\n <ng-template #nonAmount>\n <!-- NUMBER-->\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.NUMBER;\n else nonNumber\n \"\n >\n {{\n formatNumber(\n data[col.code!],\n col.decimalPlaces,\n col.thousandSeparator,\n col.decimalSeparator\n )\n }}\n </ng-container>\n\n <ng-template #nonNumber>\n <!-- DATE -->\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.DATE ||\n col.type === TableTypeEnum.DATETIME;\n else normalTypes\n \"\n >\n <ng-container *ngIf=\"col.type === TableTypeEnum.DATE\">\n {{\n formatDateWithColumn(\n parseAnyDate(data[col.code!]),\n col\n )\n }}\n </ng-container>\n\n <ng-container\n *ngIf=\"col.type === TableTypeEnum.DATETIME\"\n >\n {{\n formatDateTimeWithColumn(\n parseAnyDate(data[col.code!]),\n col\n )\n }}\n </ng-container>\n </ng-container>\n\n <ng-template #normalTypes>\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 </ng-template>\n </ng-template>\n </ng-template>\n </ng-template>\n </ng-template>\n </td>\n </ng-template>\n </ng-container>\n\n <!-- CHILD COLUMNS -->\n <ng-template #childColumns>\n <ng-container *ngFor=\"let child of col.children\">\n <td\n [style.width]=\"child.width || getHeaderWidth(child)\"\n [ngClass]=\"getDataAlignClass(child)\"\n >\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]=\"child.code ? data[child.code] : null\"\n (change)=\"onChange($event, data.id, child.code)\"\n />\n </ng-template>\n <ng-template pTemplate=\"output\">\n {{ child.code ? data[child.code] : \"\" }}\n </ng-template>\n </p-cellEditor>\n </ng-container>\n\n <ng-template #childNormalTD>\n {{ child.code ? data[child.code] : \"\" }}\n </ng-template>\n </td>\n </ng-container>\n </ng-template>\n </ng-container>\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 .header-title-left,.pt-advanced-prime-table ::ng-deep .header-title-center,.pt-advanced-prime-table ::ng-deep .header-title-right{flex:1}.pt-advanced-prime-table ::ng-deep .header-title-left{text-align:left}.pt-advanced-prime-table ::ng-deep .header-title-center{text-align:center}.pt-advanced-prime-table ::ng-deep .header-title-right{text-align:right}.pt-advanced-prime-table ::ng-deep .header-align-left{text-align:left}.pt-advanced-prime-table ::ng-deep .header-align-center{text-align:center}.pt-advanced-prime-table ::ng-deep .header-align-right{text-align:right}.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:block;width:100%}.pt-advanced-prime-table ::ng-deep .p-datatable-emptymessage>td.empty-message-cell{padding:0!important}.pt-advanced-prime-table .empty-message-wrapper{width:100%;height:100%;min-height:180px;display:flex;align-items:center;justify-content:center}.pt-advanced-prime-table .empty-message{text-align:center;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}.pt-advanced-prime-table ::ng-deep .p-datatable-tbody>tr>td.cell-align-left{text-align:left!important}.pt-advanced-prime-table ::ng-deep .p-datatable-tbody>tr>td.cell-align-center{text-align:center!important}.pt-advanced-prime-table ::ng-deep .p-datatable-tbody>tr>td.cell-align-right{text-align:right!important}.pt-advanced-prime-table ::ng-deep th.action-column,.pt-advanced-prime-table ::ng-deep td.action-column{text-align:center!important;justify-content:center;align-items:center;overflow:hidden;white-space:nowrap;padding:0}.pt-advanced-prime-table ::ng-deep .action-buttons-container{width:100%;display:flex;justify-content:center!important;align-items:center!important;gap:.35rem}.pt-advanced-prime-table ::ng-deep .action-buttons-container .p-button.p-button-rounded.p-button-text{padding:.25rem!important;min-width:22px!important;height:22px!important}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { 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: i1$1.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: i3.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain"] }, { kind: "component", type: i6.Calendar, selector: "p-calendar", inputs: ["iconDisplay", "style", "styleClass", "inputStyle", "inputId", "name", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "disabled", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "appendTo", "readonlyInput", "shortYearCutoff", "monthNavigator", "yearNavigator", "hourFormat", "timeOnly", "stepYearPicker", "stepHour", "stepMinute", "stepSecond", "showSeconds", "required", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "variant", "minDate", "maxDate", "disabledDates", "disabledDays", "yearRange", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "locale", "view", "defaultDate"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i8.MultiSelect, selector: "p-multiSelect", inputs: ["id", "ariaLabel", "style", "styleClass", "panelStyle", "panelStyleClass", "inputId", "disabled", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "variant", "appendTo", "dataKey", "name", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "defaultLabel", "placeholder", "options", "filterValue", "itemSize", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "component", type: i9.Tag, selector: "p-tag", inputs: ["style", "styleClass", "severity", "value", "icon", "rounded"] }, { kind: "component", type: i10.IconField, selector: "p-iconField", inputs: ["iconPosition"] }, { kind: "component", type: i11.InputIcon, selector: "p-inputIcon", inputs: ["styleClass"] }, { kind: "pipe", type: CustomCurrencyPipe, name: "customCurrency" }, { kind: "pipe", type: CustomDatePipe, name: "customDate" }] }); }
865
855
  }
866
856
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: PTAdvancedPrimeTableComponent, decorators: [{
867
857
  type: Component,
868
- args: [{ selector: 'pt-advanced-prime-table', template: "<div class=\"pt-advanced-prime-table table-container\">\n <p-table\n #dt\n [value]=\"data\"\n [loading]=\"loading\"\n [rows]=\"rows\"\n [paginator]=\"isPaginated\"\n [globalFilterFields]=\"globalFilterFields\"\n [rowsPerPageOptions]=\"rowsPerPage\"\n [totalRecords]=\"totalRecords\"\n [lazy]=\"isPaginated\"\n dataKey=\"id\"\n styleClass=\"p-datatable-gridlines p-datatable-striped\"\n [scrollable]=\"true\"\n [scrollHeight]=\"maxHeight !== null ? maxHeight : undefined\"\n (onFilter)=\"filterColumn($event)\"\n (onPage)=\"changePage($event)\"\n (onSort)=\"sortColumn($event)\"\n >\n <!-- COLGROUP: sync header/body widths -->\n <ng-template pTemplate=\"colgroup\" let-columns>\n <colgroup>\n <col\n *ngFor=\"let col of columns\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n />\n </colgroup>\n </ng-template>\n\n <!-- CAPTION -->\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\n <div class=\"ml-auto\" *ngIf=\"hasSearchFilter\">\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 <!-- HEADER -->\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 pSortableColumn=\"{{ col.code }}\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n [ngClass]=\"[\n getHeaderAlignClass(col),\n col.type === TableTypeEnum.ACTION ? 'action-column' : ''\n ]\"\n colspan=\"1\"\n >\n <!-- SORTABLE HEADER -->\n <ng-container\n *ngIf=\"isSortable && col.isSortable !== false; else noSortHeader\"\n >\n <div\n class=\"header-container d-flex align-items-center justify-content-between\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n [style.margin]=\"'10px'\"\n >\n <span [ngClass]=\"getHeaderTitleClass(col)\">\n {{ col.title }}\n </span>\n <div\n class=\"icons d-flex align-items-center\"\n [style.width]=\"'77px'\"\n >\n <p-sortIcon field=\"{{ col.code }}\" />\n\n <!-- COLUMN FILTERS -->\n <ng-container\n *ngIf=\"hasColumnFilter && col.isFilter !== false\"\n >\n <!-- COMPOSED FILTER (uses built-in Apply/Clear) -->\n <p-columnFilter\n *ngIf=\"col.type === TableTypeEnum.COMPOSED\"\n display=\"menu\"\n [field]=\"col.code\"\n type=\"text\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n (onClear)=\"onComposedColumnClear(col)\"\n >\n <ng-template\n pTemplate=\"filter\"\n let-filter=\"filterCallback\"\n let-filterModel=\"filterModel\"\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 [options]=\"filters[composedName]?.options\"\n [ngModel]=\"filters[composedName]?.value\"\n (ngModelChange)=\"\n onComposedFilterValueChange(\n col,\n composedName,\n $event,\n filterModel\n )\n \"\n [placeholder]=\"filters[composedName]?.placeholder\"\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 </ng-template>\n </p-columnFilter>\n\n <!-- OTHER TYPES -->\n <p-columnFilter\n *ngIf=\"col.type !== TableTypeEnum.COMPOSED\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n (onClear)=\"onFilterClear(col.code!)\"\n >\n <!-- NUMBER / AMOUNT: use text input so '.' works -->\n <ng-template\n pTemplate=\"filter\"\n *ngIf=\"\n col.type === TableTypeEnum.NUMBER ||\n col.type === TableTypeEnum.AMOUNT\n \"\n let-value\n >\n <input\n pInputText\n type=\"text\"\n inputmode=\"decimal\"\n [ngModel]=\"latestFilterValues[col.code!] ?? value\"\n (ngModelChange)=\"\n onNumberFilterChange(col.code!, $event)\n \"\n placeholder=\"Enter a number\"\n />\n </ng-template>\n\n <!-- DATE -->\n <ng-template\n pTemplate=\"filter\"\n *ngIf=\"col.type === TableTypeEnum.DATE\"\n let-filterModel=\"filterModel\"\n >\n <p-calendar\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\n dateFormat=\"dd/mm/yy\"\n placeholder=\"Choose a date\"\n ></p-calendar>\n </ng-template>\n\n <!-- MULTISELECT -->\n <ng-template\n pTemplate=\"filter\"\n *ngIf=\"\n col.type === TableTypeEnum.MULTISELECT &&\n col.filterOptions &&\n col.filterOptions.length > 0\n \"\n let-filterModel=\"filterModel\"\n >\n <p-multiSelect\n [options]=\"col.filterOptions\"\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\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 </ng-container>\n\n <!-- NON-SORTABLE HEADER -->\n <ng-template #noSortHeader>\n <div\n class=\"header-container d-flex align-items-center justify-content-between\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n [style.margin]=\"'10px'\"\n >\n <span [ngClass]=\"getHeaderTitleClass(col)\">\n {{ col.title }}\n </span>\n\n <ng-container *ngIf=\"hasColumnFilter && col.isFilter !== false\">\n <!-- AMOUNT behaves like NUMBER -->\n <p-columnFilter\n *ngIf=\"col.type === 'AMOUNT'\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [currency]=\"getCurrencySymbol(col)\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n >\n <ng-template\n pTemplate=\"filter\"\n let-filterModel=\"filterModel\"\n >\n <input\n pInputText\n type=\"text\"\n inputmode=\"decimal\"\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\n placeholder=\"Enter an amount\"\n />\n </ng-template>\n </p-columnFilter>\n\n <p-columnFilter\n *ngIf=\"col.type !== 'AMOUNT'\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n >\n <!-- DATE -->\n <ng-template\n pTemplate=\"filter\"\n let-filterModel=\"filterModel\"\n *ngIf=\"getColumnFilterType(col) === 'date'\"\n >\n <p-calendar\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\n dateFormat=\"dd/mm/yy\"\n ></p-calendar>\n </ng-template>\n\n <!-- MULTISELECT -->\n <ng-template\n pTemplate=\"filter\"\n let-filterModel=\"filterModel\"\n *ngIf=\"getColumnFilterType(col) === 'multiSelect'\"\n >\n <p-multiSelect\n [options]=\"col.filterOptions\"\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\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 </ng-template>\n </th>\n\n <!-- GROUPED HEADER -->\n <ng-template #groupHeader>\n <th\n [attr.colspan]=\"col.children?.length\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngClass]=\"getHeaderAlignClass(col)\"\n >\n <span>{{ col.title }}</span>\n </th>\n </ng-template>\n </ng-container>\n </tr>\n\n <!-- CHILD HEADERS -->\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]=\"child.width || getHeaderWidth(child)\"\n [style.padding]=\"'0px'\"\n ></th>\n </ng-container>\n </ng-container>\n </tr>\n </ng-template>\n\n <!-- EMPTY MESSAGE -->\n <ng-template pTemplate=\"emptymessage\">\n <tr class=\"p-datatable-emptymessage\">\n <!-- span all columns -->\n <td class=\"empty-message-cell\" [attr.colspan]=\"columns.length || 1\">\n <div class=\"empty-message-wrapper\">\n <div class=\"empty-message\">\n <i class=\"pi pi-info-circle\"></i>\n <p>No records available to display.</p>\n </div>\n </div>\n </td>\n </tr>\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 <tr *ngIf=\"!loading\" [pEditableRow]=\"isEdit ? data : null\">\n <ng-container *ngFor=\"let col of columns\">\n <!-- SIMPLE COLUMNS (no children) -->\n <ng-container *ngIf=\"!col.children; else childColumns\">\n <!-- EDITABLE CELL -->\n <td\n *ngIf=\"\n isEditable(col.code!) && col.type !== TableTypeEnum.ACTION;\n else normalTD\n \"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngClass]=\"getDataAlignClass(col)\"\n >\n <!-- Editable input for NUMBER/DATE/STRING/MULTISELECT -->\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)=\"changeHandler(data.id, col.code, $event)\"\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 *ngIf=\"isDatePicker(col.code); else normalInput\">\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 <!-- NON-EDITABLE CELL (ALWAYS RENDERED) -->\n <ng-template #normalTD>\n <td\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngClass]=\"[\n getDataAlignClass(col),\n col.type === TableTypeEnum.ACTION ? 'action-column' : ''\n ]\"\n >\n <!-- ACTION column: built-in edit/delete + custom actions -->\n <ng-container\n *ngIf=\"col.type === TableTypeEnum.ACTION; else nonActionCell\"\n >\n <div class=\"action-buttons-container\">\n <!-- built-in delete -->\n <button\n *ngIf=\"isDelete\"\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\n <!-- built-in inline edit -->\n <div *ngIf=\"isEdit\">\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\n <!-- custom actions -->\n <button\n *ngFor=\"let act of customActions\"\n pButton\n pRipple\n type=\"button\"\n class=\"p-button-rounded p-button-text\"\n [icon]=\"act.icon || 'pi pi-ellipsis-h'\"\n [ngClass]=\"act.styleClass\"\n (click)=\"onCustomActionClick(act, data)\"\n ></button>\n </div>\n </ng-container>\n\n <!-- NON-ACTION cells -->\n <ng-template #nonActionCell>\n <!-- COMPOSED -->\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.COMPOSED;\n else nonComposed\n \"\n >\n <div class=\"composed-cell\">\n <ng-container\n *ngFor=\"\n let composedName of col.composedNames;\n let i = index\n \"\n >\n <!-- 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]=\"\n getImageStyle(col.composedStyles?.[composedName])\n \"\n />\n </ng-container>\n\n <!-- 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]=\"\n getTitleStyle(\n col.composedStyles?.[composedName]\n )\n \"\n >\n {{ data[col.code!]?.[composedName] }}\n </span>\n </ng-container>\n </ng-container>\n </div>\n </ng-container>\n\n <ng-template #nonComposed>\n <!-- AMOUNT-->\n <ng-container\n *ngIf=\"col.type === TableTypeEnum.AMOUNT; else nonAmount\"\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\n <ng-template #nonAmount>\n <!-- NUMBER-->\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.NUMBER;\n else nonNumber\n \"\n >\n {{\n formatNumber(\n data[col.code!],\n col.decimalPlaces,\n col.thousandSeparator,\n col.decimalSeparator\n )\n }}\n </ng-container>\n\n <ng-template #nonNumber>\n <!-- DATE -->\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.DATE;\n else normalTypes\n \"\n >\n {{ formatDate(data[col.code!]) }}\n </ng-container>\n\n <ng-template #normalTypes>\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 </ng-template>\n </ng-template>\n </ng-template>\n </ng-template>\n </ng-template>\n </td>\n </ng-template>\n </ng-container>\n\n <!-- CHILD COLUMNS -->\n <ng-template #childColumns>\n <ng-container *ngFor=\"let child of col.children\">\n <td\n [style.width]=\"child.width || getHeaderWidth(child)\"\n [ngClass]=\"getDataAlignClass(child)\"\n >\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]=\"child.code ? data[child.code] : null\"\n (change)=\"onChange($event, data.id, child.code)\"\n />\n </ng-template>\n <ng-template pTemplate=\"output\">\n {{ child.code ? data[child.code] : \"\" }}\n </ng-template>\n </p-cellEditor>\n </ng-container>\n\n <ng-template #childNormalTD>\n {{ child.code ? data[child.code] : \"\" }}\n </ng-template>\n </td>\n </ng-container>\n </ng-template>\n </ng-container>\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 .header-title-left,.pt-advanced-prime-table ::ng-deep .header-title-center,.pt-advanced-prime-table ::ng-deep .header-title-right{flex:1}.pt-advanced-prime-table ::ng-deep .header-title-left{text-align:left}.pt-advanced-prime-table ::ng-deep .header-title-center{text-align:center}.pt-advanced-prime-table ::ng-deep .header-title-right{text-align:right}.pt-advanced-prime-table ::ng-deep .header-align-left{text-align:left}.pt-advanced-prime-table ::ng-deep .header-align-center{text-align:center}.pt-advanced-prime-table ::ng-deep .header-align-right{text-align:right}.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:block;width:100%}.pt-advanced-prime-table ::ng-deep .p-datatable-emptymessage>td.empty-message-cell{padding:0!important}.pt-advanced-prime-table .empty-message-wrapper{width:100%;height:100%;min-height:180px;display:flex;align-items:center;justify-content:center}.pt-advanced-prime-table .empty-message{text-align:center;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}.pt-advanced-prime-table ::ng-deep .p-datatable-tbody>tr>td.cell-align-left{text-align:left!important}.pt-advanced-prime-table ::ng-deep .p-datatable-tbody>tr>td.cell-align-center{text-align:center!important}.pt-advanced-prime-table ::ng-deep .p-datatable-tbody>tr>td.cell-align-right{text-align:right!important}.pt-advanced-prime-table ::ng-deep th.action-column,.pt-advanced-prime-table ::ng-deep td.action-column{text-align:center!important;justify-content:center;align-items:center;overflow:hidden;white-space:nowrap;padding:0}.pt-advanced-prime-table ::ng-deep .action-buttons-container{width:100%;display:flex;justify-content:center!important;align-items:center!important;gap:.35rem}.pt-advanced-prime-table ::ng-deep .action-buttons-container .p-button.p-button-rounded.p-button-text{padding:.25rem!important;min-width:22px!important;height:22px!important}\n"] }]
869
- }], ctorParameters: () => [], propDecorators: { data: [{
858
+ args: [{ selector: 'pt-advanced-prime-table', template: "<div class=\"pt-advanced-prime-table table-container\">\n <p-table\n #dt\n [value]=\"data\"\n [loading]=\"loading\"\n [rows]=\"rows\"\n [paginator]=\"isPaginated\"\n [globalFilterFields]=\"globalFilterFields\"\n [rowsPerPageOptions]=\"rowsPerPage\"\n [totalRecords]=\"totalRecords\"\n [lazy]=\"isLazy\"\n dataKey=\"id\"\n styleClass=\"p-datatable-gridlines p-datatable-striped\"\n [scrollable]=\"true\"\n [scrollHeight]=\"maxHeight !== null ? maxHeight : undefined\"\n (onFilter)=\"filterColumn($event)\"\n (onPage)=\"changePage($event)\"\n (onSort)=\"sortColumn($event)\"\n >\n <!-- COLGROUP: sync header/body widths -->\n <ng-template pTemplate=\"colgroup\" let-columns>\n <colgroup>\n <col\n *ngFor=\"let col of columns\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n />\n </colgroup>\n </ng-template>\n\n <!-- CAPTION -->\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\n <div class=\"ml-auto\" *ngIf=\"hasSearchFilter\">\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 <!-- HEADER -->\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 pSortableColumn=\"{{ col.code }}\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n [ngClass]=\"[\n getHeaderAlignClass(col),\n col.type === TableTypeEnum.ACTION ? 'action-column' : ''\n ]\"\n colspan=\"1\"\n >\n <!-- SORTABLE HEADER -->\n <ng-container\n *ngIf=\"isSortable && col.isSortable !== false; else noSortHeader\"\n >\n <div\n class=\"header-container d-flex align-items-center justify-content-between\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n [style.margin]=\"'10px'\"\n >\n <span [ngClass]=\"getHeaderTitleClass(col)\">\n {{ col.title }}\n </span>\n <div\n class=\"icons d-flex align-items-center\"\n [style.width]=\"'77px'\"\n >\n <p-sortIcon field=\"{{ col.code }}\" />\n\n <!-- COLUMN FILTERS -->\n <ng-container\n *ngIf=\"hasColumnFilter && col.isFilter !== false\"\n >\n <!-- COMPOSED FILTER (uses built-in Apply/Clear) -->\n <p-columnFilter\n *ngIf=\"col.type === TableTypeEnum.COMPOSED\"\n display=\"menu\"\n [field]=\"col.code\"\n type=\"text\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n (onClear)=\"onComposedColumnClear(col)\"\n >\n <ng-template\n pTemplate=\"filter\"\n let-filter=\"filterCallback\"\n let-filterModel=\"filterModel\"\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 [options]=\"filters[composedName]?.options\"\n [ngModel]=\"filters[composedName]?.value\"\n (ngModelChange)=\"\n onComposedFilterValueChange(\n col,\n composedName,\n $event,\n filterModel\n )\n \"\n [placeholder]=\"filters[composedName]?.placeholder\"\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 </ng-template>\n </p-columnFilter>\n\n <!-- OTHER TYPES -->\n <p-columnFilter\n *ngIf=\"col.type !== TableTypeEnum.COMPOSED\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n (onClear)=\"onFilterClear(col.code!)\"\n >\n <!-- NUMBER / AMOUNT: use text input so '.' works -->\n <ng-template\n pTemplate=\"filter\"\n *ngIf=\"\n col.type === TableTypeEnum.NUMBER ||\n col.type === TableTypeEnum.AMOUNT\n \"\n let-value\n >\n <input\n pInputText\n type=\"text\"\n inputmode=\"decimal\"\n [ngModel]=\"latestFilterValues[col.code!] ?? value\"\n (ngModelChange)=\"\n onNumberFilterChange(col.code!, $event)\n \"\n placeholder=\"Enter a number\"\n />\n </ng-template>\n\n <!-- DATE -->\n <ng-template\n pTemplate=\"filter\"\n *ngIf=\"col.type === TableTypeEnum.DATE\"\n let-filterModel=\"filterModel\"\n >\n <p-calendar\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\n dateFormat=\"dd/mm/yy\"\n placeholder=\"Choose a date\"\n ></p-calendar>\n </ng-template>\n\n <!-- MULTISELECT -->\n <ng-template\n pTemplate=\"filter\"\n *ngIf=\"\n col.type === TableTypeEnum.MULTISELECT &&\n col.filterOptions &&\n col.filterOptions.length > 0\n \"\n let-filterModel=\"filterModel\"\n >\n <p-multiSelect\n [options]=\"col.filterOptions\"\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\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 </ng-container>\n\n <!-- NON-SORTABLE HEADER -->\n <ng-template #noSortHeader>\n <div\n class=\"header-container d-flex align-items-center justify-content-between\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [style.padding]=\"'0px'\"\n [style.margin]=\"'10px'\"\n >\n <span [ngClass]=\"getHeaderTitleClass(col)\">\n {{ col.title }}\n </span>\n\n <ng-container *ngIf=\"hasColumnFilter && col.isFilter !== false\">\n <!-- AMOUNT behaves like NUMBER -->\n <p-columnFilter\n *ngIf=\"col.type === 'AMOUNT'\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [currency]=\"getCurrencySymbol(col)\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n >\n <ng-template\n pTemplate=\"filter\"\n let-filterModel=\"filterModel\"\n >\n <input\n pInputText\n type=\"text\"\n inputmode=\"decimal\"\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\n placeholder=\"Enter an amount\"\n />\n </ng-template>\n </p-columnFilter>\n\n <p-columnFilter\n *ngIf=\"col.type !== 'AMOUNT'\"\n display=\"menu\"\n [field]=\"col.code\"\n [type]=\"getColumnFilterType(col)\"\n [showApplyButton]=\"true\"\n [showClearButton]=\"true\"\n >\n <!-- DATE -->\n <ng-template\n pTemplate=\"filter\"\n let-filterModel=\"filterModel\"\n *ngIf=\"getColumnFilterType(col) === 'date'\"\n >\n <p-calendar\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\n dateFormat=\"dd/mm/yy\"\n ></p-calendar>\n </ng-template>\n\n <!-- MULTISELECT -->\n <ng-template\n pTemplate=\"filter\"\n let-filterModel=\"filterModel\"\n *ngIf=\"getColumnFilterType(col) === 'multiSelect'\"\n >\n <p-multiSelect\n [options]=\"col.filterOptions\"\n [ngModel]=\"\n latestFilterValues[col.code!] ?? filterModel?.value\n \"\n (ngModelChange)=\"\n onFilterValueChange(col.code!, filterModel, $event)\n \"\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 </ng-template>\n </th>\n\n <!-- GROUPED HEADER -->\n <ng-template #groupHeader>\n <th\n [attr.colspan]=\"col.children?.length\"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngClass]=\"getHeaderAlignClass(col)\"\n >\n <span>{{ col.title }}</span>\n </th>\n </ng-template>\n </ng-container>\n </tr>\n\n <!-- CHILD HEADERS -->\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]=\"child.width || getHeaderWidth(child)\"\n [style.padding]=\"'0px'\"\n ></th>\n </ng-container>\n </ng-container>\n </tr>\n </ng-template>\n\n <!-- EMPTY MESSAGE -->\n <ng-template pTemplate=\"emptymessage\">\n <tr class=\"p-datatable-emptymessage\">\n <!-- span all columns -->\n <td class=\"empty-message-cell\" [attr.colspan]=\"columns.length || 1\">\n <div class=\"empty-message-wrapper\">\n <div class=\"empty-message\">\n <i class=\"pi pi-info-circle\"></i>\n <p>No records available to display.</p>\n </div>\n </div>\n </td>\n </tr>\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 <tr *ngIf=\"!loading\" [pEditableRow]=\"isEdit ? data : null\">\n <ng-container *ngFor=\"let col of columns\">\n <!-- SIMPLE COLUMNS (no children) -->\n <ng-container *ngIf=\"!col.children; else childColumns\">\n <!-- EDITABLE CELL -->\n <td\n *ngIf=\"\n isEditable(col.code!) && col.type !== TableTypeEnum.ACTION;\n else normalTD\n \"\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngClass]=\"getDataAlignClass(col)\"\n >\n <!-- Editable input for NUMBER/DATE/STRING/MULTISELECT -->\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)=\"changeHandler(data.id, col.code, $event)\"\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 *ngIf=\"isDatePicker(col.code); else normalInput\">\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 <!-- NON-EDITABLE CELL (ALWAYS RENDERED) -->\n <ng-template #normalTD>\n <td\n [style.width]=\"col.width || getHeaderWidth(col)\"\n [ngClass]=\"[\n getDataAlignClass(col),\n col.type === TableTypeEnum.ACTION ? 'action-column' : ''\n ]\"\n >\n <!-- ACTION column: built-in edit/delete + custom actions -->\n <ng-container\n *ngIf=\"col.type === TableTypeEnum.ACTION; else nonActionCell\"\n >\n <div class=\"action-buttons-container\">\n <!-- built-in delete -->\n <button\n *ngIf=\"isDelete\"\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\n <!-- built-in inline edit -->\n <div *ngIf=\"isEdit\">\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\n <!-- custom actions -->\n <button\n *ngFor=\"let act of customActions\"\n pButton\n pRipple\n type=\"button\"\n class=\"p-button-rounded p-button-text\"\n [icon]=\"act.icon || 'pi pi-ellipsis-h'\"\n [ngClass]=\"act.styleClass\"\n (click)=\"onCustomActionClick(act, data)\"\n ></button>\n </div>\n </ng-container>\n\n <!-- NON-ACTION cells -->\n <ng-template #nonActionCell>\n <!-- COMPOSED -->\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.COMPOSED;\n else nonComposed\n \"\n >\n <div class=\"composed-cell\">\n <ng-container\n *ngFor=\"\n let composedName of col.composedNames;\n let i = index\n \"\n >\n <!-- 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]=\"\n getImageStyle(col.composedStyles?.[composedName])\n \"\n />\n </ng-container>\n\n <!-- 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]=\"\n getTitleStyle(\n col.composedStyles?.[composedName]\n )\n \"\n >\n {{ data[col.code!]?.[composedName] }}\n </span>\n </ng-container>\n </ng-container>\n </div>\n </ng-container>\n\n <ng-template #nonComposed>\n <!-- AMOUNT-->\n <ng-container\n *ngIf=\"col.type === TableTypeEnum.AMOUNT; else nonAmount\"\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\n <ng-template #nonAmount>\n <!-- NUMBER-->\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.NUMBER;\n else nonNumber\n \"\n >\n {{\n formatNumber(\n data[col.code!],\n col.decimalPlaces,\n col.thousandSeparator,\n col.decimalSeparator\n )\n }}\n </ng-container>\n\n <ng-template #nonNumber>\n <!-- DATE -->\n <ng-container\n *ngIf=\"\n col.type === TableTypeEnum.DATE ||\n col.type === TableTypeEnum.DATETIME;\n else normalTypes\n \"\n >\n <ng-container *ngIf=\"col.type === TableTypeEnum.DATE\">\n {{\n formatDateWithColumn(\n parseAnyDate(data[col.code!]),\n col\n )\n }}\n </ng-container>\n\n <ng-container\n *ngIf=\"col.type === TableTypeEnum.DATETIME\"\n >\n {{\n formatDateTimeWithColumn(\n parseAnyDate(data[col.code!]),\n col\n )\n }}\n </ng-container>\n </ng-container>\n\n <ng-template #normalTypes>\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 </ng-template>\n </ng-template>\n </ng-template>\n </ng-template>\n </ng-template>\n </td>\n </ng-template>\n </ng-container>\n\n <!-- CHILD COLUMNS -->\n <ng-template #childColumns>\n <ng-container *ngFor=\"let child of col.children\">\n <td\n [style.width]=\"child.width || getHeaderWidth(child)\"\n [ngClass]=\"getDataAlignClass(child)\"\n >\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]=\"child.code ? data[child.code] : null\"\n (change)=\"onChange($event, data.id, child.code)\"\n />\n </ng-template>\n <ng-template pTemplate=\"output\">\n {{ child.code ? data[child.code] : \"\" }}\n </ng-template>\n </p-cellEditor>\n </ng-container>\n\n <ng-template #childNormalTD>\n {{ child.code ? data[child.code] : \"\" }}\n </ng-template>\n </td>\n </ng-container>\n </ng-template>\n </ng-container>\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 .header-title-left,.pt-advanced-prime-table ::ng-deep .header-title-center,.pt-advanced-prime-table ::ng-deep .header-title-right{flex:1}.pt-advanced-prime-table ::ng-deep .header-title-left{text-align:left}.pt-advanced-prime-table ::ng-deep .header-title-center{text-align:center}.pt-advanced-prime-table ::ng-deep .header-title-right{text-align:right}.pt-advanced-prime-table ::ng-deep .header-align-left{text-align:left}.pt-advanced-prime-table ::ng-deep .header-align-center{text-align:center}.pt-advanced-prime-table ::ng-deep .header-align-right{text-align:right}.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:block;width:100%}.pt-advanced-prime-table ::ng-deep .p-datatable-emptymessage>td.empty-message-cell{padding:0!important}.pt-advanced-prime-table .empty-message-wrapper{width:100%;height:100%;min-height:180px;display:flex;align-items:center;justify-content:center}.pt-advanced-prime-table .empty-message{text-align:center;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}.pt-advanced-prime-table ::ng-deep .p-datatable-tbody>tr>td.cell-align-left{text-align:left!important}.pt-advanced-prime-table ::ng-deep .p-datatable-tbody>tr>td.cell-align-center{text-align:center!important}.pt-advanced-prime-table ::ng-deep .p-datatable-tbody>tr>td.cell-align-right{text-align:right!important}.pt-advanced-prime-table ::ng-deep th.action-column,.pt-advanced-prime-table ::ng-deep td.action-column{text-align:center!important;justify-content:center;align-items:center;overflow:hidden;white-space:nowrap;padding:0}.pt-advanced-prime-table ::ng-deep .action-buttons-container{width:100%;display:flex;justify-content:center!important;align-items:center!important;gap:.35rem}.pt-advanced-prime-table ::ng-deep .action-buttons-container .p-button.p-button-rounded.p-button-text{padding:.25rem!important;min-width:22px!important;height:22px!important}\n"] }]
859
+ }], propDecorators: { data: [{
870
860
  type: Input
871
861
  }], columns: [{
872
862
  type: Input
@@ -884,6 +874,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImpo
884
874
  type: Input
885
875
  }], isPaginated: [{
886
876
  type: Input
877
+ }], isLazy: [{
878
+ type: Input
887
879
  }], actions: [{
888
880
  type: Input
889
881
  }], isSortable: [{
@@ -1198,6 +1190,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImpo
1198
1190
  var TableTypeEnum;
1199
1191
  (function (TableTypeEnum) {
1200
1192
  TableTypeEnum["DATE"] = "DATE";
1193
+ TableTypeEnum["DATETIME"] = "DATETIME";
1201
1194
  TableTypeEnum["STRING"] = "STRING";
1202
1195
  TableTypeEnum["AMOUNT"] = "AMOUNT";
1203
1196
  TableTypeEnum["NUMBER"] = "NUMBER";
@@ -2185,11 +2178,11 @@ class PTDropdownComponent {
2185
2178
  return 'left';
2186
2179
  }
2187
2180
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: PTDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2188
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.11", type: PTDropdownComponent, selector: "pt-dropdown", inputs: { formGroup: "formGroup", formField: "formField" }, ngImport: i0, template: "<!-- ng-prime-tools/projects/ng-prime-tools/src/lib/pt-dropdown/pt-dropdown.component.html -->\n<div\n [formGroup]=\"formGroup\"\n class=\"form-field\"\n [ngStyle]=\"{ width: formField.width || '100%' }\"\n *ngIf=\"!formField.hidden\"\n>\n <label *ngIf=\"formField.label\">{{ formField.label }}</label>\n\n <p-dropdown\n [formControlName]=\"formField.name\"\n [options]=\"formField.options\"\n [placeholder]=\"formField.placeholder || 'Select an option'\"\n optionLabel=\"label\"\n >\n <!-- Selected value template -->\n <ng-template pTemplate=\"selectedItem\" let-option>\n <ng-container *ngIf=\"option; else placeholderTpl\">\n <div class=\"pt-dropdown-item\">\n <ng-container *ngIf=\"getResolvedIcon(option) as iconMeta\">\n <!-- IMAGE ICON -->\n <img\n *ngIf=\"iconMeta.type === 'image'\"\n [src]=\"iconMeta.imageUrl\"\n [ngStyle]=\"iconMeta.imageStyle\"\n class=\"pt-dropdown-icon-image\"\n />\n\n <!-- FONT ICON (PrimeNG / FontAwesome) -->\n <i\n *ngIf=\"iconMeta.type === 'font'\"\n [ngClass]=\"iconMeta.fontIconClass\"\n [ngStyle]=\"iconMeta.fontIconStyle\"\n class=\"pt-dropdown-icon-font\"\n ></i>\n </ng-container>\n\n <span class=\"pt-dropdown-label\">{{ option.label }}</span>\n </div>\n </ng-container>\n </ng-template>\n\n <!-- Placeholder when no value selected -->\n <ng-template #placeholderTpl>\n <span>{{ formField.placeholder || \"Select an option\" }}</span>\n </ng-template>\n\n <!-- Items template -->\n <ng-template pTemplate=\"item\" let-option>\n <div class=\"pt-dropdown-item\">\n <ng-container *ngIf=\"getResolvedIcon(option) as iconMeta\">\n <img\n *ngIf=\"iconMeta.type === 'image'\"\n [src]=\"iconMeta.imageUrl\"\n [ngStyle]=\"iconMeta.imageStyle\"\n class=\"pt-dropdown-icon-image\"\n />\n\n <i\n *ngIf=\"iconMeta.type === 'font'\"\n [ngClass]=\"iconMeta.fontIconClass\"\n [ngStyle]=\"iconMeta.fontIconStyle\"\n class=\"pt-dropdown-icon-font\"\n ></i>\n </ng-container>\n\n <span class=\"pt-dropdown-label\">{{ option.label }}</span>\n </div>\n </ng-template>\n </p-dropdown>\n\n <div\n *ngIf=\"\n formGroup.get(formField.name)?.invalid &&\n formGroup.get(formField.name)?.touched\n \"\n >\n <small class=\"p-error\">{{ getErrorMessage() }}</small>\n </div>\n</div>\n", styles: [".form-field{display:flex;flex-direction:column;gap:.25rem}.pt-dropdown-item{display:flex;align-items:center;gap:.5rem}.pt-dropdown-icon-image{object-fit:cover}.pt-dropdown-icon-font{display:inline-flex;align-items:center;justify-content:center}.pt-dropdown-label{display:inline-block}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3$5.Dropdown, selector: "p-dropdown", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "directive", type: i1$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }] }); }
2181
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.11", type: PTDropdownComponent, selector: "pt-dropdown", inputs: { formGroup: "formGroup", formField: "formField" }, ngImport: i0, template: "<!-- ng-prime-tools/projects/ng-prime-tools/src/lib/pt-dropdown/pt-dropdown.component.html -->\n<div\n [formGroup]=\"formGroup\"\n class=\"form-field\"\n [ngStyle]=\"{ width: formField.width || '100%' }\"\n *ngIf=\"!formField.hidden\"\n>\n <label *ngIf=\"formField.label\">{{ formField.label }}</label>\n\n <p-dropdown\n [formControlName]=\"formField.name\"\n [options]=\"formField.options\"\n [placeholder]=\"formField.placeholder || 'Select an option'\"\n optionLabel=\"label\"\n >\n <!-- Selected value template -->\n <ng-template pTemplate=\"selectedItem\" let-option>\n <ng-container *ngIf=\"option; else placeholderTpl\">\n <div class=\"pt-dropdown-item\">\n <ng-container *ngIf=\"getResolvedIcon(option) as iconMeta\">\n <!-- IMAGE ICON -->\n <img\n *ngIf=\"iconMeta.type === 'image'\"\n [src]=\"iconMeta.imageUrl\"\n [ngStyle]=\"iconMeta.imageStyle\"\n class=\"pt-dropdown-icon-image\"\n />\n\n <!-- FONT ICON (PrimeNG / FontAwesome) -->\n <i\n *ngIf=\"iconMeta.type === 'font'\"\n [ngClass]=\"iconMeta.fontIconClass\"\n [ngStyle]=\"iconMeta.fontIconStyle\"\n class=\"pt-dropdown-icon-font\"\n ></i>\n </ng-container>\n\n <span class=\"pt-dropdown-label\">{{ option.label }}</span>\n </div>\n </ng-container>\n </ng-template>\n\n <!-- Placeholder when no value selected -->\n <ng-template #placeholderTpl>\n <span>{{ formField.placeholder || \"Select an option\" }}</span>\n </ng-template>\n\n <!-- Items template -->\n <ng-template pTemplate=\"item\" let-option>\n <div class=\"pt-dropdown-item\">\n <ng-container *ngIf=\"getResolvedIcon(option) as iconMeta\">\n <img\n *ngIf=\"iconMeta.type === 'image'\"\n [src]=\"iconMeta.imageUrl\"\n [ngStyle]=\"iconMeta.imageStyle\"\n class=\"pt-dropdown-icon-image\"\n />\n\n <i\n *ngIf=\"iconMeta.type === 'font'\"\n [ngClass]=\"iconMeta.fontIconClass\"\n [ngStyle]=\"iconMeta.fontIconStyle\"\n class=\"pt-dropdown-icon-font\"\n ></i>\n </ng-container>\n\n <span class=\"pt-dropdown-label\">{{ option.label }}</span>\n </div>\n </ng-template>\n </p-dropdown>\n\n <div\n *ngIf=\"\n formGroup.get(formField.name)?.invalid &&\n formGroup.get(formField.name)?.touched\n \"\n >\n <small class=\"p-error\">{{ getErrorMessage() }}</small>\n </div>\n</div>\n", styles: [".form-field{display:flex;flex-direction:column;margin-bottom:1rem;gap:.25rem}.form-field label{display:block;margin-bottom:.5rem;font-weight:700;font-size:1rem}.pt-dropdown-item{display:flex;align-items:center;gap:.5rem}.pt-dropdown-icon-image{object-fit:cover}.pt-dropdown-icon-font{display:inline-flex;align-items:center;justify-content:center}.pt-dropdown-label{display:inline-block}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3$5.Dropdown, selector: "p-dropdown", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "directive", type: i1$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }] }); }
2189
2182
  }
2190
2183
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: PTDropdownComponent, decorators: [{
2191
2184
  type: Component,
2192
- args: [{ selector: 'pt-dropdown', template: "<!-- ng-prime-tools/projects/ng-prime-tools/src/lib/pt-dropdown/pt-dropdown.component.html -->\n<div\n [formGroup]=\"formGroup\"\n class=\"form-field\"\n [ngStyle]=\"{ width: formField.width || '100%' }\"\n *ngIf=\"!formField.hidden\"\n>\n <label *ngIf=\"formField.label\">{{ formField.label }}</label>\n\n <p-dropdown\n [formControlName]=\"formField.name\"\n [options]=\"formField.options\"\n [placeholder]=\"formField.placeholder || 'Select an option'\"\n optionLabel=\"label\"\n >\n <!-- Selected value template -->\n <ng-template pTemplate=\"selectedItem\" let-option>\n <ng-container *ngIf=\"option; else placeholderTpl\">\n <div class=\"pt-dropdown-item\">\n <ng-container *ngIf=\"getResolvedIcon(option) as iconMeta\">\n <!-- IMAGE ICON -->\n <img\n *ngIf=\"iconMeta.type === 'image'\"\n [src]=\"iconMeta.imageUrl\"\n [ngStyle]=\"iconMeta.imageStyle\"\n class=\"pt-dropdown-icon-image\"\n />\n\n <!-- FONT ICON (PrimeNG / FontAwesome) -->\n <i\n *ngIf=\"iconMeta.type === 'font'\"\n [ngClass]=\"iconMeta.fontIconClass\"\n [ngStyle]=\"iconMeta.fontIconStyle\"\n class=\"pt-dropdown-icon-font\"\n ></i>\n </ng-container>\n\n <span class=\"pt-dropdown-label\">{{ option.label }}</span>\n </div>\n </ng-container>\n </ng-template>\n\n <!-- Placeholder when no value selected -->\n <ng-template #placeholderTpl>\n <span>{{ formField.placeholder || \"Select an option\" }}</span>\n </ng-template>\n\n <!-- Items template -->\n <ng-template pTemplate=\"item\" let-option>\n <div class=\"pt-dropdown-item\">\n <ng-container *ngIf=\"getResolvedIcon(option) as iconMeta\">\n <img\n *ngIf=\"iconMeta.type === 'image'\"\n [src]=\"iconMeta.imageUrl\"\n [ngStyle]=\"iconMeta.imageStyle\"\n class=\"pt-dropdown-icon-image\"\n />\n\n <i\n *ngIf=\"iconMeta.type === 'font'\"\n [ngClass]=\"iconMeta.fontIconClass\"\n [ngStyle]=\"iconMeta.fontIconStyle\"\n class=\"pt-dropdown-icon-font\"\n ></i>\n </ng-container>\n\n <span class=\"pt-dropdown-label\">{{ option.label }}</span>\n </div>\n </ng-template>\n </p-dropdown>\n\n <div\n *ngIf=\"\n formGroup.get(formField.name)?.invalid &&\n formGroup.get(formField.name)?.touched\n \"\n >\n <small class=\"p-error\">{{ getErrorMessage() }}</small>\n </div>\n</div>\n", styles: [".form-field{display:flex;flex-direction:column;gap:.25rem}.pt-dropdown-item{display:flex;align-items:center;gap:.5rem}.pt-dropdown-icon-image{object-fit:cover}.pt-dropdown-icon-font{display:inline-flex;align-items:center;justify-content:center}.pt-dropdown-label{display:inline-block}\n"] }]
2185
+ args: [{ selector: 'pt-dropdown', template: "<!-- ng-prime-tools/projects/ng-prime-tools/src/lib/pt-dropdown/pt-dropdown.component.html -->\n<div\n [formGroup]=\"formGroup\"\n class=\"form-field\"\n [ngStyle]=\"{ width: formField.width || '100%' }\"\n *ngIf=\"!formField.hidden\"\n>\n <label *ngIf=\"formField.label\">{{ formField.label }}</label>\n\n <p-dropdown\n [formControlName]=\"formField.name\"\n [options]=\"formField.options\"\n [placeholder]=\"formField.placeholder || 'Select an option'\"\n optionLabel=\"label\"\n >\n <!-- Selected value template -->\n <ng-template pTemplate=\"selectedItem\" let-option>\n <ng-container *ngIf=\"option; else placeholderTpl\">\n <div class=\"pt-dropdown-item\">\n <ng-container *ngIf=\"getResolvedIcon(option) as iconMeta\">\n <!-- IMAGE ICON -->\n <img\n *ngIf=\"iconMeta.type === 'image'\"\n [src]=\"iconMeta.imageUrl\"\n [ngStyle]=\"iconMeta.imageStyle\"\n class=\"pt-dropdown-icon-image\"\n />\n\n <!-- FONT ICON (PrimeNG / FontAwesome) -->\n <i\n *ngIf=\"iconMeta.type === 'font'\"\n [ngClass]=\"iconMeta.fontIconClass\"\n [ngStyle]=\"iconMeta.fontIconStyle\"\n class=\"pt-dropdown-icon-font\"\n ></i>\n </ng-container>\n\n <span class=\"pt-dropdown-label\">{{ option.label }}</span>\n </div>\n </ng-container>\n </ng-template>\n\n <!-- Placeholder when no value selected -->\n <ng-template #placeholderTpl>\n <span>{{ formField.placeholder || \"Select an option\" }}</span>\n </ng-template>\n\n <!-- Items template -->\n <ng-template pTemplate=\"item\" let-option>\n <div class=\"pt-dropdown-item\">\n <ng-container *ngIf=\"getResolvedIcon(option) as iconMeta\">\n <img\n *ngIf=\"iconMeta.type === 'image'\"\n [src]=\"iconMeta.imageUrl\"\n [ngStyle]=\"iconMeta.imageStyle\"\n class=\"pt-dropdown-icon-image\"\n />\n\n <i\n *ngIf=\"iconMeta.type === 'font'\"\n [ngClass]=\"iconMeta.fontIconClass\"\n [ngStyle]=\"iconMeta.fontIconStyle\"\n class=\"pt-dropdown-icon-font\"\n ></i>\n </ng-container>\n\n <span class=\"pt-dropdown-label\">{{ option.label }}</span>\n </div>\n </ng-template>\n </p-dropdown>\n\n <div\n *ngIf=\"\n formGroup.get(formField.name)?.invalid &&\n formGroup.get(formField.name)?.touched\n \"\n >\n <small class=\"p-error\">{{ getErrorMessage() }}</small>\n </div>\n</div>\n", styles: [".form-field{display:flex;flex-direction:column;margin-bottom:1rem;gap:.25rem}.form-field label{display:block;margin-bottom:.5rem;font-weight:700;font-size:1rem}.pt-dropdown-item{display:flex;align-items:center;gap:.5rem}.pt-dropdown-icon-image{object-fit:cover}.pt-dropdown-icon-font{display:inline-flex;align-items:center;justify-content:center}.pt-dropdown-label{display:inline-block}\n"] }]
2193
2186
  }], propDecorators: { formGroup: [{
2194
2187
  type: Input
2195
2188
  }], formField: [{