TSK-1612: Divide workplace into Workbasket search and Task search

This commit is contained in:
Sofie Hofmann 2021-04-07 09:19:54 +02:00
parent d4bd0b9ef1
commit a6535b408a
8 changed files with 229 additions and 178 deletions

View File

@ -14,6 +14,14 @@
*ngIf="monitorAccess" (click)="toggleSidenav()">Monitor</a>
<a mat-list-item class="navlist__item navlist__workplace" [routerLink]=[workplaceUrl]
[routerLinkActive]="['active']" *ngIf="workplaceAccess" (click)="toggleSidenav()">Workplace</a>
<div class="navlist__admin-item">
<a mat-list-item class="navlist__item navlist__workplace--workbaskets"
[routerLinkActive]="['active__sub']" *ngIf="workplaceAccess" [routerLink]=[workplaceUrl] [queryParams]="{component: 'workbaskets'}"
(click)="toggleSidenav()">Workbaskets</a>
<a mat-list-item class="navlist__item navlist__workplace--task-search"
[routerLinkActive]="['active__sub']" *ngIf="workplaceAccess" [routerLink]=[workplaceUrl] [queryParams]="{component: 'task-search'}"
(click)="toggleSidenav()">Task Search</a>
</div>
<a mat-list-item class="navlist__item navlist__history" [routerLink]=[historyUrl] [routerLinkActive]="['active']"
*ngIf="historyAccess" (click)="toggleSidenav()">History</a>
</mat-nav-list>
</mat-nav-list>

View File

@ -1,110 +1,118 @@
<div class="task-list-toolbar">
<!-- SEARCH INPUT -->
<div class="task-list-toolbar__search">
<mat-tab-group animationDuration="0ms" [selectedIndex]="activeTab" (click)="onTabChange($event)">
<div class="task-list-toolbar__filter">
<!-- WORKBASKETS -->
<mat-tab label="Workbaskets">
<!-- BUTTON TO FURTHER FILTER OPTIONS -->
<button mat-stroked-button style="margin-right: 8px; min-width: 1%" class="task-list-toolbar__button--secondary" matTooltip="Display more filter options" (click)="toolbarState=!toolbarState">
<mat-icon *ngIf="!toolbarState">keyboard_arrow_down</mat-icon>
<mat-icon *ngIf="toolbarState">keyboard_arrow_up</mat-icon>
</button>
<div class="task-list-toolbar__tab">
<!-- BUTTON TO FURTHER FILTER OPTIONS -->
<button mat-stroked-button style="min-width: 1%" class="task-list-toolbar__button--secondary" matTooltip="Display more filter options" (click)="toolbarState=!toolbarState">
<mat-icon *ngIf="!toolbarState">keyboard_arrow_down</mat-icon>
<mat-icon *ngIf="toolbarState">keyboard_arrow_up</mat-icon>
</button>
<div class="task-list-toolbar__spacer"> </div>
<!-- SEARCH FOR WORKBASKET -->
<div class="task-list-toolbar__filter-input">
<mat-form-field class="task-list-toolbar__filter--workbasket">
<mat-label>Select Workbasket</mat-label>
<input matInput
type="text"
placeholder="Workbasket"
[(ngModel)]="resultName"
(ngModelChange)="filterWorkbasketNames()"
[matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="searchBasket(); navigateBack()">
<mat-option *ngFor="let workbasketName of filteredWorkbasketNames" [value]="workbasketName">
{{workbasketName}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
<!-- SEARCH FOR WORKBASKET -->
<mat-form-field *ngIf="searchSelected === search.byWorkbasket" class="task-list-toolbar__filter--workbasket">
<mat-label>Search for Workbasket</mat-label>
<input matInput
type="text"
placeholder="Workbasket"
[(ngModel)]="resultName"
(ngModelChange)="filterWorkbasketNames()"
[matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="searchBasket()">
<mat-option *ngFor="let workbasketName of filteredWorkbasketNames" [value]="workbasketName">
{{workbasketName}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<!-- SEARCH BY TYPE AND VALUE-->
<div class="task-list-toolbar__filter--type-value" *ngIf="searchSelected === search.byTypeAndValue">
<!-- SEARCH BY TYPE -->
<mat-form-field style="padding-right: 8px">
<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>Filter by value</mat-label>
<input matInput type="text" placeholder="Value" [(ngModel)]="resultValue" (keyup.enter)="searchBasket()">
</mat-form-field>
<div class="task-list-toolbar__spacer"> </div>
<!-- SEARCH BUTTON -->
<button class="task-list-toolbar__button--primary" style="top: 11px; min-width: 1%"
mat-flat-button matTooltip="Search by given type and value" (click)="searchBasket()">
<mat-icon class="md-20">search</mat-icon>
<button class="task-list-toolbar__button--primary" style="top: 11px"
mat-stroked-button matTooltip="Filter Tasks" (click)="onFilter(); navigateBack()">
<mat-icon>search</mat-icon>
</button>
<!-- CLEAR BUTTON -->
<button class="task-list-toolbar__button--secondary" style="top: 11px;"
mat-stroked-button matTooltip="Clear Filter" (click)="onClearFilter(); navigateBack()">
<mat-icon>clear</mat-icon>
</button>
</div>
</div>
</mat-tab>
<!-- TASK SEARCH -->
<mat-tab label="Task search">
<!-- SELECT WHETHER TO SEARCH BY WORKBASKET OR BY TYPE / VALUE -->
<div class="task-list-toolbar__select-search">
<button mat-stroked-button class="task-list-toolbar__button--secondary" [matMenuTriggerFor]="menu" matTooltip="Select search">
Search by
<svg-icon *ngIf="searchSelected==='workbasket'" style="top: -10px; margin: 0 4px" class="task-list-toolbar__workbasket-icon" src="./assets/icons/wb-empty.svg"></svg-icon>
<mat-icon *ngIf="searchSelected==='type-and-value'" style="fill: #555" class="">title</mat-icon>
</button>
<div class="task-list-toolbar__tab">
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="selectSearch(search.byWorkbasket)">
<span>
<svg-icon style="left: 4px; margin-right: 20px" class="task-list-toolbar__workbasket-icon" src="./assets/icons/wb-empty.svg"></svg-icon>
Workbasket
</span>
<!-- BUTTON TO FURTHER FILTER OPTIONS -->
<button mat-stroked-button style="min-width: 1%" class="task-list-toolbar__button--secondary"
matTooltip="Display more filter options" (click)="toolbarState=!toolbarState">
<mat-icon *ngIf="!toolbarState">keyboard_arrow_down</mat-icon>
<mat-icon *ngIf="toolbarState">keyboard_arrow_up</mat-icon>
</button>
<button mat-menu-item (click)="selectSearch(search.byTypeAndValue)">
<span>
<mat-icon style="color: #555" class="">title</mat-icon>
Type and Value
</span>
<div class="task-list-toolbar__spacer"> </div>
<!-- FILTER TASKS -->
<div class="task-list-toolbar__filter-input">
<mat-form-field>
<mat-label>Filter Tasks</mat-label>
<input matInput type="text" placeholder="Filter" [(ngModel)]="filterInput" (keyup.enter)="onFilter(); navigateBack(); this.searched = true;" (ngModelChange)="updateState()">
</mat-form-field>
</div>
<div class="task-list-toolbar__spacer"> </div>
<!-- SEARCH BUTTON -->
<button class="task-list-toolbar__button--primary" style="top: 11px"
mat-stroked-button matTooltip="Filter Tasks" (click)="onFilter(); navigateBack(); this.searched = true">
<mat-icon>search</mat-icon>
</button>
</mat-menu>
</div>
</div>
<!-- CLEAR BUTTON -->
<button class="task-list-toolbar__button--secondary" style="top: 11px;"
mat-stroked-button matTooltip="Clear Filter" (click)="onClearFilter(); navigateBack(); this.searched = true;">
<mat-icon>clear</mat-icon>
</button>
</div>
</mat-tab>
</mat-tab-group>
<!-- FURTHER FILTER OPTIONS -->
<div *ngIf="toolbarState">
<taskana-shared-task-filter (performFilter)="filtering($event)"></taskana-shared-task-filter>
<div *ngIf="toolbarState" class="task-list-toolbar__additional-filter">
<taskana-shared-task-filter> </taskana-shared-task-filter>
</div>
<!-- ADDITIONAL MENU WHEN TASK LIST IS DISPLAYED -->
<div *ngIf="searched" class="task-list-toolbar__additional-toolbar">
<!-- ADD TASK BUTTON -->
<button class="task-list-toolbar__button--primary"
mat-flat-button matTooltip="Add Task" (click)="createTask()">
<button class="task-list-toolbar__button--primary" mat-flat-button matTooltip="Add Task" (click)="createTask()">
Add
<mat-icon class="md-20">add</mat-icon>
</button>
<!-- SORT TASKS BUTTON -->
<taskana-shared-sort
[sortingFields]="sortingFields" [defaultSortBy]="taskDefaultSortBy" (performSorting)="sorting($event)">
</taskana-shared-sort>
<taskana-shared-sort
[sortingFields]="sortingFields" [defaultSortBy]="taskDefaultSortBy" (performSorting)="sorting($event)">
</taskana-shared-sort>
</div>

View File

@ -1,22 +1,28 @@
@import 'src/theme/_colors.scss';
.task-list-toolbar {
padding: 4px 16px 16px 16px;
// padding: 4px 16px 16px 16px;
padding: 0 0 16px 0;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
&__search {
&__tab {
display: flex;
justify-content: space-between;
height: 54px;
margin: 8px 16px 0 16px;
}
&__filter {
display: flex;
&__spacer {
flex-grow: 1;
}
&__filter--type-value {
&__additional-toolbar {
display: flex;
justify-content: space-between;
margin: 0 16px;
}
&__additional-filter {
position: relative;
top: -8px;
margin: 0 16px;
}
&__button--primary {
@ -31,20 +37,12 @@
top: 12px;
color: #555;
}
&__workbasket-icon {
width: 16px;
height: 16px;
fill: #555;
}
&__additional-toolbar {
display: flex;
margin-top: 12px;
}
}
::ng-deep .task-list-toolbar__filter--type-value .mat-form-field-wrapper {
width: 100px;
::ng-deep .mat-tab-labels {
background-color: $light-grey;
}
::ng-deep .task-list-toolbar__filter-input .mat-form-field-wrapper {
width: 240px;
}

View File

@ -5,13 +5,14 @@ import { TaskService } from 'app/workplace/services/task.service';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { Sorting, TASK_SORT_PARAMETER_NAMING, TaskQuerySortParameter } from 'app/shared/models/sorting';
import { expandDown } from 'app/shared/animations/expand.animation';
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { WorkplaceService } from 'app/workplace/services/workplace.service';
import { ObjectReference } from 'app/workplace/models/object-reference';
import { TaskQueryFilterParameter } from '../../../shared/models/task-query-filter-parameter';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { TaskanaEngineService } from '../../../shared/services/taskana-engine/taskana-engine.service';
import { Actions, ofActionCompleted, Store } from '@ngxs/store';
import { ClearTaskFilter, SetTaskFilter } from '../../../shared/store/filter-store/filter.actions';
export enum Search {
byWorkbasket = 'workbasket',
@ -45,8 +46,8 @@ export class TaskListToolbarComponent implements OnInit {
search = Search;
searchSelected: Search = Search.byWorkbasket;
resultType = '';
resultValue = '';
activeTab: number = 0;
filterInput = '';
destroy$ = new Subject<void>();
@ -56,10 +57,16 @@ export class TaskListToolbarComponent implements OnInit {
private workbasketService: WorkbasketService,
private workplaceService: WorkplaceService,
private router: Router,
private route: ActivatedRoute
private route: ActivatedRoute,
private store: Store,
private ngxsActions$: Actions
) {}
ngOnInit() {
this.ngxsActions$.pipe(ofActionCompleted(ClearTaskFilter), takeUntil(this.destroy$)).subscribe(() => {
this.filterInput = '';
});
this.workbasketService
.getAllWorkBaskets()
.pipe(takeUntil(this.destroy$))
@ -88,24 +95,58 @@ export class TaskListToolbarComponent implements OnInit {
this.taskService
.getSelectedTask()
.pipe(takeUntil(this.destroy$))
.subscribe((t) => {
if (!this.resultName) {
this.resultName = t.workbasketSummary.name;
this.resultId = t.workbasketSummary.workbasketId;
this.currentBasket = t.workbasketSummary;
this.workplaceService.selectWorkbasket(this.currentBasket);
this.workbasketSelected = true;
.subscribe((task) => {
if (typeof task !== 'undefined') {
const workbasketSummary = task.workbasketSummary;
if (this.searchSelected === this.search.byWorkbasket && this.resultName !== workbasketSummary.name) {
this.resultName = workbasketSummary.name;
this.resultId = workbasketSummary.workbasketId;
this.currentBasket = workbasketSummary;
this.workplaceService.selectWorkbasket(this.currentBasket);
this.workbasketSelected = true;
}
}
});
if (this.route.snapshot.queryParams.search === this.search.byTypeAndValue) {
this.searchSelected = this.search.byTypeAndValue;
}
this.route.queryParams.subscribe((params) => {
const component = params.component;
if (component == 'workbaskets') {
this.activeTab = 0;
if (this.currentBasket) {
this.resultName = this.currentBasket.name;
this.resultId = this.currentBasket.workbasketId;
}
this.selectSearch(this.search.byWorkbasket);
}
if (component == 'task-search') {
this.activeTab = 1;
this.searched = true;
this.selectSearch(this.search.byTypeAndValue);
}
});
if (this.router.url.includes('taskdetail')) {
this.searched = true;
}
}
onTabChange(search) {
const tab = search.path[0].innerText;
if (tab === 'Workbaskets') {
this.router.navigate(['taskana/workplace'], { queryParams: { component: 'workbaskets' } });
}
if (tab === 'Task search') {
this.router.navigate(['taskana/workplace'], { queryParams: { component: 'task-search' } });
}
}
updateState() {
const wildcardFilter: TaskQueryFilterParameter = {
'wildcard-search-value': [this.filterInput]
};
this.store.dispatch(new SetTaskFilter(wildcardFilter));
}
filterWorkbasketNames() {
this.filteredWorkbasketNames = this.workbasketNames.filter((value) =>
value.toLowerCase().includes(this.resultName.toLowerCase())
@ -115,60 +156,58 @@ export class TaskListToolbarComponent implements OnInit {
searchBasket() {
this.toolbarState = false;
this.workbasketSelected = true;
if (this.searchSelected === this.search.byTypeAndValue) {
const objectReference = new ObjectReference();
objectReference.type = this.resultType;
objectReference.value = this.resultValue;
this.workplaceService.selectObjectReference(objectReference);
this.searched = true;
} else {
if (this.workbaskets) {
this.workbaskets.forEach((workbasket) => {
if (workbasket.name === this.resultName) {
this.resultId = workbasket.workbasketId;
this.currentBasket = workbasket;
this.searched = true;
this.workplaceService.selectWorkbasket(this.currentBasket);
}
});
if (!this.resultId) {
delete this.currentBasket;
this.workplaceService.selectWorkbasket();
if (this.searchSelected === this.search.byWorkbasket && this.workbaskets) {
this.workbaskets.forEach((workbasket) => {
if (workbasket.name === this.resultName) {
this.resultId = workbasket.workbasketId;
this.currentBasket = workbasket;
this.workplaceService.selectWorkbasket(this.currentBasket);
}
});
this.searched = !!this.currentBasket;
if (!this.resultId) {
delete this.currentBasket;
this.workplaceService.selectWorkbasket();
}
}
this.resultId = '';
this.router.navigate(['']);
}
navigateBack() {
this.router.navigate([''], { queryParamsHandling: 'merge' });
}
sorting(sort: Sorting<TaskQuerySortParameter>) {
this.performSorting.emit(sort);
}
filtering(filterBy: TaskQueryFilterParameter) {
this.performFilter.emit(filterBy);
onFilter() {
this.performFilter.emit();
}
onClearFilter() {
this.store.dispatch(new ClearTaskFilter()).subscribe(() => {
this.performFilter.emit();
});
}
createTask() {
this.taskService.selectTask();
this.router.navigate([{ outlets: { detail: 'taskdetail/new-task' } }], { relativeTo: this.route });
this.router.navigate([{ outlets: { detail: 'taskdetail/new-task' } }], {
relativeTo: this.route,
queryParamsHandling: 'merge'
});
}
selectSearch(type: Search) {
this.searched = false;
delete this.resultId;
delete this.currentBasket;
this.selectSearchType.emit(type);
this.searchSelected = type;
const navigationExtras: NavigationExtras = {
queryParams: { search: type }
};
this.router.navigate([''], navigationExtras);
delete this.resultId;
this.selectSearchType.emit(type);
this.searchBasket();
this.onClearFilter();
}
ngOnDestroy() {

View File

@ -1,10 +1,10 @@
<div class="footer-space-pagination-list">
<div #wbToolbar>
<taskana-task-list-toolbar (performSorting)="performSorting($event)" (performFilter)="performFilter($event)"
<taskana-task-list-toolbar (performSorting)="performSorting($event)" (performFilter)="performFilter()"
(selectSearchType)="selectSearchType($event)" [taskDefaultSortBy]="taskDefaultSortBy">
</taskana-task-list-toolbar>
</div>
<taskana-task-list *ngIf="!requestInProgress" [tasks]="tasks" [(selectedId)]="selectedId"></taskana-task-list>
</div>
<taskana-shared-pagination [numberOfItems]="tasks.length" *ngIf="tasks && tasks.length > 0" [(page)]="tasksPageInformation"
<taskana-shared-pagination [numberOfItems]="tasks.length" *ngIf="tasks && tasks.length > 0" [page]="tasksPageInformation"
[type]="type" (changePage)="changePage($event)"></taskana-shared-pagination>

View File

@ -1,19 +1,20 @@
import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Task } from 'app/workplace/models/task';
import { TaskService } from 'app/workplace/services/task.service';
import { Subject } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { Direction, Sorting, TaskQuerySortParameter } from 'app/shared/models/sorting';
import { Workbasket } from 'app/shared/models/workbasket';
import { WorkplaceService } from 'app/workplace/services/workplace.service';
import { OrientationService } from 'app/shared/services/orientation/orientation.service';
import { Page } from 'app/shared/models/page';
import { takeUntil } from 'rxjs/operators';
import { ObjectReference } from '../../models/object-reference';
import { take, takeUntil } from 'rxjs/operators';
import { Search } from '../task-list-toolbar/task-list-toolbar.component';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
import { QueryPagingParameter } from '../../../shared/models/query-paging-parameter';
import { TaskQueryFilterParameter } from '../../../shared/models/task-query-filter-parameter';
import { Select } from '@ngxs/store';
import { FilterSelectors } from '../../../shared/store/filter-store/filter.selectors';
@Component({
selector: 'taskana-task-master',
@ -45,6 +46,8 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
@ViewChild('wbToolbar', { static: true })
private toolbarElement: ElementRef;
@Select(FilterSelectors.getTaskFilter) filter$: Observable<TaskQueryFilterParameter>;
constructor(
private taskService: TaskService,
private workplaceService: WorkplaceService,
@ -87,16 +90,6 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
this.getTasks();
}
});
this.workplaceService
.getSelectedObjectReference()
.pipe(takeUntil(this.destroy$))
.subscribe((objectReference) => {
if (objectReference) {
delete this.currentBasket;
this.getTasks(objectReference);
}
});
}
performSorting(sort: Sorting<TaskQuerySortParameter>) {
@ -104,9 +97,12 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
this.getTasks();
}
performFilter(filterBy: TaskQueryFilterParameter) {
this.filterBy = filterBy;
this.getTasks();
performFilter() {
this.paging.page = 1;
this.filter$.pipe(take(1)).subscribe((filter) => {
this.filterBy = { ...filter };
this.getTasks();
});
}
selectSearchType(type: Search) {
@ -135,24 +131,32 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
}
}
private getTasks(objectReference?: ObjectReference): void {
this.filterBy['workbasket-id'] = [this.currentBasket?.workbasketId];
private getTasks(): void {
this.requestInProgress = true;
if (!this.currentBasket && !objectReference) {
if (this.selectedSearchType === Search.byTypeAndValue) {
delete this.currentBasket;
}
this.filterBy['workbasket-id'] = [this.currentBasket?.workbasketId];
if (this.selectedSearchType === Search.byWorkbasket && !this.currentBasket) {
this.requestInProgress = false;
this.tasks = [];
} else {
this.calculateHeightCard();
this.taskService
.findTasksWithWorkbasket(this.filterBy, this.sort, this.paging)
.pipe(takeUntil(this.destroy$))
.pipe(take(1))
.subscribe((taskResource) => {
this.requestInProgress = false;
if (taskResource.tasks && taskResource.tasks.length > 0) {
this.tasks = taskResource.tasks;
} else {
this.tasks = [];
this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT_2);
if (this.selectedSearchType === Search.byWorkbasket) {
this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT_2);
}
}
this.tasksPageInformation = taskResource.page;
});

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { BehaviorSubject, Observable } from 'rxjs';
import { Workbasket } from 'app/shared/models/workbasket';
import { ObjectReference } from '../models/object-reference';
@ -15,12 +15,4 @@ export class WorkplaceService {
getSelectedWorkbasket(): Observable<Workbasket> {
return this.workbasketSelected.asObservable();
}
selectObjectReference(objectReference?: ObjectReference): void {
this.objectReferenceSelected.next(objectReference);
}
getSelectedObjectReference(): Observable<ObjectReference> {
return this.objectReferenceSelected.asObservable();
}
}

View File

@ -36,6 +36,7 @@ import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core';
import { MatTabsModule } from '@angular/material/tabs';
const MODULES = [
TypeaheadModule.forRoot(),
@ -78,7 +79,8 @@ const DECLARATIONS = [
MatMenuModule,
MatSelectModule,
MatDatepickerModule,
MatNativeDateModule
MatNativeDateModule,
MatTabsModule
],
providers: [
TaskService,