UI: fixed and improve geofencing cf
This commit is contained in:
parent
fcba7004f9
commit
6b5e339921
@ -160,12 +160,16 @@
|
||||
[tenantId]="data.tenantId"
|
||||
[entityName]="data.entityName"/>
|
||||
<div class="tb-form-row space-between flex-1 columns-xs" [class.!hidden]="!isRelatedEntity">
|
||||
<div tb-hint-tooltip-icon="{{'calculated-fields.hint.zone-group-refresh-interval' | translate}}">{{ 'calculated-fields.zone-group-refresh-interval' | translate }}</div>
|
||||
<mat-slide-toggle class="mat-slide" formControlName="scheduledUpdateEnabled">
|
||||
<div tb-hint-tooltip-icon="{{'calculated-fields.hint.zone-group-refresh-interval' | translate}}">
|
||||
{{ 'calculated-fields.zone-group-refresh-interval' | translate }}
|
||||
</div>
|
||||
</mat-slide-toggle>
|
||||
<div class="flex flex-row items-center justify-start gap-2">
|
||||
<tb-time-unit-input required
|
||||
inlineField
|
||||
requiredText="{{ 'calculated-fields.hint.zone-group-refresh-interval-required' | translate }}"
|
||||
minErrorText="{{ 'calculated-fields.hint.zone-group-refresh-interval-min' | translate }}"
|
||||
minErrorText="{{ 'calculated-fields.hint.zone-group-refresh-interval-min' | translate: {min: minAllowedScheduledUpdateIntervalInSecForCF} }}"
|
||||
[minTime]="minAllowedScheduledUpdateIntervalInSecForCF"
|
||||
formControlName="scheduledUpdateInterval">
|
||||
</tb-time-unit-input>
|
||||
|
||||
@ -81,6 +81,7 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
|
||||
}),
|
||||
arguments: this.fb.control({}),
|
||||
zoneGroups: this.fb.control({}),
|
||||
scheduledUpdateEnabled: [true],
|
||||
scheduledUpdateInterval: [this.minAllowedScheduledUpdateIntervalInSecForCF],
|
||||
expressionSIMPLE: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]],
|
||||
expressionSCRIPT: [calculatedFieldDefaultScript],
|
||||
@ -144,6 +145,7 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
|
||||
this.applyDialogData();
|
||||
this.observeTypeChanges();
|
||||
this.observeZoneChanges();
|
||||
this.observeScheduledUpdateEnabled();
|
||||
this.currentEntityFilter = getCalculatedFieldCurrentEntityFilter(this.data.entityName, this.data.entityId);
|
||||
}
|
||||
|
||||
@ -248,6 +250,23 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
|
||||
this.checkRelatedEntity(this.configFormGroup.get('zoneGroups').value);
|
||||
}
|
||||
|
||||
private observeScheduledUpdateEnabled(): void {
|
||||
this.configFormGroup.get('scheduledUpdateEnabled').valueChanges
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe((value: boolean) =>
|
||||
this.checkScheduledUpdateEnabled(value)
|
||||
);
|
||||
this.checkScheduledUpdateEnabled(this.configFormGroup.get('scheduledUpdateEnabled').value);
|
||||
}
|
||||
|
||||
private checkScheduledUpdateEnabled(value: boolean) {
|
||||
if (value) {
|
||||
this.configFormGroup.get('scheduledUpdateInterval').enable({emitEvent: false});
|
||||
} else {
|
||||
this.configFormGroup.get('scheduledUpdateInterval').disable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
|
||||
private checkRelatedEntity(zoneGroups: CalculatedFieldGeofencing) {
|
||||
this.isRelatedEntity = Object.values(zoneGroups).some(zone => zone.refDynamicSourceConfiguration?.type === ArgumentEntityType.RelationQuery);
|
||||
}
|
||||
|
||||
@ -168,7 +168,7 @@ export class CalculatedFieldGeofencingZoneGroupsTableComponent implements Contro
|
||||
renderer: this.renderer,
|
||||
componentType: CalculatedFieldGeofencingZoneGroupsPanelComponent,
|
||||
hostView: this.viewContainerRef,
|
||||
preferredPlacement: isExists ? ['left', 'leftTop', 'leftBottom'] : ['topRight', 'right', 'rightTop'],
|
||||
preferredPlacement: 'right',
|
||||
context: ctx,
|
||||
isModal: true
|
||||
});
|
||||
|
||||
@ -80,12 +80,13 @@
|
||||
@if (ArgumentEntityTypeParamsMap.has(entityType)) {
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width tb-required">{{ ArgumentEntityTypeParamsMap.get(entityType).title | translate }}</div>
|
||||
<tb-entity-autocomplete class="flex flex-1"
|
||||
<tb-entity-autocomplete class="flex-1"
|
||||
#entityAutocomplete
|
||||
formControlName="id"
|
||||
inlineField
|
||||
appearance="outline"
|
||||
[placeholder]="'action.set' | translate"
|
||||
[required]="true"
|
||||
required
|
||||
[entityType]="ArgumentEntityTypeParamsMap.get(entityType).entityType"
|
||||
(entityChanged)="entityNameSubject.next($event?.name)"/>
|
||||
</div>
|
||||
@ -94,76 +95,115 @@
|
||||
<ng-container [formGroup]="refDynamicSourceFormGroup">
|
||||
<div class="tb-form-panel stroked" *ngIf="entityType === ArgumentEntityType.RelationQuery">
|
||||
<mat-expansion-panel class="tb-settings" expanded>
|
||||
<mat-expansion-panel-header>{{ 'calculated-fields.relation-query' | translate }}*</mat-expansion-panel-header>
|
||||
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width">{{ 'calculated-fields.direction' | translate }}</div>
|
||||
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
|
||||
<mat-select formControlName="direction">
|
||||
@for (direction of GeofencingDirectionList; track direction) {
|
||||
<mat-option [value]="direction">{{ GeofencingDirectionTranslations.get(direction) | translate }}</mat-option>
|
||||
<mat-expansion-panel-header>{{ 'calculated-fields.entity-zone-relationship' | translate }}</mat-expansion-panel-header>
|
||||
<div class="tb-form-table">
|
||||
<div class="tb-form-table-header no-padding-right">
|
||||
<div class="tb-form-table-header-cell" translate>calculated-fields.level</div>
|
||||
<div class="tb-form-table-header-cell flex-1" translate>calculated-fields.direction-level</div>
|
||||
<div class="tb-form-table-header-cell flex-1 tb-required" translate>calculated-fields.relation-type</div>
|
||||
<div class="tb-form-table-header-cell tb-actions-header"></div>
|
||||
</div>
|
||||
@if (levelsFormArray()?.controls?.length) {
|
||||
<div class="tb-form-table-body tb-drop-list"
|
||||
cdkDropList cdkDropListOrientation="vertical"
|
||||
[cdkDropListDisabled]="!dragEnabled"
|
||||
(cdkDropListDropped)="keyDrop($event)">
|
||||
@for (keyControl of levelsFormArray().controls; track trackByKey;) {
|
||||
<div cdkDrag [cdkDragDisabled]="!dragEnabled" class="tb-draggable-form-table-row">
|
||||
<div class="tb-form-row no-border flex-1" [formGroup]="keyControl">
|
||||
<div class="level-text">{{ $index+1 }}</div>
|
||||
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
|
||||
<mat-select formControlName="direction">
|
||||
@for (direction of GeofencingDirectionList; track direction) {
|
||||
<mat-option [value]="direction">{{ GeofencingDirectionLevelTranslations.get(direction) | translate }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<tb-string-autocomplete [fetchOptionsFn]="fetchOptions.bind(this)"
|
||||
additionalClass="tb-suffix-show-on-hover"
|
||||
class="flex-1"
|
||||
appearance="outline"
|
||||
panelWidth=""
|
||||
required
|
||||
[errorText]="'calculated-fields.hint.relation-type-required' | translate"
|
||||
formControlName="relationType">
|
||||
</tb-string-autocomplete>
|
||||
</div>
|
||||
<div class="tb-form-table-row-cell-buttons">
|
||||
<button type="button"
|
||||
mat-icon-button
|
||||
(click)="removeKey($index)"
|
||||
matTooltip="{{ 'calculated-fields.delete-level' | translate }}"
|
||||
matTooltipPosition="above">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button
|
||||
type="button"
|
||||
cdkDragHandle
|
||||
class="lt-lg:!hidden"
|
||||
[class.tb-hidden]="!dragEnabled"
|
||||
matTooltip="{{ 'action.drag' | translate }}"
|
||||
matTooltipPosition="above">
|
||||
<mat-icon>drag_indicator</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
} @else {
|
||||
<span class="tb-prompt flex items-center justify-center">{{ 'calculated-fields.no-level' | translate }}</span>
|
||||
}
|
||||
@if (levelsFormArray().errors) {
|
||||
<tb-error noMargin error="{{ 'calculated-fields.levels-required' | translate }}" style="padding-left: 12px;"></tb-error>
|
||||
}
|
||||
</div>
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width tb-required">{{ 'calculated-fields.relation-type' | translate }}</div>
|
||||
<tb-string-autocomplete [fetchOptionsFn]="fetchOptions.bind(this)"
|
||||
additionalClass="tb-suffix-show-on-hover"
|
||||
class="flex-1"
|
||||
appearance="outline"
|
||||
panelWidth=""
|
||||
required
|
||||
[errorText]="'calculated-fields.hint.relation-type-required' | translate"
|
||||
formControlName="relationType">
|
||||
</tb-string-autocomplete>
|
||||
</div>
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width tb-required">{{ 'calculated-fields.relation-level' | translate }}</div>
|
||||
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
|
||||
<input matInput type="number" step="1" min="0" formControlName="maxLevel" placeholder="{{ 'action.set' | translate }}"/>
|
||||
@if (refDynamicSourceFormGroup.get('maxLevel').touched && refDynamicSourceFormGroup.get('maxLevel').hasError('required')) {
|
||||
<mat-icon matSuffix
|
||||
matTooltipPosition="above"
|
||||
matTooltipClass="tb-error-tooltip"
|
||||
[matTooltip]="'calculated-fields.hint.relation-level-required' | translate"
|
||||
class="tb-error">
|
||||
warning
|
||||
</mat-icon>
|
||||
} @else if (refDynamicSourceFormGroup.get('maxLevel').touched && refDynamicSourceFormGroup.get('maxLevel').hasError('min')) {
|
||||
<mat-icon matSuffix
|
||||
matTooltipPosition="above"
|
||||
matTooltipClass="tb-error-tooltip"
|
||||
[matTooltip]="'calculated-fields.hint.relation-level-min' | translate"
|
||||
class="tb-error">
|
||||
warning
|
||||
</mat-icon>
|
||||
} @else if (refDynamicSourceFormGroup.get('maxLevel').touched && refDynamicSourceFormGroup.get('maxLevel').hasError('max')) {
|
||||
<mat-icon matSuffix
|
||||
matTooltipPosition="above"
|
||||
matTooltipClass="tb-error-tooltip"
|
||||
[matTooltip]="'calculated-fields.hint.relation-level-max' | translate: {max: maxRelationLevelPerCfArgument}"
|
||||
class="tb-error">
|
||||
warning
|
||||
</mat-icon>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="tb-form-row" [class.!hidden]="!(this.refDynamicSourceFormGroup.get('maxLevel').value > 1)">
|
||||
<mat-slide-toggle class="mat-slide margin" formControlName="fetchLastLevelOnly">
|
||||
{{ 'calculated-fields.fetch-last-available-level' | translate }}
|
||||
</mat-slide-toggle>
|
||||
<div>
|
||||
@if (maxRelationLevelPerCfArgument && levelsFormArray().length >= maxRelationLevelPerCfArgument) {
|
||||
<div class="tb-form-hint tb-primary-fill max-args-warning flex items-center gap-2">
|
||||
<mat-icon>warning</mat-icon>
|
||||
<span>{{ 'calculated-fields.max-allowed-levels-error' | translate }}</span>
|
||||
</div>
|
||||
} @else {
|
||||
<button type="button" mat-stroked-button color="primary" (click)="addKey()">
|
||||
{{ 'calculated-fields.add-level' | translate }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container>
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width tb-required" tb-hint-tooltip-icon="{{'calculated-fields.hint.perimeter-attribute-key' | translate}}">
|
||||
{{ 'calculated-fields.perimeter-attribute-key' | translate }}
|
||||
@if (entityFilter.singleEntity.id) {
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width tb-required" tb-hint-tooltip-icon="{{'calculated-fields.hint.perimeter-attribute-key' | translate}}">
|
||||
{{ 'calculated-fields.perimeter-attribute-key' | translate }}
|
||||
</div>
|
||||
@if (entityType === ArgumentEntityType.RelationQuery) {
|
||||
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
|
||||
<input matInput autocomplete="new-name" name="value" formControlName="perimeterKeyName" maxlength="255" placeholder="{{ 'action.set' | translate }}"/>
|
||||
@if (geofencingFormGroup.get('perimeterKeyName').touched && geofencingFormGroup.get('perimeterKeyName').hasError('required')) {
|
||||
<mat-icon matSuffix
|
||||
matTooltipPosition="above"
|
||||
matTooltipClass="tb-error-tooltip"
|
||||
[matTooltip]="'calculated-fields.perimeter-attribute-key-required' | translate"
|
||||
class="tb-error">
|
||||
warning
|
||||
</mat-icon>
|
||||
} @else if (geofencingFormGroup.get('perimeterKeyName').touched && geofencingFormGroup.get('perimeterKeyName').hasError('pattern')) {
|
||||
<mat-icon matSuffix
|
||||
matTooltipPosition="above"
|
||||
matTooltipClass="tb-error-tooltip"
|
||||
[matTooltip]="'calculated-fields.perimeter-attribute-key-pattern' | translate"
|
||||
class="tb-error">
|
||||
warning
|
||||
</mat-icon>
|
||||
}
|
||||
</mat-form-field>
|
||||
} @else {
|
||||
<tb-entity-key-autocomplete class="flex-1" formControlName="perimeterKeyName" [dataKeyType]="DataKeyType.attribute" [entityFilter]="entityFilter" [keyScopeType]="AttributeScope.SERVER_SCOPE"/>
|
||||
}
|
||||
</div>
|
||||
<tb-entity-key-autocomplete class="flex-1" formControlName="perimeterKeyName" [dataKeyType]="DataKeyType.attribute" [entityFilter]="entityFilter"/>
|
||||
</div>
|
||||
}
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'calculated-fields.hint.report-strategy' | translate}}">{{ 'calculated-fields.report-strategy' | translate }}</div>
|
||||
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
|
||||
|
||||
@ -29,6 +29,26 @@ $panel-width: 520px;
|
||||
}
|
||||
}
|
||||
|
||||
.level-text {
|
||||
width: 25px;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
|
||||
.tb-form-table {
|
||||
.tb-form-row {
|
||||
gap: 12px;
|
||||
}
|
||||
.tb-form-table-body {
|
||||
gap: unset;
|
||||
}
|
||||
}
|
||||
.tb-form-table-header-cell {
|
||||
&.tb-actions-header {
|
||||
width: 80px;
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.limit-field-row {
|
||||
@media screen and (max-width: $panel-width) {
|
||||
display: flex;
|
||||
@ -48,4 +68,9 @@ $panel-width: 520px;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
tb-entity-autocomplete {
|
||||
.mat-mdc-form-field-has-icon-suffix .mat-mdc-text-field-wrapper {
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,15 @@
|
||||
|
||||
import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, output, ViewChild } from '@angular/core';
|
||||
import { TbPopoverComponent } from '@shared/components/popover.component';
|
||||
import { FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
|
||||
import {
|
||||
AbstractControl,
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
UntypedFormArray,
|
||||
ValidatorFn,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { charsWithNumRegex, oneSpaceInsideRegex } from '@shared/models/regex.constants';
|
||||
import {
|
||||
ArgumentEntityType,
|
||||
@ -25,14 +33,15 @@ import {
|
||||
CalculatedFieldGeofencing,
|
||||
CalculatedFieldGeofencingValue,
|
||||
CalculatedFieldType,
|
||||
GeofencingDirectionLevelTranslations,
|
||||
GeofencingDirectionTranslations,
|
||||
GeofencingReportStrategy,
|
||||
GeofencingReportStrategyTranslations,
|
||||
getCalculatedFieldCurrentEntityFilter
|
||||
} from '@shared/models/calculated-field.models';
|
||||
import { debounceTime, delay, distinctUntilChanged, filter, map } from 'rxjs/operators';
|
||||
import { debounceTime, delay, distinctUntilChanged, map } from 'rxjs/operators';
|
||||
import { EntityType } from '@shared/models/entity-type.models';
|
||||
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
|
||||
import { AttributeScope, DataKeyType } from '@shared/models/telemetry/telemetry.models';
|
||||
import { EntityId } from '@shared/models/id/entity-id';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { EntityFilter } from '@shared/models/query/query.models';
|
||||
@ -44,6 +53,7 @@ import { Store } from '@ngrx/store';
|
||||
import { EntityAutocompleteComponent } from '@shared/components/entity/entity-autocomplete.component';
|
||||
import { NULL_UUID } from '@shared/models/id/has-uuid';
|
||||
import { EntitySearchDirection } from '@shared/models/relation.models';
|
||||
import { CdkDragDrop } from "@angular/cdk/drag-drop";
|
||||
|
||||
@Component({
|
||||
selector: 'tb-calculated-field-geofencing-zone-groups-panel',
|
||||
@ -73,12 +83,9 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
|
||||
id: ['']
|
||||
}),
|
||||
refDynamicSourceConfiguration: this.fb.group({
|
||||
direction: [EntitySearchDirection.TO],
|
||||
relationType: ['', [Validators.required]],
|
||||
maxLevel: [1, [Validators.required, Validators.min(1), Validators.max(this.maxRelationLevelPerCfArgument)]],
|
||||
fetchLastLevelOnly: [false],
|
||||
levels: this.fb.array([], [this.levelsRequired()])
|
||||
}),
|
||||
perimeterKeyName: ['', [Validators.pattern(oneSpaceInsideRegex)]],
|
||||
perimeterKeyName: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex)]],
|
||||
reportStrategy: [GeofencingReportStrategy.REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS],
|
||||
createRelationsWithMatchedZones: [false],
|
||||
direction: [EntitySearchDirection.TO],
|
||||
@ -97,6 +104,8 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
|
||||
readonly GeofencingReportStrategyTranslations = GeofencingReportStrategyTranslations;
|
||||
readonly GeofencingDirectionList = Object.values(EntitySearchDirection) as Array<EntitySearchDirection>;
|
||||
readonly GeofencingDirectionTranslations = GeofencingDirectionTranslations;
|
||||
readonly GeofencingDirectionLevelTranslations = GeofencingDirectionLevelTranslations;
|
||||
readonly AttributeScope = AttributeScope;
|
||||
|
||||
private currentEntityFilter: EntityFilter;
|
||||
|
||||
@ -107,7 +116,6 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
|
||||
private store: Store<AppState>
|
||||
) {
|
||||
|
||||
this.observeMaxLevelChanges();
|
||||
this.observeEntityFilterChanges();
|
||||
this.observeEntityTypeChanges();
|
||||
this.observeUpdatePosition();
|
||||
@ -131,7 +139,16 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
|
||||
if (this.zone.refDynamicSourceConfiguration?.type) {
|
||||
this.refEntityIdFormGroup.get('entityType').setValue(this.zone.refDynamicSourceConfiguration.type, {emitEvent: false});
|
||||
}
|
||||
this.validateFetchLastLevelOnly(this.zone?.refDynamicSourceConfiguration?.maxLevel);
|
||||
if (this.zone?.refDynamicSourceConfiguration?.levels?.length > 0) {
|
||||
this.zone.refDynamicSourceConfiguration.levels.forEach(level => {
|
||||
this.levelsFormArray().push(this.fb.group({
|
||||
direction: [level.direction],
|
||||
relationType: [level.relationType, [Validators.required]]
|
||||
}));
|
||||
})
|
||||
} else {
|
||||
this.addKey();
|
||||
}
|
||||
this.validateDirectionAndRelationType(this.zone?.createRelationsWithMatchedZones);
|
||||
this.validateRefDynamicSourceConfiguration(this.zone?.refEntityId?.entityType || this.zone?.refDynamicSourceConfiguration?.type);
|
||||
|
||||
@ -241,17 +258,19 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
|
||||
private observeEntityFilterChanges(): void {
|
||||
merge(
|
||||
this.refEntityIdFormGroup.get('entityType').valueChanges,
|
||||
this.refEntityIdFormGroup.get('id').valueChanges.pipe(filter(Boolean)),
|
||||
this.refEntityIdFormGroup.get('id').valueChanges,
|
||||
)
|
||||
.pipe(debounceTime(50), takeUntilDestroyed())
|
||||
.subscribe(() => this.updateEntityFilter(this.entityType));
|
||||
|
||||
this.refEntityIdFormGroup.get('id').valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed()).subscribe(() => this.geofencingFormGroup.get('perimeterKeyName').reset(''));
|
||||
}
|
||||
|
||||
private observeEntityTypeChanges(): void {
|
||||
this.refEntityIdFormGroup.get('entityType').valueChanges
|
||||
.pipe(distinctUntilChanged(), takeUntilDestroyed())
|
||||
.subscribe(type => {
|
||||
this.geofencingFormGroup.get('refEntityId').get('id').setValue('');
|
||||
this.geofencingFormGroup.get('refEntityId').get('id').setValue(null);
|
||||
const isEntityWithId = type !== ArgumentEntityType.Tenant && type !== ArgumentEntityType.Current && type !== ArgumentEntityType.RelationQuery;
|
||||
this.geofencingFormGroup.get('refEntityId')
|
||||
.get('id')[isEntityWithId ? 'enable' : 'disable']();
|
||||
@ -271,6 +290,12 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
|
||||
};
|
||||
}
|
||||
|
||||
private levelsRequired(): ValidatorFn {
|
||||
return (control: FormControl) => {
|
||||
return control.value.length ? null : { levelsRequired: true };
|
||||
};
|
||||
}
|
||||
|
||||
private forbiddenNameValidator(): ValidatorFn {
|
||||
return (control: FormControl) => {
|
||||
const trimmedValue = control.value.trim().toLowerCase();
|
||||
@ -282,10 +307,40 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
|
||||
private observeUpdatePosition(): void {
|
||||
merge(
|
||||
this.refEntityIdFormGroup.get('entityType').valueChanges,
|
||||
this.refEntityIdFormGroup.get('id').valueChanges,
|
||||
this.geofencingFormGroup.get('createRelationsWithMatchedZones').valueChanges
|
||||
)
|
||||
.pipe(delay(50), takeUntilDestroyed())
|
||||
.subscribe(() => this.popover.updatePosition());
|
||||
}
|
||||
|
||||
levelsFormArray(): UntypedFormArray {
|
||||
return this.refDynamicSourceFormGroup.get('levels') as UntypedFormArray;
|
||||
}
|
||||
|
||||
trackByKey(index: number, keyControl: AbstractControl): any {
|
||||
return keyControl;
|
||||
}
|
||||
|
||||
removeKey(index: number) {
|
||||
this.levelsFormArray().removeAt(index);
|
||||
}
|
||||
|
||||
addKey() {
|
||||
this.levelsFormArray().push(this.fb.group({
|
||||
direction: [EntitySearchDirection.TO],
|
||||
relationType: ['', [Validators.required]]
|
||||
}));
|
||||
}
|
||||
|
||||
keyDrop(event: CdkDragDrop<string[]>) {
|
||||
const keysArray = this.levelsFormArray();
|
||||
const key = keysArray.at(event.previousIndex);
|
||||
keysArray.removeAt(event.previousIndex);
|
||||
keysArray.insert(event.currentIndex, key);
|
||||
}
|
||||
|
||||
get dragEnabled(): boolean {
|
||||
return this.levelsFormArray().controls.length > 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ export enum ArgumentEntityType {
|
||||
Asset = 'ASSET',
|
||||
Customer = 'CUSTOMER',
|
||||
Tenant = 'TENANT',
|
||||
RelationQuery = 'RELATION_QUERY',
|
||||
RelationQuery = 'RELATION_PATH_QUERY',
|
||||
}
|
||||
|
||||
export const ArgumentEntityTypeTranslations = new Map<ArgumentEntityType, string>(
|
||||
@ -108,6 +108,13 @@ export const GeofencingDirectionTranslations = new Map<EntitySearchDirection, st
|
||||
]
|
||||
)
|
||||
|
||||
export const GeofencingDirectionLevelTranslations = new Map<EntitySearchDirection, string>(
|
||||
[
|
||||
[EntitySearchDirection.FROM, 'calculated-fields.direction-down'],
|
||||
[EntitySearchDirection.TO, 'calculated-fields.direction-up'],
|
||||
]
|
||||
)
|
||||
|
||||
export enum ArgumentType {
|
||||
Attribute = 'ATTRIBUTE',
|
||||
LatestTelemetry = 'TS_LATEST',
|
||||
@ -167,10 +174,7 @@ export interface CalculatedFieldGeofencing {
|
||||
|
||||
export interface RefDynamicSourceConfiguration {
|
||||
type?: ArgumentEntityType.RelationQuery;
|
||||
direction: EntitySearchDirection;
|
||||
relationType: string;
|
||||
maxLevel: number;
|
||||
fetchLastLevelOnly?: boolean;
|
||||
levels?: Array<{direction: EntitySearchDirection; relationType: string;}>;
|
||||
}
|
||||
|
||||
export interface CalculatedFieldGeofencingValue extends CalculatedFieldGeofencing {
|
||||
|
||||
@ -1122,17 +1122,28 @@
|
||||
"report-presence-status-only": "Presence status only",
|
||||
"report-transition-event-and-presence": "Presence status and transition events",
|
||||
"perimeter-attribute-key": "Perimeter attribute key",
|
||||
"relation-query": "Relations query",
|
||||
"direction": "Direction",
|
||||
"direction-from": "From source entity",
|
||||
"direction-to": "To source entity",
|
||||
"perimeter-attribute-key-required": "Perimeter attribute key is required.",
|
||||
"perimeter-attribute-key-pattern": "Perimeter attribute key is invalid.",
|
||||
"entity-zone-relationship": "Path from Entity to Zones *",
|
||||
"direction": "Relation direction",
|
||||
"direction-from": "From entity to zone",
|
||||
"direction-to": "From zone to entity",
|
||||
"relation-type": "Relation type",
|
||||
"create-relation-with-matched-zones": "Create relations with matched zones",
|
||||
"create-relation-with-matched-zones": "Create relations for source entity with matched zones",
|
||||
"relation-level": "Relation level",
|
||||
"fetch-last-available-level": "Fetch last available level only",
|
||||
"zone-group-refresh-interval": "Zone groups refresh interval",
|
||||
"copy-zone-group-name": "Copy zone group name",
|
||||
"open-details-page": "Open entity details page",
|
||||
"level": "Level",
|
||||
"direction-level": "Direction",
|
||||
"direction-up": "Up",
|
||||
"direction-down": "Down",
|
||||
"add-level": "Add level",
|
||||
"delete-level": "Delete level",
|
||||
"no-level": "No level configured",
|
||||
"levels-required": "At least one level must be configured.",
|
||||
"max-allowed-levels-error": "Relation level exceeds the maximum allowed.",
|
||||
"hint": {
|
||||
"arguments-simple-with-rolling": "Simple type calculated field should not contain keys with time series rolling type.",
|
||||
"arguments-empty": "Arguments should not be empty.",
|
||||
@ -1158,7 +1169,7 @@
|
||||
"entity-coordinates": "Specify the time series keys that provide entity GPS coordinates (latitude and longitude).",
|
||||
"geofencing-zone-groups": "Define one or more geofencing zones groups to check (e.g. 'allowedZones', 'restrictedZones'). Each group must have a unique name, which is used as a prefix for calculated field output telemetry keys.",
|
||||
"perimeter-attribute-key": "Set the attribute key that contains the geofencing zone perimeter definition. The perimeter is always taken from server-side attributes of the zone entity.",
|
||||
"report-strategy": "Presence status reports whether the entity is currently INSIDE or OUTSIDE the zone group.Transition events report when the entity ENTERED or LEFT the zone group.",
|
||||
"report-strategy": "Presence status reports whether the entity is currently INSIDE or OUTSIDE the zone group. Transition events report when the entity ENTERED or LEFT the zone group.",
|
||||
"create-relation-with-matched-zones": "Automatically create and maintain relations between the entity and the zones it is currently inside. Relations are removed when the entity leaves a zone and created when it enters a new one.",
|
||||
"relation-type-required": "Relation type is required.",
|
||||
"relation-level-required": "Relation level is required.",
|
||||
@ -1167,9 +1178,9 @@
|
||||
"geofencing-empty": "At least one zone group must be configured.",
|
||||
"geofencing-entity-not-found": "Geofencing target entity not found.",
|
||||
"max-geofencing-zone": "Maximum number of geofencing zones reached.",
|
||||
"zone-group-refresh-interval": "Defines how often zone groups configured via related entities are refreshed. Set to 0 to disable scheduled refresh.",
|
||||
"zone-group-refresh-interval": "Defines how often zone groups configured via related entities are refreshed.",
|
||||
"zone-group-refresh-interval-required": "Zone groups refresh interval is required.",
|
||||
"zone-group-refresh-interval-min": "Zone group refresh interval is below the minimum allowed system interval."
|
||||
"zone-group-refresh-interval-min": "Zone group refresh interval should be at least {{ min }} second."
|
||||
}
|
||||
},
|
||||
"ai-models": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user