UI: Add copy/edit notification template and notify again

This commit is contained in:
Vladyslav_Prykhodko 2023-01-30 10:23:36 +02:00
parent ebb5c634a3
commit ac9b3303b6
8 changed files with 110 additions and 88 deletions

View File

@ -251,6 +251,9 @@ export class EntityService {
(id) => this.widgetService.getWidgetsBundle(id, config),
entityIds);
break;
case EntityType.NOTIFICATION_TARGET:
observable = of([]);
break;
}
return observable;
}

View File

@ -81,7 +81,12 @@ export class RequestTableConfig extends EntityTableConfig<NotificationRequest, P
}
private configureCellActions(): Array<CellActionDescriptor<NotificationRequestInfo>> {
return [];
return [{
name: this.translate.instant('notification.notify-again'),
mdiIcon: 'mdi:repeat-variant',
isEnabled: (request) => request.status !== NotificationRequestStatus.SCHEDULED,
onAction: ($event, entity) => this.createRequest($event, entity)
}];
}
private createRequest($event: Event, request: NotificationRequest, isAdd = false, updateData = true) {

View File

@ -21,7 +21,7 @@ import {
} from '@home/models/entity/entities-table-config.models';
import { EntityTypeResource } from '@shared/models/entity-type.models';
import { Direction } from '@shared/models/page/sort-order';
import { NotificationTemplate } from '@shared/models/notification.models';
import { NotificationTemplate, NotificationTemplateTypeTranslateMap } from '@shared/models/notification.models';
import { NotificationService } from '@core/http/notification.service';
import { EntityAction } from '@home/models/entity/entity-component.models';
import { MatDialog } from '@angular/material/dialog';
@ -61,7 +61,8 @@ export class TemplateTableConfig extends EntityTableConfig<NotificationTemplate>
this.defaultSortOrder = {property: 'notificationType', direction: Direction.ASC};
this.columns.push(
new EntityTableColumn<NotificationTemplate>('notificationType', 'notification.type', '15%'),
new EntityTableColumn<NotificationTemplate>('notificationType', 'notification.type', '15%',
(template) => this.translate.instant(NotificationTemplateTypeTranslateMap.get(template.notificationType).name)),
new EntityTableColumn<NotificationTemplate>('name', 'notification.template', '25%'),
new EntityTableColumn<NotificationTemplate>('configuration.notificationSubject', 'notification.subject', '25%',
(template) => template.configuration.notificationSubject, () => ({}), false),
@ -71,10 +72,22 @@ export class TemplateTableConfig extends EntityTableConfig<NotificationTemplate>
}
private configureCellActions(): Array<CellActionDescriptor<NotificationTemplate>> {
return [];
return [
{
name: this.translate.instant('notification.copy-template'),
icon: 'content_copy',
isEnabled: () => true,
onAction: ($event, entity) => this.editTemplate($event, entity, false, true)
}, {
name: this.translate.instant('notification.edit-template'),
icon: 'edit',
isEnabled: () => true,
onAction: ($event, entity) => this.editTemplate($event, entity)
}
];
}
private editTemplate($event: Event, template: NotificationTemplate, isAdd = false) {
private editTemplate($event: Event, template: NotificationTemplate, isAdd = false, isCopy = false) {
if ($event) {
$event.stopPropagation();
}
@ -84,6 +97,7 @@ export class TemplateTableConfig extends EntityTableConfig<NotificationTemplate>
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
isAdd,
isCopy,
template
}
}).afterClosed()

View File

@ -16,7 +16,7 @@
-->
<mat-toolbar color="primary">
<h2 translate>notification.new-notification</h2>
<h2>{{ dialogTitle | translate }}</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"

View File

@ -23,7 +23,7 @@ import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service';
import { deepTrim, isDefined } from '@core/utils';
import { deepTrim } from '@core/utils';
import { Observable, Subject } from 'rxjs';
import { EntityType } from '@shared/models/entity-type.models';
import { BreakpointObserver } from '@angular/cdk/layout';
@ -55,6 +55,8 @@ export class RequestNotificationDialogComponent extends
selectedIndex = 0;
preview: NotificationRequestPreview = null;
dialogTitle = 'notification.notify-again';
private readonly destroy$ = new Subject<void>();
constructor(protected store: Store<AppState>,
@ -66,10 +68,6 @@ export class RequestNotificationDialogComponent extends
private notificationService: NotificationService) {
super(store, router, dialogRef);
if (isDefined(data.isAdd)) {
this.isAdd = data.isAdd;
}
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs'])
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
@ -77,6 +75,13 @@ export class RequestNotificationDialogComponent extends
templateId: [null, Validators.required],
targets: [null, Validators.required]
});
if (data.isAdd) {
this.dialogTitle = 'notification.new-notification';
}
if (data.request) {
this.notificationRequestForm.patchValue(this.data.request, {emitEvent: false});
}
}
ngOnDestroy() {

View File

@ -28,7 +28,7 @@
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content>
<mat-horizontal-stepper [linear]="true" labelPosition="end" #addNotificationTemplate [orientation]="(stepperOrientation | async)"
<mat-horizontal-stepper [linear]="true" labelPosition="end" #notificationTemplateStepper [orientation]="(stepperOrientation | async)"
(selectionChange)="changeStep($event)">
<ng-template matStepperIcon="edit">
<mat-icon>check</mat-icon>

View File

@ -31,13 +31,14 @@ import { Router } from '@angular/router';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import { NotificationService } from '@core/http/notification.service';
import { deepTrim, isDefined } from '@core/utils';
import { deepClone, deepTrim } from '@core/utils';
import { Observable, Subject } from 'rxjs';
import { map, takeUntil } 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';
export interface TemplateNotificationDialogData {
template?: NotificationTemplate;
@ -53,7 +54,7 @@ export interface TemplateNotificationDialogData {
export class TemplateNotificationDialogComponent
extends DialogComponent<TemplateNotificationDialogComponent, NotificationTemplate> implements OnDestroy {
@ViewChild('addNotificationTemplate', {static: true}) addNotificationTemplate: MatStepper;
@ViewChild('notificationTemplateStepper', {static: true}) notificationTemplateStepper: MatStepper;
stepperOrientation: Observable<StepperOrientation>;
@ -63,7 +64,6 @@ export class TemplateNotificationDialogComponent
smsTemplateForm: FormGroup;
slackTemplateForm: FormGroup;
dialogTitle = 'notification.edit-notification-template';
saveButtonLabel = 'action.save';
notificationTypes = Object.keys(NotificationType) as NotificationType[];
slackChanelTypes = Object.keys(SlackChanelType) as SlackChanelType[];
@ -74,40 +74,33 @@ export class TemplateNotificationDialogComponent
selectedIndex = 0;
tinyMceOptions: Record<string, any>;
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,
private notificationService: NotificationService) {
private notificationService: NotificationService,
private translate: TranslateService) {
super(store, router, dialogRef);
if (data.isAdd) {
this.dialogTitle = 'notification.add-notification-template';
this.saveButtonLabel = 'action.add';
}
if (data.isCopy) {
this.dialogTitle = 'notification.copy-notification-template';
}
this.tinyMceOptions = {
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
};
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-xs'])
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
@ -122,7 +115,7 @@ export class TemplateNotificationDialogComponent
});
this.notificationDeliveryMethods.forEach(method => {
(this.templateNotificationForm.get('configuration.deliveryMethodsTemplates') as FormGroup)
.addControl(method, this.fb.group({method, enabled: method === NotificationDeliveryMethod.PUSH}), {emitEvent: false});
.addControl(method, this.fb.group({enabled: method === NotificationDeliveryMethod.PUSH}), {emitEvent: false});
});
this.pushTemplateForm = this.fb.group({
@ -179,6 +172,30 @@ export class TemplateNotificationDialogComponent
conversationId: ['', Validators.required],
conversationType: [SlackChanelType.DIRECT]
});
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';
}
this.templateNotification = deepClone(this.data.template);
if (this.templateNotification) {
if (this.data.isCopy) {
this.templateNotification.name += ` (${this.translate.instant('action.copy')})`;
}
this.templateNotificationForm.reset({}, {emitEvent: false});
this.templateNotificationForm.patchValue(this.templateNotification, {emitEvent: false});
for (const method in this.templateNotification.configuration.deliveryMethodsTemplates) {
this.deliveryMethodFormsMap.get(NotificationDeliveryMethod[method])
.patchValue(this.templateNotification.configuration.deliveryMethodsTemplates[method]);
}
}
}
ngOnDestroy() {
@ -191,86 +208,61 @@ export class TemplateNotificationDialogComponent
this.dialogRef.close(null);
}
save() {
let formValue = deepTrim(this.templateNotificationForm.value);
if (isDefined(this.data.template)) {
formValue = Object.assign({}, this.data.template, formValue);
}
this.notificationService.saveNotificationTemplate(formValue).subscribe(
(target) => this.dialogRef.close(target)
);
}
changeStep($event: StepperSelectionEvent) {
this.selectedIndex = $event.selectedIndex;
}
backStep() {
this.addNotificationTemplate.previous();
this.notificationTemplateStepper.previous();
}
nextStep() {
if (this.selectedIndex >= this.maxStepperIndex) {
this.add();
} else {
this.addNotificationTemplate.next();
this.notificationTemplateStepper.next();
}
}
nextStepLabel(): string {
if (this.selectedIndex === 1 && this.selectedIndex < this.maxStepperIndex && this.pushTemplateForm.pristine) {
return 'action.skip';
}
if (this.selectedIndex === 2 && this.selectedIndex < this.maxStepperIndex && this.emailTemplateForm.pristine) {
return 'action.skip';
}
if (this.selectedIndex === 3 && this.selectedIndex < this.maxStepperIndex && this.smsTemplateForm.pristine) {
return 'action.skip';
}
if (this.selectedIndex !== 0 && this.selectedIndex >= this.maxStepperIndex) {
return 'action.add';
if (this.selectedIndex !== 0) {
if (this.selectedIndex >= this.maxStepperIndex) {
return (this.data.isAdd || this.data.isCopy) ? 'action.add' : 'action.save';
} else if (this.notificationTemplateStepper.selected.stepControl.pristine) {
return 'action.skip';
}
}
return 'action.next';
}
private get maxStepperIndex(): number {
return this.addNotificationTemplate?._steps?.length - 1;
return this.notificationTemplateStepper?._steps?.length - 1;
}
private add(): void {
if (this.allValid()) {
const formValue: NotificationTemplate = this.templateNotificationForm.value;
if (formValue.configuration.deliveryMethodsTemplates.PUSH.enabled) {
Object.assign(formValue.configuration.deliveryMethodsTemplates.PUSH, this.pushTemplateForm.value);
} else {
delete formValue.configuration.deliveryMethodsTemplates.PUSH;
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];
}
});
if (this.templateNotification) {
template = {...this.templateNotification, ...template};
}
if (formValue.configuration.deliveryMethodsTemplates.EMAIL.enabled) {
Object.assign(formValue.configuration.deliveryMethodsTemplates.EMAIL, this.emailTemplateForm.value);
} else {
delete formValue.configuration.deliveryMethodsTemplates.EMAIL;
}
if (formValue.configuration.deliveryMethodsTemplates.SMS.enabled) {
Object.assign(formValue.configuration.deliveryMethodsTemplates.SMS, this.smsTemplateForm.value);
} else {
delete formValue.configuration.deliveryMethodsTemplates.SMS;
}
if (formValue.configuration.deliveryMethodsTemplates.SLACK.enabled) {
Object.assign(formValue.configuration.deliveryMethodsTemplates.SLACK, this.slackTemplateForm.value);
} else {
delete formValue.configuration.deliveryMethodsTemplates.SLACK;
}
this.notificationService.saveNotificationTemplate(deepTrim(formValue)).subscribe(
this.notificationService.saveNotificationTemplate(deepTrim(template)).subscribe(
(target) => this.dialogRef.close(target)
);
}
}
private allValid(): boolean {
return !this.addNotificationTemplate.steps.find((item, index) => {
return !this.notificationTemplateStepper.steps.find((item, index) => {
if (item.stepControl.invalid) {
item.interacted = true;
this.addNotificationTemplate.selectedIndex = index;
this.notificationTemplateStepper.selectedIndex = index;
return true;
} else {
return false;

View File

@ -2674,8 +2674,8 @@
"add-notification-template": "Add notification template",
"add-target": "Add recipient",
"add-template": "Add template",
"at-least-one-should-be-selected": "At least one should be selected",
"all": "All",
"at-least-one-should-be-selected": "At least one should be selected",
"basic-settings": "Basic settings",
"button-text": "Button text",
"button-text-required": "Button text is required",
@ -2683,6 +2683,7 @@
"conversation": "Conversation",
"conversation-required": "Conversation is required",
"copy-notification-template": "Copy notification template",
"copy-template": "Copy template",
"create-target": "Create recipient",
"created-time": "Created time",
"delete-request-text": "Be careful, after the confirmation the notification request will become unrecoverable.",
@ -2692,16 +2693,17 @@
"delete-template-text": "Be careful, after the confirmation the notification template will become unrecoverable.",
"delete-template-title": "Are you sure you want to delete the notification template '{{templateName}}'?",
"delivery-method": "Delivery method",
"delivery-methods": "Delivery methods",
"delivery-method-type": {
"email": "Email",
"push": "Web",
"slack": "Slack",
"sms": "SMS"
},
"delivery-methods": "Delivery methods",
"description": "Description",
"edit-notification-target": "Edit notification recipient",
"edit-notification-template": "Edit notification template",
"edit-template": "Edit template",
"email-settings": "Email settings",
"get-customer-id-from-originator": "Get Customer id from originator",
"inbox": "Inbox",
@ -2722,6 +2724,7 @@
"notification": "Notification",
"notification-center": "Notification center",
"notification-target": "Notification recipient",
"notify-again": "Notify again",
"recipients-count": "{ count, plural, 1 {1 Recipient} other {# Recipients} }",
"requet-status": {
"processing": "Processing",