@firestitch/report 18.0.3 → 18.0.4
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/app/reports/interfaces/report.interface.d.ts +1 -0
- package/app/reports/views/report/fs-ai-report.component.d.ts +43 -0
- package/app/reports/views/reports/fs-ai-reports.component.d.ts +23 -0
- package/esm2022/app/reports/interfaces/report.interface.mjs +1 -1
- package/esm2022/app/reports/views/report/fs-ai-report.component.mjs +312 -0
- package/esm2022/app/reports/views/reports/fs-ai-reports.component.mjs +112 -0
- package/esm2022/public_api.mjs +5 -1
- package/fesm2022/{firestitch-report-echarts-DibqM0Fw.mjs → firestitch-report-echarts-m2yVum4J.mjs} +2 -2
- package/fesm2022/{firestitch-report-echarts-DibqM0Fw.mjs.map → firestitch-report-echarts-m2yVum4J.mjs.map} +1 -1
- package/fesm2022/{firestitch-report-firestitch-report-UX1baw15.mjs → firestitch-report-firestitch-report-BclPOljK.mjs} +404 -14
- package/fesm2022/firestitch-report-firestitch-report-BclPOljK.mjs.map +1 -0
- package/fesm2022/firestitch-report.mjs +1 -1
- package/package.json +1 -1
- package/public_api.d.ts +2 -0
- package/fesm2022/firestitch-report-firestitch-report-UX1baw15.mjs.map +0 -1
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Injectable, Component, ChangeDetectionStrategy, Input, DestroyRef, ViewChild, EventEmitter, ChangeDetectorRef, NgZone, ElementRef, Output, HostBinding, signal, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
2
|
+
import { inject, Injectable, Component, ChangeDetectionStrategy, Input, DestroyRef, ViewChild, EventEmitter, ChangeDetectorRef, NgZone, ElementRef, Output, HostBinding, signal, CUSTOM_ELEMENTS_SCHEMA, input, output, effect } from '@angular/core';
|
|
3
3
|
import * as i1$3 from '@angular/forms';
|
|
4
4
|
import { FormsModule, ControlContainer, NgForm } from '@angular/forms';
|
|
5
5
|
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialog } from '@angular/material/dialog';
|
|
6
|
-
import
|
|
6
|
+
import * as i4$2 from '@angular/material/icon';
|
|
7
|
+
import { MatIcon, MatIconModule } from '@angular/material/icon';
|
|
7
8
|
import * as i2$2 from '@firestitch/autocomplete-chips';
|
|
8
9
|
import { FsAutocompleteChipsModule } from '@firestitch/autocomplete-chips';
|
|
9
10
|
import * as i2 from '@firestitch/filter';
|
|
10
11
|
import { ItemType, FsFilterModule } from '@firestitch/filter';
|
|
11
|
-
import * as
|
|
12
|
+
import * as i2$3 from '@firestitch/menu';
|
|
12
13
|
import { FsMenuModule } from '@firestitch/menu';
|
|
13
14
|
import { FsProcess } from '@firestitch/process';
|
|
14
15
|
import { FsPrompt } from '@firestitch/prompt';
|
|
@@ -17,8 +18,10 @@ import { map, shareReplay, filter, tap, switchMap, catchError, finalize } from '
|
|
|
17
18
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
18
19
|
import { NgxEchartsDirective, provideEchartsCore } from 'ngx-echarts';
|
|
19
20
|
import { FsAiChatComponent } from '@firestitch/ai';
|
|
20
|
-
import
|
|
21
|
-
import {
|
|
21
|
+
import * as i3$1 from '@angular/material/button';
|
|
22
|
+
import { MatIconButton, MatButton, MatButtonModule } from '@angular/material/button';
|
|
23
|
+
import * as i5$1 from '@angular/material/tooltip';
|
|
24
|
+
import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip';
|
|
22
25
|
import * as i1$2 from '@firestitch/zoom-pan';
|
|
23
26
|
import { FsZoomPanComponent, FsZoomPanModule } from '@firestitch/zoom-pan';
|
|
24
27
|
import { FsApi } from '@firestitch/api';
|
|
@@ -47,6 +50,9 @@ import * as i6 from '@firestitch/datepicker';
|
|
|
47
50
|
import { FsDatePickerModule } from '@firestitch/datepicker';
|
|
48
51
|
import { MatOption } from '@angular/material/core';
|
|
49
52
|
import { guid } from '@firestitch/common';
|
|
53
|
+
import { Router, ActivatedRoute, RouterLink } from '@angular/router';
|
|
54
|
+
import * as i1$4 from '@firestitch/date';
|
|
55
|
+
import { FsDateModule } from '@firestitch/date';
|
|
50
56
|
|
|
51
57
|
// API gateway for the Reports module. Every endpoint hangs off [basePath],
|
|
52
58
|
// which defaults to "reports" (the framework reports backend at /api/reports)
|
|
@@ -2462,7 +2468,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2462
2468
|
// complete synchronously after setOption.
|
|
2463
2469
|
async function renderChartImage(component, data, width, height, pixelRatio = 3) {
|
|
2464
2470
|
// The same tree-shaken echarts build + 'report' theme the live canvas uses.
|
|
2465
|
-
const echarts = (await import('./firestitch-report-echarts-
|
|
2471
|
+
const echarts = (await import('./firestitch-report-echarts-m2yVum4J.mjs')).default;
|
|
2466
2472
|
const host = document.createElement('div');
|
|
2467
2473
|
host.style.width = `${width}px`;
|
|
2468
2474
|
host.style.height = `${height}px`;
|
|
@@ -2916,7 +2922,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2916
2922
|
|
|
2917
2923
|
// fs-filter query key for the runtime Frequency control (a render-layer bucket
|
|
2918
2924
|
// override, not a filter group — so it's read straight off the query).
|
|
2919
|
-
const FREQUENCY_ITEM = 'frequency';
|
|
2925
|
+
const FREQUENCY_ITEM$1 = 'frequency';
|
|
2920
2926
|
// The Reports page: AI chat on the left, the report canvas on the right —
|
|
2921
2927
|
// a split-pane layout; the viewer is our own canvas (no iframe, no embed URLs).
|
|
2922
2928
|
// The chat is always scoped to the selected report; its id rides on every
|
|
@@ -3154,7 +3160,7 @@ class ReportComponent {
|
|
|
3154
3160
|
: [];
|
|
3155
3161
|
if (reportId && this._hasTimeSeriesChart()) {
|
|
3156
3162
|
items.push({
|
|
3157
|
-
name: FREQUENCY_ITEM,
|
|
3163
|
+
name: FREQUENCY_ITEM$1,
|
|
3158
3164
|
type: ItemType.Select,
|
|
3159
3165
|
label: 'Frequency',
|
|
3160
3166
|
multiple: false,
|
|
@@ -3173,7 +3179,7 @@ class ReportComponent {
|
|
|
3173
3179
|
this._filterState.setValue(groupId, value);
|
|
3174
3180
|
}
|
|
3175
3181
|
// Frequency isn't a filter group — it's a render-layer bucket override.
|
|
3176
|
-
this._filterState.setFrequency(query?.[FREQUENCY_ITEM] || null);
|
|
3182
|
+
this._filterState.setFrequency(query?.[FREQUENCY_ITEM$1] || null);
|
|
3177
3183
|
},
|
|
3178
3184
|
};
|
|
3179
3185
|
}
|
|
@@ -3206,8 +3212,8 @@ class ReportComponent {
|
|
|
3206
3212
|
ReportPdfService,
|
|
3207
3213
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
3208
3214
|
// this route's chunk.
|
|
3209
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
3210
|
-
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }], ngImport: i0, template: "<div\n #split\n class=\"report fs-row.align-start\">\n <fs-ai-chat\n #chatPanel\n class=\"chat\"\n style=\"min-height: 500px;\"\n basePath=\"reports\"\n [requestData]=\"{ reportId: selected?.id ?? null }\"\n [introMessage]=\"introMessage\"\n (response)=\"onChatResponse($event)\">\n </fs-ai-chat>\n <div\n class=\"resizer\"\n (pointerdown)=\"onResizeStart($event)\">\n </div>\n <div class=\"viewer fs-flex fs-column\">\n <div class=\"fs-row.align-center.gap-sm\">\n <fs-autocomplete-chips\n class=\"fs-flex\"\n [fetch]=\"fetchReports\"\n [(ngModel)]=\"selected\"\n [disabled]=\"loadingReports\"\n [padless]=\"true\"\n [multiple]=\"false\"\n [fetchOnFocus]=\"true\"\n (ngModelChange)=\"reportChange($event)\"\n placeholder=\"Report\"\n name=\"report\">\n <ng-template\n fsAutocompleteChipsTemplate\n let-object=\"object\">\n {{ object.name }}\n </ng-template>\n <ng-template\n fsAutocompleteChipsStatic\n (click)=\"createReport()\">\n Create Report\n </ng-template>\n </fs-autocomplete-chips>\n @if (report) {\n <fs-menu>\n <ng-template\n fs-menu-item\n (click)=\"reportSettings()\">\n <mat-icon>\n tune\n </mat-icon>\n Report settings\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"toggleEditMode()\">\n <mat-icon>\n {{ editMode ? 'lock' : 'open_with' }}\n </mat-icon>\n {{ editMode ? 'Done editing layout' : 'Edit layout' }}\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPowerpoint()\">\n <mat-icon>\n slideshow\n </mat-icon>\n Export PowerPoint\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPdf()\">\n <mat-icon>\n picture_as_pdf\n </mat-icon>\n Export PDF\n </ng-template>\n </fs-menu>\n }\n </div>\n @if (report) {\n <!-- Report-level filters only (the report's actions live in the menu\n above). fs-filter reads its config once at init, so it's keyed on the\n filter signature: when the report-level filter set changes the block\n is recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter [config]=\"reportFilterConfig\"></fs-filter>\n }\n }\n <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (editDone)=\"toggleEditMode()\">\n </app-report-canvas>\n }\n </div>\n</div>", styles: [".report{height:100%;min-height:500px}.report .chat{display:block;flex:0 0 25%;width:100%;height:100%;min-height:500px;min-width:0;border:none}.report .viewer{display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden}.report .viewer fs-filter{flex:0 0 auto;margin-top:10px;margin-bottom:0}.report .viewer ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.report .resizer{flex:0 0 11px;align-self:stretch;display:flex;justify-content:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}.report .resizer:before{content:\"\";width:0px;background:#0000001f;transition:width .12s ease,background-color .12s ease}.report .resizer:hover:before{width:3px;background:var(--brand-primary-color)}.report.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.report.resizing .resizer:before{width:3px;background:var(--brand-primary-color)}.report.resizing .chat,.report.resizing .viewer{pointer-events:none}::ng-deep body.body-report-reports .mat-mdc-card-content{display:flex;flex-direction:column;box-sizing:border-box}::ng-deep body.body-report-reports .mat-mdc-card-content mat-tab-nav-panel{flex:1;min-height:0}::ng-deep body.body-report-reports .mat-mdc-card-content mat-tab-nav-panel router-outlet+ng-component{height:100%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: FsAutocompleteChipsModule }, { kind: "component", type: i2$2.FsAutocompleteChipsComponent, selector: "fs-autocomplete-chips", inputs: ["fetch", "appearance", "floatLabel", "readonly", "size", "label", "placeholder", "chipImage", "chipBackground", "chipColor", "chipIcon", "chipIconColor", "chipClass", "chipPadding", "shape", "hint", "allowText", "allowObject", "delay", "minPanelWidth", "maxPanelHeight", "validateText", "removable", "allowClear", "color", "background", "orderable", "padless", "initOnClick", "fetchOnFocus", "multiple", "multipleAdd", "confirm", "disabled", "groupBy", "panelWidth", "panelClass", "compareWith"], outputs: ["selected", "removed", "reordered", "clear", "panelOpened", "panelClosed"] }, { kind: "directive", type: i2$2.FsAutocompleteObjectDirective, selector: "[fsAutocompleteObject],[fsAutocompleteChipsTemplate]" }, { kind: "directive", type: i2$2.FsAutocompleteChipsStaticDirective, selector: "[fsAutocompleteChipsStatic]", inputs: ["show", "disable"], outputs: ["click", "selected"] }, { kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsMenuModule }, { kind: "component", type:
|
|
3215
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-m2yVum4J.mjs').then((module) => module.default) }),
|
|
3216
|
+
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }], ngImport: i0, template: "<div\n #split\n class=\"report fs-row.align-start\">\n <fs-ai-chat\n #chatPanel\n class=\"chat\"\n style=\"min-height: 500px;\"\n basePath=\"reports\"\n [requestData]=\"{ reportId: selected?.id ?? null }\"\n [introMessage]=\"introMessage\"\n (response)=\"onChatResponse($event)\">\n </fs-ai-chat>\n <div\n class=\"resizer\"\n (pointerdown)=\"onResizeStart($event)\">\n </div>\n <div class=\"viewer fs-flex fs-column\">\n <div class=\"fs-row.align-center.gap-sm\">\n <fs-autocomplete-chips\n class=\"fs-flex\"\n [fetch]=\"fetchReports\"\n [(ngModel)]=\"selected\"\n [disabled]=\"loadingReports\"\n [padless]=\"true\"\n [multiple]=\"false\"\n [fetchOnFocus]=\"true\"\n (ngModelChange)=\"reportChange($event)\"\n placeholder=\"Report\"\n name=\"report\">\n <ng-template\n fsAutocompleteChipsTemplate\n let-object=\"object\">\n {{ object.name }}\n </ng-template>\n <ng-template\n fsAutocompleteChipsStatic\n (click)=\"createReport()\">\n Create Report\n </ng-template>\n </fs-autocomplete-chips>\n @if (report) {\n <fs-menu>\n <ng-template\n fs-menu-item\n (click)=\"reportSettings()\">\n <mat-icon>\n tune\n </mat-icon>\n Report settings\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"toggleEditMode()\">\n <mat-icon>\n {{ editMode ? 'lock' : 'open_with' }}\n </mat-icon>\n {{ editMode ? 'Done editing layout' : 'Edit layout' }}\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPowerpoint()\">\n <mat-icon>\n slideshow\n </mat-icon>\n Export PowerPoint\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPdf()\">\n <mat-icon>\n picture_as_pdf\n </mat-icon>\n Export PDF\n </ng-template>\n </fs-menu>\n }\n </div>\n @if (report) {\n <!-- Report-level filters only (the report's actions live in the menu\n above). fs-filter reads its config once at init, so it's keyed on the\n filter signature: when the report-level filter set changes the block\n is recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter [config]=\"reportFilterConfig\"></fs-filter>\n }\n }\n <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (editDone)=\"toggleEditMode()\">\n </app-report-canvas>\n }\n </div>\n</div>", styles: [".report{height:100%;min-height:500px}.report .chat{display:block;flex:0 0 25%;width:100%;height:100%;min-height:500px;min-width:0;border:none}.report .viewer{display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden}.report .viewer fs-filter{flex:0 0 auto;margin-top:10px;margin-bottom:0}.report .viewer ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.report .resizer{flex:0 0 11px;align-self:stretch;display:flex;justify-content:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}.report .resizer:before{content:\"\";width:0px;background:#0000001f;transition:width .12s ease,background-color .12s ease}.report .resizer:hover:before{width:3px;background:var(--brand-primary-color)}.report.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.report.resizing .resizer:before{width:3px;background:var(--brand-primary-color)}.report.resizing .chat,.report.resizing .viewer{pointer-events:none}::ng-deep body.body-report-reports .mat-mdc-card-content{display:flex;flex-direction:column;box-sizing:border-box}::ng-deep body.body-report-reports .mat-mdc-card-content mat-tab-nav-panel{flex:1;min-height:0}::ng-deep body.body-report-reports .mat-mdc-card-content mat-tab-nav-panel router-outlet+ng-component{height:100%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: FsAutocompleteChipsModule }, { kind: "component", type: i2$2.FsAutocompleteChipsComponent, selector: "fs-autocomplete-chips", inputs: ["fetch", "appearance", "floatLabel", "readonly", "size", "label", "placeholder", "chipImage", "chipBackground", "chipColor", "chipIcon", "chipIconColor", "chipClass", "chipPadding", "shape", "hint", "allowText", "allowObject", "delay", "minPanelWidth", "maxPanelHeight", "validateText", "removable", "allowClear", "color", "background", "orderable", "padless", "initOnClick", "fetchOnFocus", "multiple", "multipleAdd", "confirm", "disabled", "groupBy", "panelWidth", "panelClass", "compareWith"], outputs: ["selected", "removed", "reordered", "clear", "panelOpened", "panelClosed"] }, { kind: "directive", type: i2$2.FsAutocompleteObjectDirective, selector: "[fsAutocompleteObject],[fsAutocompleteChipsTemplate]" }, { kind: "directive", type: i2$2.FsAutocompleteChipsStaticDirective, selector: "[fsAutocompleteChipsStatic]", inputs: ["show", "disable"], outputs: ["click", "selected"] }, { kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsMenuModule }, { kind: "component", type: i2$3.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$3.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "introMessage"], outputs: ["response"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: ReportCanvasComponent, selector: "app-report-canvas", inputs: ["report", "editMode"], outputs: ["componentSettings", "reportChanged", "editDone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3211
3217
|
}
|
|
3212
3218
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportComponent, decorators: [{
|
|
3213
3219
|
type: Component,
|
|
@@ -3220,7 +3226,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3220
3226
|
ReportPdfService,
|
|
3221
3227
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
3222
3228
|
// this route's chunk.
|
|
3223
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
3229
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-m2yVum4J.mjs').then((module) => module.default) }),
|
|
3224
3230
|
], imports: [
|
|
3225
3231
|
FormsModule,
|
|
3226
3232
|
FsAutocompleteChipsModule,
|
|
@@ -3238,6 +3244,390 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3238
3244
|
args: ['chatPanel', { static: true, read: ElementRef }]
|
|
3239
3245
|
}] } });
|
|
3240
3246
|
|
|
3247
|
+
// The reports listing — an fs-list of reports. Rows are links built from a URL
|
|
3248
|
+
// pattern (a ':id' token → the report id; a leading '/' makes it absolute,
|
|
3249
|
+
// otherwise it's relative to this component's route). Create prompts for a
|
|
3250
|
+
// title, creates the report, then routes to it. The header gear emits so the
|
|
3251
|
+
// host can open its Semantic Database dialog. No routing is hard-coded here.
|
|
3252
|
+
class FsAiReportsComponent {
|
|
3253
|
+
// API base for the reports endpoints; override to mount elsewhere.
|
|
3254
|
+
basePath = input('reports');
|
|
3255
|
+
// Where a row links to; ':id' is replaced with the report id. Default takes
|
|
3256
|
+
// you to <current route>/:id. Absolute when it starts with '/'.
|
|
3257
|
+
openPattern = input(':id');
|
|
3258
|
+
// Where a freshly-created report routes to; same ':id' pattern rules.
|
|
3259
|
+
createPattern = input(':id');
|
|
3260
|
+
// The list-header gear — the host opens its Semantic Database dialog (the
|
|
3261
|
+
// builder lives in the app, so the package only signals the intent).
|
|
3262
|
+
semantic = output();
|
|
3263
|
+
list = null;
|
|
3264
|
+
listConfig;
|
|
3265
|
+
_reportData = inject(ReportData);
|
|
3266
|
+
_router = inject(Router);
|
|
3267
|
+
_route = inject(ActivatedRoute);
|
|
3268
|
+
_prompt = inject(FsPrompt);
|
|
3269
|
+
_destroyRef = inject(DestroyRef);
|
|
3270
|
+
constructor() {
|
|
3271
|
+
effect(() => {
|
|
3272
|
+
this._reportData.basePath = this.basePath();
|
|
3273
|
+
});
|
|
3274
|
+
this._initListConfig();
|
|
3275
|
+
}
|
|
3276
|
+
// routerLink commands for a report row (built from openPattern).
|
|
3277
|
+
openLink(report) {
|
|
3278
|
+
return this._patternCommands(this.openPattern(), report.id);
|
|
3279
|
+
}
|
|
3280
|
+
// Prompt for a title, create the report, then route to it (createPattern).
|
|
3281
|
+
createReport() {
|
|
3282
|
+
this._prompt.input({
|
|
3283
|
+
title: 'Create Report',
|
|
3284
|
+
label: 'Title',
|
|
3285
|
+
commitLabel: 'Create',
|
|
3286
|
+
required: true,
|
|
3287
|
+
})
|
|
3288
|
+
.pipe(switchMap((name) => this._reportData.create(name)), takeUntilDestroyed(this._destroyRef))
|
|
3289
|
+
.subscribe((report) => {
|
|
3290
|
+
const pattern = this.createPattern();
|
|
3291
|
+
this._router.navigate(this._patternCommands(pattern, report.id), pattern.startsWith('/') ? {} : { relativeTo: this._route });
|
|
3292
|
+
});
|
|
3293
|
+
}
|
|
3294
|
+
// Turn a ':id' pattern into router commands. Relative unless it starts
|
|
3295
|
+
// with '/', in which case the first segment carries the leading slash.
|
|
3296
|
+
_patternCommands(pattern, id) {
|
|
3297
|
+
const absolute = pattern.startsWith('/');
|
|
3298
|
+
const segments = pattern.split('/')
|
|
3299
|
+
.filter((segment) => segment.length > 0)
|
|
3300
|
+
.map((segment) => (segment === ':id' ? id : segment));
|
|
3301
|
+
if (!segments.length) {
|
|
3302
|
+
return [id];
|
|
3303
|
+
}
|
|
3304
|
+
if (absolute) {
|
|
3305
|
+
segments[0] = `/${segments[0]}`;
|
|
3306
|
+
}
|
|
3307
|
+
return segments;
|
|
3308
|
+
}
|
|
3309
|
+
_initListConfig() {
|
|
3310
|
+
this.listConfig = {
|
|
3311
|
+
// The backend list is not paged yet; show the full set until it is.
|
|
3312
|
+
paging: false,
|
|
3313
|
+
actions: [
|
|
3314
|
+
{
|
|
3315
|
+
// Icon-only: opens the host's Semantic Database dialog.
|
|
3316
|
+
icon: 'settings',
|
|
3317
|
+
tooltip: 'Semantic database',
|
|
3318
|
+
primary: false,
|
|
3319
|
+
click: () => this.semantic.emit(),
|
|
3320
|
+
},
|
|
3321
|
+
{
|
|
3322
|
+
label: 'Create',
|
|
3323
|
+
primary: true,
|
|
3324
|
+
click: () => this.createReport(),
|
|
3325
|
+
},
|
|
3326
|
+
],
|
|
3327
|
+
fetch: (query) => {
|
|
3328
|
+
return this._reportData.reports(query)
|
|
3329
|
+
.pipe(map((reports) => ({ data: reports ?? [] })));
|
|
3330
|
+
},
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3333
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3334
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: FsAiReportsComponent, isStandalone: true, selector: "fs-ai-reports", inputs: { basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null }, openPattern: { classPropertyName: "openPattern", publicName: "openPattern", isSignal: true, isRequired: false, transformFunction: null }, createPattern: { classPropertyName: "createPattern", publicName: "createPattern", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { semantic: "semantic" }, viewQueries: [{ propertyName: "list", first: true, predicate: FsListComponent, descendants: true }], ngImport: i0, template: "<fs-list\n [config]=\"listConfig\"\n #list>\n\n <fs-list-column title=\"Name\">\n <ng-template\n fs-list-cell\n let-row=\"row\">\n <a [routerLink]=\"openLink(row)\">{{ row.name }}</a>\n </ng-template>\n </fs-list-column>\n\n <fs-list-column title=\"Modified\">\n <ng-template\n fs-list-cell\n let-row=\"row\">\n <fs-date [date]=\"row.modifyDate\"></fs-date>\n </ng-template>\n </fs-list-column>\n\n</fs-list>\n", styles: ["a{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: FsDateModule }, { kind: "component", type: i1$4.FsDateComponent, selector: "fs-date", inputs: ["date", "format", "timezone"] }, { kind: "ngmodule", type: FsListModule }, { kind: "component", type: i1.FsListComponent, selector: "fs-list", inputs: ["config", "loaderLines", "cellRowType"], outputs: ["filtersReady"] }, { kind: "directive", type: i1.FsListColumnDirective, selector: "fs-list-column", inputs: ["show", "title", "name", "customizable", "sortable", "sortableDefault", "sortableDirection", "direction", "align", "width", "class"] }, { kind: "directive", type: i1.FsListCellDirective, selector: "[fs-list-cell],[fsListCell]", inputs: ["colspan", "align", "class", "fsListCell", "configTyping"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3335
|
+
}
|
|
3336
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportsComponent, decorators: [{
|
|
3337
|
+
type: Component,
|
|
3338
|
+
args: [{ selector: 'fs-ai-reports', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
|
3339
|
+
FsDateModule,
|
|
3340
|
+
FsListModule,
|
|
3341
|
+
RouterLink,
|
|
3342
|
+
], template: "<fs-list\n [config]=\"listConfig\"\n #list>\n\n <fs-list-column title=\"Name\">\n <ng-template\n fs-list-cell\n let-row=\"row\">\n <a [routerLink]=\"openLink(row)\">{{ row.name }}</a>\n </ng-template>\n </fs-list-column>\n\n <fs-list-column title=\"Modified\">\n <ng-template\n fs-list-cell\n let-row=\"row\">\n <fs-date [date]=\"row.modifyDate\"></fs-date>\n </ng-template>\n </fs-list-column>\n\n</fs-list>\n", styles: ["a{cursor:pointer}\n"] }]
|
|
3343
|
+
}], ctorParameters: () => [], propDecorators: { list: [{
|
|
3344
|
+
type: ViewChild,
|
|
3345
|
+
args: [FsListComponent]
|
|
3346
|
+
}] } });
|
|
3347
|
+
|
|
3348
|
+
// fs-filter query key for the runtime Frequency control (a render-layer bucket
|
|
3349
|
+
// override, not a filter group — so it's read straight off the query).
|
|
3350
|
+
const FREQUENCY_ITEM = 'frequency';
|
|
3351
|
+
// A single report: the AI chat on the left, the report canvas on the right,
|
|
3352
|
+
// under an H2 title + settings gear. The host supplies [reportId] (from its
|
|
3353
|
+
// route) and switches reports by navigating — this component never picks one.
|
|
3354
|
+
class FsAiReportComponent {
|
|
3355
|
+
// The report this view is focused on — supplied by the host from the route.
|
|
3356
|
+
reportId = input(null);
|
|
3357
|
+
// API base for the reports + chat endpoints; override to mount elsewhere.
|
|
3358
|
+
basePath = input('reports');
|
|
3359
|
+
// The report was deleted from its settings dialog — the host routes back to
|
|
3360
|
+
// the listing.
|
|
3361
|
+
deleted = output();
|
|
3362
|
+
// The report was renamed (settings, or the agent) — the host can refresh a
|
|
3363
|
+
// breadcrumb / document title.
|
|
3364
|
+
renamed = output();
|
|
3365
|
+
report = null;
|
|
3366
|
+
editMode = false;
|
|
3367
|
+
// The report-level filter toolbar (fs-filter). Rebuilt whenever the loaded
|
|
3368
|
+
// report (and thus its groups) changes.
|
|
3369
|
+
reportFilterConfig;
|
|
3370
|
+
_split;
|
|
3371
|
+
_chatPanel;
|
|
3372
|
+
_reportData = inject(ReportData);
|
|
3373
|
+
_filterState = inject(ReportFilterStateService);
|
|
3374
|
+
_exportCollector = inject(ReportExportCollectorService);
|
|
3375
|
+
_pptxService = inject(ReportPptxService);
|
|
3376
|
+
_pdfService = inject(ReportPdfService);
|
|
3377
|
+
_process = inject(FsProcess);
|
|
3378
|
+
_dialog = inject(MatDialog);
|
|
3379
|
+
_destroyRef = inject(DestroyRef);
|
|
3380
|
+
_cdRef = inject(ChangeDetectorRef);
|
|
3381
|
+
_zone = inject(NgZone);
|
|
3382
|
+
constructor() {
|
|
3383
|
+
// Point the shared data gateway at the configured base first (before any
|
|
3384
|
+
// load below reads it).
|
|
3385
|
+
effect(() => {
|
|
3386
|
+
this._reportData.basePath = this.basePath();
|
|
3387
|
+
});
|
|
3388
|
+
// (Re)load whenever the host switches the report id.
|
|
3389
|
+
effect(() => {
|
|
3390
|
+
const reportId = this.reportId();
|
|
3391
|
+
this.editMode = false;
|
|
3392
|
+
if (reportId != null) {
|
|
3393
|
+
this._loadReport(reportId);
|
|
3394
|
+
}
|
|
3395
|
+
else {
|
|
3396
|
+
this.report = null;
|
|
3397
|
+
this._buildReportConfig();
|
|
3398
|
+
this._cdRef.markForCheck();
|
|
3399
|
+
}
|
|
3400
|
+
});
|
|
3401
|
+
this._buildReportConfig();
|
|
3402
|
+
}
|
|
3403
|
+
toggleEditMode() {
|
|
3404
|
+
this.editMode = !this.editMode;
|
|
3405
|
+
}
|
|
3406
|
+
// Report settings — name, page size, orientation, layout mode, delete. The
|
|
3407
|
+
// dialog performs the delete itself; we react to what it reports back.
|
|
3408
|
+
reportSettings() {
|
|
3409
|
+
if (!this.report) {
|
|
3410
|
+
return;
|
|
3411
|
+
}
|
|
3412
|
+
const reportId = this.report.id;
|
|
3413
|
+
this._dialog.open(ReportSettingsComponent, {
|
|
3414
|
+
data: { report: this.report },
|
|
3415
|
+
width: '460px',
|
|
3416
|
+
})
|
|
3417
|
+
.afterClosed()
|
|
3418
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
3419
|
+
.subscribe((result) => {
|
|
3420
|
+
if (result?.action === 'deleted') {
|
|
3421
|
+
this.deleted.emit(reportId);
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3424
|
+
if (result?.action === 'saved') {
|
|
3425
|
+
this._loadReport(reportId, true);
|
|
3426
|
+
}
|
|
3427
|
+
});
|
|
3428
|
+
}
|
|
3429
|
+
// Component settings — title, exact geometry, and the diagnostic views
|
|
3430
|
+
// (SQL, config, filter wiring).
|
|
3431
|
+
componentSettings(component) {
|
|
3432
|
+
if (!this.report) {
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
3435
|
+
const reportId = this.report.id;
|
|
3436
|
+
this._dialog.open(ComponentSettingsComponent, {
|
|
3437
|
+
data: { report: this.report, component },
|
|
3438
|
+
width: '640px',
|
|
3439
|
+
})
|
|
3440
|
+
.afterClosed()
|
|
3441
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
3442
|
+
.subscribe(() => {
|
|
3443
|
+
// Always reload: filter add/remove/level changes persist immediately
|
|
3444
|
+
// inside the dialog (no "save"), so the canvas + report bar must
|
|
3445
|
+
// refresh however the dialog was closed.
|
|
3446
|
+
this._loadReport(reportId);
|
|
3447
|
+
});
|
|
3448
|
+
}
|
|
3449
|
+
// The canvas changed the report's structure (added a page) — re-fetch so the
|
|
3450
|
+
// new page renders; the canvas lands the view on it.
|
|
3451
|
+
onCanvasReportChanged() {
|
|
3452
|
+
if (this.report) {
|
|
3453
|
+
this._loadReport(this.report.id);
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
// Export the report to PowerPoint, entirely client-side (native editable
|
|
3457
|
+
// charts; page = slide). The dock shows progress until the file saves.
|
|
3458
|
+
exportPowerpoint() {
|
|
3459
|
+
if (!this.report) {
|
|
3460
|
+
return;
|
|
3461
|
+
}
|
|
3462
|
+
const report = this.report;
|
|
3463
|
+
this._process.run('Exporting PowerPoint', this._exportCollector.collect(report)
|
|
3464
|
+
.pipe(switchMap((collected) => from(this._pptxService.export(collected)))));
|
|
3465
|
+
}
|
|
3466
|
+
// Export to PDF, entirely client-side (offscreen ECharts at 3×; page = PDF page).
|
|
3467
|
+
exportPdf() {
|
|
3468
|
+
if (!this.report) {
|
|
3469
|
+
return;
|
|
3470
|
+
}
|
|
3471
|
+
const report = this.report;
|
|
3472
|
+
this._process.run('Exporting PDF', this._exportCollector.collect(report)
|
|
3473
|
+
.pipe(switchMap((collected) => from(this._pdfService.export(collected)))));
|
|
3474
|
+
}
|
|
3475
|
+
// React to each chat turn (fs-chat handles the request + persistence); we
|
|
3476
|
+
// only care about the report side effects this context's backend flags.
|
|
3477
|
+
onChatResponse(response) {
|
|
3478
|
+
const reportId = this.reportId();
|
|
3479
|
+
if (reportId == null) {
|
|
3480
|
+
return;
|
|
3481
|
+
}
|
|
3482
|
+
const reportResponse = response;
|
|
3483
|
+
// A structural change or a rename both re-fetch the report (session filter
|
|
3484
|
+
// values survive); a rename additionally notifies the host so it can
|
|
3485
|
+
// refresh a breadcrumb / title.
|
|
3486
|
+
if (reportResponse.reportChanged || reportResponse.reportRenamed) {
|
|
3487
|
+
this._loadReport(reportId, !!reportResponse.reportRenamed);
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
// Drag the divider between the chat and canvas panes (pointer-capture loop
|
|
3491
|
+
// outside Angular).
|
|
3492
|
+
onResizeStart(event) {
|
|
3493
|
+
event.preventDefault();
|
|
3494
|
+
const handle = event.target;
|
|
3495
|
+
handle.setPointerCapture(event.pointerId);
|
|
3496
|
+
this._split.nativeElement.classList.add('resizing');
|
|
3497
|
+
this._zone.runOutsideAngular(() => {
|
|
3498
|
+
const move = (e) => this._resizeTo(e.clientX);
|
|
3499
|
+
const end = () => {
|
|
3500
|
+
handle.releasePointerCapture(event.pointerId);
|
|
3501
|
+
handle.removeEventListener('pointermove', move);
|
|
3502
|
+
handle.removeEventListener('pointerup', end);
|
|
3503
|
+
handle.removeEventListener('pointercancel', end);
|
|
3504
|
+
this._split.nativeElement.classList.remove('resizing');
|
|
3505
|
+
};
|
|
3506
|
+
handle.addEventListener('pointermove', move);
|
|
3507
|
+
handle.addEventListener('pointerup', end);
|
|
3508
|
+
handle.addEventListener('pointercancel', end);
|
|
3509
|
+
});
|
|
3510
|
+
}
|
|
3511
|
+
_resizeTo(clientX) {
|
|
3512
|
+
const rect = this._split.nativeElement.getBoundingClientRect();
|
|
3513
|
+
if (rect.width === 0) {
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
const ratio = (clientX - rect.left) / rect.width;
|
|
3517
|
+
const clamped = Math.min(0.8, Math.max(0.2, ratio));
|
|
3518
|
+
this._chatPanel.nativeElement.style.flexBasis = `${clamped * 100}%`;
|
|
3519
|
+
}
|
|
3520
|
+
_loadReport(reportId, emitRenamed = false) {
|
|
3521
|
+
this._reportData.get(reportId)
|
|
3522
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
3523
|
+
.subscribe((report) => {
|
|
3524
|
+
this.report = report;
|
|
3525
|
+
this._filterState.init(report);
|
|
3526
|
+
this._buildReportConfig();
|
|
3527
|
+
if (emitRenamed) {
|
|
3528
|
+
this.renamed.emit(report);
|
|
3529
|
+
}
|
|
3530
|
+
this._cdRef.markForCheck();
|
|
3531
|
+
});
|
|
3532
|
+
}
|
|
3533
|
+
// Report-level filter groups in their saved order — the ones that render in
|
|
3534
|
+
// the toolbar (component-level groups render on their components).
|
|
3535
|
+
_reportGroups() {
|
|
3536
|
+
return (this.report?.filterGroups ?? [])
|
|
3537
|
+
.filter((group) => group.level === 'report' || group.level === 'both')
|
|
3538
|
+
.sort((a, b) => a.order - b.order);
|
|
3539
|
+
}
|
|
3540
|
+
// Whether the loaded report has any report-level filters OR a time-series
|
|
3541
|
+
// chart (which earns the runtime Frequency control) — gates the toolbar.
|
|
3542
|
+
get reportHasFilters() {
|
|
3543
|
+
return this._reportGroups().length > 0 || this._hasTimeSeriesChart();
|
|
3544
|
+
}
|
|
3545
|
+
// fs-filter reads its config only once (at init). The template keys the
|
|
3546
|
+
// toolbar fs-filter on this signature so it's recreated (and re-reads the
|
|
3547
|
+
// rebuilt config) whenever the report-level filter set changes.
|
|
3548
|
+
get reportFilterKey() {
|
|
3549
|
+
return [
|
|
3550
|
+
...this._reportGroups().map((group) => `${group.id}:${group.level}:${group.label}`),
|
|
3551
|
+
this._hasTimeSeriesChart() ? 'frequency' : '',
|
|
3552
|
+
].join('|');
|
|
3553
|
+
}
|
|
3554
|
+
// (Re)build the toolbar fs-filter config — report-level filter items, plus
|
|
3555
|
+
// the runtime Frequency control when the report has a time-series chart.
|
|
3556
|
+
_buildReportConfig() {
|
|
3557
|
+
const reportId = this.report?.id ?? null;
|
|
3558
|
+
const items = reportId
|
|
3559
|
+
? this._reportGroups().map((group) => filterItemForGroup(group, this._reportData, reportId, this._filterState.value(group.id)))
|
|
3560
|
+
: [];
|
|
3561
|
+
if (reportId && this._hasTimeSeriesChart()) {
|
|
3562
|
+
items.push({
|
|
3563
|
+
name: FREQUENCY_ITEM,
|
|
3564
|
+
type: ItemType.Select,
|
|
3565
|
+
label: 'Frequency',
|
|
3566
|
+
multiple: false,
|
|
3567
|
+
values: () => FREQUENCY_OPTIONS,
|
|
3568
|
+
default: this._filterState.frequency() ?? undefined,
|
|
3569
|
+
});
|
|
3570
|
+
}
|
|
3571
|
+
this.reportFilterConfig = {
|
|
3572
|
+
// Never touch the URL or persist filter state — report filters are
|
|
3573
|
+
// session-only, owned by ReportFilterStateService.
|
|
3574
|
+
queryParam: false,
|
|
3575
|
+
persist: false,
|
|
3576
|
+
items,
|
|
3577
|
+
change: (query) => {
|
|
3578
|
+
for (const { groupId, value } of groupValuesFromQuery(query ?? {}, this._reportGroups())) {
|
|
3579
|
+
this._filterState.setValue(groupId, value);
|
|
3580
|
+
}
|
|
3581
|
+
// Frequency isn't a filter group — it's a render-layer bucket override.
|
|
3582
|
+
this._filterState.setFrequency(query?.[FREQUENCY_ITEM] || null);
|
|
3583
|
+
},
|
|
3584
|
+
};
|
|
3585
|
+
}
|
|
3586
|
+
// The report has at least one time-series chart (x-axis is a time axis) — the
|
|
3587
|
+
// only kind the Frequency control affects.
|
|
3588
|
+
_hasTimeSeriesChart() {
|
|
3589
|
+
return (this.report?.pages ?? []).some((page) => page.components.some((component) => component.type === 'chart' && component.config?.xAxis?.kind === 'time'));
|
|
3590
|
+
}
|
|
3591
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3592
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FsAiReportComponent, isStandalone: true, selector: "fs-ai-report", inputs: { reportId: { classPropertyName: "reportId", publicName: "reportId", isSignal: true, isRequired: false, transformFunction: null }, basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { deleted: "deleted", renamed: "renamed" }, providers: [
|
|
3593
|
+
// Session filter state is per-open-report, not app-global; the export
|
|
3594
|
+
// services resolve filters through it, so they live at the same level.
|
|
3595
|
+
ReportFilterStateService,
|
|
3596
|
+
ReportExportCollectorService,
|
|
3597
|
+
ReportPptxService,
|
|
3598
|
+
ReportPdfService,
|
|
3599
|
+
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
3600
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-m2yVum4J.mjs').then((module) => module.default) }),
|
|
3601
|
+
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }], ngImport: i0, template: "<div class=\"fs-ai-report fs-column\">\n\n <div class=\"fs-ai-report__header fs-row.align-center\">\n <h2 class=\"fs-ai-report__title fs-flex\">{{ report?.name }}</h2>\n\n @if (report) {\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"editMode ? 'Done' : 'Edit layout'\"\n (click)=\"toggleEditMode()\">\n <mat-icon>{{ editMode ? 'lock' : 'open_with' }}</mat-icon>\n </button>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Export\"\n [fsMenuTriggerFor]=\"exportMenu\">\n <mat-icon>download</mat-icon>\n </button>\n <fs-menu #exportMenu>\n <ng-template\n fs-menu-item\n (click)=\"exportPowerpoint()\">\n Export PowerPoint\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPdf()\">\n Export PDF\n </ng-template>\n </fs-menu>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Report settings\"\n (click)=\"reportSettings()\">\n <mat-icon>settings</mat-icon>\n </button>\n }\n </div>\n\n <div\n #split\n class=\"fs-ai-report__split fs-row.align-start\">\n <fs-ai-chat\n #chatPanel\n class=\"chat\"\n [basePath]=\"basePath()\"\n [requestData]=\"{ reportId: reportId() }\"\n (response)=\"onChatResponse($event)\">\n </fs-ai-chat>\n\n <div\n class=\"resizer\"\n (pointerdown)=\"onResizeStart($event)\">\n </div>\n\n <div class=\"viewer fs-flex fs-column\">\n @if (report) {\n <!-- Report-level filters only (the report's actions live in the header\n above). fs-filter reads its config once at init, so it's keyed on\n the filter signature: when the report-level filter set changes the\n block is recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter [config]=\"reportFilterConfig\"></fs-filter>\n }\n }\n <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (editDone)=\"toggleEditMode()\">\n </app-report-canvas>\n }\n </div>\n </div>\n</div>\n", styles: [".fs-ai-report{height:100%;min-height:500px;display:flex;flex-direction:column}.fs-ai-report__header{flex:0 0 auto;margin-bottom:10px}.fs-ai-report__title{margin:0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fs-ai-report__split{flex:1 1 auto;min-height:0}.fs-ai-report .chat{display:block;flex:0 0 25%;width:100%;height:100%;min-height:400px;min-width:0;border:none}.fs-ai-report .viewer{display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden}.fs-ai-report .viewer fs-filter{flex:0 0 auto;margin-top:10px;margin-bottom:0}.fs-ai-report .viewer ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.fs-ai-report .resizer{flex:0 0 11px;align-self:stretch;display:flex;justify-content:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}.fs-ai-report .resizer:before{content:\"\";width:1px;background:#0000001f;transition:width .12s ease,background-color .12s ease}.fs-ai-report .resizer:hover:before{width:3px;background:var(--brand-primary-color)}.fs-ai-report__split.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.fs-ai-report__split.resizing .resizer:before{width:3px;background:var(--brand-primary-color)}.fs-ai-report__split.resizing .chat,.fs-ai-report__split.resizing .viewer{pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsMenuModule }, { kind: "component", type: i2$3.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$3.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "directive", type: i2$3.FsMenuTriggerDirective, selector: "[fsMenuTriggerFor]", inputs: ["fsMenuTriggerFor"] }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "introMessage"], outputs: ["response"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5$1.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ReportCanvasComponent, selector: "app-report-canvas", inputs: ["report", "editMode"], outputs: ["componentSettings", "reportChanged", "editDone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3602
|
+
}
|
|
3603
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportComponent, decorators: [{
|
|
3604
|
+
type: Component,
|
|
3605
|
+
args: [{ selector: 'fs-ai-report', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [
|
|
3606
|
+
// Session filter state is per-open-report, not app-global; the export
|
|
3607
|
+
// services resolve filters through it, so they live at the same level.
|
|
3608
|
+
ReportFilterStateService,
|
|
3609
|
+
ReportExportCollectorService,
|
|
3610
|
+
ReportPptxService,
|
|
3611
|
+
ReportPdfService,
|
|
3612
|
+
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
3613
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-m2yVum4J.mjs').then((module) => module.default) }),
|
|
3614
|
+
], imports: [
|
|
3615
|
+
FsFilterModule,
|
|
3616
|
+
FsMenuModule,
|
|
3617
|
+
FsAiChatComponent,
|
|
3618
|
+
MatButtonModule,
|
|
3619
|
+
MatIconModule,
|
|
3620
|
+
MatTooltipModule,
|
|
3621
|
+
ReportCanvasComponent,
|
|
3622
|
+
], template: "<div class=\"fs-ai-report fs-column\">\n\n <div class=\"fs-ai-report__header fs-row.align-center\">\n <h2 class=\"fs-ai-report__title fs-flex\">{{ report?.name }}</h2>\n\n @if (report) {\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"editMode ? 'Done' : 'Edit layout'\"\n (click)=\"toggleEditMode()\">\n <mat-icon>{{ editMode ? 'lock' : 'open_with' }}</mat-icon>\n </button>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Export\"\n [fsMenuTriggerFor]=\"exportMenu\">\n <mat-icon>download</mat-icon>\n </button>\n <fs-menu #exportMenu>\n <ng-template\n fs-menu-item\n (click)=\"exportPowerpoint()\">\n Export PowerPoint\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPdf()\">\n Export PDF\n </ng-template>\n </fs-menu>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Report settings\"\n (click)=\"reportSettings()\">\n <mat-icon>settings</mat-icon>\n </button>\n }\n </div>\n\n <div\n #split\n class=\"fs-ai-report__split fs-row.align-start\">\n <fs-ai-chat\n #chatPanel\n class=\"chat\"\n [basePath]=\"basePath()\"\n [requestData]=\"{ reportId: reportId() }\"\n (response)=\"onChatResponse($event)\">\n </fs-ai-chat>\n\n <div\n class=\"resizer\"\n (pointerdown)=\"onResizeStart($event)\">\n </div>\n\n <div class=\"viewer fs-flex fs-column\">\n @if (report) {\n <!-- Report-level filters only (the report's actions live in the header\n above). fs-filter reads its config once at init, so it's keyed on\n the filter signature: when the report-level filter set changes the\n block is recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter [config]=\"reportFilterConfig\"></fs-filter>\n }\n }\n <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (editDone)=\"toggleEditMode()\">\n </app-report-canvas>\n }\n </div>\n </div>\n</div>\n", styles: [".fs-ai-report{height:100%;min-height:500px;display:flex;flex-direction:column}.fs-ai-report__header{flex:0 0 auto;margin-bottom:10px}.fs-ai-report__title{margin:0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fs-ai-report__split{flex:1 1 auto;min-height:0}.fs-ai-report .chat{display:block;flex:0 0 25%;width:100%;height:100%;min-height:400px;min-width:0;border:none}.fs-ai-report .viewer{display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden}.fs-ai-report .viewer fs-filter{flex:0 0 auto;margin-top:10px;margin-bottom:0}.fs-ai-report .viewer ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.fs-ai-report .resizer{flex:0 0 11px;align-self:stretch;display:flex;justify-content:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}.fs-ai-report .resizer:before{content:\"\";width:1px;background:#0000001f;transition:width .12s ease,background-color .12s ease}.fs-ai-report .resizer:hover:before{width:3px;background:var(--brand-primary-color)}.fs-ai-report__split.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.fs-ai-report__split.resizing .resizer:before{width:3px;background:var(--brand-primary-color)}.fs-ai-report__split.resizing .chat,.fs-ai-report__split.resizing .viewer{pointer-events:none}\n"] }]
|
|
3623
|
+
}], ctorParameters: () => [], propDecorators: { _split: [{
|
|
3624
|
+
type: ViewChild,
|
|
3625
|
+
args: ['split', { static: true }]
|
|
3626
|
+
}], _chatPanel: [{
|
|
3627
|
+
type: ViewChild,
|
|
3628
|
+
args: ['chatPanel', { static: true, read: ElementRef }]
|
|
3629
|
+
}] } });
|
|
3630
|
+
|
|
3241
3631
|
/*
|
|
3242
3632
|
* Public API Surface of @firestitch/reports
|
|
3243
3633
|
*
|
|
@@ -3250,5 +3640,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3250
3640
|
* Generated bundle index. Do not edit.
|
|
3251
3641
|
*/
|
|
3252
3642
|
|
|
3253
|
-
export {
|
|
3254
|
-
//# sourceMappingURL=firestitch-report-firestitch-report-
|
|
3643
|
+
export { FsAiReportsComponent as F, REPORT_CHART_COLORS_CSS as R, ReportComponent as a, FsAiReportComponent as b, ReportData as c, ReportService as d, ReportFilterStateService as e, FREQUENCY_OPTIONS as f };
|
|
3644
|
+
//# sourceMappingURL=firestitch-report-firestitch-report-BclPOljK.mjs.map
|