Browse Source

UI: Add customize notification web template

pull/7980/head
Vladyslav_Prykhodko 4 years ago
parent
commit
26c4303919
  1. 4
      ui-ngx/src/app/core/http/notification.service.ts
  2. 5
      ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.html
  3. 23
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html
  4. 5
      ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts
  5. 12
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts
  6. 16
      ui-ngx/src/app/modules/home/pages/notification-center/notification-table/request-table-config.ts
  7. 13
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.html
  8. 21
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.component.scss
  9. 9
      ui-ngx/src/app/modules/home/pages/notification-center/request-table/request-notification-dialog.componet.ts
  10. 38
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.html
  11. 14
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.scss
  12. 39
      ui-ngx/src/app/modules/home/pages/notification-center/template-table/template-notification-dialog.component.ts
  13. 11
      ui-ngx/src/app/shared/components/notification/notification.component.html
  14. 7
      ui-ngx/src/app/shared/components/notification/notification.component.scss
  15. 55
      ui-ngx/src/app/shared/components/notification/notification.component.ts
  16. 5
      ui-ngx/src/app/shared/models/notification-ws.models.ts
  17. 19
      ui-ngx/src/app/shared/models/notification.models.ts
  18. 2
      ui-ngx/src/app/shared/pipe/date-ago.pipe.ts
  19. 5
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
ui-ngx/src/app/core/http/notification.service.ts

@ -53,6 +53,10 @@ export class NotificationService {
return this.http.put<void>(`/api/notification/${id}/read`, defaultHttpOptionsFromConfig(config));
}
public markAllNotificationsAsRead(config?: RequestConfig): Observable<void> {
return this.http.put<void>('/api/notifications/read', defaultHttpOptionsFromConfig(config));
}
public createNotificationRequest(notification: NotificationRequest, config?: RequestConfig): Observable<NotificationRequest> {
return this.http.post<NotificationRequest>('/api/notification/request', notification, defaultHttpOptionsFromConfig(config));
}

5
ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.html

@ -26,7 +26,10 @@
<mat-divider></mat-divider>
<section style="min-height: 100px; overflow: auto">
<div *ngFor="let notification of (notifications$ | async); let last = last">
<tb-notification [notification]="notification" (markAsRead)="markAsRead($event)"></tb-notification>
<tb-notification [notification]="notification"
[onClose]="onClose"
(markAsRead)="markAsRead($event)">
</tb-notification>
<mat-divider *ngIf="!last" style="margin-bottom: 4px"></mat-divider>
</div>
</section>

23
ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.html

@ -17,40 +17,25 @@
-->
<div class="tb-notification-center">
<div class="tb-notification-center-content">
<!-- <section fxLayout="row" fxLayoutAlign="space-between center" class="title-container">-->
<!-- <h2 translate class="title">notification.notification-center</h2>-->
<!-- <section class="action-buttons" fxLayoutAlign="end center">-->
<!-- <button mat-icon-button [disabled]="isLoading$ | async" (click)="openSetting()"-->
<!-- matTooltip="{{ 'notification.settings' | translate }}"-->
<!-- matTooltipPosition="above">-->
<!-- <mat-icon>settings</mat-icon>-->
<!-- </button>-->
<!-- <button mat-icon-button [disabled]="isLoading$ | async" (click)="updateData()"-->
<!-- matTooltip="{{ 'action.refresh' | translate }}"-->
<!-- matTooltipPosition="above">-->
<!-- <mat-icon>refresh</mat-icon>-->
<!-- </button>-->
<!-- </section>-->
<!-- </section>-->
<div class="tabs-container">
<mat-tab-group #matTabGroup (selectedIndexChange)="updateData()">
<mat-tab label="{{ 'notification.inbox' | translate }}" #inboxTab="matTab">
<mat-tab label="{{ 'notification.inbox' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.sent' | translate }}">
<mat-tab label="{{ 'notification.sent' | translate }}" #requestTab="matTab">
<tb-notification-table
#notificationRequest
[notificationType]="entityType.NOTIFICATION_REQUEST">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.templates' | translate }}" #templateTab="matTab">
<mat-tab label="{{ 'notification.templates' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_TEMPLATE">
</tb-notification-table>
</mat-tab>
<mat-tab label="{{ 'notification.targets' | translate }}" #targetsTab="matTab">
<mat-tab label="{{ 'notification.targets' | translate }}">
<tb-notification-table
[notificationType]="entityType.NOTIFICATION_TARGET">
</tb-notification-table>

5
ui-ngx/src/app/modules/home/pages/notification-center/notification-center.component.ts

@ -18,7 +18,7 @@ import { Component, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { MatTabGroup } from '@angular/material/tabs';
import { MatTab, MatTabGroup } from '@angular/material/tabs';
import {
NotificationTableComponent
} from '@home/pages/notification-center/notification-table/notification-table.component';
@ -34,6 +34,7 @@ export class NotificationCenterComponent extends PageComponent {
entityType = EntityType;
@ViewChild('matTabGroup', {static: true}) matTabs: MatTabGroup;
@ViewChild('requestTab', {static: true}) requestTab: MatTab;
@ViewChild('notificationRequest', {static: true}) notificationRequestTable: NotificationTableComponent;
@ViewChildren(NotificationTableComponent) tableComponent: QueryList<NotificationTableComponent>;
@ -56,6 +57,6 @@ export class NotificationCenterComponent extends PageComponent {
}
sendNotification($event: Event) {
this.notificationRequestTable.entityTableConfig.onEntityAction({event: $event, action: 'add', entity: null});
this.notificationRequestTable.entityTableConfig.onEntityAction({event: $event, action: this.requestTab.isActive ? 'add' : 'add-without-update', entity: null});
}
}

12
ui-ngx/src/app/modules/home/pages/notification-center/notification-table/inbox-table-config.ts

@ -88,11 +88,13 @@ export class InboxTableConfig extends EntityTableConfig<Notification> {
if ($event) {
$event.stopPropagation();
}
if (this.componentsData.unreadOnly) {
this.getTable().resetSortAndFilter(true);
} else {
this.updateData();
}
this.notificationService.markAllNotificationsAsRead().subscribe(() => {
if (this.componentsData.unreadOnly) {
this.getTable().resetSortAndFilter(true);
} else {
this.updateData();
}
});
}
private markAsRead($event, entity){

16
ui-ngx/src/app/modules/home/pages/notification-center/notification-table/request-table-config.ts

@ -84,7 +84,7 @@ export class RequestTableConfig extends EntityTableConfig<NotificationRequest, P
return [];
}
private createRequest($event: Event, request: NotificationRequest, isAdd = false) {
private createRequest($event: Event, request: NotificationRequest, isAdd = false, updateData = true) {
if ($event) {
$event.stopPropagation();
}
@ -96,12 +96,11 @@ export class RequestTableConfig extends EntityTableConfig<NotificationRequest, P
isAdd,
request
}
}).afterClosed()
.subscribe((res) => {
if (res) {
this.updateData();
}
});
}).afterClosed().subscribe((res) => {
if (res && updateData) {
this.updateData();
}
});
}
private onRequestAction(action: EntityAction<NotificationRequest>): boolean {
@ -109,6 +108,9 @@ export class RequestTableConfig extends EntityTableConfig<NotificationRequest, P
case 'add':
this.createRequest(action.event, action.entity, true);
return true;
case 'add-without-update':
this.createRequest(action.event, action.entity, true, false);
return true;
}
return false;
}

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

@ -57,12 +57,21 @@
strokeWidth="5" *ngIf="(isLoading$ | async) && !preview">
</mat-progress-spinner>
<div *ngIf="preview">
<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>
<div class="group-title">Web notification preview</div>
</div>
<div class="web-preview">
<tb-notification preview [notification]="preview.processedTemplates.PUSH"></tb-notification>
</div>
</section>
<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 class="group-title">{{ 'notification.recipients-count' | translate : {count: preview.totalRecipientsCount} }}</div>
</div>
<div class="details-recipients" *ngIf="notificationRequestForm.get('targets').value.length">
<div class="details-recipients" *ngIf="notificationRequestForm.get('targets').value.length > 1">
<div *ngFor="let detail of preview.recipientsCountByTarget | keyvalue" class="details-recipient">
<span class="number">{{ detail.value }}</span>{{ detail.key }}
</div>

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

@ -14,6 +14,7 @@
* limitations under the License.
*/
@import "../../../../../../scss/constants";
@import "../../../../../../theme";
:host-context(.tb-fullscreen-dialog .mat-dialog-container) {
width: 640px;
@ -24,11 +25,15 @@
border: 1px groove rgba(0, 0, 0, .12);
border-radius: 4px;
&.notification {
background-color: #F3F6FA; //map_get($tb-mat-indigo, 50);
}
.mat-icon {
color: rgba(0, 0, 0, 0.38);
}
.total-recipients {
.group-title {
font-weight: 500;
letter-spacing: .25px;
}
@ -51,6 +56,20 @@
margin-bottom: 8px;
}
}
.web-preview {
flex-direction: row;
align-items: center;
justify-content: center;
display: flex;
tb-notification {
border: 1px groove $tb-primary-color;
border-radius: 4px;
width: 80%;
background-color: #fff;
}
}
}
@media #{$mat-gt-xs} {

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

@ -129,7 +129,14 @@ export class RequestNotificationDialogComponent extends
private getPreview() {
if (this.allValid()) {
this.preview = null;
this.notificationService.getNotificationRequestPreview(this.notificationFormValue).subscribe(
this.notificationService.getNotificationRequestPreview(this.notificationFormValue).pipe(
map(data => {
if (data.processedTemplates.PUSH?.enabled) {
(data.processedTemplates.PUSH as any).text = data.processedTemplates.PUSH.body;
}
return data;
})
).subscribe(
(preview) => this.preview = preview
);
}

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

@ -95,6 +95,44 @@
<mat-label translate>notification.message</mat-label>
<input matInput formControlName="body" [placeholder]="templateNotificationForm.get('configuration.defaultTextTemplate').value">
</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 optional *ngIf="templateNotificationForm.get('configuration.deliveryMethodsTemplates.EMAIL.enabled').value"

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

@ -38,6 +38,19 @@
}
}
.additional-config-group {
padding: 16px 16px 4px;
margin-bottom: 12px;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 6px;
width: 100%;
height: 100%;
.toggle {
margin-bottom: 12px;
}
}
@media #{$mat-gt-xs} {
.mat-dialog-content {
max-height: 75vh;
@ -86,6 +99,7 @@
.mat-radio-group {
display: flex;
flex-direction: row;
margin-bottom: 8px;
.mat-radio-button {
flex: 1 1 100%;

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

@ -32,7 +32,7 @@ import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/f
import { NotificationService } from '@core/http/notification.service';
import { deepTrim, isDefined } from '@core/utils';
import { Observable, Subject } from 'rxjs';
import { map } from 'rxjs/operators';
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';
@ -125,8 +125,41 @@ export class TemplateNotificationDialogComponent
this.pushTemplateForm = this.fb.group({
subject: [''],
body: [''],
icon: [''],
actionButtonConfig: ['']
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({

11
ui-ngx/src/app/shared/components/notification/notification.component.html

@ -16,15 +16,22 @@
-->
<section fxLayout="row" fxLayoutAlign="space-between start">
<mat-icon>account_circle</mat-icon>
<mat-icon class="icon" *ngIf="showIcon" [ngStyle]="{color: notification.additionalConfig.icon.color}">
{{ notification.additionalConfig.icon.icon }}
</mat-icon>
<div class="content" fxFlex>
<div class="title">{{ notification.subject }}</div>
<div class="message">{{ notification.text }}</div>
<button *ngIf="showButton" mat-stroked-button class="button"
[ngStyle]="{color: notification.additionalConfig.actionButtonConfig.color}"
(click)="navigate($event)">
{{ buttonLabel }}
</button>
</div>
<div class="description" fxLayout="column" fxLayoutAlign="start end">
<button mat-icon-button class="tb-mat-28 mark-read" (click)="markRead($event)">
<mat-icon>check_circle_outline</mat-icon>
</button>
<span class="time">{{ notification.createdTime | dateAgo }}</span>
<span class="time" *ngIf="preview">{{ notification.createdTime | dateAgo }}</span>
</div>
</section>

7
ui-ngx/src/app/shared/components/notification/notification.component.scss

@ -17,7 +17,7 @@
padding: 10px;
display: block;
.mat-icon {
.icon {
margin-right: 14px;
color: rgba(0, 0, 0, 0.54);
}
@ -33,6 +33,11 @@
line-height: 16px;
color: rgba(0, 0, 0, 0.76);
}
.button {
margin-top: 8px;
border-color: initial;
}
}
.description {

55
ui-ngx/src/app/shared/components/notification/notification.component.ts

@ -14,28 +14,75 @@
/// limitations under the License.
///
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Notification } from '@shared/models/notification.models';
import { UtilsService } from '@core/services/utils.service';
import { Router } from '@angular/router';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
@Component({
selector: 'tb-notification',
templateUrl: './notification.component.html',
styleUrls: ['./notification.component.scss']
})
export class NotificationComponent {
export class NotificationComponent implements OnInit {
@Input()
notification: Notification;
@Input()
onClose: () => void;
@Output()
markAsRead = new EventEmitter<string>();
constructor() {
private previewValue = false;
get preview(): boolean {
return this.previewValue;
}
@Input()
set preview(value: boolean) {
this.previewValue = coerceBooleanProperty(value);
}
showIcon = false;
showButton = false;
buttonLabel = '';
constructor(
private utils: UtilsService,
private router: Router
) {
}
ngOnInit() {
this.showIcon = this.notification.additionalConfig?.icon?.enabled;
this.showButton = this.notification.additionalConfig?.actionButtonConfig?.enabled;
if (this.showButton) {
this.buttonLabel = this.utils.customTranslation(this.notification.additionalConfig.actionButtonConfig.text,
this.notification.additionalConfig.actionButtonConfig.text);
}
}
markRead($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.markAsRead.next(this.notification.id.id);
if (!this.preview) {
this.markAsRead.next(this.notification.id.id);
}
}
navigate($event: Event) {
if ($event) {
$event.stopPropagation();
}
if (!this.preview) {
this.router.navigateByUrl(this.router.parseUrl(this.notification.additionalConfig.actionButtonConfig.link)).then(() => {
});
if (this.onClose) {
this.onClose();
}
}
}
}

5
ui-ngx/src/app/shared/models/notification-ws.models.ts

@ -183,11 +183,12 @@ export class UnsubscribeCmd implements WebsocketCmd {
cmdId: number;
}
export class MarkAsReadCmd extends UnreadCountSubCmd {
export class MarkAsReadCmd implements WebsocketCmd {
cmdId: number;
notifications: string[];
constructor(ids: string[]) {
super();
this.notifications = ids;
}
}

19
ui-ngx/src/app/shared/models/notification.models.ts

@ -35,6 +35,8 @@ export interface Notification {
readonly info: NotificationInfo;
readonly originatorType: NotificationOriginatorType;
readonly status: NotificationStatus;
readonly createdTime: number;
readonly additionalConfig?: PushDeliveryMethodAdditionalConfig;
}
export interface NotificationInfo {
@ -155,8 +157,21 @@ export interface DeliveryMethodNotificationTemplate extends
interface PushDeliveryMethodNotificationTemplate {
subject?: string;
icon?: string;
actionButtonConfig?: string;
additionalConfig: PushDeliveryMethodAdditionalConfig;
}
interface PushDeliveryMethodAdditionalConfig {
icon: {
enabled: boolean;
icon: string;
color: string;
};
actionButtonConfig: {
enabled: boolean;
text: string;
color: string;
link: string;
};
}
interface EmailDeliveryMethodNotificationTemplate {

2
ui-ngx/src/app/shared/pipe/date-ago.pipe.ts

@ -51,7 +51,7 @@ export class DateAgoPipe implements PipeTransform {
}
}
}
return value.toString();
return '';
}
}

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

@ -2669,6 +2669,7 @@
"copied": "Copied!"
},
"notification": {
"action-button": "Action button",
"add-notification-target": "Add notification target",
"add-notification-template": "Add notification template",
"add-target": "Add target",
@ -2676,6 +2677,8 @@
"at-least-one-should-be-selected": "At least one should be selected",
"all": "All",
"basic-settings": "Basic settings",
"button-text": "Button text",
"button-text-required": "Button text is required",
"compose": "Compose",
"copy-notification-template": "Copy notification template",
"create-target": "Create target",
@ -2700,6 +2703,8 @@
"email-settings": "Email settings",
"get-customer-id-from-originator": "Get Customer id from originator",
"inbox": "Inbox",
"link": "Link",
"link-required": "Link is required",
"management": "Notification management",
"mark-all-as-read": "Mark all as read",
"mark-as-read": "Mark as read",

Loading…
Cancel
Save