From 1518aa260b9b39c937212c0a47bc895ee0a86312 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 17 Dec 2025 12:27:10 +0200 Subject: [PATCH] UI: Add calculated fields page actions --- ui-ngx/src/app/core/services/menu.models.ts | 2 +- .../calculated-fields-table-config.ts | 57 +++++++++++++------ ...lated-field-arguments-table.component.html | 3 +- ...culated-field-arguments-table.component.ts | 4 ++ .../calculated-field-dialog.component.html | 12 +++- .../calculated-field-dialog.component.ts | 52 +++++++++-------- ...eofencing-zone-groups-table.component.html | 6 +- ...-geofencing-zone-groups-table.component.ts | 5 ++ ...culated-field-metrics-table.component.html | 6 +- ...alculated-field-metrics-table.component.ts | 5 ++ .../calculated-field-output.component.html | 4 +- .../calculated-field-output.component.ts | 3 + .../propagation-configuration.component.html | 4 +- .../propagation-configuration.component.ts | 3 + .../simple-configuration.component.html | 4 +- .../simple-configuration.component.ts | 5 +- ui-ngx/src/form.scss | 10 ++++ 17 files changed, 129 insertions(+), 56 deletions(-) diff --git a/ui-ngx/src/app/core/services/menu.models.ts b/ui-ngx/src/app/core/services/menu.models.ts index a97acf344e..e573cade2c 100644 --- a/ui-ngx/src/app/core/services/menu.models.ts +++ b/ui-ngx/src/app/core/services/menu.models.ts @@ -634,7 +634,7 @@ export const menuSectionMap = new Map([ name: 'entity.type-calculated-fields', type: 'link', path: '/calculatedFields', - icon: 'calculate', + icon: 'mdi:plus-minus-variant', } ], [ diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts index 7d691c6fc8..2ac2f05bf4 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts @@ -57,7 +57,7 @@ import { CalculatedFieldTestScriptDialogData } from './components/public-api'; import { ImportExportService } from '@shared/import-export/import-export.service'; -import { getEntityDetailsPageURL, isObject } from '@core/utils'; +import { deepClone, getEntityDetailsPageURL, isObject } from '@core/utils'; import { EntityDebugSettingsService } from '@home/components/entity/debug/entity-debug-settings.service'; import { DatePipe } from '@angular/common'; import { UtilsService } from "@core/services/utils.service"; @@ -73,7 +73,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig this.openDebugEventsDialog.call(this, calculatedField), + action: (calculatedField: CalculatedField) => this.openDebugEventsDialog.call(this, null, calculatedField), }; calculatedFieldFilterConfig: CalculatedFieldsQuery; @@ -96,6 +96,11 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { + this.editCalculatedField($event, entity); + return true; + }; } this.tableTitle = this.pageMode ? '' : this.translate.instant('entity.type-calculated-fields'); this.detailsPanelEnabled = false; @@ -152,17 +157,23 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig true, + onAction: ($event, entity) => this.copyCalculatedField($event, entity), + }, { name: this.translate.instant('action.export'), icon: 'file_download', isEnabled: () => true, - onAction: (event$, entity) => this.exportCalculatedField(event$, entity), + onAction: ($event, entity) => this.exportCalculatedField($event, entity), }, { name: this.translate.instant('entity-view.events'), icon: 'mdi:clipboard-text-clock', isEnabled: () => true, - onAction: (_, entity) => this.openDebugEventsDialog(entity), + onAction: ($event, entity) => this.openDebugEventsDialog($event, entity), }, { name: '', @@ -176,7 +187,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig true, - onAction: (_, entity) => this.editCalculatedField(entity), + onAction: ($event, entity) => this.editCalculatedField($event, entity), } ); } @@ -198,14 +209,12 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig this.openDebugEventsDialog(calculatedField) + action: () => this.openDebugEventsDialog($event, calculatedField) }; - if ($event) { - $event.stopPropagation(); - } const { viewContainerRef, renderer } = this.entityDebugSettingsService; if (!viewContainerRef || !renderer) { @@ -223,7 +232,8 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { if (res) { @@ -233,13 +243,14 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { + const entityId = this.entityId || value?.entityId; return this.dialog.open(CalculatedFieldDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { value, buttonTitle, - entityId: this.entityId, + entityId, tenantId: this.tenantId, entityName: this.entityName, ownerId: this.ownerId, @@ -253,7 +264,8 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { return (calculatedField.type === CalculatedFieldType.SCRIPT || (calculatedField.type === CalculatedFieldType.PROPAGATION && @@ -285,12 +297,25 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { + if (res) { + this.updateData(); + } + }); + } + private importCalculatedField(): void { this.importExportService.openCalculatedFieldImportDialog() .pipe( @@ -376,7 +401,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { if (openCalculatedFieldEdit) { - this.editCalculatedField({ + this.editCalculatedField(null, { entityId: this.entityId, ...calculatedField, configuration: {...calculatedField.configuration, expression} as any }, true) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html index c6f2a5d479..99b4225db2 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.html @@ -111,6 +111,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts index 967e931ded..4d2df6e416 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts @@ -42,7 +42,7 @@ import { AdditionalDebugActionConfig } from '@home/components/entity/debug/entit import { deepTrim, isDefined } from '@core/utils'; import { EntityTypeSelectComponent } from '@shared/components/entity/entity-type-select.component'; import { EntityAutocompleteComponent } from '@shared/components/entity/entity-autocomplete.component'; -import { EntityService } from '@core/http/entity.service'; +import { BaseData } from '@shared/models/base-data'; export interface CalculatedFieldDialogData { value?: CalculatedField; @@ -82,6 +82,9 @@ export class CalculatedFieldDialogComponent extends DialogComponent, private calculatedFieldsService: CalculatedFieldsService, - private entityService: EntityService, private destroyRef: DestroyRef, private fb: FormBuilder) { super(store, router, dialogRef); - this.observeIsLoading(); this.observeType(); this.applyDialogData(); - if (!this.entityName) { + if (this.data.isDirty) { + this.fieldFormGroup.markAsDirty(); + } + + if (!this.data.entityId) { this.fieldFormGroup.get('entityId.id').valueChanges.pipe( takeUntilDestroyed(this.destroyRef) ).subscribe((entityId) => { - if (entityId && (this.fieldFormGroup.get('entityId.entityType').value === EntityType.DEVICE_PROFILE || - this.fieldFormGroup.get('entityId.entityType').value === EntityType.ASSET_PROFILE)) { - this.entityService.getEntity(this.fieldFormGroup.get('entityId.entityType').value as EntityType, entityId, {ignoreLoading: true, ignoreErrors: true}).subscribe( - value => { - this.entityName = value.name; - } - ) + this.disabledConfiguration = !entityId; + if (this.disabledConfiguration) { + this.fieldFormGroup.get('configuration').disable({emitEvent: false}); + } else { + this.fieldFormGroup.get('configuration').enable({emitEvent: false}); } }); } @@ -130,9 +133,13 @@ export class CalculatedFieldDialogComponent extends DialogComponent this.dialogRef.close(calculatedField)); + .subscribe({ + next: calculatedField => this.dialogRef.close(calculatedField), + error: () => this.isLoading = false + }); } else { this.fieldFormGroup.get('name').markAsTouched(); this.entityTypeSelect?.markAsTouched(); @@ -155,6 +162,10 @@ export class CalculatedFieldDialogComponent extends DialogComponent): void { + this.entityName = entity.name; + } + get entityId(): EntityId { return this.data.entityId || this.fieldFormGroup.get('entityId').value; } @@ -168,19 +179,10 @@ export class CalculatedFieldDialogComponent extends DialogComponent this.fieldFormGroup.get('type').updateValueAndValidity({onlySelf: true})); - } - - private observeIsLoading(): void { - this.isLoading$.pipe(takeUntilDestroyed()).subscribe(loading => { - if (loading) { - this.fieldFormGroup.disable({emitEvent: false}); - } else { - this.fieldFormGroup.enable({emitEvent: false}); - if (this.data.isDirty) { - this.fieldFormGroup.markAsDirty(); - } - } - }); + if (!this.data.entityId) { + this.fieldFormGroup.get('configuration').disable({emitEvent: false}); + this.disabledConfiguration = true; + } } private observeType(): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html index 20db5c732d..fa0ba32f22 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.html @@ -101,6 +101,7 @@ #button (click)="manageZone($event, button, geofenceZone)" [matTooltip]="'action.edit' | translate" + [disabled]="disable" matTooltipPosition="above"> delete @@ -128,7 +130,7 @@ }
+ class="tb-prompt flex flex-1 items-end justify-center text-base" [class.required]="!disable"> {{ 'calculated-fields.no-zone-configured' | translate }}
@@ -137,7 +139,7 @@ color="primary" #button (click)="manageZone($event, button)" - [disabled]="maxArgumentsPerCF > 0 && zoneGroupsFormArray.length >= maxArgumentsPerCF"> + [disabled]="maxArgumentsPerCF > 0 && zoneGroupsFormArray.length >= maxArgumentsPerCF || disable"> {{ 'calculated-fields.add-zone-group' | translate }} @if (maxArgumentsPerCF && zoneGroupsFormArray.length >= maxArgumentsPerCF) { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts index b3e5edb80a..228d08fd90 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts @@ -90,6 +90,7 @@ export class CalculatedFieldGeofencingZoneGroupsTableComponent implements Contro entityNameMap = new Map(); sortOrder = { direction: 'asc', property: '' }; dataSource = new CalculatedFieldZoneDatasource(); + disable = false; readonly GeofencingReportStrategyTranslations = GeofencingReportStrategyTranslations; readonly entityTypeTranslations = entityTypeTranslations; @@ -135,6 +136,10 @@ export class CalculatedFieldGeofencingZoneGroupsTableComponent implements Contro return this.errorText ? { zonesFormArray: false } : null; } + setDisabledState(isDisabled: boolean): void { + this.disable = isDisabled; + } + onDelete($event: Event, zone: CalculatedFieldGeofencingValue): void { $event.stopPropagation(); const index = this.zoneGroupsFormArray.controls.findIndex(control => isEqual(control.value, zone)); diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.html index a35dc97d8a..4693d3afb4 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.html @@ -97,6 +97,7 @@ mat-icon-button #button (click)="manageMetrics($event, button, metric)" + [disabled]="disable" [matTooltip]="'action.edit' | translate" matTooltipPosition="above"> edit @@ -104,6 +105,7 @@
+ class="tb-prompt flex flex-1 items-end justify-center text-base" [class.required]="!disable"> {{ 'calculated-fields.metrics.no-metrics-configured' | translate }}
@@ -125,7 +127,7 @@ color="primary" #button (click)="manageMetrics($event, button)" - [disabled]="maxArgumentsPerCF > 0 && metricsFormArray.length >= maxArgumentsPerCF"> + [disabled]="maxArgumentsPerCF > 0 && metricsFormArray.length >= maxArgumentsPerCF || disable"> {{ 'calculated-fields.metrics.add-metric' | translate }} @if (maxArgumentsPerCF && metricsFormArray.length >= maxArgumentsPerCF) { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts index 78021529d6..21b21d07ac 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts @@ -89,6 +89,7 @@ export class CalculatedFieldMetricsTableComponent implements OnInit, ControlValu metricsFormArray = this.fb.array([]); sortOrder = { direction: 'asc' as SortDirection, property: '' }; dataSource = new CalculatedFieldMetricsDatasource(); + disable = false; displayColumns = ['name', 'function', 'filter', 'valueSource', 'actions']; @@ -141,6 +142,10 @@ export class CalculatedFieldMetricsTableComponent implements OnInit, ControlValu return this.errorText ? { metricsFormArray: false } : null; } + setDisabledState(isDisabled: boolean): void { + this.disable = isDisabled; + } + onDelete($event: Event, metric: CalculatedFieldAggMetricValue): void { $event.stopPropagation(); const index = this.metricsFormArray.controls.findIndex(control => isEqual(control.value, metric)); diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.html index 3a47c00b1c..c2c989d0c8 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.html @@ -78,7 +78,7 @@
-
+
{{ 'calculated-fields.output-strategy.strategy' | translate }}
@@ -90,7 +90,7 @@ @if (outputForm.get('strategy.type').value === OutputStrategyType.IMMEDIATE) { -
+
calculated-fields.output-strategy.processing-parameters
@if (outputForm.get('type').value === OutputType.Timeseries) { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts index 239a4921e4..149b5aa3b9 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts @@ -78,6 +78,8 @@ export class CalculatedFieldOutputComponent implements ControlValueAccessor, Val @Input({required: true}) entityId: EntityId; + disabled = false; + readonly outputTypes = Object.values(OutputType) as OutputType[]; readonly OutputType = OutputType; readonly AttributeScope = AttributeScope; @@ -171,6 +173,7 @@ export class CalculatedFieldOutputComponent implements ControlValueAccessor, Val registerOnTouched(_: any): void { } setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; if (isDisabled) { this.outputForm.disable({emitEvent: false}); } else { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.html index bd92d25f92..6dc5702735 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.html @@ -80,7 +80,7 @@ matTooltip="{{ 'calculated-fields.test-expression-function' | translate }}" matTooltipPosition="above" class="tb-mat-32" - [disabled]="propagateConfiguration.get('arguments').invalid" + [disabled]="propagateConfiguration.get('arguments').invalid || disabled" (click)="onTestScript()"> bug_report @@ -89,7 +89,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts index bce244c7ba..1c493148b3 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts @@ -88,6 +88,8 @@ export class PropagationConfigurationComponent implements ControlValueAccessor, output: this.fb.control(defaultCalculatedFieldOutput), }); + disabled = false; + readonly ScriptLanguage = ScriptLanguage; readonly CalculatedFieldType = CalculatedFieldType; readonly OutputType = OutputType; @@ -142,6 +144,7 @@ export class PropagationConfigurationComponent implements ControlValueAccessor, registerOnTouched(_: any): void { } setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; if (isDisabled) { this.propagateConfiguration.disable({emitEvent: false}); } else { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.html index 178f8b47d6..e6a37043d5 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.html @@ -72,7 +72,7 @@ matTooltip="{{ 'calculated-fields.test-script-function' | translate }}" matTooltipPosition="above" class="tb-mat-32" - [disabled]="simpleConfiguration.get('arguments').invalid" + [disabled]="simpleConfiguration.get('arguments').invalid || disabled" (click)="onTestScript()"> bug_report @@ -81,7 +81,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts index 8dc6f01726..e6ff1d1bad 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts @@ -104,6 +104,8 @@ export class SimpleConfigurationComponent implements ControlValueAccessor, Valid map(argumentsObj => getCalculatedFieldArgumentsHighlights(argumentsObj)) ); + disabled = false; + private propagateChange: (config: SimpeConfiguration) => void = () => { }; constructor(private fb: FormBuilder) { @@ -127,7 +129,7 @@ export class SimpleConfigurationComponent implements ControlValueAccessor, Valid for (const propName of Object.keys(changes)) { const change = changes[propName]; if (change.currentValue !== change.previousValue) { - if (propName === 'isScript') { + if (propName === 'isScript' && !this.disabled) { this.updatedFormWithScript(); if (!change.firstChange) { this.simpleConfiguration.updateValueAndValidity(); @@ -163,6 +165,7 @@ export class SimpleConfigurationComponent implements ControlValueAccessor, Valid } setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; if (isDisabled) { this.simpleConfiguration.disable({emitEvent: false}); } else { diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index b8e269c14e..f300b5e42b 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -107,6 +107,9 @@ } } } + &.disabled { + color: var(--mdc-outlined-text-field-disabled-input-text-color); + } .mat-expansion-panel { &.tb-settings { box-shadow: none; @@ -160,6 +163,13 @@ } } + .tb-form-panel-disabled { + color: var(--mdc-outlined-text-field-disabled-input-text-color); + .tb-form-panel { + color: var(--mdc-outlined-text-field-disabled-input-text-color); + } + } + .tb-form-panel-title { font-weight: 500; font-size: 16px;