diff --git a/docs/process-services/task-standalone.component.md b/docs/process-services/task-standalone.component.md new file mode 100644 index 0000000000..a550b63b2e --- /dev/null +++ b/docs/process-services/task-standalone.component.md @@ -0,0 +1,38 @@ +--- +Added: v2.0.0 +Status: Active +--- +# Task Standalone component + +This component can be used when there is no form attached to a task. + +## Contents + +- [Basic Usage](#basic-usage) + + - [Properties](#properties) + - [Events](#events) + +## Basic Usage + +```html + + +``` +### Properties + +| Name | Type | Default value | Description | +| ---- | ---- | ------------- | ----------- | +| taskName | `string` | | Name of the task | +| isCompleted | `boolean` | `false` | If true then Task completed message is shown and `Complete` and `Cancel` buttons are hidden | +| hasCompletePermission | `boolean` | `true` | Toggle rendering of the `Complete` button. | +| hideCancelButton | `boolean` | `true` | Toggle rendering of the `Cancel` button. | + +### Events + +| Name | Type | Description | +| ---- | ---- | ----------- | +| cancel | `EventEmitter` | Emitted when the `Cancel` button is clicked. | +| complete | `EventEmitter` | Emitted when the form associated with the task is completed. | + diff --git a/lib/process-services/i18n/en.json b/lib/process-services/i18n/en.json index 15b76fe336..93b6c3e442 100644 --- a/lib/process-services/i18n/en.json +++ b/lib/process-services/i18n/en.json @@ -161,6 +161,11 @@ "EMPTY-LIST": { "HEADER": "No files are available" } + }, + "STANDALONE_TASK":{ + "NO_FORM_MESSAGE": "No forms attached", + "COMPLETE_TASK_MESSAGE": "Task {{taskName}} completed", + "COMPLETE_TASK_SUB_MESSAGE": "No forms to be added" } }, "ADF_PROCESS_LIST": { diff --git a/lib/process-services/styles/_index.scss b/lib/process-services/styles/_index.scss index 0367b78a89..79f5c206bb 100644 --- a/lib/process-services/styles/_index.scss +++ b/lib/process-services/styles/_index.scss @@ -8,6 +8,7 @@ @import '../task-list/components/start-task.component'; @import '../task-list/components/task-filters.component'; @import '../task-list/components/task-header.component'; +@import '../task-list/components/task-standalone.component'; @import '../app-list/apps-list.component'; @mixin adf-process-services-theme($theme) { @@ -22,4 +23,5 @@ @include adf-process-attachment-list-theme($theme); @include adf-task-attachment-list-theme($theme); @include adf-apps-theme($theme); + @include adf-task-standalone-component-theme($theme); } diff --git a/lib/process-services/task-list/components/task-details.component.html b/lib/process-services/task-list/components/task-details.component.html index f7e393dd7a..bd832d61c3 100644 --- a/lib/process-services/task-list/components/task-details.component.html +++ b/lib/process-services/task-list/components/task-details.component.html @@ -38,16 +38,18 @@ (formLoaded)='onFormLoaded($event)' (error)='onFormError($event)' (executeOutcome)='onFormExecuteOutcome($event)'> -

Please select a Task

+ +
{{ 'ADF_TASK_LIST.DETAILS.MESSAGES.CLAIM' | translate }}
-
diff --git a/lib/process-services/task-list/components/task-details.component.spec.ts b/lib/process-services/task-list/components/task-details.component.spec.ts index 6009ab783f..63dcd2acfd 100644 --- a/lib/process-services/task-list/components/task-details.component.spec.ts +++ b/lib/process-services/task-list/components/task-details.component.spec.ts @@ -52,7 +52,6 @@ describe('TaskDetailsComponent', () => { let completeTaskSpy: jasmine.Spy; let logService: LogService; let commentProcessService: CommentProcessService; - let authService: AuthenticationService; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -85,7 +84,6 @@ describe('TaskDetailsComponent', () => { service = fixture.debugElement.injector.get(TaskListService); formService = fixture.debugElement.injector.get(FormService); commentProcessService = TestBed.get(CommentProcessService); - authService = TestBed.get(AuthenticationService); getTaskDetailsSpy = spyOn(service, 'getTaskDetails').and.returnValue(Observable.of(taskDetailsMock)); spyOn(formService, 'getTaskForm').and.returnValue(Observable.of(taskFormMock)); @@ -149,27 +147,21 @@ describe('TaskDetailsComponent', () => { }); })); - it('should display complete button when task without form is assigned to current user', async(() => { - spyOn(authService, 'getBpmUsername').and.returnValue(taskDetailsMock.assignee.email); - taskDetailsMock.formKey = undefined; + it('should display task standalone component when the task does not have an associated form', async(() => { component.taskId = '123'; fixture.detectChanges(); fixture.whenStable().then(() => { - const completeBtn = fixture.nativeElement.querySelector('button'); - expect(completeBtn).toBeDefined(); - expect(completeBtn.disabled).toBeFalsy(); - expect(completeBtn.innerText).toBe('ADF_TASK_LIST.DETAILS.BUTTON.COMPLETE'); + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('adf-task-standalone'))).not.toBeNull(); }); })); - it('should not display complete button when task without form is not assigned to current user', async(() => { - spyOn(authService, 'getBpmUsername').and.returnValue(''); + it('should not display task standalone component when the task have an associated form', async(() => { component.taskId = '123'; - taskDetailsMock.formKey = undefined; fixture.detectChanges(); fixture.whenStable().then(() => { - const completeBtn = fixture.nativeElement.querySelector('.activiti-task-details__action-button'); - expect(completeBtn).toBeNull(); + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('adf-task-standalone'))).not.toBeNull(); }); })); diff --git a/lib/process-services/task-list/components/task-standalone.component.html b/lib/process-services/task-list/components/task-standalone.component.html new file mode 100644 index 0000000000..e7ff34cf68 --- /dev/null +++ b/lib/process-services/task-list/components/task-standalone.component.html @@ -0,0 +1,24 @@ + + +
+
+
+ {{'ADF_TASK_LIST.STANDALONE_TASK.NO_FORM_MESSAGE' | translate}} +
+ +
+

{{'ADF_TASK_LIST.STANDALONE_TASK.COMPLETE_TASK_MESSAGE' | translate : {taskName : taskName} }}

+
+
+ {{'ADF_TASK_LIST.STANDALONE_TASK.COMPLETE_TASK_SUB_MESSAGE' | translate}} +
+
+
+
+
+ + + + + +
\ No newline at end of file diff --git a/lib/process-services/task-list/components/task-standalone.component.scss b/lib/process-services/task-list/components/task-standalone.component.scss new file mode 100644 index 0000000000..bfee7edc17 --- /dev/null +++ b/lib/process-services/task-list/components/task-standalone.component.scss @@ -0,0 +1,53 @@ +@mixin adf-task-standalone-component-theme($theme) { + + $config: mat-typography-config(); + $background: map-get($theme, background); + + .adf { + &-message-card { + width: 60%; + box-sizing: border-box; + margin: 16px auto; + .mat-card-actions { + border-top: solid 1px mat-color($background, status-bar); + } + } + &-no-form-message-container { + height:256px; + width: 100%; + display: table; + } + &-no-form-message-list { + display: table-cell; + vertical-align: middle; + text-align: center !important; + } + &-no-form-message { + padding-bottom: 10px; + font-size: mat-font-size($config, display-1); + line-height: 36px; + letter-spacing: -1.3px; + opacity: .54; + margin: auto; + width: fit-content !important; + } + &-no-form-submessage { + font-size: mat-font-size($config, subheading-2); + opacity: .54; + margin: auto; + width: fit-content !important; + } + &-no-form-mat-card-actions { + text-align: right; + & .mat-button { + text-transform: uppercase; + border-radius: 5px; + } + & .mat-button-wrapper { + opacity: 0.54; + font-size: mat-font-size($config, button); + font-weight: bold; + } + } + } +} \ No newline at end of file diff --git a/lib/process-services/task-list/components/task-standalone.component.spec.ts b/lib/process-services/task-list/components/task-standalone.component.spec.ts new file mode 100644 index 0000000000..3cd7ce8f0d --- /dev/null +++ b/lib/process-services/task-list/components/task-standalone.component.spec.ts @@ -0,0 +1,139 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TaskStandaloneComponent } from './task-standalone.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { + MatButtonModule, + MatCardModule +} from '@angular/material'; + +describe('TaskStandaloneComponent', () => { + let component: TaskStandaloneComponent; + let fixture: ComponentFixture; + let element: HTMLElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + + declarations: [ + TaskStandaloneComponent + ], + imports: [ + MatButtonModule, + MatCardModule + ] + }).compileComponents().then(() => { + fixture = TestBed.createComponent(TaskStandaloneComponent); + component = fixture.componentInstance; + element = fixture.nativeElement; + fixture.detectChanges(); + }); + })); + + it('should show Completed message if isCompleted is true', async(() => { + component.isCompleted = true; + fixture.detectChanges(); + const completedFormElement = fixture.debugElement.nativeElement.querySelector('#adf-completed-form-message'); + const completedFormSubElement = fixture.debugElement.nativeElement.querySelector('.adf-no-form-submessage'); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(element.querySelector('#adf-no-form-message')).toBeNull(); + expect(completedFormElement).toBeDefined(); + expect(completedFormElement.innerText.trim()).toBe('ADF_TASK_LIST.STANDALONE_TASK.COMPLETE_TASK_MESSAGE'); + expect(completedFormSubElement).toBeDefined(); + expect(completedFormSubElement.innerText).toBe('ADF_TASK_LIST.STANDALONE_TASK.COMPLETE_TASK_SUB_MESSAGE'); + expect(element.querySelector('.adf-no-form-mat-card-actions')).toBeDefined(); + }); + })); + + it('should show No form message if isCompleted is false', async(() => { + component.isCompleted = false; + fixture.detectChanges(); + const noformElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-message'); + fixture.whenStable().then(() => { + expect(noformElement).toBeDefined(); + expect(noformElement.innerText).toBe('ADF_TASK_LIST.STANDALONE_TASK.NO_FORM_MESSAGE'); + expect(element.querySelector('#adf-completed-form-message')).toBeNull(); + expect(element.querySelector('.adf-no-form-submessage')).toBeNull(); + }); + })); + + it('should hide Cancel button by default', async(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(element.querySelector('#adf-no-form-cancel-button')).toBeNull(); + }); + })); + + it('should emit cancel event if clicked on Cancel Button ', async(() => { + component.hideCancelButton = false; + component.isCompleted = false; + fixture.detectChanges(); + fixture.whenStable().then(() => { + const emitSpy = spyOn(component.cancel, 'emit'); + const el = fixture.nativeElement.querySelector('#adf-no-form-cancel-button'); + el.click(); + expect(emitSpy).toHaveBeenCalled(); + }); + })); + + it('should hide Cancel button if hideCancelButton is true', async(() => { + component.hideCancelButton = true; + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(element.querySelector('#adf-no-form-cancel-button')).toBeNull(); + }); + })); + + it('should hide Cancel button if isCompleted is true', async(() => { + component.isCompleted = true; + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(element.querySelector('#adf-no-form-cancel-button')).toBeNull(); + }); + })); + + it('should emit complete event if clicked on Complete Button', async(() => { + component.hasCompletePermission = true; + component.isCompleted = false; + fixture.detectChanges(); + fixture.whenStable().then(() => { + const emitSpy = spyOn(component.complete, 'emit'); + expect(element.querySelector('#adf-no-form-complete-button')).toBeDefined(); + const el = fixture.nativeElement.querySelector('#adf-no-form-complete-button'); + el.click(); + expect(emitSpy).toHaveBeenCalled(); + }); + })); + + it('should hide Complete button if isCompleted is true', async(() => { + component.isCompleted = true; + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(element.querySelector('#adf-no-form-complete-button')).toBeNull(); + }); + })); + + it('should hide Complete button if hasCompletePermission is false', async(() => { + component.hasCompletePermission = false; + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(element.querySelector('#adf-no-form-complete-button')).toBeNull(); + }); + })); +}); diff --git a/lib/process-services/task-list/components/task-standalone.component.ts b/lib/process-services/task-list/components/task-standalone.component.ts new file mode 100644 index 0000000000..2c5500c244 --- /dev/null +++ b/lib/process-services/task-list/components/task-standalone.component.ts @@ -0,0 +1,64 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'adf-task-standalone', + templateUrl: './task-standalone.component.html', + styleUrls: ['./task-standalone.component.scss'], + encapsulation: ViewEncapsulation.None +}) + +export class TaskStandaloneComponent { + + @Input() + taskName: string; + + @Input() + isCompleted: boolean = false; + + @Input() + hasCompletePermission: boolean = true; + + @Input() + hideCancelButton: boolean = true; + + @Output() + cancel: EventEmitter = new EventEmitter(); + + @Output() + complete: EventEmitter = new EventEmitter(); + + constructor() { } + + onCancelButtonClick(): void { + this.cancel.emit(); + } + + onCompleteButtonClick(): void { + this.complete.emit(); + } + + hasCompleteButton(): boolean { + return this.hasCompletePermission && !this.isCompleted; + } + + hasCancelButton(): boolean { + return !this.hideCancelButton && !this.isCompleted; + } +} diff --git a/lib/process-services/task-list/public-api.ts b/lib/process-services/task-list/public-api.ts index b25ea0ad48..031f1e8d2f 100644 --- a/lib/process-services/task-list/public-api.ts +++ b/lib/process-services/task-list/public-api.ts @@ -23,6 +23,7 @@ export * from './components/task-filters.component'; export * from './components/task-details.component'; export * from './components/task-audit.directive'; export * from './components/start-task.component'; +export * from './components/task-standalone.component'; export * from './services/tasklist.service'; export * from './services/process-upload.service'; diff --git a/lib/process-services/task-list/task-list.module.ts b/lib/process-services/task-list/task-list.module.ts index ab8010e789..b76e1f79d8 100644 --- a/lib/process-services/task-list/task-list.module.ts +++ b/lib/process-services/task-list/task-list.module.ts @@ -41,6 +41,7 @@ import { TaskDetailsComponent } from './components/task-details.component'; import { TaskFiltersComponent } from './components/task-filters.component'; import { TaskHeaderComponent } from './components/task-header.component'; import { TaskListComponent } from './components/task-list.component'; +import { TaskStandaloneComponent } from './components/task-standalone.component'; @NgModule({ imports: [ @@ -68,7 +69,8 @@ import { TaskListComponent } from './components/task-list.component'; TaskAuditDirective, ChecklistComponent, TaskHeaderComponent, - StartTaskComponent + StartTaskComponent, + TaskStandaloneComponent ], providers: [ TaskListService, @@ -85,7 +87,8 @@ import { TaskListComponent } from './components/task-list.component'; TaskAuditDirective, ChecklistComponent, TaskHeaderComponent, - StartTaskComponent + StartTaskComponent, + TaskStandaloneComponent ] }) export class TaskListModule {}