@meshmakers/octo-meshboard 3.3.690 → 3.3.710

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.
@@ -5,14 +5,14 @@ import { Injectable, inject, EventEmitter, forwardRef, Output, Input, ViewChild,
5
5
  import * as i1$1 from '@angular/forms';
6
6
  import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
7
7
  import { firstValueFrom, from, map, Observable, of, catchError, interval, filter } from 'rxjs';
8
- import * as i1$7 from '@meshmakers/shared-ui';
8
+ import * as i1$8 from '@meshmakers/shared-ui';
9
9
  import { EntitySelectInputComponent, WindowStateService, ListViewComponent, FetchResultTyped, DataSourceBase, TimeRangePickerComponent, ImportStrategyDialogService, TimeRangeUtils, HAS_UNSAVED_CHANGES, UnsavedChangesDirective } from '@meshmakers/shared-ui';
10
10
  import * as i1 from 'apollo-angular';
11
11
  import { gql, Apollo } from 'apollo-angular';
12
12
  import * as i1$3 from '@angular/common';
13
13
  import { CommonModule } from '@angular/common';
14
14
  import { CkTypeSelectorInputComponent, FieldFilterEditorComponent, AttributeValueTypeDto, PropertyGridComponent, OctoGraphQlDataSource, AttributeSelectorDialogService, AttributeSortSelectorDialogService } from '@meshmakers/octo-ui';
15
- import * as i1$4 from '@progress/kendo-angular-dialog';
15
+ import * as i1$5 from '@progress/kendo-angular-dialog';
16
16
  import { WindowService, WindowCloseResult, WindowRef, DialogsModule, DialogRef, DialogModule, DialogService } from '@progress/kendo-angular-dialog';
17
17
  import * as i2 from '@progress/kendo-angular-buttons';
18
18
  import { ButtonsModule, ButtonModule } from '@progress/kendo-angular-buttons';
@@ -20,27 +20,27 @@ import * as i3 from '@progress/kendo-angular-inputs';
20
20
  import { InputsModule, CheckBoxModule, NumericTextBoxModule, TextBoxModule, FormFieldModule } from '@progress/kendo-angular-inputs';
21
21
  import * as i1$2 from '@progress/kendo-angular-indicators';
22
22
  import { LoaderModule } from '@progress/kendo-angular-indicators';
23
- import * as i2$1 from '@progress/kendo-angular-icons';
23
+ import * as i1$4 from '@progress/kendo-angular-icons';
24
24
  import { SVGIconModule } from '@progress/kendo-angular-icons';
25
- import { arrowUpIcon, arrowDownIcon, minusIcon, arrowRightIcon, arrowLeftIcon, linkIcon, chevronDownIcon, chevronRightIcon, columnsIcon, sortAscIcon, filterIcon, searchIcon, chartPieIcon, plusIcon, trashIcon, pencilIcon, checkCircleIcon, chartLineIcon, gearsIcon, clipboardMarkdownIcon, copyIcon, gridIcon, heartIcon, gridLayoutIcon, chartLineMarkersIcon, chartColumnStackedIcon, chartDoughnutIcon, tableIcon, shareIcon, fileTxtIcon, checkIcon, xIcon, downloadIcon, uploadIcon, gearIcon, saveIcon, arrowRotateCwIcon, undoIcon, infoCircleIcon } from '@progress/kendo-svg-icons';
25
+ import { arrowUpIcon, arrowDownIcon, minusIcon, arrowRightIcon, arrowLeftIcon, linkIcon, chevronDownIcon, chevronRightIcon, circleIcon, questionCircleIcon, minusCircleIcon, warningTriangleIcon, exclamationCircleIcon, xCircleIcon, checkCircleIcon, columnsIcon, sortAscIcon, filterIcon, searchIcon, chartPieIcon, infoCircleIcon, plusIcon, trashIcon, pencilIcon, chartLineIcon, gearsIcon, clipboardMarkdownIcon, copyIcon, gridIcon, heartIcon, gridLayoutIcon, chartLineMarkersIcon, chartColumnStackedIcon, chartDoughnutIcon, tableIcon, shareIcon, fileTxtIcon, checkIcon, xIcon, downloadIcon, uploadIcon, gearIcon, saveIcon, arrowRotateCwIcon, undoIcon } from '@progress/kendo-svg-icons';
26
26
  import * as i4 from '@progress/kendo-angular-dropdowns';
27
27
  import { DropDownsModule, DropDownListModule } from '@progress/kendo-angular-dropdowns';
28
28
  import { map as map$1, catchError as catchError$1 } from 'rxjs/operators';
29
29
  import { NotificationService } from '@progress/kendo-angular-notification';
30
- import * as i1$5 from '@progress/kendo-angular-gauges';
30
+ import * as i1$6 from '@progress/kendo-angular-gauges';
31
31
  import { CollectionChangesService, KENDO_GAUGES } from '@progress/kendo-angular-gauges';
32
- import * as i1$6 from '@progress/kendo-angular-charts';
32
+ import * as i1$7 from '@progress/kendo-angular-charts';
33
33
  import { ChartsModule } from '@progress/kendo-angular-charts';
34
34
  import * as i3$1 from '@angular/router';
35
35
  import { Router, ActivatedRoute, NavigationEnd, RouterModule } from '@angular/router';
36
- import * as i2$2 from 'ngx-markdown';
36
+ import * as i2$1 from 'ngx-markdown';
37
37
  import { MarkdownModule } from 'ngx-markdown';
38
+ import * as i4$1 from '@progress/kendo-angular-label';
39
+ import { LabelModule, KENDO_LABELS } from '@progress/kendo-angular-label';
38
40
  import { DomSanitizer } from '@angular/platform-browser';
39
41
  import { GetProcessDiagramDtoGQL, GetProcessDiagramsDtoGQL, CreateProcessDiagramDtoGQL, UpdateProcessDiagramDtoGQL, SymbolLibraryService, ExpressionEvaluatorService, renderAnimations, getFlowParticlesAnimation, renderFlowParticles } from '@meshmakers/octo-process-diagrams';
40
42
  import * as i5 from '@progress/kendo-angular-layout';
41
- import { LayoutModule, TabStripModule, TileLayoutModule } from '@progress/kendo-angular-layout';
42
- import * as i4$1 from '@progress/kendo-angular-label';
43
- import { LabelModule, KENDO_LABELS } from '@progress/kendo-angular-label';
43
+ import { LayoutModule, TabStripModule, TileLayoutModule, TileLayoutComponent } from '@progress/kendo-angular-layout';
44
44
  import * as i4$2 from '@progress/kendo-angular-dateinputs';
45
45
  import { DatePickerModule, DateTimePickerModule } from '@progress/kendo-angular-dateinputs';
46
46
  import { BreadCrumbService } from '@meshmakers/shared-services';
@@ -1120,6 +1120,10 @@ class WidgetRegistryService {
1120
1120
  * Builds base configuration from persisted data.
1121
1121
  */
1122
1122
  buildBaseConfig(data) {
1123
+ const parsedConfig = data.config ? (typeof data.config === 'string' ? JSON.parse(data.config) : data.config) : {};
1124
+ const chromeless = parsedConfig['chromeless'] === true;
1125
+ // Migration: if chromeless is set but no zone, infer zone from chromeless (legacy data)
1126
+ const zone = parsedConfig['zone'] ?? (chromeless ? 'banner' : undefined);
1123
1127
  return {
1124
1128
  id: data.rtId,
1125
1129
  title: data.name,
@@ -1127,7 +1131,9 @@ class WidgetRegistryService {
1127
1131
  row: data.row,
1128
1132
  colSpan: data.colSpan,
1129
1133
  rowSpan: data.rowSpan,
1130
- configurable: true
1134
+ configurable: true,
1135
+ chromeless: chromeless || undefined,
1136
+ zone
1131
1137
  };
1132
1138
  }
1133
1139
  /**
@@ -1549,7 +1555,11 @@ class MeshBoardPersistenceService {
1549
1555
  dataSourceType,
1550
1556
  dataSourceCkTypeId: persistenceData.dataSourceCkTypeId ?? '',
1551
1557
  dataSourceRtId: persistenceData.dataSourceRtId ?? '',
1552
- config: JSON.stringify(persistenceData.config)
1558
+ config: JSON.stringify({
1559
+ ...persistenceData.config,
1560
+ ...(widget.chromeless ? { chromeless: true } : {}),
1561
+ ...(widget.zone && widget.zone !== 'grid' ? { zone: widget.zone } : {})
1562
+ })
1553
1563
  };
1554
1564
  // Only include parent if there are association changes
1555
1565
  if (parentAssociations.length > 0) {
@@ -1728,7 +1738,7 @@ class MeshBoardGridService {
1728
1738
  wouldCauseOverlap(widgets, widgetId, col, row, colSpan, rowSpan) {
1729
1739
  const widgetCells = this.getCells(col, row, colSpan, rowSpan);
1730
1740
  for (const other of widgets) {
1731
- if (other.id === widgetId)
1741
+ if (other.id === widgetId || other.zone === 'banner')
1732
1742
  continue;
1733
1743
  const otherCells = this.getCells(other.col, other.row, other.colSpan, other.rowSpan);
1734
1744
  for (const cell of widgetCells) {
@@ -1773,6 +1783,10 @@ class MeshBoardGridService {
1773
1783
  const movedWidgets = [];
1774
1784
  const occupiedCells = new Set();
1775
1785
  for (const widget of widgets) {
1786
+ // Skip banner-zone widgets — they are rendered outside the grid
1787
+ // and should not participate in grid overlap detection.
1788
+ if (widget.zone === 'banner')
1789
+ continue;
1776
1790
  const cells = this.getCells(widget.col, widget.row, widget.colSpan, widget.rowSpan);
1777
1791
  const hasOverlap = cells.some(c => occupiedCells.has(`${c.row},${c.col}`));
1778
1792
  if (hasOverlap) {
@@ -4019,6 +4033,12 @@ class KpiWidgetComponent {
4019
4033
  default: return 'trend-neutral';
4020
4034
  }
4021
4035
  }, ...(ngDevMode ? [{ debugName: "trendClass" }] : /* istanbul ignore next */ []));
4036
+ comparisonText = computed(() => {
4037
+ if (!this.config?.comparisonText)
4038
+ return null;
4039
+ const variables = this.stateService.getVariables();
4040
+ return this.variableService.resolveVariables(this.config.comparisonText, variables);
4041
+ }, ...(ngDevMode ? [{ debugName: "comparisonText" }] : /* istanbul ignore next */ []));
4022
4042
  ngOnInit() {
4023
4043
  this.loadData();
4024
4044
  }
@@ -4265,11 +4285,11 @@ class KpiWidgetComponent {
4265
4285
  return this.variableService.convertToFieldFilterDto(filters, variables);
4266
4286
  }
4267
4287
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: KpiWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4268
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: KpiWidgetComponent, isStandalone: true, selector: "mm-kpi-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"kpi-widget\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else {\n <div class=\"kpi-content\">\n <div class=\"kpi-value-container\">\n @if (config.prefix) {\n <span class=\"kpi-prefix\">{{ config.prefix }}</span>\n }\n <span class=\"kpi-value\">{{ value() }}</span>\n @if (config.suffix) {\n <span class=\"kpi-suffix\">{{ config.suffix }}</span>\n }\n </div>\n\n @if (label()) {\n <div class=\"kpi-label\">{{ label() }}</div>\n }\n </div>\n\n @if (config.trend) {\n <div class=\"kpi-trend\" [ngClass]=\"trendClass()\">\n <kendo-svg-icon [icon]=\"trendIcon()\" size=\"medium\"></kendo-svg-icon>\n </div>\n }\n }\n</div>\n", styles: [".kpi-widget{display:flex;align-items:center;justify-content:center;height:100%;padding:16px;gap:16px}.kpi-content{display:flex;flex-direction:column;align-items:center;text-align:center}.kpi-value-container{display:flex;align-items:baseline;gap:4px}.kpi-prefix,.kpi-suffix{font-size:1.25rem;font-weight:500;color:var(--kendo-color-subtle, #6c757d)}.kpi-value{font-size:2.5rem;font-weight:700;color:var(--kendo-color-on-surface, #212529);line-height:1}.kpi-label{margin-top:8px;font-size:.875rem;color:var(--kendo-color-subtle, #6c757d)}.kpi-trend{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%}.kpi-trend.trend-up{background:#28a74526;color:var(--kendo-color-success, #28a745)}.kpi-trend.trend-down{background:#dc354526;color:var(--kendo-color-error, #dc3545)}.kpi-trend.trend-neutral{background:#6c757d26;color:var(--kendo-color-subtle, #6c757d)}@media(max-width:768px){.kpi-value{font-size:1.75rem}.kpi-prefix,.kpi-suffix{font-size:1rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i2$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
4288
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: KpiWidgetComponent, isStandalone: true, selector: "mm-kpi-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"kpi-widget\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else {\n <div class=\"kpi-content\">\n <div class=\"kpi-value-container\">\n @if (config.prefix) {\n <span class=\"kpi-prefix\">{{ config.prefix }}</span>\n }\n <span class=\"kpi-value\">{{ value() }}</span>\n @if (config.suffix) {\n <span class=\"kpi-suffix\">{{ config.suffix }}</span>\n }\n </div>\n\n @if (comparisonText()) {\n <div class=\"kpi-comparison\" [ngClass]=\"trendClass()\">\n @if (config.trend) {\n <kendo-svg-icon [icon]=\"trendIcon()\" size=\"xsmall\"></kendo-svg-icon>\n }\n {{ comparisonText() }}\n </div>\n }\n\n @if (label()) {\n <div class=\"kpi-label\">{{ label() }}</div>\n }\n </div>\n\n @if (config.trend) {\n <div class=\"kpi-trend\" [ngClass]=\"trendClass()\">\n <kendo-svg-icon [icon]=\"trendIcon()\" size=\"medium\"></kendo-svg-icon>\n </div>\n }\n }\n</div>\n", styles: [".kpi-widget{display:flex;align-items:center;justify-content:center;height:100%;padding:16px;gap:16px}.kpi-content{display:flex;flex-direction:column;align-items:center;text-align:center}.kpi-value-container{display:flex;align-items:baseline;gap:4px}.kpi-prefix,.kpi-suffix{font-size:1.25rem;font-weight:500;color:var(--kendo-color-subtle, #6c757d)}.kpi-value{font-size:2.5rem;font-weight:700;color:var(--kendo-color-on-surface, #212529);line-height:1}.kpi-comparison{margin-top:4px;font-size:.75rem;font-weight:500;display:flex;align-items:center;gap:4px;justify-content:center}.kpi-comparison.trend-up{color:var(--kendo-color-success, #28a745)}.kpi-comparison.trend-down{color:var(--kendo-color-error, #dc3545)}.kpi-comparison.trend-neutral{color:var(--kendo-color-subtle, #6c757d)}.kpi-label{margin-top:8px;font-size:.875rem;color:var(--kendo-color-subtle, #6c757d)}.kpi-trend{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%}.kpi-trend.trend-up{background:#28a74526;color:var(--kendo-color-success, #28a745)}.kpi-trend.trend-down{background:#dc354526;color:var(--kendo-color-error, #dc3545)}.kpi-trend.trend-neutral{background:#6c757d26;color:var(--kendo-color-subtle, #6c757d)}@media(max-width:768px){.kpi-value{font-size:1.75rem}.kpi-prefix,.kpi-suffix{font-size:1rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i1$4.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
4269
4289
  }
4270
4290
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: KpiWidgetComponent, decorators: [{
4271
4291
  type: Component,
4272
- args: [{ selector: 'mm-kpi-widget', standalone: true, imports: [CommonModule, SVGIconModule, WidgetNotConfiguredComponent], template: "<div class=\"kpi-widget\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else {\n <div class=\"kpi-content\">\n <div class=\"kpi-value-container\">\n @if (config.prefix) {\n <span class=\"kpi-prefix\">{{ config.prefix }}</span>\n }\n <span class=\"kpi-value\">{{ value() }}</span>\n @if (config.suffix) {\n <span class=\"kpi-suffix\">{{ config.suffix }}</span>\n }\n </div>\n\n @if (label()) {\n <div class=\"kpi-label\">{{ label() }}</div>\n }\n </div>\n\n @if (config.trend) {\n <div class=\"kpi-trend\" [ngClass]=\"trendClass()\">\n <kendo-svg-icon [icon]=\"trendIcon()\" size=\"medium\"></kendo-svg-icon>\n </div>\n }\n }\n</div>\n", styles: [".kpi-widget{display:flex;align-items:center;justify-content:center;height:100%;padding:16px;gap:16px}.kpi-content{display:flex;flex-direction:column;align-items:center;text-align:center}.kpi-value-container{display:flex;align-items:baseline;gap:4px}.kpi-prefix,.kpi-suffix{font-size:1.25rem;font-weight:500;color:var(--kendo-color-subtle, #6c757d)}.kpi-value{font-size:2.5rem;font-weight:700;color:var(--kendo-color-on-surface, #212529);line-height:1}.kpi-label{margin-top:8px;font-size:.875rem;color:var(--kendo-color-subtle, #6c757d)}.kpi-trend{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%}.kpi-trend.trend-up{background:#28a74526;color:var(--kendo-color-success, #28a745)}.kpi-trend.trend-down{background:#dc354526;color:var(--kendo-color-error, #dc3545)}.kpi-trend.trend-neutral{background:#6c757d26;color:var(--kendo-color-subtle, #6c757d)}@media(max-width:768px){.kpi-value{font-size:1.75rem}.kpi-prefix,.kpi-suffix{font-size:1rem}}\n"] }]
4292
+ args: [{ selector: 'mm-kpi-widget', standalone: true, imports: [CommonModule, SVGIconModule, WidgetNotConfiguredComponent], template: "<div class=\"kpi-widget\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else {\n <div class=\"kpi-content\">\n <div class=\"kpi-value-container\">\n @if (config.prefix) {\n <span class=\"kpi-prefix\">{{ config.prefix }}</span>\n }\n <span class=\"kpi-value\">{{ value() }}</span>\n @if (config.suffix) {\n <span class=\"kpi-suffix\">{{ config.suffix }}</span>\n }\n </div>\n\n @if (comparisonText()) {\n <div class=\"kpi-comparison\" [ngClass]=\"trendClass()\">\n @if (config.trend) {\n <kendo-svg-icon [icon]=\"trendIcon()\" size=\"xsmall\"></kendo-svg-icon>\n }\n {{ comparisonText() }}\n </div>\n }\n\n @if (label()) {\n <div class=\"kpi-label\">{{ label() }}</div>\n }\n </div>\n\n @if (config.trend) {\n <div class=\"kpi-trend\" [ngClass]=\"trendClass()\">\n <kendo-svg-icon [icon]=\"trendIcon()\" size=\"medium\"></kendo-svg-icon>\n </div>\n }\n }\n</div>\n", styles: [".kpi-widget{display:flex;align-items:center;justify-content:center;height:100%;padding:16px;gap:16px}.kpi-content{display:flex;flex-direction:column;align-items:center;text-align:center}.kpi-value-container{display:flex;align-items:baseline;gap:4px}.kpi-prefix,.kpi-suffix{font-size:1.25rem;font-weight:500;color:var(--kendo-color-subtle, #6c757d)}.kpi-value{font-size:2.5rem;font-weight:700;color:var(--kendo-color-on-surface, #212529);line-height:1}.kpi-comparison{margin-top:4px;font-size:.75rem;font-weight:500;display:flex;align-items:center;gap:4px;justify-content:center}.kpi-comparison.trend-up{color:var(--kendo-color-success, #28a745)}.kpi-comparison.trend-down{color:var(--kendo-color-error, #dc3545)}.kpi-comparison.trend-neutral{color:var(--kendo-color-subtle, #6c757d)}.kpi-label{margin-top:8px;font-size:.875rem;color:var(--kendo-color-subtle, #6c757d)}.kpi-trend{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%}.kpi-trend.trend-up{background:#28a74526;color:var(--kendo-color-success, #28a745)}.kpi-trend.trend-down{background:#dc354526;color:var(--kendo-color-error, #dc3545)}.kpi-trend.trend-neutral{background:#6c757d26;color:var(--kendo-color-subtle, #6c757d)}@media(max-width:768px){.kpi-value{font-size:1.75rem}.kpi-prefix,.kpi-suffix{font-size:1rem}}\n"] }]
4273
4293
  }], propDecorators: { config: [{
4274
4294
  type: Input
4275
4295
  }] } });
@@ -4296,6 +4316,7 @@ class KpiConfigDialogComponent {
4296
4316
  initialPrefix;
4297
4317
  initialSuffix;
4298
4318
  initialTrend;
4319
+ initialComparisonText;
4299
4320
  // Initial values for editing - Persistent Query
4300
4321
  initialDataSourceType;
4301
4322
  initialQueryRtId;
@@ -4338,6 +4359,7 @@ class KpiConfigDialogComponent {
4338
4359
  prefix: '',
4339
4360
  suffix: '',
4340
4361
  trend: undefined,
4362
+ comparisonText: '',
4341
4363
  // Query-specific form fields
4342
4364
  queryValueField: '',
4343
4365
  queryCategoryField: '',
@@ -4395,6 +4417,7 @@ class KpiConfigDialogComponent {
4395
4417
  this.form.prefix = this.initialPrefix || '';
4396
4418
  this.form.suffix = this.initialSuffix || '';
4397
4419
  this.form.trend = this.initialTrend;
4420
+ this.form.comparisonText = this.initialComparisonText || '';
4398
4421
  this.form.queryValueField = this.initialQueryValueField || '';
4399
4422
  this.form.queryCategoryField = this.initialQueryCategoryField || '';
4400
4423
  this.form.queryCategoryValue = this.initialQueryCategoryValue || '';
@@ -4557,7 +4580,8 @@ class KpiConfigDialogComponent {
4557
4580
  staticValue: this.form.staticValue,
4558
4581
  prefix: this.form.prefix || undefined,
4559
4582
  suffix: this.form.suffix || undefined,
4560
- trend: this.form.trend
4583
+ trend: this.form.trend,
4584
+ comparisonText: this.form.comparisonText || undefined
4561
4585
  });
4562
4586
  return;
4563
4587
  }
@@ -4576,6 +4600,7 @@ class KpiConfigDialogComponent {
4576
4600
  prefix: this.form.prefix || undefined,
4577
4601
  suffix: this.form.suffix || undefined,
4578
4602
  trend: this.form.trend,
4603
+ comparisonText: this.form.comparisonText || undefined,
4579
4604
  filters: filtersDto
4580
4605
  });
4581
4606
  }
@@ -4589,6 +4614,7 @@ class KpiConfigDialogComponent {
4589
4614
  prefix: this.form.prefix || undefined,
4590
4615
  suffix: this.form.suffix || undefined,
4591
4616
  trend: this.form.trend,
4617
+ comparisonText: this.form.comparisonText || undefined,
4592
4618
  filters: filtersDto
4593
4619
  });
4594
4620
  }
@@ -4734,7 +4760,7 @@ class KpiConfigDialogComponent {
4734
4760
  this.filters = updatedFilters;
4735
4761
  }
4736
4762
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: KpiConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4737
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: KpiConfigDialogComponent, isStandalone: true, selector: "mm-kpi-config-dialog", inputs: { initialCkTypeId: "initialCkTypeId", initialRtId: "initialRtId", initialValueAttribute: "initialValueAttribute", initialLabelAttribute: "initialLabelAttribute", initialPrefix: "initialPrefix", initialSuffix: "initialSuffix", initialTrend: "initialTrend", initialDataSourceType: "initialDataSourceType", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryMode: "initialQueryMode", initialQueryValueField: "initialQueryValueField", initialQueryCategoryField: "initialQueryCategoryField", initialQueryCategoryValue: "initialQueryCategoryValue", initialStaticValue: "initialStaticValue", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "ckTypeSelectorInput", first: true, predicate: ["ckTypeSelector"], descendants: true }, { propertyName: "entitySelectorInput", first: true, predicate: ["entitySelector"], descendants: true }, { propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
4763
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: KpiConfigDialogComponent, isStandalone: true, selector: "mm-kpi-config-dialog", inputs: { initialCkTypeId: "initialCkTypeId", initialRtId: "initialRtId", initialValueAttribute: "initialValueAttribute", initialLabelAttribute: "initialLabelAttribute", initialPrefix: "initialPrefix", initialSuffix: "initialSuffix", initialTrend: "initialTrend", initialComparisonText: "initialComparisonText", initialDataSourceType: "initialDataSourceType", initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialQueryMode: "initialQueryMode", initialQueryValueField: "initialQueryValueField", initialQueryCategoryField: "initialQueryCategoryField", initialQueryCategoryValue: "initialQueryCategoryValue", initialStaticValue: "initialStaticValue", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "ckTypeSelectorInput", first: true, predicate: ["ckTypeSelector"], descendants: true }, { propertyName: "entitySelectorInput", first: true, predicate: ["entitySelector"], descendants: true }, { propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
4738
4764
  <div class="config-container">
4739
4765
 
4740
4766
  <div class="config-form" [class.loading]="isLoadingInitial">
@@ -5070,6 +5096,11 @@ class KpiConfigDialogComponent {
5070
5096
  [(ngModel)]="form.trend">
5071
5097
  </kendo-dropdownlist>
5072
5098
  </div>
5099
+ <div class="form-field">
5100
+ <label>Comparison Text</label>
5101
+ <kendo-textbox [(ngModel)]="form.comparisonText" placeholder="e.g. +3,1% vs. last week"></kendo-textbox>
5102
+ <p class="section-hint">Displayed below the value in the trend color. Supports {{ variableSyntaxHint }}</p>
5103
+ </div>
5073
5104
  </div>
5074
5105
 
5075
5106
  <!-- Filters Section -->
@@ -5450,6 +5481,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
5450
5481
  [(ngModel)]="form.trend">
5451
5482
  </kendo-dropdownlist>
5452
5483
  </div>
5484
+ <div class="form-field">
5485
+ <label>Comparison Text</label>
5486
+ <kendo-textbox [(ngModel)]="form.comparisonText" placeholder="e.g. +3,1% vs. last week"></kendo-textbox>
5487
+ <p class="section-hint">Displayed below the value in the trend color. Supports {{ variableSyntaxHint }}</p>
5488
+ </div>
5453
5489
  </div>
5454
5490
 
5455
5491
  <!-- Filters Section -->
@@ -5503,6 +5539,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
5503
5539
  type: Input
5504
5540
  }], initialTrend: [{
5505
5541
  type: Input
5542
+ }], initialComparisonText: [{
5543
+ type: Input
5506
5544
  }], initialDataSourceType: [{
5507
5545
  type: Input
5508
5546
  }], initialQueryRtId: [{
@@ -5688,7 +5726,7 @@ class EntityDetailDialogComponent {
5688
5726
  <button kendoButton (click)="onClose()">Close</button>
5689
5727
  </kendo-dialog-actions>
5690
5728
  </kendo-dialog>
5691
- `, isInline: true, styles: [".entity-detail-content{display:flex;flex-direction:column;gap:16px;min-height:400px}.loading-indicator,.error-message{display:flex;align-items:center;justify-content:center;height:200px;color:var(--kendo-color-subtle, #6c757d)}.error-message{color:var(--kendo-color-error, #dc3545)}.entity-header{padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.entity-info{display:flex;flex-direction:column;gap:6px}.info-row{display:flex;gap:8px;font-size:.875rem}.info-row .label{font-weight:600;color:var(--kendo-color-subtle, #6c757d);min-width:80px}.info-row .value{color:var(--kendo-color-on-surface, #212529);word-break:break-all}.attributes-section{flex:1;display:flex;flex-direction:column}.attributes-section h4{margin:0 0 8px;font-size:.9rem;color:var(--kendo-color-primary, #0d6efd)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DialogsModule }, { kind: "component", type: i1$4.DialogComponent, selector: "kendo-dialog", inputs: ["actions", "actionsLayout", "autoFocusedElement", "title", "width", "minWidth", "maxWidth", "height", "minHeight", "maxHeight", "animation", "themeColor"], outputs: ["action", "close"], exportAs: ["kendoDialog"] }, { kind: "component", type: i1$4.DialogActionsComponent, selector: "kendo-dialog-actions", inputs: ["actions", "layout"], outputs: ["action"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: PropertyGridComponent, selector: "mm-property-grid", inputs: ["data", "config", "showTypeColumn"], outputs: ["propertyChange", "saveRequested", "binaryDownload"] }] });
5729
+ `, isInline: true, styles: [".entity-detail-content{display:flex;flex-direction:column;gap:16px;min-height:400px}.loading-indicator,.error-message{display:flex;align-items:center;justify-content:center;height:200px;color:var(--kendo-color-subtle, #6c757d)}.error-message{color:var(--kendo-color-error, #dc3545)}.entity-header{padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.entity-info{display:flex;flex-direction:column;gap:6px}.info-row{display:flex;gap:8px;font-size:.875rem}.info-row .label{font-weight:600;color:var(--kendo-color-subtle, #6c757d);min-width:80px}.info-row .value{color:var(--kendo-color-on-surface, #212529);word-break:break-all}.attributes-section{flex:1;display:flex;flex-direction:column}.attributes-section h4{margin:0 0 8px;font-size:.9rem;color:var(--kendo-color-primary, #0d6efd)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DialogsModule }, { kind: "component", type: i1$5.DialogComponent, selector: "kendo-dialog", inputs: ["actions", "actionsLayout", "autoFocusedElement", "title", "width", "minWidth", "maxWidth", "height", "minHeight", "maxHeight", "animation", "themeColor"], outputs: ["action", "close"], exportAs: ["kendoDialog"] }, { kind: "component", type: i1$5.DialogActionsComponent, selector: "kendo-dialog-actions", inputs: ["actions", "layout"], outputs: ["action"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: PropertyGridComponent, selector: "mm-property-grid", inputs: ["data", "config", "showTypeColumn"], outputs: ["propertyChange", "saveRequested", "binaryDownload"] }] });
5692
5730
  }
5693
5731
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: EntityDetailDialogComponent, decorators: [{
5694
5732
  type: Component,
@@ -6061,7 +6099,7 @@ class EntityAssociationsWidgetComponent {
6061
6099
  .trim();
6062
6100
  }
6063
6101
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: EntityAssociationsWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6064
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: EntityAssociationsWidgetComponent, isStandalone: true, selector: "mm-entity-associations-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"associations-widget\" [class.no-data]=\"!data()\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>Loading...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else if (data()) {\n <!-- Entity Info Header -->\n <div class=\"entity-header\">\n <div class=\"entity-box\">\n <span class=\"entity-name\">{{ entityName() }}</span>\n </div>\n <div class=\"entity-details\">\n <span class=\"entity-id\" title=\"{{ entityCkTypeId() }}\">{{ entityRtId() }}</span>\n </div>\n </div>\n\n <!-- Entity Attributes (Source) -->\n @if (filteredEntityAttributes().length > 0) {\n <div class=\"entity-attributes\">\n @for (attr of filteredEntityAttributes(); track attr.attributeName) {\n <div class=\"attribute-row\">\n <span class=\"attribute-name\">{{ formatAttributeName(attr.attributeName) }}</span>\n <span class=\"attribute-value\">{{ formatValue(attr.value) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Associations List -->\n <div class=\"associations-container\">\n @for (group of groupedAssociations(); track group.roleId + group.direction) {\n <div class=\"association-group\" [class.expanded]=\"isGroupExpanded(group)\">\n <!-- Group Header -->\n <div class=\"group-header\"\n [class.clickable]=\"displayMode() === 'expandable'\"\n (click)=\"toggleGroup(group)\">\n <div class=\"direction-indicator\">\n @if (group.direction === 'out') {\n <kendo-svg-icon [icon]=\"arrowRightIcon\" size=\"small\"></kendo-svg-icon>\n } @else {\n <kendo-svg-icon [icon]=\"arrowLeftIcon\" size=\"small\"></kendo-svg-icon>\n }\n </div>\n <div class=\"group-info\">\n <span class=\"role-name\">{{ group.roleName }}</span>\n <span class=\"target-type\">{{ group.targetType }}</span>\n <span class=\"count-badge\">{{ group.count }}</span>\n </div>\n @if (displayMode() === 'expandable') {\n <div class=\"expand-icon\">\n @if (isGroupExpanded(group)) {\n <kendo-svg-icon [icon]=\"chevronDownIcon\" size=\"small\"></kendo-svg-icon>\n } @else {\n <kendo-svg-icon [icon]=\"chevronRightIcon\" size=\"small\"></kendo-svg-icon>\n }\n </div>\n }\n </div>\n\n <!-- Expanded Target List -->\n @if (displayMode() === 'expandable' && isGroupExpanded(group)) {\n <div class=\"target-list\">\n @if (isLoadingTargets(group)) {\n <div class=\"target-loading\">Loading attributes...</div>\n }\n @for (target of getTargetEntities(group); track target.rtId) {\n <div class=\"target-entry\">\n <div class=\"target-item\" (click)=\"onTargetClick(target)\">\n <span class=\"target-id\">{{ target.displayName }}</span>\n <span class=\"target-type-hint\" [title]=\"target.ckTypeId\">{{ target.ckTypeId }}</span>\n </div>\n @if (target.attributes?.length) {\n <div class=\"target-attributes\">\n @for (attr of target.attributes; track attr.attributeName) {\n <div class=\"target-attr-row\">\n <span class=\"target-attr-name\">{{ formatAttributeName(attr.attributeName) }}</span>\n <span class=\"target-attr-value\">{{ formatValue(attr.value) }}</span>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Summary Footer -->\n <div class=\"associations-summary\">\n <kendo-svg-icon [icon]=\"linkIcon\" size=\"small\"></kendo-svg-icon>\n <span>{{ totalAssociations() }} relationship(s)</span>\n </div>\n } @else {\n <mm-widget-not-configured></mm-widget-not-configured>\n }\n</div>\n\n<!-- Entity Detail Dialog -->\n@if (showDetailDialog) {\n <mm-entity-detail-dialog\n [rtId]=\"detailEntityRtId\"\n [ckTypeId]=\"detailEntityCkTypeId\"\n (closed)=\"onDetailDialogClosed()\">\n </mm-entity-detail-dialog>\n}\n", styles: [".associations-widget{display:flex;flex-direction:column;height:100%}.associations-widget.no-data{justify-content:center;align-items:center;color:var(--kendo-color-subtle, #6c757d)}.entity-header{display:flex;align-items:center;gap:12px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.entity-header .entity-box{display:flex;align-items:center;justify-content:center;padding:8px 16px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);border-radius:4px;font-weight:600;font-size:.875rem}.entity-header .entity-details{display:flex;flex-direction:column;gap:2px}.entity-header .entity-id{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);font-family:monospace}.entity-attributes{padding:4px 12px 8px;border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.entity-attributes .attribute-row{display:flex;justify-content:space-between;padding:2px 0;font-size:.8125rem}.entity-attributes .attribute-name{color:var(--kendo-color-subtle, #6c757d)}.entity-attributes .attribute-value{font-weight:500;color:var(--kendo-color-on-surface, #212529)}.associations-container{flex:1;overflow:auto;padding:8px;display:flex;flex-direction:column;gap:4px}.association-group{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;overflow:hidden}.association-group.expanded .group-header{border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.group-header{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa)}.group-header.clickable{cursor:pointer}.group-header.clickable:hover{background:var(--kendo-color-surface, #ffffff)}.direction-indicator{color:var(--kendo-color-subtle, #6c757d);display:flex;align-items:center;width:20px}.group-info{display:flex;align-items:center;gap:8px;flex:1;font-size:.8125rem}.group-info .role-name{color:var(--kendo-color-subtle, #6c757d);font-style:italic}.group-info .target-type{font-weight:500;color:var(--kendo-color-on-surface, #212529)}.group-info .count-badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);border-radius:10px;font-size:.6875rem;font-weight:600}.expand-icon{color:var(--kendo-color-subtle, #6c757d);display:flex;align-items:center}.target-list{padding:4px 0;max-height:200px;overflow-y:auto}.target-entry+.target-entry{border-top:1px solid var(--kendo-color-border, #dee2e6)}.target-item{display:flex;align-items:center;gap:8px;padding:6px 12px 6px 40px;cursor:pointer;font-size:.8125rem;transition:background .15s ease}.target-item:hover{background:var(--kendo-color-surface-alt, #f8f9fa)}.target-item .target-id{font-family:monospace;color:var(--kendo-color-on-surface, #212529);flex-shrink:0}.target-item .target-type-hint{font-size:.6875rem;color:var(--kendo-color-subtle, #6c757d);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex-shrink:1}.target-attributes{padding:2px 12px 6px 56px}.target-attributes .target-attr-row{display:flex;justify-content:space-between;padding:1px 0;font-size:.75rem}.target-attributes .target-attr-name{color:var(--kendo-color-subtle, #6c757d)}.target-attributes .target-attr-value{color:var(--kendo-color-on-surface, #212529);font-weight:500}.target-loading{padding:6px 12px 6px 40px;font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.associations-summary{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-top:1px solid var(--kendo-color-border, #dee2e6);font-size:.75rem;color:var(--kendo-color-subtle, #6c757d)}.no-data-message{font-size:.875rem}.loading-indicator{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-subtle, #6c757d)}.error-message{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-error, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i2$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: EntityDetailDialogComponent, selector: "mm-entity-detail-dialog", inputs: ["rtId", "ckTypeId"], outputs: ["closed"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
6102
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: EntityAssociationsWidgetComponent, isStandalone: true, selector: "mm-entity-associations-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"associations-widget\" [class.no-data]=\"!data()\" [class.loading]=\"isLoading()\" [class.error]=\"error()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured></mm-widget-not-configured>\n } @else if (isLoading()) {\n <div class=\"loading-indicator\">\n <span>Loading...</span>\n </div>\n } @else if (error()) {\n <div class=\"error-message\">\n <span>{{ error() }}</span>\n </div>\n } @else if (data()) {\n <!-- Entity Info Header -->\n <div class=\"entity-header\">\n <div class=\"entity-box\">\n <span class=\"entity-name\">{{ entityName() }}</span>\n </div>\n <div class=\"entity-details\">\n <span class=\"entity-id\" title=\"{{ entityCkTypeId() }}\">{{ entityRtId() }}</span>\n </div>\n </div>\n\n <!-- Entity Attributes (Source) -->\n @if (filteredEntityAttributes().length > 0) {\n <div class=\"entity-attributes\">\n @for (attr of filteredEntityAttributes(); track attr.attributeName) {\n <div class=\"attribute-row\">\n <span class=\"attribute-name\">{{ formatAttributeName(attr.attributeName) }}</span>\n <span class=\"attribute-value\">{{ formatValue(attr.value) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Associations List -->\n <div class=\"associations-container\">\n @for (group of groupedAssociations(); track group.roleId + group.direction) {\n <div class=\"association-group\" [class.expanded]=\"isGroupExpanded(group)\">\n <!-- Group Header -->\n <div class=\"group-header\"\n [class.clickable]=\"displayMode() === 'expandable'\"\n (click)=\"toggleGroup(group)\">\n <div class=\"direction-indicator\">\n @if (group.direction === 'out') {\n <kendo-svg-icon [icon]=\"arrowRightIcon\" size=\"small\"></kendo-svg-icon>\n } @else {\n <kendo-svg-icon [icon]=\"arrowLeftIcon\" size=\"small\"></kendo-svg-icon>\n }\n </div>\n <div class=\"group-info\">\n <span class=\"role-name\">{{ group.roleName }}</span>\n <span class=\"target-type\">{{ group.targetType }}</span>\n <span class=\"count-badge\">{{ group.count }}</span>\n </div>\n @if (displayMode() === 'expandable') {\n <div class=\"expand-icon\">\n @if (isGroupExpanded(group)) {\n <kendo-svg-icon [icon]=\"chevronDownIcon\" size=\"small\"></kendo-svg-icon>\n } @else {\n <kendo-svg-icon [icon]=\"chevronRightIcon\" size=\"small\"></kendo-svg-icon>\n }\n </div>\n }\n </div>\n\n <!-- Expanded Target List -->\n @if (displayMode() === 'expandable' && isGroupExpanded(group)) {\n <div class=\"target-list\">\n @if (isLoadingTargets(group)) {\n <div class=\"target-loading\">Loading attributes...</div>\n }\n @for (target of getTargetEntities(group); track target.rtId) {\n <div class=\"target-entry\">\n <div class=\"target-item\" (click)=\"onTargetClick(target)\">\n <span class=\"target-id\">{{ target.displayName }}</span>\n <span class=\"target-type-hint\" [title]=\"target.ckTypeId\">{{ target.ckTypeId }}</span>\n </div>\n @if (target.attributes?.length) {\n <div class=\"target-attributes\">\n @for (attr of target.attributes; track attr.attributeName) {\n <div class=\"target-attr-row\">\n <span class=\"target-attr-name\">{{ formatAttributeName(attr.attributeName) }}</span>\n <span class=\"target-attr-value\">{{ formatValue(attr.value) }}</span>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Summary Footer -->\n <div class=\"associations-summary\">\n <kendo-svg-icon [icon]=\"linkIcon\" size=\"small\"></kendo-svg-icon>\n <span>{{ totalAssociations() }} relationship(s)</span>\n </div>\n } @else {\n <mm-widget-not-configured></mm-widget-not-configured>\n }\n</div>\n\n<!-- Entity Detail Dialog -->\n@if (showDetailDialog) {\n <mm-entity-detail-dialog\n [rtId]=\"detailEntityRtId\"\n [ckTypeId]=\"detailEntityCkTypeId\"\n (closed)=\"onDetailDialogClosed()\">\n </mm-entity-detail-dialog>\n}\n", styles: [".associations-widget{display:flex;flex-direction:column;height:100%}.associations-widget.no-data{justify-content:center;align-items:center;color:var(--kendo-color-subtle, #6c757d)}.entity-header{display:flex;align-items:center;gap:12px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.entity-header .entity-box{display:flex;align-items:center;justify-content:center;padding:8px 16px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);border-radius:4px;font-weight:600;font-size:.875rem}.entity-header .entity-details{display:flex;flex-direction:column;gap:2px}.entity-header .entity-id{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);font-family:monospace}.entity-attributes{padding:4px 12px 8px;border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.entity-attributes .attribute-row{display:flex;justify-content:space-between;padding:2px 0;font-size:.8125rem}.entity-attributes .attribute-name{color:var(--kendo-color-subtle, #6c757d)}.entity-attributes .attribute-value{font-weight:500;color:var(--kendo-color-on-surface, #212529)}.associations-container{flex:1;overflow:auto;padding:8px;display:flex;flex-direction:column;gap:4px}.association-group{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;overflow:hidden}.association-group.expanded .group-header{border-bottom:1px solid var(--kendo-color-border, #dee2e6)}.group-header{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa)}.group-header.clickable{cursor:pointer}.group-header.clickable:hover{background:var(--kendo-color-surface, #ffffff)}.direction-indicator{color:var(--kendo-color-subtle, #6c757d);display:flex;align-items:center;width:20px}.group-info{display:flex;align-items:center;gap:8px;flex:1;font-size:.8125rem}.group-info .role-name{color:var(--kendo-color-subtle, #6c757d);font-style:italic}.group-info .target-type{font-weight:500;color:var(--kendo-color-on-surface, #212529)}.group-info .count-badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;background:var(--kendo-color-primary, #0d6efd);color:var(--kendo-color-on-primary, #fff);border-radius:10px;font-size:.6875rem;font-weight:600}.expand-icon{color:var(--kendo-color-subtle, #6c757d);display:flex;align-items:center}.target-list{padding:4px 0;max-height:200px;overflow-y:auto}.target-entry+.target-entry{border-top:1px solid var(--kendo-color-border, #dee2e6)}.target-item{display:flex;align-items:center;gap:8px;padding:6px 12px 6px 40px;cursor:pointer;font-size:.8125rem;transition:background .15s ease}.target-item:hover{background:var(--kendo-color-surface-alt, #f8f9fa)}.target-item .target-id{font-family:monospace;color:var(--kendo-color-on-surface, #212529);flex-shrink:0}.target-item .target-type-hint{font-size:.6875rem;color:var(--kendo-color-subtle, #6c757d);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex-shrink:1}.target-attributes{padding:2px 12px 6px 56px}.target-attributes .target-attr-row{display:flex;justify-content:space-between;padding:1px 0;font-size:.75rem}.target-attributes .target-attr-name{color:var(--kendo-color-subtle, #6c757d)}.target-attributes .target-attr-value{color:var(--kendo-color-on-surface, #212529);font-weight:500}.target-loading{padding:6px 12px 6px 40px;font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.associations-summary{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--kendo-color-surface-alt, #f8f9fa);border-top:1px solid var(--kendo-color-border, #dee2e6);font-size:.75rem;color:var(--kendo-color-subtle, #6c757d)}.no-data-message{font-size:.875rem}.loading-indicator{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-subtle, #6c757d)}.error-message{display:flex;align-items:center;justify-content:center;height:100%;color:var(--kendo-color-error, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i1$4.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: EntityDetailDialogComponent, selector: "mm-entity-detail-dialog", inputs: ["rtId", "ckTypeId"], outputs: ["closed"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
6065
6103
  }
6066
6104
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: EntityAssociationsWidgetComponent, decorators: [{
6067
6105
  type: Component,
@@ -7205,6 +7243,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
7205
7243
  type: Input
7206
7244
  }] } });
7207
7245
 
7246
+ const ICON_MAP = {
7247
+ 'check-circle': checkCircleIcon,
7248
+ 'x-circle': xCircleIcon,
7249
+ 'exclamation-circle': exclamationCircleIcon,
7250
+ 'warning-triangle': warningTriangleIcon,
7251
+ 'minus-circle': minusCircleIcon,
7252
+ 'question-circle': questionCircleIcon,
7253
+ 'circle': circleIcon,
7254
+ };
7255
+ function resolveStatusMapping(config) {
7256
+ return Object.fromEntries(Object.entries(config).map(([key, val]) => [
7257
+ key,
7258
+ { icon: ICON_MAP[val.icon] ?? questionCircleIcon, tooltip: val.tooltip, color: val.color }
7259
+ ]));
7260
+ }
7208
7261
  class TableWidgetComponent {
7209
7262
  config;
7210
7263
  dataSource;
@@ -7235,25 +7288,25 @@ class TableWidgetComponent {
7235
7288
  * For persistent queries, uses dynamically derived columns from the query response.
7236
7289
  */
7237
7290
  listViewColumns = computed(() => {
7238
- // For persistent query, use derived columns
7291
+ // If explicit columns are configured, use them (works for both data source types)
7292
+ if (this.config?.columns && this.config.columns.length > 0) {
7293
+ return this.config.columns.map(col => ({
7294
+ field: col.field,
7295
+ displayName: col.title,
7296
+ dataType: (col.dataType ?? 'text'),
7297
+ width: col.width,
7298
+ ...(col.statusMapping ? { statusMapping: resolveStatusMapping(col.statusMapping) } : {})
7299
+ }));
7300
+ }
7301
+ // For persistent query without explicit columns, use derived columns
7239
7302
  if (this.config?.dataSource?.type === 'persistentQuery') {
7240
- // Return query-derived columns if available
7241
7303
  const queryColumns = this._queryColumnsForView();
7242
7304
  if (queryColumns.length > 0) {
7243
7305
  return queryColumns;
7244
7306
  }
7245
- // Return empty array while waiting for first data load
7246
7307
  return [];
7247
7308
  }
7248
- // For runtime entity, use configured columns
7249
- if (!this.config?.columns)
7250
- return [];
7251
- return this.config.columns.map(col => ({
7252
- field: col.field,
7253
- displayName: col.title,
7254
- dataType: 'text',
7255
- width: col.width
7256
- }));
7309
+ return [];
7257
7310
  }, ...(ngDevMode ? [{ debugName: "listViewColumns" }] : /* istanbul ignore next */ []));
7258
7311
  /**
7259
7312
  * Checks if the widget has a valid configuration.
@@ -7847,7 +7900,7 @@ class TableConfigDialogComponent {
7847
7900
  </button>
7848
7901
  </div>
7849
7902
  </div>
7850
- `, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:16px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.config-card{padding:12px 16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.card-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.card-header kendo-svgicon{color:var(--kendo-color-primary, #0d6efd)}.card-title{font-weight:600;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.card-count{color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.card-content{display:flex;align-items:center;justify-content:space-between;gap:16px}.config-summary{margin:0;font-size:.85rem;color:var(--kendo-color-on-app-surface, #212529);flex:1}.filters-card .card-content{flex-direction:column;align-items:stretch}.filter-content{width:100%}.options-card .options-content{display:flex;gap:24px;align-items:flex-end}.checkbox-field{flex-direction:row;align-items:center}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;color:var(--kendo-color-on-app-surface, #212529)}.data-source-type .radio-group{display:flex;gap:24px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400;color:var(--kendo-color-on-app-surface, #212529)}.radio-label span{color:var(--kendo-color-on-app-surface, #212529)}.query-info{flex-direction:column;align-items:stretch;gap:8px}.info-row{display:flex;gap:8px}.info-label{font-weight:600;min-width:100px;color:var(--kendo-color-subtle, #6c757d)}.info-value{flex:1;color:var(--kendo-color-on-app-surface, #212529)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: NumericTextBoxModule }, { kind: "ngmodule", type: DropDownsModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i2$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
7903
+ `, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:16px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.config-card{padding:12px 16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.card-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.card-header kendo-svgicon{color:var(--kendo-color-primary, #0d6efd)}.card-title{font-weight:600;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.card-count{color:var(--kendo-color-subtle, #6c757d);font-size:.85rem}.card-content{display:flex;align-items:center;justify-content:space-between;gap:16px}.config-summary{margin:0;font-size:.85rem;color:var(--kendo-color-on-app-surface, #212529);flex:1}.filters-card .card-content{flex-direction:column;align-items:stretch}.filter-content{width:100%}.options-card .options-content{display:flex;gap:24px;align-items:flex-end}.checkbox-field{flex-direction:row;align-items:center}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;color:var(--kendo-color-on-app-surface, #212529)}.data-source-type .radio-group{display:flex;gap:24px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400;color:var(--kendo-color-on-app-surface, #212529)}.radio-label span{color:var(--kendo-color-on-app-surface, #212529)}.query-info{flex-direction:column;align-items:stretch;gap:8px}.info-row{display:flex;gap:8px}.info-label{font-weight:600;min-width:100px;color:var(--kendo-color-subtle, #6c757d)}.info-value{flex:1;color:var(--kendo-color-on-app-surface, #212529)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: NumericTextBoxModule }, { kind: "ngmodule", type: DropDownsModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i1$4.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: CkTypeSelectorInputComponent, selector: "mm-ck-type-selector-input", inputs: ["placeholder", "minSearchLength", "maxResults", "debounceMs", "ckModelIds", "allowAbstract", "dialogTitle", "advancedSearchLabel", "derivedFromRtCkTypeId", "disabled", "required"], outputs: ["ckTypeSelected", "ckTypeCleared"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
7851
7904
  }
7852
7905
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TableConfigDialogComponent, decorators: [{
7853
7906
  type: Component,
@@ -8521,7 +8574,7 @@ class GaugeWidgetComponent {
8521
8574
  </div>
8522
8575
  }
8523
8576
  </div>
8524
- `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.gauge-widget{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.gauge-widget.loading,.gauge-widget.error{opacity:.7}.loading-indicator,.error-message{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.gauge-container{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;height:100%}.gauge-center-label{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center}.gauge-center-label .value-text{font-size:1.25rem;font-weight:600;color:var(--kendo-color-on-surface, #212529)}.gauge-center-label .label-text{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);margin-top:2px}.gauge-value-label{display:flex;flex-direction:column;align-items:center;margin-top:8px;font-size:1.1rem;font-weight:600;color:var(--kendo-color-on-surface, #212529)}.gauge-value-label .label-text{font-size:.75rem;font-weight:400;color:var(--kendo-color-subtle, #6c757d);margin-top:2px}kendo-arcgauge,kendo-circulargauge,kendo-radialgauge{width:100%;max-width:200px;height:auto}.linear-gauge-wrapper{display:flex;flex-direction:row;align-items:center;justify-content:center;height:100%;width:100%;gap:8px}.linear-gauge-wrapper .gauge-value-label{margin-top:0;writing-mode:horizontal-tb}kendo-lineargauge{height:100%;max-height:180px;width:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: i1$5.ArcGaugeComponent, selector: "kendo-arcgauge", inputs: ["value", "color", "colors", "opacity", "scale"], exportAs: ["kendoArcGauge"] }, { kind: "directive", type: i1$5.ArcCenterTemplateDirective, selector: "[kendoArcGaugeCenterTemplate]" }, { kind: "component", type: i1$5.ArcScaleComponent, selector: "kendo-arcgauge-scale", inputs: ["labels", "rangeDistance", "rangeLineCap", "startAngle", "endAngle"] }, { kind: "component", type: i1$5.ColorsComponent, selector: "kendo-arcgauge-colors" }, { kind: "component", type: i1$5.ColorComponent, selector: "kendo-arcgauge-color", inputs: ["color", "opacity", "from", "to"] }, { kind: "component", type: i1$5.CircularGaugeComponent, selector: "kendo-circulargauge", inputs: ["scale"], exportAs: ["kendoCircularGauge"] }, { kind: "directive", type: i1$5.CircularGaugeCenterTemplateDirective, selector: "[kendoCircularGaugeCenterTemplate]" }, { kind: "component", type: i1$5.CircularGaugeScaleComponent, selector: "kendo-circulargauge-scale" }, { kind: "component", type: i1$5.LinearGaugeComponent, selector: "kendo-lineargauge", inputs: ["pointer", "scale"], exportAs: ["kendoLinearGauge"] }, { kind: "component", type: i1$5.LinearScaleComponent, selector: "kendo-lineargauge-scale", inputs: ["line", "ranges", "mirror", "vertical"] }, { kind: "component", type: i1$5.LinearLabelsComponent, selector: "kendo-lineargauge-scale-labels" }, { kind: "component", type: i1$5.LinearPointersComponent, selector: "kendo-lineargauge-pointers" }, { kind: "component", type: i1$5.LinearPointerComponent, selector: "kendo-lineargauge-pointer", inputs: ["border", "color", "margin", "opacity", "shape", "size", "value"] }, { kind: "component", type: i1$5.LinearRangeComponent, selector: "kendo-lineargauge-scale-range" }, { kind: "component", type: i1$5.LinearRangesComponent, selector: "kendo-lineargauge-scale-ranges" }, { kind: "component", type: i1$5.RadialGaugeComponent, selector: "kendo-radialgauge", inputs: ["pointer", "scale"], exportAs: ["kendoRadialGauge"] }, { kind: "component", type: i1$5.RadialScaleComponent, selector: "kendo-radialgauge-scale", inputs: ["labels", "rangeDistance", "ranges", "startAngle", "endAngle"] }, { kind: "component", type: i1$5.RadialPointerComponent, selector: "kendo-radialgauge-pointer", inputs: ["cap", "color", "length", "value"] }, { kind: "component", type: i1$5.RadialRangeComponent, selector: "kendo-radialgauge-scale-range" }, { kind: "component", type: i1$5.RadialRangesComponent, selector: "kendo-radialgauge-scale-ranges" }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
8577
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.gauge-widget{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.gauge-widget.loading,.gauge-widget.error{opacity:.7}.loading-indicator,.error-message{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.gauge-container{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;height:100%}.gauge-center-label{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center}.gauge-center-label .value-text{font-size:1.25rem;font-weight:600;color:var(--kendo-color-on-surface, inherit)}.gauge-center-label .label-text{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);margin-top:2px}.gauge-value-label{display:flex;flex-direction:column;align-items:center;margin-top:8px;font-size:1.1rem;font-weight:600;color:var(--kendo-color-on-surface, inherit)}.gauge-value-label .label-text{font-size:.75rem;font-weight:400;color:var(--kendo-color-subtle, #6c757d);margin-top:2px}kendo-arcgauge,kendo-circulargauge,kendo-radialgauge{width:100%;max-width:200px;height:auto}.linear-gauge-wrapper{display:flex;flex-direction:row;align-items:center;justify-content:center;height:100%;width:100%;gap:8px}.linear-gauge-wrapper .gauge-value-label{margin-top:0;writing-mode:horizontal-tb}kendo-lineargauge{height:100%;max-height:180px;width:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: i1$6.ArcGaugeComponent, selector: "kendo-arcgauge", inputs: ["value", "color", "colors", "opacity", "scale"], exportAs: ["kendoArcGauge"] }, { kind: "directive", type: i1$6.ArcCenterTemplateDirective, selector: "[kendoArcGaugeCenterTemplate]" }, { kind: "component", type: i1$6.ArcScaleComponent, selector: "kendo-arcgauge-scale", inputs: ["labels", "rangeDistance", "rangeLineCap", "startAngle", "endAngle"] }, { kind: "component", type: i1$6.ColorsComponent, selector: "kendo-arcgauge-colors" }, { kind: "component", type: i1$6.ColorComponent, selector: "kendo-arcgauge-color", inputs: ["color", "opacity", "from", "to"] }, { kind: "component", type: i1$6.CircularGaugeComponent, selector: "kendo-circulargauge", inputs: ["scale"], exportAs: ["kendoCircularGauge"] }, { kind: "directive", type: i1$6.CircularGaugeCenterTemplateDirective, selector: "[kendoCircularGaugeCenterTemplate]" }, { kind: "component", type: i1$6.CircularGaugeScaleComponent, selector: "kendo-circulargauge-scale" }, { kind: "component", type: i1$6.LinearGaugeComponent, selector: "kendo-lineargauge", inputs: ["pointer", "scale"], exportAs: ["kendoLinearGauge"] }, { kind: "component", type: i1$6.LinearScaleComponent, selector: "kendo-lineargauge-scale", inputs: ["line", "ranges", "mirror", "vertical"] }, { kind: "component", type: i1$6.LinearLabelsComponent, selector: "kendo-lineargauge-scale-labels" }, { kind: "component", type: i1$6.LinearPointersComponent, selector: "kendo-lineargauge-pointers" }, { kind: "component", type: i1$6.LinearPointerComponent, selector: "kendo-lineargauge-pointer", inputs: ["border", "color", "margin", "opacity", "shape", "size", "value"] }, { kind: "component", type: i1$6.LinearRangeComponent, selector: "kendo-lineargauge-scale-range" }, { kind: "component", type: i1$6.LinearRangesComponent, selector: "kendo-lineargauge-scale-ranges" }, { kind: "component", type: i1$6.RadialGaugeComponent, selector: "kendo-radialgauge", inputs: ["pointer", "scale"], exportAs: ["kendoRadialGauge"] }, { kind: "component", type: i1$6.RadialScaleComponent, selector: "kendo-radialgauge-scale", inputs: ["labels", "rangeDistance", "ranges", "startAngle", "endAngle"] }, { kind: "component", type: i1$6.RadialPointerComponent, selector: "kendo-radialgauge-pointer", inputs: ["cap", "color", "length", "value"] }, { kind: "component", type: i1$6.RadialRangeComponent, selector: "kendo-radialgauge-scale-range" }, { kind: "component", type: i1$6.RadialRangesComponent, selector: "kendo-radialgauge-scale-ranges" }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
8525
8578
  }
8526
8579
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: GaugeWidgetComponent, decorators: [{
8527
8580
  type: Component,
@@ -8698,7 +8751,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
8698
8751
  </div>
8699
8752
  }
8700
8753
  </div>
8701
- `, styles: [":host{display:block;width:100%;height:100%}.gauge-widget{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.gauge-widget.loading,.gauge-widget.error{opacity:.7}.loading-indicator,.error-message{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.gauge-container{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;height:100%}.gauge-center-label{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center}.gauge-center-label .value-text{font-size:1.25rem;font-weight:600;color:var(--kendo-color-on-surface, #212529)}.gauge-center-label .label-text{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);margin-top:2px}.gauge-value-label{display:flex;flex-direction:column;align-items:center;margin-top:8px;font-size:1.1rem;font-weight:600;color:var(--kendo-color-on-surface, #212529)}.gauge-value-label .label-text{font-size:.75rem;font-weight:400;color:var(--kendo-color-subtle, #6c757d);margin-top:2px}kendo-arcgauge,kendo-circulargauge,kendo-radialgauge{width:100%;max-width:200px;height:auto}.linear-gauge-wrapper{display:flex;flex-direction:row;align-items:center;justify-content:center;height:100%;width:100%;gap:8px}.linear-gauge-wrapper .gauge-value-label{margin-top:0;writing-mode:horizontal-tb}kendo-lineargauge{height:100%;max-height:180px;width:auto}\n"] }]
8754
+ `, styles: [":host{display:block;width:100%;height:100%}.gauge-widget{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.gauge-widget.loading,.gauge-widget.error{opacity:.7}.loading-indicator,.error-message{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.gauge-container{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;height:100%}.gauge-center-label{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center}.gauge-center-label .value-text{font-size:1.25rem;font-weight:600;color:var(--kendo-color-on-surface, inherit)}.gauge-center-label .label-text{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);margin-top:2px}.gauge-value-label{display:flex;flex-direction:column;align-items:center;margin-top:8px;font-size:1.1rem;font-weight:600;color:var(--kendo-color-on-surface, inherit)}.gauge-value-label .label-text{font-size:.75rem;font-weight:400;color:var(--kendo-color-subtle, #6c757d);margin-top:2px}kendo-arcgauge,kendo-circulargauge,kendo-radialgauge{width:100%;max-width:200px;height:auto}.linear-gauge-wrapper{display:flex;flex-direction:row;align-items:center;justify-content:center;height:100%;width:100%;gap:8px}.linear-gauge-wrapper .gauge-value-label{margin-top:0;writing-mode:horizontal-tb}kendo-lineargauge{height:100%;max-height:180px;width:auto}\n"] }]
8702
8755
  }], propDecorators: { config: [{
8703
8756
  type: Input
8704
8757
  }] } });
@@ -10241,8 +10294,8 @@ class PieChartWidgetComponent {
10241
10294
  <span>{{ error() }}</span>
10242
10295
  </div>
10243
10296
  } @else {
10244
- <kendo-chart class="chart-container">
10245
- <kendo-chart-plot-area [margin]="plotAreaMargin"></kendo-chart-plot-area>
10297
+ <kendo-chart class="chart-container" [plotArea]="{ background: 'transparent', margin: plotAreaMargin }">
10298
+ <kendo-chart-area [background]="'transparent'"></kendo-chart-area>
10246
10299
  <kendo-chart-series>
10247
10300
  <kendo-chart-series-item
10248
10301
  [type]="config.chartType"
@@ -10266,7 +10319,7 @@ class PieChartWidgetComponent {
10266
10319
  </kendo-chart>
10267
10320
  }
10268
10321
  </div>
10269
- `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.pie-chart-widget{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.pie-chart-widget.loading,.pie-chart-widget.error{opacity:.7}.loading-indicator,.error-message,.no-config-overlay{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.no-config-overlay span{color:var(--kendo-color-subtle, #6c757d);font-style:italic}.chart-container{width:100%;height:100%}kendo-chart{width:100%;height:100%}.chart-tooltip{padding:4px 8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ChartsModule }, { kind: "component", type: i1$6.ChartComponent, selector: "kendo-chart", inputs: ["pannable", "renderAs", "seriesColors", "subtitle", "title", "noData", "observeStyles", "transitions", "zoomable", "axisDefaults", "categoryAxis", "chartArea", "legend", "panes", "paneDefaults", "plotArea", "series", "seriesDefaults", "tooltip", "valueAxis", "xAxis", "yAxis", "resizeRateLimit", "popupSettings", "drilldownLevel"], outputs: ["axisLabelClick", "drag", "dragEnd", "dragStart", "legendItemHover", "legendItemLeave", "noteClick", "noteHover", "noteLeave", "paneRender", "plotAreaClick", "plotAreaHover", "plotAreaLeave", "render", "select", "selectEnd", "selectStart", "seriesClick", "drilldown", "seriesHover", "seriesOver", "seriesLeave", "zoom", "zoomEnd", "zoomStart", "legendItemClick", "drilldownLevelChange"], exportAs: ["kendoChart"] }, { kind: "directive", type: i1$6.SeriesTooltipTemplateDirective, selector: "[kendoChartSeriesTooltipTemplate]" }, { kind: "component", type: i1$6.LegendComponent, selector: "kendo-chart-legend", inputs: ["align", "background", "border", "height", "labels", "margin", "offsetX", "offsetY", "orientation", "padding", "position", "reverse", "visible", "width", "markers", "spacing", "inactiveItems", "item", "title", "focusHighlight"] }, { kind: "component", type: i1$6.PlotAreaComponent, selector: "kendo-chart-plot-area", inputs: ["background", "border", "margin", "opacity", "padding"] }, { kind: "component", type: i1$6.SeriesComponent, selector: "kendo-chart-series" }, { kind: "component", type: i1$6.SeriesItemComponent, selector: "kendo-chart-series-item", inputs: ["aggregate", "autoFit", "axis", "border", "categoryAxis", "categoryField", "closeField", "color", "colorField", "connectors", "currentField", "dashType", "data", "downColor", "downColorField", "drilldownField", "dynamicHeight", "dynamicSlope", "errorHighField", "errorLowField", "explodeField", "field", "fromField", "gap", "highField", "holeSize", "line", "lowField", "lowerField", "margin", "maxSize", "mean", "meanField", "median", "medianField", "minSize", "missingValues", "name", "neckRatio", "negativeColor", "negativeValues", "noteTextField", "opacity", "openField", "outliersField", "overlay", "padding", "q1Field", "q3Field", "segmentSpacing", "size", "sizeField", "spacing", "stack", "startAngle", "style", "summaryField", "target", "toField", "type", "upperField", "visible", "visibleInLegend", "visibleInLegendField", "visual", "width", "whiskers", "xAxis", "xErrorHighField", "xErrorLowField", "xField", "yAxis", "yErrorHighField", "yErrorLowField", "yField", "zIndex", "trendline", "for", "legendItem", "pattern", "patternField", "errorBars", "extremes", "highlight", "labels", "markers", "notes", "outliers", "tooltip"] }, { kind: "component", type: i1$6.TooltipComponent, selector: "kendo-chart-tooltip", inputs: ["background", "border", "color", "font", "format", "opacity", "padding", "shared", "visible"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
10322
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.pie-chart-widget{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.pie-chart-widget.loading,.pie-chart-widget.error{opacity:.7}.loading-indicator,.error-message,.no-config-overlay{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.no-config-overlay span{color:var(--kendo-color-subtle, #6c757d);font-style:italic}.chart-container{width:100%;height:100%}kendo-chart{width:100%;height:100%}.chart-tooltip{padding:4px 8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ChartsModule }, { kind: "component", type: i1$7.ChartComponent, selector: "kendo-chart", inputs: ["pannable", "renderAs", "seriesColors", "subtitle", "title", "noData", "observeStyles", "transitions", "zoomable", "axisDefaults", "categoryAxis", "chartArea", "legend", "panes", "paneDefaults", "plotArea", "series", "seriesDefaults", "tooltip", "valueAxis", "xAxis", "yAxis", "resizeRateLimit", "popupSettings", "drilldownLevel"], outputs: ["axisLabelClick", "drag", "dragEnd", "dragStart", "legendItemHover", "legendItemLeave", "noteClick", "noteHover", "noteLeave", "paneRender", "plotAreaClick", "plotAreaHover", "plotAreaLeave", "render", "select", "selectEnd", "selectStart", "seriesClick", "drilldown", "seriesHover", "seriesOver", "seriesLeave", "zoom", "zoomEnd", "zoomStart", "legendItemClick", "drilldownLevelChange"], exportAs: ["kendoChart"] }, { kind: "directive", type: i1$7.SeriesTooltipTemplateDirective, selector: "[kendoChartSeriesTooltipTemplate]" }, { kind: "component", type: i1$7.ChartAreaComponent, selector: "kendo-chart-area", inputs: ["background", "border", "height", "margin", "opacity", "width"] }, { kind: "component", type: i1$7.LegendComponent, selector: "kendo-chart-legend", inputs: ["align", "background", "border", "height", "labels", "margin", "offsetX", "offsetY", "orientation", "padding", "position", "reverse", "visible", "width", "markers", "spacing", "inactiveItems", "item", "title", "focusHighlight"] }, { kind: "component", type: i1$7.SeriesComponent, selector: "kendo-chart-series" }, { kind: "component", type: i1$7.SeriesItemComponent, selector: "kendo-chart-series-item", inputs: ["aggregate", "autoFit", "axis", "border", "categoryAxis", "categoryField", "closeField", "color", "colorField", "connectors", "currentField", "dashType", "data", "downColor", "downColorField", "drilldownField", "dynamicHeight", "dynamicSlope", "errorHighField", "errorLowField", "explodeField", "field", "fromField", "gap", "highField", "holeSize", "line", "lowField", "lowerField", "margin", "maxSize", "mean", "meanField", "median", "medianField", "minSize", "missingValues", "name", "neckRatio", "negativeColor", "negativeValues", "noteTextField", "opacity", "openField", "outliersField", "overlay", "padding", "q1Field", "q3Field", "segmentSpacing", "size", "sizeField", "spacing", "stack", "startAngle", "style", "summaryField", "target", "toField", "type", "upperField", "visible", "visibleInLegend", "visibleInLegendField", "visual", "width", "whiskers", "xAxis", "xErrorHighField", "xErrorLowField", "xField", "yAxis", "yErrorHighField", "yErrorLowField", "yField", "zIndex", "trendline", "for", "legendItem", "pattern", "patternField", "errorBars", "extremes", "highlight", "labels", "markers", "notes", "outliers", "tooltip"] }, { kind: "component", type: i1$7.TooltipComponent, selector: "kendo-chart-tooltip", inputs: ["background", "border", "color", "font", "format", "opacity", "padding", "shared", "visible"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
10270
10323
  }
10271
10324
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: PieChartWidgetComponent, decorators: [{
10272
10325
  type: Component,
@@ -10287,8 +10340,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
10287
10340
  <span>{{ error() }}</span>
10288
10341
  </div>
10289
10342
  } @else {
10290
- <kendo-chart class="chart-container">
10291
- <kendo-chart-plot-area [margin]="plotAreaMargin"></kendo-chart-plot-area>
10343
+ <kendo-chart class="chart-container" [plotArea]="{ background: 'transparent', margin: plotAreaMargin }">
10344
+ <kendo-chart-area [background]="'transparent'"></kendo-chart-area>
10292
10345
  <kendo-chart-series>
10293
10346
  <kendo-chart-series-item
10294
10347
  [type]="config.chartType"
@@ -11195,7 +11248,7 @@ class BarChartWidgetComponent {
11195
11248
  continue;
11196
11249
  const sanitizedPath = this.sanitizeFieldName(cell.attributePath);
11197
11250
  if (sanitizedPath === this.sanitizeFieldName(this.config.categoryField)) {
11198
- categoryValue = String(cell.value ?? '');
11251
+ categoryValue = this.formatCategoryValue(cell.value);
11199
11252
  }
11200
11253
  // Check if this cell is one of our series fields
11201
11254
  for (const seriesConfig of (this.config.series ?? [])) {
@@ -11215,11 +11268,14 @@ class BarChartWidgetComponent {
11215
11268
  }
11216
11269
  }
11217
11270
  // Convert to series data array
11218
- const seriesData = (this.config.series ?? []).map(seriesConfig => ({
11219
- name: seriesConfig.name ?? seriesConfig.field,
11220
- data: seriesMap.get(seriesConfig.field) ?? [],
11221
- color: seriesConfig.color
11222
- }));
11271
+ const seriesData = (this.config.series ?? []).map(seriesConfig => {
11272
+ const rawData = seriesMap.get(seriesConfig.field) ?? [];
11273
+ return {
11274
+ name: seriesConfig.name ?? seriesConfig.field,
11275
+ data: this.hasColorThresholds() ? this.applyColorThresholds(rawData) : rawData,
11276
+ color: seriesConfig.color
11277
+ };
11278
+ });
11223
11279
  this._categories.set(categories);
11224
11280
  this._seriesData.set(seriesData);
11225
11281
  }
@@ -11257,7 +11313,7 @@ class BarChartWidgetComponent {
11257
11313
  continue;
11258
11314
  const sanitizedPath = this.sanitizeFieldName(cell.attributePath);
11259
11315
  if (sanitizedPath === this.sanitizeFieldName(categoryField)) {
11260
- categoryValue = String(cell.value ?? '');
11316
+ categoryValue = this.formatCategoryValue(cell.value);
11261
11317
  }
11262
11318
  else if (sanitizedPath === this.sanitizeFieldName(seriesGroupField)) {
11263
11319
  seriesGroupValue = String(cell.value ?? '');
@@ -11283,12 +11339,12 @@ class BarChartWidgetComponent {
11283
11339
  const seriesGroups = Array.from(allSeriesGroups);
11284
11340
  // Build series data
11285
11341
  const seriesData = seriesGroups.map(seriesGroup => {
11286
- const data = categories.map(category => {
11342
+ const rawData = categories.map(category => {
11287
11343
  return dataMap.get(category)?.get(seriesGroup) ?? 0;
11288
11344
  });
11289
11345
  return {
11290
11346
  name: seriesGroup,
11291
- data
11347
+ data: this.hasColorThresholds() ? this.applyColorThresholds(rawData) : rawData
11292
11348
  };
11293
11349
  });
11294
11350
  this._categories.set(categories);
@@ -11298,6 +11354,42 @@ class BarChartWidgetComponent {
11298
11354
  * Sanitizes field names for comparison.
11299
11355
  * Replaces dots with underscores (same as table widget).
11300
11356
  */
11357
+ dataLabelFormat = computed(() => {
11358
+ const suffix = this.config?.dataLabelSuffix ?? '';
11359
+ return `{0:n0}${suffix}`;
11360
+ }, ...(ngDevMode ? [{ debugName: "dataLabelFormat" }] : /* istanbul ignore next */ []));
11361
+ hasColorThresholds() {
11362
+ return (this.config?.colorThresholds?.length ?? 0) > 0;
11363
+ }
11364
+ applyColorThresholds(data) {
11365
+ const thresholds = this.config.colorThresholds ?? [];
11366
+ const defaultColor = this.config.defaultBarColor ?? '#6b7280';
11367
+ return data.map(val => ({
11368
+ value: val,
11369
+ _color: this.getColorForValue(val, thresholds, defaultColor)
11370
+ }));
11371
+ }
11372
+ getColorForValue(val, thresholds, defaultColor) {
11373
+ for (const t of thresholds) {
11374
+ if (val <= t.value)
11375
+ return t.color;
11376
+ }
11377
+ return defaultColor;
11378
+ }
11379
+ formatCategoryValue(value) {
11380
+ const str = String(value ?? '');
11381
+ // Detect ISO 8601 timestamps and format as readable date/time
11382
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(str)) {
11383
+ const date = new Date(str);
11384
+ if (!isNaN(date.getTime())) {
11385
+ return date.toLocaleString('de-AT', {
11386
+ day: '2-digit', month: '2-digit',
11387
+ hour: '2-digit', minute: '2-digit'
11388
+ });
11389
+ }
11390
+ }
11391
+ return str;
11392
+ }
11301
11393
  sanitizeFieldName(fieldName) {
11302
11394
  return fieldName.replace(/\./g, '_');
11303
11395
  }
@@ -11323,10 +11415,13 @@ class BarChartWidgetComponent {
11323
11415
  <span>{{ error() }}</span>
11324
11416
  </div>
11325
11417
  } @else {
11326
- <kendo-chart class="chart-container">
11327
- <kendo-chart-plot-area [margin]="plotAreaMargin()"></kendo-chart-plot-area>
11418
+ <kendo-chart class="chart-container" [plotArea]="{ background: 'transparent', margin: plotAreaMargin() }">
11419
+ <kendo-chart-area [background]="'transparent'"></kendo-chart-area>
11328
11420
  <kendo-chart-category-axis>
11329
- <kendo-chart-category-axis-item [categories]="categories()">
11421
+ <kendo-chart-category-axis-item
11422
+ [categories]="categories()"
11423
+ [line]="{ visible: false }"
11424
+ [majorGridLines]="{ visible: false }">
11330
11425
  <kendo-chart-category-axis-item-labels
11331
11426
  [rotation]="labelRotation()"
11332
11427
  [content]="categoryLabelContent">
@@ -11334,6 +11429,13 @@ class BarChartWidgetComponent {
11334
11429
  </kendo-chart-category-axis-item>
11335
11430
  </kendo-chart-category-axis>
11336
11431
 
11432
+ <kendo-chart-value-axis>
11433
+ <kendo-chart-value-axis-item
11434
+ [line]="{ visible: false }"
11435
+ [majorGridLines]="{ color: 'rgba(255,255,255,0.06)' }">
11436
+ </kendo-chart-value-axis-item>
11437
+ </kendo-chart-value-axis>
11438
+
11337
11439
  <kendo-chart-series>
11338
11440
  @for (series of seriesData(); track series.name) {
11339
11441
  <kendo-chart-series-item
@@ -11341,11 +11443,15 @@ class BarChartWidgetComponent {
11341
11443
  [data]="series.data"
11342
11444
  [name]="series.name"
11343
11445
  [color]="series.color"
11446
+ [field]="hasColorThresholds() ? 'value' : ''"
11447
+ [colorField]="hasColorThresholds() ? '_color' : ''"
11448
+ [border]="{ width: 0 }"
11449
+ [gap]="0.8"
11344
11450
  [stack]="stackConfig()">
11345
11451
  @if (config.showDataLabels) {
11346
11452
  <kendo-chart-series-item-labels
11347
11453
  [visible]="true"
11348
- [format]="'{0:n0}'">
11454
+ [format]="dataLabelFormat()">
11349
11455
  </kendo-chart-series-item-labels>
11350
11456
  }
11351
11457
  </kendo-chart-series-item>
@@ -11368,7 +11474,7 @@ class BarChartWidgetComponent {
11368
11474
  </kendo-chart>
11369
11475
  }
11370
11476
  </div>
11371
- `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.bar-chart-widget{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.bar-chart-widget.loading,.bar-chart-widget.error{opacity:.7}.loading-indicator,.error-message,.no-config-overlay{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.no-config-overlay span{color:var(--kendo-color-subtle, #6c757d);font-style:italic}.chart-container{width:100%;height:100%}kendo-chart{width:100%;height:100%}.chart-tooltip{padding:4px 8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ChartsModule }, { kind: "component", type: i1$6.ChartComponent, selector: "kendo-chart", inputs: ["pannable", "renderAs", "seriesColors", "subtitle", "title", "noData", "observeStyles", "transitions", "zoomable", "axisDefaults", "categoryAxis", "chartArea", "legend", "panes", "paneDefaults", "plotArea", "series", "seriesDefaults", "tooltip", "valueAxis", "xAxis", "yAxis", "resizeRateLimit", "popupSettings", "drilldownLevel"], outputs: ["axisLabelClick", "drag", "dragEnd", "dragStart", "legendItemHover", "legendItemLeave", "noteClick", "noteHover", "noteLeave", "paneRender", "plotAreaClick", "plotAreaHover", "plotAreaLeave", "render", "select", "selectEnd", "selectStart", "seriesClick", "drilldown", "seriesHover", "seriesOver", "seriesLeave", "zoom", "zoomEnd", "zoomStart", "legendItemClick", "drilldownLevelChange"], exportAs: ["kendoChart"] }, { kind: "directive", type: i1$6.SeriesTooltipTemplateDirective, selector: "[kendoChartSeriesTooltipTemplate]" }, { kind: "component", type: i1$6.CategoryAxisComponent, selector: "kendo-chart-category-axis" }, { kind: "component", type: i1$6.CategoryAxisItemComponent, selector: "kendo-chart-category-axis-item", inputs: ["autoBaseUnitSteps", "axisCrossingValue", "background", "baseUnit", "baseUnitStep", "categories", "color", "justified", "line", "majorGridLines", "majorTicks", "max", "maxDateGroups", "maxDivisions", "min", "minorGridLines", "minorTicks", "name", "pane", "plotBands", "reverse", "roundToBaseUnit", "startAngle", "type", "visible", "weekStartDay", "crosshair", "labels", "notes", "select", "title", "rangeLabels"] }, { kind: "component", type: i1$6.CategoryAxisLabelsComponent, selector: "kendo-chart-category-axis-item-labels", inputs: ["background", "border", "color", "content", "culture", "dateFormats", "font", "format", "margin", "mirror", "padding", "position", "rotation", "skip", "step", "visible", "visual"] }, { kind: "component", type: i1$6.LegendComponent, selector: "kendo-chart-legend", inputs: ["align", "background", "border", "height", "labels", "margin", "offsetX", "offsetY", "orientation", "padding", "position", "reverse", "visible", "width", "markers", "spacing", "inactiveItems", "item", "title", "focusHighlight"] }, { kind: "component", type: i1$6.PlotAreaComponent, selector: "kendo-chart-plot-area", inputs: ["background", "border", "margin", "opacity", "padding"] }, { kind: "component", type: i1$6.SeriesComponent, selector: "kendo-chart-series" }, { kind: "component", type: i1$6.SeriesItemComponent, selector: "kendo-chart-series-item", inputs: ["aggregate", "autoFit", "axis", "border", "categoryAxis", "categoryField", "closeField", "color", "colorField", "connectors", "currentField", "dashType", "data", "downColor", "downColorField", "drilldownField", "dynamicHeight", "dynamicSlope", "errorHighField", "errorLowField", "explodeField", "field", "fromField", "gap", "highField", "holeSize", "line", "lowField", "lowerField", "margin", "maxSize", "mean", "meanField", "median", "medianField", "minSize", "missingValues", "name", "neckRatio", "negativeColor", "negativeValues", "noteTextField", "opacity", "openField", "outliersField", "overlay", "padding", "q1Field", "q3Field", "segmentSpacing", "size", "sizeField", "spacing", "stack", "startAngle", "style", "summaryField", "target", "toField", "type", "upperField", "visible", "visibleInLegend", "visibleInLegendField", "visual", "width", "whiskers", "xAxis", "xErrorHighField", "xErrorLowField", "xField", "yAxis", "yErrorHighField", "yErrorLowField", "yField", "zIndex", "trendline", "for", "legendItem", "pattern", "patternField", "errorBars", "extremes", "highlight", "labels", "markers", "notes", "outliers", "tooltip"] }, { kind: "component", type: i1$6.SeriesLabelsComponent, selector: "kendo-chart-series-item-labels", inputs: ["align", "background", "border", "color", "content", "ariaContent", "distance", "font", "format", "margin", "padding", "position", "rotation", "visible", "visual", "from", "to"] }, { kind: "component", type: i1$6.TooltipComponent, selector: "kendo-chart-tooltip", inputs: ["background", "border", "color", "font", "format", "opacity", "padding", "shared", "visible"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
11477
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.bar-chart-widget{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.bar-chart-widget.loading,.bar-chart-widget.error{opacity:.7}.loading-indicator,.error-message,.no-config-overlay{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.no-config-overlay span{color:var(--kendo-color-subtle, #6c757d);font-style:italic}.chart-container{width:100%;height:100%}kendo-chart{width:100%;height:100%}.chart-tooltip{padding:4px 8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ChartsModule }, { kind: "component", type: i1$7.ChartComponent, selector: "kendo-chart", inputs: ["pannable", "renderAs", "seriesColors", "subtitle", "title", "noData", "observeStyles", "transitions", "zoomable", "axisDefaults", "categoryAxis", "chartArea", "legend", "panes", "paneDefaults", "plotArea", "series", "seriesDefaults", "tooltip", "valueAxis", "xAxis", "yAxis", "resizeRateLimit", "popupSettings", "drilldownLevel"], outputs: ["axisLabelClick", "drag", "dragEnd", "dragStart", "legendItemHover", "legendItemLeave", "noteClick", "noteHover", "noteLeave", "paneRender", "plotAreaClick", "plotAreaHover", "plotAreaLeave", "render", "select", "selectEnd", "selectStart", "seriesClick", "drilldown", "seriesHover", "seriesOver", "seriesLeave", "zoom", "zoomEnd", "zoomStart", "legendItemClick", "drilldownLevelChange"], exportAs: ["kendoChart"] }, { kind: "directive", type: i1$7.SeriesTooltipTemplateDirective, selector: "[kendoChartSeriesTooltipTemplate]" }, { kind: "component", type: i1$7.CategoryAxisComponent, selector: "kendo-chart-category-axis" }, { kind: "component", type: i1$7.CategoryAxisItemComponent, selector: "kendo-chart-category-axis-item", inputs: ["autoBaseUnitSteps", "axisCrossingValue", "background", "baseUnit", "baseUnitStep", "categories", "color", "justified", "line", "majorGridLines", "majorTicks", "max", "maxDateGroups", "maxDivisions", "min", "minorGridLines", "minorTicks", "name", "pane", "plotBands", "reverse", "roundToBaseUnit", "startAngle", "type", "visible", "weekStartDay", "crosshair", "labels", "notes", "select", "title", "rangeLabels"] }, { kind: "component", type: i1$7.CategoryAxisLabelsComponent, selector: "kendo-chart-category-axis-item-labels", inputs: ["background", "border", "color", "content", "culture", "dateFormats", "font", "format", "margin", "mirror", "padding", "position", "rotation", "skip", "step", "visible", "visual"] }, { kind: "component", type: i1$7.ChartAreaComponent, selector: "kendo-chart-area", inputs: ["background", "border", "height", "margin", "opacity", "width"] }, { kind: "component", type: i1$7.LegendComponent, selector: "kendo-chart-legend", inputs: ["align", "background", "border", "height", "labels", "margin", "offsetX", "offsetY", "orientation", "padding", "position", "reverse", "visible", "width", "markers", "spacing", "inactiveItems", "item", "title", "focusHighlight"] }, { kind: "component", type: i1$7.SeriesComponent, selector: "kendo-chart-series" }, { kind: "component", type: i1$7.SeriesItemComponent, selector: "kendo-chart-series-item", inputs: ["aggregate", "autoFit", "axis", "border", "categoryAxis", "categoryField", "closeField", "color", "colorField", "connectors", "currentField", "dashType", "data", "downColor", "downColorField", "drilldownField", "dynamicHeight", "dynamicSlope", "errorHighField", "errorLowField", "explodeField", "field", "fromField", "gap", "highField", "holeSize", "line", "lowField", "lowerField", "margin", "maxSize", "mean", "meanField", "median", "medianField", "minSize", "missingValues", "name", "neckRatio", "negativeColor", "negativeValues", "noteTextField", "opacity", "openField", "outliersField", "overlay", "padding", "q1Field", "q3Field", "segmentSpacing", "size", "sizeField", "spacing", "stack", "startAngle", "style", "summaryField", "target", "toField", "type", "upperField", "visible", "visibleInLegend", "visibleInLegendField", "visual", "width", "whiskers", "xAxis", "xErrorHighField", "xErrorLowField", "xField", "yAxis", "yErrorHighField", "yErrorLowField", "yField", "zIndex", "trendline", "for", "legendItem", "pattern", "patternField", "errorBars", "extremes", "highlight", "labels", "markers", "notes", "outliers", "tooltip"] }, { kind: "component", type: i1$7.SeriesLabelsComponent, selector: "kendo-chart-series-item-labels", inputs: ["align", "background", "border", "color", "content", "ariaContent", "distance", "font", "format", "margin", "padding", "position", "rotation", "visible", "visual", "from", "to"] }, { kind: "component", type: i1$7.TooltipComponent, selector: "kendo-chart-tooltip", inputs: ["background", "border", "color", "font", "format", "opacity", "padding", "shared", "visible"] }, { kind: "component", type: i1$7.ValueAxisComponent, selector: "kendo-chart-value-axis" }, { kind: "component", type: i1$7.ValueAxisItemComponent, selector: "kendo-chart-value-axis-item", inputs: ["axisCrossingValue", "background", "color", "line", "majorGridLines", "majorTicks", "majorUnit", "max", "min", "minorGridLines", "minorTicks", "minorUnit", "name", "narrowRange", "pane", "plotBands", "reverse", "type", "visible", "crosshair", "labels", "notes", "title"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
11372
11478
  }
11373
11479
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: BarChartWidgetComponent, decorators: [{
11374
11480
  type: Component,
@@ -11389,10 +11495,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
11389
11495
  <span>{{ error() }}</span>
11390
11496
  </div>
11391
11497
  } @else {
11392
- <kendo-chart class="chart-container">
11393
- <kendo-chart-plot-area [margin]="plotAreaMargin()"></kendo-chart-plot-area>
11498
+ <kendo-chart class="chart-container" [plotArea]="{ background: 'transparent', margin: plotAreaMargin() }">
11499
+ <kendo-chart-area [background]="'transparent'"></kendo-chart-area>
11394
11500
  <kendo-chart-category-axis>
11395
- <kendo-chart-category-axis-item [categories]="categories()">
11501
+ <kendo-chart-category-axis-item
11502
+ [categories]="categories()"
11503
+ [line]="{ visible: false }"
11504
+ [majorGridLines]="{ visible: false }">
11396
11505
  <kendo-chart-category-axis-item-labels
11397
11506
  [rotation]="labelRotation()"
11398
11507
  [content]="categoryLabelContent">
@@ -11400,6 +11509,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
11400
11509
  </kendo-chart-category-axis-item>
11401
11510
  </kendo-chart-category-axis>
11402
11511
 
11512
+ <kendo-chart-value-axis>
11513
+ <kendo-chart-value-axis-item
11514
+ [line]="{ visible: false }"
11515
+ [majorGridLines]="{ color: 'rgba(255,255,255,0.06)' }">
11516
+ </kendo-chart-value-axis-item>
11517
+ </kendo-chart-value-axis>
11518
+
11403
11519
  <kendo-chart-series>
11404
11520
  @for (series of seriesData(); track series.name) {
11405
11521
  <kendo-chart-series-item
@@ -11407,11 +11523,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
11407
11523
  [data]="series.data"
11408
11524
  [name]="series.name"
11409
11525
  [color]="series.color"
11526
+ [field]="hasColorThresholds() ? 'value' : ''"
11527
+ [colorField]="hasColorThresholds() ? '_color' : ''"
11528
+ [border]="{ width: 0 }"
11529
+ [gap]="0.8"
11410
11530
  [stack]="stackConfig()">
11411
11531
  @if (config.showDataLabels) {
11412
11532
  <kendo-chart-series-item-labels
11413
11533
  [visible]="true"
11414
- [format]="'{0:n0}'">
11534
+ [format]="dataLabelFormat()">
11415
11535
  </kendo-chart-series-item-labels>
11416
11536
  }
11417
11537
  </kendo-chart-series-item>
@@ -11459,6 +11579,8 @@ class BarChartConfigDialogComponent {
11459
11579
  initialShowLegend;
11460
11580
  initialLegendPosition;
11461
11581
  initialShowDataLabels;
11582
+ initialColorThresholds;
11583
+ initialDefaultBarColor;
11462
11584
  initialFilters;
11463
11585
  // State
11464
11586
  isLoadingInitial = false;
@@ -11495,7 +11617,9 @@ class BarChartConfigDialogComponent {
11495
11617
  categoryField: '',
11496
11618
  showLegend: true,
11497
11619
  legendPosition: 'right',
11498
- showDataLabels: false
11620
+ showDataLabels: false,
11621
+ colorThresholds: [],
11622
+ defaultBarColor: ''
11499
11623
  };
11500
11624
  get isValid() {
11501
11625
  if (this.selectedPersistentQuery === null || this.form.categoryField === '') {
@@ -11521,6 +11645,8 @@ class BarChartConfigDialogComponent {
11521
11645
  this.form.showLegend = this.initialShowLegend ?? true;
11522
11646
  this.form.legendPosition = this.initialLegendPosition ?? 'right';
11523
11647
  this.form.showDataLabels = this.initialShowDataLabels ?? false;
11648
+ this.form.colorThresholds = this.initialColorThresholds ? [...this.initialColorThresholds] : [];
11649
+ this.form.defaultBarColor = this.initialDefaultBarColor ?? '';
11524
11650
  // Determine series mode from initial values
11525
11651
  if (this.initialSeriesGroupField && this.initialValueField) {
11526
11652
  this.seriesMode = 'dynamic';
@@ -11664,6 +11790,8 @@ class BarChartConfigDialogComponent {
11664
11790
  showLegend: this.form.showLegend,
11665
11791
  legendPosition: this.form.legendPosition,
11666
11792
  showDataLabels: this.form.showDataLabels,
11793
+ colorThresholds: this.form.colorThresholds.length > 0 ? this.form.colorThresholds : undefined,
11794
+ defaultBarColor: this.form.defaultBarColor || undefined,
11667
11795
  filters: filtersDto
11668
11796
  };
11669
11797
  // Add dynamic series fields if in dynamic mode
@@ -11673,11 +11801,17 @@ class BarChartConfigDialogComponent {
11673
11801
  }
11674
11802
  this.windowRef.close(result);
11675
11803
  }
11804
+ addColorThreshold() {
11805
+ this.form.colorThresholds.push({ value: 0, color: '#10b981' });
11806
+ }
11807
+ removeColorThreshold(index) {
11808
+ this.form.colorThresholds.splice(index, 1);
11809
+ }
11676
11810
  onCancel() {
11677
11811
  this.windowRef.close();
11678
11812
  }
11679
11813
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: BarChartConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11680
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: BarChartConfigDialogComponent, isStandalone: true, selector: "mm-bar-chart-config-dialog", inputs: { initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialChartType: "initialChartType", initialCategoryField: "initialCategoryField", initialSeries: "initialSeries", initialSeriesGroupField: "initialSeriesGroupField", initialValueField: "initialValueField", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialShowDataLabels: "initialShowDataLabels", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
11814
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: BarChartConfigDialogComponent, isStandalone: true, selector: "mm-bar-chart-config-dialog", inputs: { initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialChartType: "initialChartType", initialCategoryField: "initialCategoryField", initialSeries: "initialSeries", initialSeriesGroupField: "initialSeriesGroupField", initialValueField: "initialValueField", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialShowDataLabels: "initialShowDataLabels", initialColorThresholds: "initialColorThresholds", initialDefaultBarColor: "initialDefaultBarColor", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
11681
11815
  <div class="config-container">
11682
11816
 
11683
11817
  <div class="config-form" [class.loading]="isLoadingInitial">
@@ -11907,6 +12041,24 @@ class BarChartConfigDialogComponent {
11907
12041
  </div>
11908
12042
  }
11909
12043
  </div>
12044
+
12045
+ <div class="form-section">
12046
+ <h4>Conditional Colors</h4>
12047
+ <p class="section-hint">Color bars based on value thresholds (sorted ascending).</p>
12048
+ @for (threshold of form.colorThresholds; track $index) {
12049
+ <div class="threshold-row">
12050
+ <label>Values &le;</label>
12051
+ <kendo-numerictextbox [(ngModel)]="threshold.value" [format]="'n0'" [spinners]="false" style="width: 100px;"></kendo-numerictextbox>
12052
+ <kendo-textbox [(ngModel)]="threshold.color" [placeholder]="'Color (#10b981)'" style="width: 120px;"></kendo-textbox>
12053
+ <button kendoButton fillMode="flat" (click)="removeColorThreshold($index)">Remove</button>
12054
+ </div>
12055
+ }
12056
+ <div class="threshold-row">
12057
+ <label>Default color</label>
12058
+ <kendo-textbox [(ngModel)]="form.defaultBarColor" [placeholder]="'#ef4444'" style="width: 120px;"></kendo-textbox>
12059
+ </div>
12060
+ <button kendoButton fillMode="flat" (click)="addColorThreshold()">+ Add Threshold</button>
12061
+ </div>
11910
12062
  </div>
11911
12063
 
11912
12064
  <div class="action-bar mm-dialog-actions">
@@ -11920,7 +12072,7 @@ class BarChartConfigDialogComponent {
11920
12072
  </button>
11921
12073
  </div>
11922
12074
  </div>
11923
- `, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.chart-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.form-row{display:flex;gap:24px}.checkbox-field{flex-direction:row;align-items:center}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: i4.MultiSelectComponent, selector: "kendo-multiselect", inputs: ["showStickyHeader", "focusableId", "autoClose", "loading", "data", "value", "valueField", "textField", "tabindex", "tabIndex", "size", "rounded", "fillMode", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "disabled", "itemDisabled", "checkboxes", "readonly", "filterable", "virtual", "popupSettings", "listHeight", "valuePrimitive", "clearButton", "tagMapper", "allowCustom", "valueNormalizer", "inputAttributes"], outputs: ["filterChange", "valueChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "removeTag"], exportAs: ["kendoMultiSelect"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
12075
+ `, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.chart-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.form-row{display:flex;gap:24px}.checkbox-field{flex-direction:row;align-items:center}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{margin-top:8px}.form-section h4{margin:0 0 4px;font-size:.95rem;font-weight:600}.threshold-row{display:flex;gap:8px;align-items:center;margin-bottom:8px}.threshold-row label{font-size:.85rem;min-width:70px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: i4.MultiSelectComponent, selector: "kendo-multiselect", inputs: ["showStickyHeader", "focusableId", "autoClose", "loading", "data", "value", "valueField", "textField", "tabindex", "tabIndex", "size", "rounded", "fillMode", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "disabled", "itemDisabled", "checkboxes", "readonly", "filterable", "virtual", "popupSettings", "listHeight", "valuePrimitive", "clearButton", "tagMapper", "allowCustom", "valueNormalizer", "inputAttributes"], outputs: ["filterChange", "valueChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "removeTag"], exportAs: ["kendoMultiSelect"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
11924
12076
  }
11925
12077
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: BarChartConfigDialogComponent, decorators: [{
11926
12078
  type: Component,
@@ -12164,6 +12316,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
12164
12316
  </div>
12165
12317
  }
12166
12318
  </div>
12319
+
12320
+ <div class="form-section">
12321
+ <h4>Conditional Colors</h4>
12322
+ <p class="section-hint">Color bars based on value thresholds (sorted ascending).</p>
12323
+ @for (threshold of form.colorThresholds; track $index) {
12324
+ <div class="threshold-row">
12325
+ <label>Values &le;</label>
12326
+ <kendo-numerictextbox [(ngModel)]="threshold.value" [format]="'n0'" [spinners]="false" style="width: 100px;"></kendo-numerictextbox>
12327
+ <kendo-textbox [(ngModel)]="threshold.color" [placeholder]="'Color (#10b981)'" style="width: 120px;"></kendo-textbox>
12328
+ <button kendoButton fillMode="flat" (click)="removeColorThreshold($index)">Remove</button>
12329
+ </div>
12330
+ }
12331
+ <div class="threshold-row">
12332
+ <label>Default color</label>
12333
+ <kendo-textbox [(ngModel)]="form.defaultBarColor" [placeholder]="'#ef4444'" style="width: 120px;"></kendo-textbox>
12334
+ </div>
12335
+ <button kendoButton fillMode="flat" (click)="addColorThreshold()">+ Add Threshold</button>
12336
+ </div>
12167
12337
  </div>
12168
12338
 
12169
12339
  <div class="action-bar mm-dialog-actions">
@@ -12177,7 +12347,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
12177
12347
  </button>
12178
12348
  </div>
12179
12349
  </div>
12180
- `, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.chart-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.form-row{display:flex;gap:24px}.checkbox-field{flex-direction:row;align-items:center}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"] }]
12350
+ `, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.chart-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.form-row{display:flex;gap:24px}.checkbox-field{flex-direction:row;align-items:center}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.query-item{display:flex;flex-direction:column;gap:2px}.query-name{font-weight:500}.query-description{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{margin-top:8px}.form-section h4{margin:0 0 4px;font-size:.95rem;font-weight:600}.threshold-row{display:flex;gap:8px;align-items:center;margin-bottom:8px}.threshold-row label{font-size:.85rem;min-width:70px}\n"] }]
12181
12351
  }], propDecorators: { querySelector: [{
12182
12352
  type: ViewChild,
12183
12353
  args: ['querySelector']
@@ -12201,6 +12371,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
12201
12371
  type: Input
12202
12372
  }], initialShowDataLabels: [{
12203
12373
  type: Input
12374
+ }], initialColorThresholds: [{
12375
+ type: Input
12376
+ }], initialDefaultBarColor: [{
12377
+ type: Input
12204
12378
  }], initialFilters: [{
12205
12379
  type: Input
12206
12380
  }] } });
@@ -12223,6 +12397,28 @@ class LineChartWidgetComponent {
12223
12397
  valueAxes = this._valueAxes.asReadonly();
12224
12398
  error = this._error.asReadonly();
12225
12399
  data = computed(() => this._seriesData(), ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
12400
+ plotBands = computed(() => {
12401
+ if (!this.config?.referenceLines?.length)
12402
+ return [];
12403
+ return this.config.referenceLines.map(ref => {
12404
+ const lineColor = ref.color ?? '#ef4444';
12405
+ const bandWidth = ref.value * 0.002 || 1;
12406
+ return {
12407
+ from: ref.value - bandWidth,
12408
+ to: ref.value + bandWidth,
12409
+ color: lineColor,
12410
+ opacity: ref.opacity ?? 0.8,
12411
+ label: ref.label ? {
12412
+ text: ref.label,
12413
+ position: 'top',
12414
+ align: 'right',
12415
+ color: lineColor,
12416
+ font: '500 0.8rem sans-serif',
12417
+ padding: { top: 2, right: 4, bottom: 2, left: 4 }
12418
+ } : undefined
12419
+ };
12420
+ });
12421
+ }, ...(ngDevMode ? [{ debugName: "plotBands" }] : /* istanbul ignore next */ []));
12226
12422
  chartType = computed(() => {
12227
12423
  return this.config?.chartType ?? 'line';
12228
12424
  }, ...(ngDevMode ? [{ debugName: "chartType" }] : /* istanbul ignore next */ []));
@@ -12482,11 +12678,14 @@ class LineChartWidgetComponent {
12482
12678
  <span>{{ error() }}</span>
12483
12679
  </div>
12484
12680
  } @else {
12485
- <kendo-chart class="chart-container">
12486
- <kendo-chart-plot-area [margin]="{ top: 0, right: 0, bottom: 0, left: 0 }"></kendo-chart-plot-area>
12681
+ <kendo-chart class="chart-container" [plotArea]="{ background: 'transparent', margin: { top: 0, right: 0, bottom: 0, left: 0 } }">
12682
+ <kendo-chart-area [background]="'transparent'"></kendo-chart-area>
12487
12683
 
12488
12684
  <kendo-chart-category-axis>
12489
- <kendo-chart-category-axis-item [categories]="categories()">
12685
+ <kendo-chart-category-axis-item
12686
+ [categories]="categories()"
12687
+ [line]="{ visible: false }"
12688
+ [majorGridLines]="{ visible: false }">
12490
12689
  <kendo-chart-category-axis-item-labels
12491
12690
  [rotation]="labelRotation()"
12492
12691
  [step]="labelStep()"
@@ -12500,19 +12699,36 @@ class LineChartWidgetComponent {
12500
12699
  @for (axis of valueAxes(); track axis.name) {
12501
12700
  <kendo-chart-value-axis-item
12502
12701
  [name]="axis.name"
12503
- [title]="{ text: axis.unit }">
12702
+ [title]="{ text: axis.unit }"
12703
+ [line]="{ visible: false }"
12704
+ [majorGridLines]="{ color: 'rgba(255,255,255,0.06)' }"
12705
+ [plotBands]="plotBands()">
12504
12706
  </kendo-chart-value-axis-item>
12505
12707
  }
12506
12708
  </kendo-chart-value-axis>
12507
12709
  }
12508
12710
 
12711
+ @if (valueAxes().length === 0) {
12712
+ <kendo-chart-value-axis>
12713
+ <kendo-chart-value-axis-item
12714
+ [name]="''"
12715
+ [title]="{ text: config.valueAxisTitle ?? '' }"
12716
+ [line]="{ visible: false }"
12717
+ [majorGridLines]="{ color: 'rgba(255,255,255,0.06)' }"
12718
+ [plotBands]="plotBands()">
12719
+ </kendo-chart-value-axis-item>
12720
+ </kendo-chart-value-axis>
12721
+ }
12722
+
12509
12723
  <kendo-chart-series>
12510
12724
  @for (series of seriesData(); track series.name) {
12511
12725
  <kendo-chart-series-item
12512
12726
  [type]="chartType()"
12727
+ [style]="'smooth'"
12513
12728
  [data]="series.data"
12514
12729
  [name]="series.name"
12515
12730
  [axis]="series.axisName ?? ''"
12731
+ [opacity]="0.7"
12516
12732
  [markers]="{ visible: config.showMarkers ?? false }">
12517
12733
  </kendo-chart-series-item>
12518
12734
  }
@@ -12534,7 +12750,7 @@ class LineChartWidgetComponent {
12534
12750
  </kendo-chart>
12535
12751
  }
12536
12752
  </div>
12537
- `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.line-chart-widget{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.line-chart-widget.loading,.line-chart-widget.error{opacity:.7}.loading-indicator,.error-message{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.chart-container{width:100%;height:100%}kendo-chart{width:100%;height:100%}.chart-tooltip{padding:4px 8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ChartsModule }, { kind: "component", type: i1$6.ChartComponent, selector: "kendo-chart", inputs: ["pannable", "renderAs", "seriesColors", "subtitle", "title", "noData", "observeStyles", "transitions", "zoomable", "axisDefaults", "categoryAxis", "chartArea", "legend", "panes", "paneDefaults", "plotArea", "series", "seriesDefaults", "tooltip", "valueAxis", "xAxis", "yAxis", "resizeRateLimit", "popupSettings", "drilldownLevel"], outputs: ["axisLabelClick", "drag", "dragEnd", "dragStart", "legendItemHover", "legendItemLeave", "noteClick", "noteHover", "noteLeave", "paneRender", "plotAreaClick", "plotAreaHover", "plotAreaLeave", "render", "select", "selectEnd", "selectStart", "seriesClick", "drilldown", "seriesHover", "seriesOver", "seriesLeave", "zoom", "zoomEnd", "zoomStart", "legendItemClick", "drilldownLevelChange"], exportAs: ["kendoChart"] }, { kind: "directive", type: i1$6.SeriesTooltipTemplateDirective, selector: "[kendoChartSeriesTooltipTemplate]" }, { kind: "component", type: i1$6.CategoryAxisComponent, selector: "kendo-chart-category-axis" }, { kind: "component", type: i1$6.CategoryAxisItemComponent, selector: "kendo-chart-category-axis-item", inputs: ["autoBaseUnitSteps", "axisCrossingValue", "background", "baseUnit", "baseUnitStep", "categories", "color", "justified", "line", "majorGridLines", "majorTicks", "max", "maxDateGroups", "maxDivisions", "min", "minorGridLines", "minorTicks", "name", "pane", "plotBands", "reverse", "roundToBaseUnit", "startAngle", "type", "visible", "weekStartDay", "crosshair", "labels", "notes", "select", "title", "rangeLabels"] }, { kind: "component", type: i1$6.CategoryAxisLabelsComponent, selector: "kendo-chart-category-axis-item-labels", inputs: ["background", "border", "color", "content", "culture", "dateFormats", "font", "format", "margin", "mirror", "padding", "position", "rotation", "skip", "step", "visible", "visual"] }, { kind: "component", type: i1$6.LegendComponent, selector: "kendo-chart-legend", inputs: ["align", "background", "border", "height", "labels", "margin", "offsetX", "offsetY", "orientation", "padding", "position", "reverse", "visible", "width", "markers", "spacing", "inactiveItems", "item", "title", "focusHighlight"] }, { kind: "component", type: i1$6.PlotAreaComponent, selector: "kendo-chart-plot-area", inputs: ["background", "border", "margin", "opacity", "padding"] }, { kind: "component", type: i1$6.SeriesComponent, selector: "kendo-chart-series" }, { kind: "component", type: i1$6.SeriesItemComponent, selector: "kendo-chart-series-item", inputs: ["aggregate", "autoFit", "axis", "border", "categoryAxis", "categoryField", "closeField", "color", "colorField", "connectors", "currentField", "dashType", "data", "downColor", "downColorField", "drilldownField", "dynamicHeight", "dynamicSlope", "errorHighField", "errorLowField", "explodeField", "field", "fromField", "gap", "highField", "holeSize", "line", "lowField", "lowerField", "margin", "maxSize", "mean", "meanField", "median", "medianField", "minSize", "missingValues", "name", "neckRatio", "negativeColor", "negativeValues", "noteTextField", "opacity", "openField", "outliersField", "overlay", "padding", "q1Field", "q3Field", "segmentSpacing", "size", "sizeField", "spacing", "stack", "startAngle", "style", "summaryField", "target", "toField", "type", "upperField", "visible", "visibleInLegend", "visibleInLegendField", "visual", "width", "whiskers", "xAxis", "xErrorHighField", "xErrorLowField", "xField", "yAxis", "yErrorHighField", "yErrorLowField", "yField", "zIndex", "trendline", "for", "legendItem", "pattern", "patternField", "errorBars", "extremes", "highlight", "labels", "markers", "notes", "outliers", "tooltip"] }, { kind: "component", type: i1$6.TooltipComponent, selector: "kendo-chart-tooltip", inputs: ["background", "border", "color", "font", "format", "opacity", "padding", "shared", "visible"] }, { kind: "component", type: i1$6.ValueAxisComponent, selector: "kendo-chart-value-axis" }, { kind: "component", type: i1$6.ValueAxisItemComponent, selector: "kendo-chart-value-axis-item", inputs: ["axisCrossingValue", "background", "color", "line", "majorGridLines", "majorTicks", "majorUnit", "max", "min", "minorGridLines", "minorTicks", "minorUnit", "name", "narrowRange", "pane", "plotBands", "reverse", "type", "visible", "crosshair", "labels", "notes", "title"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
12753
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.line-chart-widget{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.line-chart-widget.loading,.line-chart-widget.error{opacity:.7}.loading-indicator,.error-message{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.chart-container{width:100%;height:100%}kendo-chart{width:100%;height:100%}.chart-tooltip{padding:4px 8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ChartsModule }, { kind: "component", type: i1$7.ChartComponent, selector: "kendo-chart", inputs: ["pannable", "renderAs", "seriesColors", "subtitle", "title", "noData", "observeStyles", "transitions", "zoomable", "axisDefaults", "categoryAxis", "chartArea", "legend", "panes", "paneDefaults", "plotArea", "series", "seriesDefaults", "tooltip", "valueAxis", "xAxis", "yAxis", "resizeRateLimit", "popupSettings", "drilldownLevel"], outputs: ["axisLabelClick", "drag", "dragEnd", "dragStart", "legendItemHover", "legendItemLeave", "noteClick", "noteHover", "noteLeave", "paneRender", "plotAreaClick", "plotAreaHover", "plotAreaLeave", "render", "select", "selectEnd", "selectStart", "seriesClick", "drilldown", "seriesHover", "seriesOver", "seriesLeave", "zoom", "zoomEnd", "zoomStart", "legendItemClick", "drilldownLevelChange"], exportAs: ["kendoChart"] }, { kind: "directive", type: i1$7.SeriesTooltipTemplateDirective, selector: "[kendoChartSeriesTooltipTemplate]" }, { kind: "component", type: i1$7.CategoryAxisComponent, selector: "kendo-chart-category-axis" }, { kind: "component", type: i1$7.CategoryAxisItemComponent, selector: "kendo-chart-category-axis-item", inputs: ["autoBaseUnitSteps", "axisCrossingValue", "background", "baseUnit", "baseUnitStep", "categories", "color", "justified", "line", "majorGridLines", "majorTicks", "max", "maxDateGroups", "maxDivisions", "min", "minorGridLines", "minorTicks", "name", "pane", "plotBands", "reverse", "roundToBaseUnit", "startAngle", "type", "visible", "weekStartDay", "crosshair", "labels", "notes", "select", "title", "rangeLabels"] }, { kind: "component", type: i1$7.CategoryAxisLabelsComponent, selector: "kendo-chart-category-axis-item-labels", inputs: ["background", "border", "color", "content", "culture", "dateFormats", "font", "format", "margin", "mirror", "padding", "position", "rotation", "skip", "step", "visible", "visual"] }, { kind: "component", type: i1$7.ChartAreaComponent, selector: "kendo-chart-area", inputs: ["background", "border", "height", "margin", "opacity", "width"] }, { kind: "component", type: i1$7.LegendComponent, selector: "kendo-chart-legend", inputs: ["align", "background", "border", "height", "labels", "margin", "offsetX", "offsetY", "orientation", "padding", "position", "reverse", "visible", "width", "markers", "spacing", "inactiveItems", "item", "title", "focusHighlight"] }, { kind: "component", type: i1$7.SeriesComponent, selector: "kendo-chart-series" }, { kind: "component", type: i1$7.SeriesItemComponent, selector: "kendo-chart-series-item", inputs: ["aggregate", "autoFit", "axis", "border", "categoryAxis", "categoryField", "closeField", "color", "colorField", "connectors", "currentField", "dashType", "data", "downColor", "downColorField", "drilldownField", "dynamicHeight", "dynamicSlope", "errorHighField", "errorLowField", "explodeField", "field", "fromField", "gap", "highField", "holeSize", "line", "lowField", "lowerField", "margin", "maxSize", "mean", "meanField", "median", "medianField", "minSize", "missingValues", "name", "neckRatio", "negativeColor", "negativeValues", "noteTextField", "opacity", "openField", "outliersField", "overlay", "padding", "q1Field", "q3Field", "segmentSpacing", "size", "sizeField", "spacing", "stack", "startAngle", "style", "summaryField", "target", "toField", "type", "upperField", "visible", "visibleInLegend", "visibleInLegendField", "visual", "width", "whiskers", "xAxis", "xErrorHighField", "xErrorLowField", "xField", "yAxis", "yErrorHighField", "yErrorLowField", "yField", "zIndex", "trendline", "for", "legendItem", "pattern", "patternField", "errorBars", "extremes", "highlight", "labels", "markers", "notes", "outliers", "tooltip"] }, { kind: "component", type: i1$7.TooltipComponent, selector: "kendo-chart-tooltip", inputs: ["background", "border", "color", "font", "format", "opacity", "padding", "shared", "visible"] }, { kind: "component", type: i1$7.ValueAxisComponent, selector: "kendo-chart-value-axis" }, { kind: "component", type: i1$7.ValueAxisItemComponent, selector: "kendo-chart-value-axis-item", inputs: ["axisCrossingValue", "background", "color", "line", "majorGridLines", "majorTicks", "majorUnit", "max", "min", "minorGridLines", "minorTicks", "minorUnit", "name", "narrowRange", "pane", "plotBands", "reverse", "type", "visible", "crosshair", "labels", "notes", "title"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
12538
12754
  }
12539
12755
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: LineChartWidgetComponent, decorators: [{
12540
12756
  type: Component,
@@ -12555,11 +12771,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
12555
12771
  <span>{{ error() }}</span>
12556
12772
  </div>
12557
12773
  } @else {
12558
- <kendo-chart class="chart-container">
12559
- <kendo-chart-plot-area [margin]="{ top: 0, right: 0, bottom: 0, left: 0 }"></kendo-chart-plot-area>
12774
+ <kendo-chart class="chart-container" [plotArea]="{ background: 'transparent', margin: { top: 0, right: 0, bottom: 0, left: 0 } }">
12775
+ <kendo-chart-area [background]="'transparent'"></kendo-chart-area>
12560
12776
 
12561
12777
  <kendo-chart-category-axis>
12562
- <kendo-chart-category-axis-item [categories]="categories()">
12778
+ <kendo-chart-category-axis-item
12779
+ [categories]="categories()"
12780
+ [line]="{ visible: false }"
12781
+ [majorGridLines]="{ visible: false }">
12563
12782
  <kendo-chart-category-axis-item-labels
12564
12783
  [rotation]="labelRotation()"
12565
12784
  [step]="labelStep()"
@@ -12573,19 +12792,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
12573
12792
  @for (axis of valueAxes(); track axis.name) {
12574
12793
  <kendo-chart-value-axis-item
12575
12794
  [name]="axis.name"
12576
- [title]="{ text: axis.unit }">
12795
+ [title]="{ text: axis.unit }"
12796
+ [line]="{ visible: false }"
12797
+ [majorGridLines]="{ color: 'rgba(255,255,255,0.06)' }"
12798
+ [plotBands]="plotBands()">
12577
12799
  </kendo-chart-value-axis-item>
12578
12800
  }
12579
12801
  </kendo-chart-value-axis>
12580
12802
  }
12581
12803
 
12804
+ @if (valueAxes().length === 0) {
12805
+ <kendo-chart-value-axis>
12806
+ <kendo-chart-value-axis-item
12807
+ [name]="''"
12808
+ [title]="{ text: config.valueAxisTitle ?? '' }"
12809
+ [line]="{ visible: false }"
12810
+ [majorGridLines]="{ color: 'rgba(255,255,255,0.06)' }"
12811
+ [plotBands]="plotBands()">
12812
+ </kendo-chart-value-axis-item>
12813
+ </kendo-chart-value-axis>
12814
+ }
12815
+
12582
12816
  <kendo-chart-series>
12583
12817
  @for (series of seriesData(); track series.name) {
12584
12818
  <kendo-chart-series-item
12585
12819
  [type]="chartType()"
12820
+ [style]="'smooth'"
12586
12821
  [data]="series.data"
12587
12822
  [name]="series.name"
12588
12823
  [axis]="series.axisName ?? ''"
12824
+ [opacity]="0.7"
12589
12825
  [markers]="{ visible: config.showMarkers ?? false }">
12590
12826
  </kendo-chart-series-item>
12591
12827
  }
@@ -12632,6 +12868,7 @@ class LineChartConfigDialogComponent {
12632
12868
  initialShowLegend;
12633
12869
  initialLegendPosition;
12634
12870
  initialShowMarkers;
12871
+ initialReferenceLines;
12635
12872
  initialFilters;
12636
12873
  // State
12637
12874
  isLoadingInitial = false;
@@ -12660,7 +12897,8 @@ class LineChartConfigDialogComponent {
12660
12897
  unitField: '',
12661
12898
  showLegend: true,
12662
12899
  legendPosition: 'right',
12663
- showMarkers: false
12900
+ showMarkers: false,
12901
+ referenceLines: []
12664
12902
  };
12665
12903
  get isValid() {
12666
12904
  return this.selectedPersistentQuery !== null
@@ -12684,6 +12922,7 @@ class LineChartConfigDialogComponent {
12684
12922
  this.form.showLegend = this.initialShowLegend ?? true;
12685
12923
  this.form.legendPosition = this.initialLegendPosition ?? 'right';
12686
12924
  this.form.showMarkers = this.initialShowMarkers ?? false;
12925
+ this.form.referenceLines = this.initialReferenceLines ? [...this.initialReferenceLines] : [];
12687
12926
  // Initialize filters
12688
12927
  if (this.initialFilters && this.initialFilters.length > 0) {
12689
12928
  this.filters = this.initialFilters.map((f, index) => ({
@@ -12784,15 +13023,22 @@ class LineChartConfigDialogComponent {
12784
13023
  showLegend: this.form.showLegend,
12785
13024
  legendPosition: this.form.legendPosition,
12786
13025
  showMarkers: this.form.showMarkers,
13026
+ referenceLines: this.form.referenceLines.length > 0 ? this.form.referenceLines : undefined,
12787
13027
  filters: filtersDto
12788
13028
  };
12789
13029
  this.windowRef.close(result);
12790
13030
  }
13031
+ addReferenceLine() {
13032
+ this.form.referenceLines.push({ value: 0, label: '', color: '#ef4444' });
13033
+ }
13034
+ removeReferenceLine(index) {
13035
+ this.form.referenceLines.splice(index, 1);
13036
+ }
12791
13037
  onCancel() {
12792
13038
  this.windowRef.close();
12793
13039
  }
12794
13040
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: LineChartConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12795
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: LineChartConfigDialogComponent, isStandalone: true, selector: "mm-line-chart-config-dialog", inputs: { initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialChartType: "initialChartType", initialCategoryField: "initialCategoryField", initialSeriesGroupField: "initialSeriesGroupField", initialValueField: "initialValueField", initialUnitField: "initialUnitField", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialShowMarkers: "initialShowMarkers", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
13041
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: LineChartConfigDialogComponent, isStandalone: true, selector: "mm-line-chart-config-dialog", inputs: { initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialChartType: "initialChartType", initialCategoryField: "initialCategoryField", initialSeriesGroupField: "initialSeriesGroupField", initialValueField: "initialValueField", initialUnitField: "initialUnitField", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialShowMarkers: "initialShowMarkers", initialReferenceLines: "initialReferenceLines", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
12796
13042
  <div class="config-container">
12797
13043
 
12798
13044
  <div class="config-form" [class.loading]="isLoadingInitial">
@@ -12968,6 +13214,20 @@ class LineChartConfigDialogComponent {
12968
13214
  </div>
12969
13215
  }
12970
13216
  </div>
13217
+
13218
+ <div class="form-section">
13219
+ <h4>Reference Lines</h4>
13220
+ <p class="section-hint">Add horizontal threshold lines to the chart.</p>
13221
+ @for (refLine of form.referenceLines; track $index) {
13222
+ <div class="reference-line-row">
13223
+ <kendo-numerictextbox [(ngModel)]="refLine.value" [format]="'n0'" [placeholder]="'Value'" [spinners]="false" style="width: 100px;"></kendo-numerictextbox>
13224
+ <kendo-textbox [(ngModel)]="refLine.label" [placeholder]="'Label (e.g. Limit)'" style="flex: 1;"></kendo-textbox>
13225
+ <kendo-textbox [(ngModel)]="refLine.color" [placeholder]="'Color (#ef4444)'" style="width: 110px;"></kendo-textbox>
13226
+ <button kendoButton fillMode="flat" (click)="removeReferenceLine($index)">Remove</button>
13227
+ </div>
13228
+ }
13229
+ <button kendoButton fillMode="flat" (click)="addReferenceLine()">+ Add Reference Line</button>
13230
+ </div>
12971
13231
  </div>
12972
13232
 
12973
13233
  <div class="action-bar mm-dialog-actions">
@@ -12981,7 +13241,7 @@ class LineChartConfigDialogComponent {
12981
13241
  </button>
12982
13242
  </div>
12983
13243
  </div>
12984
- `, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.chart-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.form-row{display:flex;gap:24px;align-items:center}.checkbox-field{flex-direction:row;align-items:center;margin-bottom:0}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
13244
+ `, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.chart-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.form-row{display:flex;gap:24px;align-items:center}.checkbox-field{flex-direction:row;align-items:center;margin-bottom:0}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{margin-top:8px}.form-section h4{margin:0 0 4px;font-size:.95rem;font-weight:600}.reference-line-row{display:flex;gap:8px;align-items:center;margin-bottom:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "directive", type: i3.RadioButtonDirective, selector: "input[kendoRadioButton]", inputs: ["size"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "directive", type: i4.ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: i4.ComboBoxComponent, selector: "kendo-combobox", inputs: ["icon", "svgIcon", "inputAttributes", "showStickyHeader", "focusableId", "allowCustom", "data", "value", "textField", "valueField", "valuePrimitive", "valueNormalizer", "placeholder", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "loading", "suggest", "clearButton", "disabled", "itemDisabled", "readonly", "tabindex", "tabIndex", "filterable", "virtual", "size", "rounded", "fillMode"], outputs: ["valueChange", "selectionChange", "filterChange", "open", "opened", "close", "closed", "focus", "blur", "inputFocus", "inputBlur", "escape"], exportAs: ["kendoComboBox"] }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "hideNavigationProperties", "attributePaths", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: QuerySelectorComponent, selector: "mm-query-selector", inputs: ["placeholder", "hint", "disabled"], outputs: ["querySelected", "queriesLoaded"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
12985
13245
  }
12986
13246
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: LineChartConfigDialogComponent, decorators: [{
12987
13247
  type: Component,
@@ -13171,6 +13431,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
13171
13431
  </div>
13172
13432
  }
13173
13433
  </div>
13434
+
13435
+ <div class="form-section">
13436
+ <h4>Reference Lines</h4>
13437
+ <p class="section-hint">Add horizontal threshold lines to the chart.</p>
13438
+ @for (refLine of form.referenceLines; track $index) {
13439
+ <div class="reference-line-row">
13440
+ <kendo-numerictextbox [(ngModel)]="refLine.value" [format]="'n0'" [placeholder]="'Value'" [spinners]="false" style="width: 100px;"></kendo-numerictextbox>
13441
+ <kendo-textbox [(ngModel)]="refLine.label" [placeholder]="'Label (e.g. Limit)'" style="flex: 1;"></kendo-textbox>
13442
+ <kendo-textbox [(ngModel)]="refLine.color" [placeholder]="'Color (#ef4444)'" style="width: 110px;"></kendo-textbox>
13443
+ <button kendoButton fillMode="flat" (click)="removeReferenceLine($index)">Remove</button>
13444
+ </div>
13445
+ }
13446
+ <button kendoButton fillMode="flat" (click)="addReferenceLine()">+ Add Reference Line</button>
13447
+ </div>
13174
13448
  </div>
13175
13449
 
13176
13450
  <div class="action-bar mm-dialog-actions">
@@ -13184,7 +13458,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
13184
13458
  </button>
13185
13459
  </div>
13186
13460
  </div>
13187
- `, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.chart-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.form-row{display:flex;gap:24px;align-items:center}.checkbox-field{flex-direction:row;align-items:center;margin-bottom:0}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}\n"] }]
13461
+ `, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:20px;flex:1;overflow-y:auto;padding:16px;position:relative}.config-form.loading{pointer-events:none}.config-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.section-title{margin:0 0 16px;font-size:1rem;font-weight:600;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.form-field:last-child{margin-bottom:0}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.chart-type-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.form-row{display:flex;gap:24px;align-items:center}.checkbox-field{flex-direction:row;align-items:center;margin-bottom:0}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.column-item{display:flex;justify-content:space-between;gap:16px}.column-path{font-weight:500}.column-type{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{margin-top:8px}.form-section h4{margin:0 0 4px;font-size:.95rem;font-weight:600}.reference-line-row{display:flex;gap:8px;align-items:center;margin-bottom:8px}\n"] }]
13188
13462
  }], propDecorators: { querySelector: [{
13189
13463
  type: ViewChild,
13190
13464
  args: ['querySelector']
@@ -13208,6 +13482,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
13208
13482
  type: Input
13209
13483
  }], initialShowMarkers: [{
13210
13484
  type: Input
13485
+ }], initialReferenceLines: [{
13486
+ type: Input
13211
13487
  }], initialFilters: [{
13212
13488
  type: Input
13213
13489
  }] } });
@@ -13667,7 +13943,7 @@ class HeatmapWidgetComponent {
13667
13943
  </kendo-chart>
13668
13944
  }
13669
13945
  </div>
13670
- `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.heatmap-widget{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.heatmap-widget.loading,.heatmap-widget.error{opacity:.7}.loading-indicator,.error-message{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.chart-container{width:100%;height:100%}kendo-chart{width:100%;height:100%}.chart-tooltip{padding:4px 8px;color:#212529;background:#fff;border-radius:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ChartsModule }, { kind: "component", type: i1$6.ChartComponent, selector: "kendo-chart", inputs: ["pannable", "renderAs", "seriesColors", "subtitle", "title", "noData", "observeStyles", "transitions", "zoomable", "axisDefaults", "categoryAxis", "chartArea", "legend", "panes", "paneDefaults", "plotArea", "series", "seriesDefaults", "tooltip", "valueAxis", "xAxis", "yAxis", "resizeRateLimit", "popupSettings", "drilldownLevel"], outputs: ["axisLabelClick", "drag", "dragEnd", "dragStart", "legendItemHover", "legendItemLeave", "noteClick", "noteHover", "noteLeave", "paneRender", "plotAreaClick", "plotAreaHover", "plotAreaLeave", "render", "select", "selectEnd", "selectStart", "seriesClick", "drilldown", "seriesHover", "seriesOver", "seriesLeave", "zoom", "zoomEnd", "zoomStart", "legendItemClick", "drilldownLevelChange"], exportAs: ["kendoChart"] }, { kind: "component", type: i1$6.XAxisComponent, selector: "kendo-chart-x-axis" }, { kind: "component", type: i1$6.XAxisItemComponent, selector: "kendo-chart-x-axis-item", inputs: ["axisCrossingValue", "background", "baseUnit", "categories", "color", "line", "majorGridLines", "majorTicks", "majorUnit", "max", "min", "minorGridLines", "minorTicks", "minorUnit", "name", "narrowRange", "pane", "plotBands", "reverse", "startAngle", "type", "visible", "weekStartDay", "crosshair", "labels", "notes", "title"] }, { kind: "component", type: i1$6.XAxisLabelsComponent, selector: "kendo-chart-x-axis-item-labels", inputs: ["background", "border", "color", "content", "culture", "dateFormats", "font", "format", "margin", "mirror", "padding", "position", "rotation", "skip", "step", "visible", "visual"] }, { kind: "component", type: i1$6.YAxisComponent, selector: "kendo-chart-y-axis" }, { kind: "component", type: i1$6.YAxisItemComponent, selector: "kendo-chart-y-axis-item", inputs: ["axisCrossingValue", "background", "baseUnit", "categories", "color", "line", "majorGridLines", "majorTicks", "majorUnit", "max", "min", "minorGridLines", "minorTicks", "minorUnit", "name", "narrowRange", "pane", "plotBands", "reverse", "type", "visible", "crosshair", "labels", "notes", "title"] }, { kind: "directive", type: i1$6.SeriesTooltipTemplateDirective, selector: "[kendoChartSeriesTooltipTemplate]" }, { kind: "component", type: i1$6.LegendComponent, selector: "kendo-chart-legend", inputs: ["align", "background", "border", "height", "labels", "margin", "offsetX", "offsetY", "orientation", "padding", "position", "reverse", "visible", "width", "markers", "spacing", "inactiveItems", "item", "title", "focusHighlight"] }, { kind: "component", type: i1$6.SeriesComponent, selector: "kendo-chart-series" }, { kind: "component", type: i1$6.SeriesItemComponent, selector: "kendo-chart-series-item", inputs: ["aggregate", "autoFit", "axis", "border", "categoryAxis", "categoryField", "closeField", "color", "colorField", "connectors", "currentField", "dashType", "data", "downColor", "downColorField", "drilldownField", "dynamicHeight", "dynamicSlope", "errorHighField", "errorLowField", "explodeField", "field", "fromField", "gap", "highField", "holeSize", "line", "lowField", "lowerField", "margin", "maxSize", "mean", "meanField", "median", "medianField", "minSize", "missingValues", "name", "neckRatio", "negativeColor", "negativeValues", "noteTextField", "opacity", "openField", "outliersField", "overlay", "padding", "q1Field", "q3Field", "segmentSpacing", "size", "sizeField", "spacing", "stack", "startAngle", "style", "summaryField", "target", "toField", "type", "upperField", "visible", "visibleInLegend", "visibleInLegendField", "visual", "width", "whiskers", "xAxis", "xErrorHighField", "xErrorLowField", "xField", "yAxis", "yErrorHighField", "yErrorLowField", "yField", "zIndex", "trendline", "for", "legendItem", "pattern", "patternField", "errorBars", "extremes", "highlight", "labels", "markers", "notes", "outliers", "tooltip"] }, { kind: "component", type: i1$6.SeriesLabelsComponent, selector: "kendo-chart-series-item-labels", inputs: ["align", "background", "border", "color", "content", "ariaContent", "distance", "font", "format", "margin", "padding", "position", "rotation", "visible", "visual", "from", "to"] }, { kind: "component", type: i1$6.TooltipComponent, selector: "kendo-chart-tooltip", inputs: ["background", "border", "color", "font", "format", "opacity", "padding", "shared", "visible"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
13946
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.heatmap-widget{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:8px;box-sizing:border-box;overflow:hidden}.heatmap-widget.loading,.heatmap-widget.error{opacity:.7}.loading-indicator,.error-message{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.loading-indicator span{font-size:1.5rem;color:var(--kendo-color-subtle, #6c757d)}.error-message span{color:var(--kendo-color-error, #dc3545);font-size:.875rem}.chart-container{width:100%;height:100%}kendo-chart{width:100%;height:100%}.chart-tooltip{padding:4px 8px;color:#212529;background:#fff;border-radius:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ChartsModule }, { kind: "component", type: i1$7.ChartComponent, selector: "kendo-chart", inputs: ["pannable", "renderAs", "seriesColors", "subtitle", "title", "noData", "observeStyles", "transitions", "zoomable", "axisDefaults", "categoryAxis", "chartArea", "legend", "panes", "paneDefaults", "plotArea", "series", "seriesDefaults", "tooltip", "valueAxis", "xAxis", "yAxis", "resizeRateLimit", "popupSettings", "drilldownLevel"], outputs: ["axisLabelClick", "drag", "dragEnd", "dragStart", "legendItemHover", "legendItemLeave", "noteClick", "noteHover", "noteLeave", "paneRender", "plotAreaClick", "plotAreaHover", "plotAreaLeave", "render", "select", "selectEnd", "selectStart", "seriesClick", "drilldown", "seriesHover", "seriesOver", "seriesLeave", "zoom", "zoomEnd", "zoomStart", "legendItemClick", "drilldownLevelChange"], exportAs: ["kendoChart"] }, { kind: "component", type: i1$7.XAxisComponent, selector: "kendo-chart-x-axis" }, { kind: "component", type: i1$7.XAxisItemComponent, selector: "kendo-chart-x-axis-item", inputs: ["axisCrossingValue", "background", "baseUnit", "categories", "color", "line", "majorGridLines", "majorTicks", "majorUnit", "max", "min", "minorGridLines", "minorTicks", "minorUnit", "name", "narrowRange", "pane", "plotBands", "reverse", "startAngle", "type", "visible", "weekStartDay", "crosshair", "labels", "notes", "title"] }, { kind: "component", type: i1$7.XAxisLabelsComponent, selector: "kendo-chart-x-axis-item-labels", inputs: ["background", "border", "color", "content", "culture", "dateFormats", "font", "format", "margin", "mirror", "padding", "position", "rotation", "skip", "step", "visible", "visual"] }, { kind: "component", type: i1$7.YAxisComponent, selector: "kendo-chart-y-axis" }, { kind: "component", type: i1$7.YAxisItemComponent, selector: "kendo-chart-y-axis-item", inputs: ["axisCrossingValue", "background", "baseUnit", "categories", "color", "line", "majorGridLines", "majorTicks", "majorUnit", "max", "min", "minorGridLines", "minorTicks", "minorUnit", "name", "narrowRange", "pane", "plotBands", "reverse", "type", "visible", "crosshair", "labels", "notes", "title"] }, { kind: "directive", type: i1$7.SeriesTooltipTemplateDirective, selector: "[kendoChartSeriesTooltipTemplate]" }, { kind: "component", type: i1$7.LegendComponent, selector: "kendo-chart-legend", inputs: ["align", "background", "border", "height", "labels", "margin", "offsetX", "offsetY", "orientation", "padding", "position", "reverse", "visible", "width", "markers", "spacing", "inactiveItems", "item", "title", "focusHighlight"] }, { kind: "component", type: i1$7.SeriesComponent, selector: "kendo-chart-series" }, { kind: "component", type: i1$7.SeriesItemComponent, selector: "kendo-chart-series-item", inputs: ["aggregate", "autoFit", "axis", "border", "categoryAxis", "categoryField", "closeField", "color", "colorField", "connectors", "currentField", "dashType", "data", "downColor", "downColorField", "drilldownField", "dynamicHeight", "dynamicSlope", "errorHighField", "errorLowField", "explodeField", "field", "fromField", "gap", "highField", "holeSize", "line", "lowField", "lowerField", "margin", "maxSize", "mean", "meanField", "median", "medianField", "minSize", "missingValues", "name", "neckRatio", "negativeColor", "negativeValues", "noteTextField", "opacity", "openField", "outliersField", "overlay", "padding", "q1Field", "q3Field", "segmentSpacing", "size", "sizeField", "spacing", "stack", "startAngle", "style", "summaryField", "target", "toField", "type", "upperField", "visible", "visibleInLegend", "visibleInLegendField", "visual", "width", "whiskers", "xAxis", "xErrorHighField", "xErrorLowField", "xField", "yAxis", "yErrorHighField", "yErrorLowField", "yField", "zIndex", "trendline", "for", "legendItem", "pattern", "patternField", "errorBars", "extremes", "highlight", "labels", "markers", "notes", "outliers", "tooltip"] }, { kind: "component", type: i1$7.SeriesLabelsComponent, selector: "kendo-chart-series-item-labels", inputs: ["align", "background", "border", "color", "content", "ariaContent", "distance", "font", "format", "margin", "padding", "position", "rotation", "visible", "visual", "from", "to"] }, { kind: "component", type: i1$7.TooltipComponent, selector: "kendo-chart-tooltip", inputs: ["background", "border", "color", "font", "format", "opacity", "padding", "shared", "visible"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
13671
13947
  }
13672
13948
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: HeatmapWidgetComponent, decorators: [{
13673
13949
  type: Component,
@@ -17021,7 +17297,7 @@ class MarkdownWidgetComponent {
17021
17297
  this._data.set(this.config?.content ?? '');
17022
17298
  }
17023
17299
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MarkdownWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
17024
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MarkdownWidgetComponent, isStandalone: true, selector: "mm-markdown-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"markdown-widget\"\n [class.loading]=\"isLoading()\"\n [class.error]=\"error()\"\n [ngStyle]=\"containerStyle()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured\n message=\"No markdown content configured.\">\n </mm-widget-not-configured>\n } @else {\n <div class=\"markdown-content mm-prose\">\n <markdown [data]=\"resolvedContent()\"></markdown>\n </div>\n }\n</div>\n", styles: [".markdown-widget{display:flex;flex-direction:column;height:100%;overflow:auto}.markdown-content{flex:1;overflow:auto}:host{--mm-prose-text: #333333;--mm-prose-heading: #1976d2;--mm-prose-link: #1565c0;--mm-prose-link-hover: #0d47a1;--mm-prose-code-bg: #f5f5f5;--mm-prose-code-text: #d32f2f;--mm-prose-pre-bg: #f5f5f5;--mm-prose-pre-border: #e0e0e0;--mm-prose-blockquote-border: #6c4da8;--mm-prose-blockquote-bg: rgba(108, 77, 168, .05);--mm-prose-table-header-bg: #f5f5f5;--mm-prose-table-header-text: #333333;--mm-prose-table-border: #e0e0e0;--mm-prose-table-row-even: rgba(0, 0, 0, .02);--mm-prose-strong: #111111;--mm-prose-em: #1565c0;--mm-prose-marker-ul: #1976d2;--mm-prose-marker-ol: #1565c0;--mm-prose-hr-start: #1976d2;--mm-prose-hr-end: #1565c0;--mm-prose-img-border: #e0e0e0;--mm-prose-font: \"Roboto\", sans-serif}:host ::ng-deep .mm-prose{color:var(--mm-prose-text);font-family:var(--mm-prose-font);line-height:1.6}:host ::ng-deep .mm-prose h1,:host ::ng-deep .mm-prose h2,:host ::ng-deep .mm-prose h3,:host ::ng-deep .mm-prose h4,:host ::ng-deep .mm-prose h5,:host ::ng-deep .mm-prose h6{color:var(--mm-prose-heading);font-weight:600;margin-top:1.5em;margin-bottom:.5em;letter-spacing:.5px}:host ::ng-deep .mm-prose h1:first-child,:host ::ng-deep .mm-prose h2:first-child,:host ::ng-deep .mm-prose h3:first-child,:host ::ng-deep .mm-prose h4:first-child,:host ::ng-deep .mm-prose h5:first-child,:host ::ng-deep .mm-prose h6:first-child{margin-top:0}:host ::ng-deep .mm-prose h1{font-size:1.75rem;border-bottom:2px solid var(--mm-prose-heading);padding-bottom:.25em}:host ::ng-deep .mm-prose h2{font-size:1.5rem;border-bottom:1px solid var(--mm-prose-table-border);padding-bottom:.2em}:host ::ng-deep .mm-prose h3{font-size:1.25rem}:host ::ng-deep .mm-prose h4{font-size:1.1rem}:host ::ng-deep .mm-prose h5,:host ::ng-deep .mm-prose h6{font-size:1rem}:host ::ng-deep .mm-prose p{margin-bottom:1em}:host ::ng-deep .mm-prose p:last-child{margin-bottom:0}:host ::ng-deep .mm-prose a{color:var(--mm-prose-link);text-decoration:none;border-bottom:1px solid transparent;transition:all .15s ease}:host ::ng-deep .mm-prose a:hover{color:var(--mm-prose-link-hover);border-bottom-color:var(--mm-prose-link-hover)}:host ::ng-deep .mm-prose ul,:host ::ng-deep .mm-prose ol{margin-bottom:1em;padding-left:1.5em}:host ::ng-deep .mm-prose li{margin-bottom:.25em}:host ::ng-deep .mm-prose ul li::marker{color:var(--mm-prose-marker-ul)}:host ::ng-deep .mm-prose ol li::marker{color:var(--mm-prose-marker-ol)}:host ::ng-deep .mm-prose code{font-family:Roboto Mono,monospace;background:var(--mm-prose-code-bg);color:var(--mm-prose-code-text);padding:.2em .4em;border-radius:4px;font-size:.9em}:host ::ng-deep .mm-prose pre{background:var(--mm-prose-pre-bg);border:1px solid var(--mm-prose-pre-border);border-radius:8px;padding:1em;overflow-x:auto;margin-bottom:1em}:host ::ng-deep .mm-prose pre code{background:transparent;padding:0;color:var(--mm-prose-text)}:host ::ng-deep .mm-prose blockquote{border-left:4px solid var(--mm-prose-blockquote-border);background:var(--mm-prose-blockquote-bg);padding:.75em 1em;margin:1em 0;border-radius:0 8px 8px 0}:host ::ng-deep .mm-prose blockquote p:last-child{margin-bottom:0}:host ::ng-deep .mm-prose table{width:100%;border-collapse:collapse;margin-bottom:1em}:host ::ng-deep .mm-prose th,:host ::ng-deep .mm-prose td{padding:.75em;border:1px solid var(--mm-prose-table-border);text-align:left}:host ::ng-deep .mm-prose th{background:var(--mm-prose-table-header-bg);color:var(--mm-prose-table-header-text);font-weight:600;text-transform:uppercase;font-size:.85em;letter-spacing:.5px}:host ::ng-deep .mm-prose tr:nth-child(2n){background:var(--mm-prose-table-row-even)}:host ::ng-deep .mm-prose hr{border:none;height:2px;background:linear-gradient(90deg,var(--mm-prose-hr-start),var(--mm-prose-hr-end),transparent);margin:1.5em 0}:host ::ng-deep .mm-prose img{max-width:100%;height:auto;border-radius:8px;border:1px solid var(--mm-prose-img-border)}:host ::ng-deep .mm-prose strong,:host ::ng-deep .mm-prose b{font-weight:600;color:var(--mm-prose-strong)}:host ::ng-deep .mm-prose em,:host ::ng-deep .mm-prose i{color:var(--mm-prose-em)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MarkdownModule }, { kind: "component", type: i2$2.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
17300
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MarkdownWidgetComponent, isStandalone: true, selector: "mm-markdown-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"markdown-widget\"\n [class.loading]=\"isLoading()\"\n [class.error]=\"error()\"\n [ngStyle]=\"containerStyle()\">\n @if (isNotConfigured()) {\n <mm-widget-not-configured\n message=\"No markdown content configured.\">\n </mm-widget-not-configured>\n } @else {\n <div class=\"markdown-content mm-prose\">\n <markdown [data]=\"resolvedContent()\"></markdown>\n </div>\n }\n</div>\n", styles: [".markdown-widget{display:flex;flex-direction:column;height:100%;overflow:auto}.markdown-content{flex:1;overflow:auto}:host{--mm-prose-text: #333333;--mm-prose-heading: #1976d2;--mm-prose-link: #1565c0;--mm-prose-link-hover: #0d47a1;--mm-prose-code-bg: #f5f5f5;--mm-prose-code-text: #d32f2f;--mm-prose-pre-bg: #f5f5f5;--mm-prose-pre-border: #e0e0e0;--mm-prose-blockquote-border: #6c4da8;--mm-prose-blockquote-bg: rgba(108, 77, 168, .05);--mm-prose-table-header-bg: #f5f5f5;--mm-prose-table-header-text: #333333;--mm-prose-table-border: #e0e0e0;--mm-prose-table-row-even: rgba(0, 0, 0, .02);--mm-prose-strong: #111111;--mm-prose-em: #1565c0;--mm-prose-marker-ul: #1976d2;--mm-prose-marker-ol: #1565c0;--mm-prose-hr-start: #1976d2;--mm-prose-hr-end: #1565c0;--mm-prose-img-border: #e0e0e0;--mm-prose-font: \"Roboto\", sans-serif}:host ::ng-deep .mm-prose{color:var(--mm-prose-text);font-family:var(--mm-prose-font);line-height:1.6}:host ::ng-deep .mm-prose h1,:host ::ng-deep .mm-prose h2,:host ::ng-deep .mm-prose h3,:host ::ng-deep .mm-prose h4,:host ::ng-deep .mm-prose h5,:host ::ng-deep .mm-prose h6{color:var(--mm-prose-heading);font-weight:600;margin-top:1.5em;margin-bottom:.5em;letter-spacing:.5px}:host ::ng-deep .mm-prose h1:first-child,:host ::ng-deep .mm-prose h2:first-child,:host ::ng-deep .mm-prose h3:first-child,:host ::ng-deep .mm-prose h4:first-child,:host ::ng-deep .mm-prose h5:first-child,:host ::ng-deep .mm-prose h6:first-child{margin-top:0}:host ::ng-deep .mm-prose h1{font-size:1.75rem;border-bottom:2px solid var(--mm-prose-heading);padding-bottom:.25em}:host ::ng-deep .mm-prose h2{font-size:1.5rem;border-bottom:1px solid var(--mm-prose-table-border);padding-bottom:.2em}:host ::ng-deep .mm-prose h3{font-size:1.25rem}:host ::ng-deep .mm-prose h4{font-size:1.1rem}:host ::ng-deep .mm-prose h5,:host ::ng-deep .mm-prose h6{font-size:1rem}:host ::ng-deep .mm-prose p{margin-bottom:1em}:host ::ng-deep .mm-prose p:last-child{margin-bottom:0}:host ::ng-deep .mm-prose a{color:var(--mm-prose-link);text-decoration:none;border-bottom:1px solid transparent;transition:all .15s ease}:host ::ng-deep .mm-prose a:hover{color:var(--mm-prose-link-hover);border-bottom-color:var(--mm-prose-link-hover)}:host ::ng-deep .mm-prose ul,:host ::ng-deep .mm-prose ol{margin-bottom:1em;padding-left:1.5em}:host ::ng-deep .mm-prose li{margin-bottom:.25em}:host ::ng-deep .mm-prose ul li::marker{color:var(--mm-prose-marker-ul)}:host ::ng-deep .mm-prose ol li::marker{color:var(--mm-prose-marker-ol)}:host ::ng-deep .mm-prose code{font-family:Roboto Mono,monospace;background:var(--mm-prose-code-bg);color:var(--mm-prose-code-text);padding:.2em .4em;border-radius:4px;font-size:.9em}:host ::ng-deep .mm-prose pre{background:var(--mm-prose-pre-bg);border:1px solid var(--mm-prose-pre-border);border-radius:8px;padding:1em;overflow-x:auto;margin-bottom:1em}:host ::ng-deep .mm-prose pre code{background:transparent;padding:0;color:var(--mm-prose-text)}:host ::ng-deep .mm-prose blockquote{border-left:4px solid var(--mm-prose-blockquote-border);background:var(--mm-prose-blockquote-bg);padding:.75em 1em;margin:1em 0;border-radius:0 8px 8px 0}:host ::ng-deep .mm-prose blockquote p:last-child{margin-bottom:0}:host ::ng-deep .mm-prose table{width:100%;border-collapse:collapse;margin-bottom:1em}:host ::ng-deep .mm-prose th,:host ::ng-deep .mm-prose td{padding:.75em;border:1px solid var(--mm-prose-table-border);text-align:left}:host ::ng-deep .mm-prose th{background:var(--mm-prose-table-header-bg);color:var(--mm-prose-table-header-text);font-weight:600;text-transform:uppercase;font-size:.85em;letter-spacing:.5px}:host ::ng-deep .mm-prose tr:nth-child(2n){background:var(--mm-prose-table-row-even)}:host ::ng-deep .mm-prose hr{border:none;height:2px;background:linear-gradient(90deg,var(--mm-prose-hr-start),var(--mm-prose-hr-end),transparent);margin:1.5em 0}:host ::ng-deep .mm-prose img{max-width:100%;height:auto;border-radius:8px;border:1px solid var(--mm-prose-img-border)}:host ::ng-deep .mm-prose strong,:host ::ng-deep .mm-prose b{font-weight:600;color:var(--mm-prose-strong)}:host ::ng-deep .mm-prose em,:host ::ng-deep .mm-prose i{color:var(--mm-prose-em)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MarkdownModule }, { kind: "component", type: i2$1.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
17025
17301
  }
17026
17302
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MarkdownWidgetComponent, decorators: [{
17027
17303
  type: Component,
@@ -17159,7 +17435,7 @@ Variables: $variableName or \${variableName}">
17159
17435
  <button kendoButton themeColor="primary" (click)="onSave()">Save</button>
17160
17436
  </div>
17161
17437
  </div>
17162
- `, isInline: true, styles: [":host{display:block;height:100%;--mm-prose-editor-bg: #f5f5f5;--mm-prose-editor-text: #333333;--mm-prose-editor-placeholder: #999999;--mm-prose-editor-border: #e0e0e0;--mm-prose-preview-border: #e0e0e0;--mm-prose-form-bg: #f5f5f5;--mm-prose-form-border: #e0e0e0;--mm-prose-label-text: #333333;--mm-prose-hint-text: #666666}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:16px;height:100%;flex:1;overflow-y:auto}.mode-toggle{display:flex;gap:8px}.editor-area{flex:1;min-height:300px;display:flex;flex-direction:column}.markdown-editor{flex:1;width:100%;font-family:Roboto Mono,monospace;font-size:.9rem;padding:12px;border:1px solid var(--mm-prose-editor-border);border-radius:4px;resize:none;background:var(--mm-prose-editor-bg);color:var(--mm-prose-editor-text)}.markdown-editor::placeholder{color:var(--mm-prose-editor-placeholder)}.markdown-preview{flex:1;padding:12px;border:1px solid var(--mm-prose-preview-border);border-radius:4px;overflow:auto;background:var(--mm-prose-editor-bg)}.form-section{padding:12px;background:var(--mm-prose-form-bg);border:1px solid var(--mm-prose-form-border);border-radius:4px}.form-row{display:flex;gap:16px;align-items:flex-start}.form-field{display:flex;flex-direction:column;gap:4px}.form-field label{font-weight:500;display:flex;align-items:center;gap:8px;color:var(--mm-prose-label-text)}.field-hint{margin:0;font-size:.8rem;color:var(--mm-prose-hint-text)}.mm-prose{color:var(--mm-prose-text, #333333);line-height:1.6}.mm-prose h1,.mm-prose h2,.mm-prose h3,.mm-prose h4,.mm-prose h5,.mm-prose h6{color:var(--mm-prose-heading, #1976d2);font-weight:600;margin-top:1em;margin-bottom:.5em}.mm-prose h1{font-size:1.5rem}.mm-prose h2{font-size:1.25rem}.mm-prose h3{font-size:1.1rem}.mm-prose p{margin-bottom:.75em}.mm-prose a{color:var(--mm-prose-link, #1565c0)}.mm-prose code{background:var(--mm-prose-code-bg, #f5f5f5);color:var(--mm-prose-code-text, #d32f2f);padding:.2em .4em;border-radius:4px}.mm-prose pre{background:var(--mm-prose-pre-bg, #f5f5f5);color:var(--mm-prose-text, #333333);padding:1em;border-radius:8px;overflow-x:auto}.mm-prose pre code{background:transparent;color:var(--mm-prose-text, #333333)}.mm-prose blockquote{border-left:4px solid var(--mm-prose-blockquote-border, #6c4da8);background:var(--mm-prose-blockquote-bg, rgba(108, 77, 168, .05));padding:.75em 1em;margin:1em 0;border-radius:0 8px 8px 0}.mm-prose strong,.mm-prose b{color:var(--mm-prose-strong, #111111);font-weight:600}.mm-prose em,.mm-prose i{color:var(--mm-prose-em, #1565c0)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: i2.ButtonGroupComponent, selector: "kendo-buttongroup", inputs: ["disabled", "selection", "width", "tabIndex", "navigable"], outputs: ["navigate"], exportAs: ["kendoButtonGroup"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "ngmodule", type: MarkdownModule }, { kind: "component", type: i2$2.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }] });
17438
+ `, isInline: true, styles: [":host{display:block;height:100%;--mm-prose-editor-bg: #f5f5f5;--mm-prose-editor-text: #333333;--mm-prose-editor-placeholder: #999999;--mm-prose-editor-border: #e0e0e0;--mm-prose-preview-border: #e0e0e0;--mm-prose-form-bg: #f5f5f5;--mm-prose-form-border: #e0e0e0;--mm-prose-label-text: #333333;--mm-prose-hint-text: #666666}.config-container{display:flex;flex-direction:column;height:100%}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.config-form{display:flex;flex-direction:column;gap:16px;height:100%;flex:1;overflow-y:auto}.mode-toggle{display:flex;gap:8px}.editor-area{flex:1;min-height:300px;display:flex;flex-direction:column}.markdown-editor{flex:1;width:100%;font-family:Roboto Mono,monospace;font-size:.9rem;padding:12px;border:1px solid var(--mm-prose-editor-border);border-radius:4px;resize:none;background:var(--mm-prose-editor-bg);color:var(--mm-prose-editor-text)}.markdown-editor::placeholder{color:var(--mm-prose-editor-placeholder)}.markdown-preview{flex:1;padding:12px;border:1px solid var(--mm-prose-preview-border);border-radius:4px;overflow:auto;background:var(--mm-prose-editor-bg)}.form-section{padding:12px;background:var(--mm-prose-form-bg);border:1px solid var(--mm-prose-form-border);border-radius:4px}.form-row{display:flex;gap:16px;align-items:flex-start}.form-field{display:flex;flex-direction:column;gap:4px}.form-field label{font-weight:500;display:flex;align-items:center;gap:8px;color:var(--mm-prose-label-text)}.field-hint{margin:0;font-size:.8rem;color:var(--mm-prose-hint-text)}.mm-prose{color:var(--mm-prose-text, #333333);line-height:1.6}.mm-prose h1,.mm-prose h2,.mm-prose h3,.mm-prose h4,.mm-prose h5,.mm-prose h6{color:var(--mm-prose-heading, #1976d2);font-weight:600;margin-top:1em;margin-bottom:.5em}.mm-prose h1{font-size:1.5rem}.mm-prose h2{font-size:1.25rem}.mm-prose h3{font-size:1.1rem}.mm-prose p{margin-bottom:.75em}.mm-prose a{color:var(--mm-prose-link, #1565c0)}.mm-prose code{background:var(--mm-prose-code-bg, #f5f5f5);color:var(--mm-prose-code-text, #d32f2f);padding:.2em .4em;border-radius:4px}.mm-prose pre{background:var(--mm-prose-pre-bg, #f5f5f5);color:var(--mm-prose-text, #333333);padding:1em;border-radius:8px;overflow-x:auto}.mm-prose pre code{background:transparent;color:var(--mm-prose-text, #333333)}.mm-prose blockquote{border-left:4px solid var(--mm-prose-blockquote-border, #6c4da8);background:var(--mm-prose-blockquote-bg, rgba(108, 77, 168, .05));padding:.75em 1em;margin:1em 0;border-radius:0 8px 8px 0}.mm-prose strong,.mm-prose b{color:var(--mm-prose-strong, #111111);font-weight:600}.mm-prose em,.mm-prose i{color:var(--mm-prose-em, #1565c0)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: i2.ButtonGroupComponent, selector: "kendo-buttongroup", inputs: ["disabled", "selection", "width", "tabIndex", "navigable"], outputs: ["navigate"], exportAs: ["kendoButtonGroup"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "ngmodule", type: MarkdownModule }, { kind: "component", type: i2$1.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }] });
17163
17439
  }
17164
17440
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MarkdownConfigDialogComponent, decorators: [{
17165
17441
  type: Component,
@@ -17279,149 +17555,1620 @@ Variables: $variableName or \${variableName}">
17279
17555
  type: Input
17280
17556
  }] } });
17281
17557
 
17282
- class ProcessDataService {
17283
- dataService = inject(MeshBoardDataService);
17284
- variableService = inject(MeshBoardVariableService);
17285
- stateService = inject(MeshBoardStateService);
17286
- getProcessDiagramGQL = inject(GetProcessDiagramDtoGQL);
17287
- getProcessDiagramsGQL = inject(GetProcessDiagramsDtoGQL);
17288
- createProcessDiagramGQL = inject(CreateProcessDiagramDtoGQL);
17289
- updateProcessDiagramGQL = inject(UpdateProcessDiagramDtoGQL);
17290
- executeRuntimeQueryGQL = inject(ExecuteRuntimeQueryDtoGQL);
17291
- /**
17292
- * Loads a list of available process diagrams from the backend
17293
- *
17294
- * @param searchText - Optional search text to filter diagrams
17295
- * @returns List of process diagram summaries
17296
- */
17297
- async loadDiagramList(searchText) {
17298
- try {
17299
- const result = await firstValueFrom(this.getProcessDiagramsGQL.fetch({
17300
- first: 100,
17301
- searchFilter: searchText ? { searchTerm: searchText, language: 'de' } : undefined
17302
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Apollo fetch requires flexible variable typing
17303
- }));
17304
- const items = result.data?.runtime?.systemUIProcessDiagram?.items || [];
17305
- return items
17306
- .filter((item) => item !== null)
17307
- .map(item => ({
17308
- rtId: item.rtId,
17309
- name: item.name,
17310
- description: item.description,
17311
- version: item.version,
17312
- canvasWidth: item.canvasWidth,
17313
- canvasHeight: item.canvasHeight
17314
- }));
17315
- }
17316
- catch (error) {
17317
- console.error('Error loading process diagrams:', error);
17318
- return [];
17319
- }
17558
+ class StatusListWidgetComponent {
17559
+ getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
17560
+ config;
17561
+ _isLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isLoading" }] : /* istanbul ignore next */ []));
17562
+ _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : /* istanbul ignore next */ []));
17563
+ _items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : /* istanbul ignore next */ []));
17564
+ isLoading = this._isLoading.asReadonly();
17565
+ error = this._error.asReadonly();
17566
+ data = this._items.asReadonly();
17567
+ items = this._items.asReadonly();
17568
+ isNotConfigured() {
17569
+ return !this.config?.ckTypeId || !this.config?.labelField || !this.config?.statusField;
17320
17570
  }
17321
- /**
17322
- * Loads a process diagram configuration from the backend
17323
- *
17324
- * @param rtId - The runtime ID of the process diagram
17325
- * @returns The process diagram configuration
17326
- */
17327
- async loadDiagram(rtId) {
17328
- const result = await firstValueFrom(this.getProcessDiagramGQL.fetch({ variables: { rtId } }));
17329
- const item = result.data?.runtime?.systemUIProcessDiagram?.items?.[0];
17330
- if (!item) {
17331
- throw new Error(`Process diagram not found: ${rtId}`);
17332
- }
17333
- // Parse JSON fields
17334
- const elements = this.parseJsonField(item.elements, []);
17335
- const itemWithExtensions = item;
17336
- const primitives = itemWithExtensions.primitives
17337
- ? this.parseJsonField(itemWithExtensions.primitives, [])
17338
- : undefined;
17339
- const symbolInstances = itemWithExtensions.symbolInstances
17340
- ? this.parseJsonField(itemWithExtensions.symbolInstances, [])
17341
- : undefined;
17342
- const connections = this.parseJsonField(item.connections, []);
17343
- const variables = item.variables ? this.parseJsonField(item.variables, []) : undefined;
17344
- // Parse diagram-level property fields
17345
- const transformProperties = itemWithExtensions.transformProperties
17346
- ? this.parseJsonField(itemWithExtensions.transformProperties, [])
17347
- : undefined;
17348
- const propertyBindings = itemWithExtensions.propertyBindings
17349
- ? this.parseJsonField(itemWithExtensions.propertyBindings, [])
17350
- : undefined;
17351
- const animations = itemWithExtensions.animations
17352
- ? this.parseJsonField(itemWithExtensions.animations, [])
17353
- : undefined;
17354
- return {
17355
- id: item.rtId,
17356
- name: item.name,
17357
- description: item.description ?? undefined,
17358
- version: item.version,
17359
- canvas: {
17360
- width: item.canvasWidth,
17361
- height: item.canvasHeight,
17362
- backgroundColor: item.canvasBackgroundColor ?? undefined
17363
- },
17364
- elements,
17365
- primitives,
17366
- symbolInstances,
17367
- connections,
17368
- variables,
17369
- transformProperties,
17370
- propertyBindings,
17371
- animations,
17372
- refreshInterval: item.refreshInterval ?? undefined
17373
- };
17571
+ ngOnInit() {
17572
+ this.loadData();
17374
17573
  }
17375
- /**
17376
- * Creates a new process diagram in the backend
17377
- *
17378
- * @param diagram - The process diagram configuration to create
17379
- * @returns The created diagram with its new rtId
17380
- */
17381
- async createDiagram(diagram) {
17382
- const input = this.toInputDto(diagram);
17383
- const result = await firstValueFrom(this.createProcessDiagramGQL.mutate({
17384
- variables: { entities: [input] }
17385
- }));
17386
- const created = result.data?.runtime?.systemUIProcessDiagrams?.create?.[0];
17387
- if (!created) {
17388
- throw new Error('Failed to create process diagram');
17574
+ ngOnChanges(changes) {
17575
+ if (changes['config'] && !changes['config'].firstChange) {
17576
+ this.loadData();
17389
17577
  }
17390
- return {
17391
- ...diagram,
17392
- id: created.rtId
17393
- };
17394
17578
  }
17395
- /**
17396
- * Updates an existing process diagram in the backend
17397
- *
17398
- * @param diagram - The process diagram configuration to update
17399
- * @returns The updated diagram
17400
- */
17401
- async updateDiagram(diagram) {
17402
- if (!diagram.id) {
17403
- throw new Error('Cannot update diagram without rtId');
17579
+ refresh() {
17580
+ this.loadData();
17581
+ }
17582
+ async loadData() {
17583
+ if (this.isNotConfigured())
17584
+ return;
17585
+ this._isLoading.set(true);
17586
+ this._error.set(null);
17587
+ try {
17588
+ const result = await firstValueFrom(this.getEntitiesByCkTypeGQL.fetch({
17589
+ variables: {
17590
+ ckTypeId: this.config.ckTypeId,
17591
+ first: 50
17592
+ }
17593
+ }));
17594
+ const entities = result.data?.runtime?.runtimeEntities?.items ?? [];
17595
+ const items = [];
17596
+ for (const entity of entities) {
17597
+ if (!entity)
17598
+ continue;
17599
+ const attrs = (entity.attributes?.items ?? [])
17600
+ .filter((a) => a != null && a.attributeName != null);
17601
+ const label = this.getAttributeValue(attrs, this.config.labelField) ?? '';
17602
+ const status = this.getAttributeValue(attrs, this.config.statusField) ?? '';
17603
+ const colorConfig = this.config.statusColors?.[status];
17604
+ items.push({
17605
+ label,
17606
+ status,
17607
+ color: colorConfig?.color ?? '#6b7280',
17608
+ displayLabel: colorConfig?.label ?? status
17609
+ });
17610
+ }
17611
+ this._items.set(items);
17404
17612
  }
17405
- const input = {
17406
- rtId: diagram.id,
17407
- item: this.toInputDto(diagram)
17408
- };
17409
- const result = await firstValueFrom(this.updateProcessDiagramGQL.mutate({
17410
- variables: { entities: [input] }
17411
- }));
17412
- const updated = result.data?.runtime?.systemUIProcessDiagrams?.update?.[0];
17413
- if (!updated) {
17414
- throw new Error('Failed to update process diagram');
17613
+ catch (err) {
17614
+ this._error.set(err instanceof Error ? err.message : 'Failed to load data');
17615
+ }
17616
+ finally {
17617
+ this._isLoading.set(false);
17415
17618
  }
17416
- return diagram;
17417
17619
  }
17418
- /**
17419
- * Saves a process diagram (creates new or updates existing)
17420
- *
17421
- * @param diagram - The process diagram configuration to save
17422
- * @param isNew - Whether this is a new diagram (default: false)
17423
- * @returns The saved diagram
17424
- */
17620
+ getAttributeValue(attrs, field) {
17621
+ const attr = attrs.find(a => a.attributeName === field);
17622
+ return attr?.value != null ? String(attr.value) : null;
17623
+ }
17624
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: StatusListWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
17625
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: StatusListWidgetComponent, isStandalone: true, selector: "mm-status-list-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: `
17626
+ <div class="status-list-widget">
17627
+ @if (isNotConfigured()) {
17628
+ <mm-widget-not-configured></mm-widget-not-configured>
17629
+ } @else if (error()) {
17630
+ <div class="error-message">{{ error() }}</div>
17631
+ } @else {
17632
+ <div class="status-list">
17633
+ @for (item of items(); track item.label) {
17634
+ <div class="status-list-item">
17635
+ <span class="item-label">{{ item.label }}</span>
17636
+ <span class="item-badge" [style.background-color]="item.color">
17637
+ {{ item.displayLabel }}
17638
+ </span>
17639
+ </div>
17640
+ }
17641
+ @if (!isLoading() && items().length === 0) {
17642
+ <div class="empty-message">No items found</div>
17643
+ }
17644
+ </div>
17645
+ }
17646
+ </div>
17647
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.status-list-widget{height:100%;display:flex;flex-direction:column;padding:8px 0;overflow-y:auto}.status-list{display:flex;flex-direction:column;gap:6px;padding:0 12px}.status-list-item{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-radius:6px;background:var(--mm-status-list-item-bg, rgba(255, 255, 255, .04));border:1px solid var(--mm-status-list-item-border, rgba(255, 255, 255, .06))}.item-label{font-size:.9rem;font-weight:500;color:var(--kendo-color-on-app-surface, inherit)}.item-badge{font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:4px 10px;border-radius:4px;color:#fff;white-space:nowrap}.empty-message{text-align:center;padding:16px;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.error-message{text-align:center;padding:16px;color:var(--kendo-color-error, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
17648
+ }
17649
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: StatusListWidgetComponent, decorators: [{
17650
+ type: Component,
17651
+ args: [{ selector: 'mm-status-list-widget', standalone: true, imports: [CommonModule, WidgetNotConfiguredComponent], template: `
17652
+ <div class="status-list-widget">
17653
+ @if (isNotConfigured()) {
17654
+ <mm-widget-not-configured></mm-widget-not-configured>
17655
+ } @else if (error()) {
17656
+ <div class="error-message">{{ error() }}</div>
17657
+ } @else {
17658
+ <div class="status-list">
17659
+ @for (item of items(); track item.label) {
17660
+ <div class="status-list-item">
17661
+ <span class="item-label">{{ item.label }}</span>
17662
+ <span class="item-badge" [style.background-color]="item.color">
17663
+ {{ item.displayLabel }}
17664
+ </span>
17665
+ </div>
17666
+ }
17667
+ @if (!isLoading() && items().length === 0) {
17668
+ <div class="empty-message">No items found</div>
17669
+ }
17670
+ </div>
17671
+ }
17672
+ </div>
17673
+ `, styles: [":host{display:block;width:100%;height:100%}.status-list-widget{height:100%;display:flex;flex-direction:column;padding:8px 0;overflow-y:auto}.status-list{display:flex;flex-direction:column;gap:6px;padding:0 12px}.status-list-item{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-radius:6px;background:var(--mm-status-list-item-bg, rgba(255, 255, 255, .04));border:1px solid var(--mm-status-list-item-border, rgba(255, 255, 255, .06))}.item-label{font-size:.9rem;font-weight:500;color:var(--kendo-color-on-app-surface, inherit)}.item-badge{font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:4px 10px;border-radius:4px;color:#fff;white-space:nowrap}.empty-message{text-align:center;padding:16px;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.error-message{text-align:center;padding:16px;color:var(--kendo-color-error, #dc3545)}\n"] }]
17674
+ }], propDecorators: { config: [{
17675
+ type: Input
17676
+ }] } });
17677
+
17678
+ class StatusListConfigDialogComponent {
17679
+ windowRef = inject(WindowRef);
17680
+ initialCkTypeId;
17681
+ initialLabelField;
17682
+ initialStatusField;
17683
+ initialStatusColors;
17684
+ form = {
17685
+ ckTypeId: '',
17686
+ labelField: '',
17687
+ statusField: ''
17688
+ };
17689
+ statusColorEntries = [];
17690
+ get isValid() {
17691
+ return !!this.form.ckTypeId && !!this.form.labelField && !!this.form.statusField;
17692
+ }
17693
+ ngOnInit() {
17694
+ this.form.ckTypeId = this.initialCkTypeId ?? '';
17695
+ this.form.labelField = this.initialLabelField ?? '';
17696
+ this.form.statusField = this.initialStatusField ?? '';
17697
+ if (this.initialStatusColors) {
17698
+ this.statusColorEntries = Object.entries(this.initialStatusColors).map(([key, val]) => ({
17699
+ key,
17700
+ color: val.color,
17701
+ label: val.label ?? ''
17702
+ }));
17703
+ }
17704
+ }
17705
+ addStatusColor() {
17706
+ this.statusColorEntries.push({ key: '', color: '#10b981', label: '' });
17707
+ }
17708
+ removeStatusColor(index) {
17709
+ this.statusColorEntries.splice(index, 1);
17710
+ }
17711
+ onSave() {
17712
+ const statusColors = {};
17713
+ for (const entry of this.statusColorEntries) {
17714
+ if (entry.key) {
17715
+ statusColors[entry.key] = { color: entry.color, label: entry.label || undefined };
17716
+ }
17717
+ }
17718
+ this.windowRef.close({
17719
+ ckTypeId: this.form.ckTypeId,
17720
+ labelField: this.form.labelField,
17721
+ statusField: this.form.statusField,
17722
+ statusColors: Object.keys(statusColors).length > 0 ? statusColors : undefined
17723
+ });
17724
+ }
17725
+ onCancel() {
17726
+ this.windowRef.close();
17727
+ }
17728
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: StatusListConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
17729
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: StatusListConfigDialogComponent, isStandalone: true, selector: "mm-status-list-config-dialog", inputs: { initialCkTypeId: "initialCkTypeId", initialLabelField: "initialLabelField", initialStatusField: "initialStatusField", initialStatusColors: "initialStatusColors" }, ngImport: i0, template: `
17730
+ <div class="config-container">
17731
+ <div class="config-form">
17732
+ <div class="form-field">
17733
+ <label>Entity Type <span class="required">*</span></label>
17734
+ <kendo-textbox [(ngModel)]="form.ckTypeId" placeholder="e.g. Environment/ComplianceRecord"></kendo-textbox>
17735
+ <p class="field-hint">Full CK type ID</p>
17736
+ </div>
17737
+
17738
+ <div class="form-field">
17739
+ <label>Label Field <span class="required">*</span></label>
17740
+ <kendo-textbox [(ngModel)]="form.labelField" placeholder="e.g. name"></kendo-textbox>
17741
+ <p class="field-hint">Attribute name for the item label</p>
17742
+ </div>
17743
+
17744
+ <div class="form-field">
17745
+ <label>Status Field <span class="required">*</span></label>
17746
+ <kendo-textbox [(ngModel)]="form.statusField" placeholder="e.g. complianceStatus"></kendo-textbox>
17747
+ <p class="field-hint">Attribute name for the status value (enum)</p>
17748
+ </div>
17749
+
17750
+ <div class="form-section">
17751
+ <h4>Status Colors</h4>
17752
+ <p class="field-hint">Map status values to badge colors and labels.</p>
17753
+ @for (entry of statusColorEntries; track $index) {
17754
+ <div class="color-row">
17755
+ <kendo-textbox [(ngModel)]="entry.key" placeholder="Status value" style="width: 120px;"></kendo-textbox>
17756
+ <kendo-textbox [(ngModel)]="entry.color" placeholder="#10b981" style="width: 100px;"></kendo-textbox>
17757
+ <kendo-textbox [(ngModel)]="entry.label" placeholder="Badge label" style="flex: 1;"></kendo-textbox>
17758
+ <button kendoButton fillMode="flat" (click)="removeStatusColor($index)">Remove</button>
17759
+ </div>
17760
+ }
17761
+ <button kendoButton fillMode="flat" (click)="addStatusColor()">+ Add Status Color</button>
17762
+ </div>
17763
+ </div>
17764
+
17765
+ <div class="action-bar mm-dialog-actions">
17766
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
17767
+ <button kendoButton themeColor="primary" [disabled]="!isValid" (click)="onSave()">Save</button>
17768
+ </div>
17769
+ </div>
17770
+ `, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.config-form{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.form-field{display:flex;flex-direction:column;gap:6px}.form-field label{font-weight:600;font-size:.9rem}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{margin-top:8px}.form-section h4{margin:0 0 4px;font-size:.95rem;font-weight:600}.color-row{display:flex;gap:8px;align-items:center;margin-bottom:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }] });
17771
+ }
17772
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: StatusListConfigDialogComponent, decorators: [{
17773
+ type: Component,
17774
+ args: [{ selector: 'mm-status-list-config-dialog', standalone: true, imports: [CommonModule, FormsModule, ButtonsModule, InputsModule], template: `
17775
+ <div class="config-container">
17776
+ <div class="config-form">
17777
+ <div class="form-field">
17778
+ <label>Entity Type <span class="required">*</span></label>
17779
+ <kendo-textbox [(ngModel)]="form.ckTypeId" placeholder="e.g. Environment/ComplianceRecord"></kendo-textbox>
17780
+ <p class="field-hint">Full CK type ID</p>
17781
+ </div>
17782
+
17783
+ <div class="form-field">
17784
+ <label>Label Field <span class="required">*</span></label>
17785
+ <kendo-textbox [(ngModel)]="form.labelField" placeholder="e.g. name"></kendo-textbox>
17786
+ <p class="field-hint">Attribute name for the item label</p>
17787
+ </div>
17788
+
17789
+ <div class="form-field">
17790
+ <label>Status Field <span class="required">*</span></label>
17791
+ <kendo-textbox [(ngModel)]="form.statusField" placeholder="e.g. complianceStatus"></kendo-textbox>
17792
+ <p class="field-hint">Attribute name for the status value (enum)</p>
17793
+ </div>
17794
+
17795
+ <div class="form-section">
17796
+ <h4>Status Colors</h4>
17797
+ <p class="field-hint">Map status values to badge colors and labels.</p>
17798
+ @for (entry of statusColorEntries; track $index) {
17799
+ <div class="color-row">
17800
+ <kendo-textbox [(ngModel)]="entry.key" placeholder="Status value" style="width: 120px;"></kendo-textbox>
17801
+ <kendo-textbox [(ngModel)]="entry.color" placeholder="#10b981" style="width: 100px;"></kendo-textbox>
17802
+ <kendo-textbox [(ngModel)]="entry.label" placeholder="Badge label" style="flex: 1;"></kendo-textbox>
17803
+ <button kendoButton fillMode="flat" (click)="removeStatusColor($index)">Remove</button>
17804
+ </div>
17805
+ }
17806
+ <button kendoButton fillMode="flat" (click)="addStatusColor()">+ Add Status Color</button>
17807
+ </div>
17808
+ </div>
17809
+
17810
+ <div class="action-bar mm-dialog-actions">
17811
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
17812
+ <button kendoButton themeColor="primary" [disabled]="!isValid" (click)="onSave()">Save</button>
17813
+ </div>
17814
+ </div>
17815
+ `, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.config-form{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.form-field{display:flex;flex-direction:column;gap:6px}.form-field label{font-weight:600;font-size:.9rem}.required{color:var(--kendo-color-error, #dc3545)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{margin-top:8px}.form-section h4{margin:0 0 4px;font-size:.95rem;font-weight:600}.color-row{display:flex;gap:8px;align-items:center;margin-bottom:8px}\n"] }]
17816
+ }], propDecorators: { initialCkTypeId: [{
17817
+ type: Input
17818
+ }], initialLabelField: [{
17819
+ type: Input
17820
+ }], initialStatusField: [{
17821
+ type: Input
17822
+ }], initialStatusColors: [{
17823
+ type: Input
17824
+ }] } });
17825
+
17826
+ class SummaryCardWidgetComponent {
17827
+ entityGQL = inject(GetDashboardEntityDtoGQL);
17828
+ dataService = inject(DashboardDataService);
17829
+ config;
17830
+ _isLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isLoading" }] : /* istanbul ignore next */ []));
17831
+ _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : /* istanbul ignore next */ []));
17832
+ _tileValues = signal([], ...(ngDevMode ? [{ debugName: "_tileValues" }] : /* istanbul ignore next */ []));
17833
+ isLoading = this._isLoading.asReadonly();
17834
+ error = this._error.asReadonly();
17835
+ data = this._tileValues.asReadonly();
17836
+ tileValues = this._tileValues.asReadonly();
17837
+ isNotConfigured() {
17838
+ return !this.config?.tiles?.length;
17839
+ }
17840
+ ngOnInit() {
17841
+ this.loadData();
17842
+ }
17843
+ ngOnChanges(changes) {
17844
+ if (changes['config'] && !changes['config'].firstChange) {
17845
+ this.loadData();
17846
+ }
17847
+ }
17848
+ refresh() {
17849
+ this.loadData();
17850
+ }
17851
+ async loadData() {
17852
+ if (this.isNotConfigured())
17853
+ return;
17854
+ this._isLoading.set(true);
17855
+ this._error.set(null);
17856
+ try {
17857
+ // Cache entities by rtId to avoid duplicate fetches
17858
+ const entityCache = new Map();
17859
+ const results = await Promise.all(this.config.tiles.map(tile => this.fetchTileValue(tile, entityCache)));
17860
+ const tileValues = this.config.tiles.map((tile, i) => ({
17861
+ id: tile.id,
17862
+ label: tile.label,
17863
+ value: this.formatValue(results[i]),
17864
+ prefix: tile.prefix ?? '',
17865
+ suffix: tile.suffix ?? '',
17866
+ color: tile.color ?? 'default',
17867
+ fullWidth: tile.size === 'full'
17868
+ }));
17869
+ this._tileValues.set(tileValues);
17870
+ }
17871
+ catch (err) {
17872
+ this._error.set(err instanceof Error ? err.message : 'Failed to load data');
17873
+ }
17874
+ finally {
17875
+ this._isLoading.set(false);
17876
+ }
17877
+ }
17878
+ async fetchTileValue(tile, entityCache) {
17879
+ if (tile.entitySource) {
17880
+ const { rtId, ckTypeId, attributePath } = tile.entitySource;
17881
+ if (!entityCache.has(rtId)) {
17882
+ const attrs = await this.fetchEntityAttributes(rtId, ckTypeId);
17883
+ entityCache.set(rtId, attrs);
17884
+ }
17885
+ return entityCache.get(rtId)?.get(attributePath) ?? null;
17886
+ }
17887
+ if (tile.aggregationSource) {
17888
+ const query = {
17889
+ id: tile.id,
17890
+ ckTypeId: tile.aggregationSource.ckTypeId,
17891
+ aggregation: tile.aggregationSource.aggregation,
17892
+ attribute: tile.aggregationSource.attribute,
17893
+ filters: tile.aggregationSource.filters
17894
+ };
17895
+ const results = await this.dataService.fetchAggregations([query]);
17896
+ return results.get(tile.id) ?? null;
17897
+ }
17898
+ return null;
17899
+ }
17900
+ async fetchEntityAttributes(rtId, ckTypeId) {
17901
+ const result = await firstValueFrom(this.entityGQL.fetch({
17902
+ variables: { rtId, ckTypeId }
17903
+ }));
17904
+ const attrs = new Map();
17905
+ const items = result.data?.runtime?.runtimeEntities?.items?.[0]?.attributes?.items ?? [];
17906
+ for (const item of items) {
17907
+ if (item?.attributeName) {
17908
+ attrs.set(item.attributeName, item.value);
17909
+ }
17910
+ }
17911
+ return attrs;
17912
+ }
17913
+ formatValue(value) {
17914
+ if (value == null)
17915
+ return '-';
17916
+ if (typeof value === 'number') {
17917
+ return value.toLocaleString('de-AT', {
17918
+ minimumFractionDigits: value % 1 !== 0 ? 1 : 0,
17919
+ maximumFractionDigits: 2
17920
+ });
17921
+ }
17922
+ return String(value);
17923
+ }
17924
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SummaryCardWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
17925
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SummaryCardWidgetComponent, isStandalone: true, selector: "mm-summary-card-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: `
17926
+ <div class="summary-card-widget">
17927
+ @if (isNotConfigured()) {
17928
+ <mm-widget-not-configured></mm-widget-not-configured>
17929
+ } @else if (error()) {
17930
+ <div class="error-message">{{ error() }}</div>
17931
+ } @else {
17932
+ <div class="summary-grid" [style.--columns]="config.columns ?? 2">
17933
+ @for (tile of tileValues(); track tile.id) {
17934
+ <div class="tile" [class]="tile.color" [class.full-width]="tile.fullWidth">
17935
+ <div class="tile-value">
17936
+ {{ tile.prefix }}{{ tile.value }}{{ tile.suffix }}
17937
+ </div>
17938
+ <div class="tile-label">{{ tile.label }}</div>
17939
+ </div>
17940
+ }
17941
+ </div>
17942
+ }
17943
+ </div>
17944
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.summary-card-widget{height:100%;display:flex;align-items:center;justify-content:center}.summary-grid{display:grid;grid-template-columns:repeat(var(--columns, 2),1fr);gap:8px;padding:8px 12px;width:100%}.tile{text-align:center;padding:12px 8px;border-radius:6px;background:var(--mm-summary-tile-bg, rgba(255, 255, 255, .03))}.tile-value{font-size:1.5rem;font-weight:700;color:inherit}.tile-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.5px;margin-top:4px;opacity:.6}.tile.full-width{grid-column:1 / -1}.tile.primary .tile-value{color:var(--kendo-color-primary, #06b6d4)}.tile.success .tile-value{color:var(--kendo-color-success, #10b981)}.tile.warning .tile-value{color:var(--kendo-color-warning, #f59e0b)}.tile.error .tile-value{color:var(--kendo-color-error, #ef4444)}.error-message{text-align:center;padding:16px;color:var(--kendo-color-error, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
17945
+ }
17946
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SummaryCardWidgetComponent, decorators: [{
17947
+ type: Component,
17948
+ args: [{ selector: 'mm-summary-card-widget', standalone: true, imports: [CommonModule, WidgetNotConfiguredComponent], template: `
17949
+ <div class="summary-card-widget">
17950
+ @if (isNotConfigured()) {
17951
+ <mm-widget-not-configured></mm-widget-not-configured>
17952
+ } @else if (error()) {
17953
+ <div class="error-message">{{ error() }}</div>
17954
+ } @else {
17955
+ <div class="summary-grid" [style.--columns]="config.columns ?? 2">
17956
+ @for (tile of tileValues(); track tile.id) {
17957
+ <div class="tile" [class]="tile.color" [class.full-width]="tile.fullWidth">
17958
+ <div class="tile-value">
17959
+ {{ tile.prefix }}{{ tile.value }}{{ tile.suffix }}
17960
+ </div>
17961
+ <div class="tile-label">{{ tile.label }}</div>
17962
+ </div>
17963
+ }
17964
+ </div>
17965
+ }
17966
+ </div>
17967
+ `, styles: [":host{display:block;width:100%;height:100%}.summary-card-widget{height:100%;display:flex;align-items:center;justify-content:center}.summary-grid{display:grid;grid-template-columns:repeat(var(--columns, 2),1fr);gap:8px;padding:8px 12px;width:100%}.tile{text-align:center;padding:12px 8px;border-radius:6px;background:var(--mm-summary-tile-bg, rgba(255, 255, 255, .03))}.tile-value{font-size:1.5rem;font-weight:700;color:inherit}.tile-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.5px;margin-top:4px;opacity:.6}.tile.full-width{grid-column:1 / -1}.tile.primary .tile-value{color:var(--kendo-color-primary, #06b6d4)}.tile.success .tile-value{color:var(--kendo-color-success, #10b981)}.tile.warning .tile-value{color:var(--kendo-color-warning, #f59e0b)}.tile.error .tile-value{color:var(--kendo-color-error, #ef4444)}.error-message{text-align:center;padding:16px;color:var(--kendo-color-error, #dc3545)}\n"] }]
17968
+ }], propDecorators: { config: [{
17969
+ type: Input
17970
+ }] } });
17971
+
17972
+ class SummaryCardConfigDialogComponent {
17973
+ windowRef = inject(WindowRef);
17974
+ initialColumns;
17975
+ initialTiles;
17976
+ form = {
17977
+ columns: 2,
17978
+ tiles: []
17979
+ };
17980
+ colorOptions = ['default', 'primary', 'success', 'warning', 'error'];
17981
+ sizeOptions = ['normal', 'full'];
17982
+ sourceTypeOptions = ['entity', 'aggregation'];
17983
+ aggregationOptions = ['count', 'sum', 'avg', 'min', 'max'];
17984
+ get isValid() {
17985
+ return this.form.tiles.length > 0 && this.form.tiles.every(t => t.label && t.ckTypeId);
17986
+ }
17987
+ ngOnInit() {
17988
+ this.form.columns = this.initialColumns ?? 2;
17989
+ if (this.initialTiles) {
17990
+ this.form.tiles = this.initialTiles.map((t, i) => ({
17991
+ id: t.id || `tile-${i}`,
17992
+ label: t.label,
17993
+ prefix: t.prefix ?? '',
17994
+ suffix: t.suffix ?? '',
17995
+ color: t.color ?? 'default',
17996
+ size: t.size ?? 'normal',
17997
+ sourceType: t.entitySource ? 'entity' : 'aggregation',
17998
+ rtId: t.entitySource?.rtId ?? '',
17999
+ ckTypeId: t.entitySource?.ckTypeId ?? t.aggregationSource?.ckTypeId ?? '',
18000
+ attributePath: t.entitySource?.attributePath ?? '',
18001
+ aggregation: t.aggregationSource?.aggregation ?? 'count',
18002
+ aggAttribute: t.aggregationSource?.attribute ?? ''
18003
+ }));
18004
+ }
18005
+ }
18006
+ addTile() {
18007
+ this.form.tiles.push({
18008
+ id: `tile-${Date.now()}`,
18009
+ label: '', prefix: '', suffix: '',
18010
+ color: 'default', size: 'normal',
18011
+ sourceType: 'entity',
18012
+ rtId: '', ckTypeId: '', attributePath: '',
18013
+ aggregation: 'count', aggAttribute: ''
18014
+ });
18015
+ }
18016
+ removeTile(index) {
18017
+ this.form.tiles.splice(index, 1);
18018
+ }
18019
+ onSave() {
18020
+ const tiles = this.form.tiles.map(t => {
18021
+ const tile = {
18022
+ id: t.id,
18023
+ label: t.label,
18024
+ prefix: t.prefix || undefined,
18025
+ suffix: t.suffix || undefined,
18026
+ color: t.color || undefined,
18027
+ size: t.size || undefined
18028
+ };
18029
+ if (t.sourceType === 'entity') {
18030
+ tile.entitySource = { rtId: t.rtId, ckTypeId: t.ckTypeId, attributePath: t.attributePath };
18031
+ }
18032
+ else {
18033
+ tile.aggregationSource = {
18034
+ ckTypeId: t.ckTypeId,
18035
+ aggregation: t.aggregation,
18036
+ attribute: t.aggAttribute || undefined
18037
+ };
18038
+ }
18039
+ return tile;
18040
+ });
18041
+ this.windowRef.close({
18042
+ ckTypeId: '',
18043
+ columns: this.form.columns,
18044
+ tiles
18045
+ });
18046
+ }
18047
+ onCancel() {
18048
+ this.windowRef.close();
18049
+ }
18050
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SummaryCardConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18051
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SummaryCardConfigDialogComponent, isStandalone: true, selector: "mm-summary-card-config-dialog", inputs: { initialColumns: "initialColumns", initialTiles: "initialTiles" }, ngImport: i0, template: `
18052
+ <div class="config-container">
18053
+ <div class="config-form">
18054
+ <div class="form-field">
18055
+ <label>Grid Columns</label>
18056
+ <kendo-numerictextbox [(ngModel)]="form.columns" [min]="1" [max]="6" [format]="'n0'" [spinners]="true"></kendo-numerictextbox>
18057
+ </div>
18058
+
18059
+ <h4>Tiles</h4>
18060
+ @for (tile of form.tiles; track $index) {
18061
+ <div class="tile-config">
18062
+ <div class="tile-header">
18063
+ <strong>Tile {{ $index + 1 }}</strong>
18064
+ <button kendoButton fillMode="flat" (click)="removeTile($index)">Remove</button>
18065
+ </div>
18066
+ <div class="tile-row">
18067
+ <kendo-textbox [(ngModel)]="tile.label" placeholder="Label" style="flex: 1;"></kendo-textbox>
18068
+ <kendo-textbox [(ngModel)]="tile.prefix" placeholder="Prefix" style="width: 60px;"></kendo-textbox>
18069
+ <kendo-textbox [(ngModel)]="tile.suffix" placeholder="Suffix" style="width: 80px;"></kendo-textbox>
18070
+ </div>
18071
+ <div class="tile-row">
18072
+ <kendo-dropdownlist [data]="colorOptions" [valuePrimitive]="true" [(ngModel)]="tile.color" style="width: 110px;"></kendo-dropdownlist>
18073
+ <kendo-dropdownlist [data]="sizeOptions" [valuePrimitive]="true" [(ngModel)]="tile.size" style="width: 110px;"></kendo-dropdownlist>
18074
+ <kendo-dropdownlist [data]="sourceTypeOptions" [valuePrimitive]="true" [(ngModel)]="tile.sourceType" style="width: 120px;"></kendo-dropdownlist>
18075
+ </div>
18076
+ @if (tile.sourceType === 'entity') {
18077
+ <div class="tile-row">
18078
+ <kendo-textbox [(ngModel)]="tile.ckTypeId" placeholder="CK Type ID" style="flex: 1;"></kendo-textbox>
18079
+ <kendo-textbox [(ngModel)]="tile.rtId" placeholder="Runtime ID" style="flex: 1;"></kendo-textbox>
18080
+ <kendo-textbox [(ngModel)]="tile.attributePath" placeholder="Attribute" style="width: 120px;"></kendo-textbox>
18081
+ </div>
18082
+ }
18083
+ @if (tile.sourceType === 'aggregation') {
18084
+ <div class="tile-row">
18085
+ <kendo-textbox [(ngModel)]="tile.ckTypeId" placeholder="CK Type ID" style="flex: 1;"></kendo-textbox>
18086
+ <kendo-dropdownlist [data]="aggregationOptions" [valuePrimitive]="true" [(ngModel)]="tile.aggregation" style="width: 100px;"></kendo-dropdownlist>
18087
+ <kendo-textbox [(ngModel)]="tile.aggAttribute" placeholder="Attribute (for sum/avg)" style="flex: 1;"></kendo-textbox>
18088
+ </div>
18089
+ }
18090
+ </div>
18091
+ }
18092
+ <button kendoButton fillMode="flat" (click)="addTile()">+ Add Tile</button>
18093
+ </div>
18094
+
18095
+ <div class="action-bar mm-dialog-actions">
18096
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
18097
+ <button kendoButton themeColor="primary" [disabled]="!isValid" (click)="onSave()">Save</button>
18098
+ </div>
18099
+ </div>
18100
+ `, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.config-form{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.form-field{display:flex;flex-direction:column;gap:6px}.form-field label{font-weight:600;font-size:.9rem}h4{margin:8px 0 4px;font-size:.95rem}.tile-config{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;padding:10px;margin-bottom:8px;display:flex;flex-direction:column;gap:6px}.tile-header{display:flex;justify-content:space-between;align-items:center}.tile-row{display:flex;gap:6px;align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }] });
18101
+ }
18102
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SummaryCardConfigDialogComponent, decorators: [{
18103
+ type: Component,
18104
+ args: [{ selector: 'mm-summary-card-config-dialog', standalone: true, imports: [CommonModule, FormsModule, ButtonsModule, InputsModule, DropDownsModule], template: `
18105
+ <div class="config-container">
18106
+ <div class="config-form">
18107
+ <div class="form-field">
18108
+ <label>Grid Columns</label>
18109
+ <kendo-numerictextbox [(ngModel)]="form.columns" [min]="1" [max]="6" [format]="'n0'" [spinners]="true"></kendo-numerictextbox>
18110
+ </div>
18111
+
18112
+ <h4>Tiles</h4>
18113
+ @for (tile of form.tiles; track $index) {
18114
+ <div class="tile-config">
18115
+ <div class="tile-header">
18116
+ <strong>Tile {{ $index + 1 }}</strong>
18117
+ <button kendoButton fillMode="flat" (click)="removeTile($index)">Remove</button>
18118
+ </div>
18119
+ <div class="tile-row">
18120
+ <kendo-textbox [(ngModel)]="tile.label" placeholder="Label" style="flex: 1;"></kendo-textbox>
18121
+ <kendo-textbox [(ngModel)]="tile.prefix" placeholder="Prefix" style="width: 60px;"></kendo-textbox>
18122
+ <kendo-textbox [(ngModel)]="tile.suffix" placeholder="Suffix" style="width: 80px;"></kendo-textbox>
18123
+ </div>
18124
+ <div class="tile-row">
18125
+ <kendo-dropdownlist [data]="colorOptions" [valuePrimitive]="true" [(ngModel)]="tile.color" style="width: 110px;"></kendo-dropdownlist>
18126
+ <kendo-dropdownlist [data]="sizeOptions" [valuePrimitive]="true" [(ngModel)]="tile.size" style="width: 110px;"></kendo-dropdownlist>
18127
+ <kendo-dropdownlist [data]="sourceTypeOptions" [valuePrimitive]="true" [(ngModel)]="tile.sourceType" style="width: 120px;"></kendo-dropdownlist>
18128
+ </div>
18129
+ @if (tile.sourceType === 'entity') {
18130
+ <div class="tile-row">
18131
+ <kendo-textbox [(ngModel)]="tile.ckTypeId" placeholder="CK Type ID" style="flex: 1;"></kendo-textbox>
18132
+ <kendo-textbox [(ngModel)]="tile.rtId" placeholder="Runtime ID" style="flex: 1;"></kendo-textbox>
18133
+ <kendo-textbox [(ngModel)]="tile.attributePath" placeholder="Attribute" style="width: 120px;"></kendo-textbox>
18134
+ </div>
18135
+ }
18136
+ @if (tile.sourceType === 'aggregation') {
18137
+ <div class="tile-row">
18138
+ <kendo-textbox [(ngModel)]="tile.ckTypeId" placeholder="CK Type ID" style="flex: 1;"></kendo-textbox>
18139
+ <kendo-dropdownlist [data]="aggregationOptions" [valuePrimitive]="true" [(ngModel)]="tile.aggregation" style="width: 100px;"></kendo-dropdownlist>
18140
+ <kendo-textbox [(ngModel)]="tile.aggAttribute" placeholder="Attribute (for sum/avg)" style="flex: 1;"></kendo-textbox>
18141
+ </div>
18142
+ }
18143
+ </div>
18144
+ }
18145
+ <button kendoButton fillMode="flat" (click)="addTile()">+ Add Tile</button>
18146
+ </div>
18147
+
18148
+ <div class="action-bar mm-dialog-actions">
18149
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
18150
+ <button kendoButton themeColor="primary" [disabled]="!isValid" (click)="onSave()">Save</button>
18151
+ </div>
18152
+ </div>
18153
+ `, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.config-form{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}.form-field{display:flex;flex-direction:column;gap:6px}.form-field label{font-weight:600;font-size:.9rem}h4{margin:8px 0 4px;font-size:.95rem}.tile-config{border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;padding:10px;margin-bottom:8px;display:flex;flex-direction:column;gap:6px}.tile-header{display:flex;justify-content:space-between;align-items:center}.tile-row{display:flex;gap:6px;align-items:center}\n"] }]
18154
+ }], propDecorators: { initialColumns: [{
18155
+ type: Input
18156
+ }], initialTiles: [{
18157
+ type: Input
18158
+ }] } });
18159
+
18160
+ function getAlertSeverityColor(level) {
18161
+ switch (level?.toUpperCase()) {
18162
+ case 'CRITICAL': return 'var(--mm-alert-critical, #dc2626)';
18163
+ case 'ERROR': return 'var(--mm-alert-error, #ef4444)';
18164
+ case 'WARNING': return 'var(--mm-alert-warning, #f59e0b)';
18165
+ case 'INFORMATION': return 'var(--mm-alert-info, #3b82f6)';
18166
+ case 'DEBUG': return 'var(--mm-alert-debug, #6b7280)';
18167
+ default: return 'var(--mm-alert-debug, #6b7280)';
18168
+ }
18169
+ }
18170
+ function getAlertSeverityIcon(level) {
18171
+ switch (level?.toUpperCase()) {
18172
+ case 'CRITICAL':
18173
+ case 'ERROR':
18174
+ return xCircleIcon;
18175
+ case 'WARNING':
18176
+ return exclamationCircleIcon;
18177
+ default:
18178
+ return infoCircleIcon;
18179
+ }
18180
+ }
18181
+ function getAlertSeverityOrder(level) {
18182
+ switch (level?.toUpperCase()) {
18183
+ case 'CRITICAL': return 4;
18184
+ case 'ERROR': return 3;
18185
+ case 'WARNING': return 2;
18186
+ case 'INFORMATION': return 1;
18187
+ case 'DEBUG': return 0;
18188
+ default: return -1;
18189
+ }
18190
+ }
18191
+ const DEFAULT_ALERT_CK_TYPE = 'System.Notification/StatefulEvent';
18192
+
18193
+ class AlertBannerWidgetComponent {
18194
+ getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
18195
+ rotationTimer = null;
18196
+ config;
18197
+ _isLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isLoading" }] : /* istanbul ignore next */ []));
18198
+ _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : /* istanbul ignore next */ []));
18199
+ _items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : /* istanbul ignore next */ []));
18200
+ _currentIndex = signal(0, ...(ngDevMode ? [{ debugName: "_currentIndex" }] : /* istanbul ignore next */ []));
18201
+ isLoading = this._isLoading.asReadonly();
18202
+ error = this._error.asReadonly();
18203
+ data = this._items.asReadonly();
18204
+ items = this._items.asReadonly();
18205
+ currentIndex = this._currentIndex.asReadonly();
18206
+ currentAlert = computed(() => {
18207
+ const items = this._items();
18208
+ if (items.length === 0)
18209
+ return null;
18210
+ return items[this._currentIndex() % items.length];
18211
+ }, ...(ngDevMode ? [{ debugName: "currentAlert" }] : /* istanbul ignore next */ []));
18212
+ currentColor = computed(() => {
18213
+ const alert = this.currentAlert();
18214
+ return alert ? getAlertSeverityColor(alert.level) : '';
18215
+ }, ...(ngDevMode ? [{ debugName: "currentColor" }] : /* istanbul ignore next */ []));
18216
+ currentIcon = computed(() => {
18217
+ const alert = this.currentAlert();
18218
+ return alert ? getAlertSeverityIcon(alert.level) : getAlertSeverityIcon('DEBUG');
18219
+ }, ...(ngDevMode ? [{ debugName: "currentIcon" }] : /* istanbul ignore next */ []));
18220
+ isNotConfigured() {
18221
+ return false;
18222
+ }
18223
+ ngOnInit() {
18224
+ this.loadData();
18225
+ }
18226
+ ngOnChanges(changes) {
18227
+ if (changes['config'] && !changes['config'].firstChange) {
18228
+ this.loadData();
18229
+ }
18230
+ }
18231
+ ngOnDestroy() {
18232
+ this.stopRotation();
18233
+ }
18234
+ refresh() {
18235
+ this.loadData();
18236
+ }
18237
+ startRotation() {
18238
+ this.stopRotation();
18239
+ const interval = this.config?.rotationInterval ?? 5000;
18240
+ if (this._items().length > 1) {
18241
+ this.rotationTimer = setInterval(() => {
18242
+ this._currentIndex.update(i => (i + 1) % this._items().length);
18243
+ }, interval);
18244
+ }
18245
+ }
18246
+ stopRotation() {
18247
+ if (this.rotationTimer) {
18248
+ clearInterval(this.rotationTimer);
18249
+ this.rotationTimer = null;
18250
+ }
18251
+ }
18252
+ async loadData() {
18253
+ this._isLoading.set(true);
18254
+ this._error.set(null);
18255
+ try {
18256
+ const ckTypeId = this.config?.ckTypeId || DEFAULT_ALERT_CK_TYPE;
18257
+ const result = await firstValueFrom(this.getEntitiesByCkTypeGQL.fetch({
18258
+ variables: {
18259
+ ckTypeId,
18260
+ first: this.config?.maxAlerts ?? 20,
18261
+ fieldFilters: [
18262
+ { attributePath: 'state', operator: FieldFilterOperatorsDto.EqualsDto, comparisonValue: '0' }
18263
+ ],
18264
+ sort: [
18265
+ { attributePath: 'level', sortOrder: SortOrdersDto.DescendingDto }
18266
+ ]
18267
+ }
18268
+ }));
18269
+ const entities = result.data?.runtime?.runtimeEntities?.items ?? [];
18270
+ const items = [];
18271
+ for (const entity of entities) {
18272
+ if (!entity)
18273
+ continue;
18274
+ const attrs = (entity.attributes?.items ?? [])
18275
+ .filter((a) => a != null && a.attributeName != null);
18276
+ items.push({
18277
+ rtId: entity.rtId ?? '',
18278
+ message: this.getAttr(attrs, 'message') ?? '',
18279
+ level: this.getAttr(attrs, 'level') ?? 'INFORMATION',
18280
+ state: this.getAttr(attrs, 'state') ?? '',
18281
+ source: this.getAttr(attrs, 'source') ?? '',
18282
+ timestamp: entity.rtCreationDateTime ?? undefined
18283
+ });
18284
+ }
18285
+ items.sort((a, b) => getAlertSeverityOrder(b.level) - getAlertSeverityOrder(a.level));
18286
+ this._items.set(items);
18287
+ this._currentIndex.set(0);
18288
+ this.startRotation();
18289
+ }
18290
+ catch (err) {
18291
+ this._error.set(err instanceof Error ? err.message : 'Failed to load alerts');
18292
+ }
18293
+ finally {
18294
+ this._isLoading.set(false);
18295
+ }
18296
+ }
18297
+ getAttr(attrs, name) {
18298
+ const attr = attrs.find(a => a.attributeName === name);
18299
+ return attr?.value != null ? String(attr.value) : null;
18300
+ }
18301
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AlertBannerWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18302
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: AlertBannerWidgetComponent, isStandalone: true, selector: "mm-alert-banner-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: `
18303
+ <div class="alert-banner-widget">
18304
+ @if (isNotConfigured()) {
18305
+ <mm-widget-not-configured></mm-widget-not-configured>
18306
+ } @else if (error()) {
18307
+ <div class="error-message">{{ error() }}</div>
18308
+ } @else if (currentAlert()) {
18309
+ <div class="alert-banner" [class.critical]="currentAlert()!.level === 'CRITICAL'"
18310
+ [style.--alert-color]="currentColor()">
18311
+ @if (config.showIcon !== false) {
18312
+ <kendo-svg-icon [icon]="currentIcon()" class="alert-icon"></kendo-svg-icon>
18313
+ }
18314
+ <span class="alert-severity-badge">{{ currentAlert()!.level }}</span>
18315
+ <span class="alert-message">{{ currentAlert()!.message }}</span>
18316
+ @if (items().length > 1) {
18317
+ <span class="alert-counter">{{ currentIndex() + 1 }}/{{ items().length }}</span>
18318
+ }
18319
+ </div>
18320
+ } @else if (!isLoading()) {
18321
+ <div class="alert-banner no-alerts">
18322
+ <span class="alert-message">No active alerts</span>
18323
+ </div>
18324
+ }
18325
+ </div>
18326
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.alert-banner-widget{height:100%;display:flex;align-items:center}.alert-banner{display:flex;align-items:center;gap:10px;width:100%;padding:10px 16px;border-left:4px solid var(--alert-color, #6b7280);background:color-mix(in srgb,var(--alert-color, #6b7280) 10%,transparent)}.alert-banner.critical{animation:pulse-bg 1.5s ease-in-out infinite}@keyframes pulse-bg{0%,to{background:color-mix(in srgb,var(--alert-color) 10%,transparent)}50%{background:color-mix(in srgb,var(--alert-color) 25%,transparent)}}.alert-icon{flex-shrink:0;color:var(--alert-color, #6b7280)}.alert-severity-badge{font-size:.65rem;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 8px;border-radius:3px;background:var(--alert-color, #6b7280);color:#fff;white-space:nowrap;flex-shrink:0}.alert-message{flex:1;font-size:.85rem;color:var(--kendo-color-on-app-surface, inherit);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.alert-counter{font-size:.7rem;opacity:.5;white-space:nowrap;flex-shrink:0}.no-alerts{border-left-color:var(--mm-alert-debug, #6b7280);opacity:.5}.error-message{text-align:center;padding:16px;width:100%;color:var(--kendo-color-error, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i1$4.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
18327
+ }
18328
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AlertBannerWidgetComponent, decorators: [{
18329
+ type: Component,
18330
+ args: [{ selector: 'mm-alert-banner-widget', standalone: true, imports: [CommonModule, SVGIconModule, WidgetNotConfiguredComponent], template: `
18331
+ <div class="alert-banner-widget">
18332
+ @if (isNotConfigured()) {
18333
+ <mm-widget-not-configured></mm-widget-not-configured>
18334
+ } @else if (error()) {
18335
+ <div class="error-message">{{ error() }}</div>
18336
+ } @else if (currentAlert()) {
18337
+ <div class="alert-banner" [class.critical]="currentAlert()!.level === 'CRITICAL'"
18338
+ [style.--alert-color]="currentColor()">
18339
+ @if (config.showIcon !== false) {
18340
+ <kendo-svg-icon [icon]="currentIcon()" class="alert-icon"></kendo-svg-icon>
18341
+ }
18342
+ <span class="alert-severity-badge">{{ currentAlert()!.level }}</span>
18343
+ <span class="alert-message">{{ currentAlert()!.message }}</span>
18344
+ @if (items().length > 1) {
18345
+ <span class="alert-counter">{{ currentIndex() + 1 }}/{{ items().length }}</span>
18346
+ }
18347
+ </div>
18348
+ } @else if (!isLoading()) {
18349
+ <div class="alert-banner no-alerts">
18350
+ <span class="alert-message">No active alerts</span>
18351
+ </div>
18352
+ }
18353
+ </div>
18354
+ `, styles: [":host{display:block;width:100%;height:100%}.alert-banner-widget{height:100%;display:flex;align-items:center}.alert-banner{display:flex;align-items:center;gap:10px;width:100%;padding:10px 16px;border-left:4px solid var(--alert-color, #6b7280);background:color-mix(in srgb,var(--alert-color, #6b7280) 10%,transparent)}.alert-banner.critical{animation:pulse-bg 1.5s ease-in-out infinite}@keyframes pulse-bg{0%,to{background:color-mix(in srgb,var(--alert-color) 10%,transparent)}50%{background:color-mix(in srgb,var(--alert-color) 25%,transparent)}}.alert-icon{flex-shrink:0;color:var(--alert-color, #6b7280)}.alert-severity-badge{font-size:.65rem;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 8px;border-radius:3px;background:var(--alert-color, #6b7280);color:#fff;white-space:nowrap;flex-shrink:0}.alert-message{flex:1;font-size:.85rem;color:var(--kendo-color-on-app-surface, inherit);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.alert-counter{font-size:.7rem;opacity:.5;white-space:nowrap;flex-shrink:0}.no-alerts{border-left-color:var(--mm-alert-debug, #6b7280);opacity:.5}.error-message{text-align:center;padding:16px;width:100%;color:var(--kendo-color-error, #dc3545)}\n"] }]
18355
+ }], propDecorators: { config: [{
18356
+ type: Input
18357
+ }] } });
18358
+
18359
+ class AlertBannerConfigDialogComponent {
18360
+ windowRef = inject(WindowRef);
18361
+ ckTypeId = 'System.Notification/StatefulEvent';
18362
+ rotationInterval = 5000;
18363
+ showIcon = true;
18364
+ maxAlerts = 20;
18365
+ onSave() {
18366
+ const result = {
18367
+ ckTypeId: this.ckTypeId,
18368
+ rotationInterval: this.rotationInterval,
18369
+ showIcon: this.showIcon,
18370
+ maxAlerts: this.maxAlerts
18371
+ };
18372
+ this.windowRef.close(result);
18373
+ }
18374
+ onCancel() {
18375
+ this.windowRef.close();
18376
+ }
18377
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AlertBannerConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18378
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.5", type: AlertBannerConfigDialogComponent, isStandalone: true, selector: "mm-alert-banner-config-dialog", ngImport: i0, template: `
18379
+ <div class="config-form">
18380
+ <div class="form-group">
18381
+ <kendo-label text="CK Type ID">
18382
+ <kendo-textbox [(ngModel)]="ckTypeId"></kendo-textbox>
18383
+ </kendo-label>
18384
+ </div>
18385
+ <div class="form-group">
18386
+ <kendo-label text="Rotation Interval (ms)">
18387
+ <kendo-numerictextbox [(ngModel)]="rotationInterval" [min]="1000" [step]="1000" [format]="'n0'"></kendo-numerictextbox>
18388
+ </kendo-label>
18389
+ </div>
18390
+ <div class="form-group">
18391
+ <kendo-label text="Max Alerts">
18392
+ <kendo-numerictextbox [(ngModel)]="maxAlerts" [min]="1" [max]="100" [format]="'n0'"></kendo-numerictextbox>
18393
+ </kendo-label>
18394
+ </div>
18395
+ <div class="mm-dialog-actions">
18396
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
18397
+ <button kendoButton themeColor="primary" (click)="onSave()">Save</button>
18398
+ </div>
18399
+ </div>
18400
+ `, isInline: true, styles: [".config-form{display:flex;flex-direction:column;gap:12px;padding:16px}.form-group{display:flex;flex-direction:column;gap:4px}.mm-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "ngmodule", type: LabelModule }, { kind: "component", type: i4$1.LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }] });
18401
+ }
18402
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AlertBannerConfigDialogComponent, decorators: [{
18403
+ type: Component,
18404
+ args: [{ selector: 'mm-alert-banner-config-dialog', standalone: true, imports: [CommonModule, FormsModule, ButtonsModule, InputsModule, LabelModule], template: `
18405
+ <div class="config-form">
18406
+ <div class="form-group">
18407
+ <kendo-label text="CK Type ID">
18408
+ <kendo-textbox [(ngModel)]="ckTypeId"></kendo-textbox>
18409
+ </kendo-label>
18410
+ </div>
18411
+ <div class="form-group">
18412
+ <kendo-label text="Rotation Interval (ms)">
18413
+ <kendo-numerictextbox [(ngModel)]="rotationInterval" [min]="1000" [step]="1000" [format]="'n0'"></kendo-numerictextbox>
18414
+ </kendo-label>
18415
+ </div>
18416
+ <div class="form-group">
18417
+ <kendo-label text="Max Alerts">
18418
+ <kendo-numerictextbox [(ngModel)]="maxAlerts" [min]="1" [max]="100" [format]="'n0'"></kendo-numerictextbox>
18419
+ </kendo-label>
18420
+ </div>
18421
+ <div class="mm-dialog-actions">
18422
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
18423
+ <button kendoButton themeColor="primary" (click)="onSave()">Save</button>
18424
+ </div>
18425
+ </div>
18426
+ `, styles: [".config-form{display:flex;flex-direction:column;gap:12px;padding:16px}.form-group{display:flex;flex-direction:column;gap:4px}.mm-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}\n"] }]
18427
+ }] });
18428
+
18429
+ class AlertListWidgetComponent {
18430
+ getEntitiesByCkTypeGQL = inject(GetEntitiesByCkTypeDtoGQL);
18431
+ config;
18432
+ _isLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isLoading" }] : /* istanbul ignore next */ []));
18433
+ _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : /* istanbul ignore next */ []));
18434
+ _items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : /* istanbul ignore next */ []));
18435
+ isLoading = this._isLoading.asReadonly();
18436
+ error = this._error.asReadonly();
18437
+ data = this._items.asReadonly();
18438
+ items = this._items.asReadonly();
18439
+ isNotConfigured() {
18440
+ return false;
18441
+ }
18442
+ ngOnInit() {
18443
+ this.loadData();
18444
+ }
18445
+ ngOnChanges(changes) {
18446
+ if (changes['config'] && !changes['config'].firstChange) {
18447
+ this.loadData();
18448
+ }
18449
+ }
18450
+ refresh() {
18451
+ this.loadData();
18452
+ }
18453
+ getColor(level) {
18454
+ return getAlertSeverityColor(level);
18455
+ }
18456
+ getIcon(level) {
18457
+ return getAlertSeverityIcon(level);
18458
+ }
18459
+ formatTime(timestamp) {
18460
+ try {
18461
+ const date = new Date(timestamp);
18462
+ return date.toLocaleString('de-AT', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
18463
+ }
18464
+ catch {
18465
+ return '';
18466
+ }
18467
+ }
18468
+ async loadData() {
18469
+ this._isLoading.set(true);
18470
+ this._error.set(null);
18471
+ try {
18472
+ const ckTypeId = this.config?.ckTypeId || DEFAULT_ALERT_CK_TYPE;
18473
+ const result = await firstValueFrom(this.getEntitiesByCkTypeGQL.fetch({
18474
+ variables: {
18475
+ ckTypeId,
18476
+ first: this.config?.maxAlerts ?? 50,
18477
+ fieldFilters: [
18478
+ { attributePath: 'state', operator: FieldFilterOperatorsDto.EqualsDto, comparisonValue: '0' }
18479
+ ],
18480
+ sort: [
18481
+ { attributePath: 'level', sortOrder: SortOrdersDto.DescendingDto }
18482
+ ]
18483
+ }
18484
+ }));
18485
+ const entities = result.data?.runtime?.runtimeEntities?.items ?? [];
18486
+ const items = [];
18487
+ for (const entity of entities) {
18488
+ if (!entity)
18489
+ continue;
18490
+ const attrs = (entity.attributes?.items ?? [])
18491
+ .filter((a) => a != null && a.attributeName != null);
18492
+ items.push({
18493
+ rtId: entity.rtId ?? '',
18494
+ message: this.getAttr(attrs, 'message') ?? '',
18495
+ level: this.getAttr(attrs, 'level') ?? 'INFORMATION',
18496
+ state: this.getAttr(attrs, 'state') ?? '',
18497
+ source: this.getAttr(attrs, 'source') ?? '',
18498
+ timestamp: entity.rtCreationDateTime ?? undefined
18499
+ });
18500
+ }
18501
+ if (this.config?.sortBySeverity !== false) {
18502
+ items.sort((a, b) => getAlertSeverityOrder(b.level) - getAlertSeverityOrder(a.level));
18503
+ }
18504
+ this._items.set(items);
18505
+ }
18506
+ catch (err) {
18507
+ this._error.set(err instanceof Error ? err.message : 'Failed to load alerts');
18508
+ }
18509
+ finally {
18510
+ this._isLoading.set(false);
18511
+ }
18512
+ }
18513
+ getAttr(attrs, name) {
18514
+ const attr = attrs.find(a => a.attributeName === name);
18515
+ return attr?.value != null ? String(attr.value) : null;
18516
+ }
18517
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AlertListWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18518
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: AlertListWidgetComponent, isStandalone: true, selector: "mm-alert-list-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: `
18519
+ <div class="alert-list-widget">
18520
+ @if (isNotConfigured()) {
18521
+ <mm-widget-not-configured></mm-widget-not-configured>
18522
+ } @else if (error()) {
18523
+ <div class="error-message">{{ error() }}</div>
18524
+ } @else {
18525
+ <div class="alert-list">
18526
+ @for (item of items(); track item.rtId) {
18527
+ <div class="alert-item" [style.--alert-color]="getColor(item.level)">
18528
+ <kendo-svg-icon [icon]="getIcon(item.level)" class="alert-icon"></kendo-svg-icon>
18529
+ <span class="alert-badge" [style.background-color]="getColor(item.level)">
18530
+ {{ item.level }}
18531
+ </span>
18532
+ <span class="alert-message">{{ item.message }}</span>
18533
+ @if (config.showTimestamp !== false && item.timestamp) {
18534
+ <span class="alert-time">{{ formatTime(item.timestamp) }}</span>
18535
+ }
18536
+ </div>
18537
+ }
18538
+ @if (!isLoading() && items().length === 0) {
18539
+ <div class="empty-message">No active alerts</div>
18540
+ }
18541
+ </div>
18542
+ }
18543
+ </div>
18544
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.alert-list-widget{height:100%;display:flex;flex-direction:column;overflow-y:auto;padding:8px 0}.alert-list{display:flex;flex-direction:column;gap:4px;padding:0 12px}.alert-item{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:6px;border-left:3px solid var(--alert-color, #6b7280);background:var(--mm-alert-list-item-bg, rgba(255, 255, 255, .03))}.alert-icon{flex-shrink:0;color:var(--alert-color, #6b7280)}.alert-badge{font-size:.6rem;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:3px;color:#fff;white-space:nowrap;flex-shrink:0}.alert-message{flex:1;font-size:.8rem;color:var(--kendo-color-on-app-surface, inherit);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.alert-time{font-size:.7rem;opacity:.5;white-space:nowrap;flex-shrink:0}.empty-message{text-align:center;padding:16px;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.error-message{text-align:center;padding:16px;color:var(--kendo-color-error, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i1$4.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
18545
+ }
18546
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AlertListWidgetComponent, decorators: [{
18547
+ type: Component,
18548
+ args: [{ selector: 'mm-alert-list-widget', standalone: true, imports: [CommonModule, SVGIconModule, WidgetNotConfiguredComponent], template: `
18549
+ <div class="alert-list-widget">
18550
+ @if (isNotConfigured()) {
18551
+ <mm-widget-not-configured></mm-widget-not-configured>
18552
+ } @else if (error()) {
18553
+ <div class="error-message">{{ error() }}</div>
18554
+ } @else {
18555
+ <div class="alert-list">
18556
+ @for (item of items(); track item.rtId) {
18557
+ <div class="alert-item" [style.--alert-color]="getColor(item.level)">
18558
+ <kendo-svg-icon [icon]="getIcon(item.level)" class="alert-icon"></kendo-svg-icon>
18559
+ <span class="alert-badge" [style.background-color]="getColor(item.level)">
18560
+ {{ item.level }}
18561
+ </span>
18562
+ <span class="alert-message">{{ item.message }}</span>
18563
+ @if (config.showTimestamp !== false && item.timestamp) {
18564
+ <span class="alert-time">{{ formatTime(item.timestamp) }}</span>
18565
+ }
18566
+ </div>
18567
+ }
18568
+ @if (!isLoading() && items().length === 0) {
18569
+ <div class="empty-message">No active alerts</div>
18570
+ }
18571
+ </div>
18572
+ }
18573
+ </div>
18574
+ `, styles: [":host{display:block;width:100%;height:100%}.alert-list-widget{height:100%;display:flex;flex-direction:column;overflow-y:auto;padding:8px 0}.alert-list{display:flex;flex-direction:column;gap:4px;padding:0 12px}.alert-item{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:6px;border-left:3px solid var(--alert-color, #6b7280);background:var(--mm-alert-list-item-bg, rgba(255, 255, 255, .03))}.alert-icon{flex-shrink:0;color:var(--alert-color, #6b7280)}.alert-badge{font-size:.6rem;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:3px;color:#fff;white-space:nowrap;flex-shrink:0}.alert-message{flex:1;font-size:.8rem;color:var(--kendo-color-on-app-surface, inherit);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.alert-time{font-size:.7rem;opacity:.5;white-space:nowrap;flex-shrink:0}.empty-message{text-align:center;padding:16px;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.error-message{text-align:center;padding:16px;color:var(--kendo-color-error, #dc3545)}\n"] }]
18575
+ }], propDecorators: { config: [{
18576
+ type: Input
18577
+ }] } });
18578
+
18579
+ class AlertListConfigDialogComponent {
18580
+ windowRef = inject(WindowRef);
18581
+ ckTypeId = 'System.Notification/StatefulEvent';
18582
+ showTimestamp = true;
18583
+ sortBySeverity = true;
18584
+ maxAlerts = 50;
18585
+ onSave() {
18586
+ const result = {
18587
+ ckTypeId: this.ckTypeId,
18588
+ showTimestamp: this.showTimestamp,
18589
+ sortBySeverity: this.sortBySeverity,
18590
+ maxAlerts: this.maxAlerts
18591
+ };
18592
+ this.windowRef.close(result);
18593
+ }
18594
+ onCancel() {
18595
+ this.windowRef.close();
18596
+ }
18597
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AlertListConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18598
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.5", type: AlertListConfigDialogComponent, isStandalone: true, selector: "mm-alert-list-config-dialog", ngImport: i0, template: `
18599
+ <div class="config-form">
18600
+ <div class="form-group">
18601
+ <kendo-label text="CK Type ID">
18602
+ <kendo-textbox [(ngModel)]="ckTypeId"></kendo-textbox>
18603
+ </kendo-label>
18604
+ </div>
18605
+ <div class="form-group">
18606
+ <kendo-label text="Max Alerts">
18607
+ <kendo-numerictextbox [(ngModel)]="maxAlerts" [min]="1" [max]="200" [format]="'n0'"></kendo-numerictextbox>
18608
+ </kendo-label>
18609
+ </div>
18610
+ <div class="mm-dialog-actions">
18611
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
18612
+ <button kendoButton themeColor="primary" (click)="onSave()">Save</button>
18613
+ </div>
18614
+ </div>
18615
+ `, isInline: true, styles: [".config-form{display:flex;flex-direction:column;gap:12px;padding:16px}.form-group{display:flex;flex-direction:column;gap:4px}.mm-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "ngmodule", type: LabelModule }, { kind: "component", type: i4$1.LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }] });
18616
+ }
18617
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AlertListConfigDialogComponent, decorators: [{
18618
+ type: Component,
18619
+ args: [{ selector: 'mm-alert-list-config-dialog', standalone: true, imports: [CommonModule, FormsModule, ButtonsModule, InputsModule, LabelModule], template: `
18620
+ <div class="config-form">
18621
+ <div class="form-group">
18622
+ <kendo-label text="CK Type ID">
18623
+ <kendo-textbox [(ngModel)]="ckTypeId"></kendo-textbox>
18624
+ </kendo-label>
18625
+ </div>
18626
+ <div class="form-group">
18627
+ <kendo-label text="Max Alerts">
18628
+ <kendo-numerictextbox [(ngModel)]="maxAlerts" [min]="1" [max]="200" [format]="'n0'"></kendo-numerictextbox>
18629
+ </kendo-label>
18630
+ </div>
18631
+ <div class="mm-dialog-actions">
18632
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
18633
+ <button kendoButton themeColor="primary" (click)="onSave()">Save</button>
18634
+ </div>
18635
+ </div>
18636
+ `, styles: [".config-form{display:flex;flex-direction:column;gap:12px;padding:16px}.form-group{display:flex;flex-direction:column;gap:4px}.mm-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}\n"] }]
18637
+ }] });
18638
+
18639
+ class AiInsightsService {
18640
+ async generateInsights(context) {
18641
+ if (context.apiKey) {
18642
+ return this.callClaudeApi(context);
18643
+ }
18644
+ return this.getSimulatedInsights(context);
18645
+ }
18646
+ gatherWidgetContext(widgets) {
18647
+ return widgets
18648
+ .filter(w => w.type !== 'aiInsights')
18649
+ .map(w => this.extractWidgetSummary(w))
18650
+ .filter(s => s.dataPoints.length > 0 || s.type !== 'unknown');
18651
+ }
18652
+ extractWidgetSummary(widget) {
18653
+ const summary = {
18654
+ title: widget.title,
18655
+ type: widget.type,
18656
+ dataPoints: []
18657
+ };
18658
+ if (widget.type === 'kpi') {
18659
+ const kpi = widget;
18660
+ if (kpi.staticValue) {
18661
+ summary.dataPoints.push({ label: 'value', value: `${kpi.staticValue}${kpi.suffix ?? ''}` });
18662
+ }
18663
+ if (kpi.comparisonText) {
18664
+ summary.dataPoints.push({ label: 'comparison', value: kpi.comparisonText });
18665
+ }
18666
+ }
18667
+ if (widget.type === 'gauge') {
18668
+ const gauge = widget;
18669
+ if (gauge.valueAttribute) {
18670
+ summary.dataPoints.push({ label: 'attribute', value: gauge.valueAttribute });
18671
+ }
18672
+ }
18673
+ return summary;
18674
+ }
18675
+ async callClaudeApi(context) {
18676
+ const systemPrompt = context.systemPrompt ?? this.buildDefaultSystemPrompt(context);
18677
+ const userMessage = this.buildUserMessage(context);
18678
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
18679
+ method: 'POST',
18680
+ headers: {
18681
+ 'Content-Type': 'application/json',
18682
+ 'x-api-key': context.apiKey,
18683
+ 'anthropic-version': '2023-06-01',
18684
+ 'anthropic-dangerous-direct-browser-access': 'true'
18685
+ },
18686
+ body: JSON.stringify({
18687
+ model: context.model ?? 'claude-sonnet-4-20250514',
18688
+ max_tokens: 1024,
18689
+ system: systemPrompt,
18690
+ messages: [{ role: 'user', content: userMessage }]
18691
+ })
18692
+ });
18693
+ if (!response.ok) {
18694
+ throw new Error(`Claude API error: ${response.status}`);
18695
+ }
18696
+ const data = await response.json();
18697
+ const text = data.content?.[0]?.text ?? '';
18698
+ return this.parseInsightsFromResponse(text, context.maxInsights ?? 4);
18699
+ }
18700
+ buildDefaultSystemPrompt(context) {
18701
+ const domain = context.domainContext ?? 'energy management';
18702
+ return `You are an AI advisor for an industrial ${domain} dashboard.
18703
+ Analyze the provided dashboard data and generate actionable insights.
18704
+ Respond ONLY with a JSON array of insight objects. Each object has:
18705
+ - "title": short headline (max 8 words)
18706
+ - "description": actionable recommendation (1-2 sentences, German language)
18707
+ - "severity": one of "info", "warning", "success", "critical"
18708
+
18709
+ Focus on energy efficiency, cost savings, anomalies, and optimization opportunities.
18710
+ Return at most ${context.maxInsights ?? 4} insights. No markdown, no explanation, only the JSON array.`;
18711
+ }
18712
+ buildUserMessage(context) {
18713
+ const widgetData = context.widgetSummaries.map(w => ({
18714
+ widget: w.title,
18715
+ type: w.type,
18716
+ data: w.dataPoints
18717
+ }));
18718
+ return `Current dashboard state:\n${JSON.stringify(widgetData, null, 2)}`;
18719
+ }
18720
+ parseInsightsFromResponse(text, maxInsights) {
18721
+ try {
18722
+ const jsonMatch = text.match(/\[[\s\S]*\]/);
18723
+ if (!jsonMatch)
18724
+ return this.fallbackInsights();
18725
+ const parsed = JSON.parse(jsonMatch[0]);
18726
+ return parsed.slice(0, maxInsights).map(i => ({
18727
+ title: i.title ?? 'Insight',
18728
+ description: i.description ?? '',
18729
+ severity: (['info', 'warning', 'success', 'critical'].includes(i.severity) ? i.severity : 'info')
18730
+ }));
18731
+ }
18732
+ catch {
18733
+ return this.fallbackInsights();
18734
+ }
18735
+ }
18736
+ getSimulatedInsights(_context) {
18737
+ const hour = new Date().getHours();
18738
+ const insights = [
18739
+ {
18740
+ title: 'Lastspitze prognostiziert',
18741
+ description: 'Basierend auf dem aktuellen Lastprofil wird die 3.000 kW Grenze voraussichtlich um 14:30 Uhr erreicht. Lastabwurf der HVAC-Anlage um 30% empfohlen.',
18742
+ severity: 'warning'
18743
+ },
18744
+ {
18745
+ title: 'PV-Optimierung möglich',
18746
+ description: hour < 14
18747
+ ? 'Wettervorhersage: sonnig bis 16:00 Uhr. Energieintensive Prozesse jetzt starten — erwartete PV-Einspeisung von 810 kW nutzen.'
18748
+ : 'PV-Einspeisung sinkt ab 16:00 Uhr. Batteriespeicher auf 80% laden, solange Überschuss verfügbar ist.',
18749
+ severity: 'success'
18750
+ },
18751
+ {
18752
+ title: 'Kompressoranlage C3 auffällig',
18753
+ description: 'Verbrauch 18% über dem 30-Tage-Durchschnitt bei gleicher Auslastung. Mögliche Ursache: verschlissene Dichtungen oder Druckverlust. Wartung empfohlen.',
18754
+ severity: 'warning'
18755
+ },
18756
+ {
18757
+ title: 'CO2-Budget im Zeitplan',
18758
+ description: 'Mit 489 t von 680 t Budget (72%) und 9 Monaten verbleibend liegt der Verbrauch 4% unter Plan. Zertifikats-Beschaffung kann verschoben werden.',
18759
+ severity: 'success'
18760
+ }
18761
+ ];
18762
+ return insights;
18763
+ }
18764
+ fallbackInsights() {
18765
+ return [{
18766
+ title: 'Analyse nicht verfügbar',
18767
+ description: 'Die KI-Analyse konnte nicht durchgeführt werden. Bitte prüfen Sie den API-Schlüssel in den Widget-Einstellungen.',
18768
+ severity: 'info'
18769
+ }];
18770
+ }
18771
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AiInsightsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
18772
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AiInsightsService, providedIn: 'root' });
18773
+ }
18774
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AiInsightsService, decorators: [{
18775
+ type: Injectable,
18776
+ args: [{ providedIn: 'root' }]
18777
+ }] });
18778
+
18779
+ class AiInsightsWidgetComponent {
18780
+ aiService = inject(AiInsightsService);
18781
+ stateService = inject(MeshBoardStateService);
18782
+ refreshTimer = null;
18783
+ infoIcon = infoCircleIcon;
18784
+ warningIcon = exclamationCircleIcon;
18785
+ successIcon = checkCircleIcon;
18786
+ criticalIcon = xCircleIcon;
18787
+ config;
18788
+ _isLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isLoading" }] : /* istanbul ignore next */ []));
18789
+ _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : /* istanbul ignore next */ []));
18790
+ _insights = signal(null, ...(ngDevMode ? [{ debugName: "_insights" }] : /* istanbul ignore next */ []));
18791
+ isLoading = this._isLoading.asReadonly();
18792
+ error = this._error.asReadonly();
18793
+ data = this._insights.asReadonly();
18794
+ insights = this._insights.asReadonly();
18795
+ isNotConfigured() {
18796
+ return false;
18797
+ }
18798
+ ngOnInit() {
18799
+ this.loadInsights();
18800
+ this.startAutoRefresh();
18801
+ }
18802
+ ngOnChanges(changes) {
18803
+ if (changes['config'] && !changes['config'].firstChange) {
18804
+ this.stopAutoRefresh();
18805
+ this.loadInsights();
18806
+ this.startAutoRefresh();
18807
+ }
18808
+ }
18809
+ ngOnDestroy() {
18810
+ this.stopAutoRefresh();
18811
+ }
18812
+ refresh() {
18813
+ this.loadInsights();
18814
+ }
18815
+ getIcon(severity) {
18816
+ switch (severity) {
18817
+ case 'warning': return this.warningIcon;
18818
+ case 'success': return this.successIcon;
18819
+ case 'critical': return this.criticalIcon;
18820
+ default: return this.infoIcon;
18821
+ }
18822
+ }
18823
+ async loadInsights() {
18824
+ this._isLoading.set(true);
18825
+ this._error.set(null);
18826
+ try {
18827
+ const widgets = this.stateService.widgets();
18828
+ const widgetSummaries = this.aiService.gatherWidgetContext(widgets);
18829
+ const context = {
18830
+ apiKey: this.config.apiKey,
18831
+ model: this.config.model,
18832
+ systemPrompt: this.config.systemPrompt,
18833
+ maxInsights: this.config.maxInsights ?? 4,
18834
+ domainContext: this.config.domainContext ?? 'energy management',
18835
+ widgetSummaries
18836
+ };
18837
+ const insights = await this.aiService.generateInsights(context);
18838
+ this._insights.set(insights);
18839
+ }
18840
+ catch (err) {
18841
+ this._error.set(err instanceof Error ? err.message : 'Failed to generate insights');
18842
+ }
18843
+ finally {
18844
+ this._isLoading.set(false);
18845
+ }
18846
+ }
18847
+ startAutoRefresh() {
18848
+ this.stopAutoRefresh();
18849
+ const interval = (this.config?.refreshInterval ?? 0) * 1000;
18850
+ if (interval > 0) {
18851
+ this.refreshTimer = setInterval(() => this.loadInsights(), interval);
18852
+ }
18853
+ }
18854
+ stopAutoRefresh() {
18855
+ if (this.refreshTimer) {
18856
+ clearInterval(this.refreshTimer);
18857
+ this.refreshTimer = null;
18858
+ }
18859
+ }
18860
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AiInsightsWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18861
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: AiInsightsWidgetComponent, isStandalone: true, selector: "mm-ai-insights-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: `
18862
+ <div class="ai-insights-widget">
18863
+ @if (isLoading() && !insights()) {
18864
+ <div class="loading-state">
18865
+ <div class="loading-dots">
18866
+ <span></span><span></span><span></span>
18867
+ </div>
18868
+ <span class="loading-text">Generating AI insights...</span>
18869
+ </div>
18870
+ } @else if (error()) {
18871
+ <div class="error-state">{{ error() }}</div>
18872
+ } @else if (insights(); as items) {
18873
+ <div class="insights-list">
18874
+ @for (insight of items; track insight.title) {
18875
+ <div class="insight-card" [class]="'severity-' + insight.severity">
18876
+ <div class="insight-icon">
18877
+ <kendo-svg-icon [icon]="getIcon(insight.severity)" size="medium"></kendo-svg-icon>
18878
+ </div>
18879
+ <div class="insight-content">
18880
+ <div class="insight-title">{{ insight.title }}</div>
18881
+ <div class="insight-description">{{ insight.description }}</div>
18882
+ </div>
18883
+ </div>
18884
+ }
18885
+ </div>
18886
+ @if (isLoading()) {
18887
+ <div class="refresh-indicator">Aktualisierung...</div>
18888
+ }
18889
+ }
18890
+ </div>
18891
+ `, isInline: true, styles: [":host{display:block;width:100%;height:100%}.ai-insights-widget{height:100%;overflow-y:auto;padding:8px 12px}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:12px;opacity:.6}.loading-dots{display:flex;gap:6px}.loading-dots span{width:8px;height:8px;border-radius:50%;background:var(--kendo-color-primary, #06b6d4);animation:pulse-dot 1.4s ease-in-out infinite}.loading-dots span:nth-child(2){animation-delay:.2s}.loading-dots span:nth-child(3){animation-delay:.4s}@keyframes pulse-dot{0%,80%,to{opacity:.3;transform:scale(.8)}40%{opacity:1;transform:scale(1.2)}}.loading-text{font-size:.8rem}.insights-list{display:flex;flex-direction:column;gap:8px}.insight-card{display:flex;gap:10px;padding:10px 12px;border-radius:6px;border-left:3px solid var(--mm-ai-border-color, #6b7280);background:var(--mm-ai-card-bg, rgba(255, 255, 255, .03))}.insight-icon{flex-shrink:0;padding-top:2px;color:var(--mm-ai-border-color, #6b7280)}.insight-content{flex:1;min-width:0}.insight-title{font-size:.85rem;font-weight:600;margin-bottom:4px;color:var(--kendo-color-on-app-surface, inherit)}.insight-description{font-size:.8rem;line-height:1.4;opacity:.8}.severity-info{--mm-ai-border-color: var(--mm-ai-info, #3b82f6)}.severity-warning{--mm-ai-border-color: var(--mm-ai-warning, #f59e0b)}.severity-success{--mm-ai-border-color: var(--mm-ai-success, #10b981)}.severity-critical{--mm-ai-border-color: var(--mm-ai-critical, #ef4444)}.refresh-indicator{text-align:center;padding:8px;font-size:.7rem;opacity:.5}.error-state{text-align:center;padding:16px;color:var(--kendo-color-error, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i1$4.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }] });
18892
+ }
18893
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AiInsightsWidgetComponent, decorators: [{
18894
+ type: Component,
18895
+ args: [{ selector: 'mm-ai-insights-widget', standalone: true, imports: [CommonModule, SVGIconModule, WidgetNotConfiguredComponent], template: `
18896
+ <div class="ai-insights-widget">
18897
+ @if (isLoading() && !insights()) {
18898
+ <div class="loading-state">
18899
+ <div class="loading-dots">
18900
+ <span></span><span></span><span></span>
18901
+ </div>
18902
+ <span class="loading-text">Generating AI insights...</span>
18903
+ </div>
18904
+ } @else if (error()) {
18905
+ <div class="error-state">{{ error() }}</div>
18906
+ } @else if (insights(); as items) {
18907
+ <div class="insights-list">
18908
+ @for (insight of items; track insight.title) {
18909
+ <div class="insight-card" [class]="'severity-' + insight.severity">
18910
+ <div class="insight-icon">
18911
+ <kendo-svg-icon [icon]="getIcon(insight.severity)" size="medium"></kendo-svg-icon>
18912
+ </div>
18913
+ <div class="insight-content">
18914
+ <div class="insight-title">{{ insight.title }}</div>
18915
+ <div class="insight-description">{{ insight.description }}</div>
18916
+ </div>
18917
+ </div>
18918
+ }
18919
+ </div>
18920
+ @if (isLoading()) {
18921
+ <div class="refresh-indicator">Aktualisierung...</div>
18922
+ }
18923
+ }
18924
+ </div>
18925
+ `, styles: [":host{display:block;width:100%;height:100%}.ai-insights-widget{height:100%;overflow-y:auto;padding:8px 12px}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:12px;opacity:.6}.loading-dots{display:flex;gap:6px}.loading-dots span{width:8px;height:8px;border-radius:50%;background:var(--kendo-color-primary, #06b6d4);animation:pulse-dot 1.4s ease-in-out infinite}.loading-dots span:nth-child(2){animation-delay:.2s}.loading-dots span:nth-child(3){animation-delay:.4s}@keyframes pulse-dot{0%,80%,to{opacity:.3;transform:scale(.8)}40%{opacity:1;transform:scale(1.2)}}.loading-text{font-size:.8rem}.insights-list{display:flex;flex-direction:column;gap:8px}.insight-card{display:flex;gap:10px;padding:10px 12px;border-radius:6px;border-left:3px solid var(--mm-ai-border-color, #6b7280);background:var(--mm-ai-card-bg, rgba(255, 255, 255, .03))}.insight-icon{flex-shrink:0;padding-top:2px;color:var(--mm-ai-border-color, #6b7280)}.insight-content{flex:1;min-width:0}.insight-title{font-size:.85rem;font-weight:600;margin-bottom:4px;color:var(--kendo-color-on-app-surface, inherit)}.insight-description{font-size:.8rem;line-height:1.4;opacity:.8}.severity-info{--mm-ai-border-color: var(--mm-ai-info, #3b82f6)}.severity-warning{--mm-ai-border-color: var(--mm-ai-warning, #f59e0b)}.severity-success{--mm-ai-border-color: var(--mm-ai-success, #10b981)}.severity-critical{--mm-ai-border-color: var(--mm-ai-critical, #ef4444)}.refresh-indicator{text-align:center;padding:8px;font-size:.7rem;opacity:.5}.error-state{text-align:center;padding:16px;color:var(--kendo-color-error, #dc3545)}\n"] }]
18926
+ }], propDecorators: { config: [{
18927
+ type: Input
18928
+ }] } });
18929
+
18930
+ class AiInsightsConfigDialogComponent {
18931
+ windowRef = inject(WindowRef);
18932
+ apiKey = '';
18933
+ model = 'claude-sonnet-4-20250514';
18934
+ domainContext = 'energy management';
18935
+ refreshInterval = 0;
18936
+ maxInsights = 4;
18937
+ onSave() {
18938
+ const result = {
18939
+ ckTypeId: '',
18940
+ apiKey: this.apiKey || undefined,
18941
+ model: this.model,
18942
+ domainContext: this.domainContext,
18943
+ refreshInterval: this.refreshInterval,
18944
+ maxInsights: this.maxInsights
18945
+ };
18946
+ this.windowRef.close(result);
18947
+ }
18948
+ onCancel() {
18949
+ this.windowRef.close();
18950
+ }
18951
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AiInsightsConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18952
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.5", type: AiInsightsConfigDialogComponent, isStandalone: true, selector: "mm-ai-insights-config-dialog", ngImport: i0, template: `
18953
+ <div class="config-form">
18954
+ <div class="form-group">
18955
+ <kendo-label text="Anthropic API Key (optional for demo)">
18956
+ <kendo-textbox [(ngModel)]="apiKey" type="password" placeholder="sk-ant-..."></kendo-textbox>
18957
+ </kendo-label>
18958
+ <span class="hint">Without API key, simulated insights are shown.</span>
18959
+ </div>
18960
+ <div class="form-group">
18961
+ <kendo-label text="Model">
18962
+ <kendo-textbox [(ngModel)]="model"></kendo-textbox>
18963
+ </kendo-label>
18964
+ </div>
18965
+ <div class="form-group">
18966
+ <kendo-label text="Domain Context">
18967
+ <kendo-textbox [(ngModel)]="domainContext" placeholder="e.g. energy management"></kendo-textbox>
18968
+ </kendo-label>
18969
+ </div>
18970
+ <div class="form-row">
18971
+ <div class="form-group">
18972
+ <kendo-label text="Refresh Interval (seconds, 0=off)">
18973
+ <kendo-numerictextbox [(ngModel)]="refreshInterval" [min]="0" [step]="10" [format]="'n0'"></kendo-numerictextbox>
18974
+ </kendo-label>
18975
+ </div>
18976
+ <div class="form-group">
18977
+ <kendo-label text="Max Insights">
18978
+ <kendo-numerictextbox [(ngModel)]="maxInsights" [min]="1" [max]="8" [format]="'n0'"></kendo-numerictextbox>
18979
+ </kendo-label>
18980
+ </div>
18981
+ </div>
18982
+ <div class="mm-dialog-actions">
18983
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
18984
+ <button kendoButton themeColor="primary" (click)="onSave()">Save</button>
18985
+ </div>
18986
+ </div>
18987
+ `, isInline: true, styles: [".config-form{display:flex;flex-direction:column;gap:12px;padding:16px}.form-group{display:flex;flex-direction:column;gap:4px}.form-row{display:flex;gap:16px}.form-row .form-group{flex:1}.mm-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}.hint{font-size:.75rem;opacity:.6}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "ngmodule", type: LabelModule }, { kind: "component", type: i4$1.LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }] });
18988
+ }
18989
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AiInsightsConfigDialogComponent, decorators: [{
18990
+ type: Component,
18991
+ args: [{ selector: 'mm-ai-insights-config-dialog', standalone: true, imports: [CommonModule, FormsModule, ButtonsModule, InputsModule, LabelModule], template: `
18992
+ <div class="config-form">
18993
+ <div class="form-group">
18994
+ <kendo-label text="Anthropic API Key (optional for demo)">
18995
+ <kendo-textbox [(ngModel)]="apiKey" type="password" placeholder="sk-ant-..."></kendo-textbox>
18996
+ </kendo-label>
18997
+ <span class="hint">Without API key, simulated insights are shown.</span>
18998
+ </div>
18999
+ <div class="form-group">
19000
+ <kendo-label text="Model">
19001
+ <kendo-textbox [(ngModel)]="model"></kendo-textbox>
19002
+ </kendo-label>
19003
+ </div>
19004
+ <div class="form-group">
19005
+ <kendo-label text="Domain Context">
19006
+ <kendo-textbox [(ngModel)]="domainContext" placeholder="e.g. energy management"></kendo-textbox>
19007
+ </kendo-label>
19008
+ </div>
19009
+ <div class="form-row">
19010
+ <div class="form-group">
19011
+ <kendo-label text="Refresh Interval (seconds, 0=off)">
19012
+ <kendo-numerictextbox [(ngModel)]="refreshInterval" [min]="0" [step]="10" [format]="'n0'"></kendo-numerictextbox>
19013
+ </kendo-label>
19014
+ </div>
19015
+ <div class="form-group">
19016
+ <kendo-label text="Max Insights">
19017
+ <kendo-numerictextbox [(ngModel)]="maxInsights" [min]="1" [max]="8" [format]="'n0'"></kendo-numerictextbox>
19018
+ </kendo-label>
19019
+ </div>
19020
+ </div>
19021
+ <div class="mm-dialog-actions">
19022
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
19023
+ <button kendoButton themeColor="primary" (click)="onSave()">Save</button>
19024
+ </div>
19025
+ </div>
19026
+ `, styles: [".config-form{display:flex;flex-direction:column;gap:12px;padding:16px}.form-group{display:flex;flex-direction:column;gap:4px}.form-row{display:flex;gap:16px}.form-row .form-group{flex:1}.mm-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}.hint{font-size:.75rem;opacity:.6}\n"] }]
19027
+ }] });
19028
+
19029
+ class ProcessDataService {
19030
+ dataService = inject(MeshBoardDataService);
19031
+ variableService = inject(MeshBoardVariableService);
19032
+ stateService = inject(MeshBoardStateService);
19033
+ getProcessDiagramGQL = inject(GetProcessDiagramDtoGQL);
19034
+ getProcessDiagramsGQL = inject(GetProcessDiagramsDtoGQL);
19035
+ createProcessDiagramGQL = inject(CreateProcessDiagramDtoGQL);
19036
+ updateProcessDiagramGQL = inject(UpdateProcessDiagramDtoGQL);
19037
+ executeRuntimeQueryGQL = inject(ExecuteRuntimeQueryDtoGQL);
19038
+ /**
19039
+ * Loads a list of available process diagrams from the backend
19040
+ *
19041
+ * @param searchText - Optional search text to filter diagrams
19042
+ * @returns List of process diagram summaries
19043
+ */
19044
+ async loadDiagramList(searchText) {
19045
+ try {
19046
+ const result = await firstValueFrom(this.getProcessDiagramsGQL.fetch({
19047
+ first: 100,
19048
+ searchFilter: searchText ? { searchTerm: searchText, language: 'de' } : undefined
19049
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Apollo fetch requires flexible variable typing
19050
+ }));
19051
+ const items = result.data?.runtime?.systemUIProcessDiagram?.items || [];
19052
+ return items
19053
+ .filter((item) => item !== null)
19054
+ .map(item => ({
19055
+ rtId: item.rtId,
19056
+ name: item.name,
19057
+ description: item.description,
19058
+ version: item.version,
19059
+ canvasWidth: item.canvasWidth,
19060
+ canvasHeight: item.canvasHeight
19061
+ }));
19062
+ }
19063
+ catch (error) {
19064
+ console.error('Error loading process diagrams:', error);
19065
+ return [];
19066
+ }
19067
+ }
19068
+ /**
19069
+ * Loads a process diagram configuration from the backend
19070
+ *
19071
+ * @param rtId - The runtime ID of the process diagram
19072
+ * @returns The process diagram configuration
19073
+ */
19074
+ async loadDiagram(rtId) {
19075
+ const result = await firstValueFrom(this.getProcessDiagramGQL.fetch({ variables: { rtId } }));
19076
+ const item = result.data?.runtime?.systemUIProcessDiagram?.items?.[0];
19077
+ if (!item) {
19078
+ throw new Error(`Process diagram not found: ${rtId}`);
19079
+ }
19080
+ // Parse JSON fields
19081
+ const elements = this.parseJsonField(item.elements, []);
19082
+ const itemWithExtensions = item;
19083
+ const primitives = itemWithExtensions.primitives
19084
+ ? this.parseJsonField(itemWithExtensions.primitives, [])
19085
+ : undefined;
19086
+ const symbolInstances = itemWithExtensions.symbolInstances
19087
+ ? this.parseJsonField(itemWithExtensions.symbolInstances, [])
19088
+ : undefined;
19089
+ const connections = this.parseJsonField(item.connections, []);
19090
+ const variables = item.variables ? this.parseJsonField(item.variables, []) : undefined;
19091
+ // Parse diagram-level property fields
19092
+ const transformProperties = itemWithExtensions.transformProperties
19093
+ ? this.parseJsonField(itemWithExtensions.transformProperties, [])
19094
+ : undefined;
19095
+ const propertyBindings = itemWithExtensions.propertyBindings
19096
+ ? this.parseJsonField(itemWithExtensions.propertyBindings, [])
19097
+ : undefined;
19098
+ const animations = itemWithExtensions.animations
19099
+ ? this.parseJsonField(itemWithExtensions.animations, [])
19100
+ : undefined;
19101
+ return {
19102
+ id: item.rtId,
19103
+ name: item.name,
19104
+ description: item.description ?? undefined,
19105
+ version: item.version,
19106
+ canvas: {
19107
+ width: item.canvasWidth,
19108
+ height: item.canvasHeight,
19109
+ backgroundColor: item.canvasBackgroundColor ?? undefined
19110
+ },
19111
+ elements,
19112
+ primitives,
19113
+ symbolInstances,
19114
+ connections,
19115
+ variables,
19116
+ transformProperties,
19117
+ propertyBindings,
19118
+ animations,
19119
+ refreshInterval: item.refreshInterval ?? undefined
19120
+ };
19121
+ }
19122
+ /**
19123
+ * Creates a new process diagram in the backend
19124
+ *
19125
+ * @param diagram - The process diagram configuration to create
19126
+ * @returns The created diagram with its new rtId
19127
+ */
19128
+ async createDiagram(diagram) {
19129
+ const input = this.toInputDto(diagram);
19130
+ const result = await firstValueFrom(this.createProcessDiagramGQL.mutate({
19131
+ variables: { entities: [input] }
19132
+ }));
19133
+ const created = result.data?.runtime?.systemUIProcessDiagrams?.create?.[0];
19134
+ if (!created) {
19135
+ throw new Error('Failed to create process diagram');
19136
+ }
19137
+ return {
19138
+ ...diagram,
19139
+ id: created.rtId
19140
+ };
19141
+ }
19142
+ /**
19143
+ * Updates an existing process diagram in the backend
19144
+ *
19145
+ * @param diagram - The process diagram configuration to update
19146
+ * @returns The updated diagram
19147
+ */
19148
+ async updateDiagram(diagram) {
19149
+ if (!diagram.id) {
19150
+ throw new Error('Cannot update diagram without rtId');
19151
+ }
19152
+ const input = {
19153
+ rtId: diagram.id,
19154
+ item: this.toInputDto(diagram)
19155
+ };
19156
+ const result = await firstValueFrom(this.updateProcessDiagramGQL.mutate({
19157
+ variables: { entities: [input] }
19158
+ }));
19159
+ const updated = result.data?.runtime?.systemUIProcessDiagrams?.update?.[0];
19160
+ if (!updated) {
19161
+ throw new Error('Failed to update process diagram');
19162
+ }
19163
+ return diagram;
19164
+ }
19165
+ /**
19166
+ * Saves a process diagram (creates new or updates existing)
19167
+ *
19168
+ * @param diagram - The process diagram configuration to save
19169
+ * @param isNew - Whether this is a new diagram (default: false)
19170
+ * @returns The saved diagram
19171
+ */
17425
19172
  async saveDiagram(diagram, isNew = false) {
17426
19173
  if (isNew || !diagram.id) {
17427
19174
  return this.createDiagram(diagram);
@@ -23011,6 +24758,7 @@ function registerDefaultWidgets(registry) {
23011
24758
  initialPrefix: kpiWidget.prefix,
23012
24759
  initialSuffix: kpiWidget.suffix,
23013
24760
  initialTrend: kpiWidget.trend,
24761
+ initialComparisonText: kpiWidget.comparisonText,
23014
24762
  // Filters
23015
24763
  initialFilters: kpiWidget.filters
23016
24764
  };
@@ -23135,7 +24883,8 @@ function registerDefaultWidgets(registry) {
23135
24883
  prefix: config['prefix'],
23136
24884
  suffix: config['suffix'],
23137
24885
  icon: config['icon'],
23138
- trend: config['trend']
24886
+ trend: config['trend'],
24887
+ comparisonText: config['comparisonText']
23139
24888
  };
23140
24889
  }
23141
24890
  if (dataSource.type === 'persistentQuery') {
@@ -23153,6 +24902,7 @@ function registerDefaultWidgets(registry) {
23153
24902
  suffix: config['suffix'],
23154
24903
  icon: config['icon'],
23155
24904
  trend: config['trend'],
24905
+ comparisonText: config['comparisonText'],
23156
24906
  filters: config['filters']
23157
24907
  };
23158
24908
  }
@@ -23167,6 +24917,7 @@ function registerDefaultWidgets(registry) {
23167
24917
  suffix: config['suffix'],
23168
24918
  icon: config['icon'],
23169
24919
  trend: config['trend'],
24920
+ comparisonText: config['comparisonText'],
23170
24921
  filters: config['filters']
23171
24922
  };
23172
24923
  }
@@ -23741,6 +25492,8 @@ function registerDefaultWidgets(registry) {
23741
25492
  initialShowLegend: barWidget.showLegend,
23742
25493
  initialLegendPosition: barWidget.legendPosition,
23743
25494
  initialShowDataLabels: barWidget.showDataLabels,
25495
+ initialColorThresholds: barWidget.colorThresholds,
25496
+ initialDefaultBarColor: barWidget.defaultBarColor,
23744
25497
  initialFilters: barWidget.filters
23745
25498
  };
23746
25499
  },
@@ -23767,6 +25520,8 @@ function registerDefaultWidgets(registry) {
23767
25520
  showLegend: result.showLegend,
23768
25521
  legendPosition: result.legendPosition,
23769
25522
  showDataLabels: result.showDataLabels,
25523
+ colorThresholds: result.colorThresholds,
25524
+ defaultBarColor: result.defaultBarColor,
23770
25525
  filters
23771
25526
  };
23772
25527
  },
@@ -23797,6 +25552,9 @@ function registerDefaultWidgets(registry) {
23797
25552
  showLegend: widget.showLegend,
23798
25553
  legendPosition: widget.legendPosition,
23799
25554
  showDataLabels: widget.showDataLabels,
25555
+ dataLabelSuffix: widget.dataLabelSuffix,
25556
+ colorThresholds: widget.colorThresholds,
25557
+ defaultBarColor: widget.defaultBarColor,
23800
25558
  queryName: widget.dataSource.queryName,
23801
25559
  queryRtId: widget.dataSource.queryRtId,
23802
25560
  filters: widget.filters
@@ -23819,6 +25577,9 @@ function registerDefaultWidgets(registry) {
23819
25577
  showLegend: config['showLegend'] ?? true,
23820
25578
  legendPosition: config['legendPosition'] ?? 'right',
23821
25579
  showDataLabels: config['showDataLabels'] ?? false,
25580
+ dataLabelSuffix: config['dataLabelSuffix'],
25581
+ colorThresholds: config['colorThresholds'],
25582
+ defaultBarColor: config['defaultBarColor'],
23822
25583
  filters: config['filters']
23823
25584
  };
23824
25585
  }
@@ -23848,6 +25609,7 @@ function registerDefaultWidgets(registry) {
23848
25609
  initialShowLegend: lineWidget.showLegend,
23849
25610
  initialLegendPosition: lineWidget.legendPosition,
23850
25611
  initialShowMarkers: lineWidget.showMarkers,
25612
+ initialReferenceLines: lineWidget.referenceLines,
23851
25613
  initialFilters: lineWidget.filters
23852
25614
  };
23853
25615
  },
@@ -23873,6 +25635,7 @@ function registerDefaultWidgets(registry) {
23873
25635
  showLegend: result.showLegend,
23874
25636
  legendPosition: result.legendPosition,
23875
25637
  showMarkers: result.showMarkers,
25638
+ referenceLines: result.referenceLines,
23876
25639
  filters
23877
25640
  };
23878
25641
  },
@@ -23902,6 +25665,7 @@ function registerDefaultWidgets(registry) {
23902
25665
  showLegend: widget.showLegend,
23903
25666
  legendPosition: widget.legendPosition,
23904
25667
  showMarkers: widget.showMarkers,
25668
+ referenceLines: widget.referenceLines,
23905
25669
  queryName: widget.dataSource.queryName,
23906
25670
  queryRtId: widget.dataSource.queryRtId,
23907
25671
  filters: widget.filters
@@ -23923,6 +25687,7 @@ function registerDefaultWidgets(registry) {
23923
25687
  showLegend: config['showLegend'] ?? true,
23924
25688
  legendPosition: config['legendPosition'] ?? 'right',
23925
25689
  showMarkers: config['showMarkers'] ?? false,
25690
+ referenceLines: config['referenceLines'],
23926
25691
  filters: config['filters']
23927
25692
  };
23928
25693
  }
@@ -24360,107 +26125,401 @@ function registerDefaultWidgets(registry) {
24360
26125
  configDialogSize: { width: 750, height: 650, minWidth: 550, minHeight: 450 },
24361
26126
  configDialogTitle: 'Heatmap Configuration',
24362
26127
  defaultSize: { colSpan: 3, rowSpan: 2 },
24363
- supportedDataSources: ['persistentQuery'],
26128
+ supportedDataSources: ['persistentQuery'],
26129
+ getInitialConfig: (widget) => {
26130
+ const heatmapWidget = widget;
26131
+ const dataSource = heatmapWidget.dataSource;
26132
+ const isPersistentQuery = dataSource.type === 'persistentQuery';
26133
+ return {
26134
+ initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
26135
+ initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
26136
+ initialDateField: heatmapWidget.dateField,
26137
+ initialDateEndField: heatmapWidget.dateEndField,
26138
+ initialValueField: heatmapWidget.valueField,
26139
+ initialAggregation: heatmapWidget.aggregation,
26140
+ initialColorScheme: heatmapWidget.colorScheme,
26141
+ initialShowLegend: heatmapWidget.showLegend,
26142
+ initialLegendPosition: heatmapWidget.legendPosition,
26143
+ initialDecimalPlaces: heatmapWidget.decimalPlaces,
26144
+ initialCompactNumbers: heatmapWidget.compactNumbers,
26145
+ initialValueMultiplier: heatmapWidget.valueMultiplier,
26146
+ initialFilters: heatmapWidget.filters
26147
+ };
26148
+ },
26149
+ applyConfigResult: (widget, result) => {
26150
+ const dataSource = {
26151
+ type: 'persistentQuery',
26152
+ queryRtId: result.queryRtId,
26153
+ queryName: result.queryName
26154
+ };
26155
+ const filters = result.filters?.map(f => ({
26156
+ attributePath: f.attributePath,
26157
+ operator: String(f.operator),
26158
+ comparisonValue: f.comparisonValue
26159
+ }));
26160
+ return {
26161
+ ...widget,
26162
+ dataSource,
26163
+ dateField: result.dateField,
26164
+ dateEndField: result.dateEndField,
26165
+ valueField: result.valueField,
26166
+ aggregation: result.aggregation,
26167
+ colorScheme: result.colorScheme,
26168
+ showLegend: result.showLegend,
26169
+ legendPosition: result.legendPosition,
26170
+ decimalPlaces: result.decimalPlaces,
26171
+ compactNumbers: result.compactNumbers,
26172
+ valueMultiplier: result.valueMultiplier,
26173
+ filters
26174
+ };
26175
+ },
26176
+ // SOLID: Factory function
26177
+ createDefaultConfig: (base) => ({
26178
+ ...base,
26179
+ type: 'heatmap',
26180
+ colSpan: 3,
26181
+ rowSpan: 2,
26182
+ dataSource: createDefaultPersistentQueryDataSource(),
26183
+ dateField: '',
26184
+ aggregation: 'count',
26185
+ colorScheme: 'green',
26186
+ showLegend: true,
26187
+ legendPosition: 'bottom'
26188
+ }),
26189
+ // SOLID: Serialization for persistence
26190
+ toPersistedConfig: (widget) => ({
26191
+ dataSourceType: 'persistentQuery',
26192
+ dataSourceRtId: widget.dataSource.queryRtId,
26193
+ config: {
26194
+ dateField: widget.dateField,
26195
+ dateEndField: widget.dateEndField,
26196
+ valueField: widget.valueField,
26197
+ aggregation: widget.aggregation,
26198
+ colorScheme: widget.colorScheme,
26199
+ showLegend: widget.showLegend,
26200
+ legendPosition: widget.legendPosition,
26201
+ decimalPlaces: widget.decimalPlaces,
26202
+ compactNumbers: widget.compactNumbers,
26203
+ valueMultiplier: widget.valueMultiplier,
26204
+ queryName: widget.dataSource.queryName,
26205
+ queryRtId: widget.dataSource.queryRtId,
26206
+ filters: widget.filters
26207
+ }
26208
+ }),
26209
+ // SOLID: Deserialization from persistence
26210
+ fromPersistedConfig: (data, base) => {
26211
+ const config = parseConfig(data);
26212
+ const dataSource = buildDataSourceFromPersisted(data, config);
26213
+ return {
26214
+ ...base,
26215
+ rtId: data.rtId,
26216
+ type: 'heatmap',
26217
+ dataSource,
26218
+ dateField: config['dateField'] ?? '',
26219
+ dateEndField: config['dateEndField'],
26220
+ valueField: config['valueField'],
26221
+ aggregation: config['aggregation'] ?? 'count',
26222
+ colorScheme: config['colorScheme'] ?? 'green',
26223
+ showLegend: config['showLegend'] ?? true,
26224
+ legendPosition: config['legendPosition'] ?? 'bottom',
26225
+ decimalPlaces: config['decimalPlaces'],
26226
+ compactNumbers: config['compactNumbers'],
26227
+ valueMultiplier: config['valueMultiplier'],
26228
+ filters: config['filters']
26229
+ };
26230
+ }
26231
+ });
26232
+ // Status List Widget
26233
+ registry.registerWidget({
26234
+ type: 'statusList',
26235
+ label: 'Status List',
26236
+ component: StatusListWidgetComponent,
26237
+ configDialogComponent: StatusListConfigDialogComponent,
26238
+ configDialogSize: { width: 650, height: 550, minWidth: 500, minHeight: 400 },
26239
+ configDialogTitle: 'Status List Configuration',
26240
+ defaultSize: { colSpan: 2, rowSpan: 2 },
26241
+ supportedDataSources: ['runtimeEntity'],
26242
+ getInitialConfig: (widget) => {
26243
+ const slWidget = widget;
26244
+ return {
26245
+ initialCkTypeId: slWidget.ckTypeId,
26246
+ initialLabelField: slWidget.labelField,
26247
+ initialStatusField: slWidget.statusField,
26248
+ initialStatusColors: slWidget.statusColors
26249
+ };
26250
+ },
26251
+ applyConfigResult: (widget, result) => ({
26252
+ ...widget,
26253
+ ckTypeId: result.ckTypeId,
26254
+ labelField: result.labelField,
26255
+ statusField: result.statusField,
26256
+ statusColors: result.statusColors,
26257
+ dataSource: { type: 'runtimeEntity', ckTypeId: result.ckTypeId }
26258
+ }),
26259
+ createDefaultConfig: (base) => ({
26260
+ ...base,
26261
+ type: 'statusList',
26262
+ colSpan: 3,
26263
+ rowSpan: 1,
26264
+ dataSource: { type: 'runtimeEntity' },
26265
+ ckTypeId: '',
26266
+ labelField: 'name',
26267
+ statusField: ''
26268
+ }),
26269
+ toPersistedConfig: (widget) => ({
26270
+ dataSourceType: 'runtimeEntity',
26271
+ dataSourceCkTypeId: widget.ckTypeId,
26272
+ config: {
26273
+ ckTypeId: widget.ckTypeId,
26274
+ labelField: widget.labelField,
26275
+ statusField: widget.statusField,
26276
+ statusColors: widget.statusColors
26277
+ }
26278
+ }),
26279
+ fromPersistedConfig: (data, base) => {
26280
+ const config = parseConfig(data);
26281
+ return {
26282
+ ...base,
26283
+ rtId: data.rtId,
26284
+ type: 'statusList',
26285
+ dataSource: { type: 'runtimeEntity', ckTypeId: data.ckTypeId ?? config['ckTypeId'] },
26286
+ ckTypeId: config['ckTypeId'] ?? data.ckTypeId ?? '',
26287
+ labelField: config['labelField'] ?? 'name',
26288
+ statusField: config['statusField'] ?? '',
26289
+ statusColors: config['statusColors']
26290
+ };
26291
+ }
26292
+ });
26293
+ // Summary Card Widget
26294
+ registry.registerWidget({
26295
+ type: 'summaryCard',
26296
+ label: 'Summary Card',
26297
+ component: SummaryCardWidgetComponent,
26298
+ configDialogComponent: SummaryCardConfigDialogComponent,
26299
+ configDialogSize: { width: 750, height: 650, minWidth: 600, minHeight: 500 },
26300
+ configDialogTitle: 'Summary Card Configuration',
26301
+ defaultSize: { colSpan: 2, rowSpan: 2 },
26302
+ supportedDataSources: ['runtimeEntity'],
26303
+ getInitialConfig: (widget) => {
26304
+ const scWidget = widget;
26305
+ return {
26306
+ initialColumns: scWidget.columns,
26307
+ initialTiles: scWidget.tiles
26308
+ };
26309
+ },
26310
+ applyConfigResult: (widget, result) => ({
26311
+ ...widget,
26312
+ columns: result.columns,
26313
+ tiles: result.tiles,
26314
+ dataSource: { type: 'runtimeEntity' }
26315
+ }),
26316
+ createDefaultConfig: (base) => ({
26317
+ ...base,
26318
+ type: 'summaryCard',
26319
+ colSpan: 2,
26320
+ rowSpan: 2,
26321
+ dataSource: { type: 'runtimeEntity' },
26322
+ columns: 2,
26323
+ tiles: []
26324
+ }),
26325
+ toPersistedConfig: (widget) => ({
26326
+ dataSourceType: 'runtimeEntity',
26327
+ config: {
26328
+ columns: widget.columns,
26329
+ tiles: widget.tiles
26330
+ }
26331
+ }),
26332
+ fromPersistedConfig: (data, base) => {
26333
+ const config = parseConfig(data);
26334
+ return {
26335
+ ...base,
26336
+ rtId: data.rtId,
26337
+ type: 'summaryCard',
26338
+ dataSource: { type: 'runtimeEntity', ckTypeId: data.ckTypeId ?? 'configured' },
26339
+ columns: config['columns'] ?? 2,
26340
+ tiles: config['tiles'] ?? []
26341
+ };
26342
+ }
26343
+ });
26344
+ // Alert Banner Widget
26345
+ registry.registerWidget({
26346
+ type: 'alertBanner',
26347
+ label: 'Alert Banner',
26348
+ component: AlertBannerWidgetComponent,
26349
+ configDialogComponent: AlertBannerConfigDialogComponent,
26350
+ configDialogSize: { width: 500, height: 400, minWidth: 400, minHeight: 300 },
26351
+ configDialogTitle: 'Alert Banner Configuration',
26352
+ defaultSize: { colSpan: 4, rowSpan: 1 },
26353
+ supportedDataSources: ['runtimeEntity'],
26354
+ getInitialConfig: (widget) => {
26355
+ const abWidget = widget;
26356
+ return {
26357
+ ckTypeId: abWidget.ckTypeId ?? 'System.Notification/StatefulEvent',
26358
+ rotationInterval: abWidget.rotationInterval ?? 5000,
26359
+ showIcon: abWidget.showIcon ?? true,
26360
+ maxAlerts: abWidget.maxAlerts ?? 20
26361
+ };
26362
+ },
26363
+ applyConfigResult: (widget, result) => ({
26364
+ ...widget,
26365
+ ckTypeId: result.ckTypeId,
26366
+ rotationInterval: result.rotationInterval,
26367
+ showIcon: result.showIcon,
26368
+ maxAlerts: result.maxAlerts
26369
+ }),
26370
+ createDefaultConfig: (base) => ({
26371
+ ...base,
26372
+ type: 'alertBanner',
26373
+ colSpan: 4,
26374
+ rowSpan: 1,
26375
+ dataSource: { type: 'runtimeEntity' },
26376
+ ckTypeId: 'System.Notification/StatefulEvent'
26377
+ }),
26378
+ toPersistedConfig: (widget) => ({
26379
+ dataSourceType: 'runtimeEntity',
26380
+ dataSourceCkTypeId: widget.ckTypeId ?? 'System.Notification/StatefulEvent',
26381
+ config: {
26382
+ ckTypeId: widget.ckTypeId,
26383
+ rotationInterval: widget.rotationInterval,
26384
+ showIcon: widget.showIcon,
26385
+ maxAlerts: widget.maxAlerts
26386
+ }
26387
+ }),
26388
+ fromPersistedConfig: (data, base) => {
26389
+ const config = parseConfig(data);
26390
+ return {
26391
+ ...base,
26392
+ rtId: data.rtId,
26393
+ type: 'alertBanner',
26394
+ dataSource: { type: 'runtimeEntity', ckTypeId: data.ckTypeId ?? 'System.Notification/StatefulEvent' },
26395
+ ckTypeId: config['ckTypeId'] ?? data.ckTypeId ?? 'System.Notification/StatefulEvent',
26396
+ rotationInterval: config['rotationInterval'],
26397
+ showIcon: config['showIcon'],
26398
+ maxAlerts: config['maxAlerts']
26399
+ };
26400
+ }
26401
+ });
26402
+ // Alert List Widget
26403
+ registry.registerWidget({
26404
+ type: 'alertList',
26405
+ label: 'Alert List',
26406
+ component: AlertListWidgetComponent,
26407
+ configDialogComponent: AlertListConfigDialogComponent,
26408
+ configDialogSize: { width: 500, height: 400, minWidth: 400, minHeight: 300 },
26409
+ configDialogTitle: 'Alert List Configuration',
26410
+ defaultSize: { colSpan: 3, rowSpan: 3 },
26411
+ supportedDataSources: ['runtimeEntity'],
26412
+ getInitialConfig: (widget) => {
26413
+ const alWidget = widget;
26414
+ return {
26415
+ ckTypeId: alWidget.ckTypeId ?? 'System.Notification/StatefulEvent',
26416
+ showTimestamp: alWidget.showTimestamp ?? true,
26417
+ sortBySeverity: alWidget.sortBySeverity ?? true,
26418
+ maxAlerts: alWidget.maxAlerts ?? 50
26419
+ };
26420
+ },
26421
+ applyConfigResult: (widget, result) => ({
26422
+ ...widget,
26423
+ ckTypeId: result.ckTypeId,
26424
+ showTimestamp: result.showTimestamp,
26425
+ sortBySeverity: result.sortBySeverity,
26426
+ maxAlerts: result.maxAlerts
26427
+ }),
26428
+ createDefaultConfig: (base) => ({
26429
+ ...base,
26430
+ type: 'alertList',
26431
+ colSpan: 3,
26432
+ rowSpan: 3,
26433
+ dataSource: { type: 'runtimeEntity' },
26434
+ ckTypeId: 'System.Notification/StatefulEvent'
26435
+ }),
26436
+ toPersistedConfig: (widget) => ({
26437
+ dataSourceType: 'runtimeEntity',
26438
+ dataSourceCkTypeId: widget.ckTypeId ?? 'System.Notification/StatefulEvent',
26439
+ config: {
26440
+ ckTypeId: widget.ckTypeId,
26441
+ showTimestamp: widget.showTimestamp,
26442
+ sortBySeverity: widget.sortBySeverity,
26443
+ maxAlerts: widget.maxAlerts
26444
+ }
26445
+ }),
26446
+ fromPersistedConfig: (data, base) => {
26447
+ const config = parseConfig(data);
26448
+ return {
26449
+ ...base,
26450
+ rtId: data.rtId,
26451
+ type: 'alertList',
26452
+ dataSource: { type: 'runtimeEntity', ckTypeId: data.ckTypeId ?? 'System.Notification/StatefulEvent' },
26453
+ ckTypeId: config['ckTypeId'] ?? data.ckTypeId ?? 'System.Notification/StatefulEvent',
26454
+ showTimestamp: config['showTimestamp'],
26455
+ sortBySeverity: config['sortBySeverity'],
26456
+ maxAlerts: config['maxAlerts']
26457
+ };
26458
+ }
26459
+ });
26460
+ // AI Insights Widget
26461
+ registry.registerWidget({
26462
+ type: 'aiInsights',
26463
+ label: 'AI Insights',
26464
+ component: AiInsightsWidgetComponent,
26465
+ configDialogComponent: AiInsightsConfigDialogComponent,
26466
+ configDialogSize: { width: 600, height: 500, minWidth: 450, minHeight: 400 },
26467
+ configDialogTitle: 'AI Insights Configuration',
26468
+ defaultSize: { colSpan: 3, rowSpan: 2 },
26469
+ supportedDataSources: ['static'],
24364
26470
  getInitialConfig: (widget) => {
24365
- const heatmapWidget = widget;
24366
- const dataSource = heatmapWidget.dataSource;
24367
- const isPersistentQuery = dataSource.type === 'persistentQuery';
24368
- return {
24369
- initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
24370
- initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
24371
- initialDateField: heatmapWidget.dateField,
24372
- initialDateEndField: heatmapWidget.dateEndField,
24373
- initialValueField: heatmapWidget.valueField,
24374
- initialAggregation: heatmapWidget.aggregation,
24375
- initialColorScheme: heatmapWidget.colorScheme,
24376
- initialShowLegend: heatmapWidget.showLegend,
24377
- initialLegendPosition: heatmapWidget.legendPosition,
24378
- initialDecimalPlaces: heatmapWidget.decimalPlaces,
24379
- initialCompactNumbers: heatmapWidget.compactNumbers,
24380
- initialValueMultiplier: heatmapWidget.valueMultiplier,
24381
- initialFilters: heatmapWidget.filters
24382
- };
24383
- },
24384
- applyConfigResult: (widget, result) => {
24385
- const dataSource = {
24386
- type: 'persistentQuery',
24387
- queryRtId: result.queryRtId,
24388
- queryName: result.queryName
24389
- };
24390
- const filters = result.filters?.map(f => ({
24391
- attributePath: f.attributePath,
24392
- operator: String(f.operator),
24393
- comparisonValue: f.comparisonValue
24394
- }));
26471
+ const aiWidget = widget;
24395
26472
  return {
24396
- ...widget,
24397
- dataSource,
24398
- dateField: result.dateField,
24399
- dateEndField: result.dateEndField,
24400
- valueField: result.valueField,
24401
- aggregation: result.aggregation,
24402
- colorScheme: result.colorScheme,
24403
- showLegend: result.showLegend,
24404
- legendPosition: result.legendPosition,
24405
- decimalPlaces: result.decimalPlaces,
24406
- compactNumbers: result.compactNumbers,
24407
- valueMultiplier: result.valueMultiplier,
24408
- filters
26473
+ apiKey: aiWidget.apiKey,
26474
+ model: aiWidget.model ?? 'claude-sonnet-4-20250514',
26475
+ domainContext: aiWidget.domainContext ?? 'energy management',
26476
+ refreshInterval: aiWidget.refreshInterval ?? 0,
26477
+ maxInsights: aiWidget.maxInsights ?? 4
24409
26478
  };
24410
26479
  },
24411
- // SOLID: Factory function
26480
+ applyConfigResult: (widget, result) => ({
26481
+ ...widget,
26482
+ apiKey: result.apiKey,
26483
+ model: result.model,
26484
+ domainContext: result.domainContext,
26485
+ refreshInterval: result.refreshInterval,
26486
+ maxInsights: result.maxInsights,
26487
+ dataSource: { type: 'static' }
26488
+ }),
24412
26489
  createDefaultConfig: (base) => ({
24413
26490
  ...base,
24414
- type: 'heatmap',
26491
+ type: 'aiInsights',
24415
26492
  colSpan: 3,
24416
26493
  rowSpan: 2,
24417
- dataSource: createDefaultPersistentQueryDataSource(),
24418
- dateField: '',
24419
- aggregation: 'count',
24420
- colorScheme: 'green',
24421
- showLegend: true,
24422
- legendPosition: 'bottom'
26494
+ dataSource: { type: 'static' },
26495
+ refreshInterval: 0,
26496
+ maxInsights: 4,
26497
+ domainContext: 'energy management'
24423
26498
  }),
24424
- // SOLID: Serialization for persistence
24425
26499
  toPersistedConfig: (widget) => ({
24426
- dataSourceType: 'persistentQuery',
24427
- dataSourceRtId: widget.dataSource.queryRtId,
26500
+ dataSourceType: 'static',
24428
26501
  config: {
24429
- dateField: widget.dateField,
24430
- dateEndField: widget.dateEndField,
24431
- valueField: widget.valueField,
24432
- aggregation: widget.aggregation,
24433
- colorScheme: widget.colorScheme,
24434
- showLegend: widget.showLegend,
24435
- legendPosition: widget.legendPosition,
24436
- decimalPlaces: widget.decimalPlaces,
24437
- compactNumbers: widget.compactNumbers,
24438
- valueMultiplier: widget.valueMultiplier,
24439
- queryName: widget.dataSource.queryName,
24440
- queryRtId: widget.dataSource.queryRtId,
24441
- filters: widget.filters
26502
+ apiKey: widget.apiKey,
26503
+ model: widget.model,
26504
+ systemPrompt: widget.systemPrompt,
26505
+ refreshInterval: widget.refreshInterval,
26506
+ maxInsights: widget.maxInsights,
26507
+ domainContext: widget.domainContext
24442
26508
  }
24443
26509
  }),
24444
- // SOLID: Deserialization from persistence
24445
26510
  fromPersistedConfig: (data, base) => {
24446
26511
  const config = parseConfig(data);
24447
- const dataSource = buildDataSourceFromPersisted(data, config);
24448
26512
  return {
24449
26513
  ...base,
24450
26514
  rtId: data.rtId,
24451
- type: 'heatmap',
24452
- dataSource,
24453
- dateField: config['dateField'] ?? '',
24454
- dateEndField: config['dateEndField'],
24455
- valueField: config['valueField'],
24456
- aggregation: config['aggregation'] ?? 'count',
24457
- colorScheme: config['colorScheme'] ?? 'green',
24458
- showLegend: config['showLegend'] ?? true,
24459
- legendPosition: config['legendPosition'] ?? 'bottom',
24460
- decimalPlaces: config['decimalPlaces'],
24461
- compactNumbers: config['compactNumbers'],
24462
- valueMultiplier: config['valueMultiplier'],
24463
- filters: config['filters']
26515
+ type: 'aiInsights',
26516
+ dataSource: { type: 'static' },
26517
+ apiKey: config['apiKey'],
26518
+ model: config['model'],
26519
+ systemPrompt: config['systemPrompt'],
26520
+ refreshInterval: config['refreshInterval'] ?? 0,
26521
+ maxInsights: config['maxInsights'] ?? 4,
26522
+ domainContext: config['domainContext'] ?? 'energy management'
24464
26523
  };
24465
26524
  }
24466
26525
  });
@@ -25355,7 +27414,7 @@ class AddWidgetDialogComponent {
25355
27414
  this.dialogRef.close();
25356
27415
  }
25357
27416
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AddWidgetDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
25358
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: AddWidgetDialogComponent, isStandalone: true, selector: "mm-add-widget-dialog", ngImport: i0, template: "<div class=\"add-widget-dialog\">\n <div class=\"dialog-content\">\n <p class=\"dialog-description\">\n Select a widget type to add to your MeshBoard:\n </p>\n\n <div class=\"widget-types-list\">\n <div class=\"list-header\">Available Widgets</div>\n\n @for (item of widgetTypes(); track item.type) {\n <div\n class=\"widget-type-item\"\n [class.selected]=\"isSelected(item.type)\"\n (click)=\"selectType(item.type)\">\n <div class=\"widget-icon\">\n <kendo-svg-icon [icon]=\"item.icon\" size=\"large\"></kendo-svg-icon>\n </div>\n <div class=\"widget-info\">\n <h4 class=\"widget-label\">{{ item.label }}</h4>\n <p class=\"widget-description\">{{ item.description }}</p>\n </div>\n @if (isSelected(item.type)) {\n <div class=\"selected-indicator\">\n <kendo-svg-icon [icon]=\"checkCircleIcon\" size=\"medium\"></kendo-svg-icon>\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Dialog Actions -->\n <div class=\"dialog-actions mm-dialog-actions\">\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"add()\"\n [disabled]=\"!selectedType()\"\n themeColor=\"primary\">\n Add Widget\n </button>\n </div>\n</div>\n", styles: [".add-widget-dialog{display:flex;flex-direction:column;gap:.75rem;padding:.75rem;max-height:60vh;overflow:hidden}.add-widget-dialog .dialog-content{display:flex;flex-direction:column;gap:1rem;flex:1;min-height:0;overflow:hidden}.add-widget-dialog .dialog-content .dialog-description{margin:0;color:var(--kendo-color-subtle, #757575);font-size:.875rem}.add-widget-dialog .dialog-content .widget-types-list{flex:1;overflow-y:auto;border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:4px}.add-widget-dialog .dialog-content .widget-types-list .list-header{font-weight:600;font-size:.75rem;color:var(--kendo-color-on-app-surface, #424242);padding:.5rem .75rem;background-color:var(--kendo-color-surface-alt, #fafafa);border-bottom:1px solid var(--kendo-color-border, #e0e0e0);text-transform:uppercase;letter-spacing:.5px}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item{display:flex;align-items:center;gap:.5rem;padding:.375rem .5rem;cursor:pointer;border-bottom:1px solid var(--kendo-color-border, #e0e0e0);transition:background-color .2s,box-shadow .2s}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item:last-child{border-bottom:none}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item:hover{background-color:var(--kendo-color-base-hover, #f5f5f5)}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item.selected{background-color:var(--kendo-color-primary-subtle, #e8eaf6);box-shadow:inset 3px 0 0 var(--kendo-color-primary, #3f51b5)}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .widget-icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:4px;background-color:var(--kendo-color-surface-alt, #f5f5f5);color:var(--kendo-color-primary, #3f51b5)}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .widget-icon kendo-svg-icon{color:inherit}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .widget-info{flex:1;min-width:0}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .widget-info .widget-label{margin:0 0 .125rem;font-size:.875rem;font-weight:500;color:var(--kendo-color-on-app-surface, #212121)}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .widget-info .widget-description{margin:0;font-size:.75rem;color:var(--kendo-color-subtle, #757575);line-height:1.3}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .selected-indicator{flex-shrink:0;color:var(--kendo-color-primary, #3f51b5)}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .selected-indicator kendo-svg-icon{color:inherit}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item.selected .widget-icon{background-color:var(--kendo-color-primary, #3f51b5);color:var(--kendo-color-on-primary, #ffffff)}.add-widget-dialog .dialog-actions{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0}.widget-types-list::-webkit-scrollbar{width:8px}.widget-types-list::-webkit-scrollbar-track{background:var(--kendo-color-surface-alt, #f5f5f5)}.widget-types-list::-webkit-scrollbar-thumb{background:var(--kendo-color-border, #e0e0e0);border-radius:4px}.widget-types-list::-webkit-scrollbar-thumb:hover{background:var(--kendo-color-subtle, #9e9e9e)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i2$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }] });
27417
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: AddWidgetDialogComponent, isStandalone: true, selector: "mm-add-widget-dialog", ngImport: i0, template: "<div class=\"add-widget-dialog\">\n <div class=\"dialog-content\">\n <p class=\"dialog-description\">\n Select a widget type to add to your MeshBoard:\n </p>\n\n <div class=\"widget-types-list\">\n <div class=\"list-header\">Available Widgets</div>\n\n @for (item of widgetTypes(); track item.type) {\n <div\n class=\"widget-type-item\"\n [class.selected]=\"isSelected(item.type)\"\n (click)=\"selectType(item.type)\">\n <div class=\"widget-icon\">\n <kendo-svg-icon [icon]=\"item.icon\" size=\"large\"></kendo-svg-icon>\n </div>\n <div class=\"widget-info\">\n <h4 class=\"widget-label\">{{ item.label }}</h4>\n <p class=\"widget-description\">{{ item.description }}</p>\n </div>\n @if (isSelected(item.type)) {\n <div class=\"selected-indicator\">\n <kendo-svg-icon [icon]=\"checkCircleIcon\" size=\"medium\"></kendo-svg-icon>\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Dialog Actions -->\n <div class=\"dialog-actions mm-dialog-actions\">\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"add()\"\n [disabled]=\"!selectedType()\"\n themeColor=\"primary\">\n Add Widget\n </button>\n </div>\n</div>\n", styles: [".add-widget-dialog{display:flex;flex-direction:column;gap:.75rem;padding:.75rem;max-height:60vh;overflow:hidden}.add-widget-dialog .dialog-content{display:flex;flex-direction:column;gap:1rem;flex:1;min-height:0;overflow:hidden}.add-widget-dialog .dialog-content .dialog-description{margin:0;color:var(--kendo-color-subtle, #757575);font-size:.875rem}.add-widget-dialog .dialog-content .widget-types-list{flex:1;overflow-y:auto;border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:4px}.add-widget-dialog .dialog-content .widget-types-list .list-header{font-weight:600;font-size:.75rem;color:var(--kendo-color-on-app-surface, #424242);padding:.5rem .75rem;background-color:var(--kendo-color-surface-alt, #fafafa);border-bottom:1px solid var(--kendo-color-border, #e0e0e0);text-transform:uppercase;letter-spacing:.5px}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item{display:flex;align-items:center;gap:.5rem;padding:.375rem .5rem;cursor:pointer;border-bottom:1px solid var(--kendo-color-border, #e0e0e0);transition:background-color .2s,box-shadow .2s}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item:last-child{border-bottom:none}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item:hover{background-color:var(--kendo-color-base-hover, #f5f5f5)}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item.selected{background-color:var(--kendo-color-primary-subtle, #e8eaf6);box-shadow:inset 3px 0 0 var(--kendo-color-primary, #3f51b5)}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .widget-icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:4px;background-color:var(--kendo-color-surface-alt, #f5f5f5);color:var(--kendo-color-primary, #3f51b5)}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .widget-icon kendo-svg-icon{color:inherit}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .widget-info{flex:1;min-width:0}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .widget-info .widget-label{margin:0 0 .125rem;font-size:.875rem;font-weight:500;color:var(--kendo-color-on-app-surface, #212121)}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .widget-info .widget-description{margin:0;font-size:.75rem;color:var(--kendo-color-subtle, #757575);line-height:1.3}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .selected-indicator{flex-shrink:0;color:var(--kendo-color-primary, #3f51b5)}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item .selected-indicator kendo-svg-icon{color:inherit}.add-widget-dialog .dialog-content .widget-types-list .widget-type-item.selected .widget-icon{background-color:var(--kendo-color-primary, #3f51b5);color:var(--kendo-color-on-primary, #ffffff)}.add-widget-dialog .dialog-actions{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0}.widget-types-list::-webkit-scrollbar{width:8px}.widget-types-list::-webkit-scrollbar-track{background:var(--kendo-color-surface-alt, #f5f5f5)}.widget-types-list::-webkit-scrollbar-thumb{background:var(--kendo-color-border, #e0e0e0);border-radius:4px}.widget-types-list::-webkit-scrollbar-thumb:hover{background:var(--kendo-color-subtle, #9e9e9e)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i1$4.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }] });
25359
27418
  }
25360
27419
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AddWidgetDialogComponent, decorators: [{
25361
27420
  type: Component,
@@ -25735,7 +27794,7 @@ class MeshBoardManagerDialogComponent {
25735
27794
  }
25736
27795
  }
25737
27796
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MeshBoardManagerDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
25738
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MeshBoardManagerDialogComponent, isStandalone: true, selector: "mm-meshboard-manager-dialog", ngImport: i0, template: "<div class=\"meshboard-manager-dialog\">\n <!-- Header with Create/Import Buttons -->\n <div class=\"dialog-header\">\n <p class=\"dialog-description\">Manage your MeshBoards</p>\n <div class=\"header-actions\">\n @if (canExport) {\n <button\n kendoButton\n [svgIcon]=\"uploadIcon\"\n (click)=\"triggerImport()\"\n [disabled]=\"isCreating() || isLoading() || isImporting()\"\n fillMode=\"outline\">\n Import\n </button>\n }\n <button\n kendoButton\n [svgIcon]=\"plusIcon\"\n (click)=\"startCreate()\"\n [disabled]=\"isCreating() || isLoading()\"\n themeColor=\"primary\">\n New MeshBoard\n </button>\n </div>\n </div>\n\n <!-- Create New MeshBoard Form -->\n @if (isCreating()) {\n <div class=\"create-form\">\n <h4 class=\"form-title\">Create New MeshBoard</h4>\n <div class=\"form-fields\">\n <kendo-textbox\n [(ngModel)]=\"newName\"\n placeholder=\"MeshBoard name\"\n [disabled]=\"isLoading()\">\n </kendo-textbox>\n <kendo-textarea\n [(ngModel)]=\"newDescription\"\n placeholder=\"Description (optional)\"\n [rows]=\"2\"\n [disabled]=\"isLoading()\">\n </kendo-textarea>\n </div>\n <div class=\"form-actions\">\n <button\n kendoButton\n [svgIcon]=\"xIcon\"\n (click)=\"cancelCreate()\"\n [disabled]=\"isLoading()\"\n fillMode=\"flat\"\n size=\"small\">\n Cancel\n </button>\n <button\n kendoButton\n [svgIcon]=\"checkIcon\"\n (click)=\"create()\"\n [disabled]=\"newName().trim().length === 0 || isLoading()\"\n themeColor=\"primary\"\n size=\"small\">\n Create\n </button>\n </div>\n </div>\n }\n\n <!-- MeshBoards List -->\n <div class=\"meshboards-list\">\n @if (isLoading()) {\n <div class=\"loading-state\">\n <span class=\"k-loading-text\">Loading...</span>\n </div>\n } @else if (hasMeshBoards()) {\n @for (item of meshBoards(); track item.rtId) {\n <div\n class=\"meshboard-item\"\n [class.active]=\"isActive(item)\"\n [class.editing]=\"isEditing(item)\">\n\n @if (!isEditing(item)) {\n <!-- Display Mode -->\n <div class=\"item-content\" (click)=\"switchTo(item)\">\n <div class=\"item-icon\">\n <kendo-svg-icon [icon]=\"gridLayoutIcon\" size=\"large\"></kendo-svg-icon>\n </div>\n <div class=\"item-info\">\n <h4 class=\"item-name\">\n {{ item.name }}\n @if (isActive(item)) {\n <span class=\"active-badge\">Active</span>\n }\n </h4>\n @if (getDisplayDescription(item)) {\n <p class=\"item-description\">{{ getDisplayDescription(item) }}</p>\n }\n </div>\n <div class=\"item-actions\">\n @if (canExport) {\n <button\n kendoButton\n [svgIcon]=\"downloadIcon\"\n (click)=\"exportMeshBoard(item, $event)\"\n [disabled]=\"isLoading() || isExporting()\"\n fillMode=\"flat\"\n size=\"small\"\n title=\"Export\">\n </button>\n }\n <button\n kendoButton\n [svgIcon]=\"copyIcon\"\n (click)=\"duplicate(item); $event.stopPropagation()\"\n [disabled]=\"isLoading() || isDuplicating()\"\n fillMode=\"flat\"\n size=\"small\"\n title=\"Duplicate\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"pencilIcon\"\n (click)=\"startEdit(item); $event.stopPropagation()\"\n [disabled]=\"isLoading()\"\n fillMode=\"flat\"\n size=\"small\"\n title=\"Edit\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"trashIcon\"\n (click)=\"delete(item); $event.stopPropagation()\"\n [disabled]=\"isLoading()\"\n fillMode=\"flat\"\n size=\"small\"\n themeColor=\"error\"\n title=\"Delete\">\n </button>\n </div>\n </div>\n } @else {\n <!-- Edit Mode -->\n <div class=\"item-edit\">\n <div class=\"edit-fields\">\n <kendo-textbox\n [(ngModel)]=\"editingName\"\n placeholder=\"MeshBoard name\"\n [disabled]=\"isLoading()\">\n </kendo-textbox>\n <kendo-textarea\n [(ngModel)]=\"editingDescription\"\n placeholder=\"Description (optional)\"\n [rows]=\"2\"\n [disabled]=\"isLoading()\">\n </kendo-textarea>\n </div>\n <div class=\"edit-actions mm-dialog-actions\">\n <button\n kendoButton\n [svgIcon]=\"xIcon\"\n (click)=\"cancelEdit()\"\n [disabled]=\"isLoading()\"\n fillMode=\"flat\"\n size=\"small\">\n Cancel\n </button>\n <button\n kendoButton\n [svgIcon]=\"checkIcon\"\n (click)=\"saveEdit()\"\n [disabled]=\"editingName().trim().length === 0 || isLoading()\"\n themeColor=\"primary\"\n size=\"small\">\n Save\n </button>\n </div>\n </div>\n }\n </div>\n }\n } @else {\n <div class=\"empty-state\">\n <kendo-svg-icon [icon]=\"gridLayoutIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h3>No MeshBoards</h3>\n <p>Create your first MeshBoard to get started.</p>\n </div>\n }\n </div>\n\n <!-- Dialog Actions -->\n <div class=\"dialog-actions mm-dialog-actions\">\n <button kendoButton (click)=\"close()\" fillMode=\"flat\">\n Close\n </button>\n </div>\n</div>\n", styles: [".meshboard-manager-dialog{display:flex;flex-direction:column;gap:.75rem;padding:.75rem .75rem 1.5rem;overflow:hidden}.meshboard-manager-dialog .dialog-header{display:flex;justify-content:space-between;align-items:center;gap:1rem}.meshboard-manager-dialog .dialog-header .dialog-description{margin:0;color:var(--kendo-color-subtle, #757575);font-size:.875rem;flex:1}.meshboard-manager-dialog .dialog-header .header-actions{display:flex;gap:.5rem}.meshboard-manager-dialog .create-form{padding:1rem;background-color:var(--kendo-color-surface-alt, #fafafa);border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:4px}.meshboard-manager-dialog .create-form .form-title{margin:0 0 1rem;font-size:1rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-manager-dialog .create-form .form-fields{display:flex;flex-direction:column;gap:.75rem;margin-bottom:1rem}.meshboard-manager-dialog .create-form .form-fields kendo-textbox,.meshboard-manager-dialog .create-form .form-fields kendo-textarea{width:100%}.meshboard-manager-dialog .create-form .form-actions{display:flex;justify-content:flex-end;gap:.5rem}.meshboard-manager-dialog .meshboards-list{flex:1;min-height:250px;overflow-y:auto;border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:4px;background-color:var(--kendo-color-surface, #ffffff)}.meshboard-manager-dialog .meshboards-list .loading-state{display:flex;align-items:center;justify-content:center;padding:2rem;color:var(--kendo-color-subtle, #757575)}.meshboard-manager-dialog .meshboards-list .empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:3rem 2rem;text-align:center}.meshboard-manager-dialog .meshboards-list .empty-state kendo-svg-icon{color:var(--kendo-color-subtle, #9e9e9e);margin-bottom:1rem}.meshboard-manager-dialog .meshboards-list .empty-state h3{margin:.5rem 0;color:var(--kendo-color-on-app-surface, #424242);font-size:1.125rem;font-weight:500}.meshboard-manager-dialog .meshboards-list .empty-state p{margin:0;color:var(--kendo-color-subtle, #757575);font-size:.875rem}.meshboard-manager-dialog .meshboards-list .meshboard-item{border-bottom:1px solid var(--kendo-color-border, #e0e0e0);transition:background-color .2s,box-shadow .2s}.meshboard-manager-dialog .meshboards-list .meshboard-item:last-child{border-bottom:none}.meshboard-manager-dialog .meshboards-list .meshboard-item:not(.active):not(.editing):hover{background-color:var(--kendo-color-base-hover, #f5f5f5)}.meshboard-manager-dialog .meshboards-list .meshboard-item.active{background-color:var(--kendo-color-primary-subtle, #e8eaf6);box-shadow:inset 4px 0 0 var(--kendo-color-primary, #3f51b5)}.meshboard-manager-dialog .meshboards-list .meshboard-item.editing{background-color:var(--kendo-color-surface-alt, #fafafa)}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content{display:flex;align-items:center;gap:1rem;padding:1rem;cursor:pointer}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:48px;height:48px;border-radius:8px;background-color:var(--kendo-color-surface-alt, #f5f5f5);color:var(--kendo-color-primary, #3f51b5)}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-icon kendo-svg-icon{color:inherit}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-info{flex:1;min-width:0}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-info .item-name{margin:0 0 .25rem;font-size:1rem;font-weight:500;color:var(--kendo-color-on-app-surface, #212121);display:flex;align-items:center;gap:.5rem}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-info .item-name .active-badge{display:inline-block;padding:.125rem .5rem;font-size:.75rem;font-weight:600;color:var(--kendo-color-on-primary, #ffffff);background-color:var(--kendo-color-primary, #3f51b5);border-radius:12px}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-info .item-description{margin:0;font-size:.875rem;color:var(--kendo-color-subtle, #757575);line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-actions{flex-shrink:0;display:flex;gap:.25rem;opacity:.5;transition:opacity .2s}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content:hover .item-actions{opacity:1}.meshboard-manager-dialog .meshboards-list .meshboard-item.active .item-content .item-icon{background-color:var(--kendo-color-primary, #3f51b5);color:var(--kendo-color-on-primary, #ffffff)}.meshboard-manager-dialog .meshboards-list .meshboard-item:not(.active) .item-content:after{content:\"Click to switch\";font-size:.75rem;color:var(--kendo-color-subtle, #9e9e9e);opacity:0;transition:opacity .2s;white-space:nowrap}.meshboard-manager-dialog .meshboards-list .meshboard-item:not(.active):hover .item-content:after{opacity:1}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-edit{padding:1rem}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-edit .edit-fields{display:flex;flex-direction:column;gap:.75rem;margin-bottom:.75rem}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-edit .edit-fields kendo-textbox,.meshboard-manager-dialog .meshboards-list .meshboard-item .item-edit .edit-fields kendo-textarea{width:100%}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-edit .edit-actions{display:flex;justify-content:flex-end;gap:.5rem}.meshboard-manager-dialog .dialog-actions{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.75rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;margin-bottom:.5rem}.meshboards-list::-webkit-scrollbar{width:8px}.meshboards-list::-webkit-scrollbar-track{background:var(--kendo-color-surface-alt, #f5f5f5)}.meshboards-list::-webkit-scrollbar-thumb{background:var(--kendo-color-border, #e0e0e0);border-radius:4px}.meshboards-list::-webkit-scrollbar-thumb:hover{background:var(--kendo-color-subtle, #9e9e9e)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "maxResizableRows", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i2$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }] });
27797
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MeshBoardManagerDialogComponent, isStandalone: true, selector: "mm-meshboard-manager-dialog", ngImport: i0, template: "<div class=\"meshboard-manager-dialog\">\n <!-- Header with Create/Import Buttons -->\n <div class=\"dialog-header\">\n <p class=\"dialog-description\">Manage your MeshBoards</p>\n <div class=\"header-actions\">\n @if (canExport) {\n <button\n kendoButton\n [svgIcon]=\"uploadIcon\"\n (click)=\"triggerImport()\"\n [disabled]=\"isCreating() || isLoading() || isImporting()\"\n fillMode=\"outline\">\n Import\n </button>\n }\n <button\n kendoButton\n [svgIcon]=\"plusIcon\"\n (click)=\"startCreate()\"\n [disabled]=\"isCreating() || isLoading()\"\n themeColor=\"primary\">\n New MeshBoard\n </button>\n </div>\n </div>\n\n <!-- Create New MeshBoard Form -->\n @if (isCreating()) {\n <div class=\"create-form\">\n <h4 class=\"form-title\">Create New MeshBoard</h4>\n <div class=\"form-fields\">\n <kendo-textbox\n [(ngModel)]=\"newName\"\n placeholder=\"MeshBoard name\"\n [disabled]=\"isLoading()\">\n </kendo-textbox>\n <kendo-textarea\n [(ngModel)]=\"newDescription\"\n placeholder=\"Description (optional)\"\n [rows]=\"2\"\n [disabled]=\"isLoading()\">\n </kendo-textarea>\n </div>\n <div class=\"form-actions\">\n <button\n kendoButton\n [svgIcon]=\"xIcon\"\n (click)=\"cancelCreate()\"\n [disabled]=\"isLoading()\"\n fillMode=\"flat\"\n size=\"small\">\n Cancel\n </button>\n <button\n kendoButton\n [svgIcon]=\"checkIcon\"\n (click)=\"create()\"\n [disabled]=\"newName().trim().length === 0 || isLoading()\"\n themeColor=\"primary\"\n size=\"small\">\n Create\n </button>\n </div>\n </div>\n }\n\n <!-- MeshBoards List -->\n <div class=\"meshboards-list\">\n @if (isLoading()) {\n <div class=\"loading-state\">\n <span class=\"k-loading-text\">Loading...</span>\n </div>\n } @else if (hasMeshBoards()) {\n @for (item of meshBoards(); track item.rtId) {\n <div\n class=\"meshboard-item\"\n [class.active]=\"isActive(item)\"\n [class.editing]=\"isEditing(item)\">\n\n @if (!isEditing(item)) {\n <!-- Display Mode -->\n <div class=\"item-content\" (click)=\"switchTo(item)\">\n <div class=\"item-icon\">\n <kendo-svg-icon [icon]=\"gridLayoutIcon\" size=\"large\"></kendo-svg-icon>\n </div>\n <div class=\"item-info\">\n <h4 class=\"item-name\">\n {{ item.name }}\n @if (isActive(item)) {\n <span class=\"active-badge\">Active</span>\n }\n </h4>\n @if (getDisplayDescription(item)) {\n <p class=\"item-description\">{{ getDisplayDescription(item) }}</p>\n }\n </div>\n <div class=\"item-actions\">\n @if (canExport) {\n <button\n kendoButton\n [svgIcon]=\"downloadIcon\"\n (click)=\"exportMeshBoard(item, $event)\"\n [disabled]=\"isLoading() || isExporting()\"\n fillMode=\"flat\"\n size=\"small\"\n title=\"Export\">\n </button>\n }\n <button\n kendoButton\n [svgIcon]=\"copyIcon\"\n (click)=\"duplicate(item); $event.stopPropagation()\"\n [disabled]=\"isLoading() || isDuplicating()\"\n fillMode=\"flat\"\n size=\"small\"\n title=\"Duplicate\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"pencilIcon\"\n (click)=\"startEdit(item); $event.stopPropagation()\"\n [disabled]=\"isLoading()\"\n fillMode=\"flat\"\n size=\"small\"\n title=\"Edit\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"trashIcon\"\n (click)=\"delete(item); $event.stopPropagation()\"\n [disabled]=\"isLoading()\"\n fillMode=\"flat\"\n size=\"small\"\n themeColor=\"error\"\n title=\"Delete\">\n </button>\n </div>\n </div>\n } @else {\n <!-- Edit Mode -->\n <div class=\"item-edit\">\n <div class=\"edit-fields\">\n <kendo-textbox\n [(ngModel)]=\"editingName\"\n placeholder=\"MeshBoard name\"\n [disabled]=\"isLoading()\">\n </kendo-textbox>\n <kendo-textarea\n [(ngModel)]=\"editingDescription\"\n placeholder=\"Description (optional)\"\n [rows]=\"2\"\n [disabled]=\"isLoading()\">\n </kendo-textarea>\n </div>\n <div class=\"edit-actions mm-dialog-actions\">\n <button\n kendoButton\n [svgIcon]=\"xIcon\"\n (click)=\"cancelEdit()\"\n [disabled]=\"isLoading()\"\n fillMode=\"flat\"\n size=\"small\">\n Cancel\n </button>\n <button\n kendoButton\n [svgIcon]=\"checkIcon\"\n (click)=\"saveEdit()\"\n [disabled]=\"editingName().trim().length === 0 || isLoading()\"\n themeColor=\"primary\"\n size=\"small\">\n Save\n </button>\n </div>\n </div>\n }\n </div>\n }\n } @else {\n <div class=\"empty-state\">\n <kendo-svg-icon [icon]=\"gridLayoutIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h3>No MeshBoards</h3>\n <p>Create your first MeshBoard to get started.</p>\n </div>\n }\n </div>\n\n <!-- Dialog Actions -->\n <div class=\"dialog-actions mm-dialog-actions\">\n <button kendoButton (click)=\"close()\" fillMode=\"flat\">\n Close\n </button>\n </div>\n</div>\n", styles: [".meshboard-manager-dialog{display:flex;flex-direction:column;gap:.75rem;padding:.75rem .75rem 1.5rem;overflow:hidden}.meshboard-manager-dialog .dialog-header{display:flex;justify-content:space-between;align-items:center;gap:1rem}.meshboard-manager-dialog .dialog-header .dialog-description{margin:0;color:var(--kendo-color-subtle, #757575);font-size:.875rem;flex:1}.meshboard-manager-dialog .dialog-header .header-actions{display:flex;gap:.5rem}.meshboard-manager-dialog .create-form{padding:1rem;background-color:var(--kendo-color-surface-alt, #fafafa);border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:4px}.meshboard-manager-dialog .create-form .form-title{margin:0 0 1rem;font-size:1rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-manager-dialog .create-form .form-fields{display:flex;flex-direction:column;gap:.75rem;margin-bottom:1rem}.meshboard-manager-dialog .create-form .form-fields kendo-textbox,.meshboard-manager-dialog .create-form .form-fields kendo-textarea{width:100%}.meshboard-manager-dialog .create-form .form-actions{display:flex;justify-content:flex-end;gap:.5rem}.meshboard-manager-dialog .meshboards-list{flex:1;min-height:250px;overflow-y:auto;border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:4px;background-color:var(--kendo-color-surface, #ffffff)}.meshboard-manager-dialog .meshboards-list .loading-state{display:flex;align-items:center;justify-content:center;padding:2rem;color:var(--kendo-color-subtle, #757575)}.meshboard-manager-dialog .meshboards-list .empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:3rem 2rem;text-align:center}.meshboard-manager-dialog .meshboards-list .empty-state kendo-svg-icon{color:var(--kendo-color-subtle, #9e9e9e);margin-bottom:1rem}.meshboard-manager-dialog .meshboards-list .empty-state h3{margin:.5rem 0;color:var(--kendo-color-on-app-surface, #424242);font-size:1.125rem;font-weight:500}.meshboard-manager-dialog .meshboards-list .empty-state p{margin:0;color:var(--kendo-color-subtle, #757575);font-size:.875rem}.meshboard-manager-dialog .meshboards-list .meshboard-item{border-bottom:1px solid var(--kendo-color-border, #e0e0e0);transition:background-color .2s,box-shadow .2s}.meshboard-manager-dialog .meshboards-list .meshboard-item:last-child{border-bottom:none}.meshboard-manager-dialog .meshboards-list .meshboard-item:not(.active):not(.editing):hover{background-color:var(--kendo-color-base-hover, #f5f5f5)}.meshboard-manager-dialog .meshboards-list .meshboard-item.active{background-color:var(--kendo-color-primary-subtle, #e8eaf6);box-shadow:inset 4px 0 0 var(--kendo-color-primary, #3f51b5)}.meshboard-manager-dialog .meshboards-list .meshboard-item.editing{background-color:var(--kendo-color-surface-alt, #fafafa)}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content{display:flex;align-items:center;gap:1rem;padding:1rem;cursor:pointer}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:48px;height:48px;border-radius:8px;background-color:var(--kendo-color-surface-alt, #f5f5f5);color:var(--kendo-color-primary, #3f51b5)}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-icon kendo-svg-icon{color:inherit}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-info{flex:1;min-width:0}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-info .item-name{margin:0 0 .25rem;font-size:1rem;font-weight:500;color:var(--kendo-color-on-app-surface, #212121);display:flex;align-items:center;gap:.5rem}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-info .item-name .active-badge{display:inline-block;padding:.125rem .5rem;font-size:.75rem;font-weight:600;color:var(--kendo-color-on-primary, #ffffff);background-color:var(--kendo-color-primary, #3f51b5);border-radius:12px}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-info .item-description{margin:0;font-size:.875rem;color:var(--kendo-color-subtle, #757575);line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content .item-actions{flex-shrink:0;display:flex;gap:.25rem;opacity:.5;transition:opacity .2s}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-content:hover .item-actions{opacity:1}.meshboard-manager-dialog .meshboards-list .meshboard-item.active .item-content .item-icon{background-color:var(--kendo-color-primary, #3f51b5);color:var(--kendo-color-on-primary, #ffffff)}.meshboard-manager-dialog .meshboards-list .meshboard-item:not(.active) .item-content:after{content:\"Click to switch\";font-size:.75rem;color:var(--kendo-color-subtle, #9e9e9e);opacity:0;transition:opacity .2s;white-space:nowrap}.meshboard-manager-dialog .meshboards-list .meshboard-item:not(.active):hover .item-content:after{opacity:1}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-edit{padding:1rem}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-edit .edit-fields{display:flex;flex-direction:column;gap:.75rem;margin-bottom:.75rem}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-edit .edit-fields kendo-textbox,.meshboard-manager-dialog .meshboards-list .meshboard-item .item-edit .edit-fields kendo-textarea{width:100%}.meshboard-manager-dialog .meshboards-list .meshboard-item .item-edit .edit-actions{display:flex;justify-content:flex-end;gap:.5rem}.meshboard-manager-dialog .dialog-actions{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.75rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;margin-bottom:.5rem}.meshboards-list::-webkit-scrollbar{width:8px}.meshboards-list::-webkit-scrollbar-track{background:var(--kendo-color-surface-alt, #f5f5f5)}.meshboards-list::-webkit-scrollbar-thumb{background:var(--kendo-color-border, #e0e0e0);border-radius:4px}.meshboards-list::-webkit-scrollbar-thumb:hover{background:var(--kendo-color-subtle, #9e9e9e)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.TextAreaComponent, selector: "kendo-textarea", inputs: ["focusableId", "flow", "inputAttributes", "adornmentsOrientation", "rows", "cols", "maxlength", "maxResizableRows", "tabindex", "tabIndex", "resizable", "size", "rounded", "fillMode", "showPrefixSeparator", "showSuffixSeparator"], outputs: ["focus", "blur", "valueChange"], exportAs: ["kendoTextArea"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i1$4.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }] });
25739
27798
  }
25740
27799
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MeshBoardManagerDialogComponent, decorators: [{
25741
27800
  type: Component,
@@ -25756,6 +27815,11 @@ class EditWidgetDialogComponent {
25756
27815
  gridService;
25757
27816
  save = new EventEmitter();
25758
27817
  cancelled = new EventEmitter();
27818
+ zoneOptions = [
27819
+ { text: 'Grid', value: 'grid' },
27820
+ { text: 'Banner', value: 'banner' }
27821
+ ];
27822
+ selectedZoneOption = this.zoneOptions[0];
25759
27823
  form = {
25760
27824
  id: '',
25761
27825
  title: '',
@@ -25766,27 +27830,38 @@ class EditWidgetDialogComponent {
25766
27830
  };
25767
27831
  error = '';
25768
27832
  ngOnInit() {
25769
- // Copy widget data to form
27833
+ const zone = this.widget.zone ?? 'grid';
25770
27834
  this.form = {
25771
27835
  id: this.widget.id,
25772
27836
  title: this.widget.title,
25773
27837
  col: this.widget.col,
25774
27838
  row: this.widget.row,
25775
27839
  colSpan: this.widget.colSpan,
25776
- rowSpan: this.widget.rowSpan
27840
+ rowSpan: this.widget.rowSpan,
27841
+ chromeless: this.widget.chromeless ?? false,
27842
+ zone
25777
27843
  };
27844
+ this.selectedZoneOption = this.zoneOptions.find(o => o.value === zone) ?? this.zoneOptions[0];
27845
+ }
27846
+ onZoneChange(option) {
27847
+ this.selectedZoneOption = option;
27848
+ this.form.zone = option.value;
27849
+ this.error = '';
25778
27850
  }
25779
27851
  onSave() {
25780
- // Validate: check for overlaps
25781
- const wouldOverlap = this.gridService.wouldCauseOverlap(this.widgets, this.form.id, this.form.col, this.form.row, this.form.colSpan, this.form.rowSpan);
25782
- if (wouldOverlap) {
25783
- this.error = 'Position overlaps with another widget. Please choose a different position.';
25784
- return;
25785
- }
25786
- // Validate: check bounds
25787
- if (this.gridService.isOutOfBounds(this.form.col, this.form.colSpan, this.maxColumns)) {
25788
- this.error = 'Widget extends beyond the grid. Reduce column or width.';
25789
- return;
27852
+ // Skip grid validation for banner zone widgets
27853
+ if (this.form.zone !== 'banner') {
27854
+ // Validate: check for overlaps
27855
+ const wouldOverlap = this.gridService.wouldCauseOverlap(this.widgets, this.form.id, this.form.col, this.form.row, this.form.colSpan, this.form.rowSpan);
27856
+ if (wouldOverlap) {
27857
+ this.error = 'Position overlaps with another widget. Please choose a different position.';
27858
+ return;
27859
+ }
27860
+ // Validate: check bounds
27861
+ if (this.gridService.isOutOfBounds(this.form.col, this.form.colSpan, this.maxColumns)) {
27862
+ this.error = 'Widget extends beyond the grid. Reduce column or width.';
27863
+ return;
27864
+ }
25790
27865
  }
25791
27866
  this.save.emit(this.form);
25792
27867
  }
@@ -25811,52 +27886,75 @@ class EditWidgetDialogComponent {
25811
27886
  </kendo-textbox>
25812
27887
  </div>
25813
27888
 
25814
- <div class="form-row">
25815
- <div class="form-field">
25816
- <label for="editWidgetCol">Column</label>
25817
- <kendo-numerictextbox
25818
- id="editWidgetCol"
25819
- [(value)]="form.col"
25820
- [min]="1"
25821
- [max]="maxColumns"
25822
- [decimals]="0"
25823
- [format]="'n0'">
25824
- </kendo-numerictextbox>
25825
- </div>
25826
- <div class="form-field">
25827
- <label for="editWidgetRow">Row</label>
25828
- <kendo-numerictextbox
25829
- id="editWidgetRow"
25830
- [(value)]="form.row"
25831
- [min]="1"
25832
- [decimals]="0"
25833
- [format]="'n0'">
25834
- </kendo-numerictextbox>
25835
- </div>
27889
+ <div class="form-field">
27890
+ <label for="editWidgetZone">Zone</label>
27891
+ <kendo-dropdownlist
27892
+ id="editWidgetZone"
27893
+ [data]="zoneOptions"
27894
+ textField="text"
27895
+ valueField="value"
27896
+ [value]="selectedZoneOption"
27897
+ (valueChange)="onZoneChange($event)">
27898
+ </kendo-dropdownlist>
25836
27899
  </div>
25837
27900
 
25838
- <div class="form-row">
25839
- <div class="form-field">
25840
- <label for="editWidgetColSpan">Width (columns)</label>
25841
- <kendo-numerictextbox
25842
- id="editWidgetColSpan"
25843
- [(value)]="form.colSpan"
25844
- [min]="1"
25845
- [max]="maxColumns"
25846
- [decimals]="0"
25847
- [format]="'n0'">
25848
- </kendo-numerictextbox>
27901
+ @if (form.zone !== 'banner') {
27902
+ <div class="form-row">
27903
+ <div class="form-field">
27904
+ <label for="editWidgetCol">Column</label>
27905
+ <kendo-numerictextbox
27906
+ id="editWidgetCol"
27907
+ [(value)]="form.col"
27908
+ [min]="1"
27909
+ [max]="maxColumns"
27910
+ [decimals]="0"
27911
+ [format]="'n0'">
27912
+ </kendo-numerictextbox>
27913
+ </div>
27914
+ <div class="form-field">
27915
+ <label for="editWidgetRow">Row</label>
27916
+ <kendo-numerictextbox
27917
+ id="editWidgetRow"
27918
+ [(value)]="form.row"
27919
+ [min]="1"
27920
+ [decimals]="0"
27921
+ [format]="'n0'">
27922
+ </kendo-numerictextbox>
27923
+ </div>
25849
27924
  </div>
25850
- <div class="form-field">
25851
- <label for="editWidgetRowSpan">Height (rows)</label>
25852
- <kendo-numerictextbox
25853
- id="editWidgetRowSpan"
25854
- [(value)]="form.rowSpan"
25855
- [min]="1"
25856
- [decimals]="0"
25857
- [format]="'n0'">
25858
- </kendo-numerictextbox>
27925
+
27926
+ <div class="form-row">
27927
+ <div class="form-field">
27928
+ <label for="editWidgetColSpan">Width (columns)</label>
27929
+ <kendo-numerictextbox
27930
+ id="editWidgetColSpan"
27931
+ [(value)]="form.colSpan"
27932
+ [min]="1"
27933
+ [max]="maxColumns"
27934
+ [decimals]="0"
27935
+ [format]="'n0'">
27936
+ </kendo-numerictextbox>
27937
+ </div>
27938
+ <div class="form-field">
27939
+ <label for="editWidgetRowSpan">Height (rows)</label>
27940
+ <kendo-numerictextbox
27941
+ id="editWidgetRowSpan"
27942
+ [(value)]="form.rowSpan"
27943
+ [min]="1"
27944
+ [decimals]="0"
27945
+ [format]="'n0'">
27946
+ </kendo-numerictextbox>
27947
+ </div>
27948
+ </div>
27949
+ } @else {
27950
+ <div class="form-hint">
27951
+ Banner widgets are rendered as a full-width stack above the grid.
25859
27952
  </div>
27953
+ }
27954
+
27955
+ <div class="form-field form-field-checkbox">
27956
+ <input type="checkbox" id="editWidgetChromeless" [(ngModel)]="form.chromeless" />
27957
+ <label for="editWidgetChromeless">Hide title bar and border in view mode</label>
25860
27958
  </div>
25861
27959
 
25862
27960
  @if (error) {
@@ -25875,7 +27973,7 @@ class EditWidgetDialogComponent {
25875
27973
  </kendo-dialog-actions>
25876
27974
 
25877
27975
  </kendo-dialog>
25878
- `, isInline: true, styles: [".edit-widget-form{display:flex;flex-direction:column;gap:16px;padding:8px 0}.form-field{display:flex;flex-direction:column;gap:4px}.form-field label{font-weight:500;font-size:.875rem;color:var(--kendo-color-on-app-surface, #212529)}.form-row{display:flex;gap:16px}.form-row .form-field{flex:1}.form-error{color:var(--kendo-color-error, #dc3545);font-size:.875rem;padding:8px;background:var(--kendo-color-error-subtle, rgba(220, 53, 69, .1));border-radius:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i1$4.DialogComponent, selector: "kendo-dialog", inputs: ["actions", "actionsLayout", "autoFocusedElement", "title", "width", "minWidth", "maxWidth", "height", "minHeight", "maxHeight", "animation", "themeColor"], outputs: ["action", "close"], exportAs: ["kendoDialog"] }, { kind: "component", type: i1$4.DialogActionsComponent, selector: "kendo-dialog-actions", inputs: ["actions", "layout"], outputs: ["action"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }] });
27976
+ `, isInline: true, styles: [".edit-widget-form{display:flex;flex-direction:column;gap:16px;padding:8px 0}.form-field{display:flex;flex-direction:column;gap:4px}.form-field label{font-weight:500;font-size:.875rem;color:var(--kendo-color-on-app-surface, #212529)}.form-row{display:flex;gap:16px}.form-row .form-field{flex:1}.form-field-checkbox{flex-direction:row;align-items:center;gap:8px}.form-field-checkbox label{font-weight:400}.form-hint{font-size:.8rem;opacity:.7;padding:8px 12px;border-left:3px solid var(--kendo-color-primary, #0d6efd);background:var(--kendo-color-surface-alt, rgba(0, 0, 0, .05));border-radius:2px}.form-error{color:var(--kendo-color-error, #dc3545);font-size:.875rem;padding:8px;background:var(--kendo-color-error-subtle, rgba(220, 53, 69, .1));border-radius:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i1$5.DialogComponent, selector: "kendo-dialog", inputs: ["actions", "actionsLayout", "autoFocusedElement", "title", "width", "minWidth", "maxWidth", "height", "minHeight", "maxHeight", "animation", "themeColor"], outputs: ["action", "close"], exportAs: ["kendoDialog"] }, { kind: "component", type: i1$5.DialogActionsComponent, selector: "kendo-dialog-actions", inputs: ["actions", "layout"], outputs: ["action"] }, { kind: "ngmodule", type: InputsModule }, { kind: "component", type: i3.TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: i3.NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "ngmodule", type: ButtonsModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: DropDownsModule }, { kind: "component", type: i4.DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }] });
25879
27977
  }
25880
27978
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: EditWidgetDialogComponent, decorators: [{
25881
27979
  type: Component,
@@ -25884,7 +27982,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
25884
27982
  FormsModule,
25885
27983
  DialogModule,
25886
27984
  InputsModule,
25887
- ButtonsModule
27985
+ ButtonsModule,
27986
+ DropDownsModule
25888
27987
  ], template: `
25889
27988
  <kendo-dialog
25890
27989
  title="Edit Widget"
@@ -25902,52 +28001,75 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
25902
28001
  </kendo-textbox>
25903
28002
  </div>
25904
28003
 
25905
- <div class="form-row">
25906
- <div class="form-field">
25907
- <label for="editWidgetCol">Column</label>
25908
- <kendo-numerictextbox
25909
- id="editWidgetCol"
25910
- [(value)]="form.col"
25911
- [min]="1"
25912
- [max]="maxColumns"
25913
- [decimals]="0"
25914
- [format]="'n0'">
25915
- </kendo-numerictextbox>
25916
- </div>
25917
- <div class="form-field">
25918
- <label for="editWidgetRow">Row</label>
25919
- <kendo-numerictextbox
25920
- id="editWidgetRow"
25921
- [(value)]="form.row"
25922
- [min]="1"
25923
- [decimals]="0"
25924
- [format]="'n0'">
25925
- </kendo-numerictextbox>
25926
- </div>
28004
+ <div class="form-field">
28005
+ <label for="editWidgetZone">Zone</label>
28006
+ <kendo-dropdownlist
28007
+ id="editWidgetZone"
28008
+ [data]="zoneOptions"
28009
+ textField="text"
28010
+ valueField="value"
28011
+ [value]="selectedZoneOption"
28012
+ (valueChange)="onZoneChange($event)">
28013
+ </kendo-dropdownlist>
25927
28014
  </div>
25928
28015
 
25929
- <div class="form-row">
25930
- <div class="form-field">
25931
- <label for="editWidgetColSpan">Width (columns)</label>
25932
- <kendo-numerictextbox
25933
- id="editWidgetColSpan"
25934
- [(value)]="form.colSpan"
25935
- [min]="1"
25936
- [max]="maxColumns"
25937
- [decimals]="0"
25938
- [format]="'n0'">
25939
- </kendo-numerictextbox>
28016
+ @if (form.zone !== 'banner') {
28017
+ <div class="form-row">
28018
+ <div class="form-field">
28019
+ <label for="editWidgetCol">Column</label>
28020
+ <kendo-numerictextbox
28021
+ id="editWidgetCol"
28022
+ [(value)]="form.col"
28023
+ [min]="1"
28024
+ [max]="maxColumns"
28025
+ [decimals]="0"
28026
+ [format]="'n0'">
28027
+ </kendo-numerictextbox>
28028
+ </div>
28029
+ <div class="form-field">
28030
+ <label for="editWidgetRow">Row</label>
28031
+ <kendo-numerictextbox
28032
+ id="editWidgetRow"
28033
+ [(value)]="form.row"
28034
+ [min]="1"
28035
+ [decimals]="0"
28036
+ [format]="'n0'">
28037
+ </kendo-numerictextbox>
28038
+ </div>
25940
28039
  </div>
25941
- <div class="form-field">
25942
- <label for="editWidgetRowSpan">Height (rows)</label>
25943
- <kendo-numerictextbox
25944
- id="editWidgetRowSpan"
25945
- [(value)]="form.rowSpan"
25946
- [min]="1"
25947
- [decimals]="0"
25948
- [format]="'n0'">
25949
- </kendo-numerictextbox>
28040
+
28041
+ <div class="form-row">
28042
+ <div class="form-field">
28043
+ <label for="editWidgetColSpan">Width (columns)</label>
28044
+ <kendo-numerictextbox
28045
+ id="editWidgetColSpan"
28046
+ [(value)]="form.colSpan"
28047
+ [min]="1"
28048
+ [max]="maxColumns"
28049
+ [decimals]="0"
28050
+ [format]="'n0'">
28051
+ </kendo-numerictextbox>
28052
+ </div>
28053
+ <div class="form-field">
28054
+ <label for="editWidgetRowSpan">Height (rows)</label>
28055
+ <kendo-numerictextbox
28056
+ id="editWidgetRowSpan"
28057
+ [(value)]="form.rowSpan"
28058
+ [min]="1"
28059
+ [decimals]="0"
28060
+ [format]="'n0'">
28061
+ </kendo-numerictextbox>
28062
+ </div>
28063
+ </div>
28064
+ } @else {
28065
+ <div class="form-hint">
28066
+ Banner widgets are rendered as a full-width stack above the grid.
25950
28067
  </div>
28068
+ }
28069
+
28070
+ <div class="form-field form-field-checkbox">
28071
+ <input type="checkbox" id="editWidgetChromeless" [(ngModel)]="form.chromeless" />
28072
+ <label for="editWidgetChromeless">Hide title bar and border in view mode</label>
25951
28073
  </div>
25952
28074
 
25953
28075
  @if (error) {
@@ -25966,7 +28088,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
25966
28088
  </kendo-dialog-actions>
25967
28089
 
25968
28090
  </kendo-dialog>
25969
- `, styles: [".edit-widget-form{display:flex;flex-direction:column;gap:16px;padding:8px 0}.form-field{display:flex;flex-direction:column;gap:4px}.form-field label{font-weight:500;font-size:.875rem;color:var(--kendo-color-on-app-surface, #212529)}.form-row{display:flex;gap:16px}.form-row .form-field{flex:1}.form-error{color:var(--kendo-color-error, #dc3545);font-size:.875rem;padding:8px;background:var(--kendo-color-error-subtle, rgba(220, 53, 69, .1));border-radius:4px}\n"] }]
28091
+ `, styles: [".edit-widget-form{display:flex;flex-direction:column;gap:16px;padding:8px 0}.form-field{display:flex;flex-direction:column;gap:4px}.form-field label{font-weight:500;font-size:.875rem;color:var(--kendo-color-on-app-surface, #212529)}.form-row{display:flex;gap:16px}.form-row .form-field{flex:1}.form-field-checkbox{flex-direction:row;align-items:center;gap:8px}.form-field-checkbox label{font-weight:400}.form-hint{font-size:.8rem;opacity:.7;padding:8px 12px;border-left:3px solid var(--kendo-color-primary, #0d6efd);background:var(--kendo-color-surface-alt, rgba(0, 0, 0, .05));border-radius:2px}.form-error{color:var(--kendo-color-error, #dc3545);font-size:.875rem;padding:8px;background:var(--kendo-color-error-subtle, rgba(220, 53, 69, .1));border-radius:4px}\n"] }]
25970
28092
  }], propDecorators: { widget: [{
25971
28093
  type: Input
25972
28094
  }], widgets: [{
@@ -26017,6 +28139,9 @@ class MeshBoardViewComponent {
26017
28139
  undoIcon = undoIcon;
26018
28140
  copyIcon = copyIcon;
26019
28141
  infoCircleIcon = infoCircleIcon;
28142
+ arrowUpIcon = arrowUpIcon;
28143
+ arrowDownIcon = arrowDownIcon;
28144
+ tileLayout;
26020
28145
  // Edit widget dialog state
26021
28146
  showEditWidgetDialog = false;
26022
28147
  editingWidget = null;
@@ -26049,6 +28174,9 @@ class MeshBoardViewComponent {
26049
28174
  }, ...(ngDevMode ? [{ debugName: "meshBoardPageLink" }] : /* istanbul ignore next */ []));
26050
28175
  // Computed
26051
28176
  hasWidgets = computed(() => this.config().widgets.length > 0, ...(ngDevMode ? [{ debugName: "hasWidgets" }] : /* istanbul ignore next */ []));
28177
+ bannerWidgets = computed(() => this.config().widgets.filter(w => w.zone === 'banner'), ...(ngDevMode ? [{ debugName: "bannerWidgets" }] : /* istanbul ignore next */ []));
28178
+ gridWidgets = computed(() => this.config().widgets.filter(w => w.zone !== 'banner'), ...(ngDevMode ? [{ debugName: "gridWidgets" }] : /* istanbul ignore next */ []));
28179
+ hasGridWidgets = computed(() => this.gridWidgets().length > 0, ...(ngDevMode ? [{ debugName: "hasGridWidgets" }] : /* istanbul ignore next */ []));
26052
28180
  canSave = computed(() => this.isEditMode() && !this.isSaving(), ...(ngDevMode ? [{ debugName: "canSave" }] : /* istanbul ignore next */ []));
26053
28181
  // Time Filter computed signals
26054
28182
  isTimeFilterEnabled = computed(() => this.stateService.isTimeFilterEnabled(), ...(ngDevMode ? [{ debugName: "isTimeFilterEnabled" }] : /* istanbul ignore next */ []));
@@ -26714,6 +28842,50 @@ class MeshBoardViewComponent {
26714
28842
  this.editModeService.enterEditMode(this.stateService.getConfig());
26715
28843
  }
26716
28844
  }
28845
+ /**
28846
+ * Moves a banner widget up (earlier) in the widgets array.
28847
+ */
28848
+ moveBannerUp(widgetId) {
28849
+ this.stateService.updateConfig(config => {
28850
+ const widgets = [...config.widgets];
28851
+ const index = widgets.findIndex(w => w.id === widgetId);
28852
+ if (index <= 0)
28853
+ return config;
28854
+ // Find previous banner widget
28855
+ for (let i = index - 1; i >= 0; i--) {
28856
+ if (widgets[i].zone === 'banner') {
28857
+ [widgets[i], widgets[index]] = [widgets[index], widgets[i]];
28858
+ return { ...config, widgets };
28859
+ }
28860
+ }
28861
+ return config;
28862
+ });
28863
+ if (!this.isEditMode()) {
28864
+ this.editModeService.enterEditMode(this.stateService.getConfig());
28865
+ }
28866
+ }
28867
+ /**
28868
+ * Moves a banner widget down (later) in the widgets array.
28869
+ */
28870
+ moveBannerDown(widgetId) {
28871
+ this.stateService.updateConfig(config => {
28872
+ const widgets = [...config.widgets];
28873
+ const index = widgets.findIndex(w => w.id === widgetId);
28874
+ if (index === -1)
28875
+ return config;
28876
+ // Find next banner widget
28877
+ for (let i = index + 1; i < widgets.length; i++) {
28878
+ if (widgets[i].zone === 'banner') {
28879
+ [widgets[i], widgets[index]] = [widgets[index], widgets[i]];
28880
+ return { ...config, widgets };
28881
+ }
28882
+ }
28883
+ return config;
28884
+ });
28885
+ if (!this.isEditMode()) {
28886
+ this.editModeService.enterEditMode(this.stateService.getConfig());
28887
+ }
28888
+ }
26717
28889
  /**
26718
28890
  * Opens the configuration dialog for a widget.
26719
28891
  * Uses the WidgetRegistryService to open a resizable Kendo Window.
@@ -26750,12 +28922,16 @@ class MeshBoardViewComponent {
26750
28922
  * Handles TileLayout reorder events.
26751
28923
  */
26752
28924
  onReorder(event) {
26753
- // TileLayout handles the DOM reordering, we just need to update our state
26754
- const config = this.stateService.getConfig();
26755
- const widgets = [...config.widgets];
26756
- const [movedWidget] = widgets.splice(event.oldIndex, 1);
26757
- widgets.splice(event.newIndex, 0, movedWidget);
26758
- this.stateService.updateConfig(c => ({ ...c, widgets }));
28925
+ // The event provides the new position of the reordered item explicitly,
28926
+ // because the @Input() properties on TileLayoutItemComponent still reflect
28927
+ // the old Angular binding values at event time.
28928
+ // We use event.newCol/newRow for the moved item and read other items'
28929
+ // positions from the TileLayout after Angular has processed the change.
28930
+ this.syncWidgetPositionsFromEvent({
28931
+ movedItemIndex: event.oldIndex,
28932
+ newCol: event.newCol,
28933
+ newRow: event.newRow
28934
+ });
26759
28935
  // Enter edit mode if not already in it
26760
28936
  if (!this.isEditMode()) {
26761
28937
  this.editModeService.enterEditMode(this.stateService.getConfig());
@@ -26765,26 +28941,85 @@ class MeshBoardViewComponent {
26765
28941
  * Handles TileLayout resize events.
26766
28942
  */
26767
28943
  onResize(event) {
26768
- // Extract the resized item from the event
26769
- const item = event.item;
26770
- // Find the widget in our config that matches this item
26771
- const config = this.stateService.getConfig();
26772
- const widgetIndex = config.widgets.findIndex(w => w.col === item.col && w.row === item.row);
26773
- if (widgetIndex !== -1) {
26774
- const widget = config.widgets[widgetIndex];
26775
- // Update the widget configuration with new size
26776
- this.stateService.updateWidget(widget.id, w => ({
26777
- ...w,
26778
- col: item.col,
26779
- row: item.row,
26780
- colSpan: item.colSpan,
26781
- rowSpan: item.rowSpan
26782
- }));
26783
- // Enter edit mode if not already in it
26784
- if (!this.isEditMode()) {
26785
- this.editModeService.enterEditMode(this.stateService.getConfig());
26786
- }
28944
+ // The event provides the new spans of the resized item explicitly.
28945
+ // We find the resized item by matching its old col/row position.
28946
+ const currentGridWidgets = this.gridWidgets();
28947
+ const resizedIndex = currentGridWidgets.findIndex(w => w.col === event.item.col && w.row === event.item.row);
28948
+ this.syncWidgetPositionsFromEvent({
28949
+ movedItemIndex: resizedIndex,
28950
+ newCol: event.item.col,
28951
+ newRow: event.item.row,
28952
+ newColSpan: event.newColSpan,
28953
+ newRowSpan: event.newRowSpan
28954
+ });
28955
+ // Enter edit mode if not already in it
28956
+ if (!this.isEditMode()) {
28957
+ this.editModeService.enterEditMode(this.stateService.getConfig());
28958
+ }
28959
+ }
28960
+ /**
28961
+ * Syncs widget positions from a TileLayout reorder or resize event.
28962
+ *
28963
+ * At event time, the @Input() properties on TileLayoutItemComponent still
28964
+ * hold their old Angular binding values. The only reliable source for the
28965
+ * changed item's new position are the explicit event properties
28966
+ * (newCol/newRow for reorder, newColSpan/newRowSpan for resize).
28967
+ *
28968
+ * After updating the changed item, we schedule a deferred read of all
28969
+ * item positions from the TileLayout ViewChild (after Angular has processed
28970
+ * the change) to pick up any reflow of other widgets.
28971
+ */
28972
+ syncWidgetPositionsFromEvent(change) {
28973
+ const currentGridWidgets = this.gridWidgets();
28974
+ if (change.movedItemIndex < 0 || change.movedItemIndex >= currentGridWidgets.length) {
28975
+ console.warn('Could not find moved/resized widget at index', change.movedItemIndex);
28976
+ return;
26787
28977
  }
28978
+ const movedWidget = currentGridWidgets[change.movedItemIndex];
28979
+ // Update the moved/resized widget with the explicit event values
28980
+ this.stateService.updateWidget(movedWidget.id, w => ({
28981
+ ...w,
28982
+ col: change.newCol ?? w.col,
28983
+ row: change.newRow ?? w.row,
28984
+ colSpan: change.newColSpan ?? w.colSpan,
28985
+ rowSpan: change.newRowSpan ?? w.rowSpan
28986
+ }));
28987
+ // After Angular processes the change and Kendo re-renders,
28988
+ // read back all item positions to catch any reflow of other widgets.
28989
+ setTimeout(() => this.syncAllWidgetPositionsFromTileLayout(), 0);
28990
+ }
28991
+ /**
28992
+ * Reads the current col/row/colSpan/rowSpan from all TileLayout items
28993
+ * and updates the widget state accordingly.
28994
+ * Called after Angular change detection so @Input() values are current.
28995
+ */
28996
+ syncAllWidgetPositionsFromTileLayout() {
28997
+ if (!this.tileLayout)
28998
+ return;
28999
+ const tileItems = this.tileLayout.items?.toArray();
29000
+ if (!tileItems)
29001
+ return;
29002
+ const currentGridWidgets = this.gridWidgets();
29003
+ if (tileItems.length !== currentGridWidgets.length)
29004
+ return;
29005
+ this.stateService.updateConfig(config => ({
29006
+ ...config,
29007
+ widgets: config.widgets.map(widget => {
29008
+ if (widget.zone === 'banner')
29009
+ return widget;
29010
+ const gridIndex = currentGridWidgets.findIndex(gw => gw.id === widget.id);
29011
+ if (gridIndex === -1 || gridIndex >= tileItems.length)
29012
+ return widget;
29013
+ const tileItem = tileItems[gridIndex];
29014
+ return {
29015
+ ...widget,
29016
+ col: tileItem.col,
29017
+ row: tileItem.row,
29018
+ colSpan: tileItem.colSpan,
29019
+ rowSpan: tileItem.rowSpan
29020
+ };
29021
+ })
29022
+ }));
26788
29023
  }
26789
29024
  /**
26790
29025
  * Track by function for widget rendering.
@@ -26833,7 +29068,9 @@ class MeshBoardViewComponent {
26833
29068
  col: update.col,
26834
29069
  row: update.row,
26835
29070
  colSpan: update.colSpan,
26836
- rowSpan: update.rowSpan
29071
+ rowSpan: update.rowSpan,
29072
+ chromeless: update.chromeless,
29073
+ zone: update.zone
26837
29074
  }));
26838
29075
  this.closeEditWidgetDialog();
26839
29076
  // Enter edit mode if not already in it
@@ -26995,7 +29232,7 @@ class MeshBoardViewComponent {
26995
29232
  }
26996
29233
  }
26997
29234
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MeshBoardViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
26998
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MeshBoardViewComponent, isStandalone: true, selector: "mm-meshboard-view", providers: [{ provide: HAS_UNSAVED_CHANGES, useExisting: MeshBoardViewComponent }], hostDirectives: [{ directive: i1$7.UnsavedChangesDirective }], ngImport: i0, template: "<div class=\"meshboard-view\">\n @if (isLoading()) {\n <div class=\"loading-container\">\n <div class=\"k-loading-mask\">\n <span class=\"k-loading-text\">Loading MeshBoard...</span>\n <div class=\"k-loading-image\"></div>\n <div class=\"k-loading-color\"></div>\n </div>\n </div>\n } @else if (isModelAvailable() === false) {\n <div class=\"model-unavailable\">\n <div class=\"message-card\">\n <kendo-svg-icon [icon]=\"xIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h2>MeshBoard Not Available</h2>\n <p>The MeshBoard feature requires the CK model 'System.UI' version 1.0.1 or higher.</p>\n <p>Please install the required model in your tenant to use this feature.</p>\n </div>\n </div>\n } @else if (notFoundError()) {\n <div class=\"model-unavailable\">\n <div class=\"message-card\">\n <kendo-svg-icon [icon]=\"xIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h2>MeshBoard Not Found</h2>\n <p>{{ notFoundError() }}</p>\n <p>To create a MeshBoard with a Well-Known Name:</p>\n <ol>\n <li>Go to the <a [routerLink]=\"meshBoardPageLink()\">MeshBoard</a> page</li>\n <li>Open Settings</li>\n <li>Set the \"Well-Known Name\" field</li>\n <li>Save the MeshBoard</li>\n </ol>\n </div>\n </div>\n } @else if (isInitialized()) {\n <!-- Toolbar -->\n <div class=\"meshboard-toolbar\">\n <div class=\"toolbar-left\">\n <h2 class=\"meshboard-title\">{{ config().name }}</h2>\n @if (config().description) {\n <span class=\"meshboard-description\">{{ config().description }}</span>\n }\n </div>\n\n <!-- Time Filter (center) -->\n @if (isTimeFilterEnabled()) {\n <div class=\"toolbar-center\">\n <mm-time-range-picker\n [config]=\"timeFilterConfig()?.pickerConfig ?? {}\"\n [labels]=\"timeRangeLabels()\"\n [initialSelection]=\"initialTimeSelection()\"\n (rangeChange)=\"onTimeRangeChange($event)\"\n (selectionChange)=\"onTimeSelectionChange($event)\">\n </mm-time-range-picker>\n @if (canResetTimeFilter()) {\n <button\n kendoButton\n [svgIcon]=\"undoIcon\"\n (click)=\"resetTimeFilterToDefault()\"\n title=\"Reset to default time filter\"\n fillMode=\"flat\"\n size=\"small\">\n </button>\n }\n </div>\n }\n\n <!-- Entity Selectors -->\n @if (hasEntitySelectors()) {\n @if (isTimeFilterEnabled()) {\n <div class=\"toolbar-separator\"></div>\n }\n <div class=\"toolbar-entity-selectors\">\n <mm-entity-selector-toolbar\n [entitySelectors]=\"entitySelectorsConfig()\"\n (entitySelected)=\"onEntitySelectorSelected($event)\"\n (entityCleared)=\"onEntitySelectorCleared($event)\">\n </mm-entity-selector-toolbar>\n </div>\n }\n\n @if (isTimeFilterEnabled() || hasEntitySelectors()) {\n <div class=\"toolbar-separator\"></div>\n }\n <div class=\"toolbar-right mm-toolbar-actions\">\n @if (!isReadonly()) {\n <!-- Manager Button (icon only) -->\n <button\n kendoButton\n [svgIcon]=\"gridLayoutIcon\"\n (click)=\"openManager()\"\n title=\"Manage MeshBoards\"\n fillMode=\"flat\">\n </button>\n\n <!-- Settings Button (icon only) -->\n <button\n kendoButton\n [svgIcon]=\"gearIcon\"\n (click)=\"openSettings()\"\n title=\"MeshBoard Settings\"\n fillMode=\"flat\">\n </button>\n }\n\n <!-- Refresh Button (icon only) -->\n <button\n kendoButton\n [svgIcon]=\"arrowRotateCwIcon\"\n (click)=\"refresh()\"\n title=\"Refresh All Widgets\"\n fillMode=\"flat\">\n </button>\n\n @if (!isReadonly()) {\n <!-- Edit Mode Toggle (icon only) -->\n @if (!isEditMode()) {\n <button\n kendoButton\n [svgIcon]=\"pencilIcon\"\n (click)=\"toggleEditMode()\"\n title=\"Enter Edit Mode\"\n fillMode=\"flat\">\n </button>\n } @else {\n <button\n kendoButton\n [svgIcon]=\"xIcon\"\n (click)=\"cancelEdit()\"\n title=\"Cancel Edit Mode\"\n fillMode=\"flat\">\n </button>\n }\n\n <!-- Add Widget Button (with text) -->\n <button\n kendoButton\n [svgIcon]=\"plusIcon\"\n (click)=\"openAddWidget()\"\n title=\"Add Widget\"\n themeColor=\"primary\">\n Add Widget\n </button>\n\n <!-- Save Button (with text) -->\n @if (canSave()) {\n <button\n kendoButton\n [svgIcon]=\"saveIcon\"\n (click)=\"save()\"\n [disabled]=\"isSaving()\"\n title=\"Save MeshBoard\"\n themeColor=\"primary\">\n {{ isSaving() ? 'Saving...' : 'Save' }}\n </button>\n }\n }\n </div>\n </div>\n\n <!-- Entity Selector Hint -->\n @if (unselectedToolbarSelectors().length > 0) {\n <div class=\"entity-selector-hint\">\n <kendo-svg-icon [icon]=\"infoCircleIcon\" size=\"medium\"></kendo-svg-icon>\n <span>\n Please select\n @for (selector of unselectedToolbarSelectors(); track selector.id; let last = $last) {\n <strong>{{ selector.label }}</strong>@if (!last) {, }\n }\n to display dashboard data.\n </span>\n </div>\n }\n\n <!-- Variable Resolution Errors -->\n @if (variableResolutionErrors().length > 0) {\n <div class=\"variable-resolution-errors\">\n <strong>Variable resolution errors:</strong>\n @for (error of variableResolutionErrors(); track error.variableName) {\n <span class=\"error-item\">{{ error.variableName }}: {{ error.message }}</span>\n }\n </div>\n }\n\n <!-- MeshBoard Grid -->\n @if (hasWidgets()) {\n <kendo-tilelayout\n [columns]=\"config().columns\"\n [rowHeight]=\"config().rowHeight\"\n [gap]=\"config().gap\"\n [resizable]=\"isEditMode()\"\n [reorderable]=\"isEditMode()\"\n (reorder)=\"onReorder($event)\"\n (resize)=\"onResize($event)\">\n @for (widget of config().widgets; track trackByWidgetId($index, widget)) {\n <kendo-tilelayout-item\n [col]=\"widget.col\"\n [row]=\"widget.row\"\n [colSpan]=\"widget.colSpan\"\n [rowSpan]=\"widget.rowSpan\">\n <kendo-tilelayout-item-header>\n <div class=\"widget-header\" [class.unconfigured]=\"isWidgetUnconfigured(widget)\">\n <span class=\"widget-title\">{{ widget.title }}</span>\n @if (isWidgetUnconfigured(widget) && !isEditMode()) {\n <span class=\"unconfigured-badge\" title=\"Widget needs configuration\">!</span>\n }\n @if (isEditMode()) {\n <div class=\"widget-actions\" (pointerdown)=\"$event.stopPropagation()\">\n @if (supportsConfiguration(widget)) {\n <button\n kendoButton\n [svgIcon]=\"linkIcon\"\n (click)=\"openWidgetConfig(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"config-widget-btn\"\n [class.needs-config]=\"isWidgetUnconfigured(widget)\"\n [title]=\"isWidgetUnconfigured(widget) ? 'Configure data source (required)' : 'Configure data source'\">\n </button>\n }\n <button\n kendoButton\n [svgIcon]=\"gearIcon\"\n (click)=\"openEditWidgetDialog(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"edit-widget-btn\"\n title=\"Edit position\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"copyIcon\"\n (click)=\"duplicateWidget(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"duplicate-widget-btn\"\n title=\"Duplicate widget\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"trashIcon\"\n (click)=\"removeWidget(widget.id)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"remove-widget-btn\"\n title=\"Delete widget\">\n </button>\n </div>\n }\n </div>\n </kendo-tilelayout-item-header>\n <kendo-tilelayout-item-body>\n @if (getWidgetComponentType(widget.type); as componentType) {\n <ng-container *ngComponentOutlet=\"componentType; inputs: { config: widget }\"></ng-container>\n } @else {\n <div class=\"widget-error\">\n <p>Unknown widget type: {{ widget.type }}</p>\n </div>\n }\n </kendo-tilelayout-item-body>\n </kendo-tilelayout-item>\n }\n </kendo-tilelayout>\n } @else {\n <div class=\"empty-state\">\n <div class=\"empty-state-content\">\n <kendo-svg-icon [icon]=\"plusIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h3>No Widgets</h3>\n <p>Get started by adding widgets to your MeshBoard.</p>\n <button\n kendoButton\n [svgIcon]=\"plusIcon\"\n (click)=\"openAddWidget()\"\n themeColor=\"primary\">\n Add Your First Widget\n </button>\n </div>\n </div>\n }\n\n <!-- Edit Widget Dialog -->\n @if (showEditWidgetDialog && editingWidget) {\n <mm-edit-widget-dialog\n [widget]=\"editingWidget\"\n [widgets]=\"config().widgets\"\n [maxColumns]=\"config().columns\"\n [gridService]=\"gridService\"\n (save)=\"onEditWidgetSave($event)\"\n (cancelled)=\"closeEditWidgetDialog()\">\n </mm-edit-widget-dialog>\n }\n }\n</div>\n", styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0;height:100%;width:100%}.meshboard-view{display:flex;flex-direction:column;flex:1;min-height:0;width:100%;background-color:var(--kendo-color-surface, #f5f5f5)}.loading-container{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.entity-selector-hint{display:flex;align-items:center;gap:8px;padding:10px 16px;margin-bottom:8px;background:color-mix(in srgb,var(--kendo-color-info, #2196f3) 10%,var(--kendo-color-surface-alt, #fafafa));border:1px solid color-mix(in srgb,var(--kendo-color-info, #2196f3) 40%,transparent);border-radius:4px;font-size:13px;color:var(--kendo-color-on-app-surface, #424242)}.entity-selector-hint strong{color:var(--kendo-color-info, #2196f3)}.variable-resolution-errors{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:8px 16px;margin-bottom:8px;background:color-mix(in srgb,var(--kendo-color-error, #f44336) 10%,var(--kendo-color-surface-alt, #fafafa));border:1px solid var(--kendo-color-error, #f44336);border-radius:4px;font-size:13px;color:var(--kendo-color-on-app-surface, #424242)}.variable-resolution-errors strong{color:var(--kendo-color-error, #f44336)}.variable-resolution-errors .error-item{padding:2px 8px;background:color-mix(in srgb,var(--kendo-color-error, #f44336) 8%,transparent);border-radius:3px;font-family:Consolas,Monaco,monospace;font-size:12px}.model-unavailable{display:flex;align-items:center;justify-content:center;height:100%;width:100%;padding:2rem;overflow:hidden;box-sizing:border-box}.model-unavailable .message-card{text-align:center;max-width:500px;padding:2rem;background:var(--kendo-color-surface-alt, #fafafa);border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:8px;overflow:hidden}.model-unavailable .message-card kendo-svg-icon{color:var(--kendo-color-warning, #ff9800);margin-bottom:1rem}.model-unavailable .message-card h2{margin:1rem 0;color:var(--kendo-color-on-app-surface, #424242)}.model-unavailable .message-card p{margin:.5rem 0;color:var(--kendo-color-subtle, #757575);line-height:1.5}.model-unavailable .message-card ol{text-align:left;margin:1rem 0;padding-left:1.5rem;color:var(--kendo-color-subtle, #757575);line-height:1.8}.model-unavailable .message-card ol li{margin:.25rem 0}.model-unavailable .message-card ol a{color:var(--kendo-color-primary, #3f51b5);text-decoration:none}.model-unavailable .message-card ol a:hover{text-decoration:underline}.meshboard-toolbar{display:flex;justify-content:space-between;align-items:flex-end;padding:1rem 1.5rem;background-color:var(--kendo-color-surface-alt, white);border-bottom:1px solid var(--kendo-color-border, #e0e0e0);gap:1rem;flex-shrink:0}.meshboard-toolbar .toolbar-left{display:flex;align-items:baseline;gap:1rem;flex:1;min-width:0;align-self:center}.meshboard-toolbar .toolbar-left .meshboard-title{margin:0;font-size:1.5rem;font-weight:500;color:var(--kendo-color-on-app-surface, #212121);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.meshboard-toolbar .toolbar-left .meshboard-description{color:var(--kendo-color-subtle, #757575);font-size:.875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.meshboard-toolbar .toolbar-center{display:flex;justify-content:center;align-items:flex-end;flex-shrink:0}.meshboard-toolbar .toolbar-center mm-time-range-picker ::ng-deep .time-range-picker{gap:.5rem}.meshboard-toolbar .toolbar-separator{width:1px;align-self:stretch;background-color:var(--kendo-color-border, #e0e0e0);flex-shrink:0}.meshboard-toolbar .toolbar-entity-selectors{display:flex;align-items:flex-end;flex-shrink:0}.meshboard-toolbar .toolbar-right{display:flex;gap:.5rem;align-items:center;flex-shrink:0}.empty-state{display:flex;align-items:flex-start;justify-content:center;height:calc(100% - 80px);width:100%;padding-top:4rem}.empty-state .empty-state-content{text-align:center;padding:2rem}.empty-state .empty-state-content kendo-svg-icon{color:var(--kendo-color-subtle, #9e9e9e);margin-bottom:1rem}.empty-state .empty-state-content h3{margin:1rem 0 .5rem;color:var(--kendo-color-on-app-surface, #424242);font-size:1.25rem;font-weight:500}.empty-state .empty-state-content p{margin:0 0 1.5rem;color:var(--kendo-color-subtle, #757575)}kendo-tilelayout{padding:1.5rem;overflow:auto;flex:1;min-height:0}.widget-header{display:flex;justify-content:space-between;align-items:center;width:100%;gap:.5rem}.widget-header.unconfigured{background-color:#ff98001a}.widget-header .widget-title{flex:1;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.widget-header .unconfigured-badge{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background-color:var(--kendo-color-warning, #ff9800);color:#fff;font-size:12px;font-weight:700;flex-shrink:0}.widget-header .widget-actions{display:flex;align-items:center;gap:.25rem;flex-shrink:0}.widget-header .config-widget-btn,.widget-header .edit-widget-btn,.widget-header .remove-widget-btn{opacity:.6;transition:opacity .2s}.widget-header .config-widget-btn:hover,.widget-header .edit-widget-btn:hover,.widget-header .remove-widget-btn:hover{opacity:1}.widget-header .config-widget-btn.needs-config{color:var(--kendo-color-warning, #ff9800);opacity:1}.widget-header .remove-widget-btn{color:var(--kendo-color-error, #f44336)}.widget-error{display:flex;align-items:center;justify-content:center;height:100%;padding:1rem;color:var(--kendo-color-error, #f44336);text-align:center}.meshboard-view.edit-mode kendo-tilelayout-item:hover{box-shadow:0 0 0 2px var(--kendo-color-primary, #3f51b5)}@media(max-width:768px){.meshboard-toolbar{flex-direction:column;align-items:stretch;gap:.75rem}.meshboard-toolbar .toolbar-left{flex-direction:column;align-items:flex-start;gap:.25rem}.meshboard-toolbar .toolbar-right{flex-wrap:wrap;justify-content:flex-end}kendo-tilelayout{padding:1rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i3$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: TileLayoutModule }, { kind: "component", type: i5.TileLayoutComponent, selector: "kendo-tilelayout", inputs: ["columns", "columnWidth", "gap", "reorderable", "resizable", "rowHeight", "autoFlow", "navigable"], outputs: ["reorder", "resize"] }, { kind: "component", type: i5.TileLayoutItemBodyComponent, selector: "kendo-tilelayout-item-body" }, { kind: "component", type: i5.TileLayoutItemComponent, selector: "kendo-tilelayout-item", inputs: ["title", "rowSpan", "colSpan", "order", "col", "row", "reorderable", "resizable"] }, { kind: "component", type: i5.TileLayoutItemHeaderComponent, selector: "kendo-tilelayout-item-header" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i2$1.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: EditWidgetDialogComponent, selector: "mm-edit-widget-dialog", inputs: ["widget", "widgets", "maxColumns", "gridService"], outputs: ["save", "cancelled"] }, { kind: "component", type: TimeRangePickerComponent, selector: "mm-time-range-picker", inputs: ["config", "labels", "initialSelection"], outputs: ["rangeChange", "rangeChangeISO", "selectionChange"] }, { kind: "component", type: EntitySelectorToolbarComponent, selector: "mm-entity-selector-toolbar", inputs: ["entitySelectors"], outputs: ["entitySelected", "entityCleared"] }] });
29235
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MeshBoardViewComponent, isStandalone: true, selector: "mm-meshboard-view", providers: [{ provide: HAS_UNSAVED_CHANGES, useExisting: MeshBoardViewComponent }], viewQueries: [{ propertyName: "tileLayout", first: true, predicate: TileLayoutComponent, descendants: true }], hostDirectives: [{ directive: i1$8.UnsavedChangesDirective }], ngImport: i0, template: "<div class=\"meshboard-view\">\n @if (isLoading()) {\n <div class=\"loading-container\">\n <div class=\"k-loading-mask\">\n <span class=\"k-loading-text\">Loading MeshBoard...</span>\n <div class=\"k-loading-image\"></div>\n <div class=\"k-loading-color\"></div>\n </div>\n </div>\n } @else if (isModelAvailable() === false) {\n <div class=\"model-unavailable\">\n <div class=\"message-card\">\n <kendo-svg-icon [icon]=\"xIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h2>MeshBoard Not Available</h2>\n <p>The MeshBoard feature requires the CK model 'System.UI' version 1.0.1 or higher.</p>\n <p>Please install the required model in your tenant to use this feature.</p>\n </div>\n </div>\n } @else if (notFoundError()) {\n <div class=\"model-unavailable\">\n <div class=\"message-card\">\n <kendo-svg-icon [icon]=\"xIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h2>MeshBoard Not Found</h2>\n <p>{{ notFoundError() }}</p>\n <p>To create a MeshBoard with a Well-Known Name:</p>\n <ol>\n <li>Go to the <a [routerLink]=\"meshBoardPageLink()\">MeshBoard</a> page</li>\n <li>Open Settings</li>\n <li>Set the \"Well-Known Name\" field</li>\n <li>Save the MeshBoard</li>\n </ol>\n </div>\n </div>\n } @else if (isInitialized()) {\n <!-- Toolbar -->\n <div class=\"meshboard-toolbar\">\n <div class=\"toolbar-left\">\n <h2 class=\"meshboard-title\">{{ config().name }}</h2>\n @if (config().description) {\n <span class=\"meshboard-description\">{{ config().description }}</span>\n }\n </div>\n\n <!-- Time Filter (center) -->\n @if (isTimeFilterEnabled()) {\n <div class=\"toolbar-center\">\n <mm-time-range-picker\n [config]=\"timeFilterConfig()?.pickerConfig ?? {}\"\n [labels]=\"timeRangeLabels()\"\n [initialSelection]=\"initialTimeSelection()\"\n (rangeChange)=\"onTimeRangeChange($event)\"\n (selectionChange)=\"onTimeSelectionChange($event)\">\n </mm-time-range-picker>\n @if (canResetTimeFilter()) {\n <button\n kendoButton\n [svgIcon]=\"undoIcon\"\n (click)=\"resetTimeFilterToDefault()\"\n title=\"Reset to default time filter\"\n fillMode=\"flat\"\n size=\"small\">\n </button>\n }\n </div>\n }\n\n <!-- Entity Selectors -->\n @if (hasEntitySelectors()) {\n @if (isTimeFilterEnabled()) {\n <div class=\"toolbar-separator\"></div>\n }\n <div class=\"toolbar-entity-selectors\">\n <mm-entity-selector-toolbar\n [entitySelectors]=\"entitySelectorsConfig()\"\n (entitySelected)=\"onEntitySelectorSelected($event)\"\n (entityCleared)=\"onEntitySelectorCleared($event)\">\n </mm-entity-selector-toolbar>\n </div>\n }\n\n @if (isTimeFilterEnabled() || hasEntitySelectors()) {\n <div class=\"toolbar-separator\"></div>\n }\n <div class=\"toolbar-right mm-toolbar-actions\">\n @if (!isReadonly()) {\n <!-- Manager Button (icon only) -->\n <button\n kendoButton\n [svgIcon]=\"gridLayoutIcon\"\n (click)=\"openManager()\"\n title=\"Manage MeshBoards\"\n fillMode=\"flat\">\n </button>\n\n <!-- Settings Button (icon only) -->\n <button\n kendoButton\n [svgIcon]=\"gearIcon\"\n (click)=\"openSettings()\"\n title=\"MeshBoard Settings\"\n fillMode=\"flat\">\n </button>\n }\n\n <!-- Refresh Button (icon only) -->\n <button\n kendoButton\n [svgIcon]=\"arrowRotateCwIcon\"\n (click)=\"refresh()\"\n title=\"Refresh All Widgets\"\n fillMode=\"flat\">\n </button>\n\n @if (!isReadonly()) {\n <!-- Edit Mode Toggle (icon only) -->\n @if (!isEditMode()) {\n <button\n kendoButton\n [svgIcon]=\"pencilIcon\"\n (click)=\"toggleEditMode()\"\n title=\"Enter Edit Mode\"\n fillMode=\"flat\">\n </button>\n } @else {\n <button\n kendoButton\n [svgIcon]=\"xIcon\"\n (click)=\"cancelEdit()\"\n title=\"Cancel Edit Mode\"\n fillMode=\"flat\">\n </button>\n }\n\n <!-- Add Widget Button (with text) -->\n <button\n kendoButton\n [svgIcon]=\"plusIcon\"\n (click)=\"openAddWidget()\"\n title=\"Add Widget\"\n themeColor=\"primary\">\n Add Widget\n </button>\n\n <!-- Save Button (with text) -->\n @if (canSave()) {\n <button\n kendoButton\n [svgIcon]=\"saveIcon\"\n (click)=\"save()\"\n [disabled]=\"isSaving()\"\n title=\"Save MeshBoard\"\n themeColor=\"primary\">\n {{ isSaving() ? 'Saving...' : 'Save' }}\n </button>\n }\n }\n </div>\n </div>\n\n <!-- Entity Selector Hint -->\n @if (unselectedToolbarSelectors().length > 0) {\n <div class=\"entity-selector-hint\">\n <kendo-svg-icon [icon]=\"infoCircleIcon\" size=\"medium\"></kendo-svg-icon>\n <span>\n Please select\n @for (selector of unselectedToolbarSelectors(); track selector.id; let last = $last) {\n <strong>{{ selector.label }}</strong>@if (!last) {, }\n }\n to display dashboard data.\n </span>\n </div>\n }\n\n <!-- Variable Resolution Errors -->\n @if (variableResolutionErrors().length > 0) {\n <div class=\"variable-resolution-errors\">\n <strong>Variable resolution errors:</strong>\n @for (error of variableResolutionErrors(); track error.variableName) {\n <span class=\"error-item\">{{ error.variableName }}: {{ error.message }}</span>\n }\n </div>\n }\n\n <!-- Banner Zone: widgets placed in the banner stack above the grid -->\n @if (isEditMode()) {\n <div class=\"zone-label banner-zone-label\">Banner Zone</div>\n }\n @for (widget of bannerWidgets(); track trackByWidgetId($index, widget); let first = $first; let last = $last) {\n <div class=\"banner-zone-item\" [class.banner-edit]=\"isEditMode()\">\n @if (isEditMode()) {\n <div class=\"banner-edit-header\">\n <span class=\"widget-title\">{{ widget.title }}</span>\n <div class=\"widget-actions\">\n <button\n kendoButton\n [svgIcon]=\"arrowUpIcon\"\n (click)=\"moveBannerUp(widget.id)\"\n fillMode=\"flat\"\n size=\"small\"\n [disabled]=\"first\"\n title=\"Move up\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"arrowDownIcon\"\n (click)=\"moveBannerDown(widget.id)\"\n fillMode=\"flat\"\n size=\"small\"\n [disabled]=\"last\"\n title=\"Move down\">\n </button>\n @if (supportsConfiguration(widget)) {\n <button\n kendoButton\n [svgIcon]=\"linkIcon\"\n (click)=\"openWidgetConfig(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"config-widget-btn\"\n [class.needs-config]=\"isWidgetUnconfigured(widget)\"\n [title]=\"isWidgetUnconfigured(widget) ? 'Configure data source (required)' : 'Configure data source'\">\n </button>\n }\n <button\n kendoButton\n [svgIcon]=\"gearIcon\"\n (click)=\"openEditWidgetDialog(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"edit-widget-btn\"\n title=\"Edit widget\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"trashIcon\"\n (click)=\"removeWidget(widget.id)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"remove-widget-btn\"\n title=\"Delete widget\">\n </button>\n </div>\n </div>\n }\n @if (getWidgetComponentType(widget.type); as componentType) {\n <ng-container *ngComponentOutlet=\"componentType; inputs: { config: widget }\"></ng-container>\n }\n </div>\n }\n\n <!-- MeshBoard Grid -->\n @if (isEditMode()) {\n <div class=\"zone-label grid-zone-label\">Grid Zone</div>\n }\n @if (hasGridWidgets()) {\n <kendo-tilelayout\n [columns]=\"config().columns\"\n [rowHeight]=\"config().rowHeight\"\n [gap]=\"config().gap\"\n [class.has-banners]=\"bannerWidgets().length > 0 && !isEditMode()\"\n [resizable]=\"isEditMode()\"\n [reorderable]=\"isEditMode()\"\n (reorder)=\"onReorder($event)\"\n (resize)=\"onResize($event)\">\n @for (widget of gridWidgets(); track trackByWidgetId($index, widget)) {\n <kendo-tilelayout-item\n [col]=\"widget.col\"\n [row]=\"widget.row\"\n [colSpan]=\"widget.colSpan\"\n [rowSpan]=\"widget.rowSpan\">\n <kendo-tilelayout-item-header>\n <div class=\"widget-header\"\n [class.unconfigured]=\"isWidgetUnconfigured(widget)\"\n [class.chromeless-header]=\"widget.chromeless && !isEditMode()\">\n @if (!(widget.chromeless && !isEditMode())) {\n <span class=\"widget-title\">{{ widget.title }}</span>\n @if (isWidgetUnconfigured(widget)) {\n <span class=\"unconfigured-badge\" title=\"Widget needs configuration\">!</span>\n }\n }\n @if (isEditMode()) {\n <div class=\"widget-actions\" (pointerdown)=\"$event.stopPropagation()\">\n @if (supportsConfiguration(widget)) {\n <button\n kendoButton\n [svgIcon]=\"linkIcon\"\n (click)=\"openWidgetConfig(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"config-widget-btn\"\n [class.needs-config]=\"isWidgetUnconfigured(widget)\"\n [title]=\"isWidgetUnconfigured(widget) ? 'Configure data source (required)' : 'Configure data source'\">\n </button>\n }\n <button\n kendoButton\n [svgIcon]=\"gearIcon\"\n (click)=\"openEditWidgetDialog(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"edit-widget-btn\"\n title=\"Edit position\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"copyIcon\"\n (click)=\"duplicateWidget(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"duplicate-widget-btn\"\n title=\"Duplicate widget\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"trashIcon\"\n (click)=\"removeWidget(widget.id)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"remove-widget-btn\"\n title=\"Delete widget\">\n </button>\n </div>\n }\n </div>\n </kendo-tilelayout-item-header>\n <kendo-tilelayout-item-body>\n @if (getWidgetComponentType(widget.type); as componentType) {\n <ng-container *ngComponentOutlet=\"componentType; inputs: { config: widget }\"></ng-container>\n } @else {\n <div class=\"widget-error\">\n <p>Unknown widget type: {{ widget.type }}</p>\n </div>\n }\n </kendo-tilelayout-item-body>\n </kendo-tilelayout-item>\n }\n </kendo-tilelayout>\n } @else {\n <div class=\"empty-state\">\n <div class=\"empty-state-content\">\n <kendo-svg-icon [icon]=\"plusIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h3>No Widgets</h3>\n <p>Get started by adding widgets to your MeshBoard.</p>\n <button\n kendoButton\n [svgIcon]=\"plusIcon\"\n (click)=\"openAddWidget()\"\n themeColor=\"primary\">\n Add Your First Widget\n </button>\n </div>\n </div>\n }\n\n <!-- Edit Widget Dialog -->\n @if (showEditWidgetDialog && editingWidget) {\n <mm-edit-widget-dialog\n [widget]=\"editingWidget\"\n [widgets]=\"config().widgets\"\n [maxColumns]=\"config().columns\"\n [gridService]=\"gridService\"\n (save)=\"onEditWidgetSave($event)\"\n (cancelled)=\"closeEditWidgetDialog()\">\n </mm-edit-widget-dialog>\n }\n }\n</div>\n", styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0;height:100%;width:100%}.meshboard-view{display:flex;flex-direction:column;flex:1;min-height:0;width:100%;background-color:var(--kendo-color-surface, #f5f5f5)}.loading-container{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.entity-selector-hint{display:flex;align-items:center;gap:8px;padding:10px 16px;margin-bottom:8px;background:color-mix(in srgb,var(--kendo-color-info, #2196f3) 10%,var(--kendo-color-surface-alt, #fafafa));border:1px solid color-mix(in srgb,var(--kendo-color-info, #2196f3) 40%,transparent);border-radius:4px;font-size:13px;color:var(--kendo-color-on-app-surface, #424242)}.entity-selector-hint strong{color:var(--kendo-color-info, #2196f3)}.variable-resolution-errors{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:8px 16px;margin-bottom:8px;background:color-mix(in srgb,var(--kendo-color-error, #f44336) 10%,var(--kendo-color-surface-alt, #fafafa));border:1px solid var(--kendo-color-error, #f44336);border-radius:4px;font-size:13px;color:var(--kendo-color-on-app-surface, #424242)}.variable-resolution-errors strong{color:var(--kendo-color-error, #f44336)}.variable-resolution-errors .error-item{padding:2px 8px;background:color-mix(in srgb,var(--kendo-color-error, #f44336) 8%,transparent);border-radius:3px;font-family:Consolas,Monaco,monospace;font-size:12px}.model-unavailable{display:flex;align-items:center;justify-content:center;height:100%;width:100%;padding:2rem;overflow:hidden;box-sizing:border-box}.model-unavailable .message-card{text-align:center;max-width:500px;padding:2rem;background:var(--kendo-color-surface-alt, #fafafa);border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:8px;overflow:hidden}.model-unavailable .message-card kendo-svg-icon{color:var(--kendo-color-warning, #ff9800);margin-bottom:1rem}.model-unavailable .message-card h2{margin:1rem 0;color:var(--kendo-color-on-app-surface, #424242)}.model-unavailable .message-card p{margin:.5rem 0;color:var(--kendo-color-subtle, #757575);line-height:1.5}.model-unavailable .message-card ol{text-align:left;margin:1rem 0;padding-left:1.5rem;color:var(--kendo-color-subtle, #757575);line-height:1.8}.model-unavailable .message-card ol li{margin:.25rem 0}.model-unavailable .message-card ol a{color:var(--kendo-color-primary, #3f51b5);text-decoration:none}.model-unavailable .message-card ol a:hover{text-decoration:underline}.meshboard-toolbar{display:flex;justify-content:space-between;align-items:flex-end;padding:1rem 1.5rem;background-color:var(--kendo-color-surface-alt, white);border-bottom:1px solid var(--kendo-color-border, #e0e0e0);gap:1rem;flex-shrink:0}.meshboard-toolbar .toolbar-left{display:flex;align-items:baseline;gap:1rem;flex:1;min-width:0;align-self:center}.meshboard-toolbar .toolbar-left .meshboard-title{margin:0;font-size:1.5rem;font-weight:500;color:var(--kendo-color-on-app-surface, #212121);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.meshboard-toolbar .toolbar-left .meshboard-description{color:var(--kendo-color-subtle, #757575);font-size:.875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.meshboard-toolbar .toolbar-center{display:flex;justify-content:center;align-items:flex-end;flex-shrink:0}.meshboard-toolbar .toolbar-center mm-time-range-picker ::ng-deep .time-range-picker{gap:.5rem}.meshboard-toolbar .toolbar-separator{width:1px;align-self:stretch;background-color:var(--kendo-color-border, #e0e0e0);flex-shrink:0}.meshboard-toolbar .toolbar-entity-selectors{display:flex;align-items:flex-end;flex-shrink:0}.meshboard-toolbar .toolbar-right{display:flex;gap:.5rem;align-items:center;flex-shrink:0}.empty-state{display:flex;align-items:flex-start;justify-content:center;height:calc(100% - 80px);width:100%;padding-top:4rem}.empty-state .empty-state-content{text-align:center;padding:2rem}.empty-state .empty-state-content kendo-svg-icon{color:var(--kendo-color-subtle, #9e9e9e);margin-bottom:1rem}.empty-state .empty-state-content h3{margin:1rem 0 .5rem;color:var(--kendo-color-on-app-surface, #424242);font-size:1.25rem;font-weight:500}.empty-state .empty-state-content p{margin:0 0 1.5rem;color:var(--kendo-color-subtle, #757575)}kendo-tilelayout{padding:1.5rem;overflow:auto;flex:1;min-height:0}.widget-header{display:flex;justify-content:space-between;align-items:center;width:100%;gap:.5rem}.widget-header.unconfigured{background-color:#ff98001a}.widget-header .widget-title{flex:1;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.widget-header .unconfigured-badge{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background-color:var(--kendo-color-warning, #ff9800);color:#fff;font-size:12px;font-weight:700;flex-shrink:0}.widget-header .widget-actions{display:flex;align-items:center;gap:.25rem;flex-shrink:0}.widget-header .config-widget-btn,.widget-header .edit-widget-btn,.widget-header .remove-widget-btn{opacity:.6;transition:opacity .2s}.widget-header .config-widget-btn:hover,.widget-header .edit-widget-btn:hover,.widget-header .remove-widget-btn:hover{opacity:1}.widget-header .config-widget-btn.needs-config{color:var(--kendo-color-warning, #ff9800);opacity:1}.widget-header .remove-widget-btn{color:var(--kendo-color-error, #f44336)}.widget-error{display:flex;align-items:center;justify-content:center;height:100%;padding:1rem;color:var(--kendo-color-error, #f44336);text-align:center}.meshboard-view.edit-mode kendo-tilelayout-item:hover{box-shadow:0 0 0 2px var(--kendo-color-primary, #3f51b5)}@media(max-width:768px){.meshboard-toolbar{flex-direction:column;align-items:stretch;gap:.75rem}.meshboard-toolbar .toolbar-left{flex-direction:column;align-items:flex-start;gap:.25rem}.meshboard-toolbar .toolbar-right{flex-wrap:wrap;justify-content:flex-end}kendo-tilelayout{padding:1rem}}:host ::ng-deep kendo-tilelayout.has-banners{padding-top:0!important}:host ::ng-deep kendo-tilelayout{padding-bottom:1.5rem!important}.widget-header.chromeless-header{display:none}:host ::ng-deep .k-tilelayout-item-header:has(.chromeless-header){display:none}:host ::ng-deep kendo-tilelayout-item:has(.chromeless-header).k-tilelayout-item,:host ::ng-deep kendo-tilelayout-item:has(.chromeless-header).k-card{border:none;box-shadow:none;background:transparent}.zone-label{font-size:.65rem;text-transform:uppercase;letter-spacing:1.5px;opacity:.4;padding:6px 1rem 2px;font-weight:600}.banner-zone-item{margin:0;padding:8px 1rem}.banner-zone-item.banner-edit{border:1px dashed var(--kendo-color-border, rgba(255, 255, 255, .2));border-radius:4px;margin:0 1rem 12px;padding:4px 0}.banner-edit-header{display:flex;align-items:center;justify-content:space-between;padding:4px 8px;opacity:.7}.banner-edit-header .widget-title{font-size:.75rem;text-transform:uppercase;letter-spacing:.5px}.banner-edit-header .widget-actions{display:flex;gap:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i3$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: TileLayoutModule }, { kind: "component", type: i5.TileLayoutComponent, selector: "kendo-tilelayout", inputs: ["columns", "columnWidth", "gap", "reorderable", "resizable", "rowHeight", "autoFlow", "navigable"], outputs: ["reorder", "resize"] }, { kind: "component", type: i5.TileLayoutItemBodyComponent, selector: "kendo-tilelayout-item-body" }, { kind: "component", type: i5.TileLayoutItemComponent, selector: "kendo-tilelayout-item", inputs: ["title", "rowSpan", "colSpan", "order", "col", "row", "reorderable", "resizable"] }, { kind: "component", type: i5.TileLayoutItemHeaderComponent, selector: "kendo-tilelayout-item-header" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i1$4.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: EditWidgetDialogComponent, selector: "mm-edit-widget-dialog", inputs: ["widget", "widgets", "maxColumns", "gridService"], outputs: ["save", "cancelled"] }, { kind: "component", type: TimeRangePickerComponent, selector: "mm-time-range-picker", inputs: ["config", "labels", "initialSelection"], outputs: ["rangeChange", "rangeChangeISO", "selectionChange"] }, { kind: "component", type: EntitySelectorToolbarComponent, selector: "mm-entity-selector-toolbar", inputs: ["entitySelectors"], outputs: ["entitySelected", "entityCleared"] }] });
26999
29236
  }
27000
29237
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MeshBoardViewComponent, decorators: [{
27001
29238
  type: Component,
@@ -27009,8 +29246,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
27009
29246
  EditWidgetDialogComponent,
27010
29247
  TimeRangePickerComponent,
27011
29248
  EntitySelectorToolbarComponent
27012
- ], hostDirectives: [UnsavedChangesDirective], providers: [{ provide: HAS_UNSAVED_CHANGES, useExisting: MeshBoardViewComponent }], template: "<div class=\"meshboard-view\">\n @if (isLoading()) {\n <div class=\"loading-container\">\n <div class=\"k-loading-mask\">\n <span class=\"k-loading-text\">Loading MeshBoard...</span>\n <div class=\"k-loading-image\"></div>\n <div class=\"k-loading-color\"></div>\n </div>\n </div>\n } @else if (isModelAvailable() === false) {\n <div class=\"model-unavailable\">\n <div class=\"message-card\">\n <kendo-svg-icon [icon]=\"xIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h2>MeshBoard Not Available</h2>\n <p>The MeshBoard feature requires the CK model 'System.UI' version 1.0.1 or higher.</p>\n <p>Please install the required model in your tenant to use this feature.</p>\n </div>\n </div>\n } @else if (notFoundError()) {\n <div class=\"model-unavailable\">\n <div class=\"message-card\">\n <kendo-svg-icon [icon]=\"xIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h2>MeshBoard Not Found</h2>\n <p>{{ notFoundError() }}</p>\n <p>To create a MeshBoard with a Well-Known Name:</p>\n <ol>\n <li>Go to the <a [routerLink]=\"meshBoardPageLink()\">MeshBoard</a> page</li>\n <li>Open Settings</li>\n <li>Set the \"Well-Known Name\" field</li>\n <li>Save the MeshBoard</li>\n </ol>\n </div>\n </div>\n } @else if (isInitialized()) {\n <!-- Toolbar -->\n <div class=\"meshboard-toolbar\">\n <div class=\"toolbar-left\">\n <h2 class=\"meshboard-title\">{{ config().name }}</h2>\n @if (config().description) {\n <span class=\"meshboard-description\">{{ config().description }}</span>\n }\n </div>\n\n <!-- Time Filter (center) -->\n @if (isTimeFilterEnabled()) {\n <div class=\"toolbar-center\">\n <mm-time-range-picker\n [config]=\"timeFilterConfig()?.pickerConfig ?? {}\"\n [labels]=\"timeRangeLabels()\"\n [initialSelection]=\"initialTimeSelection()\"\n (rangeChange)=\"onTimeRangeChange($event)\"\n (selectionChange)=\"onTimeSelectionChange($event)\">\n </mm-time-range-picker>\n @if (canResetTimeFilter()) {\n <button\n kendoButton\n [svgIcon]=\"undoIcon\"\n (click)=\"resetTimeFilterToDefault()\"\n title=\"Reset to default time filter\"\n fillMode=\"flat\"\n size=\"small\">\n </button>\n }\n </div>\n }\n\n <!-- Entity Selectors -->\n @if (hasEntitySelectors()) {\n @if (isTimeFilterEnabled()) {\n <div class=\"toolbar-separator\"></div>\n }\n <div class=\"toolbar-entity-selectors\">\n <mm-entity-selector-toolbar\n [entitySelectors]=\"entitySelectorsConfig()\"\n (entitySelected)=\"onEntitySelectorSelected($event)\"\n (entityCleared)=\"onEntitySelectorCleared($event)\">\n </mm-entity-selector-toolbar>\n </div>\n }\n\n @if (isTimeFilterEnabled() || hasEntitySelectors()) {\n <div class=\"toolbar-separator\"></div>\n }\n <div class=\"toolbar-right mm-toolbar-actions\">\n @if (!isReadonly()) {\n <!-- Manager Button (icon only) -->\n <button\n kendoButton\n [svgIcon]=\"gridLayoutIcon\"\n (click)=\"openManager()\"\n title=\"Manage MeshBoards\"\n fillMode=\"flat\">\n </button>\n\n <!-- Settings Button (icon only) -->\n <button\n kendoButton\n [svgIcon]=\"gearIcon\"\n (click)=\"openSettings()\"\n title=\"MeshBoard Settings\"\n fillMode=\"flat\">\n </button>\n }\n\n <!-- Refresh Button (icon only) -->\n <button\n kendoButton\n [svgIcon]=\"arrowRotateCwIcon\"\n (click)=\"refresh()\"\n title=\"Refresh All Widgets\"\n fillMode=\"flat\">\n </button>\n\n @if (!isReadonly()) {\n <!-- Edit Mode Toggle (icon only) -->\n @if (!isEditMode()) {\n <button\n kendoButton\n [svgIcon]=\"pencilIcon\"\n (click)=\"toggleEditMode()\"\n title=\"Enter Edit Mode\"\n fillMode=\"flat\">\n </button>\n } @else {\n <button\n kendoButton\n [svgIcon]=\"xIcon\"\n (click)=\"cancelEdit()\"\n title=\"Cancel Edit Mode\"\n fillMode=\"flat\">\n </button>\n }\n\n <!-- Add Widget Button (with text) -->\n <button\n kendoButton\n [svgIcon]=\"plusIcon\"\n (click)=\"openAddWidget()\"\n title=\"Add Widget\"\n themeColor=\"primary\">\n Add Widget\n </button>\n\n <!-- Save Button (with text) -->\n @if (canSave()) {\n <button\n kendoButton\n [svgIcon]=\"saveIcon\"\n (click)=\"save()\"\n [disabled]=\"isSaving()\"\n title=\"Save MeshBoard\"\n themeColor=\"primary\">\n {{ isSaving() ? 'Saving...' : 'Save' }}\n </button>\n }\n }\n </div>\n </div>\n\n <!-- Entity Selector Hint -->\n @if (unselectedToolbarSelectors().length > 0) {\n <div class=\"entity-selector-hint\">\n <kendo-svg-icon [icon]=\"infoCircleIcon\" size=\"medium\"></kendo-svg-icon>\n <span>\n Please select\n @for (selector of unselectedToolbarSelectors(); track selector.id; let last = $last) {\n <strong>{{ selector.label }}</strong>@if (!last) {, }\n }\n to display dashboard data.\n </span>\n </div>\n }\n\n <!-- Variable Resolution Errors -->\n @if (variableResolutionErrors().length > 0) {\n <div class=\"variable-resolution-errors\">\n <strong>Variable resolution errors:</strong>\n @for (error of variableResolutionErrors(); track error.variableName) {\n <span class=\"error-item\">{{ error.variableName }}: {{ error.message }}</span>\n }\n </div>\n }\n\n <!-- MeshBoard Grid -->\n @if (hasWidgets()) {\n <kendo-tilelayout\n [columns]=\"config().columns\"\n [rowHeight]=\"config().rowHeight\"\n [gap]=\"config().gap\"\n [resizable]=\"isEditMode()\"\n [reorderable]=\"isEditMode()\"\n (reorder)=\"onReorder($event)\"\n (resize)=\"onResize($event)\">\n @for (widget of config().widgets; track trackByWidgetId($index, widget)) {\n <kendo-tilelayout-item\n [col]=\"widget.col\"\n [row]=\"widget.row\"\n [colSpan]=\"widget.colSpan\"\n [rowSpan]=\"widget.rowSpan\">\n <kendo-tilelayout-item-header>\n <div class=\"widget-header\" [class.unconfigured]=\"isWidgetUnconfigured(widget)\">\n <span class=\"widget-title\">{{ widget.title }}</span>\n @if (isWidgetUnconfigured(widget) && !isEditMode()) {\n <span class=\"unconfigured-badge\" title=\"Widget needs configuration\">!</span>\n }\n @if (isEditMode()) {\n <div class=\"widget-actions\" (pointerdown)=\"$event.stopPropagation()\">\n @if (supportsConfiguration(widget)) {\n <button\n kendoButton\n [svgIcon]=\"linkIcon\"\n (click)=\"openWidgetConfig(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"config-widget-btn\"\n [class.needs-config]=\"isWidgetUnconfigured(widget)\"\n [title]=\"isWidgetUnconfigured(widget) ? 'Configure data source (required)' : 'Configure data source'\">\n </button>\n }\n <button\n kendoButton\n [svgIcon]=\"gearIcon\"\n (click)=\"openEditWidgetDialog(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"edit-widget-btn\"\n title=\"Edit position\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"copyIcon\"\n (click)=\"duplicateWidget(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"duplicate-widget-btn\"\n title=\"Duplicate widget\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"trashIcon\"\n (click)=\"removeWidget(widget.id)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"remove-widget-btn\"\n title=\"Delete widget\">\n </button>\n </div>\n }\n </div>\n </kendo-tilelayout-item-header>\n <kendo-tilelayout-item-body>\n @if (getWidgetComponentType(widget.type); as componentType) {\n <ng-container *ngComponentOutlet=\"componentType; inputs: { config: widget }\"></ng-container>\n } @else {\n <div class=\"widget-error\">\n <p>Unknown widget type: {{ widget.type }}</p>\n </div>\n }\n </kendo-tilelayout-item-body>\n </kendo-tilelayout-item>\n }\n </kendo-tilelayout>\n } @else {\n <div class=\"empty-state\">\n <div class=\"empty-state-content\">\n <kendo-svg-icon [icon]=\"plusIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h3>No Widgets</h3>\n <p>Get started by adding widgets to your MeshBoard.</p>\n <button\n kendoButton\n [svgIcon]=\"plusIcon\"\n (click)=\"openAddWidget()\"\n themeColor=\"primary\">\n Add Your First Widget\n </button>\n </div>\n </div>\n }\n\n <!-- Edit Widget Dialog -->\n @if (showEditWidgetDialog && editingWidget) {\n <mm-edit-widget-dialog\n [widget]=\"editingWidget\"\n [widgets]=\"config().widgets\"\n [maxColumns]=\"config().columns\"\n [gridService]=\"gridService\"\n (save)=\"onEditWidgetSave($event)\"\n (cancelled)=\"closeEditWidgetDialog()\">\n </mm-edit-widget-dialog>\n }\n }\n</div>\n", styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0;height:100%;width:100%}.meshboard-view{display:flex;flex-direction:column;flex:1;min-height:0;width:100%;background-color:var(--kendo-color-surface, #f5f5f5)}.loading-container{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.entity-selector-hint{display:flex;align-items:center;gap:8px;padding:10px 16px;margin-bottom:8px;background:color-mix(in srgb,var(--kendo-color-info, #2196f3) 10%,var(--kendo-color-surface-alt, #fafafa));border:1px solid color-mix(in srgb,var(--kendo-color-info, #2196f3) 40%,transparent);border-radius:4px;font-size:13px;color:var(--kendo-color-on-app-surface, #424242)}.entity-selector-hint strong{color:var(--kendo-color-info, #2196f3)}.variable-resolution-errors{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:8px 16px;margin-bottom:8px;background:color-mix(in srgb,var(--kendo-color-error, #f44336) 10%,var(--kendo-color-surface-alt, #fafafa));border:1px solid var(--kendo-color-error, #f44336);border-radius:4px;font-size:13px;color:var(--kendo-color-on-app-surface, #424242)}.variable-resolution-errors strong{color:var(--kendo-color-error, #f44336)}.variable-resolution-errors .error-item{padding:2px 8px;background:color-mix(in srgb,var(--kendo-color-error, #f44336) 8%,transparent);border-radius:3px;font-family:Consolas,Monaco,monospace;font-size:12px}.model-unavailable{display:flex;align-items:center;justify-content:center;height:100%;width:100%;padding:2rem;overflow:hidden;box-sizing:border-box}.model-unavailable .message-card{text-align:center;max-width:500px;padding:2rem;background:var(--kendo-color-surface-alt, #fafafa);border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:8px;overflow:hidden}.model-unavailable .message-card kendo-svg-icon{color:var(--kendo-color-warning, #ff9800);margin-bottom:1rem}.model-unavailable .message-card h2{margin:1rem 0;color:var(--kendo-color-on-app-surface, #424242)}.model-unavailable .message-card p{margin:.5rem 0;color:var(--kendo-color-subtle, #757575);line-height:1.5}.model-unavailable .message-card ol{text-align:left;margin:1rem 0;padding-left:1.5rem;color:var(--kendo-color-subtle, #757575);line-height:1.8}.model-unavailable .message-card ol li{margin:.25rem 0}.model-unavailable .message-card ol a{color:var(--kendo-color-primary, #3f51b5);text-decoration:none}.model-unavailable .message-card ol a:hover{text-decoration:underline}.meshboard-toolbar{display:flex;justify-content:space-between;align-items:flex-end;padding:1rem 1.5rem;background-color:var(--kendo-color-surface-alt, white);border-bottom:1px solid var(--kendo-color-border, #e0e0e0);gap:1rem;flex-shrink:0}.meshboard-toolbar .toolbar-left{display:flex;align-items:baseline;gap:1rem;flex:1;min-width:0;align-self:center}.meshboard-toolbar .toolbar-left .meshboard-title{margin:0;font-size:1.5rem;font-weight:500;color:var(--kendo-color-on-app-surface, #212121);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.meshboard-toolbar .toolbar-left .meshboard-description{color:var(--kendo-color-subtle, #757575);font-size:.875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.meshboard-toolbar .toolbar-center{display:flex;justify-content:center;align-items:flex-end;flex-shrink:0}.meshboard-toolbar .toolbar-center mm-time-range-picker ::ng-deep .time-range-picker{gap:.5rem}.meshboard-toolbar .toolbar-separator{width:1px;align-self:stretch;background-color:var(--kendo-color-border, #e0e0e0);flex-shrink:0}.meshboard-toolbar .toolbar-entity-selectors{display:flex;align-items:flex-end;flex-shrink:0}.meshboard-toolbar .toolbar-right{display:flex;gap:.5rem;align-items:center;flex-shrink:0}.empty-state{display:flex;align-items:flex-start;justify-content:center;height:calc(100% - 80px);width:100%;padding-top:4rem}.empty-state .empty-state-content{text-align:center;padding:2rem}.empty-state .empty-state-content kendo-svg-icon{color:var(--kendo-color-subtle, #9e9e9e);margin-bottom:1rem}.empty-state .empty-state-content h3{margin:1rem 0 .5rem;color:var(--kendo-color-on-app-surface, #424242);font-size:1.25rem;font-weight:500}.empty-state .empty-state-content p{margin:0 0 1.5rem;color:var(--kendo-color-subtle, #757575)}kendo-tilelayout{padding:1.5rem;overflow:auto;flex:1;min-height:0}.widget-header{display:flex;justify-content:space-between;align-items:center;width:100%;gap:.5rem}.widget-header.unconfigured{background-color:#ff98001a}.widget-header .widget-title{flex:1;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.widget-header .unconfigured-badge{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background-color:var(--kendo-color-warning, #ff9800);color:#fff;font-size:12px;font-weight:700;flex-shrink:0}.widget-header .widget-actions{display:flex;align-items:center;gap:.25rem;flex-shrink:0}.widget-header .config-widget-btn,.widget-header .edit-widget-btn,.widget-header .remove-widget-btn{opacity:.6;transition:opacity .2s}.widget-header .config-widget-btn:hover,.widget-header .edit-widget-btn:hover,.widget-header .remove-widget-btn:hover{opacity:1}.widget-header .config-widget-btn.needs-config{color:var(--kendo-color-warning, #ff9800);opacity:1}.widget-header .remove-widget-btn{color:var(--kendo-color-error, #f44336)}.widget-error{display:flex;align-items:center;justify-content:center;height:100%;padding:1rem;color:var(--kendo-color-error, #f44336);text-align:center}.meshboard-view.edit-mode kendo-tilelayout-item:hover{box-shadow:0 0 0 2px var(--kendo-color-primary, #3f51b5)}@media(max-width:768px){.meshboard-toolbar{flex-direction:column;align-items:stretch;gap:.75rem}.meshboard-toolbar .toolbar-left{flex-direction:column;align-items:flex-start;gap:.25rem}.meshboard-toolbar .toolbar-right{flex-wrap:wrap;justify-content:flex-end}kendo-tilelayout{padding:1rem}}\n"] }]
27013
- }], ctorParameters: () => [] });
29249
+ ], hostDirectives: [UnsavedChangesDirective], providers: [{ provide: HAS_UNSAVED_CHANGES, useExisting: MeshBoardViewComponent }], template: "<div class=\"meshboard-view\">\n @if (isLoading()) {\n <div class=\"loading-container\">\n <div class=\"k-loading-mask\">\n <span class=\"k-loading-text\">Loading MeshBoard...</span>\n <div class=\"k-loading-image\"></div>\n <div class=\"k-loading-color\"></div>\n </div>\n </div>\n } @else if (isModelAvailable() === false) {\n <div class=\"model-unavailable\">\n <div class=\"message-card\">\n <kendo-svg-icon [icon]=\"xIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h2>MeshBoard Not Available</h2>\n <p>The MeshBoard feature requires the CK model 'System.UI' version 1.0.1 or higher.</p>\n <p>Please install the required model in your tenant to use this feature.</p>\n </div>\n </div>\n } @else if (notFoundError()) {\n <div class=\"model-unavailable\">\n <div class=\"message-card\">\n <kendo-svg-icon [icon]=\"xIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h2>MeshBoard Not Found</h2>\n <p>{{ notFoundError() }}</p>\n <p>To create a MeshBoard with a Well-Known Name:</p>\n <ol>\n <li>Go to the <a [routerLink]=\"meshBoardPageLink()\">MeshBoard</a> page</li>\n <li>Open Settings</li>\n <li>Set the \"Well-Known Name\" field</li>\n <li>Save the MeshBoard</li>\n </ol>\n </div>\n </div>\n } @else if (isInitialized()) {\n <!-- Toolbar -->\n <div class=\"meshboard-toolbar\">\n <div class=\"toolbar-left\">\n <h2 class=\"meshboard-title\">{{ config().name }}</h2>\n @if (config().description) {\n <span class=\"meshboard-description\">{{ config().description }}</span>\n }\n </div>\n\n <!-- Time Filter (center) -->\n @if (isTimeFilterEnabled()) {\n <div class=\"toolbar-center\">\n <mm-time-range-picker\n [config]=\"timeFilterConfig()?.pickerConfig ?? {}\"\n [labels]=\"timeRangeLabels()\"\n [initialSelection]=\"initialTimeSelection()\"\n (rangeChange)=\"onTimeRangeChange($event)\"\n (selectionChange)=\"onTimeSelectionChange($event)\">\n </mm-time-range-picker>\n @if (canResetTimeFilter()) {\n <button\n kendoButton\n [svgIcon]=\"undoIcon\"\n (click)=\"resetTimeFilterToDefault()\"\n title=\"Reset to default time filter\"\n fillMode=\"flat\"\n size=\"small\">\n </button>\n }\n </div>\n }\n\n <!-- Entity Selectors -->\n @if (hasEntitySelectors()) {\n @if (isTimeFilterEnabled()) {\n <div class=\"toolbar-separator\"></div>\n }\n <div class=\"toolbar-entity-selectors\">\n <mm-entity-selector-toolbar\n [entitySelectors]=\"entitySelectorsConfig()\"\n (entitySelected)=\"onEntitySelectorSelected($event)\"\n (entityCleared)=\"onEntitySelectorCleared($event)\">\n </mm-entity-selector-toolbar>\n </div>\n }\n\n @if (isTimeFilterEnabled() || hasEntitySelectors()) {\n <div class=\"toolbar-separator\"></div>\n }\n <div class=\"toolbar-right mm-toolbar-actions\">\n @if (!isReadonly()) {\n <!-- Manager Button (icon only) -->\n <button\n kendoButton\n [svgIcon]=\"gridLayoutIcon\"\n (click)=\"openManager()\"\n title=\"Manage MeshBoards\"\n fillMode=\"flat\">\n </button>\n\n <!-- Settings Button (icon only) -->\n <button\n kendoButton\n [svgIcon]=\"gearIcon\"\n (click)=\"openSettings()\"\n title=\"MeshBoard Settings\"\n fillMode=\"flat\">\n </button>\n }\n\n <!-- Refresh Button (icon only) -->\n <button\n kendoButton\n [svgIcon]=\"arrowRotateCwIcon\"\n (click)=\"refresh()\"\n title=\"Refresh All Widgets\"\n fillMode=\"flat\">\n </button>\n\n @if (!isReadonly()) {\n <!-- Edit Mode Toggle (icon only) -->\n @if (!isEditMode()) {\n <button\n kendoButton\n [svgIcon]=\"pencilIcon\"\n (click)=\"toggleEditMode()\"\n title=\"Enter Edit Mode\"\n fillMode=\"flat\">\n </button>\n } @else {\n <button\n kendoButton\n [svgIcon]=\"xIcon\"\n (click)=\"cancelEdit()\"\n title=\"Cancel Edit Mode\"\n fillMode=\"flat\">\n </button>\n }\n\n <!-- Add Widget Button (with text) -->\n <button\n kendoButton\n [svgIcon]=\"plusIcon\"\n (click)=\"openAddWidget()\"\n title=\"Add Widget\"\n themeColor=\"primary\">\n Add Widget\n </button>\n\n <!-- Save Button (with text) -->\n @if (canSave()) {\n <button\n kendoButton\n [svgIcon]=\"saveIcon\"\n (click)=\"save()\"\n [disabled]=\"isSaving()\"\n title=\"Save MeshBoard\"\n themeColor=\"primary\">\n {{ isSaving() ? 'Saving...' : 'Save' }}\n </button>\n }\n }\n </div>\n </div>\n\n <!-- Entity Selector Hint -->\n @if (unselectedToolbarSelectors().length > 0) {\n <div class=\"entity-selector-hint\">\n <kendo-svg-icon [icon]=\"infoCircleIcon\" size=\"medium\"></kendo-svg-icon>\n <span>\n Please select\n @for (selector of unselectedToolbarSelectors(); track selector.id; let last = $last) {\n <strong>{{ selector.label }}</strong>@if (!last) {, }\n }\n to display dashboard data.\n </span>\n </div>\n }\n\n <!-- Variable Resolution Errors -->\n @if (variableResolutionErrors().length > 0) {\n <div class=\"variable-resolution-errors\">\n <strong>Variable resolution errors:</strong>\n @for (error of variableResolutionErrors(); track error.variableName) {\n <span class=\"error-item\">{{ error.variableName }}: {{ error.message }}</span>\n }\n </div>\n }\n\n <!-- Banner Zone: widgets placed in the banner stack above the grid -->\n @if (isEditMode()) {\n <div class=\"zone-label banner-zone-label\">Banner Zone</div>\n }\n @for (widget of bannerWidgets(); track trackByWidgetId($index, widget); let first = $first; let last = $last) {\n <div class=\"banner-zone-item\" [class.banner-edit]=\"isEditMode()\">\n @if (isEditMode()) {\n <div class=\"banner-edit-header\">\n <span class=\"widget-title\">{{ widget.title }}</span>\n <div class=\"widget-actions\">\n <button\n kendoButton\n [svgIcon]=\"arrowUpIcon\"\n (click)=\"moveBannerUp(widget.id)\"\n fillMode=\"flat\"\n size=\"small\"\n [disabled]=\"first\"\n title=\"Move up\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"arrowDownIcon\"\n (click)=\"moveBannerDown(widget.id)\"\n fillMode=\"flat\"\n size=\"small\"\n [disabled]=\"last\"\n title=\"Move down\">\n </button>\n @if (supportsConfiguration(widget)) {\n <button\n kendoButton\n [svgIcon]=\"linkIcon\"\n (click)=\"openWidgetConfig(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"config-widget-btn\"\n [class.needs-config]=\"isWidgetUnconfigured(widget)\"\n [title]=\"isWidgetUnconfigured(widget) ? 'Configure data source (required)' : 'Configure data source'\">\n </button>\n }\n <button\n kendoButton\n [svgIcon]=\"gearIcon\"\n (click)=\"openEditWidgetDialog(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"edit-widget-btn\"\n title=\"Edit widget\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"trashIcon\"\n (click)=\"removeWidget(widget.id)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"remove-widget-btn\"\n title=\"Delete widget\">\n </button>\n </div>\n </div>\n }\n @if (getWidgetComponentType(widget.type); as componentType) {\n <ng-container *ngComponentOutlet=\"componentType; inputs: { config: widget }\"></ng-container>\n }\n </div>\n }\n\n <!-- MeshBoard Grid -->\n @if (isEditMode()) {\n <div class=\"zone-label grid-zone-label\">Grid Zone</div>\n }\n @if (hasGridWidgets()) {\n <kendo-tilelayout\n [columns]=\"config().columns\"\n [rowHeight]=\"config().rowHeight\"\n [gap]=\"config().gap\"\n [class.has-banners]=\"bannerWidgets().length > 0 && !isEditMode()\"\n [resizable]=\"isEditMode()\"\n [reorderable]=\"isEditMode()\"\n (reorder)=\"onReorder($event)\"\n (resize)=\"onResize($event)\">\n @for (widget of gridWidgets(); track trackByWidgetId($index, widget)) {\n <kendo-tilelayout-item\n [col]=\"widget.col\"\n [row]=\"widget.row\"\n [colSpan]=\"widget.colSpan\"\n [rowSpan]=\"widget.rowSpan\">\n <kendo-tilelayout-item-header>\n <div class=\"widget-header\"\n [class.unconfigured]=\"isWidgetUnconfigured(widget)\"\n [class.chromeless-header]=\"widget.chromeless && !isEditMode()\">\n @if (!(widget.chromeless && !isEditMode())) {\n <span class=\"widget-title\">{{ widget.title }}</span>\n @if (isWidgetUnconfigured(widget)) {\n <span class=\"unconfigured-badge\" title=\"Widget needs configuration\">!</span>\n }\n }\n @if (isEditMode()) {\n <div class=\"widget-actions\" (pointerdown)=\"$event.stopPropagation()\">\n @if (supportsConfiguration(widget)) {\n <button\n kendoButton\n [svgIcon]=\"linkIcon\"\n (click)=\"openWidgetConfig(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"config-widget-btn\"\n [class.needs-config]=\"isWidgetUnconfigured(widget)\"\n [title]=\"isWidgetUnconfigured(widget) ? 'Configure data source (required)' : 'Configure data source'\">\n </button>\n }\n <button\n kendoButton\n [svgIcon]=\"gearIcon\"\n (click)=\"openEditWidgetDialog(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"edit-widget-btn\"\n title=\"Edit position\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"copyIcon\"\n (click)=\"duplicateWidget(widget)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"duplicate-widget-btn\"\n title=\"Duplicate widget\">\n </button>\n <button\n kendoButton\n [svgIcon]=\"trashIcon\"\n (click)=\"removeWidget(widget.id)\"\n fillMode=\"flat\"\n size=\"small\"\n class=\"remove-widget-btn\"\n title=\"Delete widget\">\n </button>\n </div>\n }\n </div>\n </kendo-tilelayout-item-header>\n <kendo-tilelayout-item-body>\n @if (getWidgetComponentType(widget.type); as componentType) {\n <ng-container *ngComponentOutlet=\"componentType; inputs: { config: widget }\"></ng-container>\n } @else {\n <div class=\"widget-error\">\n <p>Unknown widget type: {{ widget.type }}</p>\n </div>\n }\n </kendo-tilelayout-item-body>\n </kendo-tilelayout-item>\n }\n </kendo-tilelayout>\n } @else {\n <div class=\"empty-state\">\n <div class=\"empty-state-content\">\n <kendo-svg-icon [icon]=\"plusIcon\" size=\"xlarge\"></kendo-svg-icon>\n <h3>No Widgets</h3>\n <p>Get started by adding widgets to your MeshBoard.</p>\n <button\n kendoButton\n [svgIcon]=\"plusIcon\"\n (click)=\"openAddWidget()\"\n themeColor=\"primary\">\n Add Your First Widget\n </button>\n </div>\n </div>\n }\n\n <!-- Edit Widget Dialog -->\n @if (showEditWidgetDialog && editingWidget) {\n <mm-edit-widget-dialog\n [widget]=\"editingWidget\"\n [widgets]=\"config().widgets\"\n [maxColumns]=\"config().columns\"\n [gridService]=\"gridService\"\n (save)=\"onEditWidgetSave($event)\"\n (cancelled)=\"closeEditWidgetDialog()\">\n </mm-edit-widget-dialog>\n }\n }\n</div>\n", styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0;height:100%;width:100%}.meshboard-view{display:flex;flex-direction:column;flex:1;min-height:0;width:100%;background-color:var(--kendo-color-surface, #f5f5f5)}.loading-container{display:flex;align-items:center;justify-content:center;height:100%;width:100%}.entity-selector-hint{display:flex;align-items:center;gap:8px;padding:10px 16px;margin-bottom:8px;background:color-mix(in srgb,var(--kendo-color-info, #2196f3) 10%,var(--kendo-color-surface-alt, #fafafa));border:1px solid color-mix(in srgb,var(--kendo-color-info, #2196f3) 40%,transparent);border-radius:4px;font-size:13px;color:var(--kendo-color-on-app-surface, #424242)}.entity-selector-hint strong{color:var(--kendo-color-info, #2196f3)}.variable-resolution-errors{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:8px 16px;margin-bottom:8px;background:color-mix(in srgb,var(--kendo-color-error, #f44336) 10%,var(--kendo-color-surface-alt, #fafafa));border:1px solid var(--kendo-color-error, #f44336);border-radius:4px;font-size:13px;color:var(--kendo-color-on-app-surface, #424242)}.variable-resolution-errors strong{color:var(--kendo-color-error, #f44336)}.variable-resolution-errors .error-item{padding:2px 8px;background:color-mix(in srgb,var(--kendo-color-error, #f44336) 8%,transparent);border-radius:3px;font-family:Consolas,Monaco,monospace;font-size:12px}.model-unavailable{display:flex;align-items:center;justify-content:center;height:100%;width:100%;padding:2rem;overflow:hidden;box-sizing:border-box}.model-unavailable .message-card{text-align:center;max-width:500px;padding:2rem;background:var(--kendo-color-surface-alt, #fafafa);border:1px solid var(--kendo-color-border, #e0e0e0);border-radius:8px;overflow:hidden}.model-unavailable .message-card kendo-svg-icon{color:var(--kendo-color-warning, #ff9800);margin-bottom:1rem}.model-unavailable .message-card h2{margin:1rem 0;color:var(--kendo-color-on-app-surface, #424242)}.model-unavailable .message-card p{margin:.5rem 0;color:var(--kendo-color-subtle, #757575);line-height:1.5}.model-unavailable .message-card ol{text-align:left;margin:1rem 0;padding-left:1.5rem;color:var(--kendo-color-subtle, #757575);line-height:1.8}.model-unavailable .message-card ol li{margin:.25rem 0}.model-unavailable .message-card ol a{color:var(--kendo-color-primary, #3f51b5);text-decoration:none}.model-unavailable .message-card ol a:hover{text-decoration:underline}.meshboard-toolbar{display:flex;justify-content:space-between;align-items:flex-end;padding:1rem 1.5rem;background-color:var(--kendo-color-surface-alt, white);border-bottom:1px solid var(--kendo-color-border, #e0e0e0);gap:1rem;flex-shrink:0}.meshboard-toolbar .toolbar-left{display:flex;align-items:baseline;gap:1rem;flex:1;min-width:0;align-self:center}.meshboard-toolbar .toolbar-left .meshboard-title{margin:0;font-size:1.5rem;font-weight:500;color:var(--kendo-color-on-app-surface, #212121);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.meshboard-toolbar .toolbar-left .meshboard-description{color:var(--kendo-color-subtle, #757575);font-size:.875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.meshboard-toolbar .toolbar-center{display:flex;justify-content:center;align-items:flex-end;flex-shrink:0}.meshboard-toolbar .toolbar-center mm-time-range-picker ::ng-deep .time-range-picker{gap:.5rem}.meshboard-toolbar .toolbar-separator{width:1px;align-self:stretch;background-color:var(--kendo-color-border, #e0e0e0);flex-shrink:0}.meshboard-toolbar .toolbar-entity-selectors{display:flex;align-items:flex-end;flex-shrink:0}.meshboard-toolbar .toolbar-right{display:flex;gap:.5rem;align-items:center;flex-shrink:0}.empty-state{display:flex;align-items:flex-start;justify-content:center;height:calc(100% - 80px);width:100%;padding-top:4rem}.empty-state .empty-state-content{text-align:center;padding:2rem}.empty-state .empty-state-content kendo-svg-icon{color:var(--kendo-color-subtle, #9e9e9e);margin-bottom:1rem}.empty-state .empty-state-content h3{margin:1rem 0 .5rem;color:var(--kendo-color-on-app-surface, #424242);font-size:1.25rem;font-weight:500}.empty-state .empty-state-content p{margin:0 0 1.5rem;color:var(--kendo-color-subtle, #757575)}kendo-tilelayout{padding:1.5rem;overflow:auto;flex:1;min-height:0}.widget-header{display:flex;justify-content:space-between;align-items:center;width:100%;gap:.5rem}.widget-header.unconfigured{background-color:#ff98001a}.widget-header .widget-title{flex:1;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.widget-header .unconfigured-badge{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background-color:var(--kendo-color-warning, #ff9800);color:#fff;font-size:12px;font-weight:700;flex-shrink:0}.widget-header .widget-actions{display:flex;align-items:center;gap:.25rem;flex-shrink:0}.widget-header .config-widget-btn,.widget-header .edit-widget-btn,.widget-header .remove-widget-btn{opacity:.6;transition:opacity .2s}.widget-header .config-widget-btn:hover,.widget-header .edit-widget-btn:hover,.widget-header .remove-widget-btn:hover{opacity:1}.widget-header .config-widget-btn.needs-config{color:var(--kendo-color-warning, #ff9800);opacity:1}.widget-header .remove-widget-btn{color:var(--kendo-color-error, #f44336)}.widget-error{display:flex;align-items:center;justify-content:center;height:100%;padding:1rem;color:var(--kendo-color-error, #f44336);text-align:center}.meshboard-view.edit-mode kendo-tilelayout-item:hover{box-shadow:0 0 0 2px var(--kendo-color-primary, #3f51b5)}@media(max-width:768px){.meshboard-toolbar{flex-direction:column;align-items:stretch;gap:.75rem}.meshboard-toolbar .toolbar-left{flex-direction:column;align-items:flex-start;gap:.25rem}.meshboard-toolbar .toolbar-right{flex-wrap:wrap;justify-content:flex-end}kendo-tilelayout{padding:1rem}}:host ::ng-deep kendo-tilelayout.has-banners{padding-top:0!important}:host ::ng-deep kendo-tilelayout{padding-bottom:1.5rem!important}.widget-header.chromeless-header{display:none}:host ::ng-deep .k-tilelayout-item-header:has(.chromeless-header){display:none}:host ::ng-deep kendo-tilelayout-item:has(.chromeless-header).k-tilelayout-item,:host ::ng-deep kendo-tilelayout-item:has(.chromeless-header).k-card{border:none;box-shadow:none;background:transparent}.zone-label{font-size:.65rem;text-transform:uppercase;letter-spacing:1.5px;opacity:.4;padding:6px 1rem 2px;font-weight:600}.banner-zone-item{margin:0;padding:8px 1rem}.banner-zone-item.banner-edit{border:1px dashed var(--kendo-color-border, rgba(255, 255, 255, .2));border-radius:4px;margin:0 1rem 12px;padding:4px 0}.banner-edit-header{display:flex;align-items:center;justify-content:space-between;padding:4px 8px;opacity:.7}.banner-edit-header .widget-title{font-size:.75rem;text-transform:uppercase;letter-spacing:.5px}.banner-edit-header .widget-actions{display:flex;gap:2px}\n"] }]
29250
+ }], ctorParameters: () => [], propDecorators: { tileLayout: [{
29251
+ type: ViewChild,
29252
+ args: [TileLayoutComponent]
29253
+ }] } });
27014
29254
 
27015
29255
  /*
27016
29256
  * Public API Surface of @meshmakers/octo-meshboard
@@ -27021,5 +29261,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
27021
29261
  * Generated bundle index. Do not edit.
27022
29262
  */
27023
29263
 
27024
- export { AddWidgetDialogComponent, AssociationsConfigDialogComponent, BarChartConfigDialogComponent, BarChartWidgetComponent, DashboardDataService, DashboardGridService, EditModeStateService, EditWidgetDialogComponent, EntityAssociationsWidgetComponent, EntityCardConfigDialogComponent, EntityCardWidgetComponent, EntityDetailDialogComponent, EntitySelectorEditorComponent, EntitySelectorToolbarComponent, GaugeConfigDialogComponent, GaugeWidgetComponent, HeatmapConfigDialogComponent, HeatmapWidgetComponent, KpiConfigDialogComponent, KpiWidgetComponent, LineChartConfigDialogComponent, LineChartWidgetComponent, MESHBOARD_OPTIONS, MESHBOARD_TENANT_ID_PROVIDER, MarkdownConfigDialogComponent, MarkdownWidgetComponent, MeshBoardDataService, MeshBoardGridService, MeshBoardManagerDialogComponent, MeshBoardPersistenceService, MeshBoardSettingsDialogComponent, MeshBoardSettingsResult, MeshBoardStateService, MeshBoardViewComponent, PieChartConfigDialogComponent, PieChartWidgetComponent, QuerySelectorComponent, RuntimeEntitySelectorComponent, ServiceHealthConfigDialogComponent, ServiceHealthWidgetComponent, StatsGridConfigDialogComponent, StatsGridWidgetComponent, StatusIndicatorConfigDialogComponent, StatusIndicatorWidgetComponent, TableConfigDialogComponent, TableWidgetComponent, TableWidgetDataSourceDirective, WidgetFactoryService, WidgetGroupComponent, WidgetGroupConfigDialogComponent, WidgetNotConfiguredComponent, WidgetRegistryService, provideDefaultWidgets, provideMeshBoard, provideProcessWidget, provideWidgetRegistrations, registerDefaultWidgets, registerProcessWidget };
29264
+ export { AddWidgetDialogComponent, AiInsightsConfigDialogComponent, AiInsightsService, AiInsightsWidgetComponent, AlertBannerConfigDialogComponent, AlertBannerWidgetComponent, AlertListConfigDialogComponent, AlertListWidgetComponent, AssociationsConfigDialogComponent, BarChartConfigDialogComponent, BarChartWidgetComponent, DashboardDataService, DashboardGridService, EditModeStateService, EditWidgetDialogComponent, EntityAssociationsWidgetComponent, EntityCardConfigDialogComponent, EntityCardWidgetComponent, EntityDetailDialogComponent, EntitySelectorEditorComponent, EntitySelectorToolbarComponent, GaugeConfigDialogComponent, GaugeWidgetComponent, HeatmapConfigDialogComponent, HeatmapWidgetComponent, KpiConfigDialogComponent, KpiWidgetComponent, LineChartConfigDialogComponent, LineChartWidgetComponent, MESHBOARD_OPTIONS, MESHBOARD_TENANT_ID_PROVIDER, MarkdownConfigDialogComponent, MarkdownWidgetComponent, MeshBoardDataService, MeshBoardGridService, MeshBoardManagerDialogComponent, MeshBoardPersistenceService, MeshBoardSettingsDialogComponent, MeshBoardSettingsResult, MeshBoardStateService, MeshBoardViewComponent, PieChartConfigDialogComponent, PieChartWidgetComponent, QuerySelectorComponent, RuntimeEntitySelectorComponent, ServiceHealthConfigDialogComponent, ServiceHealthWidgetComponent, StatsGridConfigDialogComponent, StatsGridWidgetComponent, StatusIndicatorConfigDialogComponent, StatusIndicatorWidgetComponent, StatusListConfigDialogComponent, StatusListWidgetComponent, SummaryCardConfigDialogComponent, SummaryCardWidgetComponent, TableConfigDialogComponent, TableWidgetComponent, TableWidgetDataSourceDirective, WidgetFactoryService, WidgetGroupComponent, WidgetGroupConfigDialogComponent, WidgetNotConfiguredComponent, WidgetRegistryService, provideDefaultWidgets, provideMeshBoard, provideProcessWidget, provideWidgetRegistrations, registerDefaultWidgets, registerProcessWidget };
27025
29265
  //# sourceMappingURL=meshmakers-octo-meshboard.mjs.map