From 15d64b4401677a1b3c7ee007d4921e9394092092 Mon Sep 17 00:00:00 2001 From: amentis Date: Fri, 28 Jun 2024 15:15:55 +0300 Subject: [PATCH] add annotation status page --- .../opencdmp/authorization/Permission.java | 6 + .../src/main/resources/config/permissions.yml | 30 +++ .../core/enum/internal-status.enum.ts | 3 + .../core/enum/is-active.enum.ts | 4 + .../core/formatting/enum-utils.service.ts | 26 +++ .../core/formatting/formatting.module.ts | 36 ++++ .../pipes/internal-status-type.pipe.ts | 11 ++ .../formatting/pipes/is-active-type.pipe.ts | 11 ++ .../core/model/annotation-status.model.ts | 16 ++ .../core/model/annotation.model.ts | 2 + .../core/model/status.model.ts | 12 ++ .../core/query/status.lookup.ts | 26 +++ .../services/core-service.module.ts | 4 +- .../services/http/annotation.service.ts | 9 + .../services/http/status.service.ts | 54 ++++++ .../editor/status-editor.component.html | 71 +++++++ .../editor/status-editor.component.scss | 26 +++ .../status/editor/status-editor.component.ts | 177 +++++++++++++++++ .../status/editor/status-editor.model.ts | 50 +++++ .../status/editor/status-editor.resolver.ts | 42 ++++ .../status/editor/status-editor.service.ts | 15 ++ .../status-listing-filters.component.html | 59 ++++++ .../status-listing-filters.component.scss | 26 +++ .../status-listing-filters.component.ts | 100 ++++++++++ .../listing/status-listing.component.html | 93 +++++++++ .../listing/status-listing.component.scss | 60 ++++++ .../listing/status-listing.component.ts | 180 ++++++++++++++++++ .../ui/admin/status/status.module.ts | 45 +++++ .../ui/admin/status/status.routing.ts | 58 ++++++ dmp-frontend/src/app/app-routing.module.ts | 13 ++ .../app/core/common/enum/permission.enum.ts | 6 + .../src/app/ui/sidebar/sidebar.component.ts | 1 + 32 files changed, 1271 insertions(+), 1 deletion(-) create mode 100644 dmp-frontend/src/annotation-service/core/enum/internal-status.enum.ts create mode 100644 dmp-frontend/src/annotation-service/core/enum/is-active.enum.ts create mode 100644 dmp-frontend/src/annotation-service/core/formatting/enum-utils.service.ts create mode 100644 dmp-frontend/src/annotation-service/core/formatting/formatting.module.ts create mode 100644 dmp-frontend/src/annotation-service/core/formatting/pipes/internal-status-type.pipe.ts create mode 100644 dmp-frontend/src/annotation-service/core/formatting/pipes/is-active-type.pipe.ts create mode 100644 dmp-frontend/src/annotation-service/core/model/annotation-status.model.ts create mode 100644 dmp-frontend/src/annotation-service/core/model/status.model.ts create mode 100644 dmp-frontend/src/annotation-service/core/query/status.lookup.ts create mode 100644 dmp-frontend/src/annotation-service/services/http/status.service.ts create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.html create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.scss create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.ts create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.model.ts create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.resolver.ts create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.service.ts create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.html create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.scss create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.ts create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.html create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.scss create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.ts create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/status.module.ts create mode 100644 dmp-frontend/src/annotation-service/ui/admin/status/status.routing.ts diff --git a/backend/core/src/main/java/org/opencdmp/authorization/Permission.java b/backend/core/src/main/java/org/opencdmp/authorization/Permission.java index 4a08a8a74..6f5f98f49 100644 --- a/backend/core/src/main/java/org/opencdmp/authorization/Permission.java +++ b/backend/core/src/main/java/org/opencdmp/authorization/Permission.java @@ -200,6 +200,11 @@ public final class Permission { public static String EditPrefillingSource= "EditPrefillingSource"; public static String DeletePrefillingSource = "DeletePrefillingSource"; + //NotificationTemplate + public static String BrowseStatus = "BrowseStatus"; + public static String EditStatus = "EditStatus"; + public static String DeleteStatus = "DeleteStatus"; + // UI Pages public static String ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage"; @@ -224,5 +229,6 @@ public final class Permission { public static String ViewHomePage = "ViewHomePage"; public static String ViewMineInAppNotificationPage = "ViewMineInAppNotificationPage"; public static String ViewTenantConfigurationPage = "ViewTenantConfigurationPage"; + public static String ViewStatusPage = "ViewStatusPage"; } diff --git a/backend/web/src/main/resources/config/permissions.yml b/backend/web/src/main/resources/config/permissions.yml index 2d9a29a9f..a0922bd7c 100644 --- a/backend/web/src/main/resources/config/permissions.yml +++ b/backend/web/src/main/resources/config/permissions.yml @@ -1060,6 +1060,29 @@ permissions: clients: [ ] allowAnonymous: false allowAuthenticated: false + # Status + BrowseStatus: + roles: + - Admin + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + EditStatus: + roles: + - Admin + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + DeleteStatus: + roles: + - Admin + - TenantAdmin + claims: [ ] + clients: [ ] + allowAnonymous: false + allowAuthenticated: false # ViewPage Permissions ViewDescriptionTemplateTypePage: @@ -1206,6 +1229,13 @@ permissions: allowAnonymous: false allowAuthenticated: true ViewTenantConfigurationPage: + roles: + - Admin + - TenantAdmin + clients: [ ] + allowAnonymous: false + allowAuthenticated: false + ViewStatusPage: roles: - Admin - TenantAdmin diff --git a/dmp-frontend/src/annotation-service/core/enum/internal-status.enum.ts b/dmp-frontend/src/annotation-service/core/enum/internal-status.enum.ts new file mode 100644 index 000000000..37e9db80e --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/enum/internal-status.enum.ts @@ -0,0 +1,3 @@ +export enum InternalStatus { + Resolved = 0, +} diff --git a/dmp-frontend/src/annotation-service/core/enum/is-active.enum.ts b/dmp-frontend/src/annotation-service/core/enum/is-active.enum.ts new file mode 100644 index 000000000..cdadd23b4 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/enum/is-active.enum.ts @@ -0,0 +1,4 @@ +export enum IsActive { + Inactive = 0, + Active = 1 +} diff --git a/dmp-frontend/src/annotation-service/core/formatting/enum-utils.service.ts b/dmp-frontend/src/annotation-service/core/formatting/enum-utils.service.ts new file mode 100644 index 000000000..e43362098 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/formatting/enum-utils.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { BaseEnumUtilsService } from '@common/base/base-enum-utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { IsActive } from '../enum/is-active.enum'; +import { InternalStatus } from '../enum/internal-status.enum'; + +@Injectable() +export class AnnotationServiceEnumUtils extends BaseEnumUtilsService { + constructor(private language: TranslateService) { super(); } + + + toIsActiveString(value: IsActive): string { + switch (value) { + case IsActive.Active: return this.language.instant('ANNOTATION-SERVICE.TYPES.IS-ACTIVE.ACTIVE'); + case IsActive.Inactive: return this.language.instant('ANNOTATION-SERVICE.TYPES.IS-ACTIVE.INACTIVE'); + default: return ''; + } + } + + toInternalStatusString(status: InternalStatus): string { + switch (status) { + case InternalStatus.Resolved: return this.language.instant('ANNOTATION-SERVICE.TYPES.INTERNAL-STATUS.RESOLVED'); + } + } + +} diff --git a/dmp-frontend/src/annotation-service/core/formatting/formatting.module.ts b/dmp-frontend/src/annotation-service/core/formatting/formatting.module.ts new file mode 100644 index 000000000..e7f028491 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/formatting/formatting.module.ts @@ -0,0 +1,36 @@ + + +// +// +// This is shared module that provides all notification service formatting utils. Its imported only once. +// + +import { CommonFormattingModule } from "@common/formatting/common-formatting.module"; +import { IsActiveTypePipe } from "./pipes/is-active-type.pipe"; +import { InternalStatusTypePipe } from "./pipes/internal-status-type.pipe"; +import { PipeService } from "@common/formatting/pipe.service"; +import { AnnotationServiceEnumUtils } from "./enum-utils.service"; +import { NgModule } from "@angular/core"; + +// +@NgModule({ + imports: [ + CommonFormattingModule, + ], + declarations: [ + IsActiveTypePipe, + InternalStatusTypePipe + ], + exports: [ + CommonFormattingModule, + IsActiveTypePipe, + InternalStatusTypePipe, + ], + providers: [ + PipeService, + IsActiveTypePipe, + InternalStatusTypePipe, + AnnotationServiceEnumUtils + ] +}) +export class AnnotationServiceFormattingModule { } diff --git a/dmp-frontend/src/annotation-service/core/formatting/pipes/internal-status-type.pipe.ts b/dmp-frontend/src/annotation-service/core/formatting/pipes/internal-status-type.pipe.ts new file mode 100644 index 000000000..1a61c2ab1 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/formatting/pipes/internal-status-type.pipe.ts @@ -0,0 +1,11 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { AnnotationServiceEnumUtils } from '../enum-utils.service'; + +@Pipe({ name: 'InternalStatusTypeFormat' }) +export class InternalStatusTypePipe implements PipeTransform { + constructor(private enumUtils: AnnotationServiceEnumUtils) { } + + public transform(value): any { + return this.enumUtils.toInternalStatusString(value); + } +} diff --git a/dmp-frontend/src/annotation-service/core/formatting/pipes/is-active-type.pipe.ts b/dmp-frontend/src/annotation-service/core/formatting/pipes/is-active-type.pipe.ts new file mode 100644 index 000000000..458a8b5c6 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/formatting/pipes/is-active-type.pipe.ts @@ -0,0 +1,11 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { AnnotationServiceEnumUtils } from '../enum-utils.service'; + +@Pipe({ name: 'IsActiveTypeFormat' }) +export class IsActiveTypePipe implements PipeTransform { + constructor(private enumUtils: AnnotationServiceEnumUtils) { } + + public transform(value): any { + return this.enumUtils.toIsActiveString(value); + } +} diff --git a/dmp-frontend/src/annotation-service/core/model/annotation-status.model.ts b/dmp-frontend/src/annotation-service/core/model/annotation-status.model.ts new file mode 100644 index 000000000..7e2c651c4 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/model/annotation-status.model.ts @@ -0,0 +1,16 @@ +import { BaseEntity } from "@common/base/base-entity.model"; +import { Annotation } from "./annotation.model"; +import { Status } from "./status.model"; +import { Guid } from "@common/types/guid"; + +export interface AnnotationStatus extends BaseEntity { + annotation: Annotation; + status: Status; +} + +// persist + +export interface AnnotationStatusPersist extends BaseEntity { + annotationId: Guid; + statusId: Guid; +} \ No newline at end of file diff --git a/dmp-frontend/src/annotation-service/core/model/annotation.model.ts b/dmp-frontend/src/annotation-service/core/model/annotation.model.ts index 8c868f66c..6a5cfa871 100644 --- a/dmp-frontend/src/annotation-service/core/model/annotation.model.ts +++ b/dmp-frontend/src/annotation-service/core/model/annotation.model.ts @@ -2,6 +2,7 @@ import { AnnotationProtectionType } from "@app/core/common/enum/annotation-prote import { User } from "@app/core/model/user/user"; import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model"; import { Guid } from "@common/types/guid"; +import { AnnotationStatus } from "./annotation-status.model"; export interface Annotation extends BaseEntity { entityId: Guid; @@ -13,6 +14,7 @@ export interface Annotation extends BaseEntity { threadId: Guid; parent: Annotation; protectionType: AnnotationProtectionType; + annotationStatuses: AnnotationStatus[]; } export interface AnnotationPersist extends BaseEntityPersist { diff --git a/dmp-frontend/src/annotation-service/core/model/status.model.ts b/dmp-frontend/src/annotation-service/core/model/status.model.ts new file mode 100644 index 000000000..aa374663f --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/model/status.model.ts @@ -0,0 +1,12 @@ +import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model"; +import { InternalStatus } from "../enum/internal-status.enum"; + +export interface Status extends BaseEntity { + label: string; + internalStatus: InternalStatus; +} + +export interface StatusPersist extends BaseEntityPersist { + label: string; + internalStatus: InternalStatus; +} \ No newline at end of file diff --git a/dmp-frontend/src/annotation-service/core/query/status.lookup.ts b/dmp-frontend/src/annotation-service/core/query/status.lookup.ts new file mode 100644 index 000000000..3078f8703 --- /dev/null +++ b/dmp-frontend/src/annotation-service/core/query/status.lookup.ts @@ -0,0 +1,26 @@ +import { IsActive } from "@app/core/common/enum/is-active.enum"; +import { Lookup } from "@common/model/lookup"; +import { Guid } from "@common/types/guid"; +import { InternalStatus } from "../enum/internal-status.enum"; + +export class StatusLookup extends Lookup implements StatusFilter { + + like: string; + ids: Guid[]; + excludedIds: Guid[]; + isActive: IsActive[]; + internalStatuses: InternalStatus[]; + + constructor() { + super(); + } + +} + +export interface StatusFilter { + like: string; + ids: Guid[]; + excludedIds: Guid[]; + isActive: IsActive[]; + internalStatuses: InternalStatus[]; +} \ No newline at end of file diff --git a/dmp-frontend/src/annotation-service/services/core-service.module.ts b/dmp-frontend/src/annotation-service/services/core-service.module.ts index 58e1e9189..579cd9ddb 100644 --- a/dmp-frontend/src/annotation-service/services/core-service.module.ts +++ b/dmp-frontend/src/annotation-service/services/core-service.module.ts @@ -11,6 +11,7 @@ import { FormService } from "@common/forms/form-service"; import { HttpErrorHandlingService } from "@common/modules/errors/error-handling/http-error-handling.service"; import { FilterService } from "@common/modules/text-filter/filter-service"; import { AnnotationService } from "@annotation-service/services/http/annotation.service"; +import { StatusService } from "./http/status.service"; @NgModule({}) export class CoreAnnotationServiceModule { @@ -30,7 +31,8 @@ export class CoreAnnotationServiceModule { FormService, LoggingService, PrincipalService, - AnnotationService + AnnotationService, + StatusService ], }; } diff --git a/dmp-frontend/src/annotation-service/services/http/annotation.service.ts b/dmp-frontend/src/annotation-service/services/http/annotation.service.ts index d89f5d059..44c683da1 100644 --- a/dmp-frontend/src/annotation-service/services/http/annotation.service.ts +++ b/dmp-frontend/src/annotation-service/services/http/annotation.service.ts @@ -1,4 +1,5 @@ import { Injectable } from "@angular/core"; +import { AnnotationStatus, AnnotationStatusPersist } from "@annotation-service/core/model/annotation-status.model"; import { Annotation, AnnotationPersist } from "@annotation-service/core/model/annotation.model"; import { AnnotationLookup } from "@annotation-service/core/query/annotation.lookup"; import { ConfigurationService } from "@app/core/services/configuration/configuration.service"; @@ -42,6 +43,14 @@ export class AnnotationService { catchError((error: any) => throwError(error))); } + persistStatus(item: AnnotationStatusPersist) { + const url = `${this.apiBase}/persist-status`; + + return this.http + .post(url, item).pipe( + catchError((error: any) => throwError(error))); + } + delete(id: Guid, ): Observable { const url = `${this.apiBase}/${id}`; return this.http diff --git a/dmp-frontend/src/annotation-service/services/http/status.service.ts b/dmp-frontend/src/annotation-service/services/http/status.service.ts new file mode 100644 index 000000000..49a05b1d5 --- /dev/null +++ b/dmp-frontend/src/annotation-service/services/http/status.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from "@angular/core"; +import { Status, StatusPersist } from "@annotation-service/core/model/status.model"; +import { StatusLookup } from "@annotation-service/core/query/status.lookup"; +import { ConfigurationService } from "@app/core/services/configuration/configuration.service"; +import { BaseHttpV2Service } from "@app/core/services/http/base-http-v2.service"; +import { QueryResult } from "@common/model/query-result"; +import { FilterService } from "@common/modules/text-filter/filter-service"; +import { Guid } from "@common/types/guid"; +import { Observable, throwError } from "rxjs"; +import { catchError } from "rxjs/operators"; + +@Injectable() +export class StatusService { + private get apiBase(): string { return `${this.installationConfiguration.annotationServiceAddress}api/status`; } + + constructor( + private installationConfiguration: ConfigurationService, + private http: BaseHttpV2Service, + private filterService: FilterService + ) { } + + query(q: StatusLookup): 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))); + } + + persist(item: StatusPersist) { + 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/annotation-service/ui/admin/status/editor/status-editor.component.html b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.html new file mode 100644 index 000000000..88c2d4216 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.html @@ -0,0 +1,71 @@ +
+
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ + + +
+
+
+
+
+
+ + {{'ANNOTATION-SERVICE.STATUS-EDITOR.FIELDS.LABEL' | translate}} + +
+
+
+
+
+
+
+
+ + + {{formGroup.get('label').getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'ANNOTATION-SERVICE.STATUS-EDITOR.FIELDS.INTERNAL-STATUS' | translate}} + + {{enumUtils.toInternalStatusString(internalStatus)}} + + {{formGroup.get('internalStatus').getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+
+
+
+
+
+
+
+
+
diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.scss b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.scss new file mode 100644 index 000000000..489faf25d --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.scss @@ -0,0 +1,26 @@ +.status-editor { + // padding-top: 1em; + + .editor-actions { + margin-top: 30px; + } +} + +.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; + } +} \ No newline at end of file diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.ts b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.ts new file mode 100644 index 000000000..e4d57f7fb --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.component.ts @@ -0,0 +1,177 @@ +import { Component, OnInit } from '@angular/core'; +import { UntypedFormGroup } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { Title } from '@angular/platform-browser'; +import { ActivatedRoute, Router } from '@angular/router'; +import { IsActive } from '@app/core/common/enum/is-active.enum'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { ConfigurationService } from '@app/core/services/configuration/configuration.service'; +import { LockService } from '@app/core/services/lock/lock.service'; +import { LoggingService } from '@app/core/services/logging/logging-service'; +import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; +import { QueryParamsService } from '@app/core/services/utilities/query-params.service'; +import { BaseEditor } from '@common/base/base-editor'; +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 { FilterService } from '@common/modules/text-filter/filter-service'; +import { Guid } from '@common/types/guid'; +import { TranslateService } from '@ngx-translate/core'; +import { map, takeUntil } from 'rxjs/operators'; +import { StatusEditorModel } from './status-editor.model'; +import { StatusEditorService } from './status-editor.service'; +import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; +import { AnnotationServiceEnumUtils } from '@annotation-service/core/formatting/enum-utils.service'; +import { Status, StatusPersist } from '@annotation-service/core/model/status.model'; +import { StatusService } from '@annotation-service/services/http/status.service'; +import { StatusEditorResolver } from './status-editor.resolver'; +import { InternalStatus } from '@annotation-service/core/enum/internal-status.enum'; + +@Component({ + templateUrl: './status-editor.component.html', + styleUrls: ['./status-editor.component.scss'], + providers: [StatusEditorService] +}) +export class StatusEditorComponent extends BaseEditor implements OnInit { + + isNew = true; + isDeleted = false; + formGroup: UntypedFormGroup = null; + showInactiveDetails = false; + public internalStatusEnum = this.enumUtils.getEnumValues(InternalStatus); + + + protected get canDelete(): boolean { + return !this.isDeleted && !this.isNew && this.hasPermission(this.authService.permissionEnum.DeleteStatus) && this.editorModel.belongsToCurrentTenant != false; + } + + protected get canSave(): boolean { + return !this.isDeleted && this.hasPermission(this.authService.permissionEnum.EditStatus) && this.editorModel.belongsToCurrentTenant != false; + } + + 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 route: ActivatedRoute, + protected queryParamsService: QueryParamsService, + protected lockService: LockService, + protected authService: AuthService, + protected configurationService: ConfigurationService, + // Rest dependencies. Inject any other needed deps here: + public enumUtils: AnnotationServiceEnumUtils, + private statusService: StatusService, + private logger: LoggingService, + private statusEditorService: StatusEditorService, + public titleService: Title, + protected routerUtils: RouterUtilsService + ) { + const statusLabel: string = route.snapshot.data['entity']?.name; + if (statusLabel) { + titleService.setTitle(statusLabel); + } else { + titleService.setTitle('ANNOTATION-SERVICE.STATUS-EDITOR.TITLE-EDIT-ANNOTATION-STATUS'); + } + super(dialog, language, formService, router, uiNotificationService, httpErrorHandlingService, filterService, route, queryParamsService, lockService, authService, configurationService); + } + + ngOnInit(): void { + super.ngOnInit(); + } + + getItem(itemId: Guid, successFunction: (item: Status) => void) { + this.statusService.getSingle(itemId, StatusEditorResolver.lookupFields()) + .pipe(map(data => data as Status), takeUntil(this._destroyed)) + .subscribe( + data => successFunction(data), + error => this.onCallbackError(error) + ); + } + + prepareForm(data: Status) { + try { + this.editorModel = data ? new StatusEditorModel().fromModel(data) : new StatusEditorModel(); + this.isDeleted = data ? data.isActive === IsActive.Inactive : false; + this.buildForm(); + } catch (error) { + this.logger.error('Could not parse Status 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.EditStatus)); + this.statusEditorService.setValidationErrorModel(this.editorModel.validationErrorModel); + } + + refreshData(): void { + this.getItem(this.editorModel.id, (data: Status) => this.prepareForm(data)); + } + + refreshOnNavigateToData(id?: Guid): void { + this.formGroup.markAsPristine(); + + this.router.navigate([this.routerUtils.generateUrl('/annotation-statuses')], { 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 StatusPersist; + + this.statusService.persist(formData) + .pipe(takeUntil(this._destroyed)).subscribe( + // for each state navigate to listing page + complete => { + this.onCallbackSuccess(); + }, + error => this.onCallbackError(error) + ); + } + + formSubmit(): void { + this.formService.removeAllBackEndErrors(this.formGroup); + 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.statusService.delete(value.id).pipe(takeUntil(this._destroyed)) + .subscribe( + complete => this.onCallbackDeleteSuccess(), + error => this.onCallbackError(error) + ); + } + }); + } + } + + clearErrorModel() { + this.editorModel.validationErrorModel.clear(); + this.formService.validateAllFormFields(this.formGroup); + } +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.model.ts b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.model.ts new file mode 100644 index 000000000..d860dc2ea --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.model.ts @@ -0,0 +1,50 @@ +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { InternalStatus } from '@annotation-service/core/enum/internal-status.enum'; +import { Status, StatusPersist } from '@annotation-service/core/model/status.model'; +import { BaseEditorModel } from '@common/base/base-form-editor-model'; +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'; + +export class StatusEditorModel extends BaseEditorModel implements StatusPersist { + label: string; + internalStatus: InternalStatus; + permissions: string[]; + + public validationErrorModel: ValidationErrorModel = new ValidationErrorModel(); + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor() { super(); } + + public fromModel(item: Status): StatusEditorModel { + if (item) { + super.fromModel(item); + this.label = item.label; + this.internalStatus = item.internalStatus; + } + 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], + label: [{ value: this.label, disabled: disabled }, context.getValidation('label').validators], + internalStatus: [{ value: this.internalStatus, disabled: disabled }, context.getValidation('internalStatus').validators], + 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: 'label', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'label')] }); + baseValidationArray.push({ key: 'internalStatus', validators: [BackendErrorValidator(this.validationErrorModel, 'internalStatus')] }); + baseValidationArray.push({ key: 'hash', validators: [] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.resolver.ts b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.resolver.ts new file mode 100644 index 000000000..54f9a1077 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.resolver.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { Status } from '@annotation-service/core/model/status.model'; +import { StatusService } from '@annotation-service/services/http/status.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 StatusEditorResolver extends BaseEditorResolver { + + constructor(private statusService: StatusService, private breadcrumbService: BreadcrumbService) { + super(); + } + + public static lookupFields(): string[] { + return [ + ...BaseEditorResolver.lookupFields(), + nameof(x => x.id), + nameof(x => x.label), + nameof(x => x.internalStatus), + nameof(x => x.createdAt), + nameof(x => x.updatedAt), + nameof(x => x.hash), + nameof(x => x.isActive) + ] + } + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + + const fields = [ + ...StatusEditorResolver.lookupFields() + ]; + const id = route.paramMap.get('id'); + + if (id != null) { + return this.statusService.getSingle(Guid.parse(id), fields).pipe(tap(x => this.breadcrumbService.addIdResolvedValue(x.id?.toString(), x.label)), takeUntil(this._destroyed)); + } + } +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.service.ts b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-editor.service.ts new file mode 100644 index 000000000..2bef89bd8 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/editor/status-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 StatusEditorService { + private validationErrorModel: ValidationErrorModel; + + public setValidationErrorModel(validationErrorModel: ValidationErrorModel): void { + this.validationErrorModel = validationErrorModel; + } + + public getValidationErrorModel(): ValidationErrorModel { + return this.validationErrorModel; + } +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.html b/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.html new file mode 100644 index 000000000..bc96f462b --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.html @@ -0,0 +1,59 @@ +
+ + + + + +
+
+
+

{{'ANNOTATION-SERVICE.STATUS-LISTING.FILTER.TITLE' | translate}}

+
+
+ +
+
+ +
+
+
+ + {{'ANNOTATION-SERVICE.STATUS-LISTING.FILTER.IS-ACTIVE' | translate}} + +
+
+
+ +
+
+ + {{'ANNOTATION-SERVICE.STATUS-LISTING.FILTER.INTERNAL-STATUS' | translate}} + + {{enumUtils.toInternalStatusString(type)}} + + +
+
+ +
+
+ +
+
+ +
+
+
+
+ + +
diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.scss b/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.scss new file mode 100644 index 000000000..8e2ed08bf --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.scss @@ -0,0 +1,26 @@ + +::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; + // } +} + +::ng-deep .mdc-form-field { + label { + margin: 0; + } +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.ts b/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.ts new file mode 100644 index 000000000..9868cab51 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/listing/filters/status-listing-filters.component.ts @@ -0,0 +1,100 @@ +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { BaseComponent } from '@common/base/base.component'; +import { IsActive } from '@notification-service/core/enum/is-active.enum'; +import { nameof } from 'ts-simple-nameof'; +import { StatusFilter } from '@annotation-service/core/query/status.lookup'; +import { InternalStatus } from '@annotation-service/core/enum/internal-status.enum'; +import { AnnotationServiceEnumUtils } from '@annotation-service/core/formatting/enum-utils.service'; + +@Component({ + selector: 'app-status-listing-filters', + templateUrl: './status-listing-filters.component.html', + styleUrls: ['./status-listing-filters.component.scss'] +}) +export class StatusListingFiltersComponent extends BaseComponent implements OnInit, OnChanges { + + @Input() readonly filter: StatusFilter; + @Output() filterChange = new EventEmitter(); + internalStatusEnumValues = this.enumUtils.getEnumValues(InternalStatus) + + // * State + internalFilters: StatusListingFilters = this._getEmptyFilters(); + + protected appliedFilterCount: number = 0; + constructor( + public enumUtils: AnnotationServiceEnumUtils, + ) { super(); } + + ngOnInit() { + } + + ngOnChanges(changes: SimpleChanges): void { + const filterChange = changes[nameof(x => x.filter)]?.currentValue as StatusFilter; + 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, internalStatuses, like } = this.internalFilters ?? {} + this.filterChange.emit({ + ...this.filter, + like: like, + isActive: isActive ? [IsActive.Active] : [IsActive.Inactive], + internalStatuses + }) + } + + + private _parseToInternalFilters(inputFilter: StatusFilter): StatusListingFilters { + if (!inputFilter) { + return this._getEmptyFilters(); + } + + let { isActive, internalStatuses, like } = inputFilter; + + return { + isActive: (isActive ?? [])?.includes(IsActive.Active) || !isActive?.length, + internalStatuses: internalStatuses, + like: like + } + + } + + private _getEmptyFilters(): StatusListingFilters { + return { + isActive: true, + internalStatuses: null, + like: null, + } + } + + private _computeAppliedFilters(filters: StatusListingFilters): number { + let count = 0; + if (filters?.isActive) { + count++ + } + return count; + } + + clearFilters() { + this.internalFilters = this._getEmptyFilters(); + } +} + +interface StatusListingFilters { + like: string; + isActive: boolean; + internalStatuses: InternalStatus[]; +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.html b/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.html new file mode 100644 index 000000000..adbed7be5 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.html @@ -0,0 +1,93 @@ +
+
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + +
+
+
+ + + + +
+
+ + {{item?.name | nullifyValue}} +
+
+ + + {{'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.INTERNAL-STATUS' | translate}}: + + {{enumUtils.toNotificationTypeString(item.notificationType) | nullifyValue}} + + +
+
+ + + {{'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.CREATED-AT' | translate}}: + + {{item?.createdAt | dateTimeFormatter : 'short' | nullifyValue}} + + + + + + {{'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.UPDATED-AT' | translate}}: + + {{item?.updatedAt | dateTimeFormatter : 'short' | nullifyValue}} + + + +
+
+
+ + +
+
+ + + + + +
+
+
\ No newline at end of file diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.scss b/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.scss new file mode 100644 index 000000000..248f88bac --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.scss @@ -0,0 +1,60 @@ +.status-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/annotation-service/ui/admin/status/listing/status-listing.component.ts b/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.ts new file mode 100644 index 000000000..50211dc9a --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/listing/status-listing.component.ts @@ -0,0 +1,180 @@ +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, Router } from '@angular/router'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-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 '@app/core/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 { IsActive } from '@notification-service/core/enum/is-active.enum'; +import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; +import { Status } from '@annotation-service/core/model/status.model'; +import { StatusLookup } from '@annotation-service/core/query/status.lookup'; +import { StatusService } from '@annotation-service/services/http/status.service'; +import { AnnotationServiceEnumUtils } from '@annotation-service/core/formatting/enum-utils.service'; +import { InternalStatusTypePipe } from '@annotation-service/core/formatting/pipes/internal-status-type.pipe'; +import { IsActiveTypePipe } from '@annotation-service/core/formatting/pipes/is-active-type.pipe'; + +@Component({ + templateUrl: './status-listing.component.html', + styleUrls: ['./status-listing.component.scss'] +}) +export class StatusListingComponent extends BaseListingComponent implements OnInit { + publish = false; + userSettingsKey = { key: 'StatusListingUserSettings' }; + 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.label), + nameof(x => x.internalStatus), + nameof(x => x.updatedAt), + nameof(x => x.createdAt), + nameof(x => x.hash), + nameof(x => x.isActive) + ]; + + rowIdentity = x => x.id; + + constructor( + public routerUtils: RouterUtilsService, + protected router: Router, + protected route: ActivatedRoute, + protected uiNotificationService: UiNotificationService, + protected httpErrorHandlingService: HttpErrorHandlingService, + protected queryParamsService: QueryParamsService, + private statusService: StatusService, + public authService: AuthService, + private pipeService: PipeService, + public enumUtils: AnnotationServiceEnumUtils, + 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(): StatusLookup { + const lookup = new StatusLookup(); + 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.label), + sortable: true, + languageName: 'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.LABEL', + }, + { + prop: nameof(x => x.internalStatus), + sortable: true, + languageName: 'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.INTERNAL-STATUS', + pipe: this.pipeService.getPipe(InternalStatusTypePipe) + }, + { + prop: nameof(x => x.createdAt), + sortable: true, + languageName: 'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.CREATED-AT', + pipe: this.pipeService.getPipe(DataTableDateTimeFormatPipe).withFormat('short') + }, + { + prop: nameof(x => x.updatedAt), + sortable: true, + languageName: 'ANNOTATION-SERVICE.STATUS-LISTING.FIELDS.UPDATED-AT', + pipe: this.pipeService.getPipe(DataTableDateTimeFormatPipe).withFormat('short') + }, + { + prop: nameof(x => x.isActive), + sortable: true, + languageName: 'ANNOTATION-SERVICE.STATUS-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.statusService.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.statusService.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.refresh(); + } +} diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/status.module.ts b/dmp-frontend/src/annotation-service/ui/admin/status/status.module.ts new file mode 100644 index 000000000..e82942316 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/status.module.ts @@ -0,0 +1,45 @@ +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { NgModule } from "@angular/core"; +import { AutoCompleteModule } from "@app/library/auto-complete/auto-complete.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 { StatusListingComponent } from './listing/status-listing.component'; +import { RichTextEditorModule } from '@app/library/rich-text-editor/rich-text-editor.module'; +import { MatIconModule } from '@angular/material/icon'; +import { EditorModule } from '@tinymce/tinymce-angular'; +import { StatusRoutingModule } from './status.routing'; +import { AnnotationServiceFormattingModule } from '@annotation-service/core/formatting/formatting.module'; +import { StatusListingFiltersComponent } from './listing/filters/status-listing-filters.component'; +import { StatusEditorComponent } from './editor/status-editor.component'; + +@NgModule({ + imports: [ + CommonUiModule, + CommonFormsModule, + ConfirmationDialogModule, + StatusRoutingModule, + NgxDropzoneModule, + DragDropModule, + AutoCompleteModule, + HybridListingModule, + TextFilterModule, + UserSettingsModule, + CommonFormattingModule, + RichTextEditorModule, + MatIconModule, + EditorModule, + AnnotationServiceFormattingModule + ], + declarations: [ + StatusEditorComponent, + StatusListingComponent, + StatusListingFiltersComponent, + ] +}) +export class StatusModule { } diff --git a/dmp-frontend/src/annotation-service/ui/admin/status/status.routing.ts b/dmp-frontend/src/annotation-service/ui/admin/status/status.routing.ts new file mode 100644 index 000000000..655503825 --- /dev/null +++ b/dmp-frontend/src/annotation-service/ui/admin/status/status.routing.ts @@ -0,0 +1,58 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +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 { StatusListingComponent } from './listing/status-listing.component'; +import { StatusEditorResolver } from './editor/status-editor.resolver'; +import { StatusEditorComponent } from './editor/status-editor.component'; + +const routes: Routes = [ + { + path: '', + component: StatusListingComponent, + canActivate: [AuthGuard] + }, + { + path: 'new', + canActivate: [AuthGuard], + component: StatusEditorComponent, + canDeactivate: [PendingChangesGuard], + data: { + authContext: { + permissions: [AppPermission.EditStatus] + }, + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.NEW-TENANT' + }), + getFromTitleService: true, + usePrefix: false + } + }, + { + path: ':id', + canActivate: [AuthGuard], + component: StatusEditorComponent, + canDeactivate: [PendingChangesGuard], + resolve: { + 'entity': StatusEditorResolver + }, + data: { + authContext: { + permissions: [AppPermission.EditStatus] + }, + getFromTitleService: true, + usePrefix: false + } + + }, + { path: '**', loadChildren: () => import('@common/modules/page-not-found/page-not-found.module').then(m => m.PageNotFoundModule) }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [StatusEditorResolver] +}) +export class StatusRoutingModule { } diff --git a/dmp-frontend/src/app/app-routing.module.ts b/dmp-frontend/src/app/app-routing.module.ts index b5231d0ac..db44db47f 100644 --- a/dmp-frontend/src/app/app-routing.module.ts +++ b/dmp-frontend/src/app/app-routing.module.ts @@ -353,6 +353,19 @@ const appRoutes: Routes = [ title: 'GENERAL.TITLES.ENTITY-LOCKS' }, }, + { + path: 'annotation-statuses', + loadChildren: () => import('@annotation-service/ui/admin/status/status.module').then(m => m.StatusModule), + data: { + authContext: { + permissions: [AppPermission.ViewStatusPage] + }, + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.ANNOTATION-STATUSES' + }), + title: 'GENERAL.TITLES.ANNOTATION-STATUSES' + }, + }, { path: 'maintenance-tasks', loadChildren: () => import('./ui/admin/maintenance-tasks/maintenance-tasks.module').then(m => m.MaintenanceTasksModule), 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 5c585f061..820fa5fc4 100644 --- a/dmp-frontend/src/app/core/common/enum/permission.enum.ts +++ b/dmp-frontend/src/app/core/common/enum/permission.enum.ts @@ -195,6 +195,11 @@ export enum AppPermission { EditPrefillingSource= "EditPrefillingSource", DeletePrefillingSource = "DeletePrefillingSource", + //Status + BrowseStatus = "BrowseStatus", + EditStatus = "EditStatus", + DeleteStatus = "DeleteStatus", + // UI Pages ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage", @@ -219,5 +224,6 @@ export enum AppPermission { ViewHomePage = "ViewHomePage", ViewMineInAppNotificationPage = "ViewMineInAppNotificationPage", ViewTenantConfigurationPage = "ViewTenantConfigurationPage", + ViewStatusPage = "ViewStatusPage", } diff --git a/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts b/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts index 367d37d19..e4ef8af3b 100644 --- a/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts +++ b/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts @@ -119,6 +119,7 @@ export class SidebarComponent implements OnInit { if (this.authentication.hasPermission(AppPermission.ViewSupportiveMaterialPage)) this.adminItems.routes.push({ path: '/supportive-material', title: 'SIDE-BAR.SUPPORTIVE-MATERIAL', icon: 'help_center' }); if (this.authentication.hasPermission(AppPermission.ViewNotificationTemplatePage)) this.adminItems.routes.push({ path: '/notification-templates', title: 'SIDE-BAR.NOTIFICATION-TEMPLATES', icon: 'grid_guides' }); if (this.authentication.hasPermission(AppPermission.ViewNotificationPage)) this.adminItems.routes.push({ path: '/notifications', title: 'SIDE-BAR.NOTIFICATIONS', icon: 'notifications' }); + if (this.authentication.hasPermission(AppPermission.ViewStatusPage)) this.adminItems.routes.push({ path: '/annotation-statuses', title: 'SIDE-BAR.ANNOTATION-STATUSES', icon: 'notifications' }); if (this.authentication.hasPermission(AppPermission.ViewMaintenancePage)) this.adminItems.routes.push({ path: '/maintenance-tasks', title: 'SIDE-BAR.MAINTENANCE', icon: 'build' }); this.groupMenuItems.push(this.adminItems);