src/app/features/notification/notification-rule/notification-rule.component.ts

Description

Configure a single notification rule.

Example

Metadata

Relationships

Depends on

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor()

Inputs

value
Type : NotificationRule

Outputs

removeNotificationRule
Type : void
value
Type : NotificationRule

Methods

initForm
initForm(value: NotificationRule)
Parameters :
Name Type Optional
value NotificationRule No
Returns : void
onConditionsChange
onConditionsChange(updatedConditions: any)

Handle conditions updates from the editor.

Parameters :
Name Type Optional
updatedConditions any No
Returns : void

Properties

entityConstructor
Type : unknown
Default value : signal<EntityConstructor | null>(null)
entityTypeControl
Type : AbstractControl
form
Type : FormGroup
import {
  ChangeDetectionStrategy,
  Component,
  effect,
  inject,
  model,
  output,
  signal,
} from "@angular/core";
import {
  AbstractControl,
  FormControl,
  FormGroup,
  ReactiveFormsModule,
} from "@angular/forms";
import { MatOption } from "@angular/material/core";
import {
  MatExpansionPanel,
  MatExpansionPanelHeader,
} from "@angular/material/expansion";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatInputModule } from "@angular/material/input";
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
import { MatSelect } from "@angular/material/select";
import { MatSlideToggle } from "@angular/material/slide-toggle";
import { MatButtonModule } from "@angular/material/button";
import { MatTooltipModule } from "@angular/material/tooltip";
import { FontAwesomeModule } from "@fortawesome/angular-fontawesome";
import { HelpButtonComponent } from "app/core/common-components/help-button/help-button.component";
import { EntityTypeSelectComponent } from "app/core/entity/entity-type-select/entity-type-select.component";
import { ConditionsEditorComponent } from "app/core/common-components/conditions-editor/conditions-editor.component";
import { EntityRegistry } from "app/core/entity/database-entity.decorator";
import { EntityConstructor } from "app/core/entity/model/entity";
import { IconButtonComponent } from "../../../core/common-components/icon-button/icon-button.component";
import { NotificationRule } from "../model/notification-config";

/**
 * Configure a single notification rule.
 */
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: "app-notification-rule",
  standalone: true,
  imports: [
    MatSlideToggle,
    MatInputModule,
    FontAwesomeModule,
    MatFormFieldModule,
    MatButtonModule,
    MatTooltipModule,
    EntityTypeSelectComponent,
    HelpButtonComponent,
    ReactiveFormsModule,
    MatProgressSpinnerModule,
    MatOption,
    MatSelect,
    MatExpansionPanel,
    MatExpansionPanelHeader,
    IconButtonComponent,
    ConditionsEditorComponent,
  ],
  templateUrl: "./notification-rule.component.html",
  styleUrl: "./notification-rule.component.scss",
})
export class NotificationRuleComponent {
  value = model<NotificationRule>();
  removeNotificationRule = output<void>();

  form: FormGroup;
  entityTypeControl: AbstractControl;
  entityConstructor = signal<EntityConstructor | null>(null);

  private readonly entityRegistry = inject(EntityRegistry);

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

      if (!this.form) {
        this.initForm(value);
        return;
      }

      this.form.patchValue(value, { emitEvent: false });
      this.updateEntityConstructor(this.entityTypeControl.value);
      this.setEntityTypeControlState(this.form.get("conditions")?.value);
    });
  }

  initForm(value: NotificationRule) {
    this.form = new FormGroup({
      label: new FormControl(value.label ?? ""),
      entityType: new FormControl({
        value: value.entityType ?? "",
        disabled: Object.keys(value.conditions ?? {}).length > 0,
      }),
      changeType: new FormControl(value.changeType ?? ["created", "updated"]),
      enabled: new FormControl(value.enabled || false),
      conditions: new FormControl(value.conditions ?? {}),
      notificationType: new FormControl(
        value.notificationType ?? "entity_change",
      ),
    });

    this.entityTypeControl = this.form.get("entityType");
    this.updateEntityConstructor(this.entityTypeControl.value);
    this.entityTypeControl.valueChanges.subscribe((entityType) =>
      this.updateEntityConstructor(entityType),
    );

    this.updateEntityTypeControlState();
    this.form.valueChanges.subscribe((newValue) => this.updateValue(newValue));
  }

  /**
   * Disable the entityType field if there are notification conditions.
   */
  private updateEntityTypeControlState() {
    const conditionsControl = this.form.get("conditions");

    if (!conditionsControl) {
      return;
    }

    this.setEntityTypeControlState(conditionsControl.value);
    conditionsControl.valueChanges.subscribe((v) =>
      this.setEntityTypeControlState(v),
    );
  }

  private setEntityTypeControlState(conditions: unknown) {
    const hasConditions =
      typeof conditions === "object" &&
      conditions !== null &&
      Object.keys(conditions).length > 0;
    if (hasConditions) {
      this.entityTypeControl.disable({ emitEvent: false });
    } else {
      this.entityTypeControl.enable({ emitEvent: false });
    }
  }

  private updateValue(value: NotificationRule) {
    const entityTypeControl = this.form.get("entityType");
    const nextValue = {
      ...value,
      entityType: entityTypeControl?.disabled
        ? entityTypeControl.value
        : value.entityType,
    };

    if (JSON.stringify(nextValue) === JSON.stringify(this.value())) {
      return;
    }

    this.value.set(nextValue);
  }

  /**
   * Handle conditions updates from the editor.
   */
  onConditionsChange(updatedConditions: any) {
    const conditionsForm = this.form.get("conditions");
    conditionsForm?.setValue(updatedConditions ?? {});
  }

  private updateEntityConstructor(entityType: string) {
    this.entityConstructor.set(
      entityType && this.entityRegistry.has(entityType)
        ? this.entityRegistry.get(entityType)
        : null,
    );
  }
}
<form [formGroup]="form">
  <mat-expansion-panel class="padding-regular">
    <mat-expansion-panel-header style="height: fit-content">
      <div
        class="flex-row align-baseline gap-regular margin-right-large full-width"
      >
        <mat-form-field class="full-width-field flex-grow">
          <mat-label
            i18n
            matTooltip="Give this notification rule a description to make it easier managing different notification types."
            i18n-matTooltip
          >
            Notify me about
          </mat-label>
          <input
            matInput
            formControlName="label"
            (keydown)="$event.stopPropagation()"
          />
        </mat-form-field>

        <mat-slide-toggle
          formControlName="enabled"
          class="notification-toggle"
          matTooltip='You can enable or disable rules to "pause" these notifications. You can also completely delete a rule below.'
          i18n-matTooltip
        ></mat-slide-toggle>
      </div>
    </mat-expansion-panel-header>

    <div class="flex-column gap-regular">
      <div>
        <div class="flex-row align-center">
          <strong i18n>Define Notification Criteria</strong>
          <app-help-button
            text="You can choose a notification based on the selected entity. For example, if you select 'task', you can create a notification for when a task is assigned to a user."
            i18n-text
          ></app-help-button>
        </div>

        <div class="flex-row">
          <mat-form-field
            class="full-width-field"
            matTooltip="You cannot change the record type once you have added conditions. Please first remove all conditions below."
            i18n-matTooltip
            [matTooltipDisabled]="!entityTypeControl.disabled"
            matTooltipPositionAtOrigin
            [matTooltipShowDelay]="500"
          >
            <mat-label i18n>Notify about changes for</mat-label>
            <app-entity-type-select
              formControlName="entityType"
            ></app-entity-type-select>
          </mat-form-field>
          <app-help-button
            text="Select the record type for which you want to get notified when data changes. For instance, choosing 'task' will enable notifications for newly created tasks in the system."
            i18n-text
          ></app-help-button>

          <mat-form-field>
            <mat-label i18n>Type of change</mat-label>

            <mat-select formControlName="changeType" multiple>
              <mat-option value="created" i18n>created</mat-option>
              <mat-option value="updated" i18n>updated</mat-option>
            </mat-select>
          </mat-form-field>
        </div>

        <!-- Conditions -->
        <app-conditions-editor
          [disabled]="!value()?.entityType"
          [conditions]="form.get('conditions')?.value"
          [entityConstructor]="entityConstructor()"
          (conditionsChange)="onConditionsChange($event)"
        ></app-conditions-editor>
      </div>

      <div class="flex-row gap-regular flex-wrap align-center">
        <app-icon-button
          buttonType="mat-stroked-button"
          color="warn"
          icon="trash"
          (buttonClick)="removeNotificationRule.emit()"
          i18n
        >
          Delete
        </app-icon-button>

        <!-- TODO: re-enable selection of "per-rule notification channels or remove completely (currently hidden to simplify UX)
        <mat-form-field class="flex-grow">
          <mat-label
            i18n
            matTooltip="Define a notification method to receive notifications."
            matTooltipPosition="above"
            i18n-matTooltip
            >Notification method(s)
            <fa-icon icon="question-circle"></fa-icon>
          </mat-label>

          <mat-select
            formControlName="channels"
            multiple
            placeholder="Choose notification channels"
            i18n-placeholder="Notification Method Select placeholder"
          >
            @for (notificationMethod of notificationMethods; track $index) {
              <mat-option [value]="notificationMethod.key">
                {{ notificationMethod.label }}
              </mat-option>
            }
          </mat-select>
        </mat-form-field>
        -->
      </div>
    </div>
  </mat-expansion-panel>
</form>

./notification-rule.component.scss

@use "../../../../styles/variables/breakpoints";
@use "variables/sizes";

.full-width {
  width: 100%;
}

.full-width-field {
  @extend .full-width;
  display: inline-block;

  ::ng-deep mat-form-field {
    @extend .full-width;
  }
}

.new-rule-condition-button {
  background-color: transparent;
  padding: sizes.$regular sizes.$large;
  cursor: pointer;
  border-radius: sizes.$large;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""