diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html index f6166239b0..96dff1c1be 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html @@ -16,108 +16,112 @@ -->
-
-
-
{{ 'calculated-fields.argument-name' | translate }}
-
{{ 'calculated-fields.datasource' | translate }}
-
{{ 'common.type' | translate }}
-
{{ 'entity.key' | translate }}
-
-
-
- @for (group of argumentsFormArray.controls; track group) { -
- - - -
- @if (group.get('refEntityId')?.get('id')?.value) { - - - - - {{ entityTypeTranslations.get(group.get('refEntityId').get('entityType').value)?.type | translate }} - - - - - - } @else { - - - - {{ - (group.get('refEntityId')?.get('entityType')?.value === ArgumentEntityType.Tenant - ? 'calculated-fields.argument-current-tenant' - : 'calculated-fields.argument-current') | translate - }} - - - - } -
- - - @if (group.get('refEntityKey').get('type').value; as type) { - - - {{ ArgumentTypeTranslations.get(type) | translate }} - - - } - - - -
- {{ group.get('refEntityKey').get('key').value }} -
-
-
-
-
- - -
-
- } @empty { - {{ 'calculated-fields.no-arguments' | translate }} +
+ + + +
{{ 'common.name' | translate }}
+
+ +
{{ argument.argumentName }}
+
+
+ + + {{ 'entity.entity-type' | translate }} + + +
+ @if (argument.refEntityId?.entityType === ArgumentEntityType.Tenant) { + {{ 'calculated-fields.argument-current-tenant' | translate }} + } @else if (argument.refEntityId?.id) { + {{ entityTypeTranslations.get(argument.refEntityId.entityType).type | translate }} + } @else { + {{ 'calculated-fields.argument-current' | translate }} } -
- @if (errorText) { - - } - -
- +
+
+
+ + + {{ 'entity-view.target-entity' | translate }} + + +
+ @if (argument.refEntityId?.id) { + + {{ entityNameMap.get(argument.refEntityId.id) ?? '' }} + + } +
+
+
+ + + {{ 'common.type' | translate }} + + +
{{ ArgumentTypeTranslations.get(argument.refEntityKey.type) | translate }}
+
+
+ + + {{ 'entity.key' | translate }} + + + +
{{ argument.refEntityKey.key }}
+
+
+
+ + + +
+ + +
+
+
+ + +
+
+ {{ 'calculated-fields.no-arguments' | translate }}
+ @if (errorText) { + + } +
+
+ +
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.scss b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.scss index fe04269150..c4efe323c6 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.scss @@ -13,11 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@use '../../../../../../../scss/constants' as constants; - :host { - .entity-key-field { - border-bottom: 1px solid rgba(0, 0, 0, 0.12); + .arguments-table { + min-height: 108px; + + &-with-error { + min-height: 150px; + } + + .mat-mdc-table { + table-layout: fixed; + } + + .key-text { + font-size: 13px; + } } .tb-form-table-row-cell-buttons { @@ -25,38 +35,24 @@ --mat-badge-small-size-container-overlap-offset: -5px; --mat-badge-small-size-text-size: 0; } - - .argument-name-field { - @media #{constants.$mat-sm} { - min-width: 25%; - max-width: 25%; - } - @media #{constants.$mat-xs} { - min-width: 33%; - max-width: 33%; - } - } - - .datasource-field { - min-width: 33%; - max-width: 33%; - } } :host ::ng-deep { - .entity-field { - a { - font-size: 14px; - white-space: nowrap; - display: block; - overflow: hidden; - text-overflow: ellipsis; - } - } - .mat-mdc-standard-chip { .mdc-evolution-chip__cell--primary, .mdc-evolution-chip__action--primary, .mdc-evolution-chip__text-label { overflow: hidden; } } + + .arguments-table:not(.arguments-table-with-error) { + .mdc-data-table__row:last-child .mat-mdc-cell { + border-bottom: none; + } + } + + .arguments-table { + .mat-mdc-header-row.mat-row-select .mat-mdc-header-cell.entity-type-header { + padding: 0 28px 0 0; + } + } } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts index 4414c17dd9..2537f34059 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts @@ -15,20 +15,22 @@ /// import { + AfterViewInit, ChangeDetectorRef, Component, + DestroyRef, forwardRef, Input, OnChanges, Renderer2, SimpleChanges, + ViewChild, ViewContainerRef, } from '@angular/core'; import { AbstractControl, ControlValueAccessor, FormBuilder, - FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, @@ -48,8 +50,11 @@ import { TbPopoverService } from '@shared/components/popover.service'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { EntityId } from '@shared/models/id/entity-id'; import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; -import { isDefined, isDefinedAndNotNull } from '@core/utils'; +import { getEntityDetailsPageURL, isDefined, isDefinedAndNotNull } from '@core/utils'; import { TbPopoverComponent } from '@shared/components/popover.component'; +import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract'; +import { EntityService } from '@core/http/entity.service'; +import { MatSort } from '@angular/material/sort'; @Component({ selector: 'tb-calculated-field-arguments-table', @@ -68,19 +73,23 @@ import { TbPopoverComponent } from '@shared/components/popover.component'; } ], }) -export class CalculatedFieldArgumentsTableComponent implements ControlValueAccessor, Validator, OnChanges { +export class CalculatedFieldArgumentsTableComponent implements ControlValueAccessor, Validator, OnChanges, AfterViewInit { @Input() entityId: EntityId; @Input() tenantId: string; @Input() entityName: string; @Input() calculatedFieldType: CalculatedFieldType; + @ViewChild(MatSort, { static: true }) sort: MatSort; + errorText = ''; argumentsFormArray = this.fb.array([]); + entityNameMap = new Map(); + sortOrder = { direction: 'asc', property: '' }; + dataSource = new CalculatedFieldArgumentDatasource(); readonly entityTypeTranslations = entityTypeTranslations; readonly ArgumentTypeTranslations = ArgumentTypeTranslations; - readonly EntityType = EntityType; readonly ArgumentEntityType = ArgumentEntityType; readonly ArgumentType = ArgumentType; readonly CalculatedFieldType = CalculatedFieldType; @@ -93,10 +102,14 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces private popoverService: TbPopoverService, private viewContainerRef: ViewContainerRef, private cd: ChangeDetectorRef, - private renderer: Renderer2 + private renderer: Renderer2, + private entityService: EntityService, + private destroyRef: DestroyRef, ) { - this.argumentsFormArray.valueChanges.pipe(takeUntilDestroyed()).subscribe(() => { - this.propagateChange(this.getArgumentsObject()); + this.argumentsFormArray.valueChanges.pipe(takeUntilDestroyed()).subscribe(value => { + this.updateEntityNameMap(value); + this.updateDataSource(value); + this.propagateChange(this.getArgumentsObject(value)); }); } @@ -107,6 +120,14 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces } } + ngAfterViewInit(): void { + this.sort.sortChange.asObservable().pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => { + this.sortOrder.property = this.sort.active; + this.sortOrder.direction = this.sort.direction; + this.updateDataSource(this.argumentsFormArray.value); + }); + } + registerOnChange(fn: (argumentsObj: Record) => void): void { this.propagateChange = fn; } @@ -118,12 +139,13 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces return this.errorText ? { argumentsFormArray: false } : null; } - onDelete(index: number): void { + onDelete($event: Event, index: number): void { + $event.stopPropagation(); this.argumentsFormArray.removeAt(index); this.argumentsFormArray.markAsDirty(); } - manageArgument($event: Event, matButton: MatButton, index?: number): void { + manageArgument($event: Event, matButton: MatButton, argument = {} as CalculatedFieldArgumentValue, index?: number): void { $event?.stopPropagation(); if (this.popoverComponent && !this.popoverComponent.tbHidden) { this.popoverComponent.hide(); @@ -132,16 +154,15 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const argumentObj = this.argumentsFormArray.at(index)?.getRawValue() ?? {}; const ctx = { index, - argument: argumentObj, + argument, entityId: this.entityId, calculatedFieldType: this.calculatedFieldType, buttonTitle: this.argumentsFormArray.at(index)?.value ? 'action.apply' : 'action.add', tenantId: this.tenantId, entityName: this.entityName, - usedArgumentNames: this.argumentsFormArray.getRawValue().map(({ argumentName }) => argumentName).filter(name => name !== argumentObj.argumentName), + usedArgumentNames: this.argumentsFormArray.value.map(({ argumentName }) => argumentName).filter(name => name !== argument.argumentName), }; this.popoverComponent = this.popoverService.displayPopover(trigger, this.renderer, this.viewContainerRef, CalculatedFieldArgumentPanelComponent, isDefined(index) ? 'left' : 'right', false, null, @@ -150,7 +171,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces {}, {}, true); this.popoverComponent.tbComponentRef.instance.argumentsDataApplied.subscribe(({ value, index }) => { this.popoverComponent.hide(); - const formGroup = this.getArgumentFormGroup(value); + const formGroup = this.fb.group(value); if (isDefinedAndNotNull(index)) { this.argumentsFormArray.setControl(index, formGroup); } else { @@ -162,9 +183,14 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces } } + private updateDataSource(value: CalculatedFieldArgumentValue[]): void { + const sortedValue = this.sortData(value); + this.dataSource.loadData(sortedValue); + } + private updateErrorText(): void { if (this.calculatedFieldType === CalculatedFieldType.SIMPLE - && this.argumentsFormArray.controls.some(control => control.get('refEntityKey').get('type').value === ArgumentType.Rolling)) { + && this.argumentsFormArray.controls.some(control => control.value.refEntityKey.type === ArgumentType.Rolling)) { this.errorText = 'calculated-fields.hint.arguments-simple-with-rolling'; } else if (!this.argumentsFormArray.controls.length) { this.errorText = 'calculated-fields.hint.arguments-empty'; @@ -173,9 +199,9 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces } } - private getArgumentsObject(): Record { - return this.argumentsFormArray.getRawValue().reduce((acc, rawValue) => { - const { argumentName, ...argument } = rawValue as CalculatedFieldArgumentValue; + private getArgumentsObject(value: CalculatedFieldArgumentValue[]): Record { + return value.reduce((acc, argumentValue) => { + const { argumentName, ...argument } = argumentValue as CalculatedFieldArgumentValue; acc[argumentName] = argument; return acc; }, {} as Record); @@ -186,31 +212,62 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces this.populateArgumentsFormArray(argumentsObj) } + getEntityDetailsPageURL(id: string, type: EntityType): string { + return getEntityDetailsPageURL(id, type); + } + private populateArgumentsFormArray(argumentsObj: Record): void { Object.keys(argumentsObj).forEach(key => { const value: CalculatedFieldArgumentValue = { ...argumentsObj[key], argumentName: key }; - this.argumentsFormArray.push(this.getArgumentFormGroup(value), {emitEvent: false}); + this.argumentsFormArray.push(this.fb.group(value), { emitEvent: false }); + }); + this.argumentsFormArray.updateValueAndValidity(); + } + + private updateEntityNameMap(value: CalculatedFieldArgumentValue[]): void { + value.forEach(({ refEntityId = {}}) => { + if (refEntityId.id && !this.entityNameMap.has(refEntityId.id)) { + const { id, entityType } = refEntityId as EntityId; + this.entityService.getEntity(entityType as EntityType, id, { ignoreLoading: true, ignoreErrors: true }) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(entity => this.entityNameMap.set(id, entity.name)); + } }); } - private getArgumentFormGroup(value: CalculatedFieldArgumentValue): FormGroup { - return this.fb.group({ - ...value, - argumentName: [{ value: value.argumentName, disabled: true }], - ...(value.refEntityId ? { - refEntityId: this.fb.group({ - entityType: [{ value: value.refEntityId.entityType, disabled: true }], - id: [{ value: value.refEntityId.id , disabled: true }], - }), - } : {}), - refEntityKey: this.fb.group({ - ...value.refEntityKey, - type: [{ value: value.refEntityKey.type, disabled: true }], - key: [{ value: value.refEntityKey.key, disabled: true }], - }), - }) + private getSortValue(argument: CalculatedFieldArgumentValue, column: string): string { + switch (column) { + case 'entityType': + if (argument.refEntityId?.entityType === ArgumentEntityType.Tenant) { + return 'calculated-fields.argument-current-tenant'; + } else if (argument.refEntityId?.id) { + return entityTypeTranslations.get((argument.refEntityId)?.entityType as unknown as EntityType).type; + } else { + return 'calculated-fields.argument-current'; + } + case 'type': + return ArgumentTypeTranslations.get(argument.refEntityKey.type); + case 'key': + return argument.refEntityKey.key; + default: + return argument.argumentName; + } + } + + private sortData(data: CalculatedFieldArgumentValue[]): CalculatedFieldArgumentValue[] { + return data.sort((a, b) => { + const valA = this.getSortValue(a, this.sortOrder.property) ?? ''; + const valB = this.getSortValue(b, this.sortOrder.property) ?? ''; + return (this.sortOrder.direction === 'asc' ? 1 : -1) * valA.localeCompare(valB); + }); + } +} + +class CalculatedFieldArgumentDatasource extends TbTableDatasource { + constructor() { + super(); } } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts index 63e684bf6e..c3e3d52a72 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts @@ -143,10 +143,10 @@ export class CalculatedFieldDialogComponent extends DialogComponent, 'label'>, HasVersion, HasTenantId, ExportableEntity { - debugSettings?: EntityDebugSettings; +export interface CalculatedField extends Omit, 'label'>, HasVersion, HasEntityDebugSettings, HasTenantId, ExportableEntity { configuration: CalculatedFieldConfiguration; type: CalculatedFieldType; entityId: EntityId;