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

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