Browse Source

Moved dashboard filters to new style (#15394)

* Moved dashboard filters to new style

* Clean up

* Fixed table alignment

* Added validators
pull/15286/head
Maksym Tsymbarov 6 months ago
committed by GitHub
parent
commit
055db10939
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 23
      ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html
  2. 112
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html
  3. 42
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.scss
  4. 121
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html
  5. 101
      ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.html
  6. 142
      ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts
  7. 111
      ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html
  8. 42
      ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.scss
  9. 2
      ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.ts
  10. 205
      ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html

23
ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html

@ -26,17 +26,16 @@
</button> </button>
</mat-toolbar> </mat-toolbar>
<div mat-dialog-content> <div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async" class="flex flex-col"> <section class="tb-form-panel">
<mat-form-field class="mat-block"> <div class="flex flex-row items-center justify-between">
<mat-label translate>filter.operation.operation</mat-label> <div class="tb-form-panel-title">{{ 'filter.filters' | translate }}
<mat-select required formControlName="operation"> </div>
@for (operation of complexOperations; track operation) { <tb-toggle-select formControlName="operation"
<mat-option [value]="operation"> selectMediaBreakpoint="xs">
{{complexOperationTranslations.get(complexOperationEnum[operation]) | translate}} <tb-toggle-option [value]="complexOperationEnum.AND">{{ complexOperationTranslations.get(complexOperationEnum.AND) | translate }}</tb-toggle-option>
</mat-option> <tb-toggle-option [value]="complexOperationEnum.OR">{{ complexOperationTranslations.get(complexOperationEnum.OR) | translate }}</tb-toggle-option>
} </tb-toggle-select>
</mat-select> </div>
</mat-form-field>
<tb-filter-predicate-list <tb-filter-predicate-list
[valueType]="data.valueType" [valueType]="data.valueType"
[displayUserParameters]="data.displayUserParameters" [displayUserParameters]="data.displayUserParameters"
@ -46,7 +45,7 @@
[key]="data.key" [key]="data.key"
formControlName="predicates"> formControlName="predicates">
</tb-filter-predicate-list> </tb-filter-predicate-list>
</fieldset> </section>
</div> </div>
<div mat-dialog-actions class="flex items-center justify-end"> <div mat-dialog-actions class="flex items-center justify-end">
<button mat-button color="primary" <button mat-button color="primary"

112
ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html

@ -15,82 +15,67 @@
limitations under the License. limitations under the License.
--> -->
<section class="flex flex-col" [formGroup]="filterListFormGroup"> <section class="flex flex-col gap-4" [formGroup]="filterListFormGroup">
<mat-expansion-panel [expanded]="true"> @if (predicatesFormArray.length) {
<mat-expansion-panel-header> <div class="tb-form-panel no-padding no-gap stroked">
<mat-panel-title> <div class="flex flex-row">
<div translate>filter.filters</div> <div class="flex flex-full flex-row items-center justify-start gap-2">
</mat-panel-title> <div class="flex flex-1 flex-row pl-2">
</mat-expansion-panel-header> <label translate class="filter-title max-w-30% flex-full">filter.operation.operation</label>
<div class="flex flex-row"> <label translate class="filter-title max-w-70% flex-full">filter.value</label>
<span class="max-w-8% flex-full"></span>
<div class="flex max-w-92% flex-full flex-row items-center justify-start gap-2">
<div class="flex flex-1 flex-row gap-2">
<div class="flex max-w-30% flex-full flex-row items-center justify-start gap-2">
<label translate class="tb-title no-padding flex-1">filter.operation.operation</label>
@if (valueType === valueTypeEnum.STRING) {
<label
translate class="tb-title no-padding" style="min-width: 70px;">filter.ignore-case</label>
}
</div> </div>
<label translate class="tb-title no-padding max-w-70% flex-full">filter.value</label> @if (displayUserParameters) {
<span [class.!hidden]="disabled" class="min-w-12">&nbsp;</span>
}
</div> </div>
@if (displayUserParameters) {
<label
translate class="tb-title no-padding" style="width: 60px;">filter.user-parameters</label>
}
<span [class.!hidden]="disabled" style="min-width: 40px;">&nbsp;</span>
</div> </div>
</div> <mat-divider style="padding-bottom: 5px;"></mat-divider>
<mat-divider style="padding-bottom: 5px;"></mat-divider> <div class="predicate-list">
<div class="predicate-list"> @for (predicateControl of predicatesFormArray.controls; track predicateControl) {
@for (predicateControl of predicatesFormArray.controls; track predicateControl) { <div class="relative flex flex-row items-stretch justify-start" [class.key-filter-list-divider]="$index">
<div class="flex flex-row items-stretch justify-start h-full min-h-19 max-h-24" @if ($index) {
formArrayName="predicates" <div class="filters-operation-container">
> <span class="filters-operation-label">{{ complexOperationTranslations.get(operation) | translate }}</span>
<div class="filters-operation flex max-w-8% flex-full flex-row items-stretch justify-center"> </div>
@if ($index > 0) {
<span>{{ complexOperationTranslations.get(operation) | translate }}</span>
} }
</div> <div class="flex flex-full flex-col">
<div class="flex max-w-92% min-w-147.5 flex-full flex-col"> <div class="flex flex-1 flex-row items-center justify-start">
<div class="flex flex-1 flex-row items-center justify-start gap-2"> <tb-filter-predicate
<tb-filter-predicate class="flex-1"
class="flex-1" [valueType]="valueType"
[valueType]="valueType" [displayUserParameters]="displayUserParameters"
[displayUserParameters]="displayUserParameters" [allowUserDynamicSource]="allowUserDynamicSource"
[allowUserDynamicSource]="allowUserDynamicSource" [onlyUserDynamicSource]="onlyUserDynamicSource"
[onlyUserDynamicSource]="onlyUserDynamicSource" [key]="key"
[key]="key" [formControl]="predicateControl">
[formControl]="predicateControl"> </tb-filter-predicate>
</tb-filter-predicate> <button mat-icon-button color="primary"
<button mat-icon-button color="primary" [class.!hidden]="disabled"
[class.!hidden]="disabled" type="button"
type="button" (click)="removePredicate($index)"
(click)="removePredicate($index)" matTooltip="{{ 'filter.remove-filter' | translate }}"
matTooltip="{{ 'filter.remove-filter' | translate }}" matTooltipPosition="above">
matTooltipPosition="above"> <mat-icon>close</mat-icon>
<mat-icon>close</mat-icon> </button>
</button> </div>
</div> </div>
</div> </div>
</div> }
} </div>
<span [class.!hidden]="!!predicatesFormArray.length"
[class.disabled]="disabled"
class="no-data-found flex items-center justify-center" translate>filter.no-filters</span>
</div> </div>
<div style="margin-top: 16px;" class="flex flex-row gap-2"> } @else {
<button mat-button mat-raised-button color="primary" <span [class.disabled]="disabled" class="no-data-found tb-prompt flex items-center justify-center" translate>filter.no-filters</span>
[class.!hidden]="disabled" }
<div class="flex flex-row gap-2" [class.!hidden]="disabled">
<button mat-button mat-stroked-button color="primary"
(click)="addPredicate(false)" (click)="addPredicate(false)"
type="button" type="button"
matTooltip="{{ 'filter.add-filter' | translate }}" matTooltip="{{ 'filter.add-filter' | translate }}"
matTooltipPosition="above"> matTooltipPosition="above">
{{ 'action.add' | translate }} {{ 'action.add' | translate }}
</button> </button>
<button mat-button mat-raised-button color="primary" <button mat-button
[class.!hidden]="disabled" color="primary"
(click)="addPredicate(true)" (click)="addPredicate(true)"
type="button" type="button"
matTooltip="{{ 'filter.add-complex-filter' | translate }}" matTooltip="{{ 'filter.add-complex-filter' | translate }}"
@ -98,5 +83,4 @@
{{ 'filter.add-complex' | translate }} {{ 'filter.add-complex' | translate }}
</button> </button>
</div> </div>
</mat-expansion-panel>
</section> </section>

42
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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
@import "../../../../../scss/constants";
:host { :host {
.predicate-list { .filter-title {
overflow-y: auto; padding: 12px 8px;
overflow-x: hidden; font-size: 14px;
max-height: 350px; font-weight: 500;
.no-data-found { }
height: 50px; .no-data-found {
} height: 50px;
font-size: 16px;
}
.key-filter-list-divider {
border-top: 1px solid rgba(0, 0, 0, 0.12);
} }
.filters-operation { .filters-operation {
margin-top: -18px; display: flex;
color: #666; justify-content: center;
font-weight: 500; 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);
}
} }
} }

121
ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html

@ -15,70 +15,75 @@
limitations under the License. limitations under the License.
--> -->
<div class="flex flex-1 flex-row items-center justify-start gap-2" [formGroup]="filterPredicateValueFormGroup"> <div class="flex flex-1 flex-row items-center justify-between gap-2" [formGroup]="filterPredicateValueFormGroup">
<div class="flex flex-1 flex-col" [class.!hidden]="dynamicMode || onlyUserDynamicSource"> <div class="flex flex-1 flex-col">
<div class="flex flex-col"> <div class="flex flex-1 flex-col" [class.!hidden]="dynamicMode || onlyUserDynamicSource">
@switch (valueType) { <div class="tb-form-row no-padding no-border column-xs">
@case (valueTypeEnum.STRING) { @switch (valueType) {
<mat-form-field hideRequiredMarker class="mat-block"> @case (valueTypeEnum.STRING) {
<input matInput formControlName="defaultValue" placeholder="{{'filter.value' | translate}}"> <mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
</mat-form-field> <mat-label> {{ hintText | translate }}</mat-label>
} <input matInput formControlName="defaultValue" placeholder="{{'filter.value' | translate}}">
@case (valueTypeEnum.NUMERIC) { </mat-form-field>
<mat-form-field hideRequiredMarker class="mat-block"> }
<input required type="number" matInput formControlName="defaultValue" @case (valueTypeEnum.NUMERIC) {
placeholder="{{'filter.value' | translate}}"> <mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
</mat-form-field> <mat-label> {{ hintText | translate }}</mat-label>
} <input required type="number" matInput formControlName="defaultValue"
@case (valueTypeEnum.DATE_TIME) { placeholder="{{'filter.value' | translate}}">
<tb-datetime formControlName="defaultValue" </mat-form-field>
dateText="{{ 'filter.date' | translate }}" }
required [showLabel]="false"></tb-datetime> @case (valueTypeEnum.DATE_TIME) {
<tb-datetime formControlName="defaultValue"
appearance="outline"
dateText="{{ 'filter.date' | translate }}"
required [showLabel]="false"></tb-datetime>
}
@case (valueTypeEnum.BOOLEAN) {
<mat-checkbox formControlName="defaultValue">
{{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }}
</mat-checkbox>
}
} }
@case (valueTypeEnum.BOOLEAN) { </div>
<mat-checkbox formControlName="defaultValue">
{{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }}
</mat-checkbox>
}
}
</div> </div>
<div class="tb-hint">{{ hintText | translate }}</div> <div class="flex flex-col flex-1" [class.!hidden]="!dynamicMode && !onlyUserDynamicSource">
</div> <div formGroupName="dynamicValue" class="flex flex-row justify-start gap-2">
<div class="flex flex-col flex-1" [class.!hidden]="!dynamicMode && !onlyUserDynamicSource"> <div class="tb-form-row no-padding no-border max-w-35% flex-full">
<div formGroupName="dynamicValue" class="flex flex-row justify-start gap-2"> <mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
<div class="flex max-w-35% flex-full flex-col"> <mat-label translate>filter.dynamic-source-type</mat-label>
<mat-form-field hideRequiredMarker class="mat-block"> <mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}">
<mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}"> <mat-option [value]="null">
<mat-option [value]="null"> {{'filter.no-dynamic-value' | translate}}
{{'filter.no-dynamic-value' | translate}}
</mat-option>
@for (sourceType of dynamicValueSourceTypes; track sourceType) {
<mat-option [value]="sourceType">
{{dynamicValueSourceTypeTranslations.get(sourceType) | translate}}
</mat-option> </mat-option>
} @for (sourceType of dynamicValueSourceTypes; track sourceType) {
</mat-select> <mat-option [value]="sourceType">
</mat-form-field> {{dynamicValueSourceTypeTranslations.get(sourceType) | translate}}
<div class="tb-hint" translate>filter.dynamic-source-type</div> </mat-option>
</div> }
<div class="flex flex-1 flex-col"> </mat-select>
<mat-form-field hideRequiredMarker class="mat-block"> </mat-form-field>
<input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}"> </div>
</mat-form-field> <div class="tb-form-row no-padding no-border column-xs flex-full">
<div class="tb-hint" translate>filter.source-attribute</div> <mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
</div> <mat-label translate>filter.source-attribute</mat-label>
@if (!allow && inheritMode) { <input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}">
<div </mat-form-field>
class="flex flex-col"
style="padding-top: 6px">
<mat-checkbox formControlName="inherit">
{{ 'filter.inherit-owner' | translate}}
</mat-checkbox>
<div class="tb-hint" translate>filter.source-attribute-not-set</div>
</div> </div>
} @if (!allow && inheritMode) {
<div
class="flex flex-col"
style="padding-top: 6px">
<mat-checkbox formControlName="inherit">
{{ 'filter.inherit-owner' | translate}}
</mat-checkbox>
<div class="tb-hint" translate>filter.source-attribute-not-set</div>
</div>
}
</div>
</div> </div>
</div> </div>
@if (!onlyUserDynamicSource) { @if (!onlyUserDynamicSource) {
<button mat-icon-button <button mat-icon-button
class="mat-elevation-z1 tb-mat-32" class="mat-elevation-z1 tb-mat-32"

101
ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.html

@ -15,51 +15,84 @@
limitations under the License. limitations under the License.
--> -->
<div class="flex flex-1 flex-row items-center justify-start gap-2" [formGroup]="filterPredicateFormGroup"> <div class="flex flex-1 flex-row items-center justify-start gap-4" [formGroup]="filterPredicateFormGroup">
<div class="flex flex-1 flex-col"> <div class="tb-form-panel no-border no-padding-right flex-1 items-center" style="flex-direction: row">
@switch (type) { @switch (type) {
@case (filterPredicateType.STRING) { @case (filterPredicateType.STRING) {
<tb-string-filter-predicate <div class="tb-form-row no-border no-padding w-full max-w-30%">
[allowUserDynamicSource]="allowUserDynamicSource" <mat-form-field hideRequiredMarker class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
[onlyUserDynamicSource]="onlyUserDynamicSource" <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
formControlName="predicate"> @for (operation of stringOperations; track operation) {
</tb-string-filter-predicate> <mat-option [value]="operation">
{{stringOperationTranslations.get(stringOperationEnum[operation]) | translate}}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-chip-listbox formControlName="ignoreCase" [hideSingleSelectionIndicator]="true">
<mat-chip-option color="primary" [value]="true">{{ 'alarm-rule.ignore-case' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
} }
@case (filterPredicateType.NUMERIC) { @case (filterPredicateType.NUMERIC) {
<tb-numeric-filter-predicate <div class="tb-form-row no-border no-padding w-full max-w-30%">
[allowUserDynamicSource]="allowUserDynamicSource" <mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
[onlyUserDynamicSource]="onlyUserDynamicSource" <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
[valueType]="valueType" @for (operation of numericOperations; track operation) {
formControlName="predicate"> <mat-option [value]="operation">
</tb-numeric-filter-predicate> {{numericOperationTranslations.get(numericOperationEnum[operation]) | translate}}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
} }
@case (filterPredicateType.BOOLEAN) { @case (filterPredicateType.BOOLEAN) {
<tb-boolean-filter-predicate <div class="tb-form-row no-border no-padding w-full max-w-30%">
[allowUserDynamicSource]="allowUserDynamicSource" <mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
[onlyUserDynamicSource]="onlyUserDynamicSource" <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
formControlName="predicate"> @for (operation of booleanOperations; track operation) {
</tb-boolean-filter-predicate> <mat-option [value]="operation">
{{booleanOperationTranslations.get(booleanOperationEnum[operation]) | translate}}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
} }
@case (filterPredicateType.COMPLEX) { @case (filterPredicateType.COMPLEX) {
<tb-complex-filter-predicate <div class="tb-form-row no-border no-padding flex-1">
[allowUserDynamicSource]="allowUserDynamicSource" <mat-label translate>filter.complex-filter</mat-label>
[onlyUserDynamicSource]="onlyUserDynamicSource" <button mat-icon-button color="primary"
[key]="key" class="tb-mat-32"
[valueType]="valueType" type="button"
[displayUserParameters]="displayUserParameters" (click)="openComplexFilterDialog()"
formControlName="predicate"> matTooltip="{{ (disabled ? 'filter.complex-filter' : 'filter.edit-complex-filter') | translate }}"
</tb-complex-filter-predicate> matTooltipPosition="above">
<mat-icon>{{ disabled ? 'more_vert' : 'edit' }}</mat-icon>
</button>
</div>
} }
} }
@if (type !== filterPredicateType.COMPLEX) {
<tb-filter-predicate-value
[allowUserDynamicSource]="allowUserDynamicSource"
[onlyUserDynamicSource]="onlyUserDynamicSource"
[operation]="filterPredicateFormGroup.get('operation').value"
class="max-w-70% flex-full"
[valueType]="valueType"
formControlName="value">
</tb-filter-predicate-value>
}
</div> </div>
@if (type !== filterPredicateType.COMPLEX && displayUserParameters) { @if (type !== filterPredicateType.COMPLEX && displayUserParameters) {
<tb-filter-user-info <button mat-icon-button color="primary"
style="width: 60px;" class="tb-mat-32"
class="flex flex-row items-stretch justify-center" type="button"
[valueType]="valueType" (click)="openFilterUserInfoDialog()"
[operation]="filterPredicateFormGroup.get('predicate').value?.operation" matTooltip="{{ (disabled ? 'filter.filter-user-params' : 'filter.edit-filter-user-params') | translate }}"
[key]="key" matTooltipPosition="above">
formControlName="userInfo"> <mat-icon>settings</mat-icon>
</tb-filter-user-info> </button>
} }
</div> </div>

142
ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts

@ -14,19 +14,35 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; import { Component, DestroyRef, forwardRef, Input } from '@angular/core';
import { import {
ControlValueAccessor, ControlValueAccessor,
UntypedFormBuilder, UntypedFormBuilder,
UntypedFormGroup,
NG_VALIDATORS, NG_VALIDATORS,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
ValidationErrors, ValidationErrors,
Validator, Validator,
Validators Validators,
} from '@angular/forms'; } 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 { 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({ @Component({
selector: 'tb-filter-predicate', selector: 'tb-filter-predicate',
@ -46,7 +62,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
], ],
standalone: false standalone: false
}) })
export class FilterPredicateComponent implements ControlValueAccessor, Validator, OnInit { export class FilterPredicateComponent implements ControlValueAccessor, Validator {
@Input() disabled: boolean; @Input() disabled: boolean;
@ -60,23 +76,35 @@ export class FilterPredicateComponent implements ControlValueAccessor, Validator
@Input() onlyUserDynamicSource = false; @Input() onlyUserDynamicSource = false;
filterPredicateFormGroup: UntypedFormGroup; filterPredicateFormGroup = this.fb.group({
operation: [],
ignoreCase: false,
predicates: [],
value: [],
userInfo: []
});
type: FilterPredicateType; type: FilterPredicateType;
filterPredicateType = 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; private propagateChange = null;
constructor(private fb: UntypedFormBuilder, constructor(private fb: UntypedFormBuilder,
private destroyRef: DestroyRef) { private destroyRef: DestroyRef,
} private dialog: MatDialog) {
ngOnInit(): void {
this.filterPredicateFormGroup = this.fb.group({
predicate: [null, [Validators.required]],
userInfo: [null, []]
});
this.filterPredicateFormGroup.valueChanges.pipe( this.filterPredicateFormGroup.valueChanges.pipe(
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
).subscribe(() => { ).subscribe(() => {
@ -93,7 +121,7 @@ export class FilterPredicateComponent implements ControlValueAccessor, Validator
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (this.disabled) { if (isDisabled) {
this.filterPredicateFormGroup.disable({emitEvent: false}); this.filterPredicateFormGroup.disable({emitEvent: false});
} else { } else {
this.filterPredicateFormGroup.enable({emitEvent: false}); this.filterPredicateFormGroup.enable({emitEvent: false});
@ -108,19 +136,97 @@ export class FilterPredicateComponent implements ControlValueAccessor, Validator
writeValue(predicate: KeyFilterPredicateInfo): void { writeValue(predicate: KeyFilterPredicateInfo): void {
this.type = predicate.keyFilterPredicate.type; this.type = predicate.keyFilterPredicate.type;
this.filterPredicateFormGroup.get('predicate').patchValue(predicate.keyFilterPredicate, {emitEvent: false}); this.updateValidators();
this.filterPredicateFormGroup.patchValue(predicate.keyFilterPredicate, {emitEvent: false});
this.filterPredicateFormGroup.get('userInfo').patchValue(predicate.userInfo, {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() { private updateModel() {
let predicate: KeyFilterPredicateInfo = null; let predicate: KeyFilterPredicateInfo = null;
if (this.filterPredicateFormGroup.valid) { 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 = { predicate = {
keyFilterPredicate: this.filterPredicateFormGroup.getRawValue().predicate, keyFilterPredicate,
userInfo: this.filterPredicateFormGroup.getRawValue().userInfo userInfo: v.userInfo
}; };
} }
this.propagateChange(predicate); this.propagateChange(predicate);
} }
public openComplexFilterDialog() {
this.dialog.open<ComplexFilterPredicateDialogComponent, ComplexFilterPredicateDialogData,
ComplexFilterPredicateInfo>(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, FilterUserInfoDialogData,
KeyFilterPredicateUserInfo>(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);
}
}
);
}
} }

111
ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html

@ -25,72 +25,61 @@
<mat-icon class="material-icons">close</mat-icon> <mat-icon class="material-icons">close</mat-icon>
</button> </button>
</mat-toolbar> </mat-toolbar>
@if (isLoading$ | async) {
<mat-progress-bar color="warn" mode="indeterminate">
</mat-progress-bar>
}
@if ((isLoading$ | async) === false) {
<div style="height: 4px;"></div>
}
<div mat-dialog-content> <div mat-dialog-content>
<div class="tb-filters-header flex flex-row items-center justify-start"> <div class="tb-form-panel no-border no-padding">
<span class="max-w-5% flex-full"></span> <div class="tb-form-panel stroked no-padding no-gap" [class.!hidden]="!filtersFormArray().length">
<div class="flex max-w-95% flex-full flex-row items-center justify-start"> <table mat-table [dataSource]="filtersFormArray().controls" class="overflow-hidden bg-transparent">
<div class="tb-header-label flex-1" translate>filter.filter</div> <ng-container matColumnDef="filter">
<div class="tb-header-label flex min-w-20 max-w-20 flex-1 basis-20 flex-col items-center justify-center" translate <mat-header-cell *matHeaderCellDef translate>filter.filter</mat-header-cell>
style="padding-left: 5px;">filter.editable</div> <mat-cell *matCellDef="let control">
<div style="min-width: 132px;"></div> <span>{{ control.get('filter').value }}</span>
</mat-cell>
</ng-container>
<ng-container matColumnDef="editable">
<mat-header-cell *matHeaderCellDef translate>filter.editable</mat-header-cell>
<mat-cell *matCellDef="let control">
<mat-slide-toggle [formControl]="control.get('editable')"></mat-slide-toggle>
</mat-cell>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<mat-header-cell *matHeaderCellDef/>
<mat-cell *matCellDef="let control; let i = index">
<div class="tb-form-table-row-cell-buttons flex-end">
<button mat-icon-button color="primary" type="button"
[disabled]="isLoading$ | async"
(click)="editFilter(i)"
matTooltip="{{ 'filter.edit' | translate }}"
matTooltipPosition="above">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button color="primary" type="button"
[disabled]="isLoading$ | async"
(click)="duplicateFilter(i)"
matTooltip="{{ 'filter.duplicate-filter-action' | translate }}"
matTooltipPosition="above">
<mat-icon>content_copy</mat-icon>
</button>
<button mat-icon-button color="primary" type="button"
[disabled]="isLoading$ | async"
(click)="removeFilter(i)"
matTooltip="{{ 'filter.remove-filter' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</table>
</div>
<div [class.!hidden]="!!filtersFormArray().length" class="tb-prompt flex items-center justify-center" translate>
filter.no-filters
</div> </div>
</div> </div>
<fieldset [disabled]="isLoading$ | async">
<mat-divider></mat-divider>
@for (filterControl of filtersFormArray().controls; track filterControl) {
<div class="flex flex-1 flex-row items-center justify-start"
formArrayName="filters"
>
<span class="max-w-5% flex-full">{{$index + 1}}.</span>
<div class="mat-elevation-z4 tb-filter flex max-w-95% flex-full flex-row items-center justify-start">
<mat-label class="flex-1">{{filterControl.get('filter').value}}</mat-label>
<section style="padding-left: 10px;"
class="tb-editable-switch flex min-w-20 max-w-20 flex-1 basis-20 flex-col items-center justify-center">
<mat-slide-toggle class="editable-switch"
[formControl]="filterControl.get('editable')">
</mat-slide-toggle>
</section>
<button [disabled]="isLoading$ | async"
mat-icon-button color="primary"
style="min-width: 40px;"
type="button"
(click)="editFilter($index)"
matTooltip="{{ 'filter.edit' | translate }}"
matTooltipPosition="above">
<mat-icon>edit</mat-icon>
</button>
<button [disabled]="isLoading$ | async"
mat-icon-button color="primary"
style="min-width: 48px;"
type="button"
(click)="duplicateFilter($index)"
matTooltip="{{ 'filter.duplicate-filter-action' | translate }}"
matTooltipPosition="above">
<mat-icon>content_copy</mat-icon>
</button>
<button [disabled]="isLoading$ | async"
mat-icon-button color="primary"
style="min-width: 40px;"
type="button"
(click)="removeFilter($index)"
matTooltip="{{ 'filter.remove-filter' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
}
</fieldset>
</div> </div>
<div mat-dialog-actions> <div mat-dialog-actions>
<button mat-raised-button color="primary" <button mat-stroked-button color="primary"
type="button" type="button"
(click)="addFilter()" (click)="addFilter()"
[class.!hidden]="disableAdd" [class.!hidden]="disableAdd"

42
ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.scss

@ -14,39 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
:host { :host {
.mat-mdc-header-cell.mat-column-editable,
.tb-filters-header { .mat-mdc-cell.mat-column-editable,
min-height: 40px; .mat-mdc-header-cell.mat-column-actions,
padding: 0 11px; .mat-mdc-cell.mat-column-actions {
margin: 5px; width: 1%;
white-space: nowrap;
.tb-header-label {
font-size: 14px;
color: rgba(0, 0, 0, .570588);
}
}
mat-divider{
margin: -1px -24px;
}
.tb-filter {
padding: 0 0 0 10px;
margin: 5px;
.tb-editable-switch {
padding-left: 10px;
.editable-switch {
margin: 0;
}
}
} }
}
:host ::ng-deep { .mat-mdc-row:last-child .mat-mdc-cell {
.mat-mdc-dialog-content { border-bottom: none;
padding-top: 0 !important;
padding-bottom: 0 !important;
} }
} }

2
ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.ts

@ -70,6 +70,8 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
filtersFormGroup: UntypedFormGroup; filtersFormGroup: UntypedFormGroup;
displayedColumns = ['filter', 'editable', 'actions'];
submitted = false; submitted = false;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,

205
ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html

@ -26,119 +26,128 @@
</button> </button>
</mat-toolbar> </mat-toolbar>
<div mat-dialog-content> <div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async" class="flex flex-col"> <section class="tb-form-panel no-border no-padding">
<section class="entity-key flex flex-row gap-2"> <section class="tb-form-panel">
<section class="flex flex-full flex-row gap-2" <div class="tb-form-panel-title">{{ 'filter.key-filter' | translate }}</div>
[class]="{ <div class="flex flex-row gap-2 xs:flex-col">
<div class="flex flex-row flex-full gap-2 xs:flex-col"
[class]="{
'max-w-45%': isConstantKeyType, 'max-w-45%': isConstantKeyType,
'max-w-70%': !isConstantKeyType 'max-w-70%': !isConstantKeyType
}" }"
formGroupName="key"> formGroupName="key">
<mat-form-field class="mat-block max-w-40% flex-full"> <mat-form-field class="mat-block max-w-40% flex-full" appearance="outline">
<mat-label translate>filter.key-type.key-type</mat-label> <mat-label translate>filter.key-type.key-type</mat-label>
<mat-select required formControlName="type"> <mat-select required formControlName="type">
@for (type of entityKeyTypes; track type) { @for (type of entityKeyTypes; track type) {
<mat-option [value]="type"> <mat-option [value]="type">
{{entityKeyTypeTranslations.get(type) | translate}} {{entityKeyTypeTranslations.get(type) | translate}}
</mat-option> </mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block max-w-60% flex-full" appearance="outline">
<mat-label translate>filter.key-name</mat-label>
<input matInput required formControlName="key"
#keyNameInput
(focusin)="onFocus()"
[matAutocomplete]="keyName"
[matAutocompleteDisabled]="!showAutocomplete">
@if (keyFilterFormGroup.get('key.key').value && showAutocomplete) {
<button
type="button"
matSuffix mat-icon-button aria-label="Clear"
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
} }
</mat-select> <mat-autocomplete autoActiveFirstOption #keyName="matAutocomplete">
</mat-form-field> @for (keyName of filteredKeysName | async; track keyName) {
<mat-form-field class="mat-block max-w-60% flex-full"> <mat-option [value]="keyName">
<mat-label translate>filter.key-name</mat-label> <span [innerHTML]="keyName | highlight:searchText"></span>
<input matInput required formControlName="key" </mat-option>
#keyNameInput }
(focusin)="onFocus()" </mat-autocomplete>
[matAutocomplete]="keyName" @if (keyFilterFormGroup.get('key.key').hasError('required')) {
[matAutocompleteDisabled]="!showAutocomplete"> <mat-error>
@if (keyFilterFormGroup.get('key.key').value && showAutocomplete) { {{ 'filter.key-name-required' | translate }}
<button </mat-error>
type="button" }
matSuffix mat-icon-button aria-label="Clear" </mat-form-field>
(click)="clear()"> </div>
<mat-icon class="material-icons">close</mat-icon> <mat-form-field class="mat-block tb-value-type flex-full" appearance="outline"
</button> [class]="{
} 'max-w-20%': isConstantKeyType,
<mat-autocomplete autoActiveFirstOption #keyName="matAutocomplete"> 'max-w-30%': !isConstantKeyType
@for (keyName of filteredKeysName | async; track keyName) { }">
<mat-option [value]="keyName"> <mat-label translate>filter.value-type.value-type</mat-label>
<span [innerHTML]="keyName | highlight:searchText"></span> <mat-select formControlName="valueType">
<mat-select-trigger>
<mat-icon class="tb-mat-18" svgIcon="{{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.icon }}"></mat-icon>
<span>{{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.name | translate }}</span>
</mat-select-trigger>
@for (valueType of entityKeyValueTypesKeys; track valueType) {
<mat-option [value]="valueType">
<mat-icon class="tb-mat-18" svgIcon="{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).icon }}"></mat-icon>
<span>{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).name | translate }}</span>
</mat-option> </mat-option>
} }
</mat-autocomplete> </mat-select>
@if (keyFilterFormGroup.get('key.key').hasError('required')) { @if (keyFilterFormGroup.get('valueType').hasError('required')) {
<mat-error> <mat-error>
{{ 'filter.key-name-required' | translate }} {{ 'filter.value-type-required' | translate }}
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
</section> @if (isConstantKeyType) {
<mat-form-field class="mat-block tb-value-type flex-full" <div class="flex-1">
[class]="{ <div class="flex flex-col">
'max-w-20%': isConstantKeyType, @switch (keyFilterFormGroup.get('valueType').value) {
'max-w-30%': !isConstantKeyType @case (entityKeyValueTypeEnum.STRING) {
}"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>filter.value-type.value-type</mat-label> <mat-label translate>filter.value</mat-label>
<mat-select formControlName="valueType"> <input matInput required formControlName="value" placeholder="{{'filter.value' | translate}}">
<mat-select-trigger> </mat-form-field>
<mat-icon class="tb-mat-18" svgIcon="{{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.icon }}"></mat-icon> }
<span>{{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.name | translate }}</span> @case (entityKeyValueTypeEnum.NUMERIC) {
</mat-select-trigger> <mat-form-field class="mat-block" appearance="outline">
@for (valueType of entityKeyValueTypesKeys; track valueType) { <mat-label translate>filter.value</mat-label>
<mat-option [value]="valueType"> <input required type="number" matInput formControlName="value">
<mat-icon class="tb-mat-18" svgIcon="{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).icon }}"></mat-icon> </mat-form-field>
<span>{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).name | translate }}</span> }
</mat-option> @case (entityKeyValueTypeEnum.DATE_TIME) {
} <tb-datetime formControlName="value"
</mat-select> dateText="{{ 'filter.date' | translate }}"
@if (keyFilterFormGroup.get('valueType').hasError('required')) { required></tb-datetime>
<mat-error> }
{{ 'filter.value-type-required' | translate }} @case (entityKeyValueTypeEnum.BOOLEAN) {
</mat-error> <mat-checkbox formControlName="value" style="margin-top: 20px">
} {{ (keyFilterFormGroup.get('value').value ? 'value.true' : 'value.false') | translate }}
</mat-form-field> </mat-checkbox>
@if (isConstantKeyType) { }
<div class="flex-1">
<div class="flex flex-col">
@switch (keyFilterFormGroup.get('valueType').value) {
@case (entityKeyValueTypeEnum.STRING) {
<mat-form-field class="mat-block">
<mat-label translate>filter.value</mat-label>
<input matInput required formControlName="value" placeholder="{{'filter.value' | translate}}">
</mat-form-field>
}
@case (entityKeyValueTypeEnum.NUMERIC) {
<mat-form-field class="mat-block">
<mat-label translate>filter.value</mat-label>
<input required type="number" matInput formControlName="value">
</mat-form-field>
}
@case (entityKeyValueTypeEnum.DATE_TIME) {
<tb-datetime formControlName="value"
dateText="{{ 'filter.date' | translate }}"
required></tb-datetime>
}
@case (entityKeyValueTypeEnum.BOOLEAN) {
<mat-checkbox formControlName="value" style="margin-top: 20px">
{{ (keyFilterFormGroup.get('value').value ? 'value.true' : 'value.false') | translate }}
</mat-checkbox>
} }
} </div>
</div> </div>
</div> }
} </div>
</section> </section>
@if (keyFilterFormGroup.get('valueType').value) { @if (keyFilterFormGroup.get('valueType').value) {
<tb-filter-predicate-list <section class="tb-form-panel">
[allowUserDynamicSource]="data.allowUserDynamicSource" <div class="flex flex-row items-center justify-between">
[onlyUserDynamicSource]="isConstantKeyType" <div class="tb-form-panel-title">{{ 'filter.filters' | translate }}
[displayUserParameters]="data.displayUserParameters" </div>
[valueType]="keyFilterFormGroup.get('valueType').value" </div>
[key]="keyFilterFormGroup.get('key.key').value" <tb-filter-predicate-list
formControlName="predicates"> [allowUserDynamicSource]="data.allowUserDynamicSource"
</tb-filter-predicate-list> [onlyUserDynamicSource]="isConstantKeyType"
[displayUserParameters]="data.displayUserParameters"
[valueType]="keyFilterFormGroup.get('valueType').value"
[key]="keyFilterFormGroup.get('key.key').value"
formControlName="predicates">
</tb-filter-predicate-list>
</section>
} }
</fieldset> </section>
</div> </div>
<div mat-dialog-actions class="flex items-center justify-end"> <div mat-dialog-actions class="flex items-center justify-end">
<button mat-button color="primary" <button mat-button color="primary"

Loading…
Cancel
Save