@firestitch/report 18.0.23 → 18.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/reports/interfaces/report.interface.d.ts +7 -1
- package/app/reports/report-filter-items.d.ts +6 -1
- package/app/reports/views/report/fs-ai-report.component.d.ts +1 -1
- package/app/reports/views/report/report.component.d.ts +1 -1
- package/esm2022/app/reports/components/report-component/report-component.component.mjs +5 -5
- package/esm2022/app/reports/export/report-export-collector.service.mjs +11 -7
- package/esm2022/app/reports/interfaces/report.interface.mjs +5 -5
- package/esm2022/app/reports/option-builder.mjs +5 -3
- package/esm2022/app/reports/report-filter-items.mjs +67 -1
- package/esm2022/app/reports/services/report-filter-state.service.mjs +8 -7
- package/esm2022/app/reports/views/report/fs-ai-report.component.mjs +30 -28
- package/esm2022/app/reports/views/report/report.component.mjs +32 -31
- package/fesm2022/{firestitch-report-echarts-CWXfRyqK.mjs → firestitch-report-echarts-gBhnkDol.mjs} +2 -2
- package/fesm2022/{firestitch-report-echarts-CWXfRyqK.mjs.map → firestitch-report-echarts-gBhnkDol.mjs.map} +1 -1
- package/fesm2022/{firestitch-report-firestitch-report-BCb2lTJC.mjs → firestitch-report-firestitch-report-DpoqO7tP.mjs} +185 -109
- package/fesm2022/firestitch-report-firestitch-report-DpoqO7tP.mjs.map +1 -0
- package/fesm2022/firestitch-report.mjs +1 -1
- package/package.json +1 -1
- package/fesm2022/firestitch-report-firestitch-report-BCb2lTJC.mjs.map +0 -1
|
@@ -398,6 +398,40 @@ function pageGeometry(report, page) {
|
|
|
398
398
|
const PAGE_DIMENSION_MIN = 1;
|
|
399
399
|
const PAGE_DIMENSION_MAX = 50;
|
|
400
400
|
|
|
401
|
+
// The assembled report structure returned by GET /api/reports/:id — composed
|
|
402
|
+
// server-side from the normalized tables (reports → report_pages →
|
|
403
|
+
// report_components + report_filters, plus report_filter_groups). All keys are
|
|
404
|
+
// camelCase; component `config` is the standard format the renderer maps to
|
|
405
|
+
// ECharts options / FsList config.
|
|
406
|
+
// How each granularity reads in the group-by control and on an export cover
|
|
407
|
+
// sheet. WHICH of them a given report offers comes from its ReportGroupBy;
|
|
408
|
+
// this is only the naming. 'year' reads as "Annually".
|
|
409
|
+
const GRANULARITY_OPTIONS = [
|
|
410
|
+
{ value: 'day', name: 'Day' },
|
|
411
|
+
{ value: 'week', name: 'Week' },
|
|
412
|
+
{ value: 'month', name: 'Month' },
|
|
413
|
+
{ value: 'quarter', name: 'Quarter' },
|
|
414
|
+
{ value: 'year', name: 'Annually' },
|
|
415
|
+
];
|
|
416
|
+
var ReportNodeType;
|
|
417
|
+
(function (ReportNodeType) {
|
|
418
|
+
ReportNodeType["Folder"] = "folder";
|
|
419
|
+
ReportNodeType["Report"] = "report";
|
|
420
|
+
})(ReportNodeType || (ReportNodeType = {}));
|
|
421
|
+
// Reports are soft-deletable: a deleted report leaves the listing but keeps its
|
|
422
|
+
// pages and components, and the listing's "Show Deleted" mode restores it.
|
|
423
|
+
// Folders have no equivalent — they are only deletable once empty, so they hard
|
|
424
|
+
// delete and there is nothing to restore.
|
|
425
|
+
var ReportState;
|
|
426
|
+
(function (ReportState) {
|
|
427
|
+
ReportState["Active"] = "active";
|
|
428
|
+
ReportState["Deleted"] = "deleted";
|
|
429
|
+
})(ReportState || (ReportState = {}));
|
|
430
|
+
const ReportStates = [
|
|
431
|
+
{ name: 'Active', value: ReportState.Active },
|
|
432
|
+
{ name: 'Deleted', value: ReportState.Deleted },
|
|
433
|
+
];
|
|
434
|
+
|
|
401
435
|
// A date-range boundary as the picked LOCAL calendar day (no time, no tz).
|
|
402
436
|
// Date-range filters are calendar-day semantics, so the boundary must carry
|
|
403
437
|
// only Y-M-D — never a timezone-bearing instant the backend would shift.
|
|
@@ -419,6 +453,12 @@ function dateBoundString(value) {
|
|
|
419
453
|
// the group-keyed path for charts/KPIs and the report bar.)
|
|
420
454
|
// The query key fs-filter uses for a group's item(s).
|
|
421
455
|
const itemName = (group) => `g${group.id}`;
|
|
456
|
+
// The query key for the report's group-by control. Fixed rather than derived
|
|
457
|
+
// from an id because a report has exactly one — and it is NOT a filter group,
|
|
458
|
+
// so it is read straight off the change query instead of through
|
|
459
|
+
// groupValuesFromQuery. Client-side only: the grain travels to the server as
|
|
460
|
+
// the data request's `state.frequency`.
|
|
461
|
+
const GROUP_BY_ITEM = 'groupBy';
|
|
422
462
|
// A select group's distinct options, as { name, value } pairs. The server sends
|
|
423
463
|
// { value, label }: the value is what the filter matches on (an entity's stable
|
|
424
464
|
// id) and the label is what the viewer reads — so a renamed entity keeps its id
|
|
@@ -500,6 +540,65 @@ function filterItemForGroup(group, reportData, reportId, initial) {
|
|
|
500
540
|
};
|
|
501
541
|
}
|
|
502
542
|
}
|
|
543
|
+
// Whether the report has a chart the group-by control can re-bucket. A time
|
|
544
|
+
// x-axis is the whole qualification: the control changes the grain of that
|
|
545
|
+
// axis, and a category axis has no grain to change.
|
|
546
|
+
function hasTimeSeriesChart(report) {
|
|
547
|
+
return (report?.pages ?? []).some((page) => (page.components ?? []).some((component) => component.type === 'chart' && component.config?.xAxis?.kind === 'time'));
|
|
548
|
+
}
|
|
549
|
+
// The grains the report's group-by control offers, named for the viewer. WHICH
|
|
550
|
+
// of them comes from the report; how they read comes from GRANULARITY_OPTIONS.
|
|
551
|
+
// An empty stored list means every grain — the same rule the backend applies.
|
|
552
|
+
function groupByOptions(report) {
|
|
553
|
+
const granularities = report.config?.groupBy?.granularities ?? [];
|
|
554
|
+
return granularities.length
|
|
555
|
+
? GRANULARITY_OPTIONS.filter((option) => granularities.includes(option.value))
|
|
556
|
+
: GRANULARITY_OPTIONS;
|
|
557
|
+
}
|
|
558
|
+
// Whether a grain is one the report's control still offers. A grain it doesn't
|
|
559
|
+
// cannot stay in effect: the agent can narrow the list at any time, and a chart
|
|
560
|
+
// left bucketing at a dropped grain is one the viewer can neither see nor change.
|
|
561
|
+
function isGranularityOffered(report, granularity) {
|
|
562
|
+
return !granularity || groupByOptions(report).some((option) => option.value === granularity);
|
|
563
|
+
}
|
|
564
|
+
// The fs-filter config item for the report's group-by control, or null when the
|
|
565
|
+
// report has none to show.
|
|
566
|
+
//
|
|
567
|
+
// It leads the same bar the filters render in, but it is NOT one: a filter
|
|
568
|
+
// narrows WHICH rows are counted, this changes how the surviving rows are
|
|
569
|
+
// BUCKETED — and it comes first because the grain is what a viewer settles
|
|
570
|
+
// before deciding which rows they want.
|
|
571
|
+
// Picking a grain sets a session-wide override that rides along in each chart's
|
|
572
|
+
// data request, so the charts themselves are never rewritten.
|
|
573
|
+
function groupByFilterItem(report, initial) {
|
|
574
|
+
const groupBy = report.config?.groupBy;
|
|
575
|
+
// Only an EXPLICIT false hides the control. An absent block means a report
|
|
576
|
+
// served by a build that predates the control being stored, and every one of
|
|
577
|
+
// those had one — the backend resolves it the same way (ReportGroupBy).
|
|
578
|
+
if (groupBy?.enabled === false || !hasTimeSeriesChart(report)) {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
const values = groupByOptions(report);
|
|
582
|
+
return {
|
|
583
|
+
name: GROUP_BY_ITEM,
|
|
584
|
+
type: ItemType.Select,
|
|
585
|
+
label: groupBy?.label || 'Group By',
|
|
586
|
+
multiple: false,
|
|
587
|
+
values: () => values,
|
|
588
|
+
default: isGranularityOffered(report, initial ?? null) ? (initial ?? undefined) : undefined,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
// The group-by control's contribution to the toolbar's fs-filter key. fs-filter
|
|
592
|
+
// reads its config once at init, and the agent can change the control's label
|
|
593
|
+
// or the grains it offers — without those in the key the bar keeps rendering
|
|
594
|
+
// the control it was created with until a full page reload.
|
|
595
|
+
function groupBySignature(report) {
|
|
596
|
+
if (!report || !groupByFilterItem(report)) {
|
|
597
|
+
return '';
|
|
598
|
+
}
|
|
599
|
+
const groupBy = report.config?.groupBy;
|
|
600
|
+
return ['groupBy', groupBy?.label ?? '', (groupBy?.granularities ?? []).join(',')].join(':');
|
|
601
|
+
}
|
|
503
602
|
// Read an fs-filter change query into per-group values. Returns one entry per
|
|
504
603
|
// group with the value to store (or null to clear) — callers feed these to
|
|
505
604
|
// ReportFilterStateService.setValue.
|
|
@@ -579,10 +678,11 @@ class ReportFilterStateService {
|
|
|
579
678
|
_values = new Map();
|
|
580
679
|
_groups = new Map();
|
|
581
680
|
_sessionDisabled = new Set();
|
|
582
|
-
// The
|
|
681
|
+
// The report's group-by control: a single session-wide bucket override for all
|
|
583
682
|
// time-series charts. Unset = each chart uses its authored granularity. Never
|
|
584
683
|
// persisted (it dies with this report view), and carried in the data request's
|
|
585
|
-
// `state`, so it propagates through to the PDF/PowerPoint exports too.
|
|
684
|
+
// `state`, so it propagates through to the PDF/PowerPoint exports too. Named
|
|
685
|
+
// for the `frequency` wire key it travels under, not for the control's label.
|
|
586
686
|
_frequency = null;
|
|
587
687
|
_frequencyChanged$ = new Subject();
|
|
588
688
|
// Emits the group id that changed; components listen filtered to their own
|
|
@@ -633,7 +733,7 @@ class ReportFilterStateService {
|
|
|
633
733
|
}
|
|
634
734
|
this._changed$.next(groupId);
|
|
635
735
|
}
|
|
636
|
-
// The active
|
|
736
|
+
// The active group-by grain, or null when the viewer hasn't chosen one.
|
|
637
737
|
frequency() {
|
|
638
738
|
return this._frequency;
|
|
639
739
|
}
|
|
@@ -641,9 +741,9 @@ class ReportFilterStateService {
|
|
|
641
741
|
this._frequency = frequency;
|
|
642
742
|
this._frequencyChanged$.next();
|
|
643
743
|
}
|
|
644
|
-
// Fires whenever the
|
|
645
|
-
// refetches and re-buckets (the override isn't scoped to any filter
|
|
646
|
-
// changesFor() can't carry it).
|
|
744
|
+
// Fires whenever the group-by grain changes — every time-series chart listens
|
|
745
|
+
// so it refetches and re-buckets (the override isn't scoped to any filter
|
|
746
|
+
// group, so changesFor() can't carry it).
|
|
647
747
|
frequencyChanges() {
|
|
648
748
|
return this._frequencyChanged$.asObservable();
|
|
649
749
|
}
|
|
@@ -782,7 +882,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
782
882
|
type: Injectable
|
|
783
883
|
}] });
|
|
784
884
|
|
|
785
|
-
const BUCKET_GRANULARITIES = ['day', 'week', 'month', 'quarter', 'year'];
|
|
786
885
|
// colorBy applies only to single-dimension bar charts, and only when splitBy
|
|
787
886
|
// isn't already driving multiple series: it colors each bar by its category and
|
|
788
887
|
// shows a legend (one series per category, overlapped to full width).
|
|
@@ -1006,7 +1105,9 @@ function numeric(value) {
|
|
|
1006
1105
|
// The effective time bucket the rows were grouped into (from the data endpoint),
|
|
1007
1106
|
// or null when the x-axis isn't a time axis — gates the compact period labels.
|
|
1008
1107
|
function bucketGranularity(data) {
|
|
1009
|
-
return data.granularity &&
|
|
1108
|
+
return data.granularity && GRANULARITY_OPTIONS.some((option) => option.value === data.granularity)
|
|
1109
|
+
? data.granularity
|
|
1110
|
+
: null;
|
|
1010
1111
|
}
|
|
1011
1112
|
// Whether the period-start categories cross a year boundary. When they don't,
|
|
1012
1113
|
// the year is pure noise on every label, so it's dropped entirely.
|
|
@@ -1444,7 +1545,7 @@ class ReportComponentComponent {
|
|
|
1444
1545
|
this._filterState.changesFor(this.component)
|
|
1445
1546
|
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
1446
1547
|
.subscribe(() => this._refresh$.next());
|
|
1447
|
-
// A
|
|
1548
|
+
// A group-by change re-buckets every time-series chart; it isn't scoped
|
|
1448
1549
|
// to a filter group, so it rides its own stream.
|
|
1449
1550
|
if (this._isTimeSeries) {
|
|
1450
1551
|
this._filterState.frequencyChanges()
|
|
@@ -1688,7 +1789,7 @@ class ReportComponentComponent {
|
|
|
1688
1789
|
h: this.component.h,
|
|
1689
1790
|
});
|
|
1690
1791
|
}
|
|
1691
|
-
// A chart on a time x-axis — the only component the
|
|
1792
|
+
// A chart on a time x-axis — the only component the report's group-by control
|
|
1692
1793
|
// applies to.
|
|
1693
1794
|
get _isTimeSeries() {
|
|
1694
1795
|
return this.component.type === 'chart' && this.component.config?.xAxis?.kind === 'time';
|
|
@@ -1743,8 +1844,8 @@ class ReportComponentComponent {
|
|
|
1743
1844
|
this.error = null;
|
|
1744
1845
|
this._cdRef.markForCheck();
|
|
1745
1846
|
const filters = this._filterState.resolveForComponent(this.component);
|
|
1746
|
-
// Time-series charts carry the
|
|
1747
|
-
// backend re-buckets; everything else sends no interaction state here.
|
|
1847
|
+
// Time-series charts carry the group-by grain (if the viewer picked one) so
|
|
1848
|
+
// the backend re-buckets; everything else sends no interaction state here.
|
|
1748
1849
|
const state = this._isTimeSeries && this._filterState.frequency()
|
|
1749
1850
|
? { frequency: this._filterState.frequency() }
|
|
1750
1851
|
: {};
|
|
@@ -2628,40 +2729,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2628
2729
|
], template: "<form\n fsForm\n [submit]=\"save\">\n <fs-dialog>\n <h1 mat-dialog-title>\n Report Settings\n </h1>\n <mat-dialog-content>\n <mat-tab-group [(selected)]=\"selectedTab\">\n <mat-tab\n label=\"Settings\"\n name=\"settings\">\n <div class=\"fs-column tab-body\">\n <mat-form-field>\n <mat-label>\n Name\n </mat-label>\n <input\n matInput\n [(ngModel)]=\"name\"\n name=\"name\"\n [fsFormRequired]=\"true\">\n </mat-form-field>\n <!--\n The page, typed in inches. Orientation is not asked for because it\n is not an independent choice - a page wider than it is tall IS\n landscape - so the two numbers say everything there is to say.\n -->\n <div class=\"fs-row.gap-sm.align-start\">\n <mat-form-field class=\"fs-flex\">\n <mat-label>\n Width\n </mat-label>\n <input\n matInput\n type=\"number\"\n step=\"0.1\"\n [(ngModel)]=\"pageWidth\"\n name=\"pageWidth\"\n [fsFormRequired]=\"true\"\n [fsFormMin]=\"pageDimensionMin\"\n [fsFormMax]=\"pageDimensionMax\">\n <span matTextSuffix>\n inches\n </span>\n </mat-form-field>\n <mat-form-field class=\"fs-flex\">\n <mat-label>\n Height\n </mat-label>\n <input\n matInput\n type=\"number\"\n step=\"0.1\"\n [(ngModel)]=\"pageHeight\"\n name=\"pageHeight\"\n [fsFormRequired]=\"true\"\n [fsFormMin]=\"pageDimensionMin\"\n [fsFormMax]=\"pageDimensionMax\">\n <span matTextSuffix>\n inches\n </span>\n </mat-form-field>\n </div>\n <mat-form-field>\n <mat-label>\n Layout\n </mat-label>\n <mat-select\n [(ngModel)]=\"layout\"\n name=\"layout\">\n <mat-option value=\"freeform\">\n Freeform \u2014 position components anywhere on the page\n </mat-option>\n <mat-option value=\"flow\">\n Flow \u2014 components flow into rows by width %\n </mat-option>\n </mat-select>\n <mat-hint>\n Freeform is like PowerPoint; Flow is like a responsive dashboard.\n </mat-hint>\n </mat-form-field>\n <fs-ai-report-timezone-select [(timezone)]=\"timezone\"></fs-ai-report-timezone-select>\n </div>\n </mat-tab>\n <mat-tab\n label=\"Styles\"\n name=\"styles\">\n <div class=\"fs-column tab-body\">\n <mat-form-field>\n <mat-label>\n Heading Size\n </mat-label>\n <input\n matInput\n type=\"number\"\n step=\"1\"\n min=\"6\"\n max=\"96\"\n [(ngModel)]=\"headingSize\"\n name=\"headingSize\"\n [fsFormMin]=\"6\"\n [fsFormMax]=\"96\">\n <span matTextSuffix>\n pt\n </span>\n <mat-hint>\n Applies to component titles. Points map 1:1 to PDF and PowerPoint.\n </mat-hint>\n </mat-form-field>\n </div>\n </mat-tab>\n <mat-tab\n label=\"Filters\"\n name=\"filters\">\n <div class=\"fs-column tab-body\">\n @if (filterRows().length) {\n @for (row of filterRows(); track row.group.id) {\n @switch (row.group.type) {\n @case ('dateRange') {\n <div class=\"fs-row.gap-sm.align-center fs-flex\">\n <mat-form-field class=\"fs-flex\">\n <mat-label>\n Default From {{ row.label }}\n </mat-label>\n <input\n matInput\n fsDatePicker\n [(ngModel)]=\"row.start\"\n [name]=\"'start' + row.group.id\">\n </mat-form-field>\n <mat-form-field class=\"fs-flex\">\n <mat-label>\n Default To {{ row.label }}\n </mat-label>\n <input\n matInput\n fsDatePicker\n [(ngModel)]=\"row.end\"\n [name]=\"'end' + row.group.id\">\n </mat-form-field>\n </div>\n }\n @case ('select') {\n <fs-autocomplete-chips\n class=\"fs-flex\"\n [label]=\"'Default ' + row.label\"\n [fetch]=\"row.fetch\"\n [(ngModel)]=\"row.values\"\n [name]=\"'values' + row.group.id\"\n [multiple]=\"true\"\n [fetchOnFocus]=\"true\">\n <ng-template\n fsAutocompleteChipsTemplate\n let-object=\"object\">\n {{ object.name }}\n </ng-template>\n </fs-autocomplete-chips>\n }\n @default {\n <mat-form-field class=\"fs-flex\">\n <mat-label>\n Default {{ row.label }}\n </mat-label>\n <input\n matInput\n [(ngModel)]=\"row.value\"\n [name]=\"'value' + row.group.id\">\n </mat-form-field>\n }\n }\n }\n } @else {\n <fs-message-info>\n This report has no report-level filters yet. Add a filter to a\n component (its settings \u2192 Filters) and show it in the report bar,\n then set its default here.\n </fs-message-info>\n }\n </div>\n </mat-tab>\n </mat-tab-group>\n </mat-dialog-content>\n <mat-dialog-actions>\n <fs-form-dialog-actions>\n <button\n type=\"button\"\n mat-button\n color=\"warn\"\n (click)=\"delete()\">\n Delete\n </button>\n </fs-form-dialog-actions>\n </mat-dialog-actions>\n </fs-dialog>\n</form>", styles: ["mat-form-field{width:100%}.tab-body{padding-top:16px}\n"] }]
|
|
2629
2730
|
}] });
|
|
2630
2731
|
|
|
2631
|
-
// The assembled report structure returned by GET /api/reports/:id — composed
|
|
2632
|
-
// server-side from the normalized tables (reports → report_pages →
|
|
2633
|
-
// report_components + report_filters, plus report_filter_groups). All keys are
|
|
2634
|
-
// camelCase; component `config` is the standard format the renderer maps to
|
|
2635
|
-
// ECharts options / FsList config.
|
|
2636
|
-
// The runtime Frequency control's choices — re-buckets every time-series chart
|
|
2637
|
-
// at view time, overriding each chart's authored granularity. 'year' reads as
|
|
2638
|
-
// "Annually" in the control.
|
|
2639
|
-
const FREQUENCY_OPTIONS = [
|
|
2640
|
-
{ value: 'day', name: 'Day' },
|
|
2641
|
-
{ value: 'week', name: 'Week' },
|
|
2642
|
-
{ value: 'month', name: 'Month' },
|
|
2643
|
-
{ value: 'quarter', name: 'Quarter' },
|
|
2644
|
-
{ value: 'year', name: 'Annually' },
|
|
2645
|
-
];
|
|
2646
|
-
var ReportNodeType;
|
|
2647
|
-
(function (ReportNodeType) {
|
|
2648
|
-
ReportNodeType["Folder"] = "folder";
|
|
2649
|
-
ReportNodeType["Report"] = "report";
|
|
2650
|
-
})(ReportNodeType || (ReportNodeType = {}));
|
|
2651
|
-
// Reports are soft-deletable: a deleted report leaves the listing but keeps its
|
|
2652
|
-
// pages and components, and the listing's "Show Deleted" mode restores it.
|
|
2653
|
-
// Folders have no equivalent — they are only deletable once empty, so they hard
|
|
2654
|
-
// delete and there is nothing to restore.
|
|
2655
|
-
var ReportState;
|
|
2656
|
-
(function (ReportState) {
|
|
2657
|
-
ReportState["Active"] = "active";
|
|
2658
|
-
ReportState["Deleted"] = "deleted";
|
|
2659
|
-
})(ReportState || (ReportState = {}));
|
|
2660
|
-
const ReportStates = [
|
|
2661
|
-
{ name: 'Active', value: ReportState.Active },
|
|
2662
|
-
{ name: 'Deleted', value: ReportState.Deleted },
|
|
2663
|
-
];
|
|
2664
|
-
|
|
2665
2732
|
// Rows fetched for a list component's export table — enough to fill a
|
|
2666
2733
|
// slide/page table; the cap note covers the rest.
|
|
2667
2734
|
const LIST_EXPORT_ROWS = 100;
|
|
@@ -2699,7 +2766,7 @@ class ReportExportCollectorService {
|
|
|
2699
2766
|
}
|
|
2700
2767
|
_componentData(report, component) {
|
|
2701
2768
|
const filters = this._filterState.resolveForComponent(component);
|
|
2702
|
-
// A time-series chart carries the viewer's
|
|
2769
|
+
// A time-series chart carries the viewer's group-by grain so the export
|
|
2703
2770
|
// re-buckets exactly as the screen does; lists carry their export paging.
|
|
2704
2771
|
const frequency = this._filterState.frequency();
|
|
2705
2772
|
const isTimeSeries = component.type === 'chart' && component.config?.xAxis?.kind === 'time';
|
|
@@ -2733,10 +2800,14 @@ class ReportExportCollectorService {
|
|
|
2733
2800
|
lines.push(of(`${label}: "${value.value.trim()}"`));
|
|
2734
2801
|
}
|
|
2735
2802
|
}
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2803
|
+
// The group-by grain, under the report's own name for the control — a cover
|
|
2804
|
+
// sheet reading "Frequency" when the report calls it "Period" describes a
|
|
2805
|
+
// control the reader cannot find on screen.
|
|
2806
|
+
const granularity = this._filterState.frequency();
|
|
2807
|
+
if (granularity) {
|
|
2808
|
+
const option = GRANULARITY_OPTIONS.find((item) => item.value === granularity);
|
|
2809
|
+
const label = report.config?.groupBy?.label || 'Group By';
|
|
2810
|
+
lines.push(of(`${label}: ${option?.name ?? granularity}`));
|
|
2740
2811
|
}
|
|
2741
2812
|
return lines.length ? forkJoin(lines) : of([]);
|
|
2742
2813
|
}
|
|
@@ -2774,7 +2845,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2774
2845
|
// complete synchronously after setOption.
|
|
2775
2846
|
async function renderChartImage(component, data, width, height, pixelRatio = 3) {
|
|
2776
2847
|
// The same tree-shaken echarts build + 'report' theme the live canvas uses.
|
|
2777
|
-
const echarts = (await import('./firestitch-report-echarts-
|
|
2848
|
+
const echarts = (await import('./firestitch-report-echarts-gBhnkDol.mjs')).default;
|
|
2778
2849
|
const host = document.createElement('div');
|
|
2779
2850
|
host.style.width = `${width}px`;
|
|
2780
2851
|
host.style.height = `${height}px`;
|
|
@@ -3228,9 +3299,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3228
3299
|
}]
|
|
3229
3300
|
}] });
|
|
3230
3301
|
|
|
3231
|
-
// fs-filter query key for the runtime Frequency control (a render-layer bucket
|
|
3232
|
-
// override, not a filter group — so it's read straight off the query).
|
|
3233
|
-
const FREQUENCY_ITEM$1 = 'frequency';
|
|
3234
3302
|
// The Reports page: AI chat on the left, the report canvas on the right —
|
|
3235
3303
|
// a split-pane layout; the viewer is our own canvas (no iframe, no embed URLs).
|
|
3236
3304
|
// The chat is always scoped to the selected report; its id rides on every
|
|
@@ -3457,11 +3525,10 @@ class ReportComponent {
|
|
|
3457
3525
|
.filter((group) => group.level === 'report' || group.level === 'both')
|
|
3458
3526
|
.sort((a, b) => a.order - b.order);
|
|
3459
3527
|
}
|
|
3460
|
-
//
|
|
3461
|
-
//
|
|
3462
|
-
// fs-filter so it isn't rendered (taking space) when there's nothing to show.
|
|
3528
|
+
// Gates the toolbar fs-filter so it isn't rendered (taking space) when there
|
|
3529
|
+
// is nothing to put in it — no report-level filters and no group-by control.
|
|
3463
3530
|
get reportHasFilters() {
|
|
3464
|
-
return this._reportGroups().length > 0 || this.
|
|
3531
|
+
return this._reportGroups().length > 0 || !!this._groupByItem();
|
|
3465
3532
|
}
|
|
3466
3533
|
// fs-filter reads its config only once (at init) — re-binding does nothing.
|
|
3467
3534
|
// The template keys the toolbar fs-filter on this signature so it's recreated
|
|
@@ -3472,28 +3539,31 @@ class ReportComponent {
|
|
|
3472
3539
|
// The stored default is part of the signature: saving a new default in report
|
|
3473
3540
|
// settings rebuilds the config, but without a key change fs-filter keeps
|
|
3474
3541
|
// rendering the values it was created with until a full page reload.
|
|
3542
|
+
groupBySignature(this.report),
|
|
3475
3543
|
...this._reportGroups().map((group) => `${group.id}:${group.level}:${group.label}:${defaultSignature(group)}`),
|
|
3476
|
-
this._hasTimeSeriesChart() ? 'frequency' : '',
|
|
3477
3544
|
].join('|');
|
|
3478
3545
|
}
|
|
3479
|
-
// (Re)build the toolbar fs-filter config — report-level filter items
|
|
3480
|
-
//
|
|
3481
|
-
//
|
|
3482
|
-
//
|
|
3546
|
+
// (Re)build the toolbar fs-filter config — the report-level filter items and
|
|
3547
|
+
// then the group-by control. The report's actions live in the fs-menu beside
|
|
3548
|
+
// the picker, not here. Only built/used once a report is loaded (the template
|
|
3549
|
+
// gates it on `report`).
|
|
3483
3550
|
_buildReportConfig() {
|
|
3484
3551
|
const reportId = this.report?.id ?? null;
|
|
3552
|
+
// Drop a grain the control no longer offers — the agent can narrow the list
|
|
3553
|
+
// mid-session, and an override the viewer can't see is worse than none.
|
|
3554
|
+
if (this.report && !isGranularityOffered(this.report, this._filterState.frequency())) {
|
|
3555
|
+
this._filterState.setFrequency(null);
|
|
3556
|
+
}
|
|
3485
3557
|
const items = reportId
|
|
3486
3558
|
? this._reportGroups().map((group) => filterItemForGroup(group, this._reportData, reportId, this._filterState.value(group.id)))
|
|
3487
3559
|
: [];
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
default: this._filterState.frequency() ?? undefined,
|
|
3496
|
-
});
|
|
3560
|
+
// The group-by control leads the bar, ahead of every filter: it sets the
|
|
3561
|
+
// grain the whole report is read at, so it is the first thing a viewer
|
|
3562
|
+
// decides. It is not a filter group — it re-buckets rows rather than
|
|
3563
|
+
// narrowing them — so it is placed here rather than ordered among them.
|
|
3564
|
+
const groupByItem = this._groupByItem();
|
|
3565
|
+
if (groupByItem) {
|
|
3566
|
+
items.unshift(groupByItem);
|
|
3497
3567
|
}
|
|
3498
3568
|
this.reportFilterConfig = {
|
|
3499
3569
|
// Never touch the URL or persist filter state — report filters are
|
|
@@ -3505,15 +3575,18 @@ class ReportComponent {
|
|
|
3505
3575
|
for (const { groupId, value } of groupValuesFromQuery(query ?? {}, this._reportGroups())) {
|
|
3506
3576
|
this._filterState.setValue(groupId, value);
|
|
3507
3577
|
}
|
|
3508
|
-
//
|
|
3509
|
-
this._filterState.setFrequency(query?.[
|
|
3578
|
+
// Not a filter group — the grain is read straight off the query.
|
|
3579
|
+
this._filterState.setFrequency(query?.[GROUP_BY_ITEM] || null);
|
|
3510
3580
|
},
|
|
3511
3581
|
};
|
|
3512
3582
|
}
|
|
3513
|
-
// The report
|
|
3514
|
-
//
|
|
3515
|
-
|
|
3516
|
-
|
|
3583
|
+
// The report's group-by control as an fs-filter item, or null when the report
|
|
3584
|
+
// has none to show. Seeded from the session state so the grain the viewer
|
|
3585
|
+
// picked survives the report being re-fetched after a chat edit.
|
|
3586
|
+
_groupByItem() {
|
|
3587
|
+
return this.report
|
|
3588
|
+
? groupByFilterItem(this.report, this._filterState.frequency())
|
|
3589
|
+
: null;
|
|
3517
3590
|
}
|
|
3518
3591
|
_refreshReportName(reportId) {
|
|
3519
3592
|
this._reportService.load()
|
|
@@ -3539,7 +3612,7 @@ class ReportComponent {
|
|
|
3539
3612
|
ReportPdfService,
|
|
3540
3613
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
3541
3614
|
// this route's chunk.
|
|
3542
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
3615
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-gBhnkDol.mjs').then((module) => module.default) }),
|
|
3543
3616
|
], 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) {\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 <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 (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 });
|
|
3544
3617
|
}
|
|
3545
3618
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportComponent, decorators: [{
|
|
@@ -3553,7 +3626,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3553
3626
|
ReportPdfService,
|
|
3554
3627
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
3555
3628
|
// this route's chunk.
|
|
3556
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
3629
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-gBhnkDol.mjs').then((module) => module.default) }),
|
|
3557
3630
|
], imports: [
|
|
3558
3631
|
FormsModule,
|
|
3559
3632
|
FsAutocompleteChipsModule,
|
|
@@ -4602,9 +4675,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4602
4675
|
], 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 class=\"fs-row.gap-sm.align-center node-row\">\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) {\n <span class=\"node-name\">{{ data.name }}</span>\n } @else if (isDeleted(data.report)) {\n <!-- A deleted report can't be opened (the backend only assembles\n active ones), 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 </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-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"] }]
|
|
4603
4676
|
}], ctorParameters: () => [] });
|
|
4604
4677
|
|
|
4605
|
-
// fs-filter query key for the runtime Frequency control (a render-layer bucket
|
|
4606
|
-
// override, not a filter group — so it's read straight off the query).
|
|
4607
|
-
const FREQUENCY_ITEM = 'frequency';
|
|
4608
4678
|
// A single report: the AI chat on the left, the report canvas on the right,
|
|
4609
4679
|
// under an H2 title + settings gear. The host supplies [reportId] (from its
|
|
4610
4680
|
// route) and switches reports by navigating — this component never picks one.
|
|
@@ -4810,10 +4880,10 @@ class FsAiReportComponent {
|
|
|
4810
4880
|
.filter((group) => group.level === 'report' || group.level === 'both')
|
|
4811
4881
|
.sort((a, b) => a.order - b.order);
|
|
4812
4882
|
}
|
|
4813
|
-
//
|
|
4814
|
-
//
|
|
4883
|
+
// Gates the toolbar fs-filter so it isn't rendered when there is nothing to
|
|
4884
|
+
// put in it — no report-level filters and no group-by control.
|
|
4815
4885
|
get reportHasFilters() {
|
|
4816
|
-
return this._reportGroups().length > 0 || this.
|
|
4886
|
+
return this._reportGroups().length > 0 || !!this._groupByItem();
|
|
4817
4887
|
}
|
|
4818
4888
|
// fs-filter reads its config only once (at init). The template keys the
|
|
4819
4889
|
// toolbar fs-filter on this signature so it's recreated (and re-reads the
|
|
@@ -4823,26 +4893,29 @@ class FsAiReportComponent {
|
|
|
4823
4893
|
// The stored default is part of the signature: saving a new default in report
|
|
4824
4894
|
// settings rebuilds the config, but without a key change fs-filter keeps
|
|
4825
4895
|
// rendering the values it was created with until a full page reload.
|
|
4896
|
+
groupBySignature(this.report),
|
|
4826
4897
|
...this._reportGroups().map((group) => `${group.id}:${group.level}:${group.label}:${defaultSignature(group)}`),
|
|
4827
|
-
this._hasTimeSeriesChart() ? 'frequency' : '',
|
|
4828
4898
|
].join('|');
|
|
4829
4899
|
}
|
|
4830
|
-
// (Re)build the toolbar fs-filter config — report-level filter items
|
|
4831
|
-
//
|
|
4900
|
+
// (Re)build the toolbar fs-filter config — the report-level filter items and
|
|
4901
|
+
// then the group-by control.
|
|
4832
4902
|
_buildReportConfig() {
|
|
4833
4903
|
const reportId = this.report?.id ?? null;
|
|
4904
|
+
// Drop a grain the control no longer offers — the agent can narrow the list
|
|
4905
|
+
// mid-session, and an override the viewer can't see is worse than none.
|
|
4906
|
+
if (this.report && !isGranularityOffered(this.report, this._filterState.frequency())) {
|
|
4907
|
+
this._filterState.setFrequency(null);
|
|
4908
|
+
}
|
|
4834
4909
|
const items = reportId
|
|
4835
4910
|
? this._reportGroups().map((group) => filterItemForGroup(group, this._reportData, reportId, this._filterState.value(group.id)))
|
|
4836
4911
|
: [];
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
default: this._filterState.frequency() ?? undefined,
|
|
4845
|
-
});
|
|
4912
|
+
// The group-by control leads the bar, ahead of every filter: it sets the
|
|
4913
|
+
// grain the whole report is read at, so it is the first thing a viewer
|
|
4914
|
+
// decides. It is not a filter group — it re-buckets rows rather than
|
|
4915
|
+
// narrowing them — so it is placed here rather than ordered among them.
|
|
4916
|
+
const groupByItem = this._groupByItem();
|
|
4917
|
+
if (groupByItem) {
|
|
4918
|
+
items.unshift(groupByItem);
|
|
4846
4919
|
}
|
|
4847
4920
|
this.reportFilterConfig = {
|
|
4848
4921
|
// Never touch the URL or persist filter state — report filters are
|
|
@@ -4854,15 +4927,18 @@ class FsAiReportComponent {
|
|
|
4854
4927
|
for (const { groupId, value } of groupValuesFromQuery(query ?? {}, this._reportGroups())) {
|
|
4855
4928
|
this._filterState.setValue(groupId, value);
|
|
4856
4929
|
}
|
|
4857
|
-
//
|
|
4858
|
-
this._filterState.setFrequency(query?.[
|
|
4930
|
+
// Not a filter group — the grain is read straight off the query.
|
|
4931
|
+
this._filterState.setFrequency(query?.[GROUP_BY_ITEM] || null);
|
|
4859
4932
|
},
|
|
4860
4933
|
};
|
|
4861
4934
|
}
|
|
4862
|
-
// The report
|
|
4863
|
-
//
|
|
4864
|
-
|
|
4865
|
-
|
|
4935
|
+
// The report's group-by control as an fs-filter item, or null when the report
|
|
4936
|
+
// has none to show. Seeded from the session state so the grain the viewer
|
|
4937
|
+
// picked survives the report being re-fetched after a chat edit.
|
|
4938
|
+
_groupByItem() {
|
|
4939
|
+
return this.report
|
|
4940
|
+
? groupByFilterItem(this.report, this._filterState.frequency())
|
|
4941
|
+
: null;
|
|
4866
4942
|
}
|
|
4867
4943
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4868
4944
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FsAiReportComponent, isStandalone: true, selector: "fs-ai-report", inputs: { reportId: { classPropertyName: "reportId", publicName: "reportId", isSignal: true, isRequired: false, transformFunction: null }, basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { deleted: "deleted", renamed: "renamed" }, providers: [
|
|
@@ -4873,7 +4949,7 @@ class FsAiReportComponent {
|
|
|
4873
4949
|
ReportPptxService,
|
|
4874
4950
|
ReportPdfService,
|
|
4875
4951
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
4876
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
4952
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-gBhnkDol.mjs').then((module) => module.default) }),
|
|
4877
4953
|
], 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 <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 <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]=\"{ 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$2.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 });
|
|
4878
4954
|
}
|
|
4879
4955
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportComponent, decorators: [{
|
|
@@ -4886,7 +4962,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4886
4962
|
ReportPptxService,
|
|
4887
4963
|
ReportPdfService,
|
|
4888
4964
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
4889
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
4965
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-gBhnkDol.mjs').then((module) => module.default) }),
|
|
4890
4966
|
], imports: [
|
|
4891
4967
|
FsFilterModule,
|
|
4892
4968
|
FsMenuModule,
|
|
@@ -4919,5 +4995,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4919
4995
|
* Generated bundle index. Do not edit.
|
|
4920
4996
|
*/
|
|
4921
4997
|
|
|
4922
|
-
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,
|
|
4923
|
-
//# sourceMappingURL=firestitch-report-firestitch-report-
|
|
4998
|
+
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, ReportStates as h };
|
|
4999
|
+
//# sourceMappingURL=firestitch-report-firestitch-report-DpoqO7tP.mjs.map
|