mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-26 17:24:56 +00:00
[MNT-24496] ADW Integration with APS Improvements - Re-assign Tasks (#10350)
* [MNT-24496] ADW Integration with APS Improvements - Re-assign Tasks * [MNT-24496] code improvements * [MNT-24496] remove duplications * [MNT-24496] add unit test * [MNT-24496] cr fixes * [MNT-24496] empty commit [ci:force] * [MNT-24496] fix unit test * [MNT-24496] empty commit [ci:force] * [MNT-24496] cr fix * [MNT-24496] remove redundant import
This commit is contained in:
parent
558ff71878
commit
258f01803c
@ -2,7 +2,7 @@
|
|||||||
Title: Card View component
|
Title: Card View component
|
||||||
Added: v2.0.0
|
Added: v2.0.0
|
||||||
Status: Active
|
Status: Active
|
||||||
Last reviewed: 2018-05-09
|
Last reviewed: 2024-10-29
|
||||||
---
|
---
|
||||||
|
|
||||||
# [Card View component](../../../lib/core/src/lib/card-view/components/card-view/card-view.component.ts "Defined in card-view.component.ts")
|
# [Card View component](../../../lib/core/src/lib/card-view/components/card-view/card-view.component.ts "Defined in card-view.component.ts")
|
||||||
@ -353,6 +353,7 @@ const selectItemProperty = new CardViewSelectItemModel(options);
|
|||||||
| key\* | string | | Identifying key (important when editing the item) |
|
| key\* | string | | Identifying key (important when editing the item) |
|
||||||
| editable | boolean | false | Toggles whether the item is editable |
|
| editable | boolean | false | Toggles whether the item is editable |
|
||||||
| value | string | | The original data value for the item |
|
| value | string | | The original data value for the item |
|
||||||
|
| autocompleteBased | boolean | false | Indicates whether the select item should use autocomplete functionality. If set to true, the select item will provide an autocomplete input field. |
|
||||||
| options$\* | [`Observable`](http://reactivex.io/documentation/observable.html)<[`CardViewSelectItemOption`](../../../lib/core/src/lib/card-view/interfaces/card-view-selectitem-properties.interface.ts)\[]> | | The original data value for the item |
|
| options$\* | [`Observable`](http://reactivex.io/documentation/observable.html)<[`CardViewSelectItemOption`](../../../lib/core/src/lib/card-view/interfaces/card-view-selectitem-properties.interface.ts)\[]> | | The original data value for the item |
|
||||||
|
|
||||||
#### Card Array Item
|
#### Card Array Item
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
Title: Card View Update service
|
Title: Card View Update service
|
||||||
Added: v2.0.0
|
Added: v2.0.0
|
||||||
Status: Active
|
Status: Active
|
||||||
Last reviewed: 2022-11-25
|
Last reviewed: 2024-10-29
|
||||||
---
|
---
|
||||||
|
|
||||||
# [Card View Update service](../../../lib/core/src/lib/card-view/services/card-view-update.service.ts "Defined in card-view-update.service.ts")
|
# [Card View Update service](../../../lib/core/src/lib/card-view/services/card-view-update.service.ts "Defined in card-view-update.service.ts")
|
||||||
@ -137,6 +137,19 @@ Example
|
|||||||
this.cardViewUpdateService.updateElement(cardViewBaseItemModel)
|
this.cardViewUpdateService.updateElement(cardViewBaseItemModel)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Autocomplete Input Value
|
||||||
|
|
||||||
|
The `autocompleteInputValue$` property is a Subject that emits the current value of the autocomplete input field. This can be used to track changes in the input field and respond accordingly.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
You can subscribe to `autocompleteInputValue$` to get the current value of the autocomplete input field and update the options accordingly.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
this.cardViewUpdateService.autocompleteInputValue$.subscribe(value => {
|
||||||
|
this.options$ = this.getOptions(value);
|
||||||
|
});
|
||||||
|
```
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
- [Card view component](../components/card-view.component.md)
|
- [Card view component](../components/card-view.component.md)
|
||||||
|
@ -1,36 +1,80 @@
|
|||||||
<ng-container *ngIf="!property.isEmpty() || isEditable">
|
<ng-container *ngIf="!property.isEmpty() || isEditable">
|
||||||
<div
|
<div [ngSwitch]="templateType">
|
||||||
[attr.data-automation-id]="'card-select-label-' + property.key"
|
<div *ngSwitchDefault>
|
||||||
class="adf-property-label"
|
<div
|
||||||
[ngClass]="{
|
[attr.data-automation-id]="'card-select-label-' + property.key"
|
||||||
'adf-property-value-editable': isEditable,
|
class="adf-property-label"
|
||||||
'adf-property-readonly-value': isReadonlyProperty
|
[ngClass]="{
|
||||||
}"
|
'adf-property-value-editable': isEditable,
|
||||||
>{{ property.label | translate }}</div>
|
'adf-property-readonly-value': isReadonlyProperty
|
||||||
<div class="adf-property-field">
|
}"
|
||||||
<div
|
>{{ property.label | translate }}
|
||||||
*ngIf="!isEditable"
|
</div>
|
||||||
class="adf-property-value adf-property-read-only"
|
<div class="adf-property-field">
|
||||||
[attr.data-automation-id]="'select-readonly-value-' + property.key"
|
<div
|
||||||
data-automation-class="read-only-value">{{ (property.displayValue | async) | translate }}
|
*ngIf="!isEditable"
|
||||||
|
class="adf-property-value adf-property-read-only"
|
||||||
|
[attr.data-automation-id]="'select-readonly-value-' + property.key"
|
||||||
|
data-automation-class="read-only-value">{{ (property.displayValue | async) | translate }}
|
||||||
|
</div>
|
||||||
|
<div *ngIf="isEditable">
|
||||||
|
<mat-form-field class="adf-property-value" [ngClass]="{'adf-property-value-editable': isEditable}">
|
||||||
|
<mat-select
|
||||||
|
[(value)]="value"
|
||||||
|
[ngClass]="{ 'adf-property-readonly-value': isReadonlyProperty }"
|
||||||
|
panelClass="adf-select-filter"
|
||||||
|
(selectionChange)="onChange($event)"
|
||||||
|
data-automation-class="select-box"
|
||||||
|
[aria-label]="property.label | translate">
|
||||||
|
<adf-select-filter-input *ngIf="showInputFilter" (change)="onFilterInputChange($event)"/>
|
||||||
|
<mat-option *ngIf="displayNoneOption">{{ 'CORE.CARDVIEW.NONE' | translate }}</mat-option>
|
||||||
|
<mat-option
|
||||||
|
*ngFor="let option of list$ | async"
|
||||||
|
[value]="option.key">
|
||||||
|
{{ option.label | translate }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="isEditable">
|
<div *ngSwitchCase="'autocompleteBased'">
|
||||||
<mat-form-field class="adf-property-value" [ngClass]="{'adf-property-value-editable': isEditable}">
|
<mat-form-field
|
||||||
<mat-select
|
class="adf-property-field adf-card-selectitem-autocomplete"
|
||||||
[(value)]="value"
|
[ngClass]="{ 'adf-property-read-only': !isEditable }"
|
||||||
[ngClass]="{ 'adf-property-readonly-value': isReadonlyProperty }"
|
[floatLabel]="'always'">
|
||||||
panelClass="adf-select-filter"
|
<mat-label
|
||||||
(selectionChange)="onChange($event)"
|
*ngIf="showProperty || isEditable"
|
||||||
data-automation-class="select-box"
|
[attr.data-automation-id]="'card-autocomplete-based-selectitem-label-' + property.key"
|
||||||
[aria-label]="property.label | translate">
|
class="adf-property-label"
|
||||||
<adf-select-filter-input *ngIf="showInputFilter" (change)="onFilterInputChange($event)" />
|
[ngClass]="{
|
||||||
<mat-option *ngIf="displayNoneOption">{{ 'CORE.CARDVIEW.NONE' | translate }}</mat-option>
|
'adf-property-value-editable': isEditable,
|
||||||
<mat-option
|
'adf-property-readonly-value': isReadonlyProperty
|
||||||
*ngFor="let option of list$ | async"
|
}">
|
||||||
[value]="option.key">
|
{{ property.label | translate }}
|
||||||
{{ option.label | translate }}
|
</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[matAutocomplete]="auto"
|
||||||
|
class="adf-property-value"
|
||||||
|
[ngClass]="{
|
||||||
|
'adf-property-value-editable': isEditable,
|
||||||
|
'adf-property-readonly-value': isReadonlyProperty
|
||||||
|
}"
|
||||||
|
title="{{ property.label | translate }}"
|
||||||
|
[placeholder]="property.default"
|
||||||
|
[attr.aria-label]="property.label | translate"
|
||||||
|
[formControl]="autocompleteControl"
|
||||||
|
[title]="'CORE.METADATA.ACTIONS.COPY_TO_CLIPBOARD' | translate"
|
||||||
|
[attr.data-automation-id]="'card-autocomplete-based-selectitem-value-' + property.key"
|
||||||
|
/>
|
||||||
|
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete"
|
||||||
|
(optionSelected)="onOptionSelected($event)">
|
||||||
|
<mat-option *ngFor="let option of property.options$ | async" [value]="option.key"
|
||||||
|
[attr.data-automation-id]="'card-autocomplete-based-selectitem-option-' + property.key">
|
||||||
|
{{ option.label }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-autocomplete>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,6 +39,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.adf-card-selectitem-autocomplete .adf-property-value-editable {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
#{$mat-form-field-subscript-wrapper} {
|
#{$mat-form-field-subscript-wrapper} {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -25,14 +25,17 @@ import { HarnessLoader } from '@angular/cdk/testing';
|
|||||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
import { MatSelectHarness } from '@angular/material/select/testing';
|
import { MatSelectHarness } from '@angular/material/select/testing';
|
||||||
import { MatFormFieldHarness } from '@angular/material/form-field/testing';
|
import { MatFormFieldHarness } from '@angular/material/form-field/testing';
|
||||||
import { NoopTranslateModule } from '@alfresco/adf-core';
|
import { CardViewUpdateService, NoopTranslateModule } from '@alfresco/adf-core';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||||
|
import { MatAutocompleteHarness } from '@angular/material/autocomplete/testing';
|
||||||
|
|
||||||
describe('CardViewSelectItemComponent', () => {
|
describe('CardViewSelectItemComponent', () => {
|
||||||
let loader: HarnessLoader;
|
let loader: HarnessLoader;
|
||||||
let fixture: ComponentFixture<CardViewSelectItemComponent>;
|
let fixture: ComponentFixture<CardViewSelectItemComponent>;
|
||||||
let component: CardViewSelectItemComponent;
|
let component: CardViewSelectItemComponent;
|
||||||
let appConfig: AppConfigService;
|
let appConfig: AppConfigService;
|
||||||
|
let cardViewUpdateService: CardViewUpdateService;
|
||||||
const mockData = [
|
const mockData = [
|
||||||
{ key: 'one', label: 'One' },
|
{ key: 'one', label: 'One' },
|
||||||
{ key: 'two', label: 'Two' },
|
{ key: 'two', label: 'Two' },
|
||||||
@ -65,6 +68,7 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
fixture = TestBed.createComponent(CardViewSelectItemComponent);
|
fixture = TestBed.createComponent(CardViewSelectItemComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
appConfig = TestBed.inject(AppConfigService);
|
appConfig = TestBed.inject(AppConfigService);
|
||||||
|
cardViewUpdateService = TestBed.inject(CardViewUpdateService);
|
||||||
component.property = new CardViewSelectItemModel(mockDefaultProps);
|
component.property = new CardViewSelectItemModel(mockDefaultProps);
|
||||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||||
});
|
});
|
||||||
@ -91,7 +95,7 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
editable: false
|
editable: false
|
||||||
});
|
});
|
||||||
|
|
||||||
component.ngOnChanges();
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const readOnly = fixture.debugElement.query(By.css('[data-automation-class="read-only-value"]'));
|
const readOnly = fixture.debugElement.query(By.css('[data-automation-class="read-only-value"]'));
|
||||||
@ -108,7 +112,7 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
});
|
});
|
||||||
component.editable = true;
|
component.editable = true;
|
||||||
component.displayNoneOption = true;
|
component.displayNoneOption = true;
|
||||||
component.ngOnChanges();
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(component.value).toEqual('two');
|
expect(component.value).toEqual('two');
|
||||||
@ -131,7 +135,7 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
});
|
});
|
||||||
component.editable = true;
|
component.editable = true;
|
||||||
component.displayNoneOption = true;
|
component.displayNoneOption = true;
|
||||||
component.ngOnChanges();
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(component.value).toEqual(2);
|
expect(component.value).toEqual(2);
|
||||||
@ -155,7 +159,7 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
});
|
});
|
||||||
component.editable = true;
|
component.editable = true;
|
||||||
component.displayNoneOption = true;
|
component.displayNoneOption = true;
|
||||||
component.ngOnChanges();
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(component.isEditable).toBe(true);
|
expect(component.isEditable).toBe(true);
|
||||||
@ -168,7 +172,7 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render select box if editable property is TRUE', async () => {
|
it('should render select box if editable property is TRUE', async () => {
|
||||||
component.ngOnChanges();
|
component.ngOnChanges({});
|
||||||
component.editable = true;
|
component.editable = true;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
@ -176,7 +180,7 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not have label twice', async () => {
|
it('should not have label twice', async () => {
|
||||||
component.ngOnChanges();
|
component.ngOnChanges({});
|
||||||
component.editable = true;
|
component.editable = true;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
@ -197,7 +201,7 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
});
|
});
|
||||||
component.editable = true;
|
component.editable = true;
|
||||||
component.displayNoneOption = false;
|
component.displayNoneOption = false;
|
||||||
component.ngOnChanges();
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const select = await loader.getHarness(MatSelectHarness);
|
const select = await loader.getHarness(MatSelectHarness);
|
||||||
@ -225,7 +229,7 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
});
|
});
|
||||||
component.editable = true;
|
component.editable = true;
|
||||||
component.displayNoneOption = false;
|
component.displayNoneOption = false;
|
||||||
component.ngOnChanges();
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const select = await loader.getHarness(MatSelectHarness);
|
const select = await loader.getHarness(MatSelectHarness);
|
||||||
@ -245,7 +249,7 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
});
|
});
|
||||||
component.editable = true;
|
component.editable = true;
|
||||||
component.displayNoneOption = false;
|
component.displayNoneOption = false;
|
||||||
component.ngOnChanges();
|
component.ngOnChanges({});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const select = await loader.getHarness(MatSelectHarness);
|
const select = await loader.getHarness(MatSelectHarness);
|
||||||
@ -255,4 +259,94 @@ describe('CardViewSelectItemComponent', () => {
|
|||||||
expect(filterInput).not.toBe(null);
|
expect(filterInput).not.toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Autocomplete based', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.property = new CardViewSelectItemModel({
|
||||||
|
label: 'Test Label',
|
||||||
|
value: 'initial value',
|
||||||
|
key: 'test-key',
|
||||||
|
default: 'Placeholder',
|
||||||
|
editable: true,
|
||||||
|
autocompleteBased: true,
|
||||||
|
options$: of([
|
||||||
|
{ key: '1', label: 'Option 1' },
|
||||||
|
{ key: '2', label: 'Option 2' }
|
||||||
|
])
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set templateType to autocompleteBased', () => {
|
||||||
|
component.property.autocompleteBased = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.templateType).toBe('autocompleteBased');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set initial value to autocompleteControl', () => {
|
||||||
|
component.ngOnChanges({});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.autocompleteControl.value).toBe('initial value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit autocompleteInputValue$ with new value on autocompleteControl change', async () => {
|
||||||
|
const autocompleteValueSpy = spyOn(cardViewUpdateService.autocompleteInputValue$, 'next');
|
||||||
|
component.editedValue = '';
|
||||||
|
component.editable = true;
|
||||||
|
component.ngOnChanges({ property: { firstChange: true } } as any);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
component.autocompleteControl.setValue('new value');
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(autocompleteValueSpy).toHaveBeenCalledWith('new value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update value correctly on option selected', () => {
|
||||||
|
cardViewUpdateService.update = jasmine.createSpy('update');
|
||||||
|
const event: MatAutocompleteSelectedEvent = {
|
||||||
|
option: {
|
||||||
|
value: '1'
|
||||||
|
}
|
||||||
|
} as MatAutocompleteSelectedEvent;
|
||||||
|
|
||||||
|
component.ngOnChanges({});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
component.onOptionSelected(event);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.autocompleteControl.value).toBe('Option 1');
|
||||||
|
expect(cardViewUpdateService.update).toHaveBeenCalledWith(jasmine.objectContaining(component.property), '1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable the autocomplete control', () => {
|
||||||
|
component.editable = false;
|
||||||
|
component.ngOnChanges({ editable: { currentValue: false, previousValue: true, firstChange: false, isFirstChange: () => false } });
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.autocompleteControl.disabled).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should enable the autocomplete control', () => {
|
||||||
|
component.editable = true;
|
||||||
|
component.ngOnChanges({ editable: { currentValue: true, previousValue: false, firstChange: false, isFirstChange: () => false } });
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.autocompleteControl.enabled).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should populate options for autocomplete', async () => {
|
||||||
|
component.ngOnChanges({});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const autocomplete = await loader.getHarness(MatAutocompleteHarness);
|
||||||
|
await autocomplete.enterText('Op');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const options = await autocomplete.getOptions();
|
||||||
|
expect(options.length).toBe(2);
|
||||||
|
expect(await options[0].getText()).toContain('Option 1');
|
||||||
|
expect(await options[1].getText()).toContain('Option 2');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,30 +15,44 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, Input, OnChanges, OnDestroy, OnInit, inject, ViewEncapsulation } from '@angular/core';
|
import { Component, Input, OnChanges, OnInit, inject, ViewEncapsulation, SimpleChanges, DestroyRef } from '@angular/core';
|
||||||
import { CardViewSelectItemModel } from '../../models/card-view-selectitem.model';
|
import { CardViewSelectItemModel } from '../../models/card-view-selectitem.model';
|
||||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||||
import { CardViewSelectItemOption } from '../../interfaces/card-view.interfaces';
|
import { CardViewSelectItemOption } from '../../interfaces/card-view.interfaces';
|
||||||
import { MatSelectChange, MatSelectModule } from '@angular/material/select';
|
import { MatSelectChange, MatSelectModule } from '@angular/material/select';
|
||||||
import { BaseCardView } from '../base-card-view';
|
import { BaseCardView } from '../base-card-view';
|
||||||
import { AppConfigService } from '../../../app-config/app-config.service';
|
import { AppConfigService } from '../../../app-config/app-config.service';
|
||||||
import { takeUntil, map } from 'rxjs/operators';
|
import { map, debounceTime, filter, first } from 'rxjs/operators';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { SelectFilterInputComponent } from './select-filter-input/select-filter-input.component';
|
import { SelectFilterInputComponent } from './select-filter-input/select-filter-input.component';
|
||||||
|
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-card-view-selectitem',
|
selector: 'adf-card-view-selectitem',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, TranslateModule, MatFormFieldModule, MatSelectModule, SelectFilterInputComponent],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TranslateModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatSelectModule,
|
||||||
|
SelectFilterInputComponent,
|
||||||
|
MatAutocompleteModule,
|
||||||
|
MatInputModule,
|
||||||
|
ReactiveFormsModule
|
||||||
|
],
|
||||||
templateUrl: './card-view-selectitem.component.html',
|
templateUrl: './card-view-selectitem.component.html',
|
||||||
styleUrls: ['./card-view-selectitem.component.scss'],
|
styleUrls: ['./card-view-selectitem.component.scss'],
|
||||||
encapsulation: ViewEncapsulation.None,
|
encapsulation: ViewEncapsulation.None,
|
||||||
host: { class: 'adf-card-view-selectitem' }
|
host: { class: 'adf-card-view-selectitem' }
|
||||||
})
|
})
|
||||||
export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItemModel<string | number>> implements OnInit, OnChanges, OnDestroy {
|
export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItemModel<string | number>> implements OnInit, OnChanges {
|
||||||
private appConfig = inject(AppConfigService);
|
private appConfig = inject(AppConfigService);
|
||||||
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
static HIDE_FILTER_LIMIT = 5;
|
static HIDE_FILTER_LIMIT = 5;
|
||||||
|
|
||||||
@Input() options$: Observable<CardViewSelectItemOption<string | number>[]>;
|
@Input() options$: Observable<CardViewSelectItemOption<string | number>[]>;
|
||||||
@ -53,19 +67,47 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
|||||||
filter$ = new BehaviorSubject<string>('');
|
filter$ = new BehaviorSubject<string>('');
|
||||||
showInputFilter: boolean = false;
|
showInputFilter: boolean = false;
|
||||||
list$: Observable<CardViewSelectItemOption<string | number>[]> = null;
|
list$: Observable<CardViewSelectItemOption<string | number>[]> = null;
|
||||||
|
templateType = '';
|
||||||
|
autocompleteControl = new UntypedFormControl();
|
||||||
|
editedValue: string | number;
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
this.value = this.property.value;
|
this.value = this.property.value;
|
||||||
|
if (changes.property?.firstChange) {
|
||||||
|
this.autocompleteControl.valueChanges
|
||||||
|
.pipe(
|
||||||
|
filter((textInputValue) => textInputValue !== this.editedValue && textInputValue !== null),
|
||||||
|
debounceTime(50),
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
)
|
||||||
|
.subscribe((textInputValue) => {
|
||||||
|
this.editedValue = textInputValue;
|
||||||
|
this.cardViewUpdateService.autocompleteInputValue$.next(textInputValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes.editable) {
|
||||||
|
if (this.isEditable) {
|
||||||
|
this.autocompleteControl.enable();
|
||||||
|
} else {
|
||||||
|
this.autocompleteControl.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
if (this.property.autocompleteBased) {
|
||||||
|
this.templateType = 'autocompleteBased';
|
||||||
|
}
|
||||||
|
|
||||||
this.getOptions()
|
this.getOptions()
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||||
.subscribe((options) => {
|
.subscribe((options) => {
|
||||||
this.showInputFilter = options.length > this.optionsLimit;
|
this.showInputFilter = options.length > this.optionsLimit;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.list$ = this.getList();
|
this.list$ = this.getList();
|
||||||
|
this.autocompleteControl.setValue(this.property.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilterInputChange(value: string) {
|
onFilterInputChange(value: string) {
|
||||||
@ -78,11 +120,22 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
|||||||
|
|
||||||
getList(): Observable<CardViewSelectItemOption<string | number>[]> {
|
getList(): Observable<CardViewSelectItemOption<string | number>[]> {
|
||||||
return combineLatest([this.getOptions(), this.filter$]).pipe(
|
return combineLatest([this.getOptions(), this.filter$]).pipe(
|
||||||
map(([items, filter]) => items.filter((item) => (filter ? item.label.toLowerCase().includes(filter.toLowerCase()) : true))),
|
map(([items, searchTerm]) => items.filter((item) => (filter ? item.label.toLowerCase().includes(searchTerm.toLowerCase()) : true)))
|
||||||
takeUntil(this.destroy$)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onOptionSelected(event: MatAutocompleteSelectedEvent) {
|
||||||
|
this.getOptions()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((options) => {
|
||||||
|
const selectedOption = options.find((option) => option.key === event.option.value);
|
||||||
|
if (selectedOption) {
|
||||||
|
this.autocompleteControl.setValue(selectedOption.label);
|
||||||
|
this.cardViewUpdateService.update({ ...this.property } as CardViewSelectItemModel<string>, selectedOption.key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onChange(event: MatSelectChange): void {
|
onChange(event: MatSelectChange): void {
|
||||||
const selectedOption = event.value !== undefined ? event.value : null;
|
const selectedOption = event.value !== undefined ? event.value : null;
|
||||||
this.cardViewUpdateService.update({ ...this.property } as CardViewSelectItemModel<string>, selectedOption);
|
this.cardViewUpdateService.update({ ...this.property } as CardViewSelectItemModel<string>, selectedOption);
|
||||||
@ -93,10 +146,6 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
|||||||
return this.displayEmpty || !this.property.isEmpty();
|
return this.displayEmpty || !this.property.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
super.ngOnDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private get optionsLimit(): number {
|
private get optionsLimit(): number {
|
||||||
return this.appConfig.get<number>('content-metadata.selectFilterLimit', CardViewSelectItemComponent.HIDE_FILTER_LIMIT);
|
return this.appConfig.get<number>('content-metadata.selectFilterLimit', CardViewSelectItemComponent.HIDE_FILTER_LIMIT);
|
||||||
}
|
}
|
||||||
|
@ -27,4 +27,5 @@ export interface CardViewSelectItemProperties<T> extends CardViewItemProperties
|
|||||||
value: string | number;
|
value: string | number;
|
||||||
options$: Observable<CardViewSelectItemOption<T>[]>;
|
options$: Observable<CardViewSelectItemOption<T>[]>;
|
||||||
displayNoneOption?: boolean;
|
displayNoneOption?: boolean;
|
||||||
|
autocompleteBased?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,11 @@ import { of } from 'rxjs';
|
|||||||
|
|
||||||
describe('CardViewSelectItemModel', () => {
|
describe('CardViewSelectItemModel', () => {
|
||||||
let properties: CardViewSelectItemProperties<string>;
|
let properties: CardViewSelectItemProperties<string>;
|
||||||
const mockData = [{ key: 'one', label: 'One' }, { key: 'two', label: 'Two' }, { key: 'three', label: 'Three' }];
|
const mockData = [
|
||||||
|
{ key: 'one', label: 'One' },
|
||||||
|
{ key: 'two', label: 'Two' },
|
||||||
|
{ key: 'three', label: 'Three' }
|
||||||
|
];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
properties = {
|
properties = {
|
||||||
@ -57,5 +61,16 @@ describe('CardViewSelectItemModel', () => {
|
|||||||
|
|
||||||
expect(itemModel.displayNoneOption).toBe(false);
|
expect(itemModel.displayNoneOption).toBe(false);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should set autocompleteBased to false by default', fakeAsync(() => {
|
||||||
|
const itemModel = new CardViewSelectItemModel(properties);
|
||||||
|
expect(itemModel.autocompleteBased).toBe(false);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should set autocompleteBased to true when it passed through the properties', fakeAsync(() => {
|
||||||
|
properties.autocompleteBased = true;
|
||||||
|
const itemModel = new CardViewSelectItemModel(properties);
|
||||||
|
expect(itemModel.autocompleteBased).toBe(true);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -26,6 +26,7 @@ export class CardViewSelectItemModel<T> extends CardViewBaseItemModel implements
|
|||||||
type = 'select';
|
type = 'select';
|
||||||
options$: Observable<CardViewSelectItemOption<T>[]>;
|
options$: Observable<CardViewSelectItemOption<T>[]>;
|
||||||
displayNoneOption: boolean;
|
displayNoneOption: boolean;
|
||||||
|
autocompleteBased = false;
|
||||||
|
|
||||||
valueFetch$: Observable<string> = null;
|
valueFetch$: Observable<string> = null;
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ export class CardViewSelectItemModel<T> extends CardViewBaseItemModel implements
|
|||||||
this.displayNoneOption = cardViewSelectItemProperties.displayNoneOption !== undefined ? cardViewSelectItemProperties.displayNoneOption : true;
|
this.displayNoneOption = cardViewSelectItemProperties.displayNoneOption !== undefined ? cardViewSelectItemProperties.displayNoneOption : true;
|
||||||
|
|
||||||
this.options$ = cardViewSelectItemProperties.options$;
|
this.options$ = cardViewSelectItemProperties.options$;
|
||||||
|
this.autocompleteBased = cardViewSelectItemProperties.autocompleteBased || false;
|
||||||
|
|
||||||
this.valueFetch$ = this.options$.pipe(
|
this.valueFetch$ = this.options$.pipe(
|
||||||
switchMap((options) => {
|
switchMap((options) => {
|
||||||
|
@ -25,17 +25,17 @@ import { CardViewBaseItemModel } from '../models/card-view-baseitem.model';
|
|||||||
export const transformKeyToObject = (key: string, value): any => {
|
export const transformKeyToObject = (key: string, value): any => {
|
||||||
const objectLevels: string[] = key.split('.').reverse();
|
const objectLevels: string[] = key.split('.').reverse();
|
||||||
|
|
||||||
return objectLevels.reduce<any>((previousValue, currentValue) => ({ [currentValue]: previousValue}), value);
|
return objectLevels.reduce<any>((previousValue, currentValue) => ({ [currentValue]: previousValue }), value);
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class CardViewUpdateService implements BaseCardViewUpdate {
|
export class CardViewUpdateService implements BaseCardViewUpdate {
|
||||||
|
|
||||||
itemUpdated$ = new Subject<UpdateNotification>();
|
itemUpdated$ = new Subject<UpdateNotification>();
|
||||||
itemClicked$ = new Subject<ClickNotification>();
|
itemClicked$ = new Subject<ClickNotification>();
|
||||||
updateItem$ = new Subject<CardViewBaseItemModel>();
|
updateItem$ = new Subject<CardViewBaseItemModel>();
|
||||||
|
autocompleteInputValue$ = new Subject<string>();
|
||||||
|
|
||||||
update(property: CardViewBaseItemModel, newValue: any) {
|
update(property: CardViewBaseItemModel, newValue: any) {
|
||||||
this.itemUpdated$.next({
|
this.itemUpdated$.next({
|
||||||
@ -58,5 +58,4 @@ export class CardViewUpdateService implements BaseCardViewUpdate {
|
|||||||
updateElement(notification: CardViewBaseItemModel) {
|
updateElement(notification: CardViewBaseItemModel) {
|
||||||
this.updateItem$.next(notification);
|
this.updateItem$.next(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ describe('TaskDetailsComponent', () => {
|
|||||||
let getTaskDetailsSpy: jasmine.Spy;
|
let getTaskDetailsSpy: jasmine.Spy;
|
||||||
let getTasksSpy: jasmine.Spy;
|
let getTasksSpy: jasmine.Spy;
|
||||||
let assignTaskSpy: jasmine.Spy;
|
let assignTaskSpy: jasmine.Spy;
|
||||||
|
let getWorkflowUsersSpy: jasmine.Spy;
|
||||||
let taskCommentsService: CommentsService;
|
let taskCommentsService: CommentsService;
|
||||||
let peopleProcessService: PeopleProcessService;
|
let peopleProcessService: PeopleProcessService;
|
||||||
|
|
||||||
@ -63,6 +64,7 @@ describe('TaskDetailsComponent', () => {
|
|||||||
});
|
});
|
||||||
peopleProcessService = TestBed.inject(PeopleProcessService);
|
peopleProcessService = TestBed.inject(PeopleProcessService);
|
||||||
spyOn(peopleProcessService, 'getCurrentUserInfo').and.returnValue(of({ email: 'fake-email' } as any));
|
spyOn(peopleProcessService, 'getCurrentUserInfo').and.returnValue(of({ email: 'fake-email' } as any));
|
||||||
|
getWorkflowUsersSpy = spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(of([]));
|
||||||
|
|
||||||
const taskListService = TestBed.inject(TaskListService);
|
const taskListService = TestBed.inject(TaskListService);
|
||||||
spyOn(taskListService, 'getTaskChecklist').and.returnValue(of(noDataMock));
|
spyOn(taskListService, 'getTaskChecklist').and.returnValue(of(noDataMock));
|
||||||
@ -371,7 +373,7 @@ describe('TaskDetailsComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return an observable with user search results', () => {
|
it('should return an observable with user search results', () => {
|
||||||
spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(
|
getWorkflowUsersSpy.and.returnValue(
|
||||||
of([
|
of([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -402,7 +404,7 @@ describe('TaskDetailsComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return an empty list for not valid search', () => {
|
it('should return an empty list for not valid search', () => {
|
||||||
spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(of([]));
|
getWorkflowUsersSpy.and.returnValue(of([]));
|
||||||
|
|
||||||
let lastValue: LightUserRepresentation[];
|
let lastValue: LightUserRepresentation[];
|
||||||
component.peopleSearch.subscribe((users) => (lastValue = users));
|
component.peopleSearch.subscribe((users) => (lastValue = users));
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
<mat-card appearance="outlined" *ngIf="taskDetails" class="adf-card-container">
|
<mat-card appearance="outlined" *ngIf="taskDetails" class="adf-card-container">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<adf-card-view [properties]="properties" [editable]="!isCompleted()" [displayClearAction]="displayDateClearAction" />
|
<adf-card-view [properties]="properties" [editable]="!readOnly && !isCompleted()" [displayClearAction]="displayDateClearAction"/>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
|
|
||||||
<mat-card-actions class="adf-controls" *ngIf="showClaimRelease">
|
<mat-card-actions class="adf-controls" *ngIf="showClaimRelease">
|
||||||
<button *ngIf="isTaskClaimedByCandidateMember()"
|
<button *ngIf="isTaskClaimedByCandidateMember()"
|
||||||
mat-button
|
mat-button
|
||||||
data-automation-id="header-unclaim-button"
|
data-automation-id="header-unclaim-button"
|
||||||
id="unclaim-task"
|
id="unclaim-task"
|
||||||
class="adf-claim-controls"
|
class="adf-claim-controls"
|
||||||
adf-unclaim-task
|
adf-unclaim-task
|
||||||
[taskId]="taskDetails.id"
|
[taskId]="taskDetails.id"
|
||||||
(success)="onUnclaimTask($event)">
|
(success)="onUnclaimTask($event)">
|
||||||
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM' | translate }}
|
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="isTaskClaimable()"
|
<button *ngIf="isTaskClaimable()"
|
||||||
mat-button
|
mat-button
|
||||||
data-automation-id="header-claim-button"
|
data-automation-id="header-claim-button"
|
||||||
id="claim-task"
|
id="claim-task"
|
||||||
class="adf-claim-controls"
|
class="adf-claim-controls"
|
||||||
adf-claim-task
|
adf-claim-task
|
||||||
[taskId]="taskDetails.id"
|
[taskId]="taskDetails.id"
|
||||||
|
@ -15,10 +15,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
import { AppConfigService, CardViewUpdateService } from '@alfresco/adf-core';
|
||||||
import { of } from 'rxjs';
|
import { of, Subject } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
completedTaskDetailsMock,
|
completedTaskDetailsMock,
|
||||||
taskDetailsMock,
|
taskDetailsMock,
|
||||||
@ -32,6 +32,7 @@ import { TaskHeaderComponent } from './task-header.component';
|
|||||||
import { ProcessTestingModule } from '../../../testing/process.testing.module';
|
import { ProcessTestingModule } from '../../../testing/process.testing.module';
|
||||||
import { PeopleProcessService } from '../../../services/people-process.service';
|
import { PeopleProcessService } from '../../../services/people-process.service';
|
||||||
import { TaskRepresentation } from '@alfresco/js-api';
|
import { TaskRepresentation } from '@alfresco/js-api';
|
||||||
|
import { SimpleChanges } from '@angular/core';
|
||||||
|
|
||||||
describe('TaskHeaderComponent', () => {
|
describe('TaskHeaderComponent', () => {
|
||||||
let service: TaskListService;
|
let service: TaskListService;
|
||||||
@ -39,6 +40,7 @@ describe('TaskHeaderComponent', () => {
|
|||||||
let fixture: ComponentFixture<TaskHeaderComponent>;
|
let fixture: ComponentFixture<TaskHeaderComponent>;
|
||||||
let peopleProcessService: PeopleProcessService;
|
let peopleProcessService: PeopleProcessService;
|
||||||
let appConfigService: AppConfigService;
|
let appConfigService: AppConfigService;
|
||||||
|
let cardViewUpdateService: CardViewUpdateService;
|
||||||
|
|
||||||
const fakeBpmAssignedUser: any = {
|
const fakeBpmAssignedUser: any = {
|
||||||
id: 1001,
|
id: 1001,
|
||||||
@ -63,8 +65,10 @@ describe('TaskHeaderComponent', () => {
|
|||||||
service = TestBed.inject(TaskListService);
|
service = TestBed.inject(TaskListService);
|
||||||
peopleProcessService = TestBed.inject(PeopleProcessService);
|
peopleProcessService = TestBed.inject(PeopleProcessService);
|
||||||
spyOn(peopleProcessService, 'getCurrentUserInfo').and.returnValue(of(fakeBpmAssignedUser));
|
spyOn(peopleProcessService, 'getCurrentUserInfo').and.returnValue(of(fakeBpmAssignedUser));
|
||||||
|
spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(of([{ id: 1, firstName: 'Test', lastName: 'User' }]));
|
||||||
component.taskDetails = new TaskRepresentation(taskDetailsMock);
|
component.taskDetails = new TaskRepresentation(taskDetailsMock);
|
||||||
appConfigService = TestBed.inject(AppConfigService);
|
appConfigService = TestBed.inject(AppConfigService);
|
||||||
|
cardViewUpdateService = TestBed.inject(CardViewUpdateService);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getClaimButton = () => fixture.debugElement.query(By.css('[data-automation-id="header-claim-button"]'))?.nativeElement as HTMLButtonElement;
|
const getClaimButton = () => fixture.debugElement.query(By.css('[data-automation-id="header-claim-button"]'))?.nativeElement as HTMLButtonElement;
|
||||||
@ -72,6 +76,66 @@ describe('TaskHeaderComponent', () => {
|
|||||||
const getUnclaimButton = () =>
|
const getUnclaimButton = () =>
|
||||||
fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'))?.nativeElement as HTMLButtonElement;
|
fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'))?.nativeElement as HTMLButtonElement;
|
||||||
|
|
||||||
|
const triggerNgOnChanges = (currentValue: any, previousValue: any) => {
|
||||||
|
const changes: SimpleChanges = {
|
||||||
|
taskDetails: {
|
||||||
|
currentValue,
|
||||||
|
previousValue,
|
||||||
|
firstChange: false,
|
||||||
|
isFirstChange: () => false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
component.ngOnChanges(changes);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should set users$ when autocompleteInputValue$ emits new value', fakeAsync(() => {
|
||||||
|
const autocompleteInputValue$ = cardViewUpdateService.autocompleteInputValue$;
|
||||||
|
component.ngOnInit();
|
||||||
|
|
||||||
|
autocompleteInputValue$.next('test');
|
||||||
|
tick(300);
|
||||||
|
|
||||||
|
component.users$.subscribe((users) => {
|
||||||
|
expect(users).toEqual([{ key: 1, label: 'Test User' }]);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should call initData on resetChanges subscription', () => {
|
||||||
|
const resetChanges$ = new Subject<void>();
|
||||||
|
component.resetChanges = resetChanges$;
|
||||||
|
spyOn(component, 'initData');
|
||||||
|
component.ngOnInit();
|
||||||
|
|
||||||
|
expect(component.initData).toHaveBeenCalledTimes(1);
|
||||||
|
resetChanges$.next();
|
||||||
|
expect(component.initData).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call initData when assignee changes', () => {
|
||||||
|
spyOn(component, 'initData');
|
||||||
|
triggerNgOnChanges({ id: '1', assignee: { id: '2' } }, { id: '1', assignee: { id: '1' } });
|
||||||
|
expect(component.initData).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call initData when task id changes', () => {
|
||||||
|
spyOn(component, 'initData');
|
||||||
|
triggerNgOnChanges({ id: '2', assignee: { id: '1' } }, { id: '1', assignee: { id: '1' } });
|
||||||
|
expect(component.initData).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call refreshData when taskDetails change', () => {
|
||||||
|
spyOn(component, 'refreshData');
|
||||||
|
triggerNgOnChanges(
|
||||||
|
{ id: '1', assignee: { id: '1' }, description: 'one' },
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
assignee: { id: '1' },
|
||||||
|
description: 'two'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(component.refreshData).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('should render empty component if no task details provided', async () => {
|
it('should render empty component if no task details provided', async () => {
|
||||||
component.taskDetails = undefined;
|
component.taskDetails = undefined;
|
||||||
|
|
||||||
@ -87,7 +151,7 @@ describe('TaskHeaderComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-textitem-clickable-value'));
|
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-property-value'));
|
||||||
expect(formNameEl.nativeElement.value).toBe('Wilbur Adams');
|
expect(formNameEl.nativeElement.value).toBe('Wilbur Adams');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -98,7 +162,7 @@ describe('TaskHeaderComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-textitem-clickable-value'));
|
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-property-value'));
|
||||||
expect(valueEl.nativeElement.value).toBe('ADF_TASK_LIST.PROPERTIES.ASSIGNEE_DEFAULT');
|
expect(valueEl.nativeElement.value).toBe('ADF_TASK_LIST.PROPERTIES.ASSIGNEE_DEFAULT');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
import { Component, EventEmitter, DestroyRef, Input, OnChanges, OnInit, Output, SimpleChanges, ViewEncapsulation, inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
CardViewDateItemModel,
|
CardViewDateItemModel,
|
||||||
CardViewMapItemModel,
|
CardViewMapItemModel,
|
||||||
@ -25,7 +25,10 @@ import {
|
|||||||
AppConfigService,
|
AppConfigService,
|
||||||
CardViewIntItemModel,
|
CardViewIntItemModel,
|
||||||
CardViewItemLengthValidator,
|
CardViewItemLengthValidator,
|
||||||
CardViewComponent
|
CardViewComponent,
|
||||||
|
CardViewUpdateService,
|
||||||
|
CardViewSelectItemModel,
|
||||||
|
CardViewSelectItemOption
|
||||||
} from '@alfresco/adf-core';
|
} from '@alfresco/adf-core';
|
||||||
import { PeopleProcessService } from '../../../services/people-process.service';
|
import { PeopleProcessService } from '../../../services/people-process.service';
|
||||||
import { TaskDescriptionValidator } from '../../validators/task-description.validator';
|
import { TaskDescriptionValidator } from '../../validators/task-description.validator';
|
||||||
@ -36,6 +39,9 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { UnclaimTaskDirective } from '../task-form/unclaim-task.directive';
|
import { UnclaimTaskDirective } from '../task-form/unclaim-task.directive';
|
||||||
import { ClaimTaskDirective } from '../task-form/claim-task.directive';
|
import { ClaimTaskDirective } from '../task-form/claim-task.directive';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { debounceTime, filter, map, switchMap } from 'rxjs/operators';
|
||||||
|
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-task-header',
|
selector: 'adf-task-header',
|
||||||
@ -58,6 +64,18 @@ export class TaskHeaderComponent implements OnChanges, OnInit {
|
|||||||
@Input()
|
@Input()
|
||||||
showClaimRelease = true;
|
showClaimRelease = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (optional) This flag sets read-only mode, preventing changes.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
readOnly = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the card data when an event emitted.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
resetChanges = new Subject<void>();
|
||||||
|
|
||||||
/** Emitted when the task is claimed. */
|
/** Emitted when the task is claimed. */
|
||||||
@Output()
|
@Output()
|
||||||
claim: EventEmitter<any> = new EventEmitter<any>();
|
claim: EventEmitter<any> = new EventEmitter<any>();
|
||||||
@ -72,24 +90,51 @@ export class TaskHeaderComponent implements OnChanges, OnInit {
|
|||||||
dateLocale: string;
|
dateLocale: string;
|
||||||
|
|
||||||
private currentUserId: number;
|
private currentUserId: number;
|
||||||
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
|
private readonly usersSubject$ = new BehaviorSubject<CardViewSelectItemOption<number>[]>([]);
|
||||||
|
users$ = this.usersSubject$.asObservable();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private peopleProcessService: PeopleProcessService,
|
private peopleProcessService: PeopleProcessService,
|
||||||
private translationService: TranslationService,
|
private translationService: TranslationService,
|
||||||
private appConfig: AppConfigService
|
private readonly appConfig: AppConfigService,
|
||||||
|
private readonly cardViewUpdateService: CardViewUpdateService
|
||||||
) {
|
) {
|
||||||
this.dateFormat = this.appConfig.get('dateValues.defaultDateFormat');
|
this.dateFormat = this.appConfig.get('dateValues.defaultDateFormat');
|
||||||
this.dateLocale = this.appConfig.get('dateValues.defaultDateLocale');
|
this.dateLocale = this.appConfig.get('dateValues.defaultDateLocale');
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.loadCurrentBpmUserId();
|
this.peopleProcessService
|
||||||
this.initData();
|
.getCurrentUserInfo()
|
||||||
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||||
|
.subscribe((res) => {
|
||||||
|
this.currentUserId = res ? +res.id : null;
|
||||||
|
this.initData();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.cardViewUpdateService.autocompleteInputValue$
|
||||||
|
.pipe(
|
||||||
|
filter((res) => res.length > 0),
|
||||||
|
debounceTime(300),
|
||||||
|
switchMap((res) => this.getUsers(res)),
|
||||||
|
takeUntilDestroyed(this.destroyRef)
|
||||||
|
)
|
||||||
|
.subscribe((users) => {
|
||||||
|
this.usersSubject$.next(users);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.resetChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
||||||
|
this.initData();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
const taskDetailsChange = changes['taskDetails'];
|
const taskDetailsChange = changes['taskDetails'];
|
||||||
if (taskDetailsChange?.currentValue?.id !== taskDetailsChange?.previousValue?.id) {
|
if (
|
||||||
|
taskDetailsChange?.currentValue?.id !== taskDetailsChange?.previousValue?.id ||
|
||||||
|
taskDetailsChange?.currentValue?.assignee?.id !== taskDetailsChange?.previousValue?.assignee?.id
|
||||||
|
) {
|
||||||
this.initData();
|
this.initData();
|
||||||
} else {
|
} else {
|
||||||
this.refreshData();
|
this.refreshData();
|
||||||
@ -249,15 +294,31 @@ export class TaskHeaderComponent implements OnChanges, OnInit {
|
|||||||
return this.taskDetails.duration ? `${this.taskDetails.duration} ms` : '';
|
return this.taskDetails.duration ? `${this.taskDetails.duration} ms` : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getUsers(searchQuery: string): Observable<CardViewSelectItemOption<number>[]> {
|
||||||
|
return this.peopleProcessService.getWorkflowUsers(undefined, searchQuery).pipe(
|
||||||
|
map((users) =>
|
||||||
|
users
|
||||||
|
.filter((user) => user.id !== this.currentUserId)
|
||||||
|
.map(({ id, firstName = '', lastName = '' }) => ({
|
||||||
|
key: id,
|
||||||
|
label: `${firstName} ${lastName}`.trim()
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private initDefaultProperties(parentInfoMap: Map<string, string>): any[] {
|
private initDefaultProperties(parentInfoMap: Map<string, string>): any[] {
|
||||||
return [
|
return [
|
||||||
new CardViewTextItemModel({
|
new CardViewSelectItemModel({
|
||||||
label: 'ADF_TASK_LIST.PROPERTIES.ASSIGNEE',
|
label: 'ADF_TASK_LIST.PROPERTIES.ASSIGNEE',
|
||||||
value: this.taskDetails.getFullName(),
|
value: this.taskDetails.getFullName()
|
||||||
|
? this.taskDetails.getFullName()
|
||||||
|
: this.translationService.instant('ADF_TASK_LIST.PROPERTIES.ASSIGNEE_DEFAULT'),
|
||||||
key: 'assignee',
|
key: 'assignee',
|
||||||
default: this.translationService.instant('ADF_TASK_LIST.PROPERTIES.ASSIGNEE_DEFAULT'),
|
editable: this.isAssignedToCurrentUser(),
|
||||||
clickable: !this.isCompleted(),
|
autocompleteBased: true,
|
||||||
icon: 'create'
|
icon: 'create',
|
||||||
|
options$: this.users$
|
||||||
}),
|
}),
|
||||||
new CardViewTextItemModel({
|
new CardViewTextItemModel({
|
||||||
label: 'ADF_TASK_LIST.PROPERTIES.STATUS',
|
label: 'ADF_TASK_LIST.PROPERTIES.STATUS',
|
||||||
@ -345,13 +406,4 @@ export class TaskHeaderComponent implements OnChanges, OnInit {
|
|||||||
private isValidSelection(filteredProperties: string[], cardItem: CardViewBaseItemModel): boolean {
|
private isValidSelection(filteredProperties: string[], cardItem: CardViewBaseItemModel): boolean {
|
||||||
return filteredProperties ? filteredProperties.indexOf(cardItem.key) >= 0 : true;
|
return filteredProperties ? filteredProperties.indexOf(cardItem.key) >= 0 : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads current bpm userId
|
|
||||||
*/
|
|
||||||
private loadCurrentBpmUserId(): void {
|
|
||||||
this.peopleProcessService.getCurrentUserInfo().subscribe((res) => {
|
|
||||||
this.currentUserId = res ? +res.id : null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user