@masterteam/client-components 0.0.31 → 0.0.32

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.
@@ -6,9 +6,11 @@ import { Button } from '@masterteam/components/button';
6
6
  import { HttpClient } from '@angular/common/http';
7
7
  import { switchMap, of, map } from 'rxjs';
8
8
  import { Card } from '@masterteam/components/card';
9
- import { Table } from '@masterteam/components/table';
9
+ import { Table, TableExportService, TableGroupingController, TableValueResolver, TableCaption } from '@masterteam/components/table';
10
10
  import * as i2 from 'primeng/skeleton';
11
11
  import { SkeletonModule } from 'primeng/skeleton';
12
+ import { MTDateFormatPipe } from '@masterteam/components';
13
+ import { EntitiesPreview } from '@masterteam/components/entities';
12
14
  import { DashboardViewer } from '@masterteam/dashboard-builder';
13
15
 
14
16
  class ClientListApiService {
@@ -487,8 +489,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
487
489
  type: Injectable
488
490
  }] });
489
491
 
492
+ /**
493
+ * Per-list toolbar state holder. Buckets are keyed by list `key` (see
494
+ * `ClientListBaseState.key`) so switching between table and cards for the same
495
+ * list carries search/filter/group/tab forward. In-memory only — per-component
496
+ * `storageKey` persistence on the underlying mt-table continues to handle
497
+ * refresh-resilient hydration.
498
+ */
499
+ class ClientListToolbarService {
500
+ buckets = new Map();
501
+ forList(key) {
502
+ let bucket = this.buckets.get(key);
503
+ if (!bucket) {
504
+ bucket = {
505
+ filters: signal({}),
506
+ filterTerm: signal(''),
507
+ groupBy: signal(null),
508
+ activeTab: signal(null),
509
+ };
510
+ this.buckets.set(key, bucket);
511
+ }
512
+ return bucket;
513
+ }
514
+ clear(key) {
515
+ this.buckets.delete(key);
516
+ }
517
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListToolbarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
518
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListToolbarService, providedIn: 'root' });
519
+ }
520
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListToolbarService, decorators: [{
521
+ type: Injectable,
522
+ args: [{ providedIn: 'root' }]
523
+ }] });
524
+
490
525
  const DEFAULT_GRID_COLUMNS$2 = 12;
491
526
  class ClientListTableView {
527
+ toolbar = inject(ClientListToolbarService);
492
528
  state = input.required(...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
493
529
  rowActions = input([], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
494
530
  actionShape = input(undefined, ...(ngDevMode ? [{ debugName: "actionShape" }] : /* istanbul ignore next */ []));
@@ -497,6 +533,34 @@ class ClientListTableView {
497
533
  table = computed(() => {
498
534
  return this.state();
499
535
  }, ...(ngDevMode ? [{ debugName: "table" }] : /* istanbul ignore next */ []));
536
+ /**
537
+ * Shared toolbar bucket (search / filters / groupBy / activeTab) keyed by
538
+ * list identity. The cards view for the same list resolves the same bucket
539
+ * so switching area types preserves the toolbar state.
540
+ */
541
+ bucket = computed(() => this.toolbar.forList(this.state().key), ...(ngDevMode ? [{ debugName: "bucket" }] : /* istanbul ignore next */ []));
542
+ /**
543
+ * Stable, per-instance storage key. Excludes `areaType` so table and cards
544
+ * views for the same logical list share persistence (matches the toolbar
545
+ * bucket sharing behaviour).
546
+ */
547
+ tableStorageKey = computed(() => {
548
+ const s = this.state();
549
+ const cfg = s.config;
550
+ const parts = [
551
+ 'client-list',
552
+ s.key,
553
+ cfg.type,
554
+ cfg.contextKey ?? '',
555
+ cfg.moduleId ?? '',
556
+ cfg.instanceId ?? '',
557
+ ];
558
+ const key = parts
559
+ .map((p) => String(p ?? ''))
560
+ .filter((p) => p.length > 0)
561
+ .join(':');
562
+ return key.length > 'client-list'.length ? key : null;
563
+ }, ...(ngDevMode ? [{ debugName: "tableStorageKey" }] : /* istanbul ignore next */ []));
500
564
  gridTemplateColumns = `repeat(${DEFAULT_GRID_COLUMNS$2}, minmax(0, 1fr))`;
501
565
  slotGridSpan(span) {
502
566
  return `span ${span} / span ${span}`;
@@ -512,24 +576,363 @@ class ClientListTableView {
512
576
  };
513
577
  }
514
578
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListTableView, deps: [], target: i0.ɵɵFactoryTarget.Component });
515
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientListTableView, isStandalone: true, selector: "mt-client-list-table-view", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null }, actionShape: { classPropertyName: "actionShape", publicName: "actionShape", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { lazyLoad: "lazyLoad", rowClick: "rowClick" }, ngImport: i0, template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\r\n @if (table().config.contentStart) {\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.startSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table().config.contentStart\"\r\n [ngTemplateOutletContext]=\"templateContext(table())\"\r\n />\r\n </div>\r\n }\r\n\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.tableSpan)\">\r\n <mt-card>\r\n @if (table().loading && table().rows.length === 0) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n </div>\r\n } @else if (table().error) {\r\n <div\r\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\r\n >\r\n {{ table().error }}\r\n </div>\r\n } @else {\r\n <mt-table\r\n [noCard]=\"true\"\r\n [data]=\"table().rows\"\r\n [columns]=\"table().columns\"\r\n dataKey=\"Id\"\r\n storageKey=\"client-list-table\"\r\n [rowActions]=\"rowActions()\"\r\n [actionShape]=\"actionShape()\"\r\n [generalSearch]=\"table().config.table.searchable\"\r\n [showFilters]=\"true\"\r\n [exportable]=\"table().config.table.exportable\"\r\n [loading]=\"table().loading\"\r\n [clickableRows]=\"true\"\r\n [lazy]=\"table().config.isPaginated\"\r\n [lazyTotalRecords]=\"table().totalCount\"\r\n [pageSize]=\"table().config.table.pageSize\"\r\n (lazyLoad)=\"lazyLoad.emit($event)\"\r\n (rowClick)=\"rowClick.emit($event)\"\r\n />\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (table().config.contentEnd) {\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.endSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table().config.contentEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(table())\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "exportFilename", "actionShape", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }] });
579
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientListTableView, isStandalone: true, selector: "mt-client-list-table-view", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null }, actionShape: { classPropertyName: "actionShape", publicName: "actionShape", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { lazyLoad: "lazyLoad", rowClick: "rowClick" }, ngImport: i0, template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\r\n @if (table().config.contentStart) {\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.startSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table().config.contentStart\"\r\n [ngTemplateOutletContext]=\"templateContext(table())\"\r\n />\r\n </div>\r\n }\r\n\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.tableSpan)\">\r\n <mt-card>\r\n @if (table().loading && table().rows.length === 0) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n </div>\r\n } @else if (table().error) {\r\n <div\r\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\r\n >\r\n {{ table().error }}\r\n </div>\r\n } @else {\r\n <mt-table\r\n [noCard]=\"true\"\r\n [data]=\"table().rows\"\r\n [columns]=\"table().columns\"\r\n dataKey=\"Id\"\r\n [storageKey]=\"tableStorageKey()\"\r\n [rowActions]=\"rowActions()\"\r\n [actionShape]=\"actionShape()\"\r\n [generalSearch]=\"table().config.table.searchable\"\r\n [showFilters]=\"true\"\r\n filterMode=\"column\"\r\n [printable]=\"true\"\r\n [groupable]=\"true\"\r\n [cellClickFilter]=\"true\"\r\n [exportable]=\"table().config.table.exportable\"\r\n [loading]=\"table().loading\"\r\n [clickableRows]=\"true\"\r\n [lazy]=\"table().config.isPaginated\"\r\n [lazyTotalRecords]=\"table().totalCount\"\r\n [pageSize]=\"table().config.table.pageSize\"\r\n [filters]=\"bucket().filters()\"\r\n (filtersChange)=\"bucket().filters.set($event)\"\r\n [filterTerm]=\"bucket().filterTerm()\"\r\n (filterTermChange)=\"bucket().filterTerm.set($event)\"\r\n [groupBy]=\"bucket().groupBy()\"\r\n (groupByChange)=\"bucket().groupBy.set($event)\"\r\n [activeTab]=\"bucket().activeTab()\"\r\n (activeTabChange)=\"bucket().activeTab.set($event)\"\r\n (lazyLoad)=\"lazyLoad.emit($event)\"\r\n (rowClick)=\"rowClick.emit($event)\"\r\n />\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (table().config.contentEnd) {\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.endSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table().config.contentEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(table())\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "printable", "groupable", "cellClickFilter", "printTitle", "exportFilename", "actionShape", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }] });
516
580
  }
517
581
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListTableView, decorators: [{
518
582
  type: Component,
519
- args: [{ selector: 'mt-client-list-table-view', standalone: true, imports: [CommonModule, Card, Table, SkeletonModule], template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\r\n @if (table().config.contentStart) {\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.startSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table().config.contentStart\"\r\n [ngTemplateOutletContext]=\"templateContext(table())\"\r\n />\r\n </div>\r\n }\r\n\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.tableSpan)\">\r\n <mt-card>\r\n @if (table().loading && table().rows.length === 0) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n </div>\r\n } @else if (table().error) {\r\n <div\r\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\r\n >\r\n {{ table().error }}\r\n </div>\r\n } @else {\r\n <mt-table\r\n [noCard]=\"true\"\r\n [data]=\"table().rows\"\r\n [columns]=\"table().columns\"\r\n dataKey=\"Id\"\r\n storageKey=\"client-list-table\"\r\n [rowActions]=\"rowActions()\"\r\n [actionShape]=\"actionShape()\"\r\n [generalSearch]=\"table().config.table.searchable\"\r\n [showFilters]=\"true\"\r\n [exportable]=\"table().config.table.exportable\"\r\n [loading]=\"table().loading\"\r\n [clickableRows]=\"true\"\r\n [lazy]=\"table().config.isPaginated\"\r\n [lazyTotalRecords]=\"table().totalCount\"\r\n [pageSize]=\"table().config.table.pageSize\"\r\n (lazyLoad)=\"lazyLoad.emit($event)\"\r\n (rowClick)=\"rowClick.emit($event)\"\r\n />\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (table().config.contentEnd) {\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.endSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table().config.contentEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(table())\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n" }]
583
+ args: [{ selector: 'mt-client-list-table-view', standalone: true, imports: [CommonModule, Card, Table, SkeletonModule], template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\r\n @if (table().config.contentStart) {\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.startSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table().config.contentStart\"\r\n [ngTemplateOutletContext]=\"templateContext(table())\"\r\n />\r\n </div>\r\n }\r\n\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.tableSpan)\">\r\n <mt-card>\r\n @if (table().loading && table().rows.length === 0) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n </div>\r\n } @else if (table().error) {\r\n <div\r\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\r\n >\r\n {{ table().error }}\r\n </div>\r\n } @else {\r\n <mt-table\r\n [noCard]=\"true\"\r\n [data]=\"table().rows\"\r\n [columns]=\"table().columns\"\r\n dataKey=\"Id\"\r\n [storageKey]=\"tableStorageKey()\"\r\n [rowActions]=\"rowActions()\"\r\n [actionShape]=\"actionShape()\"\r\n [generalSearch]=\"table().config.table.searchable\"\r\n [showFilters]=\"true\"\r\n filterMode=\"column\"\r\n [printable]=\"true\"\r\n [groupable]=\"true\"\r\n [cellClickFilter]=\"true\"\r\n [exportable]=\"table().config.table.exportable\"\r\n [loading]=\"table().loading\"\r\n [clickableRows]=\"true\"\r\n [lazy]=\"table().config.isPaginated\"\r\n [lazyTotalRecords]=\"table().totalCount\"\r\n [pageSize]=\"table().config.table.pageSize\"\r\n [filters]=\"bucket().filters()\"\r\n (filtersChange)=\"bucket().filters.set($event)\"\r\n [filterTerm]=\"bucket().filterTerm()\"\r\n (filterTermChange)=\"bucket().filterTerm.set($event)\"\r\n [groupBy]=\"bucket().groupBy()\"\r\n (groupByChange)=\"bucket().groupBy.set($event)\"\r\n [activeTab]=\"bucket().activeTab()\"\r\n (activeTabChange)=\"bucket().activeTab.set($event)\"\r\n (lazyLoad)=\"lazyLoad.emit($event)\"\r\n (rowClick)=\"rowClick.emit($event)\"\r\n />\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (table().config.contentEnd) {\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.endSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table().config.contentEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(table())\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n" }]
520
584
  }], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], actionShape: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionShape", required: false }] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }] } });
521
585
 
586
+ const EMPTY_KEY_SET = new Set();
522
587
  class ClientListCardsView {
588
+ toolbar = inject(ClientListToolbarService);
589
+ exportService = inject(TableExportService);
590
+ dateFormat = inject(MTDateFormatPipe);
523
591
  state = input.required(...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
592
+ rowActions = input([], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
593
+ lazyLoad = output();
524
594
  cardClick = output();
525
595
  cardsState = computed(() => this.state(), ...(ngDevMode ? [{ debugName: "cardsState" }] : /* istanbul ignore next */ []));
596
+ /** Shared toolbar bucket — same instance the table view uses. */
597
+ bucket = computed(() => this.toolbar.forList(this.state().key), ...(ngDevMode ? [{ debugName: "bucket" }] : /* istanbul ignore next */ []));
598
+ /**
599
+ * Columns for filter/group/export. `state.columns` is empty for cards area
600
+ * (the state service doesn't populate it — see `setCardsResult`), so we
601
+ * reconstruct `ColumnDef[]` from the first card's entity metadata. Each
602
+ * entity carries its `key`, `name` (label), and `viewType`, which is
603
+ * everything `TableValueResolver.getColumnFilterType` needs to classify
604
+ * filter behaviour (select / boolean / date / user / text).
605
+ */
606
+ columns = computed(() => {
607
+ const fromState = this.state().columns;
608
+ if (fromState && fromState.length > 0)
609
+ return fromState;
610
+ const sampleCard = this.cardsState().cards[0];
611
+ if (!sampleCard?.entities?.length)
612
+ return [];
613
+ return sampleCard.entities.map((entity) => {
614
+ const raw = entity;
615
+ const key = (typeof raw['key'] === 'string' && raw['key']) ||
616
+ (typeof raw['normalizedKey'] === 'string' && raw['normalizedKey']) ||
617
+ entity.name;
618
+ return {
619
+ key: String(key),
620
+ label: entity.name ?? String(key),
621
+ type: 'entity',
622
+ viewType: (entity.viewType ?? 'Text'),
623
+ };
624
+ });
625
+ }, ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
626
+ /**
627
+ * Row view of each card. Cards carry their data as `entities: EntityData[]`,
628
+ * not as a pre-built record, so we rebuild the row shape mt-table expects:
629
+ * `{ [entityKey]: EntityData | scalar }`. Falls back to `displayProperties`
630
+ * if a newer backend populates it. Row index matches card index so we can
631
+ * look up by position elsewhere.
632
+ */
633
+ rowRepresentations = computed(() => this.cardsState().cards.map((card) => this.cardToRow(card)), ...(ngDevMode ? [{ debugName: "rowRepresentations" }] : /* istanbul ignore next */ []));
634
+ cardToRow(card) {
635
+ const fromDisplay = card.displayProperties;
636
+ if (fromDisplay && Object.keys(fromDisplay).length > 0) {
637
+ return fromDisplay;
638
+ }
639
+ const record = { Id: card.id };
640
+ for (const entity of card.entities ?? []) {
641
+ const e = entity;
642
+ const key = (typeof e['key'] === 'string' && e['key']) ||
643
+ (typeof e['normalizedKey'] === 'string' && e['normalizedKey']) ||
644
+ entity.name;
645
+ if (key && typeof key === 'string') {
646
+ record[key] = entity;
647
+ }
648
+ }
649
+ return record;
650
+ }
651
+ // Local throwaway signals for the grouping controller's sort arguments —
652
+ // cards don't sort via column click so the controller's sort-pinning
653
+ // side-effect is a no-op here.
654
+ noopSortField = signal(null, ...(ngDevMode ? [{ debugName: "noopSortField" }] : /* istanbul ignore next */ []));
655
+ noopSortDir = signal(null, ...(ngDevMode ? [{ debugName: "noopSortDir" }] : /* istanbul ignore next */ []));
656
+ /**
657
+ * Writable proxy around `bucket().groupBy` — the grouping controller wants a
658
+ * `WritableSignal<string | null>`. Reads delegate to the current bucket's
659
+ * signal; writes mutate it via `.set()`. This keeps the controller's
660
+ * `setGroup()` side-effect safe even though cards view callers never invoke
661
+ * it today.
662
+ */
663
+ groupBySignal = (() => {
664
+ const read = computed(() => this.bucket().groupBy(), ...(ngDevMode ? [{ debugName: "read" }] : /* istanbul ignore next */ []));
665
+ const proxy = ((...args) => args.length === 0 ? read() : this.bucket().groupBy.set(args[0]));
666
+ proxy.set = (value) => this.bucket().groupBy.set(value);
667
+ proxy.update = (fn) => this.bucket().groupBy.set(fn(this.bucket().groupBy()));
668
+ proxy.asReadonly = () => read;
669
+ return proxy;
670
+ })();
671
+ /**
672
+ * Grouping controller bound to the shared toolbar bucket, so picking a group
673
+ * in the cards menu surfaces in the table view's group header (and vice
674
+ * versa) when the user toggles area types.
675
+ */
676
+ grouping = new TableGroupingController(this.columns, this.rowRepresentations, this.groupBySignal, this.noopSortField, this.noopSortDir);
677
+ /**
678
+ * Visible (after search + filters) cards. Backend-paginated lists re-query
679
+ * on filter change so `cards` already reflects the server response and this
680
+ * just passes through; local lists filter in-place.
681
+ */
682
+ visibleCards = computed(() => {
683
+ const state = this.cardsState();
684
+ const isLazy = state.config.isPaginated;
685
+ const cards = state.cards;
686
+ if (isLazy)
687
+ return cards;
688
+ const term = this.bucket().filterTerm().trim().toLowerCase();
689
+ const filters = this.bucket().filters();
690
+ const columns = this.columns();
691
+ const filterKeys = Object.keys(filters);
692
+ return cards.filter((card, index) => {
693
+ const row = this.rowRepresentations()[index] ?? {};
694
+ if (term) {
695
+ // Row values are EntityData envelopes (or scalars). Pull the display
696
+ // string from each and substring-match — same semantics mt-table's
697
+ // `generalSearch` applies to its rows.
698
+ const matches = Object.values(row).some((value) => {
699
+ const display = TableValueResolver.resolveDisplayValue(value);
700
+ if (display == null)
701
+ return false;
702
+ return String(display).toLowerCase().includes(term);
703
+ });
704
+ if (!matches)
705
+ return false;
706
+ }
707
+ if (filterKeys.length === 0)
708
+ return true;
709
+ return filterKeys.every((key) => {
710
+ if (key === 'generalSearch')
711
+ return true;
712
+ const column = columns.find((c) => c.key === key);
713
+ return TableValueResolver.matchesFilter(row, key, filters[key], column);
714
+ });
715
+ });
716
+ }, ...(ngDevMode ? [{ debugName: "visibleCards" }] : /* istanbul ignore next */ []));
717
+ /**
718
+ * Grouped cards. Reuses TableGroupingController helpers so the accent color,
719
+ * label, and count rendering match the table's subheader exactly.
720
+ */
721
+ groupedCards = computed(() => {
722
+ const column = this.grouping.activeGroupColumn();
723
+ if (!column)
724
+ return [];
725
+ const groups = new Map();
726
+ this.visibleCards().forEach((card) => {
727
+ const row = this.cardToRow(card);
728
+ const label = this.grouping.getGroupLabel(row);
729
+ const accent = this.grouping.getGroupAccentColor(row);
730
+ const key = `${label}__${accent ?? ''}`;
731
+ const existing = groups.get(key);
732
+ if (existing) {
733
+ existing.cards.push(card);
734
+ existing.count += 1;
735
+ }
736
+ else {
737
+ groups.set(key, {
738
+ key,
739
+ columnLabel: column.label,
740
+ label,
741
+ accent,
742
+ count: 1,
743
+ cards: [card],
744
+ });
745
+ }
746
+ });
747
+ return Array.from(groups.values());
748
+ }, ...(ngDevMode ? [{ debugName: "groupedCards" }] : /* istanbul ignore next */ []));
749
+ /**
750
+ * Export the visible (after search + filter) cards as XLSX. Uses the cards'
751
+ * derived row shape + the state's columns, so the file mirrors what the
752
+ * user sees. Custom columns are excluded upstream by `TableExportService`.
753
+ */
754
+ /**
755
+ * Set of column keys that are click-to-filter eligible — same predicate
756
+ * mt-table's `isCellClickFilterable` uses (select / boolean / user). One
757
+ * set computed once per columns / first-card change; all visible cards
758
+ * read from it. Reactive via signal — when columns load, the entities
759
+ * inside cards automatically become clickable.
760
+ */
761
+ clickableEntityKeys = computed(() => {
762
+ const sample = this.cardsState().cards[0]
763
+ ? this.cardToRow(this.cardsState().cards[0])
764
+ : undefined;
765
+ const result = new Set();
766
+ for (const column of this.columns()) {
767
+ const filterType = TableValueResolver.getColumnFilterType(column, sample);
768
+ if (filterType === 'select' ||
769
+ filterType === 'user' ||
770
+ filterType === 'boolean') {
771
+ result.add(column.key);
772
+ }
773
+ }
774
+ return result;
775
+ }, ...(ngDevMode ? [{ debugName: "clickableEntityKeys" }] : /* istanbul ignore next */ []));
776
+ /**
777
+ * Per-card set of entity keys that match the active filter — passed into
778
+ * `<mt-entities-preview [activeKeys]>`. Computed lazily per card via the
779
+ * helper below; bucket changes (filter add / remove) automatically
780
+ * invalidate any place that reads from this in a template.
781
+ */
782
+ activeKeysForCard(card) {
783
+ const filters = this.bucket().filters();
784
+ const filterKeys = Object.keys(filters);
785
+ if (filterKeys.length === 0)
786
+ return EMPTY_KEY_SET;
787
+ const row = this.cardToRow(card);
788
+ const result = new Set();
789
+ for (const key of filterKeys) {
790
+ if (key === 'generalSearch')
791
+ continue;
792
+ const column = this.columns().find((c) => c.key === key);
793
+ if (!column)
794
+ continue;
795
+ const cellTarget = TableValueResolver.buildClickFilterValue(row, column);
796
+ if (cellTarget === null || cellTarget === undefined)
797
+ continue;
798
+ if (TableValueResolver.isFilterValueEqual(filters[key], cellTarget)) {
799
+ result.add(key);
800
+ }
801
+ }
802
+ return result;
803
+ }
804
+ /**
805
+ * Click-to-filter handler invoked from `<mt-entities-preview (entityClick)>`.
806
+ * Mirrors mt-table's `onCellFilterClick`: builds the filter value via the
807
+ * shared `TableValueResolver`, toggles it on the shared toolbar bucket so
808
+ * the funnel chip in the filter popover lights up, and clears on a second
809
+ * click against the same value.
810
+ */
811
+ onEntityFilterClick(card, event) {
812
+ const column = this.columns().find((c) => c.key === event.key);
813
+ if (!column)
814
+ return;
815
+ const row = this.cardToRow(card);
816
+ const next = TableValueResolver.buildClickFilterValue(row, column);
817
+ if (next === null || next === undefined)
818
+ return;
819
+ const current = this.bucket().filters();
820
+ const patched = { ...current };
821
+ if (TableValueResolver.isFilterValueEqual(current[column.key], next)) {
822
+ delete patched[column.key];
823
+ }
824
+ else {
825
+ patched[column.key] = next;
826
+ }
827
+ this.bucket().filters.set(patched);
828
+ }
829
+ exportExcel() {
830
+ const rows = this.visibleCards().map((card) => this.cardToRow(card));
831
+ this.exportService.exportToExcel({
832
+ rows,
833
+ columns: this.columns(),
834
+ filename: this.resolveExportFilename(),
835
+ resolveValue: (row, column) => this.resolveCellValue(row, column),
836
+ });
837
+ }
838
+ printPdf() {
839
+ const rows = this.visibleCards().map((card) => this.cardToRow(card));
840
+ this.exportService.printPdf({
841
+ rows,
842
+ columns: this.columns(),
843
+ filename: this.resolveExportFilename(),
844
+ title: this.cardsState().title || undefined,
845
+ resolveValue: (row, column) => this.resolveCellValue(row, column),
846
+ });
847
+ }
848
+ resolveExportFilename() {
849
+ const title = this.cardsState().title?.trim();
850
+ return title || this.cardsState().moduleKey || 'cards';
851
+ }
852
+ /**
853
+ * Value resolver compatible with `TableExportConfig.resolveValue`. Reuses
854
+ * `TableValueResolver` so currency, dates, entities, booleans export the
855
+ * same way they would from the table view. Date columns (native or entity
856
+ * with viewType `Date`/`DateTime`) are passed through `mtDateFormat` so
857
+ * Excel and PDF show locale-formatted dates instead of raw ISO strings.
858
+ */
859
+ resolveCellValue(row, column) {
860
+ const cellValue = TableValueResolver.getProperty(row, column.key);
861
+ if (cellValue === null || cellValue === undefined)
862
+ return null;
863
+ const dateType = this.resolveDateColumnType(column, cellValue);
864
+ if (dateType) {
865
+ const source = column.type === 'entity'
866
+ ? TableValueResolver.getEntityValueByType(cellValue, 'sort')
867
+ : cellValue;
868
+ const dateValue = TableValueResolver.resolveDateValue(source);
869
+ if (dateValue === null)
870
+ return null;
871
+ const formatted = this.dateFormat.transform(dateValue, dateType);
872
+ return formatted || null;
873
+ }
874
+ if (column.type === 'entity') {
875
+ return TableValueResolver.getEntityValueByType(cellValue, 'export');
876
+ }
877
+ return TableValueResolver.resolveDisplayValue(cellValue);
878
+ }
879
+ resolveDateColumnType(column, cellValue) {
880
+ if (column.type === 'date')
881
+ return 'date';
882
+ if (column.type === 'dateTime')
883
+ return 'dateTime';
884
+ if (column.type !== 'entity')
885
+ return null;
886
+ const viewType = column.viewType;
887
+ const fromColumn = typeof viewType === 'string' ? viewType.toLowerCase() : '';
888
+ if (fromColumn === 'datetime')
889
+ return 'dateTime';
890
+ if (fromColumn === 'date')
891
+ return 'date';
892
+ if (cellValue && typeof cellValue === 'object') {
893
+ const inner = cellValue.viewType;
894
+ if (inner === 'DateTime')
895
+ return 'dateTime';
896
+ if (inner === 'Date')
897
+ return 'date';
898
+ }
899
+ return null;
900
+ }
901
+ constructor() {
902
+ // Emit lazyLoad only on filter / search changes for paginated lists,
903
+ // matching mt-table's behaviour (group-by is a pure FE concern, not a
904
+ // re-fetch trigger). Skip the initial firing while the bucket is still at
905
+ // its default empty state so we don't double-fetch on mount.
906
+ let primed = false;
907
+ effect(() => {
908
+ const state = this.cardsState();
909
+ const filters = this.bucket().filters();
910
+ const filterTerm = this.bucket().filterTerm();
911
+ if (!state.config.isPaginated)
912
+ return;
913
+ if (!primed) {
914
+ primed = true;
915
+ if (Object.keys(filters).length === 0 && !filterTerm)
916
+ return;
917
+ }
918
+ queueMicrotask(() => this.lazyLoad.emit({
919
+ filters: {
920
+ ...filters,
921
+ ...(filterTerm ? { generalSearch: filterTerm } : {}),
922
+ },
923
+ pageSize: state.take,
924
+ first: state.skip,
925
+ currentPage: state.skip / Math.max(state.take, 1) + 1,
926
+ }));
927
+ });
928
+ }
526
929
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListCardsView, deps: [], target: i0.ɵɵFactoryTarget.Component });
527
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientListCardsView, isStandalone: true, selector: "mt-client-list-cards-view", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { cardClick: "cardClick" }, ngImport: i0, template: "@if (cardsState().loading && cardsState().cards.length === 0) {\r\n <div class=\"grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3\">\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\r\n }\r\n </div>\r\n} @else if (cardsState().error) {\r\n <div class=\"rounded-lg border border-red-200 bg-red-50 p-6 text-red-600\">\r\n {{ cardsState().error }}\r\n </div>\r\n} @else if (cardsState().cards.length === 0) {\r\n <div class=\"p-6 text-center text-gray-400\">No cards found.</div>\r\n} @else {\r\n <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3\">\r\n @for (card of cardsState().cards; track card.id) {\r\n @defer (on viewport) {\r\n <mt-card\r\n class=\"cursor-pointer p-5 shadow-sm transition-shadow hover:shadow-md\"\r\n (click)=\"cardClick.emit(card)\"\r\n >\r\n <ng-template #headless>\r\n <mt-entities-preview\r\n [entities]=\"card.entities\"\r\n attachmentShape=\"compact\"\r\n />\r\n </ng-template>\r\n </mt-card>\r\n } @placeholder {\r\n <div class=\"min-h-[20rem]\">\r\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n}\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }], deferBlockDependencies: [() => [import('@masterteam/components/card').then(m => m.Card), import('@masterteam/components/entities').then(m => m.EntitiesPreview)]] });
930
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientListCardsView, isStandalone: true, selector: "mt-client-list-cards-view", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { lazyLoad: "lazyLoad", cardClick: "cardClick" }, providers: [MTDateFormatPipe], ngImport: i0, template: "<mt-table-caption\n [generalSearch]=\"true\"\n [showFilters]=\"true\"\n filterMode=\"popover\"\n [printable]=\"true\"\n [groupable]=\"true\"\n [exportable]=\"true\"\n [columns]=\"columns()\"\n [data]=\"rowRepresentations()\"\n [groupColumns]=\"grouping.groupableColumns()\"\n [filters]=\"bucket().filters()\"\n (filtersChange)=\"bucket().filters.set($event)\"\n [filterTerm]=\"bucket().filterTerm()\"\n (filterTermChange)=\"bucket().filterTerm.set($event)\"\n [groupBy]=\"bucket().groupBy()\"\n (groupByChange)=\"bucket().groupBy.set($event)\"\n [activeTab]=\"bucket().activeTab()\"\n (activeTabChange)=\"bucket().activeTab.set($event)\"\n (exportRequested)=\"exportExcel()\"\n (printRequested)=\"printPdf()\"\n/>\n\n@if (cardsState().loading && cardsState().cards.length === 0) {\n <div class=\"grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3\">\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\n }\n </div>\n} @else if (cardsState().error) {\n <div class=\"rounded-lg border border-red-200 bg-red-50 p-6 text-red-600\">\n {{ cardsState().error }}\n </div>\n} @else if (visibleCards().length === 0) {\n <div class=\"p-6 text-center text-gray-400\">No cards found.</div>\n} @else if (grouping.groupingActive()) {\n <div class=\"flex flex-col gap-6\">\n @for (group of groupedCards(); track group.key) {\n <section class=\"flex flex-col gap-3\">\n <header\n class=\"flex items-center gap-3 px-2 py-2 bg-surface-50 dark:bg-surface-900 border-y border-surface-200 dark:border-surface-700 rounded\"\n [style.--mt-group-accent]=\"group.accent ?? 'var(--p-primary-color)'\"\n >\n <span\n class=\"inline-block w-1 h-5 rounded-full\"\n [style.background]=\"'var(--mt-group-accent)'\"\n ></span>\n <span\n class=\"text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400\"\n >\n {{ group.columnLabel }}\n </span>\n <span class=\"text-sm font-semibold text-gray-900 dark:text-gray-100\">\n {{ group.label }}\n </span>\n <span\n class=\"ml-auto inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-primary-50 text-primary-700 dark:bg-primary-950 dark:text-primary-300\"\n >\n {{ group.count }}\n </span>\n </header>\n <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3\">\n @for (card of group.cards; track card.id) {\n <mt-card\n class=\"cursor-pointer p-5 shadow-sm transition-shadow hover:shadow-md\"\n (click)=\"cardClick.emit(card)\"\n >\n <ng-template #headless>\n <mt-entities-preview\n [entities]=\"card.entities\"\n attachmentShape=\"compact\"\n [clickableKeys]=\"clickableEntityKeys()\"\n [activeKeys]=\"activeKeysForCard(card)\"\n (entityClick)=\"onEntityFilterClick(card, $event)\"\n />\n </ng-template>\n </mt-card>\n }\n </div>\n </section>\n }\n </div>\n} @else {\n <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3\">\n @for (card of visibleCards(); track card.id) {\n @defer (on viewport) {\n <mt-card\n class=\"cursor-pointer p-5 shadow-sm transition-shadow hover:shadow-md\"\n (click)=\"cardClick.emit(card)\"\n >\n <ng-template #headless>\n <mt-entities-preview\n [entities]=\"card.entities\"\n attachmentShape=\"compact\"\n [clickableKeys]=\"clickableEntityKeys()\"\n [activeKeys]=\"activeKeysForCard(card)\"\n (entityClick)=\"onEntityFilterClick(card, $event)\"\n />\n </ng-template>\n </mt-card>\n } @placeholder {\n <div class=\"min-h-[20rem]\">\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\n </div>\n }\n }\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: EntitiesPreview, selector: "mt-entities-preview", inputs: ["entities", "attachmentShape", "clickableKeys", "activeKeys"], outputs: ["entityClick"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: TableCaption, selector: "mt-table-caption", inputs: ["generalSearch", "showFilters", "filterMode", "exportable", "printable", "groupable", "columns", "data", "groupColumns", "tabs", "tabsOptionLabel", "tabsOptionValue", "actions", "captionStart", "captionEnd", "activeTab", "filters", "filterTerm", "groupBy"], outputs: ["activeTabChange", "filtersChange", "filterTermChange", "groupByChange", "exportRequested", "printRequested", "onTabChange", "searchChange", "filterApplied", "filterReset"] }], deferBlockDependencies: [() => [Card, EntitiesPreview]] });
528
931
  }
529
- i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "21.2.8", ngImport: i0, type: ClientListCardsView, resolveDeferredDeps: () => [import('@masterteam/components/card').then(m => m.Card), import('@masterteam/components/entities').then(m => m.EntitiesPreview)], resolveMetadata: (Card, EntitiesPreview) => ({ decorators: [{
530
- type: Component,
531
- args: [{ selector: 'mt-client-list-cards-view', standalone: true, imports: [CommonModule, Card, EntitiesPreview, SkeletonModule], template: "@if (cardsState().loading && cardsState().cards.length === 0) {\r\n <div class=\"grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3\">\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\r\n }\r\n </div>\r\n} @else if (cardsState().error) {\r\n <div class=\"rounded-lg border border-red-200 bg-red-50 p-6 text-red-600\">\r\n {{ cardsState().error }}\r\n </div>\r\n} @else if (cardsState().cards.length === 0) {\r\n <div class=\"p-6 text-center text-gray-400\">No cards found.</div>\r\n} @else {\r\n <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3\">\r\n @for (card of cardsState().cards; track card.id) {\r\n @defer (on viewport) {\r\n <mt-card\r\n class=\"cursor-pointer p-5 shadow-sm transition-shadow hover:shadow-md\"\r\n (click)=\"cardClick.emit(card)\"\r\n >\r\n <ng-template #headless>\r\n <mt-entities-preview\r\n [entities]=\"card.entities\"\r\n attachmentShape=\"compact\"\r\n />\r\n </ng-template>\r\n </mt-card>\r\n } @placeholder {\r\n <div class=\"min-h-[20rem]\">\r\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\r\n </div>\r\n }\r\n }\r\n </div>\r\n}\r\n" }]
532
- }], ctorParameters: null, propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }] } }) });
932
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListCardsView, decorators: [{
933
+ type: Component,
934
+ args: [{ selector: 'mt-client-list-cards-view', standalone: true, imports: [CommonModule, Card, EntitiesPreview, SkeletonModule, TableCaption], providers: [MTDateFormatPipe], template: "<mt-table-caption\n [generalSearch]=\"true\"\n [showFilters]=\"true\"\n filterMode=\"popover\"\n [printable]=\"true\"\n [groupable]=\"true\"\n [exportable]=\"true\"\n [columns]=\"columns()\"\n [data]=\"rowRepresentations()\"\n [groupColumns]=\"grouping.groupableColumns()\"\n [filters]=\"bucket().filters()\"\n (filtersChange)=\"bucket().filters.set($event)\"\n [filterTerm]=\"bucket().filterTerm()\"\n (filterTermChange)=\"bucket().filterTerm.set($event)\"\n [groupBy]=\"bucket().groupBy()\"\n (groupByChange)=\"bucket().groupBy.set($event)\"\n [activeTab]=\"bucket().activeTab()\"\n (activeTabChange)=\"bucket().activeTab.set($event)\"\n (exportRequested)=\"exportExcel()\"\n (printRequested)=\"printPdf()\"\n/>\n\n@if (cardsState().loading && cardsState().cards.length === 0) {\n <div class=\"grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3\">\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\n }\n </div>\n} @else if (cardsState().error) {\n <div class=\"rounded-lg border border-red-200 bg-red-50 p-6 text-red-600\">\n {{ cardsState().error }}\n </div>\n} @else if (visibleCards().length === 0) {\n <div class=\"p-6 text-center text-gray-400\">No cards found.</div>\n} @else if (grouping.groupingActive()) {\n <div class=\"flex flex-col gap-6\">\n @for (group of groupedCards(); track group.key) {\n <section class=\"flex flex-col gap-3\">\n <header\n class=\"flex items-center gap-3 px-2 py-2 bg-surface-50 dark:bg-surface-900 border-y border-surface-200 dark:border-surface-700 rounded\"\n [style.--mt-group-accent]=\"group.accent ?? 'var(--p-primary-color)'\"\n >\n <span\n class=\"inline-block w-1 h-5 rounded-full\"\n [style.background]=\"'var(--mt-group-accent)'\"\n ></span>\n <span\n class=\"text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400\"\n >\n {{ group.columnLabel }}\n </span>\n <span class=\"text-sm font-semibold text-gray-900 dark:text-gray-100\">\n {{ group.label }}\n </span>\n <span\n class=\"ml-auto inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-primary-50 text-primary-700 dark:bg-primary-950 dark:text-primary-300\"\n >\n {{ group.count }}\n </span>\n </header>\n <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3\">\n @for (card of group.cards; track card.id) {\n <mt-card\n class=\"cursor-pointer p-5 shadow-sm transition-shadow hover:shadow-md\"\n (click)=\"cardClick.emit(card)\"\n >\n <ng-template #headless>\n <mt-entities-preview\n [entities]=\"card.entities\"\n attachmentShape=\"compact\"\n [clickableKeys]=\"clickableEntityKeys()\"\n [activeKeys]=\"activeKeysForCard(card)\"\n (entityClick)=\"onEntityFilterClick(card, $event)\"\n />\n </ng-template>\n </mt-card>\n }\n </div>\n </section>\n }\n </div>\n} @else {\n <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3\">\n @for (card of visibleCards(); track card.id) {\n @defer (on viewport) {\n <mt-card\n class=\"cursor-pointer p-5 shadow-sm transition-shadow hover:shadow-md\"\n (click)=\"cardClick.emit(card)\"\n >\n <ng-template #headless>\n <mt-entities-preview\n [entities]=\"card.entities\"\n attachmentShape=\"compact\"\n [clickableKeys]=\"clickableEntityKeys()\"\n [activeKeys]=\"activeKeysForCard(card)\"\n (entityClick)=\"onEntityFilterClick(card, $event)\"\n />\n </ng-template>\n </mt-card>\n } @placeholder {\n <div class=\"min-h-[20rem]\">\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\n </div>\n }\n }\n </div>\n}\n" }]
935
+ }], ctorParameters: () => [], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }] } });
533
936
 
534
937
  const DEFAULT_GRID_COLUMNS$1 = 12;
535
938
  class ClientListInformativeView {
@@ -1113,7 +1516,7 @@ class ClientList {
1113
1516
  return JSON.stringify(filters ?? []);
1114
1517
  }
1115
1518
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientList, deps: [], target: i0.ɵɵFactoryTarget.Component });
1116
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientList, isStandalone: true, selector: "mt-client-list", inputs: { configurations: { classPropertyName: "configurations", publicName: "configurations", isSignal: true, isRequired: true, transformFunction: null }, defaultTake: { classPropertyName: "defaultTake", publicName: "defaultTake", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", errored: "errored", itemClicked: "itemClicked" }, providers: [ClientListStateService], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\r\n @for (item of items(); track item.key) {\r\n <section class=\"flex flex-col gap-4\">\r\n @if (item.config.showHeader) {\r\n <div class=\"flex w-full items-center gap-2\">\r\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\r\n @if (item.config.collapse.enabled) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n [icon]=\"\r\n item.expanded\r\n ? item.config.collapse.collapseIcon\r\n : item.config.collapse.expandIcon\r\n \"\r\n (onClick)=\"toggleExpanded(item.key)\"\r\n />\r\n }\r\n <h3 class=\"m-0 text-lg font-semibold\">\r\n {{ item.title || item.moduleKey || defaultTitle(item) }}\r\n </h3>\r\n @if (item.config.headerStart) {\r\n <div class=\"flex items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.headerStart\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n @if (item.config.headerEnd) {\r\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.headerEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (item.expanded || !item.config.collapse.enabled) {\r\n @if (item.config.templateContent) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.templateContent\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n } @else if (item.type === \"informative\") {\r\n <mt-client-list-informative-view [state]=\"item\" />\r\n } @else if (item.areaType === \"table\") {\r\n <mt-client-list-table-view\r\n [state]=\"item\"\r\n [rowActions]=\"item.config.rowActions\"\r\n [actionShape]=\"item.config.actionShape\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n (rowClick)=\"onTableRowClick(item, $event)\"\r\n />\r\n } @else {\r\n <mt-client-list-cards-view\r\n [state]=\"item\"\r\n (cardClick)=\"onCardClick(item, $event)\"\r\n />\r\n }\r\n }\r\n </section>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ClientListTableView, selector: "mt-client-list-table-view", inputs: ["state", "rowActions", "actionShape"], outputs: ["lazyLoad", "rowClick"] }, { kind: "component", type: ClientListCardsView, selector: "mt-client-list-cards-view", inputs: ["state"], outputs: ["cardClick"] }, { kind: "component", type: ClientListInformativeView, selector: "mt-client-list-informative-view", inputs: ["state"] }] });
1519
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientList, isStandalone: true, selector: "mt-client-list", inputs: { configurations: { classPropertyName: "configurations", publicName: "configurations", isSignal: true, isRequired: true, transformFunction: null }, defaultTake: { classPropertyName: "defaultTake", publicName: "defaultTake", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", errored: "errored", itemClicked: "itemClicked" }, providers: [ClientListStateService], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\r\n @for (item of items(); track item.key) {\r\n <section class=\"flex flex-col gap-4\">\r\n @if (item.config.showHeader) {\r\n <div class=\"flex w-full items-center gap-2\">\r\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\r\n @if (item.config.collapse.enabled) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n [icon]=\"\r\n item.expanded\r\n ? item.config.collapse.collapseIcon\r\n : item.config.collapse.expandIcon\r\n \"\r\n (onClick)=\"toggleExpanded(item.key)\"\r\n />\r\n }\r\n <h3 class=\"m-0 text-lg font-semibold\">\r\n {{ item.title || item.moduleKey || defaultTitle(item) }}\r\n </h3>\r\n @if (item.config.headerStart) {\r\n <div class=\"flex items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.headerStart\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n @if (item.config.headerEnd) {\r\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.headerEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (item.expanded || !item.config.collapse.enabled) {\r\n @if (item.config.templateContent) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.templateContent\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n } @else if (item.type === \"informative\") {\r\n <mt-client-list-informative-view [state]=\"item\" />\r\n } @else if (item.areaType === \"table\") {\r\n <mt-client-list-table-view\r\n [state]=\"item\"\r\n [rowActions]=\"item.config.rowActions\"\r\n [actionShape]=\"item.config.actionShape\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n (rowClick)=\"onTableRowClick(item, $event)\"\r\n />\r\n } @else {\r\n <mt-client-list-cards-view\r\n [state]=\"item\"\r\n [rowActions]=\"item.config.rowActions\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n (cardClick)=\"onCardClick(item, $event)\"\r\n />\r\n }\r\n }\r\n </section>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ClientListTableView, selector: "mt-client-list-table-view", inputs: ["state", "rowActions", "actionShape"], outputs: ["lazyLoad", "rowClick"] }, { kind: "component", type: ClientListCardsView, selector: "mt-client-list-cards-view", inputs: ["state", "rowActions"], outputs: ["lazyLoad", "cardClick"] }, { kind: "component", type: ClientListInformativeView, selector: "mt-client-list-informative-view", inputs: ["state"] }] });
1117
1520
  }
1118
1521
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientList, decorators: [{
1119
1522
  type: Component,
@@ -1123,12 +1526,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1123
1526
  ClientListTableView,
1124
1527
  ClientListCardsView,
1125
1528
  ClientListInformativeView,
1126
- ], providers: [ClientListStateService], template: "<div class=\"flex flex-col gap-4\">\r\n @for (item of items(); track item.key) {\r\n <section class=\"flex flex-col gap-4\">\r\n @if (item.config.showHeader) {\r\n <div class=\"flex w-full items-center gap-2\">\r\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\r\n @if (item.config.collapse.enabled) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n [icon]=\"\r\n item.expanded\r\n ? item.config.collapse.collapseIcon\r\n : item.config.collapse.expandIcon\r\n \"\r\n (onClick)=\"toggleExpanded(item.key)\"\r\n />\r\n }\r\n <h3 class=\"m-0 text-lg font-semibold\">\r\n {{ item.title || item.moduleKey || defaultTitle(item) }}\r\n </h3>\r\n @if (item.config.headerStart) {\r\n <div class=\"flex items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.headerStart\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n @if (item.config.headerEnd) {\r\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.headerEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (item.expanded || !item.config.collapse.enabled) {\r\n @if (item.config.templateContent) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.templateContent\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n } @else if (item.type === \"informative\") {\r\n <mt-client-list-informative-view [state]=\"item\" />\r\n } @else if (item.areaType === \"table\") {\r\n <mt-client-list-table-view\r\n [state]=\"item\"\r\n [rowActions]=\"item.config.rowActions\"\r\n [actionShape]=\"item.config.actionShape\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n (rowClick)=\"onTableRowClick(item, $event)\"\r\n />\r\n } @else {\r\n <mt-client-list-cards-view\r\n [state]=\"item\"\r\n (cardClick)=\"onCardClick(item, $event)\"\r\n />\r\n }\r\n }\r\n </section>\r\n }\r\n</div>\r\n" }]
1529
+ ], providers: [ClientListStateService], template: "<div class=\"flex flex-col gap-4\">\r\n @for (item of items(); track item.key) {\r\n <section class=\"flex flex-col gap-4\">\r\n @if (item.config.showHeader) {\r\n <div class=\"flex w-full items-center gap-2\">\r\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\r\n @if (item.config.collapse.enabled) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n [icon]=\"\r\n item.expanded\r\n ? item.config.collapse.collapseIcon\r\n : item.config.collapse.expandIcon\r\n \"\r\n (onClick)=\"toggleExpanded(item.key)\"\r\n />\r\n }\r\n <h3 class=\"m-0 text-lg font-semibold\">\r\n {{ item.title || item.moduleKey || defaultTitle(item) }}\r\n </h3>\r\n @if (item.config.headerStart) {\r\n <div class=\"flex items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.headerStart\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n @if (item.config.headerEnd) {\r\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.headerEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (item.expanded || !item.config.collapse.enabled) {\r\n @if (item.config.templateContent) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"item.config.templateContent\"\r\n [ngTemplateOutletContext]=\"templateContext(item)\"\r\n />\r\n } @else if (item.type === \"informative\") {\r\n <mt-client-list-informative-view [state]=\"item\" />\r\n } @else if (item.areaType === \"table\") {\r\n <mt-client-list-table-view\r\n [state]=\"item\"\r\n [rowActions]=\"item.config.rowActions\"\r\n [actionShape]=\"item.config.actionShape\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n (rowClick)=\"onTableRowClick(item, $event)\"\r\n />\r\n } @else {\r\n <mt-client-list-cards-view\r\n [state]=\"item\"\r\n [rowActions]=\"item.config.rowActions\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n (cardClick)=\"onCardClick(item, $event)\"\r\n />\r\n }\r\n }\r\n </section>\r\n }\r\n</div>\r\n" }]
1127
1530
  }], ctorParameters: () => [], propDecorators: { configurations: [{ type: i0.Input, args: [{ isSignal: true, alias: "configurations", required: true }] }], defaultTake: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultTake", required: false }] }], loaded: [{ type: i0.Output, args: ["loaded"] }], errored: [{ type: i0.Output, args: ["errored"] }], itemClicked: [{ type: i0.Output, args: ["itemClicked"] }] } });
1128
1531
 
1129
1532
  /**
1130
1533
  * Generated bundle index. Do not edit.
1131
1534
  */
1132
1535
 
1133
- export { ClientList, ClientListApiService, ClientListStateService };
1536
+ export { ClientList, ClientListApiService, ClientListStateService, ClientListToolbarService };
1134
1537
  //# sourceMappingURL=masterteam-client-components-client-list.mjs.map