@masterteam/client-components 0.0.95 → 0.0.97

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.
@@ -7,8 +7,8 @@ import { TranslocoService, TranslocoModule, TranslocoPipe } from '@jsverse/trans
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';
10
- import { switchMap, map, of, Observable, share, take } from 'rxjs';
11
- import { resolveLocalizedLabel, readEntityValueColor, serializeEntityRawValue, isSectionKey, EntitiesPreview } from '@masterteam/components/entities';
10
+ import { switchMap, map, forkJoin, catchError, of, shareReplay, Observable, share, take } from 'rxjs';
11
+ import { isSectionKey, resolveLocalizedLabel, readEntityValueColor, serializeEntityRawValue, EntitiesPreview } from '@masterteam/components/entities';
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';
@@ -45,6 +45,8 @@ class ClientListApiService {
45
45
  http = inject(HttpClient);
46
46
  runtimeFetchBaseUrl = 'fetch/query';
47
47
  informativeBaseUrl = 'LevelData';
48
+ /** Card layouts already read back, keyed by context. See {@link getCardLayout}. */
49
+ cardLayouts = new Map();
48
50
  /**
49
51
  * Fetches a table page. Resolves to the response **and** the `propertyKeys`
50
52
  * the request carried, because a write that wants its record projected the
@@ -97,21 +99,132 @@ class ClientListApiService {
97
99
  : {}),
98
100
  });
99
101
  }
100
- getCards(contextKey, filters = [], skip, take) {
102
+ /**
103
+ * Fetches a board of cards.
104
+ *
105
+ * Without a `hierarchy` scope this is a plain `Card` projection. With one it
106
+ * cannot be: the backend accepts descendant scope on the **`Table`
107
+ * projection only** and answers any other projection with a 400 that takes
108
+ * the whole list down. So a descendant board is fetched as a hierarchy table
109
+ * and re-dressed as cards — the rows carry the same records, their owning
110
+ * record and the pre-pagination totals, and {@link getCardLayout} supplies
111
+ * the card layout the board would otherwise lose.
112
+ */
113
+ getCards(contextKey, filters = [], skip, take, hierarchy) {
101
114
  // `page` and `pageSize` must be sent together or not at all (fetch/query
102
115
  // request constraints), so a missing or non-positive `take` means the
103
116
  // caller wants the whole set in one call.
104
117
  const pageSize = typeof take === 'number' && take > 0 ? take : null;
105
- return this.queryRuntime({
118
+ const paging = pageSize === null
119
+ ? {}
120
+ : { page: this.toPage(skip ?? 0, pageSize), pageSize };
121
+ if (!hierarchy) {
122
+ return this.queryRuntime({
123
+ contextKey,
124
+ projection: 'Card',
125
+ includeState: [...DEFAULT_INCLUDE_STATE],
126
+ filters,
127
+ display: { areas: ['card'] },
128
+ ...paging,
129
+ });
130
+ }
131
+ return forkJoin({
132
+ layout: this.getCardLayout(contextKey),
133
+ // No `propertyKeys`: a descendant request rejects any key it cannot
134
+ // resolve in one of the child catalogs, and a card-only property (one
135
+ // the module's table does not show) is exactly such a key — asking for
136
+ // it would 400 the board instead of costing it one field. So the
137
+ // hierarchy request takes the module's table selection and the card
138
+ // layout picks from what came back.
139
+ rows: this.queryRuntime({
140
+ contextKey,
141
+ projection: 'Table',
142
+ surfaceKey: 'table',
143
+ includeState: [...DEFAULT_INCLUDE_STATE],
144
+ filters,
145
+ ...paging,
146
+ hierarchy: this.toHierarchyScope(hierarchy),
147
+ }),
148
+ }).pipe(map(({ layout, rows }) => this.withCardLayout(rows, layout)));
149
+ }
150
+ /**
151
+ * The card layout authored for a context, with every property key rewritten
152
+ * to its normalized form.
153
+ *
154
+ * A `Card` response names its properties by the owning context's own key
155
+ * (`leveldatafinancial_title`), while a hierarchy response normalizes every
156
+ * row, column and catalog entry across the child levels down to the shared
157
+ * key (`title`). Sent through unchanged the layout would resolve against
158
+ * nothing and every card would render empty.
159
+ *
160
+ * Cached per context for the life of the app: it is a second request on a
161
+ * path that already makes one, it is pure configuration, and it is re-read
162
+ * on the next full load anyway. A card layout edited in the control panel
163
+ * therefore needs a reload of the app to show up on a descendant board —
164
+ * the same page-load boundary the rest of the display configuration has.
165
+ */
166
+ getCardLayout(contextKey) {
167
+ const cached = this.cardLayouts.get(contextKey);
168
+ if (cached)
169
+ return cached;
170
+ const layout = this.queryRuntime({
106
171
  contextKey,
107
172
  projection: 'Card',
108
173
  includeState: [...DEFAULT_INCLUDE_STATE],
109
- filters,
174
+ filters: [],
110
175
  display: { areas: ['card'] },
111
- ...(pageSize === null
112
- ? {}
113
- : { page: this.toPage(skip ?? 0, pageSize), pageSize }),
114
- });
176
+ // The layout lives in `projectionMeta`, so one record is enough to carry
177
+ // it back — the board's own records come from the hierarchy request.
178
+ page: 1,
179
+ pageSize: 1,
180
+ }).pipe(map((response) => this.toNormalizedDisplayOrder(response.data)),
181
+ // A board that renders its rows with the table's columns still shows the
182
+ // right records; one that fails outright shows nothing. The layout is
183
+ // the optional half of the pair.
184
+ catchError(() => of([])), shareReplay({ bufferSize: 1, refCount: false }));
185
+ this.cardLayouts.set(contextKey, layout);
186
+ return layout;
187
+ }
188
+ toNormalizedDisplayOrder(data) {
189
+ const normalizedByKey = new Map((data?.catalog?.properties ?? []).map((property) => [
190
+ property.key,
191
+ property.normalizedKey || property.key,
192
+ ]));
193
+ return (data?.projectionMeta?.displayOrder ?? []).map((item) => ({
194
+ ...item,
195
+ propertyKey: normalizedByKey.get(item.propertyKey) ?? item.propertyKey,
196
+ }));
197
+ }
198
+ /**
199
+ * Dresses a hierarchy table response as the card payload the board reads:
200
+ * the same records, plus the authored card layout narrowed to the properties
201
+ * this response actually carries. A layout entry with no property behind it
202
+ * would render as a nameless blank slot, so it is dropped — section markers
203
+ * excepted, since they are layout rather than data and have no property to
204
+ * begin with.
205
+ *
206
+ * Leaves the response untouched when there is no usable layout: the card
207
+ * mapper then falls back to the response's own table columns, which is a
208
+ * plainer board but a complete one.
209
+ */
210
+ withCardLayout(response, displayOrder) {
211
+ const data = response.data;
212
+ if (!data || displayOrder.length === 0)
213
+ return response;
214
+ const available = new Set((data.catalog?.properties ?? []).flatMap((property) => [property.key, property.normalizedKey].filter((key) => !!key)));
215
+ const usable = displayOrder.filter((item) => isSectionKey(item.propertyKey) || available.has(item.propertyKey));
216
+ if (usable.every((item) => isSectionKey(item.propertyKey)))
217
+ return response;
218
+ return {
219
+ ...response,
220
+ data: {
221
+ ...data,
222
+ projectionMeta: {
223
+ ...(data.projectionMeta ?? { kind: 'Card' }),
224
+ displayOrder: usable,
225
+ },
226
+ },
227
+ };
115
228
  }
116
229
  getInformativeDashboard(levelId, moduleId) {
117
230
  return this.http.get(`${this.informativeBaseUrl}/levels/${levelId}/modules/${moduleId}/dashboard`);
@@ -292,6 +405,67 @@ function toDashboardExtraFilters(filters = []) {
292
405
  return extraFilters;
293
406
  }
294
407
 
408
+ /**
409
+ * A card in the row shape `mt-table` and the table toolbar work with:
410
+ * `{ Id, [entityKey]: EntityData, ... }`. Grouping, filtering, search, export
411
+ * and row actions all run over this, never over the card itself, so a board
412
+ * and a table of the same list read the same fields.
413
+ *
414
+ * `displayProperties` are spread in first: they hold row-only cells a host
415
+ * derived for the list (see `ClientListStateService.setCardsResult`) — values
416
+ * a board can group or filter on without ever rendering them on the card.
417
+ */
418
+ function toClientListCardRow(card) {
419
+ const row = {
420
+ Id: card.id,
421
+ ...(card.displayProperties ?? {}),
422
+ [CLIENT_LIST_RECORD_STATE_KEY]: card.state ?? null,
423
+ // Only present on a descendant board. Row actions read the owning record
424
+ // off the row, so a card's actions address its own owner rather than the
425
+ // aggregation context — the same contract a hierarchy table row carries.
426
+ [CLIENT_LIST_RECORD_SOURCE_KEY]: card.source ?? null,
427
+ };
428
+ for (const entity of card.entities ?? []) {
429
+ if (entity.viewType === 'Section')
430
+ continue;
431
+ const e = entity;
432
+ const key = (typeof e['key'] === 'string' && e['key']) ||
433
+ (typeof e['normalizedKey'] === 'string' && e['normalizedKey']) ||
434
+ entity.name;
435
+ const normalizedKey = typeof e['normalizedKey'] === 'string' ? e['normalizedKey'] : null;
436
+ const keys = new Set([key, normalizedKey].filter((entry) => typeof entry === 'string' && entry.length > 0));
437
+ for (const entityKey of keys) {
438
+ row[entityKey] = entity;
439
+ }
440
+ }
441
+ return row;
442
+ }
443
+ /**
444
+ * The columns a board's toolbar filters, groups and exports by, read off one
445
+ * card's entities — each carries its `key`, `name` (label) and `viewType`,
446
+ * which is everything `TableValueResolver.getColumnFilterType` needs.
447
+ */
448
+ function toClientListCardColumns(card) {
449
+ // Section markers are card-layout only — never table columns.
450
+ return (card?.entities ?? [])
451
+ .filter((entity) => entity.viewType !== 'Section')
452
+ .map((entity) => {
453
+ const raw = entity;
454
+ const key = (typeof raw['key'] === 'string' && raw['key']) ||
455
+ (typeof raw['normalizedKey'] === 'string' && raw['normalizedKey']) ||
456
+ entity.name;
457
+ return {
458
+ key: String(key),
459
+ normalizedKey: typeof raw['normalizedKey'] === 'string'
460
+ ? raw['normalizedKey']
461
+ : String(key),
462
+ label: entity.name ?? String(key),
463
+ type: 'entity',
464
+ viewType: (entity.viewType ?? 'Text'),
465
+ };
466
+ });
467
+ }
468
+
295
469
  const UNSORTABLE_ENTITY_VIEW_TYPES = new Set([
296
470
  'Attachment',
297
471
  'Checkbox',
@@ -451,6 +625,7 @@ class ClientListStateService {
451
625
  const cards = isAppend
452
626
  ? this.concatDistinctCards(target.cards, newCards)
453
627
  : newCards;
628
+ const shaped = this.shapeCards(cards, config, response);
454
629
  const schema = this.resolvePrimarySchema(response);
455
630
  const firstCardModule = newCards[0]?.module;
456
631
  const moduleKey = schema?.key ?? firstCardModule?.key ?? target.moduleKey;
@@ -481,9 +656,13 @@ class ClientListStateService {
481
656
  ...target,
482
657
  title,
483
658
  moduleKey,
484
- columns: [],
659
+ columns: shaped.columns,
485
660
  rows: [],
486
- cards,
661
+ cards: shaped.cards,
662
+ // Pre-pagination aggregates, kept verbatim off the response, exactly
663
+ // as a hierarchy table keeps them. Absent on an ordinary board.
664
+ totals: response.projectionMeta?.totals,
665
+ sourceGroups: response.projectionMeta?.sourceGroups,
487
666
  // `skip` is the offset of the *next* page to fetch, always a multiple
488
667
  // of the effective page size so `toPage()` stays exact.
489
668
  skip: config.isPaginated
@@ -611,9 +790,11 @@ class ClientListStateService {
611
790
  records[recordIndex] = merged;
612
791
  else
613
792
  records.unshift(merged);
793
+ const shaped = this.shapeCards(cards, target.config, raw);
614
794
  this.patchItem(key, {
615
795
  ...target,
616
- cards,
796
+ cards: shaped.cards,
797
+ columns: shaped.columns,
617
798
  totalCount: isInsert ? target.totalCount + 1 : target.totalCount,
618
799
  error: null,
619
800
  rawData: this.withRecords(raw, records, isInsert ? 1 : 0),
@@ -742,6 +923,46 @@ class ClientListStateService {
742
923
  rawData: existing.rawData,
743
924
  };
744
925
  }
926
+ /**
927
+ * Runs the host's `transformResult` over a board, so it offers the same
928
+ * columns the table of the same list does — the ones a host derives
929
+ * included (a descendant list's level and owning record, say). That is what
930
+ * lets a board group, filter and export by them, and what lets a
931
+ * `table.groupBy` default naming a derived column group a board at all.
932
+ *
933
+ * The card face is left alone: the card keeps rendering its own layout, and
934
+ * the derived cells ride on `displayProperties`, which only the card's row
935
+ * representation reads. Cells the transform rewrote rather than added are
936
+ * not carried over — the card already shows those fields its own way.
937
+ *
938
+ * Without a transform the board keeps its old behaviour: no `columns`, so
939
+ * the view derives them from the first card's entities.
940
+ */
941
+ shapeCards(cards, config, response) {
942
+ if (!config.transformResult || cards.length === 0) {
943
+ return { cards, columns: [] };
944
+ }
945
+ // Built from the card's own fields only, never from cells an earlier
946
+ // shaping added, so reshaping an accumulated (appended) board is
947
+ // idempotent.
948
+ const baseRows = cards.map((card) => toClientListCardRow({ ...card, displayProperties: undefined }));
949
+ const shaped = config.transformResult({ columns: toClientListCardColumns(cards[0]), rows: baseRows }, response);
950
+ const shapedById = new Map(shaped.rows.map((row) => [row['Id'], row]));
951
+ return {
952
+ columns: shaped.columns,
953
+ cards: cards.map((card, index) => {
954
+ const row = shapedById.get(card.id);
955
+ if (!row)
956
+ return card;
957
+ const base = baseRows[index];
958
+ const derived = Object.fromEntries(Object.entries(row).filter(([rowKey]) => !(rowKey in base)));
959
+ return {
960
+ ...card,
961
+ displayProperties: Object.keys(derived).length ? derived : undefined,
962
+ };
963
+ }),
964
+ };
965
+ }
745
966
  toCards(data) {
746
967
  const records = this.orderRecords(data?.records ?? [], data?.projectionMeta?.groups ?? []);
747
968
  const catalogProperties = data?.catalog?.properties ?? [];
@@ -774,6 +995,10 @@ class ClientListStateService {
774
995
  state: record.state ?? null,
775
996
  properties: normalizedEntities,
776
997
  entities: normalizedEntities,
998
+ // Only present on a descendant board, where cards come from several
999
+ // owners at once and the list's own context no longer identifies the
1000
+ // record a card belongs to.
1001
+ source: record.source ?? null,
777
1002
  };
778
1003
  });
779
1004
  }
@@ -1275,11 +1500,11 @@ class ClientListTableView {
1275
1500
  };
1276
1501
  }
1277
1502
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListTableView, deps: [], target: i0.ɵɵFactoryTarget.Component });
1278
- 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 [captionStartTemplate]=\"table().config.toolbarStart ?? null\"\r\n [captionEndTemplate]=\"table().config.toolbarEnd ?? null\"\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 [sortField]=\"bucket().sortField()\"\r\n (sortFieldChange)=\"bucket().sortField.set($event)\"\r\n [sortDirection]=\"bucket().sortDirection()\"\r\n (sortDirectionChange)=\"bucket().sortDirection.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", "captionStartTemplate", "captionEndTemplate", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "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"] }] });
1503
+ 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\">\n @if (table().config.contentStart) {\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.startSpan)\">\n <ng-container\n [ngTemplateOutlet]=\"table().config.contentStart\"\n [ngTemplateOutletContext]=\"templateContext(table())\"\n />\n </div>\n }\n\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.tableSpan)\">\n @if (table().loading && table().rows.length === 0) {\n <div class=\"flex flex-col gap-3 py-3\">\n <p-skeleton height=\"3rem\" />\n <p-skeleton height=\"3rem\" />\n <p-skeleton height=\"3rem\" />\n </div>\n } @else if (table().error) {\n <div\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\n >\n {{ table().error }}\n </div>\n } @else {\n <mt-table\n [noCard]=\"true\"\n [data]=\"table().rows\"\n [columns]=\"table().columns\"\n dataKey=\"Id\"\n [storageKey]=\"tableStorageKey()\"\n [persistStateExclude]=\"table().config.table.persistStateExclude\"\n [captionStartTemplate]=\"table().config.toolbarStart ?? null\"\n [captionEndTemplate]=\"table().config.toolbarEnd ?? null\"\n [rowActions]=\"rowActions()\"\n [actionShape]=\"actionShape()\"\n [rowActionsLoadingFn]=\"rowActionsLoadingFn()\"\n (rowActionsRequested)=\"rowActionsRequested.emit($event)\"\n [generalSearch]=\"table().config.table.searchable\"\n [showFilters]=\"true\"\n filterMode=\"column\"\n [printable]=\"true\"\n [groupable]=\"true\"\n [cellClickFilter]=\"true\"\n [freezeActions]=\"true\"\n [exportable]=\"table().config.table.exportable\"\n [loading]=\"table().loading\"\n [clickableRows]=\"true\"\n [lazy]=\"table().config.isPaginated\"\n [lazyTotalRecords]=\"table().totalCount\"\n [virtualScroll]=\"!table().config.isPaginated\"\n [virtualScrollItemSize]=\"56\"\n scrollHeight=\"520px\"\n [pageSize]=\"pageSize()\"\n [filters]=\"bucket().filters()\"\n (filtersChange)=\"bucket().filters.set($event)\"\n [filterTerm]=\"bucket().filterTerm()\"\n (filterTermChange)=\"bucket().filterTerm.set($event)\"\n [groupBy]=\"bucket().groupBy()\"\n (groupByChange)=\"bucket().groupBy.set($event)\"\n [sortField]=\"bucket().sortField()\"\n (sortFieldChange)=\"bucket().sortField.set($event)\"\n [sortDirection]=\"bucket().sortDirection()\"\n (sortDirectionChange)=\"bucket().sortDirection.set($event)\"\n [activeTab]=\"bucket().activeTab()\"\n (activeTabChange)=\"bucket().activeTab.set($event)\"\n (lazyLoad)=\"lazyLoad.emit($event)\"\n (rowClick)=\"rowClick.emit($event)\"\n />\n }\n </div>\n\n @if (table().config.contentEnd) {\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.endSpan)\">\n <ng-container\n [ngTemplateOutlet]=\"table().config.contentEnd\"\n [ngTemplateOutletContext]=\"templateContext(table())\"\n />\n </div>\n }\n</div>\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", "captionStartTemplate", "captionEndTemplate", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "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"] }] });
1279
1504
  }
1280
1505
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListTableView, decorators: [{
1281
1506
  type: Component,
1282
- 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 [captionStartTemplate]=\"table().config.toolbarStart ?? null\"\r\n [captionEndTemplate]=\"table().config.toolbarEnd ?? null\"\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 [sortField]=\"bucket().sortField()\"\r\n (sortFieldChange)=\"bucket().sortField.set($event)\"\r\n [sortDirection]=\"bucket().sortDirection()\"\r\n (sortDirectionChange)=\"bucket().sortDirection.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" }]
1507
+ args: [{ selector: 'mt-client-list-table-view', standalone: true, imports: [CommonModule, Table, SkeletonModule], template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\n @if (table().config.contentStart) {\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.startSpan)\">\n <ng-container\n [ngTemplateOutlet]=\"table().config.contentStart\"\n [ngTemplateOutletContext]=\"templateContext(table())\"\n />\n </div>\n }\n\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.tableSpan)\">\n @if (table().loading && table().rows.length === 0) {\n <div class=\"flex flex-col gap-3 py-3\">\n <p-skeleton height=\"3rem\" />\n <p-skeleton height=\"3rem\" />\n <p-skeleton height=\"3rem\" />\n </div>\n } @else if (table().error) {\n <div\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\n >\n {{ table().error }}\n </div>\n } @else {\n <mt-table\n [noCard]=\"true\"\n [data]=\"table().rows\"\n [columns]=\"table().columns\"\n dataKey=\"Id\"\n [storageKey]=\"tableStorageKey()\"\n [persistStateExclude]=\"table().config.table.persistStateExclude\"\n [captionStartTemplate]=\"table().config.toolbarStart ?? null\"\n [captionEndTemplate]=\"table().config.toolbarEnd ?? null\"\n [rowActions]=\"rowActions()\"\n [actionShape]=\"actionShape()\"\n [rowActionsLoadingFn]=\"rowActionsLoadingFn()\"\n (rowActionsRequested)=\"rowActionsRequested.emit($event)\"\n [generalSearch]=\"table().config.table.searchable\"\n [showFilters]=\"true\"\n filterMode=\"column\"\n [printable]=\"true\"\n [groupable]=\"true\"\n [cellClickFilter]=\"true\"\n [freezeActions]=\"true\"\n [exportable]=\"table().config.table.exportable\"\n [loading]=\"table().loading\"\n [clickableRows]=\"true\"\n [lazy]=\"table().config.isPaginated\"\n [lazyTotalRecords]=\"table().totalCount\"\n [virtualScroll]=\"!table().config.isPaginated\"\n [virtualScrollItemSize]=\"56\"\n scrollHeight=\"520px\"\n [pageSize]=\"pageSize()\"\n [filters]=\"bucket().filters()\"\n (filtersChange)=\"bucket().filters.set($event)\"\n [filterTerm]=\"bucket().filterTerm()\"\n (filterTermChange)=\"bucket().filterTerm.set($event)\"\n [groupBy]=\"bucket().groupBy()\"\n (groupByChange)=\"bucket().groupBy.set($event)\"\n [sortField]=\"bucket().sortField()\"\n (sortFieldChange)=\"bucket().sortField.set($event)\"\n [sortDirection]=\"bucket().sortDirection()\"\n (sortDirectionChange)=\"bucket().sortDirection.set($event)\"\n [activeTab]=\"bucket().activeTab()\"\n (activeTabChange)=\"bucket().activeTab.set($event)\"\n (lazyLoad)=\"lazyLoad.emit($event)\"\n (rowClick)=\"rowClick.emit($event)\"\n />\n }\n </div>\n\n @if (table().config.contentEnd) {\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.endSpan)\">\n <ng-container\n [ngTemplateOutlet]=\"table().config.contentEnd\"\n [ngTemplateOutletContext]=\"templateContext(table())\"\n />\n </div>\n }\n</div>\n" }]
1283
1508
  }], 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"] }] } });
1284
1509
 
1285
1510
  const EMPTY_KEY_SET = new Set();
@@ -1335,8 +1560,15 @@ class ClientListCardsView {
1335
1560
  * observed so scrolling it into view triggers `loadMore`. */
1336
1561
  loadMoreSentinel = viewChild('loadMoreSentinel', ...(ngDevMode ? [{ debugName: "loadMoreSentinel" }] : /* istanbul ignore next */ []));
1337
1562
  loadMoreObserver = null;
1338
- /** Shared toolbar bucket — same instance the table view uses. */
1339
- bucket = computed(() => this.toolbar.forList(this.state().key), ...(ngDevMode ? [{ debugName: "bucket" }] : /* istanbul ignore next */ []));
1563
+ /**
1564
+ * Shared toolbar bucket — same instance the table view uses. Passes the same
1565
+ * `table.groupBy` default, so a board opened first starts grouped exactly as
1566
+ * the table would. The service seeds it once per list, whichever view asks
1567
+ * first, so the user's own regrouping still sticks.
1568
+ */
1569
+ bucket = computed(() => this.toolbar.forList(this.state().key, {
1570
+ groupBy: this.state().config.table.groupBy,
1571
+ }), ...(ngDevMode ? [{ debugName: "bucket" }] : /* istanbul ignore next */ []));
1340
1572
  /** True while a column filter or search term is active — drives the
1341
1573
  * reset button's visibility in the caption toolbar. */
1342
1574
  hasActiveFilter = computed(() => Object.keys(this.bucket().filters()).length > 0 ||
@@ -1386,38 +1618,16 @@ class ClientListCardsView {
1386
1618
  this.bucket().filterTerm.set('');
1387
1619
  }
1388
1620
  /**
1389
- * Columns for filter/group/export. `state.columns` is empty for cards area
1390
- * (the state service doesn't populate it — see `setCardsResult`), so we
1391
- * reconstruct `ColumnDef[]` from the first card's entity metadata. Each
1392
- * entity carries its `key`, `name` (label), and `viewType`, which is
1393
- * everything `TableValueResolver.getColumnFilterType` needs to classify
1394
- * filter behaviour (select / boolean / date / user / text).
1621
+ * Columns for filter/group/export. A board whose host reshapes the list
1622
+ * (`transformResult`) carries the reshaped columns on `state.columns` — the
1623
+ * same ones the table shows, host-derived ones included — and otherwise
1624
+ * they are rebuilt from the first card's entity metadata.
1395
1625
  */
1396
1626
  columns = computed(() => {
1397
1627
  const fromState = this.state().columns;
1398
1628
  if (fromState && fromState.length > 0)
1399
1629
  return fromState;
1400
- const sampleCard = this.cardsState().cards[0];
1401
- if (!sampleCard?.entities?.length)
1402
- return [];
1403
- // Section markers are card-layout only — never table columns.
1404
- return sampleCard.entities
1405
- .filter((entity) => entity.viewType !== 'Section')
1406
- .map((entity) => {
1407
- const raw = entity;
1408
- const key = (typeof raw['key'] === 'string' && raw['key']) ||
1409
- (typeof raw['normalizedKey'] === 'string' && raw['normalizedKey']) ||
1410
- entity.name;
1411
- return {
1412
- key: String(key),
1413
- normalizedKey: typeof raw['normalizedKey'] === 'string'
1414
- ? raw['normalizedKey']
1415
- : String(key),
1416
- label: entity.name ?? String(key),
1417
- type: 'entity',
1418
- viewType: (entity.viewType ?? 'Text'),
1419
- };
1420
- });
1630
+ return toClientListCardColumns(this.cardsState().cards[0]);
1421
1631
  }, ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
1422
1632
  /**
1423
1633
  * Row view of each card. Cards carry their data as `entities: EntityData[]`,
@@ -1428,28 +1638,7 @@ class ClientListCardsView {
1428
1638
  */
1429
1639
  rowRepresentations = computed(() => this.cardsState().cards.map((card) => this.cardToRow(card)), ...(ngDevMode ? [{ debugName: "rowRepresentations" }] : /* istanbul ignore next */ []));
1430
1640
  cardToRow(card) {
1431
- const fromDisplay = card.displayProperties;
1432
- const record = fromDisplay && Object.keys(fromDisplay).length > 0
1433
- ? {
1434
- Id: card.id,
1435
- ...fromDisplay,
1436
- [CLIENT_LIST_RECORD_STATE_KEY]: card.state ?? null,
1437
- }
1438
- : { Id: card.id, [CLIENT_LIST_RECORD_STATE_KEY]: card.state ?? null };
1439
- for (const entity of card.entities ?? []) {
1440
- if (entity.viewType === 'Section')
1441
- continue;
1442
- const e = entity;
1443
- const key = (typeof e['key'] === 'string' && e['key']) ||
1444
- (typeof e['normalizedKey'] === 'string' && e['normalizedKey']) ||
1445
- entity.name;
1446
- const normalizedKey = typeof e['normalizedKey'] === 'string' ? e['normalizedKey'] : null;
1447
- const keys = new Set([key, normalizedKey].filter((entry) => typeof entry === 'string' && entry.length > 0));
1448
- for (const entityKey of keys) {
1449
- record[entityKey] = entity;
1450
- }
1451
- }
1452
- return record;
1641
+ return toClientListCardRow(card);
1453
1642
  }
1454
1643
  /**
1455
1644
  * Stable card → row lookup. Row actions (and their `hidden` / `action`
@@ -2001,7 +2190,7 @@ class ClientListCardsView {
2001
2190
  });
2002
2191
  }
2003
2192
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListCardsView, deps: [], target: i0.ɵɵFactoryTarget.Component });
2004
- 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\n [generalSearch]=\"true\"\n [showFilters]=\"true\"\n filterMode=\"popover\"\n [printable]=\"true\"\n [groupable]=\"true\"\n [exportable]=\"true\"\n [columns]=\"columns()\"\n [data]=\"rowRepresentations()\"\n [groupColumns]=\"grouping.groupableColumns()\"\n [actions]=\"toolbarActions()\"\n [captionStart]=\"cardsState().config.toolbarStart ?? null\"\n [captionEnd]=\"cardsState().config.toolbarEnd ?? null\"\n [filters]=\"bucket().filters()\"\n (filtersChange)=\"bucket().filters.set($event)\"\n [filterTerm]=\"bucket().filterTerm()\"\n (filterTermChange)=\"bucket().filterTerm.set($event)\"\n [groupBy]=\"bucket().groupBy()\"\n (groupByChange)=\"bucket().groupBy.set($event)\"\n [activeTab]=\"bucket().activeTab()\"\n (activeTabChange)=\"bucket().activeTab.set($event)\"\n (exportRequested)=\"exportExcel()\"\n (printRequested)=\"printPdf()\"\n/>\n\n@if (badgeLegend().length > 0) {\n <!--\n Decodes the coloured count pills on the cards below. Rendered once for the\n whole board rather than per card, so it costs one line of vertical space\n instead of repeating a key inside every tile (finding M-04).\n -->\n <div\n class=\"mb-3 flex flex-wrap items-center gap-x-4 gap-y-2 px-1\"\n role=\"list\"\n [attr.aria-label]=\"\n 'components.clientComponents.list.badgeLegend' | transloco\n \"\n >\n <span class=\"text-xs font-medium text-gray-500 dark:text-gray-400\">\n {{ \"components.clientComponents.list.badgeLegend\" | transloco }}\n </span>\n @for (entry of badgeLegend(); track entry.key) {\n <span class=\"flex items-center gap-1.5\" role=\"listitem\">\n <span\n class=\"inline-flex h-4 min-w-4 items-center justify-center rounded text-[10px] font-bold\"\n [style.background-color]=\"entry.color\"\n [style.color]=\"entry.textColor\"\n aria-hidden=\"true\"\n >\n #\n </span>\n <span class=\"text-xs text-gray-600 dark:text-gray-300\">{{\n entry.label\n }}</span>\n </span>\n }\n </div>\n}\n\n@if (cardsState().loading && cardsState().cards.length === 0) {\n <div class=\"mt-cards-grid\">\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\n }\n </div>\n} @else if (cardsState().error) {\n <div class=\"rounded-lg border border-red-200 bg-red-50 p-6 text-red-600\">\n {{ cardsState().error }}\n </div>\n} @else if (visibleCards().length === 0) {\n <!--\n The same empty state the tables use, so a board and a list stop looking like\n two products (finding H-12). A board narrowed by a search or a filter says\n so and offers to clear it, instead of the bare grey \"No cards found.\"\n -->\n <mt-empty-state\n [variant]=\"hasActiveCardFilters() ? 'filtered' : 'empty'\"\n [title]=\"\n hasActiveCardFilters()\n ? ''\n : ('components.clientComponents.list.noCardsFound' | transloco)\n \"\n [actionLabel]=\"\n hasActiveCardFilters()\n ? ('components.emptyState.clearFilters' | transloco)\n : ''\n \"\n actionIcon=\"general.x-close\"\n (actionClick)=\"clearCardFilters()\"\n />\n} @else if (grouping.groupingActive()) {\n <div class=\"flex flex-col gap-6\">\n @for (group of groupedCards(); track group.key) {\n <section class=\"flex flex-col gap-3\">\n <header\n class=\"flex items-center gap-3 px-2 py-2 bg-surface-50 dark:bg-surface-900 border-y border-surface-200 dark:border-surface-700 rounded\"\n [style.--mt-group-accent]=\"group.accent ?? 'var(--p-primary-color)'\"\n >\n <span\n class=\"inline-block w-1 h-5 rounded-full\"\n [style.background]=\"'var(--mt-group-accent)'\"\n ></span>\n <span\n class=\"text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400\"\n >\n {{ group.columnLabel }}\n </span>\n <span class=\"text-sm font-semibold text-gray-900 dark:text-gray-100\">\n {{ group.label }}\n </span>\n <span\n class=\"ml-auto inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-primary-50 text-primary-700 dark:bg-primary-950 dark:text-primary-300\"\n >\n {{ group.count }}\n </span>\n </header>\n <div class=\"mt-cards-grid\">\n @for (card of group.cards; track card.id) {\n <mt-card\n class=\"relative cursor-pointer p-5 shadow-sm transition-shadow hover:shadow-md\"\n (click)=\"cardClick.emit(card)\"\n >\n <ng-template #headless>\n <ng-container\n [ngTemplateOutlet]=\"cardActionsMenu\"\n [ngTemplateOutletContext]=\"{ $implicit: card }\"\n />\n <mt-entities-preview\n [entities]=\"card.entities\"\n attachmentShape=\"compact\"\n [stackOnNarrow]=\"false\"\n [clickableKeys]=\"clickableEntityKeys()\"\n [activeKeys]=\"activeKeysForCard(card)\"\n [clickableTooltip]=\"filterByThisLabel()\"\n [activeTooltip]=\"clearFilterLabel()\"\n (entityClick)=\"onEntityFilterClick(card, $event)\"\n />\n </ng-template>\n </mt-card>\n }\n </div>\n </section>\n }\n </div>\n} @else {\n <div class=\"mt-cards-grid\">\n @for (card of visibleCards(); track card.id) {\n @defer (on viewport) {\n <mt-card\n class=\"relative cursor-pointer p-5 shadow-sm transition-shadow hover:shadow-md\"\n (click)=\"cardClick.emit(card)\"\n >\n <ng-template #headless>\n <ng-container\n [ngTemplateOutlet]=\"cardActionsMenu\"\n [ngTemplateOutletContext]=\"{ $implicit: card }\"\n />\n <mt-entities-preview\n [entities]=\"card.entities\"\n attachmentShape=\"compact\"\n [stackOnNarrow]=\"false\"\n [clickableKeys]=\"clickableEntityKeys()\"\n [activeKeys]=\"activeKeysForCard(card)\"\n [clickableTooltip]=\"filterByThisLabel()\"\n [activeTooltip]=\"clearFilterLabel()\"\n (entityClick)=\"onEntityFilterClick(card, $event)\"\n />\n </ng-template>\n </mt-card>\n } @placeholder {\n <div class=\"min-h-[20rem]\">\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\n </div>\n }\n }\n </div>\n}\n\n<!--\n Infinite-scroll trigger. Deliberately outside the @if/@else chain above:\n inside it the element would be torn down and rebuilt every time the view\n swapped branch (error, empty filter result, group-by), and the observer\n would be left watching a detached node. Kept in the DOM and hidden via CSS\n when there is nothing left to load, so appending a page never recreates it.\n It also has to sit outside so infinite scroll keeps working while a\n group-by is active \u2014 grouping is a pure FE view over whatever has been\n fetched, not a reason to stop fetching.\n-->\n<div\n #loadMoreSentinel\n class=\"mt-cards-grid py-4\"\n [class.hidden]=\"!hasMoreCards()\"\n>\n @for (i of [1, 2, 3]; track i) {\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\n }\n</div>\n\n<!--\n Per-card escalation marker and 3-dots actions menu. The compact corner cover\n only indicates active escalation state; its preview action lives in the menu\n so the marker never displaces or competes with the menu trigger.\n-->\n<ng-template #cardActionsMenu let-card>\n @let escalationAction = escalationMarkerAction(card);\n @if (escalationAction) {\n <span\n class=\"mt-escalation-marker absolute end-0 top-0 z-10 h-[18px] w-[20px]\"\n role=\"img\"\n tabindex=\"0\"\n [attr.aria-label]=\"escalationLabel()\"\n [mtTooltip]=\"escalationLabel()\"\n tooltipPosition=\"top\"\n ></span>\n }\n @if (rowActions().length > 0) {\n <div\n class=\"absolute end-2 top-2 z-20 flex items-center gap-1\"\n (click)=\"$event.stopPropagation()\"\n >\n @for (action of inlineActionsForCard(card); track $index) {\n <mt-button\n [icon]=\"action.icon\"\n [severity]=\"action.color\"\n [variant]=\"action.variant\"\n [size]=\"actionSize(action) || 'small'\"\n [tooltip]=\"action.tooltip\"\n [label]=\"action.label\"\n [loading]=\"resolveActionLoading(action, card)\"\n (onClick)=\"runCardAction($event, action, card)\"\n />\n }\n @if (showCardActionsMenu(card)) {\n <button\n type=\"button\"\n class=\"flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-0 text-base text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 dark:text-gray-300 dark:hover:bg-surface-700 dark:hover:text-gray-100\"\n [attr.aria-label]=\"actionsLabel()\"\n [mtTooltip]=\"actionsLabel()\"\n tooltipPosition=\"top\"\n (click)=\"onCardActionsToggle($event, cardPopover, card)\"\n >\n <mt-icon icon=\"general.dots-vertical\" class=\"text-base\" />\n </button>\n <p-popover #cardPopover appendTo=\"body\">\n <div class=\"flex min-w-44 flex-col gap-1 p-1\">\n @if (isCardActionsLoading(card)) {\n <div class=\"flex flex-col gap-2 p-1\">\n <p-skeleton height=\"1.75rem\" />\n <p-skeleton height=\"1.75rem\" />\n <p-skeleton height=\"1.75rem\" />\n </div>\n } @else {\n @for (action of menuActionsForCard(card); track $index) {\n @if (isFirstDestructiveCardAction(card, $index)) {\n <hr\n class=\"my-1 border-0 border-t border-surface-200 dark:border-surface-700\"\n />\n }\n <button\n type=\"button\"\n class=\"flex cursor-pointer items-center gap-2 rounded px-2 py-2 text-start text-sm transition-colors\"\n [class]=\"\n action.color === 'danger'\n ? 'text-red-600 hover:bg-red-50 dark:hover:bg-red-950'\n : action.color === 'warn'\n ? 'text-orange-600 hover:bg-orange-50 dark:text-orange-400 dark:hover:bg-orange-950'\n : 'text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-surface-700'\n \"\n (click)=\"\n runCardAction($event, action, card); cardPopover.hide()\n \"\n >\n @if (action.icon) {\n <mt-icon [icon]=\"action.icon\" class=\"text-base\" />\n }\n <span>{{ action.label || action.tooltip }}</span>\n </button>\n }\n }\n </div>\n </p-popover>\n }\n </div>\n }\n</ng-template>\n", styles: [".mt-cards-grid{--mt-cards-gap: 1rem;--mt-cards-min: 335px;--mt-cards-track: max( var(--mt-cards-min), (100% - 3 * var(--mt-cards-gap)) / 4 );display:grid;gap:var(--mt-cards-gap);grid-template-columns:repeat(auto-fill,minmax(min(100%,var(--mt-cards-track)),1fr))}.mt-cards-grid.hidden,.mt-cards-grid[hidden]{display:none}.mt-escalation-marker{border-start-end-radius:.5rem;background:var(--p-button-text-warn-color, var(--p-orange-500, #f59e0b));clip-path:polygon(0 0,100% 0,100% 100%);transition:filter .15s ease}.mt-escalation-marker:dir(rtl){clip-path:polygon(0 0,100% 0,0 100%)}.mt-escalation-marker:hover{filter:brightness(.92)}.mt-escalation-marker:focus-visible{filter:brightness(.92) drop-shadow(0 0 2px var(--p-button-text-warn-color, #f59e0b));outline:none}\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", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: EntitiesPreview, selector: "mt-entities-preview", inputs: ["entities", "attachmentShape", "entityListContext", "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: "component", type: EmptyState, selector: "mt-empty-state", inputs: ["variant", "illustration", "title", "description", "actionLabel", "actionIcon"], outputs: ["actionClick"] }, { kind: "pipe", type: i4.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [i1.NgTemplateOutlet, Card,
2193
+ 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 [captionStart]=\"cardsState().config.toolbarStart ?? null\"\r\n [captionEnd]=\"cardsState().config.toolbarEnd ?? null\"\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 (badgeLegend().length > 0) {\r\n <!--\r\n Decodes the coloured count pills on the cards below. Rendered once for the\r\n whole board rather than per card, so it costs one line of vertical space\r\n instead of repeating a key inside every tile (finding M-04).\r\n -->\r\n <div\r\n class=\"mb-3 flex flex-wrap items-center gap-x-4 gap-y-2 px-1\"\r\n role=\"list\"\r\n [attr.aria-label]=\"\r\n 'components.clientComponents.list.badgeLegend' | transloco\r\n \"\r\n >\r\n <span class=\"text-xs font-medium text-gray-500 dark:text-gray-400\">\r\n {{ \"components.clientComponents.list.badgeLegend\" | transloco }}\r\n </span>\r\n @for (entry of badgeLegend(); track entry.key) {\r\n <span class=\"flex items-center gap-1.5\" role=\"listitem\">\r\n <span\r\n class=\"inline-flex h-4 min-w-4 items-center justify-center rounded text-[10px] font-bold\"\r\n [style.background-color]=\"entry.color\"\r\n [style.color]=\"entry.textColor\"\r\n aria-hidden=\"true\"\r\n >\r\n #\r\n </span>\r\n <span class=\"text-xs text-gray-600 dark:text-gray-300\">{{\r\n entry.label\r\n }}</span>\r\n </span>\r\n }\r\n </div>\r\n}\r\n\r\n@if (cardsState().loading && cardsState().cards.length === 0) {\r\n <div class=\"mt-cards-grid\">\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 <!--\r\n The same empty state the tables use, so a board and a list stop looking like\r\n two products (finding H-12). A board narrowed by a search or a filter says\r\n so and offers to clear it, instead of the bare grey \"No cards found.\"\r\n -->\r\n <mt-empty-state\r\n [variant]=\"hasActiveCardFilters() ? 'filtered' : 'empty'\"\r\n [title]=\"\r\n hasActiveCardFilters()\r\n ? ''\r\n : ('components.clientComponents.list.noCardsFound' | transloco)\r\n \"\r\n [actionLabel]=\"\r\n hasActiveCardFilters()\r\n ? ('components.emptyState.clearFilters' | transloco)\r\n : ''\r\n \"\r\n actionIcon=\"general.x-close\"\r\n (actionClick)=\"clearCardFilters()\"\r\n />\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=\"mt-cards-grid\">\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]=\"filterByThisLabel()\"\r\n [activeTooltip]=\"clearFilterLabel()\"\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=\"mt-cards-grid\">\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]=\"filterByThisLabel()\"\r\n [activeTooltip]=\"clearFilterLabel()\"\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=\"mt-cards-grid py-4\"\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 escalation marker and 3-dots actions menu. The compact corner cover\r\n only indicates active escalation state; its preview action lives in the menu\r\n so the marker never displaces or competes with the menu trigger.\r\n-->\r\n<ng-template #cardActionsMenu let-card>\r\n @let escalationAction = escalationMarkerAction(card);\r\n @if (escalationAction) {\r\n <span\r\n class=\"mt-escalation-marker absolute end-0 top-0 z-10 h-[18px] w-[20px]\"\r\n role=\"img\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"escalationLabel()\"\r\n [mtTooltip]=\"escalationLabel()\"\r\n tooltipPosition=\"top\"\r\n ></span>\r\n }\r\n @if (rowActions().length > 0) {\r\n <div\r\n class=\"absolute end-2 top-2 z-20 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 <button\r\n type=\"button\"\r\n class=\"flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-0 text-base text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 dark:text-gray-300 dark:hover:bg-surface-700 dark:hover:text-gray-100\"\r\n [attr.aria-label]=\"actionsLabel()\"\r\n [mtTooltip]=\"actionsLabel()\"\r\n tooltipPosition=\"top\"\r\n (click)=\"onCardActionsToggle($event, cardPopover, card)\"\r\n >\r\n <mt-icon icon=\"general.dots-vertical\" class=\"text-base\" />\r\n </button>\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 @if (isFirstDestructiveCardAction(card, $index)) {\r\n <hr\r\n class=\"my-1 border-0 border-t border-surface-200 dark:border-surface-700\"\r\n />\r\n }\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 : action.color === 'warn'\r\n ? 'text-orange-600 hover:bg-orange-50 dark:text-orange-400 dark:hover:bg-orange-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", styles: [".mt-cards-grid{--mt-cards-gap: 1rem;--mt-cards-min: 335px;--mt-cards-track: max( var(--mt-cards-min), (100% - 3 * var(--mt-cards-gap)) / 4 );display:grid;gap:var(--mt-cards-gap);grid-template-columns:repeat(auto-fill,minmax(min(100%,var(--mt-cards-track)),1fr))}.mt-cards-grid.hidden,.mt-cards-grid[hidden]{display:none}.mt-escalation-marker{border-start-end-radius:.5rem;background:var(--p-button-text-warn-color, var(--p-orange-500, #f59e0b));clip-path:polygon(0 0,100% 0,100% 100%);transition:filter .15s ease}.mt-escalation-marker:dir(rtl){clip-path:polygon(0 0,100% 0,0 100%)}.mt-escalation-marker:hover{filter:brightness(.92)}.mt-escalation-marker:focus-visible{filter:brightness(.92) drop-shadow(0 0 2px var(--p-button-text-warn-color, #f59e0b));outline:none}\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", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { 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: "component", type: EmptyState, selector: "mt-empty-state", inputs: ["variant", "illustration", "title", "description", "actionLabel", "actionIcon"], outputs: ["actionClick"] }, { kind: "pipe", type: i4.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [i1.NgTemplateOutlet, Card,
2005
2194
  EntitiesPreview]] });
2006
2195
  }
2007
2196
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListCardsView, decorators: [{
@@ -2018,7 +2207,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
2018
2207
  PopoverModule,
2019
2208
  TableCaption,
2020
2209
  EmptyState,
2021
- ], providers: [MTDateFormatPipe], template: "<mt-table-caption\n [generalSearch]=\"true\"\n [showFilters]=\"true\"\n filterMode=\"popover\"\n [printable]=\"true\"\n [groupable]=\"true\"\n [exportable]=\"true\"\n [columns]=\"columns()\"\n [data]=\"rowRepresentations()\"\n [groupColumns]=\"grouping.groupableColumns()\"\n [actions]=\"toolbarActions()\"\n [captionStart]=\"cardsState().config.toolbarStart ?? null\"\n [captionEnd]=\"cardsState().config.toolbarEnd ?? null\"\n [filters]=\"bucket().filters()\"\n (filtersChange)=\"bucket().filters.set($event)\"\n [filterTerm]=\"bucket().filterTerm()\"\n (filterTermChange)=\"bucket().filterTerm.set($event)\"\n [groupBy]=\"bucket().groupBy()\"\n (groupByChange)=\"bucket().groupBy.set($event)\"\n [activeTab]=\"bucket().activeTab()\"\n (activeTabChange)=\"bucket().activeTab.set($event)\"\n (exportRequested)=\"exportExcel()\"\n (printRequested)=\"printPdf()\"\n/>\n\n@if (badgeLegend().length > 0) {\n <!--\n Decodes the coloured count pills on the cards below. Rendered once for the\n whole board rather than per card, so it costs one line of vertical space\n instead of repeating a key inside every tile (finding M-04).\n -->\n <div\n class=\"mb-3 flex flex-wrap items-center gap-x-4 gap-y-2 px-1\"\n role=\"list\"\n [attr.aria-label]=\"\n 'components.clientComponents.list.badgeLegend' | transloco\n \"\n >\n <span class=\"text-xs font-medium text-gray-500 dark:text-gray-400\">\n {{ \"components.clientComponents.list.badgeLegend\" | transloco }}\n </span>\n @for (entry of badgeLegend(); track entry.key) {\n <span class=\"flex items-center gap-1.5\" role=\"listitem\">\n <span\n class=\"inline-flex h-4 min-w-4 items-center justify-center rounded text-[10px] font-bold\"\n [style.background-color]=\"entry.color\"\n [style.color]=\"entry.textColor\"\n aria-hidden=\"true\"\n >\n #\n </span>\n <span class=\"text-xs text-gray-600 dark:text-gray-300\">{{\n entry.label\n }}</span>\n </span>\n }\n </div>\n}\n\n@if (cardsState().loading && cardsState().cards.length === 0) {\n <div class=\"mt-cards-grid\">\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\n }\n </div>\n} @else if (cardsState().error) {\n <div class=\"rounded-lg border border-red-200 bg-red-50 p-6 text-red-600\">\n {{ cardsState().error }}\n </div>\n} @else if (visibleCards().length === 0) {\n <!--\n The same empty state the tables use, so a board and a list stop looking like\n two products (finding H-12). A board narrowed by a search or a filter says\n so and offers to clear it, instead of the bare grey \"No cards found.\"\n -->\n <mt-empty-state\n [variant]=\"hasActiveCardFilters() ? 'filtered' : 'empty'\"\n [title]=\"\n hasActiveCardFilters()\n ? ''\n : ('components.clientComponents.list.noCardsFound' | transloco)\n \"\n [actionLabel]=\"\n hasActiveCardFilters()\n ? ('components.emptyState.clearFilters' | transloco)\n : ''\n \"\n actionIcon=\"general.x-close\"\n (actionClick)=\"clearCardFilters()\"\n />\n} @else if (grouping.groupingActive()) {\n <div class=\"flex flex-col gap-6\">\n @for (group of groupedCards(); track group.key) {\n <section class=\"flex flex-col gap-3\">\n <header\n class=\"flex items-center gap-3 px-2 py-2 bg-surface-50 dark:bg-surface-900 border-y border-surface-200 dark:border-surface-700 rounded\"\n [style.--mt-group-accent]=\"group.accent ?? 'var(--p-primary-color)'\"\n >\n <span\n class=\"inline-block w-1 h-5 rounded-full\"\n [style.background]=\"'var(--mt-group-accent)'\"\n ></span>\n <span\n class=\"text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400\"\n >\n {{ group.columnLabel }}\n </span>\n <span class=\"text-sm font-semibold text-gray-900 dark:text-gray-100\">\n {{ group.label }}\n </span>\n <span\n class=\"ml-auto inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-primary-50 text-primary-700 dark:bg-primary-950 dark:text-primary-300\"\n >\n {{ group.count }}\n </span>\n </header>\n <div class=\"mt-cards-grid\">\n @for (card of group.cards; track card.id) {\n <mt-card\n class=\"relative cursor-pointer p-5 shadow-sm transition-shadow hover:shadow-md\"\n (click)=\"cardClick.emit(card)\"\n >\n <ng-template #headless>\n <ng-container\n [ngTemplateOutlet]=\"cardActionsMenu\"\n [ngTemplateOutletContext]=\"{ $implicit: card }\"\n />\n <mt-entities-preview\n [entities]=\"card.entities\"\n attachmentShape=\"compact\"\n [stackOnNarrow]=\"false\"\n [clickableKeys]=\"clickableEntityKeys()\"\n [activeKeys]=\"activeKeysForCard(card)\"\n [clickableTooltip]=\"filterByThisLabel()\"\n [activeTooltip]=\"clearFilterLabel()\"\n (entityClick)=\"onEntityFilterClick(card, $event)\"\n />\n </ng-template>\n </mt-card>\n }\n </div>\n </section>\n }\n </div>\n} @else {\n <div class=\"mt-cards-grid\">\n @for (card of visibleCards(); track card.id) {\n @defer (on viewport) {\n <mt-card\n class=\"relative cursor-pointer p-5 shadow-sm transition-shadow hover:shadow-md\"\n (click)=\"cardClick.emit(card)\"\n >\n <ng-template #headless>\n <ng-container\n [ngTemplateOutlet]=\"cardActionsMenu\"\n [ngTemplateOutletContext]=\"{ $implicit: card }\"\n />\n <mt-entities-preview\n [entities]=\"card.entities\"\n attachmentShape=\"compact\"\n [stackOnNarrow]=\"false\"\n [clickableKeys]=\"clickableEntityKeys()\"\n [activeKeys]=\"activeKeysForCard(card)\"\n [clickableTooltip]=\"filterByThisLabel()\"\n [activeTooltip]=\"clearFilterLabel()\"\n (entityClick)=\"onEntityFilterClick(card, $event)\"\n />\n </ng-template>\n </mt-card>\n } @placeholder {\n <div class=\"min-h-[20rem]\">\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\n </div>\n }\n }\n </div>\n}\n\n<!--\n Infinite-scroll trigger. Deliberately outside the @if/@else chain above:\n inside it the element would be torn down and rebuilt every time the view\n swapped branch (error, empty filter result, group-by), and the observer\n would be left watching a detached node. Kept in the DOM and hidden via CSS\n when there is nothing left to load, so appending a page never recreates it.\n It also has to sit outside so infinite scroll keeps working while a\n group-by is active \u2014 grouping is a pure FE view over whatever has been\n fetched, not a reason to stop fetching.\n-->\n<div\n #loadMoreSentinel\n class=\"mt-cards-grid py-4\"\n [class.hidden]=\"!hasMoreCards()\"\n>\n @for (i of [1, 2, 3]; track i) {\n <p-skeleton height=\"20rem\" class=\"rounded-lg\" />\n }\n</div>\n\n<!--\n Per-card escalation marker and 3-dots actions menu. The compact corner cover\n only indicates active escalation state; its preview action lives in the menu\n so the marker never displaces or competes with the menu trigger.\n-->\n<ng-template #cardActionsMenu let-card>\n @let escalationAction = escalationMarkerAction(card);\n @if (escalationAction) {\n <span\n class=\"mt-escalation-marker absolute end-0 top-0 z-10 h-[18px] w-[20px]\"\n role=\"img\"\n tabindex=\"0\"\n [attr.aria-label]=\"escalationLabel()\"\n [mtTooltip]=\"escalationLabel()\"\n tooltipPosition=\"top\"\n ></span>\n }\n @if (rowActions().length > 0) {\n <div\n class=\"absolute end-2 top-2 z-20 flex items-center gap-1\"\n (click)=\"$event.stopPropagation()\"\n >\n @for (action of inlineActionsForCard(card); track $index) {\n <mt-button\n [icon]=\"action.icon\"\n [severity]=\"action.color\"\n [variant]=\"action.variant\"\n [size]=\"actionSize(action) || 'small'\"\n [tooltip]=\"action.tooltip\"\n [label]=\"action.label\"\n [loading]=\"resolveActionLoading(action, card)\"\n (onClick)=\"runCardAction($event, action, card)\"\n />\n }\n @if (showCardActionsMenu(card)) {\n <button\n type=\"button\"\n class=\"flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-0 text-base text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 dark:text-gray-300 dark:hover:bg-surface-700 dark:hover:text-gray-100\"\n [attr.aria-label]=\"actionsLabel()\"\n [mtTooltip]=\"actionsLabel()\"\n tooltipPosition=\"top\"\n (click)=\"onCardActionsToggle($event, cardPopover, card)\"\n >\n <mt-icon icon=\"general.dots-vertical\" class=\"text-base\" />\n </button>\n <p-popover #cardPopover appendTo=\"body\">\n <div class=\"flex min-w-44 flex-col gap-1 p-1\">\n @if (isCardActionsLoading(card)) {\n <div class=\"flex flex-col gap-2 p-1\">\n <p-skeleton height=\"1.75rem\" />\n <p-skeleton height=\"1.75rem\" />\n <p-skeleton height=\"1.75rem\" />\n </div>\n } @else {\n @for (action of menuActionsForCard(card); track $index) {\n @if (isFirstDestructiveCardAction(card, $index)) {\n <hr\n class=\"my-1 border-0 border-t border-surface-200 dark:border-surface-700\"\n />\n }\n <button\n type=\"button\"\n class=\"flex cursor-pointer items-center gap-2 rounded px-2 py-2 text-start text-sm transition-colors\"\n [class]=\"\n action.color === 'danger'\n ? 'text-red-600 hover:bg-red-50 dark:hover:bg-red-950'\n : action.color === 'warn'\n ? 'text-orange-600 hover:bg-orange-50 dark:text-orange-400 dark:hover:bg-orange-950'\n : 'text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-surface-700'\n \"\n (click)=\"\n runCardAction($event, action, card); cardPopover.hide()\n \"\n >\n @if (action.icon) {\n <mt-icon [icon]=\"action.icon\" class=\"text-base\" />\n }\n <span>{{ action.label || action.tooltip }}</span>\n </button>\n }\n }\n </div>\n </p-popover>\n }\n </div>\n }\n</ng-template>\n", styles: [".mt-cards-grid{--mt-cards-gap: 1rem;--mt-cards-min: 335px;--mt-cards-track: max( var(--mt-cards-min), (100% - 3 * var(--mt-cards-gap)) / 4 );display:grid;gap:var(--mt-cards-gap);grid-template-columns:repeat(auto-fill,minmax(min(100%,var(--mt-cards-track)),1fr))}.mt-cards-grid.hidden,.mt-cards-grid[hidden]{display:none}.mt-escalation-marker{border-start-end-radius:.5rem;background:var(--p-button-text-warn-color, var(--p-orange-500, #f59e0b));clip-path:polygon(0 0,100% 0,100% 100%);transition:filter .15s ease}.mt-escalation-marker:dir(rtl){clip-path:polygon(0 0,100% 0,0 100%)}.mt-escalation-marker:hover{filter:brightness(.92)}.mt-escalation-marker:focus-visible{filter:brightness(.92) drop-shadow(0 0 2px var(--p-button-text-warn-color, #f59e0b));outline:none}\n"] }]
2210
+ ], 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 [captionStart]=\"cardsState().config.toolbarStart ?? null\"\r\n [captionEnd]=\"cardsState().config.toolbarEnd ?? null\"\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 (badgeLegend().length > 0) {\r\n <!--\r\n Decodes the coloured count pills on the cards below. Rendered once for the\r\n whole board rather than per card, so it costs one line of vertical space\r\n instead of repeating a key inside every tile (finding M-04).\r\n -->\r\n <div\r\n class=\"mb-3 flex flex-wrap items-center gap-x-4 gap-y-2 px-1\"\r\n role=\"list\"\r\n [attr.aria-label]=\"\r\n 'components.clientComponents.list.badgeLegend' | transloco\r\n \"\r\n >\r\n <span class=\"text-xs font-medium text-gray-500 dark:text-gray-400\">\r\n {{ \"components.clientComponents.list.badgeLegend\" | transloco }}\r\n </span>\r\n @for (entry of badgeLegend(); track entry.key) {\r\n <span class=\"flex items-center gap-1.5\" role=\"listitem\">\r\n <span\r\n class=\"inline-flex h-4 min-w-4 items-center justify-center rounded text-[10px] font-bold\"\r\n [style.background-color]=\"entry.color\"\r\n [style.color]=\"entry.textColor\"\r\n aria-hidden=\"true\"\r\n >\r\n #\r\n </span>\r\n <span class=\"text-xs text-gray-600 dark:text-gray-300\">{{\r\n entry.label\r\n }}</span>\r\n </span>\r\n }\r\n </div>\r\n}\r\n\r\n@if (cardsState().loading && cardsState().cards.length === 0) {\r\n <div class=\"mt-cards-grid\">\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 <!--\r\n The same empty state the tables use, so a board and a list stop looking like\r\n two products (finding H-12). A board narrowed by a search or a filter says\r\n so and offers to clear it, instead of the bare grey \"No cards found.\"\r\n -->\r\n <mt-empty-state\r\n [variant]=\"hasActiveCardFilters() ? 'filtered' : 'empty'\"\r\n [title]=\"\r\n hasActiveCardFilters()\r\n ? ''\r\n : ('components.clientComponents.list.noCardsFound' | transloco)\r\n \"\r\n [actionLabel]=\"\r\n hasActiveCardFilters()\r\n ? ('components.emptyState.clearFilters' | transloco)\r\n : ''\r\n \"\r\n actionIcon=\"general.x-close\"\r\n (actionClick)=\"clearCardFilters()\"\r\n />\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=\"mt-cards-grid\">\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]=\"filterByThisLabel()\"\r\n [activeTooltip]=\"clearFilterLabel()\"\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=\"mt-cards-grid\">\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]=\"filterByThisLabel()\"\r\n [activeTooltip]=\"clearFilterLabel()\"\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=\"mt-cards-grid py-4\"\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 escalation marker and 3-dots actions menu. The compact corner cover\r\n only indicates active escalation state; its preview action lives in the menu\r\n so the marker never displaces or competes with the menu trigger.\r\n-->\r\n<ng-template #cardActionsMenu let-card>\r\n @let escalationAction = escalationMarkerAction(card);\r\n @if (escalationAction) {\r\n <span\r\n class=\"mt-escalation-marker absolute end-0 top-0 z-10 h-[18px] w-[20px]\"\r\n role=\"img\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"escalationLabel()\"\r\n [mtTooltip]=\"escalationLabel()\"\r\n tooltipPosition=\"top\"\r\n ></span>\r\n }\r\n @if (rowActions().length > 0) {\r\n <div\r\n class=\"absolute end-2 top-2 z-20 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 <button\r\n type=\"button\"\r\n class=\"flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-0 text-base text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 dark:text-gray-300 dark:hover:bg-surface-700 dark:hover:text-gray-100\"\r\n [attr.aria-label]=\"actionsLabel()\"\r\n [mtTooltip]=\"actionsLabel()\"\r\n tooltipPosition=\"top\"\r\n (click)=\"onCardActionsToggle($event, cardPopover, card)\"\r\n >\r\n <mt-icon icon=\"general.dots-vertical\" class=\"text-base\" />\r\n </button>\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 @if (isFirstDestructiveCardAction(card, $index)) {\r\n <hr\r\n class=\"my-1 border-0 border-t border-surface-200 dark:border-surface-700\"\r\n />\r\n }\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 : action.color === 'warn'\r\n ? 'text-orange-600 hover:bg-orange-50 dark:text-orange-400 dark:hover:bg-orange-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", styles: [".mt-cards-grid{--mt-cards-gap: 1rem;--mt-cards-min: 335px;--mt-cards-track: max( var(--mt-cards-min), (100% - 3 * var(--mt-cards-gap)) / 4 );display:grid;gap:var(--mt-cards-gap);grid-template-columns:repeat(auto-fill,minmax(min(100%,var(--mt-cards-track)),1fr))}.mt-cards-grid.hidden,.mt-cards-grid[hidden]{display:none}.mt-escalation-marker{border-start-end-radius:.5rem;background:var(--p-button-text-warn-color, var(--p-orange-500, #f59e0b));clip-path:polygon(0 0,100% 0,100% 100%);transition:filter .15s ease}.mt-escalation-marker:dir(rtl){clip-path:polygon(0 0,100% 0,0 100%)}.mt-escalation-marker:hover{filter:brightness(.92)}.mt-escalation-marker:focus-visible{filter:brightness(.92) drop-shadow(0 0 2px var(--p-button-text-warn-color, #f59e0b));outline:none}\n"] }]
2022
2211
  }], 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 }] }] } });
2023
2212
 
2024
2213
  const DEFAULT_GRID_COLUMNS$1 = 12;
@@ -2043,11 +2232,11 @@ class ClientListInformativeView {
2043
2232
  };
2044
2233
  }
2045
2234
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListInformativeView, deps: [], target: i0.ɵɵFactoryTarget.Component });
2046
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientListInformativeView, isStandalone: true, selector: "mt-client-list-informative-view", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\r\n @if (informativeState().config.contentStart) {\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.startSpan)\r\n \"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"informativeState().config.contentStart\"\r\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\r\n />\r\n </div>\r\n }\r\n\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.tableSpan)\r\n \"\r\n >\r\n <mt-card>\r\n @if (\r\n informativeState().loading &&\r\n !informativeState().dashboardData?.charts?.length\r\n ) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"6rem\" />\r\n <p-skeleton height=\"12rem\" />\r\n <p-skeleton height=\"12rem\" />\r\n </div>\r\n } @else if (informativeState().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 {{ informativeState().error }}\r\n </div>\r\n } @else if (!informativeState().dashboardData?.charts?.length) {\r\n <div class=\"p-6 text-center text-gray-400\">\r\n {{\r\n \"components.clientComponents.list.noInformativeDashboard\"\r\n | transloco\r\n }}\r\n </div>\r\n } @else {\r\n <div class=\"min-h-[420px]\">\r\n <mt-dashboard-viewer\r\n [isPage]=\"false\"\r\n [showFilters]=\"false\"\r\n [dashboardData]=\"informativeState().dashboardData\"\r\n [extraFilters]=\"dashboardExtraFilters()\"\r\n />\r\n </div>\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (informativeState().config.contentEnd) {\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.endSpan)\r\n \"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"informativeState().config.contentEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: DashboardViewer, selector: "mt-dashboard-viewer", inputs: ["isPage", "pageTitle", "backButton", "pageId", "dashboardData", "chartsData", "dialogsData", "filtersData", "extraFilters", "lookups", "statuses", "levelsSchema", "levelLogsResolver", "showFilters", "showFilterTrigger", "ignoreQueryFilter"], outputs: ["pageLoaded", "onBack", "chartClick"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
2235
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ClientListInformativeView, isStandalone: true, selector: "mt-client-list-informative-view", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\n @if (informativeState().config.contentStart) {\n <div\n [style.gridColumn]=\"\n slotGridSpan(informativeState().config.layout.startSpan)\n \"\n >\n <ng-container\n [ngTemplateOutlet]=\"informativeState().config.contentStart\"\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\n />\n </div>\n }\n\n <div\n [style.gridColumn]=\"\n slotGridSpan(informativeState().config.layout.tableSpan)\n \"\n >\n <mt-card>\n @if (\n informativeState().loading &&\n !informativeState().dashboardData?.charts?.length\n ) {\n <div class=\"flex flex-col gap-3 py-3\">\n <p-skeleton height=\"6rem\" />\n <p-skeleton height=\"12rem\" />\n <p-skeleton height=\"12rem\" />\n </div>\n } @else if (informativeState().error) {\n <div\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\n >\n {{ informativeState().error }}\n </div>\n } @else if (!informativeState().dashboardData?.charts?.length) {\n <div class=\"p-6 text-center text-gray-400\">\n {{\n \"components.clientComponents.list.noInformativeDashboard\"\n | transloco\n }}\n </div>\n } @else {\n <div class=\"min-h-[420px]\">\n <mt-dashboard-viewer\n [isPage]=\"false\"\n [showFilters]=\"false\"\n [dashboardData]=\"informativeState().dashboardData\"\n [extraFilters]=\"dashboardExtraFilters()\"\n />\n </div>\n }\n </mt-card>\n </div>\n\n @if (informativeState().config.contentEnd) {\n <div\n [style.gridColumn]=\"\n slotGridSpan(informativeState().config.layout.endSpan)\n \"\n >\n <ng-container\n [ngTemplateOutlet]=\"informativeState().config.contentEnd\"\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\n />\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: DashboardViewer, selector: "mt-dashboard-viewer", inputs: ["isPage", "pageTitle", "backButton", "pageId", "dashboardData", "chartsData", "dialogsData", "filtersData", "extraFilters", "lookups", "statuses", "levelsSchema", "levelLogsResolver", "showFilters", "showFilterTrigger", "ignoreQueryFilter"], outputs: ["pageLoaded", "onBack", "chartClick"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
2047
2236
  }
2048
2237
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientListInformativeView, decorators: [{
2049
2238
  type: Component,
2050
- args: [{ selector: 'mt-client-list-informative-view', standalone: true, imports: [CommonModule, Card, DashboardViewer, SkeletonModule, TranslocoPipe], template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\r\n @if (informativeState().config.contentStart) {\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.startSpan)\r\n \"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"informativeState().config.contentStart\"\r\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\r\n />\r\n </div>\r\n }\r\n\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.tableSpan)\r\n \"\r\n >\r\n <mt-card>\r\n @if (\r\n informativeState().loading &&\r\n !informativeState().dashboardData?.charts?.length\r\n ) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"6rem\" />\r\n <p-skeleton height=\"12rem\" />\r\n <p-skeleton height=\"12rem\" />\r\n </div>\r\n } @else if (informativeState().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 {{ informativeState().error }}\r\n </div>\r\n } @else if (!informativeState().dashboardData?.charts?.length) {\r\n <div class=\"p-6 text-center text-gray-400\">\r\n {{\r\n \"components.clientComponents.list.noInformativeDashboard\"\r\n | transloco\r\n }}\r\n </div>\r\n } @else {\r\n <div class=\"min-h-[420px]\">\r\n <mt-dashboard-viewer\r\n [isPage]=\"false\"\r\n [showFilters]=\"false\"\r\n [dashboardData]=\"informativeState().dashboardData\"\r\n [extraFilters]=\"dashboardExtraFilters()\"\r\n />\r\n </div>\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (informativeState().config.contentEnd) {\r\n <div\r\n [style.gridColumn]=\"\r\n slotGridSpan(informativeState().config.layout.endSpan)\r\n \"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"informativeState().config.contentEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n" }]
2239
+ args: [{ selector: 'mt-client-list-informative-view', standalone: true, imports: [CommonModule, Card, DashboardViewer, SkeletonModule, TranslocoPipe], template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\n @if (informativeState().config.contentStart) {\n <div\n [style.gridColumn]=\"\n slotGridSpan(informativeState().config.layout.startSpan)\n \"\n >\n <ng-container\n [ngTemplateOutlet]=\"informativeState().config.contentStart\"\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\n />\n </div>\n }\n\n <div\n [style.gridColumn]=\"\n slotGridSpan(informativeState().config.layout.tableSpan)\n \"\n >\n <mt-card>\n @if (\n informativeState().loading &&\n !informativeState().dashboardData?.charts?.length\n ) {\n <div class=\"flex flex-col gap-3 py-3\">\n <p-skeleton height=\"6rem\" />\n <p-skeleton height=\"12rem\" />\n <p-skeleton height=\"12rem\" />\n </div>\n } @else if (informativeState().error) {\n <div\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\n >\n {{ informativeState().error }}\n </div>\n } @else if (!informativeState().dashboardData?.charts?.length) {\n <div class=\"p-6 text-center text-gray-400\">\n {{\n \"components.clientComponents.list.noInformativeDashboard\"\n | transloco\n }}\n </div>\n } @else {\n <div class=\"min-h-[420px]\">\n <mt-dashboard-viewer\n [isPage]=\"false\"\n [showFilters]=\"false\"\n [dashboardData]=\"informativeState().dashboardData\"\n [extraFilters]=\"dashboardExtraFilters()\"\n />\n </div>\n }\n </mt-card>\n </div>\n\n @if (informativeState().config.contentEnd) {\n <div\n [style.gridColumn]=\"\n slotGridSpan(informativeState().config.layout.endSpan)\n \"\n >\n <ng-container\n [ngTemplateOutlet]=\"informativeState().config.contentEnd\"\n [ngTemplateOutletContext]=\"templateContext(informativeState())\"\n />\n </div>\n }\n</div>\n" }]
2051
2240
  }], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }] } });
2052
2241
 
2053
2242
  function mergeRuntimeFilters(configured = [], runtime = {}, columns = []) {
@@ -2998,7 +3187,7 @@ class ClientList {
2998
3187
  this.state.setLoading(key, true);
2999
3188
  this.state.setError(key, null);
3000
3189
  const sub = this.api
3001
- .getCards(config.contextKey, filters, config.isPaginated ? skip : undefined, config.isPaginated ? take : undefined)
3190
+ .getCards(config.contextKey, filters, config.isPaginated ? skip : undefined, config.isPaginated ? take : undefined, config.hierarchy)
3002
3191
  .subscribe({
3003
3192
  next: (response) => {
3004
3193
  let hasLoadedData = false;
@@ -3095,15 +3284,18 @@ class ClientList {
3095
3284
  return (columnKeys ?? []).length > 0 ? 'override' : DEFAULT_MODE;
3096
3285
  }
3097
3286
  /**
3098
- * Descendant scope only survives where the backend accepts it: a table, with
3099
- * a usable root id. Anywhere else it is dropped rather than sent, because
3100
- * the backend answers an unsupported combination with a 400 that takes the
3101
- * whole list down — a cards area that happens to share a config object would
3102
- * otherwise render nothing at all.
3287
+ * Descendant scope only survives where it can actually be fetched: a table
3288
+ * or a cards board, with a usable root id. An informative dashboard drops it
3289
+ * rather than sending it, because the backend answers an unsupported
3290
+ * combination with a 400 that takes the whole list down.
3291
+ *
3292
+ * A cards board does not send it as `Card` either — the backend takes
3293
+ * descendant scope on `Table` only. `ClientListApiService.getCards` fetches
3294
+ * the hierarchy table and re-dresses it as cards.
3103
3295
  */
3104
3296
  resolveHierarchyScope(config, areaType) {
3105
3297
  const scope = config?.hierarchy;
3106
- if (!scope || areaType !== 'table')
3298
+ if (!scope || (areaType !== 'table' && areaType !== 'cards'))
3107
3299
  return null;
3108
3300
  const rootId = this.resolvePositiveInteger(scope.rootId);
3109
3301
  return rootId == null ? null : { ...scope, rootId };
@@ -3323,6 +3515,9 @@ class ClientList {
3323
3515
  config.contextKey ?? 'x',
3324
3516
  this.serializeFilters(filters),
3325
3517
  'Card',
3518
+ // Two boards on the same context and filters are still different
3519
+ // fetches when they are rooted at different records.
3520
+ this.serializeHierarchy(config.hierarchy),
3326
3521
  config.isPaginated ? skip : 0,
3327
3522
  config.isPaginated ? take : 0,
3328
3523
  ].join('|');
@@ -3382,7 +3577,7 @@ class ClientList {
3382
3577
  ClientListStateService,
3383
3578
  ClientListRuntimeActionsService,
3384
3579
  ClientListToolbarService,
3385
- ], 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", "ariaLabel"], 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"] }] });
3580
+ ], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\n @for (item of items(); track item.key) {\n <section class=\"flex flex-col gap-4\">\n @if (item.config.showHeader) {\n <div class=\"flex w-full items-center gap-2\">\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\n @if (item.config.collapse.enabled) {\n <mt-button\n variant=\"text\"\n severity=\"secondary\"\n [icon]=\"\n item.expanded\n ? item.config.collapse.collapseIcon\n : item.config.collapse.expandIcon\n \"\n (onClick)=\"toggleExpanded(item.key)\"\n />\n }\n <h3 class=\"m-0 text-lg font-semibold\">\n {{ item.title || item.moduleKey || defaultTitle(item) }}\n </h3>\n @if (item.config.headerStart) {\n <div class=\"flex items-center gap-2\">\n <ng-container\n [ngTemplateOutlet]=\"item.config.headerStart\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n </div>\n }\n </div>\n @if (item.config.headerEnd) {\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\n <ng-container\n [ngTemplateOutlet]=\"item.config.headerEnd\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n </div>\n }\n </div>\n }\n\n @if (item.expanded || !item.config.collapse.enabled) {\n @if (item.config.templateContent) {\n <ng-container\n [ngTemplateOutlet]=\"item.config.templateContent\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n } @else if (item.type === \"informative\") {\n <mt-client-list-informative-view [state]=\"item\" />\n } @else if (item.areaType === \"table\") {\n <mt-client-list-table-view\n [state]=\"item\"\n [rowActions]=\"rowActionsFor(item)\"\n [actionShape]=\"item.config.actionShape\"\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\n (rowClick)=\"onTableRowClick(item, $event)\"\n />\n } @else {\n <mt-client-list-cards-view\n [state]=\"item\"\n [rowActions]=\"rowActionsFor(item)\"\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\n (loadMore)=\"loadMoreCards(item.key)\"\n (cardClick)=\"onCardClick(item, $event)\"\n />\n }\n }\n </section>\n }\n</div>\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", "ariaLabel"], 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"] }] });
3386
3581
  }
3387
3582
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ClientList, decorators: [{
3388
3583
  type: Component,
@@ -3396,7 +3591,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
3396
3591
  ClientListStateService,
3397
3592
  ClientListRuntimeActionsService,
3398
3593
  ClientListToolbarService,
3399
- ], 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" }]
3594
+ ], template: "<div class=\"flex flex-col gap-4\">\n @for (item of items(); track item.key) {\n <section class=\"flex flex-col gap-4\">\n @if (item.config.showHeader) {\n <div class=\"flex w-full items-center gap-2\">\n <div class=\"flex min-w-0 flex-1 items-center gap-2\">\n @if (item.config.collapse.enabled) {\n <mt-button\n variant=\"text\"\n severity=\"secondary\"\n [icon]=\"\n item.expanded\n ? item.config.collapse.collapseIcon\n : item.config.collapse.expandIcon\n \"\n (onClick)=\"toggleExpanded(item.key)\"\n />\n }\n <h3 class=\"m-0 text-lg font-semibold\">\n {{ item.title || item.moduleKey || defaultTitle(item) }}\n </h3>\n @if (item.config.headerStart) {\n <div class=\"flex items-center gap-2\">\n <ng-container\n [ngTemplateOutlet]=\"item.config.headerStart\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n </div>\n }\n </div>\n @if (item.config.headerEnd) {\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\n <ng-container\n [ngTemplateOutlet]=\"item.config.headerEnd\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n </div>\n }\n </div>\n }\n\n @if (item.expanded || !item.config.collapse.enabled) {\n @if (item.config.templateContent) {\n <ng-container\n [ngTemplateOutlet]=\"item.config.templateContent\"\n [ngTemplateOutletContext]=\"templateContext(item)\"\n />\n } @else if (item.type === \"informative\") {\n <mt-client-list-informative-view [state]=\"item\" />\n } @else if (item.areaType === \"table\") {\n <mt-client-list-table-view\n [state]=\"item\"\n [rowActions]=\"rowActionsFor(item)\"\n [actionShape]=\"item.config.actionShape\"\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\n (rowClick)=\"onTableRowClick(item, $event)\"\n />\n } @else {\n <mt-client-list-cards-view\n [state]=\"item\"\n [rowActions]=\"rowActionsFor(item)\"\n [rowActionsLoadingFn]=\"rowActionsLoadingFnFor(item)\"\n (rowActionsRequested)=\"onRowActionsRequested(item, $event)\"\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\n (loadMore)=\"loadMoreCards(item.key)\"\n (cardClick)=\"onCardClick(item, $event)\"\n />\n }\n }\n </section>\n }\n</div>\n" }]
3400
3595
  }], 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"] }] } });
3401
3596
 
3402
3597
  /**