@masterteam/client-components 0.0.66 → 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.
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Injectable, signal, computed, input, output, Component, effect, untracked } from '@angular/core';
|
|
2
|
+
import { inject, Injectable, signal, computed, input, output, Component, viewChild, effect, untracked } from '@angular/core';
|
|
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,7 +889,7 @@ 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,
|
|
@@ -835,11 +897,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
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
899
|
const EMPTY_KEY_SET = new Set();
|
|
900
|
+
/**
|
|
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.
|
|
904
|
+
*/
|
|
905
|
+
const LOAD_MORE_ROOT_MARGIN = '400px';
|
|
838
906
|
class ClientListCardsView {
|
|
839
907
|
toolbar = inject(ClientListToolbarService);
|
|
840
908
|
exportService = inject(TableExportService);
|
|
841
909
|
dateFormat = inject(MTDateFormatPipe);
|
|
842
910
|
confirmationService = inject(ConfirmationService);
|
|
911
|
+
transloco = inject(TranslocoService);
|
|
843
912
|
state = input.required(...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
844
913
|
rowActions = input([], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
|
|
845
914
|
/** Per-card loading predicate — drives the popover skeleton while runtime
|
|
@@ -847,13 +916,58 @@ class ClientListCardsView {
|
|
|
847
916
|
* the predicate operates on the card's row representation (`{ Id, ... }`). */
|
|
848
917
|
rowActionsLoadingFn = input(null, ...(ngDevMode ? [{ debugName: "rowActionsLoadingFn" }] : /* istanbul ignore next */ []));
|
|
849
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();
|
|
850
923
|
cardClick = output();
|
|
851
924
|
/** Emitted when a card's 3-dots menu opens, so the parent can lazily fetch
|
|
852
925
|
* that record's runtime actions (same contract as the table view). */
|
|
853
926
|
rowActionsRequested = output();
|
|
854
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;
|
|
855
936
|
/** Shared toolbar bucket — same instance the table view uses. */
|
|
856
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
|
+
}
|
|
857
971
|
/**
|
|
858
972
|
* Columns for filter/group/export. `state.columns` is empty for cards area
|
|
859
973
|
* (the state service doesn't populate it — see `setCardsResult`), so we
|
|
@@ -1248,15 +1362,73 @@ class ClientListCardsView {
|
|
|
1248
1362
|
return null;
|
|
1249
1363
|
}
|
|
1250
1364
|
constructor() {
|
|
1251
|
-
//
|
|
1252
|
-
//
|
|
1253
|
-
//
|
|
1254
|
-
//
|
|
1255
|
-
|
|
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.
|
|
1380
|
+
effect((onCleanup) => {
|
|
1381
|
+
const el = this.loadMoreSentinel()?.nativeElement;
|
|
1382
|
+
if (!el) {
|
|
1383
|
+
this.loadMoreObserver = null;
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
const observer = new IntersectionObserver((entries) => {
|
|
1387
|
+
if (!this.hasMoreCards())
|
|
1388
|
+
return;
|
|
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
|
+
});
|
|
1400
|
+
});
|
|
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.
|
|
1256
1411
|
effect(() => {
|
|
1257
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(() => {
|
|
1258
1429
|
const filters = this.bucket().filters();
|
|
1259
1430
|
const filterTerm = this.bucket().filterTerm();
|
|
1431
|
+
const state = untracked(() => this.cardsState());
|
|
1260
1432
|
if (!state.config.isPaginated)
|
|
1261
1433
|
return;
|
|
1262
1434
|
if (!primed) {
|
|
@@ -1270,13 +1442,13 @@ class ClientListCardsView {
|
|
|
1270
1442
|
...(filterTerm ? { generalSearch: filterTerm } : {}),
|
|
1271
1443
|
},
|
|
1272
1444
|
pageSize: state.take,
|
|
1273
|
-
first:
|
|
1274
|
-
currentPage:
|
|
1445
|
+
first: 0,
|
|
1446
|
+
currentPage: 1,
|
|
1275
1447
|
}));
|
|
1276
1448
|
});
|
|
1277
1449
|
}
|
|
1278
1450
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListCardsView, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1279
|
-
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], 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 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 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,
|
|
1280
1452
|
EntitiesPreview, i4.TranslocoPipe]] });
|
|
1281
1453
|
}
|
|
1282
1454
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListCardsView, decorators: [{
|
|
@@ -1291,8 +1463,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
1291
1463
|
SkeletonModule,
|
|
1292
1464
|
PopoverModule,
|
|
1293
1465
|
TableCaption,
|
|
1294
|
-
], 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 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 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" }]
|
|
1295
|
-
}], 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"] }] } });
|
|
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 }] }] } });
|
|
1296
1468
|
|
|
1297
1469
|
function toDashboardExtraFilters(filters = []) {
|
|
1298
1470
|
const extraFilters = {};
|
|
@@ -1466,17 +1638,41 @@ class ClientList {
|
|
|
1466
1638
|
const nextFilters = event.filters ?? this.runtimeFilters.get(item.key) ?? {};
|
|
1467
1639
|
const filtersChanged = runtimeFiltersChanged(this.runtimeFilters.get(item.key), nextFilters);
|
|
1468
1640
|
this.runtimeFilters.set(item.key, nextFilters);
|
|
1469
|
-
if (item.areaType === 'cards') {
|
|
1470
|
-
this.loadCards(item.key, item.config);
|
|
1471
|
-
return;
|
|
1472
|
-
}
|
|
1473
1641
|
const take = this.resolvePositiveInteger(event.pageSize) ?? item.take;
|
|
1474
1642
|
const skip = filtersChanged
|
|
1475
1643
|
? 0
|
|
1476
1644
|
: (this.resolveNonNegativeInteger(event.first) ?? 0);
|
|
1645
|
+
if (item.areaType === 'cards') {
|
|
1646
|
+
this.loadCards(item.key, item.config, skip, take, false);
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1477
1649
|
this.state.setTablePaging(item.key, skip, take);
|
|
1478
1650
|
this.loadTable(item.key, item.config, skip, take);
|
|
1479
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
|
+
}
|
|
1480
1676
|
reload(itemKey) {
|
|
1481
1677
|
if (itemKey) {
|
|
1482
1678
|
this.reloadByKey(itemKey);
|
|
@@ -1795,12 +1991,13 @@ class ClientList {
|
|
|
1795
1991
|
? DEFAULT_MODE
|
|
1796
1992
|
: this.resolveMode(formConfig?.mode, formConfig?.columnKeys);
|
|
1797
1993
|
const isPaginated = !isInformative &&
|
|
1798
|
-
areaType === 'table'
|
|
1799
|
-
|
|
1994
|
+
(areaType === 'table'
|
|
1995
|
+
? (formConfig?.isPaginated ?? true)
|
|
1996
|
+
: (formConfig?.isPaginated ?? false));
|
|
1800
1997
|
const configuredTablePageSize = this.resolvePositiveInteger(formConfig?.table?.pageSize);
|
|
1801
1998
|
const configuredTake = this.resolvePositiveInteger(formConfig?.take);
|
|
1802
1999
|
const showHeader = config.showHeader ?? true;
|
|
1803
|
-
const take = !isInformative && areaType === 'table'
|
|
2000
|
+
const take = !isInformative && (areaType === 'table' || isPaginated)
|
|
1804
2001
|
? isPaginated
|
|
1805
2002
|
? (configuredTake ??
|
|
1806
2003
|
configuredTablePageSize ??
|
|
@@ -1849,8 +2046,12 @@ class ClientList {
|
|
|
1849
2046
|
columns: [],
|
|
1850
2047
|
rows: [],
|
|
1851
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.
|
|
1852
2053
|
skip: 0,
|
|
1853
|
-
take
|
|
2054
|
+
take,
|
|
1854
2055
|
expanded: existing?.expanded ?? config.collapse?.expandedByDefault ?? true,
|
|
1855
2056
|
dashboardData: null,
|
|
1856
2057
|
rawData: existing?.rawData ?? null,
|
|
@@ -1889,7 +2090,7 @@ class ClientList {
|
|
|
1889
2090
|
return;
|
|
1890
2091
|
}
|
|
1891
2092
|
if (item.areaType === 'cards') {
|
|
1892
|
-
this.loadCards(item.key, item.config, force);
|
|
2093
|
+
this.loadCards(item.key, item.config, 0, item.take, false, force);
|
|
1893
2094
|
return;
|
|
1894
2095
|
}
|
|
1895
2096
|
this.loadTable(item.key, item.config, item.skip, item.take, force);
|
|
@@ -1948,14 +2149,14 @@ class ClientList {
|
|
|
1948
2149
|
});
|
|
1949
2150
|
this.subscriptions.set(key, sub);
|
|
1950
2151
|
}
|
|
1951
|
-
loadCards(key, config, force = false) {
|
|
2152
|
+
loadCards(key, config, skip = 0, take = 0, append = false, force = false) {
|
|
1952
2153
|
if (!this.hasValidIdentifiers(config)) {
|
|
1953
2154
|
this.state.setLoading(key, false);
|
|
1954
2155
|
this.state.setError(key, this.getMissingIdentifiersMessage(config));
|
|
1955
2156
|
return;
|
|
1956
2157
|
}
|
|
1957
2158
|
const filters = this.resolveFetchFilters(key, config);
|
|
1958
|
-
const requestSignature = this.createCardsRequestSignature(config, filters);
|
|
2159
|
+
const requestSignature = this.createCardsRequestSignature(config, filters, skip, take);
|
|
1959
2160
|
if (force) {
|
|
1960
2161
|
this.inFlightRequestSignatures.delete(key);
|
|
1961
2162
|
this.fulfilledRequestSignatures.delete(key);
|
|
@@ -1968,11 +2169,13 @@ class ClientList {
|
|
|
1968
2169
|
this.inFlightRequestSignatures.set(key, requestSignature);
|
|
1969
2170
|
this.state.setLoading(key, true);
|
|
1970
2171
|
this.state.setError(key, null);
|
|
1971
|
-
const sub = this.api
|
|
2172
|
+
const sub = this.api
|
|
2173
|
+
.getCards(config.contextKey, filters, config.isPaginated ? skip : undefined, config.isPaginated ? take : undefined)
|
|
2174
|
+
.subscribe({
|
|
1972
2175
|
next: (response) => {
|
|
1973
2176
|
let hasLoadedData = false;
|
|
1974
2177
|
if (response.data) {
|
|
1975
|
-
this.state.setCardsResult(key, response.data, config);
|
|
2178
|
+
this.state.setCardsResult(key, response.data, config, skip, take, append);
|
|
1976
2179
|
this.fulfilledRequestSignatures.set(key, requestSignature);
|
|
1977
2180
|
hasLoadedData = true;
|
|
1978
2181
|
}
|
|
@@ -2239,13 +2442,15 @@ class ClientList {
|
|
|
2239
2442
|
...(query.columnKeys ?? []),
|
|
2240
2443
|
].join('|');
|
|
2241
2444
|
}
|
|
2242
|
-
createCardsRequestSignature(config, filters) {
|
|
2445
|
+
createCardsRequestSignature(config, filters, skip, take) {
|
|
2243
2446
|
return [
|
|
2244
2447
|
config.type,
|
|
2245
2448
|
config.areaType,
|
|
2246
2449
|
config.contextKey ?? 'x',
|
|
2247
2450
|
this.serializeFilters(filters),
|
|
2248
2451
|
'Card',
|
|
2452
|
+
config.isPaginated ? skip : 0,
|
|
2453
|
+
config.isPaginated ? take : 0,
|
|
2249
2454
|
].join('|');
|
|
2250
2455
|
}
|
|
2251
2456
|
createInformativeRequestSignature(config) {
|
|
@@ -2293,7 +2498,7 @@ class ClientList {
|
|
|
2293
2498
|
ClientListStateService,
|
|
2294
2499
|
ClientListRuntimeActionsService,
|
|
2295
2500
|
ClientListToolbarService,
|
|
2296
|
-
], 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"] }] });
|
|
2297
2502
|
}
|
|
2298
2503
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientList, decorators: [{
|
|
2299
2504
|
type: Component,
|
|
@@ -2307,7 +2512,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
2307
2512
|
ClientListStateService,
|
|
2308
2513
|
ClientListRuntimeActionsService,
|
|
2309
2514
|
ClientListToolbarService,
|
|
2310
|
-
], 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" }]
|
|
2311
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"] }] } });
|
|
2312
2517
|
|
|
2313
2518
|
/**
|