src/app/features/inherited-field/inherited-value-button/inherited-value-button.component.ts

Description

Display an indicator for form fields explaining the status of the inherited-value config of that field and allowing users to re-sync the inherited value manually.

Example

Metadata

Relationships

Index

Properties
Methods
Inputs

Constructor

constructor()

Inputs

entity
Type : Entity
field
Type : FormFieldConfig
form
Type : EntityForm<any>

Methods

Async updateLinkedRecords
updateLinkedRecords()
Returns : any

Properties

canUpdateLinkedRecords
Type : unknown
Default value : computed(() => { this.formValueChange(); const form = this.form(); const field = this.field(); return ( !!form?.entity && !!field && this.automatedFieldUpdateConfigService.hasLinkedRecordsToUpdate( form.entity, field.id, ) ); })

Whether this field's value is passed on to linked records, which can be updated through this button

defaultValueHint
Type : unknown
Default value : computed<DefaultValueHint | undefined>(() => { this.formValueChange(); const form = this.form(); const field = this.field(); if (!form || !field) { return undefined; } return this.defaultValueService.getDefaultValueUiHint(form, field.id); })

also updates on its own when asynchronously loaded details used by the hint are ready

isUpdatingLinkedRecords
Type : unknown
Default value : signal(false)

Whether the linked records are being loaded or are being reviewed in the dialog

import { EntityForm } from "../../../core/common-components/entity-form/entity-form";
import {
  Component,
  inject,
  ChangeDetectionStrategy,
  computed,
  effect,
  input,
  signal,
} from "@angular/core";
import { MatIconButton } from "@angular/material/button";
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
import { FormFieldConfig } from "../../../core/common-components/entity-form/FormConfig";
import { TemplateTooltipDirective } from "../../../core/common-components/template-tooltip/template-tooltip.directive";
import {
  DefaultValueHint,
  DefaultValueService,
} from "../../../core/default-values/default-value-service/default-value.service";
import { EntityFieldLabelComponent } from "../../../core/entity/entity-field-label/entity-field-label.component";
import { Entity } from "../../../core/entity/model/entity";
import { debounceTime } from "rxjs/operators";
import { AlertService } from "../../../core/alerts/alert.service";
import { Logging } from "../../../core/logging/logging.service";
import { AutomatedFieldUpdateConfigService } from "../automated-field-update/automated-field-update-config.service";
import { NoCommonValueHintComponent } from "../no-common-value-hint/no-common-value-hint.component";

/**
 * Display an indicator for form fields explaining the status of the inherited-value config of that field
 * and allowing users to re-sync the inherited value manually.
 */
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: "app-inherited-value-button",
  imports: [
    EntityFieldLabelComponent,
    FaIconComponent,
    NoCommonValueHintComponent,
    TemplateTooltipDirective,
    MatIconButton,
  ],
  templateUrl: "./inherited-value-button.component.html",
  styleUrl: "./inherited-value-button.component.scss",
})
export class InheritedValueButtonComponent {
  private readonly defaultValueService = inject(DefaultValueService);
  private readonly automatedFieldUpdateConfigService = inject(
    AutomatedFieldUpdateConfigService,
  );
  private readonly alertService = inject(AlertService);

  form = input<EntityForm<any>>();
  field = input<FormFieldConfig>();
  entity = input<Entity>();

  /** changes whenever a value in the form changes, because form values are not signals */
  private readonly formValueChange = signal(0);

  /** also updates on its own when asynchronously loaded details used by the hint are ready */
  defaultValueHint = computed<DefaultValueHint | undefined>(() => {
    this.formValueChange();
    const form = this.form();
    const field = this.field();
    if (!form || !field) {
      return undefined;
    }

    return this.defaultValueService.getDefaultValueUiHint(form, field.id);
  });

  /** Whether this field's value is passed on to linked records, which can be updated through this button */
  canUpdateLinkedRecords = computed(() => {
    this.formValueChange();
    const form = this.form();
    const field = this.field();
    return (
      !!form?.entity &&
      !!field &&
      this.automatedFieldUpdateConfigService.hasLinkedRecordsToUpdate(
        form.entity,
        field.id,
      )
    );
  });

  /** Whether the linked records are being loaded or are being reviewed in the dialog */
  isUpdatingLinkedRecords = signal(false);

  constructor() {
    effect((onCleanup) => {
      const form = this.form();
      if (!form) {
        return;
      }

      const sub = form.formGroup.valueChanges
        .pipe(debounceTime(50))
        .subscribe(() => this.formValueChange.update((count) => count + 1));
      onCleanup(() => sub.unsubscribe());
    });
  }

  async updateLinkedRecords() {
    this.isUpdatingLinkedRecords.set(true);
    try {
      const wereUpdated =
        await this.automatedFieldUpdateConfigService.updateLinkedRecords(
          this.form().entity,
          this.field().id,
        );
      if (!wereUpdated) {
        this.alertService.addInfo(
          $localize`The linked records are up to date.`,
        );
      }
    } catch (error) {
      Logging.error(
        "InheritedValueButton could not update the linked records",
        error,
      );
      this.alertService.addDanger(
        $localize`The linked records could not be updated.`,
      );
    } finally {
      this.isUpdatingLinkedRecords.set(false);
    }
  }
}
@if (canUpdateLinkedRecords()) {
  <div [appTemplateTooltip]="updateLinkedRecordsTooltip" class="inline-box">
    <button
      mat-icon-button
      type="button"
      [disabled]="form()?.formGroup?.enabled || isUpdatingLinkedRecords()"
      (click)="updateLinkedRecords()"
    >
      <fa-icon icon="circle-arrow-up"></fa-icon>
    </button>
  </div>
}

@if (defaultValueHint()) {
  <div [appTemplateTooltip]="tooltip" class="inline-box">
    <button
      mat-icon-button
      type="button"
      [disabled]="
        form()?.formGroup?.disabled ||
        defaultValueHint()?.isInSync ||
        defaultValueHint()?.isEmpty
      "
      (click)="defaultValueHint()?.syncFromParentField()"
    >
      <fa-icon
        [icon]="defaultValueHint()?.isInSync ? 'link' : 'link-slash'"
      ></fa-icon>
    </button>
  </div>
}

<ng-template #tooltip>
  <div class="field-hint">
    @if (defaultValueHint()?.isCollectedFromLinkedRecords) {
      <div i18n>
        Values collected from all records linking to this record in their field
        "<app-entity-field-label
          [field]="defaultValueHint()?.inheritedFromField"
          [entityType]="defaultValueHint()?.inheritedFromType"
        ></app-entity-field-label
        >"
      </div>
    } @else {
      <div i18n>
        Inherited value from parent record selected in field
        "<app-entity-field-label
          [field]="defaultValueHint()?.inheritedFromField"
          [entityType]="entity()?.getConstructor()"
        ></app-entity-field-label
        >"
      </div>
    }

    <br />

    @if (defaultValueHint()?.isCollectedFromLinkedRecords) {
      @if (defaultValueHint()?.isInSync) {
        <div i18n>(up-to-date, contains the values of all linked records)</div>
      } @else {
        <div i18n>
          (manually changed): click to replace with the values of all linked
          records
          @if (!form()?.formGroup?.enabled) {
            (requires the form to be in "Edit" mode)
          }
        </div>
      }
    } @else if (defaultValueHint()?.isLoading) {
      <div i18n>Loading the linked records ...</div>
    } @else if (defaultValueHint()?.noCommonValue; as reason) {
      <div><app-no-common-value-hint [reason]="reason" /></div>
    } @else if (defaultValueHint()?.isEmpty) {
      <div i18n>
        Select one or more records in the parent field to automatically inherit
        their shared value here.
      </div>
    } @else if (defaultValueHint()?.isInSync) {
      <div i18n>(up-to-date, inherited from parent record)</div>
    } @else {
      <div i18n>
        (manually overwritten): click to reset value to inherited from parent
        record
        @if (!form()?.formGroup?.enabled) {
          (requires the form to be in "Edit" mode)
        }
      </div>
      <div i18n></div>
    }
  </div>
</ng-template>

<ng-template #updateLinkedRecordsTooltip>
  <div class="field-hint">
    <div i18n>
      The value of this field is automatically passed on to linked records.
    </div>

    <br />

    <div i18n>
      Click to review and update them now
      @if (form()?.formGroup?.enabled) {
        (not available while editing)
      }
    </div>
  </div>
</ng-template>

./inherited-value-button.component.scss

.inline-box {
  display: inline-block;
}

.field-hint {
  color: white;
  font-size: 12px;

  border-radius: 4px;
  background-color: #3e3e3e;
  padding: 1em;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""