40 changed files with 1709 additions and 227 deletions
@ -0,0 +1,27 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<div fxFlex="100" fxLayout="row" fxLayoutAlign="start center"> |
||||
|
<mat-button-toggle-group class="tb-notification-unread-toggle-group" |
||||
|
style="width: 180px;" |
||||
|
[ngModel]="entitiesTableConfig.componentsData.unreadOnly" |
||||
|
(ngModelChange)="changeUnreadOnly($event)"> |
||||
|
<mat-button-toggle fxFlex [value]=true>Unread</mat-button-toggle> |
||||
|
<mat-button-toggle fxFlex [value]=false>{{ 'notification.all' | translate }}</mat-button-toggle> |
||||
|
</mat-button-toggle-group> |
||||
|
|
||||
|
</div> |
||||
@ -0,0 +1,71 @@ |
|||||
|
/** |
||||
|
* 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 "../../../../../../theme"; |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
.mat-button-toggle-group.tb-notification-unread-toggle-group { |
||||
|
&.mat-button-toggle-group-appearance-standard { |
||||
|
border: none; |
||||
|
border-radius: 14px; |
||||
|
|
||||
|
.mat-button-toggle + .mat-button-toggle { |
||||
|
border-left: none; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.mat-button-toggle { |
||||
|
background: rgba(0, 0, 0, 0.06); |
||||
|
height: 28px; |
||||
|
align-items: center; |
||||
|
display: flex; |
||||
|
|
||||
|
.mat-button-toggle-ripple { |
||||
|
top: 2px; |
||||
|
left: 2px; |
||||
|
right: 2px; |
||||
|
bottom: 2px; |
||||
|
border-radius: 12px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.mat-button-toggle-button { |
||||
|
color: #959595; |
||||
|
} |
||||
|
|
||||
|
.mat-button-toggle-focus-overlay { |
||||
|
border-radius: 14px; |
||||
|
margin: 2px; |
||||
|
} |
||||
|
|
||||
|
.mat-button-toggle-checked .mat-button-toggle-button { |
||||
|
background-color: $tb-primary-color; |
||||
|
color: #fff; |
||||
|
border-radius: 14px; |
||||
|
margin-left: 2px; |
||||
|
margin-right: 2px; |
||||
|
} |
||||
|
|
||||
|
.mat-button-toggle-appearance-standard .mat-button-toggle-label-content { |
||||
|
line-height: 24px; |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
///
|
||||
|
/// 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 { Component } from '@angular/core'; |
||||
|
import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { Notification } from '@shared/models/notification.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-inbox-table-header', |
||||
|
templateUrl: './inbox-table-header.component.html', |
||||
|
styleUrls: ['inbox-table-header.component.scss'] |
||||
|
}) |
||||
|
export class InboxTableHeaderComponent extends EntityTableHeaderComponent<Notification> { |
||||
|
|
||||
|
constructor(protected store: Store<AppState>) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
changeUnreadOnly(unreadOnly: boolean) { |
||||
|
this.entitiesTableConfig.componentsData.unreadOnly = unreadOnly; |
||||
|
this.entitiesTableConfig.getTable().resetSortAndFilter(true); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,148 @@ |
|||||
|
///
|
||||
|
/// 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 { |
||||
|
CellActionDescriptor, |
||||
|
DateEntityTableColumn, |
||||
|
EntityTableColumn, |
||||
|
EntityTableConfig |
||||
|
} from '@home/models/entity/entities-table-config.models'; |
||||
|
import { |
||||
|
NotificationDeliveryMethodTranslateMap, |
||||
|
NotificationRequest, |
||||
|
NotificationRequestInfo, |
||||
|
NotificationRequestStatus, |
||||
|
NotificationRequestStatusTranslateMap, |
||||
|
NotificationTemplate |
||||
|
} from '@shared/models/notification.models'; |
||||
|
import { NotificationService } from '@core/http/notification.service'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { MatDialog } from '@angular/material/dialog'; |
||||
|
import { EntityTypeResource } from '@shared/models/entity-type.models'; |
||||
|
import { Direction } from '@shared/models/page/sort-order'; |
||||
|
import { DatePipe } from '@angular/common'; |
||||
|
import { EntityAction } from '@home/models/entity/entity-component.models'; |
||||
|
import { |
||||
|
RequestNotificationDialogComponent, |
||||
|
RequestNotificationDialogData |
||||
|
} from '@home/pages/notification-center/request-table/request-notification-dialog.componet'; |
||||
|
import { PageLink } from '@shared/models/page/page-link'; |
||||
|
|
||||
|
export class RequestTableConfig extends EntityTableConfig<NotificationRequest, PageLink, NotificationRequestInfo> { |
||||
|
|
||||
|
constructor(private notificationService: NotificationService, |
||||
|
private translate: TranslateService, |
||||
|
private dialog: MatDialog, |
||||
|
private datePipe: DatePipe) { |
||||
|
super(); |
||||
|
this.loadDataOnInit = false; |
||||
|
this.searchEnabled = false; |
||||
|
this.entityTranslations = { |
||||
|
noEntities: 'notification.no-notification-request', |
||||
|
}; |
||||
|
this.entityResources = {} as EntityTypeResource<NotificationRequest>; |
||||
|
|
||||
|
this.entitiesFetchFunction = pageLink => this.notificationService.getNotificationRequests(pageLink); |
||||
|
|
||||
|
this.deleteEnabled = (request) => request.status === NotificationRequestStatus.SCHEDULED; |
||||
|
this.deleteEntityTitle = () => this.translate.instant('notification.delete-request-title'); |
||||
|
this.deleteEntityContent = () => this.translate.instant('notification.delete-request-text'); |
||||
|
this.deleteEntity = id => this.notificationService.deleteNotificationRequest(id.id); |
||||
|
|
||||
|
this.cellActionDescriptors = this.configureCellActions(); |
||||
|
|
||||
|
this.onEntityAction = action => this.onRequestAction(action); |
||||
|
|
||||
|
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; |
||||
|
|
||||
|
this.columns.push( |
||||
|
new DateEntityTableColumn<NotificationRequestInfo>('createdTime', 'notification.created-time', this.datePipe, '150px'), |
||||
|
new EntityTableColumn<NotificationRequestInfo>('status', 'notification.status', '15%', |
||||
|
request => this.requestStatus(request.status), request => this.requestStatusStyle(request.status)), |
||||
|
new EntityTableColumn<NotificationRequest>('deliveryMethods', 'notification.delivery-method', '35%', |
||||
|
(request) => request.deliveryMethods |
||||
|
.map((deliveryMethod) => this.translate.instant(NotificationDeliveryMethodTranslateMap.get(deliveryMethod))).join(', '), |
||||
|
() => ({}), false), |
||||
|
new EntityTableColumn<NotificationRequest>('templateName', 'notification.template', '50%') |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private configureCellActions(): Array<CellActionDescriptor<NotificationRequestInfo>> { |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
private createRequest($event: Event, request: NotificationRequest, isAdd = false) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
this.dialog.open<RequestNotificationDialogComponent, RequestNotificationDialogData, |
||||
|
NotificationTemplate>(RequestNotificationDialogComponent, { |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
isAdd, |
||||
|
request |
||||
|
} |
||||
|
}).afterClosed() |
||||
|
.subscribe((res) => { |
||||
|
if (res) { |
||||
|
this.updateData(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private onRequestAction(action: EntityAction<NotificationRequest>): boolean { |
||||
|
switch (action.action) { |
||||
|
case 'add': |
||||
|
this.createRequest(action.event, action.entity, true); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private requestStatus(status: NotificationRequestStatus): string { |
||||
|
const translateKey = NotificationRequestStatusTranslateMap.get(status); |
||||
|
let backgroundColor = 'rgba(25, 128, 56, 0.08)'; |
||||
|
switch (status) { |
||||
|
case NotificationRequestStatus.SCHEDULED: |
||||
|
backgroundColor = 'rgba(48, 86, 128, 0.08)'; |
||||
|
break; |
||||
|
case NotificationRequestStatus.PROCESSING: |
||||
|
backgroundColor = 'rgba(212, 125, 24, 0.08)'; |
||||
|
break; |
||||
|
} |
||||
|
return `<div style="border-radius: 16px; height: 32px; line-height: 32px; padding: 0 12px; width: fit-content; background-color: ${backgroundColor}">
|
||||
|
${this.translate.instant(translateKey)} |
||||
|
</div>`;
|
||||
|
} |
||||
|
|
||||
|
private requestStatusStyle(status: NotificationRequestStatus): object { |
||||
|
const styleObj = { |
||||
|
fontSize: '14px', |
||||
|
color: '#198038', |
||||
|
cursor: 'pointer' |
||||
|
}; |
||||
|
switch (status) { |
||||
|
case NotificationRequestStatus.SCHEDULED: |
||||
|
styleObj.color = '#305680'; |
||||
|
break; |
||||
|
case NotificationRequestStatus.PROCESSING: |
||||
|
styleObj.color = '#D47D18'; |
||||
|
break; |
||||
|
} |
||||
|
return styleObj; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2 translate>notification.new-notification</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-icon-button |
||||
|
(click)="cancel()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</mat-progress-bar> |
||||
|
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
||||
|
<div mat-dialog-content> |
||||
|
<mat-horizontal-stepper [linear]="true" labelPosition="end" #createNotification |
||||
|
[orientation]="(stepperOrientation | async)" |
||||
|
(selectionChange)="changeStep($event)"> |
||||
|
<ng-template matStepperIcon="edit"> |
||||
|
<mat-icon>check</mat-icon> |
||||
|
</ng-template> |
||||
|
<mat-step [stepControl]="notificationRequestForm"> |
||||
|
<ng-template matStepLabel>{{ 'notification.compose' | translate }}</ng-template> |
||||
|
<form [formGroup]="notificationRequestForm" style="padding-bottom: 16px;"> |
||||
|
<tb-template-autocomplete |
||||
|
required |
||||
|
formControlName="templateId"> |
||||
|
</tb-template-autocomplete> |
||||
|
<tb-entity-list |
||||
|
required |
||||
|
formControlName="targets" |
||||
|
labelText="notification.targets" |
||||
|
placeholderText="notification.target" |
||||
|
requiredText="notification.targets-required" |
||||
|
[entityType]="entityType.NOTIFICATION_TARGET"> |
||||
|
</tb-entity-list> |
||||
|
</form> |
||||
|
</mat-step> |
||||
|
<mat-step> |
||||
|
<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"> |
||||
|
<section class="preview-group"> |
||||
|
<div fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center"> |
||||
|
<mat-icon>supervisor_account</mat-icon> |
||||
|
<div class="total-recipients">{{ 'notification.recipients-count' | translate : {count: preview.totalRecipientsCount} }}</div> |
||||
|
</div> |
||||
|
<div class="details-recipients" *ngIf="notificationRequestForm.get('targets').value.length"> |
||||
|
<div *ngFor="let detail of preview.recipientsCountByTarget | keyvalue" class="details-recipient"> |
||||
|
<span class="number">{{ detail.value }}</span>{{ detail.key }} |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
</div> |
||||
|
</mat-step> |
||||
|
</mat-horizontal-stepper> |
||||
|
</div> |
||||
|
<mat-divider></mat-divider> |
||||
|
<div mat-dialog-actions fxLayout="row"> |
||||
|
<button mat-stroked-button *ngIf="selectedIndex > 0" |
||||
|
(click)="backStep()">{{ 'action.back' | translate }}</button> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-raised-button |
||||
|
color="primary" |
||||
|
(click)="nextStep()">{{ nextStepLabel() | translate }}</button> |
||||
|
</div> |
||||
@ -0,0 +1,100 @@ |
|||||
|
/** |
||||
|
* 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 "../../../../../../scss/constants"; |
||||
|
|
||||
|
:host-context(.tb-fullscreen-dialog .mat-dialog-container) { |
||||
|
width: 640px; |
||||
|
|
||||
|
.preview-group { |
||||
|
padding: 16px; |
||||
|
margin-bottom: 10px; |
||||
|
border: 1px groove rgba(0, 0, 0, .12); |
||||
|
border-radius: 4px; |
||||
|
|
||||
|
.mat-icon { |
||||
|
color: rgba(0, 0, 0, 0.38); |
||||
|
} |
||||
|
|
||||
|
.total-recipients { |
||||
|
font-weight: 500; |
||||
|
letter-spacing: .25px; |
||||
|
} |
||||
|
|
||||
|
&> div:not(:last-child) { |
||||
|
margin-bottom: 12px; |
||||
|
} |
||||
|
|
||||
|
.details-recipient { |
||||
|
font-size: 14px; |
||||
|
line-height: 14px; |
||||
|
letter-spacing: .2px; |
||||
|
|
||||
|
.number { |
||||
|
font-weight: 500; |
||||
|
margin-right: 4px; |
||||
|
} |
||||
|
|
||||
|
&:not(:last-of-type) { |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media #{$mat-gt-xs} { |
||||
|
.mat-dialog-content { |
||||
|
max-height: 75vh; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
.mat-dialog-content { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
padding: 0 !important; |
||||
|
|
||||
|
.mat-stepper-horizontal { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
overflow: hidden; |
||||
|
@media #{$mat-lt-sm} { |
||||
|
.mat-step-label { |
||||
|
white-space: normal; |
||||
|
overflow: visible; |
||||
|
.mat-step-text-label { |
||||
|
overflow: visible; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.mat-horizontal-content-container { |
||||
|
height: 400px; |
||||
|
max-height: 100%; |
||||
|
width: 100%;; |
||||
|
overflow-y: auto; |
||||
|
scrollbar-gutter: stable; |
||||
|
@media #{$mat-gt-sm} { |
||||
|
min-width: 500px; |
||||
|
} |
||||
|
} |
||||
|
.mat-horizontal-stepper-content[aria-expanded=true] { |
||||
|
height: 100%; |
||||
|
position: relative; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,155 @@ |
|||||
|
///
|
||||
|
/// 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 { NotificationRequest, NotificationRequestPreview } 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, Validators } from '@angular/forms'; |
||||
|
import { NotificationService } from '@core/http/notification.service'; |
||||
|
import { deepTrim, isDefined } from '@core/utils'; |
||||
|
import { Observable, Subject } from 'rxjs'; |
||||
|
import { EntityType } from '@shared/models/entity-type.models'; |
||||
|
import { BreakpointObserver } from '@angular/cdk/layout'; |
||||
|
import { MatStepper } from '@angular/material/stepper'; |
||||
|
import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper'; |
||||
|
import { MediaBreakpoints } from '@shared/models/constants'; |
||||
|
import { map } from 'rxjs/operators'; |
||||
|
|
||||
|
export interface RequestNotificationDialogData { |
||||
|
request?: NotificationRequest; |
||||
|
isAdd?: boolean; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-request-notification-dialog', |
||||
|
templateUrl: './request-notification-dialog.component.html', |
||||
|
styleUrls: ['./request-notification-dialog.component.scss'] |
||||
|
}) |
||||
|
export class RequestNotificationDialogComponent extends |
||||
|
DialogComponent<RequestNotificationDialogComponent, NotificationRequest> implements OnDestroy { |
||||
|
|
||||
|
@ViewChild('createNotification', {static: true}) createNotification: MatStepper; |
||||
|
stepperOrientation: Observable<StepperOrientation>; |
||||
|
isAdd = true; |
||||
|
entityType = EntityType; |
||||
|
notificationRequestForm: FormGroup; |
||||
|
|
||||
|
selectedIndex = 0; |
||||
|
preview: NotificationRequestPreview = null; |
||||
|
|
||||
|
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, |
||||
|
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')); |
||||
|
|
||||
|
this.notificationRequestForm = this.fb.group({ |
||||
|
templateId: [null, Validators.required], |
||||
|
targets: [null, Validators.required] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
super.ngOnDestroy(); |
||||
|
this.destroy$.next(); |
||||
|
this.destroy$.complete(); |
||||
|
} |
||||
|
|
||||
|
cancel(): void { |
||||
|
this.dialogRef.close(null); |
||||
|
} |
||||
|
|
||||
|
nextStepLabel(): string { |
||||
|
if (this.selectedIndex >= this.maxStepperIndex) { |
||||
|
return 'action.send'; |
||||
|
} |
||||
|
return 'action.next'; |
||||
|
} |
||||
|
|
||||
|
changeStep($event: StepperSelectionEvent) { |
||||
|
this.selectedIndex = $event.selectedIndex; |
||||
|
if (this.selectedIndex === this.maxStepperIndex) { |
||||
|
this.getPreview(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
backStep() { |
||||
|
this.createNotification.previous(); |
||||
|
} |
||||
|
|
||||
|
nextStep() { |
||||
|
if (this.selectedIndex >= this.maxStepperIndex) { |
||||
|
this.add(); |
||||
|
} else { |
||||
|
this.createNotification.next(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private get maxStepperIndex(): number { |
||||
|
return this.createNotification?._steps?.length - 1; |
||||
|
} |
||||
|
|
||||
|
private add(): void { |
||||
|
if (this.allValid()) { |
||||
|
this.notificationService.createNotificationRequest(this.notificationFormValue).subscribe( |
||||
|
(notification) => this.dialogRef.close(notification) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private getPreview() { |
||||
|
if (this.allValid()) { |
||||
|
this.preview = null; |
||||
|
this.notificationService.getNotificationRequestPreview(this.notificationFormValue).subscribe( |
||||
|
(preview) => this.preview = preview |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private get notificationFormValue(): NotificationRequest { |
||||
|
const formValue: NotificationRequest = deepTrim(this.notificationRequestForm.value); |
||||
|
formValue.additionalConfig = {sendingDelayInSec: 0}; |
||||
|
return formValue; |
||||
|
} |
||||
|
|
||||
|
private allValid(): boolean { |
||||
|
return !this.createNotification.steps.find((item, index) => { |
||||
|
if (item.stepControl?.invalid) { |
||||
|
item.interacted = true; |
||||
|
this.createNotification.selectedIndex = index; |
||||
|
return true; |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -1,74 +0,0 @@ |
|||||
<!-- |
|
||||
|
|
||||
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. |
|
||||
|
|
||||
--> |
|
||||
<mat-toolbar color="primary"> |
|
||||
<h2>{{ dialogTitle | translate }}</h2> |
|
||||
<span fxFlex></span> |
|
||||
<button mat-icon-button |
|
||||
(click)="cancel()" |
|
||||
type="button"> |
|
||||
<mat-icon class="material-icons">close</mat-icon> |
|
||||
</button> |
|
||||
</mat-toolbar> |
|
||||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|
||||
</mat-progress-bar> |
|
||||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
|
||||
<form mat-dialog-content [formGroup]="templateNotificationForm"> |
|
||||
<mat-form-field class="mat-block"> |
|
||||
<mat-label translate>notification.target-name</mat-label> |
|
||||
<input matInput formControlName="name" required> |
|
||||
<mat-error *ngIf="templateNotificationForm.get('name').hasError('required')"> |
|
||||
{{ 'notification.target-name-required' | translate }} |
|
||||
</mat-error> |
|
||||
</mat-form-field> |
|
||||
<label style="margin-bottom: 8px">{{ 'notification.type' | translate }}<sup> *</sup></label> |
|
||||
<mat-radio-group formControlName="type"> |
|
||||
<mat-radio-button [value]="NotificationType.GENERIC"> |
|
||||
<div fxLayout="column"> |
|
||||
<div>Generic</div> |
|
||||
<div class="tb-hint">hint</div> |
|
||||
</div> |
|
||||
</mat-radio-button> |
|
||||
<mat-radio-button [value]="NotificationType.ALARM"> |
|
||||
<div>Alarm</div> |
|
||||
<div class="tb-hint">hint</div> |
|
||||
</mat-radio-button> |
|
||||
</mat-radio-group> |
|
||||
<section formGroupName="configuration"> |
|
||||
<mat-form-field class="mat-block"> |
|
||||
<mat-label translate>notification.message</mat-label> |
|
||||
<input matInput formControlName="defaultTextTemplate" required> |
|
||||
<mat-error *ngIf="templateNotificationForm.get('configuration.defaultTextTemplate').hasError('required')"> |
|
||||
{{ 'notification.message-required' | translate }} |
|
||||
</mat-error> |
|
||||
</mat-form-field> |
|
||||
<mat-form-field class="mat-block"> |
|
||||
<mat-label translate>notification.subject</mat-label> |
|
||||
<input matInput required> |
|
||||
</mat-form-field> |
|
||||
</section> |
|
||||
</form> |
|
||||
<mat-divider></mat-divider> |
|
||||
<div mat-dialog-actions fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end"> |
|
||||
<button mat-button |
|
||||
[disabled]="(isLoading$ | async)" |
|
||||
(click)="cancel()">{{ 'action.cancel' | translate }}</button> |
|
||||
<button mat-raised-button |
|
||||
[disabled]="(isLoading$ | async) || templateNotificationForm.invalid || !templateNotificationForm.dirty" |
|
||||
color="primary" |
|
||||
(click)="save()">{{ saveButtonLabel | translate }}</button> |
|
||||
</div> |
|
||||
@ -1,95 +0,0 @@ |
|||||
///
|
|
||||
/// 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 { NotificationTemplate, NotificationType } from '@shared/models/notification.models'; |
|
||||
import { Component, Inject, 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 { 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 { Subject } from 'rxjs'; |
|
||||
|
|
||||
export interface TemplateNotificationDialogData { |
|
||||
template?: NotificationTemplate; |
|
||||
isAdd?: boolean; |
|
||||
isCopy?: boolean; |
|
||||
} |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'tb-template-notification-dialog', |
|
||||
templateUrl: './template-notification-dialog.component.html', |
|
||||
styleUrls: ['./template-notification-dialog.component.scss'] |
|
||||
}) |
|
||||
export class TemplateNotificationDialogComponent |
|
||||
extends DialogComponent<TemplateNotificationDialogComponent, NotificationTemplate> implements OnDestroy { |
|
||||
|
|
||||
templateNotificationForm: FormGroup; |
|
||||
dialogTitle = 'notification.edit-notification-template'; |
|
||||
saveButtonLabel = 'action.save'; |
|
||||
|
|
||||
NotificationType = NotificationType; |
|
||||
|
|
||||
private readonly destroy$ = new Subject<void>(); |
|
||||
|
|
||||
constructor(protected store: Store<AppState>, |
|
||||
protected router: Router, |
|
||||
protected dialogRef: MatDialogRef<TemplateNotificationDialogComponent, NotificationTemplate>, |
|
||||
@Inject(MAT_DIALOG_DATA) public data: TemplateNotificationDialogData, |
|
||||
private fb: FormBuilder, |
|
||||
private notificationService: NotificationService) { |
|
||||
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.templateNotificationForm = this.fb.group({ |
|
||||
name: [null, Validators.required], |
|
||||
type: [NotificationType.GENERIC], |
|
||||
configuration: this.fb.group({ |
|
||||
defaultTextTemplate: [null, Validators.required] |
|
||||
}) |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
ngOnDestroy() { |
|
||||
super.ngOnDestroy(); |
|
||||
this.destroy$.next(); |
|
||||
this.destroy$.complete(); |
|
||||
} |
|
||||
|
|
||||
cancel(): void { |
|
||||
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) |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,57 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<mat-form-field [formGroup]="selectTemplateFormGroup" class="mat-block"> |
||||
|
<input matInput type="text" placeholder="{{ 'notification.template' | translate }}" |
||||
|
#templateInput |
||||
|
formControlName="templateName" |
||||
|
(focusin)="onFocus()" |
||||
|
[required]="required" |
||||
|
[matAutocomplete]="templateAutocomplete"> |
||||
|
<button *ngIf="selectTemplateFormGroup.get('templateName').value && !disabled" |
||||
|
type="button" |
||||
|
matSuffix mat-button mat-icon-button aria-label="Clear" |
||||
|
(click)="clear()"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
<mat-autocomplete class="tb-autocomplete" |
||||
|
#templateAutocomplete="matAutocomplete" |
||||
|
[displayWith]="displayTemplateFn" |
||||
|
> |
||||
|
<mat-option *ngFor="let template of filteredTemplate | async" [value]="template" class="template-option"> |
||||
|
<span class="template-option-name" [innerHTML]="template.name | highlight:searchText"></span> |
||||
|
<mat-chip-list> |
||||
|
<mat-chip disabled *ngFor="let method of template.configuration.deliveryMethodsTemplates | keyvalue"> |
||||
|
{{ notificationDeliveryMethodTranslateMap.get(method.key) | translate }} |
||||
|
</mat-chip> |
||||
|
</mat-chip-list> |
||||
|
</mat-option> |
||||
|
<mat-option *ngIf="!(filteredTemplate | async)?.length" [value]="null" class="tb-not-found"> |
||||
|
<div class="tb-not-found-content" (click)="$event.stopPropagation()"> |
||||
|
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty"> |
||||
|
<span translate>notification.no-notification-templates</span> |
||||
|
</div> |
||||
|
<ng-template #searchNotEmpty> |
||||
|
<span>Not template matching {{ truncate.transform(searchText, true, 6, '...') }}</span> |
||||
|
</ng-template> |
||||
|
</div> |
||||
|
</mat-option> |
||||
|
</mat-autocomplete> |
||||
|
<mat-error *ngIf="selectTemplateFormGroup.get('templateName').hasError('required')"> |
||||
|
{{ 'notification.template-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
@ -0,0 +1,213 @@ |
|||||
|
///
|
||||
|
/// 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 { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; |
||||
|
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
||||
|
import { Observable, of } from 'rxjs'; |
||||
|
import { catchError, debounceTime, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { EntityId } from '@shared/models/id/entity-id'; |
||||
|
import { EntityService } from '@core/http/entity.service'; |
||||
|
import { TruncatePipe } from '@shared/pipe/truncate.pipe'; |
||||
|
import { PageLink } from '@shared/models/page/page-link'; |
||||
|
import { Direction } from '@shared/models/page/sort-order'; |
||||
|
import { emptyPageData } from '@shared/models/page/page-data'; |
||||
|
import { NotificationDeliveryMethodTranslateMap, NotificationTemplate } from '@shared/models/notification.models'; |
||||
|
import { NotificationService } from '@core/http/notification.service'; |
||||
|
import { isEqual } from '@core/utils'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-template-autocomplete', |
||||
|
templateUrl: './template-autocomplete.component.html', |
||||
|
styleUrls: ['./template-autocomplete.component.scss'], |
||||
|
providers: [{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => TemplateAutocompleteComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class TemplateAutocompleteComponent implements ControlValueAccessor, OnInit { |
||||
|
|
||||
|
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap; |
||||
|
selectTemplateFormGroup: FormGroup; |
||||
|
|
||||
|
@Input() |
||||
|
labelText: string; |
||||
|
|
||||
|
@Input() |
||||
|
requiredText: string; |
||||
|
|
||||
|
private requiredValue: boolean; |
||||
|
get required(): boolean { |
||||
|
return this.requiredValue; |
||||
|
} |
||||
|
@Input() |
||||
|
set required(value: boolean) { |
||||
|
this.requiredValue = coerceBooleanProperty(value); |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
@ViewChild('templateInput', {static: true}) templateInput: ElementRef; |
||||
|
|
||||
|
filteredTemplate: Observable<Array<NotificationTemplate>>; |
||||
|
|
||||
|
searchText = ''; |
||||
|
|
||||
|
private modelValue: EntityId | null; |
||||
|
private dirty = false; |
||||
|
|
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private store: Store<AppState>, |
||||
|
public translate: TranslateService, |
||||
|
public truncate: TruncatePipe, |
||||
|
private entityService: EntityService, |
||||
|
private notificationService: NotificationService, |
||||
|
private fb: FormBuilder) { |
||||
|
this.selectTemplateFormGroup = this.fb.group({ |
||||
|
templateName: [null] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
if (this.required) { |
||||
|
this.selectTemplateFormGroup.get('templateName').addValidators(Validators.required); |
||||
|
this.selectTemplateFormGroup.get('templateName').updateValueAndValidity({emitEvent: false}); |
||||
|
} |
||||
|
this.filteredTemplate = this.selectTemplateFormGroup.get('templateName').valueChanges |
||||
|
.pipe( |
||||
|
debounceTime(150), |
||||
|
tap(value => { |
||||
|
let modelValue; |
||||
|
if (typeof value === 'string' || !value) { |
||||
|
modelValue = null; |
||||
|
} else { |
||||
|
modelValue = value.id; |
||||
|
} |
||||
|
this.updateView(modelValue); |
||||
|
if (value === null) { |
||||
|
this.clear(); |
||||
|
} |
||||
|
}), |
||||
|
map(value => value ? (typeof value === 'string' ? value : value.name) : ''), |
||||
|
distinctUntilChanged(), |
||||
|
switchMap(name => this.fetchTemplate(name)), |
||||
|
share() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void {} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (this.disabled) { |
||||
|
this.selectTemplateFormGroup.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.selectTemplateFormGroup.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
textIsNotEmpty(text: string): boolean { |
||||
|
return (text && text.length > 0); |
||||
|
} |
||||
|
|
||||
|
writeValue(value: EntityId | null): void { |
||||
|
this.searchText = ''; |
||||
|
if (value != null) { |
||||
|
this.notificationService.getNotificationTemplateById(value.id, {ignoreLoading: true, ignoreErrors: true}).subscribe( |
||||
|
(entity) => { |
||||
|
this.modelValue = entity.id; |
||||
|
this.selectTemplateFormGroup.get('templateName').patchValue(entity, {emitEvent: false}); |
||||
|
}, |
||||
|
() => { |
||||
|
this.modelValue = null; |
||||
|
this.selectTemplateFormGroup.get('templateName').patchValue('', {emitEvent: false}); |
||||
|
if (value !== null) { |
||||
|
this.propagateChange(this.modelValue); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} else { |
||||
|
this.modelValue = null; |
||||
|
this.selectTemplateFormGroup.get('templateName').patchValue('', {emitEvent: false}); |
||||
|
} |
||||
|
this.dirty = true; |
||||
|
} |
||||
|
|
||||
|
onFocus() { |
||||
|
if (this.dirty) { |
||||
|
this.selectTemplateFormGroup.get('templateName').updateValueAndValidity({onlySelf: true, emitEvent: true}); |
||||
|
this.dirty = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
reset() { |
||||
|
this.selectTemplateFormGroup.get('templateName').patchValue('', {emitEvent: false}); |
||||
|
} |
||||
|
|
||||
|
updateView(value: EntityId | null) { |
||||
|
if (!isEqual(this.modelValue, value)) { |
||||
|
this.modelValue = value; |
||||
|
this.propagateChange(this.modelValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
displayTemplateFn(queue?: NotificationTemplate): string | undefined { |
||||
|
return queue ? queue.name : undefined; |
||||
|
} |
||||
|
|
||||
|
fetchTemplate(searchText?: string): Observable<Array<NotificationTemplate>> { |
||||
|
this.searchText = searchText; |
||||
|
const pageLink = new PageLink(10, 0, searchText, { |
||||
|
property: 'name', |
||||
|
direction: Direction.ASC |
||||
|
}); |
||||
|
return this.notificationService.getNotificationTemplates(pageLink, {ignoreLoading: true}).pipe( |
||||
|
catchError(() => of(emptyPageData<NotificationTemplate>())), |
||||
|
map(pageData => { |
||||
|
return pageData.data; |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
getDescription(value) { |
||||
|
return value.additionalInfo?.description ? value.additionalInfo.description : |
||||
|
this.translate.instant( |
||||
|
'queue.alt-description', |
||||
|
{submitStrategy: value.submitStrategy.type, processingStrategy: value.processingStrategy.type} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
clear() { |
||||
|
this.selectTemplateFormGroup.get('templateName').patchValue('', {emitEvent: true}); |
||||
|
setTimeout(() => { |
||||
|
this.templateInput.nativeElement.blur(); |
||||
|
this.templateInput.nativeElement.focus(); |
||||
|
}, 0); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,159 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2>{{ dialogTitle | translate }}</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-icon-button |
||||
|
(click)="cancel()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</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)" |
||||
|
(selectionChange)="changeStep($event)"> |
||||
|
<ng-template matStepperIcon="edit"> |
||||
|
<mat-icon>check</mat-icon> |
||||
|
</ng-template> |
||||
|
<mat-step [stepControl]="templateNotificationForm"> |
||||
|
<ng-template matStepLabel>{{ 'notification.basic-settings' | translate }}</ng-template> |
||||
|
<form [formGroup]="templateNotificationForm" style="padding-bottom: 16px;"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>notification.target-name</mat-label> |
||||
|
<input matInput formControlName="name" required> |
||||
|
<mat-error *ngIf="templateNotificationForm.get('name').hasError('required')"> |
||||
|
{{ 'notification.target-name-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<label style="margin-bottom: 8px">{{ 'notification.type' | translate }}</label> |
||||
|
<mat-radio-group formControlName="notificationType"> |
||||
|
<mat-radio-button *ngFor="let notificationType of notificationTypes" [value]="notificationType"> |
||||
|
<div fxLayout="column"> |
||||
|
<div>{{ notificationTemplateTypeTranslateMap.get(notificationType).name | translate }}</div> |
||||
|
<div class="tb-hint">{{ notificationTemplateTypeTranslateMap.get(notificationType).hint }}</div> |
||||
|
</div> |
||||
|
</mat-radio-button> |
||||
|
</mat-radio-group> |
||||
|
<section formGroupName="configuration"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>notification.subject</mat-label> |
||||
|
<input matInput formControlName="notificationSubject" required> |
||||
|
<mat-error *ngIf="templateNotificationForm.get('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="templateNotificationForm.get('configuration.defaultTextTemplate').hasError('required')"> |
||||
|
{{ 'notification.message-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<label class="tb-title tb-required" |
||||
|
[ngClass]="{'tb-error': templateNotificationForm.get('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> |
||||
|
</form> |
||||
|
</mat-step> |
||||
|
<mat-step optional *ngIf="templateNotificationForm.get('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]="templateNotificationForm.get('configuration.notificationSubject').value"> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="mat-block" floatLabel="always"> |
||||
|
<mat-label translate>notification.message</mat-label> |
||||
|
<input matInput formControlName="body" [placeholder]="templateNotificationForm.get('configuration.defaultTextTemplate').value"> |
||||
|
</mat-form-field> |
||||
|
</form> |
||||
|
</mat-step> |
||||
|
<mat-step optional *ngIf="templateNotificationForm.get('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]="templateNotificationForm.get('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="templateNotificationForm.get('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> |
||||
|
<input matInput formControlName="body" [placeholder]="templateNotificationForm.get('configuration.defaultTextTemplate').value"> |
||||
|
</mat-form-field> |
||||
|
</form> |
||||
|
</mat-step> |
||||
|
<mat-step optional *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.SLACK.enabled').value" |
||||
|
[stepControl]="slackTemplateForm"> |
||||
|
<ng-template matStepLabel>{{ 'notification.slack-settings' | translate }}</ng-template> |
||||
|
<form [formGroup]="slackTemplateForm"> |
||||
|
<mat-form-field class="mat-block" floatLabel="always"> |
||||
|
<mat-label translate>notification.message</mat-label> |
||||
|
<input matInput formControlName="body" [placeholder]="templateNotificationForm.get('configuration.defaultTextTemplate').value"> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="mat-block" floatLabel="always"> |
||||
|
<mat-label>Conversation type</mat-label> |
||||
|
<input matInput formControlName="conversationType"> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="mat-block" floatLabel="always"> |
||||
|
<mat-label>Conversation id</mat-label> |
||||
|
<input matInput formControlName="conversationId" required> |
||||
|
<mat-error *ngIf="slackTemplateForm.get('conversationId').hasError('required')"> |
||||
|
{{ 'notification.subject-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</form> |
||||
|
</mat-step> |
||||
|
</mat-horizontal-stepper> |
||||
|
</div> |
||||
|
<mat-divider></mat-divider> |
||||
|
<div mat-dialog-actions fxLayout="row"> |
||||
|
<button mat-stroked-button *ngIf="selectedIndex > 0" |
||||
|
(click)="backStep()">{{ 'action.back' | translate }}</button> |
||||
|
<span fxFlex></span> |
||||
|
<button *ngIf="selectedIndex > 0" |
||||
|
mat-button mat-stroked-button color="primary"> |
||||
|
{{ 'notification.send-test-notification' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button |
||||
|
color="primary" |
||||
|
(click)="nextStep()">{{ nextStepLabel() | translate }}</button> |
||||
|
</div> |
||||
@ -0,0 +1,101 @@ |
|||||
|
/** |
||||
|
* 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 "../../../../../../scss/constants"; |
||||
|
|
||||
|
:host-context(.tb-fullscreen-dialog .mat-dialog-container) { |
||||
|
width: 720px; |
||||
|
|
||||
|
.tb-hint { |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
.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%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media #{$mat-gt-xs} { |
||||
|
.mat-dialog-content { |
||||
|
max-height: 75vh; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
.mat-dialog-content { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
padding: 0 !important; |
||||
|
|
||||
|
.mat-stepper-horizontal { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
overflow: hidden; |
||||
|
@media #{$mat-lt-sm} { |
||||
|
.mat-step-label { |
||||
|
white-space: normal; |
||||
|
overflow: visible; |
||||
|
.mat-step-text-label { |
||||
|
overflow: visible; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.mat-horizontal-content-container { |
||||
|
height: 500px; |
||||
|
max-height: 100%; |
||||
|
width: 100%;; |
||||
|
overflow-y: auto; |
||||
|
scrollbar-gutter: stable; |
||||
|
@media #{$mat-gt-sm} { |
||||
|
min-width: 500px; |
||||
|
} |
||||
|
} |
||||
|
.mat-horizontal-stepper-content[aria-expanded=true] { |
||||
|
height: 100%; |
||||
|
position: relative; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.mat-radio-group { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
|
||||
|
.mat-radio-button { |
||||
|
flex: 1 1 100%; |
||||
|
padding: 14px; |
||||
|
border: 1px solid rgba(0, 0, 0, 0.12); |
||||
|
border-radius: 6px; |
||||
|
|
||||
|
&:not(:last-child){ |
||||
|
margin-right: 8px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,255 @@ |
|||||
|
///
|
||||
|
/// 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 { |
||||
|
NotificationDeliveryMethod, |
||||
|
NotificationDeliveryMethodTranslateMap, |
||||
|
NotificationTemplate, |
||||
|
NotificationTemplateTypeTranslateMap, |
||||
|
NotificationType, |
||||
|
SlackChanelType |
||||
|
} 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 { NotificationService } from '@core/http/notification.service'; |
||||
|
import { deepTrim, isDefined } from '@core/utils'; |
||||
|
import { Observable, Subject } 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'; |
||||
|
|
||||
|
export interface TemplateNotificationDialogData { |
||||
|
template?: NotificationTemplate; |
||||
|
isAdd?: boolean; |
||||
|
isCopy?: boolean; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-template-notification-dialog', |
||||
|
templateUrl: './template-notification-dialog.component.html', |
||||
|
styleUrls: ['./template-notification-dialog.component.scss'] |
||||
|
}) |
||||
|
export class TemplateNotificationDialogComponent |
||||
|
extends DialogComponent<TemplateNotificationDialogComponent, NotificationTemplate> implements OnDestroy { |
||||
|
|
||||
|
@ViewChild('addNotificationTemplate', {static: true}) addNotificationTemplate: MatStepper; |
||||
|
|
||||
|
stepperOrientation: Observable<StepperOrientation>; |
||||
|
|
||||
|
templateNotificationForm: FormGroup; |
||||
|
pushTemplateForm: FormGroup; |
||||
|
emailTemplateForm: FormGroup; |
||||
|
smsTemplateForm: FormGroup; |
||||
|
slackTemplateForm: FormGroup; |
||||
|
dialogTitle = 'notification.edit-notification-template'; |
||||
|
saveButtonLabel = 'action.save'; |
||||
|
|
||||
|
notificationTypes = Object.keys(NotificationType) as NotificationType[]; |
||||
|
notificationDeliveryMethods = Object.keys(NotificationDeliveryMethod) as NotificationDeliveryMethod[]; |
||||
|
notificationTemplateTypeTranslateMap = NotificationTemplateTypeTranslateMap; |
||||
|
notificationDeliveryMethodTranslateMap = NotificationDeliveryMethodTranslateMap; |
||||
|
|
||||
|
selectedIndex = 0; |
||||
|
|
||||
|
tinyMceOptions: Record<string, any>; |
||||
|
|
||||
|
private readonly destroy$ = new Subject<void>(); |
||||
|
|
||||
|
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) { |
||||
|
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')); |
||||
|
|
||||
|
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({method, enabled: method === NotificationDeliveryMethod.PUSH}), {emitEvent: false}); |
||||
|
}); |
||||
|
|
||||
|
this.pushTemplateForm = this.fb.group({ |
||||
|
subject: [''], |
||||
|
body: [''], |
||||
|
icon: [''], |
||||
|
actionButtonConfig: [''] |
||||
|
}); |
||||
|
|
||||
|
this.emailTemplateForm = this.fb.group({ |
||||
|
subject: [''], |
||||
|
body: [''] |
||||
|
}); |
||||
|
|
||||
|
this.smsTemplateForm = this.fb.group({ |
||||
|
body: [''] |
||||
|
}); |
||||
|
|
||||
|
this.slackTemplateForm = this.fb.group({ |
||||
|
body: [''], |
||||
|
conversationId: ['', Validators.required], |
||||
|
conversationType: [SlackChanelType.DIRECT] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
super.ngOnDestroy(); |
||||
|
this.destroy$.next(); |
||||
|
this.destroy$.complete(); |
||||
|
} |
||||
|
|
||||
|
cancel(): void { |
||||
|
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(); |
||||
|
} |
||||
|
|
||||
|
nextStep() { |
||||
|
if (this.selectedIndex >= this.maxStepperIndex) { |
||||
|
this.add(); |
||||
|
} else { |
||||
|
this.addNotificationTemplate.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'; |
||||
|
} |
||||
|
return 'action.next'; |
||||
|
} |
||||
|
|
||||
|
private get maxStepperIndex(): number { |
||||
|
return this.addNotificationTemplate?._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; |
||||
|
} |
||||
|
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( |
||||
|
(target) => this.dialogRef.close(target) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private allValid(): boolean { |
||||
|
return !this.addNotificationTemplate.steps.find((item, index) => { |
||||
|
if (item.stepControl.invalid) { |
||||
|
item.interacted = true; |
||||
|
this.addNotificationTemplate.selectedIndex = index; |
||||
|
return true; |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
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}; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue