TSK-1554: Rework task-filter component with Angular Material
This commit is contained in:
parent
485a11395e
commit
d4bd0b9ef1
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue