@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,
|
|
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
|
|
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)}.
|
|
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
|
-
}],
|
|
14084
|
+
}], initialDateField: [{
|
|
12069
14085
|
type: Input
|
|
12070
|
-
}],
|
|
14086
|
+
}], initialDateEndField: [{
|
|
12071
14087
|
type: Input
|
|
12072
|
-
}],
|
|
14088
|
+
}], initialValueField: [{
|
|
12073
14089
|
type: Input
|
|
12074
|
-
}],
|
|
14090
|
+
}], initialAggregation: [{
|
|
12075
14091
|
type: Input
|
|
12076
|
-
}],
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
23061
|
-
|
|
23062
|
-
|
|
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 =
|
|
23075
|
-
const
|
|
23076
|
-
this.router.navigateByUrl(
|
|
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(`${
|
|
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
|
|
23166
|
-
*
|
|
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
|
|
25481
|
+
if (!timeFilter?.enabled) {
|
|
23171
25482
|
return;
|
|
23172
25483
|
}
|
|
23173
|
-
//
|
|
23174
|
-
const
|
|
23175
|
-
|
|
23176
|
-
|
|
23177
|
-
|
|
23178
|
-
|
|
23179
|
-
|
|
23180
|
-
|
|
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
|
|
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
|
|
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
|