TSK-201 Add Distribution targets saving feature.

This commit is contained in:
Martin Rojas Miguel Angel 2018-03-15 16:50:23 +01:00 committed by Holger Hagen
parent 5de0cd5e09
commit 5778ac75c0
25 changed files with 461 additions and 243 deletions

View File

@ -20,6 +20,7 @@ import { WorkbasketListComponent } from './workbasket/list/workbasket-list.compo
import { WorkbasketDetailsComponent } from './workbasket/details/workbasket-details.component'; import { WorkbasketDetailsComponent } from './workbasket/details/workbasket-details.component';
import { WorkbasketInformationComponent } from './workbasket/details/information/workbasket-information.component'; import { WorkbasketInformationComponent } from './workbasket/details/information/workbasket-information.component';
import { DistributionTargetsComponent } from './workbasket/details/distribution-targets/distribution-targets.component'; import { DistributionTargetsComponent } from './workbasket/details/distribution-targets/distribution-targets.component';
import { DualListComponent } from './workbasket/details/distribution-targets/dual-list/dual-list.component';
import { AccessItemsComponent } from './workbasket/details/access-items/access-items.component'; import { AccessItemsComponent } from './workbasket/details/access-items/access-items.component';
import { NoAccessComponent } from './workbasket/noAccess/no-access.component'; import { NoAccessComponent } from './workbasket/noAccess/no-access.component';
import { SpinnerComponent } from './shared/spinner/spinner.component'; import { SpinnerComponent } from './shared/spinner/spinner.component';
@ -77,6 +78,7 @@ const DECLARATIONS = [
GeneralMessageModalComponent, GeneralMessageModalComponent,
DistributionTargetsComponent, DistributionTargetsComponent,
SortComponent, SortComponent,
DualListComponent,
MapValuesPipe, MapValuesPipe,
RemoveNoneTypePipe, RemoveNoneTypePipe,
SelectWorkBasketPipe SelectWorkBasketPipe

View File

@ -0,0 +1,7 @@
import { WorkbasketSummary } from './workbasket-summary';
import { Links } from './links';
export class WorkbasketDistributionTargetsResource {
constructor(public _embedded: {'distributionTargets': Array<WorkbasketSummary> } = {'distributionTargets': []}, public _links: Links = null) {
}
}

View File

@ -0,0 +1,18 @@
import {Links} from './links';
export class WorkbasketSummary {
constructor(
public workbasketId: string,
public key: string,
public name: string,
public description: string,
public owner: string,
public modified: string,
public domain: string,
public type: string,
public orgLevel1: string,
public orgLevel2: string,
public orgLevel3: string,
public orgLevel4: string,
public links: Array<Links> = undefined){}
}

View File

@ -4,7 +4,7 @@ import { Pipe, PipeTransform } from '@angular/core';
export class SelectWorkBasketPipe implements PipeTransform { export class SelectWorkBasketPipe implements PipeTransform {
transform(originArray: any, selectionArray: any, arg1: any): Object[] { transform(originArray: any, selectionArray: any, arg1: any): Object[] {
let returnArray = []; let returnArray = [];
if (!originArray) { if (!originArray || !selectionArray) {
return returnArray; return returnArray;
} }
@ -17,5 +17,4 @@ export class SelectWorkBasketPipe implements PipeTransform {
returnArray = originArray; returnArray = originArray;
return returnArray; return returnArray;
} }
} }

View File

@ -10,6 +10,7 @@ import { Subject } from 'rxjs/Subject';
import { map } from 'rxjs/operator/map'; import { map } from 'rxjs/operator/map';
import { WorkbasketSummaryResource } from '../model/workbasket-summary-resource'; import { WorkbasketSummaryResource } from '../model/workbasket-summary-resource';
import { WorkbasketAccessItemsResource } from '../model/workbasket-access-items-resource'; import { WorkbasketAccessItemsResource } from '../model/workbasket-access-items-resource';
import { WorkbasketDistributionTargetsResource } from '../model/workbasket-distribution-targets-resource';
@Injectable() @Injectable()
export class WorkbasketService { export class WorkbasketService {
@ -100,8 +101,13 @@ export class WorkbasketService {
this.httpOptions); this.httpOptions);
} }
// GET // GET
getWorkBasketsDistributionTargets(url: string): Observable<WorkbasketSummaryResource> { getWorkBasketsDistributionTargets(url: string): Observable<WorkbasketDistributionTargetsResource> {
return this.httpClient.get<WorkbasketSummaryResource>(url, this.httpOptions); return this.httpClient.get<WorkbasketDistributionTargetsResource>(url, this.httpOptions);
}
// PUT
updateWorkBasketsDistributionTargets(url: string, distributionTargetsIds :Array<string>): Observable<WorkbasketDistributionTargetsResource> {
return this.httpClient.put<WorkbasketDistributionTargetsResource>(url, distributionTargetsIds, this.httpOptions);
} }

View File

@ -5,11 +5,12 @@
margin-left: 15px; margin-left: 15px;
} }
.list-group-search {
padding: 10px 15px;
}
.btn-users-list { .btn-users-list {
border: 0px solid transparent; border: 0px solid transparent;
/* this was 1px earlier */ }
.list-group-search {
padding: 10px 15px;
margin-top: 12px;
border-top: 1px solid #ddd;
} }

View File

@ -1 +1 @@
.word-break{word-break: break-all; } .word-break{word-break: break-word; }

View File

@ -1,4 +1,4 @@
import { Component, OnInit, Input, ViewChild, OnChanges, SimpleChanges } from '@angular/core'; import { Component, OnInit, Input, ViewChild, OnChanges, SimpleChanges, Output, EventEmitter, DoCheck } from '@angular/core';
declare var $: any; declare var $: any;
@Component({ @Component({
@ -8,8 +8,8 @@ declare var $: any;
}) })
export class GeneralMessageModalComponent implements OnChanges { export class GeneralMessageModalComponent implements OnChanges {
@Input() @Input() message: string;
message: string = ''; @Output() messageChange = new EventEmitter<string>();
@Input() @Input()
title: string = ''; title: string = '';
@ -29,7 +29,8 @@ export class GeneralMessageModalComponent implements OnChanges {
} }
removeMessage() { removeMessage() {
this.message = undefined; this.message = '';
this.messageChange.emit(this.message);
} }
} }

View File

@ -1,4 +1,4 @@
import { Component, Input, ElementRef } from '@angular/core'; import { Component, Input, ElementRef, Output, EventEmitter } from '@angular/core';
import { ViewChild } from '@angular/core'; import { ViewChild } from '@angular/core';
declare var $: any; declare var $: any;
@ -9,11 +9,13 @@ declare var $: any;
}) })
export class SpinnerComponent { export class SpinnerComponent {
private currentTimeout: any; private currentTimeout: any;
private requestTimeout: any;
private maxRequestTimeout: number = 10000;
isDelayedRunning: boolean = false; isDelayedRunning: boolean = false;
@Input() @Input()
delay: number = 100; delay: number = 200;
@Input() @Input()
set isRunning(value: boolean) { set isRunning(value: boolean) {
@ -37,6 +39,8 @@ export class SpinnerComponent {
@Input() @Input()
positionClass: string = undefined; positionClass: string = undefined;
@Output()
requestTimeoutExceeded = new EventEmitter<string>()
@ViewChild('spinnerModal') @ViewChild('spinnerModal')
private modal; private modal;
@ -44,8 +48,13 @@ export class SpinnerComponent {
private runSpinner(value) { private runSpinner(value) {
this.currentTimeout = setTimeout(() => { this.currentTimeout = setTimeout(() => {
if (this.isModal) { $(this.modal.nativeElement).modal('toggle'); } if (this.isModal) { $(this.modal.nativeElement).modal('toggle'); }
this.isDelayedRunning = value; this.isDelayedRunning = value;
this.cancelTimeout(); this.cancelTimeout();
this.requestTimeout = setTimeout(() => {
this.requestTimeoutExceeded.emit('There was an error with your request, please make sure you have internet connection');
this.cancelTimeout();
this.isRunning = false;
},this.maxRequestTimeout);
}, this.delay); }, this.delay);
} }
private closeModal() { private closeModal() {
@ -57,7 +66,9 @@ export class SpinnerComponent {
private cancelTimeout(): void { private cancelTimeout(): void {
clearTimeout(this.currentTimeout); clearTimeout(this.currentTimeout);
clearTimeout(this.requestTimeout);
this.currentTimeout = undefined; this.currentTimeout = undefined;
this.requestTimeout = undefined;
} }
ngOnDestroy(): any { ngOnDestroy(): any {

View File

@ -1,5 +1,5 @@
<taskana-spinner [isRunning]="requestInProgress" [isModal]="modalSpinner" class="centered-horizontally floating"></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" [isModal]="modalSpinner" class="centered-horizontally floating"></taskana-spinner>
<taskana-general-message-modal *ngIf="modalErrorMessage" [message]="modalErrorMessage" [title]="modalTitle" error="true"></taskana-general-message-modal> <taskana-general-message-modal *ngIf="modalErrorMessage" [(message)]="modalErrorMessage" [title]="modalTitle" error="true"></taskana-general-message-modal>
<div *ngIf="workbasket && accessItems" id="wb-information" class="panel panel-default"> <div *ngIf="workbasket && accessItems" id="wb-information" class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="btn-group pull-right"> <div class="btn-group pull-right">
@ -58,7 +58,7 @@
<td [ngClass]="{'has-changes': (accessItemsClone[index].permAppend !== accessItem.permAppend)}"> <td [ngClass]="{'has-changes': (accessItemsClone[index].permAppend !== accessItem.permAppend)}">
<input type="checkbox" name="accessItem.permAppend-{{index}}" [(ngModel)]="accessItem.permAppend"> <input type="checkbox" name="accessItem.permAppend-{{index}}" [(ngModel)]="accessItem.permAppend">
</td> </td>
<td ngClass="{{(accessItemsClone[index].permTransfer != accessItem.permTransfer)? 'has-changes': 'pepe'}}"> <td [ngClass]="{'has-changes': (accessItemsClone[index].permTransfer !== accessItem.permTransfer)}">
<input type="checkbox" name="accessItem.permTransfer-{{index}}" [(ngModel)]="accessItem.permTransfer"> <input type="checkbox" name="accessItem.permTransfer-{{index}}" [(ngModel)]="accessItem.permTransfer">
</td> </td>
<td [ngClass]="{'has-changes': (accessItemsClone[index].permDistribute !== accessItem.permDistribute)}"> <td [ngClass]="{'has-changes': (accessItemsClone[index].permDistribute !== accessItem.permDistribute)}">

View File

@ -36,7 +36,7 @@ export class AccessItemsComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.accessItemsubscription = this.workbasketService.getWorkBasketAccessItems(this.workbasket._links.accessItems.href).subscribe( (accessItemsResource: WorkbasketAccessItemsResource) =>{ this.accessItemsubscription = this.workbasketService.getWorkBasketAccessItems(this.workbasket._links.accessItems.href).subscribe( (accessItemsResource: WorkbasketAccessItemsResource) =>{
this.accessItemsResource = accessItemsResource; this.accessItemsResource = accessItemsResource;
this.accessItems = accessItemsResource._embedded.accessItems; this.accessItems = accessItemsResource._embedded?accessItemsResource._embedded.accessItems: [];
this.accessItemsClone = this.cloneAccessItems(this.accessItems); this.accessItemsClone = this.cloneAccessItems(this.accessItems);
this.accessItemsResetClone = this.cloneAccessItems(this.accessItems); this.accessItemsResetClone = this.cloneAccessItems(this.accessItems);
}) })
@ -61,7 +61,7 @@ export class AccessItemsComponent implements OnInit {
onSave(): boolean { onSave(): boolean {
this.requestInProgress = true; this.requestInProgress = true;
this.workbasketService.updateWorkBasketAccessItem(this.accessItemsResource._links.self.href + '/', this.accessItems).subscribe(response =>{ this.workbasketService.updateWorkBasketAccessItem(this.accessItemsResource._links.self.href , this.accessItems).subscribe(response =>{
this.accessItemsClone = this.cloneAccessItems(this.accessItems); this.accessItemsClone = this.cloneAccessItems(this.accessItems);
this.accessItemsResetClone = this.cloneAccessItems(this.accessItems); this.accessItemsResetClone = this.cloneAccessItems(this.accessItems);
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Workbasket ${this.workbasket.name} Access items were saved successfully`)); this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Workbasket ${this.workbasket.name} Access items were saved successfully`));

View File

@ -1,101 +1,34 @@
<taskana-spinner [isRunning]="requestInProgress" [isModal]="modalSpinner" class="centered-horizontally floating"></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" isModal="true" (requestTimeoutExceeded)="requestTimeoutExceeded($event)"
<taskana-general-message-modal *ngIf="modalErrorMessage" [message]="modalErrorMessage" [title]="modalTitle" error="true"></taskana-general-message-modal> class="centered-horizontally floating"></taskana-spinner>
<taskana-general-message-modal *ngIf="modalErrorMessage" [(message)]="modalErrorMessage" [title]="modalTitle" error="true"></taskana-general-message-modal>
<div *ngIf="workbasket" id="wb-information" class="panel panel-default"> <div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<button type="button" (click)="onSave()" class="btn btn-default btn-primary">Save</button> <button type="button" (click)="onSave()" class="btn btn-default btn-primary">Save</button>
<button type="button" (click)="clear()" class="btn btn-default">Undo changes</button> <button type="button" (click)="onClear()" class="btn btn-default">Undo changes</button>
</div> </div>
<h4 class="panel-header">{{workbasket.name}}</h4> <h4 class="panel-header">{{workbasket.name}}</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div id="dual-list-Left" class="dual-list list-left col-xs-12 col-md-5-6 container"> <taskana-dual-list id="dual-list-Left" [(distributionTargets)]="distributionTargetsLeft" [distributionTargetsSelected]="distributionTargetsSelected"
<div class="row"> (performDualListFilter)="performFilter($event)" [side]="side.LEFT" [requestInProgress]="requestInProgressLeft" class="dual-list list-left col-xs-12 col-md-5-6 container"></taskana-dual-list>
<div class="col-xs-2">
<button (click)="toggleDtl = !toggleDtl; selectAll(0, toggleDtl);" class="btn btn-default selector" title="select all">
<span aria-hidden="true" class="glyphicon blue {{toggleDtl? 'glyphicon-check': 'glyphicon-unchecked'}}"></span>
</button>
</div>
<div class="col-xs-8">
<h5>Available distribution targets</h5>
</div>
<button class="btn btn-default pull-right collapsed" type="button" id="collapsedMenufilterWbDta" data-toggle="collapse" data-target="#wb-dta-filter-bar"
aria-expanded="false">
<span class="glyphicon glyphicon-filter blue"></span>
</button>
</div>
<taskana-filter target="wb-dta-filter-bar" (performFilter)="performAvailableFilter($event)"></taskana-filter>
<ul class="list-group dual-list-group">
<taskana-spinner [isRunning]="requestInProgressLeft" [isModal]="modalSpinner" positionClass="centered-spinner" class="centered-horizontally floating"></taskana-spinner>
<li class="list-group-item" *ngFor="let distributionTarget of distributionTargetsLeft | selectWorkbaskets: distributionTargetsSelected: 0"
[class.selected]="distributionTarget.selected" type="text" (click)="distributionTarget.selected = !distributionTarget.selected">
<div class="row">
<dl class="col-xs-1">
<dt>
<taskana-icon-type class="vertical-align" [type]="distributionTarget.type"></taskana-icon-type>
</dt>
</dl>
<dl class="col-xs-10">
<dt>{{distributionTarget.name}} ({{distributionTarget.key}}) </dt>
<dd>{{distributionTarget.description}}</dd>
<dd>{{distributionTarget.owner}} &nbsp;</dd>
</dl>
</div>
</li>
</ul>
</div>
<div class="hidden-xs hidden-sm col-md-1 list-arrows text-center button-margin-top"> <div class="hidden-xs hidden-sm col-md-1 list-arrows text-center button-margin-top">
<button (click)="moveDistributionTargets(0)" [disabled] = "requestInProgressLeft || requestInProgressRight" class="btn btn-default move-right"> <button (click)="moveDistributionTargets(side.LEFT)" [disabled]="requestInProgressLeft || requestInProgressRight" class="btn btn-default move-right">
<span class="glyphicon glyphicon-chevron-right blue"></span> <span class="glyphicon glyphicon-chevron-right blue"></span>
</button> </button>
<button (click)="moveDistributionTargets(1)" [disabled] = "requestInProgressLeft || requestInProgressRight" class="btn btn-default move-left"> <button (click)="moveDistributionTargets(side.RIGHT)" [disabled]="requestInProgressLeft || requestInProgressRight" class="btn btn-default move-left">
<span class="glyphicon glyphicon-chevron-left blue"></span> <span class="glyphicon glyphicon-chevron-left blue"></span>
</button> </button>
</div> </div>
<div class="hidden visible-xs visible-sm col-xs-12 list-arrows text-center"> <div class="hidden visible-xs visible-sm col-xs-12 list-arrows text-center">
<button (click)="moveDistributionTargets(0)" [disabled] = "requestInProgressLeft || requestInProgressRight" class="btn btn-default move-down"> <button (click)="moveDistributionTargets(side.LEFT)" [disabled]="requestInProgressLeft || requestInProgressRight" class="btn btn-default move-down">
<span class="glyphicon glyphicon-chevron-down blue"></span> <span class="glyphicon glyphicon-chevron-down blue"></span>
</button> </button>
<button (click)="moveDistributionTargets(1)" [disabled] = "requestInProgressLeft || requestInProgressRight" class="btn btn-default move-up"> <button (click)="moveDistributionTargets(side.RIGHT)" [disabled]="requestInProgressLeft || requestInProgressRight" class="btn btn-default move-up">
<span class="glyphicon glyphicon-chevron-up blue"></span> <span class="glyphicon glyphicon-chevron-up blue"></span>
</button> </button>
</div> </div>
<div id="dual-list-right" class="dual-list list-right col-xs-12 col-md-5-6 container"> <taskana-dual-list id="dual-list-right" [(distributionTargets)]="distributionTargetsRight" [distributionTargetsSelected]="distributionTargetsSelected"
<div class="row"> (performDualListFilter)="performFilter($event)" [requestInProgress]="requestInProgressRight" [side]="side.RIGHT" class="dual-list list-right col-xs-12 col-md-5-6 container"></taskana-dual-list>
<div class="col-xs-2">
<button (click)="toggleDtr = !toggleDtr; selectAll(1, toggleDtr);" class="btn btn-default selector" title="select all">
<span aria-hidden="true" class="glyphicon blue {{toggleDtr? 'glyphicon-check': 'glyphicon-unchecked'}}"></span>
</button>
</div>
<div class="col-xs-8">
<h5>Selected distribution targets</h5>
</div>
<button class="btn btn-default pull-right collapsed" type="button" id="collapsedMenufilterWbDta" data-toggle="collapse" data-target="#wb-dts-filter-bar"
aria-expanded="false">
<span class="glyphicon glyphicon-filter blue"></span>
</button>
</div>
<taskana-filter target="wb-dts-filter-bar" (performFilter)="performSelectedFilter($event)"></taskana-filter>
<ul class="list-group dual-list-group">
<taskana-spinner [isRunning]="requestInProgressRight" [isModal]="modalSpinner" positionClass="centered-spinner" class="centered-horizontally floating"></taskana-spinner>
<ul class="list-group dual-list-group">
<li class="list-group-item" *ngFor="let distributionTarget of distributionTargetsRight | selectWorkbaskets: distributionTargetsSelected: 1"
[class.selected]="distributionTarget.select" type="text" (click)="distributionTarget.select = !distributionTarget.select">
<div class="row">
<dl class="col-xs-1">
<dt>
<taskana-icon-type class="vertical-align" [type]="distributionTarget.type"></taskana-icon-type>
</dt>
</dl>
<dl class="col-xs-10">
<dt>{{distributionTarget.name}} ({{distributionTarget.key}}) </dt>
<dd>{{distributionTarget.description}}</dd>
<dd>{{distributionTarget.owner}} &nbsp;</dd>
</dl>
</div>
</li>
</ul>
</ul>
</div>
</div> </div>
</div> </div>

View File

@ -1,13 +1,4 @@
.list-group {
margin-top: 8px;
}
.list-left li,
.list-right li {
cursor: pointer;
}
.button-margin-top { .button-margin-top {
margin-top: 100px margin-top: 100px
} }
@ -16,25 +7,6 @@
margin: 10px 0px; margin: 10px 0px;
} }
.dual-list {
min-height: 20px;
padding: 5px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
& .row {
padding: 10px 15px;
}
@media screen and (max-width: 991px){
max-height: calc(50vh - 150px);
}
max-height: calc(100vh - 250px);
overflow: hidden;
overflow-y: scroll;
}
.col-md-5-6 { .col-md-5-6 {
@media (min-width: 992px){ @media (min-width: 992px){

View File

@ -1,9 +1,10 @@
import { Input } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AngularSvgIconModule } from 'angular-svg-icon'; import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { HttpModule, JsonpModule } from '@angular/http'; import { HttpModule, JsonpModule } from '@angular/http';
import { DistributionTargetsComponent } from './distribution-targets.component'; import { DistributionTargetsComponent, Side } from './distribution-targets.component';
import { SpinnerComponent } from '../../../shared/spinner/spinner.component'; import { SpinnerComponent } from '../../../shared/spinner/spinner.component';
import { GeneralMessageModalComponent } from '../../../shared/general-message-modal/general-message-modal.component'; import { GeneralMessageModalComponent } from '../../../shared/general-message-modal/general-message-modal.component';
import { IconTypeComponent } from '../../../shared/type-icon/icon-type.component'; import { IconTypeComponent } from '../../../shared/type-icon/icon-type.component';
@ -16,55 +17,128 @@ import { WorkbasketService } from '../../../services/workbasket.service';
import { AlertService } from '../../../services/alert.service'; import { AlertService } from '../../../services/alert.service';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Workbasket } from '../../../model/workbasket'; import { Workbasket } from '../../../model/workbasket';
import { WorkbasketDistributionTargetsResource } from '../../../model/workbasket-distribution-targets-resource';
import { FilterModel } from '../../../shared/filter/filter.component';
import { DualListComponent } from './dual-list/dual-list.component';
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 Links({ 'href': 'url' }));
@Component({ @Component({
selector: 'taskana-filter', selector: 'taskana-filter',
template: '' template: ''
}) })
export class FilterComponent { export class FilterComponent {
@Input()
target: string;
} }
describe('DistributionTargetsComponent', () => { describe('DistributionTargetsComponent', () => {
let component: DistributionTargetsComponent; let component: DistributionTargetsComponent;
let fixture: ComponentFixture<DistributionTargetsComponent>; let fixture: ComponentFixture<DistributionTargetsComponent>;
let workbasketService; let workbasketService;
let workbasket = new Workbasket('1', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' }, { 'href': 'someurl' }, { 'href': 'someurl' })); let workbasket = new Workbasket('1', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' }, { 'href': 'someurl' }, { 'href': 'someurl' }));
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [AngularSvgIconModule, HttpClientModule, HttpModule, JsonpModule], imports: [AngularSvgIconModule, HttpClientModule, HttpModule, JsonpModule],
declarations: [DistributionTargetsComponent, SpinnerComponent, GeneralMessageModalComponent, FilterComponent, SelectWorkBasketPipe, IconTypeComponent], declarations: [DistributionTargetsComponent, SpinnerComponent, GeneralMessageModalComponent, FilterComponent, SelectWorkBasketPipe, IconTypeComponent, DualListComponent],
providers: [WorkbasketService, AlertService] providers: [WorkbasketService, AlertService]
}) })
.compileComponents(); .compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DistributionTargetsComponent); fixture = TestBed.createComponent(DistributionTargetsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
component.workbasket = workbasket; component.workbasket = workbasket;
workbasketService = TestBed.get(WorkbasketService); workbasketService = TestBed.get(WorkbasketService);
spyOn(workbasketService, 'getWorkBasketsSummary').and.callFake(() => { spyOn(workbasketService, 'getWorkBasketsSummary').and.callFake(() => {
return Observable.of(new WorkbasketSummaryResource( return Observable.of(new WorkbasketSummaryResource(
{ 'workbaskets': new Array<WorkbasketSummary>(new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' }))) }, new Links({ 'href': 'someurl' }))) {
}) 'workbaskets': new Array<WorkbasketSummary>(
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => { new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })),
return Observable.of(new WorkbasketSummaryResource( new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })),
{ 'workbaskets': new Array<WorkbasketSummary>(new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' }))) }, new Links({ 'href': 'someurl' }))) new WorkbasketSummary('id3', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })))
}) }, new Links({ 'href': 'someurl' })))
})
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => {
return Observable.of(new WorkbasketDistributionTargetsResource(
{ 'distributionTargets': new Array<WorkbasketSummary>(new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' }))) }, new Links({ 'href': 'someurl' })))
})
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('should clone distribution target selected on init', () => {
expect(component.distributionTargetsClone).toBeDefined();
});
it('should clone distribution target left and distribution target right lists on init', () => {
expect(component.distributionTargetsLeft).toBeDefined();
expect(component.distributionTargetsRight).toBeDefined();
});
it('should have two list with differents elements onInit', () => {
let repeteadElemens = false;
expect(component.distributionTargetsLeft.length).toBe(2);
expect(component.distributionTargetsRight.length).toBe(1);
component.distributionTargetsLeft.forEach(leftElement => {
component.distributionTargetsRight.forEach(rightElement => {
if (leftElement.workbasketId === rightElement.workbasketId) repeteadElemens = true;
})
})
expect(repeteadElemens).toBeFalsy();
});
it('should filter left list and keep selected elements as selected', () => {
component.performFilter({filterBy:new FilterModel(), side: Side.LEFT});
component.distributionTargetsLeft = new Array<WorkbasketSummary>(
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' }))
)
expect(component.distributionTargetsLeft.length).toBe(1);
expect(component.distributionTargetsLeft[0].workbasketId).toBe('id1');
expect(component.distributionTargetsRight.length).toBe(1);
expect(component.distributionTargetsRight[0].workbasketId).toBe('id2');
});
it('should reset distribution target and distribution target selected on reset', () => {
component.distributionTargetsLeft.push(new WorkbasketSummary('id4', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })));
component.distributionTargetsRight.push(new WorkbasketSummary('id5', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })));
expect(component.distributionTargetsLeft.length).toBe(3);
expect(component.distributionTargetsRight.length).toBe(2);
component.onClear();
fixture.detectChanges();
expect(component.distributionTargetsLeft.length).toBe(2);
expect(component.distributionTargetsRight.length).toBe(1)
});
it('should save distribution targets selected and update Clone objects.', () => {
expect(component.distributionTargetsSelected.length).toBe(1);
expect(component.distributionTargetsSelectedClone.length).toBe(1);
spyOn(workbasketService, 'updateWorkBasketsDistributionTargets').and.callFake(() => {
return Observable.of(new WorkbasketDistributionTargetsResource(
{
'distributionTargets': new Array<WorkbasketSummary>(
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })),
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' })))
})
component.onSave();
fixture.detectChanges();
expect(component.distributionTargetsSelected.length).toBe(2);
expect(component.distributionTargetsSelectedClone.length).toBe(2);
expect(component.distributionTargetsLeft.length).toBe(1);
});
}); });

View File

@ -3,6 +3,7 @@ import { Workbasket } from '../../../model/workbasket';
import { WorkbasketSummary } from '../../../model/workbasket-summary'; import { WorkbasketSummary } from '../../../model/workbasket-summary';
import { WorkbasketAccessItems } from '../../../model/workbasket-access-items'; import { WorkbasketAccessItems } from '../../../model/workbasket-access-items';
import { FilterModel } from '../../../shared/filter/filter.component' import { FilterModel } from '../../../shared/filter/filter.component'
import { TREE_ACTIONS, KEYS, IActionMapping, ITreeOptions } from 'angular-tree-component';
import { WorkbasketService } from '../../../services/workbasket.service'; import { WorkbasketService } from '../../../services/workbasket.service';
import { AlertService, AlertModel, AlertType } from '../../../services/alert.service'; import { AlertService, AlertModel, AlertType } from '../../../services/alert.service';
@ -10,7 +11,12 @@ import { AlertService, AlertModel, AlertType } from '../../../services/alert.ser
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { element } from 'protractor'; import { element } from 'protractor';
import { WorkbasketSummaryResource } from '../../../model/workbasket-summary-resource'; import { WorkbasketSummaryResource } from '../../../model/workbasket-summary-resource';
import { WorkbasketDistributionTargetsResource } from '../../../model/workbasket-distribution-targets-resource';
export enum Side {
LEFT,
RIGHT
}
@Component({ @Component({
selector: 'taskana-workbaskets-distribution-targets', selector: 'taskana-workbaskets-distribution-targets',
templateUrl: './distribution-targets.component.html', templateUrl: './distribution-targets.component.html',
@ -24,54 +30,45 @@ export class DistributionTargetsComponent implements OnInit {
distributionTargetsSubscription: Subscription; distributionTargetsSubscription: Subscription;
workbasketSubscription: Subscription; workbasketSubscription: Subscription;
workbasketFilterSubscription: Subscription; workbasketFilterSubscription: Subscription;
distributionTargetsResource: WorkbasketSummaryResource;
distributionTargetsLeft: Array<WorkbasketSummary> = []; distributionTargetsSelectedResource: WorkbasketDistributionTargetsResource;
distributionTargetsRight: Array<WorkbasketSummary> = []; distributionTargetsLeft: Array<WorkbasketSummary>;
distributionTargetsSelected: Array<WorkbasketSummary> = []; distributionTargetsRight: Array<WorkbasketSummary>;
distributionTargetsSelected: Array<WorkbasketSummary>;
distributionTargetsClone: Array<WorkbasketSummary>;
distributionTargetsSelectedClone: Array<WorkbasketSummary>;
filterBy: FilterModel = new FilterModel();
requestInProgress: boolean = false; requestInProgress: boolean = false;
requestInProgressLeft: boolean = false; requestInProgressLeft: boolean = false;
requestInProgressRight: boolean = false; requestInProgressRight: boolean = false;
modalErrorMessage: string;
side = Side;
constructor(private workbasketService: WorkbasketService) { } constructor(private workbasketService: WorkbasketService, private alertService: AlertService) { }
ngOnInit() { ngOnInit() {
this.requestInProgressLeft = true; this.onRequest(undefined);
this.requestInProgressRight = true; this.distributionTargetsSubscription = this.workbasketService.getWorkBasketsDistributionTargets(this.workbasket._links.distributionTargets.href).subscribe((distributionTargetsSelectedResource: WorkbasketDistributionTargetsResource) => {
this.distributionTargetsSubscription = this.workbasketService.getWorkBasketsDistributionTargets(this.workbasket._links.distributionTargets.href).subscribe((distributionTargetsSelectedResource: WorkbasketSummaryResource) => { this.distributionTargetsSelectedResource = distributionTargetsSelectedResource;
this.distributionTargetsSelected = distributionTargetsSelectedResource._embedded ? distributionTargetsSelectedResource._embedded.workbaskets :[]; this.distributionTargetsSelected = distributionTargetsSelectedResource._embedded ? distributionTargetsSelectedResource._embedded.distributionTargets : [];
this.workbasketSubscription = this.workbasketService.getWorkBasketsSummary().subscribe((distributionTargetsAvailable: WorkbasketSummaryResource) => { this.distributionTargetsSelectedClone = Object.assign([], this.distributionTargetsSelected);
this.distributionTargetsResource = distributionTargetsAvailable; this.workbasketSubscription = this.workbasketService.getWorkBasketsSummary().subscribe((distributionTargetsAvailable: WorkbasketSummaryResource) => {
this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable._embedded.workbaskets); this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable._embedded.workbaskets);
this.distributionTargetsRight = Object.assign([], distributionTargetsAvailable._embedded.workbaskets); this.distributionTargetsRight = Object.assign([], distributionTargetsAvailable._embedded.workbaskets);
this.requestInProgressLeft = false; this.distributionTargetsClone = Object.assign([], distributionTargetsAvailable._embedded.workbaskets);
this.requestInProgressRight = false; this.onRequest(undefined, true);
});
})
}
selectAll(side: number, selected: boolean) {
if (side === 0) {
this.distributionTargetsLeft.forEach((element: any) => {
element.selected = selected;
}); });
} });
else if (side === 1) {
this.distributionTargetsRight.forEach((element: any) => {
element.selected = selected;
});
}
} }
moveDistributionTargets(side: number) { moveDistributionTargets(side: number) {
if (side === 0) { if (side === Side.LEFT) {
let itemsSelected = this.getSelectedItems(this.distributionTargetsLeft, this.distributionTargetsRight) let itemsSelected = this.getSelectedItems(this.distributionTargetsLeft, this.distributionTargetsRight)
this.distributionTargetsSelected = this.distributionTargetsSelected.concat(itemsSelected); this.distributionTargetsSelected = this.distributionTargetsSelected.concat(itemsSelected);
this.distributionTargetsRight = this.distributionTargetsRight.concat(itemsSelected); this.distributionTargetsRight = this.distributionTargetsRight.concat(itemsSelected);
} }
else if (side === 1) { else {
let itemsSelected = this.getSelectedItems(this.distributionTargetsRight, this.distributionTargetsLeft); let itemsSelected = this.getSelectedItems(this.distributionTargetsRight, this.distributionTargetsLeft);
this.distributionTargetsSelected = this.removeSeletedItems(this.distributionTargetsSelected, itemsSelected); this.distributionTargetsSelected = this.removeSeletedItems(this.distributionTargetsSelected, itemsSelected);
this.distributionTargetsRight = this.removeSeletedItems(this.distributionTargetsRight, itemsSelected); this.distributionTargetsRight = this.removeSeletedItems(this.distributionTargetsRight, itemsSelected);
@ -79,29 +76,51 @@ export class DistributionTargetsComponent implements OnInit {
} }
} }
performAvailableFilter(filterBy: FilterModel) { onSave() {
this.filterBy = filterBy; this.requestInProgress = true;
this.performFilter(0); this.workbasketService.updateWorkBasketsDistributionTargets(this.distributionTargetsSelectedResource._links.self.href, this.getSeletedIds()).subscribe(response => {
} this.requestInProgress = false;
performSelectedFilter(filterBy: FilterModel) { this.distributionTargetsSelected = response._embedded ? response._embedded.distributionTargets : [];
this.filterBy = filterBy; this.distributionTargetsSelectedClone = Object.assign([], this.distributionTargetsSelected);
this.performFilter(1); this.distributionTargetsClone = Object.assign([], this.distributionTargetsLeft);
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Workbasket ${this.workbasket.name} Access items were saved successfully`));
return true;
},
error => {
this.modalErrorMessage = error.message;
this.requestInProgress = false;
return false;
}
)
return false;
} }
private performFilter(listType: number) { onClear() {
this.alertService.triggerAlert(new AlertModel(AlertType.INFO, 'Reset edited fields'))
this.distributionTargetsLeft = Object.assign([], this.distributionTargetsClone);
this.distributionTargetsRight = Object.assign([], this.distributionTargetsSelectedClone);
this.distributionTargetsSelected = Object.assign([], this.distributionTargetsSelectedClone);
}
listType ? this.distributionTargetsRight = undefined : this.distributionTargetsLeft = undefined; requestTimeoutExceeded(message: string) {
listType ? this.requestInProgressRight = true : this.requestInProgressLeft = true; this.modalErrorMessage = message;
}
performFilter(dualListFilter: any) {
dualListFilter.side === Side.RIGHT ? this.distributionTargetsRight = undefined : this.distributionTargetsLeft = undefined;
this.onRequest(dualListFilter.side, false);
this.workbasketFilterSubscription = this.workbasketService.getWorkBasketsSummary(true, undefined, undefined, undefined, this.workbasketFilterSubscription = this.workbasketService.getWorkBasketsSummary(true, undefined, undefined, undefined,
this.filterBy.name, this.filterBy.description, undefined, this.filterBy.owner, dualListFilter.filterBy.name, dualListFilter.filterBy.description, undefined, dualListFilter.filterBy.owner,
this.filterBy.type, undefined, this.filterBy.key).subscribe((resultList: WorkbasketSummaryResource) => { dualListFilter.filterBy.type, undefined, dualListFilter.filterBy.key).subscribe(resultList => {
listType ? this.distributionTargetsRight = resultList._embedded.workbaskets : this.distributionTargetsLeft = resultList._embedded.workbaskets; (dualListFilter.side === Side.RIGHT) ?
listType ? this.requestInProgressRight = false : this.requestInProgressLeft = false; this.distributionTargetsRight = (resultList._embedded ? resultList._embedded.workbaskets : []) :
this.distributionTargetsLeft = (resultList._embedded ? resultList._embedded.workbaskets : []);
this.onRequest(dualListFilter.side, true);
}); });
} }
private getSelectedItems(originList: any, destinationList: any): Array<any> { private getSelectedItems(originList: any, destinationList: any): Array<any> {
return originList.filter((element: any) => { return (element.selected === true) }); return originList.filter((element: any) => { return (element.selected === true) });
} }
@ -113,7 +132,24 @@ export class DistributionTargetsComponent implements OnInit {
} }
} }
return originList; return originList;
}
private onRequest(side: Side = undefined, finished: boolean = false) {
if (finished) {
side === undefined ? (this.requestInProgressLeft = false, this.requestInProgressRight = false) :
side === Side.LEFT ? this.requestInProgressLeft = false : this.requestInProgressRight = false;
return;
}
side === undefined ? (this.requestInProgressLeft = true, this.requestInProgressRight = true) :
side === Side.LEFT ? this.requestInProgressLeft = true : this.requestInProgressRight = true;
}
private getSeletedIds(): Array<string> {
let distributionTargetsSelelected: Array<string> = [];
this.distributionTargetsSelected.forEach(element => {
distributionTargetsSelelected.push(element.workbasketId);
})
return distributionTargetsSelelected;
} }
private ngOnDestroy(): void { private ngOnDestroy(): void {

View File

@ -0,0 +1,38 @@
<div id="dual-list-Left" class="dual-list list-left col-xs-12 col-md-5-6 container">
<div class="row">
<div class="col-xs-2">
<button (click)="toggleDtl = !toggleDtl; selectAll(toggleDtl);" class="btn btn-default no-style" title="select all">
<span aria-hidden="true" class="glyphicon blue {{toggleDtl? 'glyphicon-check': 'glyphicon-unchecked'}}"></span>
</button>
</div>
<div class="col-xs-7">
<h5>Available distribution targets</h5>
</div>
<div class="pull-right">
<button class="btn btn-default collapsed" type="button" id="collapsedMenufilterWbDta" data-toggle="collapse"
[attr.data-target]="'#wb-dta-filter-bar-' + sideNumber"
aria-expanded="false">
<span class="glyphicon glyphicon-filter blue"></span>
</button>
</div>
</div>
<taskana-filter target="wb-dta-filter-bar-{{sideNumber}}" (performFilter)="performAvailableFilter($event)"></taskana-filter>
<taskana-spinner [isRunning]="requestInProgress" positionClass="centered-spinner" class="centered-horizontally floating"></taskana-spinner>
<ul class="list-group ">
<li class="list-group-item" *ngFor="let distributionTarget of distributionTargets | selectWorkbaskets: distributionTargetsSelected: side"
[class.selected]="distributionTarget.selected" type="text" (click)="distributionTarget.selected = !distributionTarget.selected">
<div class="row">
<dl class="col-xs-1">
<dt>
<taskana-icon-type class="vertical-align" [type]="distributionTarget.type"></taskana-icon-type>
</dt>
</dl>
<dl class="col-xs-10">
<dt>{{distributionTarget.name}} ({{distributionTarget.key}}) </dt>
<dd>{{distributionTarget.description}}</dd>
<dd>{{distributionTarget.owner}} &nbsp;</dd>
</dl>
</div>
</li>
</ul>
</div>

View File

@ -0,0 +1,60 @@
.dual-list {
min-height: 300px;
padding: 0px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
& .row {
padding: 3px 0px 0px 3px;
}
& .row:first {
border-top: 1px solid #ddd;
}
& div.pull-right {
margin-right: 18px;
}
& >.list-group {
margin-bottom: 0px;
margin-top: 0px;
}
& > .list-group > li {
border-left: none;
border-right: none;
}
overflow-x: hidden;
overflow-y: scroll;
@media screen and (max-width: 991px){
max-height: 38vh;
}
max-height: 78vh;
}
.list-group {
margin-top: 8px;
}
ul>li {
&:first-child.list-group-item.selected {
border-color: #ddd;
}
&.list-group-item.selected {
background-color: #e9f2fc;
}
}
.list-left li {
cursor: pointer;
}
button.no-style{
background: none;
border:none;
}

View File

@ -0,0 +1,40 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { WorkbasketSummary } from '../../../../model/workbasket-summary';
import { FilterModel } from '../../../../shared/filter/filter.component';
import { filter } from 'rxjs/operators';
import { Side } from '../distribution-targets.component';
@Component({
selector: 'taskana-dual-list',
templateUrl: './dual-list.component.html',
styleUrls: ['./dual-list.component.scss']
})
export class DualListComponent implements OnInit {
constructor() { }
ngOnInit() {
this.sideNumber = this.side === Side.LEFT ? 0 : 1;
}
@Input() distributionTargets: Array<WorkbasketSummary>;
@Output() distributionTargetsChange = new EventEmitter<Array<WorkbasketSummary>>();
@Input() distributionTargetsSelected: Array<WorkbasketSummary>;
@Output() performDualListFilter = new EventEmitter<{ filterBy: FilterModel, side: Side }>();
@Input() requestInProgress: boolean = false;
@Input() side: Side;
sideNumber: number = 0;
selectAll(selected: boolean) {
this.distributionTargets.forEach((element: any) => {
element.selected = selected;
});
}
performAvailableFilter(filterModel: FilterModel) {
this.performDualListFilter.emit({ filterBy: filterModel, side: this.side });
}
}

View File

@ -1,5 +1,5 @@
<taskana-spinner [isRunning]="requestInProgress" [isModal]="modalSpinner" class="centered-horizontally floating"></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" [isModal]="modalSpinner" class="centered-horizontally floating"></taskana-spinner>
<taskana-general-message-modal *ngIf="modalErrorMessage" [message]="modalErrorMessage" [title]="modalTitle" error="true"></taskana-general-message-modal> <taskana-general-message-modal *ngIf="modalErrorMessage" [(message)]="modalErrorMessage" [title]="modalTitle" error="true"></taskana-general-message-modal>
<div *ngIf="workbasket" id="wb-information" class="panel panel-default"> <div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="btn-group pull-right"> <div class="btn-group pull-right">

View File

@ -1,7 +1,7 @@
<div class="container-scrollable" > <div class="container-scrollable" >
<taskana-spinner [isRunning]="requestInProgress" class = "centered-horizontally"></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" class = "centered-horizontally"></taskana-spinner>
<app-no-access *ngIf="!requestInProgress && (!hasPermission || !workbasket && selectedId)" ></app-no-access> <app-no-access *ngIf="!requestInProgress && (!hasPermission || !workbasket && selectedId)" ></app-no-access>
<div id ="workbasket-details" class="workbasket-details" *ngIf="workbasket && !requestInProgress"> <div id ="workbasket-details" *ngIf="workbasket && !requestInProgress">
<ul class="nav nav-tabs" role="tablist"> <ul class="nav nav-tabs" role="tablist">
<li *ngIf="showDetail" class="visible-xs visible-sm hidden"> <li *ngIf="showDetail" class="visible-xs visible-sm hidden">
<a (click) = "backClicked()"><span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back</a> <a (click) = "backClicked()"><span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back</a>
@ -9,14 +9,14 @@
<li role="presentation" class="active"> <li role="presentation" class="active">
<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" class="inactive"> <li role="presentation" class="">
<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" class="inactive"> <li role="presentation" class="">
<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 detail-tab-content"> <div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="work-baskets"> <div role="tabpanel" class="tab-pane active" id="work-baskets">
<workbasket-information [workbasket]="workbasket"></workbasket-information> <workbasket-information [workbasket]="workbasket"></workbasket-information>
</div> </div>

View File

@ -1,8 +1,9 @@
.nav.nav-tabs { .nav.nav-tabs {
& > li { & > li {
& > a { & > a {
min-height: 56px; min-height: 52px;
padding-top: 17px; padding-top: 15px;
border-radius: 0px;
&.has-changes{ &.has-changes{
border-bottom: solid #f0ad4e; border-bottom: solid #f0ad4e;
} }
@ -12,11 +13,12 @@
border-left: none; border-left: none;
} }
} }
& > li.active > a {
border-top: 3px solid #479ea9;
padding-top: 13px;
background-color: #f5f5f5;
}
& > p{ & > p{
margin: 0px; margin: 0px;
} }
} }
.workbasket-details{
margin-top:1px;
}

View File

@ -1,10 +1,11 @@
import { Component } from '@angular/core'; import { Component, Input } from '@angular/core';
import { async, ComponentFixture, TestBed, } from '@angular/core/testing'; import { async, ComponentFixture, TestBed, } from '@angular/core/testing';
import { WorkbasketDetailsComponent } from './workbasket-details.component'; import { WorkbasketDetailsComponent } from './workbasket-details.component';
import { NoAccessComponent } from '../noAccess/no-access.component'; import { NoAccessComponent } from '../noAccess/no-access.component';
import { WorkbasketInformationComponent } from './information/workbasket-information.component'; import { WorkbasketInformationComponent } from './information/workbasket-information.component';
import { AccessItemsComponent } from './access-items/access-items.component'; import { AccessItemsComponent } from './access-items/access-items.component';
import { DistributionTargetsComponent } from './distribution-targets/distribution-targets.component'; import { DistributionTargetsComponent } from './distribution-targets/distribution-targets.component';
import { DualListComponent } from './distribution-targets//dual-list/dual-list.component';
import { Workbasket } from 'app/model/workbasket'; import { Workbasket } from 'app/model/workbasket';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { SpinnerComponent } from '../../shared/spinner/spinner.component'; import { SpinnerComponent } from '../../shared/spinner/spinner.component';
@ -37,6 +38,8 @@ import { WorkbasketAccessItemsResource } from '../../model/workbasket-access-ite
}) })
export class FilterComponent { export class FilterComponent {
@Input()
target: string;
} }
@ -52,7 +55,7 @@ describe('WorkbasketDetailsComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [RouterTestingModule, FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule], imports: [RouterTestingModule, FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule],
declarations: [WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent, SpinnerComponent, IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent, DistributionTargetsComponent, FilterComponent, SelectWorkBasketPipe], declarations: [WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent, SpinnerComponent, IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent, DistributionTargetsComponent, FilterComponent, DualListComponent, SelectWorkBasketPipe],
providers: [WorkbasketService, MasterAndDetailService, PermissionService, AlertService] providers: [WorkbasketService, MasterAndDetailService, PermissionService, AlertService]
}) })
.compileComponents(); .compileComponents();
@ -73,8 +76,8 @@ describe('WorkbasketDetailsComponent', () => {
}) })
spyOn(workbasketService, 'getWorkBasket').and.callFake(() => { return Observable.of(workbasket) }) spyOn(workbasketService, 'getWorkBasket').and.callFake(() => { return Observable.of(workbasket) })
spyOn(workbasketService, 'getWorkBasketAccessItems').and.callFake(() => { return Observable.of(new WorkbasketAccessItemsResource( {'accessItems': new Array<WorkbasketAccessItems>()}, new Links({'href': 'url'})) )}) spyOn(workbasketService, 'getWorkBasketAccessItems').and.callFake(() => { return Observable.of(new WorkbasketAccessItemsResource({ 'accessItems': new Array<WorkbasketAccessItems>() }, new Links({ 'href': 'url' }))) })
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => { return Observable.of(new WorkbasketSummaryResource( {'workbaskets': new Array<WorkbasketSummary>()}, new Links({'href': 'url'})) ) }) spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => { return Observable.of(new WorkbasketSummaryResource({ 'workbaskets': new Array<WorkbasketSummary>() }, new Links({ 'href': 'url' }))) })
}); });

View File

@ -17,5 +17,6 @@ a > label{
} }
.tab-align{ .tab-align{
border-bottom: 1px solid #ddd;
padding-bottom: 12px; padding-bottom: 12px;
} }

View File

@ -146,14 +146,6 @@ li > div.row > dl {
color: red; color: red;
} }
.detail-tab-content {
margin-top: 20px;
}
.col-xs-9.mod-col-9 {
width: 74%;
padding-right: 0px;
}
.user-select { .user-select {
margin-left: 2px; margin-left: 2px;
@ -225,5 +217,27 @@ li > div.row > dl {
} }
.centered-spinner { .centered-spinner {
margin-top: 100px; margin-top: 30px;
margin-bottom: 30px;
}
.list-group-item {
padding: 5px 15px;
}
.dual-list > taskana-filter >.list-group-search {
margin-top: 4px;
}
workbasket-information, taskana-workbasket-access-items, taskana-workbaskets-distribution-targets {
&> .panel{
border: none;
box-shadow: none;
margin-bottom: 0px;
&> .panel-body {
height: 84vh;
max-height: 84vh;
overflow-y: scroll;
}
}
} }