@masterteam/client-components 0.0.67 → 0.0.69
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
|
|
@@ -1321,49 +1352,87 @@ class ClientListCardsView {
|
|
|
1321
1352
|
return 'dateTime';
|
|
1322
1353
|
if (fromColumn === 'date')
|
|
1323
1354
|
return 'date';
|
|
1355
|
+
if (fromColumn === 'time')
|
|
1356
|
+
return 'time';
|
|
1324
1357
|
if (cellValue && typeof cellValue === 'object') {
|
|
1325
1358
|
const inner = cellValue.viewType;
|
|
1326
1359
|
if (inner === 'DateTime')
|
|
1327
1360
|
return 'dateTime';
|
|
1328
1361
|
if (inner === 'Date')
|
|
1329
1362
|
return 'date';
|
|
1363
|
+
if (inner === 'Time')
|
|
1364
|
+
return 'time';
|
|
1330
1365
|
}
|
|
1331
1366
|
return null;
|
|
1332
1367
|
}
|
|
1333
1368
|
constructor() {
|
|
1334
|
-
//
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
//
|
|
1340
|
-
//
|
|
1341
|
-
//
|
|
1369
|
+
// Attach the observer to whatever element the sentinel query currently
|
|
1370
|
+
// resolves to, and re-attach if Angular ever swaps that element. Keying
|
|
1371
|
+
// on the element instance is what makes this safe: appending a page
|
|
1372
|
+
// doesn't recreate the node, so the observer is *not* rebuilt per page —
|
|
1373
|
+
// which matters because a fresh IntersectionObserver always fires once
|
|
1374
|
+
// with the current intersection state, and rebuilding on every append
|
|
1375
|
+
// would cascade through the remaining pages on its own.
|
|
1376
|
+
//
|
|
1377
|
+
// Attaching only once, however, is not safe: the sentinel lives at the
|
|
1378
|
+
// end of the grid, and the grid is one branch of the view (the initial
|
|
1379
|
+
// skeleton, an error, an empty result and the grouped layout are the
|
|
1380
|
+
// others). A one-shot attach survives the initial skeleton but then
|
|
1381
|
+
// silently observes a detached node the first time any sibling branch
|
|
1382
|
+
// takes over, killing infinite scroll for the rest of the component's
|
|
1383
|
+
// life.
|
|
1342
1384
|
effect((onCleanup) => {
|
|
1343
|
-
const
|
|
1344
|
-
if (!
|
|
1385
|
+
const el = this.loadMoreSentinel()?.nativeElement;
|
|
1386
|
+
if (!el) {
|
|
1387
|
+
this.loadMoreObserver = null;
|
|
1345
1388
|
return;
|
|
1389
|
+
}
|
|
1346
1390
|
const observer = new IntersectionObserver((entries) => {
|
|
1347
|
-
if (!
|
|
1391
|
+
if (!this.hasMoreCards())
|
|
1348
1392
|
return;
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1393
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
1394
|
+
this.loadMore.emit();
|
|
1395
|
+
}
|
|
1396
|
+
}, { rootMargin: LOAD_MORE_ROOT_MARGIN });
|
|
1397
|
+
observer.observe(el);
|
|
1398
|
+
this.loadMoreObserver = observer;
|
|
1399
|
+
onCleanup(() => {
|
|
1400
|
+
observer.disconnect();
|
|
1401
|
+
if (this.loadMoreObserver === observer)
|
|
1402
|
+
this.loadMoreObserver = null;
|
|
1403
|
+
});
|
|
1357
1404
|
});
|
|
1358
|
-
//
|
|
1359
|
-
//
|
|
1360
|
-
//
|
|
1361
|
-
//
|
|
1362
|
-
|
|
1405
|
+
// Re-arm after each page settles. IntersectionObserver only reports
|
|
1406
|
+
// threshold *crossings*: if an appended page is too short to push the
|
|
1407
|
+
// sentinel back out of the root margin, the entry stays `isIntersecting`
|
|
1408
|
+
// and no further callback ever arrives — the list stalls part-loaded
|
|
1409
|
+
// until the user happens to scroll. Re-observing the same element
|
|
1410
|
+
// delivers a fresh initial callback, so the loop continues until the
|
|
1411
|
+
// sentinel really is out of range (callback reports no intersection, no
|
|
1412
|
+
// emit, no state change, effect idle) or the data runs out
|
|
1413
|
+
// (`hasMoreCards()` false). `observe()` on an already-observed target is
|
|
1414
|
+
// a no-op, hence the explicit `unobserve` first.
|
|
1363
1415
|
effect(() => {
|
|
1364
1416
|
const state = this.cardsState();
|
|
1417
|
+
const el = this.loadMoreSentinel()?.nativeElement;
|
|
1418
|
+
const observer = this.loadMoreObserver;
|
|
1419
|
+
if (!observer || !el || state.loading || !this.hasMoreCards())
|
|
1420
|
+
return;
|
|
1421
|
+
observer.unobserve(el);
|
|
1422
|
+
observer.observe(el);
|
|
1423
|
+
});
|
|
1424
|
+
// Emit lazyLoad only on genuine filter / search changes for paginated
|
|
1425
|
+
// lists, matching mt-table's behaviour (group-by is a pure FE concern,
|
|
1426
|
+
// not a re-fetch trigger). Deliberately reads `cardsState()` via
|
|
1427
|
+
// `untracked` — the item state gets a new object reference on every
|
|
1428
|
+
// page append (infinite scroll), and reacting to that here would
|
|
1429
|
+
// re-emit `lazyLoad` after every append, which re-fetches with
|
|
1430
|
+
// `append: false` and wipes out everything already loaded.
|
|
1431
|
+
let primed = false;
|
|
1432
|
+
effect(() => {
|
|
1365
1433
|
const filters = this.bucket().filters();
|
|
1366
1434
|
const filterTerm = this.bucket().filterTerm();
|
|
1435
|
+
const state = untracked(() => this.cardsState());
|
|
1367
1436
|
if (!state.config.isPaginated)
|
|
1368
1437
|
return;
|
|
1369
1438
|
if (!primed) {
|
|
@@ -1377,13 +1446,13 @@ class ClientListCardsView {
|
|
|
1377
1446
|
...(filterTerm ? { generalSearch: filterTerm } : {}),
|
|
1378
1447
|
},
|
|
1379
1448
|
pageSize: state.take,
|
|
1380
|
-
first:
|
|
1381
|
-
currentPage:
|
|
1449
|
+
first: 0,
|
|
1450
|
+
currentPage: 1,
|
|
1382
1451
|
}));
|
|
1383
1452
|
});
|
|
1384
1453
|
}
|
|
1385
1454
|
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,
|
|
1455
|
+
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
1456
|
EntitiesPreview, i4.TranslocoPipe]] });
|
|
1388
1457
|
}
|
|
1389
1458
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListCardsView, decorators: [{
|
|
@@ -1398,8 +1467,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
1398
1467
|
SkeletonModule,
|
|
1399
1468
|
PopoverModule,
|
|
1400
1469
|
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 }] }] } });
|
|
1470
|
+
], 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" }]
|
|
1471
|
+
}], 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
1472
|
|
|
1404
1473
|
function toDashboardExtraFilters(filters = []) {
|
|
1405
1474
|
const extraFilters = {};
|
|
@@ -1573,17 +1642,41 @@ class ClientList {
|
|
|
1573
1642
|
const nextFilters = event.filters ?? this.runtimeFilters.get(item.key) ?? {};
|
|
1574
1643
|
const filtersChanged = runtimeFiltersChanged(this.runtimeFilters.get(item.key), nextFilters);
|
|
1575
1644
|
this.runtimeFilters.set(item.key, nextFilters);
|
|
1576
|
-
if (item.areaType === 'cards') {
|
|
1577
|
-
this.loadCards(item.key, item.config);
|
|
1578
|
-
return;
|
|
1579
|
-
}
|
|
1580
1645
|
const take = this.resolvePositiveInteger(event.pageSize) ?? item.take;
|
|
1581
1646
|
const skip = filtersChanged
|
|
1582
1647
|
? 0
|
|
1583
1648
|
: (this.resolveNonNegativeInteger(event.first) ?? 0);
|
|
1649
|
+
if (item.areaType === 'cards') {
|
|
1650
|
+
this.loadCards(item.key, item.config, skip, take, false);
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1584
1653
|
this.state.setTablePaging(item.key, skip, take);
|
|
1585
1654
|
this.loadTable(item.key, item.config, skip, take);
|
|
1586
1655
|
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Fetches the next page of cards and appends it to the currently loaded
|
|
1658
|
+
* set — the infinite-scroll counterpart to table pagination. No-op unless
|
|
1659
|
+
* `isPaginated` is enabled for this cards list, a load is already in
|
|
1660
|
+
* flight, or every record has already been loaded.
|
|
1661
|
+
*
|
|
1662
|
+
* The offset comes from `item.skip`, which the state service advances by
|
|
1663
|
+
* the page size the backend actually applied. Deriving it from
|
|
1664
|
+
* `cards.length` instead would drift the moment a page came back short (a
|
|
1665
|
+
* capped `pageSize`, a de-duplicated overlap) and re-request a page we
|
|
1666
|
+
* already hold.
|
|
1667
|
+
*/
|
|
1668
|
+
loadMoreCards(itemKey) {
|
|
1669
|
+
const item = this.state.itemsByKey()[itemKey];
|
|
1670
|
+
if (!item ||
|
|
1671
|
+
item.type !== 'form' ||
|
|
1672
|
+
item.areaType !== 'cards' ||
|
|
1673
|
+
!item.config.isPaginated ||
|
|
1674
|
+
item.loading ||
|
|
1675
|
+
item.cards.length >= item.totalCount) {
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
this.loadCards(item.key, item.config, item.skip, item.take, true);
|
|
1679
|
+
}
|
|
1587
1680
|
reload(itemKey) {
|
|
1588
1681
|
if (itemKey) {
|
|
1589
1682
|
this.reloadByKey(itemKey);
|
|
@@ -1902,12 +1995,13 @@ class ClientList {
|
|
|
1902
1995
|
? DEFAULT_MODE
|
|
1903
1996
|
: this.resolveMode(formConfig?.mode, formConfig?.columnKeys);
|
|
1904
1997
|
const isPaginated = !isInformative &&
|
|
1905
|
-
areaType === 'table'
|
|
1906
|
-
|
|
1998
|
+
(areaType === 'table'
|
|
1999
|
+
? (formConfig?.isPaginated ?? true)
|
|
2000
|
+
: (formConfig?.isPaginated ?? false));
|
|
1907
2001
|
const configuredTablePageSize = this.resolvePositiveInteger(formConfig?.table?.pageSize);
|
|
1908
2002
|
const configuredTake = this.resolvePositiveInteger(formConfig?.take);
|
|
1909
2003
|
const showHeader = config.showHeader ?? true;
|
|
1910
|
-
const take = !isInformative && areaType === 'table'
|
|
2004
|
+
const take = !isInformative && (areaType === 'table' || isPaginated)
|
|
1911
2005
|
? isPaginated
|
|
1912
2006
|
? (configuredTake ??
|
|
1913
2007
|
configuredTablePageSize ??
|
|
@@ -1956,8 +2050,12 @@ class ClientList {
|
|
|
1956
2050
|
columns: [],
|
|
1957
2051
|
rows: [],
|
|
1958
2052
|
cards: [],
|
|
2053
|
+
// Cards restart from the first page on every reconfigure: the
|
|
2054
|
+
// loaded set above is wiped, and for infinite scroll `skip` is
|
|
2055
|
+
// the next offset into that set — carrying it forward would ask
|
|
2056
|
+
// the backend for page N with nothing before it on screen.
|
|
1959
2057
|
skip: 0,
|
|
1960
|
-
take
|
|
2058
|
+
take,
|
|
1961
2059
|
expanded: existing?.expanded ?? config.collapse?.expandedByDefault ?? true,
|
|
1962
2060
|
dashboardData: null,
|
|
1963
2061
|
rawData: existing?.rawData ?? null,
|
|
@@ -1996,7 +2094,7 @@ class ClientList {
|
|
|
1996
2094
|
return;
|
|
1997
2095
|
}
|
|
1998
2096
|
if (item.areaType === 'cards') {
|
|
1999
|
-
this.loadCards(item.key, item.config, force);
|
|
2097
|
+
this.loadCards(item.key, item.config, 0, item.take, false, force);
|
|
2000
2098
|
return;
|
|
2001
2099
|
}
|
|
2002
2100
|
this.loadTable(item.key, item.config, item.skip, item.take, force);
|
|
@@ -2055,14 +2153,14 @@ class ClientList {
|
|
|
2055
2153
|
});
|
|
2056
2154
|
this.subscriptions.set(key, sub);
|
|
2057
2155
|
}
|
|
2058
|
-
loadCards(key, config, force = false) {
|
|
2156
|
+
loadCards(key, config, skip = 0, take = 0, append = false, force = false) {
|
|
2059
2157
|
if (!this.hasValidIdentifiers(config)) {
|
|
2060
2158
|
this.state.setLoading(key, false);
|
|
2061
2159
|
this.state.setError(key, this.getMissingIdentifiersMessage(config));
|
|
2062
2160
|
return;
|
|
2063
2161
|
}
|
|
2064
2162
|
const filters = this.resolveFetchFilters(key, config);
|
|
2065
|
-
const requestSignature = this.createCardsRequestSignature(config, filters);
|
|
2163
|
+
const requestSignature = this.createCardsRequestSignature(config, filters, skip, take);
|
|
2066
2164
|
if (force) {
|
|
2067
2165
|
this.inFlightRequestSignatures.delete(key);
|
|
2068
2166
|
this.fulfilledRequestSignatures.delete(key);
|
|
@@ -2075,11 +2173,13 @@ class ClientList {
|
|
|
2075
2173
|
this.inFlightRequestSignatures.set(key, requestSignature);
|
|
2076
2174
|
this.state.setLoading(key, true);
|
|
2077
2175
|
this.state.setError(key, null);
|
|
2078
|
-
const sub = this.api
|
|
2176
|
+
const sub = this.api
|
|
2177
|
+
.getCards(config.contextKey, filters, config.isPaginated ? skip : undefined, config.isPaginated ? take : undefined)
|
|
2178
|
+
.subscribe({
|
|
2079
2179
|
next: (response) => {
|
|
2080
2180
|
let hasLoadedData = false;
|
|
2081
2181
|
if (response.data) {
|
|
2082
|
-
this.state.setCardsResult(key, response.data, config);
|
|
2182
|
+
this.state.setCardsResult(key, response.data, config, skip, take, append);
|
|
2083
2183
|
this.fulfilledRequestSignatures.set(key, requestSignature);
|
|
2084
2184
|
hasLoadedData = true;
|
|
2085
2185
|
}
|
|
@@ -2346,13 +2446,15 @@ class ClientList {
|
|
|
2346
2446
|
...(query.columnKeys ?? []),
|
|
2347
2447
|
].join('|');
|
|
2348
2448
|
}
|
|
2349
|
-
createCardsRequestSignature(config, filters) {
|
|
2449
|
+
createCardsRequestSignature(config, filters, skip, take) {
|
|
2350
2450
|
return [
|
|
2351
2451
|
config.type,
|
|
2352
2452
|
config.areaType,
|
|
2353
2453
|
config.contextKey ?? 'x',
|
|
2354
2454
|
this.serializeFilters(filters),
|
|
2355
2455
|
'Card',
|
|
2456
|
+
config.isPaginated ? skip : 0,
|
|
2457
|
+
config.isPaginated ? take : 0,
|
|
2356
2458
|
].join('|');
|
|
2357
2459
|
}
|
|
2358
2460
|
createInformativeRequestSignature(config) {
|
|
@@ -2400,7 +2502,7 @@ class ClientList {
|
|
|
2400
2502
|
ClientListStateService,
|
|
2401
2503
|
ClientListRuntimeActionsService,
|
|
2402
2504
|
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"] }] });
|
|
2505
|
+
], 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
2506
|
}
|
|
2405
2507
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientList, decorators: [{
|
|
2406
2508
|
type: Component,
|
|
@@ -2414,7 +2516,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
2414
2516
|
ClientListStateService,
|
|
2415
2517
|
ClientListRuntimeActionsService,
|
|
2416
2518
|
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" }]
|
|
2519
|
+
], 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
2520
|
}], 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
2521
|
|
|
2420
2522
|
/**
|