UI: add create notification and template from other wizard

This commit is contained in:
Vladyslav_Prykhodko 2023-02-10 18:31:05 +02:00
parent 6a0e8c03a4
commit 72348b049e
19 changed files with 249 additions and 78 deletions

View File

@ -49,7 +49,9 @@ import {
import { EscalationsComponent } from '@home/pages/notification-center/rule-table/escalations.component'; import { EscalationsComponent } from '@home/pages/notification-center/rule-table/escalations.component';
import { EscalationFormComponent } from '@home/pages/notification-center/rule-table/escalation-form.component'; import { EscalationFormComponent } from '@home/pages/notification-center/rule-table/escalation-form.component';
import { AlarmTypeListComponent } from '@home/pages/notification-center/rule-table/alarm-type-list.component'; import { AlarmTypeListComponent } from '@home/pages/notification-center/rule-table/alarm-type-list.component';
import { AlarmSeveritiesListComponent } from '@home/pages/notification-center/rule-table/alarm-severities-list.component'; import {
AlarmSeveritiesListComponent
} from '@home/pages/notification-center/rule-table/alarm-severities-list.component';
@NgModule({ @NgModule({
declarations: [ declarations: [

View File

@ -49,6 +49,11 @@
placeholderText="{{ 'notification.target' | translate }}" placeholderText="{{ 'notification.target' | translate }}"
requiredText="{{ 'notification.targets-required' | translate }}" requiredText="{{ 'notification.targets-required' | translate }}"
[entityType]="entityType.NOTIFICATION_TARGET"> [entityType]="entityType.NOTIFICATION_TARGET">
<button #createTargetButton
mat-button color="primary" matSuffix
(click)="createTarget($event, createTargetButton)">
{{ 'notification.create-new' | translate }}
</button>
</tb-entity-list> </tb-entity-list>
<section formGroupName="additionalConfig" class="additional-config-group"> <section formGroupName="additionalConfig" class="additional-config-group">
<mat-slide-toggle formControlName="enabled" class="toggle"> <mat-slide-toggle formControlName="enabled" class="toggle">

View File

@ -14,13 +14,18 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { NotificationRequest, NotificationRequestPreview, NotificationType } from '@shared/models/notification.models'; import {
NotificationRequest,
NotificationRequestPreview,
NotificationTarget,
NotificationType
} from '@shared/models/notification.models';
import { Component, Inject, OnDestroy, ViewChild } from '@angular/core'; import { Component, Inject, OnDestroy, ViewChild } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component'; import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service'; import { NotificationService } from '@core/http/notification.service';
import { deepTrim } from '@core/utils'; import { deepTrim } from '@core/utils';
@ -32,6 +37,11 @@ import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper'
import { MediaBreakpoints } from '@shared/models/constants'; import { MediaBreakpoints } from '@shared/models/constants';
import { map, takeUntil } from 'rxjs/operators'; import { map, takeUntil } from 'rxjs/operators';
import { getCurrentTime } from '@shared/models/time/time.models'; import { getCurrentTime } from '@shared/models/time/time.models';
import {
TargetNotificationDialogComponent,
TargetsNotificationDialogData
} from '@home/pages/notification-center/targets-table/target-notification-dialog.componet';
import { MatButton } from '@angular/material/button';
export interface RequestNotificationDialogData { export interface RequestNotificationDialogData {
request?: NotificationRequest; request?: NotificationRequest;
@ -66,7 +76,8 @@ export class RequestNotificationDialogComponent extends
@Inject(MAT_DIALOG_DATA) public data: RequestNotificationDialogData, @Inject(MAT_DIALOG_DATA) public data: RequestNotificationDialogData,
private breakpointObserver: BreakpointObserver, private breakpointObserver: BreakpointObserver,
private fb: FormBuilder, private fb: FormBuilder,
private notificationService: NotificationService) { private notificationService: NotificationService,
private dialog: MatDialog) {
super(store, router, dialogRef); super(store, router, dialogRef);
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs']) this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs'])
@ -199,4 +210,27 @@ export class RequestNotificationDialogComponent extends
date.setDate(date.getDate() + 7); date.setDate(date.getDate() + 7);
return date; return date;
} }
createTarget($event: Event, button: MatButton) {
if ($event) {
$event.stopPropagation();
}
button._elementRef.nativeElement.blur();
this.dialog.open<TargetNotificationDialogComponent, TargetsNotificationDialogData,
NotificationTarget>(TargetNotificationDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {}
}).afterClosed()
.subscribe((res) => {
if (res) {
let formValue: string[] = this.notificationRequestForm.get('targets').value;
if (!formValue) {
formValue = [];
}
formValue.push(res.id.id);
this.notificationRequestForm.get('targets').patchValue(formValue);
}
});
}
} }

View File

@ -19,8 +19,9 @@
<form [formGroup]="escalationFormGroup" fxLayout="column"> <form [formGroup]="escalationFormGroup" fxLayout="column">
<div class="escalation" fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center center" fxLayoutAlign.xs="start"> <div class="escalation" fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center center" fxLayoutAlign.xs="start">
<div fxFlex fxLayout="row" ngStyle.xs="padding-top: 10px"> <div fxFlex fxLayout="row" ngStyle.xs="padding-top: 10px">
<div fxFlex *ngIf="systemEscalation" class="escalation-padding" translate>notification.first-recipient</div> <div fxFlex *ngIf="systemEscalation; else selectTime" class="escalation-padding" translate>notification.first-recipient</div>
<div fxFlex *ngIf="!systemEscalation" fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center center"> <ng-template #selectTime>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center center">
<span class="escalation-padding">After</span> <span class="escalation-padding">After</span>
<tb-timeinterval <tb-timeinterval
style="min-width: 100px;" style="min-width: 100px;"
@ -31,13 +32,19 @@
disabledAdvanced></tb-timeinterval> disabledAdvanced></tb-timeinterval>
<span fxFlex class="escalation-notify" translate>notification.notify</span> <span fxFlex class="escalation-notify" translate>notification.notify</span>
</div> </div>
</ng-template>
</div> </div>
<div fxFlex class="escalation-padding"> <div fxFlex="60" class="escalation-padding">
<tb-entity-list <tb-entity-list
required required
formControlName="targets" formControlName="targets"
[entityType]="entityType.NOTIFICATION_TARGET" [entityType]="entityType.NOTIFICATION_TARGET"
placeholderText="{{ 'notification.add-target' | translate }}"> placeholderText="{{ 'notification.add-target' | translate }}">
<button #createTargetButton
mat-button [fxHide]="disabled" matSuffix
(click)="createTarget($event, createTargetButton)">
{{ 'notification.create-new' | translate }}
</button>
</tb-entity-list> </tb-entity-list>
</div> </div>
</div> </div>

View File

@ -32,3 +32,13 @@
padding: 0 10px; padding: 0 10px;
} }
} }
:host ::ng-deep {
tb-timeinterval {
min-width: 100px;
.mat-form-field-infix {
width: 100%;
}
}
}

View File

@ -25,12 +25,17 @@ import {
Validator, Validator,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { UtilsService } from '@core/services/utils.service';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { NonConfirmedNotificationEscalation } from '@shared/models/notification.models'; import { NonConfirmedNotificationEscalation, NotificationTarget } from '@shared/models/notification.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import {
TargetNotificationDialogComponent,
TargetsNotificationDialogData
} from '@home/pages/notification-center/targets-table/target-notification-dialog.componet';
import { MatDialog } from '@angular/material/dialog';
import { MatButton } from '@angular/material/button';
@Component({ @Component({
selector: 'tb-escalation-form', selector: 'tb-escalation-form',
@ -66,8 +71,8 @@ export class EscalationFormComponent implements ControlValueAccessor, OnInit, On
private propagateChangePending = false; private propagateChangePending = false;
private destroy$ = new Subject(); private destroy$ = new Subject();
constructor(private utils: UtilsService, constructor(private fb: FormBuilder,
private fb: FormBuilder) { private dialog: MatDialog) {
} }
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
@ -120,6 +125,29 @@ export class EscalationFormComponent implements ControlValueAccessor, OnInit, On
} }
} }
createTarget($event: Event, button: MatButton) {
if ($event) {
$event.stopPropagation();
}
button._elementRef.nativeElement.blur();
this.dialog.open<TargetNotificationDialogComponent, TargetsNotificationDialogData,
NotificationTarget>(TargetNotificationDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {}
}).afterClosed()
.subscribe((res) => {
if (res) {
let formValue: string[] = this.escalationFormGroup.get('targets').value;
if (!formValue) {
formValue = [];
}
formValue.push(res.id.id);
this.escalationFormGroup.get('targets').patchValue(formValue);
}
});
}
public validate(c: FormControl) { public validate(c: FormControl) {
return (this.escalationFormGroup.valid) ? null : { return (this.escalationFormGroup.valid) ? null : {
escalation: { escalation: {

View File

@ -21,7 +21,10 @@
*ngFor="let escalationControl of escalationsFormArray.controls; *ngFor="let escalationControl of escalationsFormArray.controls;
let $index = index; last as isLast;" let $index = index; last as isLast;"
[ngStyle]="!isLast ? {paddingBottom: '8px'} : {}"> [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-escalation-form fxFlex [formControl]="escalationControl" [systemEscalation]="$index === 0"></tb-escalation-form> <tb-escalation-form fxFlex
[formControl]="escalationControl"
[systemEscalation]="$index === 0">
</tb-escalation-form>
<span *ngIf="$index === 0" style="width: 40px;"></span> <span *ngIf="$index === 0" style="width: 40px;"></span>
<button *ngIf="!($index === 0) && !disabled" mat-icon-button style="min-width: 40px;" <button *ngIf="!($index === 0) && !disabled" mat-icon-button style="min-width: 40px;"
type="button" type="button"

View File

@ -31,7 +31,6 @@ import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state'; import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { UtilsService } from '@core/services/utils.service';
import { NonConfirmedNotificationEscalation } from '@shared/models/notification.models'; import { NonConfirmedNotificationEscalation } from '@shared/models/notification.models';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -79,7 +78,6 @@ export class EscalationsComponent implements ControlValueAccessor, Validator, On
private propagateChange = (v: any) => { }; private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
private utils: UtilsService,
private fb: FormBuilder) { private fb: FormBuilder) {
} }
@ -120,15 +118,18 @@ export class EscalationsComponent implements ControlValueAccessor, Validator, On
writeValue(escalations: {[key: string]: Array<string>} | null): void { writeValue(escalations: {[key: string]: Array<string>} | null): void {
const escalationParse: Array<NonConfirmedNotificationEscalation> = []; const escalationParse: Array<NonConfirmedNotificationEscalation> = [];
// tslint:disable-next-line:forin
for (const escalation in escalations) { for (const escalation in escalations) {
escalationParse.push({delayInSec: Number(escalation), targets: escalations[escalation]}); escalationParse.push({delayInSec: Number(escalation), targets: escalations[escalation]});
} }
if (escalationParse?.length === this.escalationsFormArray.length) { if (escalationParse.length === 0) {
this.addEscalation();
} else if (escalationParse?.length === this.escalationsFormArray.length) {
this.escalationsFormArray.patchValue(escalationParse, {emitEvent: false}); this.escalationsFormArray.patchValue(escalationParse, {emitEvent: false});
} else { } else {
const escalationsControls: Array<AbstractControl> = []; const escalationsControls: Array<AbstractControl> = [];
if (escalationParse) { if (escalationParse) {
escalationParse.forEach((escalation, index) => { escalationParse.forEach(escalation => {
escalationsControls.push(this.fb.control(escalation, [Validators.required])); escalationsControls.push(this.fb.control(escalation, [Validators.required]));
}); });
} else { } else {

View File

@ -56,11 +56,32 @@
</mat-form-field> </mat-form-field>
<tb-template-autocomplete <tb-template-autocomplete
required required
allowCreate
formControlName="templateId" formControlName="templateId"
[notificationTypes]="ruleNotificationForm.get('triggerType').value" [notificationTypes]="ruleNotificationForm.get('triggerType').value"
labelText="notification.template-name" labelText="notification.template-name"
requiredText="notification.template-required"> requiredText="notification.template-required">
</tb-template-autocomplete> </tb-template-autocomplete>
<section formGroupName="recipientsConfig"
*ngIf="ruleNotificationForm.get('triggerType').value !== triggerType.ALARM; else alarmTargesConfig">
<tb-entity-list
required
formControlName="targets"
[entityType]="entityType.NOTIFICATION_TARGET"
placeholderText="{{ 'notification.target' | translate }}">
<button #createTargetButton
mat-button color="primary" matSuffix
(click)="createTarget($event, createTargetButton)">
{{ 'notification.create-new' | translate }}
</button>
</tb-entity-list>
</section>
<ng-template #alarmTargesConfig>
<fieldset class="fields-group" formGroupName="recipientsConfig">
<legend translate>notification.hierarchy-of-receiving</legend>
<tb-escalations-component formControlName="escalationTable"></tb-escalations-component>
</fieldset>
</ng-template>
</form> </form>
</mat-step> </mat-step>
<mat-step [stepControl]="alarmTemplateForm" <mat-step [stepControl]="alarmTemplateForm"
@ -94,11 +115,6 @@
</mat-form-field> </mat-form-field>
</fieldset> </fieldset>
</section> </section>
<fieldset class="fields-group" formGroupName="recipientsConfig">
<legend translate>notification.hierarchy-of-receiving</legend>
<tb-escalations-component formControlName="escalationTable"></tb-escalations-component>
</fieldset>
</form> </form>
<form [formGroup]="ruleNotificationForm"> <form [formGroup]="ruleNotificationForm">
<section formGroupName="additionalConfig"> <section formGroupName="additionalConfig">
@ -143,14 +159,6 @@
<div class="tb-hint" translate>notification.device-profiles-list-rule-hint</div> <div class="tb-hint" translate>notification.device-profiles-list-rule-hint</div>
</ng-template> </ng-template>
</section> </section>
<section formGroupName="recipientsConfig">
<tb-entity-list
required
formControlName="targets"
[entityType]="entityType.NOTIFICATION_TARGET"
placeholderText="{{ 'notification.target' | translate }}">
</tb-entity-list>
</section>
</form> </form>
<form [formGroup]="ruleNotificationForm"> <form [formGroup]="ruleNotificationForm">
<section formGroupName="additionalConfig"> <section formGroupName="additionalConfig">
@ -180,14 +188,6 @@
<mat-slide-toggle formControlName="deleted">{{ 'notification.deleted' | translate }}</mat-slide-toggle> <mat-slide-toggle formControlName="deleted">{{ 'notification.deleted' | translate }}</mat-slide-toggle>
</section> </section>
</fieldset> </fieldset>
<section formGroupName="recipientsConfig">
<tb-entity-list
required
formControlName="targets"
[entityType]="entityType.NOTIFICATION_TARGET"
placeholderText="notification.target">
</tb-entity-list>
</section>
</form> </form>
<form [formGroup]="ruleNotificationForm"> <form [formGroup]="ruleNotificationForm">
<section formGroupName="additionalConfig"> <section formGroupName="additionalConfig">
@ -203,14 +203,7 @@
[stepControl]="alarmCommentTemplateForm"> [stepControl]="alarmCommentTemplateForm">
<ng-template matStepLabel>{{ 'notification.alarm-comment-trigger-settings' | translate }}</ng-template> <ng-template matStepLabel>{{ 'notification.alarm-comment-trigger-settings' | translate }}</ng-template>
<form [formGroup]="alarmCommentTemplateForm"> <form [formGroup]="alarmCommentTemplateForm">
<section formGroupName="recipientsConfig">
<tb-entity-list
required
formControlName="targets"
[entityType]="entityType.NOTIFICATION_TARGET"
placeholderText="{{ 'notification.target' | translate }}">
</tb-entity-list>
</section>
</form> </form>
<form [formGroup]="ruleNotificationForm"> <form [formGroup]="ruleNotificationForm">
<section formGroupName="additionalConfig">; <section formGroupName="additionalConfig">;

View File

@ -14,13 +14,18 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { NotificationRule, TriggerType, TriggerTypeTranslationMap } from '@shared/models/notification.models'; import {
NotificationRule,
NotificationTarget,
TriggerType,
TriggerTypeTranslationMap
} from '@shared/models/notification.models';
import { Component, Inject, OnDestroy, ViewChild } from '@angular/core'; import { Component, Inject, OnDestroy, ViewChild } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component'; import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service'; import { NotificationService } from '@core/http/notification.service';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
@ -33,6 +38,11 @@ import { MediaBreakpoints } from '@shared/models/constants';
import { BreakpointObserver } from '@angular/cdk/layout'; import { BreakpointObserver } from '@angular/cdk/layout';
import { AlarmStatus, alarmStatusTranslations } from '@shared/models/alarm.models'; import { AlarmStatus, alarmStatusTranslations } from '@shared/models/alarm.models';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import {
TargetNotificationDialogComponent,
TargetsNotificationDialogData
} from '@home/pages/notification-center/targets-table/target-notification-dialog.componet';
import { MatButton } from '@angular/material/button';
export interface RuleNotificationDialogData { export interface RuleNotificationDialogData {
rule?: NotificationRule; rule?: NotificationRule;
@ -86,7 +96,8 @@ export class RuleNotificationDialogComponent extends
private breakpointObserver: BreakpointObserver, private breakpointObserver: BreakpointObserver,
private fb: FormBuilder, private fb: FormBuilder,
public translate: TranslateService, public translate: TranslateService,
private notificationService: NotificationService) { private notificationService: NotificationService,
private dialog: MatDialog) {
super(store, router, dialogRef); super(store, router, dialogRef);
if (isDefined(data.isAdd)) { if (isDefined(data.isAdd)) {
@ -100,13 +111,28 @@ export class RuleNotificationDialogComponent extends
name: [null, Validators.required], name: [null, Validators.required],
templateId: [null, Validators.required], templateId: [null, Validators.required],
triggerType: [TriggerType.ALARM, Validators.required], triggerType: [TriggerType.ALARM, Validators.required],
recipientsConfig: [null], recipientsConfig: this.fb.group({
targets: [{value: null, disabled: true}, Validators.required],
escalationTable: [null, Validators.required]
}),
triggerConfig: [null], triggerConfig: [null],
additionalConfig: this.fb.group({ additionalConfig: this.fb.group({
description: [''] description: ['']
}) })
}); });
this.ruleNotificationForm.get('triggerType').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(value => {
if (value === TriggerType.ALARM) {
this.ruleNotificationForm.get('recipientsConfig.escalationTable').enable({emitEvent: false});
this.ruleNotificationForm.get('recipientsConfig.targets').disable({emitEvent: false});
} else {
this.ruleNotificationForm.get('recipientsConfig.escalationTable').disable({emitEvent: false});
this.ruleNotificationForm.get('recipientsConfig.targets').enable({emitEvent: false});
}
});
this.alarmTemplateForm = this.fb.group({ this.alarmTemplateForm = this.fb.group({
triggerConfig: this.fb.group({ triggerConfig: this.fb.group({
alarmTypes: [null], alarmTypes: [null],
@ -114,9 +140,6 @@ export class RuleNotificationDialogComponent extends
clearRule: this.fb.group({ clearRule: this.fb.group({
alarmStatus: [null] alarmStatus: [null]
}) })
}),
recipientsConfig: this.fb.group({
escalationTable: []
}) })
}); });
@ -125,9 +148,6 @@ export class RuleNotificationDialogComponent extends
filterByDevice: [true], filterByDevice: [true],
devices: [null], devices: [null],
deviceProfiles: [{value: null, disabled: true}] deviceProfiles: [{value: null, disabled: true}]
}),
recipientsConfig: this.fb.group({
targets: [[], Validators.required]
}) })
}); });
@ -149,17 +169,10 @@ export class RuleNotificationDialogComponent extends
created: [false], created: [false],
updated: [false], updated: [false],
deleted: [false] deleted: [false]
}),
recipientsConfig: this.fb.group({
targets: [[], Validators.required]
}) })
}); });
this.alarmCommentTemplateForm = this.fb.group({ this.alarmCommentTemplateForm = this.fb.group({ });
recipientsConfig: this.fb.group({
targets: [[], Validators.required]
})
});
this.triggerTypeFormsMap = new Map<TriggerType, FormGroup>([ this.triggerTypeFormsMap = new Map<TriggerType, FormGroup>([
[TriggerType.ALARM, this.alarmTemplateForm], [TriggerType.ALARM, this.alarmTemplateForm],
@ -179,6 +192,7 @@ export class RuleNotificationDialogComponent extends
} }
this.ruleNotificationForm.reset({}, {emitEvent: false}); this.ruleNotificationForm.reset({}, {emitEvent: false});
this.ruleNotificationForm.patchValue(this.ruleNotification, {emitEvent: false}); this.ruleNotificationForm.patchValue(this.ruleNotification, {emitEvent: false});
this.ruleNotificationForm.get('triggerType').updateValueAndValidity({onlySelf: true});
const currentForm = this.triggerTypeFormsMap.get(this.ruleNotification.triggerType); const currentForm = this.triggerTypeFormsMap.get(this.ruleNotification.triggerType);
currentForm.patchValue(this.ruleNotification, {emitEvent: false}); currentForm.patchValue(this.ruleNotification, {emitEvent: false});
if (this.ruleNotification.triggerType === TriggerType.DEVICE_INACTIVITY) { if (this.ruleNotification.triggerType === TriggerType.DEVICE_INACTIVITY) {
@ -188,6 +202,12 @@ export class RuleNotificationDialogComponent extends
} }
} }
ngOnDestroy() {
super.ngOnDestroy();
this.destroy$.next();
this.destroy$.complete();
}
changeStep($event: StepperSelectionEvent) { changeStep($event: StepperSelectionEvent) {
this.selectedIndex = $event.selectedIndex; this.selectedIndex = $event.selectedIndex;
} }
@ -247,13 +267,30 @@ export class RuleNotificationDialogComponent extends
}); });
} }
ngOnDestroy() {
super.ngOnDestroy();
this.destroy$.next();
this.destroy$.complete();
}
cancel(): void { cancel(): void {
this.dialogRef.close(null); this.dialogRef.close(null);
} }
createTarget($event: Event, button: MatButton) {
if ($event) {
$event.stopPropagation();
}
button._elementRef.nativeElement.blur();
this.dialog.open<TargetNotificationDialogComponent, TargetsNotificationDialogData,
NotificationTarget>(TargetNotificationDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {}
}).afterClosed()
.subscribe((res) => {
if (res) {
let formValue: string[] = this.ruleNotificationForm.get('recipientsConfig.targets').value;
if (!formValue) {
formValue = [];
}
formValue.push(res.id.id);
this.ruleNotificationForm.get('recipientsConfig.targets').patchValue(formValue);
}
});
}
} }

View File

@ -145,7 +145,6 @@ export class TargetNotificationDialogComponent extends
if (isDefined(this.data.target)) { if (isDefined(this.data.target)) {
formValue = Object.assign({}, this.data.target, formValue); formValue = Object.assign({}, this.data.target, formValue);
} }
formValue.type = formValue.configuration.type;
this.notificationService.saveNotificationTarget(formValue).subscribe( this.notificationService.saveNotificationTarget(formValue).subscribe(
(target) => this.dialogRef.close(target) (target) => this.dialogRef.close(target)
); );

View File

@ -28,6 +28,12 @@
(click)="clear()"> (click)="clear()">
<mat-icon class="material-icons">close</mat-icon> <mat-icon class="material-icons">close</mat-icon>
</button> </button>
<button #createTemplateButton
mat-button color="primary" matSuffix
*ngIf="allowCreate && !selectTemplateFormGroup.get('templateName').value && !disabled"
(click)="createTemplate($event, createTemplateButton)">
{{ 'notification.create-new' | translate }}
</button>
<mat-autocomplete class="tb-autocomplete" <mat-autocomplete class="tb-autocomplete"
#templateAutocomplete="matAutocomplete" #templateAutocomplete="matAutocomplete"
[displayWith]="displayTemplateFn" [displayWith]="displayTemplateFn"

View File

@ -35,6 +35,12 @@ import {
} from '@shared/models/notification.models'; } from '@shared/models/notification.models';
import { NotificationService } from '@core/http/notification.service'; import { NotificationService } from '@core/http/notification.service';
import { isEqual } from '@core/utils'; import { isEqual } from '@core/utils';
import {
TemplateNotificationDialogComponent,
TemplateNotificationDialogData
} from '@home/pages/notification-center/template-table/template-notification-dialog.component';
import { MatDialog } from '@angular/material/dialog';
import { MatButton } from '@angular/material/button';
@Component({ @Component({
selector: 'tb-template-autocomplete', selector: 'tb-template-autocomplete',
@ -66,6 +72,16 @@ export class TemplateAutocompleteComponent implements ControlValueAccessor, OnIn
this.requiredValue = coerceBooleanProperty(value); this.requiredValue = coerceBooleanProperty(value);
} }
private allowCreateValue = false;
get allowCreate(): boolean {
return this.allowCreateValue;
}
@Input()
set allowCreate(value: boolean) {
this.allowCreateValue = coerceBooleanProperty(value);
}
@Input() @Input()
disabled: boolean; disabled: boolean;
@ -97,7 +113,8 @@ export class TemplateAutocompleteComponent implements ControlValueAccessor, OnIn
public truncate: TruncatePipe, public truncate: TruncatePipe,
private entityService: EntityService, private entityService: EntityService,
private notificationService: NotificationService, private notificationService: NotificationService,
private fb: FormBuilder) { private fb: FormBuilder,
private dialog: MatDialog) {
this.selectTemplateFormGroup = this.fb.group({ this.selectTemplateFormGroup = this.fb.group({
templateName: [null] templateName: [null]
}); });
@ -192,6 +209,26 @@ export class TemplateAutocompleteComponent implements ControlValueAccessor, OnIn
}, 0); }, 0);
} }
createTemplate($event: Event, button: MatButton) {
if ($event) {
$event.stopPropagation();
}
button._elementRef.nativeElement.blur();
this.dialog.open<TemplateNotificationDialogComponent, TemplateNotificationDialogData,
NotificationTemplate>(TemplateNotificationDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
predefinedType: this.notificationTypes
}
}).afterClosed()
.subscribe((res) => {
if (res) {
this.selectTemplateFormGroup.get('templateName').patchValue(res);
}
});
}
private updateView(value: EntityId | null) { private updateView(value: EntityId | null) {
if (!isEqual(this.modelValue, value)) { if (!isEqual(this.modelValue, value)) {
this.modelValue = value; this.modelValue = value;

View File

@ -43,7 +43,7 @@
{{ 'notification.target-name-required' | translate }} {{ 'notification.target-name-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex class="mat-block" *ngIf="!hideSelectType">
<mat-label translate>notification.type</mat-label> <mat-label translate>notification.type</mat-label>
<mat-select formControlName="notificationType"> <mat-select formControlName="notificationType">
<mat-option *ngFor="let notificationType of notificationTypes" [value]="notificationType"> <mat-option *ngFor="let notificationType of notificationTypes" [value]="notificationType">

View File

@ -29,7 +29,7 @@ import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service'; import { NotificationService } from '@core/http/notification.service';
import { deepClone, deepTrim } from '@core/utils'; import { deepClone, deepTrim, isDefinedAndNotNull } from '@core/utils';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators'; import { map, takeUntil } from 'rxjs/operators';
import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper'; import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper';
@ -40,6 +40,7 @@ import { TranslateService } from '@ngx-translate/core';
export interface TemplateNotificationDialogData { export interface TemplateNotificationDialogData {
template?: NotificationTemplate; template?: NotificationTemplate;
predefinedType?: NotificationType;
isAdd?: boolean; isAdd?: boolean;
isCopy?: boolean; isCopy?: boolean;
} }
@ -69,6 +70,7 @@ export class TemplateNotificationDialogComponent
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap; notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
selectedIndex = 0; selectedIndex = 0;
hideSelectType = false;
tinyMceOptions: Record<string, any> = { tinyMceOptions: Record<string, any> = {
base_url: '/assets/tinymce', base_url: '/assets/tinymce',
@ -100,9 +102,13 @@ export class TemplateNotificationDialogComponent
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs']) this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs'])
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical')); .pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
if (isDefinedAndNotNull(this.data?.predefinedType)) {
this.hideSelectType = true;
}
this.templateNotificationForm = this.fb.group({ this.templateNotificationForm = this.fb.group({
name: ['', Validators.required], name: ['', Validators.required],
notificationType: [NotificationType.GENERAL], notificationType: [this.hideSelectType ? this.data.predefinedType : NotificationType.GENERAL],
configuration: this.fb.group({ configuration: this.fb.group({
notificationSubject: ['', Validators.required], notificationSubject: ['', Validators.required],
defaultTextTemplate: ['', Validators.required], defaultTextTemplate: ['', Validators.required],

View File

@ -52,4 +52,7 @@
<mat-error *ngIf="entityListFormGroup.get('entities').hasError('required')"> <mat-error *ngIf="entityListFormGroup.get('entities').hasError('required')">
{{ requiredText }} {{ requiredText }}
</mat-error> </mat-error>
<div matSuffix>
<ng-content select="[matSuffix]"></ng-content>
</div>
</mat-form-field> </mat-form-field>

View File

@ -131,7 +131,6 @@ export interface NonConfirmedNotificationEscalation {
export interface NotificationTarget extends Omit<BaseData<NotificationTargetId>, 'label'>{ export interface NotificationTarget extends Omit<BaseData<NotificationTargetId>, 'label'>{
tenantId: TenantId; tenantId: TenantId;
type: NotificationTargetType;
configuration: NotificationTargetConfig; configuration: NotificationTargetConfig;
} }

View File

@ -39,14 +39,14 @@ export class DateAgoPipe implements PipeTransform {
transform(value: number): string { transform(value: number): string {
if (value) { if (value) {
const seconds = Math.floor((+new Date() - +new Date(value)) / 1000); const ms = Math.floor((+new Date() - +new Date(value)));
if (seconds < 29) { // less than 30 seconds ago will show as 'Just now' if (ms < 29 * SECOND) { // less than 30 seconds ago will show as 'Just now'
return this.translate.instant('timewindow.just-now'); return this.translate.instant('timewindow.just-now');
} }
let counter; let counter;
// tslint:disable-next-line:forin // tslint:disable-next-line:forin
for (const i in intervals) { for (const i in intervals) {
counter = Math.floor(seconds / intervals[i]); counter = Math.floor(ms / intervals[i]);
if (counter > 0) { if (counter > 0) {
return this.translate.instant(`timewindow.${i}`, {[i]: counter}); return this.translate.instant(`timewindow.${i}`, {[i]: counter});
} }

View File

@ -2710,6 +2710,7 @@
"copy-rule": "Copy rule", "copy-rule": "Copy rule",
"copy-template": "Copy template", "copy-template": "Copy template",
"create-target": "Create recipient", "create-target": "Create recipient",
"create-new": "Create new",
"created": "Created", "created": "Created",
"created-time": "Created time", "created-time": "Created time",
"delete-request-text": "Be careful, after the confirmation the notification request will become unrecoverable.", "delete-request-text": "Be careful, after the confirmation the notification request will become unrecoverable.",