@masterteam/client-components 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,23 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, signal, computed, input, output, effect, untracked, Component } from '@angular/core';
2
+ import { inject, Injectable, signal, computed, input, output, Component, effect, untracked } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
4
  import { CommonModule } from '@angular/common';
5
- import { Table } from '@masterteam/components/table';
6
5
  import { Button } from '@masterteam/components/button';
7
- import * as i2 from 'primeng/skeleton';
8
- import { SkeletonModule } from 'primeng/skeleton';
9
6
  import { HttpClient, HttpParams } from '@angular/common/http';
10
7
  import { Card } from '@masterteam/components/card';
8
+ import { Table } from '@masterteam/components/table';
9
+ import * as i2 from 'primeng/skeleton';
10
+ import { SkeletonModule } from 'primeng/skeleton';
11
+ import { EntitiesPreview } from '@masterteam/components/entities';
12
+ import { DashboardViewer } from '@masterteam/dashboard-builder';
11
13
 
12
14
  class ClientListApiService {
13
15
  http = inject(HttpClient);
14
- baseUrl = 'data/tables';
16
+ tablesBaseUrl = 'data/tables';
17
+ cardsBaseUrl = 'data/modules/cards';
18
+ informativeBaseUrl = 'Modules';
15
19
  getRows(levelId, levelDataId, moduleId, query) {
16
- const endpoint = `${this.baseUrl}/level/${levelId}/level-data/${levelDataId}/module/${moduleId}/rows`;
20
+ const endpoint = `${this.tablesBaseUrl}/level/${levelId}/level-data/${levelDataId}/module/${moduleId}/rows`;
17
21
  let params = new HttpParams()
18
22
  .set('mode', query.mode)
19
23
  .set('skip', query.skip)
@@ -25,6 +29,18 @@ class ClientListApiService {
25
29
  params,
26
30
  });
27
31
  }
32
+ getCards(levelDataId, moduleId) {
33
+ const params = new HttpParams()
34
+ .set('moduleId', moduleId)
35
+ .set('levelDataId', levelDataId)
36
+ .append('displayAreas', 'card');
37
+ return this.http.get(this.cardsBaseUrl, {
38
+ params,
39
+ });
40
+ }
41
+ getInformativeDashboard(moduleId) {
42
+ return this.http.get(`${this.informativeBaseUrl}/${moduleId}/dashboard`);
43
+ }
28
44
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientListApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
29
45
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientListApiService, providedIn: 'root' });
30
46
  }
@@ -34,36 +50,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
34
50
  }] });
35
51
 
36
52
  class ClientListStateService {
37
- tablesByKey = signal({}, ...(ngDevMode ? [{ debugName: "tablesByKey" }] : []));
38
- tables = computed(() => Object.values(this.tablesByKey()).sort((a, b) => a.config.moduleId === b.config.moduleId
39
- ? a.config.levelDataId - b.config.levelDataId
40
- : a.config.moduleId - b.config.moduleId), ...(ngDevMode ? [{ debugName: "tables" }] : []));
53
+ itemsByKey = signal({}, ...(ngDevMode ? [{ debugName: "itemsByKey" }] : []));
54
+ items = computed(() => Object.values(this.itemsByKey()).sort((a, b) => a.config.moduleId === b.config.moduleId
55
+ ? (a.config.levelDataId ?? 0) - (b.config.levelDataId ?? 0)
56
+ : a.config.moduleId - b.config.moduleId), ...(ngDevMode ? [{ debugName: "items" }] : []));
41
57
  setConfigs(configs) {
58
+ const currentItems = this.itemsByKey();
42
59
  const next = {};
43
- configs.forEach((table) => {
44
- const current = this.tablesByKey()[table.key];
45
- next[table.key] = {
46
- ...table,
47
- loading: current?.loading ?? false,
48
- error: current?.error ?? null,
49
- title: current?.title ?? table.title,
50
- moduleKey: current?.moduleKey ?? table.moduleKey,
51
- columns: current?.columns ?? table.columns,
52
- rows: current?.rows ?? table.rows,
53
- skip: current?.skip ?? table.skip,
54
- take: current?.take ?? table.take,
55
- totalCount: current?.totalCount ?? table.totalCount,
56
- expanded: current?.expanded ?? table.expanded,
57
- };
60
+ configs.forEach((item) => {
61
+ next[item.key] = this.mergeItemState(item, currentItems[item.key]);
58
62
  });
59
- this.tablesByKey.set(next);
63
+ this.itemsByKey.set(next);
60
64
  }
61
65
  setLoading(key, loading) {
62
- const target = this.tablesByKey()[key];
66
+ const target = this.itemsByKey()[key];
63
67
  if (!target)
64
68
  return;
65
- this.tablesByKey.set({
66
- ...this.tablesByKey(),
69
+ this.itemsByKey.set({
70
+ ...this.itemsByKey(),
67
71
  [key]: {
68
72
  ...target,
69
73
  loading,
@@ -71,11 +75,11 @@ class ClientListStateService {
71
75
  });
72
76
  }
73
77
  setError(key, message) {
74
- const target = this.tablesByKey()[key];
78
+ const target = this.itemsByKey()[key];
75
79
  if (!target)
76
80
  return;
77
- this.tablesByKey.set({
78
- ...this.tablesByKey(),
81
+ this.itemsByKey.set({
82
+ ...this.itemsByKey(),
79
83
  [key]: {
80
84
  ...target,
81
85
  error: message,
@@ -83,23 +87,28 @@ class ClientListStateService {
83
87
  });
84
88
  }
85
89
  setRowsResult(key, response, config, skip, take) {
86
- const target = this.tablesByKey()[key];
87
- if (!target)
90
+ const target = this.itemsByKey()[key];
91
+ if (!target ||
92
+ target.type === 'informative' ||
93
+ target.areaType !== 'table') {
88
94
  return;
89
- const columns = this.toColumnDefs(response.columns);
90
- const rows = response.rows ?? [];
95
+ }
96
+ const visibleColumns = this.getVisibleColumns(response.columns);
97
+ const columns = this.toColumnDefs(visibleColumns);
98
+ const rows = this.toDisplayRows(response.rows ?? [], visibleColumns);
91
99
  const title = (config.title ?? '').trim() || response.moduleKey;
92
100
  const totalCount = config.isPaginated === false
93
101
  ? rows.length
94
102
  : (response.pagination?.totalCount ?? rows.length);
95
- this.tablesByKey.set({
96
- ...this.tablesByKey(),
103
+ this.itemsByKey.set({
104
+ ...this.itemsByKey(),
97
105
  [key]: {
98
106
  ...target,
99
107
  title,
100
108
  moduleKey: response.moduleKey ?? '',
101
109
  columns,
102
110
  rows,
111
+ cards: [],
103
112
  skip,
104
113
  take,
105
114
  totalCount,
@@ -107,27 +116,206 @@ class ClientListStateService {
107
116
  },
108
117
  });
109
118
  }
119
+ setCardsResult(key, response, config) {
120
+ const target = this.itemsByKey()[key];
121
+ if (!target ||
122
+ target.type === 'informative' ||
123
+ target.areaType !== 'cards') {
124
+ return;
125
+ }
126
+ const cards = this.toCards(response);
127
+ const firstModule = response.details?.[0]?.module;
128
+ const moduleKey = firstModule?.key ?? target.moduleKey ?? '';
129
+ const title = (config.title ?? '').trim() ||
130
+ firstModule?.name ||
131
+ moduleKey ||
132
+ target.title;
133
+ this.itemsByKey.set({
134
+ ...this.itemsByKey(),
135
+ [key]: {
136
+ ...target,
137
+ title,
138
+ moduleKey,
139
+ columns: [],
140
+ rows: [],
141
+ cards,
142
+ skip: 0,
143
+ take: 0,
144
+ totalCount: cards.length,
145
+ error: null,
146
+ },
147
+ });
148
+ }
149
+ setInformativeResult(key, response, config) {
150
+ const target = this.itemsByKey()[key];
151
+ if (!target || target.type !== 'informative')
152
+ return;
153
+ const moduleKey = target.moduleKey || `Module ${config.moduleId}`;
154
+ const title = (config.title ?? '').trim() || target.title || 'Informative Dashboard';
155
+ const dashboardData = response
156
+ ? this.toDashboardData(response, title)
157
+ : null;
158
+ const chartsCount = dashboardData?.charts?.length ?? 0;
159
+ this.itemsByKey.set({
160
+ ...this.itemsByKey(),
161
+ [key]: {
162
+ ...target,
163
+ title,
164
+ moduleKey,
165
+ columns: [],
166
+ rows: [],
167
+ cards: [],
168
+ skip: 0,
169
+ take: 0,
170
+ totalCount: chartsCount,
171
+ dashboardData,
172
+ error: null,
173
+ },
174
+ });
175
+ }
110
176
  toggleExpanded(key) {
111
- const target = this.tablesByKey()[key];
177
+ const target = this.itemsByKey()[key];
112
178
  if (!target)
113
179
  return;
114
- this.tablesByKey.set({
115
- ...this.tablesByKey(),
180
+ this.itemsByKey.set({
181
+ ...this.itemsByKey(),
116
182
  [key]: {
117
183
  ...target,
118
184
  expanded: !target.expanded,
119
185
  },
120
186
  });
121
187
  }
122
- toColumnDefs(columns) {
188
+ mergeItemState(item, existing) {
189
+ if (!existing ||
190
+ existing.type !== item.type ||
191
+ existing.areaType !== item.areaType) {
192
+ return item;
193
+ }
194
+ return {
195
+ ...item,
196
+ loading: existing.loading,
197
+ error: existing.error,
198
+ title: item.title || existing.title,
199
+ moduleKey: existing.moduleKey || item.moduleKey,
200
+ totalCount: existing.totalCount,
201
+ columns: existing.columns,
202
+ rows: existing.rows,
203
+ cards: existing.cards,
204
+ skip: existing.skip,
205
+ take: existing.take,
206
+ expanded: existing.expanded,
207
+ dashboardData: existing.dashboardData,
208
+ };
209
+ }
210
+ getVisibleColumns(columns) {
123
211
  return (columns ?? [])
124
212
  .filter((column) => column.isVisible)
125
- .sort((a, b) => a.order - b.order)
126
- .map((column) => ({
213
+ .sort((a, b) => a.order - b.order);
214
+ }
215
+ toColumnDefs(columns) {
216
+ return columns.map((column) => ({
217
+ ...column,
127
218
  key: column.columnKey,
128
- label: this.toLabel(column.columnKey),
219
+ label: column.name || this.toLabel(column.columnKey),
220
+ runtimeType: column.type,
221
+ type: 'entity',
129
222
  }));
130
223
  }
224
+ /**
225
+ * Enriches each flat API row so that every cell value becomes a
226
+ * RuntimeTableCell carrying both the original value and the full
227
+ * column metadata. The `Id` key is preserved as-is.
228
+ */
229
+ toDisplayRows(apiRows, columns) {
230
+ return apiRows.map((row) => {
231
+ const displayRow = { Id: row['Id'] };
232
+ for (const col of columns) {
233
+ displayRow[col.columnKey] = this.toCell(row[col.columnKey], col);
234
+ }
235
+ return displayRow;
236
+ });
237
+ }
238
+ toCell(value, column) {
239
+ return { ...column, value };
240
+ }
241
+ toCards(data) {
242
+ const details = data?.details ?? [];
243
+ if (details.length === 0) {
244
+ return [];
245
+ }
246
+ const configurationByKey = this.buildDisplayConfigurationLookup(data?.displayConfigurations ?? []);
247
+ return details.flatMap((detailsBlock) => (detailsBlock.data ?? []).map((card) => ({
248
+ ...card,
249
+ module: card.module ?? detailsBlock.module,
250
+ entities: this.mapEntities(card.properties ?? [], configurationByKey),
251
+ })));
252
+ }
253
+ toDashboardData(data, title) {
254
+ const charts = (data.chartLinks ?? []).map((link) => {
255
+ const configuration = link.configuration ?? {};
256
+ return {
257
+ ...configuration,
258
+ id: link.id,
259
+ chartComponentId: link.chartComponentId,
260
+ };
261
+ });
262
+ return {
263
+ page: {
264
+ id: data.moduleId,
265
+ name: { en: title, ar: title },
266
+ type: 'Dashboard',
267
+ dashboardConfig: {
268
+ ignoreQueryFilter: data.ignoreQueryFilter,
269
+ filters: data.filters,
270
+ versionNumber: data.versionNumber,
271
+ extraInfo: data.extraInfo,
272
+ },
273
+ },
274
+ charts,
275
+ dialogs: [],
276
+ filters: [],
277
+ };
278
+ }
279
+ buildDisplayConfigurationLookup(displayConfigurations) {
280
+ const configurationsByProperty = new Map();
281
+ for (const displayConfiguration of displayConfigurations) {
282
+ for (const propertyConfiguration of displayConfiguration.properties ??
283
+ []) {
284
+ if (propertyConfiguration.areaKey !== 'card' ||
285
+ !propertyConfiguration.propertyKey) {
286
+ continue;
287
+ }
288
+ configurationsByProperty.set(propertyConfiguration.propertyKey.toLowerCase(), propertyConfiguration);
289
+ }
290
+ }
291
+ return configurationsByProperty;
292
+ }
293
+ mapEntities(properties, configurationByKey) {
294
+ return properties
295
+ .map((property, index) => {
296
+ const propertyConfiguration = this.getPropertyConfiguration(property, configurationByKey);
297
+ return {
298
+ ...property,
299
+ rawValue: property.rawValue ?? undefined,
300
+ order: propertyConfiguration?.order ?? index + 1,
301
+ configuration: propertyConfiguration?.configuration ?? { size: 4 },
302
+ };
303
+ })
304
+ .sort((first, second) => (first.order ?? 0) - (second.order ?? 0));
305
+ }
306
+ getPropertyConfiguration(property, configurationByKey) {
307
+ const propertyKeys = [property.normalizedKey, property.key, property.name];
308
+ for (const propertyKey of propertyKeys) {
309
+ if (!propertyKey) {
310
+ continue;
311
+ }
312
+ const configuration = configurationByKey.get(propertyKey.toLowerCase());
313
+ if (configuration) {
314
+ return configuration;
315
+ }
316
+ }
317
+ return undefined;
318
+ }
131
319
  toLabel(key) {
132
320
  return key
133
321
  .replace(/([a-z])([A-Z])/g, '$1 $2')
@@ -144,7 +332,56 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
144
332
  type: Injectable
145
333
  }] });
146
334
 
335
+ const DEFAULT_GRID_COLUMNS$2 = 12;
336
+ class ClientListTableView {
337
+ state = input.required(...(ngDevMode ? [{ debugName: "state" }] : []));
338
+ rowActions = input([], ...(ngDevMode ? [{ debugName: "rowActions" }] : []));
339
+ lazyLoad = output();
340
+ table = computed(() => {
341
+ return this.state();
342
+ }, ...(ngDevMode ? [{ debugName: "table" }] : []));
343
+ gridTemplateColumns = `repeat(${DEFAULT_GRID_COLUMNS$2}, minmax(0, 1fr))`;
344
+ slotGridSpan(span) {
345
+ return `span ${span} / span ${span}`;
346
+ }
347
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientListTableView, deps: [], target: i0.ɵɵFactoryTarget.Component });
348
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", 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 } }, outputs: { lazyLoad: "lazyLoad" }, 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]=\"{\r\n $implicit: table(),\r\n table: table(),\r\n }\"\r\n />\r\n </div>\r\n }\r\n\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.tableSpan)\">\r\n <mt-card>\r\n @if (table().loading && table().rows.length === 0) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n </div>\r\n } @else if (table().error) {\r\n <div\r\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\r\n >\r\n {{ table().error }}\r\n </div>\r\n } @else {\r\n <mt-table\r\n [data]=\"table().rows\"\r\n [columns]=\"table().columns\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"table().loading\"\r\n [lazy]=\"table().config.isPaginated\"\r\n [lazyTotalRecords]=\"table().totalCount\"\r\n [pageSize]=\"table().config.isPaginated ? table().take : 10\"\r\n (lazyLoad)=\"lazyLoad.emit($event)\"\r\n />\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (table().config.contentEnd) {\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.endSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table().config.contentEnd\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: table(),\r\n table: table(),\r\n }\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "showFilters", "loading", "updating", "lazy", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "exportable", "exportFilename", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "pageSize", "currentPage", "first", "filterTerm"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }] });
349
+ }
350
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientListTableView, decorators: [{
351
+ type: Component,
352
+ args: [{ selector: 'mt-client-list-table-view', standalone: true, imports: [CommonModule, Card, Table, SkeletonModule], template: "<div class=\"grid gap-4\" [style.gridTemplateColumns]=\"gridTemplateColumns\">\r\n @if (table().config.contentStart) {\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.startSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table().config.contentStart\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: table(),\r\n table: table(),\r\n }\"\r\n />\r\n </div>\r\n }\r\n\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.tableSpan)\">\r\n <mt-card>\r\n @if (table().loading && table().rows.length === 0) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n </div>\r\n } @else if (table().error) {\r\n <div\r\n class=\"rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\"\r\n >\r\n {{ table().error }}\r\n </div>\r\n } @else {\r\n <mt-table\r\n [data]=\"table().rows\"\r\n [columns]=\"table().columns\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"table().loading\"\r\n [lazy]=\"table().config.isPaginated\"\r\n [lazyTotalRecords]=\"table().totalCount\"\r\n [pageSize]=\"table().config.isPaginated ? table().take : 10\"\r\n (lazyLoad)=\"lazyLoad.emit($event)\"\r\n />\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (table().config.contentEnd) {\r\n <div [style.gridColumn]=\"slotGridSpan(table().config.layout.endSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table().config.contentEnd\"\r\n [ngTemplateOutletContext]=\"{\r\n $implicit: table(),\r\n table: table(),\r\n }\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n" }]
353
+ }], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }] } });
354
+
355
+ class ClientListCardsView {
356
+ state = input.required(...(ngDevMode ? [{ debugName: "state" }] : []));
357
+ cardsState = computed(() => this.state(), ...(ngDevMode ? [{ debugName: "cardsState" }] : []));
358
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientListCardsView, deps: [], target: i0.ɵɵFactoryTarget.Component });
359
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: ClientListCardsView, isStandalone: true, selector: "mt-client-list-cards-view", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@if (cardsState().loading && cardsState().cards.length === 0) {\r\n <div class=\"grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3\">\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <p-skeleton height=\"20rem\" borderRadius=\"1rem\" />\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 (cardsState().cards.length === 0) {\r\n <div class=\"p-6 text-center text-gray-400\">No cards found.</div>\r\n} @else {\r\n <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3\">\r\n @for (card of cardsState().cards; track card.id) {\r\n <mt-card class=\"cursor-pointer p-3\">\r\n <ng-template #headless>\r\n <mt-entities-preview [entities]=\"card.entities\" />\r\n </ng-template>\r\n </mt-card>\r\n }\r\n </div>\r\n}\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: EntitiesPreview, selector: "mt-entities-preview", inputs: ["entities"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }] });
360
+ }
361
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientListCardsView, decorators: [{
362
+ type: Component,
363
+ args: [{ selector: 'mt-client-list-cards-view', standalone: true, imports: [CommonModule, Card, EntitiesPreview, SkeletonModule], template: "@if (cardsState().loading && cardsState().cards.length === 0) {\r\n <div class=\"grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3\">\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <p-skeleton height=\"20rem\" borderRadius=\"1rem\" />\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 (cardsState().cards.length === 0) {\r\n <div class=\"p-6 text-center text-gray-400\">No cards found.</div>\r\n} @else {\r\n <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-3\">\r\n @for (card of cardsState().cards; track card.id) {\r\n <mt-card class=\"cursor-pointer p-3\">\r\n <ng-template #headless>\r\n <mt-entities-preview [entities]=\"card.entities\" />\r\n </ng-template>\r\n </mt-card>\r\n }\r\n </div>\r\n}\r\n" }]
364
+ }], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }] } });
365
+
366
+ const DEFAULT_GRID_COLUMNS$1 = 12;
367
+ class ClientListInformativeView {
368
+ state = input.required(...(ngDevMode ? [{ debugName: "state" }] : []));
369
+ informativeState = computed(() => this.state(), ...(ngDevMode ? [{ debugName: "informativeState" }] : []));
370
+ gridTemplateColumns = `repeat(${DEFAULT_GRID_COLUMNS$1}, minmax(0, 1fr))`;
371
+ slotGridSpan(span) {
372
+ return `span ${span} / span ${span}`;
373
+ }
374
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientListInformativeView, deps: [], target: i0.ɵɵFactoryTarget.Component });
375
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", 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]=\"{\r\n $implicit: informativeState(),\r\n table: informativeState(),\r\n }\"\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 No informative dashboard found.\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 />\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]=\"{\r\n $implicit: informativeState(),\r\n table: informativeState(),\r\n }\"\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", "showFilters"], outputs: ["pageLoaded", "onBack", "chartClick"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }] });
376
+ }
377
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientListInformativeView, decorators: [{
378
+ type: Component,
379
+ args: [{ selector: 'mt-client-list-informative-view', standalone: true, imports: [CommonModule, Card, DashboardViewer, SkeletonModule], 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]=\"{\r\n $implicit: informativeState(),\r\n table: informativeState(),\r\n }\"\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 No informative dashboard found.\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 />\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]=\"{\r\n $implicit: informativeState(),\r\n table: informativeState(),\r\n }\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n" }]
380
+ }], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }] } });
381
+
382
+ const DEFAULT_AREA_TYPE = 'table';
147
383
  const DEFAULT_MODE = 'auto';
384
+ const DEFAULT_TYPE = 'form';
148
385
  const DEFAULT_SERVER_PAGE_SIZE = 50;
149
386
  const LOCAL_PAGE_SIZE_SOURCE = 100000;
150
387
  const DEFAULT_COLLAPSE_ICON = 'arrow.chevron-up';
@@ -158,101 +395,174 @@ class ClientList {
158
395
  defaultTake = input(DEFAULT_SERVER_PAGE_SIZE, ...(ngDevMode ? [{ debugName: "defaultTake" }] : []));
159
396
  loaded = output();
160
397
  errored = output();
161
- tables = this.state.tables;
162
- gridTemplateColumns = `repeat(${DEFAULT_GRID_COLUMNS}, minmax(0, 1fr))`;
398
+ items = this.state.items;
163
399
  subscriptions = new Map();
164
400
  inFlightRequestSignatures = new Map();
165
401
  fulfilledRequestSignatures = new Map();
166
402
  constructor() {
167
403
  effect(() => {
168
404
  const configs = this.configurations();
169
- untracked(() => this.configureTables(configs));
405
+ untracked(() => this.configureItems(configs));
170
406
  untracked(() => {
171
- this.state.tables().forEach((table) => {
172
- this.loadTable(table.key, table.config, table.skip, table.take);
407
+ this.state.items().forEach((item) => {
408
+ this.loadItem(item);
173
409
  });
174
410
  });
175
411
  });
176
412
  }
177
- onLazyLoad(tableKey, event) {
178
- const table = this.state.tablesByKey()[tableKey];
179
- if (!table || !table.config.isPaginated)
413
+ onLazyLoad(itemKey, event) {
414
+ const item = this.state.itemsByKey()[itemKey];
415
+ if (!item ||
416
+ item.type === 'informative' ||
417
+ item.areaType !== 'table' ||
418
+ !item.config.isPaginated) {
180
419
  return;
181
- const take = Number(event.pageSize ?? table.take);
420
+ }
421
+ const take = Number(event.pageSize ?? item.take);
182
422
  const skip = Number(event.first ?? 0);
183
- this.loadTable(table.key, table.config, skip, take);
423
+ this.loadTable(item.key, item.config, skip, take);
184
424
  }
185
- reload(tableKey) {
186
- if (tableKey) {
187
- this.reloadByKey(tableKey);
425
+ reload(itemKey) {
426
+ if (itemKey) {
427
+ this.reloadByKey(itemKey);
188
428
  return;
189
429
  }
190
- this.tables().forEach((table) => {
191
- this.loadTable(table.key, table.config, table.skip, table.take, true);
430
+ this.items().forEach((item) => {
431
+ this.loadItem(item, true);
192
432
  });
193
433
  }
194
- reloadByKey(tableKey) {
195
- const table = this.state.tablesByKey()[tableKey];
196
- if (!table)
434
+ reloadByKey(itemKey) {
435
+ const item = this.state.itemsByKey()[itemKey];
436
+ if (!item)
197
437
  return;
198
- this.loadTable(table.key, table.config, table.skip, table.take, true);
438
+ this.loadItem(item, true);
199
439
  }
200
440
  toggleExpanded(key) {
201
441
  this.state.toggleExpanded(key);
202
442
  }
203
- slotGridSpan(span) {
204
- return `span ${span} / span ${span}`;
205
- }
206
- templateContext(table) {
443
+ templateContext(item) {
207
444
  return {
208
- $implicit: table,
209
- table,
445
+ $implicit: item,
446
+ table: item,
210
447
  };
211
448
  }
449
+ defaultTitle(item) {
450
+ if (item.type === 'informative') {
451
+ return 'Informative Dashboard';
452
+ }
453
+ return item.areaType === 'cards' ? 'Cards' : 'Table';
454
+ }
212
455
  ngOnDestroy() {
213
456
  this.subscriptions.forEach((sub) => sub.unsubscribe());
214
457
  this.subscriptions.clear();
215
458
  }
216
- configureTables(configs) {
217
- const previousTables = this.state.tablesByKey();
459
+ configureItems(configs) {
460
+ const previousItems = this.state.itemsByKey();
218
461
  const hasMultipleConfigs = (configs ?? []).length > 1;
219
462
  const normalized = (configs ?? []).map((config, index) => {
220
- const mode = this.resolveMode(config.mode, config.columnKeys);
221
- const isPaginated = config.isPaginated ?? true;
222
- const take = isPaginated
223
- ? (config.take ?? this.defaultTake())
224
- : LOCAL_PAGE_SIZE_SOURCE;
463
+ const type = this.resolveType(config.type);
464
+ const isInformative = type === 'informative';
465
+ const areaType = isInformative
466
+ ? DEFAULT_AREA_TYPE
467
+ : this.resolveAreaType(config.areaType);
468
+ const mode = isInformative
469
+ ? DEFAULT_MODE
470
+ : this.resolveMode(config.mode, config.columnKeys);
471
+ const isPaginated = !isInformative &&
472
+ areaType === 'table' &&
473
+ (config.isPaginated ?? true);
474
+ const take = !isInformative && areaType === 'table'
475
+ ? isPaginated
476
+ ? (config.take ?? this.defaultTake())
477
+ : LOCAL_PAGE_SIZE_SOURCE
478
+ : 0;
225
479
  const collapseEnabled = (config.collapse?.enabled ?? true) && hasMultipleConfigs;
226
480
  const layout = this.resolveLayout(config);
227
481
  const key = config.key ??
228
482
  `${config.levelId ?? 'x'}-${config.levelDataId ?? 'x'}-${config.moduleId ?? 'x'}-${index}`;
229
- const current = previousTables[key];
483
+ const existing = previousItems[key];
484
+ const normalizedConfig = this.toNormalizedConfig(config, type, areaType, mode, isPaginated, take, collapseEnabled, layout);
485
+ if (isInformative) {
486
+ return {
487
+ key,
488
+ config: normalizedConfig,
489
+ type: 'informative',
490
+ areaType: 'table',
491
+ loading: false,
492
+ error: null,
493
+ title: config.title?.trim() || '',
494
+ moduleKey: '',
495
+ totalCount: 0,
496
+ columns: [],
497
+ rows: [],
498
+ cards: [],
499
+ skip: 0,
500
+ take: 0,
501
+ expanded: existing?.expanded ?? config.collapse?.expandedByDefault ?? true,
502
+ dashboardData: existing?.dashboardData ?? null,
503
+ };
504
+ }
505
+ if (areaType === 'cards') {
506
+ return {
507
+ key,
508
+ config: normalizedConfig,
509
+ type: 'form',
510
+ areaType: 'cards',
511
+ loading: false,
512
+ error: null,
513
+ title: config.title?.trim() || '',
514
+ moduleKey: '',
515
+ totalCount: 0,
516
+ columns: [],
517
+ rows: [],
518
+ cards: [],
519
+ skip: 0,
520
+ take: 0,
521
+ expanded: existing?.expanded ?? config.collapse?.expandedByDefault ?? true,
522
+ dashboardData: null,
523
+ };
524
+ }
230
525
  return {
231
526
  key,
232
- config: this.toNormalizedConfig(config, mode, isPaginated, take, collapseEnabled, layout),
527
+ config: normalizedConfig,
528
+ type: 'form',
529
+ areaType: 'table',
233
530
  loading: false,
234
531
  error: null,
235
532
  title: config.title?.trim() || '',
236
533
  moduleKey: '',
534
+ totalCount: 0,
237
535
  columns: [],
238
536
  rows: [],
239
- skip: current?.skip ?? 0,
537
+ cards: [],
538
+ skip: existing?.areaType === 'table' ? existing.skip : 0,
240
539
  take,
241
- totalCount: 0,
242
- expanded: config.collapse?.expandedByDefault ?? true,
540
+ expanded: existing?.expanded ?? config.collapse?.expandedByDefault ?? true,
541
+ dashboardData: null,
243
542
  };
244
543
  });
245
544
  this.state.setConfigs(normalized);
246
- this.cleanupStaleResources(new Set(normalized.map((table) => table.key)));
545
+ this.cleanupStaleResources(new Set(normalized.map((item) => item.key)));
546
+ }
547
+ loadItem(item, force = false) {
548
+ if (item.type === 'informative') {
549
+ this.loadInformative(item.key, item.config, force);
550
+ return;
551
+ }
552
+ if (item.areaType === 'cards') {
553
+ this.loadCards(item.key, item.config, force);
554
+ return;
555
+ }
556
+ this.loadTable(item.key, item.config, item.skip, item.take, force);
247
557
  }
248
558
  loadTable(key, config, skip, take, force = false) {
249
559
  if (!this.hasValidIdentifiers(config)) {
250
560
  this.state.setLoading(key, false);
251
- this.state.setError(key, 'Missing identifiers: levelId, levelDataId and moduleId are required');
561
+ this.state.setError(key, this.getMissingIdentifiersMessage(config));
252
562
  return;
253
563
  }
254
- const query = this.toQuery(config, skip, take);
255
- const requestSignature = this.createRequestSignature(config, query);
564
+ const query = this.toTableQuery(config, skip, take);
565
+ const requestSignature = this.createTableRequestSignature(config, query);
256
566
  if (force) {
257
567
  this.inFlightRequestSignatures.delete(key);
258
568
  this.fulfilledRequestSignatures.delete(key);
@@ -292,16 +602,117 @@ class ClientList {
292
602
  });
293
603
  this.subscriptions.set(key, sub);
294
604
  }
605
+ loadCards(key, config, force = false) {
606
+ if (!this.hasValidIdentifiers(config)) {
607
+ this.state.setLoading(key, false);
608
+ this.state.setError(key, this.getMissingIdentifiersMessage(config));
609
+ return;
610
+ }
611
+ const requestSignature = this.createCardsRequestSignature(config);
612
+ if (force) {
613
+ this.inFlightRequestSignatures.delete(key);
614
+ this.fulfilledRequestSignatures.delete(key);
615
+ }
616
+ else if (this.inFlightRequestSignatures.get(key) === requestSignature ||
617
+ this.fulfilledRequestSignatures.get(key) === requestSignature) {
618
+ return;
619
+ }
620
+ this.subscriptions.get(key)?.unsubscribe();
621
+ this.inFlightRequestSignatures.set(key, requestSignature);
622
+ this.state.setLoading(key, true);
623
+ this.state.setError(key, null);
624
+ const sub = this.api
625
+ .getCards(config.levelDataId, config.moduleId)
626
+ .subscribe({
627
+ next: (response) => {
628
+ if (response.data) {
629
+ this.state.setCardsResult(key, response.data, config);
630
+ this.fulfilledRequestSignatures.set(key, requestSignature);
631
+ this.loaded.emit(key);
632
+ }
633
+ else {
634
+ const message = response.message ?? 'Failed to load cards';
635
+ this.state.setError(key, message);
636
+ this.errored.emit({ key, message });
637
+ }
638
+ this.state.setLoading(key, false);
639
+ this.inFlightRequestSignatures.delete(key);
640
+ },
641
+ error: (error) => {
642
+ const message = error?.error?.message ?? error?.message ?? 'Failed to load cards';
643
+ this.state.setError(key, message);
644
+ this.state.setLoading(key, false);
645
+ this.inFlightRequestSignatures.delete(key);
646
+ this.errored.emit({ key, message });
647
+ },
648
+ });
649
+ this.subscriptions.set(key, sub);
650
+ }
651
+ loadInformative(key, config, force = false) {
652
+ if (!this.hasValidIdentifiers(config)) {
653
+ this.state.setLoading(key, false);
654
+ this.state.setError(key, this.getMissingIdentifiersMessage(config));
655
+ return;
656
+ }
657
+ const requestSignature = this.createInformativeRequestSignature(config);
658
+ if (force) {
659
+ this.inFlightRequestSignatures.delete(key);
660
+ this.fulfilledRequestSignatures.delete(key);
661
+ }
662
+ else if (this.inFlightRequestSignatures.get(key) === requestSignature ||
663
+ this.fulfilledRequestSignatures.get(key) === requestSignature) {
664
+ return;
665
+ }
666
+ this.subscriptions.get(key)?.unsubscribe();
667
+ this.inFlightRequestSignatures.set(key, requestSignature);
668
+ this.state.setLoading(key, true);
669
+ this.state.setError(key, null);
670
+ const sub = this.api.getInformativeDashboard(config.moduleId).subscribe({
671
+ next: (response) => {
672
+ this.state.setInformativeResult(key, response.data ?? null, config);
673
+ this.fulfilledRequestSignatures.set(key, requestSignature);
674
+ this.loaded.emit(key);
675
+ this.state.setLoading(key, false);
676
+ this.inFlightRequestSignatures.delete(key);
677
+ },
678
+ error: (error) => {
679
+ if (error?.status === 404) {
680
+ this.state.setInformativeResult(key, null, config);
681
+ this.fulfilledRequestSignatures.set(key, requestSignature);
682
+ this.loaded.emit(key);
683
+ this.state.setLoading(key, false);
684
+ this.inFlightRequestSignatures.delete(key);
685
+ return;
686
+ }
687
+ const message = error?.error?.message ??
688
+ error?.message ??
689
+ 'Failed to load informative dashboard';
690
+ this.state.setError(key, message);
691
+ this.state.setLoading(key, false);
692
+ this.inFlightRequestSignatures.delete(key);
693
+ this.errored.emit({ key, message });
694
+ },
695
+ });
696
+ this.subscriptions.set(key, sub);
697
+ }
698
+ resolveAreaType(areaType) {
699
+ return areaType === 'cards' ? 'cards' : DEFAULT_AREA_TYPE;
700
+ }
701
+ resolveType(type) {
702
+ return type === 'informative' ? 'informative' : DEFAULT_TYPE;
703
+ }
295
704
  resolveMode(mode, columnKeys) {
296
705
  if (mode !== 'override')
297
706
  return DEFAULT_MODE;
298
707
  return (columnKeys ?? []).length > 0 ? 'override' : DEFAULT_MODE;
299
708
  }
300
- toNormalizedConfig(config, mode, isPaginated, take, collapseEnabled, layout) {
709
+ toNormalizedConfig(config, type, areaType, mode, isPaginated, take, collapseEnabled, layout) {
301
710
  return {
302
711
  levelId: config.levelId,
303
712
  levelDataId: config.levelDataId,
304
713
  moduleId: config.moduleId,
714
+ type,
715
+ areaType,
305
716
  mode,
306
717
  isPaginated,
307
718
  take,
@@ -311,6 +722,7 @@ class ClientList {
311
722
  headerEnd: config.headerEnd,
312
723
  contentStart: config.contentStart,
313
724
  contentEnd: config.contentEnd,
725
+ rowActions: config.rowActions ?? [],
314
726
  collapse: {
315
727
  enabled: collapseEnabled,
316
728
  expandedByDefault: config.collapse?.expandedByDefault ?? true,
@@ -356,7 +768,7 @@ class ClientList {
356
768
  }
357
769
  }
358
770
  }
359
- toQuery(config, skip, take) {
771
+ toTableQuery(config, skip, take) {
360
772
  return {
361
773
  mode: config.mode,
362
774
  columnKeys: config.mode === 'override' ? config.columnKeys : undefined,
@@ -364,8 +776,10 @@ class ClientList {
364
776
  take: config.isPaginated ? take : LOCAL_PAGE_SIZE_SOURCE,
365
777
  };
366
778
  }
367
- createRequestSignature(config, query) {
779
+ createTableRequestSignature(config, query) {
368
780
  return [
781
+ config.type,
782
+ config.areaType,
369
783
  config.levelId,
370
784
  config.levelDataId,
371
785
  config.moduleId,
@@ -375,20 +789,53 @@ class ClientList {
375
789
  ...(query.columnKeys ?? []),
376
790
  ].join('|');
377
791
  }
792
+ createCardsRequestSignature(config) {
793
+ return [
794
+ config.type,
795
+ config.areaType,
796
+ config.levelDataId,
797
+ config.moduleId,
798
+ 'card',
799
+ ].join('|');
800
+ }
801
+ createInformativeRequestSignature(config) {
802
+ return [config.type, config.moduleId, 'dashboard'].join('|');
803
+ }
378
804
  hasValidIdentifiers(config) {
805
+ if (config.type === 'informative') {
806
+ return Number.isFinite(config.moduleId) && config.moduleId > 0;
807
+ }
808
+ const hasLevelDataId = Number.isFinite(config.levelDataId) && config.levelDataId > 0;
809
+ const hasModuleId = Number.isFinite(config.moduleId) && config.moduleId > 0;
810
+ if (config.areaType === 'cards') {
811
+ return hasLevelDataId && hasModuleId;
812
+ }
379
813
  return (Number.isFinite(config.levelId) &&
380
814
  config.levelId > 0 &&
381
- Number.isFinite(config.levelDataId) &&
382
- config.levelDataId > 0 &&
383
- Number.isFinite(config.moduleId) &&
384
- config.moduleId > 0);
815
+ hasLevelDataId &&
816
+ hasModuleId);
817
+ }
818
+ getMissingIdentifiersMessage(config) {
819
+ if (config.type === 'informative') {
820
+ return 'Missing identifiers: moduleId is required';
821
+ }
822
+ if (config.areaType === 'cards') {
823
+ return 'Missing identifiers: levelDataId and moduleId are required';
824
+ }
825
+ return 'Missing identifiers: levelId, levelDataId and moduleId are required';
385
826
  }
386
827
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientList, deps: [], target: i0.ɵɵFactoryTarget.Component });
387
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: ClientList, isStandalone: true, selector: "mt-client-list", inputs: { configurations: { classPropertyName: "configurations", publicName: "configurations", isSignal: true, isRequired: true, transformFunction: null }, defaultTake: { classPropertyName: "defaultTake", publicName: "defaultTake", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", errored: "errored" }, providers: [ClientListStateService], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\r\n @for (table of tables(); track table.key) {\r\n <section class=\"flex flex-col gap-4\">\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 (table.config.collapse.enabled) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n [icon]=\"\r\n table.expanded\r\n ? table.config.collapse.collapseIcon\r\n : table.config.collapse.expandIcon\r\n \"\r\n (onClick)=\"toggleExpanded(table.key)\"\r\n />\r\n }\r\n <h3 class=\"m-0 text-lg font-bold\">\r\n {{ table.title || table.moduleKey || \"Table\" }}\r\n </h3>\r\n @if (table.config.headerStart) {\r\n <div class=\"flex items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table.config.headerStart\"\r\n [ngTemplateOutletContext]=\"templateContext(table)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n @if (table.config.headerEnd) {\r\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table.config.headerEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(table)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (table.expanded || !table.config.collapse.enabled) {\r\n <div\r\n class=\"grid gap-4\"\r\n [style.gridTemplateColumns]=\"gridTemplateColumns\"\r\n >\r\n @if (table.config.contentStart) {\r\n <div\r\n [style.gridColumn]=\"slotGridSpan(table.config.layout.startSpan)\"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"table.config.contentStart\"\r\n [ngTemplateOutletContext]=\"templateContext(table)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div [style.gridColumn]=\"slotGridSpan(table.config.layout.tableSpan)\">\r\n <mt-card>\r\n @if (table.loading && table.rows.length === 0) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n </div>\r\n } @else if (table.error) {\r\n <div\r\n class=\"p-3 rounded-lg bg-red-50 text-red-700 border border-red-200 text-sm\"\r\n >\r\n {{ table.error }}\r\n </div>\r\n } @else {\r\n <mt-table\r\n [data]=\"table.rows\"\r\n [columns]=\"table.columns\"\r\n [loading]=\"table.loading\"\r\n [lazy]=\"table.config.isPaginated\"\r\n [lazyTotalRecords]=\"table.totalCount\"\r\n [pageSize]=\"table.config.isPaginated ? table.take : 10\"\r\n (lazyLoad)=\"onLazyLoad(table.key, $event)\"\r\n />\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (table.config.contentEnd) {\r\n <div [style.gridColumn]=\"slotGridSpan(table.config.layout.endSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table.config.contentEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(table)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\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: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "showFilters", "loading", "updating", "lazy", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "exportable", "exportFilename", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "pageSize", "currentPage", "first", "filterTerm"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }] });
828
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: ClientList, isStandalone: true, selector: "mt-client-list", inputs: { configurations: { classPropertyName: "configurations", publicName: "configurations", isSignal: true, isRequired: true, transformFunction: null }, defaultTake: { classPropertyName: "defaultTake", publicName: "defaultTake", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", errored: "errored" }, providers: [ClientListStateService], ngImport: i0, template: "<div class=\"flex flex-col gap-4\">\r\n @for (item of items(); track item.key) {\r\n <section class=\"flex flex-col gap-4\">\r\n <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-bold\">\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 @if (item.expanded || !item.config.collapse.enabled) {\r\n @if (item.type === \"informative\") {\r\n <mt-client-list-informative-view [state]=\"item\" />\r\n } @else if (item.areaType === \"table\") {\r\n <mt-client-list-table-view\r\n [state]=\"item\"\r\n [rowActions]=\"item.config.rowActions\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n />\r\n } @else {\r\n <mt-client-list-cards-view [state]=\"item\" />\r\n }\r\n }\r\n </section>\r\n }\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ClientListTableView, selector: "mt-client-list-table-view", inputs: ["state", "rowActions"], outputs: ["lazyLoad"] }, { kind: "component", type: ClientListCardsView, selector: "mt-client-list-cards-view", inputs: ["state"] }, { kind: "component", type: ClientListInformativeView, selector: "mt-client-list-informative-view", inputs: ["state"] }] });
388
829
  }
389
830
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientList, decorators: [{
390
831
  type: Component,
391
- args: [{ selector: 'mt-client-list', standalone: true, imports: [CommonModule, Card, Table, Button, SkeletonModule], providers: [ClientListStateService], template: "<div class=\"flex flex-col gap-4\">\r\n @for (table of tables(); track table.key) {\r\n <section class=\"flex flex-col gap-4\">\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 (table.config.collapse.enabled) {\r\n <mt-button\r\n variant=\"text\"\r\n severity=\"secondary\"\r\n [icon]=\"\r\n table.expanded\r\n ? table.config.collapse.collapseIcon\r\n : table.config.collapse.expandIcon\r\n \"\r\n (onClick)=\"toggleExpanded(table.key)\"\r\n />\r\n }\r\n <h3 class=\"m-0 text-lg font-bold\">\r\n {{ table.title || table.moduleKey || \"Table\" }}\r\n </h3>\r\n @if (table.config.headerStart) {\r\n <div class=\"flex items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table.config.headerStart\"\r\n [ngTemplateOutletContext]=\"templateContext(table)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n @if (table.config.headerEnd) {\r\n <div class=\"ml-auto flex shrink-0 items-center gap-2\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table.config.headerEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(table)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (table.expanded || !table.config.collapse.enabled) {\r\n <div\r\n class=\"grid gap-4\"\r\n [style.gridTemplateColumns]=\"gridTemplateColumns\"\r\n >\r\n @if (table.config.contentStart) {\r\n <div\r\n [style.gridColumn]=\"slotGridSpan(table.config.layout.startSpan)\"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"table.config.contentStart\"\r\n [ngTemplateOutletContext]=\"templateContext(table)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div [style.gridColumn]=\"slotGridSpan(table.config.layout.tableSpan)\">\r\n <mt-card>\r\n @if (table.loading && table.rows.length === 0) {\r\n <div class=\"flex flex-col gap-3 py-3\">\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n <p-skeleton height=\"3rem\" />\r\n </div>\r\n } @else if (table.error) {\r\n <div\r\n class=\"p-3 rounded-lg bg-red-50 text-red-700 border border-red-200 text-sm\"\r\n >\r\n {{ table.error }}\r\n </div>\r\n } @else {\r\n <mt-table\r\n [data]=\"table.rows\"\r\n [columns]=\"table.columns\"\r\n [loading]=\"table.loading\"\r\n [lazy]=\"table.config.isPaginated\"\r\n [lazyTotalRecords]=\"table.totalCount\"\r\n [pageSize]=\"table.config.isPaginated ? table.take : 10\"\r\n (lazyLoad)=\"onLazyLoad(table.key, $event)\"\r\n />\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n @if (table.config.contentEnd) {\r\n <div [style.gridColumn]=\"slotGridSpan(table.config.layout.endSpan)\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"table.config.contentEnd\"\r\n [ngTemplateOutletContext]=\"templateContext(table)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n</div>\r\n" }]
832
+ args: [{ selector: 'mt-client-list', standalone: true, imports: [
833
+ CommonModule,
834
+ Button,
835
+ ClientListTableView,
836
+ ClientListCardsView,
837
+ ClientListInformativeView,
838
+ ], providers: [ClientListStateService], template: "<div class=\"flex flex-col gap-4\">\r\n @for (item of items(); track item.key) {\r\n <section class=\"flex flex-col gap-4\">\r\n <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-bold\">\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 @if (item.expanded || !item.config.collapse.enabled) {\r\n @if (item.type === \"informative\") {\r\n <mt-client-list-informative-view [state]=\"item\" />\r\n } @else if (item.areaType === \"table\") {\r\n <mt-client-list-table-view\r\n [state]=\"item\"\r\n [rowActions]=\"item.config.rowActions\"\r\n (lazyLoad)=\"onLazyLoad(item.key, $event)\"\r\n />\r\n } @else {\r\n <mt-client-list-cards-view [state]=\"item\" />\r\n }\r\n }\r\n </section>\r\n }\r\n</div>\r\n" }]
392
839
  }], 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"] }] } });
393
840
 
394
841
  /**