TSK-184 Add pagination feature

This commit is contained in:
Martin Rojas Miguel Angel 2018-03-27 19:21:24 +02:00 committed by Holger Hagen
parent db8de1063e
commit 83f0472bac
40 changed files with 610 additions and 197 deletions

View File

@ -20,6 +20,7 @@ import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { SavingWorkbasketService, SavingInformation } from 'app/services/saving-workbaskets/saving-workbaskets.service'; import { SavingWorkbasketService, SavingInformation } from 'app/services/saving-workbaskets/saving-workbaskets.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service'; import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { AlertService } from 'app/services/alert/alert.service'; import { AlertService } from 'app/services/alert/alert.service';
import { SimpleChange } from '@angular/core';
@ -56,7 +57,9 @@ describe('AccessItemsComponent', () => {
spyOn(workbasketService, 'updateWorkBasketAccessItem').and.returnValue(Observable.of(true)), spyOn(workbasketService, 'updateWorkBasketAccessItem').and.returnValue(Observable.of(true)),
spyOn(alertService, 'triggerAlert').and.returnValue(Observable.of(true)), spyOn(alertService, 'triggerAlert').and.returnValue(Observable.of(true)),
debugElement = fixture.debugElement.nativeElement; debugElement = fixture.debugElement.nativeElement;
component.ngOnChanges({
active: new SimpleChange(undefined, 'accessItems', true)
});
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -1,4 +1,4 @@
import { Component, OnInit, Input, AfterViewInit, OnDestroy } from '@angular/core'; import { Component, OnInit, Input, AfterViewInit, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { Workbasket } from 'app/models/workbasket'; import { Workbasket } from 'app/models/workbasket';
@ -20,12 +20,15 @@ declare var $: any;
templateUrl: './access-items.component.html', templateUrl: './access-items.component.html',
styleUrls: ['./access-items.component.scss'] styleUrls: ['./access-items.component.scss']
}) })
export class AccessItemsComponent implements OnInit, OnDestroy { export class AccessItemsComponent implements OnChanges, OnDestroy {
@Input() @Input()
workbasket: Workbasket; workbasket: Workbasket;
@Input() @Input()
action: string; action: string;
@Input()
active: string;
badgeMessage = ''; badgeMessage = '';
accessItemsResource: WorkbasketAccessItemsResource; accessItemsResource: WorkbasketAccessItemsResource;
@ -38,6 +41,7 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
modalErrorMessage: string; modalErrorMessage: string;
accessItemsubscription: Subscription; accessItemsubscription: Subscription;
savingAccessItemsSubscription: Subscription; savingAccessItemsSubscription: Subscription;
private initialized = false;
constructor( constructor(
@ -46,7 +50,13 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
private errorModalService: ErrorModalService, private errorModalService: ErrorModalService,
private savingWorkbaskets: SavingWorkbasketService) { } private savingWorkbaskets: SavingWorkbasketService) { }
ngOnInit() { ngOnChanges(changes: SimpleChanges): void {
if (changes.active.currentValue === 'accessItems' && !this.initialized) {
this.init();
}
}
private init() {
this.initialized = true;
if (!this.workbasket._links.accessItems) { if (!this.workbasket._links.accessItems) {
return; return;
} }

View File

@ -1,4 +1,4 @@
import { Input, Component } from '@angular/core'; import { Input, Component, SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { AngularSvgIconModule } from 'angular-svg-icon'; import { AngularSvgIconModule } from 'angular-svg-icon';
@ -24,13 +24,14 @@ import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/general-message-modal.component'; import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/general-message-modal.component';
import { IconTypeComponent } from 'app/shared/type-icon/icon-type.component'; import { IconTypeComponent } from 'app/shared/type-icon/icon-type.component';
import { SelectWorkBasketPipe } from 'app/pipes/selectedWorkbasket/seleted-workbasket.pipe'; import { SelectWorkBasketPipe } from 'app/pipes/selectedWorkbasket/seleted-workbasket.pipe';
import { LinksWorkbasketSummary } from '../../../../models/links-workbasket-summary';
const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSummaryResource({ const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSummaryResource({
'workbaskets': new Array<WorkbasketSummary>( 'workbaskets': new Array<WorkbasketSummary>(
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''), new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''),
new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', '')) new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', ''))
}, new Links({ 'href': 'url' })); }, new LinksWorkbasketSummary({ 'href': 'url' }));
@Component({ @Component({
selector: 'taskana-filter', selector: 'taskana-filter',
@ -73,16 +74,18 @@ describe('DistributionTargetsComponent', () => {
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })), new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })),
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })), new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })),
new WorkbasketSummary('id3', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' }))) new WorkbasketSummary('id3', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' }))) }, new LinksWorkbasketSummary({ 'href': 'someurl' })))
}) })
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => { spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => {
return Observable.of(new WorkbasketDistributionTargetsResource( return Observable.of(new WorkbasketDistributionTargetsResource(
{ {
'distributionTargets': new Array<WorkbasketSummary>( 'distributionTargets': new Array<WorkbasketSummary>(
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' }))) new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' }))) }, new LinksWorkbasketSummary({ 'href': 'someurl' })))
}) })
component.ngOnChanges({
active: new SimpleChange(undefined, 'distributionTargets', true)
});
fixture.detectChanges(); fixture.detectChanges();
}); });
@ -144,7 +147,7 @@ describe('DistributionTargetsComponent', () => {
'distributionTargets': new Array<WorkbasketSummary>( 'distributionTargets': new Array<WorkbasketSummary>(
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })), new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })),
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' }))) new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' }))) }, new LinksWorkbasketSummary({ 'href': 'someurl' })))
}) })
component.onSave(); component.onSave();
fixture.detectChanges(); fixture.detectChanges();

View File

@ -1,4 +1,4 @@
import { Component, OnInit, Input, OnDestroy } from '@angular/core'; import { Component, OnInit, Input, OnDestroy, SimpleChanges, OnChanges } from '@angular/core';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { Workbasket } from 'app/models/workbasket'; import { Workbasket } from 'app/models/workbasket';
@ -24,12 +24,14 @@ export enum Side {
templateUrl: './distribution-targets.component.html', templateUrl: './distribution-targets.component.html',
styleUrls: ['./distribution-targets.component.scss'] styleUrls: ['./distribution-targets.component.scss']
}) })
export class DistributionTargetsComponent implements OnInit, OnDestroy { export class DistributionTargetsComponent implements OnChanges, OnDestroy {
@Input() @Input()
workbasket: Workbasket; workbasket: Workbasket;
@Input() @Input()
action: string; action: string;
@Input()
active: string;
badgeMessage = ''; badgeMessage = '';
distributionTargetsSubscription: Subscription; distributionTargetsSubscription: Subscription;
@ -44,12 +46,12 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
distributionTargetsClone: Array<WorkbasketSummary>; distributionTargetsClone: Array<WorkbasketSummary>;
distributionTargetsSelectedClone: Array<WorkbasketSummary>; distributionTargetsSelectedClone: Array<WorkbasketSummary>;
requestInProgress = false; requestInProgress = false;
requestInProgressLeft = false; requestInProgressLeft = false;
requestInProgressRight = false; requestInProgressRight = false;
modalErrorMessage: string; modalErrorMessage: string;
side = Side; side = Side;
private initialized = false;
constructor( constructor(
private workbasketService: WorkbasketService, private workbasketService: WorkbasketService,
@ -57,7 +59,14 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
private savingWorkbaskets: SavingWorkbasketService, private savingWorkbaskets: SavingWorkbasketService,
private errorModalService: ErrorModalService) { } private errorModalService: ErrorModalService) { }
ngOnInit() { ngOnChanges(changes: SimpleChanges): void {
if (changes.active.currentValue === 'distributionTargets' && !this.initialized) {
this.init();
}
}
private init() {
this.initialized = true;
this.onRequest(undefined); this.onRequest(undefined);
if (!this.workbasket._links.distributionTargets) { if (!this.workbasket._links.distributionTargets) {
return; return;
@ -69,13 +78,15 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
this.distributionTargetsSelected = distributionTargetsSelectedResource._embedded ? this.distributionTargetsSelected = distributionTargetsSelectedResource._embedded ?
distributionTargetsSelectedResource._embedded.distributionTargets : []; distributionTargetsSelectedResource._embedded.distributionTargets : [];
this.distributionTargetsSelectedClone = Object.assign([], this.distributionTargetsSelected); this.distributionTargetsSelectedClone = Object.assign([], this.distributionTargetsSelected);
this.workbasketSubscription = this.workbasketService.getWorkBasketsSummary().subscribe( this.workbasketSubscription = this.workbasketService.getWorkBasketsSummary(true,
(distributionTargetsAvailable: WorkbasketSummaryResource) => { undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable._embedded.workbaskets); undefined, undefined, undefined, true).subscribe(
this.distributionTargetsRight = Object.assign([], distributionTargetsAvailable._embedded.workbaskets); (distributionTargetsAvailable: WorkbasketSummaryResource) => {
this.distributionTargetsClone = Object.assign([], distributionTargetsAvailable._embedded.workbaskets); this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable._embedded.workbaskets);
this.onRequest(undefined, true); this.distributionTargetsRight = Object.assign([], distributionTargetsAvailable._embedded.workbaskets);
}); this.distributionTargetsClone = Object.assign([], distributionTargetsAvailable._embedded.workbaskets);
this.onRequest(undefined, true);
});
}); });
this.savingDistributionTargetsSubscription = this.savingWorkbaskets.triggeredDistributionTargetsSaving() this.savingDistributionTargetsSubscription = this.savingWorkbaskets.triggeredDistributionTargetsSaving()
@ -143,7 +154,7 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
this.onRequest(dualListFilter.side, false); this.onRequest(dualListFilter.side, false);
this.workbasketFilterSubscription = this.workbasketService.getWorkBasketsSummary(true, undefined, undefined, undefined, this.workbasketFilterSubscription = this.workbasketService.getWorkBasketsSummary(true, undefined, undefined, undefined,
dualListFilter.filterBy.name, dualListFilter.filterBy.description, undefined, dualListFilter.filterBy.owner, dualListFilter.filterBy.name, dualListFilter.filterBy.description, undefined, dualListFilter.filterBy.owner,
dualListFilter.filterBy.type, undefined, dualListFilter.filterBy.key).subscribe(resultList => { dualListFilter.filterBy.type, undefined, dualListFilter.filterBy.key, undefined, true).subscribe(resultList => {
(dualListFilter.side === Side.RIGHT) ? (dualListFilter.side === Side.RIGHT) ?
this.distributionTargetsRight = (resultList._embedded ? resultList._embedded.workbaskets : []) : this.distributionTargetsRight = (resultList._embedded ? resultList._embedded.workbaskets : []) :
this.distributionTargetsLeft = (resultList._embedded ? resultList._embedded.workbaskets : []); this.distributionTargetsLeft = (resultList._embedded ? resultList._embedded.workbaskets : []);

View File

@ -9,30 +9,32 @@
<h5>Available distribution targets</h5> <h5>Available distribution targets</h5>
</div> </div>
<div class="pull-right"> <div class="pull-right">
<button class="btn btn-default collapsed" type="button" id="collapsedMenufilterWbDta" data-toggle="collapse" <button class="btn btn-default" type="button" id="collapsedMenufilterWb" aria-expanded="false" (click)="toolbarState=!toolbarState">
[attr.data-target]="'#wb-dta-filter-bar-' + sideNumber"
aria-expanded="false">
<span class="glyphicon glyphicon-filter blue"></span> <span class="glyphicon glyphicon-filter blue"></span>
</button> </button>
</div> </div>
</div> </div>
<taskana-filter target="wb-dta-filter-bar-{{sideNumber}}" (performFilter)="performAvailableFilter($event)"></taskana-filter> <div [@toggle]="toolbarState" *ngIf="toolbarState" class="row">
<taskana-filter class="col-xs-12" (performFilter)="performAvailableFilter($event)"></taskana-filter>
</div>
<taskana-spinner [isRunning]="requestInProgress" positionClass="centered-spinner" class="centered-horizontally floating"></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" positionClass="centered-spinner" class="centered-horizontally floating"></taskana-spinner>
<ul class="list-group "> <div>
<li class="list-group-item" *ngFor="let distributionTarget of distributionTargets | selectWorkbaskets: distributionTargetsSelected: side" <ul class="list-group">
[class.selected]="distributionTarget.selected" type="text" (click)="distributionTarget.selected = !distributionTarget.selected"> <li class="list-group-item" *ngFor="let distributionTarget of distributionTargets | selectWorkbaskets: distributionTargetsSelected: side"
<div class="row"> [class.selected]="distributionTarget.selected" type="text" (click)="distributionTarget.selected = !distributionTarget.selected">
<dl class="col-xs-1"> <div class="row">
<dt> <dl class="col-xs-1">
<taskana-icon-type class="vertical-align" [type]="distributionTarget.type"></taskana-icon-type> <taskana-icon-type class="vertical-align" [type]="distributionTarget.type"></taskana-icon-type>
</dt> </dl>
</dl> <dl class="col-xs-10">
<dl class="col-xs-10"> <dt>{{distributionTarget.name}},
<dt>{{distributionTarget.name}} ({{distributionTarget.key}}) </dt> <i>{{distributionTarget.key}} </i>
<dd>{{distributionTarget.description}}</dd> </dt>
<dd>{{distributionTarget.owner}} &nbsp;</dd> <dd>{{distributionTarget.description}} &nbsp;</dd>
</dl> <dd>{{distributionTarget.owner}} &nbsp;</dd>
</div> </dl>
</li> </div>
</ul> </li>
</ul>
</div>
</div> </div>

View File

@ -58,3 +58,33 @@ button.no-style{
background: none; background: none;
border:none; border:none;
} }
.row.list-group {
margin-left: 2px;
}
.list-group > li {
border-left: none;
border-right: none;
}
a > label{
height: 2em;
width: 100%;
}
dd, dt {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
dt > i {
font-weight: normal;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
li > div.row > dl {
margin-bottom: 0px;
}

View File

@ -1,5 +1,6 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { WorkbasketSummary } from 'app/models/workbasket-summary'; import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';
import { FilterModel } from 'app/models/filter'; import { FilterModel } from 'app/models/filter';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { Side } from '../distribution-targets.component'; import { Side } from '../distribution-targets.component';
@ -7,7 +8,21 @@ import { Side } from '../distribution-targets.component';
@Component({ @Component({
selector: 'taskana-dual-list', selector: 'taskana-dual-list',
templateUrl: './dual-list.component.html', templateUrl: './dual-list.component.html',
styleUrls: ['./dual-list.component.scss'] styleUrls: ['./dual-list.component.scss'],
animations: [
trigger('toggle', [
state('*', style({ opacity: '1' })),
state('void', style({ opacity: '0' })),
transition('void => *', animate('300ms ease-in', keyframes([
style({ opacity: 0, height: '0px' }),
style({ opacity: 0.5, height: '50px' }),
style({ opacity: 1, height: '*' })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ opacity: 1, height: '*' }),
style({ opacity: 0.5, height: '50px' }),
style({ opacity: 0, height: '0px' })])))
]
)],
}) })
export class DualListComponent implements OnInit { export class DualListComponent implements OnInit {
@ -21,6 +36,7 @@ export class DualListComponent implements OnInit {
sideNumber = 0; sideNumber = 0;
toggleDtl = false; toggleDtl = false;
toolbarState = false;
constructor() { } constructor() { }

View File

@ -7,6 +7,8 @@ import { HttpClientModule } from '@angular/common/http';
import { HttpModule, JsonpModule } from '@angular/http'; import { HttpModule, JsonpModule } from '@angular/http';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import {Component} from '@angular/core';
import {Routes} from '@angular/router';
import { Workbasket } from 'app/models/workbasket'; import { Workbasket } from 'app/models/workbasket';
import { ICONTYPES } from 'app/models/type'; import { ICONTYPES } from 'app/models/type';
@ -22,8 +24,6 @@ import { RemoveNoneTypePipe } from 'app/pipes/removeNoneType/remove-none-type.pi
import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { SavingWorkbasketService, SavingInformation } from 'app/services/saving-workbaskets/saving-workbaskets.service'; import { SavingWorkbasketService, SavingInformation } from 'app/services/saving-workbaskets/saving-workbaskets.service';
import { AlertService } from 'app/services/alert/alert.service'; import { AlertService } from 'app/services/alert/alert.service';
import {Component} from '@angular/core';
import {Routes} from '@angular/router';
@Component({ @Component({
selector: 'taskana-dummy-detail', selector: 'taskana-dummy-detail',

View File

@ -130,7 +130,6 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
const date = datePipe.transform(Date.now(), dateFormat) + 'Z'; const date = datePipe.transform(Date.now(), dateFormat) + 'Z';
this.workbasket.created = date; this.workbasket.created = date;
this.workbasket.modified = date; this.workbasket.modified = date;
} }
ngOnDestroy() { ngOnDestroy() {

View File

@ -7,25 +7,25 @@
<a (click)="backClicked()"> <a (click)="backClicked()">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back</a> <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back</a>
</li> </li>
<li role="presentation" class="active"> <li role="presentation" class="active" (click)="tabSelected = 'information'" >
<a href="#work-baskets" aria-controls="work baskets" role="tab" data-toggle="tab" aria-expanded="true">Information</a> <a href="#work-baskets" aria-controls="work baskets" role="tab" data-toggle="tab" aria-expanded="true">Information</a>
</li> </li>
<li role="presentation" [ngClass]="{'disabled': !workbasket._links?.accessItems}"> <li role="presentation" [ngClass]="{'disabled': !workbasket._links?.accessItems}" (click)="tabSelected = 'accessItems'">
<a href="#access-items" aria-controls="Acccess" role="tab" data-toggle="tab" aria-expanded="true">Access</a> <a href="#access-items" aria-controls="Acccess" role="tab" data-toggle="tab" aria-expanded="true">Access</a>
</li> </li>
<li role="presentation" [ngClass]="{'disabled': !workbasket._links?.distributionTargets}"> <li role="presentation" [ngClass]="{'disabled': !workbasket._links?.distributionTargets}" (click)="tabSelected = 'distributionTargets'">
<a href="#distribution-targets" aria-controls="distribution targets" role="tab" data-toggle="tab" aria-expanded="true">Distribution targets</a> <a href="#distribution-targets" aria-controls="distribution targets" role="tab" data-toggle="tab" aria-expanded="true">Distribution targets</a>
</li> </li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div role="tabpanel" class="tab-pane fade in active" id="work-baskets"> <div role="tabpanel" class="tab-pane fade in active"id="work-baskets">
<taskana-workbasket-information [workbasket]="workbasket" [action]="action"></taskana-workbasket-information> <taskana-workbasket-information [workbasket]="workbasket" [action]="action" ></taskana-workbasket-information>
</div> </div>
<div role="tabpanel" class="tab-pane fade" id="access-items"> <div role="tabpanel" class="tab-pane fade" id="access-items">
<taskana-workbasket-access-items [workbasket]="workbasket" [action]="action"></taskana-workbasket-access-items> <taskana-workbasket-access-items [workbasket]="workbasket" [action]="action" [active] ="tabSelected"></taskana-workbasket-access-items>
</div> </div>
<div role="tabpanel" class="tab-pane fade" id="distribution-targets"> <div role="tabpanel" class="tab-pane fade" id="distribution-targets">
<taskana-workbaskets-distribution-targets [workbasket]="workbasket" [action]="action"></taskana-workbaskets-distribution-targets> <taskana-workbaskets-distribution-targets [workbasket]="workbasket" [action]="action" [active] ="tabSelected"></taskana-workbaskets-distribution-targets>
</div> </div>
</div> </div>
</div> </div>

View File

@ -37,6 +37,7 @@ import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/g
import { MapValuesPipe } from 'app/pipes/mapValues/map-values.pipe'; import { MapValuesPipe } from 'app/pipes/mapValues/map-values.pipe';
import { RemoveNoneTypePipe } from 'app/pipes/removeNoneType/remove-none-type.pipe'; import { RemoveNoneTypePipe } from 'app/pipes/removeNoneType/remove-none-type.pipe';
import { SelectWorkBasketPipe } from 'app/pipes/selectedWorkbasket/seleted-workbasket.pipe'; import { SelectWorkBasketPipe } from 'app/pipes/selectedWorkbasket/seleted-workbasket.pipe';
import { LinksWorkbasketSummary } from '../../../models/links-workbasket-summary';
@Component({ @Component({
selector: 'taskana-filter', selector: 'taskana-filter',
@ -53,10 +54,8 @@ export class FilterComponent {
template: 'dummydetail' template: 'dummydetail'
}) })
export class DummyDetailComponent { export class DummyDetailComponent {
} }
describe('WorkbasketDetailsComponent', () => { describe('WorkbasketDetailsComponent', () => {
let component: WorkbasketDetailsComponent; let component: WorkbasketDetailsComponent;
let fixture: ComponentFixture<WorkbasketDetailsComponent>; let fixture: ComponentFixture<WorkbasketDetailsComponent>;
@ -68,7 +67,7 @@ describe('WorkbasketDetailsComponent', () => {
new Links({ 'href': 'someurl' }, { 'href': 'someurl' }, { 'href': 'someurl' })); new Links({ 'href': 'someurl' }, { 'href': 'someurl' }, { 'href': 'someurl' }));
const routes: Routes = [ const routes: Routes = [
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' } { path: '*', component: DummyDetailComponent}
]; ];
beforeEach(async(() => { beforeEach(async(() => {
@ -100,7 +99,7 @@ describe('WorkbasketDetailsComponent', () => {
'workbaskets': new Array<WorkbasketSummary>( 'workbaskets': new Array<WorkbasketSummary>(
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '',
new Links({ 'href': 'someurl' }))) new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' }))) }, new LinksWorkbasketSummary({ 'href': 'someurl' })))
}) })
spyOn(workbasketService, 'getWorkBasket').and.callFake(() => { return Observable.of(workbasket) }) spyOn(workbasketService, 'getWorkBasket').and.callFake(() => { return Observable.of(workbasket) })
@ -110,7 +109,7 @@ describe('WorkbasketDetailsComponent', () => {
}) })
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => { spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => {
return Observable.of(new WorkbasketSummaryResource( return Observable.of(new WorkbasketSummaryResource(
{ 'workbaskets': new Array<WorkbasketSummary>() }, new Links({ 'href': 'url' }))) { 'workbaskets': new Array<WorkbasketSummary>() }, new LinksWorkbasketSummary({ 'href': 'url' })))
}) })
}); });

View File

@ -100,14 +100,14 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
private getWorkbasketInformation(workbasketIdSelected: string, copyId: string = undefined) { private getWorkbasketInformation(workbasketIdSelected: string, copyId: string = undefined) {
this.requestInProgress = true; this.requestInProgress = true;
this.service.getWorkBasketsSummary(true).subscribe((workbasketSummary: WorkbasketSummaryResource) => { this.service.getWorkBasketsSummary().subscribe((workbasketSummary: WorkbasketSummaryResource) => {
if (!workbasketIdSelected && this.action === ACTION.CREATE) { // CREATE if (!workbasketIdSelected && this.action === ACTION.CREATE) { // CREATE
this.workbasket = new Workbasket(undefined); this.workbasket = new Workbasket(undefined);
this.workbasket._links.self = workbasketSummary._links.self; this.workbasket._links.self = workbasketSummary._links.allWorkbaskets;
this.requestInProgress = false; this.requestInProgress = false;
} else if (!workbasketIdSelected && this.action === ACTION.COPY) { // COPY } else if (!workbasketIdSelected && this.action === ACTION.COPY) { // COPY
this.workbasket = { ...this.workbasketCopy }; this.workbasket = { ...this.workbasketCopy };
this.workbasket._links.self = workbasketSummary._links.self; this.workbasket._links.self = workbasketSummary._links.allWorkbaskets;
this.workbasket.workbasketId = undefined; this.workbasket.workbasketId = undefined;
this.requestInProgress = false; this.requestInProgress = false;
} }

View File

@ -0,0 +1,15 @@
<ul id="wb-pagination" class="pagination vertical-center">
<li>
<a (click)="changeToPage(1)" aria-label="First">
First</a>
</li>
<li *ngFor="let pageNumber of workbasketsResource?.page?.totalPages |
spreadNumber: workbasketsResource?.page?.number: maxPagesAvailable: workbasketsResource?.page?.totalPages">
<a *ngIf="pageNumber + 1 !== workbasketsResource?.page?.number" (click)="changeToPage(pageNumber+1)">{{pageNumber + 1}}</a>
<input *ngIf="pageNumber + 1 === workbasketsResource?.page?.number" [(ngModel)]="pageSelected" (keyup.enter)="changeToPage(pageSelected)"
type="text" class="pagination" (blur)="changeToPage(pageSelected)">
</li>
<li>
<a (click)="changeToPage(workbasketsResource?.page.totalPages)" aria-label="Last">Last</a>
</li>
</ul>

View File

@ -0,0 +1,11 @@
input.pagination{
width: 35px;
height: 35px;
margin: 0 10px;
text-align: center;
background-color: aliceblue;
}
ul.pagination{
overflow: hidden;
}

View File

@ -0,0 +1,84 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { PaginationComponent } from './pagination.component';
import { SpreadNumberPipe } from 'app/pipes/spreadNumber/spread-number';
import { WorkbasketSummaryResource } from '../../../../../models/workbasket-summary-resource';
import { Page } from '../../../../../models/page';
describe('PaginationComponent', () => {
let component: PaginationComponent;
let fixture: ComponentFixture<PaginationComponent>;
let debugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [PaginationComponent, SpreadNumberPipe],
imports: [FormsModule]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PaginationComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
fixture.detectChanges();
});
afterEach(() => {
fixture.detectChanges()
document.body.removeChild(debugElement);
})
it('should create', () => {
expect(component).toBeTruthy();
expect(debugElement.querySelectorAll('#wb-pagination > li').length).toBe(2);
});
it('should create 3 pages if total pages are 3', () => {
component.workbasketsResource = new WorkbasketSummaryResource(undefined, undefined, new Page(6, 3, 3, 1));
fixture.detectChanges();
expect(debugElement.querySelectorAll('#wb-pagination > li').length).toBe(5);
});
it('should emit change if previous page was different than current one', () => {
component.workbasketsResource = new WorkbasketSummaryResource(undefined, undefined, new Page(6, 3, 3, 1));
component.previousPageSelected = 2;
fixture.detectChanges();
component.changePage.subscribe(value => {
expect(value).toBe(1)
})
component.changeToPage(1);
});
it('should not emit change if previous page was the same than current one', () => {
component.workbasketsResource = new WorkbasketSummaryResource(undefined, undefined, new Page(6, 3, 3, 1));
component.previousPageSelected = 2;
fixture.detectChanges();
component.changePage.subscribe(value => {
expect(false).toBe(true)
})
component.changeToPage(2);
});
it('should emit totalPages if page is more than page.totalPages', () => {
component.workbasketsResource = new WorkbasketSummaryResource(undefined, undefined, new Page(6, 3, 3, 1));
component.previousPageSelected = 2;
fixture.detectChanges();
component.changePage.subscribe(value => {
expect(value).toBe(3)
})
component.changeToPage(100);
});
it('should emit 1 if page is less than 1', () => {
component.workbasketsResource = new WorkbasketSummaryResource(undefined, undefined, new Page(6, 3, 3, 1));
component.previousPageSelected = 2;
fixture.detectChanges();
component.changePage.subscribe(value => {
expect(value).toBe(1)
})
component.changeToPage(0);
});
});

View File

@ -0,0 +1,36 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource';
@Component({
selector: 'taskana-pagination',
templateUrl: './pagination.component.html',
styleUrls: ['./pagination.component.scss']
})
export class PaginationComponent implements OnInit {
@Input()
workbasketsResource: WorkbasketSummaryResource;
@Output()
workbasketsResourceChange = new EventEmitter<WorkbasketSummaryResource>();
@Output() changePage = new EventEmitter<number>();
previousPageSelected = 1;
pageSelected = 1;
maxPagesAvailable = 8;
constructor() { }
ngOnInit() {
}
changeToPage(page) {
if (page < 1) { page = this.pageSelected = 1; }
if (page > this.workbasketsResource.page.totalPages) {
page = this.workbasketsResource.page.totalPages;
}
if (this.previousPageSelected !== page) {
this.changePage.emit(page);
this.previousPageSelected = page;
this.pageSelected = page;
}
}
}

View File

@ -1,34 +1,30 @@
<li id="wb-action-toolbar" class="list-group-item tab-align"> <li id="wb-action-toolbar" class="list-group-item tab-align">
<div class="row"> <div class="row">
<div class="col-xs-9"> <div class="col-xs-9">
<taskana-import-export-component [currentSelection]="'workbaskets'"></taskana-import-export-component>
<button type="button" (click)="addWorkbasket()" data-toggle="tooltip" title="Add" class="btn btn-default"> <button type="button" (click)="addWorkbasket()" data-toggle="tooltip" title="Add" class="btn btn-default">
<span class="glyphicon glyphicon-plus green" aria-hidden="true"></span> <span class="glyphicon glyphicon-plus green" aria-hidden="true"></span>
</button> </button>
<button type="button" data-toggle="tooltip" title="Edit" class="btn btn-default"> <button *ngIf="workbasketIdSelected" type="button" (click)="copyWorkbasket()" data-toggle="tooltip" title="copy" class="btn btn-default">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
</button>
<button *ngIf="workbasketIdSelected" type="button" (click)="copyWorkbasket()" data-toggle="tooltip" title="copy" class="btn btn-default">
<span class="glyphicon glyphicon-copy" aria-hidden="true"></span> <span class="glyphicon glyphicon-copy" aria-hidden="true"></span>
</button> </button>
<button *ngIf="workbasketIdSelected" type="button" data-toggle="tooltip" title="Remove distibution target" class="btn btn-default"> <button *ngIf="workbasketIdSelected" type="button" data-toggle="tooltip" title="Remove distibution target" class="btn btn-default">
<span class="glyphicon glyphicon-erase" aria-hidden="true"></span> <span class="glyphicon glyphicon-erase" aria-hidden="true"></span>
</button> </button>
<button *ngIf="workbasketIdSelected" type="button" (click)="removeWorkbasket()" data-toggle="tooltip" title="Remove" class="btn btn-default remove"> <button *ngIf="workbasketIdSelected" type="button" (click)="removeWorkbasket()" data-toggle="tooltip" title="Remove" class="btn btn-default remove">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span> <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button> </button>
<taskana-import-export-component [currentSelection]="'workbaskets'"></taskana-import-export-component>
</div> </div>
<div class="pull-right margin-right"> <div class="pull-right margin-right">
<taskana-sort (performSorting)="sorting($event)"></taskana-sort> <taskana-sort (performSorting)="sorting($event)"></taskana-sort>
<div class="clearfix btn-group"> <div class="clearfix btn-group">
<button class="btn btn-default collapsed" type="button" id="collapsedMenufilterWb" data-toggle="collapse" data-target="#wb-filter-bar" <button class="btn btn-default collapsed" type="button" id="collapsedMenufilterWb" aria-expanded="false" (click)="toolbarState=!toolbarState">
aria-expanded="false">
<span class="glyphicon glyphicon-filter blue"></span> <span class="glyphicon glyphicon-filter blue"></span>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div [@toggle]="toolbarState" *ngIf="toolbarState" class="row no-overflow">
<taskana-filter target="wb-filter-bar" (performFilter)="filtering($event)"></taskana-filter> <taskana-filter (performFilter)="filtering($event)"></taskana-filter>
</div> </div>
</li> </li>

View File

@ -9,4 +9,4 @@
&>div{ &>div{
margin: 6px 0px; margin: 6px 0px;
} }
} }

View File

@ -1,4 +1,5 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Component, OnInit, Input, Output, EventEmitter, AfterViewChecked } from '@angular/core';
import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';
import { Router, ActivatedRoute } from '@angular/router'; import { Router, ActivatedRoute } from '@angular/router';
import { SortingModel } from 'app/models/sorting'; import { SortingModel } from 'app/models/sorting';
@ -11,10 +12,24 @@ import { AlertModel, AlertType } from 'app/models/alert';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service'; import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { AlertService} from 'app/services/alert/alert.service'; import { AlertService } from 'app/services/alert/alert.service';
@Component({ @Component({
selector: 'taskana-workbasket-list-toolbar', selector: 'taskana-workbasket-list-toolbar',
animations: [
trigger('toggle', [
state('*', style({ opacity: '1' })),
state('void', style({ opacity: '0' })),
transition('void => *', animate('300ms ease-in', keyframes([
style({ opacity: 0, height: '0px' }),
style({ opacity: 0.5, height: '50px' }),
style({ opacity: 1, height: '*' })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ opacity: 1, height: '*' }),
style({ opacity: 0.5, height: '50px' }),
style({ opacity: 0, height: '0px' })])))
]
)],
templateUrl: './workbasket-list-toolbar.component.html', templateUrl: './workbasket-list-toolbar.component.html',
styleUrls: ['./workbasket-list-toolbar.component.scss'] styleUrls: ['./workbasket-list-toolbar.component.scss']
}) })
@ -27,6 +42,7 @@ export class WorkbasketListToolbarComponent implements OnInit {
@Output() performSorting = new EventEmitter<SortingModel>(); @Output() performSorting = new EventEmitter<SortingModel>();
@Output() performFilter = new EventEmitter<FilterModel>(); @Output() performFilter = new EventEmitter<FilterModel>();
workbasketServiceSubscription: Subscription; workbasketServiceSubscription: Subscription;
toolbarState = false;
constructor( constructor(
private workbasketService: WorkbasketService, private workbasketService: WorkbasketService,

View File

@ -1,51 +1,30 @@
<div class="workbasket-list-full-height"> <div class="workbasket-list-full-height">
<ul id="wb-list-container" class="list-group footer-space"> <div class="footer-space">
<taskana-workbasket-list-toolbar [workbaskets]="workbaskets" (performFilter)="performFilter($event)" (performSorting)="performSorting ($event)" <div #wbToolbar>
[(workbasketIdSelected)]="selectedId"></taskana-workbasket-list-toolbar> <taskana-workbasket-list-toolbar [workbaskets]="workbaskets" (performFilter)="performFilter($event)" (performSorting)="performSorting ($event)"
[(workbasketIdSelected)]="selectedId"></taskana-workbasket-list-toolbar>
</div>
<taskana-spinner [isRunning]="requestInProgress" class="centered-horizontally"></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" class="centered-horizontally"></taskana-spinner>
<li class="list-group-item" *ngFor="let workbasket of workbaskets" [class.active]="workbasket.workbasketId == selectedId" <div>
type="text" (click)="selectWorkbasket(workbasket.workbasketId)"> <ul #wbList id="wb-list-container" class="list-group">
<div class="row">
<dl class="col-xs-1"> <li class="list-group-item" *ngFor="let workbasket of workbaskets" [class.active]="workbasket.workbasketId == selectedId"
<dt> type="text" (click)="selectWorkbasket(workbasket.workbasketId)">
<taskana-icon-type class="vertical-align" [type]="workbasket.type" [selected]="workbasket.workbasketId === selectedId"></taskana-icon-type> <div class="row">
</dt> <dl class="col-xs-1">
</dl> <taskana-icon-type class="vertical-align" [type]="workbasket.type" [selected]="workbasket.workbasketId === selectedId"></taskana-icon-type>
<dl class="col-xs-10"> </dl>
<dt>{{workbasket.name}}, <dl class="col-xs-10">
<i>{{workbasket.key}} </i> <dt>{{workbasket.name}},
</dt> <i>{{workbasket.key}} </i>
<dd>{{workbasket.description}} &nbsp;</dd> </dt>
<dd>{{workbasket.owner}} &nbsp;</dd> <dd>{{workbasket.description}} &nbsp;</dd>
</dl> <dd>{{workbasket.owner}} &nbsp;</dd>
</div> </dl>
</li> </div>
</ul> </li>
<ul id="wb-pagination" class="pagination vertical-center"> </ul>
<li> </div>
<a href="#" aria-label="Previous"> </div>
<span aria-hidden="true">«</span> <taskana-pagination [(workbasketsResource)]="workbasketsResource" (changePage)="changePage($event)"></taskana-pagination>
</a>
</li>
<li>
<a href="">1</a>
</li>
<li>
<a href="">2</a>
</li>
<li>
<a href="">3</a>
</li>
<li>
<a href="">4</a>
</li>
<li>
<a href="">5</a>
</li>
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</div> </div>

View File

@ -15,9 +15,17 @@ a > label{
height: 2em; height: 2em;
width: 100%; width: 100%;
} }
dd, dt {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
dt > i { dt > i {
font-weight: normal; font-weight: normal;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
} }
li > div.row > dl { li > div.row > dl {

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, Input, Output, EventEmitter } from '@angular/core';
import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing'; import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
@ -12,38 +12,50 @@ import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { Links } from 'app/models/links'; import { Links } from 'app/models/links';
import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource'; import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource';
import { FilterModel } from 'app/models/filter'; import { FilterModel } from 'app/models/filter';
import { LinksWorkbasketSummary } from 'app/models/links-workbasket-summary';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { AlertService } from 'app/services/alert/alert.service'; import { AlertService } from 'app/services/alert/alert.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service'; import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { OrientationService } from 'app/services/orientation/orientation.service';
import { WorkbasketListComponent } from './workbasket-list.component'; import { WorkbasketListComponent } from './workbasket-list.component';
import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar/workbasket-list-toolbar.component'; import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar/workbasket-list-toolbar.component';
import { IconTypeComponent } from 'app/shared/type-icon/icon-type.component'; import { IconTypeComponent } from 'app/shared/type-icon/icon-type.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.component'; import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { SortComponent } from 'app/shared/sort/sort.component'; import { SortComponent } from 'app/shared/sort/sort.component';
import { ImportExportComponent } from '../../../import-export/import-export.component';
import { RemoveNoneTypePipe } from 'app/pipes/removeNoneType/remove-none-type.pipe'; import { RemoveNoneTypePipe } from 'app/pipes/removeNoneType/remove-none-type.pipe';
import { MapValuesPipe } from 'app/pipes/mapValues/map-values.pipe'; import { MapValuesPipe } from 'app/pipes/mapValues/map-values.pipe';
import {ClassificationService} from '../../../../services/classification/classification.service'; import { ClassificationService } from '../../../../services/classification/classification.service';
import {WorkbasketDefinitionService} from '../../../../services/workbasket/workbasketDefinition.service'; import { WorkbasketDefinitionService } from '../../../../services/workbasket/workbasketDefinition.service';
import {ImportExportComponent} from '../../../import-export/import-export.component';
@Component({ @Component({
selector: 'taskana-dummy-detail', selector: 'taskana-dummy-detail',
template: 'dummydetail' template: 'dummydetail'
}) })
export class DummyDetailComponent { class DummyDetailComponent {
}
@Component({
selector: 'taskana-pagination',
template: 'dummydetail'
})
class PaginationComponent {
@Input()
workbasketsResource: any;
@Output()
workbasketsResourceChange = new EventEmitter<any>();
@Output() changePage = new EventEmitter<any>();
} }
@Component({ @Component({
selector: 'taskana-filter', selector: 'taskana-filter',
template: '' template: ''
}) })
export class FilterComponent { class FilterComponent {
} }
@ -51,7 +63,7 @@ const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSumma
'workbaskets': new Array<WorkbasketSummary>( 'workbaskets': new Array<WorkbasketSummary>(
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''), new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''),
new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', '')) new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', ''))
}, new Links({ 'href': 'url' })); }, new LinksWorkbasketSummary({ 'href': 'url' }));
describe('WorkbasketListComponent', () => { describe('WorkbasketListComponent', () => {
@ -59,9 +71,11 @@ describe('WorkbasketListComponent', () => {
let fixture: ComponentFixture<WorkbasketListComponent>; let fixture: ComponentFixture<WorkbasketListComponent>;
let debugElement: any = undefined; let debugElement: any = undefined;
let workbasketService: WorkbasketService; let workbasketService: WorkbasketService;
let workbasketSummarySpy;
const routes: Routes = [ const routes: Routes = [
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' } { path: ':id', component: DummyDetailComponent, outlet: 'detail' },
{ path: 'workbaskets', component: DummyDetailComponent }
]; ];
@ -69,7 +83,7 @@ describe('WorkbasketListComponent', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [WorkbasketListComponent, DummyDetailComponent, SpinnerComponent, FilterComponent, WorkbasketListToolbarComponent, declarations: [WorkbasketListComponent, DummyDetailComponent, SpinnerComponent, FilterComponent, WorkbasketListToolbarComponent,
RemoveNoneTypePipe, IconTypeComponent, SortComponent, MapValuesPipe, ImportExportComponent], RemoveNoneTypePipe, IconTypeComponent, SortComponent, PaginationComponent, ImportExportComponent, MapValuesPipe],
imports: [ imports: [
AngularSvgIconModule, AngularSvgIconModule,
HttpModule, HttpModule,
@ -77,7 +91,7 @@ describe('WorkbasketListComponent', () => {
RouterTestingModule.withRoutes(routes) RouterTestingModule.withRoutes(routes)
], ],
providers: [WorkbasketService, ErrorModalService, RequestInProgressService, AlertService, providers: [WorkbasketService, ErrorModalService, RequestInProgressService, AlertService,
ClassificationService, WorkbasketDefinitionService] ClassificationService, WorkbasketDefinitionService, OrientationService]
}) })
.compileComponents(); .compileComponents();
@ -86,13 +100,14 @@ describe('WorkbasketListComponent', () => {
component = fixture.componentInstance; component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement; debugElement = fixture.debugElement.nativeElement;
workbasketService = TestBed.get(WorkbasketService); workbasketService = TestBed.get(WorkbasketService);
spyOn(workbasketService, 'getWorkBasketsSummary').and.returnValue(Observable.of(workbasketSummaryResource)); workbasketSummarySpy = spyOn(workbasketService, 'getWorkBasketsSummary').and.returnValue(Observable.of(workbasketSummaryResource));
spyOn(workbasketService, 'getSelectedWorkBasket').and.returnValue(Observable.of('2')); spyOn(workbasketService, 'getSelectedWorkBasket').and.returnValue(Observable.of('2'));
fixture.detectChanges(); fixture.detectChanges();
})); }));
afterEach(() => { afterEach(() => {
fixture.detectChanges()
document.body.removeChild(debugElement); document.body.removeChild(debugElement);
}) })
@ -119,11 +134,16 @@ describe('WorkbasketListComponent', () => {
expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(2); expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(2);
}); });
it('should have two workbasketsummary rows created with the second one selected.', () => { it('should have two workbasketsummary rows created with the second one selected.', fakeAsync(() => {
expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(2); tick(0);
expect(debugElement.querySelectorAll('#wb-list-container > li')[0].getAttribute('class')).toBe('list-group-item'); fixture.detectChanges();
expect(debugElement.querySelectorAll('#wb-list-container > li')[1].getAttribute('class')).toBe('list-group-item active'); fixture.whenStable().then(() => {
}); expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(2);
expect(debugElement.querySelectorAll('#wb-list-container > li')[0].getAttribute('class')).toBe('list-group-item');
expect(debugElement.querySelectorAll('#wb-list-container > li')[1].getAttribute('class')).toBe('list-group-item active');
})
}));
it('should have two workbasketsummary rows created with two different icons: user and users', () => { it('should have two workbasketsummary rows created with two different icons: user and users', () => {
expect(debugElement.querySelectorAll('#wb-list-container > li')[0] expect(debugElement.querySelectorAll('#wb-list-container > li')[0]
@ -145,8 +165,9 @@ describe('WorkbasketListComponent', () => {
const type = 'PERSONAL', name = 'someName', description = 'someDescription', owner = 'someOwner', key = 'someKey'; const type = 'PERSONAL', name = 'someName', description = 'someDescription', owner = 'someOwner', key = 'someKey';
const filter = new FilterModel(type, name, description, owner, key); const filter = new FilterModel(type, name, description, owner, key);
component.performFilter(filter); component.performFilter(filter);
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalledWith(true, 'key', 'asc', undefined,
name, description, undefined, owner, type, undefined, key); expect(workbasketSummarySpy.calls.all()[1].args).toEqual([true, 'key', 'asc',
undefined, 'someName', 'someDescription', undefined, 'someOwner', 'PERSONAL', undefined, 'someKey', undefined]);
})); }));

View File

@ -1,4 +1,7 @@
import { Component, OnInit, EventEmitter, OnDestroy, ChangeDetectorRef } from '@angular/core'; import {
Component, OnInit, EventEmitter, OnDestroy,
HostListener, ViewChild, ElementRef, AfterViewChecked
} from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router'; import { Router, ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
@ -6,8 +9,10 @@ import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resourc
import { WorkbasketSummary } from 'app/models/workbasket-summary'; import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { FilterModel } from 'app/models/filter' import { FilterModel } from 'app/models/filter'
import { SortingModel } from 'app/models/sorting'; import { SortingModel } from 'app/models/sorting';
import { Orientation } from 'app/models/orientation';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service' import { WorkbasketService } from 'app/services/workbasket/workbasket.service'
import { OrientationService } from 'app/services/orientation/orientation.service';
@Component({ @Component({
selector: 'taskana-workbasket-list', selector: 'taskana-workbasket-list',
@ -17,37 +22,39 @@ import { WorkbasketService } from 'app/services/workbasket/workbasket.service'
export class WorkbasketListComponent implements OnInit, OnDestroy { export class WorkbasketListComponent implements OnInit, OnDestroy {
selectedId = ''; selectedId = '';
workbasketsResource: WorkbasketSummaryResource;
workbaskets: Array<WorkbasketSummary> = []; workbaskets: Array<WorkbasketSummary> = [];
requestInProgress = false; requestInProgress = false;
sort: SortingModel = new SortingModel(); sort: SortingModel = new SortingModel();
filterBy: FilterModel = new FilterModel(); filterBy: FilterModel = new FilterModel();
@ViewChild('wbToolbar')
private toolbarElement: ElementRef;
private workBasketSummarySubscription: Subscription; private workBasketSummarySubscription: Subscription;
private workbasketServiceSubscription: Subscription; private workbasketServiceSubscription: Subscription;
private workbasketServiceSavedSubscription: Subscription; private workbasketServiceSavedSubscription: Subscription;
private orientationSubscription: Subscription;
constructor( constructor(
private workbasketService: WorkbasketService, private workbasketService: WorkbasketService,
private router: Router, private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private cdRef: ChangeDetectorRef) { } private orientationService: OrientationService) { }
ngOnInit() { ngOnInit() {
this.requestInProgress = true; this.requestInProgress = true;
this.workBasketSummarySubscription = this.workbasketService.getWorkBasketsSummary().subscribe(resultList => {
this.workbaskets = resultList._embedded ? resultList._embedded.workbaskets : [];
this.requestInProgress = false;
});
this.workbasketServiceSubscription = this.workbasketService.getSelectedWorkBasket().subscribe(workbasketIdSelected => { this.workbasketServiceSubscription = this.workbasketService.getSelectedWorkBasket().subscribe(workbasketIdSelected => {
this.selectedId = workbasketIdSelected; //TODO should be done in a different way.
this.cdRef.detectChanges(); setTimeout(() => { this.selectedId = workbasketIdSelected; }, 0);
}); });
this.workbasketServiceSavedSubscription = this.workbasketService.workbasketSavedTriggered().subscribe(value => { this.workbasketServiceSavedSubscription = this.workbasketService.workbasketSavedTriggered().subscribe(value => {
this.performRequest(); this.performRequest();
}); });
this.orientationSubscription = this.orientationService.getOrientation().subscribe((orientation: Orientation) => {
this.refreshWorkbasketList();
})
} }
selectWorkbasket(id: string) { selectWorkbasket(id: string) {
@ -69,17 +76,34 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
this.performRequest(); this.performRequest();
} }
changePage(page) {
this.workbasketService.page = page;
this.performRequest();
}
private refreshWorkbasketList() {
const toolbarSize = this.toolbarElement.nativeElement.offsetHeight;
const cardHeight = 75;
const unusedHeight = 140
const totalHeight = window.innerHeight;
const cards = Math.round((totalHeight - (unusedHeight + toolbarSize)) / cardHeight);
this.workbasketService.pageSize = cards;
this.performRequest();
}
private performRequest(): void { private performRequest(): void {
this.requestInProgress = true; this.requestInProgress = true;
this.workbaskets = []; this.workbaskets = [];
this.workbasketServiceSubscription.add(this.workbasketService.getWorkBasketsSummary(true, this.sort.sortBy, this.workbasketServiceSubscription = this.workbasketService.getWorkBasketsSummary(
this.sort.sortDirection, undefined, true, this.sort.sortBy, this.sort.sortDirection, undefined,
this.filterBy.name, this.filterBy.description, undefined, this.filterBy.owner, this.filterBy.name, this.filterBy.description, undefined, this.filterBy.owner,
this.filterBy.type, undefined, this.filterBy.key).subscribe(resultList => { this.filterBy.type, undefined, this.filterBy.key, undefined)
.subscribe(resultList => {
this.workbasketsResource = resultList;
this.workbaskets = resultList._embedded ? resultList._embedded.workbaskets : []; this.workbaskets = resultList._embedded ? resultList._embedded.workbaskets : [];
this.requestInProgress = false; this.requestInProgress = false;
this.unSelectWorkbasket(); this.unSelectWorkbasket();
})); });
} }
@ -90,10 +114,10 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
} }
ngOnDestroy() { ngOnDestroy() {
this.workBasketSummarySubscription.unsubscribe(); if (this.workBasketSummarySubscription) { this.workBasketSummarySubscription.unsubscribe(); }
this.workbasketServiceSubscription.unsubscribe(); if (this.workbasketServiceSubscription) { this.workbasketServiceSubscription.unsubscribe(); }
this.workbasketServiceSavedSubscription.unsubscribe(); if (this.workbasketServiceSavedSubscription) { this.workbasketServiceSavedSubscription.unsubscribe(); }
if (this.orientationSubscription) { this.orientationSubscription.unsubscribe(); }
} }
} }

View File

@ -1,4 +1,4 @@
<nav class="navbar navbar-fixed-top"> <nav class="navbar navbar-fixed-top" (window:resize)="onResize($event)">
<div class="navbar show no-border-radius navbar-inverse no-gutter"> <div class="navbar show no-border-radius navbar-inverse no-gutter">
<div class="col-xs-3 col-md-4"> <div class="col-xs-3 col-md-4">
<svg-icon class="logo hidden visible-xs visible-sm" src="./assets/icons/logo.svg"></svg-icon> <svg-icon class="logo hidden visible-xs visible-sm" src="./assets/icons/logo.svg"></svg-icon>

View File

@ -8,6 +8,7 @@ import { HttpClientModule } from '@angular/common/http';
import { ErrorModalService } from './services/errorModal/error-modal.service'; import { ErrorModalService } from './services/errorModal/error-modal.service';
import { RequestInProgressService } from './services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from './services/requestInProgress/request-in-progress.service';
import { AlertService } from './services/alert/alert.service'; import { AlertService } from './services/alert/alert.service';
import { OrientationService } from './services/orientation/orientation.service';
import { GeneralMessageModalComponent } from './shared/general-message-modal/general-message-modal.component' import { GeneralMessageModalComponent } from './shared/general-message-modal/general-message-modal.component'
import { SpinnerComponent } from './shared/spinner/spinner.component' import { SpinnerComponent } from './shared/spinner/spinner.component'
@ -32,7 +33,7 @@ describe('AppComponent', () => {
RouterTestingModule.withRoutes(routes), RouterTestingModule.withRoutes(routes),
HttpClientModule HttpClientModule
], ],
providers: [ErrorModalService, RequestInProgressService, AlertService] providers: [ErrorModalService, RequestInProgressService, AlertService, OrientationService]
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(AppComponent); fixture = TestBed.createComponent(AppComponent);

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, HostListener } from '@angular/core';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { Router, NavigationStart } from '@angular/router'; import { Router, NavigationStart } from '@angular/router';
@ -6,6 +6,7 @@ import { ErrorModel } from './models/modal-error';
import { ErrorModalService } from './services/errorModal/error-modal.service'; import { ErrorModalService } from './services/errorModal/error-modal.service';
import { RequestInProgressService } from './services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from './services/requestInProgress/request-in-progress.service';
import { OrientationService } from './services/orientation/orientation.service';
@Component({ @Component({
selector: 'taskana-root', selector: 'taskana-root',
@ -25,10 +26,16 @@ export class AppComponent implements OnInit {
requestInProgress = false; requestInProgress = false;
@HostListener('window:resize', ['$event'])
onResize(event) {
this.orientationService.onResize();
}
constructor( constructor(
private router: Router, private router: Router,
private errorModalService: ErrorModalService, private errorModalService: ErrorModalService,
private requestInProgressService: RequestInProgressService) { private requestInProgressService: RequestInProgressService,
private orientationService: OrientationService) {
} }
ngOnInit() { ngOnInit() {

View File

@ -31,6 +31,9 @@ import { IconTypeComponent } from './shared/type-icon/icon-type.component';
import { AlertComponent } from './shared/alert/alert.component'; import { AlertComponent } from './shared/alert/alert.component';
import { SortComponent } from './shared/sort/sort.component'; import { SortComponent } from './shared/sort/sort.component';
import { GeneralMessageModalComponent } from './shared/general-message-modal/general-message-modal.component'; import { GeneralMessageModalComponent } from './shared/general-message-modal/general-message-modal.component';
import { PaginationComponent } from './administration/workbasket/master/list/pagination/pagination.component';
import { ClassificationListComponent } from './administration/classification/master/list/classification-list.component';
import { ImportExportComponent } from './administration/import-export/import-export.component';
// Shared // Shared
import { MasterAndDetailComponent } from './shared/masterAndDetail/master-and-detail.component'; import { MasterAndDetailComponent } from './shared/masterAndDetail/master-and-detail.component';
@ -47,8 +50,9 @@ import { AlertService } from './services/alert/alert.service';
import { ErrorModalService } from './services/errorModal/error-modal.service'; import { ErrorModalService } from './services/errorModal/error-modal.service';
import { RequestInProgressService } from './services/requestInProgress/request-in-progress.service'; import { RequestInProgressService } from './services/requestInProgress/request-in-progress.service';
import { SavingWorkbasketService } from './services/saving-workbaskets/saving-workbaskets.service'; import { SavingWorkbasketService } from './services/saving-workbaskets/saving-workbaskets.service';
import { OrientationService } from './services/orientation/orientation.service';
import { ClassificationService } from './services/classification/classification.service';
import { WorkbasketDefinitionService } from './services/workbasket/workbasketDefinition.service';
/** /**
* Pipes * Pipes
@ -56,10 +60,7 @@ import { SavingWorkbasketService } from './services/saving-workbaskets/saving-wo
import { MapValuesPipe } from './pipes/mapValues/map-values.pipe'; import { MapValuesPipe } from './pipes/mapValues/map-values.pipe';
import { RemoveNoneTypePipe } from './pipes/removeNoneType/remove-none-type.pipe'; import { RemoveNoneTypePipe } from './pipes/removeNoneType/remove-none-type.pipe';
import { SelectWorkBasketPipe } from './pipes/selectedWorkbasket/seleted-workbasket.pipe'; import { SelectWorkBasketPipe } from './pipes/selectedWorkbasket/seleted-workbasket.pipe';
import {ClassificationListComponent} from './administration/classification/master/list/classification-list.component'; import { SpreadNumberPipe } from './pipes/spreadNumber/spread-number';
import {ClassificationService} from './services/classification/classification.service';
import {WorkbasketDefinitionService} from './services/workbasket/workbasketDefinition.service';
import {ImportExportComponent} from './administration/import-export/import-export.component';
const MODULES = [ const MODULES = [
BrowserModule, BrowserModule,
@ -91,11 +92,13 @@ const DECLARATIONS = [
DistributionTargetsComponent, DistributionTargetsComponent,
SortComponent, SortComponent,
DualListComponent, DualListComponent,
PaginationComponent,
ClassificationListComponent,
ImportExportComponent,
MapValuesPipe, MapValuesPipe,
RemoveNoneTypePipe, RemoveNoneTypePipe,
SelectWorkBasketPipe, SelectWorkBasketPipe,
ClassificationListComponent, SpreadNumberPipe
ImportExportComponent
]; ];
@NgModule({ @NgModule({
@ -105,8 +108,8 @@ const DECLARATIONS = [
WorkbasketService, WorkbasketService,
MasterAndDetailService, MasterAndDetailService,
PermissionService, PermissionService,
ClassificationService, ClassificationService,
WorkbasketDefinitionService, WorkbasketDefinitionService,
{ {
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,
useClass: HttpClientInterceptor, useClass: HttpClientInterceptor,
@ -115,7 +118,8 @@ const DECLARATIONS = [
AlertService, AlertService,
ErrorModalService, ErrorModalService,
RequestInProgressService, RequestInProgressService,
SavingWorkbasketService SavingWorkbasketService,
OrientationService
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -0,0 +1,10 @@
import { Links } from './links';
export class LinksWorkbasketSummary extends Links {
constructor(
self = undefined,
distributionTargets = undefined,
accessItems = undefined,
public allWorkbaskets: { 'href': string } = undefined
) { super(self, distributionTargets, accessItems) }
}

View File

@ -0,0 +1,4 @@
export enum Orientation {
landscape = 'landscape',
portrait = 'portrait'
}

View File

@ -0,0 +1,8 @@
export class Page {
constructor(
public size: number = undefined,
public totalElements: number = undefined,
public totalPages: number = undefined,
public number: number = undefined
) { }
}

View File

@ -1,8 +1,14 @@
import { WorkbasketSummary } from './workbasket-summary'; import { WorkbasketSummary } from './workbasket-summary';
import { Links } from './links'; import { Page } from 'app/models/page';
import { LinksWorkbasketSummary } from './links-workbasket-summary';
export class WorkbasketSummaryResource { export class WorkbasketSummaryResource {
constructor(public _embedded: { 'workbaskets': Array<WorkbasketSummary> } = constructor(
{ 'workbaskets': [] }, public _links: Links = null) { public _embedded: {
'workbaskets': Array<WorkbasketSummary>
} = { 'workbaskets': [] },
public _links: LinksWorkbasketSummary = new LinksWorkbasketSummary(),
public page: Page = new Page()
) {
} }
} }

View File

@ -1,5 +1,6 @@
import { Links } from './links';
import { ICONTYPES } from './type'; import { ICONTYPES } from './type';
import { Page } from './page';
import { Links } from './links';
export class WorkbasketSummary { export class WorkbasketSummary {
constructor( constructor(
@ -15,6 +16,7 @@ export class WorkbasketSummary {
public orgLevel2: string = undefined, public orgLevel2: string = undefined,
public orgLevel3: string = undefined, public orgLevel3: string = undefined,
public orgLevel4: string = undefined, public orgLevel4: string = undefined,
public _links: Links = undefined) { public _links: Links = undefined,
public page: Page = undefined) {
} }
} }

View File

@ -0,0 +1,33 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'spreadNumber' })
export class SpreadNumberPipe implements PipeTransform {
transform(value: number, currentIndex: number, maxArrayElements: number, maxPageNumber: number): number[] {
const returnArray = new Array();
if (maxPageNumber <= 5) {
for (let i = 0; i < maxPageNumber; i++) {
returnArray.push(i);
}
return returnArray;
}
let minArrayValue = (currentIndex - maxArrayElements / 2);
let maxArrayValue = (currentIndex + maxArrayElements / 2);
let leftDifference = 0;
let rightDifference = 0;
if (minArrayValue < 0) { leftDifference = Math.abs(minArrayValue); minArrayValue = 0; }
if (maxArrayValue > maxPageNumber) {
rightDifference = maxArrayValue - maxPageNumber;
maxArrayValue = maxPageNumber;
}
const minIndex = (minArrayValue - rightDifference) <= 0 ? 0 : minArrayValue - rightDifference;
const maxIndex = (maxArrayValue + leftDifference) > maxPageNumber ? maxPageNumber : maxArrayValue + leftDifference;
for (let i = minIndex; i < maxIndex; i++) {
returnArray.push(i);
}
return returnArray;
}
}

View File

@ -0,0 +1,14 @@
import { TestBed, inject } from '@angular/core/testing';
import { OrientationService } from './orientation.service';
describe('OrientationService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [OrientationService]
});
});
it('should be created', inject([OrientationService], (service: OrientationService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,43 @@
import { Injectable, HostListener } from '@angular/core';
import { Orientation } from 'app/models/orientation';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class OrientationService {
private lock = false;
private currentOrientation = undefined;
public orientation = new BehaviorSubject<Orientation>(this.currentOrientation);
constructor() { }
onResize() {
const orientation = this.detectOrientation();
if (orientation !== this.currentOrientation) {
this.currentOrientation = orientation;
if (!this.lock) {
this.lock = !this.lock;
setTimeout(() => this.changeOrientation(orientation), 250);
}
}
}
getOrientation(): Observable<Orientation> {
return this.orientation.asObservable();
}
changeOrientation(orientation: Orientation) {
this.lock = !this.lock;
if (this.currentOrientation) {
this.orientation.next(orientation);
}
}
private detectOrientation(): Orientation {
if (window.innerHeight > window.innerWidth) {
return Orientation.portrait;
}
return Orientation.landscape;
}
}

View File

@ -27,27 +27,27 @@ describe('WorkbasketService ', () => {
it('should have a valid query parameter expression sortBy=key, order=asc as default', () => { it('should have a valid query parameter expression sortBy=key, order=asc as default', () => {
workbasketService.getWorkBasketsSummary(); workbasketService.getWorkBasketsSummary();
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=key&order=asc', expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=key&order=asc&page=1&pagesize=9',
jasmine.any(Object)); jasmine.any(Object));
}); });
it('should have a valid query parameter expression with sortBy=name and order=desc', () => { it('should have a valid query parameter expression with sortBy=name and order=desc', () => {
workbasketService.getWorkBasketsSummary(undefined, 'name', Direction.DESC); workbasketService.getWorkBasketsSummary(undefined, 'name', Direction.DESC);
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=name&order=desc', expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=name&order=desc&page=1&pagesize=9',
jasmine.any(Object)); jasmine.any(Object));
}); });
it('should have a valid query parameter expression with sortBy=name and order=desc and descLike=some description ', () => { it('should have a valid query parameter expression with sortBy=name and order=desc and descLike=some description ', () => {
workbasketService.getWorkBasketsSummary(undefined, 'name', Direction.DESC, undefined, undefined, 'some description'); workbasketService.getWorkBasketsSummary(undefined, 'name', Direction.DESC, undefined, undefined, 'some description');
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/' + expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=name&order=desc' +
'?sortBy=name&order=desc&descLike=some description', jasmine.any(Object)); '&descLike=some description&page=1&pagesize=9', jasmine.any(Object));
}); });
it('should have a valid query parameter expression with sortBy=key, order=asc, descLike=some description and type=group ', () => { it('should have a valid query parameter expression with sortBy=key, order=asc, descLike=some description and type=group ', () => {
workbasketService.getWorkBasketsSummary(undefined, 'name', Direction.DESC, workbasketService.getWorkBasketsSummary(undefined, 'name', Direction.DESC,
undefined, undefined, 'some description', undefined, undefined, 'group'); undefined, undefined, 'some description', undefined, undefined, 'group');
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/' + expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/' +
'?sortBy=name&order=desc&descLike=some description&type=group', jasmine.any(Object)); '?sortBy=name&order=desc&descLike=some description&type=group&page=1&pagesize=9', jasmine.any(Object));
}); });
}); });
}); });

View File

@ -36,6 +36,10 @@ export class WorkbasketService {
// Access // Access
readonly REQUIREDPERMISSION = 'requiredPermission'; readonly REQUIREDPERMISSION = 'requiredPermission';
// Pagination
readonly PAGE = 'page';
readonly PAGESIZE = 'pagesize';
httpOptions = { httpOptions = {
headers: new HttpHeaders({ headers: new HttpHeaders({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -43,6 +47,9 @@ export class WorkbasketService {
}) })
}; };
page = 1;
pageSize = 9;
private workbasketSummaryRef: Observable<WorkbasketSummaryResource>; private workbasketSummaryRef: Observable<WorkbasketSummaryResource>;
constructor(private httpClient: HttpClient) { } constructor(private httpClient: HttpClient) { }
@ -60,13 +67,16 @@ export class WorkbasketService {
type: string = undefined, type: string = undefined,
key: string = undefined, key: string = undefined,
keyLike: string = undefined, keyLike: string = undefined,
requiredPermission: string = undefined): Observable<WorkbasketSummaryResource> { requiredPermission: string = undefined,
allPages: boolean = false): Observable<WorkbasketSummaryResource> {
if (this.workbasketSummaryRef && !forceRequest) { if (this.workbasketSummaryRef && !forceRequest) {
return this.workbasketSummaryRef; return this.workbasketSummaryRef;
} }
return this.workbasketSummaryRef = this.httpClient.get<WorkbasketSummaryResource>( return this.workbasketSummaryRef = this.httpClient.get<WorkbasketSummaryResource>(
`${environment.taskanaRestUrl}/v1/workbaskets/${this.getWorkbasketSummaryQueryParameters(sortBy, order, name, `${environment.taskanaRestUrl}/v1/workbaskets/${this.getWorkbasketSummaryQueryParameters(
nameLike, descLike, owner, ownerLike, type, key, keyLike, requiredPermission)}`, this.httpOptions); sortBy, order, name,
nameLike, descLike, owner, ownerLike, type, key, keyLike, requiredPermission,
!allPages ? this.page : undefined, !allPages ? this.pageSize : undefined)}`, this.httpOptions);
} }
// GET // GET
@ -135,6 +145,7 @@ export class WorkbasketService {
workbasketSavedTriggered(): Observable<number> { workbasketSavedTriggered(): Observable<number> {
return this.workBasketSaved.asObservable(); return this.workBasketSaved.asObservable();
} }
// #endregion // #endregion
// #region private // #region private
@ -148,7 +159,9 @@ export class WorkbasketService {
type: string, type: string,
key: string, key: string,
keyLike: string, keyLike: string,
requiredPermission: string): string { requiredPermission: string,
page: number,
pageSize: number): string {
let query = '?'; let query = '?';
query += sortBy ? `${this.SORTBY}=${sortBy}&` : ''; query += sortBy ? `${this.SORTBY}=${sortBy}&` : '';
query += order ? `${this.ORDER}=${order}&` : ''; query += order ? `${this.ORDER}=${order}&` : '';
@ -161,6 +174,8 @@ export class WorkbasketService {
query += key ? `${this.KEY}=${key}&` : ''; query += key ? `${this.KEY}=${key}&` : '';
query += keyLike ? `${this.KEYLIKE}=${keyLike}&` : ''; query += keyLike ? `${this.KEYLIKE}=${keyLike}&` : '';
query += requiredPermission ? `${this.REQUIREDPERMISSION}=${requiredPermission}&` : ''; query += requiredPermission ? `${this.REQUIREDPERMISSION}=${requiredPermission}&` : '';
query += page ? `${this.PAGE}=${page}&` : '';
query += pageSize ? `${this.PAGESIZE}=${pageSize}&` : '';
if (query.lastIndexOf('&') === query.length - 1) { if (query.lastIndexOf('&') === query.length - 1) {
query = query.slice(0, query.lastIndexOf('&')) query = query.slice(0, query.lastIndexOf('&'))

View File

@ -1,14 +1,14 @@
<div id="{{target}}" data-toogle="collapse" class="list-group-search collapse"> <div id="{{target}}" class="list-group-search">
<div class="row"> <div class="row">
<div class="dropdown col-xs-2"> <div class="dropdown col-xs-2">
<button class="btn btn-default" (click)="toggleDropDown = !toggleDropDown" type="button" id="dropdownMenufilter" data-toggle="dropdown" <button class="btn btn-default" data-toggle="dropdown" type="button" id="dropdownMenufilter" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true"> aria-haspopup="true" aria-expanded="true">
<taskana-icon-type [type]="filter.type" class="vertical-align"></taskana-icon-type> <taskana-icon-type [type]="filter.type" class="vertical-align"></taskana-icon-type>
</button> </button>
<ul class="dropdown-menu dropdown-menu-users" [ngClass]="{'hidden': !toggleDropDown}" role="menu"> <ul class="dropdown-menu dropdown-menu-users" role="menu">
<li *ngFor="let type of allTypes | mapValues"> <li *ngFor="let type of allTypes | mapValues">
<button type="button" (click)="selectType(type.key); toggleDropDown = !toggleDropDown; search()" class="btn btn-default btn-users-list" data-toggle="tooltip" <button type="button" (click)="selectType(type.key); search()" class="btn btn-default btn-users-list" data-toggle="tooltip"
[title]="type.value"> [title]="type.value">
<taskana-icon-type class="vertical-align" [type]='type.key'></taskana-icon-type> <taskana-icon-type class="vertical-align" [type]='type.key'></taskana-icon-type>
</button> </button>
@ -25,7 +25,7 @@
data-toggle="tooltip" title="Clear"> data-toggle="tooltip" title="Clear">
</button> </button>
</div> </div>
<div class="row"> <div class="row padding">
<div class="col-xs-2"> <div class="col-xs-2">
</div> </div>
<div class="col-xs-8"> <div class="col-xs-8">

View File

@ -10,9 +10,12 @@
} }
.list-group-search { .list-group-search {
padding: 10px 15px; padding: 0px 15px;
border-top: 1px solid #ddd; border-top: 1px solid #ddd;
} }
.list-group-search { .list-group-search {
border-top: none; border-top: none;
}
row.padding {
padding: 1px 0px;
} }

View File

@ -122,8 +122,8 @@
} }
.footer-space { .footer-space {
max-height: calc(100vh - 140px); max-height: calc(100vh - 130px);
height: calc(100vh - 140px); height: calc(100vh - 130px);
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
} }