@meshmakers/octo-meshboard 3.3.540 → 3.3.560

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.
@@ -1,4 +1,4 @@
1
- import { firstValueFrom, from, map, Observable, of, catchError, interval } from 'rxjs';
1
+ import { firstValueFrom, from, map, Observable, of, catchError, interval, filter } from 'rxjs';
2
2
  import { FieldFilterOperatorsDto, CkTypeSelectorService, DeleteStrategiesDto, AssociationModOptionsDto, CkModelService, GraphDirectionDto, AttributeSelectorService, GraphQL, GetCkTypeAvailableQueryColumnsDtoGQL, SortOrdersDto, HealthService, TENANT_ID_PROVIDER, AssetRepoService, JobManagementService } from '@meshmakers/octo-services';
3
3
  export { TENANT_ID_PROVIDER } from '@meshmakers/octo-services';
4
4
  import * as i0 from '@angular/core';
@@ -6,7 +6,7 @@ import { Injectable, inject, EventEmitter, forwardRef, Output, Input, ViewChild,
6
6
  import * as i1$1 from '@angular/forms';
7
7
  import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
8
8
  import * as i1$7 from '@meshmakers/shared-ui';
9
- import { EntitySelectInputComponent, ListViewComponent, FetchResultTyped, DataSourceBase, ImportStrategyDialogService, TimeRangeUtils, TimeRangePickerComponent, HAS_UNSAVED_CHANGES, UnsavedChangesDirective } from '@meshmakers/shared-ui';
9
+ import { EntitySelectInputComponent, 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';
@@ -22,7 +22,7 @@ import * as i1$2 from '@progress/kendo-angular-indicators';
22
22
  import { LoaderModule } from '@progress/kendo-angular-indicators';
23
23
  import * as i2$1 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, checkCircleIcon, chartLineIcon, copyIcon, heartIcon, gridLayoutIcon, chartColumnStackedIcon, chartDoughnutIcon, tableIcon, shareIcon, fileTxtIcon, pencilIcon, checkIcon, xIcon, downloadIcon, uploadIcon, gearIcon, saveIcon, arrowRotateCwIcon } from '@progress/kendo-svg-icons';
25
+ import { arrowUpIcon, arrowDownIcon, minusIcon, arrowRightIcon, arrowLeftIcon, linkIcon, chevronDownIcon, chevronRightIcon, columnsIcon, sortAscIcon, filterIcon, searchIcon, chartPieIcon, plusIcon, trashIcon, checkCircleIcon, chartLineIcon, gearsIcon, clipboardMarkdownIcon, copyIcon, gridIcon, heartIcon, gridLayoutIcon, chartLineMarkersIcon, chartColumnStackedIcon, chartDoughnutIcon, tableIcon, shareIcon, fileTxtIcon, pencilIcon, 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';
@@ -32,7 +32,7 @@ import { CollectionChangesService, KENDO_GAUGES } from '@progress/kendo-angular-
32
32
  import * as i1$6 from '@progress/kendo-angular-charts';
33
33
  import { ChartsModule } from '@progress/kendo-angular-charts';
34
34
  import * as i3$1 from '@angular/router';
35
- import { Router, ActivatedRoute, RouterModule } from '@angular/router';
35
+ import { Router, ActivatedRoute, NavigationEnd, RouterModule } from '@angular/router';
36
36
  import * as i2$2 from 'ngx-markdown';
37
37
  import { MarkdownModule } from 'ngx-markdown';
38
38
  import { DomSanitizer } from '@angular/platform-browser';
@@ -43,6 +43,7 @@ import * as i5 from '@progress/kendo-angular-label';
43
43
  import { LabelModule } from '@progress/kendo-angular-label';
44
44
  import * as i4$2 from '@progress/kendo-angular-dateinputs';
45
45
  import { DatePickerModule, DateTimePickerModule } from '@progress/kendo-angular-dateinputs';
46
+ import { BreadCrumbService } from '@meshmakers/shared-services';
46
47
 
47
48
  /**
48
49
  * MeshBoard Widget Models
@@ -395,7 +396,7 @@ class QuerySelectorComponent {
395
396
  <p class="field-hint">{{ hint }}</p>
396
397
  }
397
398
  </div>
398
- `, isInline: true, styles: [".query-selector{width:100%}.field-hint{font-size:.85em;margin-top:4px;opacity:.7}\n"], dependencies: [{ kind: "component", type: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }] });
399
+ `, isInline: true, styles: [".query-selector{width:100%}.field-hint{font-size:.85em;margin-top:4px;opacity:.7}\n"], dependencies: [{ kind: "component", type: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }] });
399
400
  }
400
401
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuerySelectorComponent, decorators: [{
401
402
  type: Component,
@@ -713,7 +714,7 @@ class RuntimeEntitySelectorComponent {
713
714
  </div>
714
715
  }
715
716
  </div>
716
- `, isInline: true, styles: [".runtime-entity-selector{display:flex;flex-direction:column;gap:16px}.form-field{display:flex;flex-direction:column;gap:4px}.form-field label{font-weight:500}.form-field.disabled{opacity:.6}.field-hint{font-size:.85em;margin:0;opacity:.7}.required{color:#dc3545}\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: "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: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }] });
717
+ `, isInline: true, styles: [".runtime-entity-selector{display:flex;flex-direction:column;gap:16px}.form-field{display:flex;flex-direction:column;gap:4px}.form-field label{font-weight:500}.form-field.disabled{opacity:.6}.field-hint{font-size:.85em;margin:0;opacity:.7}.required{color:#dc3545}\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: "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: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }] });
717
718
  }
718
719
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: RuntimeEntitySelectorComponent, decorators: [{
719
720
  type: Component,
@@ -2204,7 +2205,14 @@ class MeshBoardStateService {
2204
2205
  rowHeight: settings.rowHeight,
2205
2206
  gap: settings.gap,
2206
2207
  variables: settings.variables ?? config.variables,
2207
- timeFilter: settings.timeFilter ?? config.timeFilter
2208
+ timeFilter: settings.timeFilter
2209
+ ? {
2210
+ ...config.timeFilter,
2211
+ ...settings.timeFilter,
2212
+ // When a new defaultSelection is set, reset the stored selection to match
2213
+ selection: settings.timeFilter.defaultSelection ?? config.timeFilter?.selection
2214
+ }
2215
+ : config.timeFilter
2208
2216
  }));
2209
2217
  // If time filter is disabled, clear the time filter variables
2210
2218
  if (settings.timeFilter && !settings.timeFilter.enabled) {
@@ -3742,7 +3750,7 @@ class EntityCardConfigDialogComponent {
3742
3750
  </button>
3743
3751
  </div>
3744
3752
  </div>
3745
- `, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.config-form{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:20px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.selection-preview{padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.selection-preview h4{margin:0 0 8px;font-size:.9rem;color:var(--kendo-color-primary, #0d6efd)}.preview-content p{margin:4px 0;font-size:.85rem}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}\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: 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: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
3753
+ `, isInline: true, styles: [":host{display:block;height:100%}.config-container{display:flex;flex-direction:column;height:100%}.config-form{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:20px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.selection-preview{padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.selection-preview h4{margin:0 0 8px;font-size:.9rem;color:var(--kendo-color-primary, #0d6efd)}.preview-content p{margin:4px 0;font-size:.85rem}.action-bar{display:flex;justify-content:flex-end;gap:8px;padding:8px 16px;border-top:1px solid var(--kendo-color-border, #dee2e6)}\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: 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: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
3746
3754
  }
3747
3755
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EntityCardConfigDialogComponent, decorators: [{
3748
3756
  type: Component,
@@ -4063,7 +4071,6 @@ class KpiWidgetComponent {
4063
4071
  const result = await firstValueFrom(this.executeRuntimeQueryGQL.fetch({
4064
4072
  variables: {
4065
4073
  rtId: dataSource.queryRtId,
4066
- first: 100,
4067
4074
  fieldFilter
4068
4075
  }
4069
4076
  }).pipe(catchError(err => {
@@ -4999,7 +5006,7 @@ class KpiConfigDialogComponent {
4999
5006
  </button>
5000
5007
  </div>
5001
5008
  </div>
5002
- `, 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}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field.flex-1{flex:1}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.form-section h4{margin:0 0 16px;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-row{display:flex;gap:16px}.mode-toggle{display:flex;gap:8px}.mode-toggle button{flex:1}.attribute-item{display:flex;justify-content:space-between;align-items:center;gap:8px;width:100%}.attribute-path{flex:1}.attribute-type{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);background:var(--kendo-color-surface-alt, #f8f9fa);padding:2px 6px;border-radius:3px}.required{color:var(--kendo-color-error, #dc3545)}.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.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: "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: 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: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "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"] }] });
5009
+ `, 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}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field.flex-1{flex:1}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.form-section h4{margin:0 0 16px;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-row{display:flex;gap:16px}.mode-toggle{display:flex;gap:8px}.mode-toggle button{flex:1}.attribute-item{display:flex;justify-content:space-between;align-items:center;gap:8px;width:100%}.attribute-path{flex:1}.attribute-type{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);background:var(--kendo-color-surface-alt, #f8f9fa);padding:2px 6px;border-radius:3px}.required{color:var(--kendo-color-error, #dc3545)}.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.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: "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: 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: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "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"] }] });
5003
5010
  }
5004
5011
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: KpiConfigDialogComponent, decorators: [{
5005
5012
  type: Component,
@@ -6594,7 +6601,7 @@ class AssociationsConfigDialogComponent {
6594
6601
  </button>
6595
6602
  </div>
6596
6603
  </div>
6597
- `, 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;flex:1;overflow-y:auto;gap:16px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.checkbox-group,.radio-group{display:flex;gap:16px}.checkbox-label,.radio-label{display:flex;align-items:center;gap:6px;font-weight:400;cursor:pointer}.loading-roles{padding:8px;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.selection-preview{padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.selection-preview h4{margin:0 0 8px;font-size:.9rem;color:var(--kendo-color-primary, #0d6efd)}.preview-content p{margin:4px 0;font-size:.85rem}\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: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "ngmodule", type: CheckBoxModule }, { kind: "ngmodule", type: DropDownsModule }, { 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: "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: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
6604
+ `, 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;flex:1;overflow-y:auto;gap:16px;padding:16px;position:relative}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.checkbox-group,.radio-group{display:flex;gap:16px}.checkbox-label,.radio-label{display:flex;align-items:center;gap:6px;font-weight:400;cursor:pointer}.loading-roles{padding:8px;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.selection-preview{padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.selection-preview h4{margin:0 0 8px;font-size:.9rem;color:var(--kendo-color-primary, #0d6efd)}.preview-content p{margin:4px 0;font-size:.85rem}\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: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "ngmodule", type: CheckBoxModule }, { kind: "ngmodule", type: DropDownsModule }, { 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: "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: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
6598
6605
  }
6599
6606
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AssociationsConfigDialogComponent, decorators: [{
6600
6607
  type: Component,
@@ -8104,7 +8111,6 @@ class GaugeWidgetComponent {
8104
8111
  const result = await firstValueFrom(this.executeRuntimeQueryGQL.fetch({
8105
8112
  variables: {
8106
8113
  rtId: dataSource.queryRtId,
8107
- first: 100,
8108
8114
  fieldFilter
8109
8115
  }
8110
8116
  }).pipe(catchError(err => {
@@ -9436,7 +9442,7 @@ class GaugeConfigDialogComponent {
9436
9442
  </button>
9437
9443
  </div>
9438
9444
  </div>
9439
- `, 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;padding:16px;position:relative;flex:1;overflow-y:auto}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field.flex-1{flex:1}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.form-section h4{margin:0 0 16px;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-row{display:flex;gap:16px}.checkbox-label{margin-left:8px;font-weight:400}.gauge-type-item{display:flex;flex-direction:column;gap:2px}.gauge-type-label{font-weight:500}.gauge-type-desc{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d)}.attribute-item{display:flex;justify-content:space-between;align-items:center;gap:8px;width:100%}.attribute-path{flex:1}.attribute-type{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);background:var(--kendo-color-surface-alt, #f8f9fa);padding:2px 6px;border-radius:3px}.range-row{display:flex;align-items:center;gap:8px;margin-bottom:8px}.range-input{width:80px}.range-separator{color:var(--kendo-color-subtle, #6c757d)}.color-picker{width:40px;height:32px;padding:2px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;cursor:pointer}.mode-toggle{display:flex;gap:8px}.mode-toggle button{flex:1}.required{color:var(--kendo-color-error, #dc3545)}.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.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: "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: 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: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "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"] }] });
9445
+ `, 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;padding:16px;position:relative;flex:1;overflow-y:auto}.config-form.loading{pointer-events:none}.form-field{display:flex;flex-direction:column;gap:6px}.form-field.disabled{opacity:.6}.form-field.flex-1{flex:1}.form-field label{font-weight:600;font-size:.9rem;color:var(--kendo-color-on-app-surface, #212529)}.field-hint{margin:0;font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.form-section{padding:16px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.form-section h4{margin:0 0 16px;font-size:.95rem;color:var(--kendo-color-primary, #0d6efd)}.section-hint{margin:0 0 12px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.form-row{display:flex;gap:16px}.checkbox-label{margin-left:8px;font-weight:400}.gauge-type-item{display:flex;flex-direction:column;gap:2px}.gauge-type-label{font-weight:500}.gauge-type-desc{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d)}.attribute-item{display:flex;justify-content:space-between;align-items:center;gap:8px;width:100%}.attribute-path{flex:1}.attribute-type{font-size:.75rem;color:var(--kendo-color-subtle, #6c757d);background:var(--kendo-color-surface-alt, #f8f9fa);padding:2px 6px;border-radius:3px}.range-row{display:flex;align-items:center;gap:8px;margin-bottom:8px}.range-input{width:80px}.range-separator{color:var(--kendo-color-subtle, #6c757d)}.color-picker{width:40px;height:32px;padding:2px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;cursor:pointer}.mode-toggle{display:flex;gap:8px}.mode-toggle button{flex:1}.required{color:var(--kendo-color-error, #dc3545)}.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.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: "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: 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: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "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"] }] });
9440
9446
  }
9441
9447
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: GaugeConfigDialogComponent, decorators: [{
9442
9448
  type: Component,
@@ -10032,7 +10038,6 @@ class PieChartWidgetComponent {
10032
10038
  const result = await firstValueFrom(this.executeRuntimeQueryGQL.fetch({
10033
10039
  variables: {
10034
10040
  rtId: dataSource.queryRtId,
10035
- first: 100, // Limit for pie chart
10036
10041
  fieldFilter
10037
10042
  }
10038
10043
  }).pipe(catchError(err => {
@@ -11012,7 +11017,6 @@ class BarChartWidgetComponent {
11012
11017
  const result = await firstValueFrom(this.executeRuntimeQueryGQL.fetch({
11013
11018
  variables: {
11014
11019
  rtId: queryDataSource.queryRtId,
11015
- first: 100, // Limit for bar chart
11016
11020
  fieldFilter
11017
11021
  }
11018
11022
  }).pipe(catchError(err => {
@@ -12022,13 +12026,2025 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
12022
12026
  Show Legend
12023
12027
  </label>
12024
12028
  </div>
12025
-
12026
- <div class="form-field checkbox-field">
12027
- <label>
12028
- <input type="checkbox" [(ngModel)]="form.showDataLabels" kendoCheckBox />
12029
- Show Data Labels
12030
- </label>
12031
- </div>
12029
+
12030
+ <div class="form-field checkbox-field">
12031
+ <label>
12032
+ <input type="checkbox" [(ngModel)]="form.showDataLabels" kendoCheckBox />
12033
+ Show Data Labels
12034
+ </label>
12035
+ </div>
12036
+ </div>
12037
+
12038
+ @if (form.showLegend) {
12039
+ <div class="form-field">
12040
+ <label>Legend Position</label>
12041
+ <kendo-dropdownlist
12042
+ [data]="legendPositions"
12043
+ [textField]="'label'"
12044
+ [valueField]="'value'"
12045
+ [valuePrimitive]="true"
12046
+ [(ngModel)]="form.legendPosition">
12047
+ </kendo-dropdownlist>
12048
+ </div>
12049
+ }
12050
+ </div>
12051
+ </div>
12052
+
12053
+ <div class="action-bar">
12054
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
12055
+ <button
12056
+ kendoButton
12057
+ themeColor="primary"
12058
+ [disabled]="!isValid"
12059
+ (click)="onSave()">
12060
+ Save
12061
+ </button>
12062
+ </div>
12063
+ </div>
12064
+ `, 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"] }]
12065
+ }], propDecorators: { querySelector: [{
12066
+ type: ViewChild,
12067
+ args: ['querySelector']
12068
+ }], initialQueryRtId: [{
12069
+ type: Input
12070
+ }], initialQueryName: [{
12071
+ type: Input
12072
+ }], initialChartType: [{
12073
+ type: Input
12074
+ }], initialCategoryField: [{
12075
+ type: Input
12076
+ }], initialSeries: [{
12077
+ type: Input
12078
+ }], initialSeriesGroupField: [{
12079
+ type: Input
12080
+ }], initialValueField: [{
12081
+ type: Input
12082
+ }], initialShowLegend: [{
12083
+ type: Input
12084
+ }], initialLegendPosition: [{
12085
+ type: Input
12086
+ }], initialShowDataLabels: [{
12087
+ type: Input
12088
+ }], initialFilters: [{
12089
+ type: Input
12090
+ }] } });
12091
+
12092
+ class LineChartWidgetComponent {
12093
+ executeRuntimeQueryGQL = inject(ExecuteRuntimeQueryDtoGQL);
12094
+ stateService = inject(MeshBoardStateService);
12095
+ variableService = inject(MeshBoardVariableService);
12096
+ config;
12097
+ // Widget state signals
12098
+ _isLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isLoading" }] : []));
12099
+ _categories = signal([], ...(ngDevMode ? [{ debugName: "_categories" }] : []));
12100
+ _seriesData = signal([], ...(ngDevMode ? [{ debugName: "_seriesData" }] : []));
12101
+ _valueAxes = signal([], ...(ngDevMode ? [{ debugName: "_valueAxes" }] : []));
12102
+ _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : []));
12103
+ _seriesUnitMap = signal(new Map(), ...(ngDevMode ? [{ debugName: "_seriesUnitMap" }] : []));
12104
+ isLoading = this._isLoading.asReadonly();
12105
+ categories = this._categories.asReadonly();
12106
+ seriesData = this._seriesData.asReadonly();
12107
+ valueAxes = this._valueAxes.asReadonly();
12108
+ error = this._error.asReadonly();
12109
+ data = computed(() => this._seriesData(), ...(ngDevMode ? [{ debugName: "data" }] : []));
12110
+ chartType = computed(() => {
12111
+ return this.config?.chartType ?? 'line';
12112
+ }, ...(ngDevMode ? [{ debugName: "chartType" }] : []));
12113
+ labelRotation = computed(() => {
12114
+ const categoryCount = this._categories().length;
12115
+ return categoryCount > 8 ? -45 : 0;
12116
+ }, ...(ngDevMode ? [{ debugName: "labelRotation" }] : []));
12117
+ isNotConfigured() {
12118
+ const dataSource = this.config?.dataSource;
12119
+ if (!dataSource)
12120
+ return true;
12121
+ if (dataSource.type === 'persistentQuery') {
12122
+ const ds = dataSource;
12123
+ return !ds.queryRtId || !this.config?.categoryField || !this.config?.seriesGroupField || !this.config?.valueField;
12124
+ }
12125
+ return true;
12126
+ }
12127
+ categoryLabelContent = (e) => {
12128
+ const maxLen = 18;
12129
+ return e.value.length > maxLen ? e.value.substring(0, maxLen) + '...' : e.value;
12130
+ };
12131
+ ngOnInit() {
12132
+ this.loadData();
12133
+ }
12134
+ ngOnChanges(changes) {
12135
+ if (changes['config'] && !changes['config'].firstChange) {
12136
+ this.loadData();
12137
+ }
12138
+ }
12139
+ refresh() {
12140
+ this.loadData();
12141
+ }
12142
+ hasValidConfig() {
12143
+ if (!this.config?.dataSource)
12144
+ return false;
12145
+ if (this.config.dataSource.type === 'persistentQuery') {
12146
+ const ds = this.config.dataSource;
12147
+ return !!(ds.queryRtId && this.config.categoryField && this.config.seriesGroupField && this.config.valueField);
12148
+ }
12149
+ return false;
12150
+ }
12151
+ formatValue(value) {
12152
+ return value.toLocaleString('de-AT', {
12153
+ minimumFractionDigits: 0,
12154
+ maximumFractionDigits: 2
12155
+ });
12156
+ }
12157
+ getUnitForSeries(seriesName) {
12158
+ const unit = this._seriesUnitMap().get(seriesName);
12159
+ return unit ? ` ${unit}` : '';
12160
+ }
12161
+ async loadData() {
12162
+ if (this.isNotConfigured())
12163
+ return;
12164
+ const queryDataSource = this.config.dataSource;
12165
+ this._isLoading.set(true);
12166
+ this._error.set(null);
12167
+ try {
12168
+ const fieldFilter = this.convertFiltersToDto(this.config.filters);
12169
+ const result = await firstValueFrom(this.executeRuntimeQueryGQL.fetch({
12170
+ variables: {
12171
+ rtId: queryDataSource.queryRtId,
12172
+ fieldFilter
12173
+ }
12174
+ }).pipe(catchError(err => {
12175
+ console.error('Error loading Line Chart data:', err);
12176
+ throw err;
12177
+ })));
12178
+ const queryItems = result.data?.runtime?.runtimeQuery?.items ?? [];
12179
+ if (queryItems.length === 0) {
12180
+ this._categories.set([]);
12181
+ this._seriesData.set([]);
12182
+ this._valueAxes.set([]);
12183
+ this._isLoading.set(false);
12184
+ return;
12185
+ }
12186
+ const queryResult = queryItems[0];
12187
+ if (!queryResult) {
12188
+ this._categories.set([]);
12189
+ this._seriesData.set([]);
12190
+ this._valueAxes.set([]);
12191
+ this._isLoading.set(false);
12192
+ return;
12193
+ }
12194
+ const rows = queryResult.rows?.items ?? [];
12195
+ const supportedRowTypes = ['RtSimpleQueryRow', 'RtAggregationQueryRow', 'RtGroupingAggregationQueryRow'];
12196
+ const filteredRows = rows
12197
+ .filter((row) => row !== null)
12198
+ .filter(row => supportedRowTypes.includes(row.__typename ?? ''));
12199
+ this.processData(filteredRows);
12200
+ this._isLoading.set(false);
12201
+ }
12202
+ catch (err) {
12203
+ console.error('Error loading Line Chart data:', err);
12204
+ this._error.set('Failed to load data');
12205
+ this._isLoading.set(false);
12206
+ }
12207
+ }
12208
+ /**
12209
+ * Processes query rows into line chart data.
12210
+ * Groups by seriesGroupField, orders by categoryField (date), supports multi-axis by unitField.
12211
+ */
12212
+ processData(filteredRows) {
12213
+ const categoryField = this.config.categoryField;
12214
+ const seriesGroupField = this.config.seriesGroupField;
12215
+ const valueField = this.config.valueField;
12216
+ const unitField = this.config.unitField;
12217
+ // Collect data: category -> seriesGroup -> value
12218
+ const dataMap = new Map();
12219
+ const allCategories = new Map(); // label -> parsed date for sorting
12220
+ const allSeriesGroups = new Set();
12221
+ const seriesUnitMap = new Map(); // seriesGroup -> unit
12222
+ for (const row of filteredRows) {
12223
+ const queryRow = row;
12224
+ const cells = queryRow.cells?.items ?? [];
12225
+ let categoryValue = '';
12226
+ let seriesGroupValue = '';
12227
+ let numericValue = 0;
12228
+ let unitValue = '';
12229
+ for (const cell of cells) {
12230
+ if (!cell?.attributePath)
12231
+ continue;
12232
+ const sanitizedPath = this.sanitizeFieldName(cell.attributePath);
12233
+ if (sanitizedPath === this.sanitizeFieldName(categoryField)) {
12234
+ categoryValue = String(cell.value ?? '');
12235
+ }
12236
+ else if (sanitizedPath === this.sanitizeFieldName(seriesGroupField)) {
12237
+ seriesGroupValue = String(cell.value ?? '');
12238
+ }
12239
+ else if (sanitizedPath === this.sanitizeFieldName(valueField)) {
12240
+ const val = cell.value;
12241
+ numericValue = typeof val === 'number' ? val : parseFloat(String(val));
12242
+ if (isNaN(numericValue))
12243
+ numericValue = 0;
12244
+ }
12245
+ else if (unitField && sanitizedPath === this.sanitizeFieldName(unitField)) {
12246
+ unitValue = String(cell.value ?? '');
12247
+ }
12248
+ }
12249
+ if (categoryValue && seriesGroupValue) {
12250
+ // Parse date for sorting
12251
+ if (!allCategories.has(categoryValue)) {
12252
+ allCategories.set(categoryValue, new Date(categoryValue));
12253
+ }
12254
+ allSeriesGroups.add(seriesGroupValue);
12255
+ if (!dataMap.has(categoryValue)) {
12256
+ dataMap.set(categoryValue, new Map());
12257
+ }
12258
+ dataMap.get(categoryValue).set(seriesGroupValue, numericValue);
12259
+ // Track unit per series
12260
+ if (unitField && unitValue) {
12261
+ seriesUnitMap.set(seriesGroupValue, unitValue);
12262
+ }
12263
+ }
12264
+ }
12265
+ // Sort categories chronologically
12266
+ const sortedCategoryEntries = Array.from(allCategories.entries())
12267
+ .sort((a, b) => a[1].getTime() - b[1].getTime());
12268
+ const categories = sortedCategoryEntries.map(([, date]) => this.formatDate(date));
12269
+ const categoryKeys = sortedCategoryEntries.map(([key]) => key);
12270
+ const seriesGroups = Array.from(allSeriesGroups);
12271
+ // Build value axes based on unique units
12272
+ const valueAxes = [];
12273
+ if (unitField && seriesUnitMap.size > 0) {
12274
+ const uniqueUnits = Array.from(new Set(seriesUnitMap.values()));
12275
+ uniqueUnits.forEach((unit, index) => {
12276
+ valueAxes.push({
12277
+ name: `unit_${this.sanitizeAxisName(unit)}`,
12278
+ unit,
12279
+ position: index === 0 ? 'left' : 'right'
12280
+ });
12281
+ });
12282
+ }
12283
+ // Build series data
12284
+ const seriesData = seriesGroups.map(seriesGroup => {
12285
+ const data = categoryKeys.map(categoryKey => {
12286
+ return dataMap.get(categoryKey)?.get(seriesGroup) ?? null;
12287
+ });
12288
+ const unit = seriesUnitMap.get(seriesGroup);
12289
+ const axisName = unit ? `unit_${this.sanitizeAxisName(unit)}` : undefined;
12290
+ return {
12291
+ name: seriesGroup,
12292
+ data,
12293
+ unit,
12294
+ axisName
12295
+ };
12296
+ });
12297
+ this._categories.set(categories);
12298
+ this._seriesData.set(seriesData);
12299
+ this._valueAxes.set(valueAxes);
12300
+ this._seriesUnitMap.set(seriesUnitMap);
12301
+ }
12302
+ /**
12303
+ * Formats a date for display on the category axis.
12304
+ */
12305
+ formatDate(date) {
12306
+ if (isNaN(date.getTime()))
12307
+ return '?';
12308
+ return date.toLocaleDateString('de-AT', {
12309
+ day: '2-digit',
12310
+ month: '2-digit',
12311
+ year: 'numeric'
12312
+ });
12313
+ }
12314
+ sanitizeFieldName(fieldName) {
12315
+ return fieldName.replace(/\./g, '_');
12316
+ }
12317
+ sanitizeAxisName(name) {
12318
+ return name.replace(/[^a-zA-Z0-9]/g, '_');
12319
+ }
12320
+ convertFiltersToDto(filters) {
12321
+ const variables = this.stateService.getVariables();
12322
+ return this.variableService.convertToFieldFilterDto(filters, variables);
12323
+ }
12324
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: LineChartWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12325
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: LineChartWidgetComponent, isStandalone: true, selector: "mm-line-chart-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: `
12326
+ <div class="line-chart-widget" [class.loading]="isLoading()" [class.error]="error()">
12327
+ @if (isNotConfigured()) {
12328
+ <mm-widget-not-configured></mm-widget-not-configured>
12329
+ } @else if (isLoading()) {
12330
+ <div class="loading-indicator">
12331
+ <span>...</span>
12332
+ </div>
12333
+ } @else if (error()) {
12334
+ <div class="error-message">
12335
+ <span>{{ error() }}</span>
12336
+ </div>
12337
+ } @else {
12338
+ <kendo-chart class="chart-container">
12339
+ <kendo-chart-plot-area [margin]="{ top: 0, right: 0, bottom: 0, left: 0 }"></kendo-chart-plot-area>
12340
+
12341
+ <kendo-chart-category-axis>
12342
+ <kendo-chart-category-axis-item [categories]="categories()">
12343
+ <kendo-chart-category-axis-item-labels
12344
+ [rotation]="labelRotation()"
12345
+ [content]="categoryLabelContent">
12346
+ </kendo-chart-category-axis-item-labels>
12347
+ </kendo-chart-category-axis-item>
12348
+ </kendo-chart-category-axis>
12349
+
12350
+ @if (valueAxes().length > 0) {
12351
+ <kendo-chart-value-axis>
12352
+ @for (axis of valueAxes(); track axis.name) {
12353
+ <kendo-chart-value-axis-item
12354
+ [name]="axis.name"
12355
+ [title]="{ text: axis.unit }">
12356
+ </kendo-chart-value-axis-item>
12357
+ }
12358
+ </kendo-chart-value-axis>
12359
+ }
12360
+
12361
+ <kendo-chart-series>
12362
+ @for (series of seriesData(); track series.name) {
12363
+ <kendo-chart-series-item
12364
+ [type]="chartType()"
12365
+ [data]="series.data"
12366
+ [name]="series.name"
12367
+ [axis]="series.axisName ?? ''"
12368
+ [markers]="{ visible: config.showMarkers ?? false }">
12369
+ </kendo-chart-series-item>
12370
+ }
12371
+ </kendo-chart-series>
12372
+
12373
+ <kendo-chart-legend
12374
+ [visible]="config.showLegend !== false"
12375
+ [position]="config.legendPosition ?? 'right'">
12376
+ </kendo-chart-legend>
12377
+
12378
+ <kendo-chart-tooltip>
12379
+ <ng-template kendoChartSeriesTooltipTemplate let-value="value" let-category="category" let-series="series">
12380
+ <div class="chart-tooltip">
12381
+ <strong>{{ category }}</strong><br/>
12382
+ {{ series.name }}: {{ formatValue(value) }}{{ getUnitForSeries(series.name) }}
12383
+ </div>
12384
+ </ng-template>
12385
+ </kendo-chart-tooltip>
12386
+ </kendo-chart>
12387
+ }
12388
+ </div>
12389
+ `, 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" }] });
12390
+ }
12391
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: LineChartWidgetComponent, decorators: [{
12392
+ type: Component,
12393
+ args: [{ selector: 'mm-line-chart-widget', standalone: true, imports: [
12394
+ CommonModule,
12395
+ ChartsModule,
12396
+ WidgetNotConfiguredComponent
12397
+ ], template: `
12398
+ <div class="line-chart-widget" [class.loading]="isLoading()" [class.error]="error()">
12399
+ @if (isNotConfigured()) {
12400
+ <mm-widget-not-configured></mm-widget-not-configured>
12401
+ } @else if (isLoading()) {
12402
+ <div class="loading-indicator">
12403
+ <span>...</span>
12404
+ </div>
12405
+ } @else if (error()) {
12406
+ <div class="error-message">
12407
+ <span>{{ error() }}</span>
12408
+ </div>
12409
+ } @else {
12410
+ <kendo-chart class="chart-container">
12411
+ <kendo-chart-plot-area [margin]="{ top: 0, right: 0, bottom: 0, left: 0 }"></kendo-chart-plot-area>
12412
+
12413
+ <kendo-chart-category-axis>
12414
+ <kendo-chart-category-axis-item [categories]="categories()">
12415
+ <kendo-chart-category-axis-item-labels
12416
+ [rotation]="labelRotation()"
12417
+ [content]="categoryLabelContent">
12418
+ </kendo-chart-category-axis-item-labels>
12419
+ </kendo-chart-category-axis-item>
12420
+ </kendo-chart-category-axis>
12421
+
12422
+ @if (valueAxes().length > 0) {
12423
+ <kendo-chart-value-axis>
12424
+ @for (axis of valueAxes(); track axis.name) {
12425
+ <kendo-chart-value-axis-item
12426
+ [name]="axis.name"
12427
+ [title]="{ text: axis.unit }">
12428
+ </kendo-chart-value-axis-item>
12429
+ }
12430
+ </kendo-chart-value-axis>
12431
+ }
12432
+
12433
+ <kendo-chart-series>
12434
+ @for (series of seriesData(); track series.name) {
12435
+ <kendo-chart-series-item
12436
+ [type]="chartType()"
12437
+ [data]="series.data"
12438
+ [name]="series.name"
12439
+ [axis]="series.axisName ?? ''"
12440
+ [markers]="{ visible: config.showMarkers ?? false }">
12441
+ </kendo-chart-series-item>
12442
+ }
12443
+ </kendo-chart-series>
12444
+
12445
+ <kendo-chart-legend
12446
+ [visible]="config.showLegend !== false"
12447
+ [position]="config.legendPosition ?? 'right'">
12448
+ </kendo-chart-legend>
12449
+
12450
+ <kendo-chart-tooltip>
12451
+ <ng-template kendoChartSeriesTooltipTemplate let-value="value" let-category="category" let-series="series">
12452
+ <div class="chart-tooltip">
12453
+ <strong>{{ category }}</strong><br/>
12454
+ {{ series.name }}: {{ formatValue(value) }}{{ getUnitForSeries(series.name) }}
12455
+ </div>
12456
+ </ng-template>
12457
+ </kendo-chart-tooltip>
12458
+ </kendo-chart>
12459
+ }
12460
+ </div>
12461
+ `, 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"] }]
12462
+ }], propDecorators: { config: [{
12463
+ type: Input
12464
+ }] } });
12465
+
12466
+ /**
12467
+ * Configuration dialog for Line Chart widgets.
12468
+ * Supports line and area chart types with multi-series and multi-axis configuration.
12469
+ */
12470
+ class LineChartConfigDialogComponent {
12471
+ executeRuntimeQueryGQL = inject(ExecuteRuntimeQueryDtoGQL);
12472
+ stateService = inject(MeshBoardStateService);
12473
+ windowRef = inject(WindowRef);
12474
+ querySelector;
12475
+ // Initial values for editing
12476
+ initialQueryRtId;
12477
+ initialQueryName;
12478
+ initialChartType;
12479
+ initialCategoryField;
12480
+ initialSeriesGroupField;
12481
+ initialValueField;
12482
+ initialUnitField;
12483
+ initialShowLegend;
12484
+ initialLegendPosition;
12485
+ initialShowMarkers;
12486
+ initialFilters;
12487
+ // State
12488
+ isLoadingInitial = false;
12489
+ isLoadingColumns = false;
12490
+ // Persistent Query
12491
+ selectedPersistentQuery = null;
12492
+ queryColumns = [];
12493
+ numericColumns = [];
12494
+ nonNumericColumns = [];
12495
+ // Filter state
12496
+ filters = [];
12497
+ filterVariables = [];
12498
+ // Legend position options
12499
+ legendPositions = [
12500
+ { value: 'top', label: 'Top' },
12501
+ { value: 'bottom', label: 'Bottom' },
12502
+ { value: 'left', label: 'Left' },
12503
+ { value: 'right', label: 'Right' }
12504
+ ];
12505
+ // Form
12506
+ form = {
12507
+ chartType: 'line',
12508
+ categoryField: '',
12509
+ seriesGroupField: '',
12510
+ valueField: '',
12511
+ unitField: '',
12512
+ showLegend: true,
12513
+ legendPosition: 'right',
12514
+ showMarkers: false
12515
+ };
12516
+ get isValid() {
12517
+ return this.selectedPersistentQuery !== null
12518
+ && this.form.categoryField !== ''
12519
+ && this.form.seriesGroupField !== ''
12520
+ && this.form.valueField !== '';
12521
+ }
12522
+ async ngOnInit() {
12523
+ // Initialize filter variables from MeshBoard state
12524
+ this.filterVariables = this.stateService.getVariables().map(v => ({
12525
+ name: v.name,
12526
+ label: v.label || v.name,
12527
+ type: v.type
12528
+ }));
12529
+ // Initialize form with initial values
12530
+ this.form.chartType = this.initialChartType ?? 'line';
12531
+ this.form.categoryField = this.initialCategoryField ?? '';
12532
+ this.form.seriesGroupField = this.initialSeriesGroupField ?? '';
12533
+ this.form.valueField = this.initialValueField ?? '';
12534
+ this.form.unitField = this.initialUnitField ?? '';
12535
+ this.form.showLegend = this.initialShowLegend ?? true;
12536
+ this.form.legendPosition = this.initialLegendPosition ?? 'right';
12537
+ this.form.showMarkers = this.initialShowMarkers ?? false;
12538
+ // Initialize filters
12539
+ if (this.initialFilters && this.initialFilters.length > 0) {
12540
+ this.filters = this.initialFilters.map((f, index) => ({
12541
+ id: index + 1,
12542
+ attributePath: f.attributePath,
12543
+ operator: f.operator,
12544
+ comparisonValue: f.comparisonValue
12545
+ }));
12546
+ }
12547
+ // If editing with initial query, load it after view init
12548
+ if (this.initialQueryRtId) {
12549
+ this.isLoadingInitial = true;
12550
+ setTimeout(async () => {
12551
+ if (this.querySelector) {
12552
+ const query = await this.querySelector.selectByRtId(this.initialQueryRtId);
12553
+ if (query) {
12554
+ this.selectedPersistentQuery = query;
12555
+ await this.loadQueryColumns(query.rtId);
12556
+ }
12557
+ }
12558
+ this.isLoadingInitial = false;
12559
+ }, 100);
12560
+ }
12561
+ }
12562
+ async onQuerySelected(query) {
12563
+ this.selectedPersistentQuery = query;
12564
+ this.queryColumns = [];
12565
+ this.numericColumns = [];
12566
+ this.nonNumericColumns = [];
12567
+ this.filters = [];
12568
+ this.form.categoryField = '';
12569
+ this.form.seriesGroupField = '';
12570
+ this.form.valueField = '';
12571
+ this.form.unitField = '';
12572
+ if (query) {
12573
+ await this.loadQueryColumns(query.rtId);
12574
+ }
12575
+ }
12576
+ async loadQueryColumns(queryRtId) {
12577
+ this.isLoadingColumns = true;
12578
+ try {
12579
+ const result = await firstValueFrom(this.executeRuntimeQueryGQL.fetch({
12580
+ variables: {
12581
+ rtId: queryRtId,
12582
+ first: 1
12583
+ }
12584
+ }));
12585
+ const queryItems = result.data?.runtime?.runtimeQuery?.items ?? [];
12586
+ if (queryItems.length > 0 && queryItems[0]) {
12587
+ const columns = queryItems[0].columns ?? [];
12588
+ const filteredColumns = columns
12589
+ .filter((c) => c !== null);
12590
+ this.queryColumns = filteredColumns.map(c => ({
12591
+ attributePath: this.sanitizeFieldName(c.attributePath ?? ''),
12592
+ attributeValueType: c.attributeValueType ?? ''
12593
+ }));
12594
+ const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
12595
+ this.numericColumns = this.queryColumns.filter(c => numericTypes.includes(c.attributeValueType));
12596
+ this.nonNumericColumns = this.queryColumns.filter(c => !numericTypes.includes(c.attributeValueType));
12597
+ }
12598
+ }
12599
+ catch (error) {
12600
+ console.error('Error loading query columns:', error);
12601
+ this.queryColumns = [];
12602
+ this.numericColumns = [];
12603
+ this.nonNumericColumns = [];
12604
+ }
12605
+ finally {
12606
+ this.isLoadingColumns = false;
12607
+ }
12608
+ }
12609
+ sanitizeFieldName(fieldName) {
12610
+ return fieldName.replace(/\./g, '_');
12611
+ }
12612
+ onFiltersChange(updatedFilters) {
12613
+ this.filters = updatedFilters;
12614
+ }
12615
+ onSave() {
12616
+ if (!this.selectedPersistentQuery)
12617
+ return;
12618
+ const filtersDto = this.filters.length > 0
12619
+ ? this.filters.map(f => ({
12620
+ attributePath: f.attributePath,
12621
+ operator: f.operator,
12622
+ comparisonValue: f.comparisonValue
12623
+ }))
12624
+ : undefined;
12625
+ const result = {
12626
+ ckTypeId: '',
12627
+ rtId: '',
12628
+ queryRtId: this.selectedPersistentQuery.rtId,
12629
+ queryName: this.selectedPersistentQuery.name,
12630
+ chartType: this.form.chartType,
12631
+ categoryField: this.form.categoryField,
12632
+ seriesGroupField: this.form.seriesGroupField,
12633
+ valueField: this.form.valueField,
12634
+ unitField: this.form.unitField || undefined,
12635
+ showLegend: this.form.showLegend,
12636
+ legendPosition: this.form.legendPosition,
12637
+ showMarkers: this.form.showMarkers,
12638
+ filters: filtersDto
12639
+ };
12640
+ this.windowRef.close(result);
12641
+ }
12642
+ onCancel() {
12643
+ this.windowRef.close();
12644
+ }
12645
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: LineChartConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12646
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", 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: `
12647
+ <div class="config-container">
12648
+
12649
+ <div class="config-form" [class.loading]="isLoadingInitial">
12650
+ <mm-loading-overlay [loading]="isLoadingInitial" />
12651
+
12652
+ <!-- Data Source Section -->
12653
+ <div class="config-section">
12654
+ <h3 class="section-title">Data Source</h3>
12655
+
12656
+ <div class="form-field">
12657
+ <label>Query <span class="required">*</span></label>
12658
+ <mm-query-selector
12659
+ #querySelector
12660
+ [(ngModel)]="selectedPersistentQuery"
12661
+ (querySelected)="onQuerySelected($event)"
12662
+ placeholder="Select a Query..."
12663
+ hint="Select a query that returns time-series data with grouping.">
12664
+ </mm-query-selector>
12665
+ </div>
12666
+ </div>
12667
+
12668
+ <!-- Field Mapping Section -->
12669
+ @if (selectedPersistentQuery && queryColumns.length > 0) {
12670
+ <div class="config-section">
12671
+ <h3 class="section-title">Field Mapping</h3>
12672
+
12673
+ <div class="form-field">
12674
+ <label>Category Field (X-Axis) <span class="required">*</span></label>
12675
+ <kendo-combobox
12676
+ [data]="queryColumns"
12677
+ [textField]="'attributePath'"
12678
+ [valueField]="'attributePath'"
12679
+ [valuePrimitive]="true"
12680
+ [(ngModel)]="form.categoryField"
12681
+ placeholder="Select date/time field...">
12682
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
12683
+ <div class="column-item">
12684
+ <span class="column-path">{{ dataItem.attributePath }}</span>
12685
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
12686
+ </div>
12687
+ </ng-template>
12688
+ </kendo-combobox>
12689
+ <p class="field-hint">Date/time field used for the X-axis (e.g., timeRange_from).</p>
12690
+ </div>
12691
+
12692
+ <div class="form-field">
12693
+ <label>Series Group Field <span class="required">*</span></label>
12694
+ <kendo-combobox
12695
+ [data]="nonNumericColumns"
12696
+ [textField]="'attributePath'"
12697
+ [valueField]="'attributePath'"
12698
+ [valuePrimitive]="true"
12699
+ [(ngModel)]="form.seriesGroupField"
12700
+ placeholder="Select grouping field...">
12701
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
12702
+ <div class="column-item">
12703
+ <span class="column-path">{{ dataItem.attributePath }}</span>
12704
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
12705
+ </div>
12706
+ </ng-template>
12707
+ </kendo-combobox>
12708
+ <p class="field-hint">Each unique value becomes a separate line (e.g., OBIS code).</p>
12709
+ </div>
12710
+
12711
+ <div class="form-field">
12712
+ <label>Value Field (Y-Axis) <span class="required">*</span></label>
12713
+ <kendo-combobox
12714
+ [data]="numericColumns"
12715
+ [textField]="'attributePath'"
12716
+ [valueField]="'attributePath'"
12717
+ [valuePrimitive]="true"
12718
+ [(ngModel)]="form.valueField"
12719
+ placeholder="Select value field...">
12720
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
12721
+ <div class="column-item">
12722
+ <span class="column-path">{{ dataItem.attributePath }}</span>
12723
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
12724
+ </div>
12725
+ </ng-template>
12726
+ </kendo-combobox>
12727
+ <p class="field-hint">Numeric field for Y-axis values (e.g., sum).</p>
12728
+ </div>
12729
+
12730
+ <div class="form-field">
12731
+ <label>Unit Field</label>
12732
+ <kendo-combobox
12733
+ [data]="nonNumericColumns"
12734
+ [textField]="'attributePath'"
12735
+ [valueField]="'attributePath'"
12736
+ [valuePrimitive]="true"
12737
+ [(ngModel)]="form.unitField"
12738
+ [clearButton]="true"
12739
+ placeholder="Select unit field (optional)...">
12740
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
12741
+ <div class="column-item">
12742
+ <span class="column-path">{{ dataItem.attributePath }}</span>
12743
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
12744
+ </div>
12745
+ </ng-template>
12746
+ </kendo-combobox>
12747
+ <p class="field-hint">Optional unit field (e.g., kWh, m3). Creates separate Y-axes per unit.</p>
12748
+ </div>
12749
+ </div>
12750
+ }
12751
+
12752
+ <!-- Filters Section -->
12753
+ @if (selectedPersistentQuery?.queryCkTypeId) {
12754
+ <div class="config-section">
12755
+ <h3 class="section-title">Filters</h3>
12756
+ <p class="section-hint">Define filters to narrow down the data.</p>
12757
+ <mm-field-filter-editor
12758
+ [ckTypeId]="selectedPersistentQuery?.queryCkTypeId ?? undefined"
12759
+ [filters]="filters"
12760
+ [enableVariables]="filterVariables.length > 0"
12761
+ [availableVariables]="filterVariables"
12762
+ (filtersChange)="onFiltersChange($event)">
12763
+ </mm-field-filter-editor>
12764
+ </div>
12765
+ }
12766
+
12767
+ <!-- Chart Options Section -->
12768
+ <div class="config-section">
12769
+ <h3 class="section-title">Chart Options</h3>
12770
+
12771
+ <div class="form-field">
12772
+ <label>Chart Type</label>
12773
+ <div class="chart-type-grid">
12774
+ <label class="radio-label">
12775
+ <input type="radio"
12776
+ name="chartType"
12777
+ value="line"
12778
+ [(ngModel)]="form.chartType"
12779
+ kendoRadioButton />
12780
+ <span>Line</span>
12781
+ </label>
12782
+ <label class="radio-label">
12783
+ <input type="radio"
12784
+ name="chartType"
12785
+ value="area"
12786
+ [(ngModel)]="form.chartType"
12787
+ kendoRadioButton />
12788
+ <span>Area</span>
12789
+ </label>
12790
+ </div>
12791
+ </div>
12792
+
12793
+ <div class="form-row">
12794
+ <div class="form-field checkbox-field">
12795
+ <label>
12796
+ <input type="checkbox" [(ngModel)]="form.showLegend" kendoCheckBox />
12797
+ Show Legend
12798
+ </label>
12799
+ </div>
12800
+
12801
+ <div class="form-field checkbox-field">
12802
+ <label>
12803
+ <input type="checkbox" [(ngModel)]="form.showMarkers" kendoCheckBox />
12804
+ Show Markers
12805
+ </label>
12806
+ </div>
12807
+ </div>
12808
+
12809
+ @if (form.showLegend) {
12810
+ <div class="form-field">
12811
+ <label>Legend Position</label>
12812
+ <kendo-dropdownlist
12813
+ [data]="legendPositions"
12814
+ [textField]="'label'"
12815
+ [valueField]="'value'"
12816
+ [valuePrimitive]="true"
12817
+ [(ngModel)]="form.legendPosition">
12818
+ </kendo-dropdownlist>
12819
+ </div>
12820
+ }
12821
+ </div>
12822
+ </div>
12823
+
12824
+ <div class="action-bar">
12825
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
12826
+ <button
12827
+ kendoButton
12828
+ themeColor="primary"
12829
+ [disabled]="!isValid"
12830
+ (click)="onSave()">
12831
+ Save
12832
+ </button>
12833
+ </div>
12834
+ </div>
12835
+ `, 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}.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", "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"] }] });
12836
+ }
12837
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: LineChartConfigDialogComponent, decorators: [{
12838
+ type: Component,
12839
+ args: [{ selector: 'mm-line-chart-config-dialog', standalone: true, imports: [
12840
+ CommonModule,
12841
+ FormsModule,
12842
+ ButtonsModule,
12843
+ InputsModule,
12844
+ DropDownsModule,
12845
+ SVGIconModule,
12846
+ FieldFilterEditorComponent,
12847
+ QuerySelectorComponent,
12848
+ LoadingOverlayComponent
12849
+ ], template: `
12850
+ <div class="config-container">
12851
+
12852
+ <div class="config-form" [class.loading]="isLoadingInitial">
12853
+ <mm-loading-overlay [loading]="isLoadingInitial" />
12854
+
12855
+ <!-- Data Source Section -->
12856
+ <div class="config-section">
12857
+ <h3 class="section-title">Data Source</h3>
12858
+
12859
+ <div class="form-field">
12860
+ <label>Query <span class="required">*</span></label>
12861
+ <mm-query-selector
12862
+ #querySelector
12863
+ [(ngModel)]="selectedPersistentQuery"
12864
+ (querySelected)="onQuerySelected($event)"
12865
+ placeholder="Select a Query..."
12866
+ hint="Select a query that returns time-series data with grouping.">
12867
+ </mm-query-selector>
12868
+ </div>
12869
+ </div>
12870
+
12871
+ <!-- Field Mapping Section -->
12872
+ @if (selectedPersistentQuery && queryColumns.length > 0) {
12873
+ <div class="config-section">
12874
+ <h3 class="section-title">Field Mapping</h3>
12875
+
12876
+ <div class="form-field">
12877
+ <label>Category Field (X-Axis) <span class="required">*</span></label>
12878
+ <kendo-combobox
12879
+ [data]="queryColumns"
12880
+ [textField]="'attributePath'"
12881
+ [valueField]="'attributePath'"
12882
+ [valuePrimitive]="true"
12883
+ [(ngModel)]="form.categoryField"
12884
+ placeholder="Select date/time field...">
12885
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
12886
+ <div class="column-item">
12887
+ <span class="column-path">{{ dataItem.attributePath }}</span>
12888
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
12889
+ </div>
12890
+ </ng-template>
12891
+ </kendo-combobox>
12892
+ <p class="field-hint">Date/time field used for the X-axis (e.g., timeRange_from).</p>
12893
+ </div>
12894
+
12895
+ <div class="form-field">
12896
+ <label>Series Group Field <span class="required">*</span></label>
12897
+ <kendo-combobox
12898
+ [data]="nonNumericColumns"
12899
+ [textField]="'attributePath'"
12900
+ [valueField]="'attributePath'"
12901
+ [valuePrimitive]="true"
12902
+ [(ngModel)]="form.seriesGroupField"
12903
+ placeholder="Select grouping field...">
12904
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
12905
+ <div class="column-item">
12906
+ <span class="column-path">{{ dataItem.attributePath }}</span>
12907
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
12908
+ </div>
12909
+ </ng-template>
12910
+ </kendo-combobox>
12911
+ <p class="field-hint">Each unique value becomes a separate line (e.g., OBIS code).</p>
12912
+ </div>
12913
+
12914
+ <div class="form-field">
12915
+ <label>Value Field (Y-Axis) <span class="required">*</span></label>
12916
+ <kendo-combobox
12917
+ [data]="numericColumns"
12918
+ [textField]="'attributePath'"
12919
+ [valueField]="'attributePath'"
12920
+ [valuePrimitive]="true"
12921
+ [(ngModel)]="form.valueField"
12922
+ placeholder="Select value field...">
12923
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
12924
+ <div class="column-item">
12925
+ <span class="column-path">{{ dataItem.attributePath }}</span>
12926
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
12927
+ </div>
12928
+ </ng-template>
12929
+ </kendo-combobox>
12930
+ <p class="field-hint">Numeric field for Y-axis values (e.g., sum).</p>
12931
+ </div>
12932
+
12933
+ <div class="form-field">
12934
+ <label>Unit Field</label>
12935
+ <kendo-combobox
12936
+ [data]="nonNumericColumns"
12937
+ [textField]="'attributePath'"
12938
+ [valueField]="'attributePath'"
12939
+ [valuePrimitive]="true"
12940
+ [(ngModel)]="form.unitField"
12941
+ [clearButton]="true"
12942
+ placeholder="Select unit field (optional)...">
12943
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
12944
+ <div class="column-item">
12945
+ <span class="column-path">{{ dataItem.attributePath }}</span>
12946
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
12947
+ </div>
12948
+ </ng-template>
12949
+ </kendo-combobox>
12950
+ <p class="field-hint">Optional unit field (e.g., kWh, m3). Creates separate Y-axes per unit.</p>
12951
+ </div>
12952
+ </div>
12953
+ }
12954
+
12955
+ <!-- Filters Section -->
12956
+ @if (selectedPersistentQuery?.queryCkTypeId) {
12957
+ <div class="config-section">
12958
+ <h3 class="section-title">Filters</h3>
12959
+ <p class="section-hint">Define filters to narrow down the data.</p>
12960
+ <mm-field-filter-editor
12961
+ [ckTypeId]="selectedPersistentQuery?.queryCkTypeId ?? undefined"
12962
+ [filters]="filters"
12963
+ [enableVariables]="filterVariables.length > 0"
12964
+ [availableVariables]="filterVariables"
12965
+ (filtersChange)="onFiltersChange($event)">
12966
+ </mm-field-filter-editor>
12967
+ </div>
12968
+ }
12969
+
12970
+ <!-- Chart Options Section -->
12971
+ <div class="config-section">
12972
+ <h3 class="section-title">Chart Options</h3>
12973
+
12974
+ <div class="form-field">
12975
+ <label>Chart Type</label>
12976
+ <div class="chart-type-grid">
12977
+ <label class="radio-label">
12978
+ <input type="radio"
12979
+ name="chartType"
12980
+ value="line"
12981
+ [(ngModel)]="form.chartType"
12982
+ kendoRadioButton />
12983
+ <span>Line</span>
12984
+ </label>
12985
+ <label class="radio-label">
12986
+ <input type="radio"
12987
+ name="chartType"
12988
+ value="area"
12989
+ [(ngModel)]="form.chartType"
12990
+ kendoRadioButton />
12991
+ <span>Area</span>
12992
+ </label>
12993
+ </div>
12994
+ </div>
12995
+
12996
+ <div class="form-row">
12997
+ <div class="form-field checkbox-field">
12998
+ <label>
12999
+ <input type="checkbox" [(ngModel)]="form.showLegend" kendoCheckBox />
13000
+ Show Legend
13001
+ </label>
13002
+ </div>
13003
+
13004
+ <div class="form-field checkbox-field">
13005
+ <label>
13006
+ <input type="checkbox" [(ngModel)]="form.showMarkers" kendoCheckBox />
13007
+ Show Markers
13008
+ </label>
13009
+ </div>
13010
+ </div>
13011
+
13012
+ @if (form.showLegend) {
13013
+ <div class="form-field">
13014
+ <label>Legend Position</label>
13015
+ <kendo-dropdownlist
13016
+ [data]="legendPositions"
13017
+ [textField]="'label'"
13018
+ [valueField]="'value'"
13019
+ [valuePrimitive]="true"
13020
+ [(ngModel)]="form.legendPosition">
13021
+ </kendo-dropdownlist>
13022
+ </div>
13023
+ }
13024
+ </div>
13025
+ </div>
13026
+
13027
+ <div class="action-bar">
13028
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
13029
+ <button
13030
+ kendoButton
13031
+ themeColor="primary"
13032
+ [disabled]="!isValid"
13033
+ (click)="onSave()">
13034
+ Save
13035
+ </button>
13036
+ </div>
13037
+ </div>
13038
+ `, 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}.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"] }]
13039
+ }], propDecorators: { querySelector: [{
13040
+ type: ViewChild,
13041
+ args: ['querySelector']
13042
+ }], initialQueryRtId: [{
13043
+ type: Input
13044
+ }], initialQueryName: [{
13045
+ type: Input
13046
+ }], initialChartType: [{
13047
+ type: Input
13048
+ }], initialCategoryField: [{
13049
+ type: Input
13050
+ }], initialSeriesGroupField: [{
13051
+ type: Input
13052
+ }], initialValueField: [{
13053
+ type: Input
13054
+ }], initialUnitField: [{
13055
+ type: Input
13056
+ }], initialShowLegend: [{
13057
+ type: Input
13058
+ }], initialLegendPosition: [{
13059
+ type: Input
13060
+ }], initialShowMarkers: [{
13061
+ type: Input
13062
+ }], initialFilters: [{
13063
+ type: Input
13064
+ }] } });
13065
+
13066
+ const COLOR_SCHEMES = {
13067
+ green: (min, max) => buildGradientRanges(min, max, ['#e8f5e9', '#a5d6a7', '#66bb6a', '#2e7d32', '#1b5e20']),
13068
+ redGreen: (min, max) => buildGradientRanges(min, max, ['#ef5350', '#ff8a65', '#fff176', '#aed581', '#66bb6a']),
13069
+ blue: (min, max) => buildGradientRanges(min, max, ['#e3f2fd', '#90caf9', '#42a5f5', '#1565c0', '#0d47a1']),
13070
+ heat: (min, max) => buildGradientRanges(min, max, ['#fff9c4', '#ffcc02', '#ff9800', '#f44336', '#b71c1c'])
13071
+ };
13072
+ function buildGradientRanges(min, max, colors) {
13073
+ if (max <= min) {
13074
+ return [{ from: min, to: min + 1, color: colors[0] }];
13075
+ }
13076
+ const step = (max - min) / colors.length;
13077
+ return colors.map((color, i) => ({
13078
+ from: min + i * step,
13079
+ to: min + (i + 1) * step,
13080
+ color
13081
+ }));
13082
+ }
13083
+ class HeatmapWidgetComponent {
13084
+ executeRuntimeQueryGQL = inject(ExecuteRuntimeQueryDtoGQL);
13085
+ stateService = inject(MeshBoardStateService);
13086
+ variableService = inject(MeshBoardVariableService);
13087
+ config;
13088
+ // Widget state signals
13089
+ _isLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isLoading" }] : []));
13090
+ _heatmapData = signal([], ...(ngDevMode ? [{ debugName: "_heatmapData" }] : []));
13091
+ _xCategories = signal([], ...(ngDevMode ? [{ debugName: "_xCategories" }] : []));
13092
+ _yCategories = signal([], ...(ngDevMode ? [{ debugName: "_yCategories" }] : []));
13093
+ _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : []));
13094
+ isLoading = this._isLoading.asReadonly();
13095
+ heatmapData = this._heatmapData.asReadonly();
13096
+ xCategories = this._xCategories.asReadonly();
13097
+ yCategories = this._yCategories.asReadonly();
13098
+ error = this._error.asReadonly();
13099
+ data = computed(() => this._heatmapData(), ...(ngDevMode ? [{ debugName: "data" }] : []));
13100
+ seriesColor = computed(() => {
13101
+ const scheme = this.config?.colorScheme ?? 'green';
13102
+ const data = this._heatmapData();
13103
+ if (data.length === 0)
13104
+ return '#66bb6a';
13105
+ const values = data.map(d => d.value);
13106
+ const min = Math.min(...values);
13107
+ const max = Math.max(...values);
13108
+ const ranges = COLOR_SCHEMES[scheme](min, max);
13109
+ // Return the middle color as the base series color
13110
+ return ranges[Math.floor(ranges.length / 2)]?.color ?? '#66bb6a';
13111
+ }, ...(ngDevMode ? [{ debugName: "seriesColor" }] : []));
13112
+ isNotConfigured() {
13113
+ const dataSource = this.config?.dataSource;
13114
+ if (!dataSource)
13115
+ return true;
13116
+ if (dataSource.type === 'persistentQuery') {
13117
+ const ds = dataSource;
13118
+ return !ds.queryRtId || !this.config?.dateField;
13119
+ }
13120
+ return true;
13121
+ }
13122
+ ngOnInit() {
13123
+ this.loadData();
13124
+ }
13125
+ ngOnChanges(changes) {
13126
+ if (changes['config'] && !changes['config'].firstChange) {
13127
+ this.loadData();
13128
+ }
13129
+ }
13130
+ refresh() {
13131
+ this.loadData();
13132
+ }
13133
+ hasValidConfig() {
13134
+ if (!this.config?.dataSource)
13135
+ return false;
13136
+ if (this.config.dataSource.type === 'persistentQuery') {
13137
+ const ds = this.config.dataSource;
13138
+ return !!(ds.queryRtId && this.config.dateField);
13139
+ }
13140
+ return false;
13141
+ }
13142
+ formatValue(value) {
13143
+ return value.toLocaleString('de-AT', {
13144
+ minimumFractionDigits: 0,
13145
+ maximumFractionDigits: 2
13146
+ });
13147
+ }
13148
+ xLabelContent = (e) => {
13149
+ // Truncate date labels if needed
13150
+ const maxLen = 10;
13151
+ return e.value.length > maxLen ? e.value.substring(0, maxLen) : e.value;
13152
+ };
13153
+ async loadData() {
13154
+ if (this.isNotConfigured()) {
13155
+ return;
13156
+ }
13157
+ const queryDataSource = this.config.dataSource;
13158
+ this._isLoading.set(true);
13159
+ this._error.set(null);
13160
+ try {
13161
+ const fieldFilter = this.convertFiltersToDto(this.config.filters);
13162
+ const result = await firstValueFrom(this.executeRuntimeQueryGQL.fetch({
13163
+ variables: {
13164
+ rtId: queryDataSource.queryRtId,
13165
+ fieldFilter
13166
+ }
13167
+ }).pipe(catchError(err => {
13168
+ console.error('Error loading Heatmap data:', err);
13169
+ throw err;
13170
+ })));
13171
+ const queryItems = result.data?.runtime?.runtimeQuery?.items ?? [];
13172
+ if (queryItems.length === 0) {
13173
+ this._heatmapData.set([]);
13174
+ this._xCategories.set([]);
13175
+ this._yCategories.set([]);
13176
+ this._isLoading.set(false);
13177
+ return;
13178
+ }
13179
+ const queryResult = queryItems[0];
13180
+ if (!queryResult) {
13181
+ this._heatmapData.set([]);
13182
+ this._xCategories.set([]);
13183
+ this._yCategories.set([]);
13184
+ this._isLoading.set(false);
13185
+ return;
13186
+ }
13187
+ const rows = queryResult.rows?.items ?? [];
13188
+ const supportedRowTypes = ['RtSimpleQueryRow', 'RtAggregationQueryRow', 'RtGroupingAggregationQueryRow'];
13189
+ const filteredRows = rows
13190
+ .filter((row) => row !== null)
13191
+ .filter(row => supportedRowTypes.includes(row.__typename ?? ''));
13192
+ this.processHeatmapData(filteredRows);
13193
+ this._isLoading.set(false);
13194
+ }
13195
+ catch (err) {
13196
+ console.error('Error loading Heatmap data:', err);
13197
+ this._error.set('Failed to load data');
13198
+ this._isLoading.set(false);
13199
+ }
13200
+ }
13201
+ /**
13202
+ * Processes query rows into heatmap data.
13203
+ * When dateEndField is configured, auto-detects the interval width and shows sub-hour columns.
13204
+ * Otherwise aggregates into hourly buckets.
13205
+ */
13206
+ processHeatmapData(filteredRows) {
13207
+ const dateField = this.sanitizeFieldName(this.config.dateField);
13208
+ const dateEndField = this.config.dateEndField ? this.sanitizeFieldName(this.config.dateEndField) : null;
13209
+ const valueField = this.config.valueField ? this.sanitizeFieldName(this.config.valueField) : null;
13210
+ const aggregation = this.config.aggregation ?? 'count';
13211
+ const parsedRows = [];
13212
+ for (const row of filteredRows) {
13213
+ const queryRow = row;
13214
+ const cells = queryRow.cells?.items ?? [];
13215
+ let dateFrom = null;
13216
+ let dateTo = null;
13217
+ let numericValue = 1; // default for count
13218
+ for (const cell of cells) {
13219
+ if (!cell?.attributePath)
13220
+ continue;
13221
+ const sanitizedPath = this.sanitizeFieldName(cell.attributePath);
13222
+ if (sanitizedPath === dateField) {
13223
+ dateFrom = this.parseDate(cell.value);
13224
+ }
13225
+ else if (dateEndField && sanitizedPath === dateEndField) {
13226
+ dateTo = this.parseDate(cell.value);
13227
+ }
13228
+ else if (valueField && sanitizedPath === valueField) {
13229
+ const val = cell.value;
13230
+ numericValue = typeof val === 'number' ? val : parseFloat(String(val));
13231
+ if (isNaN(numericValue))
13232
+ numericValue = 0;
13233
+ }
13234
+ }
13235
+ if (dateFrom) {
13236
+ parsedRows.push({ dateFrom, dateTo, numericValue });
13237
+ }
13238
+ }
13239
+ if (parsedRows.length === 0) {
13240
+ this._heatmapData.set([]);
13241
+ this._xCategories.set([]);
13242
+ this._yCategories.set([]);
13243
+ return;
13244
+ }
13245
+ // Detect interval width in minutes from the first row that has both from and to
13246
+ const intervalMinutes = this.detectIntervalMinutes(parsedRows);
13247
+ const hasSubHourIntervals = dateEndField && intervalMinutes > 0 && intervalMinutes < 60;
13248
+ if (hasSubHourIntervals) {
13249
+ this.processSubHourData(parsedRows, intervalMinutes, aggregation);
13250
+ }
13251
+ else {
13252
+ this.processHourlyData(parsedRows, aggregation);
13253
+ }
13254
+ }
13255
+ /**
13256
+ * Detects the interval width in minutes from the first row with from+to.
13257
+ */
13258
+ detectIntervalMinutes(rows) {
13259
+ for (const row of rows) {
13260
+ if (row.dateTo) {
13261
+ const diffMs = row.dateTo.getTime() - row.dateFrom.getTime();
13262
+ if (diffMs > 0) {
13263
+ return Math.round(diffMs / 60000);
13264
+ }
13265
+ }
13266
+ }
13267
+ return 60; // default: hourly
13268
+ }
13269
+ /**
13270
+ * Processes data into hourly buckets (original behavior).
13271
+ */
13272
+ processHourlyData(rows, aggregation) {
13273
+ const buckets = new Map();
13274
+ for (const { dateFrom, numericValue } of rows) {
13275
+ const dateKey = this.formatDateKey(dateFrom);
13276
+ const hour = dateFrom.getUTCHours();
13277
+ if (!buckets.has(dateKey))
13278
+ buckets.set(dateKey, new Map());
13279
+ const hourMap = buckets.get(dateKey);
13280
+ if (!hourMap.has(hour))
13281
+ hourMap.set(hour, []);
13282
+ hourMap.get(hour).push(numericValue);
13283
+ }
13284
+ const hourLabels = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0') + ':00');
13285
+ const yCategories = [...hourLabels].reverse();
13286
+ const sortedDates = Array.from(buckets.keys()).sort();
13287
+ const heatmapData = [];
13288
+ for (const date of sortedDates) {
13289
+ const hourMap = buckets.get(date);
13290
+ for (let h = 0; h < 24; h++) {
13291
+ const values = hourMap.get(h);
13292
+ const value = values ? this.aggregate(values, aggregation) : 0;
13293
+ heatmapData.push({ date, hour: hourLabels[h], value });
13294
+ }
13295
+ }
13296
+ this._heatmapData.set(heatmapData);
13297
+ this._xCategories.set(sortedDates);
13298
+ this._yCategories.set(yCategories);
13299
+ }
13300
+ /**
13301
+ * Processes data into sub-hour interval buckets.
13302
+ * Y-axis: hours (00:00..23:00), X-axis: interval labels (e.g. "00-15", "15-30", ...).
13303
+ * For multi-day data, X-axis labels are prefixed with the date.
13304
+ */
13305
+ processSubHourData(rows, intervalMinutes, aggregation) {
13306
+ const intervalsPerHour = Math.floor(60 / intervalMinutes);
13307
+ // Bucket: xKey (date+interval) -> hour -> values[]
13308
+ const buckets = new Map();
13309
+ const allDates = new Set();
13310
+ for (const { dateFrom, numericValue } of rows) {
13311
+ const dateKey = this.formatDateKey(dateFrom);
13312
+ allDates.add(dateKey);
13313
+ const hour = dateFrom.getUTCHours();
13314
+ const minute = dateFrom.getUTCMinutes();
13315
+ const intervalIndex = Math.floor(minute / intervalMinutes);
13316
+ const intervalStart = intervalIndex * intervalMinutes;
13317
+ const intervalEnd = intervalStart + intervalMinutes;
13318
+ const intervalLabel = intervalStart.toString().padStart(2, '0') + '-' + intervalEnd.toString().padStart(2, '0');
13319
+ // X-key: for single day just interval label, for multi-day prefix with date
13320
+ // We'll decide the format after collecting all dates
13321
+ const compositeKey = `${dateKey}|${intervalLabel}`;
13322
+ if (!buckets.has(compositeKey))
13323
+ buckets.set(compositeKey, new Map());
13324
+ const hourMap = buckets.get(compositeKey);
13325
+ if (!hourMap.has(hour))
13326
+ hourMap.set(hour, []);
13327
+ hourMap.get(hour).push(numericValue);
13328
+ }
13329
+ const sortedDates = Array.from(allDates).sort();
13330
+ const isMultiDay = sortedDates.length > 1;
13331
+ // Build interval labels for X-axis
13332
+ const intervalLabels = [];
13333
+ for (let i = 0; i < intervalsPerHour; i++) {
13334
+ const start = (i * intervalMinutes).toString().padStart(2, '0');
13335
+ const end = ((i + 1) * intervalMinutes).toString().padStart(2, '0');
13336
+ intervalLabels.push(`${start}-${end}`);
13337
+ }
13338
+ // Build X-axis categories (ordered: date × interval)
13339
+ const xCategories = [];
13340
+ for (const date of sortedDates) {
13341
+ for (const interval of intervalLabels) {
13342
+ xCategories.push(isMultiDay ? `${date} ${interval}` : interval);
13343
+ }
13344
+ }
13345
+ // Build hour labels for Y-axis
13346
+ const hourLabels = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0') + ':00');
13347
+ const yCategories = [...hourLabels].reverse();
13348
+ // Build heatmap data: for each x-category × hour, aggregate values
13349
+ const heatmapData = [];
13350
+ for (const date of sortedDates) {
13351
+ for (const interval of intervalLabels) {
13352
+ const compositeKey = `${date}|${interval}`;
13353
+ const xLabel = isMultiDay ? `${date} ${interval}` : interval;
13354
+ const hourMap = buckets.get(compositeKey);
13355
+ for (let h = 0; h < 24; h++) {
13356
+ const values = hourMap?.get(h);
13357
+ const value = values ? this.aggregate(values, aggregation) : 0;
13358
+ heatmapData.push({ date: xLabel, hour: hourLabels[h], value });
13359
+ }
13360
+ }
13361
+ }
13362
+ this._heatmapData.set(heatmapData);
13363
+ this._xCategories.set(xCategories);
13364
+ this._yCategories.set(yCategories);
13365
+ }
13366
+ aggregate(values, aggregation) {
13367
+ if (values.length === 0)
13368
+ return 0;
13369
+ switch (aggregation) {
13370
+ case 'count':
13371
+ return values.length;
13372
+ case 'sum':
13373
+ return values.reduce((a, b) => a + b, 0);
13374
+ case 'avg':
13375
+ return values.reduce((a, b) => a + b, 0) / values.length;
13376
+ default:
13377
+ return values.length;
13378
+ }
13379
+ }
13380
+ parseDate(value) {
13381
+ if (!value)
13382
+ return null;
13383
+ if (value instanceof Date)
13384
+ return value;
13385
+ const date = new Date(String(value));
13386
+ return isNaN(date.getTime()) ? null : date;
13387
+ }
13388
+ formatDateKey(date) {
13389
+ const year = date.getUTCFullYear();
13390
+ const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
13391
+ const day = date.getUTCDate().toString().padStart(2, '0');
13392
+ return `${year}-${month}-${day}`;
13393
+ }
13394
+ sanitizeFieldName(fieldName) {
13395
+ return fieldName.replace(/\./g, '_');
13396
+ }
13397
+ convertFiltersToDto(filters) {
13398
+ const variables = this.stateService.getVariables();
13399
+ return this.variableService.convertToFieldFilterDto(filters, variables);
13400
+ }
13401
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: HeatmapWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13402
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: HeatmapWidgetComponent, isStandalone: true, selector: "mm-heatmap-widget", inputs: { config: "config" }, usesOnChanges: true, ngImport: i0, template: `
13403
+ <div class="heatmap-widget" [class.loading]="isLoading()" [class.error]="error()">
13404
+ @if (isNotConfigured()) {
13405
+ <mm-widget-not-configured></mm-widget-not-configured>
13406
+ } @else if (isLoading()) {
13407
+ <div class="loading-indicator">
13408
+ <span>...</span>
13409
+ </div>
13410
+ } @else if (error()) {
13411
+ <div class="error-message">
13412
+ <span>{{ error() }}</span>
13413
+ </div>
13414
+ } @else {
13415
+ <kendo-chart class="chart-container">
13416
+ <kendo-chart-x-axis>
13417
+ <kendo-chart-x-axis-item [categories]="xCategories()" [title]="{ text: '' }">
13418
+ <kendo-chart-x-axis-item-labels
13419
+ [rotation]="-45"
13420
+ [content]="xLabelContent">
13421
+ </kendo-chart-x-axis-item-labels>
13422
+ </kendo-chart-x-axis-item>
13423
+ </kendo-chart-x-axis>
13424
+
13425
+ <kendo-chart-y-axis>
13426
+ <kendo-chart-y-axis-item [categories]="yCategories()" [title]="{ text: '' }">
13427
+ </kendo-chart-y-axis-item>
13428
+ </kendo-chart-y-axis>
13429
+
13430
+ <kendo-chart-series>
13431
+ <kendo-chart-series-item
13432
+ type="heatmap"
13433
+ [data]="heatmapData()"
13434
+ xField="date"
13435
+ yField="hour"
13436
+ field="value"
13437
+ [color]="seriesColor()">
13438
+ </kendo-chart-series-item>
13439
+ </kendo-chart-series>
13440
+
13441
+ <kendo-chart-legend
13442
+ [visible]="config.showLegend !== false"
13443
+ [position]="config.legendPosition ?? 'bottom'">
13444
+ </kendo-chart-legend>
13445
+
13446
+ <kendo-chart-tooltip>
13447
+ <ng-template kendoChartSeriesTooltipTemplate let-value="value" let-dataItem="dataItem">
13448
+ <div class="chart-tooltip">
13449
+ <strong>{{ dataItem.date }}</strong> {{ dataItem.hour }}<br/>
13450
+ Value: {{ formatValue(value?.value ?? value) }}
13451
+ </div>
13452
+ </ng-template>
13453
+ </kendo-chart-tooltip>
13454
+ </kendo-chart>
13455
+ }
13456
+ </div>
13457
+ `, 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}\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.TooltipComponent, selector: "kendo-chart-tooltip", inputs: ["background", "border", "color", "font", "format", "opacity", "padding", "shared", "visible"] }, { kind: "component", type: WidgetNotConfiguredComponent, selector: "mm-widget-not-configured" }] });
13458
+ }
13459
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: HeatmapWidgetComponent, decorators: [{
13460
+ type: Component,
13461
+ args: [{ selector: 'mm-heatmap-widget', standalone: true, imports: [
13462
+ CommonModule,
13463
+ ChartsModule,
13464
+ WidgetNotConfiguredComponent
13465
+ ], template: `
13466
+ <div class="heatmap-widget" [class.loading]="isLoading()" [class.error]="error()">
13467
+ @if (isNotConfigured()) {
13468
+ <mm-widget-not-configured></mm-widget-not-configured>
13469
+ } @else if (isLoading()) {
13470
+ <div class="loading-indicator">
13471
+ <span>...</span>
13472
+ </div>
13473
+ } @else if (error()) {
13474
+ <div class="error-message">
13475
+ <span>{{ error() }}</span>
13476
+ </div>
13477
+ } @else {
13478
+ <kendo-chart class="chart-container">
13479
+ <kendo-chart-x-axis>
13480
+ <kendo-chart-x-axis-item [categories]="xCategories()" [title]="{ text: '' }">
13481
+ <kendo-chart-x-axis-item-labels
13482
+ [rotation]="-45"
13483
+ [content]="xLabelContent">
13484
+ </kendo-chart-x-axis-item-labels>
13485
+ </kendo-chart-x-axis-item>
13486
+ </kendo-chart-x-axis>
13487
+
13488
+ <kendo-chart-y-axis>
13489
+ <kendo-chart-y-axis-item [categories]="yCategories()" [title]="{ text: '' }">
13490
+ </kendo-chart-y-axis-item>
13491
+ </kendo-chart-y-axis>
13492
+
13493
+ <kendo-chart-series>
13494
+ <kendo-chart-series-item
13495
+ type="heatmap"
13496
+ [data]="heatmapData()"
13497
+ xField="date"
13498
+ yField="hour"
13499
+ field="value"
13500
+ [color]="seriesColor()">
13501
+ </kendo-chart-series-item>
13502
+ </kendo-chart-series>
13503
+
13504
+ <kendo-chart-legend
13505
+ [visible]="config.showLegend !== false"
13506
+ [position]="config.legendPosition ?? 'bottom'">
13507
+ </kendo-chart-legend>
13508
+
13509
+ <kendo-chart-tooltip>
13510
+ <ng-template kendoChartSeriesTooltipTemplate let-value="value" let-dataItem="dataItem">
13511
+ <div class="chart-tooltip">
13512
+ <strong>{{ dataItem.date }}</strong> {{ dataItem.hour }}<br/>
13513
+ Value: {{ formatValue(value?.value ?? value) }}
13514
+ </div>
13515
+ </ng-template>
13516
+ </kendo-chart-tooltip>
13517
+ </kendo-chart>
13518
+ }
13519
+ </div>
13520
+ `, 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}\n"] }]
13521
+ }], propDecorators: { config: [{
13522
+ type: Input
13523
+ }] } });
13524
+
13525
+ class HeatmapConfigDialogComponent {
13526
+ executeRuntimeQueryGQL = inject(ExecuteRuntimeQueryDtoGQL);
13527
+ stateService = inject(MeshBoardStateService);
13528
+ windowRef = inject(WindowRef);
13529
+ querySelector;
13530
+ // Initial values for editing
13531
+ initialQueryRtId;
13532
+ initialQueryName;
13533
+ initialDateField;
13534
+ initialDateEndField;
13535
+ initialValueField;
13536
+ initialAggregation;
13537
+ initialColorScheme;
13538
+ initialShowLegend;
13539
+ initialLegendPosition;
13540
+ initialFilters;
13541
+ // State
13542
+ isLoadingInitial = false;
13543
+ isLoadingColumns = false;
13544
+ // Persistent Query
13545
+ selectedPersistentQuery = null;
13546
+ queryColumns = [];
13547
+ numericColumns = [];
13548
+ dateTimeColumns = [];
13549
+ // Filter state
13550
+ filters = [];
13551
+ filterVariables = [];
13552
+ // Aggregation options
13553
+ aggregationOptions = [
13554
+ { value: 'count', label: 'Count (number of rows)' },
13555
+ { value: 'sum', label: 'Sum (total of values)' },
13556
+ { value: 'avg', label: 'Average (mean of values)' }
13557
+ ];
13558
+ // Color scheme options
13559
+ colorSchemeOptions = [
13560
+ { value: 'green', label: 'Green', previewColor: '#66bb6a' },
13561
+ { value: 'redGreen', label: 'Red-Green', previewColor: '#aed581' },
13562
+ { value: 'blue', label: 'Blue', previewColor: '#42a5f5' },
13563
+ { value: 'heat', label: 'Heat', previewColor: '#ff9800' }
13564
+ ];
13565
+ // Legend position options
13566
+ legendPositions = [
13567
+ { value: 'top', label: 'Top' },
13568
+ { value: 'bottom', label: 'Bottom' },
13569
+ { value: 'left', label: 'Left' },
13570
+ { value: 'right', label: 'Right' }
13571
+ ];
13572
+ // Form
13573
+ form = {
13574
+ dateField: '',
13575
+ dateEndField: '',
13576
+ valueField: '',
13577
+ aggregation: 'count',
13578
+ colorScheme: 'green',
13579
+ showLegend: true,
13580
+ legendPosition: 'bottom'
13581
+ };
13582
+ get isValid() {
13583
+ return this.selectedPersistentQuery !== null && this.form.dateField !== '';
13584
+ }
13585
+ async ngOnInit() {
13586
+ // Initialize filter variables from MeshBoard state
13587
+ this.filterVariables = this.stateService.getVariables().map(v => ({
13588
+ name: v.name,
13589
+ label: v.label || v.name,
13590
+ type: v.type
13591
+ }));
13592
+ // Initialize form with initial values
13593
+ this.form.dateField = this.initialDateField ?? '';
13594
+ this.form.dateEndField = this.initialDateEndField;
13595
+ this.form.valueField = this.initialValueField;
13596
+ this.form.aggregation = this.initialAggregation ?? 'count';
13597
+ this.form.colorScheme = this.initialColorScheme ?? 'green';
13598
+ this.form.showLegend = this.initialShowLegend ?? true;
13599
+ this.form.legendPosition = this.initialLegendPosition ?? 'bottom';
13600
+ // Initialize filters
13601
+ if (this.initialFilters && this.initialFilters.length > 0) {
13602
+ this.filters = this.initialFilters.map((f, index) => ({
13603
+ id: index + 1,
13604
+ attributePath: f.attributePath,
13605
+ operator: f.operator,
13606
+ comparisonValue: f.comparisonValue
13607
+ }));
13608
+ }
13609
+ // If editing with initial query, load it after view init
13610
+ if (this.initialQueryRtId) {
13611
+ this.isLoadingInitial = true;
13612
+ setTimeout(async () => {
13613
+ if (this.querySelector) {
13614
+ const query = await this.querySelector.selectByRtId(this.initialQueryRtId);
13615
+ if (query) {
13616
+ this.selectedPersistentQuery = query;
13617
+ await this.loadQueryColumns(query.rtId);
13618
+ }
13619
+ }
13620
+ this.isLoadingInitial = false;
13621
+ }, 100);
13622
+ }
13623
+ }
13624
+ async onQuerySelected(query) {
13625
+ this.selectedPersistentQuery = query;
13626
+ this.queryColumns = [];
13627
+ this.numericColumns = [];
13628
+ this.dateTimeColumns = [];
13629
+ this.filters = [];
13630
+ this.form.dateField = '';
13631
+ this.form.dateEndField = undefined;
13632
+ this.form.valueField = undefined;
13633
+ if (query) {
13634
+ await this.loadQueryColumns(query.rtId);
13635
+ }
13636
+ }
13637
+ async loadQueryColumns(queryRtId) {
13638
+ this.isLoadingColumns = true;
13639
+ try {
13640
+ const result = await firstValueFrom(this.executeRuntimeQueryGQL.fetch({
13641
+ variables: {
13642
+ rtId: queryRtId,
13643
+ first: 1
13644
+ }
13645
+ }));
13646
+ const queryItems = result.data?.runtime?.runtimeQuery?.items ?? [];
13647
+ if (queryItems.length > 0 && queryItems[0]) {
13648
+ const columns = queryItems[0].columns ?? [];
13649
+ const filteredColumns = columns
13650
+ .filter((c) => c !== null);
13651
+ this.queryColumns = filteredColumns.map(c => ({
13652
+ attributePath: this.sanitizeFieldName(c.attributePath ?? ''),
13653
+ attributeValueType: c.attributeValueType ?? ''
13654
+ }));
13655
+ const numericTypes = ['INTEGER', 'FLOAT', 'DOUBLE', 'DECIMAL', 'LONG'];
13656
+ const dateTimeTypes = ['DATE_TIME', 'DATETIME', 'DATE'];
13657
+ this.numericColumns = this.queryColumns.filter(c => numericTypes.includes(c.attributeValueType));
13658
+ this.dateTimeColumns = this.queryColumns.filter(c => dateTimeTypes.includes(c.attributeValueType));
13659
+ // If no explicit datetime columns found, also allow string columns
13660
+ // (datetime values are sometimes returned as strings)
13661
+ if (this.dateTimeColumns.length === 0) {
13662
+ this.dateTimeColumns = this.queryColumns;
13663
+ }
13664
+ // Auto-select first datetime column if not editing
13665
+ if (!this.initialQueryRtId && this.dateTimeColumns.length > 0) {
13666
+ this.form.dateField = this.dateTimeColumns[0].attributePath;
13667
+ }
13668
+ }
13669
+ }
13670
+ catch (error) {
13671
+ console.error('Error loading query columns:', error);
13672
+ this.queryColumns = [];
13673
+ this.numericColumns = [];
13674
+ this.dateTimeColumns = [];
13675
+ }
13676
+ finally {
13677
+ this.isLoadingColumns = false;
13678
+ }
13679
+ }
13680
+ sanitizeFieldName(fieldName) {
13681
+ return fieldName.replace(/\./g, '_');
13682
+ }
13683
+ onFiltersChange(updatedFilters) {
13684
+ this.filters = updatedFilters;
13685
+ }
13686
+ onSave() {
13687
+ if (!this.selectedPersistentQuery)
13688
+ return;
13689
+ const filtersDto = this.filters.length > 0
13690
+ ? this.filters.map(f => ({
13691
+ attributePath: f.attributePath,
13692
+ operator: f.operator,
13693
+ comparisonValue: f.comparisonValue
13694
+ }))
13695
+ : undefined;
13696
+ const result = {
13697
+ ckTypeId: '',
13698
+ rtId: '',
13699
+ queryRtId: this.selectedPersistentQuery.rtId,
13700
+ queryName: this.selectedPersistentQuery.name,
13701
+ dateField: this.form.dateField,
13702
+ dateEndField: this.form.dateEndField || undefined,
13703
+ valueField: this.form.valueField || undefined,
13704
+ aggregation: this.form.aggregation,
13705
+ colorScheme: this.form.colorScheme,
13706
+ showLegend: this.form.showLegend,
13707
+ legendPosition: this.form.legendPosition,
13708
+ filters: filtersDto
13709
+ };
13710
+ this.windowRef.close(result);
13711
+ }
13712
+ onCancel() {
13713
+ this.windowRef.close();
13714
+ }
13715
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: HeatmapConfigDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13716
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: HeatmapConfigDialogComponent, isStandalone: true, selector: "mm-heatmap-config-dialog", inputs: { initialQueryRtId: "initialQueryRtId", initialQueryName: "initialQueryName", initialDateField: "initialDateField", initialDateEndField: "initialDateEndField", initialValueField: "initialValueField", initialAggregation: "initialAggregation", initialColorScheme: "initialColorScheme", initialShowLegend: "initialShowLegend", initialLegendPosition: "initialLegendPosition", initialFilters: "initialFilters" }, viewQueries: [{ propertyName: "querySelector", first: true, predicate: ["querySelector"], descendants: true }], ngImport: i0, template: `
13717
+ <div class="config-container">
13718
+
13719
+ <div class="config-form" [class.loading]="isLoadingInitial">
13720
+ <mm-loading-overlay [loading]="isLoadingInitial" />
13721
+
13722
+ <!-- Data Source Section -->
13723
+ <div class="config-section">
13724
+ <h3 class="section-title">Data Source</h3>
13725
+
13726
+ <div class="form-field">
13727
+ <label>Query <span class="required">*</span></label>
13728
+ <mm-query-selector
13729
+ #querySelector
13730
+ [(ngModel)]="selectedPersistentQuery"
13731
+ (querySelected)="onQuerySelected($event)"
13732
+ placeholder="Select a Query..."
13733
+ hint="Select a query containing datetime data for the heatmap.">
13734
+ </mm-query-selector>
13735
+ </div>
13736
+ </div>
13737
+
13738
+ <!-- Field Mapping Section -->
13739
+ @if (selectedPersistentQuery && queryColumns.length > 0) {
13740
+ <div class="config-section">
13741
+ <h3 class="section-title">Field Mapping</h3>
13742
+
13743
+ <div class="form-field">
13744
+ <label>Date/Time Field <span class="required">*</span></label>
13745
+ <kendo-combobox
13746
+ [data]="dateTimeColumns"
13747
+ [textField]="'attributePath'"
13748
+ [valueField]="'attributePath'"
13749
+ [valuePrimitive]="true"
13750
+ [(ngModel)]="form.dateField"
13751
+ placeholder="Select date/time field...">
13752
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
13753
+ <div class="column-item">
13754
+ <span class="column-path">{{ dataItem.attributePath }}</span>
13755
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
13756
+ </div>
13757
+ </ng-template>
13758
+ </kendo-combobox>
13759
+ <p class="field-hint">The datetime field used to group data into day/hour buckets.</p>
13760
+ </div>
13761
+
13762
+ <div class="form-field">
13763
+ <label>Date/Time End Field</label>
13764
+ <kendo-combobox
13765
+ [data]="dateTimeColumns"
13766
+ [textField]="'attributePath'"
13767
+ [valueField]="'attributePath'"
13768
+ [valuePrimitive]="true"
13769
+ [(ngModel)]="form.dateEndField"
13770
+ [clearButton]="true"
13771
+ placeholder="Select end field (optional)...">
13772
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
13773
+ <div class="column-item">
13774
+ <span class="column-path">{{ dataItem.attributePath }}</span>
13775
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
13776
+ </div>
13777
+ </ng-template>
13778
+ </kendo-combobox>
13779
+ <p class="field-hint">Optional: end-of-interval field (e.g. timeRange.to). When set, sub-hour intervals are auto-detected.</p>
13780
+ </div>
13781
+
13782
+ <div class="form-field">
13783
+ <label>Value Field</label>
13784
+ <kendo-combobox
13785
+ [data]="numericColumns"
13786
+ [textField]="'attributePath'"
13787
+ [valueField]="'attributePath'"
13788
+ [valuePrimitive]="true"
13789
+ [(ngModel)]="form.valueField"
13790
+ [clearButton]="true"
13791
+ placeholder="Select value field (optional)...">
13792
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
13793
+ <div class="column-item">
13794
+ <span class="column-path">{{ dataItem.attributePath }}</span>
13795
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
13796
+ </div>
13797
+ </ng-template>
13798
+ </kendo-combobox>
13799
+ <p class="field-hint">Numeric field to aggregate. Leave empty for count aggregation.</p>
13800
+ </div>
13801
+
13802
+ <div class="form-field">
13803
+ <label>Aggregation</label>
13804
+ <kendo-dropdownlist
13805
+ [data]="aggregationOptions"
13806
+ [textField]="'label'"
13807
+ [valueField]="'value'"
13808
+ [valuePrimitive]="true"
13809
+ [(ngModel)]="form.aggregation">
13810
+ </kendo-dropdownlist>
13811
+ <p class="field-hint">How to aggregate values within each time slot.</p>
13812
+ </div>
13813
+ </div>
13814
+ }
13815
+
13816
+ <!-- Filters Section -->
13817
+ @if (selectedPersistentQuery?.queryCkTypeId) {
13818
+ <div class="config-section">
13819
+ <h3 class="section-title">Filters</h3>
13820
+ <p class="section-hint">Define filters to narrow down the data.</p>
13821
+ <mm-field-filter-editor
13822
+ [ckTypeId]="selectedPersistentQuery?.queryCkTypeId ?? undefined"
13823
+ [filters]="filters"
13824
+ [enableVariables]="filterVariables.length > 0"
13825
+ [availableVariables]="filterVariables"
13826
+ (filtersChange)="onFiltersChange($event)">
13827
+ </mm-field-filter-editor>
13828
+ </div>
13829
+ }
13830
+
13831
+ <!-- Display Options Section -->
13832
+ <div class="config-section">
13833
+ <h3 class="section-title">Display Options</h3>
13834
+
13835
+ <div class="form-field">
13836
+ <label>Color Scheme</label>
13837
+ <div class="color-scheme-grid">
13838
+ @for (scheme of colorSchemeOptions; track scheme.value) {
13839
+ <label class="radio-label">
13840
+ <input type="radio"
13841
+ name="colorScheme"
13842
+ [value]="scheme.value"
13843
+ [(ngModel)]="form.colorScheme"
13844
+ kendoRadioButton />
13845
+ <span class="color-scheme-preview">
13846
+ <span class="color-swatch" [style.background]="scheme.previewColor"></span>
13847
+ {{ scheme.label }}
13848
+ </span>
13849
+ </label>
13850
+ }
13851
+ </div>
13852
+ </div>
13853
+
13854
+ <div class="form-row">
13855
+ <div class="form-field checkbox-field">
13856
+ <label>
13857
+ <input type="checkbox" [(ngModel)]="form.showLegend" kendoCheckBox />
13858
+ Show Legend
13859
+ </label>
13860
+ </div>
13861
+ </div>
13862
+
13863
+ @if (form.showLegend) {
13864
+ <div class="form-field">
13865
+ <label>Legend Position</label>
13866
+ <kendo-dropdownlist
13867
+ [data]="legendPositions"
13868
+ [textField]="'label'"
13869
+ [valueField]="'value'"
13870
+ [valuePrimitive]="true"
13871
+ [(ngModel)]="form.legendPosition">
13872
+ </kendo-dropdownlist>
13873
+ </div>
13874
+ }
13875
+ </div>
13876
+ </div>
13877
+
13878
+ <div class="action-bar">
13879
+ <button kendoButton fillMode="flat" (click)="onCancel()">Cancel</button>
13880
+ <button
13881
+ kendoButton
13882
+ themeColor="primary"
13883
+ [disabled]="!isValid"
13884
+ (click)="onSave()">
13885
+ Save
13886
+ </button>
13887
+ </div>
13888
+ </div>
13889
+ `, 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)}.color-scheme-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.color-scheme-preview{display:flex;align-items:center;gap:6px}.color-swatch{display:inline-block;width:16px;height:16px;border-radius:3px;border:1px solid var(--kendo-color-border, #dee2e6)}.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}.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", "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"] }] });
13890
+ }
13891
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: HeatmapConfigDialogComponent, decorators: [{
13892
+ type: Component,
13893
+ args: [{ selector: 'mm-heatmap-config-dialog', standalone: true, imports: [
13894
+ CommonModule,
13895
+ FormsModule,
13896
+ ButtonsModule,
13897
+ InputsModule,
13898
+ DropDownsModule,
13899
+ SVGIconModule,
13900
+ FieldFilterEditorComponent,
13901
+ QuerySelectorComponent,
13902
+ LoadingOverlayComponent
13903
+ ], template: `
13904
+ <div class="config-container">
13905
+
13906
+ <div class="config-form" [class.loading]="isLoadingInitial">
13907
+ <mm-loading-overlay [loading]="isLoadingInitial" />
13908
+
13909
+ <!-- Data Source Section -->
13910
+ <div class="config-section">
13911
+ <h3 class="section-title">Data Source</h3>
13912
+
13913
+ <div class="form-field">
13914
+ <label>Query <span class="required">*</span></label>
13915
+ <mm-query-selector
13916
+ #querySelector
13917
+ [(ngModel)]="selectedPersistentQuery"
13918
+ (querySelected)="onQuerySelected($event)"
13919
+ placeholder="Select a Query..."
13920
+ hint="Select a query containing datetime data for the heatmap.">
13921
+ </mm-query-selector>
13922
+ </div>
13923
+ </div>
13924
+
13925
+ <!-- Field Mapping Section -->
13926
+ @if (selectedPersistentQuery && queryColumns.length > 0) {
13927
+ <div class="config-section">
13928
+ <h3 class="section-title">Field Mapping</h3>
13929
+
13930
+ <div class="form-field">
13931
+ <label>Date/Time Field <span class="required">*</span></label>
13932
+ <kendo-combobox
13933
+ [data]="dateTimeColumns"
13934
+ [textField]="'attributePath'"
13935
+ [valueField]="'attributePath'"
13936
+ [valuePrimitive]="true"
13937
+ [(ngModel)]="form.dateField"
13938
+ placeholder="Select date/time field...">
13939
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
13940
+ <div class="column-item">
13941
+ <span class="column-path">{{ dataItem.attributePath }}</span>
13942
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
13943
+ </div>
13944
+ </ng-template>
13945
+ </kendo-combobox>
13946
+ <p class="field-hint">The datetime field used to group data into day/hour buckets.</p>
13947
+ </div>
13948
+
13949
+ <div class="form-field">
13950
+ <label>Date/Time End Field</label>
13951
+ <kendo-combobox
13952
+ [data]="dateTimeColumns"
13953
+ [textField]="'attributePath'"
13954
+ [valueField]="'attributePath'"
13955
+ [valuePrimitive]="true"
13956
+ [(ngModel)]="form.dateEndField"
13957
+ [clearButton]="true"
13958
+ placeholder="Select end field (optional)...">
13959
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
13960
+ <div class="column-item">
13961
+ <span class="column-path">{{ dataItem.attributePath }}</span>
13962
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
13963
+ </div>
13964
+ </ng-template>
13965
+ </kendo-combobox>
13966
+ <p class="field-hint">Optional: end-of-interval field (e.g. timeRange.to). When set, sub-hour intervals are auto-detected.</p>
13967
+ </div>
13968
+
13969
+ <div class="form-field">
13970
+ <label>Value Field</label>
13971
+ <kendo-combobox
13972
+ [data]="numericColumns"
13973
+ [textField]="'attributePath'"
13974
+ [valueField]="'attributePath'"
13975
+ [valuePrimitive]="true"
13976
+ [(ngModel)]="form.valueField"
13977
+ [clearButton]="true"
13978
+ placeholder="Select value field (optional)...">
13979
+ <ng-template kendoComboBoxItemTemplate let-dataItem>
13980
+ <div class="column-item">
13981
+ <span class="column-path">{{ dataItem.attributePath }}</span>
13982
+ <span class="column-type">{{ dataItem.attributeValueType }}</span>
13983
+ </div>
13984
+ </ng-template>
13985
+ </kendo-combobox>
13986
+ <p class="field-hint">Numeric field to aggregate. Leave empty for count aggregation.</p>
13987
+ </div>
13988
+
13989
+ <div class="form-field">
13990
+ <label>Aggregation</label>
13991
+ <kendo-dropdownlist
13992
+ [data]="aggregationOptions"
13993
+ [textField]="'label'"
13994
+ [valueField]="'value'"
13995
+ [valuePrimitive]="true"
13996
+ [(ngModel)]="form.aggregation">
13997
+ </kendo-dropdownlist>
13998
+ <p class="field-hint">How to aggregate values within each time slot.</p>
13999
+ </div>
14000
+ </div>
14001
+ }
14002
+
14003
+ <!-- Filters Section -->
14004
+ @if (selectedPersistentQuery?.queryCkTypeId) {
14005
+ <div class="config-section">
14006
+ <h3 class="section-title">Filters</h3>
14007
+ <p class="section-hint">Define filters to narrow down the data.</p>
14008
+ <mm-field-filter-editor
14009
+ [ckTypeId]="selectedPersistentQuery?.queryCkTypeId ?? undefined"
14010
+ [filters]="filters"
14011
+ [enableVariables]="filterVariables.length > 0"
14012
+ [availableVariables]="filterVariables"
14013
+ (filtersChange)="onFiltersChange($event)">
14014
+ </mm-field-filter-editor>
14015
+ </div>
14016
+ }
14017
+
14018
+ <!-- Display Options Section -->
14019
+ <div class="config-section">
14020
+ <h3 class="section-title">Display Options</h3>
14021
+
14022
+ <div class="form-field">
14023
+ <label>Color Scheme</label>
14024
+ <div class="color-scheme-grid">
14025
+ @for (scheme of colorSchemeOptions; track scheme.value) {
14026
+ <label class="radio-label">
14027
+ <input type="radio"
14028
+ name="colorScheme"
14029
+ [value]="scheme.value"
14030
+ [(ngModel)]="form.colorScheme"
14031
+ kendoRadioButton />
14032
+ <span class="color-scheme-preview">
14033
+ <span class="color-swatch" [style.background]="scheme.previewColor"></span>
14034
+ {{ scheme.label }}
14035
+ </span>
14036
+ </label>
14037
+ }
14038
+ </div>
14039
+ </div>
14040
+
14041
+ <div class="form-row">
14042
+ <div class="form-field checkbox-field">
14043
+ <label>
14044
+ <input type="checkbox" [(ngModel)]="form.showLegend" kendoCheckBox />
14045
+ Show Legend
14046
+ </label>
14047
+ </div>
12032
14048
  </div>
12033
14049
 
12034
14050
  @if (form.showLegend) {
@@ -12057,7 +14073,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
12057
14073
  </button>
12058
14074
  </div>
12059
14075
  </div>
12060
- `, 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"] }]
14076
+ `, 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)}.color-scheme-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.radio-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.color-scheme-preview{display:flex;align-items:center;gap:6px}.color-swatch{display:inline-block;width:16px;height:16px;border-radius:3px;border:1px solid var(--kendo-color-border, #dee2e6)}.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}.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"] }]
12061
14077
  }], propDecorators: { querySelector: [{
12062
14078
  type: ViewChild,
12063
14079
  args: ['querySelector']
@@ -12065,22 +14081,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
12065
14081
  type: Input
12066
14082
  }], initialQueryName: [{
12067
14083
  type: Input
12068
- }], initialChartType: [{
14084
+ }], initialDateField: [{
12069
14085
  type: Input
12070
- }], initialCategoryField: [{
14086
+ }], initialDateEndField: [{
12071
14087
  type: Input
12072
- }], initialSeries: [{
14088
+ }], initialValueField: [{
12073
14089
  type: Input
12074
- }], initialSeriesGroupField: [{
14090
+ }], initialAggregation: [{
12075
14091
  type: Input
12076
- }], initialValueField: [{
14092
+ }], initialColorScheme: [{
12077
14093
  type: Input
12078
14094
  }], initialShowLegend: [{
12079
14095
  type: Input
12080
14096
  }], initialLegendPosition: [{
12081
14097
  type: Input
12082
- }], initialShowDataLabels: [{
12083
- type: Input
12084
14098
  }], initialFilters: [{
12085
14099
  type: Input
12086
14100
  }] } });
@@ -19978,7 +21992,7 @@ class ProcessConfigDialogComponent {
19978
21992
  </button>
19979
21993
  </div>
19980
21994
  </div>
19981
- `, 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{flex:1;overflow-y:auto;padding:16px}.tab-content{padding:16px;min-height:200px;position:relative}.tab-content.loading{pointer-events:none}.section-hint{margin:0 0 16px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.empty-hint{text-align:center;padding:24px;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}.form-field:last-child{margin-bottom:0}.form-field.disabled{opacity:.6}.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)}.field-hint.warning{color:var(--kendo-color-warning, #ffc107)}.form-row{display:flex;gap:24px;margin-bottom:12px}.form-row:last-child{margin-bottom:0}.mode-toggle{display:flex;gap:8px}.checkbox-field{flex-direction:row;align-items:flex-start}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.checkbox-field .field-hint{margin-top:4px;margin-left:28px}.preview-section{margin-top:16px;padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.preview-info{display:flex;flex-direction:column;gap:6px}.preview-item{display:flex;justify-content:space-between;padding:6px 10px;background:var(--kendo-color-surface, #ffffff);border-radius:4px}.preview-label{font-weight:500;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.preview-value{font-weight:600;font-size:.85rem;color:var(--kendo-color-on-app-surface, #212529)}.diagram-item{display:flex;justify-content:space-between;gap:16px}.diagram-name{font-weight:500}.diagram-size{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.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)}.property-mappings-table{display:flex;flex-direction:column;gap:6px}.mapping-header{display:grid;grid-template-columns:minmax(80px,1fr) 55px 85px minmax(80px,1fr) minmax(90px,1.2fr);gap:6px;padding:6px 10px;background:var(--kendo-color-surface-alt, #f8f9fa);border-radius:4px;font-weight:600;font-size:.7rem;color:var(--kendo-color-subtle, #6c757d);text-transform:uppercase}.mapping-row{display:grid;grid-template-columns:minmax(80px,1fr) 55px 85px minmax(80px,1fr) minmax(90px,1.2fr);gap:6px;padding:6px 10px;background:var(--kendo-color-surface, #ffffff);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;align-items:center}.col-property{font-weight:500;font-size:.85rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.col-type.type-badge{font-size:.65rem;padding:2px 4px;background:var(--kendo-color-surface-alt, #f8f9fa);border-radius:10px;text-align:center;text-transform:uppercase;color:var(--kendo-color-subtle, #6c757d)}.col-expression kendo-textbox{font-family:Consolas,Monaco,monospace;font-size:.8rem}.expression-help{margin-top:12px}.expression-help code{background:var(--kendo-color-surface-alt, #f8f9fa);padding:1px 4px;border-radius:3px;font-size:.75rem}.loading-hint{font-style:italic;color:var(--kendo-color-subtle, #6c757d);padding:8px;text-align:center}:host ::ng-deep kendo-tabstrip{border:none}:host ::ng-deep .k-tabstrip-items-wrapper{border-bottom:1px solid var(--kendo-color-border, #dee2e6)}:host ::ng-deep .k-tabstrip-content{border:none;padding:0}\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: 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: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { 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: LayoutModule }, { kind: "component", type: i4$1.TabStripComponent, selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "component", type: i4$1.TabStripTabComponent, selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: i4$1.TabContentDirective, selector: "[kendoTabContent]" }, { 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: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
21995
+ `, 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{flex:1;overflow-y:auto;padding:16px}.tab-content{padding:16px;min-height:200px;position:relative}.tab-content.loading{pointer-events:none}.section-hint{margin:0 0 16px;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.empty-hint{text-align:center;padding:24px;color:var(--kendo-color-subtle, #6c757d);font-style:italic}.form-field{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}.form-field:last-child{margin-bottom:0}.form-field.disabled{opacity:.6}.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)}.field-hint.warning{color:var(--kendo-color-warning, #ffc107)}.form-row{display:flex;gap:24px;margin-bottom:12px}.form-row:last-child{margin-bottom:0}.mode-toggle{display:flex;gap:8px}.checkbox-field{flex-direction:row;align-items:flex-start}.checkbox-field label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:400}.checkbox-field .field-hint{margin-top:4px;margin-left:28px}.preview-section{margin-top:16px;padding:12px;background:var(--kendo-color-surface-alt, #f8f9fa);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px}.preview-info{display:flex;flex-direction:column;gap:6px}.preview-item{display:flex;justify-content:space-between;padding:6px 10px;background:var(--kendo-color-surface, #ffffff);border-radius:4px}.preview-label{font-weight:500;font-size:.85rem;color:var(--kendo-color-subtle, #6c757d)}.preview-value{font-weight:600;font-size:.85rem;color:var(--kendo-color-on-app-surface, #212529)}.diagram-item{display:flex;justify-content:space-between;gap:16px}.diagram-name{font-weight:500}.diagram-size{font-size:.8rem;color:var(--kendo-color-subtle, #6c757d)}.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)}.property-mappings-table{display:flex;flex-direction:column;gap:6px}.mapping-header{display:grid;grid-template-columns:minmax(80px,1fr) 55px 85px minmax(80px,1fr) minmax(90px,1.2fr);gap:6px;padding:6px 10px;background:var(--kendo-color-surface-alt, #f8f9fa);border-radius:4px;font-weight:600;font-size:.7rem;color:var(--kendo-color-subtle, #6c757d);text-transform:uppercase}.mapping-row{display:grid;grid-template-columns:minmax(80px,1fr) 55px 85px minmax(80px,1fr) minmax(90px,1.2fr);gap:6px;padding:6px 10px;background:var(--kendo-color-surface, #ffffff);border:1px solid var(--kendo-color-border, #dee2e6);border-radius:4px;align-items:center}.col-property{font-weight:500;font-size:.85rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.col-type.type-badge{font-size:.65rem;padding:2px 4px;background:var(--kendo-color-surface-alt, #f8f9fa);border-radius:10px;text-align:center;text-transform:uppercase;color:var(--kendo-color-subtle, #6c757d)}.col-expression kendo-textbox{font-family:Consolas,Monaco,monospace;font-size:.8rem}.expression-help{margin-top:12px}.expression-help code{background:var(--kendo-color-surface-alt, #f8f9fa);padding:1px 4px;border-radius:3px;font-size:.75rem}.loading-hint{font-style:italic;color:var(--kendo-color-subtle, #6c757d);padding:8px;text-align:center}:host ::ng-deep kendo-tabstrip{border:none}:host ::ng-deep .k-tabstrip-items-wrapper{border-bottom:1px solid var(--kendo-color-border, #dee2e6)}:host ::ng-deep .k-tabstrip-content{border:none;padding:0}\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: 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: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { 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: LayoutModule }, { kind: "component", type: i4$1.TabStripComponent, selector: "kendo-tabstrip", inputs: ["height", "animate", "tabAlignment", "tabPosition", "keepTabContent", "closable", "scrollable", "size", "closeIcon", "closeIconClass", "closeSVGIcon", "showContentArea"], outputs: ["tabSelect", "tabClose", "tabScroll"], exportAs: ["kendoTabStrip"] }, { kind: "component", type: i4$1.TabStripTabComponent, selector: "kendo-tabstrip-tab", inputs: ["title", "disabled", "cssClass", "cssStyle", "selected", "closable", "closeIcon", "closeIconClass", "closeSVGIcon"], exportAs: ["kendoTabStripTab"] }, { kind: "directive", type: i4$1.TabContentDirective, selector: "[kendoTabContent]" }, { 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: EntitySelectInputComponent, selector: "mm-entity-select-input", inputs: ["dataSource", "placeholder", "minSearchLength", "maxResults", "debounceMs", "prefix", "dialogDataSource", "dialogTitle", "multiSelect", "advancedSearchLabel", "dialogMessages", "messages", "disabled", "required"], outputs: ["entitySelected", "entityCleared", "entitiesSelected"] }, { kind: "component", type: FieldFilterEditorComponent, selector: "mm-field-filter-editor", inputs: ["availableAttributes", "ckTypeId", "enableVariables", "availableVariables", "filters"], outputs: ["filtersChange"] }, { kind: "component", type: LoadingOverlayComponent, selector: "mm-loading-overlay", inputs: ["loading"] }] });
19982
21996
  }
19983
21997
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ProcessConfigDialogComponent, decorators: [{
19984
21998
  type: Component,
@@ -21455,6 +23469,110 @@ function registerDefaultWidgets(registry) {
21455
23469
  };
21456
23470
  }
21457
23471
  });
23472
+ // Line Chart Widget
23473
+ registry.registerWidget({
23474
+ type: 'lineChart',
23475
+ label: 'Line Chart',
23476
+ component: LineChartWidgetComponent,
23477
+ configDialogComponent: LineChartConfigDialogComponent,
23478
+ configDialogSize: { width: 750, height: 650, minWidth: 550, minHeight: 450 },
23479
+ configDialogTitle: 'Line Chart Configuration',
23480
+ defaultSize: { colSpan: 3, rowSpan: 2 },
23481
+ supportedDataSources: ['persistentQuery'],
23482
+ getInitialConfig: (widget) => {
23483
+ const lineWidget = widget;
23484
+ const dataSource = lineWidget.dataSource;
23485
+ const isPersistentQuery = dataSource.type === 'persistentQuery';
23486
+ return {
23487
+ initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
23488
+ initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
23489
+ initialChartType: lineWidget.chartType,
23490
+ initialCategoryField: lineWidget.categoryField,
23491
+ initialSeriesGroupField: lineWidget.seriesGroupField,
23492
+ initialValueField: lineWidget.valueField,
23493
+ initialUnitField: lineWidget.unitField,
23494
+ initialShowLegend: lineWidget.showLegend,
23495
+ initialLegendPosition: lineWidget.legendPosition,
23496
+ initialShowMarkers: lineWidget.showMarkers,
23497
+ initialFilters: lineWidget.filters
23498
+ };
23499
+ },
23500
+ applyConfigResult: (widget, result) => {
23501
+ const dataSource = {
23502
+ type: 'persistentQuery',
23503
+ queryRtId: result.queryRtId,
23504
+ queryName: result.queryName
23505
+ };
23506
+ const filters = result.filters?.map(f => ({
23507
+ attributePath: f.attributePath,
23508
+ operator: String(f.operator),
23509
+ comparisonValue: f.comparisonValue
23510
+ }));
23511
+ return {
23512
+ ...widget,
23513
+ dataSource,
23514
+ chartType: result.chartType,
23515
+ categoryField: result.categoryField,
23516
+ seriesGroupField: result.seriesGroupField,
23517
+ valueField: result.valueField,
23518
+ unitField: result.unitField,
23519
+ showLegend: result.showLegend,
23520
+ legendPosition: result.legendPosition,
23521
+ showMarkers: result.showMarkers,
23522
+ filters
23523
+ };
23524
+ },
23525
+ createDefaultConfig: (base) => ({
23526
+ ...base,
23527
+ type: 'lineChart',
23528
+ colSpan: 3,
23529
+ rowSpan: 2,
23530
+ dataSource: createDefaultPersistentQueryDataSource(),
23531
+ chartType: 'line',
23532
+ categoryField: '',
23533
+ seriesGroupField: '',
23534
+ valueField: '',
23535
+ showLegend: true,
23536
+ legendPosition: 'right',
23537
+ showMarkers: false
23538
+ }),
23539
+ toPersistedConfig: (widget) => ({
23540
+ dataSourceType: 'persistentQuery',
23541
+ dataSourceRtId: widget.dataSource.queryRtId,
23542
+ config: {
23543
+ chartType: widget.chartType,
23544
+ categoryField: widget.categoryField,
23545
+ seriesGroupField: widget.seriesGroupField,
23546
+ valueField: widget.valueField,
23547
+ unitField: widget.unitField,
23548
+ showLegend: widget.showLegend,
23549
+ legendPosition: widget.legendPosition,
23550
+ showMarkers: widget.showMarkers,
23551
+ queryName: widget.dataSource.queryName,
23552
+ queryRtId: widget.dataSource.queryRtId,
23553
+ filters: widget.filters
23554
+ }
23555
+ }),
23556
+ fromPersistedConfig: (data, base) => {
23557
+ const config = parseConfig(data);
23558
+ const dataSource = buildDataSourceFromPersisted(data, config);
23559
+ return {
23560
+ ...base,
23561
+ rtId: data.rtId,
23562
+ type: 'lineChart',
23563
+ dataSource,
23564
+ chartType: config['chartType'] ?? 'line',
23565
+ categoryField: config['categoryField'] ?? '',
23566
+ seriesGroupField: config['seriesGroupField'] ?? '',
23567
+ valueField: config['valueField'] ?? '',
23568
+ unitField: config['unitField'],
23569
+ showLegend: config['showLegend'] ?? true,
23570
+ legendPosition: config['legendPosition'] ?? 'right',
23571
+ showMarkers: config['showMarkers'] ?? false,
23572
+ filters: config['filters']
23573
+ };
23574
+ }
23575
+ });
21458
23576
  // Stats Grid Widget
21459
23577
  registry.registerWidget({
21460
23578
  type: 'statsGrid',
@@ -21879,6 +23997,107 @@ function registerDefaultWidgets(registry) {
21879
23997
  };
21880
23998
  }
21881
23999
  });
24000
+ // Heatmap Widget
24001
+ registry.registerWidget({
24002
+ type: 'heatmap',
24003
+ label: 'Heatmap',
24004
+ component: HeatmapWidgetComponent,
24005
+ configDialogComponent: HeatmapConfigDialogComponent,
24006
+ configDialogSize: { width: 750, height: 650, minWidth: 550, minHeight: 450 },
24007
+ configDialogTitle: 'Heatmap Configuration',
24008
+ defaultSize: { colSpan: 3, rowSpan: 2 },
24009
+ supportedDataSources: ['persistentQuery'],
24010
+ getInitialConfig: (widget) => {
24011
+ const heatmapWidget = widget;
24012
+ const dataSource = heatmapWidget.dataSource;
24013
+ const isPersistentQuery = dataSource.type === 'persistentQuery';
24014
+ return {
24015
+ initialQueryRtId: isPersistentQuery ? dataSource.queryRtId : undefined,
24016
+ initialQueryName: isPersistentQuery ? dataSource.queryName : undefined,
24017
+ initialDateField: heatmapWidget.dateField,
24018
+ initialDateEndField: heatmapWidget.dateEndField,
24019
+ initialValueField: heatmapWidget.valueField,
24020
+ initialAggregation: heatmapWidget.aggregation,
24021
+ initialColorScheme: heatmapWidget.colorScheme,
24022
+ initialShowLegend: heatmapWidget.showLegend,
24023
+ initialLegendPosition: heatmapWidget.legendPosition,
24024
+ initialFilters: heatmapWidget.filters
24025
+ };
24026
+ },
24027
+ applyConfigResult: (widget, result) => {
24028
+ const dataSource = {
24029
+ type: 'persistentQuery',
24030
+ queryRtId: result.queryRtId,
24031
+ queryName: result.queryName
24032
+ };
24033
+ const filters = result.filters?.map(f => ({
24034
+ attributePath: f.attributePath,
24035
+ operator: String(f.operator),
24036
+ comparisonValue: f.comparisonValue
24037
+ }));
24038
+ return {
24039
+ ...widget,
24040
+ dataSource,
24041
+ dateField: result.dateField,
24042
+ dateEndField: result.dateEndField,
24043
+ valueField: result.valueField,
24044
+ aggregation: result.aggregation,
24045
+ colorScheme: result.colorScheme,
24046
+ showLegend: result.showLegend,
24047
+ legendPosition: result.legendPosition,
24048
+ filters
24049
+ };
24050
+ },
24051
+ // SOLID: Factory function
24052
+ createDefaultConfig: (base) => ({
24053
+ ...base,
24054
+ type: 'heatmap',
24055
+ colSpan: 3,
24056
+ rowSpan: 2,
24057
+ dataSource: createDefaultPersistentQueryDataSource(),
24058
+ dateField: '',
24059
+ aggregation: 'count',
24060
+ colorScheme: 'green',
24061
+ showLegend: true,
24062
+ legendPosition: 'bottom'
24063
+ }),
24064
+ // SOLID: Serialization for persistence
24065
+ toPersistedConfig: (widget) => ({
24066
+ dataSourceType: 'persistentQuery',
24067
+ dataSourceRtId: widget.dataSource.queryRtId,
24068
+ config: {
24069
+ dateField: widget.dateField,
24070
+ dateEndField: widget.dateEndField,
24071
+ valueField: widget.valueField,
24072
+ aggregation: widget.aggregation,
24073
+ colorScheme: widget.colorScheme,
24074
+ showLegend: widget.showLegend,
24075
+ legendPosition: widget.legendPosition,
24076
+ queryName: widget.dataSource.queryName,
24077
+ queryRtId: widget.dataSource.queryRtId,
24078
+ filters: widget.filters
24079
+ }
24080
+ }),
24081
+ // SOLID: Deserialization from persistence
24082
+ fromPersistedConfig: (data, base) => {
24083
+ const config = parseConfig(data);
24084
+ const dataSource = buildDataSourceFromPersisted(data, config);
24085
+ return {
24086
+ ...base,
24087
+ rtId: data.rtId,
24088
+ type: 'heatmap',
24089
+ dataSource,
24090
+ dateField: config['dateField'] ?? '',
24091
+ dateEndField: config['dateEndField'],
24092
+ valueField: config['valueField'],
24093
+ aggregation: config['aggregation'] ?? 'count',
24094
+ colorScheme: config['colorScheme'] ?? 'green',
24095
+ showLegend: config['showLegend'] ?? true,
24096
+ legendPosition: config['legendPosition'] ?? 'bottom',
24097
+ filters: config['filters']
24098
+ };
24099
+ }
24100
+ });
21882
24101
  // Note: Process Widget registration moved to process-widget-registration.ts
21883
24102
  // Use provideProcessWidget() or registerProcessWidget() to enable it
21884
24103
  }
@@ -22181,6 +24400,8 @@ class MeshBoardSettingsDialogComponent {
22181
24400
  gap = 16;
22182
24401
  variables = [];
22183
24402
  timeFilterEnabled = false;
24403
+ defaultSelection;
24404
+ initialDefaultSelection;
22184
24405
  // Validation
22185
24406
  get isValid() {
22186
24407
  return (this.name.trim().length > 0 &&
@@ -22200,6 +24421,32 @@ class MeshBoardSettingsDialogComponent {
22200
24421
  this.gap = settings.gap;
22201
24422
  this.variables = settings.variables ? [...settings.variables] : [];
22202
24423
  this.timeFilterEnabled = settings.timeFilter?.enabled ?? false;
24424
+ this.defaultSelection = settings.timeFilter?.defaultSelection;
24425
+ if (this.defaultSelection) {
24426
+ this.initialDefaultSelection = {
24427
+ ...this.defaultSelection,
24428
+ customFrom: this.defaultSelection.customFrom ? new Date(this.defaultSelection.customFrom) : undefined,
24429
+ customTo: this.defaultSelection.customTo ? new Date(this.defaultSelection.customTo) : undefined
24430
+ };
24431
+ }
24432
+ }
24433
+ /**
24434
+ * Handles default selection change from the time range picker.
24435
+ */
24436
+ onDefaultSelectionChange(sharedSelection) {
24437
+ this.defaultSelection = {
24438
+ type: sharedSelection.type,
24439
+ year: sharedSelection.year,
24440
+ quarter: sharedSelection.quarter,
24441
+ month: sharedSelection.month,
24442
+ day: sharedSelection.day,
24443
+ hourFrom: sharedSelection.hourFrom,
24444
+ hourTo: sharedSelection.hourTo,
24445
+ relativeValue: sharedSelection.relativeValue,
24446
+ relativeUnit: sharedSelection.relativeUnit,
24447
+ customFrom: sharedSelection.customFrom?.toISOString(),
24448
+ customTo: sharedSelection.customTo?.toISOString()
24449
+ };
22203
24450
  }
22204
24451
  /**
22205
24452
  * Saves the settings and closes the dialog.
@@ -22209,7 +24456,8 @@ class MeshBoardSettingsDialogComponent {
22209
24456
  return;
22210
24457
  }
22211
24458
  const timeFilter = {
22212
- enabled: this.timeFilterEnabled
24459
+ enabled: this.timeFilterEnabled,
24460
+ defaultSelection: this.timeFilterEnabled ? this.defaultSelection : undefined
22213
24461
  };
22214
24462
  const result = new MeshBoardSettingsResult(this.name.trim(), this.description.trim(), this.columns, this.rowHeight, this.gap, this.variables, timeFilter, this.rtWellKnownName.trim() || undefined);
22215
24463
  this.dialogRef.close(result);
@@ -22221,7 +24469,7 @@ class MeshBoardSettingsDialogComponent {
22221
24469
  this.dialogRef.close();
22222
24470
  }
22223
24471
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: MeshBoardSettingsDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
22224
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: MeshBoardSettingsDialogComponent, isStandalone: true, selector: "mm-meshboard-settings-dialog", ngImport: i0, template: "<div class=\"meshboard-settings-dialog\">\n <form class=\"settings-form\">\n <!-- Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"nameInput\" text=\"Name *\"></kendo-label>\n <kendo-textbox\n #nameInput\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Enter MeshBoard name\"\n required>\n </kendo-textbox>\n @if (name.trim().length === 0) {\n <kendo-formerror>Name is required</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Description Field -->\n <kendo-formfield>\n <kendo-label [for]=\"descriptionInput\" text=\"Description\"></kendo-label>\n <kendo-textarea\n #descriptionInput\n [(ngModel)]=\"description\"\n name=\"description\"\n placeholder=\"Enter MeshBoard description (optional)\"\n [rows]=\"3\">\n </kendo-textarea>\n </kendo-formfield>\n\n <!-- Well-Known Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"wellKnownNameInput\" text=\"Well-Known Name\"></kendo-label>\n <kendo-textbox\n #wellKnownNameInput\n [(ngModel)]=\"rtWellKnownName\"\n name=\"rtWellKnownName\"\n placeholder=\"e.g., cockpit, dashboard-main\">\n </kendo-textbox>\n <kendo-formhint>Unique identifier for routing. Use lowercase with hyphens (e.g., 'cockpit', 'sales-dashboard').</kendo-formhint>\n </kendo-formfield>\n\n <!-- Layout Settings -->\n <div class=\"section-title\">Layout Settings</div>\n\n <!-- Columns Field -->\n <kendo-formfield>\n <kendo-label [for]=\"columnsInput\" text=\"Columns *\"></kendo-label>\n <kendo-numerictextbox\n #columnsInput\n [(ngModel)]=\"columns\"\n name=\"columns\"\n [min]=\"1\"\n [max]=\"12\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Number of columns in the grid (1-12)</kendo-formhint>\n @if (columns < 1 || columns > 12) {\n <kendo-formerror>Columns must be between 1 and 12</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Row Height Field -->\n <kendo-formfield>\n <kendo-label [for]=\"rowHeightInput\" text=\"Row Height *\"></kendo-label>\n <kendo-numerictextbox\n #rowHeightInput\n [(ngModel)]=\"rowHeight\"\n name=\"rowHeight\"\n [min]=\"100\"\n [max]=\"1000\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Height of each row in pixels (100-1000)</kendo-formhint>\n @if (rowHeight < 100 || rowHeight > 1000) {\n <kendo-formerror>Row height must be between 100 and 1000</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Gap Field -->\n <kendo-formfield>\n <kendo-label [for]=\"gapInput\" text=\"Gap *\"></kendo-label>\n <kendo-numerictextbox\n #gapInput\n [(ngModel)]=\"gap\"\n name=\"gap\"\n [min]=\"0\"\n [max]=\"100\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Space between widgets in pixels (0-100)</kendo-formhint>\n @if (gap < 0 || gap > 100) {\n <kendo-formerror>Gap must be between 0 and 100</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Variables Section -->\n <mm-variables-editor\n [(variables)]=\"variables\">\n </mm-variables-editor>\n\n <!-- Time Filter Section -->\n <div class=\"section-title\">Time Filter</div>\n\n <kendo-formfield>\n <div class=\"checkbox-wrapper\">\n <input\n type=\"checkbox\"\n kendoCheckBox\n #timeFilterCheckbox\n [(ngModel)]=\"timeFilterEnabled\"\n name=\"timeFilterEnabled\"\n id=\"timeFilterEnabled\"/>\n <kendo-label\n [for]=\"timeFilterCheckbox\"\n text=\"Enable Time Filter\"\n class=\"checkbox-label\">\n </kendo-label>\n </div>\n <kendo-formhint>\n Shows a time range picker in the toolbar. Sets $timeRangeFrom and $timeRangeTo variables.\n </kendo-formhint>\n </kendo-formfield>\n </form>\n\n <!-- Dialog Actions -->\n <kendo-dialog-actions>\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"save()\"\n [disabled]=\"!isValid\"\n themeColor=\"primary\">\n Save\n </button>\n </kendo-dialog-actions>\n</div>\n", styles: [".meshboard-settings-dialog{display:flex;flex-direction:column;height:100%;max-height:calc(80vh - 60px);overflow:hidden}.meshboard-settings-dialog .settings-form{display:flex;flex-direction:column;gap:1.25rem;padding:1.5rem;overflow-y:auto;flex:1;min-height:0}.meshboard-settings-dialog .settings-form kendo-formfield{display:flex;flex-direction:column;gap:.5rem}.meshboard-settings-dialog .settings-form kendo-formfield kendo-label{font-weight:500;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-textbox,.meshboard-settings-dialog .settings-form kendo-formfield kendo-textarea,.meshboard-settings-dialog .settings-form kendo-formfield kendo-numerictextbox{width:100%}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formhint{font-size:.75rem;color:var(--kendo-color-subtle, #757575)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formerror{font-size:.75rem;color:var(--kendo-color-error, #f44336)}.meshboard-settings-dialog .settings-form .section-title{font-size:.875rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242);text-transform:uppercase;letter-spacing:.5px;margin-top:.5rem;padding-bottom:.5rem;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.meshboard-settings-dialog .settings-form .checkbox-wrapper{display:flex;align-items:center;gap:.5rem}.meshboard-settings-dialog .settings-form .checkbox-wrapper .checkbox-label{font-weight:400;cursor:pointer}.meshboard-settings-dialog kendo-dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;background-color:var(--kendo-color-surface-alt, white)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { 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: "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: "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: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "component", type: i3.FormFieldComponent, selector: "kendo-formfield", inputs: ["showHints", "orientation", "showErrors", "colSpan"] }, { kind: "component", type: i3.HintComponent, selector: "kendo-formhint", inputs: ["align"] }, { kind: "component", type: i3.ErrorComponent, selector: "kendo-formerror", inputs: ["align"] }, { kind: "ngmodule", type: CheckBoxModule }, { kind: "ngmodule", type: LabelModule }, { kind: "component", type: i5.LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }, { kind: "ngmodule", type: FormFieldModule }, { kind: "component", type: VariablesEditorComponent, selector: "mm-variables-editor", inputs: ["variables"], outputs: ["variablesChange"] }] });
24472
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: MeshBoardSettingsDialogComponent, isStandalone: true, selector: "mm-meshboard-settings-dialog", ngImport: i0, template: "<div class=\"meshboard-settings-dialog\">\n <form class=\"settings-form\">\n <!-- Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"nameInput\" text=\"Name *\"></kendo-label>\n <kendo-textbox\n #nameInput\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Enter MeshBoard name\"\n required>\n </kendo-textbox>\n @if (name.trim().length === 0) {\n <kendo-formerror>Name is required</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Description Field -->\n <kendo-formfield>\n <kendo-label [for]=\"descriptionInput\" text=\"Description\"></kendo-label>\n <kendo-textarea\n #descriptionInput\n [(ngModel)]=\"description\"\n name=\"description\"\n placeholder=\"Enter MeshBoard description (optional)\"\n [rows]=\"3\">\n </kendo-textarea>\n </kendo-formfield>\n\n <!-- Well-Known Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"wellKnownNameInput\" text=\"Well-Known Name\"></kendo-label>\n <kendo-textbox\n #wellKnownNameInput\n [(ngModel)]=\"rtWellKnownName\"\n name=\"rtWellKnownName\"\n placeholder=\"e.g., cockpit, dashboard-main\">\n </kendo-textbox>\n <kendo-formhint>Unique identifier for routing. Use lowercase with hyphens (e.g., 'cockpit', 'sales-dashboard').</kendo-formhint>\n </kendo-formfield>\n\n <!-- Layout Settings -->\n <div class=\"section-title\">Layout Settings</div>\n\n <!-- Columns Field -->\n <kendo-formfield>\n <kendo-label [for]=\"columnsInput\" text=\"Columns *\"></kendo-label>\n <kendo-numerictextbox\n #columnsInput\n [(ngModel)]=\"columns\"\n name=\"columns\"\n [min]=\"1\"\n [max]=\"12\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Number of columns in the grid (1-12)</kendo-formhint>\n @if (columns < 1 || columns > 12) {\n <kendo-formerror>Columns must be between 1 and 12</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Row Height Field -->\n <kendo-formfield>\n <kendo-label [for]=\"rowHeightInput\" text=\"Row Height *\"></kendo-label>\n <kendo-numerictextbox\n #rowHeightInput\n [(ngModel)]=\"rowHeight\"\n name=\"rowHeight\"\n [min]=\"100\"\n [max]=\"1000\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Height of each row in pixels (100-1000)</kendo-formhint>\n @if (rowHeight < 100 || rowHeight > 1000) {\n <kendo-formerror>Row height must be between 100 and 1000</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Gap Field -->\n <kendo-formfield>\n <kendo-label [for]=\"gapInput\" text=\"Gap *\"></kendo-label>\n <kendo-numerictextbox\n #gapInput\n [(ngModel)]=\"gap\"\n name=\"gap\"\n [min]=\"0\"\n [max]=\"100\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Space between widgets in pixels (0-100)</kendo-formhint>\n @if (gap < 0 || gap > 100) {\n <kendo-formerror>Gap must be between 0 and 100</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Variables Section -->\n <mm-variables-editor\n [(variables)]=\"variables\">\n </mm-variables-editor>\n\n <!-- Time Filter Section -->\n <div class=\"section-title\">Time Filter</div>\n\n <kendo-formfield>\n <div class=\"checkbox-wrapper\">\n <input\n type=\"checkbox\"\n kendoCheckBox\n #timeFilterCheckbox\n [(ngModel)]=\"timeFilterEnabled\"\n name=\"timeFilterEnabled\"\n id=\"timeFilterEnabled\"/>\n <kendo-label\n [for]=\"timeFilterCheckbox\"\n text=\"Enable Time Filter\"\n class=\"checkbox-label\">\n </kendo-label>\n </div>\n <kendo-formhint>\n Shows a time range picker in the toolbar. Sets $timeRangeFrom and $timeRangeTo variables.\n </kendo-formhint>\n </kendo-formfield>\n\n @if (timeFilterEnabled) {\n <kendo-formfield>\n <kendo-label text=\"Default Selection\"></kendo-label>\n <mm-time-range-picker\n [initialSelection]=\"initialDefaultSelection\"\n (selectionChange)=\"onDefaultSelectionChange($event)\">\n </mm-time-range-picker>\n <kendo-formhint>Initial time filter shown when no URL parameters are set. Note: User-selected filters override this default. Use the reset button in the toolbar to revert to the default.</kendo-formhint>\n </kendo-formfield>\n }\n </form>\n\n <!-- Dialog Actions -->\n <kendo-dialog-actions>\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"save()\"\n [disabled]=\"!isValid\"\n themeColor=\"primary\">\n Save\n </button>\n </kendo-dialog-actions>\n</div>\n", styles: [".meshboard-settings-dialog{display:flex;flex-direction:column;height:100%;max-height:calc(80vh - 60px);overflow:hidden}.meshboard-settings-dialog .settings-form{display:flex;flex-direction:column;gap:1.25rem;padding:1.5rem;overflow-y:auto;flex:1;min-height:0}.meshboard-settings-dialog .settings-form kendo-formfield{display:flex;flex-direction:column;gap:.5rem}.meshboard-settings-dialog .settings-form kendo-formfield kendo-label{font-weight:500;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-textbox,.meshboard-settings-dialog .settings-form kendo-formfield kendo-textarea,.meshboard-settings-dialog .settings-form kendo-formfield kendo-numerictextbox{width:100%}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formhint{font-size:.75rem;color:var(--kendo-color-subtle, #757575)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formerror{font-size:.75rem;color:var(--kendo-color-error, #f44336)}.meshboard-settings-dialog .settings-form .section-title{font-size:.875rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242);text-transform:uppercase;letter-spacing:.5px;margin-top:.5rem;padding-bottom:.5rem;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.meshboard-settings-dialog .settings-form .checkbox-wrapper{display:flex;align-items:center;gap:.5rem}.meshboard-settings-dialog .settings-form .checkbox-wrapper .checkbox-label{font-weight:400;cursor:pointer}.meshboard-settings-dialog kendo-dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;background-color:var(--kendo-color-surface-alt, white)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { 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: "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: "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: "directive", type: i3.CheckBoxDirective, selector: "input[kendoCheckBox]", inputs: ["size", "rounded"] }, { kind: "component", type: i3.FormFieldComponent, selector: "kendo-formfield", inputs: ["showHints", "orientation", "showErrors", "colSpan"] }, { kind: "component", type: i3.HintComponent, selector: "kendo-formhint", inputs: ["align"] }, { kind: "component", type: i3.ErrorComponent, selector: "kendo-formerror", inputs: ["align"] }, { kind: "ngmodule", type: CheckBoxModule }, { kind: "ngmodule", type: LabelModule }, { kind: "component", type: i5.LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }, { kind: "ngmodule", type: FormFieldModule }, { kind: "component", type: VariablesEditorComponent, selector: "mm-variables-editor", inputs: ["variables"], outputs: ["variablesChange"] }, { kind: "component", type: TimeRangePickerComponent, selector: "mm-time-range-picker", inputs: ["config", "labels", "initialSelection"], outputs: ["rangeChange", "rangeChangeISO", "selectionChange"] }] });
22225
24473
  }
22226
24474
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: MeshBoardSettingsDialogComponent, decorators: [{
22227
24475
  type: Component,
@@ -22234,8 +24482,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
22234
24482
  CheckBoxModule,
22235
24483
  LabelModule,
22236
24484
  FormFieldModule,
22237
- VariablesEditorComponent
22238
- ], template: "<div class=\"meshboard-settings-dialog\">\n <form class=\"settings-form\">\n <!-- Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"nameInput\" text=\"Name *\"></kendo-label>\n <kendo-textbox\n #nameInput\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Enter MeshBoard name\"\n required>\n </kendo-textbox>\n @if (name.trim().length === 0) {\n <kendo-formerror>Name is required</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Description Field -->\n <kendo-formfield>\n <kendo-label [for]=\"descriptionInput\" text=\"Description\"></kendo-label>\n <kendo-textarea\n #descriptionInput\n [(ngModel)]=\"description\"\n name=\"description\"\n placeholder=\"Enter MeshBoard description (optional)\"\n [rows]=\"3\">\n </kendo-textarea>\n </kendo-formfield>\n\n <!-- Well-Known Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"wellKnownNameInput\" text=\"Well-Known Name\"></kendo-label>\n <kendo-textbox\n #wellKnownNameInput\n [(ngModel)]=\"rtWellKnownName\"\n name=\"rtWellKnownName\"\n placeholder=\"e.g., cockpit, dashboard-main\">\n </kendo-textbox>\n <kendo-formhint>Unique identifier for routing. Use lowercase with hyphens (e.g., 'cockpit', 'sales-dashboard').</kendo-formhint>\n </kendo-formfield>\n\n <!-- Layout Settings -->\n <div class=\"section-title\">Layout Settings</div>\n\n <!-- Columns Field -->\n <kendo-formfield>\n <kendo-label [for]=\"columnsInput\" text=\"Columns *\"></kendo-label>\n <kendo-numerictextbox\n #columnsInput\n [(ngModel)]=\"columns\"\n name=\"columns\"\n [min]=\"1\"\n [max]=\"12\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Number of columns in the grid (1-12)</kendo-formhint>\n @if (columns < 1 || columns > 12) {\n <kendo-formerror>Columns must be between 1 and 12</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Row Height Field -->\n <kendo-formfield>\n <kendo-label [for]=\"rowHeightInput\" text=\"Row Height *\"></kendo-label>\n <kendo-numerictextbox\n #rowHeightInput\n [(ngModel)]=\"rowHeight\"\n name=\"rowHeight\"\n [min]=\"100\"\n [max]=\"1000\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Height of each row in pixels (100-1000)</kendo-formhint>\n @if (rowHeight < 100 || rowHeight > 1000) {\n <kendo-formerror>Row height must be between 100 and 1000</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Gap Field -->\n <kendo-formfield>\n <kendo-label [for]=\"gapInput\" text=\"Gap *\"></kendo-label>\n <kendo-numerictextbox\n #gapInput\n [(ngModel)]=\"gap\"\n name=\"gap\"\n [min]=\"0\"\n [max]=\"100\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Space between widgets in pixels (0-100)</kendo-formhint>\n @if (gap < 0 || gap > 100) {\n <kendo-formerror>Gap must be between 0 and 100</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Variables Section -->\n <mm-variables-editor\n [(variables)]=\"variables\">\n </mm-variables-editor>\n\n <!-- Time Filter Section -->\n <div class=\"section-title\">Time Filter</div>\n\n <kendo-formfield>\n <div class=\"checkbox-wrapper\">\n <input\n type=\"checkbox\"\n kendoCheckBox\n #timeFilterCheckbox\n [(ngModel)]=\"timeFilterEnabled\"\n name=\"timeFilterEnabled\"\n id=\"timeFilterEnabled\"/>\n <kendo-label\n [for]=\"timeFilterCheckbox\"\n text=\"Enable Time Filter\"\n class=\"checkbox-label\">\n </kendo-label>\n </div>\n <kendo-formhint>\n Shows a time range picker in the toolbar. Sets $timeRangeFrom and $timeRangeTo variables.\n </kendo-formhint>\n </kendo-formfield>\n </form>\n\n <!-- Dialog Actions -->\n <kendo-dialog-actions>\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"save()\"\n [disabled]=\"!isValid\"\n themeColor=\"primary\">\n Save\n </button>\n </kendo-dialog-actions>\n</div>\n", styles: [".meshboard-settings-dialog{display:flex;flex-direction:column;height:100%;max-height:calc(80vh - 60px);overflow:hidden}.meshboard-settings-dialog .settings-form{display:flex;flex-direction:column;gap:1.25rem;padding:1.5rem;overflow-y:auto;flex:1;min-height:0}.meshboard-settings-dialog .settings-form kendo-formfield{display:flex;flex-direction:column;gap:.5rem}.meshboard-settings-dialog .settings-form kendo-formfield kendo-label{font-weight:500;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-textbox,.meshboard-settings-dialog .settings-form kendo-formfield kendo-textarea,.meshboard-settings-dialog .settings-form kendo-formfield kendo-numerictextbox{width:100%}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formhint{font-size:.75rem;color:var(--kendo-color-subtle, #757575)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formerror{font-size:.75rem;color:var(--kendo-color-error, #f44336)}.meshboard-settings-dialog .settings-form .section-title{font-size:.875rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242);text-transform:uppercase;letter-spacing:.5px;margin-top:.5rem;padding-bottom:.5rem;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.meshboard-settings-dialog .settings-form .checkbox-wrapper{display:flex;align-items:center;gap:.5rem}.meshboard-settings-dialog .settings-form .checkbox-wrapper .checkbox-label{font-weight:400;cursor:pointer}.meshboard-settings-dialog kendo-dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;background-color:var(--kendo-color-surface-alt, white)}\n"] }]
24485
+ VariablesEditorComponent,
24486
+ TimeRangePickerComponent
24487
+ ], template: "<div class=\"meshboard-settings-dialog\">\n <form class=\"settings-form\">\n <!-- Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"nameInput\" text=\"Name *\"></kendo-label>\n <kendo-textbox\n #nameInput\n [(ngModel)]=\"name\"\n name=\"name\"\n placeholder=\"Enter MeshBoard name\"\n required>\n </kendo-textbox>\n @if (name.trim().length === 0) {\n <kendo-formerror>Name is required</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Description Field -->\n <kendo-formfield>\n <kendo-label [for]=\"descriptionInput\" text=\"Description\"></kendo-label>\n <kendo-textarea\n #descriptionInput\n [(ngModel)]=\"description\"\n name=\"description\"\n placeholder=\"Enter MeshBoard description (optional)\"\n [rows]=\"3\">\n </kendo-textarea>\n </kendo-formfield>\n\n <!-- Well-Known Name Field -->\n <kendo-formfield>\n <kendo-label [for]=\"wellKnownNameInput\" text=\"Well-Known Name\"></kendo-label>\n <kendo-textbox\n #wellKnownNameInput\n [(ngModel)]=\"rtWellKnownName\"\n name=\"rtWellKnownName\"\n placeholder=\"e.g., cockpit, dashboard-main\">\n </kendo-textbox>\n <kendo-formhint>Unique identifier for routing. Use lowercase with hyphens (e.g., 'cockpit', 'sales-dashboard').</kendo-formhint>\n </kendo-formfield>\n\n <!-- Layout Settings -->\n <div class=\"section-title\">Layout Settings</div>\n\n <!-- Columns Field -->\n <kendo-formfield>\n <kendo-label [for]=\"columnsInput\" text=\"Columns *\"></kendo-label>\n <kendo-numerictextbox\n #columnsInput\n [(ngModel)]=\"columns\"\n name=\"columns\"\n [min]=\"1\"\n [max]=\"12\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Number of columns in the grid (1-12)</kendo-formhint>\n @if (columns < 1 || columns > 12) {\n <kendo-formerror>Columns must be between 1 and 12</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Row Height Field -->\n <kendo-formfield>\n <kendo-label [for]=\"rowHeightInput\" text=\"Row Height *\"></kendo-label>\n <kendo-numerictextbox\n #rowHeightInput\n [(ngModel)]=\"rowHeight\"\n name=\"rowHeight\"\n [min]=\"100\"\n [max]=\"1000\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Height of each row in pixels (100-1000)</kendo-formhint>\n @if (rowHeight < 100 || rowHeight > 1000) {\n <kendo-formerror>Row height must be between 100 and 1000</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Gap Field -->\n <kendo-formfield>\n <kendo-label [for]=\"gapInput\" text=\"Gap *\"></kendo-label>\n <kendo-numerictextbox\n #gapInput\n [(ngModel)]=\"gap\"\n name=\"gap\"\n [min]=\"0\"\n [max]=\"100\"\n [decimals]=\"0\"\n [format]=\"'n0'\"\n [spinners]=\"true\"\n required>\n </kendo-numerictextbox>\n <kendo-formhint>Space between widgets in pixels (0-100)</kendo-formhint>\n @if (gap < 0 || gap > 100) {\n <kendo-formerror>Gap must be between 0 and 100</kendo-formerror>\n }\n </kendo-formfield>\n\n <!-- Variables Section -->\n <mm-variables-editor\n [(variables)]=\"variables\">\n </mm-variables-editor>\n\n <!-- Time Filter Section -->\n <div class=\"section-title\">Time Filter</div>\n\n <kendo-formfield>\n <div class=\"checkbox-wrapper\">\n <input\n type=\"checkbox\"\n kendoCheckBox\n #timeFilterCheckbox\n [(ngModel)]=\"timeFilterEnabled\"\n name=\"timeFilterEnabled\"\n id=\"timeFilterEnabled\"/>\n <kendo-label\n [for]=\"timeFilterCheckbox\"\n text=\"Enable Time Filter\"\n class=\"checkbox-label\">\n </kendo-label>\n </div>\n <kendo-formhint>\n Shows a time range picker in the toolbar. Sets $timeRangeFrom and $timeRangeTo variables.\n </kendo-formhint>\n </kendo-formfield>\n\n @if (timeFilterEnabled) {\n <kendo-formfield>\n <kendo-label text=\"Default Selection\"></kendo-label>\n <mm-time-range-picker\n [initialSelection]=\"initialDefaultSelection\"\n (selectionChange)=\"onDefaultSelectionChange($event)\">\n </mm-time-range-picker>\n <kendo-formhint>Initial time filter shown when no URL parameters are set. Note: User-selected filters override this default. Use the reset button in the toolbar to revert to the default.</kendo-formhint>\n </kendo-formfield>\n }\n </form>\n\n <!-- Dialog Actions -->\n <kendo-dialog-actions>\n <button kendoButton (click)=\"cancel()\" fillMode=\"flat\">\n Cancel\n </button>\n <button\n kendoButton\n (click)=\"save()\"\n [disabled]=\"!isValid\"\n themeColor=\"primary\">\n Save\n </button>\n </kendo-dialog-actions>\n</div>\n", styles: [".meshboard-settings-dialog{display:flex;flex-direction:column;height:100%;max-height:calc(80vh - 60px);overflow:hidden}.meshboard-settings-dialog .settings-form{display:flex;flex-direction:column;gap:1.25rem;padding:1.5rem;overflow-y:auto;flex:1;min-height:0}.meshboard-settings-dialog .settings-form kendo-formfield{display:flex;flex-direction:column;gap:.5rem}.meshboard-settings-dialog .settings-form kendo-formfield kendo-label{font-weight:500;color:var(--kendo-color-on-app-surface, #424242)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-textbox,.meshboard-settings-dialog .settings-form kendo-formfield kendo-textarea,.meshboard-settings-dialog .settings-form kendo-formfield kendo-numerictextbox{width:100%}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formhint{font-size:.75rem;color:var(--kendo-color-subtle, #757575)}.meshboard-settings-dialog .settings-form kendo-formfield kendo-formerror{font-size:.75rem;color:var(--kendo-color-error, #f44336)}.meshboard-settings-dialog .settings-form .section-title{font-size:.875rem;font-weight:600;color:var(--kendo-color-on-app-surface, #424242);text-transform:uppercase;letter-spacing:.5px;margin-top:.5rem;padding-bottom:.5rem;border-bottom:1px solid var(--kendo-color-border, #e0e0e0)}.meshboard-settings-dialog .settings-form .checkbox-wrapper{display:flex;align-items:center;gap:.5rem}.meshboard-settings-dialog .settings-form .checkbox-wrapper .checkbox-label{font-weight:400;cursor:pointer}.meshboard-settings-dialog kendo-dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--kendo-color-border, #e0e0e0);flex-shrink:0;background-color:var(--kendo-color-surface-alt, white)}\n"] }]
22239
24488
  }] });
22240
24489
 
22241
24490
  /**
@@ -22279,16 +24528,22 @@ class AddWidgetDialogComponent {
22279
24528
  return chartPieIcon;
22280
24529
  case 'barChart':
22281
24530
  return chartColumnStackedIcon;
24531
+ case 'lineChart':
24532
+ return chartLineMarkersIcon;
22282
24533
  case 'statsGrid':
22283
24534
  return gridLayoutIcon;
22284
24535
  case 'statusIndicator':
22285
24536
  return checkCircleIcon;
22286
24537
  case 'serviceHealth':
22287
24538
  return heartIcon;
24539
+ case 'heatmap':
24540
+ return gridIcon;
22288
24541
  case 'widgetGroup':
22289
24542
  return copyIcon;
24543
+ case 'markdown':
24544
+ return clipboardMarkdownIcon;
22290
24545
  case 'process':
22291
- return chartLineIcon;
24546
+ return gearsIcon;
22292
24547
  default:
22293
24548
  return chartLineIcon;
22294
24549
  }
@@ -22312,6 +24567,8 @@ class AddWidgetDialogComponent {
22312
24567
  return 'Display data distribution as a pie or donut chart';
22313
24568
  case 'barChart':
22314
24569
  return 'Display data as vertical or horizontal bars';
24570
+ case 'lineChart':
24571
+ return 'Display time-series data as a multi-series line chart with optional multiple Y-axes';
22315
24572
  case 'statsGrid':
22316
24573
  return 'Display multiple KPIs in a grid layout';
22317
24574
  case 'statusIndicator':
@@ -22320,6 +24577,10 @@ class AddWidgetDialogComponent {
22320
24577
  return 'Display service health status with pulse animation';
22321
24578
  case 'widgetGroup':
22322
24579
  return 'Display a repeating group of child widgets from a query or CK type';
24580
+ case 'heatmap':
24581
+ return 'Visualize data density or availability over time as a heatmap grid';
24582
+ case 'markdown':
24583
+ return 'Display formatted text content with Markdown syntax';
22323
24584
  case 'process':
22324
24585
  return 'Display and interact with process diagrams and HMI graphics';
22325
24586
  default:
@@ -22995,8 +25256,11 @@ class MeshBoardViewComponent {
22995
25256
  route = inject(ActivatedRoute);
22996
25257
  gridService = inject(MeshBoardGridService);
22997
25258
  tenantIdProvider = inject(TENANT_ID_PROVIDER, { optional: true });
25259
+ breadCrumbService = inject(BreadCrumbService, { optional: true });
22998
25260
  // Track the last known rtId to avoid unnecessary navigation
22999
25261
  lastNavigatedRtId = null;
25262
+ // Guard to prevent the effect from running during initial load
25263
+ initialLoadComplete = false;
23000
25264
  // Icons
23001
25265
  gearIcon = gearIcon;
23002
25266
  plusIcon = plusIcon;
@@ -23007,11 +25271,13 @@ class MeshBoardViewComponent {
23007
25271
  linkIcon = linkIcon;
23008
25272
  trashIcon = trashIcon;
23009
25273
  gridLayoutIcon = gridLayoutIcon;
25274
+ undoIcon = undoIcon;
23010
25275
  // Edit widget dialog state
23011
25276
  showEditWidgetDialog = false;
23012
25277
  editingWidget = null;
23013
25278
  // Config dialog state
23014
25279
  configDialogSubscription = null;
25280
+ navigationSubscription = null;
23015
25281
  // State signals
23016
25282
  config = this.stateService.meshBoardConfig;
23017
25283
  isEditMode = this.editModeService.isEditMode;
@@ -23039,45 +25305,85 @@ class MeshBoardViewComponent {
23039
25305
  // Time Filter computed signals
23040
25306
  isTimeFilterEnabled = computed(() => this.stateService.isTimeFilterEnabled(), ...(ngDevMode ? [{ debugName: "isTimeFilterEnabled" }] : []));
23041
25307
  timeFilterConfig = computed(() => this.stateService.getTimeFilterConfig(), ...(ngDevMode ? [{ debugName: "timeFilterConfig" }] : []));
25308
+ _urlTimeSelection = signal(null, ...(ngDevMode ? [{ debugName: "_urlTimeSelection" }] : []));
23042
25309
  initialTimeSelection = computed(() => {
23043
- const selection = this.timeFilterConfig()?.selection;
25310
+ // Priority: URL selection > stored selection > default selection
25311
+ const urlSelection = this._urlTimeSelection();
25312
+ const config = this.timeFilterConfig();
25313
+ const selection = urlSelection ?? config?.selection ?? config?.defaultSelection;
23044
25314
  if (!selection)
23045
25315
  return undefined;
23046
- // Convert our TimeRangeSelection to shared-ui TimeRangeSelection
23047
- // Handle customFrom/customTo as Date objects
23048
25316
  return {
23049
25317
  ...selection,
23050
25318
  customFrom: selection.customFrom ? new Date(selection.customFrom) : undefined,
23051
25319
  customTo: selection.customTo ? new Date(selection.customTo) : undefined
23052
25320
  };
23053
25321
  }, ...(ngDevMode ? [{ debugName: "initialTimeSelection" }] : []));
25322
+ /**
25323
+ * Whether the time filter can be reset to the default selection.
25324
+ * True when a default selection is configured and the stored selection differs from it.
25325
+ */
25326
+ canResetTimeFilter = computed(() => {
25327
+ const config = this.timeFilterConfig();
25328
+ if (!config?.enabled || !config.defaultSelection)
25329
+ return false;
25330
+ if (!config.selection)
25331
+ return false;
25332
+ return !this.isSelectionEqual(config.selection, config.defaultSelection);
25333
+ }, ...(ngDevMode ? [{ debugName: "canResetTimeFilter" }] : []));
23054
25334
  constructor() {
23055
- // Effect to sync URL when dashboard changes
25335
+ // Effect to sync URL when dashboard changes (after initial load)
23056
25336
  effect(() => {
23057
25337
  const currentRtId = this.stateService.persistedMeshBoardId();
23058
25338
  if (currentRtId && currentRtId !== this.lastNavigatedRtId) {
23059
25339
  this.lastNavigatedRtId = currentRtId;
23060
- this.updateUrlWithRtId(currentRtId);
23061
- // Initialize time filter variables when switching to a different MeshBoard
23062
- this.initializeTimeFilterVariables();
25340
+ if (this.initialLoadComplete) {
25341
+ // Only update URL and time filter for post-init board switches (e.g. manager dialog)
25342
+ this.updateUrlWithRtId(currentRtId);
25343
+ this.initializeTimeFilterVariables();
25344
+ }
23063
25345
  }
23064
25346
  });
25347
+ // Update breadcrumb after each navigation (BreadCrumbService recreates items on NavigationEnd)
25348
+ if (this.breadCrumbService) {
25349
+ this.navigationSubscription = this.router.events
25350
+ .pipe(filter(event => event instanceof NavigationEnd))
25351
+ .subscribe(() => {
25352
+ const name = this.config().name;
25353
+ if (name) {
25354
+ this.breadCrumbService.updateBreadcrumbLabels({ name });
25355
+ }
25356
+ });
25357
+ }
25358
+ }
25359
+ /**
25360
+ * Updates the breadcrumb with the current MeshBoard name.
25361
+ */
25362
+ updateBreadcrumb() {
25363
+ const name = this.config().name;
25364
+ if (name && this.breadCrumbService) {
25365
+ this.breadCrumbService.updateBreadcrumbLabels({ name });
25366
+ }
23065
25367
  }
23066
25368
  /**
23067
25369
  * Updates the URL to include the current MeshBoard rtId.
25370
+ * Preserves existing query parameters.
23068
25371
  */
23069
25372
  updateUrlWithRtId(rtId) {
23070
25373
  const currentUrl = this.router.url;
23071
25374
  const hasRtIdParam = this.route.snapshot.paramMap.has('rtId');
25375
+ // Split off query string to preserve it
25376
+ const [pathPart, queryPart] = currentUrl.split('?');
25377
+ const querySuffix = queryPart ? '?' + queryPart : '';
23072
25378
  if (hasRtIdParam) {
23073
25379
  // Replace the last URL segment (the old rtId) with the new one
23074
- const lastSlashIndex = currentUrl.lastIndexOf('/');
23075
- const newUrl = currentUrl.substring(0, lastSlashIndex + 1) + rtId;
23076
- this.router.navigateByUrl(newUrl, { replaceUrl: true });
25380
+ const lastSlashIndex = pathPart.lastIndexOf('/');
25381
+ const newPath = pathPart.substring(0, lastSlashIndex + 1) + rtId;
25382
+ this.router.navigateByUrl(newPath + querySuffix, { replaceUrl: true });
23077
25383
  }
23078
25384
  else {
23079
25385
  // Append the rtId to the current URL
23080
- this.router.navigateByUrl(`${currentUrl}/${rtId}`, { replaceUrl: true });
25386
+ this.router.navigateByUrl(`${pathPart}/${rtId}${querySuffix}`, { replaceUrl: true });
23081
25387
  }
23082
25388
  }
23083
25389
  async ngOnInit() {
@@ -23113,6 +25419,10 @@ class MeshBoardViewComponent {
23113
25419
  }
23114
25420
  // Initialize time filter variables if enabled with stored selection
23115
25421
  this.initializeTimeFilterVariables();
25422
+ // Mark initial load as complete so the effect can handle subsequent board switches
25423
+ this.initialLoadComplete = true;
25424
+ // Update breadcrumb with MeshBoard name
25425
+ this.updateBreadcrumb();
23116
25426
  this._isInitialized.set(true);
23117
25427
  }
23118
25428
  catch (err) {
@@ -23142,6 +25452,7 @@ class MeshBoardViewComponent {
23142
25452
  }
23143
25453
  ngOnDestroy() {
23144
25454
  this.closeConfigDialog();
25455
+ this.navigationSubscription?.unsubscribe();
23145
25456
  }
23146
25457
  /**
23147
25458
  * Preloads data for all widgets to improve initial rendering performance.
@@ -23162,29 +25473,65 @@ class MeshBoardViewComponent {
23162
25473
  await Promise.all(promises);
23163
25474
  }
23164
25475
  /**
23165
- * Initializes time filter variables based on the stored selection.
23166
- * Called after loading a MeshBoard to ensure time filter variables are set.
25476
+ * Initializes time filter variables based on URL query params or stored selection.
25477
+ * URL params take precedence over stored selection to support page reload.
23167
25478
  */
23168
25479
  initializeTimeFilterVariables() {
23169
25480
  const timeFilter = this.stateService.getTimeFilterConfig();
23170
- if (!timeFilter?.enabled || !timeFilter.selection) {
25481
+ if (!timeFilter?.enabled) {
23171
25482
  return;
23172
25483
  }
23173
- // Convert stored selection to shared-ui format for TimeRangeUtils
23174
- const sharedSelection = {
23175
- ...timeFilter.selection,
23176
- customFrom: timeFilter.selection.customFrom ? new Date(timeFilter.selection.customFrom) : undefined,
23177
- customTo: timeFilter.selection.customTo ? new Date(timeFilter.selection.customTo) : undefined
23178
- };
23179
- // Calculate the current time range from the selection
23180
- const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection);
25484
+ // Check URL query params first (takes precedence over stored selection)
25485
+ const urlSelection = this.readTimeFilterFromUrl();
25486
+ if (urlSelection) {
25487
+ // Signal the URL selection so the picker picks it up
25488
+ this._urlTimeSelection.set(urlSelection);
25489
+ // Update state so widgets use the URL-derived time range
25490
+ const sharedSelection = this.toSharedSelection(urlSelection);
25491
+ const showTime = timeFilter.pickerConfig?.showTime ?? false;
25492
+ const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime);
25493
+ if (range) {
25494
+ const rangeISO = TimeRangeUtils.toISO(range);
25495
+ this.stateService.updateTimeFilterSelection(urlSelection, rangeISO.from, rangeISO.to);
25496
+ }
25497
+ return;
25498
+ }
25499
+ // Fall back to stored selection, then default selection
25500
+ const selection = timeFilter.selection ?? timeFilter.defaultSelection;
25501
+ if (!selection) {
25502
+ return;
25503
+ }
25504
+ // If using defaultSelection (no stored selection yet), apply it as the active selection
25505
+ if (!timeFilter.selection && timeFilter.defaultSelection) {
25506
+ this._urlTimeSelection.set(selection);
25507
+ const sharedSelection = this.toSharedSelection(selection);
25508
+ const showTime = timeFilter.pickerConfig?.showTime ?? false;
25509
+ const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime);
25510
+ if (range) {
25511
+ const rangeISO = TimeRangeUtils.toISO(range);
25512
+ this.stateService.updateTimeFilterSelection(selection, rangeISO.from, rangeISO.to);
25513
+ }
25514
+ return;
25515
+ }
25516
+ const sharedSelection = this.toSharedSelection(selection);
25517
+ const showTime = timeFilter.pickerConfig?.showTime ?? false;
25518
+ const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime);
23181
25519
  if (!range) {
23182
25520
  return;
23183
25521
  }
23184
- // Convert to ISO strings and set the variables
23185
25522
  const rangeISO = TimeRangeUtils.toISO(range);
23186
25523
  this.stateService.setTimeFilterVariables(rangeISO.from, rangeISO.to);
23187
25524
  }
25525
+ /**
25526
+ * Converts a TimeRangeSelection to a SharedTimeRangeSelection.
25527
+ */
25528
+ toSharedSelection(selection) {
25529
+ return {
25530
+ ...selection,
25531
+ customFrom: selection.customFrom ? new Date(selection.customFrom) : undefined,
25532
+ customTo: selection.customTo ? new Date(selection.customTo) : undefined
25533
+ };
25534
+ }
23188
25535
  /**
23189
25536
  * Gets the component type for a widget based on its type.
23190
25537
  */
@@ -23359,7 +25706,7 @@ class MeshBoardViewComponent {
23359
25706
  }
23360
25707
  /**
23361
25708
  * Handles time selection change from the time range picker.
23362
- * Updates the time filter variables and persists the selection.
25709
+ * Updates the time filter variables, persists the selection, and syncs to URL query params.
23363
25710
  */
23364
25711
  onTimeSelectionChange(sharedSelection) {
23365
25712
  // Convert shared-ui selection to our model
@@ -23368,6 +25715,9 @@ class MeshBoardViewComponent {
23368
25715
  year: sharedSelection.year,
23369
25716
  quarter: sharedSelection.quarter,
23370
25717
  month: sharedSelection.month,
25718
+ day: sharedSelection.day,
25719
+ hourFrom: sharedSelection.hourFrom,
25720
+ hourTo: sharedSelection.hourTo,
23371
25721
  relativeValue: sharedSelection.relativeValue,
23372
25722
  relativeUnit: sharedSelection.relativeUnit,
23373
25723
  customFrom: sharedSelection.customFrom?.toISOString(),
@@ -23380,13 +25730,127 @@ class MeshBoardViewComponent {
23380
25730
  return;
23381
25731
  }
23382
25732
  // Calculate the time range from the selection
23383
- const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection);
25733
+ const showTime = this.stateService.getTimeFilterConfig()?.pickerConfig?.showTime ?? false;
25734
+ const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime);
23384
25735
  if (!range)
23385
25736
  return;
23386
25737
  // Convert to ISO strings
23387
25738
  const rangeISO = TimeRangeUtils.toISO(range);
23388
25739
  // Update state with new selection and time range values
23389
25740
  this.stateService.updateTimeFilterSelection(selection, rangeISO.from, rangeISO.to);
25741
+ // Sync selection to URL query parameters
25742
+ this.writeTimeFilterToUrl(selection);
25743
+ }
25744
+ /**
25745
+ * Resets the time filter to the configured default selection.
25746
+ * Clears the stored selection and applies the default.
25747
+ */
25748
+ resetTimeFilterToDefault() {
25749
+ const config = this.timeFilterConfig();
25750
+ if (!config?.defaultSelection)
25751
+ return;
25752
+ const defaultSel = config.defaultSelection;
25753
+ const sharedSelection = this.toSharedSelection(defaultSel);
25754
+ const showTime = config.pickerConfig?.showTime ?? false;
25755
+ const range = TimeRangeUtils.getTimeRangeFromSelection(sharedSelection, showTime);
25756
+ if (!range)
25757
+ return;
25758
+ const rangeISO = TimeRangeUtils.toISO(range);
25759
+ this.stateService.updateTimeFilterSelection(defaultSel, rangeISO.from, rangeISO.to);
25760
+ this._urlTimeSelection.set(defaultSel);
25761
+ this.writeTimeFilterToUrl(defaultSel);
25762
+ }
25763
+ /**
25764
+ * Writes the time filter selection to URL query parameters.
25765
+ * Only writes params relevant to the current type, clears all others.
25766
+ */
25767
+ writeTimeFilterToUrl(selection) {
25768
+ // Start with all params cleared
25769
+ const cleanParams = {
25770
+ tf_type: selection.type,
25771
+ tf_year: null,
25772
+ tf_quarter: null,
25773
+ tf_month: null,
25774
+ tf_day: null,
25775
+ tf_hf: null,
25776
+ tf_ht: null,
25777
+ tf_rv: null,
25778
+ tf_ru: null,
25779
+ tf_from: null,
25780
+ tf_to: null
25781
+ };
25782
+ // Set only the params relevant to the current type
25783
+ switch (selection.type) {
25784
+ case 'year':
25785
+ if (selection.year != null)
25786
+ cleanParams['tf_year'] = selection.year.toString();
25787
+ break;
25788
+ case 'quarter':
25789
+ if (selection.year != null)
25790
+ cleanParams['tf_year'] = selection.year.toString();
25791
+ if (selection.quarter != null)
25792
+ cleanParams['tf_quarter'] = selection.quarter.toString();
25793
+ break;
25794
+ case 'month':
25795
+ if (selection.year != null)
25796
+ cleanParams['tf_year'] = selection.year.toString();
25797
+ if (selection.month != null)
25798
+ cleanParams['tf_month'] = selection.month.toString();
25799
+ break;
25800
+ case 'day':
25801
+ if (selection.year != null)
25802
+ cleanParams['tf_year'] = selection.year.toString();
25803
+ if (selection.month != null)
25804
+ cleanParams['tf_month'] = selection.month.toString();
25805
+ if (selection.day != null)
25806
+ cleanParams['tf_day'] = selection.day.toString();
25807
+ if (selection.hourFrom != null)
25808
+ cleanParams['tf_hf'] = selection.hourFrom.toString();
25809
+ if (selection.hourTo != null)
25810
+ cleanParams['tf_ht'] = selection.hourTo.toString();
25811
+ break;
25812
+ case 'relative':
25813
+ if (selection.relativeValue != null)
25814
+ cleanParams['tf_rv'] = selection.relativeValue.toString();
25815
+ if (selection.relativeUnit)
25816
+ cleanParams['tf_ru'] = selection.relativeUnit;
25817
+ break;
25818
+ case 'custom':
25819
+ if (selection.customFrom)
25820
+ cleanParams['tf_from'] = selection.customFrom;
25821
+ if (selection.customTo)
25822
+ cleanParams['tf_to'] = selection.customTo;
25823
+ break;
25824
+ }
25825
+ this.router.navigate([], {
25826
+ relativeTo: this.route,
25827
+ queryParams: cleanParams,
25828
+ queryParamsHandling: 'merge',
25829
+ replaceUrl: true
25830
+ });
25831
+ }
25832
+ /**
25833
+ * Reads the time filter selection from URL query parameters.
25834
+ * Returns null if no time filter params are present.
25835
+ */
25836
+ readTimeFilterFromUrl() {
25837
+ const params = this.route.snapshot.queryParamMap;
25838
+ const type = params.get('tf_type');
25839
+ if (!type)
25840
+ return null;
25841
+ return {
25842
+ type: type,
25843
+ year: params.has('tf_year') ? Number(params.get('tf_year')) : undefined,
25844
+ quarter: params.has('tf_quarter') ? Number(params.get('tf_quarter')) : undefined,
25845
+ month: params.has('tf_month') ? Number(params.get('tf_month')) : undefined,
25846
+ day: params.has('tf_day') ? Number(params.get('tf_day')) : undefined,
25847
+ hourFrom: params.has('tf_hf') ? Number(params.get('tf_hf')) : undefined,
25848
+ hourTo: params.has('tf_ht') ? Number(params.get('tf_ht')) : undefined,
25849
+ relativeValue: params.has('tf_rv') ? Number(params.get('tf_rv')) : undefined,
25850
+ relativeUnit: params.get('tf_ru') ?? undefined,
25851
+ customFrom: params.get('tf_from') ?? undefined,
25852
+ customTo: params.get('tf_to') ?? undefined
25853
+ };
23390
25854
  }
23391
25855
  /**
23392
25856
  * Compares two TimeRangeSelection objects for equality.
@@ -23396,6 +25860,9 @@ class MeshBoardViewComponent {
23396
25860
  a.year === b.year &&
23397
25861
  a.quarter === b.quarter &&
23398
25862
  a.month === b.month &&
25863
+ a.day === b.day &&
25864
+ a.hourFrom === b.hourFrom &&
25865
+ a.hourTo === b.hourTo &&
23399
25866
  a.relativeValue === b.relativeValue &&
23400
25867
  a.relativeUnit === b.relativeUnit &&
23401
25868
  a.customFrom === b.customFrom &&
@@ -23567,7 +26034,7 @@ class MeshBoardViewComponent {
23567
26034
  }
23568
26035
  }
23569
26036
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: MeshBoardViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23570
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", 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 [initialSelection]=\"initialTimeSelection()\"\n (rangeChange)=\"onTimeRangeChange($event)\"\n (selectionChange)=\"onTimeSelectionChange($event)\">\n </mm-time-range-picker>\n </div>\n }\n\n <div class=\"toolbar-right\">\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 fillMode=\"flat\"\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 fillMode=\"solid\"\n themeColor=\"primary\">\n {{ isSaving() ? 'Saving...' : 'Save' }}\n </button>\n }\n }\n </div>\n </div>\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]=\"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%}.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:center;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}.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:center;flex-shrink:0}.meshboard-toolbar .toolbar-center mm-time-range-picker ::ng-deep .time-range-picker{gap:.5rem}.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: i4$1.TileLayoutComponent, selector: "kendo-tilelayout", inputs: ["columns", "columnWidth", "gap", "reorderable", "resizable", "rowHeight", "autoFlow", "navigable"], outputs: ["reorder", "resize"] }, { kind: "component", type: i4$1.TileLayoutItemBodyComponent, selector: "kendo-tilelayout-item-body" }, { kind: "component", type: i4$1.TileLayoutItemComponent, selector: "kendo-tilelayout-item", inputs: ["title", "rowSpan", "colSpan", "order", "col", "row", "reorderable", "resizable"] }, { kind: "component", type: i4$1.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"] }] });
26037
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", 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 [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 <div class=\"toolbar-right\">\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 fillMode=\"flat\"\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 fillMode=\"solid\"\n themeColor=\"primary\">\n {{ isSaving() ? 'Saving...' : 'Save' }}\n </button>\n }\n }\n </div>\n </div>\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]=\"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%}.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:center;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}.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:center;flex-shrink:0}.meshboard-toolbar .toolbar-center mm-time-range-picker ::ng-deep .time-range-picker{gap:.5rem}.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: i4$1.TileLayoutComponent, selector: "kendo-tilelayout", inputs: ["columns", "columnWidth", "gap", "reorderable", "resizable", "rowHeight", "autoFlow", "navigable"], outputs: ["reorder", "resize"] }, { kind: "component", type: i4$1.TileLayoutItemBodyComponent, selector: "kendo-tilelayout-item-body" }, { kind: "component", type: i4$1.TileLayoutItemComponent, selector: "kendo-tilelayout-item", inputs: ["title", "rowSpan", "colSpan", "order", "col", "row", "reorderable", "resizable"] }, { kind: "component", type: i4$1.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"] }] });
23571
26038
  }
23572
26039
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: MeshBoardViewComponent, decorators: [{
23573
26040
  type: Component,
@@ -23580,7 +26047,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
23580
26047
  SVGIconModule,
23581
26048
  EditWidgetDialogComponent,
23582
26049
  TimeRangePickerComponent
23583
- ], 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 [initialSelection]=\"initialTimeSelection()\"\n (rangeChange)=\"onTimeRangeChange($event)\"\n (selectionChange)=\"onTimeSelectionChange($event)\">\n </mm-time-range-picker>\n </div>\n }\n\n <div class=\"toolbar-right\">\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 fillMode=\"flat\"\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 fillMode=\"solid\"\n themeColor=\"primary\">\n {{ isSaving() ? 'Saving...' : 'Save' }}\n </button>\n }\n }\n </div>\n </div>\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]=\"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%}.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:center;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}.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:center;flex-shrink:0}.meshboard-toolbar .toolbar-center mm-time-range-picker ::ng-deep .time-range-picker{gap:.5rem}.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"] }]
26050
+ ], 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 [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 <div class=\"toolbar-right\">\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 fillMode=\"flat\"\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 fillMode=\"solid\"\n themeColor=\"primary\">\n {{ isSaving() ? 'Saving...' : 'Save' }}\n </button>\n }\n }\n </div>\n </div>\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]=\"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%}.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:center;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}.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:center;flex-shrink:0}.meshboard-toolbar .toolbar-center mm-time-range-picker ::ng-deep .time-range-picker{gap:.5rem}.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"] }]
23584
26051
  }], ctorParameters: () => [] });
23585
26052
 
23586
26053
  /*
@@ -23592,5 +26059,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
23592
26059
  * Generated bundle index. Do not edit.
23593
26060
  */
23594
26061
 
23595
- export { AddWidgetDialogComponent, AssociationsConfigDialogComponent, BarChartConfigDialogComponent, BarChartWidgetComponent, DashboardDataService, DashboardGridService, EditModeStateService, EditWidgetDialogComponent, EntityAssociationsWidgetComponent, EntityCardConfigDialogComponent, EntityCardWidgetComponent, EntityDetailDialogComponent, GaugeConfigDialogComponent, GaugeWidgetComponent, KpiConfigDialogComponent, KpiWidgetComponent, MESHBOARD_OPTIONS, MESHBOARD_TENANT_ID_PROVIDER, MarkdownConfigDialogComponent, MarkdownWidgetComponent, MeshBoardDataService, MeshBoardGridService, MeshBoardManagerDialogComponent, MeshBoardPersistenceService, MeshBoardSettingsDialogComponent, MeshBoardSettingsResult, MeshBoardStateService, MeshBoardViewComponent, PieChartConfigDialogComponent, PieChartWidgetComponent, QuerySelectorComponent, RuntimeEntityDialogDataSource$2 as RuntimeEntityDialogDataSource, RuntimeEntitySelectDataSource$2 as RuntimeEntitySelectDataSource, RuntimeEntitySelectorComponent, ServiceHealthConfigDialogComponent, ServiceHealthWidgetComponent, StatsGridConfigDialogComponent, StatsGridWidgetComponent, StatusIndicatorConfigDialogComponent, StatusIndicatorWidgetComponent, TableConfigDialogComponent, TableWidgetComponent, TableWidgetDataSourceDirective, WidgetFactoryService, WidgetGroupComponent, WidgetGroupConfigDialogComponent, WidgetNotConfiguredComponent, WidgetRegistryService, provideDefaultWidgets, provideMeshBoard, provideProcessWidget, provideWidgetRegistrations, registerDefaultWidgets, registerProcessWidget };
26062
+ export { AddWidgetDialogComponent, AssociationsConfigDialogComponent, BarChartConfigDialogComponent, BarChartWidgetComponent, DashboardDataService, DashboardGridService, EditModeStateService, EditWidgetDialogComponent, EntityAssociationsWidgetComponent, EntityCardConfigDialogComponent, EntityCardWidgetComponent, EntityDetailDialogComponent, 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, RuntimeEntityDialogDataSource$2 as RuntimeEntityDialogDataSource, RuntimeEntitySelectDataSource$2 as RuntimeEntitySelectDataSource, RuntimeEntitySelectorComponent, ServiceHealthConfigDialogComponent, ServiceHealthWidgetComponent, StatsGridConfigDialogComponent, StatsGridWidgetComponent, StatusIndicatorConfigDialogComponent, StatusIndicatorWidgetComponent, TableConfigDialogComponent, TableWidgetComponent, TableWidgetDataSourceDirective, WidgetFactoryService, WidgetGroupComponent, WidgetGroupConfigDialogComponent, WidgetNotConfiguredComponent, WidgetRegistryService, provideDefaultWidgets, provideMeshBoard, provideProcessWidget, provideWidgetRegistrations, registerDefaultWidgets, registerProcessWidget };
23596
26063
  //# sourceMappingURL=meshmakers-octo-meshboard.mjs.map