@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.
Files changed (25) hide show
  1. package/app/reports/data/report.data.d.ts +1 -0
  2. package/app/reports/dialogs/global-settings/components/semantic-database/semantic-database.component.d.ts +5 -3
  3. package/app/reports/export/report-export-collector.service.d.ts +1 -0
  4. package/app/reports/format.d.ts +1 -1
  5. package/app/reports/interfaces/report.interface.d.ts +8 -1
  6. package/app/reports/views/reports/fs-ai-reports.component.d.ts +2 -1
  7. package/esm2022/app/reports/components/component-kpi/component-kpi.component.mjs +2 -2
  8. package/esm2022/app/reports/data/report.data.mjs +11 -1
  9. package/esm2022/app/reports/dialogs/global-settings/components/semantic-database/semantic-database.component.mjs +11 -6
  10. package/esm2022/app/reports/export/report-export-collector.service.mjs +31 -11
  11. package/esm2022/app/reports/export/report-pdf.service.mjs +6 -5
  12. package/esm2022/app/reports/export/report-pptx.service.mjs +6 -5
  13. package/esm2022/app/reports/format.mjs +21 -6
  14. package/esm2022/app/reports/interfaces/report.interface.mjs +8 -1
  15. package/esm2022/app/reports/services/report-filter-state.service.mjs +17 -2
  16. package/esm2022/app/reports/views/report/fs-ai-report.component.mjs +2 -2
  17. package/esm2022/app/reports/views/report/report.component.mjs +2 -2
  18. package/esm2022/app/reports/views/reports/fs-ai-reports.component.mjs +34 -3
  19. package/fesm2022/{firestitch-report-echarts-F254vpF1.mjs → firestitch-report-echarts-CfwgLaOA.mjs} +2 -2
  20. package/fesm2022/{firestitch-report-echarts-F254vpF1.mjs.map → firestitch-report-echarts-CfwgLaOA.mjs.map} +1 -1
  21. package/fesm2022/{firestitch-report-firestitch-report-Dh8kteJk.mjs → firestitch-report-firestitch-report-_MCMM4XH.mjs} +144 -41
  22. package/fesm2022/firestitch-report-firestitch-report-_MCMM4XH.mjs.map +1 -0
  23. package/fesm2022/firestitch-report.mjs +1 -1
  24. package/package.json +1 -1
  25. 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) means the same thing on
987
- // screen, in PowerPoint and in PDF.
988
- function formatMeasureValue(value, format) {
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
- return all$
2513
- .pipe(map$1((collected) => {
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: this._filterSummary(report),
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: A, B" — one
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(`${label}: ${value.values.map((item) => String(item)).join(', ')}`);
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-F254vpF1.mjs')).default;
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-F254vpF1.mjs').then((module) => module.default) }),
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-F254vpF1.mjs').then((module) => module.default) }),
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 === 'log'
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 — a
3703
- * build that failed (e.g. the AI couldn't be reached) reports 'Failed' with
3704
- * a reason, and that must surface instead of the run silently doing nothing.
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 — check the AI connection and try again.');
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.1.0", version: "18.2.14", type: FsAiReportsComponent, isStandalone: true, selector: "fs-ai-reports", inputs: { basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null }, openPattern: { classPropertyName: "openPattern", publicName: "openPattern", isSignal: true, isRequired: false, transformFunction: null }, createPattern: { classPropertyName: "createPattern", publicName: "createPattern", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "list", first: true, predicate: FsListComponent, descendants: true }], ngImport: i0, template: "<fs-list\n [config]=\"listConfig\"\n #list>\n\n <fs-list-column title=\"Name\">\n <ng-template\n fs-list-cell\n let-row=\"row\">\n <a [routerLink]=\"openLink(row)\">{{ row.name }}</a>\n </ng-template>\n </fs-list-column>\n\n <fs-list-column title=\"Modified\">\n <ng-template\n fs-list-cell\n let-row=\"row\">\n <fs-date [date]=\"row.modifyDate\"></fs-date>\n </ng-template>\n </fs-list-column>\n\n</fs-list>\n", styles: ["a{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: FsDateModule }, { kind: "component", type: 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 });
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-F254vpF1.mjs').then((module) => module.default) }),
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-F254vpF1.mjs').then((module) => module.default) }),
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-Dh8kteJk.mjs.map
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