UI: fixed and improve geofencing cf
This commit is contained in:
		
							parent
							
								
									fcba7004f9
								
							
						
					
					
						commit
						6b5e339921
					
				@ -160,12 +160,16 @@
 | 
				
			|||||||
                                                              [tenantId]="data.tenantId"
 | 
					                                                              [tenantId]="data.tenantId"
 | 
				
			||||||
                                                              [entityName]="data.entityName"/>
 | 
					                                                              [entityName]="data.entityName"/>
 | 
				
			||||||
            <div class="tb-form-row space-between flex-1 columns-xs" [class.!hidden]="!isRelatedEntity">
 | 
					            <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">
 | 
					              <div class="flex flex-row items-center justify-start gap-2">
 | 
				
			||||||
                <tb-time-unit-input required
 | 
					                <tb-time-unit-input required
 | 
				
			||||||
                                    inlineField
 | 
					                                    inlineField
 | 
				
			||||||
                                    requiredText="{{ 'calculated-fields.hint.zone-group-refresh-interval-required' | translate }}"
 | 
					                                    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"
 | 
					                                    [minTime]="minAllowedScheduledUpdateIntervalInSecForCF"
 | 
				
			||||||
                                    formControlName="scheduledUpdateInterval">
 | 
					                                    formControlName="scheduledUpdateInterval">
 | 
				
			||||||
                </tb-time-unit-input>
 | 
					                </tb-time-unit-input>
 | 
				
			||||||
 | 
				
			|||||||
@ -81,6 +81,7 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
 | 
				
			|||||||
      }),
 | 
					      }),
 | 
				
			||||||
      arguments: this.fb.control({}),
 | 
					      arguments: this.fb.control({}),
 | 
				
			||||||
      zoneGroups: this.fb.control({}),
 | 
					      zoneGroups: this.fb.control({}),
 | 
				
			||||||
 | 
					      scheduledUpdateEnabled: [true],
 | 
				
			||||||
      scheduledUpdateInterval: [this.minAllowedScheduledUpdateIntervalInSecForCF],
 | 
					      scheduledUpdateInterval: [this.minAllowedScheduledUpdateIntervalInSecForCF],
 | 
				
			||||||
      expressionSIMPLE: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]],
 | 
					      expressionSIMPLE: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]],
 | 
				
			||||||
      expressionSCRIPT: [calculatedFieldDefaultScript],
 | 
					      expressionSCRIPT: [calculatedFieldDefaultScript],
 | 
				
			||||||
@ -144,6 +145,7 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
 | 
				
			|||||||
    this.applyDialogData();
 | 
					    this.applyDialogData();
 | 
				
			||||||
    this.observeTypeChanges();
 | 
					    this.observeTypeChanges();
 | 
				
			||||||
    this.observeZoneChanges();
 | 
					    this.observeZoneChanges();
 | 
				
			||||||
 | 
					    this.observeScheduledUpdateEnabled();
 | 
				
			||||||
    this.currentEntityFilter = getCalculatedFieldCurrentEntityFilter(this.data.entityName, this.data.entityId);
 | 
					    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);
 | 
					    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) {
 | 
					  private checkRelatedEntity(zoneGroups: CalculatedFieldGeofencing) {
 | 
				
			||||||
    this.isRelatedEntity = Object.values(zoneGroups).some(zone => zone.refDynamicSourceConfiguration?.type === ArgumentEntityType.RelationQuery);
 | 
					    this.isRelatedEntity = Object.values(zoneGroups).some(zone => zone.refDynamicSourceConfiguration?.type === ArgumentEntityType.RelationQuery);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -168,7 +168,7 @@ export class CalculatedFieldGeofencingZoneGroupsTableComponent implements Contro
 | 
				
			|||||||
        renderer: this.renderer,
 | 
					        renderer: this.renderer,
 | 
				
			||||||
        componentType: CalculatedFieldGeofencingZoneGroupsPanelComponent,
 | 
					        componentType: CalculatedFieldGeofencingZoneGroupsPanelComponent,
 | 
				
			||||||
        hostView: this.viewContainerRef,
 | 
					        hostView: this.viewContainerRef,
 | 
				
			||||||
        preferredPlacement: isExists ? ['left', 'leftTop', 'leftBottom'] : ['topRight', 'right', 'rightTop'],
 | 
					        preferredPlacement: 'right',
 | 
				
			||||||
        context: ctx,
 | 
					        context: ctx,
 | 
				
			||||||
        isModal: true
 | 
					        isModal: true
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
				
			|||||||
@ -80,12 +80,13 @@
 | 
				
			|||||||
        @if (ArgumentEntityTypeParamsMap.has(entityType)) {
 | 
					        @if (ArgumentEntityTypeParamsMap.has(entityType)) {
 | 
				
			||||||
          <div class="tb-form-row">
 | 
					          <div class="tb-form-row">
 | 
				
			||||||
            <div class="fixed-title-width tb-required">{{ ArgumentEntityTypeParamsMap.get(entityType).title | translate }}</div>
 | 
					            <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
 | 
					                                    #entityAutocomplete
 | 
				
			||||||
                                    formControlName="id"
 | 
					                                    formControlName="id"
 | 
				
			||||||
                                    inlineField
 | 
					                                    inlineField
 | 
				
			||||||
 | 
					                                    appearance="outline"
 | 
				
			||||||
                                    [placeholder]="'action.set' | translate"
 | 
					                                    [placeholder]="'action.set' | translate"
 | 
				
			||||||
                                    [required]="true"
 | 
					                                    required
 | 
				
			||||||
                                    [entityType]="ArgumentEntityTypeParamsMap.get(entityType).entityType"
 | 
					                                    [entityType]="ArgumentEntityTypeParamsMap.get(entityType).entityType"
 | 
				
			||||||
                                    (entityChanged)="entityNameSubject.next($event?.name)"/>
 | 
					                                    (entityChanged)="entityNameSubject.next($event?.name)"/>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
@ -94,76 +95,115 @@
 | 
				
			|||||||
      <ng-container [formGroup]="refDynamicSourceFormGroup">
 | 
					      <ng-container [formGroup]="refDynamicSourceFormGroup">
 | 
				
			||||||
        <div class="tb-form-panel stroked" *ngIf="entityType === ArgumentEntityType.RelationQuery">
 | 
					        <div class="tb-form-panel stroked" *ngIf="entityType === ArgumentEntityType.RelationQuery">
 | 
				
			||||||
          <mat-expansion-panel class="tb-settings" expanded>
 | 
					          <mat-expansion-panel class="tb-settings" expanded>
 | 
				
			||||||
            <mat-expansion-panel-header>{{ 'calculated-fields.relation-query' | translate }}*</mat-expansion-panel-header>
 | 
					            <mat-expansion-panel-header>{{ 'calculated-fields.entity-zone-relationship' | translate }}</mat-expansion-panel-header>
 | 
				
			||||||
 | 
					            <div class="tb-form-table">
 | 
				
			||||||
            <div class="tb-form-row">
 | 
					              <div class="tb-form-table-header no-padding-right">
 | 
				
			||||||
              <div class="fixed-title-width">{{ 'calculated-fields.direction' | translate }}</div>
 | 
					                <div class="tb-form-table-header-cell" translate>calculated-fields.level</div>
 | 
				
			||||||
              <mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
 | 
					                <div class="tb-form-table-header-cell flex-1" translate>calculated-fields.direction-level</div>
 | 
				
			||||||
                <mat-select formControlName="direction">
 | 
					                <div class="tb-form-table-header-cell flex-1 tb-required" translate>calculated-fields.relation-type</div>
 | 
				
			||||||
                  @for (direction of GeofencingDirectionList; track direction) {
 | 
					                <div class="tb-form-table-header-cell tb-actions-header"></div>
 | 
				
			||||||
                    <mat-option [value]="direction">{{ GeofencingDirectionTranslations.get(direction) | translate }}</mat-option>
 | 
					              </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>
 | 
					                </div>
 | 
				
			||||||
              </mat-form-field>
 | 
					              } @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>
 | 
				
			||||||
            <div class="tb-form-row">
 | 
					            <div>
 | 
				
			||||||
              <div class="fixed-title-width tb-required">{{ 'calculated-fields.relation-type' | translate }}</div>
 | 
					              @if (maxRelationLevelPerCfArgument && levelsFormArray().length >= maxRelationLevelPerCfArgument) {
 | 
				
			||||||
              <tb-string-autocomplete [fetchOptionsFn]="fetchOptions.bind(this)"
 | 
					                <div class="tb-form-hint tb-primary-fill max-args-warning flex items-center gap-2">
 | 
				
			||||||
                                      additionalClass="tb-suffix-show-on-hover"
 | 
					                  <mat-icon>warning</mat-icon>
 | 
				
			||||||
                                      class="flex-1"
 | 
					                  <span>{{ 'calculated-fields.max-allowed-levels-error' | translate }}</span>
 | 
				
			||||||
                                      appearance="outline"
 | 
					                </div>
 | 
				
			||||||
                                      panelWidth=""
 | 
					              } @else {
 | 
				
			||||||
                                      required
 | 
					                <button type="button" mat-stroked-button color="primary" (click)="addKey()">
 | 
				
			||||||
                                      [errorText]="'calculated-fields.hint.relation-type-required' | translate"
 | 
					                  {{ 'calculated-fields.add-level' | translate }}
 | 
				
			||||||
                                      formControlName="relationType">
 | 
					                </button>
 | 
				
			||||||
              </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>
 | 
					            </div>
 | 
				
			||||||
        </mat-expansion-panel>
 | 
					        </mat-expansion-panel>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </ng-container>
 | 
					      </ng-container>
 | 
				
			||||||
      <ng-container>
 | 
					      <ng-container>
 | 
				
			||||||
        <div class="tb-form-row">
 | 
					        @if (entityFilter.singleEntity.id) {
 | 
				
			||||||
          <div class="fixed-title-width tb-required" tb-hint-tooltip-icon="{{'calculated-fields.hint.perimeter-attribute-key' | translate}}">
 | 
					          <div class="tb-form-row">
 | 
				
			||||||
            {{ 'calculated-fields.perimeter-attribute-key' | translate }}
 | 
					            <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>
 | 
					          </div>
 | 
				
			||||||
          <tb-entity-key-autocomplete class="flex-1" formControlName="perimeterKeyName" [dataKeyType]="DataKeyType.attribute" [entityFilter]="entityFilter"/>
 | 
					        }
 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div class="tb-form-row">
 | 
					        <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>
 | 
					          <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">
 | 
					          <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 {
 | 
					  .limit-field-row {
 | 
				
			||||||
    @media screen and (max-width: $panel-width) {
 | 
					    @media screen and (max-width: $panel-width) {
 | 
				
			||||||
      display: flex;
 | 
					      display: flex;
 | 
				
			||||||
@ -48,4 +68,9 @@ $panel-width: 520px;
 | 
				
			|||||||
      flex-direction: column;
 | 
					      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 { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, output, ViewChild } from '@angular/core';
 | 
				
			||||||
import { TbPopoverComponent } from '@shared/components/popover.component';
 | 
					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 { charsWithNumRegex, oneSpaceInsideRegex } from '@shared/models/regex.constants';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  ArgumentEntityType,
 | 
					  ArgumentEntityType,
 | 
				
			||||||
@ -25,14 +33,15 @@ import {
 | 
				
			|||||||
  CalculatedFieldGeofencing,
 | 
					  CalculatedFieldGeofencing,
 | 
				
			||||||
  CalculatedFieldGeofencingValue,
 | 
					  CalculatedFieldGeofencingValue,
 | 
				
			||||||
  CalculatedFieldType,
 | 
					  CalculatedFieldType,
 | 
				
			||||||
 | 
					  GeofencingDirectionLevelTranslations,
 | 
				
			||||||
  GeofencingDirectionTranslations,
 | 
					  GeofencingDirectionTranslations,
 | 
				
			||||||
  GeofencingReportStrategy,
 | 
					  GeofencingReportStrategy,
 | 
				
			||||||
  GeofencingReportStrategyTranslations,
 | 
					  GeofencingReportStrategyTranslations,
 | 
				
			||||||
  getCalculatedFieldCurrentEntityFilter
 | 
					  getCalculatedFieldCurrentEntityFilter
 | 
				
			||||||
} from '@shared/models/calculated-field.models';
 | 
					} 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 { 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 { EntityId } from '@shared/models/id/entity-id';
 | 
				
			||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 | 
					import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 | 
				
			||||||
import { EntityFilter } from '@shared/models/query/query.models';
 | 
					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 { EntityAutocompleteComponent } from '@shared/components/entity/entity-autocomplete.component';
 | 
				
			||||||
import { NULL_UUID } from '@shared/models/id/has-uuid';
 | 
					import { NULL_UUID } from '@shared/models/id/has-uuid';
 | 
				
			||||||
import { EntitySearchDirection } from '@shared/models/relation.models';
 | 
					import { EntitySearchDirection } from '@shared/models/relation.models';
 | 
				
			||||||
 | 
					import { CdkDragDrop } from "@angular/cdk/drag-drop";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'tb-calculated-field-geofencing-zone-groups-panel',
 | 
					  selector: 'tb-calculated-field-geofencing-zone-groups-panel',
 | 
				
			||||||
@ -73,12 +83,9 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
 | 
				
			|||||||
      id: ['']
 | 
					      id: ['']
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    refDynamicSourceConfiguration: this.fb.group({
 | 
					    refDynamicSourceConfiguration: this.fb.group({
 | 
				
			||||||
      direction: [EntitySearchDirection.TO],
 | 
					      levels: this.fb.array([], [this.levelsRequired()])
 | 
				
			||||||
      relationType: ['', [Validators.required]],
 | 
					 | 
				
			||||||
      maxLevel: [1, [Validators.required, Validators.min(1), Validators.max(this.maxRelationLevelPerCfArgument)]],
 | 
					 | 
				
			||||||
      fetchLastLevelOnly: [false],
 | 
					 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    perimeterKeyName: ['', [Validators.pattern(oneSpaceInsideRegex)]],
 | 
					    perimeterKeyName: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex)]],
 | 
				
			||||||
    reportStrategy: [GeofencingReportStrategy.REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS],
 | 
					    reportStrategy: [GeofencingReportStrategy.REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS],
 | 
				
			||||||
    createRelationsWithMatchedZones: [false],
 | 
					    createRelationsWithMatchedZones: [false],
 | 
				
			||||||
    direction: [EntitySearchDirection.TO],
 | 
					    direction: [EntitySearchDirection.TO],
 | 
				
			||||||
@ -97,6 +104,8 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
 | 
				
			|||||||
  readonly GeofencingReportStrategyTranslations = GeofencingReportStrategyTranslations;
 | 
					  readonly GeofencingReportStrategyTranslations = GeofencingReportStrategyTranslations;
 | 
				
			||||||
  readonly GeofencingDirectionList = Object.values(EntitySearchDirection) as Array<EntitySearchDirection>;
 | 
					  readonly GeofencingDirectionList = Object.values(EntitySearchDirection) as Array<EntitySearchDirection>;
 | 
				
			||||||
  readonly GeofencingDirectionTranslations = GeofencingDirectionTranslations;
 | 
					  readonly GeofencingDirectionTranslations = GeofencingDirectionTranslations;
 | 
				
			||||||
 | 
					  readonly GeofencingDirectionLevelTranslations = GeofencingDirectionLevelTranslations;
 | 
				
			||||||
 | 
					  readonly AttributeScope = AttributeScope;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private currentEntityFilter: EntityFilter;
 | 
					  private currentEntityFilter: EntityFilter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -107,7 +116,6 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
 | 
				
			|||||||
    private store: Store<AppState>
 | 
					    private store: Store<AppState>
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.observeMaxLevelChanges();
 | 
					 | 
				
			||||||
    this.observeEntityFilterChanges();
 | 
					    this.observeEntityFilterChanges();
 | 
				
			||||||
    this.observeEntityTypeChanges();
 | 
					    this.observeEntityTypeChanges();
 | 
				
			||||||
    this.observeUpdatePosition();
 | 
					    this.observeUpdatePosition();
 | 
				
			||||||
@ -131,7 +139,16 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
 | 
				
			|||||||
    if (this.zone.refDynamicSourceConfiguration?.type) {
 | 
					    if (this.zone.refDynamicSourceConfiguration?.type) {
 | 
				
			||||||
      this.refEntityIdFormGroup.get('entityType').setValue(this.zone.refDynamicSourceConfiguration.type, {emitEvent: false});
 | 
					      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.validateDirectionAndRelationType(this.zone?.createRelationsWithMatchedZones);
 | 
				
			||||||
    this.validateRefDynamicSourceConfiguration(this.zone?.refEntityId?.entityType || this.zone?.refDynamicSourceConfiguration?.type);
 | 
					    this.validateRefDynamicSourceConfiguration(this.zone?.refEntityId?.entityType || this.zone?.refDynamicSourceConfiguration?.type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -241,17 +258,19 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
 | 
				
			|||||||
  private observeEntityFilterChanges(): void {
 | 
					  private observeEntityFilterChanges(): void {
 | 
				
			||||||
    merge(
 | 
					    merge(
 | 
				
			||||||
      this.refEntityIdFormGroup.get('entityType').valueChanges,
 | 
					      this.refEntityIdFormGroup.get('entityType').valueChanges,
 | 
				
			||||||
      this.refEntityIdFormGroup.get('id').valueChanges.pipe(filter(Boolean)),
 | 
					      this.refEntityIdFormGroup.get('id').valueChanges,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
      .pipe(debounceTime(50), takeUntilDestroyed())
 | 
					      .pipe(debounceTime(50), takeUntilDestroyed())
 | 
				
			||||||
      .subscribe(() => this.updateEntityFilter(this.entityType));
 | 
					      .subscribe(() => this.updateEntityFilter(this.entityType));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.refEntityIdFormGroup.get('id').valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed()).subscribe(() => this.geofencingFormGroup.get('perimeterKeyName').reset(''));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private observeEntityTypeChanges(): void {
 | 
					  private observeEntityTypeChanges(): void {
 | 
				
			||||||
    this.refEntityIdFormGroup.get('entityType').valueChanges
 | 
					    this.refEntityIdFormGroup.get('entityType').valueChanges
 | 
				
			||||||
      .pipe(distinctUntilChanged(), takeUntilDestroyed())
 | 
					      .pipe(distinctUntilChanged(), takeUntilDestroyed())
 | 
				
			||||||
      .subscribe(type => {
 | 
					      .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;
 | 
					        const isEntityWithId = type !== ArgumentEntityType.Tenant && type !== ArgumentEntityType.Current && type !== ArgumentEntityType.RelationQuery;
 | 
				
			||||||
        this.geofencingFormGroup.get('refEntityId')
 | 
					        this.geofencingFormGroup.get('refEntityId')
 | 
				
			||||||
          .get('id')[isEntityWithId ? 'enable' : 'disable']();
 | 
					          .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 {
 | 
					  private forbiddenNameValidator(): ValidatorFn {
 | 
				
			||||||
    return (control: FormControl) => {
 | 
					    return (control: FormControl) => {
 | 
				
			||||||
      const trimmedValue = control.value.trim().toLowerCase();
 | 
					      const trimmedValue = control.value.trim().toLowerCase();
 | 
				
			||||||
@ -282,10 +307,40 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
 | 
				
			|||||||
  private observeUpdatePosition(): void {
 | 
					  private observeUpdatePosition(): void {
 | 
				
			||||||
    merge(
 | 
					    merge(
 | 
				
			||||||
      this.refEntityIdFormGroup.get('entityType').valueChanges,
 | 
					      this.refEntityIdFormGroup.get('entityType').valueChanges,
 | 
				
			||||||
 | 
					      this.refEntityIdFormGroup.get('id').valueChanges,
 | 
				
			||||||
      this.geofencingFormGroup.get('createRelationsWithMatchedZones').valueChanges
 | 
					      this.geofencingFormGroup.get('createRelationsWithMatchedZones').valueChanges
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
      .pipe(delay(50), takeUntilDestroyed())
 | 
					      .pipe(delay(50), takeUntilDestroyed())
 | 
				
			||||||
      .subscribe(() => this.popover.updatePosition());
 | 
					      .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',
 | 
					  Asset = 'ASSET',
 | 
				
			||||||
  Customer = 'CUSTOMER',
 | 
					  Customer = 'CUSTOMER',
 | 
				
			||||||
  Tenant = 'TENANT',
 | 
					  Tenant = 'TENANT',
 | 
				
			||||||
  RelationQuery = 'RELATION_QUERY',
 | 
					  RelationQuery = 'RELATION_PATH_QUERY',
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ArgumentEntityTypeTranslations = new Map<ArgumentEntityType, string>(
 | 
					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 {
 | 
					export enum ArgumentType {
 | 
				
			||||||
  Attribute = 'ATTRIBUTE',
 | 
					  Attribute = 'ATTRIBUTE',
 | 
				
			||||||
  LatestTelemetry = 'TS_LATEST',
 | 
					  LatestTelemetry = 'TS_LATEST',
 | 
				
			||||||
@ -167,10 +174,7 @@ export interface CalculatedFieldGeofencing {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export interface RefDynamicSourceConfiguration {
 | 
					export interface RefDynamicSourceConfiguration {
 | 
				
			||||||
  type?: ArgumentEntityType.RelationQuery;
 | 
					  type?: ArgumentEntityType.RelationQuery;
 | 
				
			||||||
  direction: EntitySearchDirection;
 | 
					  levels?: Array<{direction: EntitySearchDirection; relationType: string;}>;
 | 
				
			||||||
  relationType: string;
 | 
					 | 
				
			||||||
  maxLevel: number;
 | 
					 | 
				
			||||||
  fetchLastLevelOnly?: boolean;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface CalculatedFieldGeofencingValue extends CalculatedFieldGeofencing {
 | 
					export interface CalculatedFieldGeofencingValue extends CalculatedFieldGeofencing {
 | 
				
			||||||
 | 
				
			|||||||
@ -1122,17 +1122,28 @@
 | 
				
			|||||||
        "report-presence-status-only": "Presence status only",
 | 
					        "report-presence-status-only": "Presence status only",
 | 
				
			||||||
        "report-transition-event-and-presence": "Presence status and transition events",
 | 
					        "report-transition-event-and-presence": "Presence status and transition events",
 | 
				
			||||||
        "perimeter-attribute-key": "Perimeter attribute key",
 | 
					        "perimeter-attribute-key": "Perimeter attribute key",
 | 
				
			||||||
        "relation-query": "Relations query",
 | 
					        "perimeter-attribute-key-required": "Perimeter attribute key is required.",
 | 
				
			||||||
        "direction": "Direction",
 | 
					        "perimeter-attribute-key-pattern": "Perimeter attribute key is invalid.",
 | 
				
			||||||
        "direction-from": "From source entity",
 | 
					        "entity-zone-relationship": "Path from Entity to Zones *",
 | 
				
			||||||
        "direction-to": "To source entity",
 | 
					        "direction": "Relation direction",
 | 
				
			||||||
 | 
					        "direction-from": "From entity to zone",
 | 
				
			||||||
 | 
					        "direction-to": "From zone to entity",
 | 
				
			||||||
        "relation-type": "Relation type",
 | 
					        "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",
 | 
					        "relation-level": "Relation level",
 | 
				
			||||||
        "fetch-last-available-level": "Fetch last available level only",
 | 
					        "fetch-last-available-level": "Fetch last available level only",
 | 
				
			||||||
        "zone-group-refresh-interval": "Zone groups refresh interval",
 | 
					        "zone-group-refresh-interval": "Zone groups refresh interval",
 | 
				
			||||||
        "copy-zone-group-name": "Copy zone group name",
 | 
					        "copy-zone-group-name": "Copy zone group name",
 | 
				
			||||||
        "open-details-page": "Open entity details page",
 | 
					        "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": {
 | 
					        "hint": {
 | 
				
			||||||
            "arguments-simple-with-rolling": "Simple type calculated field should not contain keys with time series rolling type.",
 | 
					            "arguments-simple-with-rolling": "Simple type calculated field should not contain keys with time series rolling type.",
 | 
				
			||||||
            "arguments-empty": "Arguments should not be empty.",
 | 
					            "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).",
 | 
					            "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.",
 | 
					            "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.",
 | 
					            "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.",
 | 
					            "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-type-required": "Relation type is required.",
 | 
				
			||||||
            "relation-level-required": "Relation level is required.",
 | 
					            "relation-level-required": "Relation level is required.",
 | 
				
			||||||
@ -1167,9 +1178,9 @@
 | 
				
			|||||||
            "geofencing-empty": "At least one zone group must be configured.",
 | 
					            "geofencing-empty": "At least one zone group must be configured.",
 | 
				
			||||||
            "geofencing-entity-not-found": "Geofencing target entity not found.",
 | 
					            "geofencing-entity-not-found": "Geofencing target entity not found.",
 | 
				
			||||||
            "max-geofencing-zone": "Maximum number of geofencing zones reached.",
 | 
					            "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-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": {
 | 
					    "ai-models": {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user