diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/DataType.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationDataType.java similarity index 61% rename from dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/DataType.java rename to dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationDataType.java index 82706d344..a0d9918b3 100644 --- a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/DataType.java +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/enums/NotificationDataType.java @@ -5,7 +5,7 @@ import gr.cite.notification.data.conventers.DatabaseEnum; import java.util.Map; -public enum DataType implements DatabaseEnum { +public enum NotificationDataType implements DatabaseEnum { Integer((short)0), Decimal((short)1), Double((short)2), @@ -14,7 +14,7 @@ public enum DataType implements DatabaseEnum { String((short)5); private final Short value; - DataType(Short value) { + NotificationDataType(Short value) { this.value = value; } @@ -23,9 +23,9 @@ public enum DataType implements DatabaseEnum { return value; } - private static final Map map = EnumUtils.getEnumValueMap(DataType.class); + private static final Map map = EnumUtils.getEnumValueMap(NotificationDataType.class); - public static DataType of(Short i) { + public static NotificationDataType of(Short i) { return map.get(i); } } diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/FieldInfo.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/FieldInfo.java index 69aa67529..b4c0ee47b 100644 --- a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/FieldInfo.java +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notification/FieldInfo.java @@ -1,16 +1,16 @@ package gr.cite.notification.common.types.notification; -import gr.cite.notification.common.enums.DataType; +import gr.cite.notification.common.enums.NotificationDataType; public class FieldInfo { private String key; public final static String _key = "key"; - private DataType type; + private NotificationDataType type; public final static String _type = "type"; private String value; public final static String _value = "value"; - public FieldInfo(String key, DataType type, String value) { + public FieldInfo(String key, NotificationDataType type, String value) { this.key = key; this.type = type; this.value = value; @@ -27,11 +27,11 @@ public class FieldInfo { this.key = key; } - public DataType getType() { + public NotificationDataType getType() { return type; } - public void setType(DataType type) { + public void setType(NotificationDataType type) { this.type = type; } diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notificationtemplate/FieldInfoEntity.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notificationtemplate/FieldInfoEntity.java index 93e93bd9d..90afc8a87 100644 --- a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notificationtemplate/FieldInfoEntity.java +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/common/types/notificationtemplate/FieldInfoEntity.java @@ -1,13 +1,13 @@ package gr.cite.notification.common.types.notificationtemplate; -import gr.cite.notification.common.enums.DataType; +import gr.cite.notification.common.enums.NotificationDataType; public class FieldInfoEntity { private String key; - private DataType type; + private NotificationDataType type; private String value; - public FieldInfoEntity(String key, DataType type, String value) { + public FieldInfoEntity(String key, NotificationDataType type, String value) { this.key = key; this.type = type; this.value = value; @@ -24,11 +24,11 @@ public class FieldInfoEntity { this.key = key; } - public DataType getType() { + public NotificationDataType getType() { return type; } - public void setType(DataType type) { + public void setType(NotificationDataType type) { this.type = type; } diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/NotificationTemplatePersist.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/NotificationTemplatePersist.java index 71927dbad..81ba162b3 100644 --- a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/NotificationTemplatePersist.java +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/NotificationTemplatePersist.java @@ -18,8 +18,6 @@ public class NotificationTemplatePersist { @ValidEnum private NotificationTemplateChannel channel; - @NotNull(message = "{validation.empty}") - @Valid private UUID notificationType; @ValidEnum diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/notificationtemplate/FieldInfoPersist.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/notificationtemplate/FieldInfoPersist.java index c61b8ae0b..26ef1a4a6 100644 --- a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/notificationtemplate/FieldInfoPersist.java +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/model/persist/notificationtemplate/FieldInfoPersist.java @@ -1,6 +1,6 @@ package gr.cite.notification.model.persist.notificationtemplate; -import gr.cite.notification.common.enums.DataType; +import gr.cite.notification.common.enums.NotificationDataType; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; @@ -13,13 +13,13 @@ public class FieldInfoPersist { @NotNull @NotEmpty - private DataType type; + private NotificationDataType type; @NotNull @NotEmpty private String value; - public FieldInfoPersist(String key, DataType type, String value) { + public FieldInfoPersist(String key, NotificationDataType type, String value) { this.key = key; this.type = type; this.value = value; @@ -36,11 +36,11 @@ public class FieldInfoPersist { this.key = key; } - public DataType getType() { + public NotificationDataType getType() { return type; } - public void setType(DataType type) { + public void setType(NotificationDataType type) { this.type = type; } diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/NotificationTemplateQuery.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/NotificationTemplateQuery.java index b06888c6b..bcffcd759 100644 --- a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/NotificationTemplateQuery.java +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/query/NotificationTemplateQuery.java @@ -180,7 +180,7 @@ public class NotificationTemplateQuery extends QueryBase { FieldInfo fieldInfo = new FieldInfo(); fieldInfo.setKey(field.getKey()); - fieldInfo.setType(DataType.valueOf(field.getType())); + fieldInfo.setType(NotificationDataType.valueOf(field.getType())); fieldInfo.setValue(field.getValue()); return fieldInfo; }).collect(Collectors.toList())); diff --git a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationtemplate/NotificationServiceTemplateImpl.java b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationtemplate/NotificationServiceTemplateImpl.java index 3155d5759..0787f468b 100644 --- a/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationtemplate/NotificationServiceTemplateImpl.java +++ b/dmp-backend/notification-service/notification/src/main/java/gr/cite/notification/service/notificationtemplate/NotificationServiceTemplateImpl.java @@ -92,16 +92,19 @@ public class NotificationServiceTemplateImpl implements NotificationTemplateServ data = this.entityManager.find(NotificationTemplateEntity.class, model.getId()); if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), NotificationTemplate.class.getSimpleName()}, LocaleContextHolder.getLocale())); if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(model.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage()); + if (model.getNotificationType() != null){ + data.setNotificationType(model.getNotificationType()); + } } else { data = new NotificationTemplateEntity(); data.setId(UUID.randomUUID()); data.setIsActive(IsActive.Active); data.setCreatedAt(Instant.now()); + data.setNotificationType(UUID.randomUUID()); } data.setChannel(model.getChannel()); data.setKind(model.getKind()); - data.setNotificationType(model.getNotificationType()); data.setLanguageId(model.getLanguageId()); data.setValue(this.jsonHandlingService.toJsonSafe(this.buildNotificationTemplateValueEntity(model.getValue()))); data.setUpdatedAt(Instant.now()); diff --git a/dmp-frontend/src/app/app-routing.module.ts b/dmp-frontend/src/app/app-routing.module.ts index c8c054f8d..6a6f50247 100644 --- a/dmp-frontend/src/app/app-routing.module.ts +++ b/dmp-frontend/src/app/app-routing.module.ts @@ -295,6 +295,18 @@ const appRoutes: Routes = [ }) }, }, + { + path: 'notification-templates', + loadChildren: () => import('./ui/admin/notification-template/notification-template.module').then(m => m.NotificationTemplateModule), + data: { + authContext: { + permissions: [AppPermission.ViewNotificationTemplatePage] + }, + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.NOTIFICATION-TEMPLATES' + }) + }, + }, { path: 'index-managment', loadChildren: () => import('./ui/admin/index-managment/index-managment.module').then(m => m.IndexManagmentModule), diff --git a/dmp-frontend/src/app/core/common/enum/email-override-mode.ts b/dmp-frontend/src/app/core/common/enum/email-override-mode.ts new file mode 100644 index 000000000..45391e9b3 --- /dev/null +++ b/dmp-frontend/src/app/core/common/enum/email-override-mode.ts @@ -0,0 +1,5 @@ +export enum EmailOverrideMode { + NotOverride = 0, + Additive = 1, + Replace = 2 +} \ No newline at end of file diff --git a/dmp-frontend/src/app/core/common/enum/notification-data-type.ts b/dmp-frontend/src/app/core/common/enum/notification-data-type.ts new file mode 100644 index 000000000..249e92573 --- /dev/null +++ b/dmp-frontend/src/app/core/common/enum/notification-data-type.ts @@ -0,0 +1,7 @@ +export enum NotificationDataType { + Integer = 0, + Demical = 1, + Double = 2, + DateTime = 3, + String = 5 +} \ No newline at end of file diff --git a/dmp-frontend/src/app/core/common/enum/notification-template-channel.ts b/dmp-frontend/src/app/core/common/enum/notification-template-channel.ts new file mode 100644 index 000000000..b4f328a34 --- /dev/null +++ b/dmp-frontend/src/app/core/common/enum/notification-template-channel.ts @@ -0,0 +1,4 @@ +export enum NotificationTemplateChannel { + Email = 0, + InApp = 1, +} \ No newline at end of file diff --git a/dmp-frontend/src/app/core/common/enum/notification-template-kind.ts b/dmp-frontend/src/app/core/common/enum/notification-template-kind.ts new file mode 100644 index 000000000..ec0e8a47e --- /dev/null +++ b/dmp-frontend/src/app/core/common/enum/notification-template-kind.ts @@ -0,0 +1,6 @@ +export enum NotificationTemplateKind { + Draft = 0, + Secondary = 1, + Primary = 2, + Default = 3 +} diff --git a/dmp-frontend/src/app/core/common/enum/permission.enum.ts b/dmp-frontend/src/app/core/common/enum/permission.enum.ts index e3b7fb10a..cd71ed394 100644 --- a/dmp-frontend/src/app/core/common/enum/permission.enum.ts +++ b/dmp-frontend/src/app/core/common/enum/permission.enum.ts @@ -28,6 +28,7 @@ export enum AppPermission { ViewReferencePage = 'ViewReferencePage', ViewTenantPage = 'ViewTenantPage', ViewLanguagePage = "ViewLanguagePage", + ViewNotificationTemplatePage = "ViewNotificationTemplatePage", //ReferenceType BrowseReferenceType = "BrowseReferenceType", @@ -54,5 +55,10 @@ export enum AppPermission { BrowseLanguage = "BrowseLanguage", EditLanguage = "EditLanguage", DeleteLanguage = "DeleteLanguage", + + //Notification Template + BrowseNotificationTemplate = "BrowseNotificationTemplate", + EditNotificationTemplate = "EditNotificationTemplate", + DeleteNotificationTemplate = "DeleteNotificationTemplate", } diff --git a/dmp-frontend/src/app/core/core-service.module.ts b/dmp-frontend/src/app/core/core-service.module.ts index deca49579..9e6ae0795 100644 --- a/dmp-frontend/src/app/core/core-service.module.ts +++ b/dmp-frontend/src/app/core/core-service.module.ts @@ -64,6 +64,7 @@ import { QueryParamsService } from './services/utilities/query-params.service'; import { LanguageHttpService } from './services/language/language.http.service'; import { DescriptionService } from './services/description/description.service'; import { MaintenanceService } from './services/maintenance/maintenance.service'; +import { NotificationTemplateService } from './services/notification-template/notification-template.service'; // // // This is shared module that provides all the services. Its imported only once on the AppModule. @@ -147,7 +148,8 @@ export class CoreServiceModule { LanguageHttpService, DmpServiceNew, DescriptionService, - MaintenanceService + MaintenanceService, + NotificationTemplateService ], }; } diff --git a/dmp-frontend/src/app/core/model/notification-template/notification-template.ts b/dmp-frontend/src/app/core/model/notification-template/notification-template.ts new file mode 100644 index 000000000..c59c0e924 --- /dev/null +++ b/dmp-frontend/src/app/core/model/notification-template/notification-template.ts @@ -0,0 +1,80 @@ +import { NotificationTemplateChannel } from "@app/core/common/enum/notification-template-channel"; +import { NotificationTemplateKind } from "@app/core/common/enum/notification-template-kind"; +import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model"; +import { Guid } from "@common/types/guid"; +import { Language } from "../language/language"; +import { NotificationDataType } from "@app/core/common/enum/notification-data-type"; +import { EmailOverrideMode } from "@app/core/common/enum/email-override-mode"; + +export interface NotificationTemplate extends BaseEntity{ + channel: NotificationTemplateChannel; + notificationType: Guid; + kind: NotificationTemplateKind; + language: Language; + value: NotificationTemplateValue; +} + +export interface NotificationTemplateValue { + subjectText: string; + subjectKey: string; + subjectFieldOptions: NotificationFieldOptions; + bodyText: string; + bodyKey: string; + priorityKey: string; + allowAttachments: Boolean; + cc: string[]; + ccMode: EmailOverrideMode; + bcc: string[]; + bccMode: EmailOverrideMode; + extraDataKeys: string[]; + bodyFieldOptions: NotificationFieldOptions; +} + +export interface NotificationFieldOptions { + mandatory?: string[]; + options?: NotificationFieldInfo[]; + formatting?: { [key: string]: string }; +} + +export interface NotificationFieldInfo { + key: string; + dataType: NotificationDataType, + value: string; +} + +// Persist + +export interface NotificationTemplatePersist extends BaseEntityPersist{ + channel: NotificationTemplateChannel; + kind: NotificationTemplateKind; + languageId: Guid; + value: NotificationTemplateValuePersist; +} + +export interface NotificationTemplateValuePersist { + subjectText: string; + subjectKey: string; + subjectFieldOptions: NotificationFieldOptionsPersist; + bodyText: string; + bodyKey: string; + priorityKey: string; + allowAttachments: Boolean; + cc: string[]; + ccMode: EmailOverrideMode; + bcc: string[]; + bccMode: EmailOverrideMode; + extraDataKeys: string[]; + bodyFieldOptions: NotificationFieldOptionsPersist; +} + +export interface NotificationFieldOptionsPersist { + mandatory?: string[]; + options?: NotificationFieldInfoPersist[]; + formatting?: { [key: string]: string }; +} + +export interface NotificationFieldInfoPersist { + key: string; + dataType: NotificationDataType, + value: string; +} \ No newline at end of file diff --git a/dmp-frontend/src/app/core/query/notification-template.lookup.ts b/dmp-frontend/src/app/core/query/notification-template.lookup.ts new file mode 100644 index 000000000..6d24e6be8 --- /dev/null +++ b/dmp-frontend/src/app/core/query/notification-template.lookup.ts @@ -0,0 +1,25 @@ +import { Lookup } from "@common/model/lookup"; +import { Guid } from "@common/types/guid"; +import { IsActive } from "../common/enum/is-active.enum"; +import { NotificationTemplateKind } from "../common/enum/notification-template-kind"; +import { NotificationTemplateChannel } from "../common/enum/notification-template-channel"; + +export class NotificationTemplateLookup extends Lookup implements NotificationTemplateFilter { + ids: Guid[]; + excludedIds: Guid[]; + isActive: IsActive[]; + kinds: NotificationTemplateKind[]; + channels: NotificationTemplateChannel[]; + + constructor() { + super(); + } +} + +export interface NotificationTemplateFilter { + ids: Guid[]; + excludedIds: Guid[]; + isActive: IsActive[]; + kinds: NotificationTemplateKind[]; + channels: NotificationTemplateChannel[]; +} \ No newline at end of file diff --git a/dmp-frontend/src/app/core/services/notification-template/notification-template.service.ts b/dmp-frontend/src/app/core/services/notification-template/notification-template.service.ts new file mode 100644 index 000000000..c82dcb395 --- /dev/null +++ b/dmp-frontend/src/app/core/services/notification-template/notification-template.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@angular/core'; +import { QueryResult } from '@common/model/query-result'; +import { Guid } from '@common/types/guid'; +import { NotificationTemplateKind } from '@app/core/common/enum/notification-template-kind'; +import { NotificationTemplateLookup } from '@app/core/query/notification-template.lookup'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { NotificationTemplate, NotificationTemplatePersist } from '@app/core/model/notification-template/notification-template'; +import { BaseHttpV2Service } from '../http/base-http-v2.service'; + +@Injectable() +export class NotificationTemplateService { + + constructor(private http: BaseHttpV2Service) { + } + + //TODO ADD CONIFG URL + private get apiBase(): string { return `http://localhost:8081/api/notification-template`; } + + query(q: NotificationTemplateLookup): Observable> { + const url = `${this.apiBase}/query`; + + return this.http + .post>(url, q).pipe( + catchError((error: any) => throwError(error))); + } + + getSingle(id: Guid, reqFields: string[] = []): Observable { + const url = `${this.apiBase}/${id}`; + const options = { params: { f: reqFields } }; + + return this.http + .get(url, options).pipe( + catchError((error: any) => throwError(error))); + } + + updateKind(id: Guid, kind: NotificationTemplateKind): Observable { + const url = `${this.apiBase}/${id}/${kind}`; + + return this.http + .post(url, {}).pipe( + catchError((error: any) => throwError(error))); + } + + persist(item: NotificationTemplatePersist): Observable { + const url = `${this.apiBase}/persist`; + + return this.http + .post(url, item).pipe( + catchError((error: any) => throwError(error))); + } + + delete(id: Guid): Observable { + const url = `${this.apiBase}/${id}`; + + return this.http + .delete(url).pipe( + catchError((error: any) => throwError(error))); + } +} \ No newline at end of file diff --git a/dmp-frontend/src/app/core/services/utilities/enum-utils.service.ts b/dmp-frontend/src/app/core/services/utilities/enum-utils.service.ts index be8df9173..5b3c35f56 100644 --- a/dmp-frontend/src/app/core/services/utilities/enum-utils.service.ts +++ b/dmp-frontend/src/app/core/services/utilities/enum-utils.service.ts @@ -28,6 +28,10 @@ import { DmpStatus } from '../../common/enum/dmp-status'; import { ValidationType } from '../../common/enum/validation-type'; import { IsActive } from '@app/core/common/enum/is-active.enum'; import { DmpUserRole } from '@app/core/common/enum/dmp-user-role'; +import { NotificationTemplateKind } from '@app/core/common/enum/notification-template-kind'; +import { NotificationTemplateChannel } from '@app/core/common/enum/notification-template-channel'; +import { NotificationDataType } from '@app/core/common/enum/notification-data-type'; +import { EmailOverrideMode } from '@app/core/common/enum/email-override-mode'; @Injectable() export class EnumUtils { @@ -376,4 +380,38 @@ export class EnumUtils { case DmpUserRole.User: return this.language.instant('TYPES.DMP-USER-ROLE.USER'); } } + + toNotificationTemplateKindString(status: NotificationTemplateKind): string { + switch (status) { + case NotificationTemplateKind.Draft: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-KIND.DRAFT'); + case NotificationTemplateKind.Secondary: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-KIND.SECONDARY'); + case NotificationTemplateKind.Primary: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-KIND.PRIMARY'); + case NotificationTemplateKind.Default: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-KIND.DEFAULT'); + } + } + + toNotificationTemplateChannelString(status: NotificationTemplateChannel): string { + switch (status) { + case NotificationTemplateChannel.Email: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-CHANNEL.EMAIL'); + case NotificationTemplateChannel.InApp: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-CHANNEL.INAPP'); + } + } + + toNotificationTemplateDataTypeString(status: NotificationDataType): string { + switch (status) { + case NotificationDataType.Integer: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-DATA-TYPE.INTEGER'); + case NotificationDataType.Demical: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-DATA-TYPE.DEMICAL'); + case NotificationDataType.Double: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-DATA-TYPE.DOUBLE'); + case NotificationDataType.DateTime: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-DATA-TYPE.DATE-TIME'); + case NotificationDataType.String: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-DATA-TYPE.STRING'); + } + } + + toEmailOverrideModeString(status: EmailOverrideMode): string { + switch (status) { + case EmailOverrideMode.NotOverride: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-EMAIL-OVERRIDE-MODE.NOT'); + case EmailOverrideMode.Additive: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-EMAIL-OVERRIDE-MODE.ADDITIVE'); + case EmailOverrideMode.Replace: return this.language.instant('TYPES.NOTIFICATION-TEMPLATE-EMAIL-OVERRIDE-MODE.REPLACE'); + } + } } diff --git a/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.component.html b/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.component.html new file mode 100644 index 000000000..bb98a6ec8 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.component.html @@ -0,0 +1,345 @@ +
+
+ +
+
+

{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.NEW' | translate}}

+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.NEW' | translate}} + + +
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.LANGUAGE' | translate}} + + + {{languageCode}} + + + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.KIND' | translate}} + + + {{enumUtils.toNotificationTemplateKindString(kind)}} + + + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.CHANNEL' | translate}} + + + {{enumUtils.toNotificationTemplateChannelString(channel)}} + + + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+ + +
+

{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.SUBJECT-SECTION' | translate}}

+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.SUBJECT-TEXT' | translate}} + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.SUBJECT-KEY' | translate}} + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+
+

{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.SUBJECT-FIELD-OPTIONS' | translate}} + +

+
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.MANDATORY' | translate}} + + + {{field}} + + + + + +
+

{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.OPTIONAL-TITLE' | translate}}

+
+
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.KEY' | translate}} + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.DATA-TYPE' | translate}} + + + {{enumUtils.toNotificationTemplateDataTypeString(type)}} + + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.VALUE' | translate}} + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+ +
+
+
+
+
+ +
+
+
+
+ + +
+

{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.BODY-SECTION' | translate}}

+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.BODY-TEXT' | translate}} + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.BODY-KEY' | translate}} + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+
+

{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.BODY-FIELD-OPTIONS' | translate}} + +

+
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.MANDATORY' | translate}} + + + {{field}} + + + + + +
+

{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.OPTIONAL-TITLE' | translate}}

+
+
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.KEY' | translate}} + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.DATA-TYPE' | translate}} + + + {{enumUtils.toNotificationTemplateDataTypeString(type)}} + + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.VALUE' | translate}} + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+ +
+
+
+
+
+ +
+
+
+
+ +
+

{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.EXTRA-OPTIONS' | translate}}

+
+

{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.ALLOW-ATTACHMENTS' | translate}} + +

+
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.PRIORITY-KEY' | translate}} + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.CC' | translate}} + + + {{field}} + + + + + +
+
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.CC-MODE' | translate}} + + + {{enumUtils.toEmailOverrideModeString(emailOverrideMode)}} + + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+
+ +
+ + {{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.BCC-MODE' | translate}} + + + {{enumUtils.toEmailOverrideModeString(emailOverrideMode)}} + + + {{'COMMONS.VALIDATION.REQUIRED' | translate}} + +
+ + +
+
+
+ +
+
+ diff --git a/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.component.scss b/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.component.scss new file mode 100644 index 000000000..60e3c667a --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.component.scss @@ -0,0 +1,43 @@ +.notification-template-editor { + margin-top: 1.3rem; + margin-left: 1em; + margin-right: 3em; + + .remove { + background-color: white; + color: black; + } + + .add { + background-color: white; + color: #009700; + } + } + + ::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background, .mat-checkbox-indeterminate.mat-accent .mat-checkbox-background { + background-color: var(--primary-color-3); + // background-color: #0070c0; + } + + ::ng-deep .mat-checkbox-disabled.mat-checkbox-checked .mat-checkbox-background, .mat-checkbox-disabled.mat-checkbox-indeterminate .mat-checkbox-background { + background-color: #b0b0b0; + } + + .action-btn { + border-radius: 30px; + background-color: var(--secondary-color); + border: 1px solid transparent; + padding-left: 2em; + padding-right: 2em; + box-shadow: 0px 3px 6px #1E202029; + + transition-property: background-color, color; + transition-duration: 200ms; + transition-delay: 50ms; + transition-timing-function: ease-in-out; + &:disabled{ + background-color: #CBCBCB; + color: #FFF; + border: 0px; + } + } diff --git a/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.component.ts b/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.component.ts new file mode 100644 index 000000000..6d1c7b937 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.component.ts @@ -0,0 +1,350 @@ +import { Component, OnInit } from '@angular/core'; +import { FormArray, UntypedFormGroup } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { FormService } from '@common/forms/form-service'; +import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; +import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; +import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; +import { TranslateService } from '@ngx-translate/core'; +import { NotificationTemplateKind } from '@app/core/common/enum/notification-template-kind'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { NotificationTemplate, NotificationTemplatePersist } from '@app/core/model/notification-template/notification-template'; +import { NotificationTemplateService } from '@app/core/services/notification-template/notification-template.service'; +import { NotificationFieldInfoEditorModel, NotificationFieldOptionsEditorModel, NotificationTemplateEditorModel } from './notification-template-editor.model'; +import { map, takeUntil } from 'rxjs/operators'; +import { BaseEditor } from '@common/base/base-editor'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FilterService } from '@common/modules/text-filter/filter-service'; +import { DatePipe } from '@angular/common'; +import { QueryParamsService } from '@app/core/services/utilities/query-params.service'; +import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; +import { MatomoService } from '@app/core/services/matomo/matomo-service'; +import { NotificationTemplateEditorService } from './notification-template-editor.service'; +import { Guid } from '@common/types/guid'; +import { NotificationTemplateEditorResolver } from './notification-template-editor.resolver'; +import { IsActive } from '@app/core/common/enum/is-active.enum'; +import { LoggingService } from '@app/core/services/logging/logging-service'; +import { LanguageHttpService } from '@app/core/services/language/language.http.service'; +import { Language } from '@app/core/model/language/language'; +import { nameof } from 'ts-simple-nameof'; +import { NotificationTemplateChannel } from '@app/core/common/enum/notification-template-channel'; +import { MatChipEditedEvent, MatChipInputEvent } from '@angular/material/chips'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { NotificationDataType } from '@app/core/common/enum/notification-data-type'; +import { EmailOverrideMode } from '@app/core/common/enum/email-override-mode'; + +@Component({ + selector: 'app-notification-template-editor', + templateUrl: './notification-template-editor.component.html', + styleUrls: ['./notification-template-editor.component.scss'], + providers: [NotificationTemplateEditorService] +}) +export class NotificationTemplateEditorComponent extends BaseEditor implements OnInit { + + isNew = true; + isDeleted = false; + formGroup: UntypedFormGroup = null; + availableLanguageCodes: string[] = []; + selectedLangId: Guid = null; + subjectMandatoryFields: string[] = []; + bodyMandatoryFields: string[] = []; + subjectFieldOptionsEnabled: Boolean = false; + bodyFieldOptionsEnabled: Boolean = false; + ccValues: string[] = []; + bccValues: string[] = []; + extraDataKeys: string[] = []; + public notificationTemplateKindEnum = this.enumUtils.getEnumValues(NotificationTemplateKind); + public notificationTemplateChannelEnum = this.enumUtils.getEnumValues(NotificationTemplateChannel); + public notificationDataTypeEnum = this.enumUtils.getEnumValues(NotificationDataType); + public emailOverrideModeEnum = this.enumUtils.getEnumValues(EmailOverrideMode); + + protected get canDelete(): boolean { + return !this.isDeleted && !this.isNew && this.hasPermission(this.authService.permissionEnum.DeleteNotificationTemplate); + } + + protected get canSave(): boolean { + return !this.isDeleted && this.hasPermission(this.authService.permissionEnum.EditNotificationTemplate); + } + + private hasPermission(permission: AppPermission): boolean { + return this.authService.hasPermission(permission) || this.editorModel?.permissions?.includes(permission); + } + + constructor( + // BaseFormEditor injected dependencies + protected dialog: MatDialog, + protected language: TranslateService, + protected formService: FormService, + protected router: Router, + protected uiNotificationService: UiNotificationService, + protected httpErrorHandlingService: HttpErrorHandlingService, + protected filterService: FilterService, + protected datePipe: DatePipe, + protected route: ActivatedRoute, + protected queryParamsService: QueryParamsService, + // Rest dependencies. Inject any other needed deps here: + public authService: AuthService, + public enumUtils: EnumUtils, + private languageHttpService: LanguageHttpService, + private notificationTemplateService: NotificationTemplateService, + private notificationTemplateEditorService: NotificationTemplateEditorService, + private logger: LoggingService, + private matomoService: MatomoService + ) { + super(dialog, language, formService, router, uiNotificationService, httpErrorHandlingService, filterService, datePipe, route, queryParamsService); + } + + // ngOnInit(): void { + // this.editorModel = new NotificationTemplateEditorModel().fromModel(this.notificationTemplate); + // this.formGroup = this.editorModel.buildForm(null, this.editorModel.kind === NotificationTemplateKind.Default ? true : !this.authService.permissionEnum.EditDescription); + // } + + ngOnInit(): void { + this.matomoService.trackPageView('Admin: Notification Tempplates'); + super.ngOnInit(); + this.languageHttpService.queryAvailableCodes(this.languageHttpService.buildAutocompleteLookup()) + .pipe(takeUntil(this._destroyed)) + .subscribe( + data => this.availableLanguageCodes = data.items, + ); + } + + getItem(itemId: Guid, successFunction: (item: NotificationTemplate) => void) { + this.notificationTemplateService.getSingle(itemId, NotificationTemplateEditorResolver.lookupFields()) + .pipe(map(data => data as NotificationTemplate), takeUntil(this._destroyed)) + .subscribe( + data => successFunction(data), + error => this.onCallbackError(error) + ); + } + + prepareForm(data: NotificationTemplate) { + try { + this.editorModel = data ? new NotificationTemplateEditorModel().fromModel(data) : new NotificationTemplateEditorModel(); + + if(data){ + if(data.value && data.value.subjectFieldOptions){ + this.subjectFieldOptionsEnabled = true; + } + } + this.isDeleted = data ? data.isActive === IsActive.Inactive : false; + this.buildForm(); + } catch (error) { + this.logger.error('Could not parse Tenant item: ' + data + error); + this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error); + } + } + + buildForm() { + this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !this.authService.hasPermission(AppPermission.EditNotificationTemplate)); + this.notificationTemplateEditorService.setValidationErrorModel(this.editorModel.validationErrorModel); + } + + refreshData(): void { + this.getItem(this.editorModel.id, (data: NotificationTemplate) => this.prepareForm(data)); + } + + refreshOnNavigateToData(id?: Guid): void { + this.formGroup.markAsPristine(); + let route = []; + + if (id === null) { + route.push('../..'); + } else if (this.isNew) { + route.push('../' + id); + } else { + route.push('..'); + } + + this.router.navigate(route, { queryParams: { 'lookup': this.queryParamsService.serializeLookup(this.lookupParams), 'lv': ++this.lv }, replaceUrl: true, relativeTo: this.route }); + } + + persistEntity(onSuccess?: (response) => void): void { + const formData = this.formService.getValue(this.formGroup.value) as NotificationTemplatePersist; + + this.notificationTemplateService.persist(formData) + .pipe(takeUntil(this._destroyed)).subscribe( + complete => onSuccess ? onSuccess(complete) : this.onCallbackSuccess(complete), + error => this.onCallbackError(error) + ); + } + + formSubmit(): void { + this.formService.touchAllFormFields(this.formGroup); + if (!this.isFormValid()) { + return; + } + + this.persistEntity(); + } + + public delete() { + const value = this.formGroup.value; + if (value.id) { + const dialogRef = this.dialog.open(ConfirmationDialogComponent, { + maxWidth: '300px', + data: { + message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'), + confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'), + cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL') + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { + if (result) { + this.notificationTemplateService.delete(value.id).pipe(takeUntil(this._destroyed)) + .subscribe( + complete => this.onCallbackSuccess(), + error => this.onCallbackError(error) + ); + } + }); + } + } + + clearErrorModel() { + this.editorModel.validationErrorModel.clear(); + this.formService.validateAllFormFields(this.formGroup); + } + + selectedLangChanged(code: string){ + + this.languageHttpService.getSingleWithCode(code, [nameof(x => x.id),nameof(x => x.code)]) + .pipe(takeUntil(this._destroyed)) + .subscribe( + data => { + this.selectedLangId = data.id + console.log(this.formGroup);} + ); + } + + + subjectFieldOptionsSelectionChanged(matCheckBox: MatCheckboxChange){ + if(matCheckBox.checked == true){ + this.subjectFieldOptionsEnabled = true; + }else{ + this.subjectFieldOptionsEnabled = false; + } + } + + bodyFieldOptionsSelectionChanged(matCheckBox: MatCheckboxChange){ + if(matCheckBox.checked == true){ + this.bodyFieldOptionsEnabled = true; + }else{ + this.bodyFieldOptionsEnabled = false; + } + } + + // subject mandatory + + addSubjectMandatory(event: MatChipInputEvent): void { + const value = (event.value || '').trim(); + + if (value) this.subjectMandatoryFields.push(value) + event.chipInput!.clear(); + } + + removeSubjectMandatory(field: string): void { + const index = this.subjectMandatoryFields.indexOf(field); + + if (index >= 0) this.subjectMandatoryFields.splice(index, 1); + } + + editSubjectMandatory(field: string, event: MatChipEditedEvent) { + const value = event.value.trim(); + + if (!value) { + this.removeSubjectMandatory(field); + return; + } + + const index = this.subjectMandatoryFields.indexOf(field); + if (index >= 0) this.subjectMandatoryFields[index] = value + } + + // chip lists + + addChipListValues(type: string, event: MatChipInputEvent){ + if(type == "cc"){ + this.ccValues = this.add(event, this.ccValues); + }else if(type == "bcc"){ + this.bccValues = this.add(event, this.bccValues); + }else if(type == "extraDataKeys"){ + this.extraDataKeys = this.add(event, this.extraDataKeys); + }else if(type == "subject"){ + this.subjectMandatoryFields = this.add(event, this.subjectMandatoryFields); + }else if(type == "body"){ + this.bodyMandatoryFields = this.add(event, this.bodyMandatoryFields); + } + } + + editChipListValues(type: string, event: MatChipEditedEvent, field: string){ + if(type == "cc"){ + this.ccValues = this.edit(field, this.ccValues, event); + }else if(type == "bcc"){ + this.bccValues = this.edit(field, this.bccValues, event); + }else if(type == "extraDataKeys"){ + this.extraDataKeys = this.edit(field, this.extraDataKeys, event); + }else if(type == "subject"){ + this.subjectMandatoryFields = this.edit(field, this.subjectMandatoryFields, event); + }else if(type == "body"){ + this.bodyMandatoryFields = this.edit(field, this.bodyMandatoryFields, event); + } + } + + removeChipListValues(type: string, field: string){ + if(type == "cc"){ + this.ccValues = this.remove(field, this.ccValues); + }else if(type == "bcc"){ + this.bccValues = this.remove(field, this.bccValues); + }else if(type == "extraDataKeys"){ + this.extraDataKeys = this.remove(field, this.extraDataKeys); + }else if(type == "subject"){ + this.subjectMandatoryFields = this.remove(field, this.subjectMandatoryFields); + }else if(type == "body"){ + this.bodyMandatoryFields = this.remove(field, this.bodyMandatoryFields); + } + } + + add(event: MatChipInputEvent, values: string[]) { + const value = (event.value || '').trim(); + + if (value) values.push(value) + event.chipInput!.clear(); + return values; + + } + + remove(field: string, values: string[]) { + const index = values.indexOf(field); + + if (index >= 0) values.splice(index, 1); + return values; + } + + edit(field: string, values: string[], event: MatChipEditedEvent) { + const value = event.value.trim(); + + if (!value) { + values = this.remove(field, values); + return values; + } + + const index = values.indexOf(field); + if (index >= 0) values[index] = value + + return values; + } + + //options fields + + addOptionalItem(formGroup: UntypedFormGroup) { + const fieldInfo: NotificationFieldInfoEditorModel = new NotificationFieldInfoEditorModel(); + (formGroup.get('options') as FormArray).push(fieldInfo.buildForm()); + } + + removeOptionalItem(formGroup: UntypedFormGroup, optionalIndex: number): void { + (formGroup.get('options') as FormArray).removeAt(optionalIndex); + } + +} diff --git a/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.model.ts b/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.model.ts new file mode 100644 index 000000000..1971718f4 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.model.ts @@ -0,0 +1,289 @@ +import { FormArray, FormControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { NotificationFieldInfo, NotificationFieldInfoPersist, NotificationFieldOptions, NotificationFieldOptionsPersist, NotificationTemplate, NotificationTemplatePersist, NotificationTemplateValue, NotificationTemplateValuePersist } from '@app/core/model/notification-template/notification-template'; +import { BackendErrorValidator } from '@common/forms/validation/custom-validator'; +import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model'; +import { Validation, ValidationContext } from '@common/forms/validation/validation-context'; +import { Guid } from '@common/types/guid'; +import { NotificationTemplateChannel } from '@app/core/common/enum/notification-template-channel'; +import { NotificationTemplateKind } from '@app/core/common/enum/notification-template-kind'; +import { NotificationDataType } from '@app/core/common/enum/notification-data-type'; +import { EmailOverrideMode } from '@app/core/common/enum/email-override-mode'; +import { BaseEditorModel } from '@common/base/base-form-editor-model'; + +export class NotificationTemplateEditorModel extends BaseEditorModel implements NotificationTemplatePersist { + channel: NotificationTemplateChannel; + notificationType: Guid; + kind: NotificationTemplateKind; + languageId: Guid; + value: NotificationTemplateValueEditorModel = new NotificationTemplateValueEditorModel(); + permissions: string[]; + + public validationErrorModel: ValidationErrorModel = new ValidationErrorModel(); + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor() { super();} + + public fromModel(item: NotificationTemplate): NotificationTemplateEditorModel { + if(item){ + super.fromModel(item); + this.channel = item.channel; + this.notificationType = item.notificationType; + this.kind = item.kind; + if(item.language) //TODO this.language = item.language; + if (item.value) { this.value = new NotificationTemplateValueEditorModel().fromModel(item.value); } + } + + return this; + } + + buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup { + if (context == null) { context = this.createValidationContext(); } + + return this.formBuilder.group({ + id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators], + channel: [{ value: this.channel, disabled: disabled }, context.getValidation('channel').validators], + notificationType: [{ value: this.notificationType, disabled: disabled }, context.getValidation('notificationType').validators], + kind: [{ value: this.kind, disabled: disabled }, context.getValidation('kind').validators], + language: [{ value: this.languageId, disabled: disabled }, context.getValidation('language').validators], + value: this.value.buildForm({ + rootPath: `value.` + }), + hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators], + }); + } + + createValidationContext(): ValidationContext { + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, 'id')] }); + baseValidationArray.push({ key: 'channel', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'channel')] }); + baseValidationArray.push({ key: 'notificationType', validators: [BackendErrorValidator(this.validationErrorModel, 'notificationType')] }); + baseValidationArray.push({ key: 'kind', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'kind')] }); + baseValidationArray.push({ key: 'language', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'language')] }); + baseValidationArray.push({ key: 'value', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'value')] }); + baseValidationArray.push({ key: 'hash', validators: [] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} + +export class NotificationTemplateValueEditorModel implements NotificationTemplateValuePersist { + subjectText: string; + subjectKey: string; + subjectFieldOptions: NotificationFieldOptionsEditorModel = new NotificationFieldOptionsEditorModel(); + bodyText: string; + bodyKey: string; + priorityKey: string; + allowAttachments: Boolean = false; + cc: string[] = []; + ccMode: EmailOverrideMode; + bcc: string[] = []; + bccMode: EmailOverrideMode; + extraDataKeys: string[] = []; + bodyFieldOptions: NotificationFieldOptionsEditorModel = new NotificationFieldOptionsEditorModel(); + + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor( + public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() + ) { } + + public fromModel(item: NotificationTemplateValue): NotificationTemplateValueEditorModel { + if (item) { + this.subjectText = item.subjectText; + this.subjectKey = item.subjectKey; + if (item.subjectFieldOptions) this.subjectFieldOptions = new NotificationFieldOptionsEditorModel().fromModel(item.subjectFieldOptions); + this.bodyText = item.bodyText; + this.bodyKey = item.bodyKey; + this.priorityKey = item.priorityKey; + this.allowAttachments = item.allowAttachments; + this.cc = item.cc; + this.ccMode = item.ccMode; + this.bcc = item.bcc; + this.bccMode = item.bccMode; + this.extraDataKeys = item.extraDataKeys; + if (item.bodyFieldOptions) this.bodyFieldOptions = new NotificationFieldOptionsEditorModel().fromModel(item.bodyFieldOptions); + } + return this; + } + + buildForm(params?: { + context?: ValidationContext, + disabled?: boolean, + rootPath?: string + }): UntypedFormGroup { + let { context = null, disabled = false, rootPath } = params ?? {} + if (context == null) { + context = NotificationTemplateValueEditorModel.createValidationContext({ + validationErrorModel: this.validationErrorModel, + rootPath + }); + } + return this.formBuilder.group({ + subjectText: [{ value: this.subjectText, disabled: disabled }, context.getValidation('subjectText').validators], + subjectKey: [{ value: this.subjectKey, disabled: disabled }, context.getValidation('subjectKey').validators], + subjectFieldOptions: this.subjectFieldOptions.buildForm({ + rootPath: `subjectFieldOptions.` + }), + bodyText: [{ value: this.bodyText, disabled: disabled }, context.getValidation('bodyText').validators], + bodyKey: [{ value: this.bodyKey, disabled: disabled }, context.getValidation('bodyKey').validators], + priorityKey: [{ value: this.priorityKey, disabled: disabled }, context.getValidation('priorityKey').validators], + allowAttachments: [{ value: this.allowAttachments, disabled: disabled }, context.getValidation('allowAttachments').validators], + cc: [{ value: this.cc, disabled: disabled }, context.getValidation('cc').validators], + ccMode: [{ value: this.ccMode, disabled: disabled }, context.getValidation('ccMode').validators], + bcc: [{ value: this.bcc, disabled: disabled }, context.getValidation('bcc').validators], + bccMode: [{ value: this.bccMode, disabled: disabled }, context.getValidation('bccMode').validators], + extraDataKeys: [{ value: this.extraDataKeys, disabled: disabled }, context.getValidation('extraDataKeys').validators], + bodyFieldOptions: this.bodyFieldOptions.buildForm({ + rootPath: `bodyFieldOptions.` + }), + }); + } + + static createValidationContext(params: { + rootPath?: string, + validationErrorModel: ValidationErrorModel + }): ValidationContext { + const { rootPath = '', validationErrorModel } = params; + + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'subjectText', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}subjectText`)] }); + baseValidationArray.push({ key: 'subjectKey', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}subjectKey`)] }); + baseValidationArray.push({ key: 'bodyText', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}bodyText`)] }); + baseValidationArray.push({ key: 'bodyKey', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}bodyKey`)] }); + baseValidationArray.push({ key: 'priorityKey', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}priorityKey`)] }); + baseValidationArray.push({ key: 'allowAttachments', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}allowAttachments`)] }); + baseValidationArray.push({ key: 'cc', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}cc`)] }); + baseValidationArray.push({ key: 'ccMode', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}ccMode`)] }); + baseValidationArray.push({ key: 'bcc', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}bcc`)] }); + baseValidationArray.push({ key: 'bccMode', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}bccMode`)] }); + baseValidationArray.push({ key: 'extraDataKeys', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}extraDataKeys`)] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} + +export class NotificationFieldOptionsEditorModel implements NotificationFieldOptionsPersist { + mandatory?: string[] = []; + options?: NotificationFieldInfoEditorModel[] = []; + formatting?: { [key: string]: string }; + + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor( + public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() + ) { } + + public fromModel(item: NotificationFieldOptions): NotificationFieldOptionsEditorModel { + if (item) { + this.mandatory = item.mandatory; + if(item.options) { item.options.map(x => this.options.push(new NotificationFieldInfoEditorModel().fromModel(x))); } + this.formatting = item.formatting; + } + return this; + } + + buildForm(params?: { + context?: ValidationContext, + disabled?: boolean, + rootPath?: string + }): UntypedFormGroup { + let { context = null, disabled = false, rootPath } = params ?? {} + if (context == null) { + context = NotificationFieldOptionsEditorModel.createValidationContext({ + validationErrorModel: this.validationErrorModel, + rootPath + }); + } + + return this.formBuilder.group({ + mandatory: [{ value: this.mandatory, disabled: disabled }, context.getValidation('mandatory').validators], + formatting: [{ value: this.formatting, disabled: disabled }, context.getValidation('formatting').validators], + options: this.formBuilder.array( + (this.options ?? []).map( + (item, index) => new NotificationFieldInfoEditorModel( + this.validationErrorModel + ).fromModel(item).buildForm({ + rootPath: `options[${index}].` + }), context.getValidation('options') + ) + ), + }); + + } + + static createValidationContext(params: { + rootPath?: string, + validationErrorModel: ValidationErrorModel + }): ValidationContext { + const { rootPath = '', validationErrorModel } = params; + + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'mandatory', validators: [BackendErrorValidator(validationErrorModel,`${rootPath}mandatory`)] }); + baseValidationArray.push({ key: 'options', validators: [BackendErrorValidator(validationErrorModel,`${rootPath}options`)] }); + baseValidationArray.push({ key: 'formatting', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}formatting`)] }); + baseContext.validation = baseValidationArray; + return baseContext; + } + +} + +export class NotificationFieldInfoEditorModel implements NotificationFieldInfoPersist { + key: string; + dataType: NotificationDataType; + value: string; + + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor(public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() + ) { } + + public fromModel(item: NotificationFieldInfo): NotificationFieldInfoEditorModel { + if (item) { + this.key = item.key; + this.dataType = item.dataType; + this.value = item.value; + } + return this; + } + + buildForm(params?: { + context?: ValidationContext, + disabled?: boolean, + rootPath?: string + }): UntypedFormGroup { + let { context = null, disabled = false, rootPath } = params ?? {} + if (context == null) { + context = NotificationFieldInfoEditorModel.createValidationContext({ + validationErrorModel: this.validationErrorModel, + rootPath + }); + } + return this.formBuilder.group({ + key: [{ value: this.key, disabled: disabled }, context.getValidation('key').validators], + dataType: [{ value: this.dataType, disabled: disabled }, context.getValidation('dataType').validators], + value: [{ value: this.value, disabled: disabled }, context.getValidation('value').validators], + }); + } + + static createValidationContext(params: { + rootPath?: string, + validationErrorModel: ValidationErrorModel + }): ValidationContext { + const { rootPath = '', validationErrorModel } = params; + + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + + baseValidationArray.push({ key: 'key', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}key`)] }); + baseValidationArray.push({ key: 'dataType', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}dataType`)] }); + baseValidationArray.push({ key: 'value', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}value`)] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} diff --git a/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.resolver.ts b/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.resolver.ts new file mode 100644 index 000000000..9d5be154c --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.resolver.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { NotificationTemplate } from '@app/core/model/notification-template/notification-template'; +import { NotificationTemplateService } from '@app/core/services/notification-template/notification-template.service'; +import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service'; +import { BaseEditorResolver } from '@common/base/base-editor.resolver'; +import { Guid } from '@common/types/guid'; +import { takeUntil, tap } from 'rxjs/operators'; +import { nameof } from 'ts-simple-nameof'; + +@Injectable() +export class NotificationTemplateEditorResolver extends BaseEditorResolver { + + constructor(private notificationTemplateService: NotificationTemplateService, private breadcrumbService: BreadcrumbService) { + super(); + } + + public static lookupFields(): string[] { + return [ + ...BaseEditorResolver.lookupFields(), + nameof(x => x.id), + nameof(x => x.channel), + nameof(x => x.notificationType), + nameof(x => x.kind), + nameof(x => x.language), + nameof(x => x.createdAt), + nameof(x => x.updatedAt), + nameof(x => x.hash), + nameof(x => x.isActive) + ] + } + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + + const fields = [ + ...NotificationTemplateEditorResolver.lookupFields() + ]; + const id = route.paramMap.get('id'); + + if (id != null) { + return this.notificationTemplateService.getSingle(Guid.parse(id), fields).pipe(tap(x => this.breadcrumbService.addIdResolvedValue(x.id?.toString(), x.language.code)), takeUntil(this._destroyed)); + } + } +} diff --git a/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.service.ts b/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.service.ts new file mode 100644 index 000000000..f8d5eace3 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/editor/notification-template-editor.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from "@angular/core"; +import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model"; + +@Injectable() +export class NotificationTemplateEditorService { + private validationErrorModel: ValidationErrorModel; + + public setValidationErrorModel(validationErrorModel: ValidationErrorModel): void { + this.validationErrorModel = validationErrorModel; + } + + public getValidationErrorModel(): ValidationErrorModel { + return this.validationErrorModel; + } +} diff --git a/dmp-frontend/src/app/ui/admin/notification-template/listing/filters/notification-template-listing-filters.component.html b/dmp-frontend/src/app/ui/admin/notification-template/listing/filters/notification-template-listing-filters.component.html new file mode 100644 index 000000000..b899a28f3 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/listing/filters/notification-template-listing-filters.component.html @@ -0,0 +1,36 @@ +
+ + + + + +
+
+
+

{{'NOTIFICATION-TEMPLATE-LISTING.FILTER.TITLE' | translate}}

+ +
+ + + {{'NOTIFICATION-TEMPLATE-LISTING.FILTER.IS-ACTIVE' | translate}} + + +
+ + +
+
+
+
+ + +
diff --git a/dmp-frontend/src/app/ui/admin/notification-template/listing/filters/notification-template-listing-filters.component.scss b/dmp-frontend/src/app/ui/admin/notification-template/listing/filters/notification-template-listing-filters.component.scss new file mode 100644 index 000000000..999f5a7c6 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/listing/filters/notification-template-listing-filters.component.scss @@ -0,0 +1,25 @@ +.description-template-type-listing-filters { + +} + +::ng-deep.mat-mdc-menu-panel { + max-width: 100% !important; + height: 100% !important; +} + +:host::ng-deep.mat-mdc-menu-content:not(:empty) { + padding-top: 0 !important; +} + + +.filter-button{ + padding-top: .6rem; + padding-bottom: .6rem; + // .mat-icon{ + // font-size: 1.5em; + // width: 1.2em; + // height: 1.2em; + // } +} + + diff --git a/dmp-frontend/src/app/ui/admin/notification-template/listing/filters/notification-template-listing-filters.component.ts b/dmp-frontend/src/app/ui/admin/notification-template/listing/filters/notification-template-listing-filters.component.ts new file mode 100644 index 000000000..a6346be2c --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/listing/filters/notification-template-listing-filters.component.ts @@ -0,0 +1,91 @@ +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { IsActive } from '@app/core/common/enum/is-active.enum'; +import { NotificationTemplateFilter } from '@app/core/query/notification-template.lookup'; +import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; +import { BaseComponent } from '@common/base/base.component'; +import { nameof } from 'ts-simple-nameof'; + +@Component({ + selector: 'app-notification-template-listing-filters', + templateUrl: './notification-template-listing-filters.component.html', + styleUrls: ['./notification-template-listing-filters.component.scss'] +}) +export class NotificationTemplateListingFiltersComponent extends BaseComponent implements OnInit, OnChanges { + + @Input() readonly filter: NotificationTemplateFilter; + @Output() filterChange = new EventEmitter(); + + // * State + internalFilters: TenantListingFilters = this._getEmptyFilters(); + + protected appliedFilterCount: number = 0; + constructor( + public enumUtils: EnumUtils, + ) { super(); } + + ngOnInit() { + } + + ngOnChanges(changes: SimpleChanges): void { + const filterChange = changes[nameof(x => x.filter)]?.currentValue as NotificationTemplateFilter; + if (filterChange) { + this.updateFilters() + } + } + + + onSearchTermChange(searchTerm: string): void { + this.applyFilters() + } + + + protected updateFilters(): void { + this.internalFilters = this._parseToInternalFilters(this.filter); + this.appliedFilterCount = this._computeAppliedFilters(this.internalFilters); + } + + protected applyFilters(): void { + const { isActive } = this.internalFilters ?? {} + this.filterChange.emit({ + ...this.filter, + // like, + isActive: isActive ? [IsActive.Active] : [IsActive.Inactive] + }) + } + + + private _parseToInternalFilters(inputFilter: NotificationTemplateFilter): TenantListingFilters { + if (!inputFilter) { + return this._getEmptyFilters(); + } + + let { excludedIds, ids, isActive } = inputFilter; + + return { + isActive: (isActive ?? [])?.includes(IsActive.Active) || !isActive?.length, + } + + } + + private _getEmptyFilters(): TenantListingFilters { + return { + isActive: true + } + } + + private _computeAppliedFilters(filters: TenantListingFilters): number { + let count = 0; + if (filters?.isActive) { + count++ + } + return count; + } + + clearFilters() { + this.internalFilters = this._getEmptyFilters(); + } +} + +interface TenantListingFilters { + isActive: boolean; +} diff --git a/dmp-frontend/src/app/ui/admin/notification-template/listing/notification-template-listing.component.html b/dmp-frontend/src/app/ui/admin/notification-template/listing/notification-template-listing.component.html new file mode 100644 index 000000000..e38459e2f --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/listing/notification-template-listing.component.html @@ -0,0 +1,95 @@ +
+
+ +
+
+

{{'NOTIFICATION-TEMPLATE-LISTING.TITLE' | translate}}

+ + +
+
+ +
+
+ + + + + + + + +
+
+ + + + +
+
+ + {{item?.name | nullifyValue}} +
+
+ + +
+
+ {{enumUtils.toDescriptionTemplateTypeStatusString(item.status) | nullifyValue}} +
+
+
+ + + + {{'NOTIFICATION-TEMPLATE-LISTING.FIELDS.CREATED-AT' | translate}}: + + {{item?.createdAt | dateTimeFormatter : 'short' | nullifyValue}} + + +
+
+ + + {{'NOTIFICATION-TEMPLATE-LISTING.FIELDS.UPDATED-AT' | translate}}: + + {{item?.updatedAt | dateTimeFormatter : 'short' | nullifyValue}} + + + +
+
+
+ + +
+
+ + + + + +
+
+
\ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/notification-template/listing/notification-template-listing.component.scss b/dmp-frontend/src/app/ui/admin/notification-template/listing/notification-template-listing.component.scss new file mode 100644 index 000000000..6e1b48814 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/listing/notification-template-listing.component.scss @@ -0,0 +1,60 @@ +.description-template-type-listing { + margin-top: 1.3rem; + margin-left: 1rem; + margin-right: 2rem; + + .mat-header-row{ + background: #f3f5f8; + } + .mat-card { + margin: 16px 0; + padding: 0px; + } + + .mat-row { + cursor: pointer; + min-height: 4.5em; + } + + mat-row:hover { + background-color: #eef5f6; + } + .mat-fab-bottom-right { + float: right; + z-index: 5; + } +} +.create-btn { + border-radius: 30px; + background-color: var(--secondary-color); + padding-left: 2em; + padding-right: 2em; + // color: #000; + + .button-text{ + display: inline-block; + } +} + +.dlt-btn { + color: rgba(0, 0, 0, 0.54); +} + +.status-chip{ + + border-radius: 20px; + padding-left: 1em; + padding-right: 1em; + padding-top: 0.2em; + font-size: .8em; +} + +.status-chip-finalized{ + color: #568b5a; + background: #9dd1a1 0% 0% no-repeat padding-box; +} + +.status-chip-draft{ + color: #00c4ff; + background: #d3f5ff 0% 0% no-repeat padding-box; +} diff --git a/dmp-frontend/src/app/ui/admin/notification-template/listing/notification-template-listing.component.ts b/dmp-frontend/src/app/ui/admin/notification-template/listing/notification-template-listing.component.ts new file mode 100644 index 000000000..c15d94102 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/listing/notification-template-listing.component.ts @@ -0,0 +1,175 @@ +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, Router } from '@angular/router'; +import { IsActive } from '@app/core/common/enum/is-active.enum'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; +import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; +import { QueryParamsService } from '@app/core/services/utilities/query-params.service'; +import { BaseListingComponent } from '@common/base/base-listing-component'; +import { PipeService } from '@common/formatting/pipe.service'; +import { DataTableDateTimeFormatPipe } from '@common/formatting/pipes/date-time-format.pipe'; +import { QueryResult } from '@common/model/query-result'; +import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; +import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; +import { ColumnDefinition, ColumnsChangedEvent, HybridListingComponent, PageLoadEvent } from '@common/modules/hybrid-listing/hybrid-listing.component'; +import { Guid } from '@common/types/guid'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { nameof } from 'ts-simple-nameof'; +import { IsActiveTypePipe } from '@common/formatting/pipes/is-active-type.pipe'; +import { NotificationTemplate } from '@app/core/model/notification-template/notification-template'; +import { NotificationTemplateLookup } from '@app/core/query/notification-template.lookup'; +import { NotificationTemplateService } from '@app/core/services/notification-template/notification-template.service'; + +@Component({ + templateUrl: './NOTIFICATION-TEMPLATE-LISTING.component.html', + styleUrls: ['./NOTIFICATION-TEMPLATE-LISTING.component.scss'] +}) +export class NotificationTemplateListingComponent extends BaseListingComponent implements OnInit { + publish = false; + userSettingsKey = { key: 'NotificationTemplateListingUserSettings' }; + propertiesAvailableForOrder: ColumnDefinition[]; + + @ViewChild('actions', { static: true }) actions?: TemplateRef; + @ViewChild(HybridListingComponent, { static: true }) hybridListingComponent: HybridListingComponent; + + private readonly lookupFields: string[] = [ + nameof(x => x.id), + nameof(x => x.language.code), + nameof(x => x.channel), + nameof(x => x.updatedAt), + nameof(x => x.createdAt), + nameof(x => x.hash), + nameof(x => x.isActive) + ]; + + rowIdentity = x => x.id; + + constructor( + protected router: Router, + protected route: ActivatedRoute, + protected uiNotificationService: UiNotificationService, + protected httpErrorHandlingService: HttpErrorHandlingService, + protected queryParamsService: QueryParamsService, + private notificationTemplateService: NotificationTemplateService, + public authService: AuthService, + private pipeService: PipeService, + public enumUtils: EnumUtils, + private language: TranslateService, + private dialog: MatDialog + ) { + super(router, route, uiNotificationService, httpErrorHandlingService, queryParamsService); + // Lookup setup + // Default lookup values are defined in the user settings class. + this.lookup = this.initializeLookup(); + } + + ngOnInit() { + super.ngOnInit(); + } + + protected initializeLookup(): NotificationTemplateLookup { + const lookup = new NotificationTemplateLookup(); + lookup.metadata = { countAll: true }; + lookup.page = { offset: 0, size: this.ITEMS_PER_PAGE }; + lookup.isActive = [IsActive.Active]; + lookup.order = { items: [this.toDescSortField(nameof(x => x.createdAt))] }; + this.updateOrderUiFields(lookup.order); + + lookup.project = { + fields: this.lookupFields + }; + + return lookup; + } + + protected setupColumns() { + this.gridColumns.push(...[{ + prop: nameof(x => x.language.code), + sortable: true, + languageName: 'NOTIFICATION-TEMPLATE-LISTING.FIELDS.LANGUAGE' + }, { + prop: nameof(x => x.channel), + sortable: true, + languageName: 'NOTIFICATION-TEMPLATE-LISTING.FIELDS.CHANNEL', + }, + { + prop: nameof(x => x.createdAt), + sortable: true, + languageName: 'NOTIFICATION-TEMPLATE-LISTING.FIELDS.CREATED-AT', + pipe: this.pipeService.getPipe(DataTableDateTimeFormatPipe).withFormat('short') + }, + { + prop: nameof(x => x.updatedAt), + sortable: true, + languageName: 'NOTIFICATION-TEMPLATE-LISTING.FIELDS.UPDATED-AT', + pipe: this.pipeService.getPipe(DataTableDateTimeFormatPipe).withFormat('short') + }, + { + prop: nameof(x => x.isActive), + sortable: true, + languageName: 'NOTIFICATION-TEMPLATE-LISTING.FIELDS.IS-ACTIVE', + pipe: this.pipeService.getPipe(IsActiveTypePipe) + }, + { + alwaysShown: true, + cellTemplate: this.actions, + maxWidth: 120 + } + ]); + this.propertiesAvailableForOrder = this.gridColumns.filter(x => x.sortable); + } + + // + // Listing Component functions + // + onColumnsChanged(event: ColumnsChangedEvent) { + super.onColumnsChanged(event); + this.onColumnsChangedInternal(event.properties.map(x => x.toString())); + } + + private onColumnsChangedInternal(columns: string[]) { + // Here are defined the projection fields that always requested from the api. + const fields = new Set(this.lookupFields); + this.gridColumns.map(x => x.prop) + .filter(x => !columns?.includes(x as string)) + .forEach(item => { + fields.delete(item as string) + }); + this.lookup.project = { fields: [...fields] }; + this.onPageLoad({ offset: 0 } as PageLoadEvent); + } + + protected loadListing(): Observable> { + return this.notificationTemplateService.query(this.lookup); + } + + public deleteType(id: Guid) { + if (id) { + const dialogRef = this.dialog.open(ConfirmationDialogComponent, { + data: { + isDeleteConfirmation: true, + message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'), + confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'), + cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL') + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { + if (result) { + this.notificationTemplateService.delete(id).pipe(takeUntil(this._destroyed)) + .subscribe( + complete => this.onCallbackSuccess(), + error => this.onCallbackError(error) + ); + } + }); + } + } + + onCallbackSuccess(): void { + this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-DELETE'), SnackBarNotificationLevel.Success); + this.ngOnInit(); + } +} diff --git a/dmp-frontend/src/app/ui/admin/notification-template/notification-template.module.ts b/dmp-frontend/src/app/ui/admin/notification-template/notification-template.module.ts new file mode 100644 index 000000000..fa01671c2 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/notification-template.module.ts @@ -0,0 +1,43 @@ +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { NgModule } from "@angular/core"; +import { AutoCompleteModule } from "@app/library/auto-complete/auto-complete.module"; +import { UrlListingModule } from '@app/library/url-listing/url-listing.module'; +import { CommonFormattingModule } from '@common/formatting/common-formatting.module'; +import { CommonFormsModule } from '@common/forms/common-forms.module'; +import { ConfirmationDialogModule } from '@common/modules/confirmation-dialog/confirmation-dialog.module'; +import { HybridListingModule } from "@common/modules/hybrid-listing/hybrid-listing.module"; +import { TextFilterModule } from "@common/modules/text-filter/text-filter.module"; +import { UserSettingsModule } from "@common/modules/user-settings/user-settings.module"; +import { CommonUiModule } from '@common/ui/common-ui.module'; +import { NgxDropzoneModule } from "ngx-dropzone"; +import { NotificationTemplateListingComponent } from './listing/notification-template-listing.component'; +import { RichTextEditorModule } from '@app/library/rich-text-editor/rich-text-editor.module'; +import { NotificationTemplateRoutingModule } from './notification-template.routing'; +import { NotificationTemplateEditorComponent } from './editor/notification-template-editor.component'; +import { NotificationTemplateListingFiltersComponent } from './listing/filters/notification-template-listing-filters.component'; +import { MatIconModule } from '@angular/material/icon'; + +@NgModule({ + imports: [ + CommonUiModule, + CommonFormsModule, + UrlListingModule, + ConfirmationDialogModule, + NotificationTemplateRoutingModule, + NgxDropzoneModule, + DragDropModule, + AutoCompleteModule, + HybridListingModule, + TextFilterModule, + UserSettingsModule, + CommonFormattingModule, + RichTextEditorModule, + MatIconModule + ], + declarations: [ + NotificationTemplateEditorComponent, + NotificationTemplateListingComponent, + NotificationTemplateListingFiltersComponent + ] +}) +export class NotificationTemplateModule { } diff --git a/dmp-frontend/src/app/ui/admin/notification-template/notification-template.routing.ts b/dmp-frontend/src/app/ui/admin/notification-template/notification-template.routing.ts new file mode 100644 index 000000000..5a0e1bfd6 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/notification-template/notification-template.routing.ts @@ -0,0 +1,58 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { AdminAuthGuard } from '@app/core/admin-auth-guard.service'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { AuthGuard } from '@app/core/auth-guard.service'; +import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service'; +import { PendingChangesGuard } from '@common/forms/pending-form-changes/pending-form-changes-guard.service'; +import { NotificationTemplateListingComponent } from './listing/notification-template-listing.component'; +import { NotificationTemplateEditorComponent } from './editor/notification-template-editor.component'; +import { NotificationTemplateEditorResolver } from './editor/notification-template-editor.resolver'; + +const routes: Routes = [ + { + path: '', + component: NotificationTemplateListingComponent, + canActivate: [AuthGuard] + }, + { + path: 'new', + canActivate: [AuthGuard], + component: NotificationTemplateEditorComponent, + canDeactivate: [PendingChangesGuard], + data: { + authContext: { + permissions: [AppPermission.EditNotificationTemplate] + }, + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.NEW-TENANT' + }) + } + }, + { + path: ':id', + canActivate: [AuthGuard], + component: NotificationTemplateEditorComponent, + canDeactivate: [PendingChangesGuard], + resolve: { + 'entity': NotificationTemplateEditorResolver + }, + data: { + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.EDIT-TENANT' + }), + authContext: { + permissions: [AppPermission.EditNotificationTemplate] + } + } + + }, + { path: '**', loadChildren: () => import('@common/modules/page-not-found/page-not-found.module').then(m => m.PageNotFoundModule) }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [NotificationTemplateEditorResolver] +}) +export class NotificationTemplateRoutingModule { } diff --git a/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts b/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts index 011c90d49..415f3afdb 100644 --- a/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts +++ b/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts @@ -58,7 +58,8 @@ export const ADMIN_ROUTES: RouteInfo[] = [ { path: '/tenants', title: 'SIDE-BAR.TENANTS', icon: 'tenancy' }, { path: '/users', title: 'SIDE-BAR.USERS', icon: 'people' }, { path: '/languages', title: 'SIDE-BAR.LANGUAGES', icon: 'language' }, - { path: '/supportive-material', title: 'SIDE-BAR.SUPPORTIVE-MATERIAL', icon: 'import_contacts' }, + { path: '/supportive-material', title: 'SIDE-BAR.SUPPORTIVE-MATERIAL', icon: 'dataset_linked' }, + { path: '/notification-templates', title: 'SIDE-BAR.NOTIFICATION-TEMPLATES', icon: 'build'}, { path: '/index-managment', title: 'SIDE-BAR.MAINTENANCE', icon: 'build'} ]; diff --git a/dmp-frontend/src/assets/i18n/en.json b/dmp-frontend/src/assets/i18n/en.json index 588ceebb5..f27e8e9d4 100644 --- a/dmp-frontend/src/assets/i18n/en.json +++ b/dmp-frontend/src/assets/i18n/en.json @@ -253,7 +253,8 @@ "EDIT-REFERENCE": "Edit", "LANGUAGES": "Languages", "NEW-LANGUAGE": "New", - "EDIT-LANGUAGE": "Edit" + "EDIT-LANGUAGE": "Edit", + "NOTIFICATION-TEMPLATES": "Notification Templates" }, "COOKIE": { "MESSAGE": "This website uses cookies to enhance the user experience.", @@ -352,7 +353,8 @@ "TENANTS": "Tenants", "REFERENCES": "References", "LANGUAGES": "Languages", - "MAINTENANCE": "Maintenance" + "MAINTENANCE": "Maintenance", + "NOTIFICATION-TEMPLATES":"Notification Templates" }, "DESCRIPTION-TEMPLATE-EDITOR": { "TITLE": { @@ -1213,6 +1215,36 @@ "SUCCESSFUL-DELETE": "Successful Delete", "UNSUCCESSFUL-DELETE": "This item could not be deleted." }, + "NOTIFICATION-TEMPLATE-LISTING": { + "TITLE": "Notification Templates", + "CREATE": "Create Notification Template", + "FIELDS": { + "NAME": "Name", + "LANGUAGE":"Language", + "CHANNEL":"Channel", + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "IS-ACTIVE":"Is Active" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "STATUS": "Status", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "CONFIRM-DELETE-DIALOG": { + "MESSAGE": "Would you like to delete this Notification Template?", + "CONFIRM-BUTTON": "Yes, delete", + "CANCEL-BUTTON": "No" + }, + "ACTIONS": { + "DELETE": "Delete", + "EDIT": "Edit" + }, + "SUCCESSFUL-DELETE": "Successful Delete", + "UNSUCCESSFUL-DELETE": "This item could not be deleted." + }, "SUPPORTIVE-MATERIAL-EDITOR": { "TITLE": "Supportive Material", "FIELDS": { @@ -1260,6 +1292,44 @@ "UNSUCCESSFUL": "Something went wrong" } }, + "NOTIFICATION-SERVICE": { + "NOTIFICATION-TEMPLATE-EDITOR":{ + "NEW": "New Notification Template", + "FIELDS": { + "LANGUAGE": "Language", + "CHANNEL": "Channel", + "KIND": "Kind", + "OPTIONAL-TITLE": "Optional Fields", + "FIELD": "Field", + "KEY":"Key", + "DATA-TYPE":"Data Type", + "VALUE":"Value", + "SUBJECT-SECTION": "Subject Section", + "SUBJECT-TEXT": "Subject Text", + "SUBJECT-KEY": "Subject Key", + "SUBJECT-FIELD-OPTIONS": "Subject Field Options", + "BODY-SECTION": "Body Section", + "BODY-TEXT": "Body Text", + "BODY-KEY": "Body Key", + "BODY-FIELD-OPTIONS": "Body Field Options", + "MANDATORY": "Mandatory", + "MANDATORY-PLACEHOLDER": "New field..", + "EXTRA-OPTIONS": "Extra Options", + "PRIORITY-KEY": "Priority Key", + "ALLOW-ATTACHMENTS": "Allow Attachments", + "CC": "CC", + "CC-MODE": "CC Mode", + "BCC": "BCC", + "BCC-MODE": "BCC Mode", + "EXTRA-DATA-KEYS": "EXTRA-DATA-KEYS" + }, + "ACTIONS": { + "SAVE": "Save", + "CANCEL": "Cancel", + "DELETE": "Delete" + } + } + }, "DESCRIPTION-TEMPLATE-TYPE-EDITOR": { "NEW": "New Description Type", "FIELDS": { @@ -2065,6 +2135,28 @@ "REFERENCE-SOURCE-TYPE":{ "INTERNAL": "Internal", "EXTERNAL": "External" + }, + "NOTIFICATION-TEMPLATE-KIND":{ + "DRAFT": "Draft", + "SECONDARY": "Secondary", + "PRIMARY": "Primary", + "DEFAULT": "Default" + }, + "NOTIFICATION-TEMPLATE-CHANNEL":{ + "EMAIL": "Email", + "INAPP": "In App" + }, + "NOTIFICATION-TEMPLATE-DATA-TYPE":{ + "INTEGER": "Integer", + "DEMICAL": "Demical", + "DOUBLE": "Double", + "DATE-TIME": "Date Time", + "STRING": "String" + }, + "NOTIFICATION-TEMPLATE-EMAIL-OVERRIDE-MODE":{ + "NOT": "Not Override", + "ADDITIVE": "Additive", + "REPLACE": "Replace" } }, "ADDRESEARCHERS-EDITOR": {