@acorex-platform/module-report-management-client 0.0.37 → 0.0.46

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.
Files changed (48) hide show
  1. package/fesm2022/acorex-platform-module-report-management-client-acorex-platform-module-report-management-client-D5dCG62j.mjs +1545 -0
  2. package/fesm2022/acorex-platform-module-report-management-client-acorex-platform-module-report-management-client-D5dCG62j.mjs.map +1 -0
  3. package/fesm2022/acorex-platform-module-report-management-client-contracts-entities-query-snapshot-policy.mjs +14 -0
  4. package/fesm2022/acorex-platform-module-report-management-client-contracts-entities-query-snapshot-policy.mjs.map +1 -1
  5. package/fesm2022/acorex-platform-module-report-management-client-contracts-entities-query-snapshot.mjs +14 -0
  6. package/fesm2022/acorex-platform-module-report-management-client-contracts-entities-query-snapshot.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-module-report-management-client-contracts-entities-report-category.mjs +13 -0
  8. package/fesm2022/acorex-platform-module-report-management-client-contracts-entities-report-category.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-module-report-management-client-contracts-entities-report-generation-request.mjs +14 -0
  10. package/fesm2022/acorex-platform-module-report-management-client-contracts-entities-report-generation-request.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-module-report-management-client-contracts-entities-report.mjs +14 -0
  12. package/fesm2022/acorex-platform-module-report-management-client-contracts-entities-report.mjs.map +1 -1
  13. package/fesm2022/{acorex-platform-module-report-management-client-contracts-entity-catalog-acorex-platform-module-report-management-client-contracts-entity-catalog-CgrfJQLl.mjs → acorex-platform-module-report-management-client-contracts-entity-catalog-acorex-platform-module-report-management-client-contracts-entity-catalog-CboBAIna.mjs} +6 -6
  14. package/fesm2022/{acorex-platform-module-report-management-client-contracts-entity-catalog-acorex-platform-module-report-management-client-contracts-entity-catalog-CgrfJQLl.mjs.map → acorex-platform-module-report-management-client-contracts-entity-catalog-acorex-platform-module-report-management-client-contracts-entity-catalog-CboBAIna.mjs.map} +1 -1
  15. package/fesm2022/{acorex-platform-module-report-management-client-contracts-entity-catalog-query-snapshot-policy.entity-D50GUBH6.mjs → acorex-platform-module-report-management-client-contracts-entity-catalog-query-snapshot-policy.entity-BUY06Dz0.mjs} +16 -2
  16. package/fesm2022/acorex-platform-module-report-management-client-contracts-entity-catalog-query-snapshot-policy.entity-BUY06Dz0.mjs.map +1 -0
  17. package/fesm2022/{acorex-platform-module-report-management-client-contracts-entity-catalog-query-snapshot.entity-DuR9ZuN2.mjs → acorex-platform-module-report-management-client-contracts-entity-catalog-query-snapshot.entity-CnnX3xZt.mjs} +16 -2
  18. package/fesm2022/acorex-platform-module-report-management-client-contracts-entity-catalog-query-snapshot.entity-CnnX3xZt.mjs.map +1 -0
  19. package/fesm2022/{acorex-platform-module-report-management-client-contracts-entity-catalog-report-category.entity-Dc4uvjsG.mjs → acorex-platform-module-report-management-client-contracts-entity-catalog-report-category.entity-BQFdNXAm.mjs} +15 -2
  20. package/fesm2022/acorex-platform-module-report-management-client-contracts-entity-catalog-report-category.entity-BQFdNXAm.mjs.map +1 -0
  21. package/fesm2022/{acorex-platform-module-report-management-client-contracts-entity-catalog-report-generation-request.entity-mJUQuk89.mjs → acorex-platform-module-report-management-client-contracts-entity-catalog-report-generation-request.entity-D381cWLE.mjs} +16 -2
  22. package/fesm2022/acorex-platform-module-report-management-client-contracts-entity-catalog-report-generation-request.entity-D381cWLE.mjs.map +1 -0
  23. package/fesm2022/{acorex-platform-module-report-management-client-contracts-entity-catalog-report.entity-CMDFRHeK.mjs → acorex-platform-module-report-management-client-contracts-entity-catalog-report.entity-Bwui0M8X.mjs} +16 -2
  24. package/fesm2022/acorex-platform-module-report-management-client-contracts-entity-catalog-report.entity-Bwui0M8X.mjs.map +1 -0
  25. package/fesm2022/acorex-platform-module-report-management-client-contracts-entity-catalog.mjs +1 -1
  26. package/fesm2022/acorex-platform-module-report-management-client-contracts.mjs +21 -1
  27. package/fesm2022/acorex-platform-module-report-management-client-contracts.mjs.map +1 -1
  28. package/fesm2022/{acorex-platform-module-report-management-client-entity-reports-page-component.provider-B0dU2K_E.mjs → acorex-platform-module-report-management-client-entity-reports-page-component.provider-0eSOzb0y.mjs} +3 -3
  29. package/fesm2022/{acorex-platform-module-report-management-client-entity-reports-page-component.provider-B0dU2K_E.mjs.map → acorex-platform-module-report-management-client-entity-reports-page-component.provider-0eSOzb0y.mjs.map} +1 -1
  30. package/fesm2022/{acorex-platform-module-report-management-client-entity-reports-page.component-D4SAGGn-.mjs → acorex-platform-module-report-management-client-entity-reports-page.component-CHBZbbaH.mjs} +4 -4
  31. package/fesm2022/{acorex-platform-module-report-management-client-entity-reports-page.component-D4SAGGn-.mjs.map → acorex-platform-module-report-management-client-entity-reports-page.component-CHBZbbaH.mjs.map} +1 -1
  32. package/fesm2022/acorex-platform-module-report-management-client-report-layout-switcher.util-BGhHRYfe.mjs +2266 -0
  33. package/fesm2022/acorex-platform-module-report-management-client-report-layout-switcher.util-BGhHRYfe.mjs.map +1 -0
  34. package/fesm2022/{acorex-platform-module-report-management-client-report-popup-viewer.component-Dg3IxjGy.mjs → acorex-platform-module-report-management-client-report-popup-viewer.component-BJq1BeUz.mjs} +5 -4
  35. package/fesm2022/{acorex-platform-module-report-management-client-report-popup-viewer.component-Dg3IxjGy.mjs.map → acorex-platform-module-report-management-client-report-popup-viewer.component-BJq1BeUz.mjs.map} +1 -1
  36. package/fesm2022/acorex-platform-module-report-management-client-report-viewer-page.component-YUdzA4B9.mjs +934 -0
  37. package/fesm2022/acorex-platform-module-report-management-client-report-viewer-page.component-YUdzA4B9.mjs.map +1 -0
  38. package/fesm2022/acorex-platform-module-report-management-client.mjs +1 -1
  39. package/package.json +1 -1
  40. package/types/acorex-platform-module-report-management-client-contracts.d.ts +8 -2
  41. package/types/acorex-platform-module-report-management-client.d.ts +16 -831
  42. package/fesm2022/acorex-platform-module-report-management-client-acorex-platform-module-report-management-client-DUdUNlnp.mjs +0 -4864
  43. package/fesm2022/acorex-platform-module-report-management-client-acorex-platform-module-report-management-client-DUdUNlnp.mjs.map +0 -1
  44. package/fesm2022/acorex-platform-module-report-management-client-contracts-entity-catalog-query-snapshot-policy.entity-D50GUBH6.mjs.map +0 -1
  45. package/fesm2022/acorex-platform-module-report-management-client-contracts-entity-catalog-query-snapshot.entity-DuR9ZuN2.mjs.map +0 -1
  46. package/fesm2022/acorex-platform-module-report-management-client-contracts-entity-catalog-report-category.entity-Dc4uvjsG.mjs.map +0 -1
  47. package/fesm2022/acorex-platform-module-report-management-client-contracts-entity-catalog-report-generation-request.entity-mJUQuk89.mjs.map +0 -1
  48. package/fesm2022/acorex-platform-module-report-management-client-contracts-entity-catalog-report.entity-CMDFRHeK.mjs.map +0 -1
@@ -0,0 +1,2266 @@
1
+ import { AXPPageStatus } from '@acorex-platform/framework-shared/widget-core';
2
+ import { AXBarChartComponent } from '@acorex/charts/bar-chart';
3
+ import { AXChartLegendComponent } from '@acorex/charts/chart-legend';
4
+ import { AXDonutChartComponent } from '@acorex/charts/donut-chart';
5
+ import { AXLineChartComponent } from '@acorex/charts/line-chart';
6
+ import * as i4 from '@acorex/core/translation';
7
+ import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
8
+ import * as i2$1 from '@acorex-platform/framework-client/layout/widget-core';
9
+ import { AXPWidgetRegistryService, AXPWidgetCoreModule } from '@acorex-platform/framework-client/layout/widget-core';
10
+ import { AXPStateMessageComponent, AXPTextCommandColumnComponent } from '@acorex-platform/framework-client/layout/components';
11
+ import * as i2 from '@angular/common';
12
+ import { CommonModule } from '@angular/common';
13
+ import * as i0 from '@angular/core';
14
+ import { inject, Injectable, computed, effect, untracked, signal, ChangeDetectionStrategy, Component, ViewEncapsulation, viewChild } from '@angular/core';
15
+ import { r as resolveReportFilterDefaultValue, c as convertContextDataToFilterClause, b as collectReportParameterDefaults, A as AXPReportViewerService, d as queryResultToChartSeries, e as AXLineChartReportMapper, f as AXPieChartReportMapper, g as AXBarChartReportMapper, q as queryResultToPdfSource } from './acorex-platform-module-report-management-client-acorex-platform-module-report-management-client-D5dCG62j.mjs';
16
+ import { AXButtonModule } from '@acorex/components/button';
17
+ import { AXDecoratorModule } from '@acorex/components/decorators';
18
+ import { DomSanitizer } from '@angular/platform-browser';
19
+ import { AXPPlatformScope, AXPWidgetsList, runtimeQueryPayloadData, runtimeCommandPayloadData, runtimeQueryCollectionData, getSystemActions, resolveMultiLanguageString } from '@acorex-platform/framework-shared/core';
20
+ import * as i1 from '@acorex/components/data-table';
21
+ import { AXDataTableModule } from '@acorex/components/data-table';
22
+ import { AXLoadingModule } from '@acorex/components/loading';
23
+ import { moveItemInArray } from '@angular/cdk/drag-drop';
24
+ import { AXLocaleService } from '@acorex/core/locale';
25
+ import { AXPSettingsService, commandMessageTextForError, AXP_USER_NOTIFICATION_SERVICE, AXPCommonSettings } from '@acorex-platform/framework-client/common';
26
+ import { AXPQueryService, AXPCommandService } from '@acorex-platform/framework-client/runtime';
27
+ import { Subscription } from 'rxjs';
28
+ import { signalStore, withState, withComputed, withMethods, patchState, withHooks } from '@ngrx/signals';
29
+ import { AXPInAppInboxConnectorService } from '@acorex-platform/module-communication-management-client';
30
+ import { set } from 'lodash';
31
+ import { buildReportGenerationNotificationCorrelationId, AXMReportManagementQueryKeys, AXMReportManagementCommandKeys } from '@acorex-platform/module-report-management-client/contracts';
32
+ import { AXDataSource } from '@acorex/cdk/common';
33
+ import { AXPExpressionEvaluatorService, applySystemActionDefault } from '@acorex-platform/framework-client/core';
34
+ import { get } from 'lodash-es';
35
+
36
+ //#region ---- Report Viewer User Settings Service ----
37
+ class AXPReportViewerUserSettingsService {
38
+ constructor() {
39
+ this.settingsService = inject(AXPSettingsService);
40
+ }
41
+ getSettingKey(reportId) {
42
+ return `ReportManagement:Report:${reportId}`;
43
+ }
44
+ async load(reportId) {
45
+ const stored = await this.settingsService.get(this.getSettingKey(reportId));
46
+ return stored ?? null;
47
+ }
48
+ getTableLayoutState(settings, layoutId) {
49
+ return settings?.layouts?.[layoutId]?.table;
50
+ }
51
+ async saveActiveLayoutId(reportId, layoutId) {
52
+ await this.patch(reportId, (prev) => ({
53
+ ...prev,
54
+ activeLayoutId: layoutId,
55
+ }));
56
+ }
57
+ async saveTableColumnOrder(reportId, layoutId, columns) {
58
+ await this.patchTable(reportId, layoutId, (table) => ({
59
+ ...table,
60
+ columns,
61
+ }));
62
+ }
63
+ async saveTableColumnWidth(reportId, layoutId, field, width, baseColumns) {
64
+ const columns = baseColumns.map((col) => col.field === field ? { ...col, width } : col);
65
+ await this.saveTableColumnOrder(reportId, layoutId, columns);
66
+ }
67
+ async saveTableSorts(reportId, layoutId, sorts) {
68
+ await this.patchTable(reportId, layoutId, (table) => ({
69
+ ...table,
70
+ sorts,
71
+ }));
72
+ }
73
+ async saveTablePageSize(reportId, layoutId, pageSize) {
74
+ await this.patchTable(reportId, layoutId, (table) => ({
75
+ ...table,
76
+ pageSize,
77
+ }));
78
+ }
79
+ async patch(reportId, updateFn) {
80
+ await this.settingsService.scope(AXPPlatformScope.User).update(this.getSettingKey(reportId), (prev) => {
81
+ const base = (prev && typeof prev === 'object' ? prev : {});
82
+ return updateFn(base);
83
+ });
84
+ }
85
+ async patchTable(reportId, layoutId, updateFn) {
86
+ await this.patch(reportId, (prev) => {
87
+ const next = { ...prev };
88
+ const currentTable = prev.layouts?.[layoutId]?.table ?? {};
89
+ const updatedTable = updateFn(currentTable);
90
+ set(next, `layouts.${layoutId}.table`, updatedTable);
91
+ return next;
92
+ });
93
+ }
94
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportViewerUserSettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
95
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportViewerUserSettingsService, providedIn: 'root' }); }
96
+ }
97
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportViewerUserSettingsService, decorators: [{
98
+ type: Injectable,
99
+ args: [{ providedIn: 'root' }]
100
+ }] });
101
+
102
+ //#region ---- Constants ----
103
+ const OPEN_REPORT_IN_POPUP_COMMAND = 'ReportManagement.Report:OpenInPopup';
104
+ //#endregion
105
+ //#region ---- Row Action Builder ----
106
+ /**
107
+ * Builds table row actions from declarative group-by drill-down definitions.
108
+ * Skips row commands when linked display columns are used.
109
+ */
110
+ function buildDrillRowActionsFromGroupBy(definitions, currentReportId) {
111
+ if (!definitions?.length || !currentReportId || hasGroupByDrillLinks(definitions)) {
112
+ return [];
113
+ }
114
+ const hasExplicitDefault = definitions.some((definition) => definition.default);
115
+ return definitions.map((definition, index) => {
116
+ const isDefault = definition.default ?? (!hasExplicitDefault && index === 0);
117
+ return {
118
+ title: definition.title ?? 'View',
119
+ command: {
120
+ name: OPEN_REPORT_IN_POPUP_COMMAND,
121
+ options: buildDrillCommandOptions(definition, currentReportId),
122
+ },
123
+ type: 'view',
124
+ priority: definition.priority ?? 'primary',
125
+ default: isDefault,
126
+ iconOnly: false,
127
+ };
128
+ });
129
+ }
130
+ //#endregion
131
+ //#region ---- Drill Link Columns ----
132
+ /**
133
+ * Whether group-by definitions use linked display columns instead of row command actions.
134
+ */
135
+ function hasGroupByDrillLinks(definitions) {
136
+ return definitions?.some((definition) => isGroupByDrillLinkDefinition(definition)) ?? false;
137
+ }
138
+ function isGroupByDrillLinkDefinition(definition) {
139
+ return Boolean(definition.displayField) && definition.linkDisplay !== false;
140
+ }
141
+ function findGroupByDefinitionForDisplayField(definitions, field) {
142
+ return definitions?.find((definition) => definition.displayField === field && isGroupByDrillLinkDefinition(definition));
143
+ }
144
+ function buildGroupByDrillCommandOptions(definition, currentReportId) {
145
+ return buildDrillCommandOptions(definition, currentReportId);
146
+ }
147
+ function resolveGroupByDrillLinkFields(definitions) {
148
+ if (!definitions?.length) {
149
+ return [];
150
+ }
151
+ return definitions
152
+ .filter((definition) => isGroupByDrillLinkDefinition(definition))
153
+ .map((definition) => definition.displayField)
154
+ .filter(Boolean);
155
+ }
156
+ //#endregion
157
+ //#region ---- Helpers ----
158
+ function buildDrillCommandOptions(definition, currentReportId) {
159
+ const drillDown = definition.drillDown;
160
+ const reportId = drillDown.reportId ?? currentReportId;
161
+ return {
162
+ reportId,
163
+ layoutId: drillDown.layoutId,
164
+ title: resolveDrillTitle(definition, drillDown.title),
165
+ parameters: mapDrillParameters(drillDown.parameters),
166
+ hideColumnFields: resolveDrillHideColumnFields(definition),
167
+ };
168
+ }
169
+ function resolveDrillHideColumnFields(definition) {
170
+ const fields = new Set(definition.fields);
171
+ if (definition.displayField) {
172
+ fields.add(definition.displayField);
173
+ }
174
+ return [...fields];
175
+ }
176
+ function resolveDrillTitle(definition, explicitTitle) {
177
+ if (explicitTitle) {
178
+ return explicitTitle;
179
+ }
180
+ if (definition.displayField) {
181
+ return `{{ context.eval('${definition.displayField}') }}`;
182
+ }
183
+ return undefined;
184
+ }
185
+ function mapDrillParameters(parameters) {
186
+ if (!parameters) {
187
+ return undefined;
188
+ }
189
+ return Object.fromEntries(Object.entries(parameters).map(([parameterKey, rowField]) => [
190
+ parameterKey,
191
+ toRowExpression(rowField),
192
+ ]));
193
+ }
194
+ function toRowExpression(value) {
195
+ const trimmed = value.trim();
196
+ if (trimmed.includes('{{') || trimmed.includes('context.eval')) {
197
+ return value;
198
+ }
199
+ return `{{ context.eval('${value}') }}`;
200
+ }
201
+ //#endregion
202
+
203
+ /** Infers a column display widget when the report definition omits `widget.type`. */
204
+ const NUMERIC_COLUMN_FIELD_PATTERN = /(?:count|total|average|avg|min|max|price|value|amount|quantity|number|year|month|quarter|experience|rate|percent|score|age|duration|hours?|days?|tokens)$/i;
205
+ function buildReportTableColumns(definitionColumns, savedColumns) {
206
+ const base = definitionColumns
207
+ ?.filter((col) => col.visible !== false)
208
+ .map((col) => mapDefinitionColumn(col)) ?? [];
209
+ return applySavedColumnLayout(base, savedColumns);
210
+ }
211
+ /**
212
+ * Builds columns and evaluates `visible` expressions (`permission.check` / `feature.check`).
213
+ */
214
+ async function buildReportTableColumnsAsync(definitionColumns, savedColumns, expressionEvaluator) {
215
+ const visibleColumns = [];
216
+ for (const col of definitionColumns ?? []) {
217
+ if (await isReportColumnVisible(col.visible, expressionEvaluator)) {
218
+ visibleColumns.push(col);
219
+ }
220
+ }
221
+ const base = visibleColumns.map((col) => mapDefinitionColumn(col));
222
+ return applySavedColumnLayout(base, savedColumns);
223
+ }
224
+ async function isReportColumnVisible(visible, expressionEvaluator) {
225
+ if (visible === false) {
226
+ return false;
227
+ }
228
+ if (visible === undefined || visible === true) {
229
+ return true;
230
+ }
231
+ if (typeof visible === 'string' && expressionEvaluator.isExpression(visible)) {
232
+ try {
233
+ return Boolean(await expressionEvaluator.evaluate(visible));
234
+ }
235
+ catch {
236
+ return true;
237
+ }
238
+ }
239
+ return Boolean(visible);
240
+ }
241
+ function applySavedColumnLayout(base, savedColumns) {
242
+ if (!savedColumns?.length) {
243
+ return base;
244
+ }
245
+ const widthMap = new Map(savedColumns.map((col) => [col.field, col.width]));
246
+ const orderIndex = new Map(savedColumns.map((col, index) => [col.field, index]));
247
+ return [...base]
248
+ .map((col) => {
249
+ const savedWidth = widthMap.get(col.field);
250
+ const resolvedWidth = typeof savedWidth === 'number' ? savedWidth : parseColumnResizeWidth(savedWidth) ?? col.width;
251
+ return {
252
+ ...col,
253
+ width: resolvedWidth,
254
+ };
255
+ })
256
+ .sort((a, b) => {
257
+ const aIndex = orderIndex.get(a.field) ?? Number.MAX_SAFE_INTEGER;
258
+ const bIndex = orderIndex.get(b.field) ?? Number.MAX_SAFE_INTEGER;
259
+ return aIndex - bIndex;
260
+ });
261
+ }
262
+ function toColumnUserState(columns) {
263
+ return columns.map((col) => ({
264
+ field: col.field,
265
+ width: typeof col.width === 'number' ? col.width : parseColumnResizeWidth(col.width) ?? undefined,
266
+ }));
267
+ }
268
+ function inferReportColumnWidgetType(field) {
269
+ return NUMERIC_COLUMN_FIELD_PATTERN.test(field)
270
+ ? AXPWidgetsList.Editors.NumberBox
271
+ : AXPWidgetsList.Editors.TextBox;
272
+ }
273
+ function resolveColumnWidget(col) {
274
+ const path = col.widget?.path ?? col.field;
275
+ if (col.widget?.type) {
276
+ return { ...col.widget, path };
277
+ }
278
+ return {
279
+ type: inferReportColumnWidgetType(col.field),
280
+ path,
281
+ };
282
+ }
283
+ function mapDefinitionColumn(col) {
284
+ return {
285
+ field: col.field,
286
+ title: col.title || col.field,
287
+ width: col.width || 'auto',
288
+ visible: col.visible !== false,
289
+ align: col.align || 'left',
290
+ sortable: col.sortable === true,
291
+ pin: col.pin,
292
+ widget: resolveColumnWidget(col),
293
+ };
294
+ }
295
+ function parseColumnResizeWidth(value) {
296
+ if (typeof value === 'number' && Number.isFinite(value)) {
297
+ return value;
298
+ }
299
+ if (typeof value === 'string') {
300
+ const parsed = Number.parseInt(value, 10);
301
+ return Number.isFinite(parsed) ? parsed : null;
302
+ }
303
+ return null;
304
+ }
305
+ function resolveColumnFieldFromDataField(dataField) {
306
+ if (dataField.startsWith('col-')) {
307
+ return dataField.slice(4);
308
+ }
309
+ if (dataField.includes('-')) {
310
+ return dataField.split('-').slice(1).join('-');
311
+ }
312
+ return dataField;
313
+ }
314
+ function applyGroupByDrillLinksToColumns(columns, groupByDefinitions) {
315
+ if (!groupByDefinitions?.length) {
316
+ return columns;
317
+ }
318
+ const linkFields = new Set(groupByDefinitions
319
+ .filter((definition) => isGroupByDrillLinkDefinition(definition))
320
+ .map((definition) => definition.displayField)
321
+ .filter(Boolean));
322
+ if (linkFields.size === 0) {
323
+ return columns;
324
+ }
325
+ return columns.map((column) => {
326
+ if (!linkFields.has(column.field)) {
327
+ return column;
328
+ }
329
+ return {
330
+ ...column,
331
+ isGroupByDrillLink: true,
332
+ drillDisplayField: column.field,
333
+ };
334
+ });
335
+ }
336
+ //#endregion
337
+
338
+ const OPEN_REPORT_VIEWER_COMMAND = 'ReportManagement.Report:OpenViewer';
339
+ const AXPReportViewerViewModel = signalStore(withState(() => ({
340
+ reportDefinition: null,
341
+ currentLayout: null,
342
+ filterContext: {},
343
+ allowFetchData: false,
344
+ shouldExecuteReport: false,
345
+ executionRequestId: 0,
346
+ lastAppliedFilterContext: null, // Track last applied filters
347
+ hasBeenExecuted: false, // Track if report has been executed at least once
348
+ status: AXPPageStatus.Idle,
349
+ error: null,
350
+ showRowIndexColumn: false,
351
+ reportUserSettings: null,
352
+ runId: null,
353
+ frozen: false,
354
+ freeze: null,
355
+ backgroundQueued: false,
356
+ backgroundGenerationRequestId: null,
357
+ /** When true, completion is delivered inline on the viewer (no server inbox toast). */
358
+ inlineGenerationWatchActive: false,
359
+ parametersReadonly: false,
360
+ selectedLayoutIds: [],
361
+ })), withComputed((store, widgetRegistryService = inject(AXPWidgetRegistryService)) => ({
362
+ //#region ---- Layout Management ----
363
+ availableLayouts: computed(() => {
364
+ const report = store.reportDefinition();
365
+ if (!report?.layouts)
366
+ return [];
367
+ const freeze = store.freeze();
368
+ const layouts = store.frozen() && freeze?.dataSources?.length
369
+ ? report.layouts.filter((layout) => freeze.dataSources.some((source) => source.layoutId === layout.id))
370
+ : report.layouts;
371
+ return layouts.map((layout) => ({
372
+ id: layout.id,
373
+ title: layout.title,
374
+ icon: layout.icon,
375
+ type: layout.layout.type,
376
+ }));
377
+ }),
378
+ defaultLayout: computed(() => {
379
+ const report = store.reportDefinition();
380
+ if (!report?.layouts?.length)
381
+ return null;
382
+ // Find layout marked as default, or use first layout
383
+ return report.layouts.find((l) => l.isDefault) || report.layouts[0];
384
+ }),
385
+ activeLayoutId: computed(() => store.currentLayout()?.id || null),
386
+ layoutType: computed(() => store.currentLayout()?.layout?.type || null),
387
+ //#endregion
388
+ //#region ---- Layout-Specific Configurations ----
389
+ tableLayoutConfig: computed(() => {
390
+ const layout = store.currentLayout();
391
+ return layout?.layout?.type === 'table' ? layout.layout : null;
392
+ }),
393
+ chartLayoutConfig: computed(() => {
394
+ const layout = store.currentLayout();
395
+ return layout?.layout?.type === 'chart' ? layout.layout : null;
396
+ }),
397
+ pdfLayoutConfig: computed(() => {
398
+ const layout = store.currentLayout();
399
+ return layout?.layout?.type === 'pdf' ? layout.layout : null;
400
+ }),
401
+ //#endregion
402
+ //#region ---- State Indicators ----
403
+ isLoading: computed(() => store.status() === AXPPageStatus.Processing),
404
+ isExecuting: computed(() => store.status() === AXPPageStatus.Submitting),
405
+ hasError: computed(() => store.error() !== null),
406
+ canExecute: computed(() => {
407
+ // Always allow execution if there's an error (to enable retry)
408
+ if (store.error() !== null) {
409
+ return true;
410
+ }
411
+ if ((store.reportDefinition()?.generation?.mode ?? 'interactive') === 'background') {
412
+ if (store.backgroundQueued() && !store.frozen()) {
413
+ return false;
414
+ }
415
+ return store.selectedLayoutIds().length > 0;
416
+ }
417
+ // Allow execution if there are unapplied filters
418
+ const currentContext = store.filterContext();
419
+ const lastApplied = store.lastAppliedFilterContext();
420
+ const hasBeenExecuted = store.hasBeenExecuted();
421
+ if (hasBeenExecuted && JSON.stringify(currentContext) !== JSON.stringify(lastApplied)) {
422
+ return true;
423
+ }
424
+ // Allow execution if report hasn't been executed yet
425
+ if (!hasBeenExecuted) {
426
+ return true;
427
+ }
428
+ // Otherwise, don't allow execution
429
+ return false;
430
+ }),
431
+ hasMultipleLayouts: computed(() => {
432
+ const report = store.reportDefinition();
433
+ const freeze = store.freeze();
434
+ if (store.frozen() && freeze?.dataSources?.length) {
435
+ return freeze.dataSources.length > 1;
436
+ }
437
+ return (report?.layouts?.length ?? 0) > 1;
438
+ }),
439
+ title: computed(() => store.reportDefinition()?.title ?? '@report-management:report-viewer.components.report-viewer.title'),
440
+ description: computed(() => store.reportDefinition()?.description ?? null),
441
+ //#endregion
442
+ //#region ---- Form Configuration ----
443
+ formDefinition: computed(() => {
444
+ const report = store.reportDefinition();
445
+ if (!report || !report.parameterGroups) {
446
+ return { groups: [] };
447
+ }
448
+ const formDefinition = {
449
+ groups: report.parameterGroups.map((group) => ({
450
+ name: group.name,
451
+ title: group.title,
452
+ description: group.description,
453
+ parameters: group.parameters.map((param) => {
454
+ const editorType = param.widget.type ?? '';
455
+ const widget = widgetRegistryService.getOptional(editorType);
456
+ const filterType = widget?.defaultFilterWidgetName ?? editorType;
457
+ // date-time / text filter aliases do not mount reliably in report filter forms;
458
+ // keep the editor widget and enable filterMode instead.
459
+ const useEditorFilter = editorType === 'date-time-editor' ||
460
+ filterType === 'datetime-filter' ||
461
+ editorType === 'text-editor' ||
462
+ filterType === 'string-filter';
463
+ const widgetType = useEditorFilter ? editorType || filterType : filterType;
464
+ const rawDefault = param.widget.defaultValue ?? param.widget.options?.['defaultValue'];
465
+ const resolvedDefault = rawDefault === undefined ? undefined : resolveReportFilterDefaultValue(rawDefault);
466
+ return {
467
+ path: param.path,
468
+ title: param.title,
469
+ description: param.description,
470
+ visible: param.visible,
471
+ badge: `{{ filter.getTitle(context.eval("${param.path}.operation.type")) }}`,
472
+ widget: {
473
+ path: param.path,
474
+ type: widgetType,
475
+ ...(resolvedDefault !== undefined ? { defaultValue: resolvedDefault } : {}),
476
+ options: {
477
+ ...param.widget.options,
478
+ clearButton: true,
479
+ hasClearButton: true,
480
+ ...(useEditorFilter ? { filterMode: true } : {}),
481
+ },
482
+ triggers: param.widget.triggers,
483
+ }
484
+ };
485
+ }),
486
+ })),
487
+ };
488
+ return formDefinition;
489
+ }),
490
+ //#endregion
491
+ //#region ---- Layout Type Detection ----
492
+ isTableLayout: computed(() => store.currentLayout()?.layout?.type === 'table'),
493
+ isChartLayout: computed(() => store.currentLayout()?.layout?.type === 'chart'),
494
+ isPdfLayout: computed(() => store.currentLayout()?.layout?.type === 'pdf'),
495
+ //#endregion
496
+ //#region ---- Filter States ----
497
+ hasUnappliedFilters: computed(() => {
498
+ const currentContext = store.filterContext();
499
+ const lastApplied = store.lastAppliedFilterContext();
500
+ const hasBeenExecuted = store.hasBeenExecuted();
501
+ // If report has never been executed, don't show unapplied filters warning
502
+ if (!hasBeenExecuted) {
503
+ return false;
504
+ }
505
+ // If no filters have been applied yet, return true
506
+ if (lastApplied === null) {
507
+ return true;
508
+ }
509
+ // Check if current filters are different from last applied filters
510
+ const currentKeys = Object.keys(currentContext);
511
+ const lastKeys = Object.keys(lastApplied);
512
+ if (currentKeys.length !== lastKeys.length) {
513
+ return true;
514
+ }
515
+ // Check if any values are different
516
+ for (const key of currentKeys) {
517
+ if (currentContext[key] !== lastApplied[key]) {
518
+ return true;
519
+ }
520
+ }
521
+ return false;
522
+ }),
523
+ //#endregion
524
+ //#region ---- Action States ----
525
+ canSelectRows: computed(() => {
526
+ const layout = store.currentLayout();
527
+ return (layout?.layout?.type === 'table' &&
528
+ layout.layout.options?.selectable?.mode &&
529
+ layout.layout.options?.selectable?.mode !== 'none');
530
+ }),
531
+ tableLayoutUserSettings: computed(() => {
532
+ const settings = store.reportUserSettings();
533
+ const layoutId = store.currentLayout()?.id;
534
+ if (!settings || !layoutId) {
535
+ return null;
536
+ }
537
+ return settings.layouts?.[layoutId]?.table ?? null;
538
+ }),
539
+ reportId: computed(() => store.reportDefinition()?.id ?? null),
540
+ isFrozen: computed(() => store.frozen()),
541
+ isBackgroundQueued: computed(() => store.backgroundQueued()),
542
+ isBackgroundReportMode: computed(() => (store.reportDefinition()?.generation?.mode ?? 'interactive') === 'background'),
543
+ hasSelectedBackgroundLayouts: computed(() => store.selectedLayoutIds().length > 0),
544
+ areParametersReadonly: computed(() => store.parametersReadonly() || store.frozen()),
545
+ isBackgroundGenerateDisabled: computed(() => {
546
+ if ((store.reportDefinition()?.generation?.mode ?? 'interactive') !== 'background') {
547
+ return false;
548
+ }
549
+ return store.backgroundQueued() || store.selectedLayoutIds().length === 0;
550
+ }),
551
+ canSwitchLayout: computed(() => {
552
+ if (store.frozen() && (store.freeze()?.dataSources?.length ?? 0) <= 1) {
553
+ return false;
554
+ }
555
+ return (store.reportDefinition()?.layouts?.length ?? 0) > 1 && store.status() !== AXPPageStatus.Submitting;
556
+ }),
557
+ generationMode: computed(() => store.reportDefinition()?.generation?.mode ?? 'interactive'),
558
+ //#endregion
559
+ })), withMethods((store, reportDefinitionService = inject(AXPReportViewerService), reportUserSettingsService = inject(AXPReportViewerUserSettingsService), queryService = inject(AXPQueryService), commandService = inject(AXPCommandService), userNotificationService = inject(AXP_USER_NOTIFICATION_SERVICE), translationService = inject(AXTranslationService), inboxConnector = inject(AXPInAppInboxConnectorService, {
560
+ optional: true,
561
+ })) => {
562
+ let activeFetchCount = 0;
563
+ let activeGenerationWatchId = null;
564
+ const BACKGROUND_GENERATION_POLL_MS = 3_000;
565
+ const dismissReportGenerationInboxNotification = async (runId) => {
566
+ const normalized = runId.trim();
567
+ if (!normalized || !inboxConnector) {
568
+ return;
569
+ }
570
+ try {
571
+ await inboxConnector.dismissByCorrelationId(buildReportGenerationNotificationCorrelationId(normalized));
572
+ }
573
+ catch {
574
+ // Best-effort — the report viewer should still open.
575
+ }
576
+ };
577
+ const openGeneratedReportFromNotification = async (runId) => {
578
+ const report = store.reportDefinition();
579
+ const normalizedRunId = runId.trim();
580
+ if (!report?.id || !normalizedRunId) {
581
+ return;
582
+ }
583
+ await dismissReportGenerationInboxNotification(normalizedRunId);
584
+ await commandService.execute(OPEN_REPORT_VIEWER_COMMAND, {
585
+ reportId: report.id,
586
+ runId: normalizedRunId,
587
+ });
588
+ };
589
+ const showGenerationNotification = async (options) => {
590
+ const runId = options.runId?.trim();
591
+ const showOpenAction = options.showOpenAction === true && !!runId;
592
+ const toastActions = showOpenAction
593
+ ? [
594
+ {
595
+ text: await translationService.translateAsync('@report-management:report-generation.notifications.actions.open-viewer'),
596
+ color: 'primary',
597
+ onClick: () => openGeneratedReportFromNotification(runId),
598
+ },
599
+ ]
600
+ : undefined;
601
+ await userNotificationService.notify({
602
+ correlationId: runId
603
+ ? buildReportGenerationNotificationCorrelationId(runId)
604
+ : `report-generation-ui:${options.titleKey}`,
605
+ title: await translationService.translateAsync(options.titleKey),
606
+ body: await translationService.translateAsync(options.bodyKey),
607
+ icon: options.icon,
608
+ delivery: {
609
+ toast: {
610
+ color: options.color,
611
+ location: 'bottom-center',
612
+ closeButton: true,
613
+ timeOut: showOpenAction ? 8_000 : 3_000,
614
+ timeOutProgress: true,
615
+ showActions: showOpenAction,
616
+ },
617
+ },
618
+ toastActions,
619
+ });
620
+ };
621
+ const showGenerationReadyNotification = async (runId) => {
622
+ await showGenerationNotification({
623
+ color: 'success',
624
+ icon: 'fa-light fa-circle-check',
625
+ titleKey: '@report-management:report-generation.notifications.complete.title',
626
+ bodyKey: '@report-management:report-generation.notifications.complete.body',
627
+ runId,
628
+ showOpenAction: false,
629
+ });
630
+ };
631
+ const showGenerationFailedNotification = async () => {
632
+ await showGenerationNotification({
633
+ color: 'danger',
634
+ icon: 'fa-light fa-circle-exclamation',
635
+ titleKey: '@report-management:report-generation.notifications.failed.title',
636
+ bodyKey: '@report-management:report-generation.notifications.failed.body',
637
+ });
638
+ };
639
+ const api = {
640
+ loadFrozenRun: async (_runId) => { },
641
+ startBackgroundGenerationWatch: async (_requestId) => { },
642
+ };
643
+ api.loadFrozenRun = async (runId) => {
644
+ patchState(store, { status: AXPPageStatus.Processing, error: null });
645
+ try {
646
+ const result = await queryService.fetch(AXMReportManagementQueryKeys.ReportGeneration.GetViewerFreeze, { id: runId });
647
+ const freeze = runtimeQueryPayloadData(result);
648
+ if (!result?.success || !freeze) {
649
+ patchState(store, {
650
+ error: commandMessageTextForError(result?.message?.text) ||
651
+ '@report-management:report-generation.errors.freeze-not-found',
652
+ });
653
+ return;
654
+ }
655
+ let report = store.reportDefinition();
656
+ if (freeze.definition) {
657
+ report = {
658
+ id: freeze.reportId,
659
+ title: freeze.definition.title,
660
+ categoryIds: report?.categoryIds ?? [],
661
+ parameterGroups: report?.parameterGroups ?? [],
662
+ layouts: freeze.definition.layouts,
663
+ defaultLayoutId: freeze.definition.defaultLayoutId,
664
+ generation: report?.generation,
665
+ };
666
+ }
667
+ else if (!report) {
668
+ report = (await reportDefinitionService.getReportById(freeze.reportId)) ?? null;
669
+ }
670
+ if (!report) {
671
+ patchState(store, {
672
+ error: '@report-management:report-viewer.messages.error.failed-to-load',
673
+ });
674
+ return;
675
+ }
676
+ const defaultLayout = report.layouts.find((layout) => layout.id === freeze.definition?.defaultLayoutId) ||
677
+ report.layouts.find((layout) => layout.id === freeze.dataSources[0]?.layoutId) ||
678
+ report.layouts[0];
679
+ patchState(store, {
680
+ reportDefinition: report,
681
+ currentLayout: defaultLayout,
682
+ filterContext: freeze.parameters ?? {},
683
+ lastAppliedFilterContext: freeze.parameters ?? {},
684
+ allowFetchData: true,
685
+ shouldExecuteReport: true,
686
+ hasBeenExecuted: true,
687
+ executionRequestId: store.executionRequestId() + 1,
688
+ runId,
689
+ frozen: true,
690
+ freeze,
691
+ backgroundQueued: false,
692
+ backgroundGenerationRequestId: null,
693
+ inlineGenerationWatchActive: false,
694
+ parametersReadonly: true,
695
+ status: AXPPageStatus.Idle,
696
+ });
697
+ }
698
+ catch (error) {
699
+ patchState(store, {
700
+ error: error instanceof Error
701
+ ? error.message
702
+ : '@report-management:report-generation.errors.freeze-not-found',
703
+ });
704
+ }
705
+ finally {
706
+ patchState(store, { status: AXPPageStatus.Idle });
707
+ }
708
+ };
709
+ api.startBackgroundGenerationWatch = async (requestId) => {
710
+ activeGenerationWatchId = requestId;
711
+ while (activeGenerationWatchId === requestId) {
712
+ if (!store.backgroundQueued() || store.backgroundGenerationRequestId() !== requestId) {
713
+ return;
714
+ }
715
+ await new Promise((resolve) => setTimeout(resolve, BACKGROUND_GENERATION_POLL_MS));
716
+ if (activeGenerationWatchId !== requestId) {
717
+ return;
718
+ }
719
+ try {
720
+ const result = await queryService.fetch(AXMReportManagementQueryKeys.ReportGeneration.Get, { id: requestId });
721
+ const request = runtimeQueryPayloadData(result);
722
+ if (!request || activeGenerationWatchId !== requestId) {
723
+ continue;
724
+ }
725
+ if (request.status === 'completed') {
726
+ activeGenerationWatchId = null;
727
+ const inlineWatchActive = store.inlineGenerationWatchActive();
728
+ patchState(store, {
729
+ backgroundQueued: false,
730
+ inlineGenerationWatchActive: false,
731
+ ...(inlineWatchActive ? {} : { backgroundGenerationRequestId: null }),
732
+ });
733
+ if (!inlineWatchActive) {
734
+ return;
735
+ }
736
+ await api.loadFrozenRun(requestId);
737
+ if (!store.error()) {
738
+ await showGenerationReadyNotification(requestId);
739
+ }
740
+ return;
741
+ }
742
+ if (request.status === 'failed' || request.status === 'cancelled') {
743
+ activeGenerationWatchId = null;
744
+ patchState(store, {
745
+ backgroundQueued: false,
746
+ backgroundGenerationRequestId: null,
747
+ inlineGenerationWatchActive: false,
748
+ status: AXPPageStatus.Error,
749
+ error: '@report-management:report-generation.notifications.failed.body',
750
+ });
751
+ await showGenerationFailedNotification();
752
+ return;
753
+ }
754
+ }
755
+ catch {
756
+ // Keep polling while the run is still active.
757
+ }
758
+ }
759
+ };
760
+ const generateInBackground = async () => {
761
+ const report = store.reportDefinition();
762
+ const isBackgroundMode = (report?.generation?.mode ?? 'interactive') === 'background';
763
+ const layoutIds = isBackgroundMode
764
+ ? store.selectedLayoutIds()
765
+ : store.currentLayout()
766
+ ? [store.currentLayout().id]
767
+ : [];
768
+ if (!report || layoutIds.length === 0) {
769
+ patchState(store, {
770
+ error: layoutIds.length === 0
771
+ ? '@report-management:report-generation.errors.no-layouts-selected'
772
+ : '@report-management:report-viewer.messages.error.report-or-layout-not-found',
773
+ });
774
+ return;
775
+ }
776
+ patchState(store, { status: AXPPageStatus.Submitting, error: null });
777
+ if (store.frozen()) {
778
+ patchState(store, {
779
+ frozen: false,
780
+ freeze: null,
781
+ runId: null,
782
+ hasBeenExecuted: false,
783
+ allowFetchData: false,
784
+ parametersReadonly: false,
785
+ });
786
+ }
787
+ try {
788
+ const result = await commandService.executeRemote(AXMReportManagementCommandKeys.ReportGeneration.Create, {
789
+ reportId: report.id,
790
+ layoutIds,
791
+ layoutId: layoutIds[0],
792
+ parameters: store.filterContext(),
793
+ trigger: 'manual',
794
+ suppressNotificationOnComplete: true,
795
+ });
796
+ if (!result?.success) {
797
+ patchState(store, {
798
+ status: AXPPageStatus.Error,
799
+ error: commandMessageTextForError(result?.message?.text) ||
800
+ '@report-management:report-generation.errors.create-failed',
801
+ });
802
+ return;
803
+ }
804
+ const createResult = runtimeCommandPayloadData(result);
805
+ const requestId = createResult?.id?.trim();
806
+ if (!createResult || !requestId) {
807
+ patchState(store, {
808
+ status: AXPPageStatus.Error,
809
+ error: '@report-management:report-generation.errors.create-failed',
810
+ });
811
+ return;
812
+ }
813
+ if (createResult.status === 'completed') {
814
+ patchState(store, { status: AXPPageStatus.Idle, error: null });
815
+ await api.loadFrozenRun(requestId);
816
+ if (!store.error()) {
817
+ await showGenerationReadyNotification(requestId);
818
+ }
819
+ return;
820
+ }
821
+ patchState(store, {
822
+ status: AXPPageStatus.Submitted,
823
+ backgroundQueued: true,
824
+ backgroundGenerationRequestId: requestId,
825
+ inlineGenerationWatchActive: true,
826
+ error: null,
827
+ });
828
+ if (createResult.deduplicated) {
829
+ await showGenerationNotification({
830
+ color: 'warning',
831
+ icon: 'fa-light fa-triangle-exclamation',
832
+ titleKey: '@report-management:report-generation.messages.deduplicated-title',
833
+ bodyKey: '@report-management:report-generation.messages.deduplicated-body',
834
+ });
835
+ }
836
+ else {
837
+ await showGenerationNotification({
838
+ color: 'success',
839
+ icon: 'fa-light fa-clock',
840
+ titleKey: '@report-management:report-generation.messages.queued-title',
841
+ bodyKey: '@report-management:report-generation.messages.queued-body',
842
+ });
843
+ }
844
+ void api.startBackgroundGenerationWatch(requestId);
845
+ }
846
+ catch (error) {
847
+ patchState(store, {
848
+ status: AXPPageStatus.Error,
849
+ error: error instanceof Error
850
+ ? error.message
851
+ : '@report-management:report-generation.errors.create-failed',
852
+ });
853
+ }
854
+ };
855
+ const releaseInlineNotificationWatch = async () => {
856
+ const requestId = store.backgroundGenerationRequestId()?.trim();
857
+ activeGenerationWatchId = null;
858
+ patchState(store, {
859
+ inlineGenerationWatchActive: false,
860
+ backgroundQueued: false,
861
+ });
862
+ if (!requestId) {
863
+ return;
864
+ }
865
+ try {
866
+ await commandService.executeRemote(AXMReportManagementCommandKeys.ReportGeneration.ReleaseInlineWatch, { id: requestId });
867
+ }
868
+ catch {
869
+ // Best-effort — completion notify re-reads the flag from storage.
870
+ }
871
+ };
872
+ const executeReport = async () => {
873
+ try {
874
+ if (store.status() === AXPPageStatus.Processing ||
875
+ store.status() === AXPPageStatus.Submitting) {
876
+ return;
877
+ }
878
+ const report = store.reportDefinition();
879
+ if ((report?.generation?.mode ?? 'interactive') === 'background') {
880
+ await generateInBackground();
881
+ return;
882
+ }
883
+ if (store.frozen()) {
884
+ patchState(store, {
885
+ allowFetchData: true,
886
+ shouldExecuteReport: true,
887
+ executionRequestId: store.executionRequestId() + 1,
888
+ hasBeenExecuted: true,
889
+ });
890
+ return;
891
+ }
892
+ const layout = store.currentLayout();
893
+ if (!report || !layout) {
894
+ patchState(store, {
895
+ error: '@report-management:report-viewer.messages.error.report-or-layout-not-found',
896
+ });
897
+ return;
898
+ }
899
+ patchState(store, {
900
+ status: AXPPageStatus.Submitting,
901
+ error: null,
902
+ allowFetchData: true,
903
+ shouldExecuteReport: true,
904
+ executionRequestId: store.executionRequestId() + 1,
905
+ lastAppliedFilterContext: { ...store.filterContext() }, // Mark current filters as applied
906
+ hasBeenExecuted: true, // Mark that report has been executed at least once
907
+ });
908
+ patchState(store, {
909
+ status: AXPPageStatus.Submitted,
910
+ error: null,
911
+ });
912
+ }
913
+ catch (error) {
914
+ patchState(store, {
915
+ status: AXPPageStatus.Error,
916
+ error: error instanceof Error ? error.message : '@report-management:report-viewer.messages.error.unexpected-execution',
917
+ });
918
+ }
919
+ };
920
+ const buildSummaryFromLayout = (layoutDef) => {
921
+ const tableLayout = layoutDef.layout;
922
+ if (tableLayout?.type !== 'table' || !Array.isArray(tableLayout.summary)) {
923
+ return undefined;
924
+ }
925
+ const footer = tableLayout.summary.filter((s) => s.displayIn === 'reportFooter');
926
+ return footer.length > 0 ? footer : undefined;
927
+ };
928
+ const fetchData = async (query) => {
929
+ const report = store.reportDefinition();
930
+ const layout = store.currentLayout();
931
+ if (!report || !layout) {
932
+ return null;
933
+ }
934
+ if (store.frozen()) {
935
+ const freeze = store.freeze();
936
+ const layoutId = layout.id;
937
+ const source = freeze?.dataSources.find((item) => item.layoutId === layoutId) ?? freeze?.dataSources[0];
938
+ if (!source?.result) {
939
+ return null;
940
+ }
941
+ const collection = runtimeQueryCollectionData(source.result);
942
+ if (!collection) {
943
+ return source.result;
944
+ }
945
+ const skip = query?.skip ?? 0;
946
+ const take = query?.take ?? collection.items.length;
947
+ return {
948
+ ...source.result,
949
+ success: true,
950
+ kind: 'collection',
951
+ data: {
952
+ ...collection,
953
+ items: collection.items.slice(skip, skip + take),
954
+ },
955
+ };
956
+ }
957
+ if (activeFetchCount > 0) {
958
+ return null;
959
+ }
960
+ activeFetchCount++;
961
+ patchState(store, {
962
+ status: AXPPageStatus.Processing,
963
+ error: null,
964
+ });
965
+ let nextStatus = AXPPageStatus.Idle;
966
+ try {
967
+ const dataSource = layout.dataSource;
968
+ if (!dataSource || dataSource.type !== 'query' || !dataSource.name) {
969
+ nextStatus = AXPPageStatus.Error;
970
+ patchState(store, {
971
+ error: '@report-management:report-viewer.messages.error.failed-to-execute',
972
+ });
973
+ return null;
974
+ }
975
+ const filterClause = convertContextDataToFilterClause(store.filterContext());
976
+ const runtimeSort = query?.sort?.length
977
+ ? query.sort.map((s) => ({
978
+ name: s.field,
979
+ dir: s.dir,
980
+ }))
981
+ : dataSource.sortFields;
982
+ const summary = buildSummaryFromLayout(layout);
983
+ const queryInput = {
984
+ skip: query?.skip ?? 0,
985
+ take: query?.take ?? null,
986
+ filter: filterClause,
987
+ ...(runtimeSort?.length
988
+ ? {
989
+ sort: runtimeSort.map((s) => ({
990
+ field: s.name,
991
+ dir: s.dir,
992
+ })),
993
+ }
994
+ : {}),
995
+ ...(summary ? { summary } : {}),
996
+ };
997
+ const queryResult = await queryService.fetch(dataSource.name, queryInput);
998
+ if (!queryResult?.success) {
999
+ nextStatus = AXPPageStatus.Error;
1000
+ patchState(store, {
1001
+ error: commandMessageTextForError(queryResult?.message?.text) ||
1002
+ '@report-management:report-viewer.messages.error.failed-to-execute',
1003
+ });
1004
+ return null;
1005
+ }
1006
+ return queryResult;
1007
+ }
1008
+ catch (error) {
1009
+ nextStatus = AXPPageStatus.Error;
1010
+ const message = error instanceof Error && error.message
1011
+ ? error.message
1012
+ : '@report-management:report-viewer.messages.error.failed-to-execute';
1013
+ patchState(store, {
1014
+ error: message,
1015
+ });
1016
+ console.error('Error executing report:', error);
1017
+ return null;
1018
+ }
1019
+ finally {
1020
+ activeFetchCount--;
1021
+ patchState(store, {
1022
+ status: nextStatus,
1023
+ });
1024
+ }
1025
+ };
1026
+ return {
1027
+ //#region ---- Report Loading ----
1028
+ async loadReport(reportId) {
1029
+ await releaseInlineNotificationWatch();
1030
+ patchState(store, {
1031
+ status: AXPPageStatus.Processing,
1032
+ error: null,
1033
+ });
1034
+ try {
1035
+ const report = await reportDefinitionService.getReportById(reportId);
1036
+ if (report) {
1037
+ // Configure parameter widgets with proper paths
1038
+ report.parameterGroups.forEach((group) => {
1039
+ group.parameters.forEach((param) => {
1040
+ param.widget.path = param.path;
1041
+ });
1042
+ });
1043
+ const userSettings = await reportUserSettingsService.load(reportId);
1044
+ const defaultLayout = report.layouts.find((l) => l.isDefault) || report.layouts[0];
1045
+ const savedLayout = userSettings?.activeLayoutId
1046
+ ? report.layouts.find((l) => l.id === userSettings.activeLayoutId)
1047
+ : undefined;
1048
+ patchState(store, {
1049
+ reportDefinition: report,
1050
+ currentLayout: savedLayout ?? defaultLayout,
1051
+ filterContext: collectReportParameterDefaults(report.parameterGroups),
1052
+ allowFetchData: false,
1053
+ hasBeenExecuted: false,
1054
+ executionRequestId: 0,
1055
+ reportUserSettings: userSettings,
1056
+ runId: null,
1057
+ frozen: false,
1058
+ freeze: null,
1059
+ backgroundQueued: false,
1060
+ backgroundGenerationRequestId: null,
1061
+ inlineGenerationWatchActive: false,
1062
+ parametersReadonly: false,
1063
+ selectedLayoutIds: report.layouts.map((layout) => layout.id),
1064
+ });
1065
+ }
1066
+ }
1067
+ catch (error) {
1068
+ patchState(store, {
1069
+ error: '@report-management:report-viewer.messages.error.failed-to-load',
1070
+ });
1071
+ console.error('Error loading report:', error);
1072
+ }
1073
+ finally {
1074
+ patchState(store, {
1075
+ status: AXPPageStatus.Idle,
1076
+ });
1077
+ }
1078
+ },
1079
+ //#endregion
1080
+ //#region ---- Layout Management ----
1081
+ switchLayout(layoutId) {
1082
+ const report = store.reportDefinition();
1083
+ if (!report)
1084
+ return;
1085
+ const layout = report.layouts.find((l) => l.id === layoutId);
1086
+ if (layout) {
1087
+ patchState(store, {
1088
+ currentLayout: layout,
1089
+ reportUserSettings: {
1090
+ ...(store.reportUserSettings() ?? {}),
1091
+ activeLayoutId: layoutId,
1092
+ },
1093
+ });
1094
+ void reportUserSettingsService.saveActiveLayoutId(report.id, layoutId);
1095
+ }
1096
+ },
1097
+ isBackgroundLayoutSelected(layoutId) {
1098
+ return store.selectedLayoutIds().includes(layoutId);
1099
+ },
1100
+ setBackgroundLayoutSelected(layoutId, selected) {
1101
+ const current = store.selectedLayoutIds();
1102
+ if (selected) {
1103
+ if (current.includes(layoutId)) {
1104
+ return;
1105
+ }
1106
+ patchState(store, { selectedLayoutIds: [...current, layoutId] });
1107
+ return;
1108
+ }
1109
+ patchState(store, {
1110
+ selectedLayoutIds: current.filter((id) => id !== layoutId),
1111
+ });
1112
+ },
1113
+ async saveTableColumnOrder(columns) {
1114
+ const reportId = store.reportDefinition()?.id;
1115
+ const layoutId = store.currentLayout()?.id;
1116
+ if (!reportId || !layoutId) {
1117
+ return;
1118
+ }
1119
+ await reportUserSettingsService.saveTableColumnOrder(reportId, layoutId, toColumnUserState(columns));
1120
+ patchState(store, {
1121
+ reportUserSettings: mergeTableColumnsIntoSettings(store.reportUserSettings(), layoutId, toColumnUserState(columns)),
1122
+ });
1123
+ },
1124
+ async saveTableColumnWidth(field, width, columns) {
1125
+ const reportId = store.reportDefinition()?.id;
1126
+ const layoutId = store.currentLayout()?.id;
1127
+ if (!reportId || !layoutId) {
1128
+ return;
1129
+ }
1130
+ const columnState = toColumnUserState(columns);
1131
+ await reportUserSettingsService.saveTableColumnWidth(reportId, layoutId, field, width, columnState);
1132
+ patchState(store, {
1133
+ reportUserSettings: mergeTableColumnsIntoSettings(store.reportUserSettings(), layoutId, columnState),
1134
+ });
1135
+ },
1136
+ async saveTableSorts(sorts) {
1137
+ const reportId = store.reportDefinition()?.id;
1138
+ const layoutId = store.currentLayout()?.id;
1139
+ if (!reportId || !layoutId) {
1140
+ return;
1141
+ }
1142
+ await reportUserSettingsService.saveTableSorts(reportId, layoutId, sorts);
1143
+ patchState(store, {
1144
+ reportUserSettings: mergeTableSortsIntoSettings(store.reportUserSettings(), layoutId, sorts),
1145
+ });
1146
+ },
1147
+ async saveTablePageSize(pageSize) {
1148
+ const reportId = store.reportDefinition()?.id;
1149
+ const layoutId = store.currentLayout()?.id;
1150
+ if (!reportId || !layoutId) {
1151
+ return;
1152
+ }
1153
+ await reportUserSettingsService.saveTablePageSize(reportId, layoutId, pageSize);
1154
+ patchState(store, {
1155
+ reportUserSettings: mergeTablePageSizeIntoSettings(store.reportUserSettings(), layoutId, pageSize),
1156
+ });
1157
+ },
1158
+ //#endregion
1159
+ //#region ---- Data Execution ----
1160
+ executeReport,
1161
+ fetchData,
1162
+ generateInBackground,
1163
+ releaseInlineNotificationWatch,
1164
+ nudgeFetch() {
1165
+ if (!store.allowFetchData()) {
1166
+ return;
1167
+ }
1168
+ patchState(store, { executionRequestId: store.executionRequestId() + 1 });
1169
+ },
1170
+ loadFrozenRun: api.loadFrozenRun,
1171
+ //#endregion
1172
+ //#region ---- Drill View ----
1173
+ /**
1174
+ * Applies the same layout in drill-down mode: hides group-by columns, keeps summary and data source.
1175
+ */
1176
+ applyDrillView(options) {
1177
+ const layout = store.currentLayout();
1178
+ if (!layout || layout.layout.type !== 'table') {
1179
+ return;
1180
+ }
1181
+ const tableLayout = layout.layout;
1182
+ const hiddenFields = new Set(options.hideColumnFields);
1183
+ const nextTableLayout = {
1184
+ ...tableLayout,
1185
+ columns: tableLayout.columns.map((column) => hiddenFields.has(column.field) ? { ...column, visible: false } : column),
1186
+ groupBy: undefined,
1187
+ actions: [],
1188
+ };
1189
+ patchState(store, {
1190
+ currentLayout: {
1191
+ ...layout,
1192
+ layout: nextTableLayout,
1193
+ },
1194
+ });
1195
+ },
1196
+ //#endregion
1197
+ //#region ---- Filter Management ----
1198
+ updateFilterContext(context) {
1199
+ patchState(store, {
1200
+ filterContext: { ...store.filterContext(), ...context },
1201
+ shouldExecuteReport: false, // Reset execution flag when filters change
1202
+ });
1203
+ },
1204
+ clearFilters() {
1205
+ patchState(store, {
1206
+ allowFetchData: false,
1207
+ shouldExecuteReport: false,
1208
+ filterContext: collectReportParameterDefaults(store.reportDefinition()?.parameterGroups),
1209
+ lastAppliedFilterContext: null, // Reset applied filters when clearing
1210
+ hasBeenExecuted: false, // Reset execution state when clearing filters
1211
+ });
1212
+ },
1213
+ resetFilters() {
1214
+ patchState(store, {
1215
+ allowFetchData: false,
1216
+ });
1217
+ },
1218
+ //#endregion
1219
+ //#region ---- Export Management ----
1220
+ async exportReport(format) {
1221
+ const layout = store.currentLayout();
1222
+ if (!layout?.export) {
1223
+ console.warn('@report-management:report-viewer.messages.warnings.export-not-configured');
1224
+ return;
1225
+ }
1226
+ try {
1227
+ // Export logic would go here
1228
+ // This would typically call a different command for export
1229
+ console.log(`Exporting report in ${format} format`);
1230
+ }
1231
+ catch (error) {
1232
+ console.error('Error exporting report:', error);
1233
+ }
1234
+ },
1235
+ //#endregion
1236
+ //#region ---- Utility Methods ----
1237
+ refresh() {
1238
+ return executeReport();
1239
+ },
1240
+ clearError() {
1241
+ patchState(store, {
1242
+ error: null,
1243
+ });
1244
+ },
1245
+ clearExecutionState() {
1246
+ patchState(store, {
1247
+ hasBeenExecuted: false,
1248
+ allowFetchData: false,
1249
+ shouldExecuteReport: false,
1250
+ executionRequestId: 0,
1251
+ });
1252
+ },
1253
+ cancel() {
1254
+ patchState(store, {
1255
+ status: AXPPageStatus.Idle,
1256
+ hasBeenExecuted: false,
1257
+ error: null,
1258
+ });
1259
+ //TODO: Cancel the report execution
1260
+ },
1261
+ reset() {
1262
+ patchState(store, {
1263
+ reportDefinition: null,
1264
+ currentLayout: null,
1265
+ filterContext: {},
1266
+ allowFetchData: false,
1267
+ shouldExecuteReport: false,
1268
+ executionRequestId: 0,
1269
+ lastAppliedFilterContext: null,
1270
+ hasBeenExecuted: false,
1271
+ status: AXPPageStatus.Idle,
1272
+ error: null,
1273
+ reportUserSettings: null,
1274
+ runId: null,
1275
+ frozen: false,
1276
+ freeze: null,
1277
+ backgroundQueued: false,
1278
+ backgroundGenerationRequestId: null,
1279
+ inlineGenerationWatchActive: false,
1280
+ parametersReadonly: false,
1281
+ });
1282
+ },
1283
+ //#endregion
1284
+ };
1285
+ }), withHooks((store, translationService = inject(AXTranslationService), localeService = inject(AXLocaleService), settingsService = inject(AXPSettingsService)) => {
1286
+ const settingSubs = new Subscription();
1287
+ const syncShowRowIndexColumn = async () => {
1288
+ try {
1289
+ const value = await settingsService.get(AXPCommonSettings.ShowRowIndexColumn);
1290
+ patchState(store, { showRowIndexColumn: value ?? false });
1291
+ }
1292
+ catch {
1293
+ patchState(store, { showRowIndexColumn: false });
1294
+ }
1295
+ };
1296
+ return {
1297
+ async onInit() {
1298
+ translationService.langChanges$.subscribe(() => {
1299
+ // Handle language change if needed
1300
+ });
1301
+ localeService.profileChanged$.subscribe(() => {
1302
+ // Handle locale change if needed
1303
+ });
1304
+ void syncShowRowIndexColumn();
1305
+ settingSubs.add(settingsService.onLoaded.subscribe(() => void syncShowRowIndexColumn()));
1306
+ settingSubs.add(settingsService.onChanged.subscribe(() => void syncShowRowIndexColumn()));
1307
+ },
1308
+ onDestroy() {
1309
+ settingSubs.unsubscribe();
1310
+ },
1311
+ };
1312
+ }));
1313
+ //#region ---- Settings Merge Helpers ----
1314
+ function mergeTableColumnsIntoSettings(settings, layoutId, columns) {
1315
+ const base = settings ?? {};
1316
+ return {
1317
+ ...base,
1318
+ layouts: {
1319
+ ...base.layouts,
1320
+ [layoutId]: {
1321
+ ...base.layouts?.[layoutId],
1322
+ table: {
1323
+ ...base.layouts?.[layoutId]?.table,
1324
+ columns,
1325
+ },
1326
+ },
1327
+ },
1328
+ };
1329
+ }
1330
+ function mergeTableSortsIntoSettings(settings, layoutId, sorts) {
1331
+ const base = settings ?? {};
1332
+ return {
1333
+ ...base,
1334
+ layouts: {
1335
+ ...base.layouts,
1336
+ [layoutId]: {
1337
+ ...base.layouts?.[layoutId],
1338
+ table: {
1339
+ ...base.layouts?.[layoutId]?.table,
1340
+ sorts,
1341
+ },
1342
+ },
1343
+ },
1344
+ };
1345
+ }
1346
+ function mergeTablePageSizeIntoSettings(settings, layoutId, pageSize) {
1347
+ const base = settings ?? {};
1348
+ return {
1349
+ ...base,
1350
+ layouts: {
1351
+ ...base.layouts,
1352
+ [layoutId]: {
1353
+ ...base.layouts?.[layoutId],
1354
+ table: {
1355
+ ...base.layouts?.[layoutId]?.table,
1356
+ pageSize,
1357
+ },
1358
+ },
1359
+ },
1360
+ };
1361
+ }
1362
+ //#endregion
1363
+
1364
+ class AXPReportChartViewComponent {
1365
+ constructor() {
1366
+ //#region ---- Dependencies ----
1367
+ this.vm = inject(AXPReportViewerViewModel);
1368
+ this.AXPPageStatus = AXPPageStatus;
1369
+ //#endregion
1370
+ //#region ---- Computed Properties ----
1371
+ /**
1372
+ * Current chart layout configuration
1373
+ */
1374
+ this.chartLayout = computed(() => {
1375
+ const layout = this.vm.currentLayout();
1376
+ return layout?.layout.type === 'chart' ? layout.layout : null;
1377
+ }, /* @ts-ignore */
1378
+ ...(ngDevMode ? [{ debugName: "chartLayout" }] : /* istanbul ignore next */ []));
1379
+ /**
1380
+ * Chart configuration options
1381
+ */
1382
+ this.chartOptions = computed(() => {
1383
+ const chartLayout = this.chartLayout();
1384
+ return chartLayout?.options || null;
1385
+ }, /* @ts-ignore */
1386
+ ...(ngDevMode ? [{ debugName: "chartOptions" }] : /* istanbul ignore next */ []));
1387
+ /**
1388
+ * Chart type for rendering
1389
+ */
1390
+ this.chartType = computed(() => {
1391
+ const options = this.chartOptions();
1392
+ return options?.chartType || 'bar';
1393
+ }, /* @ts-ignore */
1394
+ ...(ngDevMode ? [{ debugName: "chartType" }] : /* istanbul ignore next */ []));
1395
+ /**
1396
+ * Data mapping configuration
1397
+ */
1398
+ this.dataMapping = computed(() => {
1399
+ const options = this.chartOptions();
1400
+ return options?.dataMapping || null;
1401
+ }, /* @ts-ignore */
1402
+ ...(ngDevMode ? [{ debugName: "dataMapping" }] : /* istanbul ignore next */ []));
1403
+ //#endregion
1404
+ //#region ---- Effects ----
1405
+ /**
1406
+ * Effect to fetch chart data when conditions are met
1407
+ */
1408
+ this.#fetchData = effect(() => {
1409
+ if (this.vm.allowFetchData() && this.vm.shouldExecuteReport() && this.chartOptions()) {
1410
+ void untracked(() => this.vm.fetchData()).then((result) => {
1411
+ if (!result) {
1412
+ return;
1413
+ }
1414
+ const series = queryResultToChartSeries(result, this.dataMapping());
1415
+ const seriesColors = this.chartOptions()?.styling?.seriesColors;
1416
+ switch (this.chartType()) {
1417
+ case 'bar': {
1418
+ // Mapper returns flat bars for report series (not clustered).
1419
+ const bars = AXBarChartReportMapper(series);
1420
+ this.barChartData.set(applySeriesColors(bars, seriesColors));
1421
+ break;
1422
+ }
1423
+ case 'doughnut': {
1424
+ this.donutChartData.set(applySeriesColors(AXPieChartReportMapper(series), seriesColors));
1425
+ break;
1426
+ }
1427
+ case 'line':
1428
+ default: {
1429
+ this.lineChartData.set(AXLineChartReportMapper(series));
1430
+ break;
1431
+ }
1432
+ }
1433
+ });
1434
+ }
1435
+ }, /* @ts-ignore */
1436
+ ...(ngDevMode ? [{ debugName: "#fetchData" }] : /* istanbul ignore next */ []));
1437
+ this.legendOptions = signal({
1438
+ mode: 'vertical',
1439
+ showValues: true,
1440
+ showPercentage: true,
1441
+ interactive: true,
1442
+ }, /* @ts-ignore */
1443
+ ...(ngDevMode ? [{ debugName: "legendOptions" }] : /* istanbul ignore next */ []));
1444
+ this.barChartData = signal([], /* @ts-ignore */
1445
+ ...(ngDevMode ? [{ debugName: "barChartData" }] : /* istanbul ignore next */ []));
1446
+ this.barChartOptions = signal({}, /* @ts-ignore */
1447
+ ...(ngDevMode ? [{ debugName: "barChartOptions" }] : /* istanbul ignore next */ []));
1448
+ this.donutChartData = signal([], /* @ts-ignore */
1449
+ ...(ngDevMode ? [{ debugName: "donutChartData" }] : /* istanbul ignore next */ []));
1450
+ this.donutChartOptions = signal({
1451
+ totalLabel: 'Total',
1452
+ showTooltip: true,
1453
+ showDataLabels: false,
1454
+ donutWidth: 28,
1455
+ cornerRadius: 4,
1456
+ animationDuration: 800,
1457
+ animationEasing: 'cubic-out',
1458
+ }, /* @ts-ignore */
1459
+ ...(ngDevMode ? [{ debugName: "donutChartOptions" }] : /* istanbul ignore next */ []));
1460
+ this.lineChartData = signal([], /* @ts-ignore */
1461
+ ...(ngDevMode ? [{ debugName: "lineChartData" }] : /* istanbul ignore next */ []));
1462
+ this.lineChartOptions = signal({}, /* @ts-ignore */
1463
+ ...(ngDevMode ? [{ debugName: "lineChartOptions" }] : /* istanbul ignore next */ []));
1464
+ /**
1465
+ * Check if bar chart data has items
1466
+ */
1467
+ this.hasBarChartData = computed(() => {
1468
+ const data = this.barChartData();
1469
+ return Array.isArray(data) && data.length > 0;
1470
+ }, /* @ts-ignore */
1471
+ ...(ngDevMode ? [{ debugName: "hasBarChartData" }] : /* istanbul ignore next */ []));
1472
+ /**
1473
+ * Check if donut chart data has items
1474
+ */
1475
+ this.hasDonutChartData = computed(() => {
1476
+ const data = this.donutChartData();
1477
+ return Array.isArray(data) && data.length > 0;
1478
+ }, /* @ts-ignore */
1479
+ ...(ngDevMode ? [{ debugName: "hasDonutChartData" }] : /* istanbul ignore next */ []));
1480
+ /**
1481
+ * Check if line chart data has items
1482
+ */
1483
+ this.hasLineChartData = computed(() => {
1484
+ const data = this.lineChartData();
1485
+ return Array.isArray(data) && data.length > 0;
1486
+ }, /* @ts-ignore */
1487
+ ...(ngDevMode ? [{ debugName: "hasLineChartData" }] : /* istanbul ignore next */ []));
1488
+ /**
1489
+ * Check if chart has any data (for any chart type)
1490
+ */
1491
+ this.hasChartData = computed(() => {
1492
+ return this.hasBarChartData() || this.hasDonutChartData() || this.hasLineChartData();
1493
+ }, /* @ts-ignore */
1494
+ ...(ngDevMode ? [{ debugName: "hasChartData" }] : /* istanbul ignore next */ []));
1495
+ /**
1496
+ * Check if report has been executed and has no data
1497
+ */
1498
+ this.showNoDataState = computed(() => {
1499
+ return this.vm.hasBeenExecuted() && !this.hasChartData();
1500
+ }, /* @ts-ignore */
1501
+ ...(ngDevMode ? [{ debugName: "showNoDataState" }] : /* istanbul ignore next */ []));
1502
+ }
1503
+ //#endregion
1504
+ //#region ---- Effects ----
1505
+ /**
1506
+ * Effect to fetch chart data when conditions are met
1507
+ */
1508
+ #fetchData;
1509
+ //#endregion
1510
+ //#region ---- Helper Methods ----
1511
+ /**
1512
+ * Check if chart is ready to render
1513
+ */
1514
+ isChartReady() {
1515
+ return !!(this.chartOptions() && this.dataMapping());
1516
+ }
1517
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportChartViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1518
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPReportChartViewComponent, isStandalone: true, selector: "axp-report-chart-view", host: { classAttribute: "axp-report-chart-view w-full h-full flex flex-col" }, ngImport: i0, template: "<ng-container *translate=\"let t\">\n @if (vm.status() === AXPPageStatus.Processing) {\n <!-- Loading State -->\n <div class=\"__loading flex flex-col items-center justify-center h-full gap-6 p-8\">\n <div class=\"spinner spin w-10 h-10\"></div>\n <div class=\"__loading-text text-sm opacity-70\">\n {{ t('@report-management:report-viewer.components.chart.messages.loading') | async }}\n </div>\n </div>\n } @else if (vm.status() === AXPPageStatus.Error) {\n <!-- Error State -->\n <axp-state-message\n mode=\"error\"\n icon=\"fa-light fa-exclamation-triangle\"\n [title]=\"'@report-management:report-viewer.messages.error.fetch-title'\"\n [description]=\"vm.error() || '@report-management:report-viewer.messages.error.fetch-description'\"\n >\n </axp-state-message>\n } @else if (isChartReady() && showNoDataState()) {\n <!-- No Data State -->\n <axp-state-message\n icon=\"fa-light fa-chart-pie\"\n [title]=\"'@report-management:report-viewer.components.chart.empty-states.no-data.title'\"\n [description]=\"'@report-management:report-viewer.components.chart.empty-states.no-data.description'\"\n >\n </axp-state-message>\n } @else if (isChartReady()) {\n <!-- Chart Content -->\n <section\n class=\"__chart-container overflow-auto md:overflow-hidden rounded-2xl border h-full flex flex-col\"\n >\n @if (chartLayout()?.header) {\n <header\n class=\"__chart-header p-4 md:p-6 border-b flex flex-col sm:flex-row gap-2 sm:items-center sm:justify-between\"\n >\n <h3 class=\"__chart-title text-base md:text-lg font-semibold truncate\">\n {{ chartLayout()?.header?.title }}\n </h3>\n @if (chartLayout()?.header?.showDate) {\n <div class=\"__chart-date text-xs md:text-sm opacity-70 font-medium flex-shrink-0\">\n {{ '@general:common.date.today' | translate | async }}\n </div>\n }\n </header>\n }\n\n <div class=\"__chart-body flex min-h-0 flex-1 flex-col p-3 md:p-6\">\n @switch (chartType()) {\n @case ('bar') {\n <!-- ----- Bar chart with side legend ----- -->\n <div class=\"__chart-wrapper flex h-full min-h-0 w-full flex-col items-center gap-4 lg:flex-row lg:gap-6\">\n <div\n class=\"__chart-content flex h-full min-h-[250px] w-full flex-1 items-center justify-center overflow-hidden md:min-h-[300px]\"\n >\n <ax-bar-chart #barChart [data]=\"barChartData()\" [options]=\"barChartOptions()\"></ax-bar-chart>\n </div>\n @if (hasBarChartData()) {\n <div class=\"__chart-legend w-fit flex-shrink-0\">\n <ax-chart-legend [options]=\"legendOptions()\" [chart]=\"barChart\"></ax-chart-legend>\n </div>\n }\n </div>\n }\n @case ('doughnut') {\n <!-- ----- Donut in a square box; vertical legend on the right (same as bar) ----- -->\n <div class=\"__chart-wrapper flex h-full min-h-0 w-full flex-col items-center gap-4 lg:flex-row lg:gap-6\">\n <div\n class=\"__chart-content flex h-full min-h-[250px] w-full flex-1 items-center justify-center overflow-hidden md:min-h-[300px]\"\n >\n <div\n class=\"__chart-radial relative aspect-square h-full max-h-full w-auto max-w-full min-h-64 min-w-64 overflow-hidden\"\n >\n <ax-donut-chart\n #donutChart\n [data]=\"donutChartData()\"\n [options]=\"donutChartOptions()\"\n ></ax-donut-chart>\n </div>\n </div>\n @if (hasDonutChartData()) {\n <div class=\"__chart-legend w-fit flex-shrink-0\">\n <ax-chart-legend [options]=\"legendOptions()\" [chart]=\"donutChart\"></ax-chart-legend>\n </div>\n }\n </div>\n }\n @default {\n <!-- ----- Line chart with side legend ----- -->\n <div class=\"__chart-wrapper flex h-full min-h-0 w-full flex-col items-center gap-4 lg:flex-row lg:gap-6\">\n <div\n class=\"__chart-content flex h-full min-h-[250px] w-full flex-1 items-center justify-center overflow-hidden md:min-h-[300px]\"\n >\n <ax-line-chart #lineChart [data]=\"lineChartData()\" [options]=\"lineChartOptions()\"></ax-line-chart>\n </div>\n @if (hasLineChartData()) {\n <div class=\"__chart-legend w-fit flex-shrink-0\">\n <ax-chart-legend [options]=\"legendOptions()\" [chart]=\"lineChart\"></ax-chart-legend>\n </div>\n }\n </div>\n }\n }\n </div>\n\n @if (chartLayout()?.footer) {\n <footer class=\"__chart-footer p-4 md:p-6 border-t\">\n @if (chartLayout()?.footer?.customHtml) {\n <div [innerHTML]=\"$safeNavigationMigration(chartLayout()?.footer?.customHtml)\"></div>\n }\n </footer>\n }\n </section>\n } @else {\n <!-- No Configuration State -->\n <axp-state-message\n icon=\"fa-light fa-chart-line\"\n [title]=\"'@report-management:report-viewer.components.chart.empty-states.no-config.title'\"\n [description]=\"'@report-management:report-viewer.components.chart.empty-states.no-config.description'\"\n >\n </axp-state-message>\n }\n</ng-container>\n", styles: [":host ::ng-deep .__chart-radial ax-donut-chart{display:block!important;width:100%!important;height:100%!important;min-height:0!important;max-height:100%!important}:host ::ng-deep .__chart-radial ax-donut-chart .ax-donut-chart{min-height:0!important;height:100%!important;width:100%!important}:host ::ng-deep ax-chart-legend{display:block!important;height:auto!important;max-height:none;width:100%}:host ::ng-deep .ax-chart-legend{height:auto;max-height:none}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: i4.AXTranslatorDirective, selector: "[translate]" }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: AXBarChartComponent, selector: "ax-bar-chart", inputs: ["data", "options"], outputs: ["barClick"] }, { kind: "component", type: AXDonutChartComponent, selector: "ax-donut-chart", inputs: ["data", "options"], outputs: ["segmentClick", "segmentHover"] }, { kind: "component", type: AXLineChartComponent, selector: "ax-line-chart", inputs: ["data", "options"], outputs: ["pointClick"] }, { kind: "component", type: AXChartLegendComponent, selector: "ax-chart-legend", inputs: ["chart", "options"], outputs: ["itemClick", "itemMouseEnter", "itemMouseLeave"] }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }, { kind: "pipe", type: i4.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1519
+ }
1520
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportChartViewComponent, decorators: [{
1521
+ type: Component,
1522
+ args: [{ selector: 'axp-report-chart-view', imports: [
1523
+ CommonModule,
1524
+ AXTranslationModule,
1525
+ AXPWidgetCoreModule,
1526
+ AXBarChartComponent,
1527
+ AXDonutChartComponent,
1528
+ AXLineChartComponent,
1529
+ AXChartLegendComponent,
1530
+ AXPStateMessageComponent,
1531
+ ], host: {
1532
+ class: 'axp-report-chart-view w-full h-full flex flex-col',
1533
+ }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *translate=\"let t\">\n @if (vm.status() === AXPPageStatus.Processing) {\n <!-- Loading State -->\n <div class=\"__loading flex flex-col items-center justify-center h-full gap-6 p-8\">\n <div class=\"spinner spin w-10 h-10\"></div>\n <div class=\"__loading-text text-sm opacity-70\">\n {{ t('@report-management:report-viewer.components.chart.messages.loading') | async }}\n </div>\n </div>\n } @else if (vm.status() === AXPPageStatus.Error) {\n <!-- Error State -->\n <axp-state-message\n mode=\"error\"\n icon=\"fa-light fa-exclamation-triangle\"\n [title]=\"'@report-management:report-viewer.messages.error.fetch-title'\"\n [description]=\"vm.error() || '@report-management:report-viewer.messages.error.fetch-description'\"\n >\n </axp-state-message>\n } @else if (isChartReady() && showNoDataState()) {\n <!-- No Data State -->\n <axp-state-message\n icon=\"fa-light fa-chart-pie\"\n [title]=\"'@report-management:report-viewer.components.chart.empty-states.no-data.title'\"\n [description]=\"'@report-management:report-viewer.components.chart.empty-states.no-data.description'\"\n >\n </axp-state-message>\n } @else if (isChartReady()) {\n <!-- Chart Content -->\n <section\n class=\"__chart-container overflow-auto md:overflow-hidden rounded-2xl border h-full flex flex-col\"\n >\n @if (chartLayout()?.header) {\n <header\n class=\"__chart-header p-4 md:p-6 border-b flex flex-col sm:flex-row gap-2 sm:items-center sm:justify-between\"\n >\n <h3 class=\"__chart-title text-base md:text-lg font-semibold truncate\">\n {{ chartLayout()?.header?.title }}\n </h3>\n @if (chartLayout()?.header?.showDate) {\n <div class=\"__chart-date text-xs md:text-sm opacity-70 font-medium flex-shrink-0\">\n {{ '@general:common.date.today' | translate | async }}\n </div>\n }\n </header>\n }\n\n <div class=\"__chart-body flex min-h-0 flex-1 flex-col p-3 md:p-6\">\n @switch (chartType()) {\n @case ('bar') {\n <!-- ----- Bar chart with side legend ----- -->\n <div class=\"__chart-wrapper flex h-full min-h-0 w-full flex-col items-center gap-4 lg:flex-row lg:gap-6\">\n <div\n class=\"__chart-content flex h-full min-h-[250px] w-full flex-1 items-center justify-center overflow-hidden md:min-h-[300px]\"\n >\n <ax-bar-chart #barChart [data]=\"barChartData()\" [options]=\"barChartOptions()\"></ax-bar-chart>\n </div>\n @if (hasBarChartData()) {\n <div class=\"__chart-legend w-fit flex-shrink-0\">\n <ax-chart-legend [options]=\"legendOptions()\" [chart]=\"barChart\"></ax-chart-legend>\n </div>\n }\n </div>\n }\n @case ('doughnut') {\n <!-- ----- Donut in a square box; vertical legend on the right (same as bar) ----- -->\n <div class=\"__chart-wrapper flex h-full min-h-0 w-full flex-col items-center gap-4 lg:flex-row lg:gap-6\">\n <div\n class=\"__chart-content flex h-full min-h-[250px] w-full flex-1 items-center justify-center overflow-hidden md:min-h-[300px]\"\n >\n <div\n class=\"__chart-radial relative aspect-square h-full max-h-full w-auto max-w-full min-h-64 min-w-64 overflow-hidden\"\n >\n <ax-donut-chart\n #donutChart\n [data]=\"donutChartData()\"\n [options]=\"donutChartOptions()\"\n ></ax-donut-chart>\n </div>\n </div>\n @if (hasDonutChartData()) {\n <div class=\"__chart-legend w-fit flex-shrink-0\">\n <ax-chart-legend [options]=\"legendOptions()\" [chart]=\"donutChart\"></ax-chart-legend>\n </div>\n }\n </div>\n }\n @default {\n <!-- ----- Line chart with side legend ----- -->\n <div class=\"__chart-wrapper flex h-full min-h-0 w-full flex-col items-center gap-4 lg:flex-row lg:gap-6\">\n <div\n class=\"__chart-content flex h-full min-h-[250px] w-full flex-1 items-center justify-center overflow-hidden md:min-h-[300px]\"\n >\n <ax-line-chart #lineChart [data]=\"lineChartData()\" [options]=\"lineChartOptions()\"></ax-line-chart>\n </div>\n @if (hasLineChartData()) {\n <div class=\"__chart-legend w-fit flex-shrink-0\">\n <ax-chart-legend [options]=\"legendOptions()\" [chart]=\"lineChart\"></ax-chart-legend>\n </div>\n }\n </div>\n }\n }\n </div>\n\n @if (chartLayout()?.footer) {\n <footer class=\"__chart-footer p-4 md:p-6 border-t\">\n @if (chartLayout()?.footer?.customHtml) {\n <div [innerHTML]=\"$safeNavigationMigration(chartLayout()?.footer?.customHtml)\"></div>\n }\n </footer>\n }\n </section>\n } @else {\n <!-- No Configuration State -->\n <axp-state-message\n icon=\"fa-light fa-chart-line\"\n [title]=\"'@report-management:report-viewer.components.chart.empty-states.no-config.title'\"\n [description]=\"'@report-management:report-viewer.components.chart.empty-states.no-config.description'\"\n >\n </axp-state-message>\n }\n</ng-container>\n", styles: [":host ::ng-deep .__chart-radial ax-donut-chart{display:block!important;width:100%!important;height:100%!important;min-height:0!important;max-height:100%!important}:host ::ng-deep .__chart-radial ax-donut-chart .ax-donut-chart{min-height:0!important;height:100%!important;width:100%!important}:host ::ng-deep ax-chart-legend{display:block!important;height:auto!important;max-height:none;width:100%}:host ::ng-deep .ax-chart-legend{height:auto;max-height:none}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
1534
+ }] });
1535
+ //#region ----- Series color helpers -----
1536
+ function applySeriesColors(items, seriesColors) {
1537
+ if (!seriesColors || Object.keys(seriesColors).length === 0) {
1538
+ return items;
1539
+ }
1540
+ return items.map((item) => {
1541
+ const color = seriesColors[item.id] ?? (item.label ? seriesColors[item.label] : undefined);
1542
+ return color ? { ...item, color } : item;
1543
+ });
1544
+ }
1545
+
1546
+ class AXPReportPdfViewComponent {
1547
+ constructor() {
1548
+ //#region ---- Dependencies ----
1549
+ this.vm = inject(AXPReportViewerViewModel);
1550
+ this.AXPPageStatus = AXPPageStatus;
1551
+ this.sanitizer = inject(DomSanitizer);
1552
+ //#endregion
1553
+ //#region ---- Computed Properties ----
1554
+ /**
1555
+ * Current PDF layout configuration
1556
+ */
1557
+ this.pdfLayout = computed(() => {
1558
+ const layout = this.vm.currentLayout();
1559
+ return layout?.layout.type === 'pdf' ? layout.layout : null;
1560
+ }, /* @ts-ignore */
1561
+ ...(ngDevMode ? [{ debugName: "pdfLayout" }] : /* istanbul ignore next */ []));
1562
+ /**
1563
+ * PDF layout options
1564
+ */
1565
+ this.pdfOptions = computed(() => {
1566
+ const pdfLayout = this.pdfLayout();
1567
+ return pdfLayout?.options || {};
1568
+ }, /* @ts-ignore */
1569
+ ...(ngDevMode ? [{ debugName: "pdfOptions" }] : /* istanbul ignore next */ []));
1570
+ /**
1571
+ * PDF source URL (sanitized for embed)
1572
+ */
1573
+ this.pdfSource = computed(() => {
1574
+ const pdfData = this.pdfData();
1575
+ if (!pdfData) {
1576
+ return null;
1577
+ }
1578
+ // Handle URL
1579
+ if (pdfData.url) {
1580
+ return this.sanitizer.bypassSecurityTrustResourceUrl(pdfData.url);
1581
+ }
1582
+ // Handle base64
1583
+ if (pdfData.base64) {
1584
+ const base64Data = pdfData.base64.startsWith('data:')
1585
+ ? pdfData.base64
1586
+ : `data:application/pdf;base64,${pdfData.base64}`;
1587
+ return this.sanitizer.bypassSecurityTrustResourceUrl(base64Data);
1588
+ }
1589
+ // Handle blob
1590
+ if (pdfData.blob) {
1591
+ const blobUrl = URL.createObjectURL(pdfData.blob);
1592
+ return this.sanitizer.bypassSecurityTrustResourceUrl(blobUrl);
1593
+ }
1594
+ return null;
1595
+ }, /* @ts-ignore */
1596
+ ...(ngDevMode ? [{ debugName: "pdfSource" }] : /* istanbul ignore next */ []));
1597
+ /**
1598
+ * PDF filename for download
1599
+ */
1600
+ this.pdfFilename = computed(() => {
1601
+ const pdfData = this.pdfData();
1602
+ return pdfData?.filename || 'report.pdf';
1603
+ }, /* @ts-ignore */
1604
+ ...(ngDevMode ? [{ debugName: "pdfFilename" }] : /* istanbul ignore next */ []));
1605
+ /**
1606
+ * PDF embed height
1607
+ */
1608
+ this.embedHeight = computed(() => {
1609
+ const options = this.pdfOptions();
1610
+ return options.height || '100%';
1611
+ }, /* @ts-ignore */
1612
+ ...(ngDevMode ? [{ debugName: "embedHeight" }] : /* istanbul ignore next */ []));
1613
+ /**
1614
+ * Check if PDF is ready to render
1615
+ */
1616
+ this.isPdfReady = computed(() => {
1617
+ return !!this.pdfSource();
1618
+ }, /* @ts-ignore */
1619
+ ...(ngDevMode ? [{ debugName: "isPdfReady" }] : /* istanbul ignore next */ []));
1620
+ /**
1621
+ * Check if download button should be shown
1622
+ */
1623
+ this.showDownloadButton = computed(() => {
1624
+ const options = this.pdfOptions();
1625
+ return options.showDownloadButton !== false; // Default to true
1626
+ }, /* @ts-ignore */
1627
+ ...(ngDevMode ? [{ debugName: "showDownloadButton" }] : /* istanbul ignore next */ []));
1628
+ /**
1629
+ * Check if print button should be shown
1630
+ */
1631
+ this.showPrintButton = computed(() => {
1632
+ const options = this.pdfOptions();
1633
+ return options.showPrintButton !== false; // Default to true
1634
+ }, /* @ts-ignore */
1635
+ ...(ngDevMode ? [{ debugName: "showPrintButton" }] : /* istanbul ignore next */ []));
1636
+ /**
1637
+ * Check if zoom controls should be shown
1638
+ */
1639
+ this.showZoomControls = computed(() => {
1640
+ const options = this.pdfOptions();
1641
+ return options.showZoomControls !== false; // Default to true
1642
+ }, /* @ts-ignore */
1643
+ ...(ngDevMode ? [{ debugName: "showZoomControls" }] : /* istanbul ignore next */ []));
1644
+ /**
1645
+ * Check if fullscreen button should be shown
1646
+ */
1647
+ this.showFullscreenButton = computed(() => {
1648
+ const options = this.pdfOptions();
1649
+ return options.showFullscreenButton !== false; // Default to true
1650
+ }, /* @ts-ignore */
1651
+ ...(ngDevMode ? [{ debugName: "showFullscreenButton" }] : /* istanbul ignore next */ []));
1652
+ /**
1653
+ * Current zoom level
1654
+ */
1655
+ this.currentZoom = signal(100, /* @ts-ignore */
1656
+ ...(ngDevMode ? [{ debugName: "currentZoom" }] : /* istanbul ignore next */ []));
1657
+ /**
1658
+ * PDF data from execution result
1659
+ */
1660
+ this.pdfData = signal(null, /* @ts-ignore */
1661
+ ...(ngDevMode ? [{ debugName: "pdfData" }] : /* istanbul ignore next */ []));
1662
+ /**
1663
+ * Fullscreen state
1664
+ */
1665
+ this.isFullscreen = signal(false, /* @ts-ignore */
1666
+ ...(ngDevMode ? [{ debugName: "isFullscreen" }] : /* istanbul ignore next */ []));
1667
+ //#endregion
1668
+ //#region ---- Effects ----
1669
+ /**
1670
+ * Effect to fetch PDF data when conditions are met
1671
+ */
1672
+ this.#fetchData = effect(() => {
1673
+ if (this.vm.allowFetchData() && this.vm.shouldExecuteReport() && this.pdfLayout()) {
1674
+ void untracked(() => this.vm.fetchData()).then((result) => {
1675
+ if (!result) {
1676
+ return;
1677
+ }
1678
+ const pdfResult = queryResultToPdfSource(result);
1679
+ this.pdfData.set(pdfResult);
1680
+ if (pdfResult) {
1681
+ const options = this.pdfOptions();
1682
+ if (options.defaultZoom) {
1683
+ this.currentZoom.set(options.defaultZoom);
1684
+ }
1685
+ }
1686
+ });
1687
+ }
1688
+ }, /* @ts-ignore */
1689
+ ...(ngDevMode ? [{ debugName: "#fetchData" }] : /* istanbul ignore next */ []));
1690
+ }
1691
+ //#endregion
1692
+ //#region ---- Effects ----
1693
+ /**
1694
+ * Effect to fetch PDF data when conditions are met
1695
+ */
1696
+ #fetchData;
1697
+ //#endregion
1698
+ //#region ---- Methods ----
1699
+ /**
1700
+ * Handle download PDF
1701
+ */
1702
+ downloadPdf() {
1703
+ const pdfData = this.pdfData();
1704
+ if (!pdfData) {
1705
+ return;
1706
+ }
1707
+ let url;
1708
+ let filename = this.pdfFilename();
1709
+ let shouldRevokeUrl = false;
1710
+ if (pdfData.url) {
1711
+ // For URL, create a temporary link
1712
+ url = pdfData.url;
1713
+ }
1714
+ else if (pdfData.base64) {
1715
+ // Convert base64 to blob and create URL
1716
+ const base64Data = pdfData.base64.startsWith('data:')
1717
+ ? pdfData.base64.split(',')[1]
1718
+ : pdfData.base64;
1719
+ const byteCharacters = atob(base64Data);
1720
+ const byteNumbers = new Array(byteCharacters.length);
1721
+ for (let i = 0; i < byteCharacters.length; i++) {
1722
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
1723
+ }
1724
+ const byteArray = new Uint8Array(byteNumbers);
1725
+ const blob = new Blob([byteArray], { type: 'application/pdf' });
1726
+ url = URL.createObjectURL(blob);
1727
+ shouldRevokeUrl = true;
1728
+ }
1729
+ else if (pdfData.blob) {
1730
+ url = URL.createObjectURL(pdfData.blob);
1731
+ shouldRevokeUrl = true;
1732
+ }
1733
+ else {
1734
+ return;
1735
+ }
1736
+ // Create download link
1737
+ const link = document.createElement('a');
1738
+ link.href = url;
1739
+ link.download = filename;
1740
+ document.body.appendChild(link);
1741
+ link.click();
1742
+ document.body.removeChild(link);
1743
+ // Clean up blob URL if created
1744
+ if (shouldRevokeUrl) {
1745
+ setTimeout(() => URL.revokeObjectURL(url), 100);
1746
+ }
1747
+ }
1748
+ /**
1749
+ * Handle print PDF
1750
+ */
1751
+ printPdf() {
1752
+ const pdfData = this.pdfData();
1753
+ if (!pdfData) {
1754
+ return;
1755
+ }
1756
+ let url;
1757
+ if (pdfData.url) {
1758
+ url = pdfData.url;
1759
+ }
1760
+ else if (pdfData.base64) {
1761
+ const base64Data = pdfData.base64.startsWith('data:')
1762
+ ? pdfData.base64
1763
+ : `data:application/pdf;base64,${pdfData.base64}`;
1764
+ url = base64Data;
1765
+ }
1766
+ else if (pdfData.blob) {
1767
+ url = URL.createObjectURL(pdfData.blob);
1768
+ }
1769
+ else {
1770
+ return;
1771
+ }
1772
+ // Open PDF in new window for printing
1773
+ const printWindow = window.open(url, '_blank');
1774
+ if (printWindow) {
1775
+ printWindow.onload = () => {
1776
+ printWindow.print();
1777
+ };
1778
+ }
1779
+ }
1780
+ /**
1781
+ * Handle zoom in
1782
+ */
1783
+ zoomIn() {
1784
+ const current = this.currentZoom();
1785
+ if (current < 500) {
1786
+ this.currentZoom.set(Math.min(current + 25, 500));
1787
+ }
1788
+ }
1789
+ /**
1790
+ * Handle zoom out
1791
+ */
1792
+ zoomOut() {
1793
+ const current = this.currentZoom();
1794
+ if (current > 25) {
1795
+ this.currentZoom.set(Math.max(current - 25, 25));
1796
+ }
1797
+ }
1798
+ /**
1799
+ * Handle zoom reset
1800
+ */
1801
+ zoomReset() {
1802
+ const options = this.pdfOptions();
1803
+ this.currentZoom.set(options.defaultZoom || 100);
1804
+ }
1805
+ /**
1806
+ * Handle fullscreen toggle
1807
+ */
1808
+ toggleFullscreen() {
1809
+ const isFullscreen = this.isFullscreen();
1810
+ this.isFullscreen.set(!isFullscreen);
1811
+ }
1812
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportPdfViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1813
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPReportPdfViewComponent, isStandalone: true, selector: "axp-report-pdf-view", host: { classAttribute: "axp-report-pdf-view w-full h-full flex flex-col" }, ngImport: i0, template: "<ng-container *translate=\"let t\">\n @if (vm.status() === AXPPageStatus.Processing) {\n <!-- Loading State -->\n <div class=\"__loading flex flex-col items-center justify-center h-full gap-6 p-8\">\n <div class=\"spinner spin w-10 h-10\"></div>\n <div class=\"__loading-text text-sm opacity-70\">\n {{ t('@report-management:report-viewer.components.pdf.messages.loading') | async }}\n </div>\n </div>\n } @else if (vm.status() === AXPPageStatus.Error) {\n <!-- Error State -->\n <axp-state-message mode=\"error\" icon=\"fa-light fa-exclamation-triangle\"\n [title]=\"'@report-management:report-viewer.messages.error.fetch-title'\"\n [description]=\"vm.error() || '@report-management:report-viewer.messages.error.fetch-description'\">\n </axp-state-message>\n } @else if (isPdfReady()) {\n <!-- PDF Embed Only -->\n <embed class=\"__pdf-embed w-full h-full\" [src]=\"pdfSource()\" type=\"application/pdf\" />\n } @else {\n <!-- No PDF Data State -->\n <axp-state-message icon=\"fa-light fa-file-pdf\"\n [title]=\"'@report-management:report-viewer.components.pdf.empty-states.no-data.title'\"\n [description]=\"'@report-management:report-viewer.components.pdf.empty-states.no-data.description'\">\n </axp-state-message>\n }\n</ng-container>", styles: [".axp-report-pdf-view .__loading{min-height:400px}.axp-report-pdf-view .__pdf-container{background:var(--ax-color-background)}.axp-report-pdf-view .__pdf-container.__fullscreen{position:fixed;z-index:9999;inset:0;border-radius:0}.axp-report-pdf-view .__pdf-toolbar{background:var(--ax-color-surface)}.axp-report-pdf-view .__pdf-body{background:var(--ax-color-background-secondary)}.axp-report-pdf-view .__pdf-wrapper{min-height:400px}.axp-report-pdf-view .__pdf-embed{--tw-border-style: none;border-style:none;box-shadow:0 2px 8px #0000001a;transition:width .3s ease,height .3s ease}.axp-report-pdf-view .__zoom-level{-webkit-user-select:none;user-select:none}@media(max-width:640px){.axp-report-pdf-view .__zoom-controls{margin-top:calc(var(--spacing, .25rem) * 2);width:100%;justify-content:center;margin-left:0}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: i4.AXTranslatorDirective, selector: "[translate]" }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1814
+ }
1815
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportPdfViewComponent, decorators: [{
1816
+ type: Component,
1817
+ args: [{ selector: 'axp-report-pdf-view', imports: [
1818
+ CommonModule,
1819
+ AXButtonModule,
1820
+ AXDecoratorModule,
1821
+ AXTranslationModule,
1822
+ AXPWidgetCoreModule,
1823
+ AXPStateMessageComponent,
1824
+ ], host: {
1825
+ class: 'axp-report-pdf-view w-full h-full flex flex-col',
1826
+ }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<ng-container *translate=\"let t\">\n @if (vm.status() === AXPPageStatus.Processing) {\n <!-- Loading State -->\n <div class=\"__loading flex flex-col items-center justify-center h-full gap-6 p-8\">\n <div class=\"spinner spin w-10 h-10\"></div>\n <div class=\"__loading-text text-sm opacity-70\">\n {{ t('@report-management:report-viewer.components.pdf.messages.loading') | async }}\n </div>\n </div>\n } @else if (vm.status() === AXPPageStatus.Error) {\n <!-- Error State -->\n <axp-state-message mode=\"error\" icon=\"fa-light fa-exclamation-triangle\"\n [title]=\"'@report-management:report-viewer.messages.error.fetch-title'\"\n [description]=\"vm.error() || '@report-management:report-viewer.messages.error.fetch-description'\">\n </axp-state-message>\n } @else if (isPdfReady()) {\n <!-- PDF Embed Only -->\n <embed class=\"__pdf-embed w-full h-full\" [src]=\"pdfSource()\" type=\"application/pdf\" />\n } @else {\n <!-- No PDF Data State -->\n <axp-state-message icon=\"fa-light fa-file-pdf\"\n [title]=\"'@report-management:report-viewer.components.pdf.empty-states.no-data.title'\"\n [description]=\"'@report-management:report-viewer.components.pdf.empty-states.no-data.description'\">\n </axp-state-message>\n }\n</ng-container>", styles: [".axp-report-pdf-view .__loading{min-height:400px}.axp-report-pdf-view .__pdf-container{background:var(--ax-color-background)}.axp-report-pdf-view .__pdf-container.__fullscreen{position:fixed;z-index:9999;inset:0;border-radius:0}.axp-report-pdf-view .__pdf-toolbar{background:var(--ax-color-surface)}.axp-report-pdf-view .__pdf-body{background:var(--ax-color-background-secondary)}.axp-report-pdf-view .__pdf-wrapper{min-height:400px}.axp-report-pdf-view .__pdf-embed{--tw-border-style: none;border-style:none;box-shadow:0 2px 8px #0000001a;transition:width .3s ease,height .3s ease}.axp-report-pdf-view .__zoom-level{-webkit-user-select:none;user-select:none}@media(max-width:640px){.axp-report-pdf-view .__zoom-controls{margin-top:calc(var(--spacing, .25rem) * 2);width:100%;justify-content:center;margin-left:0}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
1827
+ }] });
1828
+
1829
+ class AXPReportTableViewComponent {
1830
+ constructor() {
1831
+ //#region ---- Dependencies ----
1832
+ this.vm = inject(AXPReportViewerViewModel);
1833
+ this.commandService = inject(AXPCommandService);
1834
+ this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
1835
+ this.grid = viewChild('grid', /* @ts-ignore */
1836
+ ...(ngDevMode ? [{ debugName: "grid" }] : /* istanbul ignore next */ []));
1837
+ //#endregion
1838
+ //#region ---- State ----
1839
+ this.columns = signal([], /* @ts-ignore */
1840
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
1841
+ this.sortedFields = signal([], /* @ts-ignore */
1842
+ ...(ngDevMode ? [{ debugName: "sortedFields" }] : /* istanbul ignore next */ []));
1843
+ this.tableSummary = signal(null, /* @ts-ignore */
1844
+ ...(ngDevMode ? [{ debugName: "tableSummary" }] : /* istanbul ignore next */ []));
1845
+ this.handledExecutionRequestId = 0;
1846
+ this.lastDataSourcePreferencesKey = null;
1847
+ //#endregion
1848
+ //#region ---- Effects ----
1849
+ this.#resetExecutionHandler = effect(() => {
1850
+ const reportId = this.vm.reportId();
1851
+ untracked(() => {
1852
+ this.handledExecutionRequestId = 0;
1853
+ this.lastDataSourcePreferencesKey = null;
1854
+ void reportId;
1855
+ });
1856
+ }, /* @ts-ignore */
1857
+ ...(ngDevMode ? [{ debugName: "#resetExecutionHandler" }] : /* istanbul ignore next */ []));
1858
+ this.#syncTableColumns = effect(() => {
1859
+ const layoutDef = this.vm.currentLayout();
1860
+ const tableLayout = layoutDef?.layout;
1861
+ if (!tableLayout || tableLayout.type !== 'table') {
1862
+ this.columns.set([]);
1863
+ return;
1864
+ }
1865
+ const savedTable = this.vm.tableLayoutUserSettings();
1866
+ const groupBy = tableLayout.groupBy;
1867
+ const columns = tableLayout.columns;
1868
+ const savedColumns = savedTable?.columns;
1869
+ void (async () => {
1870
+ const builtColumns = await buildReportTableColumnsAsync(columns, savedColumns, this.expressionEvaluator);
1871
+ this.columns.set(applyGroupByDrillLinksToColumns(builtColumns, groupBy));
1872
+ })();
1873
+ }, /* @ts-ignore */
1874
+ ...(ngDevMode ? [{ debugName: "#syncTableColumns" }] : /* istanbul ignore next */ []));
1875
+ this.#syncDataSourcePreferences = effect(() => {
1876
+ const layoutDef = this.vm.currentLayout();
1877
+ if (!layoutDef) {
1878
+ return;
1879
+ }
1880
+ const tableLayout = layoutDef.layout;
1881
+ if (!tableLayout || tableLayout.type !== 'table') {
1882
+ return;
1883
+ }
1884
+ const savedTable = this.vm.tableLayoutUserSettings();
1885
+ const queryDataSource = layoutDef.dataSource;
1886
+ const defaultSort = tableLayout.options?.sorting?.length
1887
+ ? tableLayout.options.sorting
1888
+ : (queryDataSource?.sortFields ?? []);
1889
+ const effectiveSort = savedTable?.sorts?.length ? savedTable.sorts : defaultSort;
1890
+ const pageSize = savedTable?.pageSize ?? 10;
1891
+ const preferencesKey = `${layoutDef.id}:${pageSize}:${JSON.stringify(effectiveSort)}`;
1892
+ if (this.lastDataSourcePreferencesKey === preferencesKey) {
1893
+ return;
1894
+ }
1895
+ untracked(() => {
1896
+ this.lastDataSourcePreferencesKey = preferencesKey;
1897
+ this.sortedFields.set(effectiveSort);
1898
+ this.applySortToDataSource(effectiveSort);
1899
+ this.dataSource.setPageSize(pageSize);
1900
+ });
1901
+ }, /* @ts-ignore */
1902
+ ...(ngDevMode ? [{ debugName: "#syncDataSourcePreferences" }] : /* istanbul ignore next */ []));
1903
+ /**
1904
+ * Refresh grid once per execute-report request.
1905
+ */
1906
+ this.#fetchData = effect(() => {
1907
+ const requestId = this.vm.executionRequestId();
1908
+ const grid = this.grid();
1909
+ if (!grid || requestId <= this.handledExecutionRequestId || !this.vm.allowFetchData()) {
1910
+ return;
1911
+ }
1912
+ untracked(() => {
1913
+ this.handledExecutionRequestId = requestId;
1914
+ grid.refresh();
1915
+ });
1916
+ }, /* @ts-ignore */
1917
+ ...(ngDevMode ? [{ debugName: "#fetchData" }] : /* istanbul ignore next */ []));
1918
+ //#endregion
1919
+ //#region ---- Computed Properties ----
1920
+ this.layout = computed(() => {
1921
+ return this.vm.currentLayout()?.layout;
1922
+ }, /* @ts-ignore */
1923
+ ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
1924
+ this.dataSource = new AXDataSource({
1925
+ load: async (e) => {
1926
+ if (!this.vm.allowFetchData()) {
1927
+ this.tableSummary.set(null);
1928
+ return { items: [], total: 0 };
1929
+ }
1930
+ const result = await this.vm.fetchData(e);
1931
+ const page = runtimeQueryCollectionData(result);
1932
+ if (!page) {
1933
+ this.tableSummary.set(null);
1934
+ return { items: [], total: 0 };
1935
+ }
1936
+ this.tableSummary.set(page.aggregateRows?.[0] ?? null);
1937
+ return {
1938
+ items: (page.items ?? []),
1939
+ total: Number(page.total ?? page.items?.length ?? 0),
1940
+ };
1941
+ },
1942
+ pageSize: 10,
1943
+ key: 'id',
1944
+ });
1945
+ this.visibleColumns = computed(() => this.columns().filter((c) => c.visible), /* @ts-ignore */
1946
+ ...(ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []));
1947
+ this.paginationEnabled = computed(() => {
1948
+ return this.layout().options?.pagination !== false;
1949
+ }, /* @ts-ignore */
1950
+ ...(ngDevMode ? [{ debugName: "paginationEnabled" }] : /* istanbul ignore next */ []));
1951
+ this.hasReportFooterSummary = computed(() => {
1952
+ return ((this.layout().summary?.some((s) => s.displayIn === 'reportFooter') ?? false) &&
1953
+ this.tableSummary() !== null);
1954
+ }, /* @ts-ignore */
1955
+ ...(ngDevMode ? [{ debugName: "hasReportFooterSummary" }] : /* istanbul ignore next */ []));
1956
+ this.rowActions = computed(() => {
1957
+ const tableLayout = this.layout();
1958
+ const generated = buildDrillRowActionsFromGroupBy(tableLayout.groupBy, this.vm.reportDefinition()?.id);
1959
+ const manual = tableLayout.actions ?? [];
1960
+ return [...generated, ...manual];
1961
+ }, /* @ts-ignore */
1962
+ ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
1963
+ this.primaryRowAction = computed(() => {
1964
+ return (this.rowActions().filter((a) => a.priority === 'primary')?.map((a) => this.convertToRowCommandItem(a)) ??
1965
+ []);
1966
+ }, /* @ts-ignore */
1967
+ ...(ngDevMode ? [{ debugName: "primaryRowAction" }] : /* istanbul ignore next */ []));
1968
+ this.hasPrimaryRowAction = computed(() => {
1969
+ return this.primaryRowAction().length > 0;
1970
+ }, /* @ts-ignore */
1971
+ ...(ngDevMode ? [{ debugName: "hasPrimaryRowAction" }] : /* istanbul ignore next */ []));
1972
+ this.secondaryRowAction = computed(() => {
1973
+ return (this.rowActions()
1974
+ .filter((a) => a.priority === 'secondary')
1975
+ ?.map((a) => this.convertToRowCommandItem(a)) ?? []);
1976
+ }, /* @ts-ignore */
1977
+ ...(ngDevMode ? [{ debugName: "secondaryRowAction" }] : /* istanbul ignore next */ []));
1978
+ this.hasSecondaryRowAction = computed(() => {
1979
+ return this.secondaryRowAction().length > 0;
1980
+ }, /* @ts-ignore */
1981
+ ...(ngDevMode ? [{ debugName: "hasSecondaryRowAction" }] : /* istanbul ignore next */ []));
1982
+ this.defaultGroupByDrillDefinition = computed(() => {
1983
+ const groupBy = this.layout().groupBy;
1984
+ if (!hasGroupByDrillLinks(groupBy)) {
1985
+ return null;
1986
+ }
1987
+ return groupBy?.find((definition) => definition.default) ?? groupBy?.[0] ?? null;
1988
+ }, /* @ts-ignore */
1989
+ ...(ngDevMode ? [{ debugName: "defaultGroupByDrillDefinition" }] : /* istanbul ignore next */ []));
1990
+ this.defaultRowAction = computed(() => {
1991
+ const defaultAction = this.rowActions().find((a) => a.default);
1992
+ if (!defaultAction) {
1993
+ return null;
1994
+ }
1995
+ return this.convertToRowCommandItem(defaultAction);
1996
+ }, /* @ts-ignore */
1997
+ ...(ngDevMode ? [{ debugName: "defaultRowAction" }] : /* istanbul ignore next */ []));
1998
+ }
1999
+ //#endregion
2000
+ //#region ---- Effects ----
2001
+ #resetExecutionHandler;
2002
+ #syncTableColumns;
2003
+ #syncDataSourcePreferences;
2004
+ /**
2005
+ * Refresh grid once per execute-report request.
2006
+ */
2007
+ #fetchData;
2008
+ //#endregion
2009
+ //#region ---- Event Handlers ----
2010
+ handleRowClick(event) {
2011
+ void this.handleRowAction(event.name, event.data);
2012
+ }
2013
+ handleDbRowClick(event) {
2014
+ const defaultGroupByDrill = this.defaultGroupByDrillDefinition();
2015
+ if (defaultGroupByDrill?.displayField) {
2016
+ void this.handleGroupByDrill(defaultGroupByDrill.displayField, event.data);
2017
+ return;
2018
+ }
2019
+ if (this.defaultRowAction()) {
2020
+ void this.handleRowAction(this.defaultRowAction().name, event.data);
2021
+ }
2022
+ }
2023
+ async handleGroupByDrill(displayField, data) {
2024
+ const definition = findGroupByDefinitionForDisplayField(this.layout().groupBy, displayField);
2025
+ if (!definition) {
2026
+ return;
2027
+ }
2028
+ const reportId = this.vm.reportDefinition()?.id;
2029
+ if (!reportId) {
2030
+ return;
2031
+ }
2032
+ const commandOptions = buildGroupByDrillCommandOptions(definition, reportId);
2033
+ const evaluatedOptions = await this.evaluateActionOptions(commandOptions, data);
2034
+ await this.commandService.execute('ReportManagement.Report:OpenInPopup', {
2035
+ data: data,
2036
+ options: evaluatedOptions,
2037
+ });
2038
+ }
2039
+ async handleRowAction(name, data) {
2040
+ const action = this.rowActions().find((item) => item.command?.name === name);
2041
+ const commandName = action?.command?.name ?? name;
2042
+ const evaluatedOptions = await this.evaluateActionOptions(action?.command?.options, data);
2043
+ await this.commandService.execute(commandName, {
2044
+ data: data,
2045
+ options: evaluatedOptions,
2046
+ });
2047
+ }
2048
+ async evaluateActionOptions(options, data) {
2049
+ if (!options) {
2050
+ return {};
2051
+ }
2052
+ const scope = {
2053
+ context: {
2054
+ eval: (path) => get(data, path),
2055
+ },
2056
+ };
2057
+ return (await this.expressionEvaluator.evaluate(options, scope));
2058
+ }
2059
+ handleColumnSort(columnField) {
2060
+ const column = this.layout().columns?.find((c) => c.field === columnField);
2061
+ if (!column?.sortable) {
2062
+ return;
2063
+ }
2064
+ const activeSorts = [...this.sortedFields()];
2065
+ const index = activeSorts.findIndex((s) => s.name === columnField);
2066
+ const currentDir = index >= 0 ? activeSorts[index].dir : undefined;
2067
+ const newDir = currentDir === 'asc' ? 'desc' : currentDir === 'desc' ? undefined : 'asc';
2068
+ const nextSorts = newDir ? [{ name: columnField, dir: newDir }] : [];
2069
+ this.sortedFields.set(nextSorts);
2070
+ this.applySortToDataSource(nextSorts);
2071
+ void this.vm.saveTableSorts(nextSorts);
2072
+ this.grid()?.refresh();
2073
+ }
2074
+ onColumnSizeChanged(event) {
2075
+ if (!event.isUserInteraction || event.type !== 'end' || !event.data) {
2076
+ return;
2077
+ }
2078
+ const dataField = String(event.data.dataField ?? '');
2079
+ const field = resolveColumnFieldFromDataField(dataField);
2080
+ const width = parseColumnResizeWidth(event.data.width);
2081
+ if (!field || width == null) {
2082
+ return;
2083
+ }
2084
+ const nextColumns = this.columns().map((col) => col.field === field ? { ...col, width } : col);
2085
+ this.columns.set(nextColumns);
2086
+ void this.vm.saveTableColumnWidth(field, width, nextColumns);
2087
+ }
2088
+ onColumnsOrderChanged(event) {
2089
+ if (!event.isUserInteraction || !event.data?.event) {
2090
+ return;
2091
+ }
2092
+ const dragEvent = event.data.event;
2093
+ const offset = this.leadingColumnCount();
2094
+ const previousIndex = dragEvent.previousIndex - offset;
2095
+ const currentIndex = dragEvent.currentIndex - offset;
2096
+ if (previousIndex < 0 || currentIndex < 0) {
2097
+ return;
2098
+ }
2099
+ const nextColumns = [...this.columns()];
2100
+ const source = nextColumns[previousIndex];
2101
+ if (!source || source.pin) {
2102
+ return;
2103
+ }
2104
+ if (nextColumns[currentIndex]?.pin) {
2105
+ return;
2106
+ }
2107
+ moveItemInArray(nextColumns, previousIndex, currentIndex);
2108
+ this.columns.set(nextColumns);
2109
+ void this.vm.saveTableColumnOrder(nextColumns);
2110
+ }
2111
+ onPageChanged(event) {
2112
+ if (event.isUserInteraction) {
2113
+ void this.vm.saveTablePageSize(event.take);
2114
+ }
2115
+ }
2116
+ //#endregion
2117
+ //#region ---- Sort Helpers ----
2118
+ resolveColumnSortDirection(columnField) {
2119
+ const dir = this.sortedFields().find((s) => s.name === columnField)?.dir;
2120
+ return dir === 'asc' || dir === 'desc' ? dir : undefined;
2121
+ }
2122
+ //#endregion
2123
+ //#region ---- Summary Helpers ----
2124
+ getSummaryDisplayValue(field) {
2125
+ const summary = this.tableSummary();
2126
+ if (!summary || !(field in summary)) {
2127
+ return null;
2128
+ }
2129
+ const value = summary[field];
2130
+ if (value == null) {
2131
+ return null;
2132
+ }
2133
+ return String(value);
2134
+ }
2135
+ isFirstVisibleColumn(field) {
2136
+ return this.visibleColumns()[0]?.field === field;
2137
+ }
2138
+ //#endregion
2139
+ //#region ---- Helpers ----
2140
+ leadingColumnCount() {
2141
+ let count = 0;
2142
+ if (this.vm.showRowIndexColumn()) {
2143
+ count++;
2144
+ }
2145
+ if (this.vm.canSelectRows()) {
2146
+ count++;
2147
+ }
2148
+ return count;
2149
+ }
2150
+ applySortToDataSource(sorts) {
2151
+ const sortOptions = sorts.map((s) => ({ field: s.name, dir: s.dir }));
2152
+ this.dataSource.sort(...sortOptions);
2153
+ }
2154
+ convertToRowCommandItem(action) {
2155
+ const systemAction = getSystemActions(action.type);
2156
+ if (!systemAction) {
2157
+ return {
2158
+ icon: action.icon ?? 'fa-light fa-eye',
2159
+ iconOnly: action.iconOnly ?? true,
2160
+ text: action.title,
2161
+ name: action.command?.name,
2162
+ divided: action.break,
2163
+ color: action.color,
2164
+ };
2165
+ }
2166
+ const defaultAction = applySystemActionDefault(action, systemAction.key);
2167
+ return {
2168
+ icon: defaultAction.icon ?? 'fa-light fa-eye',
2169
+ iconOnly: action.iconOnly ?? true,
2170
+ text: defaultAction.title,
2171
+ name: action.command?.name,
2172
+ divided: defaultAction.break ?? action.break,
2173
+ color: defaultAction.color,
2174
+ };
2175
+ }
2176
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportTableViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2177
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPReportTableViewComponent, isStandalone: true, selector: "axp-report-table-view", host: { classAttribute: "axp-report-table-view flex flex-col flex-1 min-h-0 h-full w-full" }, viewQueries: [{ propertyName: "grid", first: true, predicate: ["grid"], descendants: true, isSignal: true }], ngImport: i0, template: "<ax-data-table\n #grid\n [dataSource]=\"dataSource\"\n [paging]=\"paginationEnabled()\"\n [showFooter]=\"hasReportFooterSummary()\"\n [allowReordering]=\"true\"\n (onRowDbClick)=\"handleDbRowClick($event)\"\n (onColumnSizeChanged)=\"onColumnSizeChanged($event)\"\n (onColumnsOrderChanged)=\"onColumnsOrderChanged($event)\"\n (onPageChanged)=\"onPageChanged($event)\"\n class=\"flex-1 min-h-0 h-full\"\n>\n @if (vm.showRowIndexColumn()) {\n <ax-index-column\n [caption]=\"('@general:terms.common.row-number' | translate | async)!\"\n fixed=\"start\"\n [width]=\"'80px'\"\n [padZero]=\"false\"\n ></ax-index-column>\n }\n @if (vm.canSelectRows()) {\n <ax-select-column fixed=\"start\" [width]=\"'50px'\"></ax-select-column>\n }\n @for (column of columns(); track column.field) {\n @if (column.visible) {\n <ng-template #footerCell>\n <div\n class=\"w-full truncate\"\n [class.text-start]=\"column.align === 'left'\"\n [class.text-center]=\"column.align === 'center'\"\n [class.text-end]=\"column.align === 'right'\"\n >\n @if (isFirstVisibleColumn(column.field) && hasReportFooterSummary()) {\n <span class=\"font-medium\">\n {{ '@report-management:report-viewer.components.table.footer-total' | translate | async }}\n </span>\n } @else if (getSummaryDisplayValue(column.field); as summaryValue) {\n <span>{{ summaryValue }}</span>\n }\n </div>\n </ng-template>\n @if (column.isGroupByDrillLink && column.drillDisplayField) {\n <axp-text-command-column\n [customWidth]=\"column.width == 'auto' ? 'auto' : column.width + 'px'\"\n [caption]=\"(column.title | translate | async)!\"\n [textField]=\"column.drillDisplayField\"\n [columnFixed]=\"column.pin\"\n [sortEnabled]=\"column.sortable\"\n [headerSortDirection]=\"resolveColumnSortDirection(column.field)\"\n [footerTemplate]=\"footerCell\"\n (sortToggle)=\"handleColumnSort(column.field)\"\n (commandClick)=\"handleGroupByDrill(column.drillDisplayField, $event.data)\"\n ></axp-text-command-column>\n } @else {\n <axp-widget-column-renderer\n [customWidth]=\"column.width == 'auto' ? 'auto' : column.width + 'px'\"\n [caption]=\"(column.title | translate | async)!\"\n [node]=\"column.widget!\"\n [sortEnabled]=\"column.sortable\"\n [headerSortDirection]=\"resolveColumnSortDirection(column.field)\"\n [columnFixed]=\"column.pin\"\n [footerTemplate]=\"footerCell\"\n (sortToggle)=\"handleColumnSort(column.field)\"\n ></axp-widget-column-renderer>\n }\n }\n }\n @if (hasPrimaryRowAction()) {\n <ax-command-column\n fixed=\"end\"\n [width]=\"primaryRowAction().length * 70 + 'px'\"\n [items]=\"primaryRowAction()\"\n (onItemClick)=\"handleRowClick($event)\"\n ></ax-command-column>\n }\n @if (hasSecondaryRowAction()) {\n <ax-dropdown-command-column\n fixed=\"end\"\n [width]=\"'60px'\"\n [items]=\"secondaryRowAction()\"\n (onItemClick)=\"handleRowClick($event)\"\n ></ax-dropdown-command-column>\n }\n</ax-data-table>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXDataTableModule }, { kind: "component", type: i1.AXDataTableComponent, selector: "ax-data-table", inputs: ["dataSource", "selectedRows", "parentField", "hasChildrenField", "rowDetailsTemplate", "rowCssClass", "cellCssClass", "highlightText", "look", "rowTemplate", "emptyTemplate", "noDataTemplate", "alternative", "showHeader", "fixedHeader", "showFooter", "fixedFooter", "itemHeight", "allowReordering", "paging", "fetchDataMode", "loading", "focusedRow"], outputs: ["selectedRowsChange", "focusedRowChange", "onRowClick", "onRowDbClick", "onColumnsOrderChanged", "onColumnSizeChanged", "onPageChanged"] }, { kind: "component", type: i1.AXRowIndexColumnComponent, selector: "ax-index-column", inputs: ["width", "caption", "fixed", "footerTemplate", "padZero"] }, { kind: "component", type: i1.AXRowSelectColumnComponent, selector: "ax-select-column", inputs: ["width", "caption", "fixed", "advanceSelect"] }, { kind: "component", type: i1.AXRowCommandColumnComponent, selector: "ax-command-column", inputs: ["width", "caption", "fixed", "footerTemplate", "items"], outputs: ["onItemClick"] }, { kind: "component", type: i1.AXRowDropdownCommandColumnComponent, selector: "ax-dropdown-command-column", inputs: ["width", "caption", "fixed", "footerTemplate", "emptyStateTemplate", "emptyStateText", "items"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: i2$1.AXPWidgetColumnRendererComponent, selector: "axp-widget-column-renderer", inputs: ["caption", "customExpandIcon", "customCollapseIcon", "customWidth", "columnKey", "node", "footerTemplate", "expandHandler", "sortEnabled", "headerSortDirection", "headerSortPriority", "columnFixed", "cellTemplate", "headerTemplate"], outputs: ["sortToggle"] }, { kind: "component", type: AXPTextCommandColumnComponent, selector: "axp-text-command-column", inputs: ["caption", "textField", "customWidth", "columnFixed", "footerTemplate", "sortEnabled", "headerSortDirection", "headerSortPriority", "disabled", "emptyDisplay"], outputs: ["commandClick", "sortToggle"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }, { kind: "pipe", type: i4.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
2178
+ }
2179
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportTableViewComponent, decorators: [{
2180
+ type: Component,
2181
+ args: [{ selector: 'axp-report-table-view', imports: [
2182
+ CommonModule,
2183
+ AXDataTableModule,
2184
+ AXLoadingModule,
2185
+ AXDecoratorModule,
2186
+ AXTranslationModule,
2187
+ AXPWidgetCoreModule,
2188
+ AXPTextCommandColumnComponent,
2189
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
2190
+ class: 'axp-report-table-view flex flex-col flex-1 min-h-0 h-full w-full',
2191
+ }, template: "<ax-data-table\n #grid\n [dataSource]=\"dataSource\"\n [paging]=\"paginationEnabled()\"\n [showFooter]=\"hasReportFooterSummary()\"\n [allowReordering]=\"true\"\n (onRowDbClick)=\"handleDbRowClick($event)\"\n (onColumnSizeChanged)=\"onColumnSizeChanged($event)\"\n (onColumnsOrderChanged)=\"onColumnsOrderChanged($event)\"\n (onPageChanged)=\"onPageChanged($event)\"\n class=\"flex-1 min-h-0 h-full\"\n>\n @if (vm.showRowIndexColumn()) {\n <ax-index-column\n [caption]=\"('@general:terms.common.row-number' | translate | async)!\"\n fixed=\"start\"\n [width]=\"'80px'\"\n [padZero]=\"false\"\n ></ax-index-column>\n }\n @if (vm.canSelectRows()) {\n <ax-select-column fixed=\"start\" [width]=\"'50px'\"></ax-select-column>\n }\n @for (column of columns(); track column.field) {\n @if (column.visible) {\n <ng-template #footerCell>\n <div\n class=\"w-full truncate\"\n [class.text-start]=\"column.align === 'left'\"\n [class.text-center]=\"column.align === 'center'\"\n [class.text-end]=\"column.align === 'right'\"\n >\n @if (isFirstVisibleColumn(column.field) && hasReportFooterSummary()) {\n <span class=\"font-medium\">\n {{ '@report-management:report-viewer.components.table.footer-total' | translate | async }}\n </span>\n } @else if (getSummaryDisplayValue(column.field); as summaryValue) {\n <span>{{ summaryValue }}</span>\n }\n </div>\n </ng-template>\n @if (column.isGroupByDrillLink && column.drillDisplayField) {\n <axp-text-command-column\n [customWidth]=\"column.width == 'auto' ? 'auto' : column.width + 'px'\"\n [caption]=\"(column.title | translate | async)!\"\n [textField]=\"column.drillDisplayField\"\n [columnFixed]=\"column.pin\"\n [sortEnabled]=\"column.sortable\"\n [headerSortDirection]=\"resolveColumnSortDirection(column.field)\"\n [footerTemplate]=\"footerCell\"\n (sortToggle)=\"handleColumnSort(column.field)\"\n (commandClick)=\"handleGroupByDrill(column.drillDisplayField, $event.data)\"\n ></axp-text-command-column>\n } @else {\n <axp-widget-column-renderer\n [customWidth]=\"column.width == 'auto' ? 'auto' : column.width + 'px'\"\n [caption]=\"(column.title | translate | async)!\"\n [node]=\"column.widget!\"\n [sortEnabled]=\"column.sortable\"\n [headerSortDirection]=\"resolveColumnSortDirection(column.field)\"\n [columnFixed]=\"column.pin\"\n [footerTemplate]=\"footerCell\"\n (sortToggle)=\"handleColumnSort(column.field)\"\n ></axp-widget-column-renderer>\n }\n }\n }\n @if (hasPrimaryRowAction()) {\n <ax-command-column\n fixed=\"end\"\n [width]=\"primaryRowAction().length * 70 + 'px'\"\n [items]=\"primaryRowAction()\"\n (onItemClick)=\"handleRowClick($event)\"\n ></ax-command-column>\n }\n @if (hasSecondaryRowAction()) {\n <ax-dropdown-command-column\n fixed=\"end\"\n [width]=\"'60px'\"\n [items]=\"secondaryRowAction()\"\n (onItemClick)=\"handleRowClick($event)\"\n ></ax-dropdown-command-column>\n }\n</ax-data-table>\n" }]
2192
+ }], propDecorators: { grid: [{ type: i0.ViewChild, args: ['grid', { isSignal: true }] }] } });
2193
+
2194
+ //#region ----- Imports -----
2195
+ //#endregion
2196
+ //#region ----- Constants -----
2197
+ const STYLE_PREFIX = /^(fa-light|fa-solid|fa-regular|fa-thin|fa-duotone)\s+/;
2198
+ const REPORT_LAYOUT_TITLE_KEYS = {
2199
+ table: '@report-management:report-viewer.layouts.table.title',
2200
+ bar: '@report-management:report-viewer.layouts.bar-chart.title',
2201
+ doughnut: '@report-management:report-viewer.layouts.doughnut-chart.title',
2202
+ pie: '@report-management:report-viewer.layouts.pie-chart.title',
2203
+ line: '@report-management:report-viewer.layouts.line-chart.title',
2204
+ pdf: '@report-management:report-viewer.layouts.pdf.title',
2205
+ chart: '@report-management:report-viewer.layouts.chart.title',
2206
+ };
2207
+ const DEFAULT_LAYOUT_GLYPHS = {
2208
+ table: 'fa-table',
2209
+ pdf: 'fa-file-pdf',
2210
+ chart: 'fa-chart-mixed',
2211
+ };
2212
+ //#endregion
2213
+ //#region ----- Layout switcher helpers -----
2214
+ function reportLayoutTitleKey(option) {
2215
+ if (option.type === 'table') {
2216
+ return REPORT_LAYOUT_TITLE_KEYS.table;
2217
+ }
2218
+ if (option.type === 'pdf') {
2219
+ return REPORT_LAYOUT_TITLE_KEYS.pdf;
2220
+ }
2221
+ const glyph = stripIconStylePrefix(option.icon);
2222
+ if (glyph.includes('fa-chart-bar')) {
2223
+ return REPORT_LAYOUT_TITLE_KEYS.bar;
2224
+ }
2225
+ if (glyph.includes('fa-chart-pie')) {
2226
+ return REPORT_LAYOUT_TITLE_KEYS.doughnut;
2227
+ }
2228
+ if (glyph.includes('fa-chart-line')) {
2229
+ return REPORT_LAYOUT_TITLE_KEYS.line;
2230
+ }
2231
+ return REPORT_LAYOUT_TITLE_KEYS.chart;
2232
+ }
2233
+ function reportLayoutGlyph(icon, option) {
2234
+ const fromIcon = stripIconStylePrefix(icon);
2235
+ if (fromIcon) {
2236
+ return fromIcon;
2237
+ }
2238
+ if (option.type === 'table') {
2239
+ return DEFAULT_LAYOUT_GLYPHS['table'];
2240
+ }
2241
+ if (option.type === 'pdf') {
2242
+ return DEFAULT_LAYOUT_GLYPHS['pdf'];
2243
+ }
2244
+ return DEFAULT_LAYOUT_GLYPHS['chart'];
2245
+ }
2246
+ function reportLayoutPageIcon(icon, option) {
2247
+ return `fa-light ${reportLayoutGlyph(icon, option)}`;
2248
+ }
2249
+ function isDistinctLayoutTitle(layoutTitle, reportTitle, locale) {
2250
+ const resolvedLayout = resolveMultiLanguageString(layoutTitle, locale).trim();
2251
+ if (!resolvedLayout) {
2252
+ return false;
2253
+ }
2254
+ const resolvedReport = resolveMultiLanguageString(reportTitle, locale).trim();
2255
+ return resolvedLayout !== resolvedReport;
2256
+ }
2257
+ function stripIconStylePrefix(icon) {
2258
+ if (!icon?.trim()) {
2259
+ return '';
2260
+ }
2261
+ return icon.trim().replace(STYLE_PREFIX, '').trim();
2262
+ }
2263
+ //#endregion
2264
+
2265
+ export { AXPReportViewerViewModel as A, reportLayoutTitleKey as a, reportLayoutGlyph as b, AXPReportTableViewComponent as c, AXPReportChartViewComponent as d, AXPReportPdfViewComponent as e, isDistinctLayoutTitle as i, reportLayoutPageIcon as r };
2266
+ //# sourceMappingURL=acorex-platform-module-report-management-client-report-layout-switcher.util-BGhHRYfe.mjs.map