diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index b47ee3f62..4127b1a70 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -406,7 +406,22 @@ const appRoutes: Routes = [ breadcrumb: true, ...BreadcrumbService.generateRouteDataConfiguration({ title: 'GENERAL.TITLES.PLAN-STATUSES' - }) + }), + title: 'GENERAL.TITLES.PLAN-STATUSES' + } + }, + { + path: 'description-statuses', + loadChildren: () => import('./ui/admin/description-status/description-status.module').then(m => m.DescriptionStatusModule), + data: { + authContext: { + permissions: [AppPermission.ViewDescriptionStatusPage] + }, + breadcrumb: true, + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'GENERAL.TITLES.DESCRIPTION-STATUSES' + }), + title: 'GENERAL.TITLES.DESCRIPTION-STATUSES' } }, { diff --git a/frontend/src/app/core/common/enum/description-status.ts b/frontend/src/app/core/common/enum/description-status.ts index ab1a4210f..de773d413 100644 --- a/frontend/src/app/core/common/enum/description-status.ts +++ b/frontend/src/app/core/common/enum/description-status.ts @@ -1,4 +1,4 @@ -export enum DescriptionStatus { +export enum DescriptionStatusEnum { Draft = 0, Finalized = 1, Canceled = 2 diff --git a/frontend/src/app/core/common/enum/permission.enum.ts b/frontend/src/app/core/common/enum/permission.enum.ts index 75a004105..d2a63e292 100644 --- a/frontend/src/app/core/common/enum/permission.enum.ts +++ b/frontend/src/app/core/common/enum/permission.enum.ts @@ -90,6 +90,10 @@ export enum AppPermission { EditPlanStatus = "EditPlanStatus", DeletePlanStatus = "DeletePlanStatus", + //DescriptionStatus + EditDescriptionStatus = "EditDescriptionStatus", + DeleteDescriptionStatus = "DeleteDescriptionStatus", + //PlanBlueprint BrowsePlanBlueprint = "BrowsePlanBlueprint", EditPlanBlueprint = "EditPlanBlueprint", @@ -239,6 +243,7 @@ export enum AppPermission { ViewTenantConfigurationPage = "ViewTenantConfigurationPage", ViewStatusPage = "ViewStatusPage", ViewUsageLimitPage = "ViewUsageLimitPage", - ViewPlanStatusPage = "ViewPlanStatusPage" + ViewPlanStatusPage = "ViewPlanStatusPage", + ViewDescriptionStatusPage = "ViewDescriptionStatusPage", } diff --git a/frontend/src/app/core/core-service.module.ts b/frontend/src/app/core/core-service.module.ts index 17b4d1794..57d1dd5df 100644 --- a/frontend/src/app/core/core-service.module.ts +++ b/frontend/src/app/core/core-service.module.ts @@ -49,6 +49,7 @@ import { TenantHandlingService } from './services/tenant/tenant-handling.service import { RouterUtilsService } from './services/router/router-utils.service'; import { UsageLimitService } from './services/usage-limit/usage.service'; import { PlanStatusService } from './services/plan/plan-status.service'; +import { DescriptionStatusService } from './services/description-status/description-status.service'; // // // This is shared module that provides all the services. Its imported only once on the AppModule. @@ -117,7 +118,8 @@ export class CoreServiceModule { TenantHandlingService, RouterUtilsService, UsageLimitService, - PlanStatusService + PlanStatusService, + DescriptionStatusService ], }; } diff --git a/frontend/src/app/core/model/description-status/description-status-persist.ts b/frontend/src/app/core/model/description-status/description-status-persist.ts new file mode 100644 index 000000000..10600041a --- /dev/null +++ b/frontend/src/app/core/model/description-status/description-status-persist.ts @@ -0,0 +1,10 @@ +import { DescriptionStatusEnum } from "@app/core/common/enum/description-status"; +import { BaseEntityPersist } from "@common/base/base-entity.model"; +import { DescriptionStatusDefinition } from "./description-status"; + +export interface DescriptionStatusPersist extends BaseEntityPersist { + name: string; + description: string; + internalStatus: DescriptionStatusEnum; + definition: DescriptionStatusDefinition; +} \ No newline at end of file diff --git a/frontend/src/app/core/model/description-status/description-status.ts b/frontend/src/app/core/model/description-status/description-status.ts new file mode 100644 index 000000000..856b38958 --- /dev/null +++ b/frontend/src/app/core/model/description-status/description-status.ts @@ -0,0 +1,26 @@ +import { AppRole } from "@app/core/common/enum/app-role"; +import { DescriptionStatusEnum } from "@app/core/common/enum/description-status"; +import { PlanUserRole } from "@app/core/common/enum/plan-user-role"; +import { BaseEntity } from "@common/base/base-entity.model"; + +export interface DescriptionStatus extends BaseEntity{ + name: string; + description: string; + internalStatus: DescriptionStatusEnum; + definition: DescriptionStatusDefinition; +} + +export interface DescriptionStatusDefinition { + authorization: DescriptionStatusDefinitionAuthorization +} + +export interface DescriptionStatusDefinitionAuthorization { + edit: DescriptionStatusDefinitionAuthorizationItem; +} + +export interface DescriptionStatusDefinitionAuthorizationItem{ + roles: AppRole[], + planRoles: PlanUserRole[], + allowAuthenticated: boolean; + allowAnonymous: boolean +} \ No newline at end of file diff --git a/frontend/src/app/core/model/description/description.ts b/frontend/src/app/core/model/description/description.ts index 713c0d65d..dacfd5f27 100644 --- a/frontend/src/app/core/model/description/description.ts +++ b/frontend/src/app/core/model/description/description.ts @@ -1,4 +1,4 @@ -import { DescriptionStatus } from "@app/core/common/enum/description-status"; +import { DescriptionStatusEnum } from "@app/core/common/enum/description-status"; import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model"; import { Guid } from "@common/types/guid"; import { DescriptionTemplate } from "../description-template/description-template"; @@ -76,7 +76,7 @@ export interface DescriptionPersist extends BaseEntityPersist { planId: Guid; planDescriptionTemplateId: Guid; descriptionTemplateId: Guid; - status: DescriptionStatus; + status: DescriptionStatusEnum; description: string; properties: DescriptionPropertyDefinitionPersist; tags: string[]; @@ -120,7 +120,7 @@ export interface DescriptionReferencePersist { export interface DescriptionStatusPersist { id: Guid; - status: DescriptionStatus; + status: DescriptionStatusEnum; hash: string; } @@ -130,7 +130,7 @@ export interface DescriptionStatusPersist { export interface PublicDescription extends BaseDescription { label?: string; - status?: DescriptionStatus; + status?: DescriptionStatusEnum; description?: string; finalizedAt?: Date; descriptionTemplate?: PublicDescriptionTemplate; @@ -161,5 +161,5 @@ export interface UpdateDescriptionTemplatePersist { export interface BaseDescription extends BaseEntity { tenantId?: Guid; - status?: DescriptionStatus; + status?: DescriptionStatusEnum; } diff --git a/frontend/src/app/core/query/description-status.lookup.ts b/frontend/src/app/core/query/description-status.lookup.ts new file mode 100644 index 000000000..60ce40f88 --- /dev/null +++ b/frontend/src/app/core/query/description-status.lookup.ts @@ -0,0 +1,21 @@ +import { Lookup } from "@common/model/lookup"; +import { Guid } from "@common/types/guid"; +import { IsActive } from "../common/enum/is-active.enum"; + +export class DescriptionStatusLookup extends Lookup implements DescriptionStatusFilter { + ids: Guid[]; + excludedIds: Guid[]; + like: string; + isActive: IsActive[]; + + constructor() { + super(); + } +} + +export interface DescriptionStatusFilter { + ids: Guid[]; + excludedIds: Guid[]; + like: string; + isActive: IsActive[]; +} \ No newline at end of file diff --git a/frontend/src/app/core/query/description.lookup.ts b/frontend/src/app/core/query/description.lookup.ts index cdf78e007..810ff4bd0 100644 --- a/frontend/src/app/core/query/description.lookup.ts +++ b/frontend/src/app/core/query/description.lookup.ts @@ -1,7 +1,7 @@ import { Lookup } from '@common/model/lookup'; import { Guid } from '@common/types/guid'; import { IsActive } from '../common/enum/is-active.enum'; -import { DescriptionStatus } from '../common/enum/description-status'; +import { DescriptionStatusEnum } from '../common/enum/description-status'; import { PlanLookup } from './plan.lookup'; import { DescriptionReferenceLookup } from './reference.lookup'; import { DescriptionTagLookup } from './tag.lookup'; @@ -17,7 +17,7 @@ export class DescriptionLookup extends Lookup implements DescriptionFilter { finalizedAfter: Date; finalizedBefore: Date; isActive: IsActive[]; - statuses: DescriptionStatus[]; + statuses: DescriptionStatusEnum[]; planSubQuery: PlanLookup; tenantSubQuery: TenantLookup; @@ -39,7 +39,7 @@ export interface DescriptionFilter { finalizedAfter: Date; finalizedBefore: Date; isActive: IsActive[]; - statuses: DescriptionStatus[]; + statuses: DescriptionStatusEnum[]; planSubQuery: PlanLookup; descriptionTemplateSubQuery: DescriptionTemplateLookup; diff --git a/frontend/src/app/core/services/description-status/description-status.service.ts b/frontend/src/app/core/services/description-status/description-status.service.ts new file mode 100644 index 000000000..1c35589b8 --- /dev/null +++ b/frontend/src/app/core/services/description-status/description-status.service.ts @@ -0,0 +1,53 @@ +import { HttpHeaders } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { BaseHttpV2Service } from "../http/base-http-v2.service"; +import { ConfigurationService } from "../configuration/configuration.service"; +import { QueryResult } from "@common/model/query-result"; +import { catchError, Observable, throwError } from "rxjs"; +import { Guid } from "@common/types/guid"; +import { DescriptionStatusLookup } from "@app/core/query/description-status.lookup"; +import { DescriptionStatus } from "@app/core/model/description-status/description-status"; +import { DescriptionStatusPersist } from "@app/core/model/description/description"; + +@Injectable() +export class DescriptionStatusService { + private headers = new HttpHeaders(); + + constructor( + private http: BaseHttpV2Service, + private configurationService: ConfigurationService, + ) { + } + + private get apiBase(): string { return `${this.configurationService.server}description-status`; } + + query(q: DescriptionStatusLookup): 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: DescriptionStatusPersist): 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/frontend/src/app/core/services/matomo/analytics-service.ts b/frontend/src/app/core/services/matomo/analytics-service.ts index 8cb446a4e..f793f3179 100644 --- a/frontend/src/app/core/services/matomo/analytics-service.ts +++ b/frontend/src/app/core/services/matomo/analytics-service.ts @@ -47,6 +47,8 @@ export class AnalyticsService { public static NotificationTempplateEditor: string = 'Admin: Notification Tempplates'; public static PlanStatusListing: string = 'Plan Status Listing'; public static PlanStatusEditor: string = 'Plan Status Editor'; + public static DescriptionStatusListing: string = 'Description Status Listing'; + public static DescriptionStatusEditor: string = 'Description Status Editor'; //#endregion //#region trackDownload diff --git a/frontend/src/app/core/services/utilities/enum-utils.service.ts b/frontend/src/app/core/services/utilities/enum-utils.service.ts index ddaae4bb9..37d986f0c 100644 --- a/frontend/src/app/core/services/utilities/enum-utils.service.ts +++ b/frontend/src/app/core/services/utilities/enum-utils.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { AnnotationProtectionType } from '@app/core/common/enum/annotation-protection-type.enum'; -import { DescriptionStatus } from '@app/core/common/enum/description-status'; +import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; import { DescriptionTemplateExternalSelectAuthType } from '@app/core/common/enum/description-template-external-select-auth-type'; import { DescriptionTemplateExternalSelectHttpMethodType } from '@app/core/common/enum/description-template-external-select-http-method-type'; import { DescriptionTemplateFieldDataExternalDatasetType } from '@app/core/common/enum/description-template-field-data-external-dataset-type'; @@ -210,11 +210,11 @@ export class EnumUtils { } } - toDescriptionStatusString(status: DescriptionStatus): string { + toDescriptionStatusString(status: DescriptionStatusEnum): string { switch (status) { - case DescriptionStatus.Draft: return this.language.instant('TYPES.DESCRIPTION-STATUS.DRAFT'); - case DescriptionStatus.Finalized: return this.language.instant('TYPES.DESCRIPTION-STATUS.FINALIZED'); - case DescriptionStatus.Canceled: return this.language.instant('TYPES.DESCRIPTION-STATUS.CANCELED'); + case DescriptionStatusEnum.Draft: return this.language.instant('TYPES.DESCRIPTION-STATUS.DRAFT'); + case DescriptionStatusEnum.Finalized: return this.language.instant('TYPES.DESCRIPTION-STATUS.FINALIZED'); + case DescriptionStatusEnum.Canceled: return this.language.instant('TYPES.DESCRIPTION-STATUS.CANCELED'); } } diff --git a/frontend/src/app/ui/admin/description-status/description-status.module.ts b/frontend/src/app/ui/admin/description-status/description-status.module.ts new file mode 100644 index 000000000..4971e53cb --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/description-status.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from "@angular/core"; +import { CommonFormsModule } from '@common/forms/common-forms.module'; +import { CommonUiModule } from '@common/ui/common-ui.module'; +import { CommonFormattingModule } from "@common/formatting/common-formatting.module"; +import { ConfirmationDialogModule } from "@common/modules/confirmation-dialog/confirmation-dialog.module"; +import { HybridListingModule } from "@common/modules/hybrid-listing/hybrid-listing.module"; +import { UserSettingsModule } from "@common/modules/user-settings/user-settings.module"; +import { TextFilterModule } from "@common/modules/text-filter/text-filter.module"; +import { RichTextEditorModule } from "@app/library/rich-text-editor/rich-text-editor.module"; +import { DescriptionStatusRoutingModule } from "./description-status.routing"; +import { DescriptionStatusListingComponent } from "./listing/description-status-listing/description-status-listing.component"; +import { DescriptionStatusEditorComponent } from "./editor/description-status-editor/description-status-editor.component"; +import { DescriptionStatusListingFiltersComponent } from "./listing/description-status-listing-filters/description-status-listing-filters.component"; + +@NgModule({ + imports: [ + CommonUiModule, + CommonFormsModule, + DescriptionStatusRoutingModule, + ConfirmationDialogModule, + HybridListingModule, + UserSettingsModule, + CommonFormattingModule, + TextFilterModule, + RichTextEditorModule + ], + declarations: [ + DescriptionStatusListingComponent, + DescriptionStatusListingFiltersComponent, + DescriptionStatusEditorComponent, + ] +}) +export class DescriptionStatusModule { } diff --git a/frontend/src/app/ui/admin/description-status/description-status.routing.ts b/frontend/src/app/ui/admin/description-status/description-status.routing.ts new file mode 100644 index 000000000..6fdc14193 --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/description-status.routing.ts @@ -0,0 +1,64 @@ +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 { DescriptionStatusEditorComponent } from './editor/description-status-editor/description-status-editor.component'; +import { DescriptionStatusListingComponent } from './listing/description-status-listing/description-status-listing.component'; +import { DescriptionStatusEditorResolver } from './editor/description-status-editor/description-status-editor.resolver'; + +const routes: Routes = [ + { + path: '', + component: DescriptionStatusListingComponent, + canActivate: [AuthGuard], + data: { + breadcrumb: true, + ...BreadcrumbService.generateRouteDataConfiguration({ + hideNavigationItem: true + }), + } + }, + { + path: 'new', + canActivate: [AuthGuard], + component: DescriptionStatusEditorComponent, + canDeactivate: [PendingChangesGuard], + data: { + authContext: { + permissions: [AppPermission.EditDescriptionStatus] + }, + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.NEW-LANGUAGE' + }), + getFromTitleService: true, + usePrefix: false + } + }, + { + path: ':id', + canActivate: [AuthGuard], + component: DescriptionStatusEditorComponent, + canDeactivate: [PendingChangesGuard], + resolve: { + 'entity': DescriptionStatusEditorResolver + }, + data: { + authContext: { + permissions: [AppPermission.EditDescriptionStatus] + }, + 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: [DescriptionStatusEditorResolver] +}) +export class DescriptionStatusRoutingModule { } diff --git a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.html b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.html new file mode 100644 index 000000000..aef400cf1 --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.html @@ -0,0 +1,109 @@ +
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+ + {{'PLAN-STATUS-EDITOR.FIELDS.NAME' | translate}} + + {{formGroup.controls.name.getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'PLAN-STATUS-EDITOR.FIELDS.INTERNAL-STATUS' | translate}}* + + {{enumUtils.toDescriptionStatusString(internalStatus)}} + + {{formGroup.controls.internalStatus.getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+

{{'PLAN-STATUS-EDITOR.FIELDS.DESCRIPTION' | translate}}

+
+ + @if(formGroup.controls.description.hasError('backendError')){ +
+ {{formGroup.controls.description.getError('backendError').message}} +
+ } +
+
+ +
+

+ {{'PLAN-STATUS-EDITOR.FIELDS.AUTHORIZATION' | translate}} +

+
+ {{'PLAN-STATUS-EDITOR.FIELDS.EDIT' | translate}} +
+ + {{'PLAN-STATUS-EDITOR.FIELDS.ROLES' | translate}}* + + {{enumUtils.toAppRoleString(userRole)}} + + {{"editAuthenticationForm.controls.roles.getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'PLAN-STATUS-EDITOR.FIELDS.PLAN-ROLES' | translate}}* + + {{enumUtils.toPlanUserRoleString(planRole)}} + + {{"editAuthenticationForm.controls.planRoles.getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+
+ {{'PLAN-STATUS-EDITOR.FIELDS.ALLOW-AUTHENTICATED' | translate}} + {{editAuthenticationForm.controls.allowAuthenticated.getError('backendError').message}} +
+
+ {{'PLAN-STATUS-EDITOR.FIELDS.ALLOW-ANONYMOUS' | translate}} + {{editAuthenticationForm.controls.allowAnonymous.getError('backendError').message}} +
+
+
+
+
+
+
+
+
+
+
+ diff --git a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.scss b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.scss new file mode 100644 index 000000000..18cced2fd --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.scss @@ -0,0 +1,33 @@ +::ng-deep label { + margin: 0; +} + +.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; + } +} + +.cancel-btn { + background: #ffffff 0% 0% no-repeat padding-box; + border: 1px solid #b5b5b5; +} + +.delete-btn { + background: #ffffff; + color: #ba2c2c; + border: 1px solid #ba2c2c; +} \ No newline at end of file diff --git a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.ts b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.ts new file mode 100644 index 000000000..ceb7f816e --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.component.ts @@ -0,0 +1,169 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { Router, ActivatedRoute } from '@angular/router'; +import { AppRole } from '@app/core/common/enum/app-role'; +import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { DescriptionStatus } from '@app/core/model/description-status/description-status'; +import { DescriptionStatusPersist } from '@app/core/model/description/description'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { ConfigurationService } from '@app/core/services/configuration/configuration.service'; +import { DescriptionStatusService } from '@app/core/services/description-status/description-status.service'; +import { LockService } from '@app/core/services/lock/lock.service'; +import { LoggingService } from '@app/core/services/logging/logging-service'; +import { AnalyticsService } from '@app/core/services/matomo/analytics-service'; +import { UiNotificationService, SnackBarNotificationLevel } from '@app/core/services/notification/ui-notification-service'; +import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; +import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; +import { QueryParamsService } from '@app/core/services/utilities/query-params.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 { FilterService } from '@common/modules/text-filter/filter-service'; +import { Guid } from '@common/types/guid'; +import { TranslateService } from '@ngx-translate/core'; +import { IsActive } from '@notification-service/core/enum/is-active.enum'; +import { map, takeUntil } from 'rxjs'; +import { DescriptionStatusForm, DescriptionStatusDefinitionAuthorizationItemForm, DescriptionStatusEditorModel } from './description-status-editor.model'; +import { DescriptionStatusEditorResolver } from './description-status-editor.resolver'; +import { BaseEditor } from '@common/base/base-editor'; +import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; + +@Component({ + selector: 'app-description-status-editor', + templateUrl: './description-status-editor.component.html', + styleUrl: './description-status-editor.component.scss' +}) +export class DescriptionStatusEditorComponent extends BaseEditor implements OnInit{ + protected internalStatusEnum = this.enumUtils.getEnumValues(DescriptionStatusEnum); + protected userRolesEnum = this.enumUtils.getEnumValues(AppRole); + protected planRolesEnum = this.enumUtils.getEnumValues(PlanUserRole); + protected belongsToCurrentTenant: boolean; + + constructor( + protected enumUtils: EnumUtils, + 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, + private analyticsService: AnalyticsService, + private descriptionStatusService: DescriptionStatusService, + private logger: LoggingService, + private routerUtils: RouterUtilsService, + ){ + super(dialog, language, formService, router, uiNotificationService, httpErrorHandlingService, filterService, route, queryParamsService, lockService, authService, configurationService); + } + + formGroup: FormGroup; + + ngOnInit(){ + this.analyticsService.trackPageView(AnalyticsService.DescriptionStatusEditor); + super.ngOnInit(); + } + + get editAuthenticationForm(): FormGroup { + return this.formGroup?.controls?.definition?.controls?.authorization?.controls?.edit; + } + + getItem(itemId: Guid, successFunction: (item: DescriptionStatus) => void): void { + this.descriptionStatusService.getSingle(itemId, DescriptionStatusEditorResolver.lookupFields()) + .pipe(map(data => data as DescriptionStatus), takeUntil(this._destroyed)) + .subscribe({ + next: (data) => successFunction(data), + error: (error) => this.onCallbackError(error) + }); + } + + prepareForm(data: DescriptionStatus): void { + try { + this.editorModel = data ? new DescriptionStatusEditorModel().fromModel(data) : new DescriptionStatusEditorModel(); + this.isDeleted = data ? data.isActive === IsActive.Inactive : false; + this.belongsToCurrentTenant = data?.belongsToCurrentTenant; + this.buildForm(); + } catch (error) { + this.logger.error('Could not parse descriptionStatus item: ' + data + error); + this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error); + } + } + + buildForm(): void { + this.formGroup = this.editorModel.buildForm({disabled: !this.belongsToCurrentTenant || this.isDeleted || !this.authService.hasPermission(AppPermission.EditDescriptionStatus)}); + } + + formSubmit(): void { + this.formService.removeAllBackEndErrors(this.formGroup); + this.formService.touchAllFormFields(this.formGroup); + if (!this.isFormValid()) { + return; + } + + this.persistEntity(); + } + + delete(): void { + 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.descriptionStatusService.delete(value.id).pipe(takeUntil(this._destroyed)) + .subscribe({ + complete: () => this.onCallbackDeleteSuccess(), + error: (error) => this.onCallbackError(error) + }); + } + }); + } + } + + refreshData(): void { + this.getItem(this.editorModel.id, (data: DescriptionStatus) => this.prepareForm(data)); + } + + refreshOnNavigateToData(id?: Guid): void { + this.formGroup.markAsPristine(); + if (this.isNew) { + let route = []; + route.push(this.routerUtils.generateUrl('/description-statuses/' + id)); + this.router.navigate(route, { queryParams: { 'lookup': this.queryParamsService.serializeLookup(this.lookupParams), 'lv': ++this.lv }, replaceUrl: true, relativeTo: this.route }); + } else { + this.refreshData(); + } + } + + persistEntity(onSuccess?: (response) => void): void { + const formData = this.formGroup.value as DescriptionStatusPersist; + + this.descriptionStatusService.persist(formData) + .pipe(takeUntil(this._destroyed)).subscribe({ + next: (complete) => onSuccess ? onSuccess(complete) : this.onCallbackSuccess(complete), + error: (error) => this.onCallbackError(error) + }); + } + + protected get canSave(): boolean { + const editDescriptionStatus = this.authService.permissionEnum.EditDescriptionStatus; + return (this.isNew || (this.belongsToCurrentTenant && !this.isDeleted)) && this.authService.hasPermission(editDescriptionStatus); + } + + protected get canDelete(): boolean { + const deletedDescriptionStatus = this.authService.permissionEnum.DeleteDescriptionStatus; + return this.belongsToCurrentTenant && !this.isNew && !this.isDeleted && this.authService.hasPermission(deletedDescriptionStatus); + } +} diff --git a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.model.ts b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.model.ts new file mode 100644 index 000000000..8cf78a08c --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.model.ts @@ -0,0 +1,116 @@ +import { FormControl, FormGroup, Validators } from "@angular/forms"; +import { AppRole } from "@app/core/common/enum/app-role"; +import { DescriptionStatusEnum } from "@app/core/common/enum/description-status"; +import { PlanUserRole } from "@app/core/common/enum/plan-user-role"; +import { DescriptionStatus, DescriptionStatusDefinition, DescriptionStatusDefinitionAuthorizationItem } from "@app/core/model/description-status/description-status"; +import { DescriptionStatusPersist } from "@app/core/model/description-status/description-status-persist"; +import { BaseEditorModel } from "@common/base/base-form-editor-model"; +import { BackendErrorValidator } from "@common/forms/validation/custom-validator"; +import { Validation, ValidationContext } from "@common/forms/validation/validation-context"; +import { Guid } from "@common/types/guid"; + +export class DescriptionStatusEditorModel extends BaseEditorModel implements DescriptionStatusPersist { + name: string; + description: string; + internalStatus: DescriptionStatusEnum; + definition: DescriptionStatusDefinition; + + public fromModel(item: DescriptionStatus): DescriptionStatusEditorModel { + if (item) { + super.fromModel(item); + this.name = item?.name; + this.description = item?.description; + this.internalStatus = item?.internalStatus; + this.definition = item?.definition; + } + return this; + } + + buildForm(params: {context?: ValidationContext, disabled?: boolean}): FormGroup { + const {context = this.createValidationContext(), disabled = false} = params; + + return this.formBuilder.group({ + id: [{ value: this.id, disabled }, context.getValidation('id').validators], + name: [{value: this.name, disabled}, context.getValidation('name').validators], + description: [{value: this.description, disabled}, context.getValidation('description').validators], + internalStatus: [{value: this.internalStatus, disabled}, context.getValidation('internalStatus').validators], + hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators], + definition: this.buildDefinitionForm({context, disabled}), + }); + } + + buildDefinitionForm(params: {context: ValidationContext, disabled: boolean}): FormGroup { + const {context = this.createValidationContext(), disabled} = params; + const definitionForm = new FormGroup({ + authorization: new FormGroup({ + edit: this.buildDefinitionAuthorizationItemForm({item: this.definition?.authorization?.edit, rootPath: 'edit', disabled}) + }) + }); + definitionForm.controls.authorization.addValidators(context.getValidation('authorization').validators); + definitionForm.addValidators(context.getValidation('definition').validators); + return definitionForm; + } + + buildDefinitionAuthorizationItemForm(params: {item: DescriptionStatusDefinitionAuthorizationItem, rootPath: string, disabled: boolean}): FormGroup{ + const {item, rootPath, disabled} = params; + const context = this.createAuthorizationItemContext(rootPath); + return new FormGroup({ + allowAnonymous: new FormControl({value: item?.allowAnonymous ?? false, disabled}, context.getValidation('allowAnonymous').validators), + allowAuthenticated: new FormControl({value: item?.allowAuthenticated ?? false, disabled}, context.getValidation('allowAuthenticated').validators), + roles: new FormControl({value: item?.roles ?? [], disabled}, context.getValidation('roles').validators), + planRoles: new FormControl({value: item?.planRoles ?? [], disabled}, context.getValidation('planRoles').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: 'name', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'name')] }); + baseValidationArray.push({ key: 'description', validators: [BackendErrorValidator(this.validationErrorModel, 'description')] }); + baseValidationArray.push({ key: 'internalStatus', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'internalStatus')] }); + baseValidationArray.push({ key: 'hash', validators: [] }); + baseValidationArray.push({ key: 'definition', validators: [BackendErrorValidator(this.validationErrorModel, 'definition')] }); + baseValidationArray.push({ key: 'authorization', validators: [BackendErrorValidator(this.validationErrorModel, 'definition.authorization')] }); + baseValidationArray.push({ key: 'edit', validators: [BackendErrorValidator(this.validationErrorModel, 'definition.authorization.edit')] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } + + createAuthorizationItemContext(rootPath?: string): ValidationContext { + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'allowAnonymous', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}allowAnonymous`)] }); + baseValidationArray.push({ key: 'allowAuthenticated', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}allowAuthenticated`)] }); + baseValidationArray.push({ key: 'planRoles', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}planRoles`)] }); + baseValidationArray.push({ key: 'roles', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, `${rootPath}roles`)] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } + +} + +export interface DescriptionStatusForm { + id: FormControl; + name: FormControl; + description: FormControl; + internalStatus: FormControl; + definition: FormGroup; +} + +export interface DescriptionStatusDefinitionForm { + authorization: FormGroup +} + +export interface DescriptionStatusDefinitionAuthorizationForm { + edit: FormGroup; +} + +export interface DescriptionStatusDefinitionAuthorizationItemForm { + roles: FormControl; + planRoles: FormControl; + allowAuthenticated: FormControl; + allowAnonymous: FormControl; +} \ No newline at end of file diff --git a/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.resolver.ts b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.resolver.ts new file mode 100644 index 000000000..d66a4be28 --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/editor/description-status-editor/description-status-editor.resolver.ts @@ -0,0 +1,49 @@ +import { Injectable } from "@angular/core"; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; +import { DescriptionStatus, DescriptionStatusDefinition, DescriptionStatusDefinitionAuthorization, DescriptionStatusDefinitionAuthorizationItem } from "@app/core/model/description-status/description-status"; +import { DescriptionStatusService } from "@app/core/services/description-status/description-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"; +import { nameof } from "ts-simple-nameof"; + +@Injectable() +export class DescriptionStatusEditorResolver extends BaseEditorResolver{ + + constructor(private descriptionStatusService: DescriptionStatusService, private breadcrumbService: BreadcrumbService) { + super(); + } + + public static lookupFields(): string[] { + return [ + nameof(x => x.id), + nameof(x => x.name), + nameof(x => x.description), + nameof(x => x.internalStatus), + nameof(x => x.definition), + [nameof(x => x.definition), nameof(x => x.authorization)].join('.'), + [nameof(x => x.definition), nameof(x => x.authorization), nameof(x => x.edit), nameof(x => x.roles)].join('.'), + [nameof(x => x.definition), nameof(x => x.authorization), nameof(x => x.edit), nameof(x => x.planRoles)].join('.'), + [nameof(x => x.definition), nameof(x => x.authorization), nameof(x => x.edit), nameof(x => x.allowAnonymous)].join('.'), + [nameof(x => x.definition), nameof(x => x.authorization), nameof(x => x.edit), nameof(x => x.allowAuthenticated)].join('.'), + + nameof(x => x.updatedAt), + nameof(x => x.createdAt), + nameof(x => x.hash), + nameof(x => x.isActive), + nameof(x => x.belongsToCurrentTenant), + ] + } + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + const fields = [ + ...DescriptionStatusEditorResolver.lookupFields() + ]; + const id = route.paramMap.get('id'); + if (id != null) { + return this.descriptionStatusService.getSingle(Guid.parse(id), fields).pipe(tap(x => this.breadcrumbService.addIdResolvedValue(x.id?.toString(), x.name)), takeUntil(this._destroyed)); + } + } + +} \ No newline at end of file diff --git a/frontend/src/app/ui/admin/description-status/listing/description-status-listing-filters/description-status-listing-filters.component.html b/frontend/src/app/ui/admin/description-status/listing/description-status-listing-filters/description-status-listing-filters.component.html new file mode 100644 index 000000000..112f73933 --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/listing/description-status-listing-filters/description-status-listing-filters.component.html @@ -0,0 +1,48 @@ +
+ + + + + +
+
+
+

{{'PLAN-BLUEPRINT-LISTING.FILTER.TITLE' | translate}}

+
+
+ +
+
+ +
+
+
+ + {{'PLAN-BLUEPRINT-LISTING.FILTER.IS-ACTIVE' | translate}} +
+
+
+ + +
+
+ +
+
+ +
+
+
+
+ + +
\ No newline at end of file diff --git a/frontend/src/app/ui/admin/description-status/listing/description-status-listing-filters/description-status-listing-filters.component.scss b/frontend/src/app/ui/admin/description-status/listing/description-status-listing-filters/description-status-listing-filters.component.scss new file mode 100644 index 000000000..33e94c8b1 --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/listing/description-status-listing-filters/description-status-listing-filters.component.scss @@ -0,0 +1,22 @@ +//Maybe move these to global listing filter styles? + +::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; +} + +::ng-deep .mdc-form-field { + label { + margin: 0; + } +} diff --git a/frontend/src/app/ui/admin/description-status/listing/description-status-listing-filters/description-status-listing-filters.component.ts b/frontend/src/app/ui/admin/description-status/listing/description-status-listing-filters/description-status-listing-filters.component.ts new file mode 100644 index 000000000..1ea84d4ca --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/listing/description-status-listing-filters/description-status-listing-filters.component.ts @@ -0,0 +1,88 @@ +import { Component, effect, EventEmitter, input, Output } from '@angular/core'; +import { DescriptionStatusFilter } from '@app/core/query/description-status.lookup'; +import { BaseComponent } from '@common/base/base.component'; +import { IsActive } from '@notification-service/core/enum/is-active.enum'; + +@Component({ + selector: 'app-description-status-listing-filters', + templateUrl: './description-status-listing-filters.component.html', + styleUrl: './description-status-listing-filters.component.scss' +}) +export class DescriptionStatusListingFiltersComponent extends BaseComponent{ + readonly filter = input(); + @Output() filterChange = new EventEmitter(); + + internalFilters: DescriptionStatusListingFilters = this._getEmptyFilters(); + appliedFilterCount: number = 0; + + constructor(){ + super(); + effect(() => { + const newFilters = this.filter(); + if(newFilters){ + this.updateFilters(); + } + }) + } + + + private _parseToInternalFilters(inputFilter: DescriptionStatusFilter): DescriptionStatusListingFilters { + if (!inputFilter) { + return this._getEmptyFilters(); + } + + let { isActive, like } = inputFilter; + + return { + isActive: (isActive ?? [])?.includes(IsActive.Active) || !isActive?.length, + like: like, + } + + } + + private _computeAppliedFilters(filters: DescriptionStatusListingFilters): number { + let count = 0; + if (!filters?.isActive) { + count++ + } + if(filters?.like){ + count++; + } + return count; + } + + private _getEmptyFilters(): DescriptionStatusListingFilters { + return { + isActive: true, + like: null, + } + } + + protected updateFilters(): void { + this.internalFilters = this._parseToInternalFilters(this.filter()); + this.appliedFilterCount = this._computeAppliedFilters(this.internalFilters); + } + + protected applyFilters(): void { + const { isActive, like } = this.internalFilters ?? {} + this.filterChange.emit({ + ...this.filter(), + like, + isActive: isActive ? [IsActive.Active] : [IsActive.Inactive] + }) + } + + protected onSearchTermChange(searchTerm: string): void { + this.applyFilters(); + } + + + protected clearFilters() { + this.internalFilters = this._getEmptyFilters(); + } +} + +interface DescriptionStatusListingFilters { + isActive: boolean; + like: string; +} diff --git a/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.html b/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.html new file mode 100644 index 000000000..4ff299e29 --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.html @@ -0,0 +1,67 @@ +
+
+
+
+
+ +
+ +
+ +
+
+ + + + + + + +
+
+
+ + +
+ {{enumUtils.toDescriptionStatusString(row.internalStatus)}} +
+
+ +
+
+ + + + + +
+
+
diff --git a/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.scss b/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.scss new file mode 100644 index 000000000..d243eaa7c --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.scss @@ -0,0 +1,34 @@ +.create-btn { + border-radius: 30px; + background-color: var(--secondary-color); + padding-left: 2em; + padding-right: 2em; + + .button-text { + display: inline-block; + } +} + +.status-chip { + width: fit-content; + 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; +} + +.status-chip-cancelled { + color: #cf1407; + background: #ffc8c5 0% 0% no-repeat padding-box; +} \ No newline at end of file diff --git a/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.ts b/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.ts new file mode 100644 index 000000000..976a7f0ef --- /dev/null +++ b/frontend/src/app/ui/admin/description-status/listing/description-status-listing/description-status-listing.component.ts @@ -0,0 +1,190 @@ +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { Router, ActivatedRoute } from '@angular/router'; +import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; +import { DescriptionStatus } from '@app/core/model/description-status/description-status'; +import { UserSettingsKey } from '@app/core/model/user-settings/user-settings.model'; +import { DataTableDateTimeFormatPipe } from '@app/core/pipes/date-time-format.pipe'; +import { DescriptionStatusLookup } from '@app/core/query/description-status.lookup'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { DescriptionStatusService } from '@app/core/services/description-status/description-status.service'; +import { AnalyticsService } from '@app/core/services/matomo/analytics-service'; +import { UiNotificationService, SnackBarNotificationLevel } from '@app/core/services/notification/ui-notification-service'; +import { RouterUtilsService } from '@app/core/services/router/router-utils.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 { IsActiveTypePipe } from '@common/formatting/pipes/is-active-type.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, HybridListingComponent, ColumnsChangedEvent, PageLoadEvent } from '@common/modules/hybrid-listing/hybrid-listing.component'; +import { TranslateService } from '@ngx-translate/core'; +import { IsActive } from '@notification-service/core/enum/is-active.enum'; +import { Observable, takeUntil } from 'rxjs'; +import { nameof } from 'ts-simple-nameof'; +import { DescriptionStatusEditorResolver } from '../../editor/description-status-editor/description-status-editor.resolver'; + +@Component({ + selector: 'app-description-status-listing', + templateUrl: './description-status-listing.component.html', + styleUrl: './description-status-listing.component.scss' +}) +export class DescriptionStatusListingComponent extends BaseListingComponent implements OnInit{ + userSettingsKey: UserSettingsKey = {key: 'DescriptionStatusListingUserSettings'}; + + publish = false; + propertiesAvailableForOrder: ColumnDefinition[]; + + DescriptionStatusEnum = DescriptionStatusEnum; + + @ViewChild('actions', { static: true }) actions: TemplateRef; + @ViewChild('status', { static: true }) status: TemplateRef; + @ViewChild(HybridListingComponent, { static: true }) hybridListingComponent: HybridListingComponent; + + constructor( + public routerUtils: RouterUtilsService, + public authService: AuthService, + protected router: Router, + protected route: ActivatedRoute, + protected uiNotificationService: UiNotificationService, + protected httpErrorHandlingService: HttpErrorHandlingService, + protected queryParamsService: QueryParamsService, + protected enumUtils: EnumUtils, + private pipeService: PipeService, + private descriptionStatusService: DescriptionStatusService, + private analyticsService: AnalyticsService, + private language: TranslateService, + private dialog: MatDialog + ){ + super(router, route, uiNotificationService, httpErrorHandlingService, queryParamsService); + this.lookup = this.initializeLookup(); + } + + ngOnInit() { + super.ngOnInit(); + this.analyticsService.trackPageView(AnalyticsService.DescriptionStatusListing); + } + + private readonly lookupFields = DescriptionStatusEditorResolver.lookupFields(); + + protected initializeLookup(): DescriptionStatusLookup { + const lookup = new DescriptionStatusLookup(); + 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; + } + + onColumnsChanged(event: ColumnsChangedEvent) { + super.onColumnsChanged(event); + this.onColumnsChangedInternal(event.properties.map(x => x.toString())); + } + + private onColumnsChangedInternal(columns: string[]) { + 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.descriptionStatusService.query(this.lookup); + } + + protected setupColumns() { + this.gridColumns.push(...[ + { + prop: nameof(x => x.name), + sortable: true, + languageName: 'PLAN-STATUS-LISTING.FIELDS.NAME' + }, + { + prop: nameof(x => x.description), + languageName: 'PLAN-STATUS-LISTING.FIELDS.DESCRIPTION' + }, + { + prop: nameof(x => x.internalStatus), + sortable: true, + languageName: 'PLAN-STATUS-LISTING.FIELDS.STATUS', + cellTemplate: this.status + }, + { + prop: nameof(x => x.createdAt), + sortable: true, + languageName: 'PLAN-STATUS-LISTING.FIELDS.CREATED-AT', + pipe: this.pipeService.getPipe(DataTableDateTimeFormatPipe).withFormat('short') + }, + { + prop: nameof(x => x.updatedAt), + sortable: true, + languageName: 'PLAN-STATUS-LISTING.FIELDS.UPDATED-AT', + pipe: this.pipeService.getPipe(DataTableDateTimeFormatPipe).withFormat('short') + }, + { + prop: nameof(x => x.isActive), + sortable: true, + languageName: 'PLAN-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); + } + + delete(id){ + 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.descriptionStatusService.delete(id).pipe(takeUntil(this._destroyed)) + .subscribe({ + complete: () => this.onCallbackSuccess(), + error: (error) => this.onCallbackError(error) + } + ); + } + }); + } + } + + onCallbackSuccess(): void { + this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-DELETE'), SnackBarNotificationLevel.Success); + this.refresh(); + } + + isDeleted(row: DescriptionStatus): boolean { + return row?.isActive === IsActive.Inactive; + } + + get canEdit(): boolean { + return this.authService.hasPermission(this.authService.permissionEnum.EditDescriptionStatus); + } + + get canDelete(): boolean { + return this.authService.hasPermission(this.authService.permissionEnum.DeleteDescriptionStatus); + } +} diff --git a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.html b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.html index a1795830d..fad8ac93a 100644 --- a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.html +++ b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.html @@ -8,18 +8,18 @@
-
- -
-
- -
+
+ +
+
+ +
diff --git a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.ts b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.ts index 47aa0938e..d752c0e31 100644 --- a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.ts +++ b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.ts @@ -40,6 +40,7 @@ export class PlanStatusEditorComponent extends BaseEditor(PlanStatusEnum); protected userRolesEnum = this.enumUtils.getEnumValues(AppRole); protected planRolesEnum = this.enumUtils.getEnumValues(PlanUserRole); + protected belongsToCurrentTenant: boolean; constructor( protected enumUtils: EnumUtils, @@ -87,6 +88,7 @@ export class PlanStatusEditorComponent extends BaseEditor -
+
{{enumUtils.toPlanStatusString(row.internalStatus)}}
-
+
- - diff --git a/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.ts b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.ts index d62a07fc6..7c8328400 100644 --- a/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.ts +++ b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.ts @@ -175,4 +175,16 @@ export class PlanStatusListingComponent extends BaseListingComponent x.isActive === IsActive.Active && x.status === DescriptionStatus.Finalized); + item.plan.descriptions = item.plan.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatusEnum.Finalized); } else { - item.plan.descriptions = item.plan.descriptions.filter(x => x.isActive === IsActive.Active && x.status != DescriptionStatus.Canceled); + item.plan.descriptions = item.plan.descriptions.filter(x => x.isActive === IsActive.Active && x.status != DescriptionStatusEnum.Canceled); } } item.plan.planUsers = item.plan.planUsers.filter(x=> x.isActive === IsActive.Active); this.listingItems.push(item); } if (item.description){ - if (item.description.status != DescriptionStatus.Canceled) this.listingItems.push(item); + if (item.description.status != DescriptionStatusEnum.Canceled) this.listingItems.push(item); } }) diff --git a/frontend/src/app/ui/description/editor/description-base-fields-editor/description-base-fields-editor.component.ts b/frontend/src/app/ui/description/editor/description-base-fields-editor/description-base-fields-editor.component.ts index 5f87ea8fe..12f7fbd9d 100644 --- a/frontend/src/app/ui/description/editor/description-base-fields-editor/description-base-fields-editor.component.ts +++ b/frontend/src/app/ui/description/editor/description-base-fields-editor/description-base-fields-editor.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; -import { DescriptionStatus } from '@app/core/common/enum/description-status'; +import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; import { DescriptionTemplateVersionStatus } from '@app/core/common/enum/description-template-version-status'; import { IsActive } from '@app/core/common/enum/is-active.enum'; import { DescriptionTemplate } from '@app/core/model/description-template/description-template'; @@ -46,7 +46,7 @@ export class DescriptionBaseFieldsEditorComponent extends BaseComponent { if (this.description?.descriptionTemplate != null) { const isPreviousVersion: boolean = this.description.descriptionTemplate.versionStatus === DescriptionTemplateVersionStatus.Previous; if (isPreviousVersion === true) { - if (this.description.status === DescriptionStatus.Draft) { + if (this.description.status === DescriptionStatusEnum.Draft) { this.openDeprecatedDescriptionTemplateDialog(); } else { this.availableDescriptionTemplates.push(this.description.descriptionTemplate); diff --git a/frontend/src/app/ui/description/editor/description-editor.component.ts b/frontend/src/app/ui/description/editor/description-editor.component.ts index 67acd6736..e7ceefefb 100644 --- a/frontend/src/app/ui/description/editor/description-editor.component.ts +++ b/frontend/src/app/ui/description/editor/description-editor.component.ts @@ -3,7 +3,7 @@ import { AbstractControl, UntypedFormArray, UntypedFormGroup } from '@angular/fo import { MatDialog } from '@angular/material/dialog'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { DescriptionStatus } from '@app/core/common/enum/description-status'; +import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; import { FileTransformerEntityType } from '@app/core/common/enum/file-transformer-entity-type'; import { IsActive } from '@app/core/common/enum/is-active.enum'; import { LockTargetType } from '@app/core/common/enum/lock-target-type'; @@ -245,9 +245,9 @@ export class DescriptionEditorComponent extends BaseEditor { onSuccess ? onSuccess(complete) : this.onCallbackSuccess(complete); this.descriptionIsOnceSaved = true; - if (this.formGroup.get('status').value == DescriptionStatus.Finalized) this.isFinalized = true; + if (this.formGroup.get('status').value == DescriptionStatusEnum.Finalized) this.isFinalized = true; }, error => { - if (this.formGroup.get('status').value == DescriptionStatus.Finalized) { - this.formGroup.get('status').setValue(DescriptionStatus.Draft); + if (this.formGroup.get('status').value == DescriptionStatusEnum.Finalized) { + this.formGroup.get('status').setValue(DescriptionStatusEnum.Draft); this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.UNSUCCESSFUL-FINALIZE'), SnackBarNotificationLevel.Error); } else { this.onCallbackError(error); @@ -727,7 +727,7 @@ export class DescriptionEditorComponent extends BaseEditor { if (result) { - this.formGroup.get('status').setValue(DescriptionStatus.Finalized); + this.formGroup.get('status').setValue(DescriptionStatusEnum.Finalized); this.persistEntity(); } }); @@ -747,7 +747,7 @@ export class DescriptionEditorComponent extends BaseEditor x.isActive === IsActive.Active).map(x => x.tag?.label); this.properties = new DescriptionPropertyDefinitionEditorModel(this.validationErrorModel).fromModel(item.properties, descriptionTemplate, item.descriptionReferences); diff --git a/frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts b/frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts index d5e32f0bb..b956f2668 100644 --- a/frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts +++ b/frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; -import { DescriptionStatus } from '@app/core/common/enum/description-status'; +import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; import { AppPermission } from '@app/core/common/enum/permission.enum'; import { DescriptionTemplate } from '@app/core/model/description-template/description-template'; import { Description, DescriptionExternalIdentifier, DescriptionField, DescriptionPropertyDefinition, DescriptionPropertyDefinitionFieldSet, DescriptionPropertyDefinitionFieldSetItem, DescriptionReference, DescriptionReferenceData, DescriptionTag } from '@app/core/model/description/description'; @@ -200,7 +200,7 @@ export class DescriptionEditorEntityResolver extends BaseEditorResolver { description.id = null; description.hash = null; - description.status = DescriptionStatus.Draft; + description.status = DescriptionStatusEnum.Draft; description.plan = plan; description.planDescriptionTemplate = { id: plan.planDescriptionTemplates.filter(x => x.sectionId == Guid.parse(planSectionId) && x.descriptionTemplateGroupId == description.descriptionTemplate.groupId)[0].id, diff --git a/frontend/src/app/ui/description/listing/filtering/description-filter.component.ts b/frontend/src/app/ui/description/listing/filtering/description-filter.component.ts index e2556eb47..990606715 100644 --- a/frontend/src/app/ui/description/listing/filtering/description-filter.component.ts +++ b/frontend/src/app/ui/description/listing/filtering/description-filter.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { AbstractControl, FormBuilder, UntypedFormArray, UntypedFormGroup } from '@angular/forms'; -import { DescriptionStatus } from '@app/core/common/enum/description-status'; +import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; import { ReferenceType } from '@app/core/model/reference-type/reference-type'; import { Reference } from '@app/core/model/reference/reference'; @@ -41,7 +41,7 @@ export class DescriptionFilterComponent extends BaseCriteriaComponent implements public criteria: any; public filteringTagsAsync = false; - statuses = DescriptionStatus; + statuses = DescriptionStatusEnum; planRole = PlanUserRole; options: UntypedFormGroup; diff --git a/frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.ts b/frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.ts index 3996e781c..d3ff4f5e6 100644 --- a/frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.ts +++ b/frontend/src/app/ui/description/listing/listing-item/description-listing-item.component.ts @@ -22,7 +22,7 @@ import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog import { Guid } from '@common/types/guid'; import { TranslateService } from '@ngx-translate/core'; import { takeUntil } from 'rxjs/operators'; -import { DescriptionStatus } from '../../../../core/common/enum/description-status'; +import { DescriptionStatusEnum } from '../../../../core/common/enum/description-status'; import { DescriptionCopyDialogComponent } from '../../description-copy-dialog/description-copy-dialog.component'; import { AppPermission } from '@app/core/common/enum/permission.enum'; import { FileTransformerEntityType } from '@app/core/common/enum/file-transformer-entity-type'; @@ -47,7 +47,7 @@ export class DescriptionListingItemComponent extends BaseComponent implements On isDraft: boolean; isDeleted: boolean; isUserOwner: boolean; - descriptionStatusEnum = DescriptionStatus; + descriptionStatusEnum = DescriptionStatusEnum; fileTransformerEntityTypeEnum = FileTransformerEntityType; planAccessTypeEnum = PlanAccessType; canDelete: boolean = false; @@ -82,7 +82,7 @@ export class DescriptionListingItemComponent extends BaseComponent implements On this.analyticsService.trackPageView(AnalyticsService.DescriptionListingItem); if (this.description.isActive === IsActive.Inactive) { this.isDeleted = true; - } else if (this.description.status === DescriptionStatus.Draft) { + } else if (this.description.status === DescriptionStatusEnum.Draft) { this.isDraft = true; this.isDeleted = false; } else { diff --git a/frontend/src/app/ui/description/overview/description-overview.component.ts b/frontend/src/app/ui/description/overview/description-overview.component.ts index 0a1f34120..1e3e01e50 100644 --- a/frontend/src/app/ui/description/overview/description-overview.component.ts +++ b/frontend/src/app/ui/description/overview/description-overview.component.ts @@ -3,7 +3,7 @@ import { Component, OnInit } from '@angular/core'; import { UntypedFormBuilder, Validators } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { DescriptionStatus } from '@app/core/common/enum/description-status'; +import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; import { PlanAccessType } from '@app/core/common/enum/plan-access-type'; import { PlanStatusEnum } from '@app/core/common/enum/plan-status'; import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; @@ -61,7 +61,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni hasPublishButton: boolean = true; expand = false; isLocked: Boolean; - descriptionStatusEnum = DescriptionStatus; + descriptionStatusEnum = DescriptionStatusEnum; planAccessTypeEnum = PlanAccessType; planStatusEnum = PlanStatusEnum; planUserRoleEnum = PlanUserRole; @@ -286,7 +286,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni } isDraftDescription(description: Description) { - return description.status == DescriptionStatus.Draft; + return description.status == DescriptionStatusEnum.Draft; } editClicked(description: Description) { @@ -443,7 +443,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni if (result) { const descriptionStatusPersist: DescriptionStatusPersist = { id: description.id, - status: DescriptionStatus.Finalized, + status: DescriptionStatusEnum.Finalized, hash: description.hash }; this.descriptionService.persistStatus(descriptionStatusPersist).pipe(takeUntil(this._destroyed)) @@ -467,7 +467,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni } hasReversableStatus(description: Description): boolean { - return description.plan.status == PlanStatusEnum.Draft && description.status == DescriptionStatus.Finalized && this.canFinalize + return description.plan.status == PlanStatusEnum.Draft && description.status == DescriptionStatusEnum.Finalized && this.canFinalize } reverseFinalization(description: Description) { @@ -484,7 +484,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni if (result) { const planUserRemovePersist: DescriptionStatusPersist = { id: description.id, - status: DescriptionStatus.Draft, + status: DescriptionStatusEnum.Draft, hash: description.hash }; this.descriptionService.persistStatus(planUserRemovePersist).pipe(takeUntil(this._destroyed)) diff --git a/frontend/src/app/ui/plan/overview/plan-overview.component.ts b/frontend/src/app/ui/plan/overview/plan-overview.component.ts index 2d70e2c39..9d7cb8fab 100644 --- a/frontend/src/app/ui/plan/overview/plan-overview.component.ts +++ b/frontend/src/app/ui/plan/overview/plan-overview.component.ts @@ -2,7 +2,7 @@ import { Location } from '@angular/common'; import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { DescriptionStatus } from '@app/core/common/enum/description-status'; +import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; import { PlanAccessType } from '@app/core/common/enum/plan-access-type'; import { PlanStatusEnum } from '@app/core/common/enum/plan-status'; import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; @@ -75,7 +75,7 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { depositRepos: DepositConfiguration[] = []; - descriptionStatusEnum = DescriptionStatus; + descriptionStatusEnum = DescriptionStatusEnum; planAccessTypeEnum = PlanAccessType; planStatusEnum = PlanStatusEnum; planUserRoleEnum = PlanUserRole; @@ -133,9 +133,9 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { this.plan.otherPlanVersions = data.otherPlanVersions?.filter(x => x.isActive === IsActive.Active) || null; if (this.plan.descriptions) { if (this.plan.status == PlanStatusEnum.Finalized) { - this.plan.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatus.Finalized); + this.plan.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatusEnum.Finalized); } else { - this.plan.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status !== DescriptionStatus.Canceled); + this.plan.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status !== DescriptionStatusEnum.Canceled); } } if (data.entityDois && data.entityDois.length > 0) this.plan.entityDois = data.entityDois.filter(x => x.isActive === IsActive.Active); diff --git a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.ts b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.ts index 74f058a3a..036114b81 100644 --- a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.ts +++ b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.ts @@ -5,7 +5,7 @@ import { MatDialog } from '@angular/material/dialog'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { AnnotationEntityType } from '@app/core/common/enum/annotation-entity-type'; -import { DescriptionStatus } from '@app/core/common/enum/description-status'; +import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; import { FileTransformerEntityType } from '@app/core/common/enum/file-transformer-entity-type'; import { IsActive } from '@app/core/common/enum/is-active.enum'; import { LockTargetType } from '@app/core/common/enum/lock-target-type'; @@ -78,7 +78,7 @@ export class PlanEditorComponent extends BaseEditor imple annotationsPerAnchor: Map = new Map(); //Enums - descriptionStatusEnum = DescriptionStatus; + descriptionStatusEnum = DescriptionStatusEnum; planBlueprintSectionFieldCategoryEnum = PlanBlueprintFieldCategory; planBlueprintSystemFieldTypeEnum = PlanBlueprintSystemFieldType; planBlueprintExtraFieldDataTypeEnum = PlanBlueprintExtraFieldDataType; @@ -262,9 +262,9 @@ export class PlanEditorComponent extends BaseEditor imple if (data) { if (data.descriptions) { if (data.status == PlanStatusEnum.Finalized) { - data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatus.Finalized); + data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatusEnum.Finalized); } else { - data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status !== DescriptionStatus.Canceled); + data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status !== DescriptionStatusEnum.Canceled); } } if (data.planDescriptionTemplates) { diff --git a/frontend/src/app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component.ts b/frontend/src/app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component.ts index 0e3817307..e9de2c915 100644 --- a/frontend/src/app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component.ts +++ b/frontend/src/app/ui/plan/plan-finalize-dialog/plan-finalize-dialog.component.ts @@ -1,7 +1,7 @@ import { Component, Inject, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Router } from '@angular/router'; -import { DescriptionStatus } from '@app/core/common/enum/description-status'; +import { DescriptionStatusEnum } from '@app/core/common/enum/description-status'; import { PlanAccessType } from '@app/core/common/enum/plan-access-type'; import { Plan } from '@app/core/model/plan/plan'; import { DescriptionService } from '@app/core/services/description/description.service'; @@ -23,7 +23,7 @@ export class PlanFinalizeDialogComponent extends BaseComponent implements OnInit isPlanValid: boolean; planErrors: string[]; planAccessTypeEnum = PlanAccessType; - descriptionStatusEnum = DescriptionStatus; + descriptionStatusEnum = DescriptionStatusEnum; descriptionValidationOutputEnum = DescriptionValidationOutput; validationResults: DescriptionValidationResult[] = []; descriptionsToBeFinalized: Guid[] = []; @@ -74,7 +74,7 @@ export class PlanFinalizeDialogComponent extends BaseComponent implements OnInit getFinalizedDescriptions() { if (!this.plan.descriptions) return []; - const finalizedDescriptions = this.plan.descriptions.filter(x => x.status === DescriptionStatus.Finalized); + const finalizedDescriptions = this.plan.descriptions.filter(x => x.status === DescriptionStatusEnum.Finalized); if (finalizedDescriptions?.length > 0){ finalizedDescriptions.forEach(finalize => { this.descriptionValidationOutputMap.set(finalize.id, DescriptionValidationOutput.Valid); @@ -88,16 +88,16 @@ export class PlanFinalizeDialogComponent extends BaseComponent implements OnInit } validateDescriptions(plan: Plan) { - if (!plan.descriptions?.some(x => x.status == DescriptionStatus.Draft)) return; + if (!plan.descriptions?.some(x => x.status == DescriptionStatusEnum.Draft)) return; - const draftDescriptions = this.plan.descriptions.filter(x => x.status == DescriptionStatus.Draft) || []; + const draftDescriptions = this.plan.descriptions.filter(x => x.status == DescriptionStatusEnum.Draft) || []; if ( draftDescriptions.length > 0){ draftDescriptions.forEach(draft => { this.descriptionValidationOutputMap.set(draft.id, DescriptionValidationOutput.Pending); }); } - this.descriptionService.validate(plan.descriptions.filter(x => x.status == DescriptionStatus.Draft).map(x => x.id)).pipe(takeUntil(this._destroyed),) + this.descriptionService.validate(plan.descriptions.filter(x => x.status == DescriptionStatusEnum.Draft).map(x => x.id)).pipe(takeUntil(this._destroyed),) .subscribe(result => { this.validationResults = result; }, diff --git a/frontend/src/app/ui/sidebar/sidebar.component.ts b/frontend/src/app/ui/sidebar/sidebar.component.ts index c7f847c85..ec1cb7dd8 100644 --- a/frontend/src/app/ui/sidebar/sidebar.component.ts +++ b/frontend/src/app/ui/sidebar/sidebar.component.ts @@ -114,8 +114,10 @@ export class SidebarComponent implements OnInit { routes: [], } if (this.authentication.hasPermission(AppPermission.ViewPlanBlueprintPage)) this.adminItems.routes.push({ path: '/plan-blueprints', title: 'SIDE-BAR.PLAN-BLUEPRINTS', icon: 'library_books', routeType: RouteType.System }); + if (this.authentication.hasPermission(AppPermission.ViewPlanStatusPage)) this.adminItems.routes.push({ path: '/plan-statuses', title: 'SIDE-BAR.PLAN-STATUSES', icon: 'library_books', routeType: RouteType.System }); if (this.authentication.hasPermission(AppPermission.ViewDescriptionTemplatePage)) this.adminItems.routes.push({ path: '/description-templates', title: 'SIDE-BAR.DESCRIPTION-TEMPLATES', icon: 'description', routeType: RouteType.System }); if (this.authentication.hasPermission(AppPermission.ViewDescriptionTemplateTypePage)) this.adminItems.routes.push({ path: '/description-template-type', title: 'SIDE-BAR.DESCRIPTION-TEMPLATE-TYPES', icon: 'stack', routeType: RouteType.System }); + if (this.authentication.hasPermission(AppPermission.ViewDescriptionStatusPage)) this.adminItems.routes.push({ path: '/description-statuses', title: 'SIDE-BAR.DESCRIPTION-STATUSES', icon: 'description', routeType: RouteType.System }); if (this.authentication.hasPermission(AppPermission.ViewEntityLockPage)) this.adminItems.routes.push({ path: '/entity-locks', title: 'SIDE-BAR.ENTITY-LOCKS', icon: 'lock_person', routeType: RouteType.System }); if (this.authentication.hasPermission(AppPermission.ViewReferencePage)) this.adminItems.routes.push({ path: '/references', title: 'SIDE-BAR.REFERENCES', icon: 'dataset_linked', routeType: RouteType.System }); if (this.authentication.hasPermission(AppPermission.ViewReferenceTypePage)) this.adminItems.routes.push({ path: '/reference-type', title: 'SIDE-BAR.REFERENCE-TYPES', icon: 'add_link', routeType: RouteType.System }); diff --git a/frontend/src/assets/i18n/baq.json b/frontend/src/assets/i18n/baq.json index a8d148e8a..af0ae79f8 100644 --- a/frontend/src/assets/i18n/baq.json +++ b/frontend/src/assets/i18n/baq.json @@ -180,7 +180,8 @@ "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", "USAGE-LIMITS": "Usage Limits", - "PLAN-STATUSES": "Plan Statuses" + "PLAN-STATUSES": "Plan Statuses", + "DESCRIPTION-STATUSES": "Description Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -333,7 +334,9 @@ "TENANT-CONFIGURATION": "Tenant Configuration", "ENTITY-LOCKS": "Entity Locks", "ANNOTATION-STATUSES": "Annotation Statuses", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "DESCRIPTION-STATUSES": "Description Statuses", + "PLAN-STATUSES": "Plan Statuses" }, "DESCRIPTION-TEMPLATE-PREVIEW": { "TEMPLATE": "Template" diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index 9095e01f3..ed5cdf47c 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -180,7 +180,8 @@ "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", "USAGE-LIMITS": "Usage Limits", - "PLAN-STATUSES": "Plan Statuses" + "PLAN-STATUSES": "Plan Statuses", + "DESCRIPTION-STATUSES": "Description Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -333,7 +334,9 @@ "TENANT-CONFIGURATION": "Tenant Configuration", "ENTITY-LOCKS": "Entity Locks", "ANNOTATION-STATUSES": "Annotation Statuses", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "DESCRIPTION-STATUSES": "Description Statuses", + "PLAN-STATUSES": "Plan Statuses" }, "DESCRIPTION-TEMPLATE-PREVIEW": { "TEMPLATE": "Template" diff --git a/frontend/src/assets/i18n/en.json b/frontend/src/assets/i18n/en.json index 6faeced0a..1febe7256 100644 --- a/frontend/src/assets/i18n/en.json +++ b/frontend/src/assets/i18n/en.json @@ -180,7 +180,8 @@ "ANNOTATION-STATUSES":"Annotation Statuses", "SUPPORTIVE-MATERIAL":"Supportive Material", "USAGE-LIMITS": "Usage Limits", - "PLAN-STATUSES": "Plan Statuses" + "PLAN-STATUSES": "Plan Statuses", + "DESCRIPTION-STATUSES": "Description Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -315,7 +316,9 @@ "ADMIN": "ADMIN", "DESCRIPTION-TEMPLATES": "Description Templates", "DESCRIPTION-TEMPLATE-TYPES": "Description Types", + "DESCRIPTION-STATUSES": "Description Statuses", "PLAN-BLUEPRINTS": "Plan Blueprints", + "PLAN-STATUSES": "Plan Statuses", "USERS": "Users", "TENANT-USERS": "Tenant Users", "CO-BRANDING": "Co-Branding", diff --git a/frontend/src/assets/i18n/es.json b/frontend/src/assets/i18n/es.json index f91e542cd..dfa877d28 100644 --- a/frontend/src/assets/i18n/es.json +++ b/frontend/src/assets/i18n/es.json @@ -180,7 +180,8 @@ "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", "USAGE-LIMITS": "Usage Limits", - "PLAN-STATUSES": "Plan Statuses" + "PLAN-STATUSES": "Plan Statuses", + "DESCRIPTION-STATUSES": "Description Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -333,7 +334,9 @@ "TENANT-CONFIGURATION": "Tenant Configuration", "ENTITY-LOCKS": "Entity Locks", "ANNOTATION-STATUSES": "Annotation Statuses", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "DESCRIPTION-STATUSES": "Description Statuses", + "PLAN-STATUSES": "Plan Statuses" }, "DESCRIPTION-TEMPLATE-PREVIEW": { "TEMPLATE": "Template" diff --git a/frontend/src/assets/i18n/gr.json b/frontend/src/assets/i18n/gr.json index 0cfa2bb68..5c53c7c40 100644 --- a/frontend/src/assets/i18n/gr.json +++ b/frontend/src/assets/i18n/gr.json @@ -180,7 +180,8 @@ "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", "USAGE-LIMITS": "Usage Limits", - "PLAN-STATUSES": "Plan Statuses" + "PLAN-STATUSES": "Plan Statuses", + "DESCRIPTION-STATUSES": "Description Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -333,7 +334,9 @@ "TENANT-CONFIGURATION": "Tenant Configuration", "ENTITY-LOCKS": "Entity Locks", "ANNOTATION-STATUSES": "Annotation Statuses", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "DESCRIPTION-STATUSES": "Description Statuses", + "PLAN-STATUSES": "Plan Statuses" }, "DESCRIPTION-TEMPLATE-PREVIEW": { "TEMPLATE": "Template" diff --git a/frontend/src/assets/i18n/hr.json b/frontend/src/assets/i18n/hr.json index ac177a817..18faf42b6 100644 --- a/frontend/src/assets/i18n/hr.json +++ b/frontend/src/assets/i18n/hr.json @@ -180,7 +180,8 @@ "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", "USAGE-LIMITS": "Usage Limits", - "PLAN-STATUSES": "Plan Statuses" + "PLAN-STATUSES": "Plan Statuses", + "DESCRIPTION-STATUSES": "Description Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -333,7 +334,9 @@ "TENANT-CONFIGURATION": "Tenant Configuration", "ENTITY-LOCKS": "Entity Locks", "ANNOTATION-STATUSES": "Annotation Statuses", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "DESCRIPTION-STATUSES": "Description Statuses", + "PLAN-STATUSES": "Plan Statuses" }, "DESCRIPTION-TEMPLATE-PREVIEW": { "TEMPLATE": "Template" diff --git a/frontend/src/assets/i18n/pl.json b/frontend/src/assets/i18n/pl.json index cdc7ccd88..b0d680496 100644 --- a/frontend/src/assets/i18n/pl.json +++ b/frontend/src/assets/i18n/pl.json @@ -180,7 +180,8 @@ "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", "USAGE-LIMITS": "Usage Limits", - "PLAN-STATUSES": "Plan Statuses" + "PLAN-STATUSES": "Plan Statuses", + "DESCRIPTION-STATUSES": "Description Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -333,7 +334,9 @@ "TENANT-CONFIGURATION": "Tenant Configuration", "ENTITY-LOCKS": "Entity Locks", "ANNOTATION-STATUSES": "Annotation Statuses", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "DESCRIPTION-STATUSES": "Description Statuses", + "PLAN-STATUSES": "Plan Statuses" }, "DESCRIPTION-TEMPLATE-PREVIEW": { "TEMPLATE": "Template" diff --git a/frontend/src/assets/i18n/pt.json b/frontend/src/assets/i18n/pt.json index 07e5b97da..6c3acd3ca 100644 --- a/frontend/src/assets/i18n/pt.json +++ b/frontend/src/assets/i18n/pt.json @@ -180,7 +180,8 @@ "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", "USAGE-LIMITS": "Usage Limits", - "PLAN-STATUSES": "Plan Statuses" + "PLAN-STATUSES": "Plan Statuses", + "DESCRIPTION-STATUSES": "Description Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -333,7 +334,9 @@ "TENANT-CONFIGURATION": "Tenant Configuration", "ENTITY-LOCKS": "Entity Locks", "ANNOTATION-STATUSES": "Annotation Statuses", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "DESCRIPTION-STATUSES": "Description Statuses", + "PLAN-STATUSES": "Plan Statuses" }, "DESCRIPTION-TEMPLATE-PREVIEW": { "TEMPLATE": "Template" diff --git a/frontend/src/assets/i18n/sk.json b/frontend/src/assets/i18n/sk.json index 897de7531..945abdad1 100644 --- a/frontend/src/assets/i18n/sk.json +++ b/frontend/src/assets/i18n/sk.json @@ -180,7 +180,8 @@ "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", "USAGE-LIMITS": "Usage Limits", - "PLAN-STATUSES": "Plan Statuses" + "PLAN-STATUSES": "Plan Statuses", + "DESCRIPTION-STATUSES": "Description Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -333,7 +334,9 @@ "TENANT-CONFIGURATION": "Tenant Configuration", "ENTITY-LOCKS": "Entity Locks", "ANNOTATION-STATUSES": "Annotation Statuses", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "DESCRIPTION-STATUSES": "Description Statuses", + "PLAN-STATUSES": "Plan Statuses" }, "DESCRIPTION-TEMPLATE-PREVIEW": { "TEMPLATE": "Template" diff --git a/frontend/src/assets/i18n/sr.json b/frontend/src/assets/i18n/sr.json index cfbdafe58..0ee516ea5 100644 --- a/frontend/src/assets/i18n/sr.json +++ b/frontend/src/assets/i18n/sr.json @@ -180,7 +180,8 @@ "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", "USAGE-LIMITS": "Usage Limits", - "PLAN-STATUSES": "Plan Statuses" + "PLAN-STATUSES": "Plan Statuses", + "DESCRIPTION-STATUSES": "Description Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -333,7 +334,9 @@ "TENANT-CONFIGURATION": "Tenant Configuration", "ENTITY-LOCKS": "Entity Locks", "ANNOTATION-STATUSES": "Annotation Statuses", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "DESCRIPTION-STATUSES": "Description Statuses", + "PLAN-STATUSES": "Plan Statuses" }, "DESCRIPTION-TEMPLATE-PREVIEW": { "TEMPLATE": "Template" diff --git a/frontend/src/assets/i18n/tr.json b/frontend/src/assets/i18n/tr.json index be4e82640..4c684fdf3 100644 --- a/frontend/src/assets/i18n/tr.json +++ b/frontend/src/assets/i18n/tr.json @@ -180,7 +180,8 @@ "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", "USAGE-LIMITS": "Usage Limits", - "PLAN-STATUSES": "Plan Statuses" + "PLAN-STATUSES": "Plan Statuses", + "DESCRIPTION-STATUSES": "Description Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -333,7 +334,9 @@ "TENANT-CONFIGURATION": "Tenant Configuration", "ENTITY-LOCKS": "Entity Locks", "ANNOTATION-STATUSES": "Annotation Statuses", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "DESCRIPTION-STATUSES": "Description Statuses", + "PLAN-STATUSES": "Plan Statuses" }, "DESCRIPTION-TEMPLATE-PREVIEW": { "TEMPLATE": "Template"