@masterteam/client-components 0.0.67 → 0.0.68
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.
|
@@ -3,7 +3,7 @@ import { inject, Injectable, signal, computed, input, output, Component, viewChi
|
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
5
|
import * as i4 from '@jsverse/transloco';
|
|
6
|
-
import { TranslocoModule, TranslocoPipe
|
|
6
|
+
import { TranslocoService, TranslocoModule, TranslocoPipe } from '@jsverse/transloco';
|
|
7
7
|
import { Button } from '@masterteam/components/button';
|
|
8
8
|
import { RuntimeActionRunner, resolveActionLabel } from '@masterteam/components/runtime-action';
|
|
9
9
|
import { HttpClient } from '@angular/common/http';
|
|
@@ -12,6 +12,7 @@ import { resolveLocalizedLabel, isSectionKey, EntitiesPreview } from '@mastertea
|
|
|
12
12
|
import { Table, TableExportService, TableGroupingController, TableValueResolver, TableCaption } from '@masterteam/components/table';
|
|
13
13
|
import * as i2 from 'primeng/skeleton';
|
|
14
14
|
import { SkeletonModule } from 'primeng/skeleton';
|
|
15
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
15
16
|
import { Card } from '@masterteam/components/card';
|
|
16
17
|
import { Icon } from '@masterteam/icons';
|
|
17
18
|
import { ConfirmationService } from '@masterteam/components/confirmation';
|
|
@@ -37,13 +38,20 @@ class ClientListApiService {
|
|
|
37
38
|
pageSize: query.take,
|
|
38
39
|
})));
|
|
39
40
|
}
|
|
40
|
-
getCards(contextKey, filters = []) {
|
|
41
|
+
getCards(contextKey, filters = [], skip, take) {
|
|
42
|
+
// `page` and `pageSize` must be sent together or not at all (fetch/query
|
|
43
|
+
// request constraints), so a missing or non-positive `take` means the
|
|
44
|
+
// caller wants the whole set in one call.
|
|
45
|
+
const pageSize = typeof take === 'number' && take > 0 ? take : null;
|
|
41
46
|
return this.queryRuntime({
|
|
42
47
|
contextKey,
|
|
43
48
|
projection: 'Card',
|
|
44
49
|
includeState: [...DEFAULT_INCLUDE_STATE],
|
|
45
50
|
filters,
|
|
46
51
|
display: { areas: ['card'] },
|
|
52
|
+
...(pageSize === null
|
|
53
|
+
? {}
|
|
54
|
+
: { page: this.toPage(skip ?? 0, pageSize), pageSize }),
|
|
47
55
|
});
|
|
48
56
|
}
|
|
49
57
|
getInformativeDashboard(levelId, moduleId) {
|
|
@@ -233,22 +241,45 @@ class ClientListStateService {
|
|
|
233
241
|
},
|
|
234
242
|
});
|
|
235
243
|
}
|
|
236
|
-
setCardsResult(key, response, config) {
|
|
244
|
+
setCardsResult(key, response, config, skip = 0, take = 0, append = false) {
|
|
237
245
|
const target = this.itemsByKey()[key];
|
|
238
246
|
if (!target ||
|
|
239
247
|
target.type === 'informative' ||
|
|
240
248
|
target.areaType !== 'cards') {
|
|
241
249
|
return;
|
|
242
250
|
}
|
|
243
|
-
const
|
|
251
|
+
const newCards = this.toCards(response);
|
|
252
|
+
const isAppend = append && config.isPaginated;
|
|
253
|
+
// Never trust two pages not to overlap. A record inserted or re-sorted
|
|
254
|
+
// between fetches shifts the window, and the grid tracks by `card.id` —
|
|
255
|
+
// a repeat would raise NG0955 (duplicate keys) rather than just look odd.
|
|
256
|
+
const cards = isAppend
|
|
257
|
+
? this.concatDistinctCards(target.cards, newCards)
|
|
258
|
+
: newCards;
|
|
244
259
|
const schema = this.resolvePrimarySchema(response);
|
|
245
|
-
const firstCardModule =
|
|
260
|
+
const firstCardModule = newCards[0]?.module;
|
|
246
261
|
const moduleKey = schema?.key ?? firstCardModule?.key ?? target.moduleKey;
|
|
247
262
|
const title = (config.title ?? '').trim() ||
|
|
248
263
|
schema?.name ||
|
|
249
264
|
firstCardModule?.name ||
|
|
250
265
|
moduleKey ||
|
|
251
266
|
target.title;
|
|
267
|
+
// The backend is the authority on how big a page actually is: it may cap
|
|
268
|
+
// `pageSize` below what we asked for. Offsets are derived from that
|
|
269
|
+
// effective size, not from the requested `take` — `toPage()` floors
|
|
270
|
+
// `skip / take`, so a mismatch would re-request a page we already hold.
|
|
271
|
+
const effectiveTake = this.resolveEffectivePageSize(response, take);
|
|
272
|
+
// A page shorter than the effective size is the last page, whatever
|
|
273
|
+
// `totalCount` claims — without this the sentinel would sit there
|
|
274
|
+
// showing skeletons for records the backend will never return. A
|
|
275
|
+
// non-positive size means the response carried no usable paging info at
|
|
276
|
+
// all, which we also treat as "this is everything".
|
|
277
|
+
const exhausted = !config.isPaginated ||
|
|
278
|
+
effectiveTake <= 0 ||
|
|
279
|
+
newCards.length < effectiveTake;
|
|
280
|
+
const totalCount = config.isPaginated && !exhausted
|
|
281
|
+
? (response.pagination?.totalCount ?? cards.length)
|
|
282
|
+
: cards.length;
|
|
252
283
|
this.itemsByKey.set({
|
|
253
284
|
...this.itemsByKey(),
|
|
254
285
|
[key]: {
|
|
@@ -258,14 +289,45 @@ class ClientListStateService {
|
|
|
258
289
|
columns: [],
|
|
259
290
|
rows: [],
|
|
260
291
|
cards,
|
|
261
|
-
skip
|
|
262
|
-
|
|
263
|
-
|
|
292
|
+
// `skip` is the offset of the *next* page to fetch, always a multiple
|
|
293
|
+
// of the effective page size so `toPage()` stays exact.
|
|
294
|
+
skip: config.isPaginated
|
|
295
|
+
? exhausted
|
|
296
|
+
? cards.length
|
|
297
|
+
: skip + effectiveTake
|
|
298
|
+
: 0,
|
|
299
|
+
take: config.isPaginated && effectiveTake > 0 ? effectiveTake : target.take,
|
|
300
|
+
totalCount,
|
|
264
301
|
error: null,
|
|
265
302
|
rawData: response,
|
|
266
303
|
},
|
|
267
304
|
});
|
|
268
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Appends `next` to `current`, dropping any card whose id is already
|
|
308
|
+
* present. Ids are the grid's `@for` track key, so duplicates are a runtime
|
|
309
|
+
* error, not a cosmetic issue.
|
|
310
|
+
*/
|
|
311
|
+
concatDistinctCards(current, next) {
|
|
312
|
+
if (!next.length)
|
|
313
|
+
return current;
|
|
314
|
+
const seen = new Set(current.map((card) => card.id));
|
|
315
|
+
const fresh = next.filter((card) => !seen.has(card.id));
|
|
316
|
+
return fresh.length ? [...current, ...fresh] : current;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The page size the backend actually applied, falling back to the size we
|
|
320
|
+
* asked for. Returns `0` when neither is usable — i.e. the response carried
|
|
321
|
+
* no paging contract, so there is no stride to walk and the caller should
|
|
322
|
+
* treat the set as complete.
|
|
323
|
+
*/
|
|
324
|
+
resolveEffectivePageSize(response, requestedTake) {
|
|
325
|
+
const serverPageSize = response.pagination?.pageSize;
|
|
326
|
+
if (typeof serverPageSize === 'number' && serverPageSize > 0) {
|
|
327
|
+
return serverPageSize;
|
|
328
|
+
}
|
|
329
|
+
return requestedTake > 0 ? requestedTake : 0;
|
|
330
|
+
}
|
|
269
331
|
setInformativeResult(key, response, config) {
|
|
270
332
|
const target = this.itemsByKey()[key];
|
|
271
333
|
if (!target || target.type !== 'informative')
|
|
@@ -827,63 +889,26 @@ class ClientListTableView {
|
|
|
827
889
|
};
|
|
828
890
|
}
|
|
829
891
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListTableView, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
830
|
-
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 }, rowActionsLoadingFn: { classPropertyName: "rowActionsLoadingFn", publicName: "rowActionsLoadingFn", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { lazyLoad: "lazyLoad", rowClick: "rowClick", rowActionsRequested: "rowActionsRequested" }, 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 @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 [persistStateExclude]=\"table().config.table.persistStateExclude\"\r\n [rowActions]=\"rowActions()\"\r\n [actionShape]=\"actionShape()\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFn()\"\r\n (rowActionsRequested)=\"rowActionsRequested.emit($event)\"\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 [freezeActions]=\"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 [virtualScroll]=\"!table().config.isPaginated\"\r\n [virtualScrollItemSize]=\"56\"\r\n scrollHeight=\"520px\"\r\n [pageSize]=\"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 </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: 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", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "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"] }] });
|
|
892
|
+
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 }, rowActionsLoadingFn: { classPropertyName: "rowActionsLoadingFn", publicName: "rowActionsLoadingFn", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { lazyLoad: "lazyLoad", rowClick: "rowClick", rowActionsRequested: "rowActionsRequested" }, 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 @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 [persistStateExclude]=\"table().config.table.persistStateExclude\"\r\n [rowActions]=\"rowActions()\"\r\n [actionShape]=\"actionShape()\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFn()\"\r\n (rowActionsRequested)=\"rowActionsRequested.emit($event)\"\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 [freezeActions]=\"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 [virtualScroll]=\"!table().config.isPaginated\"\r\n [virtualScrollItemSize]=\"56\"\r\n scrollHeight=\"520px\"\r\n [pageSize]=\"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 </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: 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", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }] });
|
|
831
893
|
}
|
|
832
894
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListTableView, decorators: [{
|
|
833
895
|
type: Component,
|
|
834
896
|
args: [{ selector: 'mt-client-list-table-view', standalone: true, imports: [CommonModule, 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 @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 [persistStateExclude]=\"table().config.table.persistStateExclude\"\r\n [rowActions]=\"rowActions()\"\r\n [actionShape]=\"actionShape()\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFn()\"\r\n (rowActionsRequested)=\"rowActionsRequested.emit($event)\"\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 [freezeActions]=\"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 [virtualScroll]=\"!table().config.isPaginated\"\r\n [virtualScrollItemSize]=\"56\"\r\n scrollHeight=\"520px\"\r\n [pageSize]=\"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 </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" }]
|
|
835
897
|
}], 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 }] }], rowActionsLoadingFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActionsLoadingFn", required: false }] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowActionsRequested: [{ type: i0.Output, args: ["rowActionsRequested"] }] } });
|
|
836
898
|
|
|
837
|
-
/**
|
|
838
|
-
* Render-window helpers for the cards view.
|
|
839
|
-
*
|
|
840
|
-
* The cards grid keeps a bounded number of cards in the DOM and grows that
|
|
841
|
-
* window as the user scrolls. Search, filtering, grouping, counts, and export
|
|
842
|
-
* all keep operating on the full result set — only rendering is windowed.
|
|
843
|
-
*/
|
|
844
|
-
/**
|
|
845
|
-
* Fill groups in order until the render budget runs out.
|
|
846
|
-
*
|
|
847
|
-
* A group that fits entirely is passed through untouched (same object identity,
|
|
848
|
-
* so `track` keys stay stable); a group that straddles the budget is copied
|
|
849
|
-
* with a truncated `cards` array. `count` is never rewritten — the header badge
|
|
850
|
-
* must keep reporting the group's true size, not how much of it is rendered.
|
|
851
|
-
*/
|
|
852
|
-
function windowCardGroups(groups, limit) {
|
|
853
|
-
let remaining = limit;
|
|
854
|
-
const windowed = [];
|
|
855
|
-
for (const group of groups) {
|
|
856
|
-
if (remaining <= 0)
|
|
857
|
-
break;
|
|
858
|
-
windowed.push(remaining >= group.cards.length
|
|
859
|
-
? group
|
|
860
|
-
: { ...group, cards: group.cards.slice(0, remaining) });
|
|
861
|
-
remaining -= group.cards.length;
|
|
862
|
-
}
|
|
863
|
-
return windowed;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
899
|
const EMPTY_KEY_SET = new Set();
|
|
867
900
|
/**
|
|
868
|
-
* How
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
* responsive as the workspace grows.
|
|
872
|
-
*
|
|
873
|
-
* This is deliberately *not* a fixed-height virtual viewport: an earlier
|
|
874
|
-
* attempt wrapped every card in a fixed `h-80` row so a scroller could compute
|
|
875
|
-
* item offsets, which clipped taller cards and had to be reverted. Windowing
|
|
876
|
-
* the same `@for` leaves the existing grid, card markup, and natural card
|
|
877
|
-
* heights completely untouched.
|
|
901
|
+
* How far ahead of the viewport the infinite-scroll sentinel counts as
|
|
902
|
+
* visible — roughly one card row, so the next page is already in flight by
|
|
903
|
+
* the time the user reaches the bottom of the grid.
|
|
878
904
|
*/
|
|
879
|
-
const
|
|
880
|
-
/** Grow the window before the sentinel is actually on screen. */
|
|
881
|
-
const CARD_RENDER_ROOT_MARGIN = '600px';
|
|
905
|
+
const LOAD_MORE_ROOT_MARGIN = '400px';
|
|
882
906
|
class ClientListCardsView {
|
|
883
907
|
toolbar = inject(ClientListToolbarService);
|
|
884
908
|
exportService = inject(TableExportService);
|
|
885
909
|
dateFormat = inject(MTDateFormatPipe);
|
|
886
910
|
confirmationService = inject(ConfirmationService);
|
|
911
|
+
transloco = inject(TranslocoService);
|
|
887
912
|
state = input.required(...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
888
913
|
rowActions = input([], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
|
|
889
914
|
/** Per-card loading predicate — drives the popover skeleton while runtime
|
|
@@ -891,13 +916,58 @@ class ClientListCardsView {
|
|
|
891
916
|
* the predicate operates on the card's row representation (`{ Id, ... }`). */
|
|
892
917
|
rowActionsLoadingFn = input(null, ...(ngDevMode ? [{ debugName: "rowActionsLoadingFn" }] : /* istanbul ignore next */ []));
|
|
893
918
|
lazyLoad = output();
|
|
919
|
+
/** Emitted when the infinite-scroll sentinel enters the viewport and more
|
|
920
|
+
* pages remain — the parent responds by fetching and appending the next
|
|
921
|
+
* page of cards. Only relevant when `config.isPaginated` is true. */
|
|
922
|
+
loadMore = output();
|
|
894
923
|
cardClick = output();
|
|
895
924
|
/** Emitted when a card's 3-dots menu opens, so the parent can lazily fetch
|
|
896
925
|
* that record's runtime actions (same contract as the table view). */
|
|
897
926
|
rowActionsRequested = output();
|
|
898
927
|
cardsState = computed(() => this.state(), ...(ngDevMode ? [{ debugName: "cardsState" }] : /* istanbul ignore next */ []));
|
|
928
|
+
/** True while backend pagination is enabled and more pages remain to
|
|
929
|
+
* fetch — drives the infinite-scroll sentinel's visibility. */
|
|
930
|
+
hasMoreCards = computed(() => this.cardsState().config.isPaginated &&
|
|
931
|
+
this.cardsState().cards.length < this.cardsState().totalCount, ...(ngDevMode ? [{ debugName: "hasMoreCards" }] : /* istanbul ignore next */ []));
|
|
932
|
+
/** Anchor element rendered at the end of the grid when more cards remain;
|
|
933
|
+
* observed so scrolling it into view triggers `loadMore`. */
|
|
934
|
+
loadMoreSentinel = viewChild('loadMoreSentinel', ...(ngDevMode ? [{ debugName: "loadMoreSentinel" }] : /* istanbul ignore next */ []));
|
|
935
|
+
loadMoreObserver = null;
|
|
899
936
|
/** Shared toolbar bucket — same instance the table view uses. */
|
|
900
937
|
bucket = computed(() => this.toolbar.forList(this.state().key), ...(ngDevMode ? [{ debugName: "bucket" }] : /* istanbul ignore next */ []));
|
|
938
|
+
/** True while a column filter or search term is active — drives the
|
|
939
|
+
* reset button's visibility in the caption toolbar. */
|
|
940
|
+
hasActiveFilter = computed(() => Object.keys(this.bucket().filters()).length > 0 ||
|
|
941
|
+
this.bucket().filterTerm().trim().length > 0, ...(ngDevMode ? [{ debugName: "hasActiveFilter" }] : /* istanbul ignore next */ []));
|
|
942
|
+
/**
|
|
943
|
+
* `translate()` is a plain call, not a signal, so a computed that used it
|
|
944
|
+
* directly would keep the label in whatever language was active when it
|
|
945
|
+
* first ran. `selectTranslate` re-emits on language switch *and* once the
|
|
946
|
+
* new language's file has loaded.
|
|
947
|
+
*/
|
|
948
|
+
resetLabel = toSignal(this.transloco.selectTranslate('components.table.reset'), {
|
|
949
|
+
initialValue: this.transloco.translate('components.table.reset'),
|
|
950
|
+
});
|
|
951
|
+
/** Caption toolbar action: clears both column filters and the search
|
|
952
|
+
* term. For paginated (server-driven) cards this also re-triggers the
|
|
953
|
+
* filter-change effect below, which re-fetches page 1 without any
|
|
954
|
+
* filters/search — i.e. reloads the full unfiltered set. */
|
|
955
|
+
toolbarActions = computed(() => this.hasActiveFilter()
|
|
956
|
+
? [
|
|
957
|
+
{
|
|
958
|
+
icon: 'arrow.refresh-cw-01',
|
|
959
|
+
color: 'secondary',
|
|
960
|
+
variant: 'text',
|
|
961
|
+
tooltip: this.resetLabel(),
|
|
962
|
+
label: this.resetLabel(),
|
|
963
|
+
action: () => this.resetFilters(),
|
|
964
|
+
},
|
|
965
|
+
]
|
|
966
|
+
: [], ...(ngDevMode ? [{ debugName: "toolbarActions" }] : /* istanbul ignore next */ []));
|
|
967
|
+
resetFilters() {
|
|
968
|
+
this.bucket().filters.set({});
|
|
969
|
+
this.bucket().filterTerm.set('');
|
|
970
|
+
}
|
|
901
971
|
/**
|
|
902
972
|
* Columns for filter/group/export. `state.columns` is empty for cards area
|
|
903
973
|
* (the state service doesn't populate it — see `setCardsResult`), so we
|
|
@@ -1111,7 +1181,7 @@ class ClientListCardsView {
|
|
|
1111
1181
|
* Grouped cards. Reuses TableGroupingController helpers so the accent color,
|
|
1112
1182
|
* label, and count rendering match the table's subheader exactly.
|
|
1113
1183
|
*/
|
|
1114
|
-
|
|
1184
|
+
groupedCards = computed(() => {
|
|
1115
1185
|
const column = this.grouping.activeGroupColumn();
|
|
1116
1186
|
if (!column)
|
|
1117
1187
|
return [];
|
|
@@ -1138,46 +1208,7 @@ class ClientListCardsView {
|
|
|
1138
1208
|
}
|
|
1139
1209
|
});
|
|
1140
1210
|
return Array.from(groups.values());
|
|
1141
|
-
}, ...(ngDevMode ? [{ debugName: "
|
|
1142
|
-
// ==========================================================================
|
|
1143
|
-
// Render window
|
|
1144
|
-
//
|
|
1145
|
-
// Search, filters, grouping, counts, and export all keep operating on the
|
|
1146
|
-
// full `visibleCards()` set — only what reaches the DOM is windowed.
|
|
1147
|
-
// ==========================================================================
|
|
1148
|
-
/** Windowing needs the sentinel to observe; without it, render everything. */
|
|
1149
|
-
supportsIntersectionObserver = typeof IntersectionObserver !== 'undefined';
|
|
1150
|
-
renderLimit = signal(this.initialRenderLimit(), ...(ngDevMode ? [{ debugName: "renderLimit" }] : /* istanbul ignore next */ []));
|
|
1151
|
-
loadMoreSentinel = viewChild('loadMoreSentinel', ...(ngDevMode ? [{ debugName: "loadMoreSentinel" }] : /* istanbul ignore next */ []));
|
|
1152
|
-
initialRenderLimit() {
|
|
1153
|
-
return this.supportsIntersectionObserver
|
|
1154
|
-
? CARD_RENDER_CHUNK
|
|
1155
|
-
: Number.POSITIVE_INFINITY;
|
|
1156
|
-
}
|
|
1157
|
-
/**
|
|
1158
|
-
* Identity of the current result set. Any change — list swap, search, filter,
|
|
1159
|
-
* group toggle — restarts the window at the first chunk, so a freshly
|
|
1160
|
-
* filtered set never opens scrolled into the middle of the previous one.
|
|
1161
|
-
*/
|
|
1162
|
-
renderWindowKey = computed(() => {
|
|
1163
|
-
const bucket = this.bucket();
|
|
1164
|
-
return JSON.stringify([
|
|
1165
|
-
this.cardsState().key,
|
|
1166
|
-
bucket.filterTerm(),
|
|
1167
|
-
bucket.filters(),
|
|
1168
|
-
this.grouping.activeGroupColumn()?.key ?? null,
|
|
1169
|
-
]);
|
|
1170
|
-
}, ...(ngDevMode ? [{ debugName: "renderWindowKey" }] : /* istanbul ignore next */ []));
|
|
1171
|
-
/** Cards put in the DOM by the ungrouped grid. */
|
|
1172
|
-
renderedCards = computed(() => this.visibleCards().slice(0, this.renderLimit()), ...(ngDevMode ? [{ debugName: "renderedCards" }] : /* istanbul ignore next */ []));
|
|
1173
|
-
/**
|
|
1174
|
-
* Grouped view of the window. Groups are filled in order until the budget
|
|
1175
|
-
* runs out; `count` stays the group's true size so the header badge keeps
|
|
1176
|
-
* reporting the full number rather than what happens to be rendered.
|
|
1177
|
-
*/
|
|
1178
|
-
groupedCards = computed(() => windowCardGroups(this.allGroupedCards(), this.renderLimit()), ...(ngDevMode ? [{ debugName: "groupedCards" }] : /* istanbul ignore next */ []));
|
|
1179
|
-
/** True while cards are still being held back from the DOM. */
|
|
1180
|
-
hasMoreCards = computed(() => this.visibleCards().length > this.renderLimit(), ...(ngDevMode ? [{ debugName: "hasMoreCards" }] : /* istanbul ignore next */ []));
|
|
1211
|
+
}, ...(ngDevMode ? [{ debugName: "groupedCards" }] : /* istanbul ignore next */ []));
|
|
1181
1212
|
/**
|
|
1182
1213
|
* Export the visible (after search + filter) cards as XLSX. Uses the cards'
|
|
1183
1214
|
* derived row shape + the state's columns, so the file mirrors what the
|
|
@@ -1331,39 +1362,73 @@ class ClientListCardsView {
|
|
|
1331
1362
|
return null;
|
|
1332
1363
|
}
|
|
1333
1364
|
constructor() {
|
|
1334
|
-
//
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
//
|
|
1340
|
-
//
|
|
1341
|
-
//
|
|
1365
|
+
// Attach the observer to whatever element the sentinel query currently
|
|
1366
|
+
// resolves to, and re-attach if Angular ever swaps that element. Keying
|
|
1367
|
+
// on the element instance is what makes this safe: appending a page
|
|
1368
|
+
// doesn't recreate the node, so the observer is *not* rebuilt per page —
|
|
1369
|
+
// which matters because a fresh IntersectionObserver always fires once
|
|
1370
|
+
// with the current intersection state, and rebuilding on every append
|
|
1371
|
+
// would cascade through the remaining pages on its own.
|
|
1372
|
+
//
|
|
1373
|
+
// Attaching only once, however, is not safe: the sentinel lives at the
|
|
1374
|
+
// end of the grid, and the grid is one branch of the view (the initial
|
|
1375
|
+
// skeleton, an error, an empty result and the grouped layout are the
|
|
1376
|
+
// others). A one-shot attach survives the initial skeleton but then
|
|
1377
|
+
// silently observes a detached node the first time any sibling branch
|
|
1378
|
+
// takes over, killing infinite scroll for the rest of the component's
|
|
1379
|
+
// life.
|
|
1342
1380
|
effect((onCleanup) => {
|
|
1343
|
-
const
|
|
1344
|
-
if (!
|
|
1381
|
+
const el = this.loadMoreSentinel()?.nativeElement;
|
|
1382
|
+
if (!el) {
|
|
1383
|
+
this.loadMoreObserver = null;
|
|
1345
1384
|
return;
|
|
1385
|
+
}
|
|
1346
1386
|
const observer = new IntersectionObserver((entries) => {
|
|
1347
|
-
if (!
|
|
1387
|
+
if (!this.hasMoreCards())
|
|
1348
1388
|
return;
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1389
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
1390
|
+
this.loadMore.emit();
|
|
1391
|
+
}
|
|
1392
|
+
}, { rootMargin: LOAD_MORE_ROOT_MARGIN });
|
|
1393
|
+
observer.observe(el);
|
|
1394
|
+
this.loadMoreObserver = observer;
|
|
1395
|
+
onCleanup(() => {
|
|
1396
|
+
observer.disconnect();
|
|
1397
|
+
if (this.loadMoreObserver === observer)
|
|
1398
|
+
this.loadMoreObserver = null;
|
|
1399
|
+
});
|
|
1357
1400
|
});
|
|
1358
|
-
//
|
|
1359
|
-
//
|
|
1360
|
-
//
|
|
1361
|
-
//
|
|
1362
|
-
|
|
1401
|
+
// Re-arm after each page settles. IntersectionObserver only reports
|
|
1402
|
+
// threshold *crossings*: if an appended page is too short to push the
|
|
1403
|
+
// sentinel back out of the root margin, the entry stays `isIntersecting`
|
|
1404
|
+
// and no further callback ever arrives — the list stalls part-loaded
|
|
1405
|
+
// until the user happens to scroll. Re-observing the same element
|
|
1406
|
+
// delivers a fresh initial callback, so the loop continues until the
|
|
1407
|
+
// sentinel really is out of range (callback reports no intersection, no
|
|
1408
|
+
// emit, no state change, effect idle) or the data runs out
|
|
1409
|
+
// (`hasMoreCards()` false). `observe()` on an already-observed target is
|
|
1410
|
+
// a no-op, hence the explicit `unobserve` first.
|
|
1363
1411
|
effect(() => {
|
|
1364
1412
|
const state = this.cardsState();
|
|
1413
|
+
const el = this.loadMoreSentinel()?.nativeElement;
|
|
1414
|
+
const observer = this.loadMoreObserver;
|
|
1415
|
+
if (!observer || !el || state.loading || !this.hasMoreCards())
|
|
1416
|
+
return;
|
|
1417
|
+
observer.unobserve(el);
|
|
1418
|
+
observer.observe(el);
|
|
1419
|
+
});
|
|
1420
|
+
// Emit lazyLoad only on genuine filter / search changes for paginated
|
|
1421
|
+
// lists, matching mt-table's behaviour (group-by is a pure FE concern,
|
|
1422
|
+
// not a re-fetch trigger). Deliberately reads `cardsState()` via
|
|
1423
|
+
// `untracked` — the item state gets a new object reference on every
|
|
1424
|
+
// page append (infinite scroll), and reacting to that here would
|
|
1425
|
+
// re-emit `lazyLoad` after every append, which re-fetches with
|
|
1426
|
+
// `append: false` and wipes out everything already loaded.
|
|
1427
|
+
let primed = false;
|
|
1428
|
+
effect(() => {
|
|
1365
1429
|
const filters = this.bucket().filters();
|
|
1366
1430
|
const filterTerm = this.bucket().filterTerm();
|
|
1431
|
+
const state = untracked(() => this.cardsState());
|
|
1367
1432
|
if (!state.config.isPaginated)
|
|
1368
1433
|
return;
|
|
1369
1434
|
if (!primed) {
|
|
@@ -1377,13 +1442,13 @@ class ClientListCardsView {
|
|
|
1377
1442
|
...(filterTerm ? { generalSearch: filterTerm } : {}),
|
|
1378
1443
|
},
|
|
1379
1444
|
pageSize: state.take,
|
|
1380
|
-
first:
|
|
1381
|
-
currentPage:
|
|
1445
|
+
first: 0,
|
|
1446
|
+
currentPage: 1,
|
|
1382
1447
|
}));
|
|
1383
1448
|
});
|
|
1384
1449
|
}
|
|
1385
1450
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListCardsView, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1386
|
-
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 }, rowActionsLoadingFn: { classPropertyName: "rowActionsLoadingFn", publicName: "rowActionsLoadingFn", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { lazyLoad: "lazyLoad", cardClick: "cardClick", rowActionsRequested: "rowActionsRequested" }, providers: [MTDateFormatPipe], viewQueries: [{ propertyName: "loadMoreSentinel", first: true, predicate: ["loadMoreSentinel"], descendants: true, isSignal: true }], ngImport: i0, template: "<mt-table-caption\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n filterMode=\"popover\"\r\n [printable]=\"true\"\r\n [groupable]=\"true\"\r\n [exportable]=\"true\"\r\n [columns]=\"columns()\"\r\n [data]=\"rowRepresentations()\"\r\n [groupColumns]=\"grouping.groupableColumns()\"\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 (exportRequested)=\"exportExcel()\"\r\n (printRequested)=\"printPdf()\"\r\n/>\r\n\r\n@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 (visibleCards().length === 0) {\r\n <div class=\"p-6 text-center text-gray-400\">\r\n {{ \"components.clientComponents.list.noCardsFound\" | transloco }}\r\n </div>\r\n} @else if (grouping.groupingActive()) {\r\n <div class=\"flex flex-col gap-6\">\r\n @for (group of groupedCards(); track group.key) {\r\n <section class=\"flex flex-col gap-3\">\r\n <header\r\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\"\r\n [style.--mt-group-accent]=\"group.accent ?? 'var(--p-primary-color)'\"\r\n >\r\n <span\r\n class=\"inline-block w-1 h-5 rounded-full\"\r\n [style.background]=\"'var(--mt-group-accent)'\"\r\n ></span>\r\n <span\r\n class=\"text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400\"\r\n >\r\n {{ group.columnLabel }}\r\n </span>\r\n <span class=\"text-sm font-semibold text-gray-900 dark:text-gray-100\">\r\n {{ group.label }}\r\n </span>\r\n <span\r\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\"\r\n >\r\n {{ group.count }}\r\n </span>\r\n </header>\r\n <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3\">\r\n @for (card of group.cards; track card.id) {\r\n <mt-card\r\n class=\"relative 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 <ng-container\r\n [ngTemplateOutlet]=\"cardActionsMenu\"\r\n [ngTemplateOutletContext]=\"{ $implicit: card }\"\r\n />\r\n <mt-entities-preview\r\n [entities]=\"card.entities\"\r\n attachmentShape=\"compact\"\r\n [stackOnNarrow]=\"false\"\r\n [clickableKeys]=\"clickableEntityKeys()\"\r\n [activeKeys]=\"activeKeysForCard(card)\"\r\n [clickableTooltip]=\"\r\n 'components.table.filterByThis' | transloco\r\n \"\r\n [activeTooltip]=\"'components.table.clearFilter' | transloco\"\r\n (entityClick)=\"onEntityFilterClick(card, $event)\"\r\n />\r\n </ng-template>\r\n </mt-card>\r\n }\r\n </div>\r\n </section>\r\n }\r\n </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 renderedCards(); track card.id) {\r\n @defer (on viewport) {\r\n <mt-card\r\n class=\"relative 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 <ng-container\r\n [ngTemplateOutlet]=\"cardActionsMenu\"\r\n [ngTemplateOutletContext]=\"{ $implicit: card }\"\r\n />\r\n <mt-entities-preview\r\n [entities]=\"card.entities\"\r\n attachmentShape=\"compact\"\r\n [stackOnNarrow]=\"false\"\r\n [clickableKeys]=\"clickableEntityKeys()\"\r\n [activeKeys]=\"activeKeysForCard(card)\"\r\n [clickableTooltip]=\"'components.table.filterByThis' | transloco\"\r\n [activeTooltip]=\"'components.table.clearFilter' | transloco\"\r\n (entityClick)=\"onEntityFilterClick(card, $event)\"\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\r\n<!--\r\n Render-window sentinel. Sits after the grid (never inside it, so it is not a\r\n grid child) and grows the window as it approaches the viewport. Removed from\r\n the DOM once every card is rendered.\r\n-->\r\n@if (hasMoreCards()) {\r\n <div #loadMoreSentinel class=\"h-px w-full\" aria-hidden=\"true\"></div>\r\n}\r\n\r\n<!--\r\n Per-card 3-dots actions menu \u2014 same contract as the table view's popover\r\n row-actions. Rendered into each card's top-end corner; clicks stop at the\r\n wrapper so the card's own click (cardClick) doesn't fire.\r\n-->\r\n<ng-template #cardActionsMenu let-card>\r\n @if (rowActions().length > 0) {\r\n <div\r\n class=\"absolute end-2 top-2 z-10 flex items-center gap-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n >\r\n @for (action of inlineActionsForCard(card); track $index) {\r\n <mt-button\r\n [icon]=\"action.icon\"\r\n [severity]=\"action.color\"\r\n [variant]=\"action.variant\"\r\n [size]=\"actionSize(action) || 'small'\"\r\n [tooltip]=\"action.tooltip\"\r\n [label]=\"action.label\"\r\n [loading]=\"resolveActionLoading(action, card)\"\r\n (onClick)=\"runCardAction($event, action, card)\"\r\n />\r\n }\r\n @if (showCardActionsMenu(card)) {\r\n <mt-button\r\n icon=\"general.dots-vertical\"\r\n severity=\"secondary\"\r\n variant=\"text\"\r\n [tooltip]=\"'components.table.actions' | transloco\"\r\n (onClick)=\"onCardActionsToggle($event, cardPopover, card)\"\r\n />\r\n <p-popover #cardPopover appendTo=\"body\">\r\n <div class=\"flex min-w-44 flex-col gap-1 p-1\">\r\n @if (isCardActionsLoading(card)) {\r\n <div class=\"flex flex-col gap-2 p-1\">\r\n <p-skeleton height=\"1.75rem\" />\r\n <p-skeleton height=\"1.75rem\" />\r\n <p-skeleton height=\"1.75rem\" />\r\n </div>\r\n } @else {\r\n @for (action of menuActionsForCard(card); track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"flex cursor-pointer items-center gap-2 rounded px-2 py-2 text-start text-sm transition-colors\"\r\n [class]=\"\r\n action.color === 'danger'\r\n ? 'text-red-600 hover:bg-red-50 dark:hover:bg-red-950'\r\n : 'text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-surface-700'\r\n \"\r\n (click)=\"\r\n runCardAction($event, action, card); cardPopover.hide()\r\n \"\r\n >\r\n @if (action.icon) {\r\n <mt-icon [icon]=\"action.icon\" class=\"text-base\" />\r\n }\r\n <span>{{ action.label || action.tooltip }}</span>\r\n </button>\r\n }\r\n }\r\n </div>\r\n </p-popover>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { 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: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: EntitiesPreview, selector: "mt-entities-preview", inputs: ["entities", "attachmentShape", "stackOnNarrow", "clickableKeys", "activeKeys", "clickableTooltip", "activeTooltip"], outputs: ["entityClick"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i3.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { 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"] }, { kind: "pipe", type: i4.TranslocoPipe, name: "transloco" }], deferBlockDependencies: [() => [i1.NgTemplateOutlet, Card,
|
|
1451
|
+
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 }, rowActionsLoadingFn: { classPropertyName: "rowActionsLoadingFn", publicName: "rowActionsLoadingFn", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { lazyLoad: "lazyLoad", loadMore: "loadMore", cardClick: "cardClick", rowActionsRequested: "rowActionsRequested" }, providers: [MTDateFormatPipe], viewQueries: [{ propertyName: "loadMoreSentinel", first: true, predicate: ["loadMoreSentinel"], descendants: true, isSignal: true }], ngImport: i0, template: "<mt-table-caption\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n filterMode=\"popover\"\r\n [printable]=\"true\"\r\n [groupable]=\"true\"\r\n [exportable]=\"true\"\r\n [columns]=\"columns()\"\r\n [data]=\"rowRepresentations()\"\r\n [groupColumns]=\"grouping.groupableColumns()\"\r\n [actions]=\"toolbarActions()\"\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 (exportRequested)=\"exportExcel()\"\r\n (printRequested)=\"printPdf()\"\r\n/>\r\n\r\n@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 (visibleCards().length === 0) {\r\n <div class=\"p-6 text-center text-gray-400\">\r\n {{ \"components.clientComponents.list.noCardsFound\" | transloco }}\r\n </div>\r\n} @else if (grouping.groupingActive()) {\r\n <div class=\"flex flex-col gap-6\">\r\n @for (group of groupedCards(); track group.key) {\r\n <section class=\"flex flex-col gap-3\">\r\n <header\r\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\"\r\n [style.--mt-group-accent]=\"group.accent ?? 'var(--p-primary-color)'\"\r\n >\r\n <span\r\n class=\"inline-block w-1 h-5 rounded-full\"\r\n [style.background]=\"'var(--mt-group-accent)'\"\r\n ></span>\r\n <span\r\n class=\"text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400\"\r\n >\r\n {{ group.columnLabel }}\r\n </span>\r\n <span class=\"text-sm font-semibold text-gray-900 dark:text-gray-100\">\r\n {{ group.label }}\r\n </span>\r\n <span\r\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\"\r\n >\r\n {{ group.count }}\r\n </span>\r\n </header>\r\n <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3\">\r\n @for (card of group.cards; track card.id) {\r\n <mt-card\r\n class=\"relative 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 <ng-container\r\n [ngTemplateOutlet]=\"cardActionsMenu\"\r\n [ngTemplateOutletContext]=\"{ $implicit: card }\"\r\n />\r\n <mt-entities-preview\r\n [entities]=\"card.entities\"\r\n attachmentShape=\"compact\"\r\n [stackOnNarrow]=\"false\"\r\n [clickableKeys]=\"clickableEntityKeys()\"\r\n [activeKeys]=\"activeKeysForCard(card)\"\r\n [clickableTooltip]=\"\r\n 'components.table.filterByThis' | transloco\r\n \"\r\n [activeTooltip]=\"'components.table.clearFilter' | transloco\"\r\n (entityClick)=\"onEntityFilterClick(card, $event)\"\r\n />\r\n </ng-template>\r\n </mt-card>\r\n }\r\n </div>\r\n </section>\r\n }\r\n </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 visibleCards(); track card.id) {\r\n @defer (on viewport) {\r\n <mt-card\r\n class=\"relative 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 <ng-container\r\n [ngTemplateOutlet]=\"cardActionsMenu\"\r\n [ngTemplateOutletContext]=\"{ $implicit: card }\"\r\n />\r\n <mt-entities-preview\r\n [entities]=\"card.entities\"\r\n attachmentShape=\"compact\"\r\n [stackOnNarrow]=\"false\"\r\n [clickableKeys]=\"clickableEntityKeys()\"\r\n [activeKeys]=\"activeKeysForCard(card)\"\r\n [clickableTooltip]=\"'components.table.filterByThis' | transloco\"\r\n [activeTooltip]=\"'components.table.clearFilter' | transloco\"\r\n (entityClick)=\"onEntityFilterClick(card, $event)\"\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\r\n<!--\r\n Infinite-scroll trigger. Deliberately outside the @if/@else chain above:\r\n inside it the element would be torn down and rebuilt every time the view\r\n swapped branch (error, empty filter result, group-by), and the observer\r\n would be left watching a detached node. Kept in the DOM and hidden via CSS\r\n when there is nothing left to load, so appending a page never recreates it.\r\n It also has to sit outside so infinite scroll keeps working while a\r\n group-by is active \u2014 grouping is a pure FE view over whatever has been\r\n fetched, not a reason to stop fetching.\r\n-->\r\n<div\r\n #loadMoreSentinel\r\n class=\"grid grid-cols-1 gap-5 py-4 md:grid-cols-2 xl:grid-cols-3\"\r\n [class.hidden]=\"!hasMoreCards()\"\r\n>\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\r\n }\r\n</div>\r\n\r\n<!--\r\n Per-card 3-dots actions menu \u2014 same contract as the table view's popover\r\n row-actions. Rendered into each card's top-end corner; clicks stop at the\r\n wrapper so the card's own click (cardClick) doesn't fire.\r\n-->\r\n<ng-template #cardActionsMenu let-card>\r\n @if (rowActions().length > 0) {\r\n <div\r\n class=\"absolute end-2 top-2 z-10 flex items-center gap-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n >\r\n @for (action of inlineActionsForCard(card); track $index) {\r\n <mt-button\r\n [icon]=\"action.icon\"\r\n [severity]=\"action.color\"\r\n [variant]=\"action.variant\"\r\n [size]=\"actionSize(action) || 'small'\"\r\n [tooltip]=\"action.tooltip\"\r\n [label]=\"action.label\"\r\n [loading]=\"resolveActionLoading(action, card)\"\r\n (onClick)=\"runCardAction($event, action, card)\"\r\n />\r\n }\r\n @if (showCardActionsMenu(card)) {\r\n <mt-button\r\n icon=\"general.dots-vertical\"\r\n severity=\"secondary\"\r\n variant=\"text\"\r\n [tooltip]=\"'components.table.actions' | transloco\"\r\n (onClick)=\"onCardActionsToggle($event, cardPopover, card)\"\r\n />\r\n <p-popover #cardPopover appendTo=\"body\">\r\n <div class=\"flex min-w-44 flex-col gap-1 p-1\">\r\n @if (isCardActionsLoading(card)) {\r\n <div class=\"flex flex-col gap-2 p-1\">\r\n <p-skeleton height=\"1.75rem\" />\r\n <p-skeleton height=\"1.75rem\" />\r\n <p-skeleton height=\"1.75rem\" />\r\n </div>\r\n } @else {\r\n @for (action of menuActionsForCard(card); track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"flex cursor-pointer items-center gap-2 rounded px-2 py-2 text-start text-sm transition-colors\"\r\n [class]=\"\r\n action.color === 'danger'\r\n ? 'text-red-600 hover:bg-red-50 dark:hover:bg-red-950'\r\n : 'text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-surface-700'\r\n \"\r\n (click)=\"\r\n runCardAction($event, action, card); cardPopover.hide()\r\n \"\r\n >\r\n @if (action.icon) {\r\n <mt-icon [icon]=\"action.icon\" class=\"text-base\" />\r\n }\r\n <span>{{ action.label || action.tooltip }}</span>\r\n </button>\r\n }\r\n }\r\n </div>\r\n </p-popover>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { 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: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: EntitiesPreview, selector: "mt-entities-preview", inputs: ["entities", "attachmentShape", "stackOnNarrow", "clickableKeys", "activeKeys", "clickableTooltip", "activeTooltip"], outputs: ["entityClick"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i3.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { 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"] }, { kind: "pipe", type: i4.TranslocoPipe, name: "transloco" }], deferBlockDependencies: [() => [i1.NgTemplateOutlet, Card,
|
|
1387
1452
|
EntitiesPreview, i4.TranslocoPipe]] });
|
|
1388
1453
|
}
|
|
1389
1454
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListCardsView, decorators: [{
|
|
@@ -1398,8 +1463,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
1398
1463
|
SkeletonModule,
|
|
1399
1464
|
PopoverModule,
|
|
1400
1465
|
TableCaption,
|
|
1401
|
-
], providers: [MTDateFormatPipe], template: "<mt-table-caption\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n filterMode=\"popover\"\r\n [printable]=\"true\"\r\n [groupable]=\"true\"\r\n [exportable]=\"true\"\r\n [columns]=\"columns()\"\r\n [data]=\"rowRepresentations()\"\r\n [groupColumns]=\"grouping.groupableColumns()\"\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 (exportRequested)=\"exportExcel()\"\r\n (printRequested)=\"printPdf()\"\r\n/>\r\n\r\n@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 (visibleCards().length === 0) {\r\n <div class=\"p-6 text-center text-gray-400\">\r\n {{ \"components.clientComponents.list.noCardsFound\" | transloco }}\r\n </div>\r\n} @else if (grouping.groupingActive()) {\r\n <div class=\"flex flex-col gap-6\">\r\n @for (group of groupedCards(); track group.key) {\r\n <section class=\"flex flex-col gap-3\">\r\n <header\r\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\"\r\n [style.--mt-group-accent]=\"group.accent ?? 'var(--p-primary-color)'\"\r\n >\r\n <span\r\n class=\"inline-block w-1 h-5 rounded-full\"\r\n [style.background]=\"'var(--mt-group-accent)'\"\r\n ></span>\r\n <span\r\n class=\"text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400\"\r\n >\r\n {{ group.columnLabel }}\r\n </span>\r\n <span class=\"text-sm font-semibold text-gray-900 dark:text-gray-100\">\r\n {{ group.label }}\r\n </span>\r\n <span\r\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\"\r\n >\r\n {{ group.count }}\r\n </span>\r\n </header>\r\n <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3\">\r\n @for (card of group.cards; track card.id) {\r\n <mt-card\r\n class=\"relative 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 <ng-container\r\n [ngTemplateOutlet]=\"cardActionsMenu\"\r\n [ngTemplateOutletContext]=\"{ $implicit: card }\"\r\n />\r\n <mt-entities-preview\r\n [entities]=\"card.entities\"\r\n attachmentShape=\"compact\"\r\n [stackOnNarrow]=\"false\"\r\n [clickableKeys]=\"clickableEntityKeys()\"\r\n [activeKeys]=\"activeKeysForCard(card)\"\r\n [clickableTooltip]=\"\r\n 'components.table.filterByThis' | transloco\r\n \"\r\n [activeTooltip]=\"'components.table.clearFilter' | transloco\"\r\n (entityClick)=\"onEntityFilterClick(card, $event)\"\r\n />\r\n </ng-template>\r\n </mt-card>\r\n }\r\n </div>\r\n </section>\r\n }\r\n </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
|
|
1402
|
-
}], ctorParameters: () => [], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], rowActionsLoadingFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActionsLoadingFn", required: false }] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }], rowActionsRequested: [{ type: i0.Output, args: ["rowActionsRequested"] }], loadMoreSentinel: [{ type: i0.ViewChild, args: ['loadMoreSentinel', { isSignal: true }] }] } });
|
|
1466
|
+
], providers: [MTDateFormatPipe], template: "<mt-table-caption\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n filterMode=\"popover\"\r\n [printable]=\"true\"\r\n [groupable]=\"true\"\r\n [exportable]=\"true\"\r\n [columns]=\"columns()\"\r\n [data]=\"rowRepresentations()\"\r\n [groupColumns]=\"grouping.groupableColumns()\"\r\n [actions]=\"toolbarActions()\"\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 (exportRequested)=\"exportExcel()\"\r\n (printRequested)=\"printPdf()\"\r\n/>\r\n\r\n@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 (visibleCards().length === 0) {\r\n <div class=\"p-6 text-center text-gray-400\">\r\n {{ \"components.clientComponents.list.noCardsFound\" | transloco }}\r\n </div>\r\n} @else if (grouping.groupingActive()) {\r\n <div class=\"flex flex-col gap-6\">\r\n @for (group of groupedCards(); track group.key) {\r\n <section class=\"flex flex-col gap-3\">\r\n <header\r\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\"\r\n [style.--mt-group-accent]=\"group.accent ?? 'var(--p-primary-color)'\"\r\n >\r\n <span\r\n class=\"inline-block w-1 h-5 rounded-full\"\r\n [style.background]=\"'var(--mt-group-accent)'\"\r\n ></span>\r\n <span\r\n class=\"text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400\"\r\n >\r\n {{ group.columnLabel }}\r\n </span>\r\n <span class=\"text-sm font-semibold text-gray-900 dark:text-gray-100\">\r\n {{ group.label }}\r\n </span>\r\n <span\r\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\"\r\n >\r\n {{ group.count }}\r\n </span>\r\n </header>\r\n <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3\">\r\n @for (card of group.cards; track card.id) {\r\n <mt-card\r\n class=\"relative 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 <ng-container\r\n [ngTemplateOutlet]=\"cardActionsMenu\"\r\n [ngTemplateOutletContext]=\"{ $implicit: card }\"\r\n />\r\n <mt-entities-preview\r\n [entities]=\"card.entities\"\r\n attachmentShape=\"compact\"\r\n [stackOnNarrow]=\"false\"\r\n [clickableKeys]=\"clickableEntityKeys()\"\r\n [activeKeys]=\"activeKeysForCard(card)\"\r\n [clickableTooltip]=\"\r\n 'components.table.filterByThis' | transloco\r\n \"\r\n [activeTooltip]=\"'components.table.clearFilter' | transloco\"\r\n (entityClick)=\"onEntityFilterClick(card, $event)\"\r\n />\r\n </ng-template>\r\n </mt-card>\r\n }\r\n </div>\r\n </section>\r\n }\r\n </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 visibleCards(); track card.id) {\r\n @defer (on viewport) {\r\n <mt-card\r\n class=\"relative 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 <ng-container\r\n [ngTemplateOutlet]=\"cardActionsMenu\"\r\n [ngTemplateOutletContext]=\"{ $implicit: card }\"\r\n />\r\n <mt-entities-preview\r\n [entities]=\"card.entities\"\r\n attachmentShape=\"compact\"\r\n [stackOnNarrow]=\"false\"\r\n [clickableKeys]=\"clickableEntityKeys()\"\r\n [activeKeys]=\"activeKeysForCard(card)\"\r\n [clickableTooltip]=\"'components.table.filterByThis' | transloco\"\r\n [activeTooltip]=\"'components.table.clearFilter' | transloco\"\r\n (entityClick)=\"onEntityFilterClick(card, $event)\"\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\r\n<!--\r\n Infinite-scroll trigger. Deliberately outside the @if/@else chain above:\r\n inside it the element would be torn down and rebuilt every time the view\r\n swapped branch (error, empty filter result, group-by), and the observer\r\n would be left watching a detached node. Kept in the DOM and hidden via CSS\r\n when there is nothing left to load, so appending a page never recreates it.\r\n It also has to sit outside so infinite scroll keeps working while a\r\n group-by is active \u2014 grouping is a pure FE view over whatever has been\r\n fetched, not a reason to stop fetching.\r\n-->\r\n<div\r\n #loadMoreSentinel\r\n class=\"grid grid-cols-1 gap-5 py-4 md:grid-cols-2 xl:grid-cols-3\"\r\n [class.hidden]=\"!hasMoreCards()\"\r\n>\r\n @for (i of [1, 2, 3]; track i) {\r\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\r\n }\r\n</div>\r\n\r\n<!--\r\n Per-card 3-dots actions menu \u2014 same contract as the table view's popover\r\n row-actions. Rendered into each card's top-end corner; clicks stop at the\r\n wrapper so the card's own click (cardClick) doesn't fire.\r\n-->\r\n<ng-template #cardActionsMenu let-card>\r\n @if (rowActions().length > 0) {\r\n <div\r\n class=\"absolute end-2 top-2 z-10 flex items-center gap-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n >\r\n @for (action of inlineActionsForCard(card); track $index) {\r\n <mt-button\r\n [icon]=\"action.icon\"\r\n [severity]=\"action.color\"\r\n [variant]=\"action.variant\"\r\n [size]=\"actionSize(action) || 'small'\"\r\n [tooltip]=\"action.tooltip\"\r\n [label]=\"action.label\"\r\n [loading]=\"resolveActionLoading(action, card)\"\r\n (onClick)=\"runCardAction($event, action, card)\"\r\n />\r\n }\r\n @if (showCardActionsMenu(card)) {\r\n <mt-button\r\n icon=\"general.dots-vertical\"\r\n severity=\"secondary\"\r\n variant=\"text\"\r\n [tooltip]=\"'components.table.actions' | transloco\"\r\n (onClick)=\"onCardActionsToggle($event, cardPopover, card)\"\r\n />\r\n <p-popover #cardPopover appendTo=\"body\">\r\n <div class=\"flex min-w-44 flex-col gap-1 p-1\">\r\n @if (isCardActionsLoading(card)) {\r\n <div class=\"flex flex-col gap-2 p-1\">\r\n <p-skeleton height=\"1.75rem\" />\r\n <p-skeleton height=\"1.75rem\" />\r\n <p-skeleton height=\"1.75rem\" />\r\n </div>\r\n } @else {\r\n @for (action of menuActionsForCard(card); track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"flex cursor-pointer items-center gap-2 rounded px-2 py-2 text-start text-sm transition-colors\"\r\n [class]=\"\r\n action.color === 'danger'\r\n ? 'text-red-600 hover:bg-red-50 dark:hover:bg-red-950'\r\n : 'text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-surface-700'\r\n \"\r\n (click)=\"\r\n runCardAction($event, action, card); cardPopover.hide()\r\n \"\r\n >\r\n @if (action.icon) {\r\n <mt-icon [icon]=\"action.icon\" class=\"text-base\" />\r\n }\r\n <span>{{ action.label || action.tooltip }}</span>\r\n </button>\r\n }\r\n }\r\n </div>\r\n </p-popover>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n" }]
|
|
1467
|
+
}], ctorParameters: () => [], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], rowActionsLoadingFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActionsLoadingFn", required: false }] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], loadMore: [{ type: i0.Output, args: ["loadMore"] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }], rowActionsRequested: [{ type: i0.Output, args: ["rowActionsRequested"] }], loadMoreSentinel: [{ type: i0.ViewChild, args: ['loadMoreSentinel', { isSignal: true }] }] } });
|
|
1403
1468
|
|
|
1404
1469
|
function toDashboardExtraFilters(filters = []) {
|
|
1405
1470
|
const extraFilters = {};
|
|
@@ -1573,17 +1638,41 @@ class ClientList {
|
|
|
1573
1638
|
const nextFilters = event.filters ?? this.runtimeFilters.get(item.key) ?? {};
|
|
1574
1639
|
const filtersChanged = runtimeFiltersChanged(this.runtimeFilters.get(item.key), nextFilters);
|
|
1575
1640
|
this.runtimeFilters.set(item.key, nextFilters);
|
|
1576
|
-
if (item.areaType === 'cards') {
|
|
1577
|
-
this.loadCards(item.key, item.config);
|
|
1578
|
-
return;
|
|
1579
|
-
}
|
|
1580
1641
|
const take = this.resolvePositiveInteger(event.pageSize) ?? item.take;
|
|
1581
1642
|
const skip = filtersChanged
|
|
1582
1643
|
? 0
|
|
1583
1644
|
: (this.resolveNonNegativeInteger(event.first) ?? 0);
|
|
1645
|
+
if (item.areaType === 'cards') {
|
|
1646
|
+
this.loadCards(item.key, item.config, skip, take, false);
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1584
1649
|
this.state.setTablePaging(item.key, skip, take);
|
|
1585
1650
|
this.loadTable(item.key, item.config, skip, take);
|
|
1586
1651
|
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Fetches the next page of cards and appends it to the currently loaded
|
|
1654
|
+
* set — the infinite-scroll counterpart to table pagination. No-op unless
|
|
1655
|
+
* `isPaginated` is enabled for this cards list, a load is already in
|
|
1656
|
+
* flight, or every record has already been loaded.
|
|
1657
|
+
*
|
|
1658
|
+
* The offset comes from `item.skip`, which the state service advances by
|
|
1659
|
+
* the page size the backend actually applied. Deriving it from
|
|
1660
|
+
* `cards.length` instead would drift the moment a page came back short (a
|
|
1661
|
+
* capped `pageSize`, a de-duplicated overlap) and re-request a page we
|
|
1662
|
+
* already hold.
|
|
1663
|
+
*/
|
|
1664
|
+
loadMoreCards(itemKey) {
|
|
1665
|
+
const item = this.state.itemsByKey()[itemKey];
|
|
1666
|
+
if (!item ||
|
|
1667
|
+
item.type !== 'form' ||
|
|
1668
|
+
item.areaType !== 'cards' ||
|
|
1669
|
+
!item.config.isPaginated ||
|
|
1670
|
+
item.loading ||
|
|
1671
|
+
item.cards.length >= item.totalCount) {
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
this.loadCards(item.key, item.config, item.skip, item.take, true);
|
|
1675
|
+
}
|
|
1587
1676
|
reload(itemKey) {
|
|
1588
1677
|
if (itemKey) {
|
|
1589
1678
|
this.reloadByKey(itemKey);
|
|
@@ -1902,12 +1991,13 @@ class ClientList {
|
|
|
1902
1991
|
? DEFAULT_MODE
|
|
1903
1992
|
: this.resolveMode(formConfig?.mode, formConfig?.columnKeys);
|
|
1904
1993
|
const isPaginated = !isInformative &&
|
|
1905
|
-
areaType === 'table'
|
|
1906
|
-
|
|
1994
|
+
(areaType === 'table'
|
|
1995
|
+
? (formConfig?.isPaginated ?? true)
|
|
1996
|
+
: (formConfig?.isPaginated ?? false));
|
|
1907
1997
|
const configuredTablePageSize = this.resolvePositiveInteger(formConfig?.table?.pageSize);
|
|
1908
1998
|
const configuredTake = this.resolvePositiveInteger(formConfig?.take);
|
|
1909
1999
|
const showHeader = config.showHeader ?? true;
|
|
1910
|
-
const take = !isInformative && areaType === 'table'
|
|
2000
|
+
const take = !isInformative && (areaType === 'table' || isPaginated)
|
|
1911
2001
|
? isPaginated
|
|
1912
2002
|
? (configuredTake ??
|
|
1913
2003
|
configuredTablePageSize ??
|
|
@@ -1956,8 +2046,12 @@ class ClientList {
|
|
|
1956
2046
|
columns: [],
|
|
1957
2047
|
rows: [],
|
|
1958
2048
|
cards: [],
|
|
2049
|
+
// Cards restart from the first page on every reconfigure: the
|
|
2050
|
+
// loaded set above is wiped, and for infinite scroll `skip` is
|
|
2051
|
+
// the next offset into that set — carrying it forward would ask
|
|
2052
|
+
// the backend for page N with nothing before it on screen.
|
|
1959
2053
|
skip: 0,
|
|
1960
|
-
take
|
|
2054
|
+
take,
|
|
1961
2055
|
expanded: existing?.expanded ?? config.collapse?.expandedByDefault ?? true,
|
|
1962
2056
|
dashboardData: null,
|
|
1963
2057
|
rawData: existing?.rawData ?? null,
|
|
@@ -1996,7 +2090,7 @@ class ClientList {
|
|
|
1996
2090
|
return;
|
|
1997
2091
|
}
|
|
1998
2092
|
if (item.areaType === 'cards') {
|
|
1999
|
-
this.loadCards(item.key, item.config, force);
|
|
2093
|
+
this.loadCards(item.key, item.config, 0, item.take, false, force);
|
|
2000
2094
|
return;
|
|
2001
2095
|
}
|
|
2002
2096
|
this.loadTable(item.key, item.config, item.skip, item.take, force);
|
|
@@ -2055,14 +2149,14 @@ class ClientList {
|
|
|
2055
2149
|
});
|
|
2056
2150
|
this.subscriptions.set(key, sub);
|
|
2057
2151
|
}
|
|
2058
|
-
loadCards(key, config, force = false) {
|
|
2152
|
+
loadCards(key, config, skip = 0, take = 0, append = false, force = false) {
|
|
2059
2153
|
if (!this.hasValidIdentifiers(config)) {
|
|
2060
2154
|
this.state.setLoading(key, false);
|
|
2061
2155
|
this.state.setError(key, this.getMissingIdentifiersMessage(config));
|
|
2062
2156
|
return;
|
|
2063
2157
|
}
|
|
2064
2158
|
const filters = this.resolveFetchFilters(key, config);
|
|
2065
|
-
const requestSignature = this.createCardsRequestSignature(config, filters);
|
|
2159
|
+
const requestSignature = this.createCardsRequestSignature(config, filters, skip, take);
|
|
2066
2160
|
if (force) {
|
|
2067
2161
|
this.inFlightRequestSignatures.delete(key);
|
|
2068
2162
|
this.fulfilledRequestSignatures.delete(key);
|
|
@@ -2075,11 +2169,13 @@ class ClientList {
|
|
|
2075
2169
|
this.inFlightRequestSignatures.set(key, requestSignature);
|
|
2076
2170
|
this.state.setLoading(key, true);
|
|
2077
2171
|
this.state.setError(key, null);
|
|
2078
|
-
const sub = this.api
|
|
2172
|
+
const sub = this.api
|
|
2173
|
+
.getCards(config.contextKey, filters, config.isPaginated ? skip : undefined, config.isPaginated ? take : undefined)
|
|
2174
|
+
.subscribe({
|
|
2079
2175
|
next: (response) => {
|
|
2080
2176
|
let hasLoadedData = false;
|
|
2081
2177
|
if (response.data) {
|
|
2082
|
-
this.state.setCardsResult(key, response.data, config);
|
|
2178
|
+
this.state.setCardsResult(key, response.data, config, skip, take, append);
|
|
2083
2179
|
this.fulfilledRequestSignatures.set(key, requestSignature);
|
|
2084
2180
|
hasLoadedData = true;
|
|
2085
2181
|
}
|
|
@@ -2346,13 +2442,15 @@ class ClientList {
|
|
|
2346
2442
|
...(query.columnKeys ?? []),
|
|
2347
2443
|
].join('|');
|
|
2348
2444
|
}
|
|
2349
|
-
createCardsRequestSignature(config, filters) {
|
|
2445
|
+
createCardsRequestSignature(config, filters, skip, take) {
|
|
2350
2446
|
return [
|
|
2351
2447
|
config.type,
|
|
2352
2448
|
config.areaType,
|
|
2353
2449
|
config.contextKey ?? 'x',
|
|
2354
2450
|
this.serializeFilters(filters),
|
|
2355
2451
|
'Card',
|
|
2452
|
+
config.isPaginated ? skip : 0,
|
|
2453
|
+
config.isPaginated ? take : 0,
|
|
2356
2454
|
].join('|');
|
|
2357
2455
|
}
|
|
2358
2456
|
createInformativeRequestSignature(config) {
|
|
@@ -2400,7 +2498,7 @@ class ClientList {
|
|
|
2400
2498
|
ClientListStateService,
|
|
2401
2499
|
ClientListRuntimeActionsService,
|
|
2402
2500
|
ClientListToolbarService,
|
|
2403
|
-
], 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]=\"rowActionsFor(item)\"\r\n [actionShape]=\"item.config.actionShape\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\r\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\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]=\"rowActionsFor(item)\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\r\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\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", "rowActionsLoadingFn"], outputs: ["lazyLoad", "rowClick", "rowActionsRequested"] }, { kind: "component", type: ClientListCardsView, selector: "mt-client-list-cards-view", inputs: ["state", "rowActions", "rowActionsLoadingFn"], outputs: ["lazyLoad", "cardClick", "rowActionsRequested"] }, { kind: "component", type: ClientListInformativeView, selector: "mt-client-list-informative-view", inputs: ["state"] }] });
|
|
2501
|
+
], 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]=\"rowActionsFor(item)\"\r\n [actionShape]=\"item.config.actionShape\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\r\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\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]=\"rowActionsFor(item)\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\r\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n (loadMore)=\"loadMoreCards(item.key)\"\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", "rowActionsLoadingFn"], outputs: ["lazyLoad", "rowClick", "rowActionsRequested"] }, { kind: "component", type: ClientListCardsView, selector: "mt-client-list-cards-view", inputs: ["state", "rowActions", "rowActionsLoadingFn"], outputs: ["lazyLoad", "loadMore", "cardClick", "rowActionsRequested"] }, { kind: "component", type: ClientListInformativeView, selector: "mt-client-list-informative-view", inputs: ["state"] }] });
|
|
2404
2502
|
}
|
|
2405
2503
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientList, decorators: [{
|
|
2406
2504
|
type: Component,
|
|
@@ -2414,7 +2512,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
2414
2512
|
ClientListStateService,
|
|
2415
2513
|
ClientListRuntimeActionsService,
|
|
2416
2514
|
ClientListToolbarService,
|
|
2417
|
-
], 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]=\"rowActionsFor(item)\"\r\n [actionShape]=\"item.config.actionShape\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\r\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\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]=\"rowActionsFor(item)\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\r\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\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" }]
|
|
2515
|
+
], 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]=\"rowActionsFor(item)\"\r\n [actionShape]=\"item.config.actionShape\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\r\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\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]=\"rowActionsFor(item)\"\r\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\r\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n (loadMore)=\"loadMoreCards(item.key)\"\r\n (cardClick)=\"onCardClick(item, $event)\"\r\n />\r\n }\r\n }\r\n </section>\r\n }\r\n</div>\r\n" }]
|
|
2418
2516
|
}], 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"] }] } });
|
|
2419
2517
|
|
|
2420
2518
|
/**
|