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>
</mat-toolbar>
<div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async" class="flex flex-col">
<mat-form-field class="mat-block">
<mat-label translate>filter.operation.operation</mat-label>
<mat-select required formControlName="operation">
@for (operation of complexOperations; track operation) {
<mat-option [value]="operation">
{{complexOperationTranslations.get(complexOperationEnum[operation]) | translate}}
</mat-option>
}
</mat-select>
</mat-form-field>
<section class="tb-form-panel">
<div class="flex flex-row items-center justify-between">
<div class="tb-form-panel-title">{{ 'filter.filters' | translate }}
</div>
<tb-toggle-select formControlName="operation"
selectMediaBreakpoint="xs">
<tb-toggle-option [value]="complexOperationEnum.AND">{{ complexOperationTranslations.get(complexOperationEnum.AND) | translate }}</tb-toggle-option>
<tb-toggle-option [value]="complexOperationEnum.OR">{{ complexOperationTranslations.get(complexOperationEnum.OR) | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<tb-filter-predicate-list
[valueType]="data.valueType"
[displayUserParameters]="data.displayUserParameters"
@ -46,7 +45,7 @@
[key]="data.key"
formControlName="predicates">
</tb-filter-predicate-list>
</fieldset>
</section>
</div>
<div mat-dialog-actions class="flex items-center justify-end">
<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.
-->
<section class="flex flex-col" [formGroup]="filterListFormGroup">
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div translate>filter.filters</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div class="flex flex-row">
<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>
}
<section class="flex flex-col gap-4" [formGroup]="filterListFormGroup">
@if (predicatesFormArray.length) {
<div class="tb-form-panel no-padding no-gap stroked">
<div class="flex flex-row">
<div class="flex flex-full flex-row items-center justify-start gap-2">
<div class="flex flex-1 flex-row pl-2">
<label translate class="filter-title max-w-30% flex-full">filter.operation.operation</label>
<label translate class="filter-title max-w-70% flex-full">filter.value</label>
</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>
@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>
<mat-divider style="padding-bottom: 5px;"></mat-divider>
<div class="predicate-list">
@for (predicateControl of predicatesFormArray.controls; track predicateControl) {
<div class="flex flex-row items-stretch justify-start h-full min-h-19 max-h-24"
formArrayName="predicates"
>
<div class="filters-operation flex max-w-8% flex-full flex-row items-stretch justify-center">
@if ($index > 0) {
<span>{{ complexOperationTranslations.get(operation) | translate }}</span>
<mat-divider style="padding-bottom: 5px;"></mat-divider>
<div class="predicate-list">
@for (predicateControl of predicatesFormArray.controls; track predicateControl) {
<div class="relative flex flex-row items-stretch justify-start" [class.key-filter-list-divider]="$index">
@if ($index) {
<div class="filters-operation-container">
<span class="filters-operation-label">{{ complexOperationTranslations.get(operation) | translate }}</span>
</div>
}
</div>
<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 gap-2">
<tb-filter-predicate
class="flex-1"
[valueType]="valueType"
[displayUserParameters]="displayUserParameters"
[allowUserDynamicSource]="allowUserDynamicSource"
[onlyUserDynamicSource]="onlyUserDynamicSource"
[key]="key"
[formControl]="predicateControl">
</tb-filter-predicate>
<button mat-icon-button color="primary"
[class.!hidden]="disabled"
type="button"
(click)="removePredicate($index)"
matTooltip="{{ 'filter.remove-filter' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
<div class="flex flex-full flex-col">
<div class="flex flex-1 flex-row items-center justify-start">
<tb-filter-predicate
class="flex-1"
[valueType]="valueType"
[displayUserParameters]="displayUserParameters"
[allowUserDynamicSource]="allowUserDynamicSource"
[onlyUserDynamicSource]="onlyUserDynamicSource"
[key]="key"
[formControl]="predicateControl">
</tb-filter-predicate>
<button mat-icon-button color="primary"
[class.!hidden]="disabled"
type="button"
(click)="removePredicate($index)"
matTooltip="{{ 'filter.remove-filter' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</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">
<button mat-button mat-raised-button color="primary"
[class.!hidden]="disabled"
} @else {
<span [class.disabled]="disabled" class="no-data-found tb-prompt flex items-center justify-center" translate>filter.no-filters</span>
}
<div class="flex flex-row gap-2" [class.!hidden]="disabled">
<button mat-button mat-stroked-button color="primary"
(click)="addPredicate(false)"
type="button"
matTooltip="{{ 'filter.add-filter' | translate }}"
matTooltipPosition="above">
{{ 'action.add' | translate }}
</button>
<button mat-button mat-raised-button color="primary"
[class.!hidden]="disabled"
<button mat-button
color="primary"
(click)="addPredicate(true)"
type="button"
matTooltip="{{ 'filter.add-complex-filter' | translate }}"
@ -98,5 +83,4 @@
{{ 'filter.add-complex' | translate }}
</button>
</div>
</mat-expansion-panel>
</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
* 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);
}
}
}

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

@ -15,70 +15,75 @@
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-col" [class.!hidden]="dynamicMode || onlyUserDynamicSource">
<div class="flex flex-col">
@switch (valueType) {
@case (valueTypeEnum.STRING) {
<mat-form-field hideRequiredMarker class="mat-block">
<input matInput formControlName="defaultValue" placeholder="{{'filter.value' | translate}}">
</mat-form-field>
}
@case (valueTypeEnum.NUMERIC) {
<mat-form-field hideRequiredMarker class="mat-block">
<input required type="number" matInput formControlName="defaultValue"
placeholder="{{'filter.value' | translate}}">
</mat-form-field>
}
@case (valueTypeEnum.DATE_TIME) {
<tb-datetime formControlName="defaultValue"
dateText="{{ 'filter.date' | translate }}"
required [showLabel]="false"></tb-datetime>
<div class="flex flex-1 flex-row items-center justify-between gap-2" [formGroup]="filterPredicateValueFormGroup">
<div class="flex flex-1 flex-col">
<div class="flex flex-1 flex-col" [class.!hidden]="dynamicMode || onlyUserDynamicSource">
<div class="tb-form-row no-padding no-border column-xs">
@switch (valueType) {
@case (valueTypeEnum.STRING) {
<mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label> {{ hintText | translate }}</mat-label>
<input matInput formControlName="defaultValue" placeholder="{{'filter.value' | translate}}">
</mat-form-field>
}
@case (valueTypeEnum.NUMERIC) {
<mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label> {{ hintText | translate }}</mat-label>
<input required type="number" matInput formControlName="defaultValue"
placeholder="{{'filter.value' | translate}}">
</mat-form-field>
}
@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) {
<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>
<div class="flex flex-col flex-1" [class.!hidden]="!dynamicMode && !onlyUserDynamicSource">
<div formGroupName="dynamicValue" class="flex flex-row justify-start gap-2">
<div class="flex max-w-35% flex-full flex-col">
<mat-form-field hideRequiredMarker class="mat-block">
<mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}">
<mat-option [value]="null">
{{'filter.no-dynamic-value' | translate}}
</mat-option>
@for (sourceType of dynamicValueSourceTypes; track sourceType) {
<mat-option [value]="sourceType">
{{dynamicValueSourceTypeTranslations.get(sourceType) | translate}}
<div class="flex flex-col flex-1" [class.!hidden]="!dynamicMode && !onlyUserDynamicSource">
<div formGroupName="dynamicValue" class="flex flex-row justify-start gap-2">
<div class="tb-form-row no-padding no-border max-w-35% flex-full">
<mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>filter.dynamic-source-type</mat-label>
<mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}">
<mat-option [value]="null">
{{'filter.no-dynamic-value' | translate}}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="tb-hint" translate>filter.dynamic-source-type</div>
</div>
<div class="flex flex-1 flex-col">
<mat-form-field hideRequiredMarker class="mat-block">
<input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}">
</mat-form-field>
<div class="tb-hint" translate>filter.source-attribute</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>
@for (sourceType of dynamicValueSourceTypes; track sourceType) {
<mat-option [value]="sourceType">
{{dynamicValueSourceTypeTranslations.get(sourceType) | translate}}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row no-padding no-border column-xs flex-full">
<mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>filter.source-attribute</mat-label>
<input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}">
</mat-form-field>
</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>
@if (!onlyUserDynamicSource) {
<button mat-icon-button
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.
-->
<div class="flex flex-1 flex-row items-center justify-start gap-2" [formGroup]="filterPredicateFormGroup">
<div class="flex flex-1 flex-col">
<div class="flex flex-1 flex-row items-center justify-start gap-4" [formGroup]="filterPredicateFormGroup">
<div class="tb-form-panel no-border no-padding-right flex-1 items-center" style="flex-direction: row">
@switch (type) {
@case (filterPredicateType.STRING) {
<tb-string-filter-predicate
[allowUserDynamicSource]="allowUserDynamicSource"
[onlyUserDynamicSource]="onlyUserDynamicSource"
formControlName="predicate">
</tb-string-filter-predicate>
<div class="tb-form-row no-border no-padding w-full max-w-30%">
<mat-form-field hideRequiredMarker class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
@for (operation of stringOperations; track operation) {
<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) {
<tb-numeric-filter-predicate
[allowUserDynamicSource]="allowUserDynamicSource"
[onlyUserDynamicSource]="onlyUserDynamicSource"
[valueType]="valueType"
formControlName="predicate">
</tb-numeric-filter-predicate>
<div class="tb-form-row no-border no-padding w-full max-w-30%">
<mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
@for (operation of numericOperations; track operation) {
<mat-option [value]="operation">
{{numericOperationTranslations.get(numericOperationEnum[operation]) | translate}}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
}
@case (filterPredicateType.BOOLEAN) {
<tb-boolean-filter-predicate
[allowUserDynamicSource]="allowUserDynamicSource"
[onlyUserDynamicSource]="onlyUserDynamicSource"
formControlName="predicate">
</tb-boolean-filter-predicate>
<div class="tb-form-row no-border no-padding w-full max-w-30%">
<mat-form-field hideRequiredMarker class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
@for (operation of booleanOperations; track operation) {
<mat-option [value]="operation">
{{booleanOperationTranslations.get(booleanOperationEnum[operation]) | translate}}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
}
@case (filterPredicateType.COMPLEX) {
<tb-complex-filter-predicate
[allowUserDynamicSource]="allowUserDynamicSource"
[onlyUserDynamicSource]="onlyUserDynamicSource"
[key]="key"
[valueType]="valueType"
[displayUserParameters]="displayUserParameters"
formControlName="predicate">
</tb-complex-filter-predicate>
<div class="tb-form-row no-border no-padding flex-1">
<mat-label translate>filter.complex-filter</mat-label>
<button mat-icon-button color="primary"
class="tb-mat-32"
type="button"
(click)="openComplexFilterDialog()"
matTooltip="{{ (disabled ? 'filter.complex-filter' : 'filter.edit-complex-filter') | translate }}"
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>
@if (type !== filterPredicateType.COMPLEX && displayUserParameters) {
<tb-filter-user-info
style="width: 60px;"
class="flex flex-row items-stretch justify-center"
[valueType]="valueType"
[operation]="filterPredicateFormGroup.get('predicate').value?.operation"
[key]="key"
formControlName="userInfo">
</tb-filter-user-info>
<button mat-icon-button color="primary"
class="tb-mat-32"
type="button"
(click)="openFilterUserInfoDialog()"
matTooltip="{{ (disabled ? 'filter.filter-user-params' : 'filter.edit-filter-user-params') | translate }}"
matTooltipPosition="above">
<mat-icon>settings</mat-icon>
</button>
}
</div>

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

@ -14,19 +14,35 @@
/// 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
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 +62,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 +76,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 +121,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 +136,97 @@ 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.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) {
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, 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>
</button>
</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 class="tb-filters-header flex flex-row items-center justify-start">
<span class="max-w-5% flex-full"></span>
<div class="flex max-w-95% flex-full flex-row items-center justify-start">
<div class="tb-header-label flex-1" translate>filter.filter</div>
<div class="tb-header-label flex min-w-20 max-w-20 flex-1 basis-20 flex-col items-center justify-center" translate
style="padding-left: 5px;">filter.editable</div>
<div style="min-width: 132px;"></div>
<div class="tb-form-panel no-border no-padding">
<div class="tb-form-panel stroked no-padding no-gap" [class.!hidden]="!filtersFormArray().length">
<table mat-table [dataSource]="filtersFormArray().controls" class="overflow-hidden bg-transparent">
<ng-container matColumnDef="filter">
<mat-header-cell *matHeaderCellDef translate>filter.filter</mat-header-cell>
<mat-cell *matCellDef="let control">
<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>
<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 mat-dialog-actions>
<button mat-raised-button color="primary"
<button mat-stroked-button color="primary"
type="button"
(click)="addFilter()"
[class.!hidden]="disableAdd"

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

@ -14,39 +14,15 @@
* limitations under the License.
*/
:host {
.tb-filters-header {
min-height: 40px;
padding: 0 11px;
margin: 5px;
.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;
}
}
.mat-mdc-header-cell.mat-column-editable,
.mat-mdc-cell.mat-column-editable,
.mat-mdc-header-cell.mat-column-actions,
.mat-mdc-cell.mat-column-actions {
width: 1%;
white-space: nowrap;
}
}
:host ::ng-deep {
.mat-mdc-dialog-content {
padding-top: 0 !important;
padding-bottom: 0 !important;
.mat-mdc-row:last-child .mat-mdc-cell {
border-bottom: none;
}
}
}

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;
displayedColumns = ['filter', 'editable', 'actions'];
submitted = false;
constructor(protected store: Store<AppState>,

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

@ -26,119 +26,128 @@
</button>
</mat-toolbar>
<div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async" class="flex flex-col">
<section class="entity-key flex flex-row gap-2">
<section class="flex flex-full flex-row gap-2"
[class]="{
<section class="tb-form-panel no-border no-padding">
<section class="tb-form-panel">
<div class="tb-form-panel-title">{{ 'filter.key-filter' | translate }}</div>
<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-70%': !isConstantKeyType
}"
formGroupName="key">
<mat-form-field class="mat-block max-w-40% flex-full">
<mat-label translate>filter.key-type.key-type</mat-label>
<mat-select required formControlName="type">
@for (type of entityKeyTypes; track type) {
<mat-option [value]="type">
{{entityKeyTypeTranslations.get(type) | translate}}
</mat-option>
formGroupName="key">
<mat-form-field class="mat-block max-w-40% flex-full" appearance="outline">
<mat-label translate>filter.key-type.key-type</mat-label>
<mat-select required formControlName="type">
@for (type of entityKeyTypes; track type) {
<mat-option [value]="type">
{{entityKeyTypeTranslations.get(type) | translate}}
</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-form-field>
<mat-form-field class="mat-block max-w-60% flex-full">
<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-autocomplete autoActiveFirstOption #keyName="matAutocomplete">
@for (keyName of filteredKeysName | async; track keyName) {
<mat-option [value]="keyName">
<span [innerHTML]="keyName | highlight:searchText"></span>
<mat-autocomplete autoActiveFirstOption #keyName="matAutocomplete">
@for (keyName of filteredKeysName | async; track keyName) {
<mat-option [value]="keyName">
<span [innerHTML]="keyName | highlight:searchText"></span>
</mat-option>
}
</mat-autocomplete>
@if (keyFilterFormGroup.get('key.key').hasError('required')) {
<mat-error>
{{ 'filter.key-name-required' | translate }}
</mat-error>
}
</mat-form-field>
</div>
<mat-form-field class="mat-block tb-value-type flex-full" appearance="outline"
[class]="{
'max-w-20%': isConstantKeyType,
'max-w-30%': !isConstantKeyType
}">
<mat-label translate>filter.value-type.value-type</mat-label>
<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-autocomplete>
@if (keyFilterFormGroup.get('key.key').hasError('required')) {
</mat-select>
@if (keyFilterFormGroup.get('valueType').hasError('required')) {
<mat-error>
{{ 'filter.key-name-required' | translate }}
{{ 'filter.value-type-required' | translate }}
</mat-error>
}
</mat-form-field>
</section>
<mat-form-field class="mat-block tb-value-type flex-full"
[class]="{
'max-w-20%': isConstantKeyType,
'max-w-30%': !isConstantKeyType
}">
<mat-label translate>filter.value-type.value-type</mat-label>
<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-select>
@if (keyFilterFormGroup.get('valueType').hasError('required')) {
<mat-error>
{{ 'filter.value-type-required' | translate }}
</mat-error>
}
</mat-form-field>
@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>
@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" appearance="outline">
<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" appearance="outline">
<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>
</section>
@if (keyFilterFormGroup.get('valueType').value) {
<tb-filter-predicate-list
[allowUserDynamicSource]="data.allowUserDynamicSource"
[onlyUserDynamicSource]="isConstantKeyType"
[displayUserParameters]="data.displayUserParameters"
[valueType]="keyFilterFormGroup.get('valueType').value"
[key]="keyFilterFormGroup.get('key.key').value"
formControlName="predicates">
</tb-filter-predicate-list>
<section class="tb-form-panel">
<div class="flex flex-row items-center justify-between">
<div class="tb-form-panel-title">{{ 'filter.filters' | translate }}
</div>
</div>
<tb-filter-predicate-list
[allowUserDynamicSource]="data.allowUserDynamicSource"
[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 mat-dialog-actions class="flex items-center justify-end">
<button mat-button color="primary"

Loading…
Cancel
Save