TSK-1401: Update Typeahead component with material design (#1312)

* TSK-1401: remove bootstrap from typeahead component

* TSK-1401: rebase

* TSK-1401: modify typeahead component

* TSK-1401: fixed CI error

* TSK-1401: fixed test error

* TSK-1401: fixed format error

* TSK-1401: add blur function after selection

* TSK-1401: fix CI error

* TSK-1401: integrate requested changes

* TSK-1401: integrate changes

* TSK-1401: update sidenav and typeahead

* TSK-1401: fix tabs

* TSK-1401: fix CI error

* TSK-1401: add pipe for tab bar
This commit is contained in:
Franzi321 2020-10-28 09:43:10 +01:00 committed by GitHub
parent d997ab1923
commit ea38c7ddba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 394 additions and 413 deletions

View File

@ -7,9 +7,8 @@
<div class="panel-body">
<div class="col-md-6 col-md-offset-3 margin">
<taskana-shared-type-ahead name="accessIdSelected" [(ngModel)]="accessIdSelected"
placeHolderMessage="Search for access id..."
(selectedItem)="onSelectAccessId($event)"
displayError=true>
placeHolderMessage="Search for access id..." (selectedItem)="onSelectAccessId($event)" displayError=true
isRequired="false">
</taskana-shared-type-ahead>
</div>
@ -25,131 +24,128 @@
<!-- TABLE WITH ACCESS ID -->
<table id="table-access-items" class="table table-striped table-center">
<thead>
<!-- TABLE FIRST ROW -->
<tr>
<th>
<taskana-shared-sort [sortingFields]="sortingFields"
(performSorting)="sorting($event)"
menuPosition="left">
</taskana-shared-sort>
</th>
<th class="text-align min-width">Workbasket Key</th>
<th colspan="2" class="text-align">Access Id</th>
<th>Read</th>
<th>Open</th>
<th>Append</th>
<th>Transfer</th>
<th>Distribute</th>
<ng-container *ngFor="let customField of customFields$ | async">
<th *ngIf="customField.visible">
{{customField.field}}
<!-- TABLE FIRST ROW -->
<tr>
<th>
<taskana-shared-sort [sortingFields]="sortingFields" (performSorting)="sorting($event)"
menuPosition="left">
</taskana-shared-sort>
</th>
</ng-container>
</tr>
<!-- TABLE SECOND ROW -->
<tr>
<th colspan="2" class="text-align"><label>
<input type="text" formControlName="workbasketKeyFilter" (keyup.enter)="searchForAccessItemsWorkbaskets()"
class="form-control" placeholder="Workbasket filter">
</label></th>
<th class="text-align"><label>
<input type="text" formControlName="accessIdFilter" (keyup.enter)="searchForAccessItemsWorkbaskets()"
class="form-control" placeholder="Access id filter">
</label></th>
<th>
<button type="button" (click)="searchForAccessItemsWorkbaskets()" class="btn btn-default"
data-toggle="tooltip"
title="Search">
<span class="material-icons md-20 blue">search</span>
</button>
</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
<th class="text-align min-width">Workbasket Key</th>
<th colspan="2" class="text-align">Access Id</th>
<th>Read</th>
<th>Open</th>
<th>Append</th>
<th>Transfer</th>
<th>Distribute</th>
<ng-container *ngFor="let customField of customFields$ | async">
<th *ngIf="customField.visible">
{{customField.field}}
</th>
</ng-container>
</tr>
<!-- TABLE SECOND ROW -->
<tr>
<th colspan="2" class="text-align"><label>
<input type="text" formControlName="workbasketKeyFilter"
(keyup.enter)="searchForAccessItemsWorkbaskets()" class="form-control"
placeholder="Workbasket filter">
</label></th>
<th class="text-align"><label>
<input type="text" formControlName="accessIdFilter" (keyup.enter)="searchForAccessItemsWorkbaskets()"
class="form-control" placeholder="Access id filter">
</label></th>
<th>
<button type="button" (click)="searchForAccessItemsWorkbaskets()" class="btn btn-default"
data-toggle="tooltip" title="Search">
<span class="material-icons md-20 blue">search</span>
</button>
</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<!-- ACCESS ITEMS GROUPS -->
<tbody formArrayName="accessItemsGroups">
<tr class="table__access-item-groups" *ngFor="let accessItem of accessItemsGroups.controls; let index = index;"
<tr class="table__access-item-groups"
*ngFor="let accessItem of accessItemsGroups.controls; let index = index;"
[formGroupName]="index.toString()">
<td colspan="2">
<label class="wrap">{{accessItem.value.workbasketKey}}</label>
</td>
<td *ngIf="(accessItemsCustomization$ | async)?.accessId.lookupField else accessIdInput" colspan="2"
<td colspan="2">
<label class="wrap">{{accessItem.value.workbasketKey}}</label>
</td>
<td *ngIf="(accessItemsCustomization$ | async)?.accessId.lookupField else accessIdInput" colspan="2"
class="text-align text-width taskana-type-ahead" style="padding-top: 0;">
<div>
<taskana-shared-type-ahead formControlName="accessId" placeHolderMessage="* Access id is required"
[validationValue]="toggleValidationAccessIdMap.get(index)"
[displayError]="!isFieldValid('accessItem.value.accessId', index)"
[disable]=true></taskana-shared-type-ahead>
</div>
</td>
<ng-template #accessIdInput>
<td colspan="2" class="text-align text-width">
<div>
<label>
<input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid?
'* Access id is required': ''}}">
</label>
<taskana-shared-type-ahead formControlName="accessId" placeHolderMessage="* Access id is required"
[validationValue]="toggleValidationAccessIdMap.get(index)"
[displayError]="!isFieldValid('accessItem.value.accessId', index)" [disable]=true>
</taskana-shared-type-ahead>
</div>
</td>
</ng-template>
<td>
<input id="checkbox-{{index}}-0" type="checkbox" formControlName="permRead" class="regular-checkbox">
<label for="checkbox-{{index}}-0"></label>
</td>
<td>
<input id="checkbox-{{index}}-1" type="checkbox" formControlName="permOpen">
<label for="checkbox-{{index}}-1"></label>
</td>
<td>
<input id="checkbox-{{index}}-2" type="checkbox" formControlName="permAppend">
<label for="checkbox-{{index}}-2"></label>
</td>
<td>
<input id="checkbox-{{index}}-3" type="checkbox" formControlName="permTransfer">
<label for="checkbox-{{index}}-3"></label>
</td>
<td>
<input id="checkbox-{{index}}-4" type="checkbox" formControlName="permDistribute">
<label for="checkbox-{{index}}-4"></label>
</td>
<ng-container *ngFor="let customField of customFields$ | async; let customIndex = index">
<td *ngIf="customField.visible">
<input id="checkbox-{{index}}-{{customIndex + 5}}" type="checkbox"
formControlName="permCustom{{customIndex + 1}}">
<label for="checkbox-{{index}}-{{customIndex + 5}}"></label>
<ng-template #accessIdInput>
<td colspan="2" class="text-align text-width">
<div>
<label>
<input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid?
'* Access id is required': ''}}">
</label>
</div>
</td>
</ng-template>
<td>
<input id="checkbox-{{index}}-0" type="checkbox" formControlName="permRead" class="regular-checkbox">
<label for="checkbox-{{index}}-0"></label>
</td>
</ng-container>
</tr>
<td>
<input id="checkbox-{{index}}-1" type="checkbox" formControlName="permOpen">
<label for="checkbox-{{index}}-1"></label>
</td>
<td>
<input id="checkbox-{{index}}-2" type="checkbox" formControlName="permAppend">
<label for="checkbox-{{index}}-2"></label>
</td>
<td>
<input id="checkbox-{{index}}-3" type="checkbox" formControlName="permTransfer">
<label for="checkbox-{{index}}-3"></label>
</td>
<td>
<input id="checkbox-{{index}}-4" type="checkbox" formControlName="permDistribute">
<label for="checkbox-{{index}}-4"></label>
</td>
<ng-container *ngFor="let customField of customFields$ | async; let customIndex = index">
<td *ngIf="customField.visible">
<input id="checkbox-{{index}}-{{customIndex + 5}}" type="checkbox"
formControlName="permCustom{{customIndex + 1}}">
<label for="checkbox-{{index}}-{{customIndex + 5}}"></label>
</td>
</ng-container>
</tr>
</tbody>
</table>
<!-- BELONGING GROUPS BUTTONS -->
<button class="btn btn-primary pull-left btn-group" type="button"
data-toggle="modal"
data-target="#myModal">
<button class="btn btn-primary pull-left btn-group" type="button" data-toggle="modal" data-target="#myModal">
Belonging groups
</button>
<!-- REVOKE ACCESS BUTTON -->
<div class="pull-right btn-group">
<button *ngIf="accessItemsForm" type="button" (click)="revokeAccess()" class="btn btn-default"
data-toggle="tooltip"
title="Revoke access" [disabled]=isGroup>
data-toggle="tooltip" title="Revoke access" [disabled]=isGroup>
<span class="material-icons md-20 red">clear</span>
</button>
</div>
@ -178,4 +174,4 @@
</div>
</div>
</div>
</div>
</div>

View File

@ -23,6 +23,11 @@ import { StartupService } from '../../../shared/services/startup/startup.service
import { TaskanaEngineService } from '../../../shared/services/taskana-engine/taskana-engine.service';
import { WindowRefService } from '../../../shared/services/window/window.service';
import { engineConfigurationMock } from '../../../shared/store/mock-data/mock-store';
import { MatSelectModule } from '@angular/material/select';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatProgressBarModule } from '@angular/material/progress-bar';
const isFieldValidFn = jest.fn().mockReturnValue(true);
const formValidatorServiceSpy = jest.fn().mockImplementation(
@ -67,7 +72,12 @@ describe('AccessItemsManagementComponent', () => {
MatSnackBarModule,
MatDialogModule,
TypeaheadModule.forRoot(),
BrowserAnimationsModule
BrowserAnimationsModule,
MatFormFieldModule,
MatSelectModule,
MatAutocompleteModule,
MatInputModule,
MatProgressBarModule
],
declarations: [
AccessItemsManagementComponent,

View File

@ -27,6 +27,7 @@ import { AccessItemsManagementSelector } from '../../../shared/store/access-item
export class AccessItemsManagementComponent implements OnInit {
accessIdSelected: string;
accessIdPrevious: string;
isRequired: boolean = false;
accessItemsForm: FormGroup;
toggleValidationAccessIdMap = new Map<number, boolean>();

View File

@ -1,6 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Subject } from 'rxjs';
import { Router, ActivatedRoute } from '@angular/router';
import { Observable, Subject } from 'rxjs';
import { DomainService } from '../../../shared/services/domain/domain.service';
import { takeUntil } from 'rxjs/operators';
@ -15,8 +15,18 @@ export class AdministrationOverviewComponent implements OnInit {
selectedDomain: string;
destroy$ = new Subject<void>();
url$: Observable<any>;
constructor(private router: Router, private domainService: DomainService) {}
constructor(private router: Router, private domainService: DomainService) {
router.events.pipe(takeUntil(this.destroy$)).subscribe((e) => {
const urlPaths = this.router.url.split('/');
if (this.router.url.includes('detail')) {
this.selectedTab = urlPaths[urlPaths.length - 2];
} else {
this.selectedTab = urlPaths[urlPaths.length - 1];
}
});
}
ngOnInit() {
this.domainService
@ -32,13 +42,6 @@ export class AdministrationOverviewComponent implements OnInit {
.subscribe((domain) => {
this.selectedDomain = domain;
});
const urlPaths = this.router.url.split('/');
if (this.router.url.includes('detail')) {
this.selectedTab = urlPaths[urlPaths.length - 2];
} else {
this.selectedTab = urlPaths[urlPaths.length - 1];
}
}
switchDomain(domain) {

View File

@ -33,6 +33,10 @@ import {
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ACTION } from '../../../shared/models/action';
import { WorkbasketAccessItems } from '../../../shared/models/workbasket-access-items';
import { MatSelectModule } from '@angular/material/select';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatProgressBarModule } from '@angular/material/progress-bar';
@Component({ selector: 'taskana-shared-spinner', template: '' })
@ -90,6 +94,10 @@ describe('WorkbasketAccessItemsComponent', () => {
HttpClientTestingModule,
RouterTestingModule.withRoutes([]),
BrowserAnimationsModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatAutocompleteModule,
MatProgressBarModule
],
declarations: [WorkbasketAccessItemsComponent, TypeAheadComponent, SpinnerStub],

View File

@ -1,6 +1,6 @@
<mat-progress-bar mode="query" *ngIf="requestInProgress"></mat-progress-bar>
<div *ngIf="workbasket" id="wb-information">
<!--
<!--
<div class="panel-heading">
<div class="pull-right btn-group">
<button type="button" (click)="onSubmit()" data-toggle="tooltip" title="Save"
@ -45,30 +45,25 @@
<mat-form-field appearance="outline">
<mat-label>Key</mat-label>
<label for="workbasket-key"></label>
<input matInput required type="text" #key="ngModel" maxlength="64"
[disabled]="action == 0 || action == 3"
id="workbasket-key" placeholder="Key" [(ngModel)]="workbasket.key"
name="workbasket.key" (input)="validateInputOverflow(key, 64, $event)">
<input matInput required type="text" #key="ngModel" maxlength="64" [disabled]="action == 0 || action == 3"
id="workbasket-key" placeholder="Key" [(ngModel)]="workbasket.key" name="workbasket.key"
(input)="validateInputOverflow(key, 64, $event)">
</mat-form-field>
<div *ngIf="inputOverflowMap.get(key.name)" class="error">{{lengthError}}</div>
<taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.key')"
[validationTrigger]="this.toggleValidationMap.get('workbasket.key')"
errorMessage="* Key is required">
[validationTrigger]="this.toggleValidationMap.get('workbasket.key')" errorMessage="* Key is required">
</taskana-shared-field-error-display>
<!-- NAME -->
<mat-form-field appearance="outline">
<mat-label>Name</mat-label>
<label for="workbasket-name"></label>
<input matInput type="text" required maxlength="255" #name="ngModel"
id="workbasket-name" placeholder="Name"
[(ngModel)]="workbasket.name" name="workbasket.name"
(input)="validateInputOverflow(name, 255)">
<input matInput type="text" required maxlength="255" #name="ngModel" id="workbasket-name" placeholder="Name"
[(ngModel)]="workbasket.name" name="workbasket.name" (input)="validateInputOverflow(name, 255)">
</mat-form-field>
<div *ngIf="inputOverflowMap.get(name.name)" class="error">{{lengthError}}</div>
<taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.name')"
[validationTrigger]="this.toggleValidationMap.get('workbasket.name')"
errorMessage="* Name is required">
[validationTrigger]="this.toggleValidationMap.get('workbasket.name')" errorMessage="* Name is required">
</taskana-shared-field-error-display>
@ -79,9 +74,8 @@
<mat-form-field class="workbasket-information__mat-form-field" appearance="outline">
<mat-label>Domain</mat-label>
<label for="workbasket-domain"></label>
<input matInput type="text" disabled id="workbasket-domain"
placeholder="Domain" [(ngModel)]="workbasket.domain"
name="classification.domain">
<input matInput type="text" disabled id="workbasket-domain" placeholder="Domain"
[(ngModel)]="workbasket.domain" name="classification.domain">
</mat-form-field>
<!-- TYPE -->
@ -93,8 +87,7 @@
{{allTypes.get(workbasket.type)}}
</mat-select-trigger>
<mat-option *ngFor="let type of allTypes | mapValues | removeEmptyType" value="{{type.key}}">
<taskana-administration-icon-type
[type]='type.key' [text]="type.value">
<taskana-administration-icon-type [type]='type.key' [text]="type.value">
</taskana-administration-icon-type>
</mat-option>
</mat-select>
@ -107,80 +100,70 @@
<mat-form-field appearance="outline">
<mat-label>Description</mat-label>
<label for="workbasket-description"></label>
<textarea matInput
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="5"
maxlength="255"
id="workbasket-description" placeholder="Description"
[(ngModel)]="workbasket.description"
name="workbasket.description" #description="ngModel"
(input)="validateInputOverflow(description, 255)"></textarea>
<textarea matInput cdkTextareaAutosize cdkAutosizeMinRows="1" cdkAutosizeMaxRows="5" maxlength="255"
id="workbasket-description" placeholder="Description" [(ngModel)]="workbasket.description"
name="workbasket.description" #description="ngModel"
(input)="validateInputOverflow(description, 255)"></textarea>
</mat-form-field>
<div *ngIf="inputOverflowMap.get(description.name)" class="error">{{lengthError}}</div>
<!-- OWNER -->
<div class="input-group form-group col-xs-12 required">
<label for="wb-owner" class="control-label ">Owner</label>
<taskana-shared-type-ahead *ngIf="lookupField else ownerInput" required maxlength="128" #owner="ngModel"
name="workbasket.owner"
[(ngModel)]="workbasket.owner"
placeHolderMessage="* Owner is required"
[validationValue]="this.toggleValidationMap.get('workbasket.owner')"
[displayError]="!isFieldValid('workbasket.owner')"
width="100%" (input)="validateInputOverflow(owner, 128)">
<div *ngIf="inputOverflowMap.get(owner.name)" class="error">{{lengthError}}</div>
</taskana-shared-type-ahead>
<ng-template #ownerInput>
<input type="text" required maxlength="128" #owner="ngModel" class="form-control" id="wb-owner"
placeholder="Owner"
[(ngModel)]="workbasket.owner" name="workbasket.owner" (input)="validateInputOverflow(owner, 128)">
<div *ngIf="inputOverflowMap.get(owner.name)" class="error">{{lengthError}}</div>
<taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.owner')"
[validationTrigger]="this.toggleValidationMap.get('workbasket.owner')"
errorMessage="* Owner is required">
</taskana-shared-field-error-display>
</ng-template>
</div>
<taskana-shared-type-ahead *ngIf="lookupField else ownerInput" isRequired="true" maxlength="128"
#owner="ngModel" name="workbasket.owner" [(ngModel)]="workbasket.owner" placeHolderMessage="Owner"
[validationValue]="this.toggleValidationMap.get('workbasket.owner') "
[displayError]="!isFieldValid('workbasket.owner')" width="100%" (input)="validateInputOverflow(owner, 128)">
<div *ngIf="inputOverflowMap.get(owner.name)" class="error">{{lengthError}}</div>
</taskana-shared-type-ahead>
<ng-template #ownerInput>
<input type="text" required maxlength="128" #owner="ngModel" class="form-control" id="wb-owner"
placeholder="Owner" [(ngModel)]="workbasket.owner" name="workbasket.owner"
(input)="validateInputOverflow(owner, 128)">
<div *ngIf="inputOverflowMap.get(owner.name)" class="error">{{lengthError}}</div>
<taskana-shared-field-error-display [displayError]="!isFieldValid('workbasket.owner')"
[validationTrigger]="this.toggleValidationMap.get('workbasket.owner')" errorMessage="* Owner is required">
</taskana-shared-field-error-display>
</ng-template>
<!-- ORGASATIONAL LEVELS -->
<h6 class="workbasket-information__subheading" style="margin-top: 65px;"> Organisational Levels </h6>
<mat-divider class="workbasket-information__horizontal-line"> </mat-divider>
<mat-form-field appearance="outline">
<mat-label>OrgLevel 1</mat-label>
<input matInput type="text" #orgLevel1="ngModel" maxlength="255"
placeholder="OrgLevel 1" [(ngModel)]="workbasket.orgLevel1"
name="workbasket.orgLevel1" (input)="validateInputOverflow(orgLevel1, 255)">
</mat-form-field>
<div *ngIf="inputOverflowMap.get(orgLevel1.name)" class="error">{{lengthError}}</div>
<!-- ORGASATIONAL LEVELS -->
<h6 class="workbasket-information__subheading" style="margin-top: 65px;"> Organisational Levels </h6>
<mat-divider class="workbasket-information__horizontal-line"> </mat-divider>
<mat-form-field appearance="outline">
<mat-label>OrgLevel 2</mat-label>
<input matInput type="text" #orgLevel2="ngModel" maxlength="255"
placeholder="OrgLevel 2" [(ngModel)]="workbasket.orgLevel2"
name="workbasket.orgLevel2" (input)="validateInputOverflow(orgLevel2, 255)">
</mat-form-field>
<div *ngIf="inputOverflowMap.get(orgLevel2.name)" class="error">{{lengthError}}</div>
<mat-form-field appearance="outline">
<mat-label>OrgLevel 1</mat-label>
<input matInput type="text" #orgLevel1="ngModel" maxlength="255" placeholder="OrgLevel 1"
[(ngModel)]="workbasket.orgLevel1" name="workbasket.orgLevel1"
(input)="validateInputOverflow(orgLevel1, 255)">
</mat-form-field>
<div *ngIf="inputOverflowMap.get(orgLevel1.name)" class="error">{{lengthError}}</div>
<mat-form-field appearance="outline">
<mat-label>OrgLevel 3</mat-label>
<input matInput type="text" #orgLevel3="ngModel" maxlength="255"
placeholder="OrgLevel 3" [(ngModel)]="workbasket.orgLevel3"
name="workbasket.orgLevel3" (input)="validateInputOverflow(orgLevel3, 255)">
</mat-form-field>
<div *ngIf="inputOverflowMap.get(orgLevel3.name)" class="error">{{lengthError}}</div>
<mat-form-field appearance="outline">
<mat-label>OrgLevel 2</mat-label>
<input matInput type="text" #orgLevel2="ngModel" maxlength="255" placeholder="OrgLevel 2"
[(ngModel)]="workbasket.orgLevel2" name="workbasket.orgLevel2"
(input)="validateInputOverflow(orgLevel2, 255)">
</mat-form-field>
<div *ngIf="inputOverflowMap.get(orgLevel2.name)" class="error">{{lengthError}}</div>
<mat-form-field appearance="outline">
<mat-label>OrgLevel 4</mat-label>
<input matInput type="text" #orgLevel4="ngModel" maxlength="255"
placeholder="OrgLevel 4" [(ngModel)]="workbasket.orgLevel4"
name="workbasket.orgLevel4" (input)="validateInputOverflow(orgLevel4, 255)">
</mat-form-field>
<div *ngIf="inputOverflowMap.get(orgLevel4.name)" class="error">{{lengthError}}</div>
<mat-form-field appearance="outline">
<mat-label>OrgLevel 3</mat-label>
<input matInput type="text" #orgLevel3="ngModel" maxlength="255" placeholder="OrgLevel 3"
[(ngModel)]="workbasket.orgLevel3" name="workbasket.orgLevel3"
(input)="validateInputOverflow(orgLevel3, 255)">
</mat-form-field>
<div *ngIf="inputOverflowMap.get(orgLevel3.name)" class="error">{{lengthError}}</div>
<mat-form-field appearance="outline">
<mat-label>OrgLevel 4</mat-label>
<input matInput type="text" #orgLevel4="ngModel" maxlength="255" placeholder="OrgLevel 4"
[(ngModel)]="workbasket.orgLevel4" name="workbasket.orgLevel4"
(input)="validateInputOverflow(orgLevel4, 255)">
</mat-form-field>
<div *ngIf="inputOverflowMap.get(orgLevel4.name)" class="error">{{lengthError}}</div>
<!-- CUSTOM FIELDS -->
@ -193,14 +176,10 @@
<mat-form-field appearance="outline" class="workbasket-information__custom-fields">
<mat-label>{{customField.field}}</mat-label>
<label for='wb-custom-{{index+1}}'></label>
<input matInput type="text"
[placeholder]="customField.field"
[(ngModel)]="workbasket[getWorkbasketCustomProperty(index + 1)]"
id="wb-custom-{{index+1}}"
name="workbasket[{{getWorkbasketCustomProperty(index + 1)}}]"
maxlength="255"
#custom="ngModel"
(input)="validateInputOverflow(custom, 255)">
<input matInput type="text" [placeholder]="customField.field"
[(ngModel)]="workbasket[getWorkbasketCustomProperty(index + 1)]" id="wb-custom-{{index+1}}"
name="workbasket[{{getWorkbasketCustomProperty(index + 1)}}]" maxlength="255" #custom="ngModel"
(input)="validateInputOverflow(custom, 255)">
</mat-form-field>
<div *ngIf="inputOverflowMap.get(custom.name)" class="error">{{lengthError}}</div>
</div>
@ -209,4 +188,4 @@
</ng-form>
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
.workbasket-information-wrapper {
height: calc(100vh - 213px);
overflow-y: auto ;
overflow-y: auto;
}
.workbasket-information {
padding: 15px;

View File

@ -39,6 +39,7 @@ import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { MatInputModule } from '@angular/material/input';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
@Component({ selector: 'taskana-shared-spinner', template: '' })
class SpinnerStub {
@ -114,7 +115,8 @@ describe('WorkbasketInformationComponent', () => {
MatDividerModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule
MatSelectModule,
MatAutocompleteModule
],
declarations: [
WorkbasketInformationComponent,

View File

@ -1,6 +1,5 @@
@import 'src/theme/_colors.scss';
.workbasket-list {
height: calc(100vh - 156px);
overflow-x: hidden;

View File

@ -1,10 +1,17 @@
<mat-sidenav-container class="sidenav">
<mat-sidenav #sidenav mode="over" class="sidenav__drawer" [autoFocus]="false">
<div class="sidenav__drawer-logout">
<button mat-icon-button data-toggle="tooltip" title="Logout" (click)="logout()" aria-expanded="true"
aria-controls="logout">
<mat-icon>exit_to_app</mat-icon>
</button>
<div class="sidenav__drawer__container">
<div class="sidenav__drawer__container-menu">
<button mat-icon-button class="navbar_button-toggle" (click)="toggleSidenav()">
<mat-icon>menu</mat-icon>
</button>
</div>
<div class="sidenav__drawer__container-logout">
<button mat-icon-button data-toggle="tooltip" title="Logout" (click)="logout()" aria-expanded="true"
aria-controls="logout">
<mat-icon>exit_to_app</mat-icon>
</button>
</div>
</div>
<taskana-shared-user-information class="sidenav__drawer-user-info"></taskana-shared-user-information>
<taskana-sidenav-list></taskana-sidenav-list>
@ -23,4 +30,4 @@
</div>
</div>
</mat-sidenav-content>
</mat-sidenav-container>
</mat-sidenav-container>

View File

@ -21,9 +21,22 @@
margin-left: 30px;
}
.sidenav__drawer-logout {
text-align: end;
.sidenav__drawer__container {
display:flex;
flex-direction:row;
justify-content: space-between;
color: white;
margin-top: 5px;
}
.sidenav__drawer__container-menu {
margin-left: 15px;
}
.sidenav__drawer__container-logout {
text-align: end;
margin-right: 15px;
}
.sidenav__drawer-version {
@ -40,6 +53,10 @@
margin-left: -16px;
}
.mat-icon-button {
outline: none;
}
mat-sidenav-content {
height: unset;
}

View File

@ -31,6 +31,7 @@ export class AppComponent implements OnInit, OnDestroy {
uploadingFileSubscription: Subscription;
error: ErrorModel;
version: string;
toggle: boolean = false;
constructor(
private router: Router,
@ -86,6 +87,11 @@ export class AppComponent implements OnInit, OnDestroy {
this.window.nativeWindow.location.href = environment.taskanaLogoutUrl;
}
toggleSidenav() {
this.toggle = !this.toggle;
this.sidenavService.toggleSidenav();
}
ngAfterViewInit(): void {
this.sidenavService.setSidenav(this.sidenav);
}

View File

@ -59,6 +59,7 @@ import { MatListModule } from '@angular/material/list';
import { MatButtonModule } from '@angular/material/button';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSelectModule } from '@angular/material/select';
const MODULES = [
TabsModule.forRoot(),
@ -78,6 +79,7 @@ const MODULES = [
MatListModule,
MatButtonModule,
MatIconModule,
MatSelectModule,
NgxsModule.forRoot(STATES, { developmentMode: !environment.production }),
NgxsReduxDevtoolsPluginModule.forRoot({ disabled: environment.production, maxAge: 25 })
];

View File

@ -25,13 +25,11 @@
margin-left: 4px;
}
.filter__name-and-key-input{
.filter__name-and-key-input {
display: flex;
justify-content: space-between;
}
.dropdown-menu-users {
& > li {
margin-bottom: 5px;

View File

@ -6,6 +6,6 @@
</div>
<div class="navbar__logo">
<svg-icon class="navbar__logo-icon" src="./assets/icons/logo-copy.svg"></svg-icon>
<div class="navbar__title">{{ title }}</div>
<div class="navbar__title">{{title}}</div>
</div>
</mat-toolbar>
</mat-toolbar>

View File

@ -6,7 +6,7 @@
mat-paginator {
background-color: #fafafa;
width: 70%;
font-feature-settings: "tnum";
font-feature-settings: 'tnum';
font-variant-numeric: tabular-nums;
}
//Original
@ -22,6 +22,6 @@ mat-paginator {
.pagination__go-to-label {
margin: 0 4px;
font-size: 12px;
color: rgba(0,0,0,.54);
color: rgba(0, 0, 0, 0.54);
}
}

View File

@ -1,18 +1,19 @@
<mat-nav-list class="navlist">
<a mat-list-item class="navlist__item navlist__admin" [routerLink]=[administrationsUrl] [routerLinkActive]="['active']"
*ngIf="administrationAccess" (click)="toggleSidenav()">Administration</a>
<a mat-list-item class="navlist__item navlist__admin" [routerLink]=[administrationsUrl]
[routerLinkActive]="['active']" *ngIf="administrationAccess" (click)="toggleSidenav()">Administration</a>
<div class="navlist__admin-item">
<a mat-list-item class="navlist__item navlist__admin-workbaskets" [routerLink]=[workbasketsUrl]
[routerLinkActive]="['active']" *ngIf="administrationAccess" (click)="toggleSidenav()">Workbaskets</a>
<a mat-list-item class="navlist__item navlist__admin-classifications" [routerLink]=[classificationUrl]
[routerLinkActive]="['active']" *ngIf="administrationAccess" (click)="toggleSidenav()">Classifications</a>
<a mat-list-item class="navlist__item navlist__admin-access-items" [routerLink]=[accessUrl]
[routerLinkActive]="['active']" (click)="toggleSidenav()" *ngIf="administrationAccess">Access Items</a>
<a mat-list-item class="navlist__item navlist__admin-workbaskets" [routerLink]=[workbasketsUrl]
[routerLinkActive]="['active__sub']" *ngIf="administrationAccess" (click)="toggleSidenav()">Workbaskets</a>
<a mat-list-item class="navlist__item navlist__admin-classifications" [routerLink]=[classificationUrl]
[routerLinkActive]="['active__sub']" *ngIf="administrationAccess"
(click)="toggleSidenav()">Classifications</a>
<a mat-list-item class="navlist__item navlist__admin-access-items" [routerLink]=[accessUrl]
[routerLinkActive]="['active__sub']" (click)="toggleSidenav()" *ngIf="administrationAccess">Access Items</a>
</div>
<a mat-list-item class="navlist__item navlist__monitor" [routerLink]=[monitorUrl] [routerLinkActive]="['active']"
*ngIf="monitorAccess" (click)="toggleSidenav()">Monitor</a>
<a mat-list-item class="navlist__item navlist__workplace" [routerLink]=[workplaceUrl] [routerLinkActive]="['active']"
*ngIf="workplaceAccess" (click)="toggleSidenav()">Workplace</a>
<a mat-list-item class="navlist__item navlist__workplace" [routerLink]=[workplaceUrl]
[routerLinkActive]="['active']" *ngIf="workplaceAccess" (click)="toggleSidenav()">Workplace</a>
<a mat-list-item class="navlist__item navlist__history" [routerLink]=[historyUrl] [routerLinkActive]="['active']"
*ngIf="historyAccess" (click)="toggleSidenav()">History</a>
</mat-nav-list>
</mat-nav-list>

View File

@ -6,7 +6,6 @@
.navlist__item {
color: #ddd;
}
.active {
@ -14,6 +13,10 @@
border-left: 5px solid $aquamarine;
}
.active__sub {
color: $aquamarine;
}
::ng-deep .mat-drawer-container {
background-color: white;
}

View File

@ -4,4 +4,3 @@
color: $aquamarine;
font-weight: bold;
}

View File

@ -1,48 +1,16 @@
<div *ngIf="dataSource" class="custom-form-control" [ngStyle]="{'width': width ? width : auto }">
<ng-template class="wrapper-text" #customItemTemplate let-model="item" let-index="indexTemplate" let-query="query">
<div (mousedown)="typeaheadOnSelect({'item':model})">
<div>
<span [innerHTML]="join(model.accessId, query)">
</span>
</div>
<div>
<span [innerHTML]="join(model.name, query)">
</span>
</div>
</div>
</ng-template>
<div [ngClass]="{'hidden': !dataSource.selected || typing,
'disable': disable}" class="wrapper-text"
(click)="setTyping(true)">
<span>
<label>
{{dataSource.selected?.name}}
</label>
</span>
<div class="input-group form-control">
<div>{{dataSource.selected?.accessId}}</div>
</div>
</div>
<div [ngClass]="{'hidden': dataSource.selected && !typing}">
<span class="field-label-wrapper">
<label>
{{dataSource.selected?.name}}
</label>
</span>
<div class="input-group">
<input #inputTypeAhead class="form-control input-text" [ngClass]="{'invalid': !dataSource.length && isRequired}" (blur)="typeaheadOnSelect({'item':dataSource.selected})"
name="accessItem-{{index}}" [required]="isRequired ? 'required' : null" #accessItemName="ngModel" [(ngModel)]="value"
[typeahead]="dataSource" typeaheadOptionField="name" [typeaheadItemTemplate]="customItemTemplate"
(typeaheadOnSelect)="typeaheadOnSelect($event)" [typeaheadScrollable]="true"
[typeaheadOptionsInScrollableView]="typeaheadOptionsInScrollableView" [typeaheadMinLength]="typeaheadMinLength"
[typeaheadWaitMs]="typeaheadWaitMs" (typeaheadLoading)="changeTypeaheadLoading($event)" placeholder="{{displayError? placeHolderMessage: ''}}"
[@validation]="validationValue" data-cy="typeahead_input">
<button *ngIf="!typeaheadLoading" type="button" title="search" class="btn rounded blue search-button" >
<span class="material-icons md-20 blue">search</span>
</button>
<div *ngIf="typeaheadLoading" class="loading">
<taskana-shared-spinner [isRunning]="typeaheadLoading" positionClass="type-ahead-spinner"></taskana-shared-spinner>
</div>
</div>
</div>
</div>
<div *ngIf="dataSource" class="typeahead">
<form>
<mat-form-field class="typeahead__form" appearance="outline">
<mat-label>{{dataSource.selected?.name ? 'Owner: ' + dataSource.selected?.name : placeHolderMessage}}</mat-label>
<input #inputTypeAhead [required]="isRequired" class="typeahead__form-input align" matInput type="text"
[matAutocomplete]="auto" placeholder="{{placeHolderMessage}}" [(ngModel)]="value" name="accessId"
(ngModelChange)="initializeDataSource()" />
<mat-autocomplete #autoComplete autoActiveFirstOption (optionSelected)="typeaheadOnSelect($event)"
#auto="matAutocomplete">
<mat-option class="typeahead__form-options" *ngFor="let item of items" [value]="item.accessId">
<small>{{item.accessId}}&nbsp;&nbsp;{{item.name}}</small>
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>
</div>

View File

@ -1,99 +1,9 @@
@import '../../../../theme/colors';
.wrapper-text {
min-width: 150px;
height: 47px;
& label {
margin-bottom: 0px;
//font-style: italic;
//color: grey;
overflow: hidden;
}
& > div {
& > div {
margin-top: 2px;
max-width: 175px;
}
}
> div {
border-bottom: 1px solid $pallete-blue !important;
border-radius: 4px !important;
height: 32px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
margin-right: 35px;
padding-left: 12px;
}
}
.custom-form-control {
margin-top: -5px;
max-height: 57px;
& .input-group {
width: 100%;
}
}
.input-text {
margin-top: 4px;
margin-bottom: 16px;
background: white;
box-shadow: none;
border-radius: 4px !important;
//border-color: lightgray;
border-bottom: 1px solid $pallete-blue !important;
min-width: 150px;
height: 32px;
padding: 13px 0 13px 12px;
}
.field-label-wrapper {
position: relative;
height: 28px;
box-sizing: content-box;
overflow: hidden;
pointer-events: none;
label {
margin-bottom: 1px;
}
}
.form-control:focus {
//border-color: unset;
box-shadow: none;
}
.loading {
position: absolute;
top: 0px;
right: 0;
}
.btn.rounded {
position: absolute;
top: 0px;
right: 0px;
padding-top: 0;
}
.search-button {
z-index: 999;
margin-top: 8px;
}
::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
color: $invalid;
opacity: 1; /* Firefox */
}
:-ms-input-placeholder {
/* Internet Explorer 10-11 */
color: $invalid;
}
::-ms-input-placeholder {
/* Microsoft Edge */
color: $invalid;
}
.disable {
cursor: not-allowed;
@ -102,3 +12,15 @@
.invalid {
color: $invalid;
}
.typeahead__form {
width: 100% !important;
}
.typeahead__form-options {
white-space: pre;
}
.align {
left: 50%;
}

View File

@ -0,0 +1,76 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DebugElement } from '@angular/core';
import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service';
import { TypeAheadComponent } from './type-ahead.component';
import { BrowserModule, By } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { RouterModule } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs/internal/observable/of';
import { MatSelectModule } from '@angular/material/select';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { FormsModule } from '@angular/forms';
import { compileComponentFromMetadata, componentFactoryName } from '@angular/compiler';
import { AccessIdDefinition } from 'app/shared/models/access-id';
const AccessIdsServiceSpy = jest.fn().mockImplementation(
(): Partial<AccessIdsService> => ({
getAccessItems: jest.fn().mockReturnValue(of()),
searchForAccessId: jest.fn().mockReturnValue(of())
})
);
describe('TypeAheadComponent', () => {
let component: TypeAheadComponent;
let fixture: ComponentFixture<TypeAheadComponent>;
let debugElement: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TypeAheadComponent],
imports: [
BrowserModule,
RouterModule,
RouterTestingModule,
HttpClientTestingModule,
MatSelectModule,
MatAutocompleteModule,
MatFormFieldModule,
MatInputModule,
FormsModule,
BrowserAnimationsModule
],
providers: [{ provide: AccessIdsService, useClass: AccessIdsServiceSpy }]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TypeAheadComponent);
debugElement = fixture.debugElement;
component = fixture.debugElement.componentInstance;
fixture.detectChanges();
});
it('should create component', () => {
expect(component).toBeTruthy();
});
it('should change value via the input field', async(() => {
component.value = 'val_1';
component.initializeDataSource();
fixture.detectChanges();
fixture.whenStable().then(() => {
let input = debugElement.query(By.css('.typeahead__form-input'));
let el = input.nativeElement;
expect(el.value).toBe('val_1');
el.value = 'val_2';
el.dispatchEvent(new Event('input'));
expect(component.value).toBe('val_2');
component.initializeDataSource();
expect(component.items.length).toBeNull;
});
}));
});

View File

@ -9,10 +9,9 @@ import {
AfterViewInit
} from '@angular/core';
import { Observable } from 'rxjs';
import { TypeaheadMatch } from 'ngx-bootstrap/typeahead';
import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { highlight } from 'app/shared/animations/validation.animation';
import { mergeMap } from 'rxjs/operators';
import { AccessIdDefinition } from 'app/shared/models/access-id';
@ -34,6 +33,8 @@ export class TypeAheadComponent implements AfterViewInit, ControlValueAccessor {
dataSource: any;
typing = false;
items = [];
@Input()
placeHolderMessage;
@ -50,7 +51,7 @@ export class TypeAheadComponent implements AfterViewInit, ControlValueAccessor {
disable;
@Input()
isRequired = true;
isRequired;
@Output()
selectedItem = new EventEmitter<AccessIdDefinition>();
@ -83,7 +84,6 @@ export class TypeAheadComponent implements AfterViewInit, ControlValueAccessor {
set value(v: any) {
if (v !== this.innerValue) {
this.innerValue = v;
this.onChangeCallback(v);
}
}
@ -116,9 +116,7 @@ export class TypeAheadComponent implements AfterViewInit, ControlValueAccessor {
observer.next(this.value);
}).pipe(mergeMap((token: string) => this.getUsersAsObservable(token)));
this.accessIdsService.searchForAccessId(this.value).subscribe((items) => {
if (items.length > 0) {
this.dataSource.selected = items.find((item) => item.accessId.toLowerCase() === this.value.toLowerCase());
}
this.items = items;
});
}
@ -126,32 +124,15 @@ export class TypeAheadComponent implements AfterViewInit, ControlValueAccessor {
return this.accessIdsService.searchForAccessId(accessId);
}
typeaheadOnSelect(event: TypeaheadMatch): void {
if (event && event.item) {
this.value = event.item.accessId;
this.dataSource.selected = event.item;
typeaheadOnSelect(event): void {
if (event) {
if (this.items.length > 0) {
this.dataSource.selected = this.items.find((item) => item.accessId.toLowerCase() === this.value.toLowerCase());
}
this.selectedItem.emit(this.dataSource.selected);
}
this.setTyping(false);
}
setTyping(value) {
if (this.disable) {
return;
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
if (value) {
setTimeout(() => {
this.inputTypeAhead.nativeElement.focus();
}, 1);
}
this.typing = value;
}
changeTypeaheadLoading(e: boolean): void {
this.typeaheadLoading = e;
}
join(text: string, str: string) {
return text.toLocaleLowerCase().split(str).join(`<strong>${str}</strong>`);
}
}

View File

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { RouterModule } from '@angular/router';
@ -59,6 +59,7 @@ import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSelectModule } from '@angular/material/select';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
const MODULES = [
CommonModule,
@ -73,7 +74,8 @@ const MODULES = [
MatDialogModule,
MatButtonModule,
RouterModule,
TreeModule.forRoot()
TreeModule.forRoot(),
MatAutocompleteModule
];
const DECLARATIONS = [
@ -113,7 +115,8 @@ const DECLARATIONS = [
MatMenuModule,
MatTooltipModule,
MatPaginatorModule,
MatSelectModule
MatSelectModule,
ReactiveFormsModule
],
exports: DECLARATIONS,
providers: [