@firestitch/report 18.0.17 → 18.0.19
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/dialogs/global-settings/components/semantic-database/semantic-database.component.d.ts +5 -3
- package/app/reports/export/report-export-collector.service.d.ts +1 -0
- package/app/reports/format.d.ts +1 -1
- package/app/reports/interfaces/report.interface.d.ts +8 -1
- package/app/reports/views/reports/fs-ai-reports.component.d.ts +2 -1
- package/esm2022/app/reports/components/component-kpi/component-kpi.component.mjs +2 -2
- package/esm2022/app/reports/data/report.data.mjs +11 -1
- package/esm2022/app/reports/dialogs/global-settings/components/semantic-database/semantic-database.component.mjs +11 -6
- package/esm2022/app/reports/export/report-export-collector.service.mjs +31 -11
- package/esm2022/app/reports/export/report-pdf.service.mjs +6 -5
- package/esm2022/app/reports/export/report-pptx.service.mjs +6 -5
- package/esm2022/app/reports/format.mjs +21 -6
- package/esm2022/app/reports/interfaces/report.interface.mjs +8 -1
- package/esm2022/app/reports/services/report-filter-state.service.mjs +17 -2
- package/esm2022/app/reports/views/report/fs-ai-report.component.mjs +2 -2
- package/esm2022/app/reports/views/report/report.component.mjs +2 -2
- package/esm2022/app/reports/views/reports/fs-ai-reports.component.mjs +34 -3
- package/fesm2022/{firestitch-report-echarts-F254vpF1.mjs → firestitch-report-echarts-CfwgLaOA.mjs} +2 -2
- package/fesm2022/{firestitch-report-echarts-F254vpF1.mjs.map → firestitch-report-echarts-CfwgLaOA.mjs.map} +1 -1
- package/fesm2022/{firestitch-report-firestitch-report-Dh8kteJk.mjs → firestitch-report-firestitch-report-_MCMM4XH.mjs} +144 -41
- package/fesm2022/firestitch-report-firestitch-report-_MCMM4XH.mjs.map +1 -0
- package/fesm2022/firestitch-report.mjs +1 -1
- package/package.json +1 -1
- package/fesm2022/firestitch-report-firestitch-report-Dh8kteJk.mjs.map +0 -1
|
@@ -27,7 +27,7 @@ import { FsZoomPanComponent, FsZoomPanModule } from '@firestitch/zoom-pan';
|
|
|
27
27
|
import { FsApi, StreamEventType } from '@firestitch/api';
|
|
28
28
|
import * as i1$1 from '@angular/material/menu';
|
|
29
29
|
import { MatMenuModule } from '@angular/material/menu';
|
|
30
|
-
import { format, startOfYear, startOfQuarter, startOfMonth, subMonths, subDays, parseISO, isValid } from 'date-fns';
|
|
30
|
+
import { format, startOfYear, startOfQuarter, startOfMonth, subMonths, subDays, subWeeks, startOfWeek, endOfWeek, parseISO, isValid } from 'date-fns';
|
|
31
31
|
import { DatePipe } from '@angular/common';
|
|
32
32
|
import * as i1 from '@firestitch/list';
|
|
33
33
|
import { PaginationStrategy, FsListComponent, FsListModule } from '@firestitch/list';
|
|
@@ -116,12 +116,22 @@ class ReportData {
|
|
|
116
116
|
...config,
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
+
// Soft-delete: the report leaves the listing but keeps its pages and
|
|
120
|
+
// components, so undelete() brings back the same document.
|
|
119
121
|
delete(reportId, config = {}) {
|
|
120
122
|
return this._api.delete(this._path(`${reportId}`), {}, {
|
|
121
123
|
key: 'report',
|
|
122
124
|
...config,
|
|
123
125
|
});
|
|
124
126
|
}
|
|
127
|
+
// Restore a soft-deleted report through its own action endpoint — the report
|
|
128
|
+
// PUT does not accept `state`.
|
|
129
|
+
undelete(reportId, config = {}) {
|
|
130
|
+
return this._api.post(this._path(`${reportId}/undelete`), {}, {
|
|
131
|
+
key: 'report',
|
|
132
|
+
...config,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
125
135
|
// The assembled structure: pages → components (+ their filters) and the
|
|
126
136
|
// report's filter groups.
|
|
127
137
|
get(reportId, config = {}) {
|
|
@@ -619,6 +629,21 @@ class ReportFilterStateService {
|
|
|
619
629
|
_relativeRange(relative) {
|
|
620
630
|
const now = new Date();
|
|
621
631
|
switch (relative) {
|
|
632
|
+
// A calendar week, Sunday to Saturday, in whole days — not the rolling
|
|
633
|
+
// seven days `last7Days` gives, which never lines up with the week a
|
|
634
|
+
// partner has already been sent. `lastWeek` is the one a weekly report
|
|
635
|
+
// wants: it ends at endOfWeek rather than at `now`, because the week it
|
|
636
|
+
// names has closed. `thisWeek` follows every other preset here and runs
|
|
637
|
+
// up to now, so it is deliberately a part-finished week.
|
|
638
|
+
case 'thisWeek':
|
|
639
|
+
return { start: startOfWeek(now, { weekStartsOn: 0 }), end: now };
|
|
640
|
+
case 'lastWeek': {
|
|
641
|
+
const previous = subWeeks(now, 1);
|
|
642
|
+
return {
|
|
643
|
+
start: startOfWeek(previous, { weekStartsOn: 0 }),
|
|
644
|
+
end: endOfWeek(previous, { weekStartsOn: 0 }),
|
|
645
|
+
};
|
|
646
|
+
}
|
|
622
647
|
case 'last7Days':
|
|
623
648
|
return { start: subDays(now, 7), end: now };
|
|
624
649
|
case 'last30Days':
|
|
@@ -983,9 +1008,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
983
1008
|
}] } });
|
|
984
1009
|
|
|
985
1010
|
// Measure-value formatting shared by the KPI renderer and the exports — the
|
|
986
|
-
// config's `format` (number | percent | currency)
|
|
987
|
-
// screen, in PowerPoint and in PDF.
|
|
988
|
-
|
|
1011
|
+
// config's `format` (number | percent | currency) and `precision` mean the same
|
|
1012
|
+
// thing on screen, in PowerPoint and in PDF.
|
|
1013
|
+
// A bare `new Intl.NumberFormat()` defaults to maximumFractionDigits: 3, which
|
|
1014
|
+
// is why an unconfigured percentage reads 38.298%. When the measure names a
|
|
1015
|
+
// precision it is pinned as BOTH the minimum and the maximum, so a column of
|
|
1016
|
+
// rates lines up instead of each value showing however many decimals it happens
|
|
1017
|
+
// to need. Undefined leaves the locale default alone, so a report authored
|
|
1018
|
+
// before precision existed renders exactly as it did.
|
|
1019
|
+
function getFractionDigits(precision) {
|
|
1020
|
+
return Number.isFinite(precision)
|
|
1021
|
+
? { minimumFractionDigits: precision, maximumFractionDigits: precision }
|
|
1022
|
+
: {};
|
|
1023
|
+
}
|
|
1024
|
+
function formatMeasureValue(value, format, precision) {
|
|
989
1025
|
if (value === null || value === undefined || value === '') {
|
|
990
1026
|
return '—';
|
|
991
1027
|
}
|
|
@@ -993,17 +1029,21 @@ function formatMeasureValue(value, format) {
|
|
|
993
1029
|
if (!Number.isFinite(numeric)) {
|
|
994
1030
|
return String(value);
|
|
995
1031
|
}
|
|
1032
|
+
const fractionDigits = getFractionDigits(precision);
|
|
996
1033
|
switch (format) {
|
|
997
1034
|
case 'percent':
|
|
998
|
-
return `${new Intl.NumberFormat().format(numeric)}%`;
|
|
1035
|
+
return `${new Intl.NumberFormat(undefined, fractionDigits).format(numeric)}%`;
|
|
999
1036
|
case 'currency':
|
|
1000
1037
|
return new Intl.NumberFormat(undefined, {
|
|
1001
1038
|
style: 'currency',
|
|
1002
1039
|
currency: 'USD',
|
|
1040
|
+
// Whole dollars unless the measure asks for decimals — cents were
|
|
1041
|
+
// unrepresentable before, because this was hardcoded to 0.
|
|
1003
1042
|
maximumFractionDigits: 0,
|
|
1043
|
+
...fractionDigits,
|
|
1004
1044
|
}).format(numeric);
|
|
1005
1045
|
default:
|
|
1006
|
-
return new Intl.NumberFormat().format(numeric);
|
|
1046
|
+
return new Intl.NumberFormat(undefined, fractionDigits).format(numeric);
|
|
1007
1047
|
}
|
|
1008
1048
|
}
|
|
1009
1049
|
|
|
@@ -1023,7 +1063,7 @@ class ComponentKpiComponent {
|
|
|
1023
1063
|
return;
|
|
1024
1064
|
}
|
|
1025
1065
|
const column = config.measure?.column ?? Object.keys(row)[0];
|
|
1026
|
-
this.value = formatMeasureValue(row[column], config.measure?.format);
|
|
1066
|
+
this.value = formatMeasureValue(row[column], config.measure?.format, config.measure?.precision);
|
|
1027
1067
|
}
|
|
1028
1068
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ComponentKpiComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1029
1069
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ComponentKpiComponent, isStandalone: true, selector: "app-report-component-kpi", inputs: { component: "component", data: "data" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"kpi\">\n <div class=\"kpi-value\">{{ value }}</div>\n @if (label) {\n <div class=\"kpi-label\">{{ label }}</div>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%}.kpi{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px}.kpi .kpi-value{font-size:34px;font-weight:600;color:#1f2933;line-height:1}.kpi .kpi-label{font-size:12px;color:#7b8794}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
@@ -2488,6 +2528,13 @@ const FREQUENCY_OPTIONS = [
|
|
|
2488
2528
|
{ value: 'quarter', name: 'Quarter' },
|
|
2489
2529
|
{ value: 'year', name: 'Annually' },
|
|
2490
2530
|
];
|
|
2531
|
+
// Reports are soft-deletable: a deleted report leaves the listing but keeps its
|
|
2532
|
+
// pages and components, and the listing's "Show Deleted" mode restores it.
|
|
2533
|
+
var ReportState;
|
|
2534
|
+
(function (ReportState) {
|
|
2535
|
+
ReportState["Active"] = "active";
|
|
2536
|
+
ReportState["Deleted"] = "deleted";
|
|
2537
|
+
})(ReportState || (ReportState = {}));
|
|
2491
2538
|
|
|
2492
2539
|
// Rows fetched for a list component's export table — enough to fill a
|
|
2493
2540
|
// slide/page table; the cap note covers the rest.
|
|
@@ -2509,8 +2556,10 @@ class ReportExportCollectorService {
|
|
|
2509
2556
|
}
|
|
2510
2557
|
});
|
|
2511
2558
|
const all$ = requests.length ? forkJoin(requests) : of([]);
|
|
2512
|
-
|
|
2513
|
-
|
|
2559
|
+
// The summary rides alongside the component data rather than being built at
|
|
2560
|
+
// the end, because naming a select filter's values is itself a request.
|
|
2561
|
+
return forkJoin({ collected: all$, filterSummary: this._filterSummary(report) })
|
|
2562
|
+
.pipe(map$1(({ collected, filterSummary }) => {
|
|
2514
2563
|
const pages = report.pages.map((page) => ({ page, components: [] }));
|
|
2515
2564
|
collected.forEach((item, index) => {
|
|
2516
2565
|
pages[slots[index].pageIndex].components.push(item);
|
|
@@ -2518,7 +2567,7 @@ class ReportExportCollectorService {
|
|
|
2518
2567
|
return {
|
|
2519
2568
|
report,
|
|
2520
2569
|
pages,
|
|
2521
|
-
filterSummary
|
|
2570
|
+
filterSummary,
|
|
2522
2571
|
};
|
|
2523
2572
|
}));
|
|
2524
2573
|
}
|
|
@@ -2534,8 +2583,12 @@ class ReportExportCollectorService {
|
|
|
2534
2583
|
return this._reportData.componentData(report.id, component.id, filters, state)
|
|
2535
2584
|
.pipe(map$1((data) => ({ component, data })), catchError(() => of({ component, data: null })));
|
|
2536
2585
|
}
|
|
2537
|
-
// "Report Period: Jun 12, 2025 – Jun 12, 2026", "Organization:
|
|
2538
|
-
// line per filter group that has an active session value.
|
|
2586
|
+
// "Report Period: Jun 12, 2025 – Jun 12, 2026", "Organization: Alnylam - AHP,
|
|
2587
|
+
// Alnylam - PH1" — one line per filter group that has an active session value.
|
|
2588
|
+
//
|
|
2589
|
+
// A select group stores ids, so its line has to be named off the option list
|
|
2590
|
+
// before it can be printed; a date or keyword group already holds its own
|
|
2591
|
+
// display value and resolves synchronously.
|
|
2539
2592
|
_filterSummary(report) {
|
|
2540
2593
|
const lines = [];
|
|
2541
2594
|
for (const group of report.filterGroups ?? []) {
|
|
@@ -2545,21 +2598,34 @@ class ReportExportCollectorService {
|
|
|
2545
2598
|
}
|
|
2546
2599
|
const label = group.label || 'Filter';
|
|
2547
2600
|
if (value.start || value.end) {
|
|
2548
|
-
lines.push(`${label}: ${this._date(value.start)} – ${this._date(value.end)}`);
|
|
2601
|
+
lines.push(of(`${label}: ${this._date(value.start)} – ${this._date(value.end)}`));
|
|
2549
2602
|
}
|
|
2550
2603
|
else if (value.values?.length) {
|
|
2551
|
-
lines.push(
|
|
2604
|
+
lines.push(this._selectLine(label, group, report.id, value.values));
|
|
2552
2605
|
}
|
|
2553
2606
|
else if (value.value?.trim()) {
|
|
2554
|
-
lines.push(`${label}: "${value.value.trim()}"`);
|
|
2607
|
+
lines.push(of(`${label}: "${value.value.trim()}"`));
|
|
2555
2608
|
}
|
|
2556
2609
|
}
|
|
2557
2610
|
const frequency = this._filterState.frequency();
|
|
2558
2611
|
if (frequency) {
|
|
2559
2612
|
const option = FREQUENCY_OPTIONS.find((item) => item.value === frequency);
|
|
2560
|
-
lines.push(`Frequency: ${option?.name ?? frequency}`);
|
|
2613
|
+
lines.push(of(`Frequency: ${option?.name ?? frequency}`));
|
|
2561
2614
|
}
|
|
2562
|
-
return lines;
|
|
2615
|
+
return lines.length ? forkJoin(lines) : of([]);
|
|
2616
|
+
}
|
|
2617
|
+
// One select group's line, with its stored ids resolved to the same names the
|
|
2618
|
+
// on-screen chips show — a cover reading "Organization: 8774, 8777" tells the
|
|
2619
|
+
// client nothing about what the report covers.
|
|
2620
|
+
//
|
|
2621
|
+
// Uses the report bar's own loader and resolver so the two can't drift, which
|
|
2622
|
+
// also means a value whose entity was deleted keeps showing its raw id rather
|
|
2623
|
+
// than dropping off the cover and silently understating the filter. A failed
|
|
2624
|
+
// option fetch degrades the whole line to ids for the same reason: an export
|
|
2625
|
+
// must never fail on a cosmetic lookup.
|
|
2626
|
+
_selectLine(label, group, reportId, values) {
|
|
2627
|
+
return resolveOptionValues(loadGroupOptions(group, this._reportData, reportId), values)
|
|
2628
|
+
.pipe(map$1((resolved) => `${label}: ${resolved.map((item) => item.name).join(', ')}`), catchError(() => of(`${label}: ${values.map((item) => String(item)).join(', ')}`)));
|
|
2563
2629
|
}
|
|
2564
2630
|
_date(value) {
|
|
2565
2631
|
if (!value) {
|
|
@@ -2582,7 +2648,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2582
2648
|
// complete synchronously after setOption.
|
|
2583
2649
|
async function renderChartImage(component, data, width, height, pixelRatio = 3) {
|
|
2584
2650
|
// The same tree-shaken echarts build + 'report' theme the live canvas uses.
|
|
2585
|
-
const echarts = (await import('./firestitch-report-echarts-
|
|
2651
|
+
const echarts = (await import('./firestitch-report-echarts-CfwgLaOA.mjs')).default;
|
|
2586
2652
|
const host = document.createElement('div');
|
|
2587
2653
|
host.style.width = `${width}px`;
|
|
2588
2654
|
host.style.height = `${height}px`;
|
|
@@ -2721,7 +2787,7 @@ class ReportPdfService {
|
|
|
2721
2787
|
const config = component.config;
|
|
2722
2788
|
const row = data.rows[0] ?? {};
|
|
2723
2789
|
const column = config.measure?.column ?? Object.keys(row)[0];
|
|
2724
|
-
const value = formatMeasureValue(row[column], config.measure?.format);
|
|
2790
|
+
const value = formatMeasureValue(row[column], config.measure?.format, config.measure?.precision);
|
|
2725
2791
|
doc.setFont('helvetica', 'bold');
|
|
2726
2792
|
doc.setFontSize(26);
|
|
2727
2793
|
doc.setTextColor('#1f2933');
|
|
@@ -2733,6 +2799,10 @@ class ReportPdfService {
|
|
|
2733
2799
|
doc.text(config.measure.label, x + w / 2, y + h / 2 + 16, { align: 'center' });
|
|
2734
2800
|
}
|
|
2735
2801
|
}
|
|
2802
|
+
// The exported cell must read like the on-screen cell: the SQL already
|
|
2803
|
+
// carries the formatting, so the value goes out verbatim. A raw date is the
|
|
2804
|
+
// one value the client renders (the column stays a RAW timestamp for the
|
|
2805
|
+
// report timezone), and it is rendered here too.
|
|
2736
2806
|
_cell(value, columnFormat) {
|
|
2737
2807
|
if (value === null || value === undefined) {
|
|
2738
2808
|
return '';
|
|
@@ -2741,9 +2811,6 @@ class ReportPdfService {
|
|
|
2741
2811
|
const date = new Date(String(value));
|
|
2742
2812
|
return Number.isNaN(date.getTime()) ? String(value) : format(date, 'MMM d, yyyy');
|
|
2743
2813
|
}
|
|
2744
|
-
if (columnFormat === 'number') {
|
|
2745
|
-
return formatMeasureValue(value, 'number');
|
|
2746
|
-
}
|
|
2747
2814
|
return String(value);
|
|
2748
2815
|
}
|
|
2749
2816
|
_slug(name) {
|
|
@@ -2950,7 +3017,7 @@ class ReportPptxService {
|
|
|
2950
3017
|
const config = component.config;
|
|
2951
3018
|
const row = data.rows[0] ?? {};
|
|
2952
3019
|
const column = config.measure?.column ?? Object.keys(row)[0];
|
|
2953
|
-
const value = formatMeasureValue(row[column], config.measure?.format);
|
|
3020
|
+
const value = formatMeasureValue(row[column], config.measure?.format, config.measure?.precision);
|
|
2954
3021
|
slide.addText([
|
|
2955
3022
|
{ text: value, options: { fontSize: 28, bold: true, color: '1F2933', breakLine: true } },
|
|
2956
3023
|
{ text: config.measure?.label ?? '', options: { fontSize: 10, color: '7B8794' } },
|
|
@@ -2960,6 +3027,10 @@ class ReportPptxService {
|
|
|
2960
3027
|
valign: 'middle',
|
|
2961
3028
|
});
|
|
2962
3029
|
}
|
|
3030
|
+
// The exported cell must read like the on-screen cell: the SQL already
|
|
3031
|
+
// carries the formatting, so the value goes out verbatim. A raw date is the
|
|
3032
|
+
// one value the client renders (the column stays a RAW timestamp for the
|
|
3033
|
+
// report timezone), and it is rendered here too.
|
|
2963
3034
|
_cell(value, columnFormat) {
|
|
2964
3035
|
if (value === null || value === undefined) {
|
|
2965
3036
|
return '';
|
|
@@ -2968,9 +3039,6 @@ class ReportPptxService {
|
|
|
2968
3039
|
const date = new Date(String(value));
|
|
2969
3040
|
return Number.isNaN(date.getTime()) ? String(value) : format(date, 'MMM d, yyyy');
|
|
2970
3041
|
}
|
|
2971
|
-
if (columnFormat === 'number') {
|
|
2972
|
-
return formatMeasureValue(value, 'number');
|
|
2973
|
-
}
|
|
2974
3042
|
return String(value);
|
|
2975
3043
|
}
|
|
2976
3044
|
_slug(name) {
|
|
@@ -3326,8 +3394,8 @@ class ReportComponent {
|
|
|
3326
3394
|
ReportPdfService,
|
|
3327
3395
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
3328
3396
|
// this route's chunk.
|
|
3329
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
3330
|
-
], 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 [disabled]=\"editMode\">\n <mat-icon>\n dashboard_2_edit\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 });
|
|
3397
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CfwgLaOA.mjs').then((module) => module.default) }),
|
|
3398
|
+
], 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 [disabled]=\"editMode\">\n <mat-icon>\n dashboard_2_edit\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", "showThinking"], outputs: ["response", "showThinkingChange"] }, { 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 });
|
|
3331
3399
|
}
|
|
3332
3400
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportComponent, decorators: [{
|
|
3333
3401
|
type: Component,
|
|
@@ -3340,7 +3408,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3340
3408
|
ReportPdfService,
|
|
3341
3409
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
3342
3410
|
// this route's chunk.
|
|
3343
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
3411
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CfwgLaOA.mjs').then((module) => module.default) }),
|
|
3344
3412
|
], imports: [
|
|
3345
3413
|
FormsModule,
|
|
3346
3414
|
FsAutocompleteChipsModule,
|
|
@@ -3548,6 +3616,10 @@ class SemanticDatabaseComponent {
|
|
|
3548
3616
|
K: 'Stopped',
|
|
3549
3617
|
F: 'Failed',
|
|
3550
3618
|
};
|
|
3619
|
+
// The streamed frames that carry a line for the feed: progress, and the final
|
|
3620
|
+
// frame the backend sends when a run ends badly. Without the error kind the
|
|
3621
|
+
// feed stops on the last progress line and never says what went wrong.
|
|
3622
|
+
static LOG_KINDS = ['log', 'error'];
|
|
3551
3623
|
// A row that is still owed content says so; a built one says nothing. On a
|
|
3552
3624
|
// finished build every stage is done on every row, so chipping them all was
|
|
3553
3625
|
// noise that buried the one row that mattered.
|
|
@@ -3687,7 +3759,7 @@ class SemanticDatabaseComponent {
|
|
|
3687
3759
|
// A hard stream/transport failure (the build couldn't even start)
|
|
3688
3760
|
// finalizes here — completed$ won't fire in that case.
|
|
3689
3761
|
tap({ error: (error) => this._finalizeBuild(error) }), filter((event) => event?.type === StreamEventType.Data
|
|
3690
|
-
&& event.data?.kind
|
|
3762
|
+
&& SemanticDatabaseComponent.LOG_KINDS.includes(event.data?.kind)
|
|
3691
3763
|
&& typeof event.data.line === 'string'), map((event) => event.data.line));
|
|
3692
3764
|
this._process
|
|
3693
3765
|
.run('Semantic database build', target$)
|
|
@@ -3699,9 +3771,10 @@ class SemanticDatabaseComponent {
|
|
|
3699
3771
|
* Wrap up one build run exactly once. On a hard stream error, surface the
|
|
3700
3772
|
* actual server-side reason pulled off the broken stream — the generic
|
|
3701
3773
|
* "could not run" line only when the failure carried no server text at all;
|
|
3702
|
-
* otherwise refresh the status and announce how the build actually ended
|
|
3703
|
-
* build
|
|
3704
|
-
*
|
|
3774
|
+
* otherwise refresh the status and announce how the build actually ended. A
|
|
3775
|
+
* failed build always names its reason on the row (the backend records one for
|
|
3776
|
+
* every way a run can die, fatals included), and that reason must surface
|
|
3777
|
+
* instead of the run silently doing nothing.
|
|
3705
3778
|
*/
|
|
3706
3779
|
_finalizeBuild(streamError) {
|
|
3707
3780
|
if (this._buildFinalized) {
|
|
@@ -3713,7 +3786,7 @@ class SemanticDatabaseComponent {
|
|
|
3713
3786
|
const detail = this._streamErrorDetail(streamError);
|
|
3714
3787
|
this._message.error(detail
|
|
3715
3788
|
? `The semantic database build failed — ${detail}`
|
|
3716
|
-
: 'The semantic database build could not run —
|
|
3789
|
+
: 'The semantic database build could not run — the connection to the server was lost.');
|
|
3717
3790
|
this._refreshStatus();
|
|
3718
3791
|
return;
|
|
3719
3792
|
}
|
|
@@ -3983,6 +4056,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3983
4056
|
// title, creates the report, then routes to it. The header gear opens the
|
|
3984
4057
|
// global Report Settings dialog (AI knowledge document + semantic-database
|
|
3985
4058
|
// build), which lives in this package. No routing is hard-coded here.
|
|
4059
|
+
//
|
|
4060
|
+
// Reports are soft-deletable: the row kebab deletes, "Show Deleted" flips the
|
|
4061
|
+
// list into deleted mode (the backend hides deleted reports otherwise), and in
|
|
4062
|
+
// that mode fs-list swaps Delete for Restore, which calls the undelete action
|
|
4063
|
+
// endpoint.
|
|
3986
4064
|
class FsAiReportsComponent {
|
|
3987
4065
|
// API base for the reports endpoints; override to mount elsewhere.
|
|
3988
4066
|
basePath = input('reports');
|
|
@@ -3993,6 +4071,9 @@ class FsAiReportsComponent {
|
|
|
3993
4071
|
createPattern = input(':id');
|
|
3994
4072
|
list = null;
|
|
3995
4073
|
listConfig;
|
|
4074
|
+
// Template-side handle on the enum, so the name cell can tell a deleted row
|
|
4075
|
+
// (shown in Show Deleted mode) from a live one.
|
|
4076
|
+
deletedState = ReportState.Deleted;
|
|
3996
4077
|
_reportData = inject(ReportData);
|
|
3997
4078
|
_router = inject(Router);
|
|
3998
4079
|
_route = inject(ActivatedRoute);
|
|
@@ -4067,6 +4148,28 @@ class FsAiReportsComponent {
|
|
|
4067
4148
|
click: () => this.createReport(),
|
|
4068
4149
|
},
|
|
4069
4150
|
],
|
|
4151
|
+
rowActions: [
|
|
4152
|
+
{
|
|
4153
|
+
click: (report) => this._reportData.delete(report.id),
|
|
4154
|
+
remove: {
|
|
4155
|
+
title: 'Confirm',
|
|
4156
|
+
template: 'Are you sure you would like to delete this report?',
|
|
4157
|
+
},
|
|
4158
|
+
menu: true,
|
|
4159
|
+
label: 'Delete',
|
|
4160
|
+
},
|
|
4161
|
+
],
|
|
4162
|
+
// `filter: true` makes fs-list render the Show Deleted toggle and send
|
|
4163
|
+
// `query` with the fetch; in that mode it also swaps the row's Delete
|
|
4164
|
+
// action for Restore.
|
|
4165
|
+
restore: {
|
|
4166
|
+
query: { state: ReportState.Deleted },
|
|
4167
|
+
filter: true,
|
|
4168
|
+
filterLabel: 'Show Deleted',
|
|
4169
|
+
menuLabel: 'Restore',
|
|
4170
|
+
reload: true,
|
|
4171
|
+
click: (report) => this._reportData.undelete(report.id),
|
|
4172
|
+
},
|
|
4070
4173
|
fetch: (query) => {
|
|
4071
4174
|
return this._reportData.reports(query)
|
|
4072
4175
|
.pipe(map((reports) => ({ data: reports ?? [] })));
|
|
@@ -4074,7 +4177,7 @@ class FsAiReportsComponent {
|
|
|
4074
4177
|
};
|
|
4075
4178
|
}
|
|
4076
4179
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4077
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
4180
|
+
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: "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 deleted report can't be opened (the backend only assembles active\n ones), so in Show Deleted mode the name is plain text \u2014 restore it\n from the row menu first. -->\n @if (row.state === deletedState) {\n {{ row.name }}\n } @else {\n <a [routerLink]=\"openLink(row)\">{{ row.name }}</a>\n }\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: i2$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 });
|
|
4078
4181
|
}
|
|
4079
4182
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportsComponent, decorators: [{
|
|
4080
4183
|
type: Component,
|
|
@@ -4082,7 +4185,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4082
4185
|
FsDateModule,
|
|
4083
4186
|
FsListModule,
|
|
4084
4187
|
RouterLink,
|
|
4085
|
-
], 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"] }]
|
|
4188
|
+
], 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 deleted report can't be opened (the backend only assembles active\n ones), so in Show Deleted mode the name is plain text \u2014 restore it\n from the row menu first. -->\n @if (row.state === deletedState) {\n {{ row.name }}\n } @else {\n <a [routerLink]=\"openLink(row)\">{{ row.name }}</a>\n }\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"] }]
|
|
4086
4189
|
}], ctorParameters: () => [], propDecorators: { list: [{
|
|
4087
4190
|
type: ViewChild,
|
|
4088
4191
|
args: [FsListComponent]
|
|
@@ -4340,8 +4443,8 @@ class FsAiReportComponent {
|
|
|
4340
4443
|
ReportPptxService,
|
|
4341
4444
|
ReportPdfService,
|
|
4342
4445
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
4343
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
4344
|
-
], 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 <!-- 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 <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 <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: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__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$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: 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"], outputs: ["componentSettings", "reportChanged", "editDone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4446
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CfwgLaOA.mjs').then((module) => module.default) }),
|
|
4447
|
+
], 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 <!-- 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 <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 <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: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__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$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", "showThinking"], outputs: ["response", "showThinkingChange"] }, { 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: 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"], outputs: ["componentSettings", "reportChanged", "editDone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4345
4448
|
}
|
|
4346
4449
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportComponent, decorators: [{
|
|
4347
4450
|
type: Component,
|
|
@@ -4353,7 +4456,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4353
4456
|
ReportPptxService,
|
|
4354
4457
|
ReportPdfService,
|
|
4355
4458
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
4356
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
4459
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CfwgLaOA.mjs').then((module) => module.default) }),
|
|
4357
4460
|
], imports: [
|
|
4358
4461
|
FsFilterModule,
|
|
4359
4462
|
FsMenuModule,
|
|
@@ -4383,5 +4486,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4383
4486
|
* Generated bundle index. Do not edit.
|
|
4384
4487
|
*/
|
|
4385
4488
|
|
|
4386
|
-
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 };
|
|
4387
|
-
//# sourceMappingURL=firestitch-report-firestitch-report-
|
|
4489
|
+
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, ReportState as g };
|
|
4490
|
+
//# sourceMappingURL=firestitch-report-firestitch-report-_MCMM4XH.mjs.map
|