diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 6321f794b..b47ee3f62 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -401,8 +401,12 @@ const appRoutes: Routes = [ loadChildren: () => import('./ui/admin/plan-status/plan-status.module').then(m => m.PlanStatusModule), data: { authContext: { - permissions: [AppPermission.ViewMaintenancePage] + permissions: [AppPermission.ViewPlanStatusPage] }, + breadcrumb: true, + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'GENERAL.TITLES.PLAN-STATUSES' + }) } }, { diff --git a/frontend/src/app/core/common/enum/permission.enum.ts b/frontend/src/app/core/common/enum/permission.enum.ts index 1a3b26a27..75a004105 100644 --- a/frontend/src/app/core/common/enum/permission.enum.ts +++ b/frontend/src/app/core/common/enum/permission.enum.ts @@ -239,5 +239,6 @@ export enum AppPermission { ViewTenantConfigurationPage = "ViewTenantConfigurationPage", ViewStatusPage = "ViewStatusPage", ViewUsageLimitPage = "ViewUsageLimitPage", + ViewPlanStatusPage = "ViewPlanStatusPage" } diff --git a/frontend/src/app/core/common/enum/plan-status.ts b/frontend/src/app/core/common/enum/plan-status.ts index 0e2550529..5a54cdf4f 100644 --- a/frontend/src/app/core/common/enum/plan-status.ts +++ b/frontend/src/app/core/common/enum/plan-status.ts @@ -1,4 +1,4 @@ -export enum PlanStatus { +export enum PlanStatusEnum { Draft = 0, Finalized = 1 } \ No newline at end of file diff --git a/frontend/src/app/core/core-service.module.ts b/frontend/src/app/core/core-service.module.ts index ad638df91..17b4d1794 100644 --- a/frontend/src/app/core/core-service.module.ts +++ b/frontend/src/app/core/core-service.module.ts @@ -48,6 +48,7 @@ import { DefaultUserLocaleService } from './services/default-user-locale/default 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'; // // // This is shared module that provides all the services. Its imported only once on the AppModule. @@ -115,7 +116,8 @@ export class CoreServiceModule { StorageFileService, TenantHandlingService, RouterUtilsService, - UsageLimitService + UsageLimitService, + PlanStatusService ], }; } diff --git a/frontend/src/app/core/model/plan-status/plan-status-persist.ts b/frontend/src/app/core/model/plan-status/plan-status-persist.ts new file mode 100644 index 000000000..03ca3beb9 --- /dev/null +++ b/frontend/src/app/core/model/plan-status/plan-status-persist.ts @@ -0,0 +1,10 @@ +import { PlanStatusEnum } from "@app/core/common/enum/plan-status"; +import { BaseEntityPersist } from "@common/base/base-entity.model"; +import { PlanStatusDefinition } from "./plan-status"; + +export interface PlanStatusPersist extends BaseEntityPersist { + name: string; + description: string; + internalStatus: PlanStatusEnum; + definition: PlanStatusDefinition; +} \ No newline at end of file diff --git a/frontend/src/app/core/model/plan-status/plan-status.ts b/frontend/src/app/core/model/plan-status/plan-status.ts new file mode 100644 index 000000000..f531d0a38 --- /dev/null +++ b/frontend/src/app/core/model/plan-status/plan-status.ts @@ -0,0 +1,26 @@ +import { AppRole } from "@app/core/common/enum/app-role"; +import { PlanStatusEnum } from "@app/core/common/enum/plan-status"; +import { PlanUserRole } from "@app/core/common/enum/plan-user-role"; +import { BaseEntity } from "@common/base/base-entity.model"; + +export interface PlanStatus extends BaseEntity{ + name: string; + description: string; + internalStatus: PlanStatusEnum; + definition: PlanStatusDefinition; +} + +export interface PlanStatusDefinition { + authorization: PlanStatusDefinitionAuthorization +} + +export interface PlanStatusDefinitionAuthorization { + edit: PlanStatusDefinitionAuthorizationItem; +} + +export interface PlanStatusDefinitionAuthorizationItem{ + roles: AppRole[], + planRoles: PlanUserRole[], + allowAuthenticated: boolean; + allowAnonymous: boolean +} \ No newline at end of file diff --git a/frontend/src/app/core/model/plan/plan.ts b/frontend/src/app/core/model/plan/plan.ts index a11acfe63..245f3f7c0 100644 --- a/frontend/src/app/core/model/plan/plan.ts +++ b/frontend/src/app/core/model/plan/plan.ts @@ -1,5 +1,5 @@ import { PlanAccessType } from '@app/core/common/enum/plan-access-type'; -import { PlanStatus } from '@app/core/common/enum/plan-status'; +import { PlanStatusEnum } from '@app/core/common/enum/plan-status'; import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; import { PlanVersionStatus } from '@app/core/common/enum/plan-version-status'; import { BaseEntity, BaseEntityPersist } from '@common/base/base-entity.model'; @@ -26,7 +26,7 @@ export interface BasePlan extends BaseEntity { planReferences?: PlanReference[]; entityDois?: EntityDoi[]; tenantId?: Guid; - status?: PlanStatus; + status?: PlanStatusEnum; descriptions?: BaseDescription[]; } export interface Plan extends BasePlan { @@ -96,7 +96,7 @@ export interface PlanDescriptionTemplate extends BaseEntity { // export interface PlanPersist extends BaseEntityPersist { label: string; - status: PlanStatus; + status: PlanStatusEnum; properties: PlanPropertiesPersist; description: String; language: String; diff --git a/frontend/src/app/core/query/plan-status.lookup.ts b/frontend/src/app/core/query/plan-status.lookup.ts new file mode 100644 index 000000000..00dac71ff --- /dev/null +++ b/frontend/src/app/core/query/plan-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 PlanStatusLookup extends Lookup implements PlanStatusFilter { + ids: Guid[]; + excludedIds: Guid[]; + like: string; + isActive: IsActive[]; + + constructor() { + super(); + } +} + +export interface PlanStatusFilter { + ids: Guid[]; + excludedIds: Guid[]; + like: string; + isActive: IsActive[]; +} \ No newline at end of file diff --git a/frontend/src/app/core/query/plan.lookup.ts b/frontend/src/app/core/query/plan.lookup.ts index da778bb2d..25642496e 100644 --- a/frontend/src/app/core/query/plan.lookup.ts +++ b/frontend/src/app/core/query/plan.lookup.ts @@ -1,7 +1,7 @@ import { Lookup } from '@common/model/lookup'; import { Guid } from '@common/types/guid'; import { PlanAccessType } from '../common/enum/plan-access-type'; -import { PlanStatus } from '../common/enum/plan-status'; +import { PlanStatusEnum } from '../common/enum/plan-status'; import { PlanVersionStatus } from '../common/enum/plan-version-status'; import { IsActive } from '../common/enum/is-active.enum'; import { PlanDescriptionTemplateLookup } from './plan-description-template.lookup'; @@ -16,7 +16,7 @@ export class PlanLookup extends Lookup implements PlanFilter { like: string; isActive: IsActive[]; versionStatuses: PlanVersionStatus[]; - statuses: PlanStatus[]; + statuses: PlanStatusEnum[]; accessTypes: PlanAccessType[]; versions: Number[]; groupIds: Guid[]; @@ -38,7 +38,7 @@ export interface PlanFilter { like: string; isActive: IsActive[]; versionStatuses: PlanVersionStatus[]; - statuses: PlanStatus[]; + statuses: PlanStatusEnum[]; accessTypes: PlanAccessType[]; versions: Number[]; groupIds: Guid[]; diff --git a/frontend/src/app/core/services/matomo/analytics-service.ts b/frontend/src/app/core/services/matomo/analytics-service.ts index 17fac8d70..8cb446a4e 100644 --- a/frontend/src/app/core/services/matomo/analytics-service.ts +++ b/frontend/src/app/core/services/matomo/analytics-service.ts @@ -45,6 +45,8 @@ export class AnalyticsService { public static UserGuideContent: string = 'User Guide Content'; public static UserProfile: string = 'User Profile'; public static NotificationTempplateEditor: string = 'Admin: Notification Tempplates'; + public static PlanStatusListing: string = 'Plan Status Listing'; + public static PlanStatusEditor: string = 'Plan Status Editor'; //#endregion //#region trackDownload diff --git a/frontend/src/app/core/services/plan/plan-status.service.ts b/frontend/src/app/core/services/plan/plan-status.service.ts new file mode 100644 index 000000000..51c66dafd --- /dev/null +++ b/frontend/src/app/core/services/plan/plan-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 { PlanStatusLookup } from "@app/core/query/plan-status.lookup"; +import { PlanStatus } from "@app/core/model/plan-status/plan-status"; +import { QueryResult } from "@common/model/query-result"; +import { catchError, Observable, throwError } from "rxjs"; +import { PlanStatusPersist } from "@app/core/model/plan-status/plan-status-persist"; +import { Guid } from "@common/types/guid"; + +@Injectable() +export class PlanStatusService { + private headers = new HttpHeaders(); + + constructor( + private http: BaseHttpV2Service, + private configurationService: ConfigurationService, + ) { + } + + private get apiBase(): string { return `${this.configurationService.server}plan-status`; } + + query(q: PlanStatusLookup): 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: PlanStatusPersist): 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/plan/plan.service.ts b/frontend/src/app/core/services/plan/plan.service.ts index f0d6c01ef..78cece372 100644 --- a/frontend/src/app/core/services/plan/plan.service.ts +++ b/frontend/src/app/core/services/plan/plan.service.ts @@ -1,6 +1,6 @@ import { HttpClient, HttpHeaders, HttpParamsOptions, HttpResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { PlanStatus } from '@app/core/common/enum/plan-status'; +import { PlanStatusEnum } from '@app/core/common/enum/plan-status'; import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; import { IsActive } from '@app/core/common/enum/is-active.enum'; import { PlanDescriptionTemplateLookup } from '@app/core/query/plan-description-template.lookup'; @@ -261,7 +261,7 @@ export class PlanService { valueAssign: (item: Plan) => item.id, }; - public buildAutocompleteLookup(isActive: IsActive[], like?: string, excludedIds?: Guid[], ids?: Guid[], statuses?: PlanStatus[], planDescriptionTemplateSubQuery?: PlanDescriptionTemplateLookup): PlanLookup { + public buildAutocompleteLookup(isActive: IsActive[], like?: string, excludedIds?: Guid[], ids?: Guid[], statuses?: PlanStatusEnum[], planDescriptionTemplateSubQuery?: PlanDescriptionTemplateLookup): PlanLookup { const lookup: PlanLookup = new PlanLookup(); lookup.page = { size: 100, offset: 0 }; if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; } 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 eead5eea0..ddaae4bb9 100644 --- a/frontend/src/app/core/services/utilities/enum-utils.service.ts +++ b/frontend/src/app/core/services/utilities/enum-utils.service.ts @@ -27,7 +27,7 @@ import { UserDescriptionTemplateRole } from '@app/core/common/enum/user-descript import { TranslateService } from '@ngx-translate/core'; import { AppRole } from '../../common/enum/app-role'; import { PlanBlueprintExtraFieldDataType } from '../../common/enum/plan-blueprint-field-type'; -import { PlanStatus } from '../../common/enum/plan-status'; +import { PlanStatusEnum } from '../../common/enum/plan-status'; import { ValidationType } from '../../common/enum/validation-type'; import { UsageLimitTargetMetric } from '@app/core/common/enum/usage-limit-target-metric'; import { UsageLimitPeriodicityRange } from '@app/core/common/enum/usage-limit-periodicity-range'; @@ -69,10 +69,10 @@ export class EnumUtils { } } - toPlanStatusString(status: PlanStatus): string { + toPlanStatusString(status: PlanStatusEnum): string { switch (status) { - case PlanStatus.Draft: return this.language.instant('TYPES.PLAN.DRAFT'); - case PlanStatus.Finalized: return this.language.instant('TYPES.PLAN.FINALISED'); + case PlanStatusEnum.Draft: return this.language.instant('TYPES.PLAN.DRAFT'); + case PlanStatusEnum.Finalized: return this.language.instant('TYPES.PLAN.FINALISED'); } } diff --git a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor.resolver.ts b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor.resolver.ts new file mode 100644 index 000000000..54f8c8285 --- /dev/null +++ b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor.resolver.ts @@ -0,0 +1,47 @@ +import { Injectable } from "@angular/core"; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; +import { PlanStatus, PlanStatusDefinition, PlanStatusDefinitionAuthorization, PlanStatusDefinitionAuthorizationItem } from "@app/core/model/plan-status/plan-status"; +import { PlanStatusService } from "@app/core/services/plan/plan-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 PlanStatusEditorResolver extends BaseEditorResolver{ + + constructor(private planStatusService: PlanStatusService, 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.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 = [ + ...PlanStatusEditorResolver.lookupFields() + ]; + const id = route.paramMap.get('id'); + if (id != null) { + return this.planStatusService.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/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 new file mode 100644 index 000000000..4134c62fc --- /dev/null +++ b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.html @@ -0,0 +1,108 @@ +
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+ + {{'PLAN-STATUS-EDITOR.FIELDS.NAME' | translate}} + + {{formGroup.controls.name.getError('backendError').message}} + {{'GENERAL.VALIDATION.REQUIRED' | translate}} + +
+
+ + {{'PLAN-STATUS-EDITOR.FIELDS.INTERNAL-STATUS' | translate}}* + + {{enumUtils.toPlanStatusString(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/plan-status/editor/plan-status-editor/plan-status-editor.component.scss b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.scss new file mode 100644 index 000000000..18cced2fd --- /dev/null +++ b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-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/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 new file mode 100644 index 000000000..fc5380475 --- /dev/null +++ b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.component.ts @@ -0,0 +1,164 @@ +import { Component, OnInit } from '@angular/core'; +import { PlanStatusDefinitionAuthorizationForm, PlanStatusDefinitionAuthorizationItemForm, PlanStatusEditorModel, PlanStatusForm } from './plan-status-editor.model'; +import { PlanStatus } from '@app/core/model/plan-status/plan-status'; +import { BaseEditor } from '@common/base/base-editor'; +import { Guid } from '@common/types/guid'; +import { AnalyticsService } from '@app/core/services/matomo/analytics-service'; +import { MatDialog } from '@angular/material/dialog'; +import { Router, ActivatedRoute } from '@angular/router'; +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 { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; +import { QueryParamsService } from '@app/core/services/utilities/query-params.service'; +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 { TranslateService } from '@ngx-translate/core'; +import { PlanStatusService } from '@app/core/services/plan/plan-status.service'; +import { PlanStatusEditorResolver } from '../plan-status-editor.resolver'; +import { map, takeUntil } from 'rxjs'; +import { IsActive } from '@app/core/common/enum/is-active.enum'; +import { LoggingService } from '@app/core/services/logging/logging-service'; +import { PlanStatusPersist } from '@app/core/model/plan-status/plan-status-persist'; +import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; +import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; +import { PlanStatusEnum } from '@app/core/common/enum/plan-status'; +import { FormGroup } from '@angular/forms'; +import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; +import { AppRole } from '@app/core/common/enum/app-role'; + +@Component({ + selector: 'app-plan-status-editor', + templateUrl: './plan-status-editor.component.html', + styleUrl: './plan-status-editor.component.scss' +}) +export class PlanStatusEditorComponent extends BaseEditor implements OnInit{ + + protected internalStatusEnum = this.enumUtils.getEnumValues(PlanStatusEnum); + protected userRolesEnum = this.enumUtils.getEnumValues(AppRole); + protected planRolesEnum = this.enumUtils.getEnumValues(PlanUserRole); + + 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 planStatusService: PlanStatusService, + 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.PlanStatusEditor); + super.ngOnInit(); + } + + get editAuthenticationForm(): FormGroup { + return this.formGroup?.controls?.definition?.controls?.authorization?.controls?.edit; + } + + getItem(itemId: Guid, successFunction: (item: PlanStatus) => void): void { + this.planStatusService.getSingle(itemId, PlanStatusEditorResolver.lookupFields()) + .pipe(map(data => data as PlanStatus), takeUntil(this._destroyed)) + .subscribe({ + next: (data) => successFunction(data), + error: (error) => this.onCallbackError(error) + }); + } + + prepareForm(data: PlanStatus): void { + try { + this.editorModel = data ? new PlanStatusEditorModel().fromModel(data) : new PlanStatusEditorModel(); + this.isDeleted = data ? data.isActive === IsActive.Inactive : false; + this.buildForm(); + } catch (error) { + this.logger.error('Could not parse planStatus item: ' + data + error); + this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error); + } + } + + buildForm(): void { + this.formGroup = this.editorModel.buildForm({disabled: this.isDeleted || !this.authService.hasPermission(AppPermission.EditPlanStatus)}); + } + + 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.planStatusService.delete(value.id).pipe(takeUntil(this._destroyed)) + .subscribe({ + complete: () => this.onCallbackDeleteSuccess(), + error: (error) => this.onCallbackError(error) + }); + } + }); + } + } + + refreshData(): void { + this.getItem(this.editorModel.id, (data: PlanStatus) => this.prepareForm(data)); + } + + refreshOnNavigateToData(id?: Guid): void { + debugger + this.formGroup.markAsPristine(); + if (this.isNew) { + let route = []; + route.push(this.routerUtils.generateUrl('/plan-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 PlanStatusPersist; + + this.planStatusService.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 editPlanStatus = this.authService.permissionEnum.EditPlanStatus; + return !this.isDeleted && this.authService.hasPermission(editPlanStatus); + } +} diff --git a/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.model.ts b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.model.ts new file mode 100644 index 000000000..5ded80ec6 --- /dev/null +++ b/frontend/src/app/ui/admin/plan-status/editor/plan-status-editor/plan-status-editor.model.ts @@ -0,0 +1,117 @@ +import { FormArray, FormControl, FormGroup, Validators } from "@angular/forms"; +import { AppRole } from "@app/core/common/enum/app-role"; +import { AppPermission } from "@app/core/common/enum/permission.enum"; +import { PlanStatusEnum } from "@app/core/common/enum/plan-status"; +import { PlanUserRole } from "@app/core/common/enum/plan-user-role"; +import { PlanStatus, PlanStatusDefinition, PlanStatusDefinitionAuthorizationItem } from "@app/core/model/plan-status/plan-status"; +import { PlanStatusPersist } from "@app/core/model/plan-status/plan-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 PlanStatusEditorModel extends BaseEditorModel implements PlanStatusPersist { + name: string; + description: string; + internalStatus: PlanStatusEnum; + definition: PlanStatusDefinition; + + public fromModel(item: PlanStatus): PlanStatusEditorModel { + 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: PlanStatusDefinitionAuthorizationItem, 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 PlanStatusForm { + id: FormControl; + name: FormControl; + description: FormControl; + internalStatus: FormControl; + definition: FormGroup; +} + +export interface PlanStatusDefinitionForm { + authorization: FormGroup +} + +export interface PlanStatusDefinitionAuthorizationForm { + edit: FormGroup; +} + +export interface PlanStatusDefinitionAuthorizationItemForm { + roles: FormControl; + planRoles: FormControl; + allowAuthenticated: FormControl; + allowAnonymous: FormControl; +} \ No newline at end of file diff --git a/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing-filters/plan-status-listing-filters.component.html b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing-filters/plan-status-listing-filters.component.html new file mode 100644 index 000000000..112f73933 --- /dev/null +++ b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing-filters/plan-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/plan-status/listing/plan-status-listing/plan-status-listing-filters/plan-status-listing-filters.component.scss b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing-filters/plan-status-listing-filters.component.scss new file mode 100644 index 000000000..33e94c8b1 --- /dev/null +++ b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing-filters/plan-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/plan-status/listing/plan-status-listing/plan-status-listing-filters/plan-status-listing-filters.component.spec.ts b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing-filters/plan-status-listing-filters.component.spec.ts new file mode 100644 index 000000000..b64ae5980 --- /dev/null +++ b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing-filters/plan-status-listing-filters.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PlanStatusListingFiltersComponent } from './plan-status-listing-filters.component'; + +describe('PlanStatusListingFiltersComponent', () => { + let component: PlanStatusListingFiltersComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PlanStatusListingFiltersComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PlanStatusListingFiltersComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing-filters/plan-status-listing-filters.component.ts b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing-filters/plan-status-listing-filters.component.ts new file mode 100644 index 000000000..897a09dee --- /dev/null +++ b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing-filters/plan-status-listing-filters.component.ts @@ -0,0 +1,88 @@ +import { Component, effect, EventEmitter, input, Input, Output } from '@angular/core'; +import { IsActive } from '@app/core/common/enum/is-active.enum'; +import { PlanStatusFilter } from '@app/core/query/plan-status.lookup'; +import { BaseComponent } from '@common/base/base.component'; + +@Component({ + selector: 'app-plan-status-listing-filters', + templateUrl: './plan-status-listing-filters.component.html', + styleUrl: './plan-status-listing-filters.component.scss' +}) +export class PlanStatusListingFiltersComponent extends BaseComponent{ + readonly filter = input(); + @Output() filterChange = new EventEmitter(); + + internalFilters: PlanStatusListingFilters = this._getEmptyFilters(); + appliedFilterCount: number = 0; + + constructor(){ + super(); + effect(() => { + const newFilters = this.filter(); + if(newFilters){ + this.updateFilters(); + } + }) + } + + + private _parseToInternalFilters(inputFilter: PlanStatusFilter): PlanStatusListingFilters { + if (!inputFilter) { + return this._getEmptyFilters(); + } + + let { isActive, like } = inputFilter; + + return { + isActive: (isActive ?? [])?.includes(IsActive.Active) || !isActive?.length, + like: like, + } + + } + + private _computeAppliedFilters(filters: PlanStatusListingFilters): number { + let count = 0; + if (!filters?.isActive) { + count++ + } + if(filters?.like){ + count++; + } + return count; + } + + private _getEmptyFilters(): PlanStatusListingFilters { + 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 PlanStatusListingFilters { + isActive: boolean; + like: string; +} diff --git a/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.html b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.html new file mode 100644 index 000000000..369985205 --- /dev/null +++ b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.html @@ -0,0 +1,97 @@ +
+
+
+
+
+ +
+ +
+ +
+
+ + + + + + + +
+
+
+ + + + +
+
+ + {{item?.name | nullifyValue}} +
+
+ + +
+
+ {{enumUtils.toPlanStatusString(item.internalStatus) | nullifyValue}} +
+
+
+ + +
+
+
+ + +
+
+ + + + + +
+
+
diff --git a/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.scss b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.scss new file mode 100644 index 000000000..7276f39cb --- /dev/null +++ b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.scss @@ -0,0 +1,10 @@ +.create-btn { + border-radius: 30px; + background-color: var(--secondary-color); + padding-left: 2em; + padding-right: 2em; + + .button-text { + display: inline-block; + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..c11bff41b --- /dev/null +++ b/frontend/src/app/ui/admin/plan-status/listing/plan-status-listing/plan-status-listing.component.ts @@ -0,0 +1,176 @@ +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { PlanStatus } from '@app/core/model/plan-status/plan-status'; +import { UserSettingsKey } from '@app/core/model/user-settings/user-settings.model'; +import { DataTableDateTimeFormatPipe } from '@app/core/pipes/date-time-format.pipe'; +import { PlanStatusLookup } from '@app/core/query/plan-status.lookup'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; +import { PlanStatusService } from '@app/core/services/plan/plan-status.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 { 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 { IsActive } from '@notification-service/core/enum/is-active.enum'; +import { Observable, takeUntil } from 'rxjs'; +import { nameof } from 'ts-simple-nameof'; +import { PlanStatusEditorResolver } from '../../editor/plan-status-editor.resolver'; +import { AnalyticsService } from '@app/core/services/matomo/analytics-service'; +import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'app-plan-status-listing', + templateUrl: './plan-status-listing.component.html', + styleUrl: './plan-status-listing.component.scss' +}) +export class PlanStatusListingComponent extends BaseListingComponent implements OnInit{ + userSettingsKey: UserSettingsKey = {key: 'PlanStatusListingUserSettings'}; + + publish = false; + propertiesAvailableForOrder: ColumnDefinition[]; + + @ViewChild('actions', { static: true }) actions: 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 planStatusService: PlanStatusService, + private analyticsService: AnalyticsService, + private language: TranslateService, + private dialog: MatDialog + ){ + super(router, route, uiNotificationService, httpErrorHandlingService, queryParamsService); + this.lookup = this.initializeLookup(); + } + + ngOnInit() { + this.analyticsService.trackPageView(AnalyticsService.PlanStatusListing); + super.ngOnInit(); + } + + private readonly lookupFields = PlanStatusEditorResolver.lookupFields(); + + protected initializeLookup(): PlanStatusLookup { + const lookup = new PlanStatusLookup(); + 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.planStatusService.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', + }, + { + 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.planStatusService.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(); + } + + changeSetting($event) { + + } +} diff --git a/frontend/src/app/ui/admin/plan-status/plan-status.module.ts b/frontend/src/app/ui/admin/plan-status/plan-status.module.ts index 99e0743f5..b8e2e5437 100644 --- a/frontend/src/app/ui/admin/plan-status/plan-status.module.ts +++ b/frontend/src/app/ui/admin/plan-status/plan-status.module.ts @@ -2,14 +2,32 @@ import { NgModule } from "@angular/core"; import { CommonFormsModule } from '@common/forms/common-forms.module'; import { CommonUiModule } from '@common/ui/common-ui.module'; import { PlanStatusRoutingModule } from "./plan-status.routing"; +import { PlanStatusListingComponent } from "./listing/plan-status-listing/plan-status-listing.component"; +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 { PlanStatusListingFiltersComponent } from "./listing/plan-status-listing/plan-status-listing-filters/plan-status-listing-filters.component"; +import { PlanStatusEditorComponent } from "./editor/plan-status-editor/plan-status-editor.component"; +import { TextFilterModule } from "@common/modules/text-filter/text-filter.module"; +import { RichTextEditorModule } from "@app/library/rich-text-editor/rich-text-editor.module"; @NgModule({ imports: [ CommonUiModule, CommonFormsModule, - PlanStatusRoutingModule, + PlanStatusRoutingModule, + ConfirmationDialogModule, + HybridListingModule, + UserSettingsModule, + CommonFormattingModule, + TextFilterModule, + RichTextEditorModule ], declarations: [ + PlanStatusListingComponent, + PlanStatusListingFiltersComponent, + PlanStatusEditorComponent ] }) export class PlanStatusModule { } diff --git a/frontend/src/app/ui/admin/plan-status/plan-status.routing.ts b/frontend/src/app/ui/admin/plan-status/plan-status.routing.ts index 439c9c961..456dd458c 100644 --- a/frontend/src/app/ui/admin/plan-status/plan-status.routing.ts +++ b/frontend/src/app/ui/admin/plan-status/plan-status.routing.ts @@ -4,17 +4,26 @@ 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 { PlanStatusListingComponent } from './listing/plan-status-listing/plan-status-listing.component'; +import { PlanStatusEditorComponent } from './editor/plan-status-editor/plan-status-editor.component'; +import { PlanStatusEditorResolver } from './editor/plan-status-editor.resolver'; const routes: Routes = [ { path: '', - // component:, - canActivate: [AuthGuard] + component: PlanStatusListingComponent, + canActivate: [AuthGuard], + data: { + breadcrumb: true, + ...BreadcrumbService.generateRouteDataConfiguration({ + hideNavigationItem: true + }), + } }, { path: 'new', canActivate: [AuthGuard], - // component:, + component: PlanStatusEditorComponent, canDeactivate: [PendingChangesGuard], data: { authContext: { @@ -30,11 +39,11 @@ const routes: Routes = [ { path: ':id', canActivate: [AuthGuard], - // component:, + component: PlanStatusEditorComponent, canDeactivate: [PendingChangesGuard], - // resolve: { - // 'entity': - // }, + resolve: { + 'entity': PlanStatusEditorResolver + }, data: { authContext: { permissions: [AppPermission.EditPlanStatus] @@ -50,5 +59,6 @@ const routes: Routes = [ @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], + providers: [PlanStatusEditorResolver] }) export class PlanStatusRoutingModule { } diff --git a/frontend/src/app/ui/dashboard/recent-edited-activity/recent-edited-activity.component.ts b/frontend/src/app/ui/dashboard/recent-edited-activity/recent-edited-activity.component.ts index 0dbab076b..09209f096 100644 --- a/frontend/src/app/ui/dashboard/recent-edited-activity/recent-edited-activity.component.ts +++ b/frontend/src/app/ui/dashboard/recent-edited-activity/recent-edited-activity.component.ts @@ -3,7 +3,7 @@ import { Component, Input, OnInit, Output } from '@angular/core'; import { UntypedFormBuilder, UntypedFormControl } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { DescriptionStatus } from '@app/core/common/enum/description-status'; -import { PlanStatus } from '@app/core/common/enum/plan-status'; +import { PlanStatusEnum } from '@app/core/common/enum/plan-status'; import { IsActive } from '@app/core/common/enum/is-active.enum'; import { AppPermission } from '@app/core/common/enum/permission.enum'; import { RecentActivityOrder } from '@app/core/common/enum/recent-activity-order'; @@ -209,7 +209,7 @@ export class RecentEditedActivityComponent extends BaseComponent implements OnIn response.forEach(item => { if (item.plan){ if (item.plan.descriptions) { - if (item.plan.status == PlanStatus.Finalized) { + if (item.plan.status == PlanStatusEnum.Finalized) { item.plan.descriptions = item.plan.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatus.Finalized); } else { item.plan.descriptions = item.plan.descriptions.filter(x => x.isActive === IsActive.Active && x.status != DescriptionStatus.Canceled); diff --git a/frontend/src/app/ui/description/description-copy-dialog/description-copy-dialog.component.ts b/frontend/src/app/ui/description/description-copy-dialog/description-copy-dialog.component.ts index dd9b5d597..e51ecefac 100644 --- a/frontend/src/app/ui/description/description-copy-dialog/description-copy-dialog.component.ts +++ b/frontend/src/app/ui/description/description-copy-dialog/description-copy-dialog.component.ts @@ -13,7 +13,7 @@ import { PlanDescriptionTemplateLookup } from '@app/core/query/plan-description- import { IsActive } from '@app/core/common/enum/is-active.enum'; import { PlanLookup } from '@app/core/query/plan.lookup'; import { Guid } from '@common/types/guid'; -import { PlanStatus } from '@app/core/common/enum/plan-status'; +import { PlanStatusEnum } from '@app/core/common/enum/plan-status'; import { nameof } from 'ts-simple-nameof'; import { FilterService } from '@common/modules/text-filter/filter-service'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; @@ -51,7 +51,7 @@ export class DescriptionCopyDialogComponent { if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; } if (ids && ids.length > 0) { lookup.ids = ids; } lookup.isActive = [IsActive.Active]; - lookup.statuses = [PlanStatus.Draft]; + lookup.statuses = [PlanStatusEnum.Draft]; lookup.project = { fields: [ nameof(x => x.id), 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 32091a8bc..67acd6736 100644 --- a/frontend/src/app/ui/description/editor/description-editor.component.ts +++ b/frontend/src/app/ui/description/editor/description-editor.component.ts @@ -8,7 +8,7 @@ import { FileTransformerEntityType } from '@app/core/common/enum/file-transforme import { IsActive } from '@app/core/common/enum/is-active.enum'; import { LockTargetType } from '@app/core/common/enum/lock-target-type'; import { AppPermission } from '@app/core/common/enum/permission.enum'; -import { PlanStatus } from '@app/core/common/enum/plan-status'; +import { PlanStatusEnum } from '@app/core/common/enum/plan-status'; import { DescriptionTemplateField, DescriptionTemplateFieldSet, DescriptionTemplatePage, DescriptionTemplateSection } from '@app/core/model/description-template/description-template'; import { Description, DescriptionPersist, DescriptionStatusPersist } from '@app/core/model/description/description'; import { PlanBlueprintDefinitionSection } from '@app/core/model/plan-blueprint/plan-blueprint'; @@ -551,7 +551,7 @@ export class DescriptionEditorComponent extends BaseEditor item.id, }; - private buildAutocompleteLookup(like?: string, excludedIds?: Guid[], ids?: Guid[], statuses?: PlanStatus[], planDescriptionTemplateSubQuery?: PlanDescriptionTemplateLookup): PlanLookup { + private buildAutocompleteLookup(like?: string, excludedIds?: Guid[], ids?: Guid[], statuses?: PlanStatusEnum[], planDescriptionTemplateSubQuery?: PlanDescriptionTemplateLookup): PlanLookup { const lookup: PlanLookup = new PlanLookup(); lookup.page = { size: 100, offset: 0 }; if (excludedIds && excludedIds.length > 0) { lookup.excludedIds = excludedIds; } if (ids && ids.length > 0) { lookup.ids = ids; } lookup.isActive = [IsActive.Active]; - lookup.statuses = [PlanStatus.Draft]; + lookup.statuses = [PlanStatusEnum.Draft]; lookup.project = { fields: [ nameof(x => x.id), diff --git a/frontend/src/app/ui/plan/listing/filtering/plan-filter.component.ts b/frontend/src/app/ui/plan/listing/filtering/plan-filter.component.ts index 916e2f319..f8e78c28c 100644 --- a/frontend/src/app/ui/plan/listing/filtering/plan-filter.component.ts +++ b/frontend/src/app/ui/plan/listing/filtering/plan-filter.component.ts @@ -6,7 +6,7 @@ import { TranslateService } from '@ngx-translate/core'; import { AuthService } from '@app/core/services/auth/auth.service'; import { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration'; import { PlanBlueprintService } from '@app/core/services/plan/plan-blueprint.service'; -import { PlanStatus } from '@app/core/common/enum/plan-status'; +import { PlanStatusEnum } from '@app/core/common/enum/plan-status'; import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service'; import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration'; @@ -35,7 +35,7 @@ export class PlanFilterComponent extends BaseCriteriaComponent implements OnInit @Input() filterFormGroup: UntypedFormGroup; @Output() filterChanged: EventEmitter = new EventEmitter(); - status = PlanStatus; + status = PlanStatusEnum; role = PlanUserRole; filteringGrantsAsync = false; sizeError = false; diff --git a/frontend/src/app/ui/plan/listing/listing-item/plan-listing-item.component.ts b/frontend/src/app/ui/plan/listing/listing-item/plan-listing-item.component.ts index a0c8429db..fef65b09b 100644 --- a/frontend/src/app/ui/plan/listing/listing-item/plan-listing-item.component.ts +++ b/frontend/src/app/ui/plan/listing/listing-item/plan-listing-item.component.ts @@ -19,7 +19,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 { PlanStatus } from '../../../../core/common/enum/plan-status'; +import { PlanStatusEnum } from '../../../../core/common/enum/plan-status'; import { AuthService } from '../../../../core/services/auth/auth.service'; import { ClonePlanDialogComponent } from '../../clone-dialog/plan-clone-dialog.component'; import { PlanInvitationDialogComponent } from '../../invitation/dialog/plan-invitation-dialog.component'; @@ -49,7 +49,7 @@ export class PlanListingItemComponent extends BaseComponent implements OnInit { isDraft: boolean; isFinalized: boolean; isPublished: boolean; - planStatusEnum = PlanStatus; + planStatusEnum = PlanStatusEnum; fileTransformerEntityTypeEnum = FileTransformerEntityType; constructor( @@ -77,16 +77,16 @@ export class PlanListingItemComponent extends BaseComponent implements OnInit { ngOnInit() { this.analyticsService.trackPageView(AnalyticsService.PlanListingItem); - if (this.plan.status == PlanStatus.Draft) { + if (this.plan.status == PlanStatusEnum.Draft) { this.isDraft = true; this.isFinalized = false; this.isPublished = false; } - else if (this.plan.status == PlanStatus.Finalized) { + else if (this.plan.status == PlanStatusEnum.Finalized) { this.isDraft = false; this.isFinalized = true; this.isPublished = false; - if (this.plan.status === PlanStatus.Finalized && this.plan.accessType === PlanAccessType.Public) { this.isPublished = true } + if (this.plan.status === PlanStatusEnum.Finalized && this.plan.accessType === PlanAccessType.Public) { this.isPublished = true } } } @@ -112,7 +112,7 @@ export class PlanListingItemComponent extends BaseComponent implements OnInit { } viewVersions(plan: Plan) { - if (plan.accessType == PlanAccessType.Public && plan.status == PlanStatus.Finalized && !this.plan.authorizationFlags?.some(x => x === AppPermission.EditPlan)) { + if (plan.accessType == PlanAccessType.Public && plan.status == PlanStatusEnum.Finalized && !this.plan.authorizationFlags?.some(x => x === AppPermission.EditPlan)) { let url = this.router.createUrlTree(['/explore-plans/versions/', plan.groupId]); window.open(url.toString(), '_blank'); } else { @@ -217,7 +217,7 @@ export class PlanListingItemComponent extends BaseComponent implements OnInit { } isDraftPlan(activity: Plan) { - return activity.status == PlanStatus.Draft; + return activity.status == PlanStatusEnum.Draft; } reloadPage(): void { 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 1b94c626f..2d70e2c39 100644 --- a/frontend/src/app/ui/plan/overview/plan-overview.component.ts +++ b/frontend/src/app/ui/plan/overview/plan-overview.component.ts @@ -4,7 +4,7 @@ import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { DescriptionStatus } from '@app/core/common/enum/description-status'; import { PlanAccessType } from '@app/core/common/enum/plan-access-type'; -import { PlanStatus } from '@app/core/common/enum/plan-status'; +import { PlanStatusEnum } from '@app/core/common/enum/plan-status'; import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; import { PlanVersionStatus } from '@app/core/common/enum/plan-version-status'; import { FileTransformerEntityType } from '@app/core/common/enum/file-transformer-entity-type'; @@ -77,7 +77,7 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { descriptionStatusEnum = DescriptionStatus; planAccessTypeEnum = PlanAccessType; - planStatusEnum = PlanStatus; + planStatusEnum = PlanStatusEnum; planUserRoleEnum = PlanUserRole; authorFocus: string; @@ -132,7 +132,7 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { this.plan.planUsers = this.isActive ? data?.planUsers?.filter((x) => x.isActive === IsActive.Active) : data?.planUsers; this.plan.otherPlanVersions = data.otherPlanVersions?.filter(x => x.isActive === IsActive.Active) || null; if (this.plan.descriptions) { - if (this.plan.status == PlanStatus.Finalized) { + if (this.plan.status == PlanStatusEnum.Finalized) { this.plan.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatus.Finalized); } else { this.plan.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status !== DescriptionStatus.Canceled); @@ -411,15 +411,15 @@ export class PlanOverviewComponent extends BaseComponent implements OnInit { } isDraftPlan() { - return this.plan.status == PlanStatus.Draft; + return this.plan.status == PlanStatusEnum.Draft; } isFinalizedPlan(plan: Plan) { - return plan.status == PlanStatus.Finalized; + return plan.status == PlanStatusEnum.Finalized; } isPublishedPlan() { - return (this.plan.status == PlanStatus.Finalized && this.plan.accessType === PlanAccessType.Public); + return (this.plan.status == PlanStatusEnum.Finalized && this.plan.accessType === PlanAccessType.Public); } hasDoi() { 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 a8c035c0d..74f058a3a 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 @@ -15,7 +15,7 @@ import { PlanBlueprintFieldCategory } from '@app/core/common/enum/plan-blueprint import { PlanBlueprintExtraFieldDataType } from '@app/core/common/enum/plan-blueprint-field-type'; import { PlanBlueprintStatus } from '@app/core/common/enum/plan-blueprint-status'; import { PlanBlueprintSystemFieldType } from '@app/core/common/enum/plan-blueprint-system-field-type'; -import { PlanStatus } from '@app/core/common/enum/plan-status'; +import { PlanStatusEnum } from '@app/core/common/enum/plan-status'; import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; import { PlanUserType } from '@app/core/common/enum/plan-user-type'; import { DescriptionTemplate } from '@app/core/model/description-template/description-template'; @@ -135,7 +135,7 @@ export class PlanEditorComponent extends BaseEditor imple } protected get canReverseFinalize(): boolean { - return !this.isDeleted && !this.isNew && this.canEdit && this.isLockedByUser && this.item.status == PlanStatus.Finalized && (this.hasPermission(this.authService.permissionEnum.EditPlan) || this.item?.authorizationFlags?.some(x => x === AppPermission.EditPlan)); + return !this.isDeleted && !this.isNew && this.canEdit && this.isLockedByUser && this.item.status == PlanStatusEnum.Finalized && (this.hasPermission(this.authService.permissionEnum.EditPlan) || this.item?.authorizationFlags?.some(x => x === AppPermission.EditPlan)); } protected canEditSection(id: Guid): boolean { @@ -261,7 +261,7 @@ export class PlanEditorComponent extends BaseEditor imple this.editorModel = data ? new PlanEditorModel().fromModel(data) : new PlanEditorModel(); if (data) { if (data.descriptions) { - if (data.status == PlanStatus.Finalized) { + if (data.status == PlanStatusEnum.Finalized) { data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatus.Finalized); } else { data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status !== DescriptionStatus.Canceled); @@ -277,7 +277,7 @@ export class PlanEditorComponent extends BaseEditor imple this.selectedBlueprint = data?.blueprint; this.isDeleted = data ? data.isActive === IsActive.Inactive : false; - this.isFinalized = data ? data.status === PlanStatus.Finalized : false; + this.isFinalized = data ? data.status === PlanStatusEnum.Finalized : false; if (data && data.id) { const descriptionSectionPermissionResolverModel: DescriptionSectionPermissionResolver = { @@ -304,9 +304,9 @@ export class PlanEditorComponent extends BaseEditor imple this.sectionToFieldsMap = this.prepareErrorIndication(); - if (this.editorModel.status == PlanStatus.Finalized || this.isDeleted) { + if (this.editorModel.status == PlanStatusEnum.Finalized || this.isDeleted) { this.viewOnly = true; - this.isFinalized = this.editorModel.status == PlanStatus.Finalized; + this.isFinalized = this.editorModel.status == PlanStatusEnum.Finalized; this.formGroup.disable(); } else { this.viewOnly = false; @@ -593,7 +593,7 @@ export class PlanEditorComponent extends BaseEditor imple label: this.formGroup.get('label').value, description: this.formGroup.get('description').value, blueprint: this.selectedBlueprint, - status: PlanStatus.Draft + status: PlanStatusEnum.Draft } this.prepareForm(plan); diff --git a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.model.ts b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.model.ts index 399a7f6f9..a6752e335 100644 --- a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.model.ts +++ b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.model.ts @@ -2,7 +2,7 @@ import { FormArray, FormControl, UntypedFormBuilder, UntypedFormGroup, Validator import { PlanAccessType } from "@app/core/common/enum/plan-access-type"; import { PlanBlueprintFieldCategory } from "@app/core/common/enum/plan-blueprint-field-category"; import { PlanBlueprintSystemFieldType } from "@app/core/common/enum/plan-blueprint-system-field-type"; -import { PlanStatus } from "@app/core/common/enum/plan-status"; +import { PlanStatusEnum } from "@app/core/common/enum/plan-status"; import { PlanUserRole } from "@app/core/common/enum/plan-user-role"; import { PlanUserType } from "@app/core/common/enum/plan-user-type"; import { IsActive } from "@app/core/common/enum/is-active.enum"; @@ -18,7 +18,7 @@ import { Guid } from "@common/types/guid"; export class PlanEditorModel extends BaseEditorModel implements PlanPersist { label: string; - status: PlanStatus; + status: PlanStatusEnum; properties: PlanPropertiesEditorModel = new PlanPropertiesEditorModel(this.validationErrorModel); description: String; language: String; diff --git a/frontend/src/assets/i18n/baq.json b/frontend/src/assets/i18n/baq.json index 4613c8001..a8d148e8a 100644 --- a/frontend/src/assets/i18n/baq.json +++ b/frontend/src/assets/i18n/baq.json @@ -179,7 +179,8 @@ "HOME": "Home", "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "PLAN-STATUSES": "Plan Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -829,6 +830,54 @@ "UNAUTHORIZED-ORCID": "This ORCID is entered by the Plan creators, without further acknowledgement of the owner." } }, + "PLAN-STATUS-LISTING": { + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "STATUS": "Status", + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "IS-ACTIVE": "Is Active" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "ACTIONS": { + "DELETE": "Delete", + "EDIT": "Edit", + "CREATE-PLAN-STATUS": "Create Plan Status" + } + }, + "PLAN-STATUS-EDITOR": { + "TITLE": { + "NEW": "New Plan Blueprint", + "EDIT": "Edit Plan Blueprint" + }, + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "DEFINITION": "Definition", + "INTERNAL-STATUS": "Internal Status", + "AUTHORIZATION": "Authorization", + "EDIT": "Edit", + "ALLOW-AUTHENTICATED": "Allow authenticated users", + "ALLOW-ANONYMOUS": "Allow anonymous users", + "ROLES": "User roles", + "PLAN-ROLES": "User plan roles" + }, + "ACTIONS": { + "SAVE": "Save", + "CANCEL": "Cancel", + "DELETE": "Delete" + }, + "LOCKED-DIALOG": { + "TITLE": "Plan Status is locked", + "MESSAGE": "Somebody else is modifying the Plan Status at this moment. You may view the Plan Status but you cannot make any changes. If you would like to modify it please come back later." + } + }, "DESCRIPTION-OVERVIEW": { "TITLE": "Description", "PUBLIC": "Public", diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index 28889b436..9095e01f3 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -179,7 +179,8 @@ "HOME": "Home", "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "PLAN-STATUSES": "Plan Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -829,6 +830,54 @@ "UNAUTHORIZED-ORCID": "This ORCID is entered by the Plan creators, without further acknowledgement of the owner." } }, + "PLAN-STATUS-LISTING": { + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "STATUS": "Status", + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "IS-ACTIVE": "Is Active" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "ACTIONS": { + "DELETE": "Delete", + "EDIT": "Edit", + "CREATE-PLAN-STATUS": "Create Plan Status" + } + }, + "PLAN-STATUS-EDITOR": { + "TITLE": { + "NEW": "New Plan Blueprint", + "EDIT": "Edit Plan Blueprint" + }, + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "DEFINITION": "Definition", + "INTERNAL-STATUS": "Internal Status", + "AUTHORIZATION": "Authorization", + "EDIT": "Edit", + "ALLOW-AUTHENTICATED": "Allow authenticated users", + "ALLOW-ANONYMOUS": "Allow anonymous users", + "ROLES": "User roles", + "PLAN-ROLES": "User plan roles" + }, + "ACTIONS": { + "SAVE": "Save", + "CANCEL": "Cancel", + "DELETE": "Delete" + }, + "LOCKED-DIALOG": { + "TITLE": "Plan Status is locked", + "MESSAGE": "Somebody else is modifying the Plan Status at this moment. You may view the Plan Status but you cannot make any changes. If you would like to modify it please come back later." + } + }, "DESCRIPTION-OVERVIEW": { "TITLE": "Description", "PUBLIC": "Public", diff --git a/frontend/src/assets/i18n/en.json b/frontend/src/assets/i18n/en.json index e491f1926..6faeced0a 100644 --- a/frontend/src/assets/i18n/en.json +++ b/frontend/src/assets/i18n/en.json @@ -179,7 +179,8 @@ "HOME": "Home", "ANNOTATION-STATUSES":"Annotation Statuses", "SUPPORTIVE-MATERIAL":"Supportive Material", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "PLAN-STATUSES": "Plan Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -829,6 +830,54 @@ "UNAUTHORIZED-ORCID": "This ORCID is entered by the Plan creators, without further acknowledgement of the owner." } }, + "PLAN-STATUS-LISTING": { + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "STATUS": "Status", + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "IS-ACTIVE": "Is Active" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "ACTIONS": { + "DELETE": "Delete", + "EDIT": "Edit", + "CREATE-PLAN-STATUS": "Create Plan Status" + } + }, + "PLAN-STATUS-EDITOR": { + "TITLE": { + "NEW": "New Plan Blueprint", + "EDIT": "Edit Plan Blueprint" + }, + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "DEFINITION": "Definition", + "INTERNAL-STATUS": "Internal Status", + "AUTHORIZATION": "Authorization", + "EDIT": "Edit", + "ALLOW-AUTHENTICATED": "Allow authenticated users", + "ALLOW-ANONYMOUS": "Allow anonymous users", + "ROLES": "User roles", + "PLAN-ROLES": "User plan roles" + }, + "ACTIONS": { + "SAVE": "Save", + "CANCEL": "Cancel", + "DELETE": "Delete" + }, + "LOCKED-DIALOG": { + "TITLE": "Plan Status is locked", + "MESSAGE": "Somebody else is modifying the Plan Status at this moment. You may view the Plan Status but you cannot make any changes. If you would like to modify it please come back later." + } + }, "DESCRIPTION-OVERVIEW": { "TITLE": "Description", "PUBLIC": "Public", diff --git a/frontend/src/assets/i18n/es.json b/frontend/src/assets/i18n/es.json index 4381dadbc..f91e542cd 100644 --- a/frontend/src/assets/i18n/es.json +++ b/frontend/src/assets/i18n/es.json @@ -179,7 +179,8 @@ "HOME": "Home", "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "PLAN-STATUSES": "Plan Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -829,6 +830,54 @@ "UNAUTHORIZED-ORCID": "This ORCID is entered by the Plan creators, without further acknowledgement of the owner." } }, + "PLAN-STATUS-LISTING": { + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "STATUS": "Status", + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "IS-ACTIVE": "Is Active" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "ACTIONS": { + "DELETE": "Delete", + "EDIT": "Edit", + "CREATE-PLAN-STATUS": "Create Plan Status" + } + }, + "PLAN-STATUS-EDITOR": { + "TITLE": { + "NEW": "New Plan Blueprint", + "EDIT": "Edit Plan Blueprint" + }, + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "DEFINITION": "Definition", + "INTERNAL-STATUS": "Internal Status", + "AUTHORIZATION": "Authorization", + "EDIT": "Edit", + "ALLOW-AUTHENTICATED": "Allow authenticated users", + "ALLOW-ANONYMOUS": "Allow anonymous users", + "ROLES": "User roles", + "PLAN-ROLES": "User plan roles" + }, + "ACTIONS": { + "SAVE": "Save", + "CANCEL": "Cancel", + "DELETE": "Delete" + }, + "LOCKED-DIALOG": { + "TITLE": "Plan Status is locked", + "MESSAGE": "Somebody else is modifying the Plan Status at this moment. You may view the Plan Status but you cannot make any changes. If you would like to modify it please come back later." + } + }, "DESCRIPTION-OVERVIEW": { "TITLE": "Description", "PUBLIC": "Public", diff --git a/frontend/src/assets/i18n/gr.json b/frontend/src/assets/i18n/gr.json index 5643b078e..0cfa2bb68 100644 --- a/frontend/src/assets/i18n/gr.json +++ b/frontend/src/assets/i18n/gr.json @@ -179,7 +179,8 @@ "HOME": "Home", "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "PLAN-STATUSES": "Plan Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -829,6 +830,54 @@ "UNAUTHORIZED-ORCID": "This ORCID is entered by the Plan creators, without further acknowledgement of the owner." } }, + "PLAN-STATUS-LISTING": { + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "STATUS": "Status", + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "IS-ACTIVE": "Is Active" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "ACTIONS": { + "DELETE": "Delete", + "EDIT": "Edit", + "CREATE-PLAN-STATUS": "Create Plan Status" + } + }, + "PLAN-STATUS-EDITOR": { + "TITLE": { + "NEW": "New Plan Blueprint", + "EDIT": "Edit Plan Blueprint" + }, + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "DEFINITION": "Definition", + "INTERNAL-STATUS": "Internal Status", + "AUTHORIZATION": "Authorization", + "EDIT": "Edit", + "ALLOW-AUTHENTICATED": "Allow authenticated users", + "ALLOW-ANONYMOUS": "Allow anonymous users", + "ROLES": "User roles", + "PLAN-ROLES": "User plan roles" + }, + "ACTIONS": { + "SAVE": "Save", + "CANCEL": "Cancel", + "DELETE": "Delete" + }, + "LOCKED-DIALOG": { + "TITLE": "Plan Status is locked", + "MESSAGE": "Somebody else is modifying the Plan Status at this moment. You may view the Plan Status but you cannot make any changes. If you would like to modify it please come back later." + } + }, "DESCRIPTION-OVERVIEW": { "TITLE": "Description", "PUBLIC": "Public", diff --git a/frontend/src/assets/i18n/hr.json b/frontend/src/assets/i18n/hr.json index 15fa6467d..ac177a817 100644 --- a/frontend/src/assets/i18n/hr.json +++ b/frontend/src/assets/i18n/hr.json @@ -179,7 +179,8 @@ "HOME": "Home", "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "PLAN-STATUSES": "Plan Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -829,6 +830,54 @@ "UNAUTHORIZED-ORCID": "This ORCID is entered by the Plan creators, without further acknowledgement of the owner." } }, + "PLAN-STATUS-LISTING": { + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "STATUS": "Status", + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "IS-ACTIVE": "Is Active" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "ACTIONS": { + "DELETE": "Delete", + "EDIT": "Edit", + "CREATE-PLAN-STATUS": "Create Plan Status" + } + }, + "PLAN-STATUS-EDITOR": { + "TITLE": { + "NEW": "New Plan Blueprint", + "EDIT": "Edit Plan Blueprint" + }, + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "DEFINITION": "Definition", + "INTERNAL-STATUS": "Internal Status", + "AUTHORIZATION": "Authorization", + "EDIT": "Edit", + "ALLOW-AUTHENTICATED": "Allow authenticated users", + "ALLOW-ANONYMOUS": "Allow anonymous users", + "ROLES": "User roles", + "PLAN-ROLES": "User plan roles" + }, + "ACTIONS": { + "SAVE": "Save", + "CANCEL": "Cancel", + "DELETE": "Delete" + }, + "LOCKED-DIALOG": { + "TITLE": "Plan Status is locked", + "MESSAGE": "Somebody else is modifying the Plan Status at this moment. You may view the Plan Status but you cannot make any changes. If you would like to modify it please come back later." + } + }, "DESCRIPTION-OVERVIEW": { "TITLE": "Description", "PUBLIC": "Public", diff --git a/frontend/src/assets/i18n/pl.json b/frontend/src/assets/i18n/pl.json index 3501f314b..cdc7ccd88 100644 --- a/frontend/src/assets/i18n/pl.json +++ b/frontend/src/assets/i18n/pl.json @@ -179,7 +179,8 @@ "HOME": "Home", "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "PLAN-STATUSES": "Plan Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -829,6 +830,54 @@ "UNAUTHORIZED-ORCID": "This ORCID is entered by the Plan creators, without further acknowledgement of the owner." } }, + "PLAN-STATUS-LISTING": { + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "STATUS": "Status", + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "IS-ACTIVE": "Is Active" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "ACTIONS": { + "DELETE": "Delete", + "EDIT": "Edit", + "CREATE-PLAN-STATUS": "Create Plan Status" + } + }, + "PLAN-STATUS-EDITOR": { + "TITLE": { + "NEW": "New Plan Blueprint", + "EDIT": "Edit Plan Blueprint" + }, + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "DEFINITION": "Definition", + "INTERNAL-STATUS": "Internal Status", + "AUTHORIZATION": "Authorization", + "EDIT": "Edit", + "ALLOW-AUTHENTICATED": "Allow authenticated users", + "ALLOW-ANONYMOUS": "Allow anonymous users", + "ROLES": "User roles", + "PLAN-ROLES": "User plan roles" + }, + "ACTIONS": { + "SAVE": "Save", + "CANCEL": "Cancel", + "DELETE": "Delete" + }, + "LOCKED-DIALOG": { + "TITLE": "Plan Status is locked", + "MESSAGE": "Somebody else is modifying the Plan Status at this moment. You may view the Plan Status but you cannot make any changes. If you would like to modify it please come back later." + } + }, "DESCRIPTION-OVERVIEW": { "TITLE": "Description", "PUBLIC": "Public", diff --git a/frontend/src/assets/i18n/pt.json b/frontend/src/assets/i18n/pt.json index 76050ab8d..07e5b97da 100644 --- a/frontend/src/assets/i18n/pt.json +++ b/frontend/src/assets/i18n/pt.json @@ -179,7 +179,8 @@ "HOME": "Home", "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "PLAN-STATUSES": "Plan Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -829,6 +830,54 @@ "UNAUTHORIZED-ORCID": "This ORCID is entered by the Plan creators, without further acknowledgement of the owner." } }, + "PLAN-STATUS-LISTING": { + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "STATUS": "Status", + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "IS-ACTIVE": "Is Active" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "ACTIONS": { + "DELETE": "Delete", + "EDIT": "Edit", + "CREATE-PLAN-STATUS": "Create Plan Status" + } + }, + "PLAN-STATUS-EDITOR": { + "TITLE": { + "NEW": "New Plan Blueprint", + "EDIT": "Edit Plan Blueprint" + }, + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "DEFINITION": "Definition", + "INTERNAL-STATUS": "Internal Status", + "AUTHORIZATION": "Authorization", + "EDIT": "Edit", + "ALLOW-AUTHENTICATED": "Allow authenticated users", + "ALLOW-ANONYMOUS": "Allow anonymous users", + "ROLES": "User roles", + "PLAN-ROLES": "User plan roles" + }, + "ACTIONS": { + "SAVE": "Save", + "CANCEL": "Cancel", + "DELETE": "Delete" + }, + "LOCKED-DIALOG": { + "TITLE": "Plan Status is locked", + "MESSAGE": "Somebody else is modifying the Plan Status at this moment. You may view the Plan Status but you cannot make any changes. If you would like to modify it please come back later." + } + }, "DESCRIPTION-OVERVIEW": { "TITLE": "Description", "PUBLIC": "Public", diff --git a/frontend/src/assets/i18n/sk.json b/frontend/src/assets/i18n/sk.json index 19d84753c..897de7531 100644 --- a/frontend/src/assets/i18n/sk.json +++ b/frontend/src/assets/i18n/sk.json @@ -179,7 +179,8 @@ "HOME": "Home", "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "PLAN-STATUSES": "Plan Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -829,6 +830,54 @@ "UNAUTHORIZED-ORCID": "This ORCID is entered by the Plan creators, without further acknowledgement of the owner." } }, + "PLAN-STATUS-LISTING": { + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "STATUS": "Status", + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "IS-ACTIVE": "Is Active" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "ACTIONS": { + "DELETE": "Delete", + "EDIT": "Edit", + "CREATE-PLAN-STATUS": "Create Plan Status" + } + }, + "PLAN-STATUS-EDITOR": { + "TITLE": { + "NEW": "New Plan Blueprint", + "EDIT": "Edit Plan Blueprint" + }, + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "DEFINITION": "Definition", + "INTERNAL-STATUS": "Internal Status", + "AUTHORIZATION": "Authorization", + "EDIT": "Edit", + "ALLOW-AUTHENTICATED": "Allow authenticated users", + "ALLOW-ANONYMOUS": "Allow anonymous users", + "ROLES": "User roles", + "PLAN-ROLES": "User plan roles" + }, + "ACTIONS": { + "SAVE": "Save", + "CANCEL": "Cancel", + "DELETE": "Delete" + }, + "LOCKED-DIALOG": { + "TITLE": "Plan Status is locked", + "MESSAGE": "Somebody else is modifying the Plan Status at this moment. You may view the Plan Status but you cannot make any changes. If you would like to modify it please come back later." + } + }, "DESCRIPTION-OVERVIEW": { "TITLE": "Description", "PUBLIC": "Public", diff --git a/frontend/src/assets/i18n/sr.json b/frontend/src/assets/i18n/sr.json index 8d23b696a..cfbdafe58 100644 --- a/frontend/src/assets/i18n/sr.json +++ b/frontend/src/assets/i18n/sr.json @@ -179,7 +179,8 @@ "HOME": "Home", "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "PLAN-STATUSES": "Plan Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -829,6 +830,54 @@ "UNAUTHORIZED-ORCID": "This ORCID is entered by the Plan creators, without further acknowledgement of the owner." } }, + "PLAN-STATUS-LISTING": { + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "STATUS": "Status", + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "IS-ACTIVE": "Is Active" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "ACTIONS": { + "DELETE": "Delete", + "EDIT": "Edit", + "CREATE-PLAN-STATUS": "Create Plan Status" + } + }, + "PLAN-STATUS-EDITOR": { + "TITLE": { + "NEW": "New Plan Blueprint", + "EDIT": "Edit Plan Blueprint" + }, + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "DEFINITION": "Definition", + "INTERNAL-STATUS": "Internal Status", + "AUTHORIZATION": "Authorization", + "EDIT": "Edit", + "ALLOW-AUTHENTICATED": "Allow authenticated users", + "ALLOW-ANONYMOUS": "Allow anonymous users", + "ROLES": "User roles", + "PLAN-ROLES": "User plan roles" + }, + "ACTIONS": { + "SAVE": "Save", + "CANCEL": "Cancel", + "DELETE": "Delete" + }, + "LOCKED-DIALOG": { + "TITLE": "Plan Status is locked", + "MESSAGE": "Somebody else is modifying the Plan Status at this moment. You may view the Plan Status but you cannot make any changes. If you would like to modify it please come back later." + } + }, "DESCRIPTION-OVERVIEW": { "TITLE": "Description", "PUBLIC": "Public", diff --git a/frontend/src/assets/i18n/tr.json b/frontend/src/assets/i18n/tr.json index 5b00f3bef..be4e82640 100644 --- a/frontend/src/assets/i18n/tr.json +++ b/frontend/src/assets/i18n/tr.json @@ -179,7 +179,8 @@ "HOME": "Home", "ANNOTATION-STATUSES": "Annotation Statuses", "SUPPORTIVE-MATERIAL": "Supportive Material", - "USAGE-LIMITS": "Usage Limits" + "USAGE-LIMITS": "Usage Limits", + "PLAN-STATUSES": "Plan Statuses" }, "FILE-TRANSFORMER": { "PDF": "PDF", @@ -829,6 +830,54 @@ "UNAUTHORIZED-ORCID": "This ORCID is entered by the Plan creators, without further acknowledgement of the owner." } }, + "PLAN-STATUS-LISTING": { + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "STATUS": "Status", + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "IS-ACTIVE": "Is Active" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "ACTIONS": { + "DELETE": "Delete", + "EDIT": "Edit", + "CREATE-PLAN-STATUS": "Create Plan Status" + } + }, + "PLAN-STATUS-EDITOR": { + "TITLE": { + "NEW": "New Plan Blueprint", + "EDIT": "Edit Plan Blueprint" + }, + "FIELDS": { + "NAME": "Name", + "DESCRIPTION": "Description", + "DEFINITION": "Definition", + "INTERNAL-STATUS": "Internal Status", + "AUTHORIZATION": "Authorization", + "EDIT": "Edit", + "ALLOW-AUTHENTICATED": "Allow authenticated users", + "ALLOW-ANONYMOUS": "Allow anonymous users", + "ROLES": "User roles", + "PLAN-ROLES": "User plan roles" + }, + "ACTIONS": { + "SAVE": "Save", + "CANCEL": "Cancel", + "DELETE": "Delete" + }, + "LOCKED-DIALOG": { + "TITLE": "Plan Status is locked", + "MESSAGE": "Somebody else is modifying the Plan Status at this moment. You may view the Plan Status but you cannot make any changes. If you would like to modify it please come back later." + } + }, "DESCRIPTION-OVERVIEW": { "TITLE": "Description", "PUBLIC": "Public",