Browse Source

UI: Add support create template without save when create notification

pull/7911/head
Vladyslav_Prykhodko 4 years ago
parent
commit
7d6fcd85c0
  1. 191
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.html
  2. 69
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.scss
  3. 68
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.componet.ts
  4. 4
      ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html
  5. 2
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.html
  6. 6
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.ts
  7. 172
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-configuration.ts
  8. 124
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts
  9. 5
      ui-ngx/src/assets/locale/locale.constant-en_US.json

191
ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.html

@ -37,11 +37,54 @@
<mat-step [stepControl]="notificationRequestForm">
<ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template>
<form [formGroup]="notificationRequestForm" style="padding-bottom: 16px;">
<div fxLayout="row" fxLayoutAlign="center">
<mat-button-toggle-group class="tb-notification-use-template-toggle-group"
style="width: 320px;"
formControlName="useTemplate">
<mat-button-toggle fxFlex [value]=true>{{ 'notification.use-template' | translate }}</mat-button-toggle>
<mat-button-toggle fxFlex [value]=false>{{ 'notification.start-from-scratch' | translate }}</mat-button-toggle>
</mat-button-toggle-group>
</div>
<tb-template-autocomplete
*ngIf="notificationRequestForm.get('useTemplate').value; else scratchTemplate"
required
[notificationTypes]="notificationType.GENERAL"
formControlName="templateId">
allowCreate
formControlName="templateId"
[notificationTypes]="notificationType.GENERAL">
</tb-template-autocomplete>
<ng-template #scratchTemplate>
<section formGroupName="template">
<section formGroupName="configuration">
<mat-form-field class="mat-block">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="notificationSubject" required>
<mat-error *ngIf="notificationRequestForm.get('template.configuration.notificationSubject').hasError('required')">
{{ 'notification.subject-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>notification.message</mat-label>
<input matInput formControlName="defaultTextTemplate" required>
<mat-error *ngIf="notificationRequestForm.get('template.configuration.defaultTextTemplate').hasError('required')">
{{ 'notification.message-required' | translate }}
</mat-error>
</mat-form-field>
<label class="tb-title tb-required"
[ngClass]="{'tb-error': notificationRequestForm.get('template.configuration.deliveryMethodsTemplates').hasError('atLeastOne')}">
{{ "notification.delivery-methods" | translate }}
</label>
<section formGroupName="deliveryMethodsTemplates" fxLayout="row warap">
<section *ngFor="let deliveryMethods of notificationDeliveryMethods; even as isEven" class="delivery-method-container"
[formGroupName]="deliveryMethods" fxFlex="50%" [ngClass]={even:isEven}>
<mat-slide-toggle class="delivery-method" formControlName="enabled">
{{ notificationDeliveryMethodTranslateMap.get(deliveryMethods) | translate }}
</mat-slide-toggle>
</section>
</section>
<div class="tb-hint" translate>notification.at-least-one-should-be-selected</div>
</section>
</section>
</ng-template>
<tb-entity-list
required
formControlName="targets"
@ -55,31 +98,122 @@
{{ 'notification.create-new' | translate }}
</button>
</tb-entity-list>
<section formGroupName="additionalConfig" class="additional-config-group">
<mat-slide-toggle formControlName="enabled" class="toggle">
{{ 'notification.scheduler-later' | translate }}
</mat-slide-toggle>
<div *ngIf="notificationRequestForm.get('additionalConfig.enabled').value" fxLayout="column">
<tb-timezone-select userTimezoneByDefault
required
formControlName="timezone">
</tb-timezone-select>
<mat-form-field>
<mat-label translate>notification.time</mat-label>
<mat-datetimepicker-toggle [for]="startTimePicker" matPrefix></mat-datetimepicker-toggle>
<mat-datetimepicker #startTimePicker type="datetime" openOnFocus="true"></mat-datetimepicker>
<input required matInput fxFlex formControlName="time" [min]="minDate()" [max]="maxDate()" [matDatetimepicker]="startTimePicker">
</mat-form-field>
</div>
</form>
</mat-step>
<mat-step optional *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.PUSH.enabled').value"
[stepControl]="pushTemplateForm">
<ng-template matStepLabel>{{ 'notification.web-settings' | translate }}</ng-template>
<form [formGroup]="pushTemplateForm">
<mat-form-field class="mat-block" floatLabel="always">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject" [placeholder]="notificationRequestForm.get('template.configuration.notificationSubject').value">
</mat-form-field>
<mat-form-field class="mat-block" floatLabel="always">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body"
[placeholder]="notificationRequestForm.get('template.configuration.defaultTextTemplate').value">
</textarea>
</mat-form-field>
<section formGroupName="additionalConfig">
<section formGroupName="icon" class="additional-config-group">
<mat-slide-toggle formControlName="enabled" class="toggle">
{{ 'icon.icon' | translate }}
</mat-slide-toggle>
<div *ngIf="pushTemplateForm.get('additionalConfig.icon.enabled').value" fxLayout="row" fxLayoutGap="12px">
<tb-material-icon-select formControlName="icon" required fxFlex>
</tb-material-icon-select>
<tb-color-input formControlName="color" fxFlex>
</tb-color-input>
</div>
</section>
<section formGroupName="actionButtonConfig" class="additional-config-group">
<mat-slide-toggle formControlName="enabled" class="toggle">
{{ 'notification.action-button' | translate }}
</mat-slide-toggle>
<div *ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.enabled').value">
<div fxLayout="row" fxLayoutGap="12px">
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>notification.button-text</mat-label>
<input matInput formControlName="text" required>
<mat-error *ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.text').hasError('required')">
{{ 'notification.button-text-required' | translate }}
</mat-error>
</mat-form-field>
<tb-color-input formControlName="color" fxFlex>
</tb-color-input>
</div>
<mat-form-field class="mat-block">
<mat-label translate>notification.link</mat-label>
<input matInput formControlName="link" required>
<mat-error *ngIf="pushTemplateForm.get('additionalConfig.actionButtonConfig.link').hasError('required')">
{{ 'notification.link-required' | translate }}
</mat-error>
</mat-form-field>
</div>
</section>
</section>
</form>
</mat-step>
<mat-step>
<mat-step optional *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.EMAIL.enabled').value"
[stepControl]="emailTemplateForm">
<ng-template matStepLabel>{{ 'notification.email-settings' | translate }}</ng-template>
<ng-template matStepContent>
<form [formGroup]="emailTemplateForm">
<mat-form-field class="mat-block" floatLabel="always">
<mat-label translate>notification.subject</mat-label>
<input matInput formControlName="subject" [placeholder]="notificationRequestForm.get('template.configuration.notificationSubject').value">
</mat-form-field>
<mat-label class="tb-title" translate>notification.message</mat-label>
<editor [init]="tinyMceOptions" formControlName="body"></editor>
</form>
</ng-template>
</mat-step>
<mat-step optional *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.SMS.enabled').value"
[stepControl]="smsTemplateForm">
<ng-template matStepLabel>{{ 'notification.sms-settings' | translate }}</ng-template>
<form [formGroup]="smsTemplateForm">
<mat-form-field class="mat-block" floatLabel="always">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body"
[placeholder]="notificationRequestForm.get('template.configuration.defaultTextTemplate').value">
</textarea>
</mat-form-field>
</form>
</mat-step>
<mat-step optional *ngIf="!notificationRequestForm.get('useTemplate').value &&
notificationRequestForm.get('template.configuration.deliveryMethodsTemplates.SLACK.enabled').value"
[stepControl]="slackTemplateForm">
<ng-template matStepLabel>{{ 'notification.slack-settings' | translate }}</ng-template>
<form [formGroup]="slackTemplateForm" fxLayoutGap="8px">
<mat-form-field class="mat-block" floatLabel="always">
<mat-label translate>notification.message</mat-label>
<textarea matInput
cdkTextareaAutosize
cols="1"
cdkAutosizeMinRows="1"
formControlName="body"
[placeholder]="notificationRequestForm.get('template.configuration.defaultTextTemplate').value">
</textarea>
</mat-form-field>
</form>
</mat-step>
<mat-step [stepControl]="notificationRequestAdditionalConfigForm">
<ng-template matStepLabel>{{ 'notification.review' | translate }}</ng-template>
<mat-progress-spinner color="warn" mode="indeterminate"
strokeWidth="5" *ngIf="(isLoading$ | async) && !preview">
</mat-progress-spinner>
<div *ngIf="preview">
<div *ngIf="preview" style="padding-bottom: 16px">
<section class="preview-group notification" *ngIf="preview.processedTemplates.PUSH.enabled">
<div fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
<mat-icon svgIcon="mdi:bell-badge"></mat-icon>
@ -100,6 +234,23 @@
</div>
</div>
</section>
<form [formGroup]="notificationRequestAdditionalConfigForm" class="additional-config-group">
<mat-slide-toggle formControlName="enabled" class="toggle">
{{ 'notification.scheduler-later' | translate }}
</mat-slide-toggle>
<div *ngIf="notificationRequestAdditionalConfigForm.get('enabled').value" fxLayout="column">
<tb-timezone-select userTimezoneByDefault
required
formControlName="timezone">
</tb-timezone-select>
<mat-form-field>
<mat-label translate>notification.time</mat-label>
<mat-datetimepicker-toggle [for]="startTimePicker" matPrefix></mat-datetimepicker-toggle>
<mat-datetimepicker #startTimePicker type="datetime" openOnFocus="true"></mat-datetimepicker>
<input required matInput fxFlex formControlName="time" [min]="minDate()" [max]="maxDate()" [matDatetimepicker]="startTimePicker">
</mat-form-field>
</div>
</form>
</div>
</mat-step>
</mat-horizontal-stepper>

69
ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.scss

@ -19,6 +19,22 @@
:host-context(.tb-fullscreen-dialog .mat-dialog-container) {
width: 640px;
.delivery-method-container {
padding-bottom: 8px;
&.even {
padding-right: 8px;
}
.delivery-method {
padding: 16px 12px;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 6px;
width: 100%;
height: 100%;
}
}
.additional-config-group {
padding: 16px 16px 4px;
margin-bottom: 12px;
@ -129,4 +145,57 @@
}
}
}
.mat-button-toggle-group.tb-notification-use-template-toggle-group {
&.mat-button-toggle-group-appearance-standard {
border: none;
border-radius: 18px;
.mat-button-toggle + .mat-button-toggle {
border-left: none;
}
}
.mat-button-toggle {
background: rgba(0, 0, 0, 0.06);
height: 36px;
align-items: center;
display: flex;
.mat-button-toggle-ripple {
top: 2px;
left: 2px;
right: 2px;
bottom: 2px;
border-radius: 18px;
}
}
.mat-button-toggle-button {
color: #959595;
}
.mat-button-toggle-focus-overlay {
border-radius: 18px;
margin: 2px;
}
.mat-button-toggle-checked .mat-button-toggle-button {
background-color: $tb-primary-color;
color: #fff;
border-radius: 18px;
margin-left: 2px;
margin-right: 2px;
}
.mat-button-toggle-appearance-standard .mat-button-toggle-label-content {
line-height: 34px;
font-size: 16px;
font-weight: 500;
}
.mat-button-toggle-checked.mat-button-toggle-appearance-standard:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay {
opacity: .01;
}
}
}

68
ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.componet.ts

@ -15,13 +15,14 @@
///
import {
NotificationDeliveryMethod,
NotificationDeliveryMethodTranslateMap,
NotificationRequest,
NotificationRequestPreview,
NotificationTarget,
NotificationType
} from '@shared/models/notification.models';
import { Component, Inject, OnDestroy, ViewChild } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
@ -29,7 +30,7 @@ import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dial
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service';
import { deepTrim } from '@core/utils';
import { Observable, Subject } from 'rxjs';
import { Observable } from 'rxjs';
import { EntityType } from '@shared/models/entity-type.models';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MatStepper } from '@angular/material/stepper';
@ -42,6 +43,7 @@ import {
TargetsNotificationDialogData
} from '@home/pages/notification-center/targets-table/target-notification-dialog.componet';
import { MatButton } from '@angular/material/button';
import { TemplateConfiguration } from '@home/pages/notification-center/template-table/template-configuration';
export interface RequestNotificationDialogData {
request?: NotificationRequest;
@ -54,54 +56,76 @@ export interface RequestNotificationDialogData {
styleUrls: ['./request-notification-dialog.component.scss']
})
export class RequestNotificationDialogComponent extends
DialogComponent<RequestNotificationDialogComponent, NotificationRequest> implements OnDestroy {
TemplateConfiguration<RequestNotificationDialogComponent, NotificationRequest> implements OnDestroy {
@ViewChild('createNotification', {static: true}) createNotification: MatStepper;
stepperOrientation: Observable<StepperOrientation>;
isAdd = true;
entityType = EntityType;
notificationType = NotificationType;
notificationRequestForm: FormGroup;
notificationRequestAdditionalConfigForm: FormGroup;
notificationDeliveryMethods = Object.keys(NotificationDeliveryMethod) as NotificationDeliveryMethod[];
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
selectedIndex = 0;
preview: NotificationRequestPreview = null;
dialogTitle = 'notification.notify-again';
private readonly destroy$ = new Subject<void>();
constructor(protected store: Store<AppState>,
protected router: Router,
protected dialogRef: MatDialogRef<RequestNotificationDialogComponent, NotificationRequest>,
@Inject(MAT_DIALOG_DATA) public data: RequestNotificationDialogData,
private breakpointObserver: BreakpointObserver,
private fb: FormBuilder,
protected fb: FormBuilder,
private notificationService: NotificationService,
private dialog: MatDialog) {
super(store, router, dialogRef);
super(store, router, dialogRef, fb);
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs'])
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
this.notificationRequestForm = this.fb.group({
useTemplate: [true],
templateId: [null, Validators.required],
targets: [null, Validators.required],
additionalConfig: this.fb.group({
enabled: [false],
timezone: [{value: '', disabled: true}, Validators.required],
time: [{value: 0, disabled: true}, Validators.required]
})
template: this.templateNotificationForm
});
this.notificationRequestForm.get('template').disable({emitEvent: false});
this.notificationRequestForm.get('template.name').removeValidators(Validators.required);
this.notificationRequestForm.get('template.name').updateValueAndValidity({emitEvent: false});
this.notificationRequestForm.get('useTemplate').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(value => {
if (value) {
this.notificationRequestForm.get('templateId').enable({emitEvent: false});
this.notificationRequestForm.get('template').disable({emitEvent: false});
} else {
this.notificationRequestForm.get('templateId').disable({emitEvent: false});
this.notificationRequestForm.get('template').enable({emitEvent: false});
}
});
this.notificationRequestAdditionalConfigForm = this.fb.group({
enabled: [false],
timezone: [{value: '', disabled: true}, Validators.required],
time: [{value: 0, disabled: true}, Validators.required]
});
this.notificationRequestForm.get('additionalConfig.enabled').valueChanges.pipe(
this.notificationRequestAdditionalConfigForm.get('enabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(value => {
if (value) {
this.notificationRequestForm.get('additionalConfig.timezone').enable({emitEvent: false});
this.notificationRequestForm.get('additionalConfig.time').enable({emitEvent: false});
this.notificationRequestAdditionalConfigForm.get('timezone').enable({emitEvent: false});
this.notificationRequestAdditionalConfigForm.get('time').enable({emitEvent: false});
} else {
this.notificationRequestForm.get('additionalConfig.timezone').disable({emitEvent: false});
this.notificationRequestForm.get('additionalConfig.time').disable({emitEvent: false});
this.notificationRequestAdditionalConfigForm.get('timezone').disable({emitEvent: false});
this.notificationRequestAdditionalConfigForm.get('time').disable({emitEvent: false});
}
});
@ -115,8 +139,6 @@ export class RequestNotificationDialogComponent extends
ngOnDestroy() {
super.ngOnDestroy();
this.destroy$.next();
this.destroy$.complete();
}
cancel(): void {
@ -179,9 +201,11 @@ export class RequestNotificationDialogComponent extends
private get notificationFormValue(): NotificationRequest {
const formValue = deepTrim(this.notificationRequestForm.value);
delete formValue.useTemplate;
delete formValue.template;
let delay = 0;
if (formValue.additionalConfig.enabled) {
delay = (this.notificationRequestForm.value.additionalConfig.time.valueOf() - this.minDate().valueOf()) / 1000;
if (this.notificationRequestAdditionalConfigForm.value.enabled) {
delay = (this.notificationRequestAdditionalConfigForm.value.time.valueOf() - this.minDate().valueOf()) / 1000;
}
formValue.additionalConfig = {
sendingDelayInSec: delay > 0 ? delay : 0
@ -202,7 +226,7 @@ export class RequestNotificationDialogComponent extends
}
minDate(): Date {
return new Date(getCurrentTime(this.notificationRequestForm.get('additionalConfig.timezone').value).format('lll'));
return new Date(getCurrentTime(this.notificationRequestAdditionalConfigForm.get('timezone').value).format('lll'));
}
maxDate(): Date {

4
ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html

@ -58,9 +58,7 @@
required
allowCreate
formControlName="templateId"
[notificationTypes]="ruleNotificationForm.get('triggerType').value"
labelText="notification.template-name"
requiredText="notification.template-required">
[notificationTypes]="ruleNotificationForm.get('triggerType').value">
</tb-template-autocomplete>
<section formGroupName="recipientsConfig"
*ngIf="ruleNotificationForm.get('triggerType').value !== triggerType.ALARM; else alarmTargesConfig">

2
ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.html

@ -52,7 +52,7 @@
<span translate>notification.no-notification-templates</span>
</div>
<ng-template #searchNotEmpty>
<span>Not template matching {{ truncate.transform(searchText, true, 6, &apos;...&apos;) }}</span>
<span>{{ 'notification.no-template-matching' | translate : {template: truncate.transform(searchText, true, 6, &apos;...&apos;)} }}</span>
</ng-template>
</div>
</mat-option>

6
ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-autocomplete.component.ts

@ -57,12 +57,6 @@ export class TemplateAutocompleteComponent implements ControlValueAccessor, OnIn
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
selectTemplateFormGroup: FormGroup;
@Input()
labelText: string;
@Input()
requiredText: string;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;

172
ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-configuration.ts

@ -0,0 +1,172 @@
///
/// Copyright © 2016-2022 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import {
NotificationDeliveryMethod,
NotificationDeliveryMethodTranslateMap,
NotificationTemplate,
NotificationType
} from '@shared/models/notification.models';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { Directive, OnDestroy } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MatDialogRef } from '@angular/material/dialog';
@Directive()
// tslint:disable-next-line:directive-class-suffix
export abstract class TemplateConfiguration<T, R = any> extends DialogComponent<T, R> implements OnDestroy{
templateNotificationForm: FormGroup;
pushTemplateForm: FormGroup;
emailTemplateForm: FormGroup;
smsTemplateForm: FormGroup;
slackTemplateForm: FormGroup;
notificationDeliveryMethods = Object.keys(NotificationDeliveryMethod) as NotificationDeliveryMethod[];
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap;
tinyMceOptions: Record<string, any> = {
base_url: '/assets/tinymce',
suffix: '.min',
plugins: ['link table image imagetools code fullscreen'],
menubar: 'edit insert tools view format table',
toolbar: 'fontselect fontsizeselect | formatselect | bold italic strikethrough forecolor backcolor ' +
'| link | table | image | alignleft aligncenter alignright alignjustify ' +
'| numlist bullist outdent indent | removeformat | code | fullscreen',
height: 400,
autofocus: false,
branding: false
};
protected readonly destroy$ = new Subject<void>();
protected deliveryMethodFormsMap: Map<NotificationDeliveryMethod, FormGroup>;
protected constructor(protected store: Store<AppState>,
protected router: Router,
protected dialogRef: MatDialogRef<T, R>,
protected fb: FormBuilder) {
super(store, router, dialogRef);
this.templateNotificationForm = this.fb.group({
name: ['', Validators.required],
notificationType: [NotificationType.GENERAL],
configuration: this.fb.group({
notificationSubject: ['', Validators.required],
defaultTextTemplate: ['', Validators.required],
deliveryMethodsTemplates: this.fb.group({}, {validators: this.atLeastOne()})
})
});
this.notificationDeliveryMethods.forEach(method => {
(this.templateNotificationForm.get('configuration.deliveryMethodsTemplates') as FormGroup)
.addControl(method, this.fb.group({enabled: method === NotificationDeliveryMethod.PUSH}), {emitEvent: false});
});
this.pushTemplateForm = this.fb.group({
subject: [''],
body: [''],
additionalConfig: this.fb.group({
icon: this.fb.group({
enabled: [false],
icon: [{value: '', disabled: true}, Validators.required],
color: ['#757575']
}),
actionButtonConfig: this.fb.group({
enabled: [false],
text: [{value: '', disabled: true}, Validators.required],
color: ['#305680'],
link: [{value: '', disabled: true}, Validators.required]
}),
})
});
this.pushTemplateForm.get('additionalConfig.icon.enabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
if (value) {
this.pushTemplateForm.get('additionalConfig.icon.icon').enable({emitEvent: false});
} else {
this.pushTemplateForm.get('additionalConfig.icon.icon').disable({emitEvent: false});
}
});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.enabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
if (value) {
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.text').enable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').enable({emitEvent: false});
} else {
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.text').disable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').disable({emitEvent: false});
}
});
this.emailTemplateForm = this.fb.group({
subject: [''],
body: ['']
});
this.smsTemplateForm = this.fb.group({
body: ['']
});
this.slackTemplateForm = this.fb.group({
body: ['']
});
this.deliveryMethodFormsMap = new Map<NotificationDeliveryMethod, FormGroup>([
[NotificationDeliveryMethod.PUSH, this.pushTemplateForm],
[NotificationDeliveryMethod.EMAIL, this.emailTemplateForm],
[NotificationDeliveryMethod.SMS, this.smsTemplateForm],
[NotificationDeliveryMethod.SLACK, this.slackTemplateForm]
]);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
private atLeastOne() {
return (group: FormGroup): ValidationErrors | null => {
let hasAtLeastOne = true;
if (group?.controls) {
const controlsFormValue: FormGroup[] = Object.entries(group.controls).map(method => method[1]) as any;
hasAtLeastOne = controlsFormValue.some(value => value.controls.enabled.value);
}
return hasAtLeastOne ? null : {atLeastOne: true};
};
}
protected getNotificationTemplateValue(): NotificationTemplate {
const template: NotificationTemplate = this.templateNotificationForm.value;
this.notificationDeliveryMethods.forEach(method => {
if (template.configuration.deliveryMethodsTemplates[method].enabled) {
Object.assign(template.configuration.deliveryMethodsTemplates[method], this.deliveryMethodFormsMap.get(method).value, {method});
} else {
delete template.configuration.deliveryMethodsTemplates[method];
}
});
return template;
}
}

124
ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts

@ -22,21 +22,21 @@ import {
NotificationType
} from '@shared/models/notification.models';
import { Component, Inject, OnDestroy, ViewChild } from '@angular/core';
import { DialogComponent } from '@shared/components/dialog.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import { FormBuilder, FormGroup } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service';
import { deepClone, deepTrim, isDefinedAndNotNull } from '@core/utils';
import { Observable, Subject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper';
import { MatStepper } from '@angular/material/stepper';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
import { TranslateService } from '@ngx-translate/core';
import { TemplateConfiguration } from '@home/pages/notification-center/template-table/template-configuration';
export interface TemplateNotificationDialogData {
template?: NotificationTemplate;
@ -51,7 +51,7 @@ export interface TemplateNotificationDialogData {
styleUrls: ['./template-notification-dialog.component.scss']
})
export class TemplateNotificationDialogComponent
extends DialogComponent<TemplateNotificationDialogComponent, NotificationTemplate> implements OnDestroy {
extends TemplateConfiguration<TemplateNotificationDialogComponent, NotificationTemplate> implements OnDestroy {
@ViewChild('notificationTemplateStepper', {static: true}) notificationTemplateStepper: MatStepper;
@ -72,114 +72,26 @@ export class TemplateNotificationDialogComponent
selectedIndex = 0;
hideSelectType = false;
tinyMceOptions: Record<string, any> = {
base_url: '/assets/tinymce',
suffix: '.min',
plugins: ['link table image imagetools code fullscreen'],
menubar: 'edit insert tools view format table',
toolbar: 'fontselect fontsizeselect | formatselect | bold italic strikethrough forecolor backcolor ' +
'| link | table | image | alignleft aligncenter alignright alignjustify ' +
'| numlist bullist outdent indent | removeformat | code | fullscreen',
height: 400,
autofocus: false,
branding: false
};
private readonly destroy$ = new Subject<void>();
private readonly templateNotification: NotificationTemplate;
private deliveryMethodFormsMap: Map<NotificationDeliveryMethod, FormGroup>;
constructor(protected store: Store<AppState>,
protected router: Router,
protected dialogRef: MatDialogRef<TemplateNotificationDialogComponent, NotificationTemplate>,
@Inject(MAT_DIALOG_DATA) public data: TemplateNotificationDialogData,
private breakpointObserver: BreakpointObserver,
private fb: FormBuilder,
protected fb: FormBuilder,
private notificationService: NotificationService,
private translate: TranslateService) {
super(store, router, dialogRef);
super(store, router, dialogRef, fb);
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs'])
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
if (isDefinedAndNotNull(this.data?.predefinedType)) {
this.hideSelectType = true;
this.templateNotificationForm.get('notificationType').setValue(this.data.predefinedType, {emitEvents: false});
}
this.templateNotificationForm = this.fb.group({
name: ['', Validators.required],
notificationType: [this.hideSelectType ? this.data.predefinedType : NotificationType.GENERAL],
configuration: this.fb.group({
notificationSubject: ['', Validators.required],
defaultTextTemplate: ['', Validators.required],
deliveryMethodsTemplates: this.fb.group({}, {validators: this.atLeastOne()})
})
});
this.notificationDeliveryMethods.forEach(method => {
(this.templateNotificationForm.get('configuration.deliveryMethodsTemplates') as FormGroup)
.addControl(method, this.fb.group({enabled: method === NotificationDeliveryMethod.PUSH}), {emitEvent: false});
});
this.pushTemplateForm = this.fb.group({
subject: [''],
body: [''],
additionalConfig: this.fb.group({
icon: this.fb.group({
enabled: [false],
icon: [{value: '', disabled: true}, Validators.required],
color: ['#757575']
}),
actionButtonConfig: this.fb.group({
enabled: [false],
text: [{value: '', disabled: true}, Validators.required],
color: ['#305680'],
link: [{value: '', disabled: true}, Validators.required]
}),
})
});
this.pushTemplateForm.get('additionalConfig.icon.enabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
if (value) {
this.pushTemplateForm.get('additionalConfig.icon.icon').enable({emitEvent: false});
} else {
this.pushTemplateForm.get('additionalConfig.icon.icon').disable({emitEvent: false});
}
});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.enabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
if (value) {
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.text').enable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').enable({emitEvent: false});
} else {
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.text').disable({emitEvent: false});
this.pushTemplateForm.get('additionalConfig.actionButtonConfig.link').disable({emitEvent: false});
}
});
this.emailTemplateForm = this.fb.group({
subject: [''],
body: ['']
});
this.smsTemplateForm = this.fb.group({
body: ['']
});
this.slackTemplateForm = this.fb.group({
body: ['']
});
this.deliveryMethodFormsMap = new Map<NotificationDeliveryMethod, FormGroup>([
[NotificationDeliveryMethod.PUSH, this.pushTemplateForm],
[NotificationDeliveryMethod.EMAIL, this.emailTemplateForm],
[NotificationDeliveryMethod.SMS, this.smsTemplateForm],
[NotificationDeliveryMethod.SLACK, this.slackTemplateForm]
]);
if (data.isAdd || data.isCopy) {
this.dialogTitle = 'notification.add-notification-template';
}
@ -241,14 +153,7 @@ export class TemplateNotificationDialogComponent
private add(): void {
if (this.allValid()) {
let template: NotificationTemplate = this.templateNotificationForm.value;
this.notificationDeliveryMethods.forEach(method => {
if (template.configuration.deliveryMethodsTemplates[method].enabled) {
Object.assign(template.configuration.deliveryMethodsTemplates[method], this.deliveryMethodFormsMap.get(method).value, {method});
} else {
delete template.configuration.deliveryMethodsTemplates[method];
}
});
let template = this.getNotificationTemplateValue();
if (this.templateNotification && !this.data.isCopy) {
template = {...this.templateNotification, ...template};
}
@ -269,15 +174,4 @@ export class TemplateNotificationDialogComponent
}
});
}
private atLeastOne() {
return (group: FormGroup): ValidationErrors | null => {
let hasAtLeastOne = true;
if (group?.controls) {
const controlsFormValue: FormGroup[] = Object.entries(group.controls).map(method => method[1]) as any;
hasAtLeastOne = controlsFormValue.some(value => value.controls.enabled.value);
}
return hasAtLeastOne ? null : {atLeastOne: true};
};
}
}

5
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2709,8 +2709,8 @@
"copy-notification-template": "Copy notification template",
"copy-rule": "Copy rule",
"copy-template": "Copy template",
"create-target": "Create recipient",
"create-new": "Create new",
"create-target": "Create recipient",
"created": "Created",
"created-time": "Created time",
"delete-request-text": "Be careful, after the confirmation the notification request will become unrecoverable.",
@ -2767,6 +2767,7 @@
"no-severity-found": "No severity found",
"no-severity-matching": "'{{severity}}' not found.",
"no-targets-notification": "No recipients notification",
"no-template-matching": "No resource matching '{{template}}' were found.",
"not-found-slack-recipient": "Not found slack recipient",
"notification": "Notification",
"notification-center": "Notification center",
@ -2805,6 +2806,7 @@
},
"slack-settings": "Slack settings",
"sms-settings": "SMS settings",
"start-from-scratch": "Start from scratch",
"status": "Status",
"subject": "Subject",
"subject-required": "Subject is required",
@ -2851,6 +2853,7 @@
"type": "Type",
"unread": "Unread",
"updated": "Updated",
"use-template": "Use template",
"view-all": "View all",
"web-settings": "Web settings"
},

Loading…
Cancel
Save