From 77dbdf2520e6323ba711332c7743e1c6ed16a59b Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Wed, 8 Apr 2026 17:26:04 +0200 Subject: [PATCH 1/5] Moved dashboard filters to new style --- ...lex-filter-predicate-dialog.component.html | 23 +- ...mplex-filter-predicate-dialog.component.ts | 1 + .../filter-predicate-list.component.html | 111 ++++------ .../filter-predicate-list.component.scss | 42 +++- .../filter-predicate-value.component.html | 121 ++++++----- .../filter/filter-predicate.component.html | 100 ++++++--- .../filter/filter-predicate.component.ts | 127 +++++++++-- .../filter/filters-dialog.component.html | 111 +++++----- .../filter/filters-dialog.component.scss | 42 +--- .../filter/filters-dialog.component.ts | 2 + .../filter/key-filter-dialog.component.html | 205 +++++++++--------- .../filter/key-filter-dialog.component.ts | 2 +- 12 files changed, 499 insertions(+), 388 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html index 59d5faa155..cc86556a26 100644 --- a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html @@ -26,17 +26,16 @@
-
- - filter.operation.operation - - @for (operation of complexOperations; track operation) { - - {{complexOperationTranslations.get(complexOperationEnum[operation]) | translate}} - - } - - +
+
+
{{ 'filter.filters' | translate }} +
+ + {{ complexOperationTranslations.get(complexOperationEnum.AND) | translate }} + {{ complexOperationTranslations.get(complexOperationEnum.OR) | translate }} + +
-
+
+
+
+ + + +
- - } - filter.no-filters + } + -
- -
- diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.scss b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.scss index 5155866e84..9a895dc7e0 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.scss +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.scss @@ -13,18 +13,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@import "../../../../../scss/constants"; + :host { - .predicate-list { - overflow-y: auto; - overflow-x: hidden; - max-height: 350px; - .no-data-found { - height: 50px; - } + .filter-title { + padding: 12px 8px; + font-size: 14px; + font-weight: 500; + } + .no-data-found { + height: 50px; + font-size: 16px; + } + + .key-filter-list-divider { + border-top: 1px solid rgba(0, 0, 0, 0.12); } .filters-operation { - margin-top: -18px; - color: #666; - font-weight: 500; + display: flex; + justify-content: center; + margin-top: -14px; + &-container { + position: absolute; + top: -12px; + left: 10px; + background-color: white; + } + &-label { + font-size: 15px; + font-weight: 400; + color: $tb-primary-color; + padding: 0 8px; + border-radius: 4px; + border: 1px solid rgba($tb-primary-color, 0.32); + background-color: rgba($tb-primary-color, 0.04); + } } } diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html index e0c3aa20c7..63554e8034 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html @@ -15,70 +15,75 @@ limitations under the License. --> -
-
-
- @switch (valueType) { - @case (valueTypeEnum.STRING) { - - - - } - @case (valueTypeEnum.NUMERIC) { - - - - } - @case (valueTypeEnum.DATE_TIME) { - +
+
+
+
+ @switch (valueType) { + @case (valueTypeEnum.STRING) { + + {{ hintText | translate }} + + + } + @case (valueTypeEnum.NUMERIC) { + + {{ hintText | translate }} + + + } + @case (valueTypeEnum.DATE_TIME) { + + } + @case (valueTypeEnum.BOOLEAN) { + + {{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }} + + } } - @case (valueTypeEnum.BOOLEAN) { - - {{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }} - - } - } +
-
{{ hintText | translate }}
-
-
-
-
- - - - {{'filter.no-dynamic-value' | translate}} - - @for (sourceType of dynamicValueSourceTypes; track sourceType) { - - {{dynamicValueSourceTypeTranslations.get(sourceType) | translate}} +
+
+
+ + filter.dynamic-source-type + + + {{'filter.no-dynamic-value' | translate}} - } - - -
filter.dynamic-source-type
-
-
- - - -
filter.source-attribute
-
- @if (!allow && inheritMode) { -
- - {{ 'filter.inherit-owner' | translate}} - -
filter.source-attribute-not-set
+ @for (sourceType of dynamicValueSourceTypes; track sourceType) { + + {{dynamicValueSourceTypeTranslations.get(sourceType) | translate}} + + } + + +
+
+ + filter.source-attribute + +
- } + @if (!allow && inheritMode) { +
+ + {{ 'filter.inherit-owner' | translate}} + +
filter.source-attribute-not-set
+
+ } +
+ @if (!onlyUserDynamicSource) { +
} } + @if (type !== filterPredicateType.COMPLEX) { + + + }
@if (type !== filterPredicateType.COMPLEX && displayUserParameters) { - - + }
diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts b/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts index e4e327bf0a..797f8f2a4f 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts @@ -14,19 +14,34 @@ /// limitations under the License. /// -import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; +import { Component, DestroyRef, forwardRef, Input } from '@angular/core'; import { ControlValueAccessor, UntypedFormBuilder, - UntypedFormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator, - Validators } from '@angular/forms'; -import { EntityKeyValueType, FilterPredicateType, KeyFilterPredicateInfo } from '@shared/models/query/query.models'; +import { + EntityKeyValueType, + FilterPredicateType, + KeyFilterPredicateInfo, + BooleanOperation, booleanOperationTranslationMap, + NumericOperation, numericOperationTranslationMap, + StringOperation, stringOperationTranslationMap, ComplexFilterPredicateInfo, KeyFilterPredicateUserInfo, + KeyFilterPredicate +} from '@shared/models/query/query.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ComplexFilterPredicateDialogData } from '@home/components/filter/filter-component.models'; +import { + ComplexFilterPredicateDialogComponent +} from '@home/components/filter/complex-filter-predicate-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { + FilterUserInfoDialogComponent, + FilterUserInfoDialogData +} from '@home/components/filter/filter-user-info-dialog.component'; @Component({ selector: 'tb-filter-predicate', @@ -46,7 +61,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; ], standalone: false }) -export class FilterPredicateComponent implements ControlValueAccessor, Validator, OnInit { +export class FilterPredicateComponent implements ControlValueAccessor, Validator { @Input() disabled: boolean; @@ -60,23 +75,35 @@ export class FilterPredicateComponent implements ControlValueAccessor, Validator @Input() onlyUserDynamicSource = false; - filterPredicateFormGroup: UntypedFormGroup; + filterPredicateFormGroup = this.fb.group({ + operation: [], + ignoreCase: false, + predicates: [], + value: [], + userInfo: [] + }); type: FilterPredicateType; filterPredicateType = FilterPredicateType; + stringOperations = Object.keys(StringOperation); + stringOperationEnum = StringOperation; + stringOperationTranslations = stringOperationTranslationMap; + + numericOperations = Object.keys(NumericOperation); + numericOperationEnum = NumericOperation; + numericOperationTranslations = numericOperationTranslationMap; + + booleanOperations = Object.keys(BooleanOperation); + booleanOperationEnum = BooleanOperation; + booleanOperationTranslations = booleanOperationTranslationMap; + private propagateChange = null; constructor(private fb: UntypedFormBuilder, - private destroyRef: DestroyRef) { - } - - ngOnInit(): void { - this.filterPredicateFormGroup = this.fb.group({ - predicate: [null, [Validators.required]], - userInfo: [null, []] - }); + private destroyRef: DestroyRef, + private dialog: MatDialog) { this.filterPredicateFormGroup.valueChanges.pipe( takeUntilDestroyed(this.destroyRef) ).subscribe(() => { @@ -93,7 +120,7 @@ export class FilterPredicateComponent implements ControlValueAccessor, Validator setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; - if (this.disabled) { + if (isDisabled) { this.filterPredicateFormGroup.disable({emitEvent: false}); } else { this.filterPredicateFormGroup.enable({emitEvent: false}); @@ -108,19 +135,83 @@ export class FilterPredicateComponent implements ControlValueAccessor, Validator writeValue(predicate: KeyFilterPredicateInfo): void { this.type = predicate.keyFilterPredicate.type; - this.filterPredicateFormGroup.get('predicate').patchValue(predicate.keyFilterPredicate, {emitEvent: false}); + this.filterPredicateFormGroup.patchValue(predicate.keyFilterPredicate, {emitEvent: false}); this.filterPredicateFormGroup.get('userInfo').patchValue(predicate.userInfo, {emitEvent: false}); } private updateModel() { let predicate: KeyFilterPredicateInfo = null; if (this.filterPredicateFormGroup.valid) { + const v = this.filterPredicateFormGroup.getRawValue(); + let keyFilterPredicate: KeyFilterPredicate; + if (this.type === FilterPredicateType.COMPLEX) { + keyFilterPredicate = { + type: FilterPredicateType.COMPLEX, + operation: v.operation, + predicates: v.predicates + } as KeyFilterPredicate; + } else { + keyFilterPredicate = { + type: this.type, + value: v.value, + operation: v.operation, + ignoreCase: v.ignoreCase + } as KeyFilterPredicate; + } predicate = { - keyFilterPredicate: this.filterPredicateFormGroup.getRawValue().predicate, - userInfo: this.filterPredicateFormGroup.getRawValue().userInfo + keyFilterPredicate, + userInfo: v.userInfo }; } this.propagateChange(predicate); } + public openComplexFilterDialog() { + this.dialog.open(ComplexFilterPredicateDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + complexPredicate: { + type: FilterPredicateType.COMPLEX, + operation: this.filterPredicateFormGroup.get('operation').value, + predicates: this.filterPredicateFormGroup.get('predicates').value + }, + readonly: this.disabled, + valueType: this.valueType, + isAdd: false, + key: this.key, + displayUserParameters: this.displayUserParameters, + allowUserDynamicSource: this.allowUserDynamicSource, + onlyUserDynamicSource: this.onlyUserDynamicSource + } + }).afterClosed().subscribe( + (result) => { + if (result) { + this.filterPredicateFormGroup.patchValue(result); + } + } + ); + } + + public openFilterUserInfoDialog() { + this.dialog.open(FilterUserInfoDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + keyFilterPredicateUserInfo: this.filterPredicateFormGroup.get('userInfo').value, + valueType: this.valueType, + key: this.key, + operation: this.filterPredicateFormGroup.get('operation').value, + readonly: this.disabled + } + }).afterClosed().subscribe( + (result) => { + if (result) { + this.filterPredicateFormGroup.get('userInfo').patchValue(result); + } + } + ); + } } diff --git a/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html index 59c4c47eea..8fdbaab47e 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html @@ -25,72 +25,61 @@ close - @if (isLoading$ | async) { - - - } - @if ((isLoading$ | async) === false) { -
- }
-
- -
-
filter.filter
-
filter.editable
-
+
+
+ + + filter.filter + + {{ control.get('filter').value }} + + + + filter.editable + + + + + + + +
+ + + +
+
+
+ + +
+
+
+ filter.no-filters
-
- - @for (filterControl of filtersFormArray().controls; track filterControl) { -
- {{$index + 1}}. -
- {{filterControl.get('filter').value}} -
- - -
- - - -
-
- } -
-
-
-
-
+
+
{{ 'filter.key-filter' | translate }}
+
+
- - filter.key-type.key-type - - @for (type of entityKeyTypes; track type) { - - {{entityKeyTypeTranslations.get(type) | translate}} - + formGroupName="key"> + + filter.key-type.key-type + + @for (type of entityKeyTypes; track type) { + + {{entityKeyTypeTranslations.get(type) | translate}} + + } + + + + filter.key-name + + @if (keyFilterFormGroup.get('key.key').value && showAutocomplete) { + } - - - - filter.key-name - - @if (keyFilterFormGroup.get('key.key').value && showAutocomplete) { - - } - - @for (keyName of filteredKeysName | async; track keyName) { - - + + @for (keyName of filteredKeysName | async; track keyName) { + + + + } + + @if (keyFilterFormGroup.get('key.key').hasError('required')) { + + {{ 'filter.key-name-required' | translate }} + + } + +
+ + filter.value-type.value-type + + + + {{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.name | translate }} + + @for (valueType of entityKeyValueTypesKeys; track valueType) { + + + {{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).name | translate }} } - - @if (keyFilterFormGroup.get('key.key').hasError('required')) { + + @if (keyFilterFormGroup.get('valueType').hasError('required')) { - {{ 'filter.key-name-required' | translate }} + {{ 'filter.value-type-required' | translate }} } -
- - filter.value-type.value-type - - - - {{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.name | translate }} - - @for (valueType of entityKeyValueTypesKeys; track valueType) { - - - {{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).name | translate }} - - } - - @if (keyFilterFormGroup.get('valueType').hasError('required')) { - - {{ 'filter.value-type-required' | translate }} - - } - - @if (isConstantKeyType) { -
-
- @switch (keyFilterFormGroup.get('valueType').value) { - @case (entityKeyValueTypeEnum.STRING) { - - filter.value - - - } - @case (entityKeyValueTypeEnum.NUMERIC) { - - filter.value - - - } - @case (entityKeyValueTypeEnum.DATE_TIME) { - - } - @case (entityKeyValueTypeEnum.BOOLEAN) { - - {{ (keyFilterFormGroup.get('value').value ? 'value.true' : 'value.false') | translate }} - + @if (isConstantKeyType) { +
+
+ @switch (keyFilterFormGroup.get('valueType').value) { + @case (entityKeyValueTypeEnum.STRING) { + + filter.value + + + } + @case (entityKeyValueTypeEnum.NUMERIC) { + + filter.value + + + } + @case (entityKeyValueTypeEnum.DATE_TIME) { + + } + @case (entityKeyValueTypeEnum.BOOLEAN) { + + {{ (keyFilterFormGroup.get('value').value ? 'value.true' : 'value.false') | translate }} + + } } - } +
-
- } + } +
@if (keyFilterFormGroup.get('valueType').value) { - - +
+
+
{{ 'filter.filters' | translate }} +
+
+ + +
} -
+
diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.html index d3a0ba01d4..6c99a7bfcd 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.html @@ -78,6 +78,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts b/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts index 797f8f2a4f..0b074c2205 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts @@ -155,7 +155,7 @@ export class FilterPredicateComponent implements ControlValueAccessor, Validator type: this.type, value: v.value, operation: v.operation, - ignoreCase: v.ignoreCase + ignoreCase: !!v.ignoreCase } as KeyFilterPredicate; } predicate = { From b15c8fd52c1417e6b3fdfa5aa5c80bb4cab7a030 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 9 Apr 2026 14:13:52 +0200 Subject: [PATCH 4/5] Added validators --- .../filter/filter-predicate.component.ts | 15 +++++++++++++++ .../filter/filters-dialog.component.html | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts b/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts index 0b074c2205..1311176976 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts @@ -22,6 +22,7 @@ import { NG_VALUE_ACCESSOR, ValidationErrors, Validator, + Validators, } from '@angular/forms'; import { EntityKeyValueType, @@ -135,10 +136,24 @@ export class FilterPredicateComponent implements ControlValueAccessor, Validator writeValue(predicate: KeyFilterPredicateInfo): void { this.type = predicate.keyFilterPredicate.type; + this.updateValidators(); this.filterPredicateFormGroup.patchValue(predicate.keyFilterPredicate, {emitEvent: false}); this.filterPredicateFormGroup.get('userInfo').patchValue(predicate.userInfo, {emitEvent: false}); } + private updateValidators(): void { + const operationCtrl = this.filterPredicateFormGroup.get('operation'); + const predicatesCtrl = this.filterPredicateFormGroup.get('predicates'); + operationCtrl.setValidators([Validators.required]); + if (this.type === FilterPredicateType.COMPLEX) { + predicatesCtrl.setValidators([Validators.required]); + } else { + predicatesCtrl.clearValidators(); + } + operationCtrl.updateValueAndValidity({emitEvent: false}); + predicatesCtrl.updateValueAndValidity({emitEvent: false}); + } + private updateModel() { let predicate: KeyFilterPredicateInfo = null; if (this.filterPredicateFormGroup.valid) { diff --git a/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html index 8fdbaab47e..452045d164 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html @@ -41,7 +41,7 @@ - +
From cbdec0fdc5c9a1425b8278d12e0785715c051fe4 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 19 May 2026 12:07:59 +0200 Subject: [PATCH 5/5] Fixed re-render filters table rows after add, duplicate, or remove --- .../home/components/filter/filters-dialog.component.html | 2 +- .../home/components/filter/filters-dialog.component.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html index 452045d164..0a1fd59275 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html @@ -32,7 +32,7 @@ filter.filter - {{ control.get('filter').value }} + {{ control.get('filter').value }} diff --git a/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.ts b/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.ts index 24e375c9c7..05240fdf4d 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, SkipSelf } from '@angular/core'; +import { Component, Inject, SkipSelf, ViewChild } from '@angular/core'; import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; @@ -40,6 +40,7 @@ import { deepClone, isUndefined } from '@core/utils'; import { ComplexOperation, Filter, Filters, KeyFilterInfo } from '@shared/models/query/query.models'; import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; +import { MatTable } from '@angular/material/table'; export interface FiltersDialogData { filters: Filters; @@ -61,6 +62,8 @@ export interface FiltersDialogData { export class FiltersDialogComponent extends DialogComponent implements ErrorStateMatcher { + @ViewChild(MatTable) table: MatTable; + title: string; disableAdd: boolean; @@ -168,6 +171,7 @@ export class FiltersDialogComponent extends DialogComponent