From 83f0472bac163a861c19bfbe860dfa73222d24e0 Mon Sep 17 00:00:00 2001 From: Martin Rojas Miguel Angel Date: Tue, 27 Mar 2018 19:21:24 +0200 Subject: [PATCH] TSK-184 Add pagination feature --- .../access-items.component.spec.ts | 5 +- .../access-items/access-items.component.ts | 16 +++- .../distribution-targets.component.spec.ts | 15 ++-- .../distribution-targets.component.ts | 35 +++++--- .../dual-list/dual-list.component.html | 42 +++++----- .../dual-list/dual-list.component.scss | 30 +++++++ .../dual-list/dual-list.component.ts | 18 +++- .../workbasket-information.component.spec.ts | 4 +- .../workbasket-information.component.ts | 1 - .../details/workbasket-details.component.html | 14 ++-- .../workbasket-details.component.spec.ts | 9 +- .../details/workbasket-details.component.ts | 6 +- .../list/pagination/pagination.component.html | 15 ++++ .../list/pagination/pagination.component.scss | 11 +++ .../pagination/pagination.component.spec.ts | 84 +++++++++++++++++++ .../list/pagination/pagination.component.ts | 36 ++++++++ .../workbasket-list-toolbar.component.html | 18 ++-- .../workbasket-list-toolbar.component.scss | 2 +- .../workbasket-list-toolbar.component.ts | 20 ++++- .../list/workbasket-list.component.html | 75 ++++++----------- .../list/workbasket-list.component.scss | 8 ++ .../list/workbasket-list.component.spec.ts | 61 +++++++++----- .../master/list/workbasket-list.component.ts | 58 +++++++++---- web/src/app/app.component.html | 2 +- web/src/app/app.component.spec.ts | 3 +- web/src/app/app.component.ts | 11 ++- web/src/app/app.module.ts | 26 +++--- .../app/models/links-workbasket-summary.ts | 10 +++ web/src/app/models/orientation.ts | 4 + web/src/app/models/page.ts | 8 ++ .../app/models/workbasket-summary-resource.ts | 12 ++- web/src/app/models/workbasket-summary.ts | 6 +- .../app/pipes/spreadNumber/spread-number.ts | 33 ++++++++ .../orientation/orientation.service.spec.ts | 14 ++++ .../orientation/orientation.service.ts | 43 ++++++++++ .../workbasket/workbasket.service.spec.ts | 10 +-- .../services/workbasket/workbasket.service.ts | 23 ++++- .../app/shared/filter/filter.component.html | 10 +-- .../app/shared/filter/filter.component.scss | 5 +- web/src/assets/_site.scss | 4 +- 40 files changed, 610 insertions(+), 197 deletions(-) create mode 100644 web/src/app/administration/workbasket/master/list/pagination/pagination.component.html create mode 100644 web/src/app/administration/workbasket/master/list/pagination/pagination.component.scss create mode 100644 web/src/app/administration/workbasket/master/list/pagination/pagination.component.spec.ts create mode 100644 web/src/app/administration/workbasket/master/list/pagination/pagination.component.ts create mode 100644 web/src/app/models/links-workbasket-summary.ts create mode 100644 web/src/app/models/orientation.ts create mode 100644 web/src/app/models/page.ts create mode 100644 web/src/app/pipes/spreadNumber/spread-number.ts create mode 100644 web/src/app/services/orientation/orientation.service.spec.ts create mode 100644 web/src/app/services/orientation/orientation.service.ts diff --git a/web/src/app/administration/workbasket/details/access-items/access-items.component.spec.ts b/web/src/app/administration/workbasket/details/access-items/access-items.component.spec.ts index bc32deb93..02a8b1c52 100644 --- a/web/src/app/administration/workbasket/details/access-items/access-items.component.spec.ts +++ b/web/src/app/administration/workbasket/details/access-items/access-items.component.spec.ts @@ -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 { WorkbasketService } from 'app/services/workbasket/workbasket.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(alertService, 'triggerAlert').and.returnValue(Observable.of(true)), debugElement = fixture.debugElement.nativeElement; - + component.ngOnChanges({ + active: new SimpleChange(undefined, 'accessItems', true) + }); fixture.detectChanges(); }); diff --git a/web/src/app/administration/workbasket/details/access-items/access-items.component.ts b/web/src/app/administration/workbasket/details/access-items/access-items.component.ts index c56b8090c..6c2c2689f 100644 --- a/web/src/app/administration/workbasket/details/access-items/access-items.component.ts +++ b/web/src/app/administration/workbasket/details/access-items/access-items.component.ts @@ -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 { Workbasket } from 'app/models/workbasket'; @@ -20,12 +20,15 @@ declare var $: any; templateUrl: './access-items.component.html', styleUrls: ['./access-items.component.scss'] }) -export class AccessItemsComponent implements OnInit, OnDestroy { +export class AccessItemsComponent implements OnChanges, OnDestroy { + @Input() workbasket: Workbasket; @Input() action: string; + @Input() + active: string; badgeMessage = ''; accessItemsResource: WorkbasketAccessItemsResource; @@ -38,6 +41,7 @@ export class AccessItemsComponent implements OnInit, OnDestroy { modalErrorMessage: string; accessItemsubscription: Subscription; savingAccessItemsSubscription: Subscription; + private initialized = false; constructor( @@ -46,7 +50,13 @@ export class AccessItemsComponent implements OnInit, OnDestroy { private errorModalService: ErrorModalService, 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) { return; } diff --git a/web/src/app/administration/workbasket/details/distribution-targets/distribution-targets.component.spec.ts b/web/src/app/administration/workbasket/details/distribution-targets/distribution-targets.component.spec.ts index 183448f99..e071cba3e 100644 --- a/web/src/app/administration/workbasket/details/distribution-targets/distribution-targets.component.spec.ts +++ b/web/src/app/administration/workbasket/details/distribution-targets/distribution-targets.component.spec.ts @@ -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 { Observable } from 'rxjs/Observable'; 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 { IconTypeComponent } from 'app/shared/type-icon/icon-type.component'; import { SelectWorkBasketPipe } from 'app/pipes/selectedWorkbasket/seleted-workbasket.pipe'; +import { LinksWorkbasketSummary } from '../../../../models/links-workbasket-summary'; const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSummaryResource({ 'workbaskets': new Array( new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''), new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', '')) -}, new Links({ 'href': 'url' })); +}, new LinksWorkbasketSummary({ 'href': 'url' })); @Component({ selector: 'taskana-filter', @@ -73,16 +74,18 @@ describe('DistributionTargetsComponent', () => { new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })), new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })), new WorkbasketSummary('id3', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' }))) - }, new Links({ 'href': 'someurl' }))) + }, new LinksWorkbasketSummary({ 'href': 'someurl' }))) }) spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => { return Observable.of(new WorkbasketDistributionTargetsResource( { 'distributionTargets': new Array( 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(); }); @@ -144,7 +147,7 @@ describe('DistributionTargetsComponent', () => { 'distributionTargets': new Array( new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })), new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' }))) - }, new Links({ 'href': 'someurl' }))) + }, new LinksWorkbasketSummary({ 'href': 'someurl' }))) }) component.onSave(); fixture.detectChanges(); diff --git a/web/src/app/administration/workbasket/details/distribution-targets/distribution-targets.component.ts b/web/src/app/administration/workbasket/details/distribution-targets/distribution-targets.component.ts index 69f3b358a..c29f6b686 100644 --- a/web/src/app/administration/workbasket/details/distribution-targets/distribution-targets.component.ts +++ b/web/src/app/administration/workbasket/details/distribution-targets/distribution-targets.component.ts @@ -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 { Workbasket } from 'app/models/workbasket'; @@ -24,12 +24,14 @@ export enum Side { templateUrl: './distribution-targets.component.html', styleUrls: ['./distribution-targets.component.scss'] }) -export class DistributionTargetsComponent implements OnInit, OnDestroy { +export class DistributionTargetsComponent implements OnChanges, OnDestroy { @Input() workbasket: Workbasket; @Input() action: string; + @Input() + active: string; badgeMessage = ''; distributionTargetsSubscription: Subscription; @@ -44,12 +46,12 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy { distributionTargetsClone: Array; distributionTargetsSelectedClone: Array; - requestInProgress = false; requestInProgressLeft = false; requestInProgressRight = false; modalErrorMessage: string; side = Side; + private initialized = false; constructor( private workbasketService: WorkbasketService, @@ -57,7 +59,14 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy { private savingWorkbaskets: SavingWorkbasketService, 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); if (!this.workbasket._links.distributionTargets) { return; @@ -69,13 +78,15 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy { this.distributionTargetsSelected = distributionTargetsSelectedResource._embedded ? distributionTargetsSelectedResource._embedded.distributionTargets : []; this.distributionTargetsSelectedClone = Object.assign([], this.distributionTargetsSelected); - this.workbasketSubscription = this.workbasketService.getWorkBasketsSummary().subscribe( - (distributionTargetsAvailable: WorkbasketSummaryResource) => { - this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable._embedded.workbaskets); - this.distributionTargetsRight = Object.assign([], distributionTargetsAvailable._embedded.workbaskets); - this.distributionTargetsClone = Object.assign([], distributionTargetsAvailable._embedded.workbaskets); - this.onRequest(undefined, true); - }); + this.workbasketSubscription = this.workbasketService.getWorkBasketsSummary(true, + undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, + undefined, undefined, undefined, true).subscribe( + (distributionTargetsAvailable: WorkbasketSummaryResource) => { + this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable._embedded.workbaskets); + this.distributionTargetsRight = Object.assign([], distributionTargetsAvailable._embedded.workbaskets); + this.distributionTargetsClone = Object.assign([], distributionTargetsAvailable._embedded.workbaskets); + this.onRequest(undefined, true); + }); }); this.savingDistributionTargetsSubscription = this.savingWorkbaskets.triggeredDistributionTargetsSaving() @@ -143,7 +154,7 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy { this.onRequest(dualListFilter.side, false); this.workbasketFilterSubscription = this.workbasketService.getWorkBasketsSummary(true, undefined, undefined, undefined, 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) ? this.distributionTargetsRight = (resultList._embedded ? resultList._embedded.workbaskets : []) : this.distributionTargetsLeft = (resultList._embedded ? resultList._embedded.workbaskets : []); diff --git a/web/src/app/administration/workbasket/details/distribution-targets/dual-list/dual-list.component.html b/web/src/app/administration/workbasket/details/distribution-targets/dual-list/dual-list.component.html index b89907b50..76028c2cb 100644 --- a/web/src/app/administration/workbasket/details/distribution-targets/dual-list/dual-list.component.html +++ b/web/src/app/administration/workbasket/details/distribution-targets/dual-list/dual-list.component.html @@ -9,30 +9,32 @@
Available distribution targets
-
- +
+ +
-
    -
  • -
    -
    -
    +
    +
      +
    • +
      +
      -
    -
    -
    -
    {{distributionTarget.name}} ({{distributionTarget.key}})
    -
    {{distributionTarget.description}}
    -
    {{distributionTarget.owner}}  
    -
    -
    -
  • -
+ +
+
{{distributionTarget.name}}, + {{distributionTarget.key}} +
+
{{distributionTarget.description}}  
+
{{distributionTarget.owner}}  
+
+ + + + \ No newline at end of file diff --git a/web/src/app/administration/workbasket/details/distribution-targets/dual-list/dual-list.component.scss b/web/src/app/administration/workbasket/details/distribution-targets/dual-list/dual-list.component.scss index 4de24722c..8699df7e5 100644 --- a/web/src/app/administration/workbasket/details/distribution-targets/dual-list/dual-list.component.scss +++ b/web/src/app/administration/workbasket/details/distribution-targets/dual-list/dual-list.component.scss @@ -58,3 +58,33 @@ button.no-style{ background: 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; +} \ No newline at end of file diff --git a/web/src/app/administration/workbasket/details/distribution-targets/dual-list/dual-list.component.ts b/web/src/app/administration/workbasket/details/distribution-targets/dual-list/dual-list.component.ts index 46f80d436..778506a20 100644 --- a/web/src/app/administration/workbasket/details/distribution-targets/dual-list/dual-list.component.ts +++ b/web/src/app/administration/workbasket/details/distribution-targets/dual-list/dual-list.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { WorkbasketSummary } from 'app/models/workbasket-summary'; +import { trigger, state, style, transition, animate, keyframes } from '@angular/animations'; import { FilterModel } from 'app/models/filter'; import { filter } from 'rxjs/operators'; import { Side } from '../distribution-targets.component'; @@ -7,7 +8,21 @@ import { Side } from '../distribution-targets.component'; @Component({ selector: 'taskana-dual-list', 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 { @@ -21,6 +36,7 @@ export class DualListComponent implements OnInit { sideNumber = 0; toggleDtl = false; + toolbarState = false; constructor() { } diff --git a/web/src/app/administration/workbasket/details/information/workbasket-information.component.spec.ts b/web/src/app/administration/workbasket/details/information/workbasket-information.component.spec.ts index a5abf52ef..f58946b97 100644 --- a/web/src/app/administration/workbasket/details/information/workbasket-information.component.spec.ts +++ b/web/src/app/administration/workbasket/details/information/workbasket-information.component.spec.ts @@ -7,6 +7,8 @@ import { HttpClientModule } from '@angular/common/http'; import { HttpModule, JsonpModule } from '@angular/http'; import { RouterTestingModule } from '@angular/router/testing'; import { Observable } from 'rxjs/Observable'; +import {Component} from '@angular/core'; +import {Routes} from '@angular/router'; import { Workbasket } from 'app/models/workbasket'; 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 { SavingWorkbasketService, SavingInformation } from 'app/services/saving-workbaskets/saving-workbaskets.service'; import { AlertService } from 'app/services/alert/alert.service'; -import {Component} from '@angular/core'; -import {Routes} from '@angular/router'; @Component({ selector: 'taskana-dummy-detail', diff --git a/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts b/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts index c91607371..2a55c1c64 100644 --- a/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts +++ b/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts @@ -130,7 +130,6 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy { const date = datePipe.transform(Date.now(), dateFormat) + 'Z'; this.workbasket.created = date; this.workbasket.modified = date; - } ngOnDestroy() { diff --git a/web/src/app/administration/workbasket/details/workbasket-details.component.html b/web/src/app/administration/workbasket/details/workbasket-details.component.html index 68d9883b3..f03671505 100644 --- a/web/src/app/administration/workbasket/details/workbasket-details.component.html +++ b/web/src/app/administration/workbasket/details/workbasket-details.component.html @@ -7,25 +7,25 @@ Back - -
  • +
  • Access
  • -
  • +
  • Distribution targets
  • -
    - +
    +
    - +
    - +
    diff --git a/web/src/app/administration/workbasket/details/workbasket-details.component.spec.ts b/web/src/app/administration/workbasket/details/workbasket-details.component.spec.ts index 3f9aec8d3..7df5954eb 100644 --- a/web/src/app/administration/workbasket/details/workbasket-details.component.spec.ts +++ b/web/src/app/administration/workbasket/details/workbasket-details.component.spec.ts @@ -37,6 +37,7 @@ import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/g import { MapValuesPipe } from 'app/pipes/mapValues/map-values.pipe'; import { RemoveNoneTypePipe } from 'app/pipes/removeNoneType/remove-none-type.pipe'; import { SelectWorkBasketPipe } from 'app/pipes/selectedWorkbasket/seleted-workbasket.pipe'; +import { LinksWorkbasketSummary } from '../../../models/links-workbasket-summary'; @Component({ selector: 'taskana-filter', @@ -53,10 +54,8 @@ export class FilterComponent { template: 'dummydetail' }) export class DummyDetailComponent { - } - describe('WorkbasketDetailsComponent', () => { let component: WorkbasketDetailsComponent; let fixture: ComponentFixture; @@ -68,7 +67,7 @@ describe('WorkbasketDetailsComponent', () => { new Links({ 'href': 'someurl' }, { 'href': 'someurl' }, { 'href': 'someurl' })); const routes: Routes = [ - { path: ':id', component: DummyDetailComponent, outlet: 'detail' } + { path: '*', component: DummyDetailComponent} ]; beforeEach(async(() => { @@ -100,7 +99,7 @@ describe('WorkbasketDetailsComponent', () => { 'workbaskets': new Array( new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' }))) - }, new Links({ 'href': 'someurl' }))) + }, new LinksWorkbasketSummary({ 'href': 'someurl' }))) }) spyOn(workbasketService, 'getWorkBasket').and.callFake(() => { return Observable.of(workbasket) }) @@ -110,7 +109,7 @@ describe('WorkbasketDetailsComponent', () => { }) spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => { return Observable.of(new WorkbasketSummaryResource( - { 'workbaskets': new Array() }, new Links({ 'href': 'url' }))) + { 'workbaskets': new Array() }, new LinksWorkbasketSummary({ 'href': 'url' }))) }) }); diff --git a/web/src/app/administration/workbasket/details/workbasket-details.component.ts b/web/src/app/administration/workbasket/details/workbasket-details.component.ts index 548552565..5bc7ec8cd 100644 --- a/web/src/app/administration/workbasket/details/workbasket-details.component.ts +++ b/web/src/app/administration/workbasket/details/workbasket-details.component.ts @@ -100,14 +100,14 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy { private getWorkbasketInformation(workbasketIdSelected: string, copyId: string = undefined) { this.requestInProgress = true; - this.service.getWorkBasketsSummary(true).subscribe((workbasketSummary: WorkbasketSummaryResource) => { + this.service.getWorkBasketsSummary().subscribe((workbasketSummary: WorkbasketSummaryResource) => { if (!workbasketIdSelected && this.action === ACTION.CREATE) { // CREATE this.workbasket = new Workbasket(undefined); - this.workbasket._links.self = workbasketSummary._links.self; + this.workbasket._links.self = workbasketSummary._links.allWorkbaskets; this.requestInProgress = false; } else if (!workbasketIdSelected && this.action === ACTION.COPY) { // COPY this.workbasket = { ...this.workbasketCopy }; - this.workbasket._links.self = workbasketSummary._links.self; + this.workbasket._links.self = workbasketSummary._links.allWorkbaskets; this.workbasket.workbasketId = undefined; this.requestInProgress = false; } diff --git a/web/src/app/administration/workbasket/master/list/pagination/pagination.component.html b/web/src/app/administration/workbasket/master/list/pagination/pagination.component.html new file mode 100644 index 000000000..7ca6af696 --- /dev/null +++ b/web/src/app/administration/workbasket/master/list/pagination/pagination.component.html @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/web/src/app/administration/workbasket/master/list/pagination/pagination.component.scss b/web/src/app/administration/workbasket/master/list/pagination/pagination.component.scss new file mode 100644 index 000000000..bcc7bec2b --- /dev/null +++ b/web/src/app/administration/workbasket/master/list/pagination/pagination.component.scss @@ -0,0 +1,11 @@ +input.pagination{ + width: 35px; + height: 35px; + margin: 0 10px; + text-align: center; + background-color: aliceblue; +} + +ul.pagination{ + overflow: hidden; +} diff --git a/web/src/app/administration/workbasket/master/list/pagination/pagination.component.spec.ts b/web/src/app/administration/workbasket/master/list/pagination/pagination.component.spec.ts new file mode 100644 index 000000000..30acb62c7 --- /dev/null +++ b/web/src/app/administration/workbasket/master/list/pagination/pagination.component.spec.ts @@ -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; + 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); + }); + +}); diff --git a/web/src/app/administration/workbasket/master/list/pagination/pagination.component.ts b/web/src/app/administration/workbasket/master/list/pagination/pagination.component.ts new file mode 100644 index 000000000..07712c85b --- /dev/null +++ b/web/src/app/administration/workbasket/master/list/pagination/pagination.component.ts @@ -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(); + @Output() changePage = new EventEmitter(); + 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; + } + } +} diff --git a/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.html b/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.html index 1d98f65b8..b009aad09 100644 --- a/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.html +++ b/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.html @@ -1,34 +1,30 @@
  • - - - - - +
    -
    -
    - +
    +
  • diff --git a/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.scss b/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.scss index a0cdc37c1..96c05108f 100644 --- a/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.scss +++ b/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.scss @@ -9,4 +9,4 @@ &>div{ margin: 6px 0px; } -} +} \ No newline at end of file diff --git a/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.ts b/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.ts index c2737ef77..166480ba5 100644 --- a/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.ts +++ b/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.ts @@ -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 { 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 { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.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({ 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', styleUrls: ['./workbasket-list-toolbar.component.scss'] }) @@ -27,6 +42,7 @@ export class WorkbasketListToolbarComponent implements OnInit { @Output() performSorting = new EventEmitter(); @Output() performFilter = new EventEmitter(); workbasketServiceSubscription: Subscription; + toolbarState = false; constructor( private workbasketService: WorkbasketService, diff --git a/web/src/app/administration/workbasket/master/list/workbasket-list.component.html b/web/src/app/administration/workbasket/master/list/workbasket-list.component.html index 4bbd3ba4b..bcf36dbad 100644 --- a/web/src/app/administration/workbasket/master/list/workbasket-list.component.html +++ b/web/src/app/administration/workbasket/master/list/workbasket-list.component.html @@ -1,51 +1,30 @@
    - - +
    +
      + +
    • +
      +
      + +
      +
      +
      {{workbasket.name}}, + {{workbasket.key}} +
      +
      {{workbasket.description}}  
      +
      {{workbasket.owner}}  
      +
      +
      +
    • +
    +
    +
    + \ No newline at end of file diff --git a/web/src/app/administration/workbasket/master/list/workbasket-list.component.scss b/web/src/app/administration/workbasket/master/list/workbasket-list.component.scss index 82e8fdbc3..284293a00 100644 --- a/web/src/app/administration/workbasket/master/list/workbasket-list.component.scss +++ b/web/src/app/administration/workbasket/master/list/workbasket-list.component.scss @@ -15,9 +15,17 @@ 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 { diff --git a/web/src/app/administration/workbasket/master/list/workbasket-list.component.spec.ts b/web/src/app/administration/workbasket/master/list/workbasket-list.component.spec.ts index 95a215fe3..1cc0635a2 100644 --- a/web/src/app/administration/workbasket/master/list/workbasket-list.component.spec.ts +++ b/web/src/app/administration/workbasket/master/list/workbasket-list.component.spec.ts @@ -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 { Observable } from 'rxjs/Observable'; @@ -12,38 +12,50 @@ import { WorkbasketSummary } from 'app/models/workbasket-summary'; import { Links } from 'app/models/links'; import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource'; import { FilterModel } from 'app/models/filter'; - +import { LinksWorkbasketSummary } from 'app/models/links-workbasket-summary'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { AlertService } from 'app/services/alert/alert.service'; import { WorkbasketService } from 'app/services/workbasket/workbasket.service'; +import { OrientationService } from 'app/services/orientation/orientation.service'; import { WorkbasketListComponent } from './workbasket-list.component'; import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar/workbasket-list-toolbar.component'; import { IconTypeComponent } from 'app/shared/type-icon/icon-type.component'; import { SpinnerComponent } from 'app/shared/spinner/spinner.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 { MapValuesPipe } from 'app/pipes/mapValues/map-values.pipe'; -import {ClassificationService} from '../../../../services/classification/classification.service'; -import {WorkbasketDefinitionService} from '../../../../services/workbasket/workbasketDefinition.service'; -import {ImportExportComponent} from '../../../import-export/import-export.component'; - +import { ClassificationService } from '../../../../services/classification/classification.service'; +import { WorkbasketDefinitionService } from '../../../../services/workbasket/workbasketDefinition.service'; @Component({ selector: 'taskana-dummy-detail', template: 'dummydetail' }) -export class DummyDetailComponent { +class DummyDetailComponent { +} + +@Component({ + selector: 'taskana-pagination', + template: 'dummydetail' +}) +class PaginationComponent { + @Input() + workbasketsResource: any; + @Output() + workbasketsResourceChange = new EventEmitter(); + @Output() changePage = new EventEmitter(); } @Component({ selector: 'taskana-filter', template: '' }) -export class FilterComponent { +class FilterComponent { } @@ -51,7 +63,7 @@ const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSumma 'workbaskets': new Array( new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''), new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', '')) -}, new Links({ 'href': 'url' })); +}, new LinksWorkbasketSummary({ 'href': 'url' })); describe('WorkbasketListComponent', () => { @@ -59,9 +71,11 @@ describe('WorkbasketListComponent', () => { let fixture: ComponentFixture; let debugElement: any = undefined; let workbasketService: WorkbasketService; + let workbasketSummarySpy; 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({ declarations: [WorkbasketListComponent, DummyDetailComponent, SpinnerComponent, FilterComponent, WorkbasketListToolbarComponent, - RemoveNoneTypePipe, IconTypeComponent, SortComponent, MapValuesPipe, ImportExportComponent], + RemoveNoneTypePipe, IconTypeComponent, SortComponent, PaginationComponent, ImportExportComponent, MapValuesPipe], imports: [ AngularSvgIconModule, HttpModule, @@ -77,7 +91,7 @@ describe('WorkbasketListComponent', () => { RouterTestingModule.withRoutes(routes) ], providers: [WorkbasketService, ErrorModalService, RequestInProgressService, AlertService, - ClassificationService, WorkbasketDefinitionService] + ClassificationService, WorkbasketDefinitionService, OrientationService] }) .compileComponents(); @@ -86,13 +100,14 @@ describe('WorkbasketListComponent', () => { component = fixture.componentInstance; debugElement = fixture.debugElement.nativeElement; 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')); fixture.detectChanges(); })); afterEach(() => { + fixture.detectChanges() document.body.removeChild(debugElement); }) @@ -119,11 +134,16 @@ describe('WorkbasketListComponent', () => { expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(2); }); - it('should have two workbasketsummary rows created with the second one selected.', () => { - 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 the second one selected.', fakeAsync(() => { + tick(0); + fixture.detectChanges(); + 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', () => { 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 filter = new FilterModel(type, name, description, owner, key); 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]); })); diff --git a/web/src/app/administration/workbasket/master/list/workbasket-list.component.ts b/web/src/app/administration/workbasket/master/list/workbasket-list.component.ts index 5d386a1e0..951153015 100644 --- a/web/src/app/administration/workbasket/master/list/workbasket-list.component.ts +++ b/web/src/app/administration/workbasket/master/list/workbasket-list.component.ts @@ -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 { Subscription } from 'rxjs/Subscription'; @@ -6,8 +9,10 @@ import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resourc import { WorkbasketSummary } from 'app/models/workbasket-summary'; import { FilterModel } from 'app/models/filter' import { SortingModel } from 'app/models/sorting'; +import { Orientation } from 'app/models/orientation'; import { WorkbasketService } from 'app/services/workbasket/workbasket.service' +import { OrientationService } from 'app/services/orientation/orientation.service'; @Component({ selector: 'taskana-workbasket-list', @@ -17,37 +22,39 @@ import { WorkbasketService } from 'app/services/workbasket/workbasket.service' export class WorkbasketListComponent implements OnInit, OnDestroy { selectedId = ''; + workbasketsResource: WorkbasketSummaryResource; workbaskets: Array = []; requestInProgress = false; sort: SortingModel = new SortingModel(); filterBy: FilterModel = new FilterModel(); + @ViewChild('wbToolbar') + private toolbarElement: ElementRef; private workBasketSummarySubscription: Subscription; private workbasketServiceSubscription: Subscription; private workbasketServiceSavedSubscription: Subscription; + private orientationSubscription: Subscription; constructor( private workbasketService: WorkbasketService, private router: Router, private route: ActivatedRoute, - private cdRef: ChangeDetectorRef) { } + private orientationService: OrientationService) { } ngOnInit() { 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.selectedId = workbasketIdSelected; - this.cdRef.detectChanges(); + //TODO should be done in a different way. + setTimeout(() => { this.selectedId = workbasketIdSelected; }, 0); }); this.workbasketServiceSavedSubscription = this.workbasketService.workbasketSavedTriggered().subscribe(value => { this.performRequest(); }); + this.orientationSubscription = this.orientationService.getOrientation().subscribe((orientation: Orientation) => { + this.refreshWorkbasketList(); + }) } selectWorkbasket(id: string) { @@ -69,17 +76,34 @@ export class WorkbasketListComponent implements OnInit, OnDestroy { 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 { this.requestInProgress = true; this.workbaskets = []; - this.workbasketServiceSubscription.add(this.workbasketService.getWorkBasketsSummary(true, this.sort.sortBy, - this.sort.sortDirection, undefined, + this.workbasketServiceSubscription = this.workbasketService.getWorkBasketsSummary( + true, this.sort.sortBy, this.sort.sortDirection, undefined, 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.requestInProgress = false; this.unSelectWorkbasket(); - })); + }); } @@ -90,10 +114,10 @@ export class WorkbasketListComponent implements OnInit, OnDestroy { } ngOnDestroy() { - this.workBasketSummarySubscription.unsubscribe(); - this.workbasketServiceSubscription.unsubscribe(); - this.workbasketServiceSavedSubscription.unsubscribe(); + if (this.workBasketSummarySubscription) { this.workBasketSummarySubscription.unsubscribe(); } + if (this.workbasketServiceSubscription) { this.workbasketServiceSubscription.unsubscribe(); } + if (this.workbasketServiceSavedSubscription) { this.workbasketServiceSavedSubscription.unsubscribe(); } + if (this.orientationSubscription) { this.orientationSubscription.unsubscribe(); } } - } diff --git a/web/src/app/app.component.html b/web/src/app/app.component.html index a033dd539..bfdaf9b59 100644 --- a/web/src/app/app.component.html +++ b/web/src/app/app.component.html @@ -1,4 +1,4 @@ -