TSK-189 Create workbasket information component with no access component and responsive view

This commit is contained in:
Martin Rojas Miguel Angel 2018-02-08 16:44:24 +01:00 committed by Holger Hagen
parent f6b9524bfe
commit 9720c6c681
37 changed files with 565 additions and 185 deletions

View File

@ -60,6 +60,14 @@
"rxjs": "5.5.6"
}
},
"@angular/animations": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-5.2.3.tgz",
"integrity": "sha512-K9rOsRGwt7Zmp/rNdvBmgBKqvEdgCyZF0kvwxrmZfq1Zj0GAkfTAKPL007493O6XFd+icfu/+kmYeqXBGB4gKA==",
"requires": {
"tslib": "1.9.0"
}
},
"@angular/cli": {
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-1.6.5.tgz",

View File

@ -13,6 +13,7 @@
},
"private": true,
"dependencies": {
"@angular/animations": "5.2.3",
"@angular/common": "5.2.1",
"@angular/compiler": "5.2.1",
"@angular/core": "5.2.1",

View File

@ -5,6 +5,7 @@ import { WorkbasketListComponent } from './workbasket/list/workbasket-list.compo
import { WorkbasketDetailsComponent } from './workbasket/details/workbasket-details.component';
import { CategoriesadministrationComponent } from './categoriesadministration/categoriesadministration.component';
import { MasterAndDetailComponent } from './shared/masterAndDetail/master-and-detail.component';
import { NoAccessComponent } from './workbasket/noAccess/no-access.component';
const appRoutes: Routes = [
{ path: 'workbaskets',
@ -15,6 +16,11 @@ const appRoutes: Routes = [
component: WorkbasketListComponent,
outlet: 'master'
},
{
path: 'noaccess',
component: NoAccessComponent,
outlet: 'detail'
},
{
path: ':id',
component: WorkbasketDetailsComponent,

View File

@ -1,13 +1,13 @@
<nav class="navbar navbar-fixed-top">
<div class="navbar show no-border-radius navbar-inverse no-gutter">
<div class="col-xs-2 col-md-4">
<div class="col-xs-3 col-md-4">
<svg-icon class="logo hidden visible-xs visible-sm" src="./assets/icons/logo.svg"></svg-icon>
<ul class="nav logo">
<svg-icon class="logo hidden-xs hidden-sm" src="./assets/icons/logo.svg"></svg-icon>
<p class="navbar-brand no-margin hidden-xs hidden-sm" > <a [href]="adminUrl">{{title}}</a></p>
</ul>
</div>
<div class="col-xs-9 col-md-7 logo-container">
<div class="col-xs-8 col-md-7 logo-container">
<ul class="nav nav-tabs no-border-bottom" id="myTabs" role="tablist">
<li role="presentation" class="{{workbasketsRoute? 'active' : 'inactive'}}" role="tab" data-toggle="tab" ><a routerLink="/workbaskets" aria-controls="Work baskets" routerLinkActive="active">Workbaskets</a></li>
<li role="presentation" class="{{workbasketsRoute? 'inactive' : 'active'}}" role="tab" data-toggle="tab" ><a routerLink="/clasifications" aria-controls="Clasifications" routerLinkActive="active">Clasifications</a></li>

View File

@ -4,7 +4,7 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule, JsonpModule } from '@angular/http';
import { HttpModule, JsonpModule, Http } from '@angular/http';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AlertModule } from 'ngx-bootstrap';
@ -17,24 +17,26 @@ import { TreeModule } from 'angular-tree-component';
*/
import { AppComponent } from './app.component';
import { WorkbasketListComponent } from './workbasket/list/workbasket-list.component';
import { WorkbasketeditorComponent } from './workbasketeditor/workbasketeditor.component';
import { CategorieslistComponent } from './categorieslist/categorieslist.component';
import { CategoriestreeComponent } from './categoriestree/categoriestree.component';
import { CategoryeditorComponent } from './categoryeditor/categoryeditor.component';
import { CategoriesadministrationComponent } from './categoriesadministration/categoriesadministration.component';
import { WorkbasketAuthorizationComponent } from './workbasket-authorization/workbasket-authorization.component';
import { WorkbasketDistributiontargetsComponent } from './workbasket-distributiontargets/workbasket-distributiontargets.component';
import { WorkbasketDetailsComponent } from './workbasket/details/workbasket-details.component'
import { WorkbasketInformationComponent } from './workbasket/details/information/workbasket-information.component'
import { WorkbasketDetailsComponent } from './workbasket/details/workbasket-details.component';
import { WorkbasketInformationComponent } from './workbasket/details/information/workbasket-information.component';
import { NoAccessComponent } from './workbasket/noAccess/no-access.component';
//Shared
import { MasterAndDetailComponent} from './shared/masterAndDetail/master-and-detail.component';
/**
* Services
*/
import { WorkbasketService } from './services/workbasketservice.service'
import { WorkbasketService } from './services/workbasketservice.service';
import { MasterAndDetailService } from './services/master-and-detail.service';
import { HttpExtensionService } from './services/http-extension.service';
import { PermissionService } from './services/permission.service';
const MODULES = [
@ -53,7 +55,6 @@ const MODULES = [
const COMPONENTS = [
AppComponent,
WorkbasketListComponent,
WorkbasketeditorComponent,
CategorieslistComponent,
CategoriestreeComponent,
CategoryeditorComponent,
@ -62,12 +63,18 @@ const COMPONENTS = [
WorkbasketDetailsComponent,
WorkbasketDistributiontargetsComponent,
MasterAndDetailComponent,
WorkbasketInformationComponent
WorkbasketInformationComponent,
NoAccessComponent
];
@NgModule({
declarations: COMPONENTS,
imports: MODULES,
providers: [HttpClientModule, WorkbasketService],
providers: [
WorkbasketService,
MasterAndDetailService,
PermissionService,
{ provide: Http, useClass: HttpExtensionService }
],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,19 @@
import { TestBed, inject } from '@angular/core/testing';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
import { HttpExtensionService } from './http-extension.service';
import { PermissionService } from './permission.service';
describe('HttpExtensionService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports:[HttpClientModule, HttpModule],
providers: [HttpExtensionService, PermissionService]
});
});
it('should be created', inject([HttpExtensionService], (service: HttpExtensionService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
import { Request, XHRBackend, RequestOptions, Response, Http, RequestOptionsArgs, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { PermissionService } from './permission.service';
@Injectable()
export class HttpExtensionService extends Http {
permissionService: PermissionService;
constructor(backend: XHRBackend, defaultOptions: RequestOptions, permissionService: PermissionService) {
super(backend, defaultOptions);
this.permissionService = permissionService;
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
this.permissionService.setPermission(true);
return super.request(url, options).catch((error: Response) => {
if ((error.status === 401 || error.status === 403) && (window.location.href.match(/\?/g) || []).length < 2) {
this.permissionService.setPermission(false);
}
return Observable.throw(error);
});
}
}

View File

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

View File

@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class MasterAndDetailService {
public showDetail = new BehaviorSubject<boolean>(false);
constructor() { }
setShowDetail(newValue : boolean){
this.showDetail.next(newValue)
}
getShowDetail(){
return this.showDetail.asObservable();
}
}

View File

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

View File

@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class PermissionService {
private permission = new BehaviorSubject<boolean>(true);
setPermission(permission: boolean) {
this.permission.next(permission);
}
hasPermission(): Observable<boolean> {
return this.permission.asObservable();
}
}

View File

@ -62,13 +62,13 @@ export class WorkbasketService {
this.workBasketSelected.next(id);
}
getSelectedWorkBasket(){
getSelectedWorkBasket(): Observable<string>{
return this.workBasketSelected.asObservable();
}
private createAuthorizationHeader() {
let headers: Headers = new Headers();
headers.append("Authorization", "Basic dXNlcl8xXzE6dXNlcl8xXzE=");
headers.append("Authorization", "Basic VEVBTUxFQURfMTpURUFNTEVBRF8x");
headers.append("content-type", "application/json");
return new RequestOptions({ headers: headers });

View File

@ -4,7 +4,6 @@
</div>
<div class="{{showDetail? 'col-xs-12 col-md-8': 'hidden'}}" >
<router-outlet name="detail">
<a class="{{showDetail? 'hidden visible-xs visible-sm': 'hidden'}}" (click) = "backClicked()"> < Back</a>
</router-outlet>
</div>
<div class="{{showDetail? 'hidden': 'hidden-xs hidden-sm col-md-8 container-no-detail'}}">

View File

@ -1,17 +1,19 @@
.container-no-detail .no-detail-icon {
display: block;
width: 150px;
height: 150px;
fill: grey;
margin: 0 auto;
margin: 20px auto;
}
.container-no-detail{
top:30vh;
}
.center-block.no-detail {
width: 250px;
text-align: center;
}

View File

@ -6,6 +6,7 @@ import { RouterTestingModule } from '@angular/router/testing';
import { Router, Routes, ActivatedRoute, NavigationStart, RouterEvent } from '@angular/router';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { MasterAndDetailService } from '../../services/master-and-detail.service'
@Component({
selector: 'dummy-master',
@ -52,7 +53,8 @@ describe('MasterAndDetailComponent ', () => {
RouterTestingModule.withRoutes(routes),
AngularSvgIconModule,
HttpClientModule
]
],
providers: [MasterAndDetailService]
})
.compileComponents();

View File

@ -1,5 +1,6 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, Routes, ActivatedRoute, NavigationStart, RouterEvent } from '@angular/router';
import { MasterAndDetailService } from '../../services/master-and-detail.service'
@Component({
selector: 'master-and-detail',
@ -12,14 +13,16 @@ export class MasterAndDetailComponent implements OnInit{
private sub: any;
showDetail: Boolean = false;
constructor(private route: ActivatedRoute, private router: Router){
constructor(private route: ActivatedRoute, private router: Router, private masterAndDetailService: MasterAndDetailService){
}
ngOnInit(): void {
this.showDetail = this.showDetails();
this.masterAndDetailService.setShowDetail(this.showDetail? true: false);
this.router.events.subscribe(event => {
if(event instanceof NavigationStart) {
this.showDetail = this.showDetails(event);
this.masterAndDetailService.setShowDetail(this.showDetail? true: false);
}
});
}

View File

@ -1,27 +1,83 @@
<div class="row">
<div class="col-md-6">
<h2>Details</h2>
<div id = "wb-information" class="panel panel-default">
<div class="panel-heading">
<div class="btn-group pull-right">
<button type="button" class="btn btn-default btn-primary">Save</button>
<button type="button" class="btn btn-default">Cancel</button>
</div>
<h4 class="panel-header">{{workbasket.name}}</h4>
</div>
<div class="col-md-6">
<button type="button" class="btn btn-default" aria-label="Left Align" (click)="onEdit()">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-default" aria-label="Left Align" (click)="onSave()">
<span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span></button>
<div class="panel-body">
<form class="col-md-6">
<div class="form-group required">
<label for="wb-key" class="control-label">Key</label>
<input type="text" class="form-control" id="wb-key" placeholder="key" [(ngModel)]="workbasket.key" name ="workbasket.key">
</div>
<div class="form-group required">
<label for="wb-name" class="control-label">Name</label>
<input type="text" class="form-control" id="wb-name" placeholder="Name" [(ngModel)]="workbasket.name" name ="workbasket.name">
</div>
<div class="form-group required">
<label for="wb-type" class="control-label">Type</label>
<div class="dropdown clearfix btn-group">
<button class="btn btn-default" type="button" id="dropdownMenu24" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<svg-icon class="small blue" src="./assets/icons/{{workbasket.type === 'PERSONAL'? 'user.svg': 'users.svg'}}"></svg-icon>
{{workbasket.type === 'PERSONAL'? 'Individual': 'Multiple'}}
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu" aria-labelledby="dropdownMenu1">
<li>
<a (click)="selectType(0)"> <svg-icon class="small blue" src="./assets/icons/user.svg"></svg-icon> Individual </a>
<a (click)="selectType(1)"> <svg-icon class="small blue" src="./assets/icons/users.svg"></svg-icon> Multiple </a>
</li>
</ul>
</div>
</div>
<div class="form-group required">
<label for="wb-owner" class="control-label">Owner</label>
<input type="text" class="form-control" id="wb-owner" placeholder="Owner" [(ngModel)]="workbasket.owner" name ="workbasket.owner">
</div>
<div class="form-group">
<label for="wb-domain" class="control-label">Domain</label>
<input type="text" class="form-control" id="wb-domain" placeholder="Domain" [(ngModel)]="workbasket.domain" name ="workbasket.domain" disabled="">
</div>
<div class="form-group">
<label for="wb-description" class="control-label">Description</label>
<textarea class="form-control" rows="5" id="wb-description" placeholder="Description" [(ngModel)]="workbasket.description" name ="workbasket.description"></textarea>
</div>
</form>
<form class="col-md-6">
<div class="form-group">
<label for="wb-org-level-1" class="control-label">OrgLevel 1</label>
<input type="text" class="form-control" id="wb-org-level-1" placeholder="OrgLevel 1" [(ngModel)]="workbasket.orgLevel1" name ="workbasket.orgLevel1">
</div>
<div class="form-group">
<label for="wb-org-level-2" class="control-label">OrgLevel 2</label>
<input type="text" class="form-control" id="wb-org-level-2" placeholder="OrgLevel 2" [(ngModel)]="workbasket.orgLevel2" name ="workbasket.orgLevel2">
</div>
<div class="form-group">
<label for="wb-org-level-3" class="control-label">OrgLevel 3</label>
<input type="text" class="form-control" id="wb-org-level-3" placeholder="OrgLevel 3" [(ngModel)]="workbasket.orgLevel3" name ="workbasket.orgLevel3">
</div>
<div class="form-group">
<label for="wb-org-level-4" class="control-label">OrgLevel 4</label>
<input type="text" class="form-control" id="wb-org-level-4" placeholder="OrgLevel 4" [(ngModel)]="workbasket.orgLevel4" name ="workbasket.orgLevel4">
</div>
<div class="form-group">
<label for="wb-custom-1" class="control-label">Custom 1</label>
<input type="text" class="form-control" id="wb-custom-1" placeholder="Custom 1" [(ngModel)]="workbasket.custom1" name ="workbasket.custom1">
</div>
<div class="form-group">
<label for="wb-custom-2" class="control-label">Custom 2</label>
<input type="text" class="form-control" id="wb-custom-2" placeholder="Custom 2" [(ngModel)]="workbasket.custom2" name ="workbasket.custom2">
</div>
<div class="form-group">
<label for="wb-custom-3" class="control-label">Custom 3</label>
<input type="text" class="form-control" id="wb-custom-3" placeholder="Custom 3" [(ngModel)]="workbasket.custom3" name ="workbasket.custom3">
</div>
<div class="form-group">
<label for="wb-custom-4" class="control-label">Custom 4</label>
<input type="text" class="form-control" id="wb-custom-4" placeholder="Custom 4" [(ngModel)]="workbasket.custom4" name ="workbasket.custom4">
</div>
</form>
</div>
</div>
<dl class="dl-horizontal" *ngIf = "workbasket">
<dt>Id</dt>
<dd>{{ workbasket.id }}</dd>
<dt>Created Date</dt>
<dd>{{ workbasket.created | date: 'dd/MM/yyyy HH:mm' }}</dd>
<dt>Modified Date</dt>
<dd>{{ workbasket.modified | date: 'dd/MM/yyyy HH:mm' }}</dd>
<dt>Name</dt>
<dd><input class="form-control" placeholder="Name" name="editName" [(ngModel)]="workbasket.name" required></dd>
<dt>Owner</dt>
<dd><input class="form-control" placeholder="Owner" name="editOwner" [(ngModel)]="workbasket.owner" required></dd>
<dt>Description</dt>
<dd><textarea class="form-control" rows="3" placeholder="Description" name="editDescription" [(ngModel)]="workbasket.description"></textarea></dd>
</dl>
</div>

View File

@ -0,0 +1,3 @@
.dropdown-menu {
min-width: auto;
}

View File

@ -1,14 +1,24 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { async, ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing';
import { WorkbasketService } from '../../../services/workbasketservice.service';
import { WorkbasketInformationComponent } from './workbasket-information.component';
import { FormsModule } from '@angular/forms';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule, JsonpModule } from '@angular/http';
import { Workbasket } from 'app/model/workbasket';
describe('InformationComponent', () => {
let component: WorkbasketInformationComponent;
let fixture: ComponentFixture<WorkbasketInformationComponent>;
let debugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ WorkbasketInformationComponent ]
declarations: [ WorkbasketInformationComponent ],
imports:[FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule],
providers:[WorkbasketService]
})
.compileComponents();
}));
@ -16,10 +26,36 @@ describe('InformationComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(WorkbasketInformationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
debugElement = fixture.debugElement.nativeElement;
});
xit('should create', () => {
afterEach(() =>{
document.body.removeChild(debugElement);
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create a panel with heading and form with all fields', async(() => {
component.workbasket = new Workbasket('id','created','keyModified','domain','type','modified','name','description','owner','custom1','custom2','custom3','custom4','orgLevel1','orgLevel2','orgLevel3','orgLevel4',null);
fixture.detectChanges();
expect(debugElement.querySelector('#wb-information')).toBeDefined();
expect(debugElement.querySelector('#wb-information > .panel-heading > h4').textContent).toBe('name');
expect(debugElement.querySelectorAll('#wb-information > .panel-body > form').length).toBe(2);
fixture.whenStable().then(() => {
expect(debugElement.querySelector('#wb-information > .panel-body > form:first-child > div:first-child > input').value).toBe('keyModified');
});
}));
it('selectType should set workbasket.type to personal with 0 and multiple in other case', () => {
component.workbasket = new Workbasket(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null);
expect(component.workbasket.type).toEqual(null);
component.selectType(0);
expect(component.workbasket.type).toEqual('PERSONAL');
component.selectType(1);
expect(component.workbasket.type).toEqual('MULTIPLE');
});
});

View File

@ -17,7 +17,7 @@ export class WorkbasketInformationComponent implements OnInit {
ngOnInit() {
}
ngOnChanges() {
selectType(type: number){
this.workbasket.type = type === 0 ? 'PERSONAL': 'MULTIPLE';
}
}

View File

@ -1,20 +1,30 @@
<div class="col-xs-1">
<ul class="nav nav-tabs tabs-left sideways">
<li [class.active]="wbClicked"><a href="#workbaskets-information" data-toggle="tab" ><br>Details</a></li>
<li [class.active]="authClicked"><a href="#workbaskets-authorization" data-toggle="tab" ><br>Authorizations</a></li>
<li [class.active]="dtClicked"><a href="#workbaskets-distribution-targets" data-toggle="tab" >Distribution Targets</a></li>
</ul>
</div>
<div class="col-xs-10">
<div class="tab-content">
<div class="tab-pane" [class.active]="wbClicked" id="workbaskets-information">
<workbasket-information [workbasket]="workbasket"></workbasket-information>
</div>
<div class="tab-pane" [class.active]="authClicked" id="workbaskets-authorization">
<app-workbasket-authorization [workbasket]="workbasket"></app-workbasket-authorization>
</div>
<div class="tab-pane" [class.active]="dtClicked" id="workbaskets-distribution-targets">
<app-workbasket-distributiontargets [workbasket]="workbasket"></app-workbasket-distributiontargets>
</div>
</div>
<div class="container-scrollable" >
<app-no-access *ngIf="!hasPermission" ></app-no-access>
<div id ="workbasket-details" class="workbasket-details" *ngIf="workbasket">
<ul class="nav nav-tabs" role="tablist">
<li *ngIf="showDetail" class="visible-xs visible-sm hidden">
<a (click) = "backClicked()"><span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back</a>
</li>
<li role="presentation" class="active">
<a href="#work-baskets" aria-controls="work baskets" role="tab" data-toggle="tab" aria-expanded="true">Work baskets information</a>
</li>
<li role="presentation" class="inactive">
<a href="#authorizations" aria-controls="Authorizations" role="tab" data-toggle="tab" aria-expanded="true">Authorizations</a>
</li>
<li role="presentation" class="inactive">
<a href="#distribution-targets" aria-controls="distribution targets" role="tab" data-toggle="tab" aria-expanded="true">Distribution targets</a>
</li>
</ul>
<div class="tab-content detail-tab-content">
<div role="tabpanel" class="tab-pane active" id="work-baskets">
<workbasket-information [workbasket]="workbasket"></workbasket-information>
</div>
<div role="tabpanel" class="tab-pane inactive" id="authorizations">
<!-- <app-workbasket-authorization [workbasket]="workbasket"></app-workbasket-authorization> -->
</div>
<div role="tabpanel" class="tab-pane inactive" id="distribution-targets">
<!-- <app-workbasket-distributiontargets [workbasket]="workbasket"></app-workbasket-distributiontargets>-->
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,19 @@
.nav.nav-tabs {
& > li {
& > a{
min-height: 56px;
padding-top: 17px;
}
&:first-child > a{
border-left: none;
}
}
& > p{
margin: 0px;
}
}
.workbasket-details{
margin-top:1px;
}

View File

@ -1,13 +1,32 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { WorkbasketDetailsComponent } from './workbasket-details.component';
import { NoAccessComponent } from '../noAccess/no-access.component';
import { WorkbasketInformationComponent } from './information/workbasket-information.component';
import { Workbasket } from 'app/model/workbasket';
import { Observable } from 'rxjs/Observable';
import { WorkbasketService } from '../../services/workbasketservice.service';
import { MasterAndDetailService } from '../../services/master-and-detail.service';
import { PermissionService } from '../../services/permission.service';
import { RouterTestingModule } from '@angular/router/testing';
import { FormsModule } from '@angular/forms';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
describe('WorkbasketDetailsComponent', () => {
let component: WorkbasketDetailsComponent;
let fixture: ComponentFixture<WorkbasketDetailsComponent>;
let debugElement;
let masterAndDetailService;
let workbasketService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ WorkbasketDetailsComponent ]
imports:[RouterTestingModule, FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule],
declarations: [ WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent ],
providers:[WorkbasketService, MasterAndDetailService, PermissionService]
})
.compileComponents();
}));
@ -15,10 +34,54 @@ describe('WorkbasketDetailsComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(WorkbasketDetailsComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
fixture.detectChanges();
masterAndDetailService = TestBed.get(MasterAndDetailService);
workbasketService = TestBed.get(WorkbasketService);
spyOn(masterAndDetailService, 'getShowDetail').and.returnValue(Observable.of(true));
spyOn(workbasketService,'getSelectedWorkBasket').and.returnValue(Observable.of('id1'));
spyOn(workbasketService,'getWorkBasket').and.returnValue(Observable.of(new Workbasket('id1',null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null)));
});
xit('should be created', () => {
afterEach(() =>{
document.body.removeChild(debugElement);
});
it('should be created', () => {
expect(component).toBeTruthy();
});
it('should has created app-no-access if workbasket is not defined', () => {
expect(component.workbasket).toBeUndefined();
expect(debugElement.querySelector('app-no-access')).toBeTruthy;
});
it('should has created workbasket-details if workbasket is defined and app-no-access should dissapear', () => {
expect(component.workbasket).toBeUndefined();
expect(debugElement.querySelector('app-no-access')).toBeTruthy;
component.workbasket = new Workbasket(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null);
fixture.detectChanges();
expect(debugElement.querySelector('app-no-access')).toBeFalsy;
expect(debugElement.querySelector('worbasket-details')).toBeTruthy;
});
it('should show back button with classes "visible-xs visible-sm hidden" when showDetail property is true', () => {
component.workbasket = new Workbasket(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null);
component.ngOnInit();
fixture.detectChanges();
expect(debugElement.querySelector('.visible-xs.visible-sm.hidden > a').textContent).toBe('Back');
});
it('should create a copy of workbasket when workbasket is selected', () => {
expect(component.workbasketClone).toBeUndefined();
component.ngOnInit();
fixture.detectChanges();
expect(component.workbasket.id).toEqual(component.workbasketClone.id);
});
});

View File

@ -1,7 +1,9 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { WorkbasketSummary } from '../../model/workbasketSummary';
import { Workbasket } from '../../model/workbasket';
import { WorkbasketService } from '../../services/workbasketservice.service'
import { MasterAndDetailService } from '../../services/master-and-detail.service'
import { ActivatedRoute, Params, Router, NavigationStart } from '@angular/router';
import { PermissionService } from '../../services/permission.service';
import { Subscription } from 'rxjs';
@Component({
@ -12,35 +14,61 @@ import { Subscription } from 'rxjs';
export class WorkbasketDetailsComponent implements OnInit {
workbasket: WorkbasketSummary;
workbasketClone: WorkbasketSummary;
workbasket: Workbasket;
workbasketClone: Workbasket;
showDetail: boolean = false;
hasPermission: boolean = true;
workbasketServiceSubscription: Subscription;
workbasketSelectedSubscription: Subscription;
workbasketSubscription: Subscription;
routeSubscription: Subscription;
masterAndDetailSubscription: Subscription;
permissionSubscription: Subscription;
constructor(private service: WorkbasketService, private route: ActivatedRoute, private router: Router) { }
constructor(private service: WorkbasketService,
private route: ActivatedRoute,
private router: Router,
private masterAndDetailService: MasterAndDetailService,
private permissionService: PermissionService) { }
ngOnInit() {
this.routeSubscription = this.route.params.subscribe(params => {
this.service.selectWorkBasket(params['id']);
});
this.workbasketServiceSubscription = this.service.getSelectedWorkBasket().subscribe( workbasketIdSelected => {
this.service.getWorkBasket(workbasketIdSelected).subscribe( workbasket => {
this.workbasketSelectedSubscription = this.service.getSelectedWorkBasket().subscribe( workbasketIdSelected => {
this.workbasket = undefined;
this.workbasketSubscription = this.service.getWorkBasket(workbasketIdSelected).subscribe( workbasket => {
this.workbasket = workbasket;
this.workbasketClone = { ...this.workbasket };
});
});
}
this.routeSubscription = this.route.params.subscribe(params => {
if( params['id'] && params['id'] !== '') {
this.service.selectWorkBasket( params['id']);
}
});
this.masterAndDetailSubscription = this.masterAndDetailService.getShowDetail().subscribe(showDetail => {
this.showDetail = showDetail;
});
this.permissionSubscription = this.permissionService.hasPermission().subscribe( permission => {
this.hasPermission = permission;
})
ngOnChanges() {
}
onSave() {
}
backClicked(): void {
this.service.selectWorkBasket(undefined);
this.router.navigate(['./'], { relativeTo: this.route.parent });
}
ngOnDestroy(){
this.workbasketServiceSubscription.unsubscribe();
this.routeSubscription.unsubscribe();
if(this.workbasketSelectedSubscription){this.workbasketSelectedSubscription.unsubscribe();}
if(this.workbasketSubscription){this.workbasketSubscription.unsubscribe();}
if(this.routeSubscription){this.routeSubscription.unsubscribe();}
if(this.masterAndDetailSubscription){this.masterAndDetailSubscription.unsubscribe();}
if(this.permissionSubscription){this.permissionSubscription.unsubscribe();}
}
}

View File

@ -107,7 +107,7 @@
</dl>
</div>
</li>
<li class="list-group-item" *ngFor= "let workbasket of workbaskets" [class.active]="workbasket.id === selectedId" type="text" [routerLink]="[ {outlets: { detail: [workbasket.id] } }]">
<li class="list-group-item" *ngFor= "let workbasket of workbaskets" [class.active]="workbasket.id == selectedId" type="text" (click) ="selectWorkbasket(workbasket.id)" [routerLink]="[ {outlets: { detail: [workbasket.id] } }]">
<div class="row">
<dl class="col-xs-1">
<dt><svg-icon class="{{workbasket.id === selectedId? 'white': 'blue' }} small" src="./assets/icons/{{workbasket.type === 'PERSONAL'? 'user.svg': 'users.svg'}}"></svg-icon></dt>

View File

@ -19,20 +19,25 @@ export class WorkbasketListComponent implements OnInit {
private workBasketSummarySubscription: Subscription;
private workbasketServiceSubscription: Subscription;
constructor(private service: WorkbasketService) { }
constructor(private workbasketService: WorkbasketService) { }
ngOnInit() {
this.workBasketSummarySubscription = this.service.getWorkBasketsSummary().subscribe(resultList => {
this.workBasketSummarySubscription = this.workbasketService.getWorkBasketsSummary().subscribe(resultList => {
this.workbaskets = resultList;
});
this.workbasketServiceSubscription = this.service.getSelectedWorkBasket().subscribe( workbasketIdSelected => {
this.selectedId = workbasketIdSelected;
this.workbasketServiceSubscription = this.workbasketService.getSelectedWorkBasket().subscribe( workbasketIdSelected => {
this.selectedId = workbasketIdSelected;
});
}
selectWorkbasket(id:string){
this.selectedId = id;
}
onDelete(workbasket: WorkbasketSummary) {
this.service.deleteWorkbasket(workbasket.id).subscribe(result => {
this.workbasketService.deleteWorkbasket(workbasket.id).subscribe(result => {
var index = this.workbaskets.indexOf(workbasket);
if (index > -1) {
this.workbaskets.splice(index, 1);
@ -41,7 +46,7 @@ export class WorkbasketListComponent implements OnInit {
}
onAdd() {
this.service.createWorkbasket(this.newWorkbasket).subscribe(result => {
this.workbasketService.createWorkbasket(this.newWorkbasket).subscribe(result => {
this.workbaskets.push(result);
this.onClear();
});

View File

@ -0,0 +1,11 @@
<button class = "btn btn-default back-button pull-left visible-xs visible-sm hidden blue" (click) = "backClicked()">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back
</button>
<div class="col-xs-12 container-no-access">
<div class="center-block no-access">
<h3 class="grey">You do not have access to edit this workbasket</h3>
<svg-icon class="img-responsive no-access-icon" src="./assets/icons/noaccess.svg"></svg-icon>
</div>
</div>

View File

@ -0,0 +1,20 @@
.container-no-access .no-access-icon {
display: block;
width: 150px;
height: 150px;
fill: grey;
margin: 20px auto;
}
.container-no-access{
top:30vh;
}
.center-block.no-access {
text-align: center;
}
.back-button {
margin:0px;
border:none;
}

View File

@ -0,0 +1,45 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NoAccessComponent } from './no-access.component';
import { Router, Routes, ActivatedRoute, NavigationStart, RouterEvent } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpModule } from '@angular/http';
import { HttpClientModule } from '@angular/common/http';
describe('NoAccessComponent', () => {
let component: NoAccessComponent;
let fixture: ComponentFixture<NoAccessComponent>;
let debugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports:[RouterTestingModule, AngularSvgIconModule, HttpModule, HttpClientModule],
declarations: [ NoAccessComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NoAccessComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
});
afterEach(() =>{
document.body.removeChild(debugElement);
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have a back button with the following classes "btn btn-default back-button pull-left visible-xs visible-sm hidden blue"', () => {
expect(debugElement.querySelector('button').attributes.class.value).toBe('btn btn-default back-button pull-left visible-xs visible-sm hidden blue');
});
it('should have a div with title and svg', () => {
expect(debugElement.querySelector('div.center-block.no-access > h3' ).textContent).toBeDefined();
expect(debugElement.querySelector('div.center-block.no-access > svg-icon' ).attributes.src.value).toBe('./assets/icons/noaccess.svg');
});
});

View File

@ -0,0 +1,20 @@
import { Component, OnInit } from '@angular/core';
import {Router, ActivatedRoute} from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
@Component({
selector: 'app-no-access',
templateUrl: './no-access.component.html',
styleUrls: ['./no-access.component.scss']
})
export class NoAccessComponent implements OnInit {
constructor(private router: Router, private route: ActivatedRoute) { }
ngOnInit() {
}
backClicked(): void {
this.router.navigate(['./'], { relativeTo: this.route.parent });
}
}

View File

@ -1,38 +0,0 @@
<h1>Edit Workbasket</h1>
<form class="form-horizontal" #workbasketForm="ngForm">
<div class="form-group">
<label class="col-sm-2 control-label" for="id">ID</label>
<div class="col-sm-10">
<input class="form-control" id="id" placeholder="ID" [(ngModel)]="workbasket.id" name="id" readonly>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="name">Name</label>
<div class="col-sm-10">
<input class="form-control" id="name" placeholder="Name" [(ngModel)]="workbasket.name" name="name" #name="ngModel" required>
<div [hidden]="name.valid || name.pristine" class="alert alert-danger">
The workbasket name is required
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="description">Description</label>
<div class="col-sm-10">
<input class="form-control" id="description" placeholder="Description" [(ngModel)]="workbasket.description" name="description">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="owner">Owner</label>
<div class="col-sm-10">
<input class="form-control" id="owner" placeholder="Owner" [(ngModel)]="workbasket.owner" name="owner" #owner="ngModel" required>
<div [hidden]="owner.valid || owner.pristine" class="alert alert-danger">
The workbasket owner is required
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default btn-primary" (click)="onSubmit()">Save changes</button>
</div>
</div>
</form>

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { WorkbasketeditorComponent } from './workbasketeditor.component';
describe('WorkbasketeditorComponent', () => {
let component: WorkbasketeditorComponent;
let fixture: ComponentFixture<WorkbasketeditorComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ WorkbasketeditorComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WorkbasketeditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
xit('should be created', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,26 +0,0 @@
import { Component, OnInit, EventEmitter } from '@angular/core';
import { WorkbasketSummary } from '../model/workbasketSummary';
@Component({
selector: 'app-workbasketeditor',
inputs: ['workbasket'],
outputs: ['workbasketSaved'],
templateUrl: './workbasketeditor.component.html',
styleUrls: ['./workbasketeditor.component.css']
})
export class WorkbasketeditorComponent implements OnInit {
public workbasket: WorkbasketSummary;
public workbasketSaved: EventEmitter<WorkbasketSummary> = new EventEmitter();
constructor() { }
ngOnInit() {
this.workbasket = new WorkbasketSummary("", "", "", "", "", "", "", "", "", "", "", "");
}
onSubmit() {
// TODO save values
console.log("changed " + this.workbasket.name);
this.workbasketSaved.next(this.workbasket);
}
}

View File

@ -32,8 +32,8 @@
}
.container-scrollable {
max-height: calc(100vh - 52px);
height: calc(100vh - 52px);
max-height: calc(100vh - 55px);
height: calc(100vh - 55px);
overflow-y: auto;
overflow-x: hidden;
}

View File

@ -0,0 +1,10 @@
<?xml version='1.0' encoding='iso-8859-1'?>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297 297" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 297 297">
<g>
<path d="m110.549,98.768c27.231,0 49.384-22.154 49.384-49.384 0.001-27.231-22.153-49.384-49.384-49.384s-49.384,22.153-49.384,49.384c0,27.231 22.154,49.384 49.384,49.384z"/>
<path d="m226.823,185.307c-30.794,0-55.846,25.053-55.846,55.846 0,30.794 25.053,55.846 55.846,55.846s55.846-25.053 55.846-55.846c0-30.793-25.052-55.846-55.846-55.846zm34.017,44.247l-36.494,36.494c-1.836,1.835-4.241,2.753-6.647,2.753-2.406,0-4.811-0.918-6.647-2.753l-18.247-18.247c-3.671-3.671-3.671-9.623 0-13.294 3.672-3.67 9.622-3.67 13.294,0l11.6,11.601 29.847-29.848c3.672-3.67 9.622-3.67 13.294,0 3.671,3.671 3.671,9.623-5.68434e-14,13.294z"/>
<path d="m206.751,172.72v-8.713c0-18.793-12.077-35.457-29.935-41.307l-.083-.027-26.121-4.325c-2.223-0.684-4.599,0.493-5.398,2.686l-29.638,81.319c-1.71,4.691-8.345,4.691-10.055,0l-29.638-81.319c-0.646-1.771-2.317-2.881-4.108-2.881-0.425,0-27.41,4.514-27.41,4.514-18.005,6-30.035,22.691-30.035,41.565v67.664c0,10.14 8.22,18.36 18.36,18.36h123.405c-0.382-2.984-0.601-6.017-0.601-9.103 0.001-32.361 21.671-59.74 51.257-68.433z"/>
<path d="m121.487,113.395c-1.157-1.26-2.848-1.895-4.558-1.895h-12.759c-1.711,0-3.402,0.634-4.558,1.895-1.791,1.951-2.051,4.769-0.779,6.972l6.82,10.282-3.193,26.934 6.287,16.725c0.613,1.682 2.992,1.682 3.605,0l6.287-16.725-3.193-26.934 6.82-10.282c1.272-2.203 1.012-5.021-0.779-6.972z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<title>Admin</title>
<title>Administration</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">