diff --git a/web/src/app/administration/classification/details/classification-details.component.html b/web/src/app/administration/classification/details/classification-details.component.html index d30c543ea..8b421419c 100644 --- a/web/src/app/administration/classification/details/classification-details.component.html +++ b/web/src/app/administration/classification/details/classification-details.component.html @@ -1,14 +1,13 @@
-
+
+ +

{{classification.name}}  [{{classification.type}}] @@ -29,54 +29,62 @@
- - + +
- +
-
- - +
+ +
+ +
+
+ + + +
+
+
+ - -
- -
@@ -86,8 +94,8 @@
- +
@@ -96,50 +104,50 @@
- +
- +
- +
- +
- +
- +
- +
- +
- +
diff --git a/web/src/app/administration/classification/master/list/classification-list.component.html b/web/src/app/administration/classification/master/list/classification-list.component.html index 3e1dd4402..5e20e54e7 100644 --- a/web/src/app/administration/classification/master/list/classification-list.component.html +++ b/web/src/app/administration/classification/master/list/classification-list.component.html @@ -34,7 +34,7 @@
-
+
diff --git a/web/src/app/administration/classification/master/list/classification-list.component.scss b/web/src/app/administration/classification/master/list/classification-list.component.scss index 89cf9b4cd..3a2232434 100644 --- a/web/src/app/administration/classification/master/list/classification-list.component.scss +++ b/web/src/app/administration/classification/master/list/classification-list.component.scss @@ -10,6 +10,7 @@ .tab-align{ margin-bottom: 0px; border-bottom: 1px dotted #ddd; + padding: 8px 12px 8px 4px; &>div{ margin: 6px 0px; } diff --git a/web/src/app/administration/workbasket/details/information/workbasket-information.component.html b/web/src/app/administration/workbasket/details/information/workbasket-information.component.html index 3f8cee95a..6d1505c97 100644 --- a/web/src/app/administration/workbasket/details/information/workbasket-information.component.html +++ b/web/src/app/administration/workbasket/details/information/workbasket-information.component.html @@ -55,32 +55,36 @@
-
- - -
-
- - diff --git a/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts b/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts index 913e47680..37d4e9418 100644 --- a/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts +++ b/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts @@ -96,7 +96,7 @@ export class WorkbasketInformationComponent ]); } - ngOnInit(): void {} + ngOnInit(): void { } ngOnChanges(changes: SimpleChanges): void { this.workbasketClone = { ...this.workbasket }; @@ -138,7 +138,7 @@ export class WorkbasketInformationComponent this.removeConfirmationService.setRemoveConfirmation( this.onRemoveConfirmed.bind(this), `You are going to delete workbasket: ${ - this.workbasket.key + this.workbasket.key }. Can you confirm this action?` ); } @@ -162,7 +162,7 @@ export class WorkbasketInformationComponent new AlertModel( AlertType.SUCCESS, `DistributionTarget for workbasketID: ${ - this.workbasket.workbasketId + this.workbasket.workbasketId } was removed successfully` ) ); @@ -171,7 +171,7 @@ export class WorkbasketInformationComponent this.errorModalService.triggerError( new ErrorModel( `There was an error removing distribution target for ${ - this.workbasket.workbasketId + this.workbasket.workbasketId }.`, error ) @@ -281,7 +281,7 @@ export class WorkbasketInformationComponent if (response) { this.errorModalService.triggerError( new ErrorModel('There was an error marking workbasket for deletion', - 'It not possible to mark the workbasket for deletion, It has been deleted.') + 'It not possible to mark the workbasket for deletion, It has been deleted.') ); } else { this.alertService.triggerAlert( diff --git a/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.html b/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.html index 3745b27c3..2ea7de907 100644 --- a/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.html +++ b/web/src/app/administration/workbasket/master/list/workbasket-list-toolbar/workbasket-list-toolbar.component.html @@ -1,12 +1,12 @@
  • -
    +
    -
    +
    -
    -
    +
    - -
    +
    +
    +
    -
    +
    +
    +
    + +
    + +
    +
    + +
    +
    + +
    +
    - - -
    - -
    -
  • -
    -
    -
    -
    -
    - -
    - - - -
    - -
    - -
    +
    +
    -
  • -
    + +
    +
    +
    + +
    + +
    + +
    \ No newline at end of file diff --git a/web/src/app/shared/filter/filter.component.ts b/web/src/app/shared/filter/filter.component.ts index e32e5d10f..16ceddfa0 100644 --- a/web/src/app/shared/filter/filter.component.ts +++ b/web/src/app/shared/filter/filter.component.ts @@ -36,7 +36,7 @@ export class FilterComponent implements OnInit { } selectType(type: ICONTYPES) { - this.filter.filterParams.type = type; + this.filter.filterParams.type = (type === ICONTYPES.ALL) ? '' : type; } clear() { diff --git a/web/src/app/shared/master-and-detail/master-and-detail.component.html b/web/src/app/shared/master-and-detail/master-and-detail.component.html index f867d65f4..0a69fbbd6 100644 --- a/web/src/app/shared/master-and-detail/master-and-detail.component.html +++ b/web/src/app/shared/master-and-detail/master-and-detail.component.html @@ -17,7 +17,7 @@

    Select a Task

    - +
    diff --git a/web/src/app/shared/pipes/numberToArray/numberToArray.ts b/web/src/app/shared/pipes/numberToArray/numberToArray.ts new file mode 100644 index 000000000..c2a3ee42c --- /dev/null +++ b/web/src/app/shared/pipes/numberToArray/numberToArray.ts @@ -0,0 +1,9 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'numberToArray' }) + +export class NumberToArray implements PipeTransform { + transform(index: number): Array { + return Array.from(Array(index), (x, i) => i) + }; +} diff --git a/web/src/app/shared/services/forms/forms-validator.service.ts b/web/src/app/shared/services/forms/forms-validator.service.ts index e779145cf..91e90fb4b 100644 --- a/web/src/app/shared/services/forms/forms-validator.service.ts +++ b/web/src/app/shared/services/forms/forms-validator.service.ts @@ -7,99 +7,99 @@ import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.serv @Injectable() export class FormsValidatorService { - public formSubmitAttempt = false; - private workbasketOwner = 'workbasket.owner'; + public formSubmitAttempt = false; + private workbasketOwner = 'workbasket.owner'; - constructor( - private alertService: AlertService, - private accessIdsService: AccessIdsService) { - } - - public validateFormInformation(form: NgForm, toogleValidationMap: Map): Promise { - let validSync = true; - - const forFieldsPromise = new Promise((resolve, reject) => { - for (const control in form.form.controls) { - if (control.indexOf('owner') === -1 && form.form.controls[control].invalid) { - const validationState = toogleValidationMap.get(control); - validationState ? toogleValidationMap.set(control, !validationState) : toogleValidationMap.set(control, true); - validSync = false; - } - } - resolve(validSync); - }); - - const ownerPromise = new Promise((resolve, reject) => { - const ownerString = 'owner'; - if (form.form.controls[this.workbasketOwner]) { - this.accessIdsService.getAccessItemsInformation(form.form.controls[this.workbasketOwner].value).subscribe(items => { - const validationState = toogleValidationMap.get(this.workbasketOwner); - validationState ? toogleValidationMap.set(this.workbasketOwner, !validationState) : - toogleValidationMap.set(this.workbasketOwner, true); - items.find(item => item.accessId === form.form.controls[this.workbasketOwner].value) ? - resolve(new ResponseOwner({valid: true, field: ownerString})) : - resolve(new ResponseOwner({valid: false, field: ownerString})); - }); - } else { - const validationState = toogleValidationMap.get(form.form.controls[this.workbasketOwner]); - validationState ? toogleValidationMap.set(this.workbasketOwner, !validationState) : - toogleValidationMap.set(this.workbasketOwner, true); - resolve(new ResponseOwner({valid: true, field: ownerString})); - } - }); - - return Promise.all([forFieldsPromise, ownerPromise]).then(values => { - const responseOwner = new ResponseOwner(values[1]); - if (!(values[0] && responseOwner.valid)) { - if (!responseOwner.valid) { - this.alertService.triggerAlert(new AlertModel(AlertType.WARNING, 'The ' + responseOwner.field + ' introduced is not valid.')) - } else { - this.alertService.triggerAlert(new AlertModel(AlertType.WARNING, 'There are some empty fields which are required.')) - } - } - return values[0] && responseOwner.valid; - }); + constructor( + private alertService: AlertService, + private accessIdsService: AccessIdsService) { } - public validateFormAccess(form: FormArray, toogleValidationAccessIdMap: Map): Promise { + public async validateFormInformation(form: NgForm, toogleValidationMap: Map): Promise { + let validSync = true; + if (!form) { + return; + } + const forFieldsPromise = new Promise((resolve, reject) => { + for (const control in form.form.controls) { + if (control.indexOf('owner') === -1 && form.form.controls[control].invalid) { + const validationState = toogleValidationMap.get(control); + validationState ? toogleValidationMap.set(control, !validationState) : toogleValidationMap.set(control, true); + validSync = false; + } + } + resolve(validSync); + }); + + const ownerPromise = new Promise((resolve, reject) => { + const ownerString = 'owner'; + if (form.form.controls[this.workbasketOwner]) { + this.accessIdsService.getAccessItemsInformation(form.form.controls[this.workbasketOwner].value).subscribe(items => { + const validationState = toogleValidationMap.get(this.workbasketOwner); + validationState ? toogleValidationMap.set(this.workbasketOwner, !validationState) : + toogleValidationMap.set(this.workbasketOwner, true); + items.find(item => item.accessId === form.form.controls[this.workbasketOwner].value) ? + resolve(new ResponseOwner({ valid: true, field: ownerString })) : + resolve(new ResponseOwner({ valid: false, field: ownerString })); + }); + } else { + const validationState = toogleValidationMap.get(form.form.controls[this.workbasketOwner]); + validationState ? toogleValidationMap.set(this.workbasketOwner, !validationState) : + toogleValidationMap.set(this.workbasketOwner, true); + resolve(new ResponseOwner({ valid: true, field: ownerString })); + } + }); + + const values = await Promise.all([forFieldsPromise, ownerPromise]); + const responseOwner = new ResponseOwner(values[1]); + if (!(values[0] && responseOwner.valid)) { + if (!responseOwner.valid) { + this.alertService.triggerAlert(new AlertModel(AlertType.WARNING, 'The ' + responseOwner.field + ' introduced is not valid.')); + } else { + this.alertService.triggerAlert(new AlertModel(AlertType.WARNING, 'There are some empty fields which are required.')); + } + } + return values[0] && responseOwner.valid; + } + + public async validateFormAccess(form: FormArray, toogleValidationAccessIdMap: Map): Promise { const ownerPromise: Array> = new Array>(); for (let i = 0; i < form.length; i++) { ownerPromise.push(new Promise((resolve, reject) => { const validationState = toogleValidationAccessIdMap.get(i); - validationState ? toogleValidationAccessIdMap.set(i, !validationState) : + validationState ? toogleValidationAccessIdMap.set(i, !validationState) : toogleValidationAccessIdMap.set(i, true); this.accessIdsService.getAccessItemsInformation(form.controls[i].value['accessId']).subscribe(items => { items.length > 0 ? - resolve(new ResponseOwner({valid: true, field: 'access id'})) : - resolve(new ResponseOwner({valid: false, field: 'access id'})); + resolve(new ResponseOwner({ valid: true, field: 'access id' })) : + resolve(new ResponseOwner({ valid: false, field: 'access id' })); }) })); } let result = true; - return Promise.all(ownerPromise).then(values => { - let responseOwner; - for (let i = 0; i < values.length; i++) { - responseOwner = new ResponseOwner(values[i]); - result = result && responseOwner.valid; - } - if (!result) { - this.alertService.triggerAlert(new AlertModel(AlertType.WARNING, 'The ' + responseOwner.field + ' introduced is not valid.')) - } - return result; - }); + const values = await Promise.all(ownerPromise); + let responseOwner; + for (let i_1 = 0; i_1 < values.length; i_1++) { + responseOwner = new ResponseOwner(values[i_1]); + result = result && responseOwner.valid; + } + if (!result) { + this.alertService.triggerAlert(new AlertModel(AlertType.WARNING, 'The ' + responseOwner.field + ' introduced is not valid.')); + } + return result; } public isFieldValid(ngForm: NgForm, field: string) { - if (!ngForm || !ngForm.form.controls || !ngForm.form.controls[field]) { - return false; - } - if (!this.formSubmitAttempt) { - return true; - } - return (this.formSubmitAttempt && ngForm.form.controls[field].valid) || - (ngForm.form.controls[field].touched && ngForm.form.controls[field].valid); + if (!ngForm || !ngForm.form.controls || !ngForm.form.controls[field]) { + return false; + } + if (!this.formSubmitAttempt) { + return true; + } + return (this.formSubmitAttempt && ngForm.form.controls[field].valid) || + (ngForm.form.controls[field].touched && ngForm.form.controls[field].valid); } } diff --git a/web/src/app/shared/shared.module.ts b/web/src/app/shared/shared.module.ts index 57c7cca80..33c2f84d0 100644 --- a/web/src/app/shared/shared.module.ts +++ b/web/src/app/shared/shared.module.ts @@ -3,10 +3,10 @@ import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { AngularSvgIconModule } from 'angular-svg-icon'; -import { AlertModule } from 'ngx-bootstrap'; import { RouterModule } from '@angular/router'; import { TreeModule } from 'angular-tree-component'; -import { TypeaheadModule } from 'ngx-bootstrap'; +import { AlertModule, TypeaheadModule } from 'ngx-bootstrap'; +import { AccordionModule } from 'ngx-bootstrap/accordion'; /** * Components @@ -33,6 +33,7 @@ import { SelectWorkBasketPipe } from './pipes/selectedWorkbasket/seleted-workbas import { SpreadNumberPipe } from './pipes/spreadNumber/spread-number'; import { OrderBy } from './pipes/orderBy/orderBy'; import { MapToIterable } from './pipes/mapToIterable/mapToIterable'; +import { NumberToArray } from './pipes/numberToArray/numberToArray'; /** * Services @@ -45,6 +46,7 @@ const MODULES = [ FormsModule, AlertModule.forRoot(), TypeaheadModule.forRoot(), + AccordionModule.forRoot(), AngularSvgIconModule, HttpClientModule, RouterModule, @@ -62,6 +64,7 @@ const DECLARATIONS = [ RemoveNoneTypePipe, SelectWorkBasketPipe, SpreadNumberPipe, + NumberToArray, OrderBy, MapToIterable, SortComponent, @@ -69,7 +72,7 @@ const DECLARATIONS = [ IconTypeComponent, RemoveConfirmationComponent, FieldErrorDisplayComponent, - PaginationComponent + PaginationComponent, ]; @NgModule({ diff --git a/web/src/app/shared/spinner/spinner.component.html b/web/src/app/shared/spinner/spinner.component.html index ed7efd908..68fe61ff2 100644 --- a/web/src/app/shared/spinner/spinner.component.html +++ b/web/src/app/shared/spinner/spinner.component.html @@ -1,5 +1,5 @@
    -
    +
    @@ -13,23 +13,21 @@
    - +
    \ No newline at end of file diff --git a/web/src/app/shared/type-ahead/type-ahead.component.scss b/web/src/app/shared/type-ahead/type-ahead.component.scss index c46dad7e4..f4a51ad85 100644 --- a/web/src/app/shared/type-ahead/type-ahead.component.scss +++ b/web/src/app/shared/type-ahead/type-ahead.component.scss @@ -11,8 +11,8 @@ } &> div{ &> div { - border-bottom: 1px solid $light-grey; - margin-top:3px; + border-bottom: 1px solid $pallete-blue; + margin-top: 2px; padding-left: 12px; min-width: 175px; } @@ -26,7 +26,8 @@ } .custom-form-control { - height: 50px; + margin-top: -5px; + height: 40px; & .input-group{ width: 100%; } @@ -40,7 +41,7 @@ border-radius: 0px; min-width: 150px; height: 28px; - border-bottom: 1px solid $light-grey; + border-bottom: 1px solid $pallete-blue; &:focus{ border-bottom: 1px solid $aquamarine; } @@ -56,6 +57,9 @@ box-sizing: content-box; overflow: hidden; pointer-events: none; + label{ + margin-bottom: 1px; + } } .form-control:focus { @@ -93,5 +97,5 @@ } .invalid { - color: $invalid; + border-bottom: 1px solid $invalid; } diff --git a/web/src/app/shared/type-ahead/type-ahead.component.ts b/web/src/app/shared/type-ahead/type-ahead.component.ts index 31e86e019..e527c993b 100644 --- a/web/src/app/shared/type-ahead/type-ahead.component.ts +++ b/web/src/app/shared/type-ahead/type-ahead.component.ts @@ -41,6 +41,9 @@ export class TypeAheadComponent implements OnInit, ControlValueAccessor { @Input() disable; + @Input() + isRequired = true; + @Output() onSelect = new EventEmitter(); diff --git a/web/src/app/shared/util/query-parameters.ts b/web/src/app/shared/util/query-parameters.ts index f3eb8d7b4..e23505750 100644 --- a/web/src/app/shared/util/query-parameters.ts +++ b/web/src/app/shared/util/query-parameters.ts @@ -1,5 +1,3 @@ -import { Direction } from 'app/models/sorting'; - export class TaskanaQueryParameters { // Sorting static SORTBY = 'sort-by'; @@ -16,7 +14,7 @@ export class TaskanaQueryParameters { static WORKBASKET_KEY = 'workbasket-key'; static KEYLIKE = 'key-like'; static PRIORITY = 'priority'; - static STATE = 'state'; + static STATELIKE = 'state-like'; static WORKBASKET_ID = 'workbasket-id'; // Access @@ -54,7 +52,7 @@ export class TaskanaQueryParameters { workbasketKeyLike: string = undefined, basketId: string = undefined, priority: string = undefined, - state: string = undefined, + stateLike: string = undefined, ): string { let query = '?'; query += sortBy ? `${this.SORTBY}=${sortBy}&` : ''; @@ -66,7 +64,7 @@ export class TaskanaQueryParameters { query += ownerLike ? `${this.OWNERLIKE}=${ownerLike}&` : ''; query += basketId ? `${this.WORKBASKET_ID}=${basketId}&` : ''; query += priority ? `${this.PRIORITY}=${priority}&` : ''; - query += state ? `${this.STATE}=${state}&` : ''; + query += stateLike ? `${this.STATELIKE}=${stateLike}&` : ''; query += type ? `${this.TYPE}=${type}&` : ''; query += key ? `${this.KEY}=${key}&` : ''; query += keyLike ? `${this.KEYLIKE}=${keyLike}&` : ''; diff --git a/web/src/app/shared/util/taskana.date.ts b/web/src/app/shared/util/taskana.date.ts index c2bf76fca..832f1f057 100644 --- a/web/src/app/shared/util/taskana.date.ts +++ b/web/src/app/shared/util/taskana.date.ts @@ -1,10 +1,11 @@ -import {DatePipe} from '@angular/common'; +import { DatePipe } from '@angular/common'; export class TaskanaDate { public static getDate(): string { const dateFormat = 'yyyy-MM-ddTHH:mm:ss.sss'; const dateLocale = 'en-US'; const datePipe = new DatePipe(dateLocale); + return datePipe.transform(Date.now(), dateFormat) + 'Z'; } @@ -14,4 +15,16 @@ export class TaskanaDate { const datePipe = new DatePipe(dateLocale); return datePipe.transform(date, dateFormat); } + + public static getDateToDisplay(date: string): string { + const dateFormat = 'yyyy-MM-dd HH:mm:ss'; + const dateLocale = 'en-US'; + const datePipe = new DatePipe(dateLocale); + + return datePipe.transform(date, dateFormat); + } + + public static convertToBrowserTimeZone(date: Date): string { + return date.toLocaleString('en-US', { timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone }) + } } diff --git a/web/src/app/workplace/services/task.service.ts b/web/src/app/workplace/services/task.service.ts index 1cd47c938..dbe8fdd73 100644 --- a/web/src/app/workplace/services/task.service.ts +++ b/web/src/app/workplace/services/task.service.ts @@ -53,21 +53,21 @@ export class TaskService { * @param {string} basketId the id of workbasket * @param {string} sortBy name of field, that the tasks should be sorted by, default is priority * @param {string} sortDirection ASC or DESC - * @param {string} name the name of the task - * @param {string} owner the owner of the task + * @param {string} nameLike the name of the task + * @param {string} ownerLike the owner of the task * @param {string} priority the priority of the task - * @param {string} statethe state of the task + * @param {string} state the state of the task */ findTasksWithWorkbasket(basketId: string, sortBy = 'priority', sortDirection: string = Direction.ASC, - name: string, - owner: string, + nameLike: string, + ownerLike: string, priority: string, state: string, allPages: boolean = false): Observable { const url = `${this.url}${TaskanaQueryParameters.getQueryParameters( - sortBy, sortDirection, name, undefined, undefined, owner, undefined, undefined, undefined, undefined, undefined, + sortBy, sortDirection, undefined, nameLike, undefined, undefined, ownerLike, undefined, undefined, undefined, undefined, !allPages ? TaskanaQueryParameters.page : undefined, !allPages ? TaskanaQueryParameters.pageSize : undefined, undefined, undefined, undefined, undefined, basketId, priority, state)}`; return this.httpClient.get(url); diff --git a/web/src/app/workplace/task/task.component.html b/web/src/app/workplace/task/task.component.html index 89b501af3..b2079ba0c 100644 --- a/web/src/app/workplace/task/task.component.html +++ b/web/src/app/workplace/task/task.component.html @@ -1,18 +1,16 @@
    -
    -
    -
    - - +
    + +
    +
    + + +
    -
    -
    -
    - - +
    +
    + + +
    +
    + + +
    -
    - - -
    -
    - + +
    +
    + +
    +
    -
    - - - - +
    \ No newline at end of file diff --git a/web/src/app/workplace/task/task.component.scss b/web/src/app/workplace/task/task.component.scss index e007a6f09..24883b1e4 100644 --- a/web/src/app/workplace/task/task.component.scss +++ b/web/src/app/workplace/task/task.component.scss @@ -1,9 +1,4 @@ iframe { - position: absolute; - margin: 0; - padding: 0; - height: 600px; - width: 98%; - overflow: auto; + height: calc(100vh - 290px); } diff --git a/web/src/app/workplace/taskdetails/general-fields-extension/general-fields-extension.component.html b/web/src/app/workplace/taskdetails/general-fields-extension/general-fields-extension.component.html new file mode 100644 index 000000000..3cca9fb11 --- /dev/null +++ b/web/src/app/workplace/taskdetails/general-fields-extension/general-fields-extension.component.html @@ -0,0 +1,45 @@ + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    \ No newline at end of file diff --git a/web/src/app/workplace/taskdetails/general-fields-extension/general-fields-extension.component.scss b/web/src/app/workplace/taskdetails/general-fields-extension/general-fields-extension.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/web/src/app/workplace/taskdetails/general-fields-extension/general-fields-extension.component.spec.ts b/web/src/app/workplace/taskdetails/general-fields-extension/general-fields-extension.component.spec.ts new file mode 100644 index 000000000..50ecf6ed2 --- /dev/null +++ b/web/src/app/workplace/taskdetails/general-fields-extension/general-fields-extension.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GeneralFieldsExtensionComponent } from './general-fields-extension.component'; + +xdescribe('GeneralFieldsExtensionComponent', () => { + let component: GeneralFieldsExtensionComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ GeneralFieldsExtensionComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(GeneralFieldsExtensionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web/src/app/workplace/taskdetails/general-fields-extension/general-fields-extension.component.ts b/web/src/app/workplace/taskdetails/general-fields-extension/general-fields-extension.component.ts new file mode 100644 index 000000000..43994823e --- /dev/null +++ b/web/src/app/workplace/taskdetails/general-fields-extension/general-fields-extension.component.ts @@ -0,0 +1,19 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Task } from 'app/workplace/models/task'; + +@Component({ + selector: 'taskana-general-fields-extension', + templateUrl: './general-fields-extension.component.html', + styleUrls: ['./general-fields-extension.component.scss'] +}) +export class GeneralFieldsExtensionComponent implements OnInit { + + @Input() task: Task; + @Output() taskChange: EventEmitter = new EventEmitter(); + + constructor() { } + + ngOnInit() { + } + +} diff --git a/web/src/app/workplace/taskdetails/general/general-fields.component.html b/web/src/app/workplace/taskdetails/general/general-fields.component.html index 6943810fa..4d1d0148b 100644 --- a/web/src/app/workplace/taskdetails/general/general-fields.component.html +++ b/web/src/app/workplace/taskdetails/general/general-fields.component.html @@ -1,167 +1,117 @@ -
    -
    - - -
    -
    +
    +
    +
    +
    + + + + +
    +
    + + + + +
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - - -
    -
    -
    - - +
    +
    + + +
    +
    + + + + + +
    +
    + + +
    +
    + + +
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - + + \ No newline at end of file diff --git a/web/src/app/workplace/taskdetails/general/general-fields.component.ts b/web/src/app/workplace/taskdetails/general/general-fields.component.ts index 8bb8f2220..04e322146 100644 --- a/web/src/app/workplace/taskdetails/general/general-fields.component.ts +++ b/web/src/app/workplace/taskdetails/general/general-fields.component.ts @@ -1,25 +1,50 @@ -import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; -import {Task} from 'app/workplace/models/task'; -import {Classification} from '../../../models/classification'; -import {ClassificationsService} from '../../../services/classifications/classifications.service'; +import { Component, EventEmitter, Input, OnInit, Output, ViewChild, SimpleChanges, OnChanges } from '@angular/core'; +import { Task } from 'app/workplace/models/task'; +import { Classification } from '../../../models/classification'; +import { ClassificationsService } from '../../../services/classifications/classifications.service'; +import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; +import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service'; +import { NgForm } from '@angular/forms'; @Component({ selector: 'taskana-task-details-general-fields', templateUrl: './general-fields.component.html', styleUrls: ['./general-fields.component.scss'] }) -export class TaskdetailsGeneralFieldsComponent implements OnInit { +export class TaskdetailsGeneralFieldsComponent implements OnInit, OnChanges { + @Input() task: Task; - @Output() taskChange: EventEmitter = new EventEmitter(); + + @Input() + saveToggleTriggered: boolean; + @Output() formValid: EventEmitter = new EventEmitter(); + @Output() classificationsReceived: EventEmitter = new EventEmitter(); + @ViewChild('TaskForm') + taskForm: NgForm; + + toogleValidationMap = new Map(); requestInProgress = false; - selectedClassification: Classification = new Classification(); classifications: Classification[] = undefined; - constructor(private classificationService: ClassificationsService) { + ownerField = this.customFieldsService.getCustomField( + 'Owner', + 'tasks.information.owner' + ); + + constructor( + private classificationService: ClassificationsService, + private customFieldsService: CustomFieldsService, + private formsValidatorService: FormsValidatorService) { + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.saveToggleTriggered && changes.saveToggleTriggered.currentValue !== changes.saveToggleTriggered.previousValue) { + this.validate(); + } } ngOnInit() { @@ -27,18 +52,28 @@ export class TaskdetailsGeneralFieldsComponent implements OnInit { this.classificationService.getClassifications().subscribe(classificationList => { this.requestInProgress = false; this.classifications = classificationList; + if (classificationList.length > 0) { this.task.classificationSummaryResource = classificationList[0]; } this.classificationsReceived.emit(this.classifications); }); } - @Input() - set _task(task: Task) { - this.task = task; - this.selectedClassification = task.classificationSummaryResource; - } - selectClassification(classification: Classification) { - this.selectedClassification = classification; this.task.classificationSummaryResource = classification; } + + validate() { + this.formsValidatorService.formSubmitAttempt = true; + this.formsValidatorService + .validateFormInformation(this.taskForm, this.toogleValidationMap) + .then(value => { + if (value) { + this.formValid.emit(true); + } + }); + } + + isFieldValid(field: string): boolean { + return this.formsValidatorService.isFieldValid(this.taskForm, field); + } + } diff --git a/web/src/app/workplace/taskdetails/taskdetails.component.html b/web/src/app/workplace/taskdetails/taskdetails.component.html index 92000e81d..1749af8d4 100644 --- a/web/src/app/workplace/taskdetails/taskdetails.component.html +++ b/web/src/app/workplace/taskdetails/taskdetails.component.html @@ -1,50 +1,25 @@ - -
    +
    + +
    - - @@ -53,25 +28,53 @@ {{'Creating Task'}}
    -
    -
    -
    -
    - -
    - -
    - -
    -
    - -
    -
    - -
    -
    -
    + + + + + + + + + + + + + + + + + + + + + +
    -
    +
    \ No newline at end of file diff --git a/web/src/app/workplace/taskdetails/taskdetails.component.scss b/web/src/app/workplace/taskdetails/taskdetails.component.scss index 3648a5600..fda925b72 100644 --- a/web/src/app/workplace/taskdetails/taskdetails.component.scss +++ b/web/src/app/workplace/taskdetails/taskdetails.component.scss @@ -1,3 +1,29 @@ -.panel > .panel-body { - max-height: calc(100vh - 150px); +@import './src/assets/_colors'; + +::ng-deep .btn-block { + background-color: whitesmoke; + font-size: 16px; + font-weight: 700; } + +::ng-deep .panel-group .panel-heading + .panel-collapse > .panel-body, .panel-group .panel-heading + .panel-collapse > .list-group { + border: none; +} + +::ng-deep .collapse { + -webkit-transition: max-height 0.5s ease; + -moz-transition: max-height 0.5s ease; + -o-transition: max-height 0.5s ease; + -ms-transition: max-height 0.5s ease; + transition: max-height 0.5s ease; + + display: block!important; + overflow: hidden!important; + visibility: visible!important; + max-height: 0; + + &.in { + max-height: 2000px; + overflow: visible!important; + } + } diff --git a/web/src/app/workplace/taskdetails/taskdetails.component.ts b/web/src/app/workplace/taskdetails/taskdetails.component.ts index 8c9b3cb71..8106624ff 100644 --- a/web/src/app/workplace/taskdetails/taskdetails.component.ts +++ b/web/src/app/workplace/taskdetails/taskdetails.component.ts @@ -1,20 +1,21 @@ -import {Component, OnDestroy, OnInit} from '@angular/core'; -import {Subscription} from 'rxjs'; -import {ActivatedRoute, Router} from '@angular/router'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { ActivatedRoute, Router } from '@angular/router'; -import {TaskService} from 'app/workplace/services/task.service'; -import {RemoveConfirmationService} from 'app/services/remove-confirmation/remove-confirmation.service'; +import { TaskService } from 'app/workplace/services/task.service'; +import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service'; -import {Task} from 'app/workplace/models/task'; -import {ErrorModel} from 'app/models/modal-error'; -import {ErrorModalService} from 'app/services/errorModal/error-modal.service'; -import {RequestInProgressService} from 'app/services/requestInProgress/request-in-progress.service'; -import {AlertService} from 'app/services/alert/alert.service'; -import {AlertModel, AlertType} from 'app/models/alert'; -import {TaskanaDate} from 'app/shared/util/taskana.date'; -import {ObjectReference} from 'app/workplace/models/object-reference'; -import {Workbasket} from 'app/models/workbasket'; -import {WorkplaceService} from 'app/workplace/services/workplace.service'; +import { Task } from 'app/workplace/models/task'; +import { ErrorModel } from 'app/models/modal-error'; +import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; +import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; +import { AlertService } from 'app/services/alert/alert.service'; +import { AlertModel, AlertType } from 'app/models/alert'; +import { TaskanaDate } from 'app/shared/util/taskana.date'; +import { ObjectReference } from 'app/workplace/models/object-reference'; +import { Workbasket } from 'app/models/workbasket'; +import { WorkplaceService } from 'app/workplace/services/workplace.service'; +import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service'; @Component({ selector: 'taskana-task-details', @@ -28,18 +29,21 @@ export class TaskdetailsComponent implements OnInit, OnDestroy { tabSelected = 'general'; currentWorkbasket: Workbasket = undefined; currentId: string = undefined; + showDetail = false; private routeSubscription: Subscription; private workbasketSubscription: Subscription; + private masterAndDetailSubscription: Subscription; constructor(private route: ActivatedRoute, - private taskService: TaskService, - private workplaceService: WorkplaceService, - private router: Router, - private removeConfirmationService: RemoveConfirmationService, - private requestInProgressService: RequestInProgressService, - private alertService: AlertService, - private errorModalService: ErrorModalService) { + private taskService: TaskService, + private workplaceService: WorkplaceService, + private router: Router, + private removeConfirmationService: RemoveConfirmationService, + private requestInProgressService: RequestInProgressService, + private alertService: AlertService, + private errorModalService: ErrorModalService, + private masterAndDetailService: MasterAndDetailService) { } ngOnInit() { @@ -55,13 +59,16 @@ export class TaskdetailsComponent implements OnInit, OnDestroy { } this.getTask(); }); + this.masterAndDetailSubscription = this.masterAndDetailService.getShowDetail().subscribe(showDetail => { + this.showDetail = showDetail; + }); } resetTask(): void { - this.task = {...this.taskClone}; + this.task = { ...this.taskClone }; this.task.customAttributes = this.taskClone.customAttributes.slice(0); this.task.callbackInfo = this.taskClone.callbackInfo.slice(0); - this.task.primaryObjRef = {...this.taskClone.primaryObjRef}; + this.task.primaryObjRef = { ...this.taskClone.primaryObjRef }; this.alertService.triggerAlert(new AlertModel(AlertType.INFO, 'Reset edited fields')); } @@ -83,43 +90,13 @@ export class TaskdetailsComponent implements OnInit, OnDestroy { } } - onSave() { - this.requestInProgressService.setRequestInProgress(true); - this.currentId === 'new-task' ? this.createTask() : this.updateTask(); + onSubmit() { + this.onSave(); } - updateTask() { - this.taskService.updateTask(this.task).subscribe(task => { - this.requestInProgressService.setRequestInProgress(false); - this.task = task; - this.cloneTask(); - this.taskService.publishUpdatedTask(task); - this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Update successful!')) - }, err => { - this.alertService.triggerAlert(new AlertModel(AlertType.DANGER, 'Update not successful!')) - }); - } - - createTask() { - this.addDateToTask(); - this.taskService.createTask(this.task).subscribe(task => { - this.requestInProgressService.setRequestInProgress(false); - this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Task ${this.currentId} was created successfully`)); - this.task = task; - this.taskService.selectTask(this.task); - this.taskService.publishAddedTask(task); - this.router.navigate(['../' + task.taskId], {relativeTo: this.route}); - }); - } - - private addDateToTask() { - const date = TaskanaDate.getDate(); - this.task.created = date; - this.task.modified = date; - } openTask() { - this.router.navigate([{outlets: {detail: `task/${this.currentId}`}}], {relativeTo: this.route.parent}); + this.router.navigate([{ outlets: { detail: `task/${this.currentId}` } }], { relativeTo: this.route.parent }); } workOnTaskDisabled(): boolean { @@ -142,22 +119,69 @@ export class TaskdetailsComponent implements OnInit, OnDestroy { this.tabSelected = tab; } + backClicked(): void { this.task = undefined; this.taskService.selectTask(this.task); - this.router.navigate(['./'], {relativeTo: this.route.parent}); + this.router.navigate(['./'], { relativeTo: this.route.parent }); + } + + private onSave() { + this.currentId === 'new-task' ? this.createTask() : this.updateTask(); + } + + private updateTask() { + this.requestInProgressService.setRequestInProgress(true); + this.taskService.updateTask(this.task).subscribe(task => { + this.requestInProgressService.setRequestInProgress(false); + this.task = task; + this.cloneTask(); + this.taskService.publishUpdatedTask(task); + this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Updating was successful.')) + }, err => { + this.requestInProgressService.setRequestInProgress(false); + this.alertService.triggerAlert(new AlertModel(AlertType.DANGER, 'There was an error while updating.')) + }); + } + + private createTask() { + this.requestInProgressService.setRequestInProgress(true); + this.addDateToTask(); + this.taskService.createTask(this.task).subscribe(task => { + this.requestInProgressService.setRequestInProgress(false); + this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Task ${this.currentId} was created successfully.`)); + this.task = task; + this.taskService.selectTask(this.task); + this.taskService.publishAddedTask(task); + this.router.navigate(['../' + task.taskId], { relativeTo: this.route }); + }, err => { + this.requestInProgressService.setRequestInProgress(false); + this.alertService.triggerAlert(new AlertModel(AlertType.DANGER, 'There was an error while creating a new task.')) + }); + } + + private addDateToTask() { + const date = TaskanaDate.getDate(); + this.task.created = date; + this.task.modified = date; + } + + private cloneTask() { + this.taskClone = { ...this.task }; + this.taskClone.customAttributes = this.task.customAttributes.slice(0); + this.taskClone.callbackInfo = this.task.callbackInfo.slice(0); + this.taskClone.primaryObjRef = { ...this.task.primaryObjRef }; } ngOnDestroy(): void { if (this.routeSubscription) { this.routeSubscription.unsubscribe(); + if (this.workbasketSubscription) { + this.workbasketSubscription.unsubscribe(); + } + if (this.masterAndDetailSubscription) { + this.masterAndDetailSubscription.unsubscribe(); + } } } - - private cloneTask() { - this.taskClone = {...this.task}; - this.taskClone.customAttributes = this.task.customAttributes.slice(0); - this.taskClone.callbackInfo = this.task.callbackInfo.slice(0); - this.taskClone.primaryObjRef = {...this.task.primaryObjRef}; - } } diff --git a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.html b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.html index 3b82f2ae0..ce2be2f41 100644 --- a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.html +++ b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.html @@ -1,35 +1,23 @@
  • -
    -
    - +
    +
    - -
    - - + +
    -
    - +
    -
  • + \ No newline at end of file diff --git a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.scss b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.scss index d4c5e7b8b..69da104c4 100644 --- a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.scss +++ b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.scss @@ -4,6 +4,7 @@ } .tab-align{ + padding: 8px 12px 8px 0px; margin-bottom: 0px; &>div{ diff --git a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.ts b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.ts index 7c22c9590..a805db0d5 100644 --- a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.ts +++ b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.ts @@ -1,14 +1,14 @@ -import {Component, EventEmitter, OnInit, Output} from '@angular/core'; -import {Task} from 'app/workplace/models/task'; -import {Workbasket} from 'app/models/workbasket'; -import {TaskService} from 'app/workplace/services/task.service'; -import {WorkbasketService} from 'app/services/workbasket/workbasket.service'; -import {SortingModel} from 'app/models/sorting'; -import {FilterModel} from 'app/models/filter'; -import {TaskanaType} from 'app/models/taskana-type'; -import {expandDown} from 'app/shared/animations/expand.animation'; -import {ActivatedRoute, Router} from '@angular/router'; -import {WorkplaceService} from 'app/workplace/services/workplace.service'; +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; +import { Task } from 'app/workplace/models/task'; +import { Workbasket } from 'app/models/workbasket'; +import { TaskService } from 'app/workplace/services/task.service'; +import { WorkbasketService } from 'app/services/workbasket/workbasket.service'; +import { SortingModel } from 'app/models/sorting'; +import { FilterModel } from 'app/models/filter'; +import { TaskanaType } from 'app/models/taskana-type'; +import { expandDown } from 'app/shared/animations/expand.animation'; +import { ActivatedRoute, Router } from '@angular/router'; +import { WorkplaceService } from 'app/workplace/services/workplace.service'; @Component({ selector: 'taskana-tasklist-toolbar', @@ -23,7 +23,7 @@ export class TaskListToolbarComponent implements OnInit { sortingFields = new Map([['name', 'Name'], ['priority', 'Priority'], ['due', 'Due'], ['planned', 'Planned']]); - filterParams = {name: '', key: '', owner: '', priority: '', state: ''}; + filterParams = { name: '', key: '', owner: '', priority: '', state: '' }; tasks: Task[] = []; workbasketNames: string[] = []; @@ -36,10 +36,10 @@ export class TaskListToolbarComponent implements OnInit { filterType = TaskanaType.TASKS; constructor(private taskService: TaskService, - private workbasketService: WorkbasketService, - private workplaceService: WorkplaceService, - private router: Router, - private route: ActivatedRoute) { + private workbasketService: WorkbasketService, + private workplaceService: WorkplaceService, + private router: Router, + private route: ActivatedRoute) { } ngOnInit() { @@ -91,6 +91,6 @@ export class TaskListToolbarComponent implements OnInit { createTask() { this.taskService.selectTask(undefined); - this.router.navigate([{outlets: {detail: 'taskdetail/new-task'}}], {relativeTo: this.route}); + this.router.navigate([{ outlets: { detail: 'taskdetail/new-task' } }], { relativeTo: this.route }); } } diff --git a/web/src/app/workplace/tasklist/tasklist.component.html b/web/src/app/workplace/tasklist/tasklist.component.html index 5cb22176c..114fde563 100644 --- a/web/src/app/workplace/tasklist/tasklist.component.html +++ b/web/src/app/workplace/tasklist/tasklist.component.html @@ -1,20 +1,37 @@ -
    +