diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskController.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskController.java index e54e50971..16a86007f 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskController.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskController.java @@ -56,6 +56,7 @@ public class TaskController extends AbstractPagingController { private static final Logger LOGGER = LoggerFactory.getLogger(TaskController.class); + private static final String LIKE = "%"; private static final String STATE = "state"; private static final String STATE_VALUE_CLAIMED = "CLAIMED"; private static final String STATE_VALUE_COMPLETED = "COMPLETED"; @@ -277,13 +278,11 @@ public class TaskController extends AbstractPagingController { params.remove(POR_SYSTEM_INSTANCE); } if (params.containsKey(POR_TYPE)) { - String[] types = extractCommaSeparatedFields(params.get(POR_TYPE)); - taskQuery.primaryObjectReferenceTypeIn(types); + taskQuery.primaryObjectReferenceTypeLike(LIKE + params.get(POR_TYPE).get(0) + LIKE); params.remove(POR_TYPE); } if (params.containsKey(POR_VALUE)) { - String[] values = extractCommaSeparatedFields(params.get(POR_VALUE)); - taskQuery.primaryObjectReferenceValueIn(values); + taskQuery.primaryObjectReferenceValueLike(LIKE + params.get(POR_VALUE).get(0) + LIKE); params.remove(POR_VALUE); } return taskQuery; diff --git a/web/src/app/administration/administration-routing.module.ts b/web/src/app/administration/administration-routing.module.ts index 0fa8d31ba..7de57e333 100644 --- a/web/src/app/administration/administration-routing.module.ts +++ b/web/src/app/administration/administration-routing.module.ts @@ -9,61 +9,78 @@ import { ClassificationDetailsComponent } from 'app/administration/classificatio import { DomainGuard } from 'app/guards/domain-guard'; import { AccessItemsManagementComponent } from './access-items-management/access-items-management.component'; - const routes: Routes = [ - { - path: 'workbaskets', - component: MasterAndDetailComponent, - canActivate: [DomainGuard], - children: [ - { - path: '', - component: WorkbasketListComponent, - outlet: 'master' - }, - { - path: 'new-classification/:id', - component: WorkbasketDetailsComponent, - outlet: 'detail' - }, - { - path: ':id', - component: WorkbasketDetailsComponent, - outlet: 'detail' - } - ] - }, - { - path: 'classifications', - component: MasterAndDetailComponent, - canActivate: [DomainGuard], - children: [ - { - path: '', - component: ClassificationListComponent, - outlet: 'master' - }, - { - path: ':id', - component: ClassificationDetailsComponent, - outlet: 'detail' - } - ] - }, - { - path: 'access-items-management', - component: AccessItemsManagementComponent, - canActivate: [DomainGuard] - }, - { + { + path: 'workbaskets', + component: MasterAndDetailComponent, + canActivate: [DomainGuard], + children: [ + { path: '', - redirectTo: 'workbaskets', - pathMatch: 'full' - } + component: WorkbasketListComponent, + outlet: 'master' + }, + { + path: 'new-classification/:id', + component: WorkbasketDetailsComponent, + outlet: 'detail' + }, + { + path: ':id', + component: WorkbasketDetailsComponent, + outlet: 'detail' + }, + { + path: '**', + redirectTo: '' + } + ] + }, + { + path: 'classifications', + component: MasterAndDetailComponent, + canActivate: [DomainGuard], + children: [ + { + path: '', + component: ClassificationListComponent, + outlet: 'master' + }, + { + path: ':id', + component: ClassificationDetailsComponent, + outlet: 'detail' + }, + { + path: '**', + redirectTo: '' + } + ] + }, + { + path: 'access-items-management', + component: AccessItemsManagementComponent, + canActivate: [DomainGuard], + children: [ + { + path: '**', + redirectTo: '' + } + ] + }, + { + path: '', + redirectTo: 'workbaskets', + pathMatch: 'full' + }, + { + path: '**', + redirectTo: 'workbaskets' + } ]; @NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] }) -export class AdministrationRoutingModule { } +export class AdministrationRoutingModule {} diff --git a/web/src/app/monitor/monitor-routing.module.ts b/web/src/app/monitor/monitor-routing.module.ts index 787a602e6..e3ebf4b69 100644 --- a/web/src/app/monitor/monitor-routing.module.ts +++ b/web/src/app/monitor/monitor-routing.module.ts @@ -1,16 +1,20 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import { MonitorComponent } from './monitor.component' +import { MonitorComponent } from './monitor.component'; const routes: Routes = [ - { - path: '', - component: MonitorComponent + { + path: '', + component: MonitorComponent + }, + { + path: '**', + redirectTo: '' } ]; @NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] }) -export class MonitorRoutingModule { } +export class MonitorRoutingModule {} diff --git a/web/src/app/shared/sort/sort.component.scss b/web/src/app/shared/sort/sort.component.scss index b84dc8996..8f49f27d5 100644 --- a/web/src/app/shared/sort/sort.component.scss +++ b/web/src/app/shared/sort/sort.component.scss @@ -1,9 +1,11 @@ - - .sortby-dropdown { - min-width: 200px; + min-width: 200px; } ul { list-style-type: none; } +.bold-blue { + color: #337ab7; + font-weight: bold; +} diff --git a/web/src/app/shared/util/query-parameters.ts b/web/src/app/shared/util/query-parameters.ts index e23505750..168028920 100644 --- a/web/src/app/shared/util/query-parameters.ts +++ b/web/src/app/shared/util/query-parameters.ts @@ -16,6 +16,8 @@ export class TaskanaQueryParameters { static PRIORITY = 'priority'; static STATELIKE = 'state-like'; static WORKBASKET_ID = 'workbasket-id'; + static TASK_PRIMARY_OBJ_REF_TYPE_LIKE = 'por.type'; + static TASK_PRIMARY_OBJ_REF_VALUE_LIKE = 'por.value'; // Access static REQUIREDPERMISSION = 'required-permission'; @@ -53,6 +55,8 @@ export class TaskanaQueryParameters { basketId: string = undefined, priority: string = undefined, stateLike: string = undefined, + objRefTypeLike: string = undefined, + objRefValueLike: string = undefined, ): string { let query = '?'; query += sortBy ? `${this.SORTBY}=${sortBy}&` : ''; @@ -79,6 +83,8 @@ export class TaskanaQueryParameters { query += workbasketKeyLike ? `${this.WORKBASKETKEYLIKE}=${workbasketKeyLike}&` : ''; + query += objRefTypeLike ? `${this.TASK_PRIMARY_OBJ_REF_TYPE_LIKE}=${objRefTypeLike}&` : ''; + query += objRefValueLike ? `${this.TASK_PRIMARY_OBJ_REF_VALUE_LIKE}=${objRefValueLike}&` : ''; if (query.lastIndexOf('&') === query.length - 1) { query = query.slice(0, query.lastIndexOf('&')); diff --git a/web/src/app/workplace/services/task.service.ts b/web/src/app/workplace/services/task.service.ts index dbe8fdd73..a451a0069 100644 --- a/web/src/app/workplace/services/task.service.ts +++ b/web/src/app/workplace/services/task.service.ts @@ -65,11 +65,13 @@ export class TaskService { ownerLike: string, priority: string, state: string, + objRefTypeLike: string, + objRefValueLike: string, allPages: boolean = false): Observable { const url = `${this.url}${TaskanaQueryParameters.getQueryParameters( sortBy, sortDirection, undefined, nameLike, undefined, undefined, ownerLike, undefined, undefined, undefined, undefined, !allPages ? TaskanaQueryParameters.page : undefined, !allPages ? TaskanaQueryParameters.pageSize : undefined, - undefined, undefined, undefined, undefined, basketId, priority, state)}`; + undefined, undefined, undefined, undefined, basketId, priority, state, objRefTypeLike, objRefValueLike)}`; return this.httpClient.get(url); } diff --git a/web/src/app/workplace/services/workplace.service.ts b/web/src/app/workplace/services/workplace.service.ts index ceb9fb396..b80b1f8c0 100644 --- a/web/src/app/workplace/services/workplace.service.ts +++ b/web/src/app/workplace/services/workplace.service.ts @@ -1,13 +1,17 @@ import { Injectable } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { Workbasket } from 'app/models/workbasket'; +import { ObjectReference } from '../models/object-reference'; @Injectable() export class WorkplaceService { // necessary because the TaskdetailsComponent is not always initialized when the first workbasket was selected. currentWorkbasket: Workbasket; + objectReference: ObjectReference private workbasketSelectedSource = new Subject(); workbasketSelectedStream = this.workbasketSelectedSource.asObservable(); + private objectReferenceSource = new Subject(); + objectReferenceSelectedStream = this.objectReferenceSource.asObservable(); selectWorkbasket(workbasket: Workbasket) { this.currentWorkbasket = workbasket; @@ -17,4 +21,17 @@ export class WorkplaceService { getSelectedWorkbasket(): Observable { return this.workbasketSelectedStream; } + + selectObjectReference(objectReference: ObjectReference) { + if (objectReference) { + this.objectReference = new ObjectReference(undefined, undefined, undefined, undefined, objectReference.type, objectReference.value); + } else { + this.objectReference = new ObjectReference(undefined); + } + this.objectReferenceSource.next(objectReference); + } + + getObjectReference() { + return this.objectReferenceSelectedStream; + } } diff --git a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.html b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.html index 4bf75cd8b..3f840a38b 100644 --- a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.html +++ b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.html @@ -1,23 +1,67 @@
  • -
    -
    -
  • \ No newline at end of file + diff --git a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.scss b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.scss index 69da104c4..d96d3fc31 100644 --- a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.scss +++ b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.scss @@ -1,13 +1,46 @@ +@import './src/assets/_colors'; + .list-group-item { padding: 5px 0px 2px 1px; border: none; } -.tab-align{ +.tab-align { padding: 8px 12px 8px 0px; margin-bottom: 0px; - &>div{ + & > div { margin: 6px 0px; } } + +ul { + list-style-type: none; +} + +.blue-font { + color: $blue; +} + +.bold-blue { + font-weight: bold; + color: $blue; +} + +svg-icon { + height: 18px; + width: 18px; + fill: $blue; +} + +svg-icon.blue { + fill: $blue; +} + +@media screen and (min-width: 991px) and (max-width: 1115px) { + .reduced-width { + width: 37px; + padding-left: 5px; + padding-right: 5px; + } +} diff --git a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.ts b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.ts index a805db0d5..429f0fdd1 100644 --- a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.ts +++ b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.ts @@ -7,9 +7,14 @@ import { SortingModel } from 'app/models/sorting'; import { FilterModel } from 'app/models/filter'; import { TaskanaType } from 'app/models/taskana-type'; import { expandDown } from 'app/shared/animations/expand.animation'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Router, NavigationExtras } from '@angular/router'; import { WorkplaceService } from 'app/workplace/services/workplace.service'; +import { ObjectReference } from 'app/workplace/models/object-reference'; +export enum Search { + byWorkbasket = 'workbasket', + byTypeAndValue = 'type-and-value' +} @Component({ selector: 'taskana-tasklist-toolbar', animations: [expandDown], @@ -20,6 +25,7 @@ export class TaskListToolbarComponent implements OnInit { @Output() performSorting = new EventEmitter(); @Output() performFilter = new EventEmitter(); + @Output() selectSearchType = new EventEmitter(); sortingFields = new Map([['name', 'Name'], ['priority', 'Priority'], ['due', 'Due'], ['planned', 'Planned']]); @@ -34,6 +40,13 @@ export class TaskListToolbarComponent implements OnInit { workbasketSelected = false; toolbarState = false; filterType = TaskanaType.TASKS; + searched = false; + searchParam = 'search'; + + search = Search; + searchSelected: Search = Search.byWorkbasket; + resultType = ''; + resultValue = ''; constructor(private taskService: TaskService, private workbasketService: WorkbasketService, @@ -57,26 +70,42 @@ export class TaskListToolbarComponent implements OnInit { this.workplaceService.selectWorkbasket(this.currentBasket); this.workbasketSelected = true; } - }) + }); + + if (this.route.snapshot.queryParams.search === this.search.byTypeAndValue) { + this.searchSelected = this.search.byTypeAndValue; + } + if (this.router.url.includes('taskdetail')) { + this.searched = true; + } } searchBasket() { this.toolbarState = false; this.workbasketSelected = true; - if (this.workbaskets) { - this.workbaskets.forEach(workbasket => { - if (workbasket.name === this.resultName) { - this.resultId = workbasket.workbasketId; - this.currentBasket = workbasket; - this.workplaceService.selectWorkbasket(this.currentBasket); - } - }); + if (this.searchSelected === this.search.byTypeAndValue) { + this.workplaceService.selectObjectReference( + new ObjectReference(undefined, undefined, undefined, undefined, this.resultType, this.resultValue)); + this.searched = true; + } else { + this.workplaceService.selectObjectReference(undefined); + 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) { - this.currentBasket = undefined; - this.workplaceService.selectWorkbasket(undefined); + if (!this.resultId) { + this.currentBasket = undefined; + this.workplaceService.selectWorkbasket(undefined); + } } } + this.resultId = ''; this.router.navigate(['']); } @@ -93,4 +122,18 @@ export class TaskListToolbarComponent implements OnInit { this.taskService.selectTask(undefined); this.router.navigate([{ outlets: { detail: 'taskdetail/new-task' } }], { relativeTo: this.route }); } + + selectSearch(type: Search) { + this.searched = false; + this.resultId = undefined; + this.currentBasket = undefined; + this.selectSearchType.emit(type); + this.searchSelected = type; + + const navigationExtras: NavigationExtras = { + queryParams: { search: type } + }; + + this.router.navigate([''], navigationExtras); + } } diff --git a/web/src/app/workplace/tasklist/tasklist.component.html b/web/src/app/workplace/tasklist/tasklist.component.html index 114fde563..41aa01711 100644 --- a/web/src/app/workplace/tasklist/tasklist.component.html +++ b/web/src/app/workplace/tasklist/tasklist.component.html @@ -1,7 +1,7 @@ - \ No newline at end of file + diff --git a/web/src/app/workplace/tasklist/tasklist.component.ts b/web/src/app/workplace/tasklist/tasklist.component.ts index 99294cfb9..392d47b65 100644 --- a/web/src/app/workplace/tasklist/tasklist.component.ts +++ b/web/src/app/workplace/tasklist/tasklist.component.ts @@ -14,6 +14,8 @@ import { OrientationService } from 'app/services/orientation/orientation.service import { Orientation } from 'app/models/orientation'; import { Page } from 'app/models/page'; import { TaskanaDate } from 'app/shared/util/taskana.date'; +import { ObjectReference } from '../models/object-reference'; +import { Search } from './tasklist-toolbar/tasklist-toolbar.component'; @Component({ selector: 'taskana-task-list', @@ -38,6 +40,8 @@ export class TasklistComponent implements OnInit, OnDestroy { workbasketKey: '' }); requestInProgress = false; + objectReference: ObjectReference; + selectedSearchType: Search = Search.byWorkbasket; @ViewChild('wbToolbar') private toolbarElement: ElementRef; @@ -46,6 +50,7 @@ export class TasklistComponent implements OnInit, OnDestroy { private taskAddedSubscription: Subscription; private workbasketChangeSubscription: Subscription; private orientationSubscription: Subscription; + private objectReferenceSubscription: Subscription; constructor(private router: Router, private route: ActivatedRoute, @@ -69,12 +74,21 @@ export class TasklistComponent implements OnInit, OnDestroy { }); this.workbasketChangeSubscription = this.workplaceService.workbasketSelectedStream.subscribe(workbasket => { this.currentBasket = workbasket; - this.getTasks(); + if (this.selectedSearchType === Search.byWorkbasket) { + this.getTasks(); + } }); this.taskAddedSubscription = this.taskService.taskAddedStream.subscribe(task => { this.getTasks(); this.selectedId = task.taskId; }); + this.objectReferenceSubscription = this.workplaceService.objectReferenceSelectedStream.subscribe(objectReference => { + this.objectReference = objectReference; + this.currentBasket = undefined; + if (objectReference) { + this.getTasks(); + } + }); } ngOnInit() { @@ -83,7 +97,6 @@ export class TasklistComponent implements OnInit, OnDestroy { if (!this.currentBasket) { this.selectedId = task.taskId; this.currentBasket = task.workbasketSummaryResource; - this.getTasks(); } if (!task) { this.selectedId = undefined; @@ -95,6 +108,7 @@ export class TasklistComponent implements OnInit, OnDestroy { } selectTask(taskId: string) { + this.workplaceService.selectObjectReference(undefined); this.selectedId = taskId; this.router.navigate([{ outlets: { detail: `taskdetail/${this.selectedId}` } }], { relativeTo: this.route }); } @@ -109,6 +123,11 @@ export class TasklistComponent implements OnInit, OnDestroy { this.getTasks(); } + selectSearchType(type: Search) { + this.selectedSearchType = type; + this.tasks = []; + } + changePage(page) { TaskanaQueryParameters.page = page; this.getTasks(); @@ -120,7 +139,7 @@ export class TasklistComponent implements OnInit, OnDestroy { } calculateHeightCard() { - if (this.toolbarElement && this.currentBasket) { + if (this.toolbarElement) { const toolbarSize = this.toolbarElement.nativeElement.offsetHeight; const cardHeight = 53; const unusedHeight = 145; @@ -132,14 +151,15 @@ export class TasklistComponent implements OnInit, OnDestroy { getTasks(): void { this.requestInProgress = true; - if (this.currentBasket === undefined) { + if (this.currentBasket === undefined && !this.objectReference) { this.requestInProgress = false; this.tasks = []; } else { this.calculateHeightCard(); - this.taskService.findTasksWithWorkbasket(this.currentBasket.workbasketId, this.sort.sortBy, this.sort.sortDirection, - this.filterBy.filterParams.name, this.filterBy.filterParams.owner, this.filterBy.filterParams.priority, - this.filterBy.filterParams.state) + this.taskService.findTasksWithWorkbasket(this.currentBasket ? this.currentBasket.workbasketId : undefined, + this.sort.sortBy, this.sort.sortDirection, this.filterBy.filterParams.name, this.filterBy.filterParams.owner, + this.filterBy.filterParams.priority, this.filterBy.filterParams.state, this.objectReference ? this.objectReference.type : undefined, + this.objectReference ? this.objectReference.value : undefined) .subscribe(tasks => { this.requestInProgress = false; if (tasks._embedded) { @@ -163,5 +183,6 @@ export class TasklistComponent implements OnInit, OnDestroy { this.workbasketChangeSubscription.unsubscribe(); this.taskAddedSubscription.unsubscribe(); this.orientationSubscription.unsubscribe(); + this.objectReferenceSubscription.unsubscribe(); } } diff --git a/web/src/app/workplace/workplace-routing.module.ts b/web/src/app/workplace/workplace-routing.module.ts index 0226fdb3b..d16605986 100644 --- a/web/src/app/workplace/workplace-routing.module.ts +++ b/web/src/app/workplace/workplace-routing.module.ts @@ -1,44 +1,45 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {MasterAndDetailComponent} from '../shared/master-and-detail/master-and-detail.component'; -import {TaskComponent} from './task/task.component'; -import {TaskdetailsComponent} from './taskdetails/taskdetails.component'; -import {TasklistComponent} from './tasklist/tasklist.component'; +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { MasterAndDetailComponent } from '../shared/master-and-detail/master-and-detail.component'; +import { TaskComponent } from './task/task.component'; +import { TaskdetailsComponent } from './taskdetails/taskdetails.component'; +import { TasklistComponent } from './tasklist/tasklist.component'; const routes: Routes = [ - { - path: 'tasks', - component: MasterAndDetailComponent, - children: [ - { - path: '', - component: TasklistComponent, - outlet: 'master' - }, - { - path: 'taskdetail/:id', - component: TaskdetailsComponent, - outlet: 'detail' - }, - { - path: 'task/:id', - component: TaskComponent, - outlet: 'detail' - } - ] - }, - { - path: '', - redirectTo: 'tasks', - pathMatch: 'full' - } - - ] -; + { + path: 'tasks', + component: MasterAndDetailComponent, + children: [ + { + path: '', + component: TasklistComponent, + outlet: 'master' + }, + { + path: 'taskdetail/:id', + component: TaskdetailsComponent, + outlet: 'detail' + }, + { + path: 'task/:id', + component: TaskComponent, + outlet: 'detail' + } + ] + }, + { + path: '', + redirectTo: 'tasks', + pathMatch: 'full' + }, + { + path: '**', + redirectTo: 'tasks' + } +]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) -export class WorkplaceRoutingModule { -} +export class WorkplaceRoutingModule {} diff --git a/web/src/assets/_colors.scss b/web/src/assets/_colors.scss index c5e8de2dd..db96fdc77 100644 --- a/web/src/assets/_colors.scss +++ b/web/src/assets/_colors.scss @@ -10,5 +10,3 @@ $aquamarine: #22a39f; $pallete-blue: #36bcee; $pallete-green: #5fbca1; $transparent-grey: rgba(192, 192, 192, 0.65); - -