@firestitch/report 18.3.1 → 18.4.0
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/data/report.data.d.ts +1 -0
- package/app/reports/export/report-pptx.service.d.ts +1 -0
- package/app/reports/interfaces/report.interface.d.ts +6 -5
- package/app/reports/option-builder.d.ts +3 -1
- package/app/reports/views/reports/fs-ai-reports.component.d.ts +4 -2
- package/esm2022/app/reports/data/report.data.mjs +14 -4
- package/esm2022/app/reports/export/report-pptx.service.mjs +33 -4
- package/esm2022/app/reports/interfaces/report.interface.mjs +5 -9
- package/esm2022/app/reports/option-builder.mjs +30 -8
- package/esm2022/app/reports/views/reports/fs-ai-reports.component.mjs +177 -76
- package/fesm2022/{firestitch-report-echarts-Dg5PjbjT.mjs → firestitch-report-echarts-CFkoNvo_.mjs} +2 -2
- package/fesm2022/{firestitch-report-echarts-Dg5PjbjT.mjs.map → firestitch-report-echarts-CFkoNvo_.mjs.map} +1 -1
- package/fesm2022/{firestitch-report-firestitch-report-JORFp_xa.mjs → firestitch-report-firestitch-report-PZzkYYTD.mjs} +259 -103
- package/fesm2022/firestitch-report-firestitch-report-PZzkYYTD.mjs.map +1 -0
- package/fesm2022/firestitch-report.mjs +1 -1
- package/package.json +1 -1
- package/fesm2022/firestitch-report-firestitch-report-JORFp_xa.mjs.map +0 -1
|
@@ -2,10 +2,10 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { inject, Injectable, Component, ChangeDetectionStrategy, Input, signal, DestroyRef, ViewChild, input, output, computed, viewChild, ElementRef, effect, untracked, EventEmitter, ChangeDetectorRef, NgZone, Output, HostBinding, ViewChildren, CUSTOM_ELEMENTS_SCHEMA, model } from '@angular/core';
|
|
3
3
|
import * as i1$1 from '@angular/forms';
|
|
4
4
|
import { FormsModule, ControlContainer, NgForm } from '@angular/forms';
|
|
5
|
-
import * as i3$
|
|
5
|
+
import * as i3$3 from '@angular/material/button';
|
|
6
6
|
import { MatIconButton, MatButton, MatButtonModule } from '@angular/material/button';
|
|
7
7
|
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, MatDialog } from '@angular/material/dialog';
|
|
8
|
-
import * as
|
|
8
|
+
import * as i5$2 from '@angular/material/icon';
|
|
9
9
|
import { MatIcon, MatIconModule } from '@angular/material/icon';
|
|
10
10
|
import * as i2$5 from '@angular/material/tooltip';
|
|
11
11
|
import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip';
|
|
@@ -57,12 +57,12 @@ import { FsDatePickerModule } from '@firestitch/datepicker';
|
|
|
57
57
|
import { MatOption } from '@angular/material/core';
|
|
58
58
|
import { guid } from '@firestitch/common';
|
|
59
59
|
import { Router, ActivatedRoute, RouterLink } from '@angular/router';
|
|
60
|
+
import * as i1$4 from '@firestitch/chip';
|
|
61
|
+
import { FsChipModule } from '@firestitch/chip';
|
|
60
62
|
import * as i4 from '@firestitch/skeleton';
|
|
61
63
|
import { FsSkeletonModule } from '@firestitch/skeleton';
|
|
62
|
-
import * as
|
|
64
|
+
import * as i4$1 from '@firestitch/tree';
|
|
63
65
|
import { FsTreeComponent, TreeDragMode, TreeActionType, FsTreeModule } from '@firestitch/tree';
|
|
64
|
-
import * as i1$4 from '@firestitch/chip';
|
|
65
|
-
import { FsChipModule } from '@firestitch/chip';
|
|
66
66
|
import * as i2$4 from '@firestitch/date';
|
|
67
67
|
import { FsDateModule } from '@firestitch/date';
|
|
68
68
|
import { FsMarkdownRendererComponent, FsMarkdownEditorComponent } from '@firestitch/markdown-editor';
|
|
@@ -123,14 +123,23 @@ class ReportData {
|
|
|
123
123
|
...config,
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
|
-
//
|
|
127
|
-
//
|
|
126
|
+
// Soft-delete, whatever the folder holds: its contents leave the listing with
|
|
127
|
+
// it, and undeleteFolder() brings the lot back.
|
|
128
128
|
deleteFolder(reportFolderId, config = {}) {
|
|
129
129
|
return this._api.delete(this._path(`folders/${reportFolderId}`), {}, {
|
|
130
130
|
key: 'folder',
|
|
131
131
|
...config,
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
|
+
// Restore a soft-deleted folder through its own action endpoint — the folder
|
|
135
|
+
// PUT does not accept `state`. Any deleted folder above it is restored too, so
|
|
136
|
+
// it lands back where it was.
|
|
137
|
+
undeleteFolder(reportFolderId, config = {}) {
|
|
138
|
+
return this._api.post(this._path(`folders/${reportFolderId}/undelete`), {}, {
|
|
139
|
+
key: 'folder',
|
|
140
|
+
...config,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
134
143
|
rename(reportId, name, config = {}) {
|
|
135
144
|
return this._api.put(this._path(`${reportId}`), { name }, {
|
|
136
145
|
key: 'report',
|
|
@@ -216,7 +225,8 @@ class ReportData {
|
|
|
216
225
|
});
|
|
217
226
|
}
|
|
218
227
|
// Restore a soft-deleted report through its own action endpoint — the report
|
|
219
|
-
// PUT does not accept `state`.
|
|
228
|
+
// PUT does not accept `state`. Its folder is restored too when it is deleted,
|
|
229
|
+
// or inside one that is, so the report lands back where it was.
|
|
220
230
|
undelete(reportId, config = {}) {
|
|
221
231
|
return this._api.post(this._path(`${reportId}/undelete`), {}, {
|
|
222
232
|
key: 'report',
|
|
@@ -493,19 +503,15 @@ var ReportNodeType;
|
|
|
493
503
|
ReportNodeType["Folder"] = "folder";
|
|
494
504
|
ReportNodeType["Report"] = "report";
|
|
495
505
|
})(ReportNodeType || (ReportNodeType = {}));
|
|
496
|
-
// Reports are soft-deletable
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
//
|
|
506
|
+
// Reports and folders are both soft-deletable. A deleted report keeps its pages
|
|
507
|
+
// and components; a deleted folder keeps everything filed under it, which leaves
|
|
508
|
+
// the listing with it and comes back with it. "Show Deleted" shows the whole
|
|
509
|
+
// tree, live and deleted together, so any of it can be restored in place.
|
|
500
510
|
var ReportState;
|
|
501
511
|
(function (ReportState) {
|
|
502
512
|
ReportState["Active"] = "active";
|
|
503
513
|
ReportState["Deleted"] = "deleted";
|
|
504
514
|
})(ReportState || (ReportState = {}));
|
|
505
|
-
const ReportStates = [
|
|
506
|
-
{ name: 'Active', value: ReportState.Active },
|
|
507
|
-
{ name: 'Deleted', value: ReportState.Deleted },
|
|
508
|
-
];
|
|
509
515
|
|
|
510
516
|
// A date-range boundary as the picked LOCAL calendar day (no time, no tz).
|
|
511
517
|
// Date-range filters are calendar-day semantics, so the boundary must carry
|
|
@@ -990,8 +996,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
990
996
|
// unrelated groups. Explicitly opting out — colorBy: {column: ''} or a
|
|
991
997
|
// config.legend of 'hidden' — still wins, so a caller that wants one flat color
|
|
992
998
|
// can say so.
|
|
999
|
+
//
|
|
1000
|
+
// "Bar chart" is the type the lone measure is actually DRAWN as: its own type
|
|
1001
|
+
// when it names one, which can turn a line chart's single series into bars or
|
|
1002
|
+
// a bar chart's into a line.
|
|
993
1003
|
function isColorBy(config) {
|
|
994
|
-
if (config.splitBy?.column || (config.chartType ?? 'bar') !== 'bar') {
|
|
1004
|
+
if (config.splitBy?.column || (config.measures?.[0]?.type ?? config.chartType ?? 'bar') !== 'bar') {
|
|
995
1005
|
return false;
|
|
996
1006
|
}
|
|
997
1007
|
if (config.colorBy) {
|
|
@@ -1035,6 +1045,7 @@ function extractChartSeries(component, data) {
|
|
|
1035
1045
|
series.push({
|
|
1036
1046
|
name: String(groupValue ?? '—'),
|
|
1037
1047
|
values: rawCategories.map((category) => numeric(byCategory.get(category))),
|
|
1048
|
+
type: measure.type,
|
|
1038
1049
|
});
|
|
1039
1050
|
}
|
|
1040
1051
|
}
|
|
@@ -1047,11 +1058,19 @@ function extractChartSeries(component, data) {
|
|
|
1047
1058
|
series.push({
|
|
1048
1059
|
name: measure.label ?? measure.column,
|
|
1049
1060
|
values: rawCategories.map((category) => numeric(byCategory.get(category))),
|
|
1061
|
+
type: measure.type,
|
|
1050
1062
|
});
|
|
1051
1063
|
}
|
|
1052
1064
|
}
|
|
1053
1065
|
return { categories, series };
|
|
1054
1066
|
}
|
|
1067
|
+
// The type each extracted series is drawn as: its measure's own type when it
|
|
1068
|
+
// names one, the chart's chartType otherwise. More than one distinct type is a
|
|
1069
|
+
// combo chart. Shared with the PowerPoint export so both media draw each series
|
|
1070
|
+
// the same way.
|
|
1071
|
+
function seriesTypes(extracted, chartType) {
|
|
1072
|
+
return extracted.series.map((series) => series.type ?? chartType);
|
|
1073
|
+
}
|
|
1055
1074
|
// The one pure mapping from our standard camelCase chart config + the data
|
|
1056
1075
|
// endpoint's rows to an ECharts option. Everything visual that isn't in the
|
|
1057
1076
|
// registered 'report' theme is decided here, so report standards have exactly
|
|
@@ -1069,11 +1088,17 @@ function buildAxisOption(component, data, chartType) {
|
|
|
1069
1088
|
const extracted = extractChartSeries(component, data);
|
|
1070
1089
|
const horizontal = config.orientation === 'horizontal';
|
|
1071
1090
|
const colorBy = isColorBy(config);
|
|
1091
|
+
const types = seriesTypes(extracted, chartType);
|
|
1092
|
+
const combo = new Set(types).size > 1;
|
|
1072
1093
|
// colorBy emits one series per category; overlapping them (barGap -100%) makes
|
|
1073
1094
|
// each render at the full category-band width — one full-width, distinctly
|
|
1074
1095
|
// colored bar per category — instead of side-by-side slivers. The layout props
|
|
1075
1096
|
// are read from the first series of the coordinate system.
|
|
1076
|
-
|
|
1097
|
+
//
|
|
1098
|
+
// Stacking sums every series that shares a stack name, whatever its type. In
|
|
1099
|
+
// a combo each type stacks only with its own kind, so a line of a rate never
|
|
1100
|
+
// sits on top of the bars' total.
|
|
1101
|
+
const series = extracted.series.map((extractedSeries, index) => buildSeries(extractedSeries.name, extractedSeries.values, types[index], config.stacked ? (combo ? `total-${types[index]}` : 'total') : undefined, colorBy && index === 0, combo));
|
|
1077
1102
|
const granularity = bucketGranularity(data);
|
|
1078
1103
|
const categoryAxis = {
|
|
1079
1104
|
type: 'category',
|
|
@@ -1143,13 +1168,13 @@ function axisGrid(position) {
|
|
|
1143
1168
|
return { ...base, top: 32 };
|
|
1144
1169
|
}
|
|
1145
1170
|
}
|
|
1146
|
-
function buildSeries(name, values,
|
|
1147
|
-
if (
|
|
1171
|
+
function buildSeries(name, values, type, stack, overlap, combo) {
|
|
1172
|
+
if (type === 'bar') {
|
|
1148
1173
|
return {
|
|
1149
1174
|
name,
|
|
1150
1175
|
type: 'bar',
|
|
1151
1176
|
data: values,
|
|
1152
|
-
stack
|
|
1177
|
+
stack,
|
|
1153
1178
|
barMaxWidth: 48,
|
|
1154
1179
|
// colorBy: overlap the per-category series so each bar renders full-width.
|
|
1155
1180
|
...(overlap ? { barGap: '-100%', barCategoryGap: '20%' } : {}),
|
|
@@ -1159,10 +1184,13 @@ function buildSeries(name, values, chartType, stacked, overlap) {
|
|
|
1159
1184
|
name,
|
|
1160
1185
|
type: 'line',
|
|
1161
1186
|
data: values,
|
|
1162
|
-
stack
|
|
1187
|
+
stack,
|
|
1163
1188
|
smooth: true,
|
|
1164
1189
|
symbolSize: 6,
|
|
1165
|
-
areaStyle:
|
|
1190
|
+
areaStyle: type === 'area' ? { opacity: 0.25 } : undefined,
|
|
1191
|
+
// In a combo the line is drawn over the bars whatever order the measures
|
|
1192
|
+
// come in; at the same z a later bar series would paint across it.
|
|
1193
|
+
...(combo ? { z: 3 } : {}),
|
|
1166
1194
|
};
|
|
1167
1195
|
}
|
|
1168
1196
|
function buildPieOption(component, data, donut) {
|
|
@@ -3882,7 +3910,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3882
3910
|
// complete synchronously after setOption.
|
|
3883
3911
|
async function renderChartImage(component, data, width, height, pixelRatio = 3) {
|
|
3884
3912
|
// The same tree-shaken echarts build + 'report' theme the live canvas uses.
|
|
3885
|
-
const echarts = (await import('./firestitch-report-echarts-
|
|
3913
|
+
const echarts = (await import('./firestitch-report-echarts-CFkoNvo_.mjs')).default;
|
|
3886
3914
|
const host = document.createElement('div');
|
|
3887
3915
|
host.style.width = `${width}px`;
|
|
3888
3916
|
host.style.height = `${height}px`;
|
|
@@ -4416,7 +4444,7 @@ class ReportPptxService {
|
|
|
4416
4444
|
const legendPosition = colorBy
|
|
4417
4445
|
? 'hidden'
|
|
4418
4446
|
: resolveLegendPosition(config, extracted.series.length);
|
|
4419
|
-
|
|
4447
|
+
const options = {
|
|
4420
4448
|
x, y, w, h,
|
|
4421
4449
|
chartColors: REPORT_CHART_COLORS,
|
|
4422
4450
|
showLegend: legendPosition !== 'hidden',
|
|
@@ -4434,7 +4462,36 @@ class ReportPptxService {
|
|
|
4434
4462
|
valAxisTitle: config.measures?.[0]?.label,
|
|
4435
4463
|
valAxisTitleFontSize: 10,
|
|
4436
4464
|
dataBorder: { pt: 0, color: 'FFFFFF' },
|
|
4437
|
-
}
|
|
4465
|
+
};
|
|
4466
|
+
// The type each chartData entry is drawn as. A pie is one entry of its own
|
|
4467
|
+
// type; colorBy collapsed its series into one, drawn as the lone measure's.
|
|
4468
|
+
const dataTypes = pie
|
|
4469
|
+
? [chartType]
|
|
4470
|
+
: seriesTypes(extracted, chartType).slice(0, colorBy ? 1 : undefined);
|
|
4471
|
+
this._addChart(slide, typeMap, dataTypes.length ? dataTypes : [chartType], chartData, options);
|
|
4472
|
+
}
|
|
4473
|
+
// One native chart when every entry is drawn as the same type. A combo is
|
|
4474
|
+
// pptxgenjs's multi-type chart: one entry per type, all on the same category
|
|
4475
|
+
// and value axes. It restarts the palette for every type, so each type is
|
|
4476
|
+
// handed the colors its series have on screen.
|
|
4477
|
+
_addChart(slide, typeMap, dataTypes, chartData, options) {
|
|
4478
|
+
const distinct = [...new Set(dataTypes)];
|
|
4479
|
+
if (distinct.length === 1) {
|
|
4480
|
+
slide.addChart(typeMap[distinct[0]], chartData, options);
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
slide.addChart(distinct.map((type) => {
|
|
4484
|
+
const indexes = dataTypes
|
|
4485
|
+
.map((dataType, index) => (dataType === type ? index : -1))
|
|
4486
|
+
.filter((index) => index >= 0);
|
|
4487
|
+
return {
|
|
4488
|
+
type: typeMap[type],
|
|
4489
|
+
data: indexes.map((index) => chartData[index]),
|
|
4490
|
+
options: {
|
|
4491
|
+
chartColors: indexes.map((index) => REPORT_CHART_COLORS[index % REPORT_CHART_COLORS.length]),
|
|
4492
|
+
},
|
|
4493
|
+
};
|
|
4494
|
+
}), options);
|
|
4438
4495
|
}
|
|
4439
4496
|
_renderTable(slide, component, data, x, y, w, h) {
|
|
4440
4497
|
const config = component.config;
|
|
@@ -4970,7 +5027,7 @@ class ReportComponent {
|
|
|
4970
5027
|
ReportExportService,
|
|
4971
5028
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
4972
5029
|
// this route's chunk.
|
|
4973
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
5030
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CFkoNvo_.mjs').then((module) => module.default) }),
|
|
4974
5031
|
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }, { propertyName: "_canvas", first: true, predicate: ReportCanvasComponent, descendants: 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 && editMode) {\n <!-- What layout editing can add or switch to, and the way out of it \u2014\n the same outlined buttons as the fs-ai-report toolbar. -->\n <button\n mat-stroked-button\n type=\"button\"\n matTooltip=\"Add a block of text\"\n (click)=\"addText()\">\n <mat-icon>title</mat-icon>\n Add text\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n class=\"master-toggle\"\n [class.active]=\"isMasterMode\"\n [matTooltip]=\"isMasterMode ? 'Back to editing this page' : 'Edit the header and footer shown on every page'\"\n (click)=\"toggleMasterMode()\">\n <mat-icon>web_asset</mat-icon>\n Header & footer\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"toggleEditMode()\">\n <mat-icon>check</mat-icon>\n Done editing\n </button>\n }\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 [disabled]=\"editMode\">\n <mat-icon>\n dashboard_customize\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 <ng-template\n fs-menu-item\n (click)=\"exportData()\">\n <mat-icon>\n table_view\n </mat-icon>\n Export Raw Data\n </ng-template>\n @if ((report?.pages?.length ?? 0) > 1) {\n <ng-template\n fs-menu-item\n (click)=\"exportData(true)\">\n <mat-icon>\n table_view\n </mat-icon>\n Export Raw Data (This Page)\n </ng-template>\n }\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 </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%}.master-toggle.active{background:#eceff3}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: FsAutocompleteChipsModule }, { kind: "component", type: i2$3.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$3.FsAutocompleteObjectDirective, selector: "[fsAutocompleteObject],[fsAutocompleteChipsTemplate]" }, { kind: "directive", type: i2$3.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$1.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$1.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "ready", "introMessage", "showThinking"], outputs: ["response", "showThinkingChange"] }, { kind: "component", type: MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: 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", "pageId"], outputs: ["componentSettings", "reportChanged", "pageChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4975
5032
|
}
|
|
4976
5033
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportComponent, decorators: [{
|
|
@@ -4985,7 +5042,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4985
5042
|
ReportExportService,
|
|
4986
5043
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
4987
5044
|
// this route's chunk.
|
|
4988
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
5045
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CFkoNvo_.mjs').then((module) => module.default) }),
|
|
4989
5046
|
], imports: [
|
|
4990
5047
|
FormsModule,
|
|
4991
5048
|
FsAutocompleteChipsModule,
|
|
@@ -5662,17 +5719,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
5662
5719
|
// report in no folder is a root node, which is where every report sat before
|
|
5663
5720
|
// folders existed, so nothing had to be migrated or bucketed as "uncategorized".
|
|
5664
5721
|
//
|
|
5665
|
-
//
|
|
5666
|
-
//
|
|
5667
|
-
//
|
|
5668
|
-
//
|
|
5669
|
-
//
|
|
5670
|
-
//
|
|
5671
|
-
//
|
|
5722
|
+
// Folders and reports are both soft-deletable, and a folder deletes whatever it
|
|
5723
|
+
// holds. Only the folder's own row is written: everything under it drops out of
|
|
5724
|
+
// the listing because it sits under a deleted folder, and comes back with it.
|
|
5725
|
+
// "Show Deleted" shows the WHOLE tree — live and deleted together, each deleted
|
|
5726
|
+
// row where it was filed — and on anything deleted the kebab offers Restore
|
|
5727
|
+
// instead. Restoring also restores any deleted folder above the row, so it lands
|
|
5728
|
+
// back where it was rather than at the root.
|
|
5729
|
+
//
|
|
5730
|
+
// Every folder starts collapsed; the tree remembers what was opened across its
|
|
5731
|
+
// own reloads (a rename, a move, the Show Deleted toggle) and across a search.
|
|
5672
5732
|
class FsAiReportsComponent {
|
|
5673
|
-
// How deep the tree opens on load. Folders are audience buckets, not a
|
|
5674
|
-
// filesystem, so there is nothing worth hiding behind a closed chevron.
|
|
5675
|
-
static EXPAND_LEVEL = 10;
|
|
5676
5733
|
// API base for the reports endpoints; override to mount elsewhere.
|
|
5677
5734
|
basePath = input('reports');
|
|
5678
5735
|
// Where a row links to; ':id' is replaced with the report id. Default takes
|
|
@@ -5696,6 +5753,10 @@ class FsAiReportsComponent {
|
|
|
5696
5753
|
_destroyRef = inject(DestroyRef);
|
|
5697
5754
|
// The filter bar's current query — carries `state` when Show Deleted is on.
|
|
5698
5755
|
_query = {};
|
|
5756
|
+
// The folders that were open when a search started; null while no search is
|
|
5757
|
+
// running. The search opens every match's path over the top of them, and
|
|
5758
|
+
// clearing it puts these back.
|
|
5759
|
+
_expandedBeforeSearch = null;
|
|
5699
5760
|
constructor() {
|
|
5700
5761
|
effect(() => {
|
|
5701
5762
|
this._reportData.basePath = this.basePath();
|
|
@@ -5713,11 +5774,6 @@ class FsAiReportsComponent {
|
|
|
5713
5774
|
openLink(report) {
|
|
5714
5775
|
return this._patternCommands(this.openPattern(), report.id);
|
|
5715
5776
|
}
|
|
5716
|
-
// A deleted report can't be opened — the backend only assembles active ones —
|
|
5717
|
-
// so in Show Deleted mode its name renders as plain text rather than a link.
|
|
5718
|
-
isDeleted(report) {
|
|
5719
|
-
return report?.state === ReportState.Deleted;
|
|
5720
|
-
}
|
|
5721
5777
|
// Prompt for a title, create the report — filed under reportFolderId when a
|
|
5722
5778
|
// folder's kebab asked, at the root otherwise — then route to it (createPattern).
|
|
5723
5779
|
createReport(reportFolderId = null) {
|
|
@@ -5752,13 +5808,16 @@ class FsAiReportsComponent {
|
|
|
5752
5808
|
type: ItemType.Keyword,
|
|
5753
5809
|
label: 'Search',
|
|
5754
5810
|
},
|
|
5811
|
+
// A checkbox rather than the usual Status select with a "Show Deleted"
|
|
5812
|
+
// isolate. Isolating the deleted rows would strand them: a deleted
|
|
5813
|
+
// report in a live folder arrives without the folder it sits in, and a
|
|
5814
|
+
// tree cannot place a node whose parent it never received. So the
|
|
5815
|
+
// toggle asks for everything, and the rows mark what is deleted.
|
|
5755
5816
|
{
|
|
5756
5817
|
name: 'state',
|
|
5757
|
-
type: ItemType.
|
|
5758
|
-
label: '
|
|
5759
|
-
|
|
5760
|
-
values: () => ReportStates,
|
|
5761
|
-
isolate: { label: 'Show Deleted', value: ReportState.Deleted },
|
|
5818
|
+
type: ItemType.Checkbox,
|
|
5819
|
+
label: 'Show Deleted',
|
|
5820
|
+
checked: [ReportState.Active, ReportState.Deleted].join(','),
|
|
5762
5821
|
},
|
|
5763
5822
|
],
|
|
5764
5823
|
actions: [
|
|
@@ -5791,15 +5850,19 @@ class FsAiReportsComponent {
|
|
|
5791
5850
|
click: () => this.createReport(),
|
|
5792
5851
|
},
|
|
5793
5852
|
],
|
|
5794
|
-
// The two items are answered in different places and
|
|
5795
|
-
// The keyword drives the tree's own search, which is the whole
|
|
5796
|
-
// searching a tree — it reveals a match nested inside collapsed
|
|
5797
|
-
// rather than only filtering what is already on screen. `state` is
|
|
5798
|
-
// Show Deleted toggle, and only the server can answer that.
|
|
5853
|
+
// The two items are answered in different places, and each only when it
|
|
5854
|
+
// changed. The keyword drives the tree's own search, which is the whole
|
|
5855
|
+
// point of searching a tree — it reveals a match nested inside collapsed
|
|
5856
|
+
// folders rather than only filtering what is already on screen. `state` is
|
|
5857
|
+
// the Show Deleted toggle, and only the server can answer that. Running the
|
|
5858
|
+
// search on a toggle would clear it, and clearing collapses the tree.
|
|
5799
5859
|
change: (query) => {
|
|
5860
|
+
const keywordChanged = String(query?.keyword ?? '') !== String(this._query.keyword ?? '');
|
|
5800
5861
|
const stateChanged = String(query?.state ?? '') !== String(this._query.state ?? '');
|
|
5801
5862
|
this._query = { ...query };
|
|
5802
|
-
|
|
5863
|
+
if (keywordChanged) {
|
|
5864
|
+
this._search(query?.keyword);
|
|
5865
|
+
}
|
|
5803
5866
|
if (stateChanged) {
|
|
5804
5867
|
this._load();
|
|
5805
5868
|
}
|
|
@@ -5807,22 +5870,26 @@ class FsAiReportsComponent {
|
|
|
5807
5870
|
};
|
|
5808
5871
|
}
|
|
5809
5872
|
_initTreeConfig() {
|
|
5873
|
+
// No expandLevel: every folder starts collapsed.
|
|
5810
5874
|
this.treeConfig = {
|
|
5811
5875
|
data: [],
|
|
5812
5876
|
childrenName: 'children',
|
|
5813
|
-
expandLevel: FsAiReportsComponent.EXPAND_LEVEL,
|
|
5814
5877
|
draggable: true,
|
|
5815
5878
|
// The whole row is the grip and the handle stays hidden; a press only
|
|
5816
5879
|
// becomes a drag once the pointer moves, so the kebab, the chevron and the
|
|
5817
5880
|
// report link all stay clickable.
|
|
5818
5881
|
dragMode: TreeDragMode.Node,
|
|
5882
|
+
// Anything deleted is restored before it is moved — the backend refuses
|
|
5883
|
+
// to edit a deleted row, and a drag would only snap back.
|
|
5884
|
+
canDrag: (node) => !this._isDeletedNode(node),
|
|
5819
5885
|
// canDrop is asked about the node being DRAGGED, so only `toParent`
|
|
5820
5886
|
// describes the destination — reading node.level/node.parent here would
|
|
5821
5887
|
// describe where it came from. A report can never be a parent because its
|
|
5822
5888
|
// type says so, and that single test is the whole "folders nest, reports
|
|
5823
|
-
// are leaves" rule on this side.
|
|
5889
|
+
// are leaves" rule on this side. A deleted folder is no destination
|
|
5890
|
+
// either: whatever landed in it would vanish from the live listing.
|
|
5824
5891
|
canDrop: (node, fromParent, toParent) => {
|
|
5825
|
-
return !toParent || toParent
|
|
5892
|
+
return !toParent || (this._isFolderNode(toParent) && !this._isDeletedNode(toParent));
|
|
5826
5893
|
},
|
|
5827
5894
|
changeReorder: (event) => this._move(event),
|
|
5828
5895
|
nodeClass: (node) => node.data?.type,
|
|
@@ -5846,12 +5913,12 @@ class FsAiReportsComponent {
|
|
|
5846
5913
|
},
|
|
5847
5914
|
{
|
|
5848
5915
|
label: 'New report',
|
|
5849
|
-
show: (node) => this._isFolderNode(node),
|
|
5916
|
+
show: (node) => this._isFolderNode(node) && !this._isDeletedNode(node),
|
|
5850
5917
|
click: (node) => this.createReport(node.data?.id),
|
|
5851
5918
|
},
|
|
5852
5919
|
{
|
|
5853
5920
|
label: 'New folder',
|
|
5854
|
-
show: (node) => this._isFolderNode(node),
|
|
5921
|
+
show: (node) => this._isFolderNode(node) && !this._isDeletedNode(node),
|
|
5855
5922
|
click: (node) => this._createFolder(node.data?.id),
|
|
5856
5923
|
},
|
|
5857
5924
|
{
|
|
@@ -5859,8 +5926,8 @@ class FsAiReportsComponent {
|
|
|
5859
5926
|
show: (node) => !this._isDeletedNode(node),
|
|
5860
5927
|
click: (node) => this._delete(node),
|
|
5861
5928
|
},
|
|
5862
|
-
//
|
|
5863
|
-
//
|
|
5929
|
+
// On every deleted row, including a live one that is only hidden by
|
|
5930
|
+
// a deleted folder above it — restoring that brings the folder back.
|
|
5864
5931
|
{
|
|
5865
5932
|
label: 'Restore',
|
|
5866
5933
|
show: (node) => this._isDeletedNode(node),
|
|
@@ -5875,66 +5942,128 @@ class FsAiReportsComponent {
|
|
|
5875
5942
|
return node?.data?.type === ReportNodeType.Folder;
|
|
5876
5943
|
}
|
|
5877
5944
|
_isDeletedNode(node) {
|
|
5878
|
-
return
|
|
5945
|
+
return !!node?.data?.deleted;
|
|
5879
5946
|
}
|
|
5880
5947
|
// Drive the tree's built-in search: it collapses everything, hides every node,
|
|
5881
5948
|
// then unhides each match and walks its ancestors open, so a report several
|
|
5882
|
-
// folders deep surfaces with its path expanded. Clearing
|
|
5883
|
-
//
|
|
5884
|
-
// left fully collapsed the moment somebody empties the search box.
|
|
5949
|
+
// folders deep surfaces with its path expanded. Clearing collapses everything
|
|
5950
|
+
// again, so the folders that were open before the search are put back.
|
|
5885
5951
|
_search(keyword) {
|
|
5886
5952
|
const tree = this._tree();
|
|
5887
5953
|
if (!tree) {
|
|
5888
5954
|
return;
|
|
5889
5955
|
}
|
|
5890
5956
|
if (keyword) {
|
|
5957
|
+
this._expandedBeforeSearch ??= this._expandedKeys();
|
|
5891
5958
|
tree.search(keyword);
|
|
5892
5959
|
return;
|
|
5893
5960
|
}
|
|
5894
5961
|
tree.clearSearch();
|
|
5895
|
-
|
|
5962
|
+
this._expandKeys(this._expandedBeforeSearch ?? []);
|
|
5963
|
+
this._expandedBeforeSearch = null;
|
|
5896
5964
|
}
|
|
5897
5965
|
_load() {
|
|
5898
|
-
|
|
5966
|
+
// Only the Show Deleted toggle is the server's to answer — the keyword is
|
|
5967
|
+
// the tree's own search — and an unchecked toggle sends nothing, leaving the
|
|
5968
|
+
// backend's active-only default in charge.
|
|
5969
|
+
const query = this._query.state ? { state: this._query.state } : {};
|
|
5970
|
+
this._reportData.tree(query)
|
|
5899
5971
|
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
5900
|
-
.subscribe((
|
|
5901
|
-
const nodes = this._buildNodes(
|
|
5972
|
+
.subscribe((reportTree) => {
|
|
5973
|
+
const nodes = this._buildNodes(reportTree);
|
|
5902
5974
|
this.nodes.set(nodes);
|
|
5903
5975
|
// The tree reads `config` once, in its own ngOnInit. The first load
|
|
5904
5976
|
// lands before it exists, so the data has to arrive through the config;
|
|
5905
5977
|
// every later one has to go through the instance or nothing changes.
|
|
5906
5978
|
this.treeConfig = { ...this.treeConfig, data: nodes };
|
|
5907
|
-
this._tree()
|
|
5979
|
+
const tree = this._tree();
|
|
5980
|
+
if (!tree) {
|
|
5981
|
+
return;
|
|
5982
|
+
}
|
|
5983
|
+
// setData() rebuilds every node collapsed, so a rename or a move would
|
|
5984
|
+
// otherwise slam shut every folder somebody had opened. Keys are stable
|
|
5985
|
+
// across loads ('f:12'), so what was open is reopened by key — and a
|
|
5986
|
+
// folder that no longer comes back simply is not.
|
|
5987
|
+
const expandedKeys = this._expandedKeys();
|
|
5988
|
+
tree.setData(nodes);
|
|
5989
|
+
this._expandKeys(expandedKeys);
|
|
5990
|
+
const keyword = String(this._query.keyword ?? '');
|
|
5991
|
+
if (keyword) {
|
|
5992
|
+
tree.search(keyword);
|
|
5993
|
+
}
|
|
5908
5994
|
});
|
|
5909
5995
|
}
|
|
5996
|
+
// The keys of every open folder, walked through the tree's public node API.
|
|
5997
|
+
_expandedKeys() {
|
|
5998
|
+
const tree = this._tree();
|
|
5999
|
+
if (!tree) {
|
|
6000
|
+
return [];
|
|
6001
|
+
}
|
|
6002
|
+
const walk = (parent) => tree.getNodes(parent)
|
|
6003
|
+
.filter((node) => node.isExpanded())
|
|
6004
|
+
.flatMap((node) => [node.data?.key, ...walk(node)]);
|
|
6005
|
+
return walk(null);
|
|
6006
|
+
}
|
|
6007
|
+
_expandKeys(keys) {
|
|
6008
|
+
const tree = this._tree();
|
|
6009
|
+
if (!tree || !keys.length) {
|
|
6010
|
+
return;
|
|
6011
|
+
}
|
|
6012
|
+
const open = new Set(keys);
|
|
6013
|
+
const walk = (parent) => {
|
|
6014
|
+
tree.getNodes(parent)
|
|
6015
|
+
.filter((node) => open.has(node.data?.key))
|
|
6016
|
+
.forEach((node) => {
|
|
6017
|
+
node.expand();
|
|
6018
|
+
walk(node);
|
|
6019
|
+
});
|
|
6020
|
+
};
|
|
6021
|
+
walk(null);
|
|
6022
|
+
}
|
|
5910
6023
|
// Flat API rows → the nested shape fs-tree renders. Folders come before
|
|
5911
6024
|
// reports at every level, each already name-ordered by the backend.
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
6025
|
+
//
|
|
6026
|
+
// `deleted` is inherited downward: a deleted folder hides everything below it
|
|
6027
|
+
// without the rows below being deleted themselves, and that is only visible
|
|
6028
|
+
// from here, where the parent is in hand. The backend applies the same rule
|
|
6029
|
+
// when it filters (ReportFolderModel::getDeletedFolderIds()).
|
|
6030
|
+
_buildNodes(reportTree) {
|
|
6031
|
+
const folders = reportTree?.folders ?? [];
|
|
6032
|
+
const reports = reportTree?.reports ?? [];
|
|
6033
|
+
const build = (parentId, parentDeleted) => {
|
|
5916
6034
|
const folderNodes = folders
|
|
5917
6035
|
.filter((folder) => (folder.parentReportFolderId ?? null) === parentId)
|
|
5918
|
-
.map((folder) =>
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
6036
|
+
.map((folder) => {
|
|
6037
|
+
const stateDeleted = folder.state === ReportState.Deleted;
|
|
6038
|
+
const deleted = parentDeleted || stateDeleted;
|
|
6039
|
+
return {
|
|
6040
|
+
key: `f:${folder.id}`,
|
|
6041
|
+
id: folder.id,
|
|
6042
|
+
name: folder.name,
|
|
6043
|
+
type: ReportNodeType.Folder,
|
|
6044
|
+
children: build(folder.id, deleted),
|
|
6045
|
+
stateDeleted,
|
|
6046
|
+
deleted,
|
|
6047
|
+
};
|
|
6048
|
+
});
|
|
5925
6049
|
const reportNodes = reports
|
|
5926
6050
|
.filter((report) => (report.reportFolderId ?? null) === parentId)
|
|
5927
|
-
.map((report) =>
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
6051
|
+
.map((report) => {
|
|
6052
|
+
const stateDeleted = report.state === ReportState.Deleted;
|
|
6053
|
+
return {
|
|
6054
|
+
key: `r:${report.id}`,
|
|
6055
|
+
id: report.id,
|
|
6056
|
+
name: report.name,
|
|
6057
|
+
type: ReportNodeType.Report,
|
|
6058
|
+
children: [],
|
|
6059
|
+
report,
|
|
6060
|
+
stateDeleted,
|
|
6061
|
+
deleted: parentDeleted || stateDeleted,
|
|
6062
|
+
};
|
|
6063
|
+
});
|
|
5935
6064
|
return [...folderNodes, ...reportNodes];
|
|
5936
6065
|
};
|
|
5937
|
-
return build(null);
|
|
6066
|
+
return build(null, false);
|
|
5938
6067
|
}
|
|
5939
6068
|
// A drop moved a node under a new parent, or out to the root. The tree has
|
|
5940
6069
|
// already moved it on screen, so both outcomes reload: a success to pick up
|
|
@@ -5995,9 +6124,9 @@ class FsAiReportsComponent {
|
|
|
5995
6124
|
this._load();
|
|
5996
6125
|
});
|
|
5997
6126
|
}
|
|
5998
|
-
//
|
|
5999
|
-
//
|
|
6000
|
-
//
|
|
6127
|
+
// A folder deletes with everything in it, so the confirm says what that is —
|
|
6128
|
+
// and that it all comes back from Show Deleted, which is what makes it safe to
|
|
6129
|
+
// offer.
|
|
6001
6130
|
_delete(node) {
|
|
6002
6131
|
const data = node?.data;
|
|
6003
6132
|
if (!data) {
|
|
@@ -6007,7 +6136,7 @@ class FsAiReportsComponent {
|
|
|
6007
6136
|
this._prompt.confirm({
|
|
6008
6137
|
title: 'Confirm',
|
|
6009
6138
|
template: folder
|
|
6010
|
-
?
|
|
6139
|
+
? this._deleteFolderMessage(data)
|
|
6011
6140
|
: 'Are you sure you would like to delete this report?',
|
|
6012
6141
|
})
|
|
6013
6142
|
.pipe(switchMap(() => (folder
|
|
@@ -6018,15 +6147,41 @@ class FsAiReportsComponent {
|
|
|
6018
6147
|
this._load();
|
|
6019
6148
|
});
|
|
6020
6149
|
}
|
|
6150
|
+
// Counted from the node already on screen, live rows only: in Show Deleted
|
|
6151
|
+
// mode the folder can also hold rows that are gone already, and those are not
|
|
6152
|
+
// leaving with it.
|
|
6153
|
+
_deleteFolderMessage(folder) {
|
|
6154
|
+
const count = (node) => node.children
|
|
6155
|
+
.filter((child) => !child.deleted)
|
|
6156
|
+
.reduce((total, child) => {
|
|
6157
|
+
const below = count(child);
|
|
6158
|
+
return {
|
|
6159
|
+
folders: total.folders + below.folders + (child.type === ReportNodeType.Folder ? 1 : 0),
|
|
6160
|
+
reports: total.reports + below.reports + (child.type === ReportNodeType.Report ? 1 : 0),
|
|
6161
|
+
};
|
|
6162
|
+
}, { folders: 0, reports: 0 });
|
|
6163
|
+
const { folders, reports } = count(folder);
|
|
6164
|
+
const contents = [
|
|
6165
|
+
folders ? `${folders} ${folders === 1 ? 'folder' : 'folders'}` : '',
|
|
6166
|
+
reports ? `${reports} ${reports === 1 ? 'report' : 'reports'}` : '',
|
|
6167
|
+
].filter(Boolean);
|
|
6168
|
+
const inside = contents.length ? ` and the ${contents.join(' and ')} inside it` : '';
|
|
6169
|
+
return `Are you sure you would like to delete the folder "${folder.name}"${inside}? It can be restored from Show Deleted.`;
|
|
6170
|
+
}
|
|
6171
|
+
// Both endpoints also restore any deleted folder above the row, so a row that
|
|
6172
|
+
// is live itself but hidden by a deleted folder is restored by the same call.
|
|
6021
6173
|
_restore(node) {
|
|
6022
6174
|
const data = node?.data;
|
|
6023
6175
|
if (!data) {
|
|
6024
6176
|
return;
|
|
6025
6177
|
}
|
|
6026
|
-
|
|
6178
|
+
const folder = data.type === ReportNodeType.Folder;
|
|
6179
|
+
(folder
|
|
6180
|
+
? this._reportData.undeleteFolder(data.id)
|
|
6181
|
+
: this._reportData.undelete(data.id))
|
|
6027
6182
|
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
6028
6183
|
.subscribe(() => {
|
|
6029
|
-
this._message.success('Restored report');
|
|
6184
|
+
this._message.success(folder ? 'Restored folder' : 'Restored report');
|
|
6030
6185
|
this._load();
|
|
6031
6186
|
});
|
|
6032
6187
|
}
|
|
@@ -6046,17 +6201,18 @@ class FsAiReportsComponent {
|
|
|
6046
6201
|
return segments;
|
|
6047
6202
|
}
|
|
6048
6203
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6049
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.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 } }, viewQueries: [{ propertyName: "_tree", first: true, predicate: FsTreeComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<fs-filter [config]=\"filterConfig\"></fs-filter>\n\n<ng-container *fsSkeleton=\"nodes()\">\n <fs-tree [config]=\"treeConfig\">\n <ng-template\n fsTreeNode\n let-data=\"data\">\n <div
|
|
6204
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.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 } }, viewQueries: [{ propertyName: "_tree", first: true, predicate: FsTreeComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<fs-filter [config]=\"filterConfig\"></fs-filter>\n\n<ng-container *fsSkeleton=\"nodes()\">\n <fs-tree [config]=\"treeConfig\">\n <ng-template\n fsTreeNode\n let-data=\"data\">\n <div\n class=\"fs-row.gap-sm.align-center node-row\"\n [class.deleted]=\"data.deleted\">\n <mat-icon class=\"node-icon\">\n {{ data.type === nodeType.Folder ? 'folder' : 'insert_chart_outlined' }}\n </mat-icon>\n\n @if (data.type === nodeType.Folder || data.deleted) {\n <!-- A deleted report can't be opened (the backend only assembles\n live ones, and one under a deleted folder is not on the live\n listing), so in Show Deleted mode the name is plain text \u2014\n restore it from the row menu first. -->\n <span class=\"node-name\">{{ data.name }}</span>\n } @else {\n <a\n class=\"node-name\"\n [routerLink]=\"openLink(data.report)\">\n {{ data.name }}\n </a>\n }\n\n <!-- Only on the row somebody deleted. Everything under a deleted\n folder is muted with it, but was not deleted itself. -->\n @if (data.stateDeleted) {\n <fs-chip size=\"tiny\">Deleted</fs-chip>\n }\n </div>\n </ng-template>\n </fs-tree>\n\n @if (!nodes().length) {\n <p class=\"small tree-empty\">\n No reports yet. Create one, or add a folder to group them.\n </p>\n }\n</ng-container>\n", styles: ["a{cursor:pointer}fs-tree .node>.container{flex:1;min-width:0}fs-tree .node>.container>.content{min-width:0}fs-tree .node>.container>button{margin-top:-5px}.node-row{min-width:0}.node-row.deleted{opacity:.55}.node-icon{font-size:18px;width:18px;height:18px;flex:none}.node-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-empty{margin-top:10px}\n"], dependencies: [{ kind: "ngmodule", type: FsChipModule }, { kind: "component", type: i1$4.FsChipComponent, selector: "fs-chip", inputs: ["selectable", "removable", "value", "maxWidth", "width", "backgroundColor", "borderColor", "color", "shape", "outlined", "outlineDash", "icon", "image", "selected", "disabled", "padding", "contrastColor", "size"], outputs: ["selectedToggled", "removed", "click"] }, { kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i4.FsSkeletonContentDirective, selector: "[fsSkeleton]", inputs: ["fsSkeleton", "fsSkeletonPattern"] }, { kind: "ngmodule", type: FsTreeModule }, { kind: "component", type: i4$1.FsTreeComponent, selector: "fs-tree", inputs: ["config"] }, { kind: "directive", type: i4$1.FsTreeNodeDirective, selector: "[fsTreeNode]", inputs: ["class"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6050
6205
|
}
|
|
6051
6206
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportsComponent, decorators: [{
|
|
6052
6207
|
type: Component,
|
|
6053
6208
|
args: [{ selector: 'fs-ai-reports', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
|
6209
|
+
FsChipModule,
|
|
6054
6210
|
FsFilterModule,
|
|
6055
6211
|
FsSkeletonModule,
|
|
6056
6212
|
FsTreeModule,
|
|
6057
6213
|
MatIconModule,
|
|
6058
6214
|
RouterLink,
|
|
6059
|
-
], template: "<fs-filter [config]=\"filterConfig\"></fs-filter>\n\n<ng-container *fsSkeleton=\"nodes()\">\n <fs-tree [config]=\"treeConfig\">\n <ng-template\n fsTreeNode\n let-data=\"data\">\n <div
|
|
6215
|
+
], template: "<fs-filter [config]=\"filterConfig\"></fs-filter>\n\n<ng-container *fsSkeleton=\"nodes()\">\n <fs-tree [config]=\"treeConfig\">\n <ng-template\n fsTreeNode\n let-data=\"data\">\n <div\n class=\"fs-row.gap-sm.align-center node-row\"\n [class.deleted]=\"data.deleted\">\n <mat-icon class=\"node-icon\">\n {{ data.type === nodeType.Folder ? 'folder' : 'insert_chart_outlined' }}\n </mat-icon>\n\n @if (data.type === nodeType.Folder || data.deleted) {\n <!-- A deleted report can't be opened (the backend only assembles\n live ones, and one under a deleted folder is not on the live\n listing), so in Show Deleted mode the name is plain text \u2014\n restore it from the row menu first. -->\n <span class=\"node-name\">{{ data.name }}</span>\n } @else {\n <a\n class=\"node-name\"\n [routerLink]=\"openLink(data.report)\">\n {{ data.name }}\n </a>\n }\n\n <!-- Only on the row somebody deleted. Everything under a deleted\n folder is muted with it, but was not deleted itself. -->\n @if (data.stateDeleted) {\n <fs-chip size=\"tiny\">Deleted</fs-chip>\n }\n </div>\n </ng-template>\n </fs-tree>\n\n @if (!nodes().length) {\n <p class=\"small tree-empty\">\n No reports yet. Create one, or add a folder to group them.\n </p>\n }\n</ng-container>\n", styles: ["a{cursor:pointer}fs-tree .node>.container{flex:1;min-width:0}fs-tree .node>.container>.content{min-width:0}fs-tree .node>.container>button{margin-top:-5px}.node-row{min-width:0}.node-row.deleted{opacity:.55}.node-icon{font-size:18px;width:18px;height:18px;flex:none}.node-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-empty{margin-top:10px}\n"] }]
|
|
6060
6216
|
}], ctorParameters: () => [] });
|
|
6061
6217
|
|
|
6062
6218
|
// A single report: the AI chat on the left, the report canvas on the right,
|
|
@@ -6438,8 +6594,8 @@ class FsAiReportComponent {
|
|
|
6438
6594
|
ReportPdfService,
|
|
6439
6595
|
ReportExportService,
|
|
6440
6596
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
6441
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
6442
|
-
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }, { propertyName: "_canvas", first: true, predicate: ReportCanvasComponent, descendants: 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 <!-- Report-level filters, inline with the toolbar actions. fs-filter\n reads its config once at init, so it's keyed on the filter\n signature: when the report-level filter set changes the block is\n recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter\n class=\"fs-ai-report__filters\"\n [config]=\"reportFilterConfig\">\n </fs-filter>\n }\n }\n\n <!-- What layout editing can add or switch to, and the way out of it.\n One style for all three \u2014 outlined, icon and text. \"Header & footer\"\n is a toggle: it keeps its label and turns gray while it is on. -->\n @if (editMode) {\n <div class=\"fs-ai-report__edit-tools fs-row.align-center.gap-xs\">\n <button\n mat-stroked-button\n type=\"button\"\n matTooltip=\"Add a block of text\"\n (click)=\"addText()\">\n <mat-icon>title</mat-icon>\n Add text\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n class=\"fs-ai-report__master\"\n [class.active]=\"isMasterMode\"\n [matTooltip]=\"isMasterMode ? 'Back to editing this page' : 'Edit the header and footer shown on every page'\"\n (click)=\"toggleMasterMode()\">\n <mat-icon>web_asset</mat-icon>\n Header & footer\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"toggleEditMode()\">\n <mat-icon>check</mat-icon>\n Done editing\n </button>\n </div>\n }\n\n <!-- One icon for both states: it reads as pressed while editing rather\n than swapping to something else. -->\n <button\n mat-icon-button\n type=\"button\"\n class=\"fs-ai-report__toggle\"\n [class.active]=\"editMode\"\n [matTooltip]=\"editMode ? 'Done editing' : 'Edit layout'\"\n (click)=\"toggleEditMode()\">\n <mat-icon>dashboard_customize</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 <ng-template\n fs-menu-item\n (click)=\"exportData()\">\n Export Raw Data\n </ng-template>\n @if ((report?.pages?.length ?? 0) > 1) {\n <ng-template\n fs-menu-item\n (click)=\"exportData(true)\">\n Export Raw Data (This Page)\n </ng-template>\n }\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]=\"chatRequestData()\"\n [ready]=\"chatPageId() !== null\"\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 <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n [pageId]=\"pageId\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (pageChanged)=\"onCanvasPageChanged($event)\">\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:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fs-ai-report__filters{flex:0 1 auto;min-width:0;display:block;margin:0 5px 0 0}.fs-ai-report__filters ::ng-deep .filter-bar-container{align-items:center!important}.fs-ai-report__filters ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.fs-ai-report__toggle.active{background:#e3ecf7;color:var(--brand-primary-color, #2196f3)}.fs-ai-report__master.active{background:#eceff3}.fs-ai-report__edit-tools{margin:0 5px}.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 .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:3px;border-radius:3px;background:transparent;transition:background-color .12s ease}.fs-ai-report .resizer:hover:before{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{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$1.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$1.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "directive", type: i2$1.FsMenuTriggerDirective, selector: "[fsMenuTriggerFor]", inputs: ["fsMenuTriggerFor"] }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "ready", "introMessage", "showThinking"], outputs: ["response", "showThinkingChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$
|
|
6597
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CFkoNvo_.mjs').then((module) => module.default) }),
|
|
6598
|
+
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }, { propertyName: "_canvas", first: true, predicate: ReportCanvasComponent, descendants: 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 <!-- Report-level filters, inline with the toolbar actions. fs-filter\n reads its config once at init, so it's keyed on the filter\n signature: when the report-level filter set changes the block is\n recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter\n class=\"fs-ai-report__filters\"\n [config]=\"reportFilterConfig\">\n </fs-filter>\n }\n }\n\n <!-- What layout editing can add or switch to, and the way out of it.\n One style for all three \u2014 outlined, icon and text. \"Header & footer\"\n is a toggle: it keeps its label and turns gray while it is on. -->\n @if (editMode) {\n <div class=\"fs-ai-report__edit-tools fs-row.align-center.gap-xs\">\n <button\n mat-stroked-button\n type=\"button\"\n matTooltip=\"Add a block of text\"\n (click)=\"addText()\">\n <mat-icon>title</mat-icon>\n Add text\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n class=\"fs-ai-report__master\"\n [class.active]=\"isMasterMode\"\n [matTooltip]=\"isMasterMode ? 'Back to editing this page' : 'Edit the header and footer shown on every page'\"\n (click)=\"toggleMasterMode()\">\n <mat-icon>web_asset</mat-icon>\n Header & footer\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"toggleEditMode()\">\n <mat-icon>check</mat-icon>\n Done editing\n </button>\n </div>\n }\n\n <!-- One icon for both states: it reads as pressed while editing rather\n than swapping to something else. -->\n <button\n mat-icon-button\n type=\"button\"\n class=\"fs-ai-report__toggle\"\n [class.active]=\"editMode\"\n [matTooltip]=\"editMode ? 'Done editing' : 'Edit layout'\"\n (click)=\"toggleEditMode()\">\n <mat-icon>dashboard_customize</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 <ng-template\n fs-menu-item\n (click)=\"exportData()\">\n Export Raw Data\n </ng-template>\n @if ((report?.pages?.length ?? 0) > 1) {\n <ng-template\n fs-menu-item\n (click)=\"exportData(true)\">\n Export Raw Data (This Page)\n </ng-template>\n }\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]=\"chatRequestData()\"\n [ready]=\"chatPageId() !== null\"\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 <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n [pageId]=\"pageId\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (pageChanged)=\"onCanvasPageChanged($event)\">\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:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fs-ai-report__filters{flex:0 1 auto;min-width:0;display:block;margin:0 5px 0 0}.fs-ai-report__filters ::ng-deep .filter-bar-container{align-items:center!important}.fs-ai-report__filters ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.fs-ai-report__toggle.active{background:#e3ecf7;color:var(--brand-primary-color, #2196f3)}.fs-ai-report__master.active{background:#eceff3}.fs-ai-report__edit-tools{margin:0 5px}.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 .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:3px;border-radius:3px;background:transparent;transition:background-color .12s ease}.fs-ai-report .resizer:hover:before{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{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$1.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$1.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "directive", type: i2$1.FsMenuTriggerDirective, selector: "[fsMenuTriggerFor]", inputs: ["fsMenuTriggerFor"] }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "ready", "introMessage", "showThinking"], outputs: ["response", "showThinkingChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3$3.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2$5.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", "pageId"], outputs: ["componentSettings", "reportChanged", "pageChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6443
6599
|
}
|
|
6444
6600
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportComponent, decorators: [{
|
|
6445
6601
|
type: Component,
|
|
@@ -6452,7 +6608,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
6452
6608
|
ReportPdfService,
|
|
6453
6609
|
ReportExportService,
|
|
6454
6610
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
6455
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
6611
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CFkoNvo_.mjs').then((module) => module.default) }),
|
|
6456
6612
|
], imports: [
|
|
6457
6613
|
FsFilterModule,
|
|
6458
6614
|
FsMenuModule,
|
|
@@ -6485,5 +6641,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
6485
6641
|
* Generated bundle index. Do not edit.
|
|
6486
6642
|
*/
|
|
6487
6643
|
|
|
6488
|
-
export { FsAiReportsComponent as F, GRANULARITY_OPTIONS as G, REPORT_CHART_COLORS_CSS as R, ReportComponent as a, FsAiReportComponent as b, ReportData as c, ReportService as d, ReportFilterStateService as e, ReportNodeType as f, ReportState as g
|
|
6489
|
-
//# sourceMappingURL=firestitch-report-firestitch-report-
|
|
6644
|
+
export { FsAiReportsComponent as F, GRANULARITY_OPTIONS as G, REPORT_CHART_COLORS_CSS as R, ReportComponent as a, FsAiReportComponent as b, ReportData as c, ReportService as d, ReportFilterStateService as e, ReportNodeType as f, ReportState as g };
|
|
6645
|
+
//# sourceMappingURL=firestitch-report-firestitch-report-PZzkYYTD.mjs.map
|