@masterteam/client-components 0.0.30 → 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.
- package/assets/client-components.css +1 -1
- package/fesm2022/masterteam-client-components-client-instance-preview.mjs +5 -3
- package/fesm2022/masterteam-client-components-client-instance-preview.mjs.map +1 -1
- package/fesm2022/masterteam-client-components-client-list.mjs +414 -11
- package/fesm2022/masterteam-client-components-client-list.mjs.map +1 -1
- package/package.json +3 -3
- package/types/masterteam-client-components-client-instance-preview.d.ts +3 -1
- package/types/masterteam-client-components-client-list.d.ts +31 -4
|
@@ -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=\"
|
|
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=\"
|
|
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) {\
|
|
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.ɵɵ
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|