TSK-1476: Rework workbasket access items (#1360)

* TSK-1476: Rework Workbasket Access Items

* TSK-1476: Rework Workbasket Access Items

* TSK-1476: update
This commit is contained in:
Franzi321 2020-12-18 15:27:59 +01:00 committed by GitHub
parent 55a575474d
commit f42f79ef69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 336 additions and 171 deletions

View File

@ -60,6 +60,11 @@ describe('AccessItemsManagementComponent', () => {
let store: Store; let store: Store;
let actions$: Observable<any>; let actions$: Observable<any>;
@Component({ selector: 'taskana-shared-spinner', template: '' })
class TaskanaSharedSpinnerStub {
@Input() isRunning: boolean;
}
@Component({ selector: 'taskana-shared-sort', template: '' }) @Component({ selector: 'taskana-shared-sort', template: '' })
class TaskanaSharedSortStub { class TaskanaSharedSortStub {
@Input() sortingFields: Map<WorkbasketAccessItemQuerySortParameter, string>; @Input() sortingFields: Map<WorkbasketAccessItemQuerySortParameter, string>;
@ -91,7 +96,12 @@ describe('AccessItemsManagementComponent', () => {
MatListModule, MatListModule,
MatExpansionModule MatExpansionModule
], ],
declarations: [AccessItemsManagementComponent, TypeAheadComponent, TaskanaSharedSortStub], declarations: [
AccessItemsManagementComponent,
TypeAheadComponent,
TaskanaSharedSortStub,
TaskanaSharedSpinnerStub
],
providers: [ providers: [
{ provide: FormsValidatorService, useClass: formValidatorServiceSpy }, { provide: FormsValidatorService, useClass: formValidatorServiceSpy },
{ provide: NotificationService, useClass: notificationServiceSpy }, { provide: NotificationService, useClass: notificationServiceSpy },
@ -148,10 +158,8 @@ describe('AccessItemsManagementComponent', () => {
const groups = store.selectSnapshot((state) => state.accessItemsManagement); const groups = store.selectSnapshot((state) => state.accessItemsManagement);
expect(selectedAccessId).not.toBeNull(); expect(selectedAccessId).not.toBeNull();
expect(groups).not.toBeNull(); expect(groups).not.toBeNull();
expect(app.accessItemsForm).not.toBeNull();
app.onSelectAccessId(null); app.onSelectAccessId(null);
expect(app.accessItemsForm).toBeNull(); expect(groups).toMatchObject({});
}); });
it('should dispatch GetAccessItems action in searchForAccessItemsWorkbaskets', async((done) => { it('should dispatch GetAccessItems action in searchForAccessItemsWorkbaskets', async((done) => {

View File

@ -1,10 +1,24 @@
<div *ngIf="workbasket" id="wb-information"> <div *ngIf="workbasket" id="wb-information">
<!-- ACCESS ITEMS --> <!-- ACCESS ITEMS -->
<div class="workbasket-access-items"> <div class="workbasket-access-items"
<form [formGroup]="AccessItemsForm"> [ngStyle]="{'width': expanded ? 'calc(' + 100 + 'vw - 500px)' : 'calc(' + 100 + 'vw - 250px)'}">
<table formArrayName="accessItemsGroups" id="table-access-items" class="workbasket-access-items__table table-striped"> <div class=" workbasket-access-items__buttons">
<button mat-stroked-button matTooltip="Add new access" type="button"
class="workbasket-access-items__buttons-add-access" (click)="addAccessItem()" data-toggle="tooltip"
title="Add new access">
Add new access
<mat-icon color="green-blue" aria-label="add new access">add</mat-icon>
</button>
<button mat-stroked-button matTooltip="Delete selected access" type="button"
class="workbasket-access-items__buttons-delete-access" (click)="deleteAccessItems()"
data-toggle="tooltip" title="delete selected access">
Delete selected access
<mat-icon color="green-blue" aria-label="delete access">delete</mat-icon>
</button>
</div>
<form [formGroup]="AccessItemsForm" class="workbasket-access-items__form">
<table formArrayName="accessItemsGroups" id="table-access-items" class="workbasket-access-items__table">
<!-- TITLE ROW --> <!-- TITLE ROW -->
<thead> <thead>
<tr> <tr>
@ -22,27 +36,25 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let accessItem of accessItemsGroups.controls; let index = index;" [formGroupName]="index"> <tr *ngFor="let accessItem of accessItemsGroups?.controls; let index = index;"
[formGroupName]="index">
<!-- REMOVE BUTTON --> <!-- REMOVE BUTTON -->
<td> <td>
<button mat-button type="button" style="padding: 3px;" (click)="remove(index)" title="Remove"> <input class="workbasket-access-items__select-row" type="checkbox" aria-label="selectRow"
<span class="material-icons md-24 red">clear</span> aria-labelledby="selectRow" (change)="selectRow($event, index)">
</button>
</td> </td>
<!-- ACCESS ID --> <!-- ACCESS ID -->
<td *ngIf="(accessItemsCustomization$ | async)?.accessId.lookupField else accessIdInput" <td *ngIf="(accessItemsCustomization$ | async)?.accessId.lookupField else accessIdInput"
class="workbasket-access-items__typeahead" class="workbasket-access-items__typeahead" [ngClass]="{ 'has-warning': (accessItemsClone[index].accessId !== accessItem.value.accessId),
[ngClass]="{ 'has-warning': (accessItemsClone[index].accessId !== accessItem.value.accessId),
'has-error': !accessItem.value.accessId }"> 'has-error': !accessItem.value.accessId }">
<taskana-shared-type-ahead formControlName="accessId" placeHolderMessage="* Access id is required" <taskana-shared-type-ahead formControlName="accessId"
placeHolderMessage="* Access id is required"
[validationValue]="toggleValidationAccessIdMap.get(index)" [validationValue]="toggleValidationAccessIdMap.get(index)"
[displayError]="!isFieldValid('accessItem.value.accessId', index)" [displayError]="!isFieldValid('accessItem.value.accessId', index)"
(selectedItem)="accessItemSelected($event, index)" (selectedItem)="accessItemSelected($event, index)">
(inputField)="focusNewInput($event)">
</taskana-shared-type-ahead> </taskana-shared-type-ahead>
</td> </td>
@ -59,49 +71,63 @@
<!-- SELECT ALL --> <!-- SELECT ALL -->
<td> <td>
<input class="workbasket-access-items__permission-checkbox" type="checkbox" id="checkbox-{{index}}-00" (change)="checkAll(index, $event)" aria-label="checkAll" aria-labelledby="checkAll"> <input class="workbasket-access-items__check-all" type="checkbox" id="checkbox-{{index}}-00"
(change)="checkAll(index, $event)" aria-label="checkAll" aria-labelledby="checkAll">
</td> </td>
<!-- READ --> <!-- READ -->
<td [ngClass]="{ 'has-changes': (accessItemsClone[index].permRead !== accessItem.value.permRead)}"> <td
<input class="workbasket-access-items__permission-checkbox" type="checkbox" id="checkbox-{{index}}-0" formControlName="permRead" aria-label="permRead" aria-labelledby="permRead"> [ngClass]="{ 'has-changes': (accessItemsClone[index].permRead !== accessItem.value.permRead)}">
<input class="workbasket-access-items__permission-checkbox" type="checkbox"
id="checkbox-{{index}}-0" formControlName="permRead" aria-label="permRead"
aria-labelledby="permRead" (change)="checkboxClicked(this.index, $event)">
</td> </td>
<!-- OPEN --> <!-- OPEN -->
<td [ngClass]="{ 'has-changes': (accessItemsClone[index].permOpen !== accessItem.value.permOpen)}"> <td
<input class="workbasket-access-items__permission-checkbox" type="checkbox" id="checkbox-{{index}}-1" formControlName="permOpen" aria-label="permOpen" aria-labelledby="permOpen"> [ngClass]="{ 'has-changes': (accessItemsClone[index].permOpen !== accessItem.value.permOpen)}">
<input class="workbasket-access-items__permission-checkbox" type="checkbox"
id="checkbox-{{index}}-1" formControlName="permOpen" aria-label="permOpen"
aria-labelledby="permOpen" (change)="checkboxClicked(this.index, $event)">
</td> </td>
<!-- APPEND --> <!-- APPEND -->
<td [ngClass]="{ 'has-changes': (accessItemsClone[index].permAppend !== accessItem.value.permAppend)}"> <td
<input class="workbasket-access-items__permission-checkbox" type="checkbox" id="checkbox-{{index}}-2" formControlName="permAppend" aria-label="permAppend" aria-labelledby="permAppend"> [ngClass]="{ 'has-changes': (accessItemsClone[index].permAppend !== accessItem.value.permAppend)}">
<input class="workbasket-access-items__permission-checkbox" type="checkbox"
id="checkbox-{{index}}-2" formControlName="permAppend" aria-label="permAppend"
aria-labelledby="permAppend" (change)="checkboxClicked(this.index, $event)">
</td> </td>
<!-- TRANSFER --> <!-- TRANSFER -->
<td [ngClass]="{ 'has-changes': (accessItemsClone[index].permTransfer !== accessItem.value.permTransfer)}"> <td
<input class="workbasket-access-items__permission-checkbox" type="checkbox" id="checkbox-{{index}}-3" formControlName="permTransfer" aria-label="permTransfer" aria-labelledby="permTransfer"> [ngClass]="{ 'has-changes': (accessItemsClone[index].permTransfer !== accessItem.value.permTransfer)}">
<input class="workbasket-access-items__permission-checkbox" type="checkbox"
id="checkbox-{{index}}-3" formControlName="permTransfer" aria-label="permTransfer"
aria-labelledby="permTransfer" (change)="checkboxClicked(this.index, $event)">
</td> </td>
<!-- DISTRIBUTE --> <!-- DISTRIBUTE -->
<td [ngClass]="{ 'has-changes': (accessItemsClone[index].permDistribute !== accessItem.value.permDistribute)}"> <td
<input class="workbasket-access-items__permission-checkbox" type="checkbox" id="checkbox-{{index}}-4" formControlName="permDistribute" aria-label="permDistribute" aria-labelledby="permDistribute"> [ngClass]="{ 'has-changes': (accessItemsClone[index].permDistribute !== accessItem.value.permDistribute)}">
<input class="workbasket-access-items__permission-checkbox" type="checkbox"
id="checkbox-{{index}}-4" formControlName="permDistribute" aria-label="permDistribute"
aria-labelledby="permDistribute" (change)="checkboxClicked(this.index, $event)">
</td> </td>
<!-- CUSTOM FIELDS --> <!-- CUSTOM FIELDS -->
<ng-container *ngFor="let customField of customFields$ | async; let customIndex = index"> <ng-container *ngFor="let customField of customFields$ | async; let customIndex = index">
<td *ngIf="customField.visible" [ngClass]="{ 'has-changes': accessItemsClone[index][getAccessItemCustomProperty(customIndex + 1)] !== accessItem.value[getAccessItemCustomProperty(customIndex+1)] }"> <td *ngIf="customField.visible"
<input class="workbasket-access-items__permission-checkbox" type="checkbox" id="checkbox-{{index}}-{{customIndex + 5}}" formControlName="permCustom{{customIndex+1}}" aria-label="customField" aria-labelledby="customField"> [ngClass]="{ 'has-changes': accessItemsClone[index][getAccessItemCustomProperty(customIndex + 1)] !== accessItem.value[getAccessItemCustomProperty(customIndex+1)] }">
<input class="workbasket-access-items__permission-checkbox" type="checkbox"
id="checkbox-{{index}}-{{customIndex + 5}}"
formControlName="permCustom{{customIndex+1}}" aria-label="customField"
aria-labelledby="customField" (change)="checkboxClicked(this.index, $event)">
</td> </td>
</ng-container> </ng-container>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</form> </form>
<!-- ADD ACCESS ITEM -->
<button mat-stroked-button type="button" class="workbasket-access-items__add-access" (click)="addAccessItem()" data-toggle="tooltip" title="Add new access">
<span class="material-icons md-20 green-blue">add</span>
<span>Add new access</span>
</button>
</div> </div>
</div> </div>

View File

@ -1,60 +1,77 @@
@import '../../../../theme/colors'; @import '../../../../theme/colors';
@import '~@angular/material/theming';
.workbasket-access-items { .workbasket-access-items {
max-width: calc(100vw - 500px); height: calc(100vh - 213px);
} overflow-y: auto;
overflow-x: auto;
display: inline-block;
.workbasket-access-items__typeahead { &__typeahead {
text-align: left; text-align: left;
min-width: 180px; min-width: 180px;
width: calc(100% + 20px); width: calc(100% + 20px);
} line-height: 20px;
td > input[type='checkbox'] { padding-bottom: 0% !important;
margin-top: 0; margin-top: 5px;
display: block;
} }
.panel-body { &__table {
overflow-x: auto; margin-top: 20px;
padding-top: 0; margin-left: auto;
} margin-right: auto;
width: 98%;
text-align: center;
.required-header { & th {
width: 200px;
}
.required-header:after {
content: ' *';
color: red;
}
th {
padding: 0.25rem; padding: 0.25rem;
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 3; z-index: 3;
background: white; background: white;
} }
.workbasket-access-items__table {
margin-top: 20px;
}
.workbasket-access-items__table thead th {
vertical-align: bottom;
border-bottom: 2px solid #dee2e6;
}
.workbasket-access-items__table td, & td {
.table th { padding-left: 0.5rem;
padding: 0.5rem; vertical-align: initial;
vertical-align: middle;
border-top: 1px solid #dee2e6; border-top: 1px solid #dee2e6;
} }
.workbasket-access-items__table > thead > tr > th { & tr:first-child > td {
max-width: 150px; border-top: 2px solid #dddddd;
border-bottom: none;
} }
.workbasket-access-items__permission-checkbox { & td > input[type='checkbox'] {
margin-top: 0;
display: block;
}
& tr:nth-child(odd) > td {
background-color: rgba(0, 0, 0, 0.05);
}
& input[type='checkbox']:checked {
filter: hue-rotate(320deg) brightness(1);
}
}
&__buttons-add-access {
color: white;
border: none;
background: $aquamarine;
margin: 10px 3px 0px 6px;
outline: none;
}
&__buttons-delete-access {
color: red;
margin: 2px 0px 0px 3px;
outline: none;
}
&__permission-checkbox,
&__check-all,
&__select-row {
display: inline-block; display: inline-block;
height: 1rem; height: 1rem;
line-height: 0; line-height: 0;
@ -72,10 +89,27 @@ th {
height: 1.25rem; height: 1.25rem;
} }
} }
input[type='checkbox']:checked {
filter: hue-rotate(320deg) brightness(1);
} }
.workbasket-access-items__add-access { .required-header {
margin: 16px 0 16px 16px; width: 200px;
}
.required-header:after {
content: ' *';
color: red;
}
::ng-deep .workbasket-access-items__form .mat-form-field-wrapper {
padding-bottom: 0em;
height: 58px;
}
::ng-deep .workbasket-access-items__typeahead .typeahead__form .mat-form-field-infix {
padding: 0 0 0 0;
position: initial;
font-size: medium;
}
::ng-deep .mat-form-field-appearance-outline .mat-form-field-flex {
margin-top: 0.7em;
} }

View File

@ -1,6 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { WorkbasketAccessItemsComponent } from './workbasket-access-items.component'; import { WorkbasketAccessItemsComponent } from './workbasket-access-items.component';
import { DebugElement } from '@angular/core'; import { Component, DebugElement, Input } from '@angular/core';
import { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store'; import { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@ -32,11 +32,20 @@ import {
} from '../../../shared/store/workbasket-store/workbasket.actions'; } from '../../../shared/store/workbasket-store/workbasket.actions';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ACTION } from '../../../shared/models/action'; import { ACTION } from '../../../shared/models/action';
import { WorkbasketAccessItems } from '../../../shared/models/workbasket-access-items';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
@Component({ selector: 'taskana-shared-spinner', template: '' })
class SpinnerStub {
@Input() isRunning: boolean;
@Input() positionClass: string;
}
const savingWorkbasketServiceSpy = jest.fn().mockImplementation( const savingWorkbasketServiceSpy = jest.fn().mockImplementation(
(): Partial<SavingWorkbasketService> => ({ (): Partial<SavingWorkbasketService> => ({
@ -91,9 +100,11 @@ describe('WorkbasketAccessItemsComponent', () => {
MatInputModule, MatInputModule,
MatSelectModule, MatSelectModule,
MatAutocompleteModule, MatAutocompleteModule,
MatProgressBarModule MatProgressBarModule,
MatCheckboxModule,
MatIconModule
], ],
declarations: [WorkbasketAccessItemsComponent, TypeAheadComponent], declarations: [WorkbasketAccessItemsComponent, TypeAheadComponent, SpinnerStub],
providers: [ providers: [
{ provide: SavingWorkbasketService, useClass: savingWorkbasketServiceSpy }, { provide: SavingWorkbasketService, useClass: savingWorkbasketServiceSpy },
{ provide: RequestInProgressService, useClass: requestInProgressServiceSpy }, { provide: RequestInProgressService, useClass: requestInProgressServiceSpy },
@ -152,7 +163,9 @@ describe('WorkbasketAccessItemsComponent', () => {
it('should add accessItems when add access item button is clicked', () => { it('should add accessItems when add access item button is clicked', () => {
fixture.detectChanges(); fixture.detectChanges();
const addAccessItemButton = debugElement.nativeElement.querySelector('button.workbasket-access-items__add-access'); const addAccessItemButton = debugElement.nativeElement.querySelector(
'button.workbasket-access-items__buttons-add-access'
);
const clearSpy = jest.spyOn(component, 'addAccessItem'); const clearSpy = jest.spyOn(component, 'addAccessItem');
expect(addAccessItemButton.title).toMatch('Add new access'); expect(addAccessItemButton.title).toMatch('Add new access');
@ -172,7 +185,6 @@ describe('WorkbasketAccessItemsComponent', () => {
const checkAllSpy = jest.spyOn(component, 'checkAll'); const checkAllSpy = jest.spyOn(component, 'checkAll');
const checkAllButton = debugElement.nativeElement.querySelector('#checkbox-0-00'); const checkAllButton = debugElement.nativeElement.querySelector('#checkbox-0-00');
expect(checkAllButton).toBeTruthy(); expect(checkAllButton).toBeTruthy();
checkAllButton.click(); checkAllButton.click();
expect(checkAllSpy).toHaveBeenCalled(); expect(checkAllSpy).toHaveBeenCalled();
}); });

View File

@ -7,7 +7,8 @@ import {
OnInit, OnInit,
QueryList, QueryList,
SimpleChanges, SimpleChanges,
ViewChildren ViewChildren,
HostListener
} from '@angular/core'; } from '@angular/core';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { Actions, ofActionCompleted, Select, Store } from '@ngxs/store'; import { Actions, ofActionCompleted, Select, Store } from '@ngxs/store';
@ -51,12 +52,18 @@ export class WorkbasketAccessItemsComponent implements OnInit, OnChanges, OnDest
@Input() @Input()
action: ACTION; action: ACTION;
@Input()
expanded: boolean;
@ViewChildren('htmlInputElement') @ViewChildren('htmlInputElement')
inputs: QueryList<ElementRef>; inputs: QueryList<ElementRef>;
badgeMessage = ''; badgeMessage = '';
selectedRows: number[] = [];
workbasketClone: Workbasket;
customFields$: Observable<CustomField[]>; customFields$: Observable<CustomField[]>;
customFields: CustomField[];
accessItemsRepresentation: WorkbasketAccessItemsRepresentation; accessItemsRepresentation: WorkbasketAccessItemsRepresentation;
accessItemsClone: Array<WorkbasketAccessItems>; accessItemsClone: Array<WorkbasketAccessItems>;
@ -99,6 +106,9 @@ export class WorkbasketAccessItemsComponent implements OnInit, OnChanges, OnDest
ngOnInit() { ngOnInit() {
this.init(); this.init();
this.customFields$ = this.accessItemsCustomization$.pipe(getCustomFields(customFieldCount)); this.customFields$ = this.accessItemsCustomization$.pipe(getCustomFields(customFieldCount));
this.customFields$.subscribe((v) => {
this.customFields = v;
});
this.accessItemsRepresentation$.subscribe((accessItemsRepresentation) => { this.accessItemsRepresentation$.subscribe((accessItemsRepresentation) => {
if (typeof accessItemsRepresentation !== 'undefined') { if (typeof accessItemsRepresentation !== 'undefined') {
this.accessItemsRepresentation = accessItemsRepresentation; this.accessItemsRepresentation = accessItemsRepresentation;
@ -134,10 +144,42 @@ export class WorkbasketAccessItemsComponent implements OnInit, OnChanges, OnDest
}); });
} }
ngAfterViewChecked() {
let elementIndex = 0;
let isTrue = true;
if (this.accessItemsGroups.controls) {
this.accessItemsGroups.controls.forEach((element) => {
for (let i in element.value) {
if (i.startsWith('perm')) {
if (this.accessItemsGroups.controls[elementIndex].value[i] === false) {
isTrue = false;
break;
}
}
}
if (isTrue) {
const checkbox = document.getElementById(`checkbox-${elementIndex}-00`) as HTMLInputElement;
if (checkbox) {
checkbox.checked = true;
elementIndex++;
}
}
});
}
}
ngOnChanges(changes?: SimpleChanges) { ngOnChanges(changes?: SimpleChanges) {
console.log('change');
if (changes.action) { if (changes.action) {
this.setBadge(); this.setBadge();
} }
if (this.workbasketClone) {
if (this.workbasketClone.workbasketId != this.workbasket.workbasketId) {
this.init();
}
}
this.workbasketClone = this.workbasket;
//var offsetWidth = document.getElementById('container').offsetWidth;
} }
init() { init() {
@ -205,8 +247,8 @@ export class WorkbasketAccessItemsComponent implements OnInit, OnChanges, OnDest
workbasketAccessItems.permRead = true; workbasketAccessItems.permRead = true;
const newForm = this.formBuilder.group(workbasketAccessItems); const newForm = this.formBuilder.group(workbasketAccessItems);
newForm.controls.accessId.setValidators(Validators.required); newForm.controls.accessId.setValidators(Validators.required);
this.accessItemsGroups.push(newForm); this.accessItemsGroups.insert(0, newForm);
this.accessItemsClone.push(workbasketAccessItems); this.accessItemsClone.unshift(workbasketAccessItems);
this.added = true; this.added = true;
} }
@ -219,11 +261,6 @@ export class WorkbasketAccessItemsComponent implements OnInit, OnChanges, OnDest
this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT); this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
} }
remove(index: number) {
this.accessItemsGroups.removeAt(index);
this.accessItemsClone.splice(index, 1);
}
isFieldValid(field: string, index: number): boolean { isFieldValid(field: string, index: number): boolean {
return this.formsValidatorService.isFieldValid(this.accessItemsGroups[index], field); return this.formsValidatorService.isFieldValid(this.accessItemsGroups[index], field);
} }
@ -255,7 +292,7 @@ export class WorkbasketAccessItemsComponent implements OnInit, OnChanges, OnDest
} }
accessItemSelected(accessItem: AccessIdDefinition, row: number) { accessItemSelected(accessItem: AccessIdDefinition, row: number) {
this.accessItemsGroups.controls[row].get('accessId').setValue(accessItem.accessId); this.accessItemsGroups.controls[row].get('accessId').setValue(accessItem?.accessId);
this.accessItemsGroups.controls[row].get('accessName').setValue(accessItem.name); this.accessItemsGroups.controls[row].get('accessName').setValue(accessItem.name);
} }
@ -273,6 +310,33 @@ export class WorkbasketAccessItemsComponent implements OnInit, OnChanges, OnDest
}); });
} }
checkboxClicked(index: number, value: any) {
if (value.currentTarget.checked) {
let isTrue = true;
let numbers = [];
const notVisibleFields = this.customFields.filter((v) => v.visible === false);
notVisibleFields.forEach((element) => {
const num = element.field.toString().replace('Custom ', 'permCustom');
numbers.push(num);
});
for (let i in this.accessItemsGroups.controls[index].value) {
if (i.startsWith('perm')) {
if (this.accessItemsGroups.controls[index].value[i] === false && !numbers.includes(i)) {
isTrue = false;
break;
}
}
}
if (isTrue) {
const checkbox = document.getElementById(`checkbox-${index}-00`) as HTMLInputElement;
checkbox.checked = true;
}
} else {
const checkbox = document.getElementById(`checkbox-${index}-00`) as HTMLInputElement;
checkbox.checked = false;
}
}
setBadge() { setBadge() {
if (this.action === ACTION.COPY) { if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`; this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;
@ -296,12 +360,27 @@ export class WorkbasketAccessItemsComponent implements OnInit, OnChanges, OnDest
return `permCustom${customNumber}`; return `permCustom${customNumber}`;
} }
focusNewInput(input: ElementRef) { selectRow(value: any, index: number) {
if (this.added) { if (value.target.checked) {
input.nativeElement.focus(); this.selectedRows.push(index);
} else {
this.selectedRows = this.selectedRows.filter(function (number) {
return number != index;
});
} }
} }
deleteAccessItems() {
this.selectedRows.sort(function (a, b) {
return b - a;
});
this.selectedRows.forEach((element) => {
this.accessItemsGroups.removeAt(element);
this.accessItemsClone.splice(element, 1);
});
this.selectedRows = [];
}
ngOnDestroy() { ngOnDestroy() {
this.destroy$.next(); this.destroy$.next();
this.destroy$.complete(); this.destroy$.complete();

View File

@ -4,49 +4,59 @@
<span class="workbasket-details__title-badge" *ngIf="!workbasket.workbasketId"> {{ badgeMessage }}</span> <span class="workbasket-details__title-badge" *ngIf="!workbasket.workbasketId"> {{ badgeMessage }}</span>
</h4> </h4>
<span class="workbasket-details__spacer"></span> <span class="workbasket-details__spacer"></span>
<button mat-button class="workbasket-details__button workbasket-details__save-button" matTooltip="Save changes in current workbasket" (click)="onSubmit()"> <button mat-button class="workbasket-details__button workbasket-details__save-button"
matTooltip="Save changes in current workbasket" (click)="onSubmit()">
Save Save
<mat-icon class="md-20">save</mat-icon> <mat-icon class="md-20">save</mat-icon>
</button> </button>
<button mat-stroked-button class="workbasket-details__button" matTooltip="Revert changes to previous saved state" (click)="onRestore()"> <button mat-stroked-button class="workbasket-details__button" matTooltip="Revert changes to previous saved state"
(click)="onRestore()">
Undo Changes Undo Changes
<mat-icon class="button__green-blue md-20">restore</mat-icon> <mat-icon class="button__green-blue md-20">restore</mat-icon>
</button> </button>
<button mat-stroked-button [matMenuTriggerFor]="buttonMenu" matTooltip="More actions" class="action-toolbar__button" id="action-toolbar__more-buttons"> <button mat-stroked-button [matMenuTriggerFor]="buttonMenu" matTooltip="More actions" class="action-toolbar__button"
id="action-toolbar__more-buttons">
<mat-icon>more_vert</mat-icon> <mat-icon>more_vert</mat-icon>
</button> </button>
<mat-menu #buttonMenu="matMenu"> <mat-menu #buttonMenu="matMenu">
<button mat-menu-item class="workbasket-details__dropdown" matTooltip="Copy current values to create new workbasket" (click)="onCopy()"> <button mat-menu-item class="workbasket-details__dropdown"
matTooltip="Copy current values to create new workbasket" (click)="onCopy()">
<mat-icon class="button__green-blue">content_copy</mat-icon> <mat-icon class="button__green-blue">content_copy</mat-icon>
<span>Copy</span> <span>Copy</span>
</button> </button>
<button mat-menu-item class="workbasket-details__dropdown" matTooltip="Remove this workbasket as distribution target" (click)="onRemoveAsDistributionTarget()"> <button mat-menu-item class="workbasket-details__dropdown"
matTooltip="Remove this workbasket as distribution target" (click)="onRemoveAsDistributionTarget()">
<mat-icon class="button__red">remove_circle_outline</mat-icon> <mat-icon class="button__red">remove_circle_outline</mat-icon>
<span>Remove as distribution target</span> <span>Remove as distribution target</span>
</button> </button>
<button mat-menu-item class="workbasket-details__dropdown" matTooltip="Delete this workbasket" (click)="onRemoveWorkbasket()"> <button mat-menu-item class="workbasket-details__dropdown" matTooltip="Delete this workbasket"
(click)="onRemoveWorkbasket()">
<mat-icon class="button__red">delete</mat-icon> <mat-icon class="button__red">delete</mat-icon>
<span>Delete</span> <span>Delete</span>
</button> </button>
<button mat-menu-item class="workbasket-details__dropdown" style="border-bottom-style: none;" matTooltip="Close this workbasket and discard all changes" (click)="onClose()"> <button mat-menu-item class="workbasket-details__dropdown" style="border-bottom-style: none;"
matTooltip="Close this workbasket and discard all changes" (click)="onClose()">
<mat-icon>close</mat-icon> <mat-icon>close</mat-icon>
<span>Close</span> <span>Close</span>
</button> </button>
</mat-menu> </mat-menu>
</mat-toolbar> </mat-toolbar>
<mat-tab-group animationDuration="0ms" (selectedIndexChange)="selectComponent($event)" [selectedIndex]="selectedTab$ | async"> <mat-tab-group animationDuration="0ms" (selectedIndexChange)="selectComponent($event)"
[selectedIndex]="selectedTab$ | async">
<mat-tab label="Information"> <mat-tab label="Information">
<taskana-administration-workbasket-information [workbasket]="workbasket" [action]="action"></taskana-administration-workbasket-information> <taskana-administration-workbasket-information [workbasket]="workbasket" [action]="action">
</taskana-administration-workbasket-information>
</mat-tab> </mat-tab>
<mat-tab label="Access"> <mat-tab label="Access">
<taskana-administration-workbasket-access-items [workbasket]="workbasket" [action]="action"></taskana-administration-workbasket-access-items> <taskana-administration-workbasket-access-items [workbasket]="workbasket" [action]="action" [expanded]="expanded">
</taskana-administration-workbasket-access-items>
</mat-tab> </mat-tab>
<mat-tab label="Distribution Targets"> <mat-tab label="Distribution Targets">
<taskana-administration-workbasket-distribution-targets [workbasket]="workbasket" [action]="action"></taskana-administration-workbasket-distribution-targets> <taskana-administration-workbasket-distribution-targets [workbasket]="workbasket" [action]="action">
</taskana-administration-workbasket-distribution-targets>
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>
</div> </div>

View File

@ -39,6 +39,7 @@ class WorkbasketAccessItemsStub {
@Input() workbasket: Workbasket; @Input() workbasket: Workbasket;
@Input() action: ACTION; @Input() action: ACTION;
@Input() active: string; @Input() active: string;
@Input() expanded: boolean;
} }
@Component({ selector: 'taskana-administration-workbasket-distribution-targets', template: '' }) @Component({ selector: 'taskana-administration-workbasket-distribution-targets', template: '' })

View File

@ -1,4 +1,4 @@
import { Component, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'; import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { Workbasket } from 'app/shared/models/workbasket'; import { Workbasket } from 'app/shared/models/workbasket';
@ -45,6 +45,8 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy, OnChanges
destroy$ = new Subject<void>(); destroy$ = new Subject<void>();
@Input() expanded: boolean;
constructor( constructor(
private location: Location, private location: Location,
private route: ActivatedRoute, private route: ActivatedRoute,

View File

@ -12,7 +12,7 @@
</div> </div>
<div class="workbasket-overview__details" *ngIf="showDetail; else showEmptyPage"> <div class="workbasket-overview__details" *ngIf="showDetail; else showEmptyPage">
<taskana-administration-workbasket-details></taskana-administration-workbasket-details> <taskana-administration-workbasket-details [expanded]="expanded"></taskana-administration-workbasket-details>
</div> </div>
<ng-template #showEmptyPage> <ng-template #showEmptyPage>

View File

@ -53,8 +53,9 @@ class WorkbasketListStub {
} }
@Component({ selector: 'taskana-administration-workbasket-details', template: '' }) @Component({ selector: 'taskana-administration-workbasket-details', template: '' })
class WorkbasketDetailsStub {} class WorkbasketDetailsStub {
@Input() expanded: boolean;
}
@Component({ selector: 'svg-icon', template: '' }) @Component({ selector: 'svg-icon', template: '' })
class SvgIconStub {} class SvgIconStub {}

View File

@ -1,7 +1,10 @@
<div *ngIf="dataSource" class="typeahead"> <div *ngIf="dataSource" class="typeahead">
<form> <form>
<mat-form-field class="typeahead__form" appearance="outline"> <mat-form-field class="typeahead__form" appearance="outline">
<mat-label>{{dataSource.selected?.name ? 'Owner: ' + dataSource.selected?.name : placeHolderMessage}}</mat-label> <mat-label *ngIf="!isFirst">
{{dataSource.selected?.name ? 'Name: ' + dataSource.selected?.name : placeHolderMessage}}</mat-label>
<mat-label *ngIf="isFirst">
{{dataSource.selected?.name}}</mat-label>
<input #inputTypeAhead [required]="isRequired" class="typeahead__form-input align" matInput type="text" <input #inputTypeAhead [required]="isRequired" class="typeahead__form-input align" matInput type="text"
[matAutocomplete]="auto" placeholder="{{placeHolderMessage}}" [(ngModel)]="value" name="accessId" [matAutocomplete]="auto" placeholder="{{placeHolderMessage}}" [(ngModel)]="value" name="accessId"
(ngModelChange)="initializeDataSource()" /> (ngModelChange)="initializeDataSource()" />

View File

@ -1,17 +1,8 @@
import { import { Component, Input, ViewChild, forwardRef, Output, EventEmitter } from '@angular/core';
Component,
Input,
ViewChild,
forwardRef,
Output,
EventEmitter,
ElementRef,
AfterViewInit
} from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service'; import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service';
import { ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { highlight } from 'app/shared/animations/validation.animation'; import { highlight } from 'app/shared/animations/validation.animation';
import { mergeMap } from 'rxjs/operators'; import { mergeMap } from 'rxjs/operators';
import { AccessIdDefinition } from 'app/shared/models/access-id'; import { AccessIdDefinition } from 'app/shared/models/access-id';
@ -29,10 +20,10 @@ import { AccessIdDefinition } from 'app/shared/models/access-id';
} }
] ]
}) })
export class TypeAheadComponent implements AfterViewInit, ControlValueAccessor { export class TypeAheadComponent implements ControlValueAccessor {
dataSource: any; dataSource: any;
typing = false; typing = false;
isFirst = false;
items = []; items = [];
@Input() @Input()
@ -56,12 +47,7 @@ export class TypeAheadComponent implements AfterViewInit, ControlValueAccessor {
@Output() @Output()
selectedItem = new EventEmitter<AccessIdDefinition>(); selectedItem = new EventEmitter<AccessIdDefinition>();
@Output()
inputField = new EventEmitter<ElementRef>();
@ViewChild('inputTypeAhead') @ViewChild('inputTypeAhead')
private inputTypeAhead;
typeaheadLoading = false; typeaheadLoading = false;
typeaheadMinLength = 3; typeaheadMinLength = 3;
typeaheadWaitMs = 500; typeaheadWaitMs = 500;
@ -91,6 +77,9 @@ export class TypeAheadComponent implements AfterViewInit, ControlValueAccessor {
writeValue(value: any) { writeValue(value: any) {
if (value !== this.innerValue) { if (value !== this.innerValue) {
this.innerValue = value; this.innerValue = value;
if (this.value) {
this.isFirst = true;
}
this.initializeDataSource(); this.initializeDataSource();
} }
} }
@ -107,16 +96,16 @@ export class TypeAheadComponent implements AfterViewInit, ControlValueAccessor {
constructor(private accessIdsService: AccessIdsService) {} constructor(private accessIdsService: AccessIdsService) {}
ngAfterViewInit() {
this.inputField.emit(this.inputTypeAhead);
}
initializeDataSource() { initializeDataSource() {
this.dataSource = new Observable((observer: any) => { this.dataSource = new Observable((observer: any) => {
observer.next(this.value); observer.next(this.value);
}).pipe(mergeMap((token: string) => this.getUsersAsObservable(token))); }).pipe(mergeMap((token: string) => this.getUsersAsObservable(token)));
this.accessIdsService.searchForAccessId(this.value).subscribe((items) => { this.accessIdsService.searchForAccessId(this.value).subscribe((items) => {
this.items = items; this.items = items;
if (this.isFirst) {
this.dataSource.selected = this.items.find((item) => item.accessId.toLowerCase() === this.value.toLowerCase());
this.selectedItem.emit(this.dataSource.selected);
}
}); });
} }

View File

@ -19,7 +19,7 @@
"lookupField": true "lookupField": true
}, },
"custom3": { "custom3": {
"field": "", "field": "Custom 3",
"visible": false "visible": false
}, },
"custom9": { "custom9": {
@ -27,15 +27,15 @@
"visible": true "visible": true
}, },
"custom10": { "custom10": {
"field": "", "field": "Custom 10",
"visible": false "visible": false
}, },
"custom11": { "custom11": {
"field": "", "field": "Custom 11",
"visible": false "visible": false
}, },
"custom12": { "custom12": {
"field": "", "field": "Custom 12",
"visible": false "visible": false
} }
} }