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