TSK-1554: Rework task-filter component with Angular Material

This commit is contained in:
Sofie Hofmann 2021-04-07 09:06:10 +02:00
parent 485a11395e
commit d4bd0b9ef1
11 changed files with 197 additions and 94 deletions

View File

@ -22,7 +22,7 @@ import { ButtonAction } from '../../models/button-action';
import { Pair } from '../../../shared/models/pair';
import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-filter-parameter';
import { FilterSelectors } from '../../../shared/store/filter-store/filter.selectors';
import { SetFilter } from '../../../shared/store/filter-store/filter.actions';
import { SetWorkbasketFilter } from '../../../shared/store/filter-store/filter.actions';
export enum Side {
AVAILABLE,
@ -283,8 +283,10 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy
this.availableDistributionTargetsFilterClone = this.availableDistributionTargets;
this.selectAllRight = true;
this.selectAllLeft = true;
this.store.dispatch(new SetFilter(this.selectedDistributionTargetsFilter, 'selectedDistributionTargets'));
this.store.dispatch(new SetFilter(this.availableDistributionTargetsFilter, 'availableDistributionTargets'));
this.store.dispatch(new SetWorkbasketFilter(this.selectedDistributionTargetsFilter, 'selectedDistributionTargets'));
this.store.dispatch(
new SetWorkbasketFilter(this.availableDistributionTargetsFilter, 'availableDistributionTargets')
);
}
onClear() {

View File

@ -1,46 +1,39 @@
<div class="row">
<div class="col-xs-2">
<taskana-shared-number-picker [(ngModel)]="filter.priority[0]"
(keyup.enter)="search()" title="priority"
id="display-priority-filter"></taskana-shared-number-picker>
<div class="task-filter">
<!-- INPUT FIELDS -->
<div class="task-filter__input-fields">
<div class="task-filter__row">
<!-- FILTER BY NAME AND OWNER -->
<mat-form-field class="task-filter__input-field--large" style="margin-right: 16px;" matTooltip="Filter Tasks by name">
<mat-label>Filter by name</mat-label>
<input matInput type="text" placeholder="Name" [(ngModel)]="filter['name-like'][0]" (keyup.enter)="search()" (keyup)="updateState()">
</mat-form-field>
<!-- FILTER BY PRIORITY -->
<mat-form-field class="task-filter__input-field--small" matTooltip="Filter Tasks by priority">
<mat-label>Filter by priority</mat-label>
<input matInput type="number" placeholder="Priority" [(ngModel)]="filter.priority[0]" (keyup.enter)="search()" (ngModelChange)="updateState()">
</mat-form-field>
</div>
<div class="task-filter__row">
<mat-form-field class="task-filter__input-field--large" style="margin-right: 16px" matTooltip="Filter Tasks by owner">
<mat-label>Filter by owner</mat-label>
<input matInput type="text" placeholder="Owner" [(ngModel)]="filter['owner-like'][0]" (keyup.enter)="search()" (keyup)="updateState()">
</mat-form-field>
<!-- FILTER BY TASK STATE -->
<mat-form-field class="task-filter__input-field--small" matTooltip="Filter Tasks by status">
<mat-label>Filter by status</mat-label>
<mat-select [value]="filter.state && filter.state[0] ? filter.state[0] : undefined">
<mat-option class="types-selector__options" *ngFor="let state of allStates | mapValues" [value]="state.key" (click)="setStatus(state.key)">
{{ state.value }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="col-xs-4">
<input type="text" [(ngModel)]="filter['name-like'][0]" (keyup.enter)="search()"
class="form-control" id="display-name-filter"
placeholder="Filter name">
</div>
<div class="col-xs-4">
<input type="text" [(ngModel)]="filter['owner-like'][0]" (keyup.enter)="search()"
class="form-control" id="display-owner-filter"
placeholder="Filter owner">
</div>
<button (click)="clear(); search()" class="btn btn-default pull-right margin-right" type="button"
data-toggle="tooltip"
title="Clear">
<span class="material-icons md-20 blue">clear</span>
</button>
</div>
<div class="row">
<div class="dropdown col-xs-2 col-xs-offset-2">
<button class="btn btn-default" type="button" data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="true"
title="State: {{filter.state && filter.state[0] ? filter.state[0] : 'All'}}">
<span>{{filter.state ? filter.state[0] : 'All'}}</span>
</button>
<ul class="dropdown-menu dropdown-menu-users" role="menu">
<li>
<a *ngFor="let state of allStates | mapValues" type="button"
(click)="selectState(state.key); search()"
data-toggle="tooltip" [title]="state.value">
<label class="blue">{{state.value}}</label>
</a>
</li>
</ul>
</div>
<button (click)="search()" type="button" class="btn btn-default pull-right margin-right"
data-toggle="tooltip"
title="Search">
<span class="material-icons md-20 blue">search</span>
</button>
</div>

View File

@ -1,15 +1,34 @@
.dropdown-menu-users {
& > li {
margin-bottom: 5px;
@import 'src/theme/_colors.scss';
.task-filter {
display: flex;
flex-direction: column;
&__button--primary {
background: $aquamarine;
color: white;
position: relative;
top: 30px;
}
margin-left: 15px;
}
&__button--secondary {
position: relative;
top: 24px;
}
button.btn.btn-default.pull-right.margin-right {
margin-top: 1px;
}
.task-filter__row {
display: flex;
flex-direction: row;
}
&__input-field--large {
width: 320px;
}
&__input-field--small {
flex-grow: 1;
min-width: 140px;
}
.blue {
color: #2e9eca;
}

View File

@ -1,29 +1,39 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ALL_STATES, TaskState } from '../../models/task-state';
import { TaskQueryFilterParameter } from '../../models/task-query-filter-parameter';
import { Actions, ofActionCompleted, Store } from '@ngxs/store';
import { ClearTaskFilter, SetTaskFilter } from '../../store/filter-store/filter.actions';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
@Component({
selector: 'taskana-shared-task-filter',
templateUrl: './task-filter.component.html',
styleUrls: ['./task-filter.component.scss']
})
export class TaskFilterComponent implements OnInit {
export class TaskFilterComponent implements OnInit, OnDestroy {
filter: TaskQueryFilterParameter;
@Output() performFilter = new EventEmitter<TaskQueryFilterParameter>();
destroy$ = new Subject<void>();
allStates: Map<TaskState, string> = ALL_STATES;
ngOnInit(): void {
constructor(private store: Store, private ngxsActions$: Actions) {}
ngOnInit() {
this.clear();
this.ngxsActions$.pipe(ofActionCompleted(ClearTaskFilter), takeUntil(this.destroy$)).subscribe(() => this.clear());
}
selectState(state: TaskState) {
setStatus(state: TaskState) {
this.filter.state = state ? [state] : [];
this.updateState();
}
search() {
this.performFilter.emit(this.filter);
// TODO: filter tasks when pressing 'enter'
search() {}
updateState() {
this.store.dispatch(new SetTaskFilter(this.filter));
}
clear() {
@ -33,4 +43,9 @@ export class TaskFilterComponent implements OnInit {
'owner-like': []
};
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -2,7 +2,7 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { ALL_TYPES, WorkbasketType } from '../../models/workbasket-type';
import { WorkbasketQueryFilterParameter } from '../../models/workbasket-query-filter-parameter';
import { Select, Store } from '@ngxs/store';
import { ClearFilter, SetFilter } from '../../store/filter-store/filter.actions';
import { ClearWorkbasketFilter, SetWorkbasketFilter } from '../../store/filter-store/filter.actions';
import { FilterSelectors } from '../../store/filter-store/filter.selectors';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -60,7 +60,7 @@ export class WorkbasketFilterComponent implements OnInit, OnDestroy {
}
clear() {
this.store.dispatch(new ClearFilter(this.component));
this.store.dispatch(new ClearWorkbasketFilter(this.component));
}
selectType(type: WorkbasketType) {
@ -68,7 +68,7 @@ export class WorkbasketFilterComponent implements OnInit, OnDestroy {
}
search() {
this.store.dispatch(new SetFilter(this.filter, this.component));
this.store.dispatch(new SetWorkbasketFilter(this.filter, this.component));
}
ngOnDestroy() {

View File

@ -1,11 +1,23 @@
import { WorkbasketQueryFilterParameter } from '../../models/workbasket-query-filter-parameter';
import { TaskQueryFilterParameter } from '../../models/task-query-filter-parameter';
export class SetFilter {
static readonly type = '[Workbasket filter] Set filter parameter';
// Workbasket Filter
export class SetWorkbasketFilter {
static readonly type = '[Workbasket filter] Set workbasket filter parameter';
constructor(public parameters: WorkbasketQueryFilterParameter, public component: string) {}
}
export class ClearFilter {
static readonly type = '[Workbasket filter] Clear filter parameter';
export class ClearWorkbasketFilter {
static readonly type = '[Workbasket filter] Clear workbasket filter parameter';
constructor(public component: string) {}
}
// Task Filter
export class SetTaskFilter {
static readonly type = '[Task filter] Set task filter parameter';
constructor(public parameters: TaskQueryFilterParameter) {}
}
export class ClearTaskFilter {
static readonly type = '[Task filter] Clear task filter parameter';
}

View File

@ -1,6 +1,7 @@
import { FilterState, FilterStateModel } from './filter.state';
import { Selector } from '@ngxs/store';
import { WorkbasketQueryFilterParameter } from '../../models/workbasket-query-filter-parameter';
import { TaskQueryFilterParameter } from '../../models/task-query-filter-parameter';
export class FilterSelectors {
@Selector([FilterState])
@ -17,4 +18,9 @@ export class FilterSelectors {
static getWorkbasketListFilter(state: FilterStateModel): WorkbasketQueryFilterParameter {
return state.workbasketList;
}
@Selector([FilterState])
static getTaskFilter(state: FilterStateModel): TaskQueryFilterParameter {
return state.tasks;
}
}

View File

@ -1,9 +1,10 @@
import { Action, NgxsOnInit, State, StateContext } from '@ngxs/store';
import { Observable, of } from 'rxjs';
import { WorkbasketQueryFilterParameter } from '../../models/workbasket-query-filter-parameter';
import { ClearFilter, SetFilter } from './filter.actions';
import { ClearTaskFilter, ClearWorkbasketFilter, SetTaskFilter, SetWorkbasketFilter } from './filter.actions';
import { TaskQueryFilterParameter } from '../../models/task-query-filter-parameter';
const emptyFilter: WorkbasketQueryFilterParameter = {
const emptyWorkbasketFilter: WorkbasketQueryFilterParameter = {
'description-like': [],
'key-like': [],
'name-like': [],
@ -11,10 +12,20 @@ const emptyFilter: WorkbasketQueryFilterParameter = {
type: []
};
const emptyTaskFilter: TaskQueryFilterParameter = {
'name-like': [],
'owner-like': [],
state: [],
priority: [],
'por.value': [],
'wildcard-search-fields': [],
'wildcard-search-value': []
};
@State<FilterStateModel>({ name: 'FilterState' })
export class FilterState implements NgxsOnInit {
@Action(SetFilter)
setAvailableDistributionTargetsFilter(ctx: StateContext<FilterStateModel>, action: SetFilter): Observable<null> {
@Action(SetWorkbasketFilter)
setWorkbasketFilter(ctx: StateContext<FilterStateModel>, action: SetWorkbasketFilter): Observable<null> {
const currentState = ctx.getState()[action.component];
const param = action.parameters;
const filter: WorkbasketQueryFilterParameter = {
@ -33,22 +44,66 @@ export class FilterState implements NgxsOnInit {
return of(null);
}
@Action(ClearFilter)
clearFilter(ctx: StateContext<FilterStateModel>, action: ClearFilter): Observable<null> {
@Action(ClearWorkbasketFilter)
clearWorkbasketFilter(ctx: StateContext<FilterStateModel>, action: ClearWorkbasketFilter): Observable<null> {
ctx.setState({
...ctx.getState(),
[action.component]: { ...emptyFilter }
[action.component]: { ...emptyWorkbasketFilter }
});
return of(null);
}
@Action(SetTaskFilter)
setTaskFilter(ctx: StateContext<FilterStateModel>, action: SetTaskFilter): Observable<null> {
const param = action.parameters;
let filter = { ...ctx.getState().tasks };
Object.keys(param).forEach((key) => {
filter[key] = [...param[key]];
});
const isWildcardSearch = filter['wildcard-search-value'].length !== 0 && filter['wildcard-search-value'] !== [''];
filter['wildcard-search-fields'] = isWildcardSearch ? this.initWildcardFields() : [];
// Delete wildcard search field 'NAME' if 'name-like' exists
if (filter['name-like'].length > 0 && filter['name-like'][0] !== '') {
filter['wildcard-search-fields'].shift();
}
ctx.setState({
...ctx.getState(),
tasks: filter
});
return of(null);
}
@Action(ClearTaskFilter)
clearTaskFilter(ctx: StateContext<FilterStateModel>): Observable<null> {
ctx.setState({
...ctx.getState(),
tasks: { ...emptyTaskFilter }
});
return of(null);
}
initWildcardFields() {
let wildcardSearchFields = ['NAME', 'DESCRIPTION'];
[...Array(16).keys()].map((number) => {
wildcardSearchFields.push(`CUSTOM_${number + 1}`);
});
return wildcardSearchFields;
}
ngxsOnInit(ctx: StateContext<FilterStateModel>): void {
ctx.setState({
...ctx.getState(),
availableDistributionTargets: emptyFilter,
selectedDistributionTargets: emptyFilter,
workbasketList: emptyFilter
availableDistributionTargets: emptyWorkbasketFilter,
selectedDistributionTargets: emptyWorkbasketFilter,
workbasketList: emptyWorkbasketFilter,
tasks: emptyTaskFilter
});
}
}
@ -57,4 +112,5 @@ export interface FilterStateModel {
availableDistributionTargets: WorkbasketQueryFilterParameter;
selectedDistributionTargets: WorkbasketQueryFilterParameter;
workbasketList: WorkbasketQueryFilterParameter;
tasks: TaskQueryFilterParameter;
}

View File

@ -37,7 +37,7 @@ import { RequestInProgressService } from '../../services/request-in-progress/req
import { WorkbasketType } from '../../models/workbasket-type';
import { TaskanaDate } from '../../util/taskana.date';
import { DomainService } from '../../services/domain/domain.service';
import { ClearFilter } from '../filter-store/filter.actions';
import { ClearWorkbasketFilter } from '../filter-store/filter.actions';
class InitializeStore {
static readonly type = '[Workbasket] Initializing state';
@ -136,8 +136,8 @@ export class WorkbasketState implements NgxsAfterBootstrap {
.replace(/(workbaskets).*/g, `workbaskets/(detail:${action.workbasketId})?tab=${selectedComponent}`)
);
ctx.dispatch(new ClearFilter('selectedDistributionTargets'));
ctx.dispatch(new ClearFilter('availableDistributionTargets'));
ctx.dispatch(new ClearWorkbasketFilter('selectedDistributionTargets'));
ctx.dispatch(new ClearWorkbasketFilter('availableDistributionTargets'));
})
);
}
@ -224,8 +224,8 @@ export class WorkbasketState implements NgxsAfterBootstrap {
badgeMessage: `Copying workbasket: ${workbasket.key}`
});
ctx.dispatch(new ClearFilter('selectedDistributionTargets'));
ctx.dispatch(new ClearFilter('availableDistributionTargets'));
ctx.dispatch(new ClearWorkbasketFilter('selectedDistributionTargets'));
ctx.dispatch(new ClearWorkbasketFilter('availableDistributionTargets'));
return of(null);
}
@ -261,8 +261,8 @@ export class WorkbasketState implements NgxsAfterBootstrap {
workbasketDistributionTargets: distributionTargets
});
ctx.dispatch(new ClearFilter('selectedDistributionTargets'));
ctx.dispatch(new ClearFilter('availableDistributionTargets'));
ctx.dispatch(new ClearWorkbasketFilter('selectedDistributionTargets'));
ctx.dispatch(new ClearWorkbasketFilter('availableDistributionTargets'));
return of(null);
})
@ -433,8 +433,8 @@ export class WorkbasketState implements NgxsAfterBootstrap {
selectedWorkbasket,
action: ACTION.READ
});
ctx.dispatch(new ClearFilter('selectedDistributionTargets'));
ctx.dispatch(new ClearFilter('availableDistributionTargets'));
ctx.dispatch(new ClearWorkbasketFilter('selectedDistributionTargets'));
ctx.dispatch(new ClearWorkbasketFilter('availableDistributionTargets'));
});
}
this.requestInProgressService.setRequestInProgress(false);

View File

@ -35,13 +35,13 @@
<!-- SEARCH BY TYPE -->
<mat-form-field style="padding-right: 8px">
<mat-label>Type</mat-label>
<mat-label>Filter by type</mat-label>
<input matInput type="text" placeholder="Type" [(ngModel)]="resultType" (keyup.enter)="searchBasket()">
</mat-form-field>
<!-- SEARCH BY VALUE-->
<mat-form-field style="padding-right: 8px">
<mat-label>Value</mat-label>
<mat-label>Filter by value</mat-label>
<input matInput type="text" placeholder="Value" [(ngModel)]="resultValue" (keyup.enter)="searchBasket()">
</mat-form-field>

View File

@ -33,7 +33,7 @@
<!-- EMPTY TASK-LIST -->
<ng-template #empty_list>
<div style="margin-top: 60px" class="container-no-items center-block">
<h3 class="grey">Select a workbasket</h3>
<h3 class="grey">Select a Workbasket</h3>
<svg-icon class="img-responsive empty-icon workbasket-icon" src="./assets/icons/wb-empty.svg"></svg-icon>
</div>
</ng-template>