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>