src/app/features/inherited-field/automated-field-update/automated-field-mapping/automated-field-mapping.component.ts

Description

Dialog to configure additional details for the "inherited-field" default value strategy, working in combination with the admin components.

Example

Metadata

Relationships

Used by

No results matching.

Depends on

Index

Properties
Methods

Constructor

constructor()

Methods

save
save()
Returns : void

Properties

isInvalidMapping
Type : boolean
Default value : false
mappingEnabled
Type : unknown
Default value : signal(false)

If the user explicitly enabled the optional value mapping functionality

selectedSourceValueField
Type : WritableSignal<string | null>

The currently selected "sourceValueField" on the related entity

sourceValueEntityType
Type : EntityConstructor

The entity type of the related entity that triggers the updates

sourceValueFieldSchema
Type : Signal<EntitySchemaField | undefined>
Default value : computed( () => { const fieldId = this.selectedSourceValueField(); if (!fieldId) return; return this.sourceValueEntityType.schema.get(fieldId); }, )
targetFieldConfig
Type : FormFieldConfig

The full schema of the field for which this default value is configured

value
Type : DefaultValueConfigInheritedField
valueMappingOptions
Type : Signal<literal type[]>
Default value : computed(() => { if ( this.sourceValueFieldSchema()?.dataType !== ConfigurableEnumDatatype.dataType ) { // only configurable-enum fields supported currently return []; } const enumEntity = this.configurableEnumService.getEnum( this.sourceValueFieldSchema().additional, ); const values = enumEntity?.values ?? []; return values.map((sourceValue) => { const sourceValueRaw = sourceValue.id; // database format of the source value // select existing mapping value if available let selectedMappedValue: any = this.value.valueMapping?.[sourceValueRaw]; if (selectedMappedValue) { selectedMappedValue = this.schemaService.valueToEntityFormat( selectedMappedValue, this.targetFieldConfig, ); } const formControl = new FormControl(selectedMappedValue); const formGroup = new FormGroup({ [this.targetFieldConfig.id]: formControl, }); return { sourceValue, sourceValueRaw, form: { formGroup, } as unknown as EntityForm<Entity>, }; }); })

The possible values of the selected sourceValueField that can be mapped to custom target values. Currently mapping only supported for ConfigurableEnum fields.

import { EntityForm } from "#src/app/core/common-components/entity-form/entity-form";
import { EntityFieldEditComponent } from "#src/app/core/entity/entity-field-edit/entity-field-edit.component";
import { EntityFieldLabelComponent } from "#src/app/core/entity/entity-field-label/entity-field-label.component";
import {
  Component,
  computed,
  inject,
  Signal,
  signal,
  WritableSignal,
  ChangeDetectionStrategy,
} from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { MatButtonModule } from "@angular/material/button";
import { MatOptionModule } from "@angular/material/core";
import {
  MAT_DIALOG_DATA,
  MatDialogModule,
  MatDialogRef,
} from "@angular/material/dialog";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatTooltipModule } from "@angular/material/tooltip";
import { ConfigurableEnumService } from "#src/app/core/basic-datatypes/configurable-enum/configurable-enum.service";
import { ConfigurableEnumValue } from "#src/app/core/basic-datatypes/configurable-enum/configurable-enum.types";
import { Entity, EntityConstructor } from "#src/app/core/entity/model/entity";
import { EntitySchemaService } from "#src/app/core/entity/schema/entity-schema.service";
import { DialogCloseComponent } from "../../../../core/common-components/dialog-close/dialog-close.component";
import { DefaultValueConfigInheritedField } from "../../inherited-field-config";
import { FormFieldConfig } from "#src/app/core/common-components/entity-form/FormConfig";
import { EntityFieldSelectComponent } from "#src/app/core/entity/entity-field-select/entity-field-select.component";
import { EntitySchemaField } from "#src/app/core/entity/schema/entity-schema-field";
import { MatSlideToggle } from "@angular/material/slide-toggle";
import { ConfigurableEnumDatatype } from "#src/app/core/basic-datatypes/configurable-enum/configurable-enum-datatype/configurable-enum.datatype";

/**
 * Dialog to configure additional details for the "inherited-field"
 * default value strategy, working in combination with the admin components.
 */
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: "app-automated-field-mapping",
  imports: [
    MatOptionModule,
    MatFormFieldModule,
    MatDialogModule,
    MatButtonModule,
    MatTooltipModule,
    EntityFieldEditComponent,
    EntityFieldLabelComponent,
    DialogCloseComponent,
    EntityFieldSelectComponent,
    MatSlideToggle,
  ],
  templateUrl: "./automated-field-mapping.component.html",
  styleUrl: "./automated-field-mapping.component.scss",
})
export class AutomatedFieldMappingComponent {
  private readonly dialogRef = inject<MatDialogRef<any>>(MatDialogRef);
  private readonly configurableEnumService = inject(ConfigurableEnumService);
  private readonly schemaService = inject(EntitySchemaService);

  /** The full schema of the field for which this default value is configured */
  targetFieldConfig: FormFieldConfig;

  /** The entity type of the related entity that triggers the updates */
  sourceValueEntityType: EntityConstructor;

  value: DefaultValueConfigInheritedField;

  /** The currently selected "sourceValueField" on the related entity */
  selectedSourceValueField: WritableSignal<string | null>;

  sourceValueFieldSchema: Signal<EntitySchemaField | undefined> = computed(
    () => {
      const fieldId = this.selectedSourceValueField();
      if (!fieldId) return;
      return this.sourceValueEntityType.schema.get(fieldId);
    },
  );

  /**
   * The possible values of the selected sourceValueField that can be mapped to custom target values.
   * Currently mapping only supported for ConfigurableEnum fields.
   */
  valueMappingOptions: Signal<
    {
      sourceValue: ConfigurableEnumValue;
      sourceValueRaw: string;
      form: EntityForm<Entity>;
    }[]
  > = computed(() => {
    if (
      this.sourceValueFieldSchema()?.dataType !==
      ConfigurableEnumDatatype.dataType
    ) {
      // only configurable-enum fields supported currently
      return [];
    }

    const enumEntity = this.configurableEnumService.getEnum(
      this.sourceValueFieldSchema().additional,
    );

    const values = enumEntity?.values ?? [];
    return values.map((sourceValue) => {
      const sourceValueRaw = sourceValue.id; // database format of the source value

      // select existing mapping value if available
      let selectedMappedValue: any = this.value.valueMapping?.[sourceValueRaw];
      if (selectedMappedValue) {
        selectedMappedValue = this.schemaService.valueToEntityFormat(
          selectedMappedValue,
          this.targetFieldConfig,
        );
      }

      const formControl = new FormControl(selectedMappedValue);

      const formGroup = new FormGroup({
        [this.targetFieldConfig.id]: formControl,
      });

      return {
        sourceValue,
        sourceValueRaw,
        form: {
          formGroup,
        } as unknown as EntityForm<Entity>,
      };
    });
  });

  /**
   * If the user explicitly enabled the optional value mapping functionality
   */
  mappingEnabled = signal(false);

  isInvalidMapping: boolean = false;

  constructor() {
    const data = inject<AutomatedFieldMappingDialogData>(MAT_DIALOG_DATA);

    this.targetFieldConfig = {
      ...data.currentField,
      id: "targetValue", // simulate a faked full entity-form to use as basis for value mapping edit components
    } as FormFieldConfig;

    this.value = data.value;
    this.sourceValueEntityType = data.sourceValueEntityType;

    this.selectedSourceValueField = signal(
      this.value?.sourceValueField ?? null,
    );

    if (this.value?.valueMapping) {
      this.mappingEnabled.set(true);
    }
  }

  save() {
    const selectedMappings = {};
    if (this.mappingEnabled()) {
      this.isInvalidMapping = this.valueMappingOptions().some((v) => {
        v.form.formGroup.markAllAsTouched();
        return v.form.formGroup.invalid;
      });
      if (this.isInvalidMapping) return;

      this.valueMappingOptions().forEach(({ sourceValueRaw, form }) => {
        const value = form.formGroup.get(this.targetFieldConfig.id)?.value;
        selectedMappings[sourceValueRaw] =
          this.schemaService.valueToDatabaseFormat(
            value,
            this.targetFieldConfig,
          );
      });
    }

    const newValue: DefaultValueConfigInheritedField = {
      ...this.value,
      sourceValueField: this.selectedSourceValueField(),
      valueMapping: selectedMappings,
    };
    if (Object.keys(selectedMappings).length === 0) {
      // do not store empty mappings and delete any potentially existing mappings
      delete newValue.valueMapping;
    }

    this.dialogRef.close(newValue);
  }
}

/**
 * The DialogData for the `AutomatedFieldMappingComponent`
 */
export interface AutomatedFieldMappingDialogData {
  currentEntityType: EntityConstructor;
  currentField: EntitySchemaField;
  sourceValueEntityType: EntityConstructor;
  value: DefaultValueConfigInheritedField;
}
<h2 mat-dialog-title i18n>Configure Automation Rule</h2>
<app-dialog-close mat-dialog-close></app-dialog-close>

<mat-dialog-content>
  <p i18n>
    You are linking this field to the value of a related
    {{ sourceValueEntityType.label }} record. The system will show a dialog to
    automatically update this field whenever the related record's field changes.
    You can still manually adjust the value if needed. This helps you ensure
    data consistency and reduces manual work.
  </p>

  <p>
    (<span i18n>Field defining the related source record:</span>
    <app-entity-field-label
      [field]="value?.sourceReferenceField"
      [entityType]="sourceValueEntityType"
    ></app-entity-field-label
    >)
  </p>

  <mat-form-field
    appearance="fill"
    matTooltip="Choose the field in the related record that will trigger updates to this field when it is changed."
    i18n-matTooltip
  >
    <mat-label i18n
      >Source value field (of {{ sourceValueEntityType.label }})
    </mat-label>

    <app-entity-field-select
      [entityType]="sourceValueEntityType"
      [value]="selectedSourceValueField()"
      (valueChange)="
        selectedSourceValueField.set(
          typeof $event === 'object' ? $event[0] : $event
        )
      "
    ></app-entity-field-select>
  </mat-form-field>

  <!--
    Detailed trigger value -> target value mapping
  -->
  <mat-slide-toggle
    [disabled]="valueMappingOptions().length === 0"
    matTooltip="If the source is a dropdown field, you can optionally also define a custom mapping for specific values."
    i18n-matTooltip
    [checked]="mappingEnabled()"
    (change)="mappingEnabled.set($event.checked)"
    i18n
  >
    Transform value with custom mapping
  </mat-slide-toggle>

  @if (valueMappingOptions().length > 0 && mappingEnabled()) {
    <div class="mapping-grid">
      <div class="header-row">
        <h3 class="mapping-header" i18n>When "trigger field" changes to</h3>
        <h3 class="mapping-header" i18n>then this field is updated to</h3>
      </div>

      @for (v of valueMappingOptions(); track v.sourceValue.id) {
        <div class="option-label">{{ v.sourceValue.label }}</div>
        <div class="dropdown-select">
          @if (v.form) {
            <app-entity-field-edit
              [field]="targetFieldConfig"
              [form]="v.form"
            ></app-entity-field-edit>
          }
        </div>
      }
    </div>
  }
</mat-dialog-content>

<mat-dialog-actions class="flex-row flex-wrap">
  <button
    mat-button
    (click)="save()"
    [disabled]="!selectedSourceValueField()"
    i18n
  >
    Save
  </button>

  <button mat-button mat-dialog-close i18n>Cancel</button>

  @if (isInvalidMapping) {
    <mat-error i18n>
      The data is invalid, please check the fields in the "Configure mapping"
    </mat-error>
  }
</mat-dialog-actions>

./automated-field-mapping.component.scss

.mapping-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  align-items: center;
  margin-top: 20px;

  .header-row {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    margin-bottom: 12px;
  }

  .option-label {
    font-weight: 500;
    padding: 8px 0;
  }
}

:host ::ng-deep .mat-mdc-form-field {
  width: 100%;
}

.mapping-header {
  font-weight: bold;
}

.no-options-message {
  font-style: italic;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""