@acorex-platform/module-report-management-client 0.0.18 → 0.0.24

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 (19) hide show
  1. package/fesm2022/acorex-platform-module-report-management-client-acorex-platform-module-report-management-client-BDCuhd5O.mjs +4232 -0
  2. package/fesm2022/acorex-platform-module-report-management-client-acorex-platform-module-report-management-client-BDCuhd5O.mjs.map +1 -0
  3. package/fesm2022/acorex-platform-module-report-management-client-contracts.mjs +85 -149
  4. package/fesm2022/acorex-platform-module-report-management-client-contracts.mjs.map +1 -1
  5. package/fesm2022/acorex-platform-module-report-management-client-entity-reports-page-component.provider-C6tJPxri.mjs +24 -0
  6. package/fesm2022/acorex-platform-module-report-management-client-entity-reports-page-component.provider-C6tJPxri.mjs.map +1 -0
  7. package/fesm2022/acorex-platform-module-report-management-client-entity-reports-page.component-fBftKdQW.mjs +166 -0
  8. package/fesm2022/acorex-platform-module-report-management-client-entity-reports-page.component-fBftKdQW.mjs.map +1 -0
  9. package/fesm2022/acorex-platform-module-report-management-client-report-popup-viewer.component-B9msMMod.mjs +113 -0
  10. package/fesm2022/acorex-platform-module-report-management-client-report-popup-viewer.component-B9msMMod.mjs.map +1 -0
  11. package/fesm2022/acorex-platform-module-report-management-client.mjs +1 -3584
  12. package/fesm2022/acorex-platform-module-report-management-client.mjs.map +1 -1
  13. package/package.json +1 -1
  14. package/types/acorex-platform-module-report-management-client-contracts.d.ts +280 -120
  15. package/types/acorex-platform-module-report-management-client.d.ts +256 -206
  16. package/fesm2022/acorex-platform-module-report-management-client-report-drill-popup.component-BdZPBD7t.mjs +0 -59
  17. package/fesm2022/acorex-platform-module-report-management-client-report-drill-popup.component-BdZPBD7t.mjs.map +0 -1
  18. package/fesm2022/acorex-platform-module-report-management-client-report-runner-root-page.component-CVG3HiJX.mjs +0 -504
  19. package/fesm2022/acorex-platform-module-report-management-client-report-runner-root-page.component-CVG3HiJX.mjs.map +0 -1
@@ -0,0 +1,4232 @@
1
+ import { AXPExpressionEvaluatorService, AXP_MODULE_MANIFEST_PROVIDER, AXP_FEATURE_DEFINITION_PROVIDER, provideLazyProvider, applySystemActionDefault, AXPDeviceService } from '@acorex-platform/framework-client/core';
2
+ import { RootConfig, AXPInsightsReportingPermissionKeys, AXPReportManagementMenuKeys, AXPReportManagementFeatureKeys, AXMReportManagementQueryKeys, AXMReportManagementCommandKeys } from '@acorex-platform/module-report-management-client/contracts';
3
+ import { AXPDomainModule } from '@acorex-platform/framework-client/domain';
4
+ import { AXPEntityService, resolveEntityPluginDetailPageOrder, AXP_ENTITY_ACTION_PLUGIN, provideEntity } from '@acorex-platform/framework-client/layout/entity';
5
+ import { AXP_PAGE_COMPONENT_PROVIDER, AXPStateMessageComponent, AXPTextCommandColumnComponent, AXPThemeLayoutStartSideComponent, AXPThemeLayoutHeaderComponent, AXPThemeLayoutBlockComponent, AXPThemeLayoutFooterComponent } from '@acorex-platform/framework-client/layout/components';
6
+ import { AXPRootLayoutComponent } from '@acorex-platform/framework-client/themes/default';
7
+ import * as i0 from '@angular/core';
8
+ import { Injectable, inject, Injector, NgModule, computed, effect, untracked, signal, ChangeDetectionStrategy, Component, ViewEncapsulation, viewChild, DestroyRef, input } from '@angular/core';
9
+ import { ROUTES, ActivatedRoute } from '@angular/router';
10
+ import { AXPTabNavigationService, AXPBackgroundOperationHandler, AXP_BACKGROUND_OPERATION_HANDLERS, AXP_PROTECTED_ROUTE_GUARDS, AXP_MENU_PROVIDER, AXPSettingsService, commandMessageTextForError, AXPCommonSettings } from '@acorex-platform/framework-client/common';
11
+ import { AXPSessionService, AXP_PERMISSION_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/auth';
12
+ import { AXPQueryService, AXPRuntimeModule, provideCommandSetups, AXPCommandService } from '@acorex-platform/framework-client/runtime';
13
+ import { AXPCommonPermissionGroupKeys } from '@acorex-platform/module-common-client/contracts';
14
+ import { get, isString } from 'lodash-es';
15
+ import { AXPopupService } from '@acorex/components/popup';
16
+ import { resolveMultiLanguageString, runtimeQueryPayloadData, runtimeQueryObjectData, runtimeQueryCollectionData, AXPPlatformScope, AXPWidgetsList, getSystemActions } from '@acorex-platform/framework-shared/core';
17
+ import * as i1 from '@acorex/core/translation';
18
+ import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
19
+ import { AXBadgeModule } from '@acorex/components/badge';
20
+ import * as i1$2 from '@acorex/components/button';
21
+ import { AXButtonModule } from '@acorex/components/button';
22
+ import * as i4 from '@acorex/components/check-box';
23
+ import { AXCheckBoxModule } from '@acorex/components/check-box';
24
+ import * as i2$3 from '@acorex/components/decorators';
25
+ import { AXDecoratorModule } from '@acorex/components/decorators';
26
+ import { AXLoadingModule } from '@acorex/components/loading';
27
+ import { AXLoadingDialogService, AXLoadingDialogModule } from '@acorex/components/loading-dialog';
28
+ import { AXUnsubscriber } from '@acorex/core/utils';
29
+ import { AXPLayoutRendererComponent } from '@acorex-platform/framework-client/layout/builder';
30
+ import { AXPPageLayoutBaseComponent, AXPPageLayoutComponent, AXPPageLayoutBase } from '@acorex-platform/framework-client/layout/views';
31
+ import { AXPLayoutThemeService } from '@acorex-platform/framework-client/themes/shared';
32
+ import * as i2 from '@angular/common';
33
+ import { CommonModule } from '@angular/common';
34
+ import * as i2$2 from '@angular/forms';
35
+ import { FormsModule } from '@angular/forms';
36
+ import { Subscription, combineLatest } from 'rxjs';
37
+ import { AXPPageStatus } from '@acorex-platform/framework-shared/widget-core';
38
+ import { AXLocaleService } from '@acorex/core/locale';
39
+ import * as i2$1 from '@acorex-platform/framework-client/layout/widget-core';
40
+ import { AXPWidgetRegistryService, AXPWidgetCoreModule } from '@acorex-platform/framework-client/layout/widget-core';
41
+ import { AXToastService } from '@acorex/components/toast';
42
+ import { signalStore, withState, withComputed, withMethods, patchState, withHooks } from '@ngrx/signals';
43
+ import { set } from 'lodash';
44
+ import { AXBarChartComponent } from '@acorex/charts/bar-chart';
45
+ import { AXChartLegendComponent } from '@acorex/charts/chart-legend';
46
+ import { AXDonutChartComponent } from '@acorex/charts/donut-chart';
47
+ import { AXLineChartComponent } from '@acorex/charts/line-chart';
48
+ import { DomSanitizer } from '@angular/platform-browser';
49
+ import * as i1$1 from '@acorex/components/data-table';
50
+ import { AXDataTableModule } from '@acorex/components/data-table';
51
+ import { moveItemInArray } from '@angular/cdk/drag-drop';
52
+ import { AXDataSource } from '@acorex/cdk/common';
53
+
54
+ //#region ---- Imports ----
55
+ //#endregion
56
+ //#region ---- Manifest Definition ----
57
+ /**
58
+ * Report Management Module Manifest.
59
+ * Defines module metadata, features, and provider references.
60
+ */
61
+ const ReportManagementManifest = {
62
+ name: RootConfig.module.name,
63
+ version: '1.0.0',
64
+ title: RootConfig.module.title,
65
+ icon: RootConfig.module.icon,
66
+ i18n: RootConfig.config.i18n,
67
+ type: 'platform',
68
+ domain: 'data',
69
+ required: true,
70
+ // Module dependencies (optional)
71
+ };
72
+ //#endregion
73
+
74
+ //#endregion
75
+ //#region ----- Permission Definition Provider -----
76
+ /**
77
+ * Registers report capabilities into the shared Insights & Reporting group.
78
+ */
79
+ class AXMPermissionDefinitionProvider {
80
+ async define(context) {
81
+ const keys = AXPInsightsReportingPermissionKeys;
82
+ const groups = AXPCommonPermissionGroupKeys;
83
+ const P = '@common:insights-reporting.permissions';
84
+ const G = '@common:permission-groups';
85
+ context.addGroup(groups.InsightsReporting.Root, `${G}.insights-reporting.title`, `${G}.insights-reporting.description`, { order: 100 });
86
+ context
87
+ .addGroup(groups.InsightsReporting.Reports, `${G}.insights-reporting-reports.title`, `${G}.insights-reporting-reports.description`, { parent: groups.InsightsReporting.Root, order: 10 })
88
+ .addPermission(keys.Access, `${P}.access.title`, `${P}.access.description`)
89
+ .endPermission()
90
+ .addPermission(keys.MaintainReports, `${P}.maintain-reports.title`, `${P}.maintain-reports.description`)
91
+ .endPermission()
92
+ .addPermission(keys.Setup, `${P}.setup.title`, `${P}.setup.description`)
93
+ .endPermission()
94
+ .endGroup();
95
+ }
96
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionDefinitionProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
97
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionDefinitionProvider }); }
98
+ }
99
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionDefinitionProvider, decorators: [{
100
+ type: Injectable
101
+ }] });
102
+
103
+ class AXMMenuProvider {
104
+ constructor() {
105
+ this.entityService = inject(AXPEntityService);
106
+ }
107
+ async provide(context) {
108
+ const module = RootConfig.module;
109
+ context.addItems([
110
+ {
111
+ name: AXPReportManagementMenuKeys.Reports,
112
+ text: RootConfig.entities.report.titlePlural,
113
+ path: this.entityService.withEntity(module.name, RootConfig.entities.report.name).createPath(),
114
+ icon: RootConfig.entities.report.icon,
115
+ priority: 8300,
116
+ policy: {
117
+ features: [module.name],
118
+ permissions: [AXPInsightsReportingPermissionKeys.MaintainReports],
119
+ },
120
+ },
121
+ ]);
122
+ }
123
+ }
124
+
125
+ //#region ---- Imports ----
126
+ //#endregion
127
+ //#region ---- Feature Definition Provider ----
128
+ /**
129
+ * Report Management Feature Definition Provider.
130
+ * Provides feature definitions for the Report Management module.
131
+ */
132
+ class AXPReportManagementFeatureDefinitionProvider {
133
+ async provide(context) {
134
+ // Report Builder feature
135
+ context.addFeature(AXPReportManagementFeatureKeys.ReportBuilder, {
136
+ title: '@report-management:features.report-builder.title',
137
+ description: '@report-management:features.report-builder.description',
138
+ defaultValue: true,
139
+ type: 'boolean',
140
+ });
141
+ // Saved Reports feature
142
+ context.addFeature(AXPReportManagementFeatureKeys.SavedReports, {
143
+ title: '@report-management:features.saved-reports.title',
144
+ description: '@report-management:features.saved-reports.description',
145
+ defaultValue: true,
146
+ type: 'boolean',
147
+ });
148
+ // Scheduled Reports feature
149
+ context.addFeature(AXPReportManagementFeatureKeys.ScheduledReports, {
150
+ title: '@report-management:features.scheduled-reports.title',
151
+ description: '@report-management:features.scheduled-reports.description',
152
+ defaultValue: true,
153
+ type: 'boolean',
154
+ });
155
+ // Background Reports feature
156
+ context.addFeature(AXPReportManagementFeatureKeys.BackgroundReports, {
157
+ title: '@report-management:features.background-reports.title',
158
+ description: '@report-management:features.background-reports.description',
159
+ defaultValue: true,
160
+ type: 'boolean',
161
+ });
162
+ }
163
+ }
164
+ //#endregion
165
+
166
+ //#region ----- Types -----
167
+ //#endregion
168
+ //#region ----- Date helpers -----
169
+ /** Local calendar date as `yyyy-MM-dd` (session "today" for report ranges). */
170
+ function formatReportDateOnly(date = new Date()) {
171
+ const year = date.getFullYear();
172
+ const month = String(date.getMonth() + 1).padStart(2, '0');
173
+ const day = String(date.getDate()).padStart(2, '0');
174
+ return `${year}-${month}-${day}`;
175
+ }
176
+ function resolveTodayDateRange(date = new Date()) {
177
+ const day = formatReportDateOnly(date);
178
+ return { from: day, to: day };
179
+ }
180
+ function isReportDateRangeObject(value) {
181
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
182
+ return false;
183
+ }
184
+ const raw = value;
185
+ const from = raw['from'] ?? raw['start'];
186
+ const to = raw['to'] ?? raw['end'];
187
+ return typeof from === 'string' && typeof to === 'string' && !!from.trim() && !!to.trim();
188
+ }
189
+ function toReportDateRangeFilterValue(range) {
190
+ return {
191
+ operation: { type: 'between' },
192
+ value: { from: range.from, to: range.to },
193
+ };
194
+ }
195
+ /**
196
+ * Converts report default / binding values into filter-form context shape.
197
+ * - `'today'` and `{ from, to }` → `{ operation: between, value }`
198
+ * - existing filter objects are left unchanged
199
+ */
200
+ function resolveReportFilterDefaultValue(value, now = new Date()) {
201
+ if (value === 'today') {
202
+ return toReportDateRangeFilterValue(resolveTodayDateRange(now));
203
+ }
204
+ if (isReportDateRangeObject(value)) {
205
+ return toReportDateRangeFilterValue({
206
+ from: String(value.from ?? value.start),
207
+ to: String(value.to ?? value.end),
208
+ });
209
+ }
210
+ return value;
211
+ }
212
+ function collectReportParameterDefaults(parameterGroups, now = new Date()) {
213
+ const defaults = {};
214
+ for (const group of parameterGroups ?? []) {
215
+ for (const param of group.parameters ?? []) {
216
+ const rawDefault = param.widget?.defaultValue ?? param.widget?.options?.['defaultValue'];
217
+ if (rawDefault === undefined) {
218
+ continue;
219
+ }
220
+ defaults[param.path] = resolveReportFilterDefaultValue(rawDefault, now);
221
+ }
222
+ }
223
+ return defaults;
224
+ }
225
+ //#endregion
226
+ //#region ----- Parameter resolution -----
227
+ /**
228
+ * Resolves special parameter tokens after expression evaluation.
229
+ * - `dateRange: 'today'` → `{ from, to }` for the local calendar day
230
+ */
231
+ function resolveReportParameterValues(parameters, now = new Date()) {
232
+ if (!parameters) {
233
+ return {};
234
+ }
235
+ const resolved = {};
236
+ for (const [key, value] of Object.entries(parameters)) {
237
+ if (value === 'today') {
238
+ resolved[key] = resolveTodayDateRange(now);
239
+ continue;
240
+ }
241
+ resolved[key] = value;
242
+ }
243
+ return resolved;
244
+ }
245
+ /**
246
+ * Maps entity-binding parameter declarations into OpenInPopup command parameters.
247
+ * Field paths become `context.eval` expressions; `today` and `{from,to}` stay literal.
248
+ */
249
+ function mapEntityBindingParameters(parameters) {
250
+ if (!parameters) {
251
+ return undefined;
252
+ }
253
+ return Object.fromEntries(Object.entries(parameters).map(([key, value]) => [key, toBindingParameterExpression(value)]));
254
+ }
255
+ function toBindingParameterExpression(value) {
256
+ if (value === 'today') {
257
+ return 'today';
258
+ }
259
+ if (isReportDateRangeObject(value)) {
260
+ return {
261
+ from: String(value.from ?? value.start),
262
+ to: String(value.to ?? value.end),
263
+ };
264
+ }
265
+ if (typeof value !== 'string') {
266
+ return value;
267
+ }
268
+ const trimmed = value.trim();
269
+ if (!trimmed || trimmed.includes('{{') || trimmed.includes('context.eval')) {
270
+ return value;
271
+ }
272
+ return `{{ context.eval('${trimmed}') }}`;
273
+ }
274
+ //#endregion
275
+ //#region ----- Viewer query params -----
276
+ /**
277
+ * Flattens popup/filter parameters into report-viewer URL query params
278
+ * (`field.operation.type` + `field.value` / nested `from`/`to`).
279
+ */
280
+ function flattenReportParametersToQueryParams(parameters) {
281
+ const result = {};
282
+ for (const [field, rawValue] of Object.entries(parameters)) {
283
+ if (rawValue === null || rawValue === undefined || rawValue === '') {
284
+ continue;
285
+ }
286
+ if (isReportDateRangeObject(rawValue)) {
287
+ const from = String(rawValue.from ?? rawValue.start);
288
+ const to = String(rawValue.to ?? rawValue.end);
289
+ result[`${field}.operation.type`] = 'between';
290
+ result[`${field}.value.from`] = from;
291
+ result[`${field}.value.to`] = to;
292
+ continue;
293
+ }
294
+ if (typeof rawValue === 'object' && !Array.isArray(rawValue) && 'operation' in rawValue) {
295
+ const filterObj = rawValue;
296
+ if (filterObj.operation?.type) {
297
+ result[`${field}.operation.type`] = String(filterObj.operation.type);
298
+ }
299
+ if (isReportDateRangeObject(filterObj.value)) {
300
+ result[`${field}.value.from`] = String(filterObj.value.from ?? filterObj.value.start);
301
+ result[`${field}.value.to`] = String(filterObj.value.to ?? filterObj.value.end);
302
+ }
303
+ else if (filterObj.value !== undefined && filterObj.value !== null && filterObj.value !== '') {
304
+ result[`${field}.value`] = Array.isArray(filterObj.value)
305
+ ? filterObj.value.map(String).join(',')
306
+ : String(filterObj.value);
307
+ }
308
+ continue;
309
+ }
310
+ result[`${field}.operation.type`] = Array.isArray(rawValue) ? 'in' : 'equal';
311
+ result[`${field}.value`] = Array.isArray(rawValue) ? rawValue.map(String).join(',') : String(rawValue);
312
+ }
313
+ return result;
314
+ }
315
+ //#endregion
316
+
317
+ //#region ----- Imports -----
318
+ //#endregion
319
+ //#region ----- Constants -----
320
+ const OPEN_REPORT_IN_POPUP_COMMAND$1 = 'ReportManagement.Report:OpenInPopup';
321
+ const ENTITY_REPORTS_PAGE_COMPONENT_KEY = 'entity-reports-page';
322
+ //#endregion
323
+ //#region ----- Display helpers -----
324
+ function normalizeReportEntityDisplays(display) {
325
+ if (!display) {
326
+ return new Set(['page']);
327
+ }
328
+ const list = Array.isArray(display) ? display : [display];
329
+ return new Set(list);
330
+ }
331
+ function bindingShowsOnPage(binding) {
332
+ const displays = normalizeReportEntityDisplays(binding.display);
333
+ // Default / empty → page. Explicit lists without `page` hide from the Reports page.
334
+ return displays.has('page') || displays.size === 0;
335
+ }
336
+ function resolveBindingTitle(report, binding) {
337
+ if (typeof binding.title === 'string' && binding.title.trim()) {
338
+ return binding.title.trim();
339
+ }
340
+ return report.title;
341
+ }
342
+ function resolveBindingDescription(report, binding) {
343
+ if (typeof binding.description === 'string' && binding.description.trim()) {
344
+ return binding.description.trim();
345
+ }
346
+ if (typeof report.description === 'string') {
347
+ const trimmed = report.description.trim();
348
+ return trimmed || undefined;
349
+ }
350
+ return report.description;
351
+ }
352
+ function buildOpenInPopupCommandOptions(report, binding) {
353
+ return {
354
+ reportId: report.id,
355
+ layoutId: binding.layoutId,
356
+ title: resolveBindingTitle(report, binding),
357
+ parameters: mapEntityBindingParameters(binding.parameters),
358
+ };
359
+ }
360
+ function extractEntityBindings(report, entityType) {
361
+ const bindings = report.entities ?? [];
362
+ return bindings
363
+ .filter((binding) => binding?.entity === entityType)
364
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
365
+ }
366
+ //#endregion
367
+
368
+ //#region ----- Imports -----
369
+ //#endregion
370
+ //#region ----- Plugin factory -----
371
+ /**
372
+ * Entity `reports` plugin — contributes a Reports detail page for definitions
373
+ * bound via `entities[]`. Reports open from that page (not entity Actions).
374
+ *
375
+ * Takes {@link Injector} for factory signature parity with other entity plugins.
376
+ */
377
+ function createEntityReportsPlugin(_injector) {
378
+ return {
379
+ name: 'reports',
380
+ order: 55,
381
+ apply: async (ctx, options) => {
382
+ const opts = options ?? {};
383
+ const entityType = `${ctx.entity.module}.${ctx.entity.name}`;
384
+ const pageTitle = opts.title?.trim() || '@report-management:entity-reports.page.title';
385
+ //#region ----- Reports detail page -----
386
+ ctx.entity.pages ??= [];
387
+ const pageExists = ctx.entity.pages.some((p) => p.componentKey === ENTITY_REPORTS_PAGE_COMPONENT_KEY);
388
+ const pageOrder = resolveEntityPluginDetailPageOrder({ relatedEntities: ctx.entity.relatedEntities, pages: ctx.entity.pages }, 'reports', {
389
+ componentKey: ENTITY_REPORTS_PAGE_COMPONENT_KEY,
390
+ skipPage: pageExists ? { componentKey: ENTITY_REPORTS_PAGE_COMPONENT_KEY } : undefined,
391
+ });
392
+ const page = {
393
+ componentKey: ENTITY_REPORTS_PAGE_COMPONENT_KEY,
394
+ title: pageTitle,
395
+ icon: opts.icon ?? 'fa-light fa-chart-mixed',
396
+ layout: { order: pageOrder },
397
+ options: { entityType },
398
+ };
399
+ if (!pageExists) {
400
+ ctx.entity.pages.push(page);
401
+ }
402
+ else {
403
+ const pages = ctx.entity.pages;
404
+ const index = pages.findIndex((p) => p.componentKey === ENTITY_REPORTS_PAGE_COMPONENT_KEY);
405
+ if (index >= 0) {
406
+ pages[index] = { ...pages[index], ...page };
407
+ }
408
+ }
409
+ //#endregion
410
+ ctx.entity.extensions ??= {};
411
+ ctx.entity.extensions.reports = {
412
+ entityType,
413
+ };
414
+ },
415
+ };
416
+ }
417
+ //#endregion
418
+
419
+ //#endregion
420
+
421
+ //#region ---- Imports ----
422
+ //#endregion
423
+ /**
424
+ * Report Viewer Service
425
+ *
426
+ * Handles report definition retrieval, caching, and layout actions.
427
+ */
428
+ class AXPReportViewerService {
429
+ constructor() {
430
+ //#region ---- Dependencies ----
431
+ this.entityService = inject(AXPEntityService);
432
+ this.reportDefinitionService = this.entityService
433
+ .withEntity(RootConfig.module.name, RootConfig.entities.report.name)
434
+ .data();
435
+ //#endregion
436
+ //#region ---- Cache Storage ----
437
+ /** Cache for individual report definitions by id */
438
+ this.definitionsById = new Map();
439
+ /** Pending API requests to prevent duplicate calls */
440
+ this.pendingReportRequests = new Map();
441
+ }
442
+ //#endregion
443
+ //#region ---- Public API: Report Definitions ----
444
+ /**
445
+ * Reports bound to a host entity type via `entities[].entity`.
446
+ * Falls back to an in-memory filter when the nested-array query returns nothing
447
+ * (or is unsupported by the current query backend).
448
+ */
449
+ async getReportsByEntityType(entityType) {
450
+ if (!entityType?.trim()) {
451
+ return [];
452
+ }
453
+ let items = [];
454
+ try {
455
+ const result = await this.reportDefinitionService.query({
456
+ skip: 0,
457
+ take: 1000,
458
+ filter: {
459
+ logic: 'and',
460
+ filters: [
461
+ {
462
+ field: 'entities.entity',
463
+ operator: { type: 'equal' },
464
+ value: entityType,
465
+ },
466
+ ],
467
+ },
468
+ });
469
+ items = (result.items ?? []);
470
+ }
471
+ catch {
472
+ items = [];
473
+ }
474
+ if (items.length === 0) {
475
+ try {
476
+ const fallback = await this.reportDefinitionService.query({
477
+ skip: 0,
478
+ take: 1000,
479
+ });
480
+ items = (fallback.items ?? []).filter((report) => (report.entities ?? []).some((binding) => binding?.entity === entityType));
481
+ }
482
+ catch {
483
+ items = [];
484
+ }
485
+ }
486
+ const bound = [];
487
+ for (const report of items) {
488
+ this.definitionsById.set(report.id, report);
489
+ for (const binding of extractEntityBindings(report, entityType)) {
490
+ bound.push({ report, binding });
491
+ }
492
+ }
493
+ return bound.sort((a, b) => (a.binding.order ?? 0) - (b.binding.order ?? 0) ||
494
+ resolveMultiLanguageString(a.report.title, 'en-US').localeCompare(resolveMultiLanguageString(b.report.title, 'en-US')));
495
+ }
496
+ /**
497
+ * Get single report definition by ID with O(1) lookup
498
+ */
499
+ async getReportById(id) {
500
+ if (this.definitionsById.has(id)) {
501
+ return this.definitionsById.get(id);
502
+ }
503
+ if (this.pendingReportRequests.has(id)) {
504
+ return this.pendingReportRequests.get(id);
505
+ }
506
+ const requestPromise = this.loadReport(id);
507
+ this.pendingReportRequests.set(id, requestPromise);
508
+ return requestPromise;
509
+ }
510
+ //#endregion
511
+ //#region ---- Public API: Layout Actions ----
512
+ /**
513
+ * Get general actions list for a layout type.
514
+ * Override in connectivity layer (mock/API) to provide general actions.
515
+ */
516
+ getGeneralActionsList(layoutType) {
517
+ return [];
518
+ }
519
+ /**
520
+ * Get merged actions for a layout definition.
521
+ * Combines general actions with layout-specific actions.
522
+ */
523
+ getMergedActions(layoutDefinition) {
524
+ const generalActions = this.getGeneralActionsList(layoutDefinition.layout.type);
525
+ const customActions = layoutDefinition.actions || [];
526
+ const customActionsMap = new Map();
527
+ customActions.forEach((action) => {
528
+ if (action.name) {
529
+ customActionsMap.set(action.name, action);
530
+ }
531
+ });
532
+ const merged = [];
533
+ const usedNames = new Set();
534
+ generalActions.forEach((action) => {
535
+ if (action.name && customActionsMap.has(action.name)) {
536
+ merged.push(customActionsMap.get(action.name));
537
+ usedNames.add(action.name);
538
+ }
539
+ else {
540
+ merged.push(action);
541
+ if (action.name) {
542
+ usedNames.add(action.name);
543
+ }
544
+ }
545
+ });
546
+ customActions.forEach((action) => {
547
+ if (action.name && !usedNames.has(action.name)) {
548
+ merged.push(action);
549
+ usedNames.add(action.name);
550
+ }
551
+ else if (!action.name) {
552
+ merged.push(action);
553
+ }
554
+ });
555
+ return merged;
556
+ }
557
+ getPrimaryActions(layoutDefinition) {
558
+ return this.getMergedActions(layoutDefinition).filter((action) => action.priority === 'primary');
559
+ }
560
+ getSecondaryActions(layoutDefinition) {
561
+ return this.getMergedActions(layoutDefinition).filter((action) => action.priority === 'secondary');
562
+ }
563
+ //#endregion
564
+ //#region ---- Private: Data Loading ----
565
+ /**
566
+ * Load single report from entity service and cache result.
567
+ * Can be overridden in extended services (e.g., API layer).
568
+ */
569
+ async loadReport(id) {
570
+ try {
571
+ const result = (await this.reportDefinitionService.byKey(id));
572
+ if (result) {
573
+ this.definitionsById.set(id, result);
574
+ return result;
575
+ }
576
+ return undefined;
577
+ }
578
+ finally {
579
+ this.pendingReportRequests.delete(id);
580
+ }
581
+ }
582
+ //#endregion
583
+ //#region ---- Cache Management ----
584
+ isReportCached(reportId) {
585
+ return this.definitionsById.has(reportId) || this.pendingReportRequests.has(reportId);
586
+ }
587
+ clearAllCache() {
588
+ this.definitionsById.clear();
589
+ this.pendingReportRequests.clear();
590
+ }
591
+ clearReportsCache() {
592
+ this.definitionsById.clear();
593
+ this.pendingReportRequests.clear();
594
+ }
595
+ //#endregion
596
+ //#region ---- Action Handlers (Override in Connectivity Layer) ----
597
+ async executeAction(commandName, options) {
598
+ console.log(`Execute action: ${commandName} - should be implemented in connectivity layer`, options);
599
+ }
600
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportViewerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
601
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportViewerService, providedIn: 'root' }); }
602
+ }
603
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportViewerService, decorators: [{
604
+ type: Injectable,
605
+ args: [{
606
+ providedIn: 'root',
607
+ }]
608
+ }] });
609
+
610
+ function AXPieChartReportMapper(data) {
611
+ return Object.entries(data)
612
+ .map(([key, value]) => ({
613
+ id: key,
614
+ label: key,
615
+ value: Number(value),
616
+ }))
617
+ .filter((item) => Number.isFinite(item.value) && item.value > 0);
618
+ }
619
+ function AXBarChartReportMapper(data) {
620
+ return Object.entries(data)
621
+ .map(([key, value]) => ({
622
+ id: key,
623
+ label: key,
624
+ value: Number(value),
625
+ }))
626
+ .filter((item) => Number.isFinite(item.value));
627
+ }
628
+ function AXLineChartReportMapper(data) {
629
+ const entries = Object.entries(data);
630
+ if (entries.length === 0) {
631
+ return [];
632
+ }
633
+ const dataPoints = entries.map(([key, value]) => ({
634
+ x: key,
635
+ y: value,
636
+ }));
637
+ return [
638
+ {
639
+ id: 'series-1',
640
+ label: 'Data Series',
641
+ tooltipLabel: 'Countries Info',
642
+ lineColor: '#d81159',
643
+ fillColor: '#d8115920',
644
+ data: dataPoints,
645
+ },
646
+ ];
647
+ }
648
+ //#region ---- Helpers ----
649
+ /**
650
+ * Determines whether a filter value should be treated as empty.
651
+ * This handles deep objects, arrays, and primitives so that
652
+ * values like { from: null, to: null } are considered empty.
653
+ */
654
+ function isEmptyFilterValue(value) {
655
+ if (value === null || value === undefined || value === '') {
656
+ return true;
657
+ }
658
+ if (Array.isArray(value)) {
659
+ return value.length === 0 || value.every((item) => isEmptyFilterValue(item));
660
+ }
661
+ if (typeof value === 'object') {
662
+ const entries = Object.entries(value);
663
+ if (entries.length === 0) {
664
+ return true;
665
+ }
666
+ // Consider object empty if all nested values are empty
667
+ return entries.every(([, v]) => isEmptyFilterValue(v));
668
+ }
669
+ return false;
670
+ }
671
+ //#endregion
672
+ /**
673
+ * Converts AXPContextData (filter context) to AXPFilterClause format
674
+ *
675
+ * Input format:
676
+ * {
677
+ * "fieldName": {
678
+ * "operation": { "type": "between" },
679
+ * "value": { "from": "...", "to": "..." }
680
+ * }
681
+ * }
682
+ *
683
+ * Output format:
684
+ * {
685
+ * logic: 'and',
686
+ * filters: [
687
+ * {
688
+ * field: 'fieldName',
689
+ * operator: { type: 'between' },
690
+ * value: { from: "...", to: "..." }
691
+ * }
692
+ * ]
693
+ * }
694
+ */
695
+ function convertContextDataToFilterClause(context) {
696
+ const filters = [];
697
+ // Iterate through all keys in context
698
+ for (const [fieldName, fieldData] of Object.entries(context)) {
699
+ if (fieldName.endsWith('EditorObj') || fieldName.startsWith('__')) {
700
+ continue;
701
+ }
702
+ // Drill-down and URL shortcuts may pass plain parameter values (e.g. { brand: 'id' })
703
+ if (fieldData === null || fieldData === undefined || typeof fieldData !== 'object' || Array.isArray(fieldData)) {
704
+ if (!isEmptyFilterValue(fieldData)) {
705
+ filters.push({
706
+ field: fieldName,
707
+ operator: {
708
+ type: Array.isArray(fieldData) ? 'in' : 'equal',
709
+ },
710
+ value: fieldData,
711
+ });
712
+ }
713
+ continue;
714
+ }
715
+ // Plain `{ from, to }` / `{ start, end }` objects (e.g. dateRange from OpenInPopup)
716
+ if (isReportDateRangeObject(fieldData) && !('operation' in fieldData)) {
717
+ // Date-range widgets use `between`; CDK operator union is narrower than runtime.
718
+ filters.push({
719
+ field: fieldName,
720
+ operator: { type: 'between' },
721
+ value: {
722
+ from: String(fieldData.from ?? fieldData.start),
723
+ to: String(fieldData.to ?? fieldData.end),
724
+ },
725
+ });
726
+ continue;
727
+ }
728
+ // Skip structured filter objects without operation metadata
729
+ if (!('operation' in fieldData)) {
730
+ continue;
731
+ }
732
+ const operation = fieldData.operation;
733
+ const value = fieldData.value;
734
+ // Skip if operation type is missing
735
+ if (!operation || !operation.type) {
736
+ continue;
737
+ }
738
+ // Skip if value is effectively empty (null/undefined/empty string,
739
+ // empty object/array, or deep object where all nested values are empty)
740
+ if (isEmptyFilterValue(value)) {
741
+ continue;
742
+ }
743
+ // Create filter clause
744
+ const filter = {
745
+ field: fieldName,
746
+ operator: {
747
+ type: operation.type,
748
+ },
749
+ value: value,
750
+ };
751
+ filters.push(filter);
752
+ }
753
+ // Return filter clause with AND logic
754
+ if (filters.length === 0) {
755
+ return {
756
+ logic: 'and',
757
+ filters: [],
758
+ };
759
+ }
760
+ return {
761
+ logic: 'and',
762
+ filters: filters,
763
+ };
764
+ }
765
+ function asPdfSource(record) {
766
+ if (!record) {
767
+ return null;
768
+ }
769
+ const url = typeof record['url'] === 'string' ? record['url'] : undefined;
770
+ const base64 = typeof record['base64'] === 'string' ? record['base64'] : undefined;
771
+ const blob = record['blob'] instanceof Blob ? record['blob'] : undefined;
772
+ const filename = typeof record['filename'] === 'string' ? record['filename'] : undefined;
773
+ if (url == null && base64 == null && blob == null) {
774
+ return null;
775
+ }
776
+ return { url, base64, blob, filename };
777
+ }
778
+ function queryResultToPdfSource(queryResult) {
779
+ if (!queryResult?.success) {
780
+ return null;
781
+ }
782
+ const fromObject = asPdfSource((runtimeQueryPayloadData(queryResult) ?? runtimeQueryObjectData(queryResult)));
783
+ if (fromObject) {
784
+ return fromObject;
785
+ }
786
+ const page = runtimeQueryCollectionData(queryResult);
787
+ return asPdfSource(page?.items?.[0]);
788
+ }
789
+ function queryResultToChartSeries(queryResult, mapping) {
790
+ if (!queryResult?.success) {
791
+ return {};
792
+ }
793
+ const objectData = runtimeQueryObjectData(queryResult);
794
+ if (objectData && !('items' in objectData)) {
795
+ const series = {};
796
+ for (const [key, value] of Object.entries(objectData)) {
797
+ const n = Number(value);
798
+ if (Number.isFinite(n)) {
799
+ series[key] = n;
800
+ }
801
+ }
802
+ if (Object.keys(series).length > 0) {
803
+ return series;
804
+ }
805
+ }
806
+ const page = runtimeQueryCollectionData(queryResult);
807
+ const rows = (page?.items ?? []);
808
+ if (rows.length === 0) {
809
+ return {};
810
+ }
811
+ const firstRow = rows[0] ?? {};
812
+ const keys = Object.keys(firstRow);
813
+ const xField = mapping?.xField ?? keys[0] ?? 'x';
814
+ const yFieldsRaw = mapping?.yFields;
815
+ const yField = Array.isArray(yFieldsRaw)
816
+ ? yFieldsRaw[0]
817
+ : yFieldsRaw
818
+ ? String(yFieldsRaw)
819
+ : (keys[1] ?? 'value');
820
+ const chartData = {};
821
+ for (const row of rows) {
822
+ const key = String(row[xField] ?? '');
823
+ if (!key) {
824
+ continue;
825
+ }
826
+ const value = Number(row[yField] ?? 0);
827
+ if (Number.isFinite(value)) {
828
+ chartData[key] = value;
829
+ }
830
+ }
831
+ return chartData;
832
+ }
833
+ //#endregion
834
+
835
+ //#region ----- Imports -----
836
+ //#endregion
837
+ //#region ----- Command -----
838
+ /** Opens a report in a results-only popup. Pass `runId` to show a frozen generation run. */
839
+ class AXPOpenReportInPopupCommand {
840
+ constructor() {
841
+ //#region ----- Services & Dependencies -----
842
+ this.popupService = inject(AXPopupService);
843
+ this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
844
+ this.translate = inject(AXTranslationService);
845
+ this.reportViewerService = inject(AXPReportViewerService);
846
+ }
847
+ //#endregion
848
+ //#region ----- Command Implementation -----
849
+ async execute(input) {
850
+ const rowData = this.resolveRowData(input);
851
+ const options = await this.resolveOptions(input, rowData);
852
+ if (!options.reportId) {
853
+ return {
854
+ success: false,
855
+ message: {
856
+ text: await this.translate.translateAsync('@report-management:report-viewer.messages.error.invalid-popup-target'),
857
+ },
858
+ };
859
+ }
860
+ const runId = typeof options.runId === 'string' ? options.runId.trim() : '';
861
+ const popupTitle = await this.resolvePopupTitle(options, rowData, runId);
862
+ const parameters = resolveReportParameterValues(options.parameters);
863
+ const comp = (await import('./acorex-platform-module-report-management-client-report-popup-viewer.component-B9msMMod.mjs')).AXPReportPopupViewerComponent;
864
+ this.popupService.open(comp, {
865
+ title: popupTitle,
866
+ size: 'lg',
867
+ maximizable: true,
868
+ data: {
869
+ reportId: options.reportId,
870
+ layoutId: options.layoutId,
871
+ runId,
872
+ parameters,
873
+ hideColumnFields: options.hideColumnFields ?? [],
874
+ },
875
+ });
876
+ return {
877
+ success: true,
878
+ kind: 'object',
879
+ data: undefined,
880
+ metadata: { refresh: false },
881
+ };
882
+ }
883
+ //#endregion
884
+ //#region ----- Helpers -----
885
+ resolveRowData(input) {
886
+ const ctx = input;
887
+ if (ctx?.data && typeof ctx.data === 'object') {
888
+ return ctx.data;
889
+ }
890
+ if (input['data'] &&
891
+ typeof input['data'] === 'object') {
892
+ return input['data'];
893
+ }
894
+ return input;
895
+ }
896
+ async resolveOptions(input, rowData) {
897
+ const typedInput = input;
898
+ const rawOptions = {
899
+ reportId: typedInput.reportId,
900
+ layoutId: typedInput.layoutId,
901
+ runId: typedInput.runId,
902
+ title: typedInput.title,
903
+ parameters: typedInput.parameters,
904
+ hideColumnFields: typedInput.hideColumnFields,
905
+ ...(typedInput.options ?? {}),
906
+ };
907
+ const scope = {
908
+ context: {
909
+ eval: (path) => get(rowData, path),
910
+ },
911
+ };
912
+ return (await this.expressionEvaluator.evaluate(rawOptions, scope));
913
+ }
914
+ async resolvePopupTitle(options, rowData, runId) {
915
+ let baseTitle = await this.resolveExplicitPopupTitle(options.title, rowData);
916
+ if (!baseTitle && options.reportId) {
917
+ const report = await this.reportViewerService.getReportById(options.reportId);
918
+ if (report?.title) {
919
+ baseTitle =
920
+ this.translate.resolve(report.title) ||
921
+ resolveMultiLanguageString(report.title, this.translate.getActiveLang() || 'en-US') ||
922
+ '';
923
+ }
924
+ }
925
+ if (!baseTitle) {
926
+ baseTitle = await this.translate.translateAsync('@report-management:report-viewer.components.report-popup-viewer.title');
927
+ }
928
+ if (!runId) {
929
+ return baseTitle;
930
+ }
931
+ return this.translate.translateAsync('@report-management:report-viewer.components.report-popup-viewer.generated-title', { params: { title: baseTitle } });
932
+ }
933
+ async resolveExplicitPopupTitle(title, rowData) {
934
+ if (title && typeof title === 'object') {
935
+ const resolved = this.translate.resolve(title);
936
+ if (resolved) {
937
+ return resolved;
938
+ }
939
+ }
940
+ if (!isString(title) || !title.trim()) {
941
+ return '';
942
+ }
943
+ const scope = {
944
+ context: {
945
+ eval: (path) => get(rowData, path),
946
+ },
947
+ };
948
+ const evaluated = await this.expressionEvaluator.evaluate(title, scope);
949
+ return isString(evaluated) ? evaluated : title;
950
+ }
951
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPOpenReportInPopupCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
952
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPOpenReportInPopupCommand, providedIn: 'root' }); }
953
+ }
954
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPOpenReportInPopupCommand, decorators: [{
955
+ type: Injectable,
956
+ args: [{ providedIn: 'root' }]
957
+ }] });
958
+
959
+ var openReportInPopup_command = /*#__PURE__*/Object.freeze({
960
+ __proto__: null,
961
+ AXPOpenReportInPopupCommand: AXPOpenReportInPopupCommand
962
+ });
963
+
964
+ //#region ----- Imports -----
965
+ //#endregion
966
+ //#region ----- Command -----
967
+ /** Opens a report in the full-page report viewer. */
968
+ class AXPOpenReportViewerCommand {
969
+ constructor() {
970
+ //#region ----- Services & Dependencies -----
971
+ this.sessionService = inject(AXPSessionService);
972
+ this.tabNavigation = inject(AXPTabNavigationService);
973
+ this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
974
+ this.translate = inject(AXTranslationService);
975
+ }
976
+ //#endregion
977
+ //#region ----- Command Implementation -----
978
+ async execute(input) {
979
+ const rowData = this.resolveRowData(input);
980
+ const options = await this.resolveOptions(input, rowData);
981
+ if (!options.reportId) {
982
+ return {
983
+ success: false,
984
+ message: {
985
+ text: await this.translate.translateAsync('@report-management:report-viewer.messages.error.invalid-popup-target'),
986
+ },
987
+ };
988
+ }
989
+ const appName = this.sessionService.application?.name;
990
+ if (!appName) {
991
+ return {
992
+ success: false,
993
+ message: {
994
+ text: await this.translate.translateAsync('@report-management:report-viewer.messages.error.invalid-popup-target'),
995
+ },
996
+ };
997
+ }
998
+ // Put reportId in the path (not queryParams): tabular navigation drops NavigationExtras.
999
+ const runId = typeof options.runId === 'string' ? options.runId.trim() : '';
1000
+ const path = runId
1001
+ ? `${appName}/${RootConfig.module.route}/viewer/${options.reportId}?runId=${encodeURIComponent(runId)}`
1002
+ : `${appName}/${RootConfig.module.route}/viewer/${options.reportId}`;
1003
+ await this.tabNavigation.navigate(path);
1004
+ return { success: true, kind: 'object', data: undefined };
1005
+ }
1006
+ //#endregion
1007
+ //#region ----- Helpers -----
1008
+ resolveRowData(input) {
1009
+ const ctx = input;
1010
+ if (ctx?.data && typeof ctx.data === 'object') {
1011
+ return ctx.data;
1012
+ }
1013
+ if (input['data'] &&
1014
+ typeof input['data'] === 'object') {
1015
+ return input['data'];
1016
+ }
1017
+ return input;
1018
+ }
1019
+ async resolveOptions(input, rowData) {
1020
+ const typedInput = input;
1021
+ const rawOptions = {
1022
+ reportId: typedInput.reportId,
1023
+ runId: typedInput.runId,
1024
+ ...(typedInput.options ?? {}),
1025
+ };
1026
+ const scope = {
1027
+ context: {
1028
+ eval: (path) => get(rowData, path),
1029
+ },
1030
+ };
1031
+ return (await this.expressionEvaluator.evaluate(rawOptions, scope));
1032
+ }
1033
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPOpenReportViewerCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1034
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPOpenReportViewerCommand, providedIn: 'root' }); }
1035
+ }
1036
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPOpenReportViewerCommand, decorators: [{
1037
+ type: Injectable,
1038
+ args: [{ providedIn: 'root' }]
1039
+ }] });
1040
+
1041
+ var openReportViewer_command = /*#__PURE__*/Object.freeze({
1042
+ __proto__: null,
1043
+ AXPOpenReportViewerCommand: AXPOpenReportViewerCommand
1044
+ });
1045
+
1046
+ //#region ----- Imports -----
1047
+ //#endregion
1048
+ //#region ----- Handler -----
1049
+ class AXMReportGenerationBackgroundOperationHandler extends AXPBackgroundOperationHandler {
1050
+ constructor() {
1051
+ super(...arguments);
1052
+ this.providerKey = 'report-management';
1053
+ this.openPopup = inject(AXPOpenReportInPopupCommand);
1054
+ this.openViewer = inject(AXPOpenReportViewerCommand);
1055
+ this.queryService = inject(AXPQueryService);
1056
+ }
1057
+ async execute(command, operation) {
1058
+ if (command.name !== 'click') {
1059
+ return;
1060
+ }
1061
+ const reportRequestId = get(operation.data, 'reportRequestId') ??
1062
+ get(operation.data, 'payload.reportRequestId') ??
1063
+ get(operation.data, 'command.payload.reportRequestId');
1064
+ if (typeof reportRequestId !== 'string' || !reportRequestId.trim()) {
1065
+ return;
1066
+ }
1067
+ const result = await this.queryService.fetch(AXMReportManagementQueryKeys.ReportGeneration.Get, { id: reportRequestId });
1068
+ const request = runtimeQueryPayloadData(result);
1069
+ if (!request?.reportId) {
1070
+ return;
1071
+ }
1072
+ if (request.status === 'completed') {
1073
+ await this.openPopup.execute({ reportId: request.reportId, runId: request.id });
1074
+ return;
1075
+ }
1076
+ await this.openViewer.execute({ reportId: request.reportId });
1077
+ }
1078
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMReportGenerationBackgroundOperationHandler, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
1079
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMReportGenerationBackgroundOperationHandler }); }
1080
+ }
1081
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMReportGenerationBackgroundOperationHandler, decorators: [{
1082
+ type: Injectable
1083
+ }] });
1084
+ //#endregion
1085
+ //#region ----- Providers -----
1086
+ const AXM_REPORT_GENERATION_BACKGROUND_OPERATION_HANDLERS = [
1087
+ {
1088
+ provide: AXP_BACKGROUND_OPERATION_HANDLERS,
1089
+ useExisting: AXMReportGenerationBackgroundOperationHandler,
1090
+ multi: true,
1091
+ },
1092
+ AXMReportGenerationBackgroundOperationHandler,
1093
+ ];
1094
+
1095
+ function routesFacory() {
1096
+ let routes = [
1097
+ {
1098
+ path: `:app/${RootConfig.module.route}/viewer`,
1099
+ canActivate: [...AXP_PROTECTED_ROUTE_GUARDS],
1100
+ data: { tab: true, rootLayout: true },
1101
+ loadComponent: () => {
1102
+ return AXPRootLayoutComponent;
1103
+ },
1104
+ children: [
1105
+ {
1106
+ path: ':reportId',
1107
+ loadComponent: () => Promise.resolve().then(function () { return reportViewerPage_component; }).then((c) => c.AXPReportViewerPageComponent),
1108
+ data: { reuse: true },
1109
+ },
1110
+ {
1111
+ path: '',
1112
+ loadComponent: () => Promise.resolve().then(function () { return reportViewerPage_component; }).then((c) => c.AXPReportViewerPageComponent),
1113
+ data: { reuse: true },
1114
+ },
1115
+ ],
1116
+ },
1117
+ ];
1118
+ return routes;
1119
+ }
1120
+ class AXMReportManagementModule {
1121
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMReportManagementModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
1122
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXMReportManagementModule, imports: [AXPDomainModule,
1123
+ AXPRuntimeModule] }); }
1124
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMReportManagementModule, providers: [
1125
+ {
1126
+ provide: ROUTES,
1127
+ multi: true,
1128
+ useFactory: routesFacory,
1129
+ },
1130
+ provideEntity([RootConfig.entities.report.fullName, RootConfig.entities.category.fullName]),
1131
+ // Module Manifest Provider
1132
+ {
1133
+ provide: AXP_MODULE_MANIFEST_PROVIDER,
1134
+ useValue: ReportManagementManifest,
1135
+ multi: true,
1136
+ },
1137
+ // Provider references (now using tokens)
1138
+ {
1139
+ provide: AXP_PERMISSION_DEFINITION_PROVIDER,
1140
+ useClass: AXMPermissionDefinitionProvider,
1141
+ multi: true,
1142
+ },
1143
+ {
1144
+ provide: AXP_MENU_PROVIDER,
1145
+ useClass: AXMMenuProvider,
1146
+ multi: true,
1147
+ },
1148
+ {
1149
+ provide: AXP_FEATURE_DEFINITION_PROVIDER,
1150
+ useClass: AXPReportManagementFeatureDefinitionProvider,
1151
+ multi: true,
1152
+ },
1153
+ ...AXM_REPORT_GENERATION_BACKGROUND_OPERATION_HANDLERS,
1154
+ provideCommandSetups([
1155
+ {
1156
+ key: 'ReportManagement.Report:OpenInPopup',
1157
+ command: () => Promise.resolve().then(function () { return openReportInPopup_command; }).then((c) => c.AXPOpenReportInPopupCommand),
1158
+ },
1159
+ {
1160
+ key: 'ReportManagement.Report:OpenViewer',
1161
+ command: () => Promise.resolve().then(function () { return openReportViewer_command; }).then((c) => c.AXPOpenReportViewerCommand),
1162
+ },
1163
+ ]),
1164
+ {
1165
+ provide: AXP_ENTITY_ACTION_PLUGIN,
1166
+ multi: true,
1167
+ useFactory: () => createEntityReportsPlugin(inject(Injector)),
1168
+ },
1169
+ provideLazyProvider(AXP_PAGE_COMPONENT_PROVIDER, () => import('./acorex-platform-module-report-management-client-entity-reports-page-component.provider-C6tJPxri.mjs').then((m) => m.AXMEntityReportsPageComponentProvider)),
1170
+ ], imports: [AXPDomainModule,
1171
+ AXPRuntimeModule] }); }
1172
+ }
1173
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMReportManagementModule, decorators: [{
1174
+ type: NgModule,
1175
+ args: [{
1176
+ imports: [
1177
+ AXPDomainModule,
1178
+ AXPRuntimeModule,
1179
+ // Entity Modules
1180
+ ],
1181
+ exports: [],
1182
+ declarations: [],
1183
+ providers: [
1184
+ {
1185
+ provide: ROUTES,
1186
+ multi: true,
1187
+ useFactory: routesFacory,
1188
+ },
1189
+ provideEntity([RootConfig.entities.report.fullName, RootConfig.entities.category.fullName]),
1190
+ // Module Manifest Provider
1191
+ {
1192
+ provide: AXP_MODULE_MANIFEST_PROVIDER,
1193
+ useValue: ReportManagementManifest,
1194
+ multi: true,
1195
+ },
1196
+ // Provider references (now using tokens)
1197
+ {
1198
+ provide: AXP_PERMISSION_DEFINITION_PROVIDER,
1199
+ useClass: AXMPermissionDefinitionProvider,
1200
+ multi: true,
1201
+ },
1202
+ {
1203
+ provide: AXP_MENU_PROVIDER,
1204
+ useClass: AXMMenuProvider,
1205
+ multi: true,
1206
+ },
1207
+ {
1208
+ provide: AXP_FEATURE_DEFINITION_PROVIDER,
1209
+ useClass: AXPReportManagementFeatureDefinitionProvider,
1210
+ multi: true,
1211
+ },
1212
+ ...AXM_REPORT_GENERATION_BACKGROUND_OPERATION_HANDLERS,
1213
+ provideCommandSetups([
1214
+ {
1215
+ key: 'ReportManagement.Report:OpenInPopup',
1216
+ command: () => Promise.resolve().then(function () { return openReportInPopup_command; }).then((c) => c.AXPOpenReportInPopupCommand),
1217
+ },
1218
+ {
1219
+ key: 'ReportManagement.Report:OpenViewer',
1220
+ command: () => Promise.resolve().then(function () { return openReportViewer_command; }).then((c) => c.AXPOpenReportViewerCommand),
1221
+ },
1222
+ ]),
1223
+ {
1224
+ provide: AXP_ENTITY_ACTION_PLUGIN,
1225
+ multi: true,
1226
+ useFactory: () => createEntityReportsPlugin(inject(Injector)),
1227
+ },
1228
+ provideLazyProvider(AXP_PAGE_COMPONENT_PROVIDER, () => import('./acorex-platform-module-report-management-client-entity-reports-page-component.provider-C6tJPxri.mjs').then((m) => m.AXMEntityReportsPageComponentProvider)),
1229
+ ],
1230
+ }]
1231
+ }] });
1232
+
1233
+ //#region ---- Report Viewer User Settings Service ----
1234
+ class AXPReportViewerUserSettingsService {
1235
+ constructor() {
1236
+ this.settingsService = inject(AXPSettingsService);
1237
+ }
1238
+ getSettingKey(reportId) {
1239
+ return `ReportManagement:Report:${reportId}`;
1240
+ }
1241
+ async load(reportId) {
1242
+ const stored = await this.settingsService.get(this.getSettingKey(reportId));
1243
+ return stored ?? null;
1244
+ }
1245
+ getTableLayoutState(settings, layoutId) {
1246
+ return settings?.layouts?.[layoutId]?.table;
1247
+ }
1248
+ async saveActiveLayoutId(reportId, layoutId) {
1249
+ await this.patch(reportId, (prev) => ({
1250
+ ...prev,
1251
+ activeLayoutId: layoutId,
1252
+ }));
1253
+ }
1254
+ async saveTableColumnOrder(reportId, layoutId, columns) {
1255
+ await this.patchTable(reportId, layoutId, (table) => ({
1256
+ ...table,
1257
+ columns,
1258
+ }));
1259
+ }
1260
+ async saveTableColumnWidth(reportId, layoutId, field, width, baseColumns) {
1261
+ const columns = baseColumns.map((col) => col.field === field ? { ...col, width } : col);
1262
+ await this.saveTableColumnOrder(reportId, layoutId, columns);
1263
+ }
1264
+ async saveTableSorts(reportId, layoutId, sorts) {
1265
+ await this.patchTable(reportId, layoutId, (table) => ({
1266
+ ...table,
1267
+ sorts,
1268
+ }));
1269
+ }
1270
+ async saveTablePageSize(reportId, layoutId, pageSize) {
1271
+ await this.patchTable(reportId, layoutId, (table) => ({
1272
+ ...table,
1273
+ pageSize,
1274
+ }));
1275
+ }
1276
+ async patch(reportId, updateFn) {
1277
+ await this.settingsService.scope(AXPPlatformScope.User).update(this.getSettingKey(reportId), (prev) => {
1278
+ const base = (prev && typeof prev === 'object' ? prev : {});
1279
+ return updateFn(base);
1280
+ });
1281
+ }
1282
+ async patchTable(reportId, layoutId, updateFn) {
1283
+ await this.patch(reportId, (prev) => {
1284
+ const next = { ...prev };
1285
+ const currentTable = prev.layouts?.[layoutId]?.table ?? {};
1286
+ const updatedTable = updateFn(currentTable);
1287
+ set(next, `layouts.${layoutId}.table`, updatedTable);
1288
+ return next;
1289
+ });
1290
+ }
1291
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportViewerUserSettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1292
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportViewerUserSettingsService, providedIn: 'root' }); }
1293
+ }
1294
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportViewerUserSettingsService, decorators: [{
1295
+ type: Injectable,
1296
+ args: [{ providedIn: 'root' }]
1297
+ }] });
1298
+
1299
+ //#region ---- Constants ----
1300
+ const OPEN_REPORT_IN_POPUP_COMMAND = 'ReportManagement.Report:OpenInPopup';
1301
+ //#endregion
1302
+ //#region ---- Row Action Builder ----
1303
+ /**
1304
+ * Builds table row actions from declarative group-by drill-down definitions.
1305
+ * Skips row commands when linked display columns are used.
1306
+ */
1307
+ function buildDrillRowActionsFromGroupBy(definitions, currentReportId) {
1308
+ if (!definitions?.length || !currentReportId || hasGroupByDrillLinks(definitions)) {
1309
+ return [];
1310
+ }
1311
+ const hasExplicitDefault = definitions.some((definition) => definition.default);
1312
+ return definitions.map((definition, index) => {
1313
+ const isDefault = definition.default ?? (!hasExplicitDefault && index === 0);
1314
+ return {
1315
+ title: definition.title ?? 'View',
1316
+ command: {
1317
+ name: OPEN_REPORT_IN_POPUP_COMMAND,
1318
+ options: buildDrillCommandOptions(definition, currentReportId),
1319
+ },
1320
+ type: 'view',
1321
+ priority: definition.priority ?? 'primary',
1322
+ default: isDefault,
1323
+ iconOnly: false,
1324
+ };
1325
+ });
1326
+ }
1327
+ //#endregion
1328
+ //#region ---- Drill Link Columns ----
1329
+ /**
1330
+ * Whether group-by definitions use linked display columns instead of row command actions.
1331
+ */
1332
+ function hasGroupByDrillLinks(definitions) {
1333
+ return definitions?.some((definition) => isGroupByDrillLinkDefinition(definition)) ?? false;
1334
+ }
1335
+ function isGroupByDrillLinkDefinition(definition) {
1336
+ return Boolean(definition.displayField) && definition.linkDisplay !== false;
1337
+ }
1338
+ function findGroupByDefinitionForDisplayField(definitions, field) {
1339
+ return definitions?.find((definition) => definition.displayField === field && isGroupByDrillLinkDefinition(definition));
1340
+ }
1341
+ function buildGroupByDrillCommandOptions(definition, currentReportId) {
1342
+ return buildDrillCommandOptions(definition, currentReportId);
1343
+ }
1344
+ function resolveGroupByDrillLinkFields(definitions) {
1345
+ if (!definitions?.length) {
1346
+ return [];
1347
+ }
1348
+ return definitions
1349
+ .filter((definition) => isGroupByDrillLinkDefinition(definition))
1350
+ .map((definition) => definition.displayField)
1351
+ .filter(Boolean);
1352
+ }
1353
+ //#endregion
1354
+ //#region ---- Helpers ----
1355
+ function buildDrillCommandOptions(definition, currentReportId) {
1356
+ const drillDown = definition.drillDown;
1357
+ const reportId = drillDown.reportId ?? currentReportId;
1358
+ return {
1359
+ reportId,
1360
+ layoutId: drillDown.layoutId,
1361
+ title: resolveDrillTitle(definition, drillDown.title),
1362
+ parameters: mapDrillParameters(drillDown.parameters),
1363
+ hideColumnFields: resolveDrillHideColumnFields(definition),
1364
+ };
1365
+ }
1366
+ function resolveDrillHideColumnFields(definition) {
1367
+ const fields = new Set(definition.fields);
1368
+ if (definition.displayField) {
1369
+ fields.add(definition.displayField);
1370
+ }
1371
+ return [...fields];
1372
+ }
1373
+ function resolveDrillTitle(definition, explicitTitle) {
1374
+ if (explicitTitle) {
1375
+ return explicitTitle;
1376
+ }
1377
+ if (definition.displayField) {
1378
+ return `{{ context.eval('${definition.displayField}') }}`;
1379
+ }
1380
+ return undefined;
1381
+ }
1382
+ function mapDrillParameters(parameters) {
1383
+ if (!parameters) {
1384
+ return undefined;
1385
+ }
1386
+ return Object.fromEntries(Object.entries(parameters).map(([parameterKey, rowField]) => [
1387
+ parameterKey,
1388
+ toRowExpression(rowField),
1389
+ ]));
1390
+ }
1391
+ function toRowExpression(value) {
1392
+ const trimmed = value.trim();
1393
+ if (trimmed.includes('{{') || trimmed.includes('context.eval')) {
1394
+ return value;
1395
+ }
1396
+ return `{{ context.eval('${value}') }}`;
1397
+ }
1398
+ //#endregion
1399
+
1400
+ /** Infers a column display widget when the report definition omits `widget.type`. */
1401
+ 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?)$/i;
1402
+ function buildReportTableColumns(definitionColumns, savedColumns) {
1403
+ const base = definitionColumns
1404
+ ?.filter((col) => col.visible !== false)
1405
+ .map((col) => mapDefinitionColumn(col)) ?? [];
1406
+ return applySavedColumnLayout(base, savedColumns);
1407
+ }
1408
+ /**
1409
+ * Builds columns and evaluates `visible` expressions (`permission.check` / `feature.check`).
1410
+ */
1411
+ async function buildReportTableColumnsAsync(definitionColumns, savedColumns, expressionEvaluator) {
1412
+ const visibleColumns = [];
1413
+ for (const col of definitionColumns ?? []) {
1414
+ if (await isReportColumnVisible(col.visible, expressionEvaluator)) {
1415
+ visibleColumns.push(col);
1416
+ }
1417
+ }
1418
+ const base = visibleColumns.map((col) => mapDefinitionColumn(col));
1419
+ return applySavedColumnLayout(base, savedColumns);
1420
+ }
1421
+ async function isReportColumnVisible(visible, expressionEvaluator) {
1422
+ if (visible === false) {
1423
+ return false;
1424
+ }
1425
+ if (visible === undefined || visible === true) {
1426
+ return true;
1427
+ }
1428
+ if (typeof visible === 'string' && expressionEvaluator.isExpression(visible)) {
1429
+ try {
1430
+ return Boolean(await expressionEvaluator.evaluate(visible));
1431
+ }
1432
+ catch {
1433
+ return true;
1434
+ }
1435
+ }
1436
+ return Boolean(visible);
1437
+ }
1438
+ function applySavedColumnLayout(base, savedColumns) {
1439
+ if (!savedColumns?.length) {
1440
+ return base;
1441
+ }
1442
+ const widthMap = new Map(savedColumns.map((col) => [col.field, col.width]));
1443
+ const orderIndex = new Map(savedColumns.map((col, index) => [col.field, index]));
1444
+ return [...base]
1445
+ .map((col) => {
1446
+ const savedWidth = widthMap.get(col.field);
1447
+ const resolvedWidth = typeof savedWidth === 'number' ? savedWidth : parseColumnResizeWidth(savedWidth) ?? col.width;
1448
+ return {
1449
+ ...col,
1450
+ width: resolvedWidth,
1451
+ };
1452
+ })
1453
+ .sort((a, b) => {
1454
+ const aIndex = orderIndex.get(a.field) ?? Number.MAX_SAFE_INTEGER;
1455
+ const bIndex = orderIndex.get(b.field) ?? Number.MAX_SAFE_INTEGER;
1456
+ return aIndex - bIndex;
1457
+ });
1458
+ }
1459
+ function toColumnUserState(columns) {
1460
+ return columns.map((col) => ({
1461
+ field: col.field,
1462
+ width: typeof col.width === 'number' ? col.width : parseColumnResizeWidth(col.width) ?? undefined,
1463
+ }));
1464
+ }
1465
+ function inferReportColumnWidgetType(field) {
1466
+ return NUMERIC_COLUMN_FIELD_PATTERN.test(field)
1467
+ ? AXPWidgetsList.Editors.NumberBox
1468
+ : AXPWidgetsList.Editors.TextBox;
1469
+ }
1470
+ function resolveColumnWidget(col) {
1471
+ const path = col.widget?.path ?? col.field;
1472
+ if (col.widget?.type) {
1473
+ return { ...col.widget, path };
1474
+ }
1475
+ return {
1476
+ type: inferReportColumnWidgetType(col.field),
1477
+ path,
1478
+ };
1479
+ }
1480
+ function mapDefinitionColumn(col) {
1481
+ return {
1482
+ field: col.field,
1483
+ title: col.title || col.field,
1484
+ width: col.width || 'auto',
1485
+ visible: col.visible !== false,
1486
+ align: col.align || 'left',
1487
+ sortable: col.sortable === true,
1488
+ pin: col.pin,
1489
+ widget: resolveColumnWidget(col),
1490
+ };
1491
+ }
1492
+ function parseColumnResizeWidth(value) {
1493
+ if (typeof value === 'number' && Number.isFinite(value)) {
1494
+ return value;
1495
+ }
1496
+ if (typeof value === 'string') {
1497
+ const parsed = Number.parseInt(value, 10);
1498
+ return Number.isFinite(parsed) ? parsed : null;
1499
+ }
1500
+ return null;
1501
+ }
1502
+ function resolveColumnFieldFromDataField(dataField) {
1503
+ if (dataField.startsWith('col-')) {
1504
+ return dataField.slice(4);
1505
+ }
1506
+ if (dataField.includes('-')) {
1507
+ return dataField.split('-').slice(1).join('-');
1508
+ }
1509
+ return dataField;
1510
+ }
1511
+ function applyGroupByDrillLinksToColumns(columns, groupByDefinitions) {
1512
+ if (!groupByDefinitions?.length) {
1513
+ return columns;
1514
+ }
1515
+ const linkFields = new Set(groupByDefinitions
1516
+ .filter((definition) => isGroupByDrillLinkDefinition(definition))
1517
+ .map((definition) => definition.displayField)
1518
+ .filter(Boolean));
1519
+ if (linkFields.size === 0) {
1520
+ return columns;
1521
+ }
1522
+ return columns.map((column) => {
1523
+ if (!linkFields.has(column.field)) {
1524
+ return column;
1525
+ }
1526
+ return {
1527
+ ...column,
1528
+ isGroupByDrillLink: true,
1529
+ drillDisplayField: column.field,
1530
+ };
1531
+ });
1532
+ }
1533
+ //#endregion
1534
+
1535
+ const AXPReportViewerViewModel = signalStore(withState(() => ({
1536
+ reportDefinition: null,
1537
+ currentLayout: null,
1538
+ filterContext: {},
1539
+ allowFetchData: false,
1540
+ shouldExecuteReport: false,
1541
+ executionRequestId: 0,
1542
+ lastAppliedFilterContext: null, // Track last applied filters
1543
+ hasBeenExecuted: false, // Track if report has been executed at least once
1544
+ status: AXPPageStatus.Idle,
1545
+ error: null,
1546
+ showRowIndexColumn: false,
1547
+ reportUserSettings: null,
1548
+ runId: null,
1549
+ frozen: false,
1550
+ freeze: null,
1551
+ backgroundQueued: false,
1552
+ parametersReadonly: false,
1553
+ selectedLayoutIds: [],
1554
+ })), withComputed((store, widgetRegistryService = inject(AXPWidgetRegistryService)) => ({
1555
+ //#region ---- Layout Management ----
1556
+ availableLayouts: computed(() => {
1557
+ const report = store.reportDefinition();
1558
+ if (!report?.layouts)
1559
+ return [];
1560
+ const freeze = store.freeze();
1561
+ const layouts = store.frozen() && freeze?.dataSources?.length
1562
+ ? report.layouts.filter((layout) => freeze.dataSources.some((source) => source.layoutId === layout.id))
1563
+ : report.layouts;
1564
+ return layouts.map((layout) => ({
1565
+ id: layout.id,
1566
+ title: layout.title,
1567
+ icon: layout.icon,
1568
+ type: layout.layout.type,
1569
+ }));
1570
+ }),
1571
+ defaultLayout: computed(() => {
1572
+ const report = store.reportDefinition();
1573
+ if (!report?.layouts?.length)
1574
+ return null;
1575
+ // Find layout marked as default, or use first layout
1576
+ return report.layouts.find((l) => l.isDefault) || report.layouts[0];
1577
+ }),
1578
+ activeLayoutId: computed(() => store.currentLayout()?.id || null),
1579
+ layoutType: computed(() => store.currentLayout()?.layout?.type || null),
1580
+ //#endregion
1581
+ //#region ---- Layout-Specific Configurations ----
1582
+ tableLayoutConfig: computed(() => {
1583
+ const layout = store.currentLayout();
1584
+ return layout?.layout?.type === 'table' ? layout.layout : null;
1585
+ }),
1586
+ chartLayoutConfig: computed(() => {
1587
+ const layout = store.currentLayout();
1588
+ return layout?.layout?.type === 'chart' ? layout.layout : null;
1589
+ }),
1590
+ pdfLayoutConfig: computed(() => {
1591
+ const layout = store.currentLayout();
1592
+ return layout?.layout?.type === 'pdf' ? layout.layout : null;
1593
+ }),
1594
+ //#endregion
1595
+ //#region ---- State Indicators ----
1596
+ isLoading: computed(() => store.status() === AXPPageStatus.Processing),
1597
+ isExecuting: computed(() => store.status() === AXPPageStatus.Submitting),
1598
+ hasError: computed(() => store.error() !== null),
1599
+ canExecute: computed(() => {
1600
+ // Always allow execution if there's an error (to enable retry)
1601
+ if (store.error() !== null) {
1602
+ return true;
1603
+ }
1604
+ if ((store.reportDefinition()?.generation?.mode ?? 'interactive') === 'background') {
1605
+ if (store.backgroundQueued()) {
1606
+ return false;
1607
+ }
1608
+ return store.selectedLayoutIds().length > 0;
1609
+ }
1610
+ // Allow execution if there are unapplied filters
1611
+ const currentContext = store.filterContext();
1612
+ const lastApplied = store.lastAppliedFilterContext();
1613
+ const hasBeenExecuted = store.hasBeenExecuted();
1614
+ if (hasBeenExecuted && JSON.stringify(currentContext) !== JSON.stringify(lastApplied)) {
1615
+ return true;
1616
+ }
1617
+ // Allow execution if report hasn't been executed yet
1618
+ if (!hasBeenExecuted) {
1619
+ return true;
1620
+ }
1621
+ // Otherwise, don't allow execution
1622
+ return false;
1623
+ }),
1624
+ hasMultipleLayouts: computed(() => {
1625
+ const report = store.reportDefinition();
1626
+ const freeze = store.freeze();
1627
+ if (store.frozen() && freeze?.dataSources?.length) {
1628
+ return freeze.dataSources.length > 1;
1629
+ }
1630
+ return (report?.layouts?.length ?? 0) > 1;
1631
+ }),
1632
+ title: computed(() => store.reportDefinition()?.title ?? '@report-management:report-viewer.components.report-viewer.title'),
1633
+ description: computed(() => store.reportDefinition()?.description ?? null),
1634
+ //#endregion
1635
+ //#region ---- Form Configuration ----
1636
+ formDefinition: computed(() => {
1637
+ const report = store.reportDefinition();
1638
+ if (!report || !report.parameterGroups) {
1639
+ return { groups: [] };
1640
+ }
1641
+ const formDefinition = {
1642
+ groups: report.parameterGroups.map((group) => ({
1643
+ name: group.name,
1644
+ title: group.title,
1645
+ description: group.description,
1646
+ parameters: group.parameters.map((param) => {
1647
+ const editorType = param.widget.type ?? '';
1648
+ const widget = widgetRegistryService.getOptional(editorType);
1649
+ const filterType = widget?.defaultFilterWidgetName ?? editorType;
1650
+ // date-time / text filter aliases do not mount reliably in report filter forms;
1651
+ // keep the editor widget and enable filterMode instead.
1652
+ const useEditorFilter = editorType === 'date-time-editor' ||
1653
+ filterType === 'datetime-filter' ||
1654
+ editorType === 'text-editor' ||
1655
+ filterType === 'string-filter';
1656
+ const widgetType = useEditorFilter ? editorType || filterType : filterType;
1657
+ const rawDefault = param.widget.defaultValue ?? param.widget.options?.['defaultValue'];
1658
+ const resolvedDefault = rawDefault === undefined ? undefined : resolveReportFilterDefaultValue(rawDefault);
1659
+ return {
1660
+ path: param.path,
1661
+ title: param.title,
1662
+ description: param.description,
1663
+ visible: param.visible,
1664
+ badge: `{{ filter.getTitle(context.eval("${param.path}.operation.type")) }}`,
1665
+ widget: {
1666
+ path: param.path,
1667
+ type: widgetType,
1668
+ ...(resolvedDefault !== undefined ? { defaultValue: resolvedDefault } : {}),
1669
+ options: {
1670
+ ...param.widget.options,
1671
+ clearButton: true,
1672
+ hasClearButton: true,
1673
+ ...(useEditorFilter ? { filterMode: true } : {}),
1674
+ },
1675
+ triggers: param.widget.triggers,
1676
+ }
1677
+ };
1678
+ }),
1679
+ })),
1680
+ };
1681
+ return formDefinition;
1682
+ }),
1683
+ //#endregion
1684
+ //#region ---- Layout Type Detection ----
1685
+ isTableLayout: computed(() => store.currentLayout()?.layout?.type === 'table'),
1686
+ isChartLayout: computed(() => store.currentLayout()?.layout?.type === 'chart'),
1687
+ isPdfLayout: computed(() => store.currentLayout()?.layout?.type === 'pdf'),
1688
+ //#endregion
1689
+ //#region ---- Filter States ----
1690
+ hasUnappliedFilters: computed(() => {
1691
+ const currentContext = store.filterContext();
1692
+ const lastApplied = store.lastAppliedFilterContext();
1693
+ const hasBeenExecuted = store.hasBeenExecuted();
1694
+ // If report has never been executed, don't show unapplied filters warning
1695
+ if (!hasBeenExecuted) {
1696
+ return false;
1697
+ }
1698
+ // If no filters have been applied yet, return true
1699
+ if (lastApplied === null) {
1700
+ return true;
1701
+ }
1702
+ // Check if current filters are different from last applied filters
1703
+ const currentKeys = Object.keys(currentContext);
1704
+ const lastKeys = Object.keys(lastApplied);
1705
+ if (currentKeys.length !== lastKeys.length) {
1706
+ return true;
1707
+ }
1708
+ // Check if any values are different
1709
+ for (const key of currentKeys) {
1710
+ if (currentContext[key] !== lastApplied[key]) {
1711
+ return true;
1712
+ }
1713
+ }
1714
+ return false;
1715
+ }),
1716
+ //#endregion
1717
+ //#region ---- Action States ----
1718
+ canSelectRows: computed(() => {
1719
+ const layout = store.currentLayout();
1720
+ return (layout?.layout?.type === 'table' &&
1721
+ layout.layout.options?.selectable?.mode &&
1722
+ layout.layout.options?.selectable?.mode !== 'none');
1723
+ }),
1724
+ tableLayoutUserSettings: computed(() => {
1725
+ const settings = store.reportUserSettings();
1726
+ const layoutId = store.currentLayout()?.id;
1727
+ if (!settings || !layoutId) {
1728
+ return null;
1729
+ }
1730
+ return settings.layouts?.[layoutId]?.table ?? null;
1731
+ }),
1732
+ reportId: computed(() => store.reportDefinition()?.id ?? null),
1733
+ isFrozen: computed(() => store.frozen()),
1734
+ isBackgroundQueued: computed(() => store.backgroundQueued()),
1735
+ isBackgroundReportMode: computed(() => (store.reportDefinition()?.generation?.mode ?? 'interactive') === 'background'),
1736
+ hasSelectedBackgroundLayouts: computed(() => store.selectedLayoutIds().length > 0),
1737
+ areParametersReadonly: computed(() => store.parametersReadonly() || store.frozen()),
1738
+ canSwitchLayout: computed(() => {
1739
+ if (store.frozen() && (store.freeze()?.dataSources?.length ?? 0) <= 1) {
1740
+ return false;
1741
+ }
1742
+ return (store.reportDefinition()?.layouts?.length ?? 0) > 1 && store.status() !== AXPPageStatus.Submitting;
1743
+ }),
1744
+ generationMode: computed(() => store.reportDefinition()?.generation?.mode ?? 'interactive'),
1745
+ //#endregion
1746
+ })), withMethods((store, reportDefinitionService = inject(AXPReportViewerService), reportUserSettingsService = inject(AXPReportViewerUserSettingsService), queryService = inject(AXPQueryService), commandService = inject(AXPCommandService), toastService = inject(AXToastService), translationService = inject(AXTranslationService)) => {
1747
+ let activeFetchCount = 0;
1748
+ const generateInBackground = async () => {
1749
+ const report = store.reportDefinition();
1750
+ const isBackgroundMode = (report?.generation?.mode ?? 'interactive') === 'background';
1751
+ const layoutIds = isBackgroundMode
1752
+ ? store.selectedLayoutIds()
1753
+ : store.currentLayout()
1754
+ ? [store.currentLayout().id]
1755
+ : [];
1756
+ if (!report || layoutIds.length === 0) {
1757
+ patchState(store, {
1758
+ error: layoutIds.length === 0
1759
+ ? '@report-management:report-generation.errors.no-layouts-selected'
1760
+ : '@report-management:report-viewer.messages.error.report-or-layout-not-found',
1761
+ });
1762
+ return;
1763
+ }
1764
+ patchState(store, { status: AXPPageStatus.Submitting, error: null });
1765
+ try {
1766
+ const result = await commandService.executeRemote(AXMReportManagementCommandKeys.ReportGeneration.Create, {
1767
+ reportId: report.id,
1768
+ layoutIds,
1769
+ layoutId: layoutIds[0],
1770
+ parameters: store.filterContext(),
1771
+ trigger: 'manual',
1772
+ });
1773
+ if (!result?.success) {
1774
+ patchState(store, {
1775
+ status: AXPPageStatus.Error,
1776
+ error: commandMessageTextForError(result?.message?.text) ||
1777
+ '@report-management:report-generation.errors.create-failed',
1778
+ });
1779
+ return;
1780
+ }
1781
+ patchState(store, {
1782
+ status: AXPPageStatus.Submitted,
1783
+ backgroundQueued: true,
1784
+ allowFetchData: false,
1785
+ hasBeenExecuted: false,
1786
+ });
1787
+ toastService.show({
1788
+ color: 'success',
1789
+ title: await translationService.translateAsync('@report-management:report-generation.messages.queued-title'),
1790
+ content: await translationService.translateAsync('@report-management:report-generation.messages.queued-body'),
1791
+ });
1792
+ }
1793
+ catch (error) {
1794
+ patchState(store, {
1795
+ status: AXPPageStatus.Error,
1796
+ error: error instanceof Error
1797
+ ? error.message
1798
+ : '@report-management:report-generation.errors.create-failed',
1799
+ });
1800
+ }
1801
+ };
1802
+ const executeReport = async () => {
1803
+ try {
1804
+ if (store.status() === AXPPageStatus.Processing ||
1805
+ store.status() === AXPPageStatus.Submitting) {
1806
+ return;
1807
+ }
1808
+ if (store.frozen()) {
1809
+ patchState(store, {
1810
+ allowFetchData: true,
1811
+ shouldExecuteReport: true,
1812
+ executionRequestId: store.executionRequestId() + 1,
1813
+ hasBeenExecuted: true,
1814
+ });
1815
+ return;
1816
+ }
1817
+ const report = store.reportDefinition();
1818
+ const layout = store.currentLayout();
1819
+ if (!report || !layout) {
1820
+ patchState(store, {
1821
+ error: '@report-management:report-viewer.messages.error.report-or-layout-not-found',
1822
+ });
1823
+ return;
1824
+ }
1825
+ if ((report.generation?.mode ?? 'interactive') === 'background') {
1826
+ await generateInBackground();
1827
+ return;
1828
+ }
1829
+ patchState(store, {
1830
+ status: AXPPageStatus.Submitting,
1831
+ error: null,
1832
+ allowFetchData: true,
1833
+ shouldExecuteReport: true,
1834
+ executionRequestId: store.executionRequestId() + 1,
1835
+ lastAppliedFilterContext: { ...store.filterContext() }, // Mark current filters as applied
1836
+ hasBeenExecuted: true, // Mark that report has been executed at least once
1837
+ });
1838
+ patchState(store, {
1839
+ status: AXPPageStatus.Submitted,
1840
+ error: null,
1841
+ });
1842
+ }
1843
+ catch (error) {
1844
+ patchState(store, {
1845
+ status: AXPPageStatus.Error,
1846
+ error: error instanceof Error ? error.message : '@report-management:report-viewer.messages.error.unexpected-execution',
1847
+ });
1848
+ }
1849
+ };
1850
+ const buildSummaryFromLayout = (layoutDef) => {
1851
+ const tableLayout = layoutDef.layout;
1852
+ if (tableLayout?.type !== 'table' || !Array.isArray(tableLayout.summary)) {
1853
+ return undefined;
1854
+ }
1855
+ const footer = tableLayout.summary.filter((s) => s.displayIn === 'reportFooter');
1856
+ return footer.length > 0 ? footer : undefined;
1857
+ };
1858
+ const fetchData = async (query) => {
1859
+ const report = store.reportDefinition();
1860
+ const layout = store.currentLayout();
1861
+ if (!report || !layout) {
1862
+ return null;
1863
+ }
1864
+ if (store.frozen()) {
1865
+ const freeze = store.freeze();
1866
+ const layoutId = layout.id;
1867
+ const source = freeze?.dataSources.find((item) => item.layoutId === layoutId) ?? freeze?.dataSources[0];
1868
+ if (!source?.result) {
1869
+ return null;
1870
+ }
1871
+ const collection = runtimeQueryCollectionData(source.result);
1872
+ if (!collection) {
1873
+ return source.result;
1874
+ }
1875
+ const skip = query?.skip ?? 0;
1876
+ const take = query?.take ?? collection.items.length;
1877
+ return {
1878
+ ...source.result,
1879
+ success: true,
1880
+ kind: 'collection',
1881
+ data: {
1882
+ ...collection,
1883
+ items: collection.items.slice(skip, skip + take),
1884
+ },
1885
+ };
1886
+ }
1887
+ if (activeFetchCount > 0) {
1888
+ return null;
1889
+ }
1890
+ activeFetchCount++;
1891
+ patchState(store, {
1892
+ status: AXPPageStatus.Processing,
1893
+ error: null,
1894
+ });
1895
+ let nextStatus = AXPPageStatus.Idle;
1896
+ try {
1897
+ const dataSource = layout.dataSource;
1898
+ if (!dataSource || dataSource.type !== 'query' || !dataSource.name) {
1899
+ nextStatus = AXPPageStatus.Error;
1900
+ patchState(store, {
1901
+ error: '@report-management:report-viewer.messages.error.failed-to-execute',
1902
+ });
1903
+ return null;
1904
+ }
1905
+ const filterClause = convertContextDataToFilterClause(store.filterContext());
1906
+ const runtimeSort = query?.sort?.length
1907
+ ? query.sort.map((s) => ({
1908
+ name: s.field,
1909
+ dir: s.dir,
1910
+ }))
1911
+ : dataSource.sortFields;
1912
+ const summary = buildSummaryFromLayout(layout);
1913
+ const queryInput = {
1914
+ skip: query?.skip ?? 0,
1915
+ take: query?.take ?? null,
1916
+ filter: filterClause,
1917
+ ...(runtimeSort?.length
1918
+ ? {
1919
+ sort: runtimeSort.map((s) => ({
1920
+ field: s.name,
1921
+ dir: s.dir,
1922
+ })),
1923
+ }
1924
+ : {}),
1925
+ ...(summary ? { summary } : {}),
1926
+ };
1927
+ const queryResult = await queryService.fetch(dataSource.name, queryInput);
1928
+ if (!queryResult?.success) {
1929
+ nextStatus = AXPPageStatus.Error;
1930
+ patchState(store, {
1931
+ error: commandMessageTextForError(queryResult?.message?.text) ||
1932
+ '@report-management:report-viewer.messages.error.failed-to-execute',
1933
+ });
1934
+ return null;
1935
+ }
1936
+ return queryResult;
1937
+ }
1938
+ catch (error) {
1939
+ nextStatus = AXPPageStatus.Error;
1940
+ const message = error instanceof Error && error.message
1941
+ ? error.message
1942
+ : '@report-management:report-viewer.messages.error.failed-to-execute';
1943
+ patchState(store, {
1944
+ error: message,
1945
+ });
1946
+ console.error('Error executing report:', error);
1947
+ return null;
1948
+ }
1949
+ finally {
1950
+ activeFetchCount--;
1951
+ patchState(store, {
1952
+ status: nextStatus,
1953
+ });
1954
+ }
1955
+ };
1956
+ return {
1957
+ //#region ---- Report Loading ----
1958
+ async loadReport(reportId) {
1959
+ patchState(store, {
1960
+ status: AXPPageStatus.Processing,
1961
+ error: null,
1962
+ });
1963
+ try {
1964
+ const report = await reportDefinitionService.getReportById(reportId);
1965
+ if (report) {
1966
+ // Configure parameter widgets with proper paths
1967
+ report.parameterGroups.forEach((group) => {
1968
+ group.parameters.forEach((param) => {
1969
+ param.widget.path = param.path;
1970
+ });
1971
+ });
1972
+ const userSettings = await reportUserSettingsService.load(reportId);
1973
+ const defaultLayout = report.layouts.find((l) => l.isDefault) || report.layouts[0];
1974
+ const savedLayout = userSettings?.activeLayoutId
1975
+ ? report.layouts.find((l) => l.id === userSettings.activeLayoutId)
1976
+ : undefined;
1977
+ patchState(store, {
1978
+ reportDefinition: report,
1979
+ currentLayout: savedLayout ?? defaultLayout,
1980
+ filterContext: collectReportParameterDefaults(report.parameterGroups),
1981
+ allowFetchData: false,
1982
+ hasBeenExecuted: false,
1983
+ executionRequestId: 0,
1984
+ reportUserSettings: userSettings,
1985
+ runId: null,
1986
+ frozen: false,
1987
+ freeze: null,
1988
+ backgroundQueued: false,
1989
+ parametersReadonly: false,
1990
+ selectedLayoutIds: report.layouts.map((layout) => layout.id),
1991
+ });
1992
+ }
1993
+ }
1994
+ catch (error) {
1995
+ patchState(store, {
1996
+ error: '@report-management:report-viewer.messages.error.failed-to-load',
1997
+ });
1998
+ console.error('Error loading report:', error);
1999
+ }
2000
+ finally {
2001
+ patchState(store, {
2002
+ status: AXPPageStatus.Idle,
2003
+ });
2004
+ }
2005
+ },
2006
+ //#endregion
2007
+ //#region ---- Layout Management ----
2008
+ switchLayout(layoutId) {
2009
+ const report = store.reportDefinition();
2010
+ if (!report)
2011
+ return;
2012
+ const layout = report.layouts.find((l) => l.id === layoutId);
2013
+ if (layout) {
2014
+ patchState(store, {
2015
+ currentLayout: layout,
2016
+ reportUserSettings: {
2017
+ ...(store.reportUserSettings() ?? {}),
2018
+ activeLayoutId: layoutId,
2019
+ },
2020
+ });
2021
+ void reportUserSettingsService.saveActiveLayoutId(report.id, layoutId);
2022
+ }
2023
+ },
2024
+ isBackgroundLayoutSelected(layoutId) {
2025
+ return store.selectedLayoutIds().includes(layoutId);
2026
+ },
2027
+ setBackgroundLayoutSelected(layoutId, selected) {
2028
+ const current = store.selectedLayoutIds();
2029
+ if (selected) {
2030
+ if (current.includes(layoutId)) {
2031
+ return;
2032
+ }
2033
+ patchState(store, { selectedLayoutIds: [...current, layoutId] });
2034
+ return;
2035
+ }
2036
+ patchState(store, {
2037
+ selectedLayoutIds: current.filter((id) => id !== layoutId),
2038
+ });
2039
+ },
2040
+ async saveTableColumnOrder(columns) {
2041
+ const reportId = store.reportDefinition()?.id;
2042
+ const layoutId = store.currentLayout()?.id;
2043
+ if (!reportId || !layoutId) {
2044
+ return;
2045
+ }
2046
+ await reportUserSettingsService.saveTableColumnOrder(reportId, layoutId, toColumnUserState(columns));
2047
+ patchState(store, {
2048
+ reportUserSettings: mergeTableColumnsIntoSettings(store.reportUserSettings(), layoutId, toColumnUserState(columns)),
2049
+ });
2050
+ },
2051
+ async saveTableColumnWidth(field, width, columns) {
2052
+ const reportId = store.reportDefinition()?.id;
2053
+ const layoutId = store.currentLayout()?.id;
2054
+ if (!reportId || !layoutId) {
2055
+ return;
2056
+ }
2057
+ const columnState = toColumnUserState(columns);
2058
+ await reportUserSettingsService.saveTableColumnWidth(reportId, layoutId, field, width, columnState);
2059
+ patchState(store, {
2060
+ reportUserSettings: mergeTableColumnsIntoSettings(store.reportUserSettings(), layoutId, columnState),
2061
+ });
2062
+ },
2063
+ async saveTableSorts(sorts) {
2064
+ const reportId = store.reportDefinition()?.id;
2065
+ const layoutId = store.currentLayout()?.id;
2066
+ if (!reportId || !layoutId) {
2067
+ return;
2068
+ }
2069
+ await reportUserSettingsService.saveTableSorts(reportId, layoutId, sorts);
2070
+ patchState(store, {
2071
+ reportUserSettings: mergeTableSortsIntoSettings(store.reportUserSettings(), layoutId, sorts),
2072
+ });
2073
+ },
2074
+ async saveTablePageSize(pageSize) {
2075
+ const reportId = store.reportDefinition()?.id;
2076
+ const layoutId = store.currentLayout()?.id;
2077
+ if (!reportId || !layoutId) {
2078
+ return;
2079
+ }
2080
+ await reportUserSettingsService.saveTablePageSize(reportId, layoutId, pageSize);
2081
+ patchState(store, {
2082
+ reportUserSettings: mergeTablePageSizeIntoSettings(store.reportUserSettings(), layoutId, pageSize),
2083
+ });
2084
+ },
2085
+ //#endregion
2086
+ //#region ---- Data Execution ----
2087
+ executeReport,
2088
+ fetchData,
2089
+ generateInBackground,
2090
+ nudgeFetch() {
2091
+ if (!store.allowFetchData()) {
2092
+ return;
2093
+ }
2094
+ patchState(store, { executionRequestId: store.executionRequestId() + 1 });
2095
+ },
2096
+ async loadFrozenRun(runId) {
2097
+ patchState(store, { status: AXPPageStatus.Processing, error: null });
2098
+ try {
2099
+ const result = await queryService.fetch(AXMReportManagementQueryKeys.ReportGeneration.GetViewerFreeze, { id: runId });
2100
+ const freeze = runtimeQueryPayloadData(result);
2101
+ if (!result?.success || !freeze) {
2102
+ patchState(store, {
2103
+ error: commandMessageTextForError(result?.message?.text) ||
2104
+ '@report-management:report-generation.errors.freeze-not-found',
2105
+ });
2106
+ return;
2107
+ }
2108
+ let report = store.reportDefinition();
2109
+ if (freeze.definition) {
2110
+ report = {
2111
+ id: freeze.reportId,
2112
+ title: freeze.definition.title,
2113
+ categoryIds: report?.categoryIds ?? [],
2114
+ parameterGroups: report?.parameterGroups ?? [],
2115
+ layouts: freeze.definition.layouts,
2116
+ defaultLayoutId: freeze.definition.defaultLayoutId,
2117
+ generation: report?.generation,
2118
+ };
2119
+ }
2120
+ else if (!report) {
2121
+ report = (await reportDefinitionService.getReportById(freeze.reportId)) ?? null;
2122
+ }
2123
+ if (!report) {
2124
+ patchState(store, {
2125
+ error: '@report-management:report-viewer.messages.error.failed-to-load',
2126
+ });
2127
+ return;
2128
+ }
2129
+ const defaultLayout = report.layouts.find((layout) => layout.id === freeze.definition?.defaultLayoutId) ||
2130
+ report.layouts.find((layout) => layout.id === freeze.dataSources[0]?.layoutId) ||
2131
+ report.layouts[0];
2132
+ patchState(store, {
2133
+ reportDefinition: report,
2134
+ currentLayout: defaultLayout,
2135
+ filterContext: freeze.parameters ?? {},
2136
+ lastAppliedFilterContext: freeze.parameters ?? {},
2137
+ allowFetchData: true,
2138
+ shouldExecuteReport: true,
2139
+ hasBeenExecuted: true,
2140
+ executionRequestId: store.executionRequestId() + 1,
2141
+ runId,
2142
+ frozen: true,
2143
+ freeze,
2144
+ backgroundQueued: false,
2145
+ parametersReadonly: true,
2146
+ status: AXPPageStatus.Idle,
2147
+ });
2148
+ }
2149
+ catch (error) {
2150
+ patchState(store, {
2151
+ error: error instanceof Error
2152
+ ? error.message
2153
+ : '@report-management:report-generation.errors.freeze-not-found',
2154
+ });
2155
+ }
2156
+ finally {
2157
+ patchState(store, { status: AXPPageStatus.Idle });
2158
+ }
2159
+ },
2160
+ //#endregion
2161
+ //#region ---- Drill View ----
2162
+ /**
2163
+ * Applies the same layout in drill-down mode: hides group-by columns, keeps summary and data source.
2164
+ */
2165
+ applyDrillView(options) {
2166
+ const layout = store.currentLayout();
2167
+ if (!layout || layout.layout.type !== 'table') {
2168
+ return;
2169
+ }
2170
+ const tableLayout = layout.layout;
2171
+ const hiddenFields = new Set(options.hideColumnFields);
2172
+ const nextTableLayout = {
2173
+ ...tableLayout,
2174
+ columns: tableLayout.columns.map((column) => hiddenFields.has(column.field) ? { ...column, visible: false } : column),
2175
+ groupBy: undefined,
2176
+ actions: [],
2177
+ };
2178
+ patchState(store, {
2179
+ currentLayout: {
2180
+ ...layout,
2181
+ layout: nextTableLayout,
2182
+ },
2183
+ });
2184
+ },
2185
+ //#endregion
2186
+ //#region ---- Filter Management ----
2187
+ updateFilterContext(context) {
2188
+ patchState(store, {
2189
+ filterContext: { ...store.filterContext(), ...context },
2190
+ shouldExecuteReport: false, // Reset execution flag when filters change
2191
+ });
2192
+ },
2193
+ clearFilters() {
2194
+ patchState(store, {
2195
+ allowFetchData: false,
2196
+ shouldExecuteReport: false,
2197
+ filterContext: collectReportParameterDefaults(store.reportDefinition()?.parameterGroups),
2198
+ lastAppliedFilterContext: null, // Reset applied filters when clearing
2199
+ hasBeenExecuted: false, // Reset execution state when clearing filters
2200
+ });
2201
+ },
2202
+ resetFilters() {
2203
+ patchState(store, {
2204
+ allowFetchData: false,
2205
+ });
2206
+ },
2207
+ //#endregion
2208
+ //#region ---- Export Management ----
2209
+ async exportReport(format) {
2210
+ const layout = store.currentLayout();
2211
+ if (!layout?.export) {
2212
+ console.warn('@report-management:report-viewer.messages.warnings.export-not-configured');
2213
+ return;
2214
+ }
2215
+ try {
2216
+ // Export logic would go here
2217
+ // This would typically call a different command for export
2218
+ console.log(`Exporting report in ${format} format`);
2219
+ }
2220
+ catch (error) {
2221
+ console.error('Error exporting report:', error);
2222
+ }
2223
+ },
2224
+ //#endregion
2225
+ //#region ---- Utility Methods ----
2226
+ refresh() {
2227
+ return executeReport();
2228
+ },
2229
+ clearError() {
2230
+ patchState(store, {
2231
+ error: null,
2232
+ });
2233
+ },
2234
+ clearExecutionState() {
2235
+ patchState(store, {
2236
+ hasBeenExecuted: false,
2237
+ allowFetchData: false,
2238
+ shouldExecuteReport: false,
2239
+ executionRequestId: 0,
2240
+ });
2241
+ },
2242
+ cancel() {
2243
+ patchState(store, {
2244
+ status: AXPPageStatus.Idle,
2245
+ hasBeenExecuted: false,
2246
+ error: null,
2247
+ });
2248
+ //TODO: Cancel the report execution
2249
+ },
2250
+ reset() {
2251
+ patchState(store, {
2252
+ reportDefinition: null,
2253
+ currentLayout: null,
2254
+ filterContext: {},
2255
+ allowFetchData: false,
2256
+ shouldExecuteReport: false,
2257
+ executionRequestId: 0,
2258
+ lastAppliedFilterContext: null,
2259
+ hasBeenExecuted: false,
2260
+ status: AXPPageStatus.Idle,
2261
+ error: null,
2262
+ reportUserSettings: null,
2263
+ runId: null,
2264
+ frozen: false,
2265
+ freeze: null,
2266
+ backgroundQueued: false,
2267
+ parametersReadonly: false,
2268
+ });
2269
+ },
2270
+ //#endregion
2271
+ };
2272
+ }), withHooks((store, translationService = inject(AXTranslationService), localeService = inject(AXLocaleService), settingsService = inject(AXPSettingsService)) => {
2273
+ const settingSubs = new Subscription();
2274
+ const syncShowRowIndexColumn = async () => {
2275
+ try {
2276
+ const value = await settingsService.get(AXPCommonSettings.ShowRowIndexColumn);
2277
+ patchState(store, { showRowIndexColumn: value ?? false });
2278
+ }
2279
+ catch {
2280
+ patchState(store, { showRowIndexColumn: false });
2281
+ }
2282
+ };
2283
+ return {
2284
+ async onInit() {
2285
+ translationService.langChanges$.subscribe(() => {
2286
+ // Handle language change if needed
2287
+ });
2288
+ localeService.profileChanged$.subscribe(() => {
2289
+ // Handle locale change if needed
2290
+ });
2291
+ void syncShowRowIndexColumn();
2292
+ settingSubs.add(settingsService.onLoaded.subscribe(() => void syncShowRowIndexColumn()));
2293
+ settingSubs.add(settingsService.onChanged.subscribe(() => void syncShowRowIndexColumn()));
2294
+ },
2295
+ onDestroy() {
2296
+ settingSubs.unsubscribe();
2297
+ },
2298
+ };
2299
+ }));
2300
+ //#region ---- Settings Merge Helpers ----
2301
+ function mergeTableColumnsIntoSettings(settings, layoutId, columns) {
2302
+ const base = settings ?? {};
2303
+ return {
2304
+ ...base,
2305
+ layouts: {
2306
+ ...base.layouts,
2307
+ [layoutId]: {
2308
+ ...base.layouts?.[layoutId],
2309
+ table: {
2310
+ ...base.layouts?.[layoutId]?.table,
2311
+ columns,
2312
+ },
2313
+ },
2314
+ },
2315
+ };
2316
+ }
2317
+ function mergeTableSortsIntoSettings(settings, layoutId, sorts) {
2318
+ const base = settings ?? {};
2319
+ return {
2320
+ ...base,
2321
+ layouts: {
2322
+ ...base.layouts,
2323
+ [layoutId]: {
2324
+ ...base.layouts?.[layoutId],
2325
+ table: {
2326
+ ...base.layouts?.[layoutId]?.table,
2327
+ sorts,
2328
+ },
2329
+ },
2330
+ },
2331
+ };
2332
+ }
2333
+ function mergeTablePageSizeIntoSettings(settings, layoutId, pageSize) {
2334
+ const base = settings ?? {};
2335
+ return {
2336
+ ...base,
2337
+ layouts: {
2338
+ ...base.layouts,
2339
+ [layoutId]: {
2340
+ ...base.layouts?.[layoutId],
2341
+ table: {
2342
+ ...base.layouts?.[layoutId]?.table,
2343
+ pageSize,
2344
+ },
2345
+ },
2346
+ },
2347
+ };
2348
+ }
2349
+ //#endregion
2350
+
2351
+ class AXPReportChartViewComponent {
2352
+ constructor() {
2353
+ //#region ---- Dependencies ----
2354
+ this.vm = inject(AXPReportViewerViewModel);
2355
+ this.AXPPageStatus = AXPPageStatus;
2356
+ //#endregion
2357
+ //#region ---- Computed Properties ----
2358
+ /**
2359
+ * Current chart layout configuration
2360
+ */
2361
+ this.chartLayout = computed(() => {
2362
+ const layout = this.vm.currentLayout();
2363
+ return layout?.layout.type === 'chart' ? layout.layout : null;
2364
+ }, /* @ts-ignore */
2365
+ ...(ngDevMode ? [{ debugName: "chartLayout" }] : /* istanbul ignore next */ []));
2366
+ /**
2367
+ * Chart configuration options
2368
+ */
2369
+ this.chartOptions = computed(() => {
2370
+ const chartLayout = this.chartLayout();
2371
+ return chartLayout?.options || null;
2372
+ }, /* @ts-ignore */
2373
+ ...(ngDevMode ? [{ debugName: "chartOptions" }] : /* istanbul ignore next */ []));
2374
+ /**
2375
+ * Chart type for rendering
2376
+ */
2377
+ this.chartType = computed(() => {
2378
+ const options = this.chartOptions();
2379
+ return options?.chartType || 'bar';
2380
+ }, /* @ts-ignore */
2381
+ ...(ngDevMode ? [{ debugName: "chartType" }] : /* istanbul ignore next */ []));
2382
+ /**
2383
+ * Data mapping configuration
2384
+ */
2385
+ this.dataMapping = computed(() => {
2386
+ const options = this.chartOptions();
2387
+ return options?.dataMapping || null;
2388
+ }, /* @ts-ignore */
2389
+ ...(ngDevMode ? [{ debugName: "dataMapping" }] : /* istanbul ignore next */ []));
2390
+ //#endregion
2391
+ //#region ---- Effects ----
2392
+ /**
2393
+ * Effect to fetch chart data when conditions are met
2394
+ */
2395
+ this.#fetchData = effect(() => {
2396
+ if (this.vm.allowFetchData() && this.vm.shouldExecuteReport() && this.chartOptions()) {
2397
+ void untracked(() => this.vm.fetchData()).then((result) => {
2398
+ if (!result) {
2399
+ return;
2400
+ }
2401
+ const series = queryResultToChartSeries(result, this.dataMapping());
2402
+ const seriesColors = this.chartOptions()?.styling?.seriesColors;
2403
+ switch (this.chartType()) {
2404
+ case 'bar': {
2405
+ // Mapper returns flat bars for report series (not clustered).
2406
+ const bars = AXBarChartReportMapper(series);
2407
+ this.barChartData.set(applySeriesColors(bars, seriesColors));
2408
+ break;
2409
+ }
2410
+ case 'doughnut': {
2411
+ this.donutChartData.set(applySeriesColors(AXPieChartReportMapper(series), seriesColors));
2412
+ break;
2413
+ }
2414
+ case 'line':
2415
+ default: {
2416
+ this.lineChartData.set(AXLineChartReportMapper(series));
2417
+ break;
2418
+ }
2419
+ }
2420
+ });
2421
+ }
2422
+ }, /* @ts-ignore */
2423
+ ...(ngDevMode ? [{ debugName: "#fetchData" }] : /* istanbul ignore next */ []));
2424
+ this.legendOptions = signal({
2425
+ mode: 'vertical',
2426
+ showValues: true,
2427
+ showPercentage: true,
2428
+ interactive: true,
2429
+ }, /* @ts-ignore */
2430
+ ...(ngDevMode ? [{ debugName: "legendOptions" }] : /* istanbul ignore next */ []));
2431
+ this.barChartData = signal([], /* @ts-ignore */
2432
+ ...(ngDevMode ? [{ debugName: "barChartData" }] : /* istanbul ignore next */ []));
2433
+ this.barChartOptions = signal({}, /* @ts-ignore */
2434
+ ...(ngDevMode ? [{ debugName: "barChartOptions" }] : /* istanbul ignore next */ []));
2435
+ this.donutChartData = signal([], /* @ts-ignore */
2436
+ ...(ngDevMode ? [{ debugName: "donutChartData" }] : /* istanbul ignore next */ []));
2437
+ this.donutChartOptions = signal({
2438
+ totalLabel: 'Total',
2439
+ showTooltip: true,
2440
+ showDataLabels: false,
2441
+ donutWidth: 28,
2442
+ cornerRadius: 4,
2443
+ animationDuration: 800,
2444
+ animationEasing: 'cubic-out',
2445
+ }, /* @ts-ignore */
2446
+ ...(ngDevMode ? [{ debugName: "donutChartOptions" }] : /* istanbul ignore next */ []));
2447
+ this.lineChartData = signal([], /* @ts-ignore */
2448
+ ...(ngDevMode ? [{ debugName: "lineChartData" }] : /* istanbul ignore next */ []));
2449
+ this.lineChartOptions = signal({}, /* @ts-ignore */
2450
+ ...(ngDevMode ? [{ debugName: "lineChartOptions" }] : /* istanbul ignore next */ []));
2451
+ /**
2452
+ * Check if bar chart data has items
2453
+ */
2454
+ this.hasBarChartData = computed(() => {
2455
+ const data = this.barChartData();
2456
+ return Array.isArray(data) && data.length > 0;
2457
+ }, /* @ts-ignore */
2458
+ ...(ngDevMode ? [{ debugName: "hasBarChartData" }] : /* istanbul ignore next */ []));
2459
+ /**
2460
+ * Check if donut chart data has items
2461
+ */
2462
+ this.hasDonutChartData = computed(() => {
2463
+ const data = this.donutChartData();
2464
+ return Array.isArray(data) && data.length > 0;
2465
+ }, /* @ts-ignore */
2466
+ ...(ngDevMode ? [{ debugName: "hasDonutChartData" }] : /* istanbul ignore next */ []));
2467
+ /**
2468
+ * Check if line chart data has items
2469
+ */
2470
+ this.hasLineChartData = computed(() => {
2471
+ const data = this.lineChartData();
2472
+ return Array.isArray(data) && data.length > 0;
2473
+ }, /* @ts-ignore */
2474
+ ...(ngDevMode ? [{ debugName: "hasLineChartData" }] : /* istanbul ignore next */ []));
2475
+ /**
2476
+ * Check if chart has any data (for any chart type)
2477
+ */
2478
+ this.hasChartData = computed(() => {
2479
+ return this.hasBarChartData() || this.hasDonutChartData() || this.hasLineChartData();
2480
+ }, /* @ts-ignore */
2481
+ ...(ngDevMode ? [{ debugName: "hasChartData" }] : /* istanbul ignore next */ []));
2482
+ /**
2483
+ * Check if report has been executed and has no data
2484
+ */
2485
+ this.showNoDataState = computed(() => {
2486
+ return this.vm.hasBeenExecuted() && !this.hasChartData();
2487
+ }, /* @ts-ignore */
2488
+ ...(ngDevMode ? [{ debugName: "showNoDataState" }] : /* istanbul ignore next */ []));
2489
+ }
2490
+ //#endregion
2491
+ //#region ---- Effects ----
2492
+ /**
2493
+ * Effect to fetch chart data when conditions are met
2494
+ */
2495
+ #fetchData;
2496
+ //#endregion
2497
+ //#region ---- Helper Methods ----
2498
+ /**
2499
+ * Check if chart is ready to render
2500
+ */
2501
+ isChartReady() {
2502
+ return !!(this.chartOptions() && this.dataMapping());
2503
+ }
2504
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportChartViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2505
+ 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: i1.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: i1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2506
+ }
2507
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportChartViewComponent, decorators: [{
2508
+ type: Component,
2509
+ args: [{ selector: 'axp-report-chart-view', imports: [
2510
+ CommonModule,
2511
+ AXTranslationModule,
2512
+ AXPWidgetCoreModule,
2513
+ AXBarChartComponent,
2514
+ AXDonutChartComponent,
2515
+ AXLineChartComponent,
2516
+ AXChartLegendComponent,
2517
+ AXPStateMessageComponent,
2518
+ ], host: {
2519
+ class: 'axp-report-chart-view w-full h-full flex flex-col',
2520
+ }, 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"] }]
2521
+ }] });
2522
+ //#region ----- Series color helpers -----
2523
+ function applySeriesColors(items, seriesColors) {
2524
+ if (!seriesColors || Object.keys(seriesColors).length === 0) {
2525
+ return items;
2526
+ }
2527
+ return items.map((item) => {
2528
+ const color = seriesColors[item.id] ?? (item.label ? seriesColors[item.label] : undefined);
2529
+ return color ? { ...item, color } : item;
2530
+ });
2531
+ }
2532
+
2533
+ class AXPReportPdfViewComponent {
2534
+ constructor() {
2535
+ //#region ---- Dependencies ----
2536
+ this.vm = inject(AXPReportViewerViewModel);
2537
+ this.AXPPageStatus = AXPPageStatus;
2538
+ this.sanitizer = inject(DomSanitizer);
2539
+ //#endregion
2540
+ //#region ---- Computed Properties ----
2541
+ /**
2542
+ * Current PDF layout configuration
2543
+ */
2544
+ this.pdfLayout = computed(() => {
2545
+ const layout = this.vm.currentLayout();
2546
+ return layout?.layout.type === 'pdf' ? layout.layout : null;
2547
+ }, /* @ts-ignore */
2548
+ ...(ngDevMode ? [{ debugName: "pdfLayout" }] : /* istanbul ignore next */ []));
2549
+ /**
2550
+ * PDF layout options
2551
+ */
2552
+ this.pdfOptions = computed(() => {
2553
+ const pdfLayout = this.pdfLayout();
2554
+ return pdfLayout?.options || {};
2555
+ }, /* @ts-ignore */
2556
+ ...(ngDevMode ? [{ debugName: "pdfOptions" }] : /* istanbul ignore next */ []));
2557
+ /**
2558
+ * PDF source URL (sanitized for embed)
2559
+ */
2560
+ this.pdfSource = computed(() => {
2561
+ const pdfData = this.pdfData();
2562
+ if (!pdfData) {
2563
+ return null;
2564
+ }
2565
+ // Handle URL
2566
+ if (pdfData.url) {
2567
+ return this.sanitizer.bypassSecurityTrustResourceUrl(pdfData.url);
2568
+ }
2569
+ // Handle base64
2570
+ if (pdfData.base64) {
2571
+ const base64Data = pdfData.base64.startsWith('data:')
2572
+ ? pdfData.base64
2573
+ : `data:application/pdf;base64,${pdfData.base64}`;
2574
+ return this.sanitizer.bypassSecurityTrustResourceUrl(base64Data);
2575
+ }
2576
+ // Handle blob
2577
+ if (pdfData.blob) {
2578
+ const blobUrl = URL.createObjectURL(pdfData.blob);
2579
+ return this.sanitizer.bypassSecurityTrustResourceUrl(blobUrl);
2580
+ }
2581
+ return null;
2582
+ }, /* @ts-ignore */
2583
+ ...(ngDevMode ? [{ debugName: "pdfSource" }] : /* istanbul ignore next */ []));
2584
+ /**
2585
+ * PDF filename for download
2586
+ */
2587
+ this.pdfFilename = computed(() => {
2588
+ const pdfData = this.pdfData();
2589
+ return pdfData?.filename || 'report.pdf';
2590
+ }, /* @ts-ignore */
2591
+ ...(ngDevMode ? [{ debugName: "pdfFilename" }] : /* istanbul ignore next */ []));
2592
+ /**
2593
+ * PDF embed height
2594
+ */
2595
+ this.embedHeight = computed(() => {
2596
+ const options = this.pdfOptions();
2597
+ return options.height || '100%';
2598
+ }, /* @ts-ignore */
2599
+ ...(ngDevMode ? [{ debugName: "embedHeight" }] : /* istanbul ignore next */ []));
2600
+ /**
2601
+ * Check if PDF is ready to render
2602
+ */
2603
+ this.isPdfReady = computed(() => {
2604
+ return !!this.pdfSource();
2605
+ }, /* @ts-ignore */
2606
+ ...(ngDevMode ? [{ debugName: "isPdfReady" }] : /* istanbul ignore next */ []));
2607
+ /**
2608
+ * Check if download button should be shown
2609
+ */
2610
+ this.showDownloadButton = computed(() => {
2611
+ const options = this.pdfOptions();
2612
+ return options.showDownloadButton !== false; // Default to true
2613
+ }, /* @ts-ignore */
2614
+ ...(ngDevMode ? [{ debugName: "showDownloadButton" }] : /* istanbul ignore next */ []));
2615
+ /**
2616
+ * Check if print button should be shown
2617
+ */
2618
+ this.showPrintButton = computed(() => {
2619
+ const options = this.pdfOptions();
2620
+ return options.showPrintButton !== false; // Default to true
2621
+ }, /* @ts-ignore */
2622
+ ...(ngDevMode ? [{ debugName: "showPrintButton" }] : /* istanbul ignore next */ []));
2623
+ /**
2624
+ * Check if zoom controls should be shown
2625
+ */
2626
+ this.showZoomControls = computed(() => {
2627
+ const options = this.pdfOptions();
2628
+ return options.showZoomControls !== false; // Default to true
2629
+ }, /* @ts-ignore */
2630
+ ...(ngDevMode ? [{ debugName: "showZoomControls" }] : /* istanbul ignore next */ []));
2631
+ /**
2632
+ * Check if fullscreen button should be shown
2633
+ */
2634
+ this.showFullscreenButton = computed(() => {
2635
+ const options = this.pdfOptions();
2636
+ return options.showFullscreenButton !== false; // Default to true
2637
+ }, /* @ts-ignore */
2638
+ ...(ngDevMode ? [{ debugName: "showFullscreenButton" }] : /* istanbul ignore next */ []));
2639
+ /**
2640
+ * Current zoom level
2641
+ */
2642
+ this.currentZoom = signal(100, /* @ts-ignore */
2643
+ ...(ngDevMode ? [{ debugName: "currentZoom" }] : /* istanbul ignore next */ []));
2644
+ /**
2645
+ * PDF data from execution result
2646
+ */
2647
+ this.pdfData = signal(null, /* @ts-ignore */
2648
+ ...(ngDevMode ? [{ debugName: "pdfData" }] : /* istanbul ignore next */ []));
2649
+ /**
2650
+ * Fullscreen state
2651
+ */
2652
+ this.isFullscreen = signal(false, /* @ts-ignore */
2653
+ ...(ngDevMode ? [{ debugName: "isFullscreen" }] : /* istanbul ignore next */ []));
2654
+ //#endregion
2655
+ //#region ---- Effects ----
2656
+ /**
2657
+ * Effect to fetch PDF data when conditions are met
2658
+ */
2659
+ this.#fetchData = effect(() => {
2660
+ if (this.vm.allowFetchData() && this.vm.shouldExecuteReport() && this.pdfLayout()) {
2661
+ void untracked(() => this.vm.fetchData()).then((result) => {
2662
+ if (!result) {
2663
+ return;
2664
+ }
2665
+ const pdfResult = queryResultToPdfSource(result);
2666
+ this.pdfData.set(pdfResult);
2667
+ if (pdfResult) {
2668
+ const options = this.pdfOptions();
2669
+ if (options.defaultZoom) {
2670
+ this.currentZoom.set(options.defaultZoom);
2671
+ }
2672
+ }
2673
+ });
2674
+ }
2675
+ }, /* @ts-ignore */
2676
+ ...(ngDevMode ? [{ debugName: "#fetchData" }] : /* istanbul ignore next */ []));
2677
+ }
2678
+ //#endregion
2679
+ //#region ---- Effects ----
2680
+ /**
2681
+ * Effect to fetch PDF data when conditions are met
2682
+ */
2683
+ #fetchData;
2684
+ //#endregion
2685
+ //#region ---- Methods ----
2686
+ /**
2687
+ * Handle download PDF
2688
+ */
2689
+ downloadPdf() {
2690
+ const pdfData = this.pdfData();
2691
+ if (!pdfData) {
2692
+ return;
2693
+ }
2694
+ let url;
2695
+ let filename = this.pdfFilename();
2696
+ let shouldRevokeUrl = false;
2697
+ if (pdfData.url) {
2698
+ // For URL, create a temporary link
2699
+ url = pdfData.url;
2700
+ }
2701
+ else if (pdfData.base64) {
2702
+ // Convert base64 to blob and create URL
2703
+ const base64Data = pdfData.base64.startsWith('data:')
2704
+ ? pdfData.base64.split(',')[1]
2705
+ : pdfData.base64;
2706
+ const byteCharacters = atob(base64Data);
2707
+ const byteNumbers = new Array(byteCharacters.length);
2708
+ for (let i = 0; i < byteCharacters.length; i++) {
2709
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
2710
+ }
2711
+ const byteArray = new Uint8Array(byteNumbers);
2712
+ const blob = new Blob([byteArray], { type: 'application/pdf' });
2713
+ url = URL.createObjectURL(blob);
2714
+ shouldRevokeUrl = true;
2715
+ }
2716
+ else if (pdfData.blob) {
2717
+ url = URL.createObjectURL(pdfData.blob);
2718
+ shouldRevokeUrl = true;
2719
+ }
2720
+ else {
2721
+ return;
2722
+ }
2723
+ // Create download link
2724
+ const link = document.createElement('a');
2725
+ link.href = url;
2726
+ link.download = filename;
2727
+ document.body.appendChild(link);
2728
+ link.click();
2729
+ document.body.removeChild(link);
2730
+ // Clean up blob URL if created
2731
+ if (shouldRevokeUrl) {
2732
+ setTimeout(() => URL.revokeObjectURL(url), 100);
2733
+ }
2734
+ }
2735
+ /**
2736
+ * Handle print PDF
2737
+ */
2738
+ printPdf() {
2739
+ const pdfData = this.pdfData();
2740
+ if (!pdfData) {
2741
+ return;
2742
+ }
2743
+ let url;
2744
+ if (pdfData.url) {
2745
+ url = pdfData.url;
2746
+ }
2747
+ else if (pdfData.base64) {
2748
+ const base64Data = pdfData.base64.startsWith('data:')
2749
+ ? pdfData.base64
2750
+ : `data:application/pdf;base64,${pdfData.base64}`;
2751
+ url = base64Data;
2752
+ }
2753
+ else if (pdfData.blob) {
2754
+ url = URL.createObjectURL(pdfData.blob);
2755
+ }
2756
+ else {
2757
+ return;
2758
+ }
2759
+ // Open PDF in new window for printing
2760
+ const printWindow = window.open(url, '_blank');
2761
+ if (printWindow) {
2762
+ printWindow.onload = () => {
2763
+ printWindow.print();
2764
+ };
2765
+ }
2766
+ }
2767
+ /**
2768
+ * Handle zoom in
2769
+ */
2770
+ zoomIn() {
2771
+ const current = this.currentZoom();
2772
+ if (current < 500) {
2773
+ this.currentZoom.set(Math.min(current + 25, 500));
2774
+ }
2775
+ }
2776
+ /**
2777
+ * Handle zoom out
2778
+ */
2779
+ zoomOut() {
2780
+ const current = this.currentZoom();
2781
+ if (current > 25) {
2782
+ this.currentZoom.set(Math.max(current - 25, 25));
2783
+ }
2784
+ }
2785
+ /**
2786
+ * Handle zoom reset
2787
+ */
2788
+ zoomReset() {
2789
+ const options = this.pdfOptions();
2790
+ this.currentZoom.set(options.defaultZoom || 100);
2791
+ }
2792
+ /**
2793
+ * Handle fullscreen toggle
2794
+ */
2795
+ toggleFullscreen() {
2796
+ const isFullscreen = this.isFullscreen();
2797
+ this.isFullscreen.set(!isFullscreen);
2798
+ }
2799
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportPdfViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2800
+ 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: i1.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 }); }
2801
+ }
2802
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportPdfViewComponent, decorators: [{
2803
+ type: Component,
2804
+ args: [{ selector: 'axp-report-pdf-view', imports: [
2805
+ CommonModule,
2806
+ AXButtonModule,
2807
+ AXDecoratorModule,
2808
+ AXTranslationModule,
2809
+ AXPWidgetCoreModule,
2810
+ AXPStateMessageComponent,
2811
+ ], host: {
2812
+ class: 'axp-report-pdf-view w-full h-full flex flex-col',
2813
+ }, 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"] }]
2814
+ }] });
2815
+
2816
+ class AXPReportTableViewComponent {
2817
+ constructor() {
2818
+ //#region ---- Dependencies ----
2819
+ this.vm = inject(AXPReportViewerViewModel);
2820
+ this.commandService = inject(AXPCommandService);
2821
+ this.expressionEvaluator = inject(AXPExpressionEvaluatorService);
2822
+ this.grid = viewChild('grid', /* @ts-ignore */
2823
+ ...(ngDevMode ? [{ debugName: "grid" }] : /* istanbul ignore next */ []));
2824
+ //#endregion
2825
+ //#region ---- State ----
2826
+ this.columns = signal([], /* @ts-ignore */
2827
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
2828
+ this.sortedFields = signal([], /* @ts-ignore */
2829
+ ...(ngDevMode ? [{ debugName: "sortedFields" }] : /* istanbul ignore next */ []));
2830
+ this.tableSummary = signal(null, /* @ts-ignore */
2831
+ ...(ngDevMode ? [{ debugName: "tableSummary" }] : /* istanbul ignore next */ []));
2832
+ this.handledExecutionRequestId = 0;
2833
+ this.lastDataSourcePreferencesKey = null;
2834
+ //#endregion
2835
+ //#region ---- Effects ----
2836
+ this.#resetExecutionHandler = effect(() => {
2837
+ const reportId = this.vm.reportId();
2838
+ untracked(() => {
2839
+ this.handledExecutionRequestId = 0;
2840
+ this.lastDataSourcePreferencesKey = null;
2841
+ void reportId;
2842
+ });
2843
+ }, /* @ts-ignore */
2844
+ ...(ngDevMode ? [{ debugName: "#resetExecutionHandler" }] : /* istanbul ignore next */ []));
2845
+ this.#syncTableColumns = effect(() => {
2846
+ const layoutDef = this.vm.currentLayout();
2847
+ const tableLayout = layoutDef?.layout;
2848
+ if (!tableLayout || tableLayout.type !== 'table') {
2849
+ this.columns.set([]);
2850
+ return;
2851
+ }
2852
+ const savedTable = this.vm.tableLayoutUserSettings();
2853
+ const groupBy = tableLayout.groupBy;
2854
+ const columns = tableLayout.columns;
2855
+ const savedColumns = savedTable?.columns;
2856
+ void (async () => {
2857
+ const builtColumns = await buildReportTableColumnsAsync(columns, savedColumns, this.expressionEvaluator);
2858
+ this.columns.set(applyGroupByDrillLinksToColumns(builtColumns, groupBy));
2859
+ })();
2860
+ }, /* @ts-ignore */
2861
+ ...(ngDevMode ? [{ debugName: "#syncTableColumns" }] : /* istanbul ignore next */ []));
2862
+ this.#syncDataSourcePreferences = effect(() => {
2863
+ const layoutDef = this.vm.currentLayout();
2864
+ if (!layoutDef) {
2865
+ return;
2866
+ }
2867
+ const tableLayout = layoutDef.layout;
2868
+ if (!tableLayout || tableLayout.type !== 'table') {
2869
+ return;
2870
+ }
2871
+ const savedTable = this.vm.tableLayoutUserSettings();
2872
+ const queryDataSource = layoutDef.dataSource;
2873
+ const defaultSort = tableLayout.options?.sorting?.length
2874
+ ? tableLayout.options.sorting
2875
+ : (queryDataSource?.sortFields ?? []);
2876
+ const effectiveSort = savedTable?.sorts?.length ? savedTable.sorts : defaultSort;
2877
+ const pageSize = savedTable?.pageSize ?? 10;
2878
+ const preferencesKey = `${layoutDef.id}:${pageSize}:${JSON.stringify(effectiveSort)}`;
2879
+ if (this.lastDataSourcePreferencesKey === preferencesKey) {
2880
+ return;
2881
+ }
2882
+ untracked(() => {
2883
+ this.lastDataSourcePreferencesKey = preferencesKey;
2884
+ this.sortedFields.set(effectiveSort);
2885
+ this.applySortToDataSource(effectiveSort);
2886
+ this.dataSource.setPageSize(pageSize);
2887
+ });
2888
+ }, /* @ts-ignore */
2889
+ ...(ngDevMode ? [{ debugName: "#syncDataSourcePreferences" }] : /* istanbul ignore next */ []));
2890
+ /**
2891
+ * Refresh grid once per execute-report request.
2892
+ */
2893
+ this.#fetchData = effect(() => {
2894
+ const requestId = this.vm.executionRequestId();
2895
+ const grid = this.grid();
2896
+ if (!grid || requestId <= this.handledExecutionRequestId || !this.vm.allowFetchData()) {
2897
+ return;
2898
+ }
2899
+ untracked(() => {
2900
+ this.handledExecutionRequestId = requestId;
2901
+ grid.refresh();
2902
+ });
2903
+ }, /* @ts-ignore */
2904
+ ...(ngDevMode ? [{ debugName: "#fetchData" }] : /* istanbul ignore next */ []));
2905
+ //#endregion
2906
+ //#region ---- Computed Properties ----
2907
+ this.layout = computed(() => {
2908
+ return this.vm.currentLayout()?.layout;
2909
+ }, /* @ts-ignore */
2910
+ ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
2911
+ this.dataSource = new AXDataSource({
2912
+ load: async (e) => {
2913
+ if (!this.vm.allowFetchData()) {
2914
+ this.tableSummary.set(null);
2915
+ return { items: [], total: 0 };
2916
+ }
2917
+ const result = await this.vm.fetchData(e);
2918
+ const page = runtimeQueryCollectionData(result);
2919
+ if (!page) {
2920
+ this.tableSummary.set(null);
2921
+ return { items: [], total: 0 };
2922
+ }
2923
+ this.tableSummary.set(page.aggregateRows?.[0] ?? null);
2924
+ return {
2925
+ items: (page.items ?? []),
2926
+ total: Number(page.total ?? page.items?.length ?? 0),
2927
+ };
2928
+ },
2929
+ pageSize: 10,
2930
+ key: 'id',
2931
+ });
2932
+ this.visibleColumns = computed(() => this.columns().filter((c) => c.visible), /* @ts-ignore */
2933
+ ...(ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []));
2934
+ this.paginationEnabled = computed(() => {
2935
+ return this.layout().options?.pagination !== false;
2936
+ }, /* @ts-ignore */
2937
+ ...(ngDevMode ? [{ debugName: "paginationEnabled" }] : /* istanbul ignore next */ []));
2938
+ this.hasReportFooterSummary = computed(() => {
2939
+ return ((this.layout().summary?.some((s) => s.displayIn === 'reportFooter') ?? false) &&
2940
+ this.tableSummary() !== null);
2941
+ }, /* @ts-ignore */
2942
+ ...(ngDevMode ? [{ debugName: "hasReportFooterSummary" }] : /* istanbul ignore next */ []));
2943
+ this.rowActions = computed(() => {
2944
+ const tableLayout = this.layout();
2945
+ const generated = buildDrillRowActionsFromGroupBy(tableLayout.groupBy, this.vm.reportDefinition()?.id);
2946
+ const manual = tableLayout.actions ?? [];
2947
+ return [...generated, ...manual];
2948
+ }, /* @ts-ignore */
2949
+ ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
2950
+ this.primaryRowAction = computed(() => {
2951
+ return (this.rowActions().filter((a) => a.priority === 'primary')?.map((a) => this.convertToRowCommandItem(a)) ??
2952
+ []);
2953
+ }, /* @ts-ignore */
2954
+ ...(ngDevMode ? [{ debugName: "primaryRowAction" }] : /* istanbul ignore next */ []));
2955
+ this.hasPrimaryRowAction = computed(() => {
2956
+ return this.primaryRowAction().length > 0;
2957
+ }, /* @ts-ignore */
2958
+ ...(ngDevMode ? [{ debugName: "hasPrimaryRowAction" }] : /* istanbul ignore next */ []));
2959
+ this.secondaryRowAction = computed(() => {
2960
+ return (this.rowActions()
2961
+ .filter((a) => a.priority === 'secondary')
2962
+ ?.map((a) => this.convertToRowCommandItem(a)) ?? []);
2963
+ }, /* @ts-ignore */
2964
+ ...(ngDevMode ? [{ debugName: "secondaryRowAction" }] : /* istanbul ignore next */ []));
2965
+ this.hasSecondaryRowAction = computed(() => {
2966
+ return this.secondaryRowAction().length > 0;
2967
+ }, /* @ts-ignore */
2968
+ ...(ngDevMode ? [{ debugName: "hasSecondaryRowAction" }] : /* istanbul ignore next */ []));
2969
+ this.defaultGroupByDrillDefinition = computed(() => {
2970
+ const groupBy = this.layout().groupBy;
2971
+ if (!hasGroupByDrillLinks(groupBy)) {
2972
+ return null;
2973
+ }
2974
+ return groupBy?.find((definition) => definition.default) ?? groupBy?.[0] ?? null;
2975
+ }, /* @ts-ignore */
2976
+ ...(ngDevMode ? [{ debugName: "defaultGroupByDrillDefinition" }] : /* istanbul ignore next */ []));
2977
+ this.defaultRowAction = computed(() => {
2978
+ const defaultAction = this.rowActions().find((a) => a.default);
2979
+ if (!defaultAction) {
2980
+ return null;
2981
+ }
2982
+ return this.convertToRowCommandItem(defaultAction);
2983
+ }, /* @ts-ignore */
2984
+ ...(ngDevMode ? [{ debugName: "defaultRowAction" }] : /* istanbul ignore next */ []));
2985
+ }
2986
+ //#endregion
2987
+ //#region ---- Effects ----
2988
+ #resetExecutionHandler;
2989
+ #syncTableColumns;
2990
+ #syncDataSourcePreferences;
2991
+ /**
2992
+ * Refresh grid once per execute-report request.
2993
+ */
2994
+ #fetchData;
2995
+ //#endregion
2996
+ //#region ---- Event Handlers ----
2997
+ handleRowClick(event) {
2998
+ void this.handleRowAction(event.name, event.data);
2999
+ }
3000
+ handleDbRowClick(event) {
3001
+ const defaultGroupByDrill = this.defaultGroupByDrillDefinition();
3002
+ if (defaultGroupByDrill?.displayField) {
3003
+ void this.handleGroupByDrill(defaultGroupByDrill.displayField, event.data);
3004
+ return;
3005
+ }
3006
+ if (this.defaultRowAction()) {
3007
+ void this.handleRowAction(this.defaultRowAction().name, event.data);
3008
+ }
3009
+ }
3010
+ async handleGroupByDrill(displayField, data) {
3011
+ const definition = findGroupByDefinitionForDisplayField(this.layout().groupBy, displayField);
3012
+ if (!definition) {
3013
+ return;
3014
+ }
3015
+ const reportId = this.vm.reportDefinition()?.id;
3016
+ if (!reportId) {
3017
+ return;
3018
+ }
3019
+ const commandOptions = buildGroupByDrillCommandOptions(definition, reportId);
3020
+ const evaluatedOptions = await this.evaluateActionOptions(commandOptions, data);
3021
+ await this.commandService.execute('ReportManagement.Report:OpenInPopup', {
3022
+ data: data,
3023
+ options: evaluatedOptions,
3024
+ });
3025
+ }
3026
+ async handleRowAction(name, data) {
3027
+ const action = this.rowActions().find((item) => item.command?.name === name);
3028
+ const commandName = action?.command?.name ?? name;
3029
+ const evaluatedOptions = await this.evaluateActionOptions(action?.command?.options, data);
3030
+ await this.commandService.execute(commandName, {
3031
+ data: data,
3032
+ options: evaluatedOptions,
3033
+ });
3034
+ }
3035
+ async evaluateActionOptions(options, data) {
3036
+ if (!options) {
3037
+ return {};
3038
+ }
3039
+ const scope = {
3040
+ context: {
3041
+ eval: (path) => get(data, path),
3042
+ },
3043
+ };
3044
+ return (await this.expressionEvaluator.evaluate(options, scope));
3045
+ }
3046
+ handleColumnSort(columnField) {
3047
+ const column = this.layout().columns?.find((c) => c.field === columnField);
3048
+ if (!column?.sortable) {
3049
+ return;
3050
+ }
3051
+ const activeSorts = [...this.sortedFields()];
3052
+ const index = activeSorts.findIndex((s) => s.name === columnField);
3053
+ const currentDir = index >= 0 ? activeSorts[index].dir : undefined;
3054
+ const newDir = currentDir === 'asc' ? 'desc' : currentDir === 'desc' ? undefined : 'asc';
3055
+ const nextSorts = newDir ? [{ name: columnField, dir: newDir }] : [];
3056
+ this.sortedFields.set(nextSorts);
3057
+ this.applySortToDataSource(nextSorts);
3058
+ void this.vm.saveTableSorts(nextSorts);
3059
+ this.grid()?.refresh();
3060
+ }
3061
+ onColumnSizeChanged(event) {
3062
+ if (!event.isUserInteraction || event.type !== 'end' || !event.data) {
3063
+ return;
3064
+ }
3065
+ const dataField = String(event.data.dataField ?? '');
3066
+ const field = resolveColumnFieldFromDataField(dataField);
3067
+ const width = parseColumnResizeWidth(event.data.width);
3068
+ if (!field || width == null) {
3069
+ return;
3070
+ }
3071
+ const nextColumns = this.columns().map((col) => col.field === field ? { ...col, width } : col);
3072
+ this.columns.set(nextColumns);
3073
+ void this.vm.saveTableColumnWidth(field, width, nextColumns);
3074
+ }
3075
+ onColumnsOrderChanged(event) {
3076
+ if (!event.isUserInteraction || !event.data?.event) {
3077
+ return;
3078
+ }
3079
+ const dragEvent = event.data.event;
3080
+ const offset = this.leadingColumnCount();
3081
+ const previousIndex = dragEvent.previousIndex - offset;
3082
+ const currentIndex = dragEvent.currentIndex - offset;
3083
+ if (previousIndex < 0 || currentIndex < 0) {
3084
+ return;
3085
+ }
3086
+ const nextColumns = [...this.columns()];
3087
+ const source = nextColumns[previousIndex];
3088
+ if (!source || source.pin) {
3089
+ return;
3090
+ }
3091
+ if (nextColumns[currentIndex]?.pin) {
3092
+ return;
3093
+ }
3094
+ moveItemInArray(nextColumns, previousIndex, currentIndex);
3095
+ this.columns.set(nextColumns);
3096
+ void this.vm.saveTableColumnOrder(nextColumns);
3097
+ }
3098
+ onPageChanged(event) {
3099
+ if (event.isUserInteraction) {
3100
+ void this.vm.saveTablePageSize(event.take);
3101
+ }
3102
+ }
3103
+ //#endregion
3104
+ //#region ---- Sort Helpers ----
3105
+ resolveColumnSortDirection(columnField) {
3106
+ const dir = this.sortedFields().find((s) => s.name === columnField)?.dir;
3107
+ return dir === 'asc' || dir === 'desc' ? dir : undefined;
3108
+ }
3109
+ //#endregion
3110
+ //#region ---- Summary Helpers ----
3111
+ getSummaryDisplayValue(field) {
3112
+ const summary = this.tableSummary();
3113
+ if (!summary || !(field in summary)) {
3114
+ return null;
3115
+ }
3116
+ const value = summary[field];
3117
+ if (value == null) {
3118
+ return null;
3119
+ }
3120
+ return String(value);
3121
+ }
3122
+ isFirstVisibleColumn(field) {
3123
+ return this.visibleColumns()[0]?.field === field;
3124
+ }
3125
+ //#endregion
3126
+ //#region ---- Helpers ----
3127
+ leadingColumnCount() {
3128
+ let count = 0;
3129
+ if (this.vm.showRowIndexColumn()) {
3130
+ count++;
3131
+ }
3132
+ if (this.vm.canSelectRows()) {
3133
+ count++;
3134
+ }
3135
+ return count;
3136
+ }
3137
+ applySortToDataSource(sorts) {
3138
+ const sortOptions = sorts.map((s) => ({ field: s.name, dir: s.dir }));
3139
+ this.dataSource.sort(...sortOptions);
3140
+ }
3141
+ convertToRowCommandItem(action) {
3142
+ const systemAction = getSystemActions(action.type);
3143
+ if (!systemAction) {
3144
+ return {
3145
+ icon: action.icon ?? 'fa-light fa-eye',
3146
+ iconOnly: action.iconOnly ?? true,
3147
+ text: action.title,
3148
+ name: action.command?.name,
3149
+ divided: action.break,
3150
+ color: action.color,
3151
+ };
3152
+ }
3153
+ const defaultAction = applySystemActionDefault(action, systemAction.key);
3154
+ return {
3155
+ icon: defaultAction.icon ?? 'fa-light fa-eye',
3156
+ iconOnly: action.iconOnly ?? true,
3157
+ text: defaultAction.title,
3158
+ name: action.command?.name,
3159
+ divided: defaultAction.break ?? action.break,
3160
+ color: defaultAction.color,
3161
+ };
3162
+ }
3163
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportTableViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3164
+ 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$1.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$1.AXRowIndexColumnComponent, selector: "ax-index-column", inputs: ["width", "caption", "fixed", "footerTemplate", "padZero"] }, { kind: "component", type: i1$1.AXRowSelectColumnComponent, selector: "ax-select-column", inputs: ["width", "caption", "fixed"] }, { kind: "component", type: i1$1.AXRowCommandColumnComponent, selector: "ax-command-column", inputs: ["width", "caption", "fixed", "footerTemplate", "items"], outputs: ["onItemClick"] }, { kind: "component", type: i1$1.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", "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: i1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
3165
+ }
3166
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportTableViewComponent, decorators: [{
3167
+ type: Component,
3168
+ args: [{ selector: 'axp-report-table-view', imports: [
3169
+ CommonModule,
3170
+ AXDataTableModule,
3171
+ AXLoadingModule,
3172
+ AXDecoratorModule,
3173
+ AXTranslationModule,
3174
+ AXPWidgetCoreModule,
3175
+ AXPTextCommandColumnComponent,
3176
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
3177
+ class: 'axp-report-table-view flex flex-col flex-1 min-h-0 h-full w-full',
3178
+ }, 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" }]
3179
+ }], propDecorators: { grid: [{ type: i0.ViewChild, args: ['grid', { isSignal: true }] }] } });
3180
+
3181
+ //#region ----- Imports -----
3182
+ //#endregion
3183
+ //#region ----- Constants -----
3184
+ const STYLE_PREFIX = /^(fa-light|fa-solid|fa-regular|fa-thin|fa-duotone)\s+/;
3185
+ const REPORT_LAYOUT_TITLE_KEYS = {
3186
+ table: '@report-management:report-viewer.layouts.table.title',
3187
+ bar: '@report-management:report-viewer.layouts.bar-chart.title',
3188
+ doughnut: '@report-management:report-viewer.layouts.doughnut-chart.title',
3189
+ pie: '@report-management:report-viewer.layouts.pie-chart.title',
3190
+ line: '@report-management:report-viewer.layouts.line-chart.title',
3191
+ pdf: '@report-management:report-viewer.layouts.pdf.title',
3192
+ chart: '@report-management:report-viewer.layouts.chart.title',
3193
+ };
3194
+ const DEFAULT_LAYOUT_GLYPHS = {
3195
+ table: 'fa-table',
3196
+ pdf: 'fa-file-pdf',
3197
+ chart: 'fa-chart-mixed',
3198
+ };
3199
+ //#endregion
3200
+ //#region ----- Layout switcher helpers -----
3201
+ function reportLayoutTitleKey(option) {
3202
+ if (option.type === 'table') {
3203
+ return REPORT_LAYOUT_TITLE_KEYS.table;
3204
+ }
3205
+ if (option.type === 'pdf') {
3206
+ return REPORT_LAYOUT_TITLE_KEYS.pdf;
3207
+ }
3208
+ const glyph = stripIconStylePrefix(option.icon);
3209
+ if (glyph.includes('fa-chart-bar')) {
3210
+ return REPORT_LAYOUT_TITLE_KEYS.bar;
3211
+ }
3212
+ if (glyph.includes('fa-chart-pie')) {
3213
+ return REPORT_LAYOUT_TITLE_KEYS.doughnut;
3214
+ }
3215
+ if (glyph.includes('fa-chart-line')) {
3216
+ return REPORT_LAYOUT_TITLE_KEYS.line;
3217
+ }
3218
+ return REPORT_LAYOUT_TITLE_KEYS.chart;
3219
+ }
3220
+ function reportLayoutGlyph(icon, option) {
3221
+ const fromIcon = stripIconStylePrefix(icon);
3222
+ if (fromIcon) {
3223
+ return fromIcon;
3224
+ }
3225
+ if (option.type === 'table') {
3226
+ return DEFAULT_LAYOUT_GLYPHS['table'];
3227
+ }
3228
+ if (option.type === 'pdf') {
3229
+ return DEFAULT_LAYOUT_GLYPHS['pdf'];
3230
+ }
3231
+ return DEFAULT_LAYOUT_GLYPHS['chart'];
3232
+ }
3233
+ function reportLayoutPageIcon(icon, option) {
3234
+ return `fa-light ${reportLayoutGlyph(icon, option)}`;
3235
+ }
3236
+ function isDistinctLayoutTitle(layoutTitle, reportTitle, locale) {
3237
+ const resolvedLayout = resolveMultiLanguageString(layoutTitle, locale).trim();
3238
+ if (!resolvedLayout) {
3239
+ return false;
3240
+ }
3241
+ const resolvedReport = resolveMultiLanguageString(reportTitle, locale).trim();
3242
+ return resolvedLayout !== resolvedReport;
3243
+ }
3244
+ function stripIconStylePrefix(icon) {
3245
+ if (!icon?.trim()) {
3246
+ return '';
3247
+ }
3248
+ return icon.trim().replace(STYLE_PREFIX, '').trim();
3249
+ }
3250
+ //#endregion
3251
+
3252
+ class AXPReportViewerPageComponent extends AXPPageLayoutBaseComponent {
3253
+ constructor() {
3254
+ super();
3255
+ //#region ---- Dependencies ----
3256
+ this.layoutService = inject(AXPLayoutThemeService);
3257
+ this.deviceService = inject(AXPDeviceService);
3258
+ this.route = inject(ActivatedRoute);
3259
+ this.unsubscriber = inject(AXUnsubscriber);
3260
+ this.loadingDialog = inject(AXLoadingDialogService);
3261
+ this.destroyRef = inject(DestroyRef);
3262
+ this.reportViewerService = inject(AXPReportViewerService);
3263
+ this.entityService = inject(AXPEntityService);
3264
+ //#endregion
3265
+ //#region ---- Inputs ----
3266
+ this.reportId = input(/* @ts-ignore */
3267
+ ...(ngDevMode ? [undefined, { debugName: "reportId" }] : /* istanbul ignore next */ []));
3268
+ this.reportParameters = input({}, /* @ts-ignore */
3269
+ ...(ngDevMode ? [{ debugName: "reportParameters" }] : /* istanbul ignore next */ []));
3270
+ //#endregion
3271
+ //#region ---- ViewChildren ----
3272
+ this.filtersForm = viewChild('filtersForm', /* @ts-ignore */
3273
+ ...(ngDevMode ? [{ debugName: "filtersForm" }] : /* istanbul ignore next */ []));
3274
+ //#endregion
3275
+ //#region ---- ViewModel ----
3276
+ this.vm = inject(AXPReportViewerViewModel);
3277
+ //#endregion
3278
+ //#region ---- Effects ----
3279
+ this.watchNavigationLoading = effect(() => {
3280
+ const isBusy = this.vm.isLoading() || this.vm.isExecuting();
3281
+ untracked(() => {
3282
+ this.layoutService.setNavigationLoading(isBusy);
3283
+ //
3284
+ if (isBusy) {
3285
+ void this.openContentLoading();
3286
+ }
3287
+ else {
3288
+ this.closeContentLoading();
3289
+ }
3290
+ });
3291
+ }, /* @ts-ignore */
3292
+ ...(ngDevMode ? [{ debugName: "watchNavigationLoading" }] : /* istanbul ignore next */ []));
3293
+ //#endregion
3294
+ this.loadingDialogRef = null;
3295
+ this.isDialogOpening = false;
3296
+ this.lastReportRouteKey = null;
3297
+ //#region ---- Computed Properties ----
3298
+ this.showFiltersPanel = () => this.deviceService.isLarge();
3299
+ this.showReportDisplay = () => !this.vm.isBackgroundReportMode() &&
3300
+ !this.vm.isBackgroundQueued() &&
3301
+ (this.deviceService.isLarge() || (this.deviceService.isSmall() && this.vm.hasBeenExecuted()));
3302
+ //#endregion
3303
+ //#region ---- Layout Configurations ----
3304
+ this.defaultLayout = () => ({
3305
+ positions: {
3306
+ sm: { colSpan: 12 },
3307
+ md: { colSpan: 6 },
3308
+ lg: { colSpan: 12 },
3309
+ },
3310
+ });
3311
+ this.compactLayout = () => ({
3312
+ positions: {
3313
+ sm: { colSpan: 12 },
3314
+ md: { colSpan: 4 },
3315
+ lg: { colSpan: 3 },
3316
+ },
3317
+ });
3318
+ this.layoutPageIcon = reportLayoutPageIcon;
3319
+ this.destroyRef.onDestroy(() => this.closeContentLoading());
3320
+ }
3321
+ reportsListPath() {
3322
+ return this.entityService
3323
+ .withEntity(RootConfig.module.name, RootConfig.entities.report.name)
3324
+ .createPath();
3325
+ }
3326
+ //#endregion
3327
+ //#region ---- Lifecycle ----
3328
+ async ngOnInit() {
3329
+ await super.ngOnInit();
3330
+ // Reload when reportId or runId changes. Ignore other query params (live filters).
3331
+ this.unsubscriber
3332
+ .takeUntilDestroy(combineLatest([this.route.paramMap, this.route.queryParamMap]))
3333
+ .subscribe(async ([params, queryParams]) => {
3334
+ const reportId = params.get('reportId') || queryParams.get('reportId') || this.reportId();
3335
+ if (!reportId) {
3336
+ return;
3337
+ }
3338
+ const runId = queryParams.get('runId')?.trim() || '';
3339
+ const routeKey = `${reportId}::${runId}`;
3340
+ if (routeKey === this.lastReportRouteKey) {
3341
+ return;
3342
+ }
3343
+ this.lastReportRouteKey = routeKey;
3344
+ await this.loadReportAndRefresh(reportId);
3345
+ });
3346
+ }
3347
+ /**
3348
+ * Effect to handle data fetching when chart configuration changes
3349
+ */
3350
+ //#endregion
3351
+ //#region ---- Helper Methods ----
3352
+ async loadReportAndRefresh(reportId) {
3353
+ const runId = this.route.snapshot.queryParamMap.get('runId')?.trim();
3354
+ if (runId) {
3355
+ await this.vm.loadReport(reportId);
3356
+ await this.vm.loadFrozenRun(runId);
3357
+ this.recompute();
3358
+ return;
3359
+ }
3360
+ await this.vm.loadReport(reportId);
3361
+ // Force refresh of page layout (breadcrumbs, title, etc.) after report loads
3362
+ this.recompute();
3363
+ // Handle initial parameters from query string
3364
+ const queryParams = this.route.snapshot.queryParams;
3365
+ const initialParams = { ...queryParams };
3366
+ delete initialParams['reportId'];
3367
+ delete initialParams['runId'];
3368
+ // Parse filter context values coming from URL (they are strings)
3369
+ const parsedInitialParams = this.parseFilterContextFromUrl(initialParams);
3370
+ // Merge with component input parameters (`today` / `{ from, to }` → filter shape)
3371
+ const rawParams = { ...parsedInitialParams, ...this.reportParameters() };
3372
+ const allParams = Object.fromEntries(Object.entries(rawParams).map(([key, value]) => [key, resolveReportFilterDefaultValue(value)]));
3373
+ if (Object.keys(allParams).length > 0) {
3374
+ console.log('Applying initial parameters:', allParams);
3375
+ this.vm.updateFilterContext(allParams);
3376
+ // Auto-execute if parameters are provided
3377
+ setTimeout(async () => {
3378
+ await this.vm.executeReport();
3379
+ }, 500); // Small delay to ensure form is ready
3380
+ }
3381
+ }
3382
+ async openContentLoading() {
3383
+ if (this.loadingDialogRef || this.isDialogOpening) {
3384
+ return;
3385
+ }
3386
+ this.isDialogOpening = true;
3387
+ try {
3388
+ const [title, description, cancelText] = await Promise.all([
3389
+ this.translateService.translateAsync('@report-management:report-viewer.messages.loading.in-progress-title'),
3390
+ this.translateService.translateAsync('@report-management:report-viewer.messages.loading.in-progress-description'),
3391
+ this.translateService.translateAsync('@report-management:report-viewer.messages.loading.cancel-button'),
3392
+ ]);
3393
+ this.loadingDialogRef = this.loadingDialog.show({
3394
+ title,
3395
+ mode: 'indeterminate',
3396
+ progressColor: 'primary',
3397
+ text: description,
3398
+ buttons: [
3399
+ {
3400
+ text: cancelText,
3401
+ color: 'danger',
3402
+ onClick: () => {
3403
+ this.closeContentLoading();
3404
+ this.vm.cancel();
3405
+ },
3406
+ },
3407
+ ],
3408
+ });
3409
+ }
3410
+ finally {
3411
+ this.isDialogOpening = false;
3412
+ }
3413
+ }
3414
+ closeContentLoading() {
3415
+ if (!this.loadingDialogRef) {
3416
+ return;
3417
+ }
3418
+ this.loadingDialogRef.close();
3419
+ this.loadingDialogRef = null;
3420
+ }
3421
+ /**
3422
+ * Synchronizes filter context to URL query parameters using dot notation
3423
+ * @param filterContext The current filter context to sync
3424
+ */
3425
+ syncFiltersToUrl(filterContext) {
3426
+ // Get current query params
3427
+ const currentParams = this.route.snapshot.queryParams;
3428
+ const reportId = currentParams['reportId'];
3429
+ // Get all filter keys from current params (excluding reportId and internal fields)
3430
+ const currentFilterKeys = new Set();
3431
+ Object.keys(currentParams).forEach((key) => {
3432
+ if (key !== 'reportId' &&
3433
+ key !== 'runId' &&
3434
+ !key.endsWith('EditorObj') &&
3435
+ !key.startsWith('__') &&
3436
+ !key.endsWith('EditorValue') &&
3437
+ !key.includes('.displayText')) {
3438
+ // Extract root key (before first dot) for filter identification
3439
+ const rootKey = key.includes('.') ? key.split('.')[0] : key;
3440
+ currentFilterKeys.add(rootKey);
3441
+ }
3442
+ });
3443
+ // Build new query params - start with current params and remove filter-related ones
3444
+ const queryParams = { ...currentParams };
3445
+ // Remove all filter-related params first (we'll add them back if they have values)
3446
+ Object.keys(queryParams).forEach((key) => {
3447
+ if (key !== 'reportId' &&
3448
+ key !== 'runId' &&
3449
+ !key.endsWith('EditorObj') &&
3450
+ !key.startsWith('__') &&
3451
+ !key.endsWith('EditorValue') &&
3452
+ !key.includes('.displayText')) {
3453
+ delete queryParams[key];
3454
+ }
3455
+ });
3456
+ const processedFilterKeys = new Set();
3457
+ // Add filter params that have values
3458
+ Object.keys(filterContext).forEach((key) => {
3459
+ // Skip internal editor fields (EditorObj, __EditorValue)
3460
+ if (key.endsWith('EditorObj') || key.startsWith('__') || key.endsWith('EditorValue')) {
3461
+ return;
3462
+ }
3463
+ processedFilterKeys.add(key);
3464
+ const value = filterContext[key];
3465
+ // Check if value is empty (null, undefined, empty string, or empty array)
3466
+ const isEmpty = this.isFilterValueEmpty(value);
3467
+ if (!isEmpty) {
3468
+ const flattened = this.flattenToDotNotation(key, value);
3469
+ Object.assign(queryParams, flattened);
3470
+ }
3471
+ // If empty, we don't add it to queryParams, so it will be removed from URL
3472
+ });
3473
+ // Remove filter keys that are no longer in filterContext
3474
+ currentFilterKeys.forEach((filterKey) => {
3475
+ if (!processedFilterKeys.has(filterKey)) {
3476
+ // This filter was removed - ensure all related params are removed
3477
+ Object.keys(currentParams).forEach((paramKey) => {
3478
+ if (paramKey === filterKey || paramKey.startsWith(`${filterKey}.`)) {
3479
+ delete queryParams[paramKey];
3480
+ }
3481
+ });
3482
+ }
3483
+ });
3484
+ // Preserve reportId / runId if exists
3485
+ if (reportId) {
3486
+ queryParams['reportId'] = String(reportId);
3487
+ }
3488
+ else {
3489
+ delete queryParams['reportId'];
3490
+ }
3491
+ const runId = currentParams['runId'];
3492
+ if (runId) {
3493
+ queryParams['runId'] = String(runId);
3494
+ }
3495
+ else {
3496
+ delete queryParams['runId'];
3497
+ }
3498
+ // Update URL without reloading
3499
+ this.router.navigate([], {
3500
+ relativeTo: this.route,
3501
+ queryParams: queryParams,
3502
+ replaceUrl: true, // Don't add to browser history
3503
+ });
3504
+ }
3505
+ /**
3506
+ * Checks if a filter value is considered empty
3507
+ */
3508
+ isFilterValueEmpty(value) {
3509
+ if (value === null || value === undefined || value === '') {
3510
+ return true;
3511
+ }
3512
+ // Check if it's a filter object with empty value
3513
+ if (typeof value === 'object' && !Array.isArray(value)) {
3514
+ const filterObj = value;
3515
+ const filterValue = filterObj.value;
3516
+ if (filterValue === null || filterValue === undefined || filterValue === '') {
3517
+ return true;
3518
+ }
3519
+ if (Array.isArray(filterValue) && filterValue.length === 0) {
3520
+ return true;
3521
+ }
3522
+ // Check nested object values
3523
+ if (typeof filterValue === 'object' && !Array.isArray(filterValue)) {
3524
+ const hasAnyValue = Object.values(filterValue).some((v) => v !== null && v !== undefined && v !== '');
3525
+ return !hasAnyValue;
3526
+ }
3527
+ }
3528
+ // Check if it's an empty array
3529
+ if (Array.isArray(value) && value.length === 0) {
3530
+ return true;
3531
+ }
3532
+ return false;
3533
+ }
3534
+ /**
3535
+ * Flattens filter context to dot notation query parameters
3536
+ * Only includes operation.type and value - skips displayText and other fields
3537
+ * Example: { operation: { type: 'in' }, value: ['POST', 'GET'] }
3538
+ * -> { 'fieldName.operation.type': 'in', 'fieldName.value': 'POST,GET' }
3539
+ */
3540
+ flattenToDotNotation(prefix, value) {
3541
+ const result = {};
3542
+ if (value == null) {
3543
+ return result;
3544
+ }
3545
+ // Handle filter context object - only extract operation.type and value
3546
+ if (typeof value === 'object' && !Array.isArray(value)) {
3547
+ const filterObj = value;
3548
+ // Extract operation.type if exists
3549
+ if (filterObj.operation?.type) {
3550
+ result[`${prefix}.operation.type`] = String(filterObj.operation.type);
3551
+ }
3552
+ // Extract value - handle arrays and primitives
3553
+ if (filterObj.value !== undefined && filterObj.value !== null && filterObj.value !== '') {
3554
+ if (Array.isArray(filterObj.value)) {
3555
+ if (filterObj.value.length > 0) {
3556
+ result[`${prefix}.value`] = filterObj.value.map((v) => String(v)).join(',');
3557
+ }
3558
+ }
3559
+ else if (typeof filterObj.value === 'object') {
3560
+ // If value is an object, flatten it recursively
3561
+ const flattenedValue = this.flattenValueObject(filterObj.value);
3562
+ Object.assign(result, flattenedValue);
3563
+ }
3564
+ else {
3565
+ result[`${prefix}.value`] = String(filterObj.value);
3566
+ }
3567
+ }
3568
+ return result;
3569
+ }
3570
+ // Handle primitives directly
3571
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
3572
+ result[prefix] = String(value);
3573
+ return result;
3574
+ }
3575
+ // Handle arrays - join with comma
3576
+ if (Array.isArray(value)) {
3577
+ if (value.length > 0) {
3578
+ result[prefix] = value.map(v => String(v)).join(',');
3579
+ }
3580
+ return result;
3581
+ }
3582
+ // Fallback: convert to string
3583
+ result[prefix] = String(value);
3584
+ return result;
3585
+ }
3586
+ /**
3587
+ * Flattens a value object (for nested objects in value field)
3588
+ */
3589
+ flattenValueObject(obj, prefix = '') {
3590
+ const result = {};
3591
+ if (obj == null || typeof obj !== 'object') {
3592
+ return result;
3593
+ }
3594
+ for (const [key, val] of Object.entries(obj)) {
3595
+ const newPrefix = prefix ? `${prefix}.${key}` : key;
3596
+ if (val == null) {
3597
+ continue;
3598
+ }
3599
+ if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
3600
+ result[newPrefix] = String(val);
3601
+ }
3602
+ else if (Array.isArray(val)) {
3603
+ if (val.length > 0) {
3604
+ result[newPrefix] = val.map((v) => String(v)).join(',');
3605
+ }
3606
+ }
3607
+ else if (typeof val === 'object') {
3608
+ const flattened = this.flattenValueObject(val, newPrefix);
3609
+ Object.assign(result, flattened);
3610
+ }
3611
+ }
3612
+ return result;
3613
+ }
3614
+ /**
3615
+ * Reconstructs filter context from dot notation query parameters
3616
+ * Only parses operation.type and value - ignores other fields
3617
+ * Example: { 'fieldName.operation.type': 'in', 'fieldName.value': 'POST,GET' }
3618
+ * -> { fieldName: { operation: { type: 'in' }, value: ['POST', 'GET'] } }
3619
+ */
3620
+ parseFilterContextFromUrl(params) {
3621
+ const result = {};
3622
+ // Filter out reportId and internal editor fields
3623
+ const filteredParams = {};
3624
+ for (const [key, value] of Object.entries(params)) {
3625
+ // Skip reportId and internal editor fields (EditorObj, __EditorValue, displayText)
3626
+ if (key !== 'reportId' &&
3627
+ key !== 'runId' &&
3628
+ !key.endsWith('EditorObj') &&
3629
+ !key.startsWith('__') &&
3630
+ !key.endsWith('EditorValue') &&
3631
+ !key.includes('.displayText')) {
3632
+ filteredParams[key] = value;
3633
+ }
3634
+ }
3635
+ // Group by root key (before first dot)
3636
+ for (const [key, value] of Object.entries(filteredParams)) {
3637
+ if (!key.includes('.')) {
3638
+ // Simple key without dot notation - skip (we only want operation.type and value)
3639
+ continue;
3640
+ }
3641
+ // Split by dot and reconstruct nested structure
3642
+ const parts = key.split('.');
3643
+ const rootKey = parts[0];
3644
+ const path = parts.slice(1);
3645
+ // Only process operation.type and value paths
3646
+ if (path.length === 2 && path[0] === 'operation' && path[1] === 'type') {
3647
+ // operation.type
3648
+ if (!result[rootKey]) {
3649
+ result[rootKey] = { operation: {} };
3650
+ }
3651
+ if (!result[rootKey].operation) {
3652
+ result[rootKey].operation = {};
3653
+ }
3654
+ result[rootKey].operation.type = String(value);
3655
+ }
3656
+ else if (path.length === 1 && path[0] === 'value') {
3657
+ // value
3658
+ if (!result[rootKey]) {
3659
+ result[rootKey] = {};
3660
+ }
3661
+ result[rootKey].value = this.parseValue(String(value));
3662
+ }
3663
+ else if (path.length > 1 && path[0] === 'value') {
3664
+ // nested value object (e.g., value.from, value.to for between)
3665
+ if (!result[rootKey]) {
3666
+ result[rootKey] = {};
3667
+ }
3668
+ if (!result[rootKey].value) {
3669
+ result[rootKey].value = {};
3670
+ }
3671
+ const valuePath = path.slice(1);
3672
+ this.setNestedValue(result[rootKey].value, valuePath, this.parseValue(String(value)));
3673
+ }
3674
+ }
3675
+ // Post-process: Convert values to arrays if operation.type is 'in' or 'notIn'
3676
+ // Also reconstruct EditorObj for lookup widgets (from value only, no displayText)
3677
+ for (const [key, value] of Object.entries(result)) {
3678
+ if (value && typeof value === 'object' && value.operation?.type) {
3679
+ const operationType = value.operation.type;
3680
+ // If operation is 'in' or 'notIn', ensure value is an array
3681
+ if ((operationType === 'in' || operationType === 'notIn') && typeof value.value === 'string') {
3682
+ // Convert comma-separated string to array
3683
+ value.value = value.value.split(',').map((v) => v.trim()).filter((v) => v !== '');
3684
+ }
3685
+ // Reconstruct EditorObj for lookup widgets from value only
3686
+ // This is needed because lookup widget expects EditorObj to exist
3687
+ if (value.value !== undefined && value.value !== null && value.value !== '') {
3688
+ const editorObjKey = `${key}EditorObj`;
3689
+ const valueField = 'id'; // Default value field for lookup
3690
+ const textField = 'title'; // Default text field for lookup
3691
+ if (operationType === 'in' || operationType === 'notIn') {
3692
+ // Multiple selection: create array of objects with value as both id and title
3693
+ const values = Array.isArray(value.value) ? value.value : [value.value];
3694
+ result[editorObjKey] = values.map((val) => ({
3695
+ [valueField]: val,
3696
+ [textField]: val // Use value as title since we don't have displayText
3697
+ }));
3698
+ }
3699
+ else {
3700
+ // Single selection: create single object
3701
+ result[editorObjKey] = {
3702
+ [valueField]: value.value,
3703
+ [textField]: value.value // Use value as title since we don't have displayText
3704
+ };
3705
+ }
3706
+ }
3707
+ }
3708
+ }
3709
+ return result;
3710
+ }
3711
+ /**
3712
+ * Sets a value in a nested object using a path array
3713
+ */
3714
+ setNestedValue(obj, path, value) {
3715
+ let current = obj;
3716
+ for (let i = 0; i < path.length - 1; i++) {
3717
+ const key = path[i];
3718
+ if (!current[key] || typeof current[key] !== 'object') {
3719
+ current[key] = {};
3720
+ }
3721
+ current = current[key];
3722
+ }
3723
+ const lastKey = path[path.length - 1];
3724
+ current[lastKey] = value;
3725
+ }
3726
+ /**
3727
+ * Parses a string value, handling arrays (comma-separated) and primitives
3728
+ */
3729
+ parseValue(value) {
3730
+ if (value === '') {
3731
+ return value;
3732
+ }
3733
+ // Check if it's a comma-separated array
3734
+ if (value.includes(',')) {
3735
+ return value.split(',').map(v => this.parsePrimitive(v.trim()));
3736
+ }
3737
+ return this.parsePrimitive(value);
3738
+ }
3739
+ /**
3740
+ * Parses a primitive value
3741
+ * For query params, we keep everything as string to match user requirement
3742
+ * Arrays are handled separately in parseValue
3743
+ */
3744
+ parsePrimitive(value) {
3745
+ return value;
3746
+ }
3747
+ //#endregion
3748
+ //#region ---- Event Handlers ----
3749
+ handleFiltersChange(event) {
3750
+ this.vm.updateFilterContext(event);
3751
+ this.syncFiltersToUrl(event);
3752
+ }
3753
+ handleFiltersInitiated(event) {
3754
+ this.vm.updateFilterContext(event);
3755
+ }
3756
+ async handleExecuteReport() {
3757
+ if (this.vm.isFrozen()) {
3758
+ return;
3759
+ }
3760
+ const isValid = await this.filtersForm()?.validate();
3761
+ if (!isValid?.result) {
3762
+ return;
3763
+ }
3764
+ await this.vm.executeReport();
3765
+ }
3766
+ async handleGenerateInBackground() {
3767
+ if (this.vm.isFrozen()) {
3768
+ return;
3769
+ }
3770
+ const isValid = await this.filtersForm()?.validate();
3771
+ if (!isValid?.result) {
3772
+ return;
3773
+ }
3774
+ await this.vm.generateInBackground();
3775
+ this.recompute();
3776
+ }
3777
+ canGenerateInBackground() {
3778
+ return (this.sessionService.isFeatureEnabled(AXPReportManagementFeatureKeys.BackgroundReports) &&
3779
+ !this.vm.isFrozen() &&
3780
+ (this.vm.generationMode() === 'choose' || this.vm.generationMode() === 'background'));
3781
+ }
3782
+ handleRefresh() {
3783
+ this.vm.refresh();
3784
+ }
3785
+ handleClearFilters() {
3786
+ this.vm.clearFilters();
3787
+ // Reset form if available
3788
+ const form = this.filtersForm();
3789
+ if (form) {
3790
+ form.reset();
3791
+ }
3792
+ // Clear URL query parameters (preserve reportId if exists)
3793
+ const currentParams = this.route.snapshot.queryParams;
3794
+ const reportId = currentParams['reportId'];
3795
+ const runId = currentParams['runId'];
3796
+ this.router.navigate([], {
3797
+ relativeTo: this.route,
3798
+ queryParams: {
3799
+ ...(reportId ? { reportId } : {}),
3800
+ ...(runId ? { runId } : {}),
3801
+ },
3802
+ replaceUrl: true,
3803
+ });
3804
+ }
3805
+ /**
3806
+ * Handle download report action
3807
+ * Gets PDF data from viewmodel and calls service executeAction method
3808
+ */
3809
+ async handleDownloadReport() {
3810
+ if (!this.vm.isPdfLayout()) {
3811
+ const warning = await this.translateService.translateAsync('@report-management:report-viewer.messages.warnings.download-pdf-only');
3812
+ console.warn(warning);
3813
+ return;
3814
+ }
3815
+ // Fetch PDF data if not already available
3816
+ const pdfData = queryResultToPdfSource(await this.vm.fetchData());
3817
+ if (pdfData) {
3818
+ await this.reportViewerService.executeAction('download-report', { pdfData });
3819
+ }
3820
+ else {
3821
+ const warning = await this.translateService.translateAsync('@report-management:report-viewer.messages.warnings.no-pdf-download');
3822
+ console.warn(warning);
3823
+ }
3824
+ }
3825
+ /**
3826
+ * Handle print report action
3827
+ * Gets PDF data from viewmodel and calls service executeAction method
3828
+ */
3829
+ async handlePrintReport() {
3830
+ if (!this.vm.isPdfLayout()) {
3831
+ const warning = await this.translateService.translateAsync('@report-management:report-viewer.messages.warnings.print-pdf-only');
3832
+ console.warn(warning);
3833
+ return;
3834
+ }
3835
+ // Fetch PDF data if not already available
3836
+ const pdfData = queryResultToPdfSource(await this.vm.fetchData());
3837
+ if (pdfData) {
3838
+ await this.reportViewerService.executeAction('print-report', { pdfData });
3839
+ }
3840
+ else {
3841
+ const warning = await this.translateService.translateAsync('@report-management:report-viewer.messages.warnings.no-pdf-print');
3842
+ console.warn(warning);
3843
+ }
3844
+ }
3845
+ /**
3846
+ * Handle export report action
3847
+ * Gets report data from viewmodel and calls service executeAction method
3848
+ * @param format - Export format (excel, csv, pdf, image)
3849
+ */
3850
+ async handleExportReport(format) {
3851
+ if (!format) {
3852
+ const warning = await this.translateService.translateAsync('@report-management:report-viewer.messages.warnings.export-format-required');
3853
+ console.warn(warning);
3854
+ return;
3855
+ }
3856
+ // Fetch report data
3857
+ const result = await this.vm.fetchData();
3858
+ const reportData = runtimeQueryPayloadData(result);
3859
+ if (reportData == null) {
3860
+ const warning = await this.translateService.translateAsync('@report-management:report-viewer.messages.warnings.no-report-data-export');
3861
+ console.warn(warning);
3862
+ return;
3863
+ }
3864
+ const layout = this.vm.currentLayout();
3865
+ await this.reportViewerService.executeAction('export-report', {
3866
+ format,
3867
+ reportData,
3868
+ layoutDefinition: layout || undefined,
3869
+ });
3870
+ }
3871
+ getLayoutSelectionLabel(layout) {
3872
+ const locale = this.translateService.getActiveLang() || 'en-US';
3873
+ const reportTitle = this.vm.reportDefinition()?.title;
3874
+ if (isDistinctLayoutTitle(layout.title, reportTitle, locale)) {
3875
+ return layout.title ?? reportLayoutTitleKey(layout);
3876
+ }
3877
+ return reportLayoutTitleKey(layout);
3878
+ }
3879
+ handleBackgroundLayoutToggle(layoutId, selected) {
3880
+ this.vm.setBackgroundLayoutSelected(layoutId, selected);
3881
+ }
3882
+ //#endregion
3883
+ //#region ---- Page Configuration Overrides ----
3884
+ async getPageBreadcrumbs() {
3885
+ const breadcrumbs = [
3886
+ {
3887
+ name: 'reports',
3888
+ title: await this.translateService.translateAsync('@report-management:module.menus.root.title'),
3889
+ command: {
3890
+ name: 'navigate',
3891
+ options: {
3892
+ path: this.reportsListPath(),
3893
+ },
3894
+ },
3895
+ },
3896
+ ];
3897
+ const reportDefinition = this.vm.reportDefinition();
3898
+ if (reportDefinition) {
3899
+ breadcrumbs.push({
3900
+ name: 'report-viewer',
3901
+ title: reportDefinition.title,
3902
+ });
3903
+ }
3904
+ return breadcrumbs;
3905
+ }
3906
+ async getPageTitle() {
3907
+ const reportDefinition = this.vm.reportDefinition();
3908
+ if (reportDefinition) {
3909
+ return reportDefinition.title;
3910
+ }
3911
+ return await this.translateService.translateAsync('@report-management:report-viewer.components.report-viewer.title');
3912
+ }
3913
+ async getPageDescription() {
3914
+ const reportDefinition = this.vm.reportDefinition();
3915
+ return reportDefinition?.description || null;
3916
+ }
3917
+ getPageIcon() {
3918
+ if (this.vm.isBackgroundReportMode() ||
3919
+ this.vm.isBackgroundQueued() ||
3920
+ !this.vm.hasMultipleLayouts() ||
3921
+ !this.showReportDisplay()) {
3922
+ return null;
3923
+ }
3924
+ const currentLayout = this.vm.currentLayout();
3925
+ if (!currentLayout) {
3926
+ return null;
3927
+ }
3928
+ return reportLayoutPageIcon(currentLayout.icon, {
3929
+ type: currentLayout.layout.type,
3930
+ });
3931
+ }
3932
+ async getTitleActions() {
3933
+ if (this.vm.isBackgroundReportMode() ||
3934
+ this.vm.isBackgroundQueued() ||
3935
+ !this.vm.hasMultipleLayouts() ||
3936
+ !this.showReportDisplay()) {
3937
+ return [];
3938
+ }
3939
+ const currentLayoutId = this.vm.currentLayout()?.id;
3940
+ const reportTitle = this.vm.reportDefinition()?.title;
3941
+ const locale = this.translateService.getActiveLang() || 'en-US';
3942
+ const items = [];
3943
+ for (const layout of this.vm.availableLayouts()) {
3944
+ items.push({
3945
+ title: await this.resolveLayoutSwitcherTitle(layout, reportTitle, locale),
3946
+ icon: reportLayoutGlyph(layout.icon, layout),
3947
+ command: {
3948
+ name: 'switch-layout',
3949
+ metadata: { isLayout: true, layoutId: layout.id, isSelected: layout.id === currentLayoutId },
3950
+ },
3951
+ });
3952
+ }
3953
+ return items.sort((a, b) => resolveMultiLanguageString(a.title, locale).localeCompare(resolveMultiLanguageString(b.title, locale), undefined, { sensitivity: 'base' }));
3954
+ }
3955
+ async resolveLayoutSwitcherTitle(layout, reportTitle, locale) {
3956
+ if (isDistinctLayoutTitle(layout.title, reportTitle, locale)) {
3957
+ if (typeof layout.title === 'string' && layout.title.startsWith('@')) {
3958
+ return await this.translateService.translateAsync(layout.title);
3959
+ }
3960
+ if (typeof layout.title === 'object') {
3961
+ return this.translateService.resolve(layout.title) || layout.title;
3962
+ }
3963
+ return layout.title;
3964
+ }
3965
+ return await this.translateService.translateAsync(reportLayoutTitleKey(layout));
3966
+ }
3967
+ async getBackButton() {
3968
+ // On mobile, if report has been executed (showing report), show "Filters" as back button
3969
+ if (this.deviceService.isSmall() && this.vm.hasBeenExecuted()) {
3970
+ return {
3971
+ title: await this.translateService.translateAsync('@general:terms.interface.filter.title'),
3972
+ };
3973
+ }
3974
+ return {
3975
+ title: await this.translateService.translateAsync('@report-management:module.menus.root.title'),
3976
+ };
3977
+ }
3978
+ async onBackButtonClick() {
3979
+ // On mobile, if report has been executed (showing report), go back to filters
3980
+ if (this.deviceService.isSmall() && this.vm.hasBeenExecuted()) {
3981
+ this.vm.clearExecutionState();
3982
+ return;
3983
+ }
3984
+ await this.router.navigateByUrl(this.reportsListPath());
3985
+ }
3986
+ async getPrimaryMenuItems() {
3987
+ if (this.vm.isBackgroundReportMode() && !this.vm.isFrozen()) {
3988
+ return [];
3989
+ }
3990
+ const currentLayout = this.vm.currentLayout();
3991
+ if (!currentLayout) {
3992
+ return [];
3993
+ }
3994
+ // Get primary actions from service (includes general actions + layout-specific actions)
3995
+ const primaryActions = this.reportViewerService.getPrimaryActions(currentLayout);
3996
+ // Convert AXPReportLayoutAction[] to AXPActionMenuItem[] (remove priority property)
3997
+ return primaryActions.map(({ priority, ...action }) => action);
3998
+ }
3999
+ async getSecondaryMenuItems() {
4000
+ if (this.vm.isBackgroundReportMode() && !this.vm.isFrozen()) {
4001
+ return [];
4002
+ }
4003
+ const currentLayout = this.vm.currentLayout();
4004
+ if (!currentLayout) {
4005
+ return [];
4006
+ }
4007
+ const secondaryActions = this.reportViewerService.getSecondaryActions(currentLayout);
4008
+ const items = secondaryActions.map(({ priority, ...action }) => action);
4009
+ if (this.canGenerateInBackground() && this.vm.generationMode() === 'choose') {
4010
+ items.unshift({
4011
+ title: '@report-management:report-generation.actions.generate-in-background',
4012
+ icon: 'fa-light fa-clock',
4013
+ command: { name: 'generate-in-background' },
4014
+ });
4015
+ }
4016
+ return items;
4017
+ }
4018
+ async execute(command) {
4019
+ // Handle layout switching from title actions
4020
+ if (command.metadata?.['isLayout']) {
4021
+ const layoutId = command.metadata?.['layoutId'];
4022
+ if (layoutId) {
4023
+ this.vm.switchLayout(layoutId);
4024
+ this.recompute();
4025
+ }
4026
+ return;
4027
+ }
4028
+ switch (command.name) {
4029
+ case 'execute-report':
4030
+ await this.handleExecuteReport();
4031
+ break;
4032
+ case 'generate-in-background':
4033
+ await this.handleGenerateInBackground();
4034
+ break;
4035
+ case 'refresh-report':
4036
+ this.handleRefresh();
4037
+ break;
4038
+ case 'reset-filters':
4039
+ this.handleClearFilters();
4040
+ break;
4041
+ case 'download-report':
4042
+ this.handleDownloadReport();
4043
+ break;
4044
+ case 'print-report':
4045
+ this.handlePrintReport();
4046
+ break;
4047
+ case 'export-report':
4048
+ await this.handleExportReport(command.options?.['format']);
4049
+ break;
4050
+ default:
4051
+ const warning = await this.translateService.translateAsync('@report-management:report-viewer.messages.warnings.unknown-command', {
4052
+ params: { commandName: command.name },
4053
+ });
4054
+ console.warn(warning);
4055
+ break;
4056
+ }
4057
+ }
4058
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportViewerPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4059
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPReportViewerPageComponent, isStandalone: true, selector: "axp-report-viewer-page", inputs: { reportId: { classPropertyName: "reportId", publicName: "reportId", isSignal: true, isRequired: false, transformFunction: null }, reportParameters: { classPropertyName: "reportParameters", publicName: "reportParameters", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "axp-report-viewer-page" }, providers: [
4060
+ AXUnsubscriber,
4061
+ AXPReportViewerViewModel,
4062
+ {
4063
+ provide: AXPPageLayoutBase,
4064
+ useExisting: AXPReportViewerPageComponent,
4065
+ },
4066
+ ], viewQueries: [{ propertyName: "filtersForm", first: true, predicate: ["filtersForm"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<axp-page-layout *translate=\"let t\">\n <!-- Left Panel: Filters (Always visible for desktop) -->\n @if (showFiltersPanel()) {\n <axp-layout-start-side>\n <axp-layout-header>\n <axp-layout-title>\n {{ t('@report-management:report-viewer.components.filters.title') | async }}\n </axp-layout-title>\n </axp-layout-header>\n <axp-layout-content>\n @if (vm.reportDefinition()) {\n <axp-layout-renderer #filtersForm [layout]=\"vm.formDefinition()\" [context]=\"vm.filterContext()\" [look]=\"'card'\"\n (contextChange)=\"handleFiltersChange($event)\" (contextInitiated)=\"handleFiltersInitiated($event)\">\n </axp-layout-renderer>\n <!-- ----- Background report layout multi-select ----- -->\n @if (vm.isBackgroundReportMode() && !vm.isBackgroundQueued() && vm.availableLayouts().length > 0) {\n <div class=\"mt-4 pt-4 border-t\">\n <div class=\"mb-2\">\n <span class=\"text-sm font-medium\">\n {{ t('@report-management:report-generation.components.background-layouts.title') | async }}\n </span>\n </div>\n <p class=\"text-xs opacity-70 mb-3\">\n {{ t('@report-management:report-generation.components.background-layouts.description') | async }}\n </p>\n <div class=\"flex flex-col gap-2\">\n @for (layout of vm.availableLayouts(); track layout.id) {\n <label class=\"flex items-center gap-2 p-2 rounded border cursor-pointer hover:bg-neutral-50\">\n <ax-check-box\n [ngModel]=\"vm.isBackgroundLayoutSelected(layout.id)\"\n (ngModelChange)=\"handleBackgroundLayoutToggle(layout.id, $event)\"\n ></ax-check-box>\n <ax-icon [icon]=\"layoutPageIcon(layout.icon, layout)\"></ax-icon>\n <span class=\"text-sm\">{{ getLayoutSelectionLabel(layout) | translate | async }}</span>\n </label>\n }\n </div>\n </div>\n }\n }\n </axp-layout-content>\n <axp-layout-footer class=\"p-4 border-t\" *translate=\"let t\">\n @if (vm.hasUnappliedFilters()) {\n <div class=\"w-full warning-lightest-surface mb-2 p-2 rounded border border-warning-200\">\n <div class=\"flex items-center gap-2 text-warning-800\">\n <ax-icon class=\"fa-light fa-exclamation-triangle text-sm\"></ax-icon>\n <span class=\"text-xs\">{{\n t('@report-management:report-viewer.components.filters.messages.warning.unapplied') | async\n }}</span>\n </div>\n </div>\n }\n @if (vm.isBackgroundReportMode() && !vm.hasSelectedBackgroundLayouts() && !vm.isBackgroundQueued()) {\n <div class=\"w-full warning-lightest-surface mb-2 p-2 rounded border border-warning-200\">\n <div class=\"flex items-center gap-2 text-warning-800\">\n <ax-icon class=\"fa-light fa-exclamation-triangle text-sm\"></ax-icon>\n <span class=\"text-xs\">{{\n t('@report-management:report-generation.errors.no-layouts-selected') | async\n }}</span>\n </div>\n </div>\n }\n <ax-button class=\"w-full\" [disabled]=\"!vm.canExecute() || vm.areParametersReadonly() || vm.isBackgroundQueued()\"\n [text]=\"(vm.isBackgroundReportMode()\n ? t('@report-management:report-generation.actions.generate-in-background')\n : t('@report-management:report-viewer.actions.execute.title')) | async\" [color]=\"'primary'\"\n (onClick)=\"execute({ name: 'execute-report' })\">\n <ax-prefix>\n <ax-icon [icon]=\"vm.isBackgroundReportMode() ? 'fa-light fa-clock' : 'fa-light fa-play'\"></ax-icon>\n </ax-prefix>\n </ax-button>\n <!-- ----- Secondary background generate for choose mode ----- -->\n @if (canGenerateInBackground() && vm.generationMode() === 'choose') {\n <ax-button class=\"w-full mt-2\" [disabled]=\"vm.areParametersReadonly()\"\n [text]=\"t('@report-management:report-generation.actions.generate-in-background') | async\" [color]=\"'default'\"\n (onClick)=\"execute({ name: 'generate-in-background' })\">\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-clock\"></ax-icon>\n </ax-prefix>\n </ax-button>\n }\n </axp-layout-footer>\n </axp-layout-start-side>\n }\n <!-- Main Content: Report Display -->\n <axp-page-content class=\"overflow-auto\">\n @if (vm.isFrozen()) {\n <!-- ----- Frozen run banner ----- -->\n <div class=\"warning-lightest-surface mb-3 p-3 rounded border border-warning-200 text-warning-800 text-sm\">\n {{ t('@report-management:report-generation.banners.frozen') | async }}\n </div>\n }\n @if (vm.hasError()) {\n <!-- Error State -->\n <axp-state-message 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.unexpected'\">\n <div slot=\"actions\">\n <ax-button look=\"outline\" color=\"primary\"\n [text]=\"t('@report-management:report-viewer.actions.retry.title') | async\" (onClick)=\"vm.clearError()\">\n <ax-prefix>\n <ax-icon class=\"fa-light fa-refresh\"></ax-icon>\n </ax-prefix>\n </ax-button>\n </div>\n </axp-state-message>\n } @else if (!vm.reportDefinition()) {\n <!-- No Report Selected State -->\n <axp-state-message icon=\"fa-light fa-chart-line\"\n [title]=\"'@report-management:report-viewer.components.report-viewer.empty-states.no-report.title'\"\n [description]=\"'@report-management:report-viewer.components.report-viewer.empty-states.no-report.description'\">\n <div slot=\"actions\">\n <ax-button look=\"outline\" color=\"primary\"\n [text]=\"t('@report-management:report-viewer.actions.browse-reports.title') | async\">\n <ax-prefix>\n <ax-icon class=\"fa-light fa-folder-open\"></ax-icon>\n </ax-prefix>\n </ax-button>\n </div>\n </axp-state-message>\n <!-- ----- Background report message state (no live layout preview) ----- -->\n } @else if (vm.isBackgroundReportMode() || vm.isBackgroundQueued()) {\n <!-- ----- Mobile filters and layout selection for background reports ----- -->\n @if (deviceService.isSmall() && !vm.isBackgroundQueued()) {\n <axp-layout-renderer #filtersForm [layout]=\"vm.formDefinition()\" [context]=\"vm.filterContext()\" [look]=\"'fieldset'\"\n (contextChange)=\"handleFiltersChange($event)\" (contextInitiated)=\"handleFiltersInitiated($event)\">\n </axp-layout-renderer>\n @if (vm.availableLayouts().length > 0) {\n <div class=\"mt-4 pt-4 border-t\">\n <div class=\"flex items-center justify-between gap-2 mb-2\">\n <span class=\"text-sm font-medium\">\n {{ t('@report-management:report-generation.components.background-layouts.title') | async }}\n </span>\n </div>\n <p class=\"text-xs opacity-70 mb-3\">\n {{ t('@report-management:report-generation.components.background-layouts.description') | async }}\n </p>\n <div class=\"flex flex-col gap-2\">\n @for (layout of vm.availableLayouts(); track layout.id) {\n <label class=\"flex items-center gap-2 p-2 rounded border cursor-pointer\">\n <ax-check-box\n [ngModel]=\"vm.isBackgroundLayoutSelected(layout.id)\"\n (ngModelChange)=\"handleBackgroundLayoutToggle(layout.id, $event)\"\n ></ax-check-box>\n <ax-icon [icon]=\"layoutPageIcon(layout.icon, layout)\"></ax-icon>\n <span class=\"text-sm\">{{ getLayoutSelectionLabel(layout) | translate | async }}</span>\n </label>\n }\n </div>\n </div>\n }\n }\n @if (vm.isBackgroundQueued()) {\n <axp-state-message icon=\"fa-light fa-clock\"\n [title]=\"'@report-management:report-generation.empty-states.background.queued-title'\"\n [description]=\"'@report-management:report-generation.empty-states.background.queued-description'\">\n </axp-state-message>\n } @else {\n <axp-state-message icon=\"fa-light fa-clock\"\n [title]=\"'@report-management:report-generation.empty-states.background.title'\"\n [description]=\"'@report-management:report-generation.empty-states.background.description'\">\n </axp-state-message>\n }\n } @else {\n <ng-container *ngTemplateOutlet=\"reportDisplay\"></ng-container>\n }\n </axp-page-content>\n</axp-page-layout>\n\n<!-- Desktop: Report Display Area -->\n<ng-template #reportDisplay>\n @if (showReportDisplay()) {\n <!-- Desktop & Mobile: Report Display Area -->\n @if (vm.isTableLayout()) {\n <axp-report-table-view> </axp-report-table-view>\n } @else if (vm.isChartLayout()) {\n <axp-report-chart-view> </axp-report-chart-view>\n } @else if (vm.isPdfLayout()) {\n <axp-report-pdf-view> </axp-report-pdf-view>\n }\n } @else if (vm.reportDefinition()) {\n <!-- Mobile: Show filters -->\n <axp-layout-renderer #filtersForm [layout]=\"vm.formDefinition()\" [context]=\"vm.filterContext()\" [look]=\"'fieldset'\">\n </axp-layout-renderer>\n }\n</ng-template>\n\n<!-- Mobile: Execute Button -->\n@if (deviceService.isSmall() && ((!vm.isBackgroundReportMode() && !vm.hasBeenExecuted()) || (vm.isBackgroundReportMode() && !vm.isBackgroundQueued()))) {\n<axp-page-footer class=\"p-2 border-t\">\n <ax-button class=\"w-full\" [disabled]=\"!vm.canExecute() || vm.areParametersReadonly()\"\n [text]=\"(vm.isBackgroundReportMode()\n ? '@report-management:report-generation.actions.generate-in-background'\n : '@report-management:report-viewer.actions.execute.title') | translate | async\" [color]=\"'primary'\"\n (onClick)=\"execute({ name: 'execute-report' })\">\n <ax-prefix>\n <ax-icon [icon]=\"vm.isBackgroundReportMode() ? 'fa-light fa-clock' : 'fa-light fa-play'\"></ax-icon>\n </ax-prefix>\n </ax-button>\n</axp-page-footer>\n}\n", styles: ["@layer properties;.axp-report-viewer-page{display:flex;height:100%;width:100%;flex-direction:column}.axp-report-viewer-page axp-layout-start-side{height:100%;width:calc(var(--spacing, .25rem) * 80);border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}@media(width>=1920px){.axp-report-viewer-page axp-layout-start-side{width:calc(var(--spacing, .25rem) * 96)}}.axp-report-viewer-page axp-layout-start-side>axp-layout-header{height:73px;border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.axp-report-viewer-page axp-layout-start-side axp-layout-content{overflow-x:auto;padding:calc(var(--spacing, .25rem) * 4)}.axp-report-viewer-page .__mobile-filters{display:flex;height:100%;flex-direction:column;overflow-y:auto}.axp-report-viewer-page .__mobile-filters .__mobile-filters-content{flex:1}.axp-report-viewer-page .__mobile-filters .__mobile-filters-footer{position:sticky;bottom:0;border-top-style:var(--tw-border-style);border-top-width:1px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);padding-top:calc(var(--spacing, .25rem) * 2)}.axp-report-viewer-page axp-page-content{display:flex;height:100%;flex-direction:column}.axp-report-viewer-page axp-report-table-view{flex:1;overflow:hidden}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$2.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXCheckBoxModule }, { kind: "component", type: i4.AXCheckBoxComponent, selector: "ax-check-box", inputs: ["disabled", "tabIndex", "readonly", "color", "value", "name", "id", "isLoading", "indeterminate"], outputs: ["onBlur", "onFocus", "valueChange", "onValueChanged"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2$3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2$3.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: i1.AXTranslatorDirective, selector: "[translate]" }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "ngmodule", type: AXLoadingDialogModule }, { kind: "component", type:
4067
+ //
4068
+ AXPLayoutRendererComponent, selector: "axp-layout-renderer", inputs: ["layout", "context", "look", "mode"], outputs: ["contextChange", "contextInitiated", "validityChange"] }, { kind: "component", type: AXPPageLayoutComponent, selector: "axp-page-layout" }, { kind: "component", type: AXPThemeLayoutStartSideComponent, selector: "axp-layout-page-start-side, axp-layout-start-side" }, { kind: "component", type: AXPThemeLayoutHeaderComponent, selector: "axp-layout-header" }, { kind: "component", type: AXPThemeLayoutBlockComponent, selector: " axp-page-content, axp-page-footer-container, axp-page-footer, axp-page-header, axp-page-header-container, axp-page-toolbar, axp-layout-content, axp-layout-page-content, axp-layout-sections, axp-layout-body, axp-layout-page-body, axp-layout-prefix, axp-layout-suffix, axp-layout-title-bar, axp-layout-title, axp-layout-title-actions, axp-layout-nav-button, axp-layout-description, axp-layout-breadcrumbs, axp-layout-list-action, " }, { kind: "component", type: AXPReportTableViewComponent, selector: "axp-report-table-view" }, { kind: "component", type: AXPThemeLayoutFooterComponent, selector: "axp-layout-footer, axp-layout-page-footer" }, { kind: "component", type: AXPReportChartViewComponent, selector: "axp-report-chart-view" }, { kind: "component", type: AXPReportPdfViewComponent, selector: "axp-report-pdf-view" }, { 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: i1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
4069
+ }
4070
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportViewerPageComponent, decorators: [{
4071
+ type: Component,
4072
+ args: [{ selector: 'axp-report-viewer-page', standalone: true, imports: [
4073
+ CommonModule,
4074
+ FormsModule,
4075
+ AXButtonModule,
4076
+ AXCheckBoxModule,
4077
+ AXDecoratorModule,
4078
+ AXLoadingModule,
4079
+ AXTranslationModule,
4080
+ AXBadgeModule,
4081
+ AXLoadingDialogModule,
4082
+ //
4083
+ AXPLayoutRendererComponent,
4084
+ AXPPageLayoutComponent,
4085
+ AXPThemeLayoutStartSideComponent,
4086
+ AXPThemeLayoutHeaderComponent,
4087
+ AXPThemeLayoutBlockComponent,
4088
+ AXPReportTableViewComponent,
4089
+ AXPThemeLayoutFooterComponent,
4090
+ AXPReportChartViewComponent,
4091
+ AXPReportPdfViewComponent,
4092
+ AXPStateMessageComponent,
4093
+ ], providers: [
4094
+ AXUnsubscriber,
4095
+ AXPReportViewerViewModel,
4096
+ {
4097
+ provide: AXPPageLayoutBase,
4098
+ useExisting: AXPReportViewerPageComponent,
4099
+ },
4100
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { class: 'axp-report-viewer-page' }, template: "<axp-page-layout *translate=\"let t\">\n <!-- Left Panel: Filters (Always visible for desktop) -->\n @if (showFiltersPanel()) {\n <axp-layout-start-side>\n <axp-layout-header>\n <axp-layout-title>\n {{ t('@report-management:report-viewer.components.filters.title') | async }}\n </axp-layout-title>\n </axp-layout-header>\n <axp-layout-content>\n @if (vm.reportDefinition()) {\n <axp-layout-renderer #filtersForm [layout]=\"vm.formDefinition()\" [context]=\"vm.filterContext()\" [look]=\"'card'\"\n (contextChange)=\"handleFiltersChange($event)\" (contextInitiated)=\"handleFiltersInitiated($event)\">\n </axp-layout-renderer>\n <!-- ----- Background report layout multi-select ----- -->\n @if (vm.isBackgroundReportMode() && !vm.isBackgroundQueued() && vm.availableLayouts().length > 0) {\n <div class=\"mt-4 pt-4 border-t\">\n <div class=\"mb-2\">\n <span class=\"text-sm font-medium\">\n {{ t('@report-management:report-generation.components.background-layouts.title') | async }}\n </span>\n </div>\n <p class=\"text-xs opacity-70 mb-3\">\n {{ t('@report-management:report-generation.components.background-layouts.description') | async }}\n </p>\n <div class=\"flex flex-col gap-2\">\n @for (layout of vm.availableLayouts(); track layout.id) {\n <label class=\"flex items-center gap-2 p-2 rounded border cursor-pointer hover:bg-neutral-50\">\n <ax-check-box\n [ngModel]=\"vm.isBackgroundLayoutSelected(layout.id)\"\n (ngModelChange)=\"handleBackgroundLayoutToggle(layout.id, $event)\"\n ></ax-check-box>\n <ax-icon [icon]=\"layoutPageIcon(layout.icon, layout)\"></ax-icon>\n <span class=\"text-sm\">{{ getLayoutSelectionLabel(layout) | translate | async }}</span>\n </label>\n }\n </div>\n </div>\n }\n }\n </axp-layout-content>\n <axp-layout-footer class=\"p-4 border-t\" *translate=\"let t\">\n @if (vm.hasUnappliedFilters()) {\n <div class=\"w-full warning-lightest-surface mb-2 p-2 rounded border border-warning-200\">\n <div class=\"flex items-center gap-2 text-warning-800\">\n <ax-icon class=\"fa-light fa-exclamation-triangle text-sm\"></ax-icon>\n <span class=\"text-xs\">{{\n t('@report-management:report-viewer.components.filters.messages.warning.unapplied') | async\n }}</span>\n </div>\n </div>\n }\n @if (vm.isBackgroundReportMode() && !vm.hasSelectedBackgroundLayouts() && !vm.isBackgroundQueued()) {\n <div class=\"w-full warning-lightest-surface mb-2 p-2 rounded border border-warning-200\">\n <div class=\"flex items-center gap-2 text-warning-800\">\n <ax-icon class=\"fa-light fa-exclamation-triangle text-sm\"></ax-icon>\n <span class=\"text-xs\">{{\n t('@report-management:report-generation.errors.no-layouts-selected') | async\n }}</span>\n </div>\n </div>\n }\n <ax-button class=\"w-full\" [disabled]=\"!vm.canExecute() || vm.areParametersReadonly() || vm.isBackgroundQueued()\"\n [text]=\"(vm.isBackgroundReportMode()\n ? t('@report-management:report-generation.actions.generate-in-background')\n : t('@report-management:report-viewer.actions.execute.title')) | async\" [color]=\"'primary'\"\n (onClick)=\"execute({ name: 'execute-report' })\">\n <ax-prefix>\n <ax-icon [icon]=\"vm.isBackgroundReportMode() ? 'fa-light fa-clock' : 'fa-light fa-play'\"></ax-icon>\n </ax-prefix>\n </ax-button>\n <!-- ----- Secondary background generate for choose mode ----- -->\n @if (canGenerateInBackground() && vm.generationMode() === 'choose') {\n <ax-button class=\"w-full mt-2\" [disabled]=\"vm.areParametersReadonly()\"\n [text]=\"t('@report-management:report-generation.actions.generate-in-background') | async\" [color]=\"'default'\"\n (onClick)=\"execute({ name: 'generate-in-background' })\">\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-clock\"></ax-icon>\n </ax-prefix>\n </ax-button>\n }\n </axp-layout-footer>\n </axp-layout-start-side>\n }\n <!-- Main Content: Report Display -->\n <axp-page-content class=\"overflow-auto\">\n @if (vm.isFrozen()) {\n <!-- ----- Frozen run banner ----- -->\n <div class=\"warning-lightest-surface mb-3 p-3 rounded border border-warning-200 text-warning-800 text-sm\">\n {{ t('@report-management:report-generation.banners.frozen') | async }}\n </div>\n }\n @if (vm.hasError()) {\n <!-- Error State -->\n <axp-state-message 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.unexpected'\">\n <div slot=\"actions\">\n <ax-button look=\"outline\" color=\"primary\"\n [text]=\"t('@report-management:report-viewer.actions.retry.title') | async\" (onClick)=\"vm.clearError()\">\n <ax-prefix>\n <ax-icon class=\"fa-light fa-refresh\"></ax-icon>\n </ax-prefix>\n </ax-button>\n </div>\n </axp-state-message>\n } @else if (!vm.reportDefinition()) {\n <!-- No Report Selected State -->\n <axp-state-message icon=\"fa-light fa-chart-line\"\n [title]=\"'@report-management:report-viewer.components.report-viewer.empty-states.no-report.title'\"\n [description]=\"'@report-management:report-viewer.components.report-viewer.empty-states.no-report.description'\">\n <div slot=\"actions\">\n <ax-button look=\"outline\" color=\"primary\"\n [text]=\"t('@report-management:report-viewer.actions.browse-reports.title') | async\">\n <ax-prefix>\n <ax-icon class=\"fa-light fa-folder-open\"></ax-icon>\n </ax-prefix>\n </ax-button>\n </div>\n </axp-state-message>\n <!-- ----- Background report message state (no live layout preview) ----- -->\n } @else if (vm.isBackgroundReportMode() || vm.isBackgroundQueued()) {\n <!-- ----- Mobile filters and layout selection for background reports ----- -->\n @if (deviceService.isSmall() && !vm.isBackgroundQueued()) {\n <axp-layout-renderer #filtersForm [layout]=\"vm.formDefinition()\" [context]=\"vm.filterContext()\" [look]=\"'fieldset'\"\n (contextChange)=\"handleFiltersChange($event)\" (contextInitiated)=\"handleFiltersInitiated($event)\">\n </axp-layout-renderer>\n @if (vm.availableLayouts().length > 0) {\n <div class=\"mt-4 pt-4 border-t\">\n <div class=\"flex items-center justify-between gap-2 mb-2\">\n <span class=\"text-sm font-medium\">\n {{ t('@report-management:report-generation.components.background-layouts.title') | async }}\n </span>\n </div>\n <p class=\"text-xs opacity-70 mb-3\">\n {{ t('@report-management:report-generation.components.background-layouts.description') | async }}\n </p>\n <div class=\"flex flex-col gap-2\">\n @for (layout of vm.availableLayouts(); track layout.id) {\n <label class=\"flex items-center gap-2 p-2 rounded border cursor-pointer\">\n <ax-check-box\n [ngModel]=\"vm.isBackgroundLayoutSelected(layout.id)\"\n (ngModelChange)=\"handleBackgroundLayoutToggle(layout.id, $event)\"\n ></ax-check-box>\n <ax-icon [icon]=\"layoutPageIcon(layout.icon, layout)\"></ax-icon>\n <span class=\"text-sm\">{{ getLayoutSelectionLabel(layout) | translate | async }}</span>\n </label>\n }\n </div>\n </div>\n }\n }\n @if (vm.isBackgroundQueued()) {\n <axp-state-message icon=\"fa-light fa-clock\"\n [title]=\"'@report-management:report-generation.empty-states.background.queued-title'\"\n [description]=\"'@report-management:report-generation.empty-states.background.queued-description'\">\n </axp-state-message>\n } @else {\n <axp-state-message icon=\"fa-light fa-clock\"\n [title]=\"'@report-management:report-generation.empty-states.background.title'\"\n [description]=\"'@report-management:report-generation.empty-states.background.description'\">\n </axp-state-message>\n }\n } @else {\n <ng-container *ngTemplateOutlet=\"reportDisplay\"></ng-container>\n }\n </axp-page-content>\n</axp-page-layout>\n\n<!-- Desktop: Report Display Area -->\n<ng-template #reportDisplay>\n @if (showReportDisplay()) {\n <!-- Desktop & Mobile: Report Display Area -->\n @if (vm.isTableLayout()) {\n <axp-report-table-view> </axp-report-table-view>\n } @else if (vm.isChartLayout()) {\n <axp-report-chart-view> </axp-report-chart-view>\n } @else if (vm.isPdfLayout()) {\n <axp-report-pdf-view> </axp-report-pdf-view>\n }\n } @else if (vm.reportDefinition()) {\n <!-- Mobile: Show filters -->\n <axp-layout-renderer #filtersForm [layout]=\"vm.formDefinition()\" [context]=\"vm.filterContext()\" [look]=\"'fieldset'\">\n </axp-layout-renderer>\n }\n</ng-template>\n\n<!-- Mobile: Execute Button -->\n@if (deviceService.isSmall() && ((!vm.isBackgroundReportMode() && !vm.hasBeenExecuted()) || (vm.isBackgroundReportMode() && !vm.isBackgroundQueued()))) {\n<axp-page-footer class=\"p-2 border-t\">\n <ax-button class=\"w-full\" [disabled]=\"!vm.canExecute() || vm.areParametersReadonly()\"\n [text]=\"(vm.isBackgroundReportMode()\n ? '@report-management:report-generation.actions.generate-in-background'\n : '@report-management:report-viewer.actions.execute.title') | translate | async\" [color]=\"'primary'\"\n (onClick)=\"execute({ name: 'execute-report' })\">\n <ax-prefix>\n <ax-icon [icon]=\"vm.isBackgroundReportMode() ? 'fa-light fa-clock' : 'fa-light fa-play'\"></ax-icon>\n </ax-prefix>\n </ax-button>\n</axp-page-footer>\n}\n", styles: ["@layer properties;.axp-report-viewer-page{display:flex;height:100%;width:100%;flex-direction:column}.axp-report-viewer-page axp-layout-start-side{height:100%;width:calc(var(--spacing, .25rem) * 80);border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}@media(width>=1920px){.axp-report-viewer-page axp-layout-start-side{width:calc(var(--spacing, .25rem) * 96)}}.axp-report-viewer-page axp-layout-start-side>axp-layout-header{height:73px;border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.axp-report-viewer-page axp-layout-start-side axp-layout-content{overflow-x:auto;padding:calc(var(--spacing, .25rem) * 4)}.axp-report-viewer-page .__mobile-filters{display:flex;height:100%;flex-direction:column;overflow-y:auto}.axp-report-viewer-page .__mobile-filters .__mobile-filters-content{flex:1}.axp-report-viewer-page .__mobile-filters .__mobile-filters-footer{position:sticky;bottom:0;border-top-style:var(--tw-border-style);border-top-width:1px;background-color:var(--color-lightest);color:var(--color-on-lightest);border-color:var(--color-border-lightest);padding-top:calc(var(--spacing, .25rem) * 2)}.axp-report-viewer-page axp-page-content{display:flex;height:100%;flex-direction:column}.axp-report-viewer-page axp-report-table-view{flex:1;overflow:hidden}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
4101
+ }], ctorParameters: () => [], propDecorators: { reportId: [{ type: i0.Input, args: [{ isSignal: true, alias: "reportId", required: false }] }], reportParameters: [{ type: i0.Input, args: [{ isSignal: true, alias: "reportParameters", required: false }] }], filtersForm: [{ type: i0.ViewChild, args: ['filtersForm', { isSignal: true }] }] } });
4102
+
4103
+ var reportViewerPage_component = /*#__PURE__*/Object.freeze({
4104
+ __proto__: null,
4105
+ AXPReportViewerPageComponent: AXPReportViewerPageComponent
4106
+ });
4107
+
4108
+ class AXPReportBarChartViewComponent {
4109
+ constructor() {
4110
+ //#region ---- Dependencies ----
4111
+ this.vm = inject(AXPReportViewerViewModel);
4112
+ this.AXPPageStatus = AXPPageStatus;
4113
+ //#endregion
4114
+ //#region ---- Computed Properties ----
4115
+ /**
4116
+ * Current chart layout configuration
4117
+ */
4118
+ this.chartLayout = computed(() => {
4119
+ const layout = this.vm.currentLayout();
4120
+ return layout?.layout.type === 'chart' ? layout.layout : null;
4121
+ }, /* @ts-ignore */
4122
+ ...(ngDevMode ? [{ debugName: "chartLayout" }] : /* istanbul ignore next */ []));
4123
+ /**
4124
+ * Chart configuration options
4125
+ */
4126
+ this.chartOptions = computed(() => {
4127
+ const chartLayout = this.chartLayout();
4128
+ return chartLayout?.options || null;
4129
+ }, /* @ts-ignore */
4130
+ ...(ngDevMode ? [{ debugName: "chartOptions" }] : /* istanbul ignore next */ []));
4131
+ /**
4132
+ * Chart type for rendering
4133
+ */
4134
+ this.chartType = computed(() => {
4135
+ const options = this.chartOptions();
4136
+ return options?.chartType || 'bar';
4137
+ }, /* @ts-ignore */
4138
+ ...(ngDevMode ? [{ debugName: "chartType" }] : /* istanbul ignore next */ []));
4139
+ /**
4140
+ * Data mapping configuration
4141
+ */
4142
+ this.dataMapping = computed(() => {
4143
+ const options = this.chartOptions();
4144
+ return options?.dataMapping || null;
4145
+ }, /* @ts-ignore */
4146
+ ...(ngDevMode ? [{ debugName: "dataMapping" }] : /* istanbul ignore next */ []));
4147
+ //#endregion
4148
+ //#region ---- Effects ----
4149
+ /**
4150
+ * Effect to handle data fetching when chart configuration changes
4151
+ */
4152
+ this.#fetchData = effect(() => {
4153
+ if (this.vm.allowFetchData() && this.vm.shouldExecuteReport() && this.chartOptions()) {
4154
+ this.vm.fetchData().then((result) => {
4155
+ const barChartData = AXBarChartReportMapper(queryResultToChartSeries(result, this.dataMapping()));
4156
+ this.barChartData.set(barChartData);
4157
+ });
4158
+ }
4159
+ }, /* @ts-ignore */
4160
+ ...(ngDevMode ? [{ debugName: "#fetchData" }] : /* istanbul ignore next */ []));
4161
+ this.barChartData = signal([], /* @ts-ignore */
4162
+ ...(ngDevMode ? [{ debugName: "barChartData" }] : /* istanbul ignore next */ []));
4163
+ this.barChartOptions = signal({}, /* @ts-ignore */
4164
+ ...(ngDevMode ? [{ debugName: "barChartOptions" }] : /* istanbul ignore next */ []));
4165
+ this.legendOptions = signal({
4166
+ mode: 'horizontal',
4167
+ }, /* @ts-ignore */
4168
+ ...(ngDevMode ? [{ debugName: "legendOptions" }] : /* istanbul ignore next */ []));
4169
+ }
4170
+ //#endregion
4171
+ //#region ---- Effects ----
4172
+ /**
4173
+ * Effect to handle data fetching when chart configuration changes
4174
+ */
4175
+ #fetchData;
4176
+ //#endregion
4177
+ //#region ---- Event Handlers ----
4178
+ /**
4179
+ * Handle chart click events
4180
+ */
4181
+ handleChartClick(event) {
4182
+ // TODO: Implement chart click handling
4183
+ console.log('Chart clicked:', event);
4184
+ }
4185
+ /**
4186
+ * Handle chart data point selection
4187
+ */
4188
+ handleDataPointSelect(event) {
4189
+ // TODO: Implement data point selection
4190
+ console.log('Data point selected:', event);
4191
+ }
4192
+ //#endregion
4193
+ //#region ---- Helper Methods ----
4194
+ /**
4195
+ * Check if chart is ready to render
4196
+ */
4197
+ isChartReady() {
4198
+ return !!(this.chartOptions() && this.dataMapping());
4199
+ }
4200
+ /**
4201
+ * Get chart configuration for rendering
4202
+ */
4203
+ getChartConfig() {
4204
+ // TODO: Transform AXPReportChartOptions to chart library format
4205
+ return {
4206
+ type: this.chartType(),
4207
+ options: this.chartOptions(),
4208
+ dataMapping: this.dataMapping(),
4209
+ };
4210
+ }
4211
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportBarChartViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4212
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPReportBarChartViewComponent, isStandalone: true, selector: "axp-report-bar-chart-view", ngImport: i0, template: "<div class=\"axp-report-chart-view\" *translate=\"let t\">\n @if (vm.status() === AXPPageStatus.Processing) {\n <!-- Loading State -->\n <div class=\"__loading\">\n <div class=\"spinner spin border-4 border-gray-200 border-t-primary rounded-full w-12 h-12\">\n </div>\n <div class=\"__loading-text\">\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 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 (isChartReady()) {\n <!-- Chart Content -->\n <div class=\"__chart-container\">\n @if (chartLayout()?.header) {\n <div class=\"__chart-header\">\n <h3 class=\"__chart-title\">{{ chartLayout()?.header?.title }}</h3>\n @if (chartLayout()?.header?.showDate) {\n <div class=\"__chart-date\">{{ '@general:common.date.today' | translate | async }}</div>\n }\n </div>\n }\n\n <div class=\"__chart-body\">\n <!-- Chart component will be rendered here -->\n <div class=\"__chart-placeholder\">\n <div class=\"__placeholder-content\">\n <div class=\"flex flex-wrap items-center justify-center gap-10\"\n [class]=\"legendOptions().mode === 'horizontal' ? 'flex-col' : 'flex-row'\">\n <!-- Chart Area -->\n <div class=\"flex-0 size-96\">\n <ax-bar-chart #chart [data]=\"barChartData()\" [options]=\"barChartOptions()\"></ax-bar-chart>\n </div>\n <!-- Legend Area -->\n @if (barChartData().length > 0) {\n <div>\n <ax-chart-legend [options]=\"legendOptions()\" [chart]=\"chart\"></ax-chart-legend>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n\n @if (chartLayout()?.footer) {\n <div class=\"__chart-footer\">\n @if (chartLayout()?.footer?.customHtml) {\n <div [innerHTML]=\"$safeNavigationMigration(chartLayout()?.footer?.customHtml)\"></div>\n }\n </div>\n }\n </div>\n } @else {\n <!-- No Configuration State -->\n <axp-state-message icon=\"fa-light fa-chart-column\"\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 </axp-state-message>\n }\n</div>", styles: ["@layer properties;.axp-report-chart-view{display:flex;height:100%;width:100%;flex-direction:column}.axp-report-chart-view .__loading{display:flex;height:100%;flex-direction:column;align-items:center;justify-content:center;gap:calc(var(--spacing, .25rem) * 4);padding:calc(var(--spacing, .25rem) * 8)}.axp-report-chart-view .__loading .__loading-text{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:var(--color-gray-600, oklch(44.6% .03 256.802))}.axp-report-chart-view .__chart-container{display:flex;height:100%;width:100%;flex-direction:column}.axp-report-chart-view .__chart-container .__chart-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;background-color:var(--color-gray-50, oklch(98.5% .002 247.839));padding:calc(var(--spacing, .25rem) * 4)}.axp-report-chart-view .__chart-container .__chart-header .__chart-title{margin:0;font-size:var(--text-lg, 1.125rem);line-height:var(--tw-leading, var(--text-lg--line-height, calc(1.75 / 1.125)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}.axp-report-chart-view .__chart-container .__chart-header .__chart-date{margin-top:var(--spacing, .25rem);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:var(--color-gray-600, oklch(44.6% .03 256.802))}.axp-report-chart-view .__chart-container .__chart-body{flex:1;overflow:hidden}.axp-report-chart-view .__chart-container .__chart-body .__chart-placeholder{display:flex;height:100%;flex-direction:column;align-items:center;justify-content:center;gap:calc(var(--spacing, .25rem) * 6);text-align:center}.axp-report-chart-view .__chart-container .__chart-body .__chart-placeholder .__placeholder-icon{font-size:var(--text-6xl, 3.75rem);line-height:var(--tw-leading, var(--text-6xl--line-height, 1));color:rgba(var(--ax-sys-color-primary-surface))}@supports (color: color-mix(in lab,red,red)){.axp-report-chart-view .__chart-container .__chart-body .__chart-placeholder .__placeholder-icon{color:color-mix(in oklab,rgba(var(--ax-sys-color-primary-surface)) 20%,transparent)}}.axp-report-chart-view .__chart-container .__chart-body .__chart-placeholder .__placeholder-content{max-width:var(--container-md, 28rem)}.axp-report-chart-view .__chart-container .__chart-body .__chart-placeholder .__placeholder-content .__placeholder-title{margin-bottom:calc(var(--spacing, .25rem) * 2);font-size:var(--text-xl, 1.25rem);line-height:var(--tw-leading, var(--text-xl--line-height, calc(1.75 / 1.25)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}.axp-report-chart-view .__chart-container .__chart-body .__chart-placeholder .__placeholder-content .__placeholder-description{margin-bottom:calc(var(--spacing, .25rem) * 4);color:var(--color-gray-600, oklch(44.6% .03 256.802))}.axp-report-chart-view .__chart-container .__chart-body .__chart-content{height:100%;width:100%}.axp-report-chart-view .__chart-container .__chart-footer{border-top-style:var(--tw-border-style);border-top-width:1px;background-color:var(--color-gray-50, oklch(98.5% .002 247.839));padding:calc(var(--spacing, .25rem) * 4);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:var(--color-gray-600, oklch(44.6% .03 256.802))}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "directive", type: i1.AXTranslatorDirective, selector: "[translate]" }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "component", type: AXBarChartComponent, selector: "ax-bar-chart", inputs: ["data", "options"], outputs: ["barClick"] }, { kind: "component", type: AXChartLegendComponent, selector: "ax-chart-legend", inputs: ["chart", "options"], outputs: ["itemClick", "itemMouseEnter", "itemMouseLeave"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }, { kind: "pipe", type: i1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
4213
+ }
4214
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPReportBarChartViewComponent, decorators: [{
4215
+ type: Component,
4216
+ args: [{ selector: 'axp-report-bar-chart-view', standalone: true, imports: [
4217
+ CommonModule,
4218
+ AXDecoratorModule,
4219
+ AXTranslationModule,
4220
+ AXPStateMessageComponent,
4221
+ AXPWidgetCoreModule,
4222
+ AXBarChartComponent,
4223
+ AXChartLegendComponent,
4224
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"axp-report-chart-view\" *translate=\"let t\">\n @if (vm.status() === AXPPageStatus.Processing) {\n <!-- Loading State -->\n <div class=\"__loading\">\n <div class=\"spinner spin border-4 border-gray-200 border-t-primary rounded-full w-12 h-12\">\n </div>\n <div class=\"__loading-text\">\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 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 (isChartReady()) {\n <!-- Chart Content -->\n <div class=\"__chart-container\">\n @if (chartLayout()?.header) {\n <div class=\"__chart-header\">\n <h3 class=\"__chart-title\">{{ chartLayout()?.header?.title }}</h3>\n @if (chartLayout()?.header?.showDate) {\n <div class=\"__chart-date\">{{ '@general:common.date.today' | translate | async }}</div>\n }\n </div>\n }\n\n <div class=\"__chart-body\">\n <!-- Chart component will be rendered here -->\n <div class=\"__chart-placeholder\">\n <div class=\"__placeholder-content\">\n <div class=\"flex flex-wrap items-center justify-center gap-10\"\n [class]=\"legendOptions().mode === 'horizontal' ? 'flex-col' : 'flex-row'\">\n <!-- Chart Area -->\n <div class=\"flex-0 size-96\">\n <ax-bar-chart #chart [data]=\"barChartData()\" [options]=\"barChartOptions()\"></ax-bar-chart>\n </div>\n <!-- Legend Area -->\n @if (barChartData().length > 0) {\n <div>\n <ax-chart-legend [options]=\"legendOptions()\" [chart]=\"chart\"></ax-chart-legend>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n\n @if (chartLayout()?.footer) {\n <div class=\"__chart-footer\">\n @if (chartLayout()?.footer?.customHtml) {\n <div [innerHTML]=\"$safeNavigationMigration(chartLayout()?.footer?.customHtml)\"></div>\n }\n </div>\n }\n </div>\n } @else {\n <!-- No Configuration State -->\n <axp-state-message icon=\"fa-light fa-chart-column\"\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 </axp-state-message>\n }\n</div>", styles: ["@layer properties;.axp-report-chart-view{display:flex;height:100%;width:100%;flex-direction:column}.axp-report-chart-view .__loading{display:flex;height:100%;flex-direction:column;align-items:center;justify-content:center;gap:calc(var(--spacing, .25rem) * 4);padding:calc(var(--spacing, .25rem) * 8)}.axp-report-chart-view .__loading .__loading-text{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:var(--color-gray-600, oklch(44.6% .03 256.802))}.axp-report-chart-view .__chart-container{display:flex;height:100%;width:100%;flex-direction:column}.axp-report-chart-view .__chart-container .__chart-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;background-color:var(--color-gray-50, oklch(98.5% .002 247.839));padding:calc(var(--spacing, .25rem) * 4)}.axp-report-chart-view .__chart-container .__chart-header .__chart-title{margin:0;font-size:var(--text-lg, 1.125rem);line-height:var(--tw-leading, var(--text-lg--line-height, calc(1.75 / 1.125)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}.axp-report-chart-view .__chart-container .__chart-header .__chart-date{margin-top:var(--spacing, .25rem);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:var(--color-gray-600, oklch(44.6% .03 256.802))}.axp-report-chart-view .__chart-container .__chart-body{flex:1;overflow:hidden}.axp-report-chart-view .__chart-container .__chart-body .__chart-placeholder{display:flex;height:100%;flex-direction:column;align-items:center;justify-content:center;gap:calc(var(--spacing, .25rem) * 6);text-align:center}.axp-report-chart-view .__chart-container .__chart-body .__chart-placeholder .__placeholder-icon{font-size:var(--text-6xl, 3.75rem);line-height:var(--tw-leading, var(--text-6xl--line-height, 1));color:rgba(var(--ax-sys-color-primary-surface))}@supports (color: color-mix(in lab,red,red)){.axp-report-chart-view .__chart-container .__chart-body .__chart-placeholder .__placeholder-icon{color:color-mix(in oklab,rgba(var(--ax-sys-color-primary-surface)) 20%,transparent)}}.axp-report-chart-view .__chart-container .__chart-body .__chart-placeholder .__placeholder-content{max-width:var(--container-md, 28rem)}.axp-report-chart-view .__chart-container .__chart-body .__chart-placeholder .__placeholder-content .__placeholder-title{margin-bottom:calc(var(--spacing, .25rem) * 2);font-size:var(--text-xl, 1.25rem);line-height:var(--tw-leading, var(--text-xl--line-height, calc(1.75 / 1.25)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600)}.axp-report-chart-view .__chart-container .__chart-body .__chart-placeholder .__placeholder-content .__placeholder-description{margin-bottom:calc(var(--spacing, .25rem) * 4);color:var(--color-gray-600, oklch(44.6% .03 256.802))}.axp-report-chart-view .__chart-container .__chart-body .__chart-content{height:100%;width:100%}.axp-report-chart-view .__chart-container .__chart-footer{border-top-style:var(--tw-border-style);border-top-width:1px;background-color:var(--color-gray-50, oklch(98.5% .002 247.839));padding:calc(var(--spacing, .25rem) * 4);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));color:var(--color-gray-600, oklch(44.6% .03 256.802))}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
4225
+ }] });
4226
+
4227
+ /**
4228
+ * Generated bundle index. Do not edit.
4229
+ */
4230
+
4231
+ export { AXPReportViewerViewModel as A, mapEntityBindingParameters as B, queryResultToChartSeries as C, queryResultToPdfSource as D, ENTITY_REPORTS_PAGE_COMPONENT_KEY as E, resolveReportParameterValues as F, resolveTodayDateRange as G, toReportDateRangeFilterValue as H, OPEN_REPORT_IN_POPUP_COMMAND$1 as O, AXPOpenReportViewerCommand as a, resolveReportFilterDefaultValue as b, reportLayoutTitleKey as c, AXPReportTableViewComponent as d, AXPReportChartViewComponent as e, AXPReportPdfViewComponent as f, AXPReportViewerService as g, bindingShowsOnPage as h, isDistinctLayoutTitle as i, buildOpenInPopupCommandOptions as j, resolveBindingTitle as k, resolveBindingDescription as l, AXBarChartReportMapper as m, AXLineChartReportMapper as n, AXMPermissionDefinitionProvider as o, AXMReportManagementModule as p, AXPOpenReportInPopupCommand as q, reportLayoutPageIcon as r, AXPReportBarChartViewComponent as s, AXPReportViewerPageComponent as t, AXPieChartReportMapper as u, collectReportParameterDefaults as v, convertContextDataToFilterClause as w, flattenReportParametersToQueryParams as x, formatReportDateOnly as y, isReportDateRangeObject as z };
4232
+ //# sourceMappingURL=acorex-platform-module-report-management-client-acorex-platform-module-report-management-client-BDCuhd5O.mjs.map