From 557c444bc12ca13fd9426c978a8a48bf7097b1d5 Mon Sep 17 00:00:00 2001 From: Diamantis Tziotzios Date: Mon, 30 Oct 2023 15:30:46 +0200 Subject: [PATCH] frontend description template listing component --- .../DescriptionTemplateStatusConverter.java | 7 +- dmp-frontend/src/app/app-routing.module.ts | 12 + .../enum/description-template-field-type.ts | 5 + .../enum/description-template-status.ts | 4 + .../app/core/common/enum/permission.enum.ts | 8 +- .../src/app/core/core-service.module.ts | 4 +- .../description-template-persist.ts | 85 ++ .../description-template.ts | 2 + .../core/query/description-template.lookup.ts | 26 + .../description-template.service.ts | 130 +++ .../services/utilities/enum-utils.service.ts | 16 + .../description-template.module.ts | 41 + .../description-template.routing.ts | 75 ++ ...description-template-editor.component.html | 228 ++++++ ...description-template-editor.component.scss | 116 +++ .../description-template-editor.component.ts | 774 ++++++++++++++++++ .../description-template-editor.model.ts | 354 ++++++++ .../description-template-editor.resolver.ts | 88 ++ .../description-template-editor.service.ts | 15 + ...escription-template-listing.component.html | 125 +++ ...escription-template-listing.component.scss | 83 ++ .../description-template-listing.component.ts | 402 +++++++++ ...on-template-listing-filters.component.html | 36 + ...on-template-listing-filters.component.scss | 25 + ...tion-template-listing-filters.component.ts | 99 +++ ...description-template.dialog.component.html | 41 + ...description-template.dialog.component.scss | 102 +++ ...t-description-template.dialog.component.ts | 61 ++ .../dmp-blueprint-listing.component.html | 4 +- .../misc/navigation/navigation.component.html | 2 +- .../src/app/ui/sidebar/sidebar.component.ts | 4 +- dmp-frontend/src/assets/i18n/en.json | 69 +- 32 files changed, 3008 insertions(+), 35 deletions(-) create mode 100644 dmp-frontend/src/app/core/common/enum/description-template-status.ts create mode 100644 dmp-frontend/src/app/core/model/description-template/description-template-persist.ts create mode 100644 dmp-frontend/src/app/core/query/description-template.lookup.ts create mode 100644 dmp-frontend/src/app/core/services/description-template/description-template.service.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/description-template.module.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/description-template.routing.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.scss create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.model.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.resolver.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.service.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.html create mode 100644 dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.scss create mode 100644 dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/listing/filters/description-template-listing-filters.component.html create mode 100644 dmp-frontend/src/app/ui/admin/description-template/listing/filters/description-template-listing-filters.component.scss create mode 100644 dmp-frontend/src/app/ui/admin/description-template/listing/filters/description-template-listing-filters.component.ts create mode 100644 dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.html create mode 100644 dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.scss create mode 100644 dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.ts diff --git a/dmp-backend/core/src/main/java/eu/eudat/data/converters/enums/DescriptionTemplateStatusConverter.java b/dmp-backend/core/src/main/java/eu/eudat/data/converters/enums/DescriptionTemplateStatusConverter.java index 3cd595685..5b34f73b7 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/data/converters/enums/DescriptionTemplateStatusConverter.java +++ b/dmp-backend/core/src/main/java/eu/eudat/data/converters/enums/DescriptionTemplateStatusConverter.java @@ -1,11 +1,12 @@ package eu.eudat.data.converters.enums; +import eu.eudat.commons.enums.DescriptionTemplateStatus; import eu.eudat.commons.enums.DescriptionTemplateTypeStatus; import jakarta.persistence.Converter; @Converter -public class DescriptionTemplateStatusConverter extends DatabaseEnumConverter { - public DescriptionTemplateTypeStatus of(Short i) { - return DescriptionTemplateTypeStatus.of(i); +public class DescriptionTemplateStatusConverter extends DatabaseEnumConverter { + public DescriptionTemplateStatus of(Short i) { + return DescriptionTemplateStatus.of(i); } } diff --git a/dmp-frontend/src/app/app-routing.module.ts b/dmp-frontend/src/app/app-routing.module.ts index fbd5a9dcc..52c026ed0 100644 --- a/dmp-frontend/src/app/app-routing.module.ts +++ b/dmp-frontend/src/app/app-routing.module.ts @@ -106,6 +106,18 @@ const appRoutes: Routes = [ title: 'GENERAL.TITLES.DATASET-PROFILES' } }, + { + path: 'description-templates', + loadChildren: () => import('./ui/admin/description-template/description-template.module').then(m => m.DescriptionTemplateModule), + data: { + authContext: { + permissions: [AppPermission.ViewDescriptionTemplatePage] + }, + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.DESCRIPTION-TEMPLATES' + }) + } + }, { path: 'description-template-type', loadChildren: () => import('./ui/admin/description-types/description-template-type.module').then(m => m.DescriptionTemplateTypesModule), diff --git a/dmp-frontend/src/app/core/common/enum/description-template-field-type.ts b/dmp-frontend/src/app/core/common/enum/description-template-field-type.ts index e9a71e194..7b621668f 100644 --- a/dmp-frontend/src/app/core/common/enum/description-template-field-type.ts +++ b/dmp-frontend/src/app/core/common/enum/description-template-field-type.ts @@ -1,8 +1,13 @@ export enum DescriptionTemplateFieldType { COMBO_BOX = "combobox", + AUTO_COMPLETE = "autocomplete", + WORD_LIST = "wordlist", BOOLEAN_DECISION = "booleanDecision", RADIO_BOX = "radiobox", INTERNAL_DMP_ENTRIES = "internalDmpEntities", + INTERNAL_DMP_ENTRIES_RESEARCHERS = "internalDmpResearchers", + INTERNAL_DMP_ENTRIES_DMPS = "internalDmpDmps", + INTERNAL_DMP_ENTRIES_DATASETS = "internalDmpDatasets", CHECK_BOX = "checkBox", FREE_TEXT = "freetext", TEXT_AREA = "textarea", diff --git a/dmp-frontend/src/app/core/common/enum/description-template-status.ts b/dmp-frontend/src/app/core/common/enum/description-template-status.ts new file mode 100644 index 000000000..f9de80988 --- /dev/null +++ b/dmp-frontend/src/app/core/common/enum/description-template-status.ts @@ -0,0 +1,4 @@ +export enum DescriptionTemplateStatus { + Draft = 0, + Finalized = 1 +} \ No newline at end of file diff --git a/dmp-frontend/src/app/core/common/enum/permission.enum.ts b/dmp-frontend/src/app/core/common/enum/permission.enum.ts index 124677746..5ec91c37f 100644 --- a/dmp-frontend/src/app/core/common/enum/permission.enum.ts +++ b/dmp-frontend/src/app/core/common/enum/permission.enum.ts @@ -9,8 +9,14 @@ export enum AppPermission { EditDmpBlueprint = "EditDmpBlueprint", DeleteDmpBlueprint = "DeleteDmpBlueprint", + //DescriptionTemplateType + BrowseDescriptionTemplate = "BrowseDescriptionTemplate", + EditDescriptionTemplate = "EditDescriptionTemplate", + DeleteDescriptionTemplate = "DeleteDescriptionTemplate", + // UI Pages ViewDescriptionTemplateTypePage = "ViewDescriptionTemplateTypePage", - ViewDmpBlueprintPage = "ViewDmpBlueprintPage" + ViewDmpBlueprintPage = "ViewDmpBlueprintPage", + ViewDescriptionTemplatePage = "ViewDescriptionTemplatePage", } diff --git a/dmp-frontend/src/app/core/core-service.module.ts b/dmp-frontend/src/app/core/core-service.module.ts index 13f9df1c8..e243a9cc9 100644 --- a/dmp-frontend/src/app/core/core-service.module.ts +++ b/dmp-frontend/src/app/core/core-service.module.ts @@ -57,6 +57,7 @@ import { UserSettingsHttpService } from './services/user-settings/user-settings- import { UserSettingsService } from './services/user-settings/user-settings.service'; import { FileUtils } from './services/utilities/file-utils.service'; import { QueryParamsService } from './services/utilities/query-params.service'; +import { DescriptionTemplateService } from './services/description-template/description-template.service'; // // // This is shared module that provides all the services. Its imported only once on the AppModule. @@ -132,7 +133,8 @@ export class CoreServiceModule { UserSettingsHttpService, FilterService, FileUtils, - ReferenceService + ReferenceService, + DescriptionTemplateService ], }; } diff --git a/dmp-frontend/src/app/core/model/description-template/description-template-persist.ts b/dmp-frontend/src/app/core/model/description-template/description-template-persist.ts new file mode 100644 index 000000000..ae052b1f5 --- /dev/null +++ b/dmp-frontend/src/app/core/model/description-template/description-template-persist.ts @@ -0,0 +1,85 @@ +import { DescriptionTemplateStatus } from "@app/core/common/enum/description-template-status"; +import { BaseEntityPersist } from "@common/base/base-entity.model"; +import { Guid } from "@common/types/guid"; +import { DescriptionTemplateType } from "../description-template-type/description-template-type"; + + +export interface DescriptionTemplatePersist extends BaseEntityPersist { + label: string; + description: string; + groupId: Guid; + version: string; + language: string; + type: DescriptionTemplateType; + status: DescriptionTemplateStatus; + definition: DescriptionTemplateDefinitionPersist; +} + +export interface DescriptionTemplateDefinitionPersist { + pages?: DescriptionTemplatePagePersist[]; + sections?: DescriptionTemplateSectionPersist[]; +} + + +export interface DescriptionTemplatePagePersist { + id: Guid; + ordinal: number; + title: string; +} + +export interface DescriptionTemplateSectionPersist { + id: Guid; + ordinal: number; + defaultVisibility: boolean; + multiplicity: boolean; + numbering: string; + page: string; + title: string; + description: string; + extendedDescription: string; + + sections?: DescriptionTemplateSectionPersist[]; + fieldSets: DescriptionTemplateFieldSetPersist[]; +} + +export interface DescriptionTemplateFieldSetPersist { + id: Guid; + ordinal: number; + numbering: string; + title: string; + description: string; + extendedDescription: string; + additionalInformation: string; + multiplicity: DescriptionTemplateMultiplicityPersist + hasCommentField: boolean; + fields: DescriptionTemplateFieldPersist[]; +} + +export interface DescriptionTemplateFieldPersist { + id: Guid; + ordinal: number; + numbering: string; + schematics: string[]; + defaultValue: string; + visibilityRules: DescriptionTemplateRulePersist[]; + // validations: DescriptionTemplateFieldValidationType[]; + // fieldType: DescriptionTemplateFieldType; + includeInExport: boolean; + data: DescriptionTemplateBaseFieldDataPersist; +} + +export interface DescriptionTemplateRulePersist { + target: string; + value: string; +} + +export interface DescriptionTemplateMultiplicityPersist { + min: number; + max: number; + placeholder: string; + tableView: boolean; +} + +export interface DescriptionTemplateBaseFieldDataPersist { + label: string; +} \ No newline at end of file diff --git a/dmp-frontend/src/app/core/model/description-template/description-template.ts b/dmp-frontend/src/app/core/model/description-template/description-template.ts index 6a9d34abf..cd14d67f4 100644 --- a/dmp-frontend/src/app/core/model/description-template/description-template.ts +++ b/dmp-frontend/src/app/core/model/description-template/description-template.ts @@ -3,6 +3,7 @@ import { DescriptionTemplateFieldDataExternalDatasetType } from "@app/core/commo import { DescriptionTemplateFieldDataInternalDmpEntryType } from "@app/core/common/enum/description-template-field-data-internal-dmp-entry-type"; import { DescriptionTemplateFieldType } from "@app/core/common/enum/description-template-field-type"; import { DescriptionTemplateFieldValidationType } from "@app/core/common/enum/description-template-field-validation-type"; +import { DescriptionTemplateStatus } from "@app/core/common/enum/description-template-status"; import { BaseEntity } from "@common/base/base-entity.model"; import { Guid } from "@common/types/guid"; import { DescriptionTemplateType } from "../description-template-type/description-template-type"; @@ -15,6 +16,7 @@ export interface DescriptionTemplate extends BaseEntity { version: string; language: string; type: DescriptionTemplateType; + status: DescriptionTemplateStatus; definition: DescriptionTemplateDefinition; } diff --git a/dmp-frontend/src/app/core/query/description-template.lookup.ts b/dmp-frontend/src/app/core/query/description-template.lookup.ts new file mode 100644 index 000000000..1e7f2b836 --- /dev/null +++ b/dmp-frontend/src/app/core/query/description-template.lookup.ts @@ -0,0 +1,26 @@ +import { Lookup } from '@common/model/lookup'; +import { Guid } from '@common/types/guid'; +import { DescriptionTemplateStatus } from '../common/enum/description-template-status'; +import { IsActive } from '../common/enum/is-active.enum'; + +export class DescriptionTemplateLookup extends Lookup implements DescriptionTemplateFilter { + ids: Guid[]; + excludedIds: Guid[]; + like: string; + isActive: IsActive[]; + typeIds: Guid[]; + statuses: DescriptionTemplateStatus[]; + + constructor() { + super(); + } +} + +export interface DescriptionTemplateFilter { + ids: Guid[]; + excludedIds: Guid[]; + like: string; + isActive: IsActive[]; + typeIds: Guid[]; + statuses: DescriptionTemplateStatus[]; +} diff --git a/dmp-frontend/src/app/core/services/description-template/description-template.service.ts b/dmp-frontend/src/app/core/services/description-template/description-template.service.ts new file mode 100644 index 000000000..cf3ad57f4 --- /dev/null +++ b/dmp-frontend/src/app/core/services/description-template/description-template.service.ts @@ -0,0 +1,130 @@ +import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { IsActive } from '@app/core/common/enum/is-active.enum'; +import { DataTableData } from '@app/core/model/data-table/data-table-data'; +import { DescriptionTemplate } from '@app/core/model/description-template/description-template'; +import { DescriptionTemplatePersist } from '@app/core/model/description-template/description-template-persist'; +import { DescriptionTemplateLookup } from '@app/core/query/description-template.lookup'; +import { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration'; +import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration'; +import { BaseHttpParams } from '@common/http/base-http-params'; +import { InterceptorType } from '@common/http/interceptors/interceptor-type'; +import { QueryResult } from '@common/model/query-result'; +import { FilterService } from '@common/modules/text-filter/filter-service'; +import { Guid } from '@common/types/guid'; +import { Observable, throwError } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; +import { nameof } from 'ts-simple-nameof'; +import { ConfigurationService } from '../configuration/configuration.service'; +import { BaseHttpV2Service } from '../http/base-http-v2.service'; + +@Injectable() +export class DescriptionTemplateService { + + private headers = new HttpHeaders(); + + constructor(private http: BaseHttpV2Service, private httpClient: HttpClient, private configurationService: ConfigurationService, private filterService: FilterService) { + } + + private get apiBase(): string { return `${this.configurationService.server}description-template`; } + + query(q: DescriptionTemplateLookup): 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: DescriptionTemplatePersist): 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))); + } + + clone(id: Guid, reqFields: string[] = []): Observable { + const url = `${this.apiBase}/clone/${id}`; + const options = { params: { f: reqFields } }; + + return this.http + .get(url, options).pipe( + catchError((error: any) => throwError(error))); + } + + downloadXML(id: Guid): Observable> { + const url = `${this.apiBase}/xml/export/${id}`; + let headerXml: HttpHeaders = this.headers.set('Content-Type', 'application/xml'); + const params = new BaseHttpParams(); + params.interceptorContext = { + excludedInterceptors: [InterceptorType.JSONContentType] + }; + return this.httpClient.get(url, { params: params, responseType: 'blob', observe: 'response', headers: headerXml }); + } + + uploadFile(file: FileList, labelSent: string, reqFields: string[] = []): Observable> { + const url = `${this.apiBase}/xml/import`; + const params = new BaseHttpParams(); + params.interceptorContext = { + excludedInterceptors: [InterceptorType.JSONContentType] + }; + const formData = new FormData(); + formData.append('file', file[0], labelSent); + return this.http.post(url, formData, { params: params }); + } + + // + // Autocomplete Commons + // + // tslint:disable-next-line: member-ordering + singleAutocompleteConfiguration: SingleAutoCompleteConfiguration = { + initialItems: (data?: any) => this.query(this.buildAutocompleteLookup()).pipe(map(x => x.items)), + filterFn: (searchQuery: string, data?: any) => this.query(this.buildAutocompleteLookup(searchQuery)).pipe(map(x => x.items)), + getSelectedItem: (selectedItem: any) => this.query(this.buildAutocompleteLookup(null, null, [selectedItem])).pipe(map(x => x.items[0])), + displayFn: (item: DescriptionTemplate) => item.label, + titleFn: (item: DescriptionTemplate) => item.label, + valueAssign: (item: DescriptionTemplate) => item.id, + }; + + // tslint:disable-next-line: member-ordering + multipleAutocompleteConfiguration: MultipleAutoCompleteConfiguration = { + initialItems: (excludedItems: any[], data?: any) => this.query(this.buildAutocompleteLookup(null, excludedItems ? excludedItems : null)).pipe(map(x => x.items)), + filterFn: (searchQuery: string, excludedItems: any[]) => this.query(this.buildAutocompleteLookup(searchQuery, excludedItems)).pipe(map(x => x.items)), + getSelectedItems: (selectedItems: any[]) => this.query(this.buildAutocompleteLookup(null, null, selectedItems)).pipe(map(x => x.items)), + displayFn: (item: DescriptionTemplate) => item.label, + titleFn: (item: DescriptionTemplate) => item.label, + valueAssign: (item: DescriptionTemplate) => item.id, + }; + + private buildAutocompleteLookup(like?: string, excludedIds?: Guid[], ids?: Guid[]): DescriptionTemplateLookup { + const lookup: DescriptionTemplateLookup = new DescriptionTemplateLookup(); + 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.project = { + fields: [ + nameof(x => x.id), + nameof(x => x.label) + ] + }; + lookup.order = { items: [nameof(x => x.label)] }; + if (like) { lookup.like = this.filterService.transformLike(like); } + return lookup; + } +} diff --git a/dmp-frontend/src/app/core/services/utilities/enum-utils.service.ts b/dmp-frontend/src/app/core/services/utilities/enum-utils.service.ts index a0436f024..e5485c9f9 100644 --- a/dmp-frontend/src/app/core/services/utilities/enum-utils.service.ts +++ b/dmp-frontend/src/app/core/services/utilities/enum-utils.service.ts @@ -15,6 +15,8 @@ import { RoleOrganizationType } from '@app/core/common/enum/role-organization-ty import { ViewStyleType } from '@app/ui/admin/dataset-profile/editor/components/field/view-style-enum'; import { DescriptionTemplateTypeStatus } from '@app/core/common/enum/description-template-type-status'; import { DmpBlueprintSystemFieldType } from '@app/core/common/enum/dmp-blueprint-system-field-type'; +import { DmpBlueprintStatus } from '@app/core/common/enum/dmp-blueprint-status'; +import { DescriptionTemplateStatus } from '@app/core/common/enum/description-template-status'; @Injectable() export class EnumUtils { @@ -195,6 +197,20 @@ export class EnumUtils { } } + toDescriptionTemplateStatusString(status: DescriptionTemplateStatus): string { + switch (status) { + case DescriptionTemplateStatus.Draft: return this.language.instant('TYPES.DESCRIPTION-TEMPLATE-STATUS.DRAFT'); + case DescriptionTemplateStatus.Finalized: return this.language.instant('TYPES.DESCRIPTION-TEMPLATE-STATUS.FINALIZED'); + } + } + + toDmpBlueprintStatusString(status: DmpBlueprintStatus): string { + switch (status) { + case DmpBlueprintStatus.Draft: return this.language.instant('TYPES.DMP-BLUEPRINT-STATUS.DRAFT'); + case DmpBlueprintStatus.Finalized: return this.language.instant('TYPES.DMP-BLUEPRINT-STATUS.FINALIZED'); + } + } + toDmpBlueprintSystemFieldTypeString(status: DmpBlueprintSystemFieldType): string { switch (status) { case DmpBlueprintSystemFieldType.TEXT: return this.language.instant('TYPES.DMP-BLUEPRINT-SYSTEM-FIELD-TYPE.TEXT'); diff --git a/dmp-frontend/src/app/ui/admin/description-template/description-template.module.ts b/dmp-frontend/src/app/ui/admin/description-template/description-template.module.ts new file mode 100644 index 000000000..a321f7ba1 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/description-template.module.ts @@ -0,0 +1,41 @@ +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { NgModule } from "@angular/core"; +import { AutoCompleteModule } from "@app/library/auto-complete/auto-complete.module"; +import { UrlListingModule } from '@app/library/url-listing/url-listing.module'; +import { CommonFormattingModule } from '@common/formatting/common-formatting.module'; +import { CommonFormsModule } from '@common/forms/common-forms.module'; +import { ConfirmationDialogModule } from '@common/modules/confirmation-dialog/confirmation-dialog.module'; +import { HybridListingModule } from "@common/modules/hybrid-listing/hybrid-listing.module"; +import { TextFilterModule } from "@common/modules/text-filter/text-filter.module"; +import { UserSettingsModule } from "@common/modules/user-settings/user-settings.module"; +import { CommonUiModule } from '@common/ui/common-ui.module'; +import { NgxDropzoneModule } from "ngx-dropzone"; +import { DescriptionTemplateRoutingModule } from './description-template.routing'; +import { DescriptionTemplateEditorComponent } from './editor/description-template-editor.component'; +import { DescriptionTemplateListingComponent } from './listing/description-template-listing.component'; +import { DescriptionTemplateListingFiltersComponent } from "./listing/filters/description-template-listing-filters.component"; +import { ImportDescriptionTemplateDialogComponent } from './listing/import-dmp-blueprint/import-description-template.dialog.component'; + +@NgModule({ + imports: [ + CommonUiModule, + CommonFormsModule, + UrlListingModule, + ConfirmationDialogModule, + DescriptionTemplateRoutingModule, + NgxDropzoneModule, + DragDropModule, + AutoCompleteModule, + HybridListingModule, + TextFilterModule, + UserSettingsModule, + CommonFormattingModule + ], + declarations: [ + // DescriptionTemplateEditorComponent, + DescriptionTemplateListingComponent, + DescriptionTemplateListingFiltersComponent, + ImportDescriptionTemplateDialogComponent + ] +}) +export class DescriptionTemplateModule { } diff --git a/dmp-frontend/src/app/ui/admin/description-template/description-template.routing.ts b/dmp-frontend/src/app/ui/admin/description-template/description-template.routing.ts new file mode 100644 index 000000000..b687b8c33 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/description-template.routing.ts @@ -0,0 +1,75 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { AuthGuard } from '@app/core/auth-guard.service'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service'; +import { PendingChangesGuard } from '@common/forms/pending-form-changes/pending-form-changes-guard.service'; +import { DescriptionTemplateEditorComponent } from './editor/description-template-editor.component'; +import { DescriptionTemplateEditorResolver } from './editor/description-template-editor.resolver'; +import { DescriptionTemplateListingComponent } from './listing/description-template-listing.component'; + +const routes: Routes = [ + { + path: '', + component: DescriptionTemplateListingComponent, + canActivate: [AuthGuard] + }, + { + path: 'new', + canActivate: [AuthGuard], + component: DescriptionTemplateEditorComponent, + canDeactivate: [PendingChangesGuard], + data: { + authContext: { + permissions: [AppPermission.EditDescriptionTemplate] + }, + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.NEW-DMP-BLUEPRINT' + }) + } + }, + { + path: 'clone/:cloneid', + canActivate: [AuthGuard], + component: DescriptionTemplateEditorComponent, + canDeactivate: [PendingChangesGuard], + resolve: { + 'entity': DescriptionTemplateEditorResolver + }, + data: { + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.EDIT-DMP-BLUEPRINT' + }), + authContext: { + permissions: [AppPermission.EditDescriptionTemplate] + } + } + + }, + { + path: ':id', + canActivate: [AuthGuard], + component: DescriptionTemplateEditorComponent, + canDeactivate: [PendingChangesGuard], + resolve: { + 'entity': DescriptionTemplateEditorResolver + }, + data: { + ...BreadcrumbService.generateRouteDataConfiguration({ + title: 'BREADCRUMBS.EDIT-DMP-BLUEPRINT' + }), + authContext: { + permissions: [AppPermission.EditDescriptionTemplate] + } + } + + }, + { path: '**', loadChildren: () => import('@common/modules/page-not-found/page-not-found.module').then(m => m.PageNotFoundModule) }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [DescriptionTemplateEditorResolver] +}) +export class DescriptionTemplateRoutingModule { } diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html new file mode 100644 index 000000000..3f73a47f1 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.html @@ -0,0 +1,228 @@ + \ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.scss b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.scss new file mode 100644 index 000000000..04e273a10 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.scss @@ -0,0 +1,116 @@ +.description-template-editor { + margin-top: 1.3rem; + margin-left: 1em; + margin-right: 3em; + + .remove { + background-color: white; + color: black; + } + + .add { + background-color: white; + color: #009700; + } +} + +::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background, .mat-checkbox-indeterminate.mat-accent .mat-checkbox-background { + background-color: var(--primary-color-3); + // background-color: #0070c0; +} + +::ng-deep .mat-checkbox-disabled.mat-checkbox-checked .mat-checkbox-background, .mat-checkbox-disabled.mat-checkbox-indeterminate .mat-checkbox-background { + background-color: #b0b0b0; +} + +.finalize-btn { + border-radius: 30px; + border: 1px solid var(--primary-color); + background: transparent; + padding-left: 2em; + padding-right: 2em; + box-shadow: 0px 3px 6px #1E202029; + color: var(--primary-color); + &:disabled{ + background-color: #CBCBCB; + color: #FFF; + border: 0px; + } +} + +.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; + } +} + +.dlt-section-btn { + margin: 0; + position: absolute; + top: 50%; + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} + +.section-input { + position: relative; +} + +.section-input .arrows { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); +} + +.action-list-item{ + display: flex; + align-items: center; + cursor: pointer; + + .action-list-icon{ + font-size: 1.2em; + // padding-right: 1em; + // width: 14px; + // margin-right: 0.5em; + // margin-left: -.09em; + // height: auto; + color: var(--primary-color); + } + + .action-list-text{ + font-size: 1em; + color: var(--primary-color); + } +} + +.field-delete{ + align-items: center; + display: flex; + cursor: pointer; + + .field-delete-icon{ + font-size: 1.2em; + width: 14px; + color: var(--primary-color); + } + + .field-delete-text{ + font-size: 1em; + margin-left: 0.5em; + color: var(--primary-color); + } +} \ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.ts new file mode 100644 index 000000000..56c3e2f27 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.component.ts @@ -0,0 +1,774 @@ + +import { Component, OnInit } from '@angular/core'; +import { FormArray, UntypedFormGroup } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, Router } from '@angular/router'; +import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; +import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; +// import { BreadcrumbItem } from '@app/ui/misc/breadcrumb/definition/breadcrumb-item'; +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { DatePipe } from '@angular/common'; +import { DescriptionTemplateStatus } from '@app/core/common/enum/description-template-status'; +import { IsActive } from '@app/core/common/enum/is-active.enum'; +import { AppPermission } from '@app/core/common/enum/permission.enum'; +import { DataTableRequest } from '@app/core/model/data-table/data-table-request'; +import { DatasetProfileModel } from '@app/core/model/dataset/dataset-profile'; +import { DescriptionTemplate } from '@app/core/model/description-template/description-template'; +import { DatasetProfileCriteria } from '@app/core/query/dataset-profile/dataset-profile-criteria'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { DmpService } from '@app/core/services/dmp/dmp.service'; +import { LoggingService } from '@app/core/services/logging/logging-service'; +import { MatomoService } from '@app/core/services/matomo/matomo-service'; +import { FileUtils } from '@app/core/services/utilities/file-utils.service'; +import { QueryParamsService } from '@app/core/services/utilities/query-params.service'; +import { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration'; +import { BaseEditor } from '@common/base/base-editor'; +import { FormService } from '@common/forms/form-service'; +import { FormValidationErrorsDialogComponent } from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.component'; +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 * as FileSaver from 'file-saver'; +import { Observable } from 'rxjs'; +import { map, takeUntil } from 'rxjs/operators'; +import { DescriptionTemplateEditorResolver } from './description-template-editor.resolver'; +import { DescriptionTemplateEditorService } from './description-template-editor.service'; +import { DescriptionTemplateEditorModel } from './description-template-editor.model'; +import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service'; +import { DescriptionTemplatePersist } from '@app/core/model/description-template/description-template-persist'; + + +@Component({ + selector: 'app-description-template-editor-component', + templateUrl: 'description-template-editor.component.html', + styleUrls: ['./description-template-editor.component.scss'], + providers: [DescriptionTemplateEditorService] +}) +export class DescriptionTemplateEditorComponent extends BaseEditor implements OnInit { + + isNew = true; + isDeleted = false; + formGroup: UntypedFormGroup = null; + showInactiveDetails = false; + // selectedSystemFields: Array = []; + // descriptionTemplateSectionFieldCategory = DescriptionTemplateSectionFieldCategory; + // descriptionTemplateSystemFieldType = DescriptionTemplateSystemFieldType; + // public descriptionTemplateSystemFieldTypeEnum = this.enumUtils.getEnumValues(DescriptionTemplateSystemFieldType); + // descriptionTemplateExtraFieldDataType = DescriptionTemplateExtraFieldDataType; + // public descriptionTemplateExtraFieldDataTypeEnum = this.enumUtils.getEnumValues(DescriptionTemplateExtraFieldDataType); + + // blueprintsAutoCompleteConfiguration: MultipleAutoCompleteConfiguration = { + // filterFn: this.filterDescriptionTempaltes.bind(this), + // initialItems: (excludedItems: any[]) => this.filterDescriptionTempaltes('').pipe(map(result => result.filter(resultItem => (excludedItems || []).map(x => x.id).indexOf(resultItem.id) === -1))), + // displayFn: (item: DatasetProfileModel) => item.label, + // titleFn: (item: DatasetProfileModel) => item.label, + // subtitleFn: (item: DatasetProfileModel) => item.description, + // popupItemActionIcon: 'visibility' + // }; + + protected get canDelete(): boolean { + return !this.isDeleted && !this.isNew && this.hasPermission(this.authService.permissionEnum.DeleteDescriptionTemplate); + } + + protected get canSave(): boolean { + return !this.isDeleted && this.hasPermission(this.authService.permissionEnum.EditDescriptionTemplate); + } + + protected get canFinalize(): boolean { + return !this.isDeleted && this.hasPermission(this.authService.permissionEnum.EditDescriptionTemplate); + } + + + private hasPermission(permission: AppPermission): boolean { + return this.authService.hasPermission(permission) || this.editorModel?.permissions?.includes(permission); + } + + constructor( + // BaseFormEditor injected dependencies + protected dialog: MatDialog, + protected language: TranslateService, + protected formService: FormService, + protected router: Router, + protected uiNotificationService: UiNotificationService, + protected httpErrorHandlingService: HttpErrorHandlingService, + protected filterService: FilterService, + protected datePipe: DatePipe, + protected route: ActivatedRoute, + protected queryParamsService: QueryParamsService, + // Rest dependencies. Inject any other needed deps here: + public authService: AuthService, + public enumUtils: EnumUtils, + private descriptionTemplateService: DescriptionTemplateService, + private logger: LoggingService, + private descriptionTemplateEditorService: DescriptionTemplateEditorService, + private fileUtils: FileUtils, + private matomoService: MatomoService, + private dmpService: DmpService + ) { + super(dialog, language, formService, router, uiNotificationService, httpErrorHandlingService, filterService, datePipe, route, queryParamsService); + } + + ngOnInit(): void { + this.matomoService.trackPageView('Admin: DMP Blueprints'); + super.ngOnInit(); + } + + getItem(itemId: Guid, successFunction: (item: DescriptionTemplate) => void) { + this.descriptionTemplateService.getSingle(itemId, DescriptionTemplateEditorResolver.lookupFields()) + .pipe(map(data => data as DescriptionTemplate), takeUntil(this._destroyed)) + .subscribe( + data => successFunction(data), + error => this.onCallbackError(error) + ); + } + + prepareForm(data: DescriptionTemplate) { + try { + this.editorModel = data ? new DescriptionTemplateEditorModel().fromModel(data) : new DescriptionTemplateEditorModel(); + this.isDeleted = data ? data.isActive === IsActive.Inactive : false; + this.buildForm(); + } catch (error) { + this.logger.error('Could not parse descriptionTemplate item: ' + data + error); + this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error); + } + } + + buildForm() { + this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !this.authService.hasPermission(AppPermission.EditDescriptionTemplate)); + //this.selectedSystemFields = this.selectedSystemFieldDisabled(); + this.descriptionTemplateEditorService.setValidationErrorModel(this.editorModel.validationErrorModel); + if (this.editorModel.status == DescriptionTemplateStatus.Finalized) { + this.formGroup.disable(); + } + } + + refreshData(): void { + this.getItem(this.editorModel.id, (data: DescriptionTemplate) => this.prepareForm(data)); + } + + refreshOnNavigateToData(id?: Guid): void { + this.formGroup.markAsPristine(); + let route = []; + + if (id === null) { + route.push('../..'); + } else if (this.isNew) { + route.push('../' + id); + } else { + route.push('..'); + } + + this.router.navigate(route, { queryParams: { 'lookup': this.queryParamsService.serializeLookup(this.lookupParams), 'lv': ++this.lv }, replaceUrl: true, relativeTo: this.route }); + } + + persistEntity(onSuccess?: (response) => void): void { + const formData = this.formService.getValue(this.formGroup.value) as DescriptionTemplatePersist; + + this.descriptionTemplateService.persist(formData) + .pipe(takeUntil(this._destroyed)).subscribe( + complete => onSuccess ? onSuccess(complete) : this.onCallbackSuccess(complete), + error => this.onCallbackError(error) + ); + } + + formSubmit(): void { + this.formService.touchAllFormFields(this.formGroup); + if (!this.isFormValid()) { + return; + } + + this.persistEntity(); + } + + public delete() { + const value = this.formGroup.value; + if (value.id) { + const dialogRef = this.dialog.open(ConfirmationDialogComponent, { + maxWidth: '300px', + data: { + message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'), + confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'), + cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL') + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { + if (result) { + this.descriptionTemplateService.delete(value.id).pipe(takeUntil(this._destroyed)) + .subscribe( + complete => this.onCallbackSuccess(), + error => this.onCallbackError(error) + ); + } + }); + } + } + + clearErrorModel() { + this.editorModel.validationErrorModel.clear(); + this.formService.validateAllFormFields(this.formGroup); + } + + // // + // // + // // Sections + // // + // // + // addSection(): void { + // const section: DescriptionTemplateDefinitionSectionEditorModel = new DescriptionTemplateDefinitionSectionEditorModel(); + // section.id = Guid.create(); + // section.ordinal = (this.formGroup.get('definition').get('sections') as FormArray).controls.length + 1; + // section.hasTemplates = false; + // (this.formGroup.get('definition').get('sections') as FormArray).push(section.buildForm()); //TODO: dtziotzios validation path + // } + + // removeSection(sectionIndex: number): void { + // (this.formGroup.get('definition').get('sections') as FormArray).removeAt(sectionIndex); + // (this.formGroup.get('definition').get('sections') as FormArray).controls.forEach((section, index) => { + // section.get('ordinal').setValue(index + 1); + // }); + // } + + // dropSections(event: CdkDragDrop) { + // const sectionsFormArray = (this.formGroup.get('definition').get('sections') as FormArray); + + // moveItemInArray(sectionsFormArray.controls, event.previousIndex, event.currentIndex); + // sectionsFormArray.updateValueAndValidity(); + // sectionsFormArray.controls.forEach((section, index) => { + // section.get('ordinal').setValue(index + 1); + // }); + // } + + + // // + // // + // // Fields + // // + // // + // systemFieldDisabled(systemField: DescriptionTemplateSystemFieldType) { + // return (this.formGroup.get('definition').get('sections') as FormArray)?.controls.some(x => (x.get('fields') as FormArray).controls.some(y => (y as UntypedFormGroup).get('systemFieldType')?.value === systemField)); + // // for (let section in (this.formGroup.get('definition').get('sections')as FormArray)?.controls) { + // // if (i != sectionIndex) { + // // for (let f of this.fieldsArray(i).controls) { + // // if ((f.get('category').value == FieldCategory.SYSTEM || f.get('category').value == DescriptionTemplateSectionFieldCategory.SYSTEM) && f.get('type').value == systemField) { + // // return true; + // // } + // // } + // // } + // // i++; + // // } + // // return false; + // } + + // selectedSystemFieldDisabled(): Array { + // return (this.formGroup.get('definition').get('sections') as FormArray)?.controls.flatMap(x => (x.get('fields') as FormArray).controls.map(y => (y as UntypedFormGroup).get('systemFieldType')?.value as DescriptionTemplateSystemFieldType)); + // } + + // addSystemField(sectionIndex: number, systemFieldType: DescriptionTemplateSystemFieldType): void { + // const field: FieldInSectionEditorModel = new FieldInSectionEditorModel(); + // field.id = Guid.create(); + // field.ordinal = ((this.formGroup.get('definition').get('sections') as FormArray).at(sectionIndex).get('fields') as FormArray).length + 1; + // field.category = DescriptionTemplateSectionFieldCategory.SYSTEM; + // field.systemFieldType = systemFieldType; + // field.required = (systemFieldType == DescriptionTemplateSystemFieldType.TEXT || systemFieldType == DescriptionTemplateSystemFieldType.HTML_TEXT) ? true : false; + // ((this.formGroup.get('definition').get('sections') as FormArray).at(sectionIndex).get('fields') as FormArray).push(field.buildForm()); //TODO: dtziotzios validation path + // } + + // removeSystemField(sectionIndex: number, fieldIndex: number): void { + // const formArray = ((this.formGroup.get('definition').get('sections') as FormArray).at(sectionIndex).get('fields') as FormArray); + // formArray.removeAt(fieldIndex); + // formArray.controls.forEach((section, index) => { + // section.get('ordinal').setValue(index + 1); + // }); + // } + + // addExtraField(sectionIndex: number): void { + // const field: FieldInSectionEditorModel = new FieldInSectionEditorModel(); + // field.id = Guid.create(); + // field.ordinal = ((this.formGroup.get('definition').get('sections') as FormArray).at(sectionIndex).get('fields') as FormArray).length + 1; + // field.category = DescriptionTemplateSectionFieldCategory.EXTRA; + // ((this.formGroup.get('definition').get('sections') as FormArray).at(sectionIndex).get('fields') as FormArray).push(field.buildForm()); //TODO: dtziotzios validation path + // } + + // removeExtraField(sectionIndex: number, fieldIndex: number): void { + // const formArray = ((this.formGroup.get('definition').get('sections') as FormArray).at(sectionIndex).get('fields') as FormArray); + // formArray.removeAt(fieldIndex); + // formArray.controls.forEach((section, index) => { + // section.get('ordinal').setValue(index + 1); + // }); + // } + + + // dropFields(event: CdkDragDrop, sectionIndex: number) { + // const fieldsFormArray = ((this.formGroup.get('definition').get('sections') as FormArray).at(sectionIndex).get('fields') as FormArray); + + // moveItemInArray(fieldsFormArray.controls, event.previousIndex, event.currentIndex); + // fieldsFormArray.updateValueAndValidity(); + // fieldsFormArray.controls.forEach((section, index) => { + // section.get('ordinal').setValue(index + 1); + // }); + // } + + // // + // // + // // Autocomplete configuration + // // + // // + + // filterDescriptionTempaltes(value: string): Observable { + // const request = new DataTableRequest(null, null, { fields: ['+label'] }); + // const criteria = new DatasetProfileCriteria(); + // criteria.like = value; + // request.criteria = criteria; + // return this.dmpService.searchDescriptionTemplates(request); + // } + + // onRemoveDescritionTemplate(event, sectionIndex: number) { + // const descriptionTemplateFormArray = (this.formGroup.get('definition').get('sections') as FormArray).at(sectionIndex).get('descriptionTemplates') as FormArray; + // const foundIndex = descriptionTemplateFormArray.controls.findIndex(blueprint => blueprint.get('descriptionTemplateId').value === event.id); + // if (foundIndex !== -1) { + // descriptionTemplateFormArray.removeAt(foundIndex); + // } + // } + + // onSelectDescritionTemplate(item, sectionIndex) { + // const descriptionTemplate: DescriptionTemplatesInSectionEditorModel = new DescriptionTemplatesInSectionEditorModel(); + // descriptionTemplate.id = Guid.create(); + // descriptionTemplate.descriptionTemplateId = item.id; + // descriptionTemplate.label = item.label; + // ((this.formGroup.get('definition').get('sections') as FormArray).at(sectionIndex).get('descriptionTemplates') as FormArray).push(descriptionTemplate.buildForm()); //TODO: dtziotzios validation path + // } + + // ngAfterViewInit() { + + + + // this.route.params + // .pipe(takeUntil(this._destroyed)) + // .subscribe((params: Params) => { + // this.descriptionTemplateId = params['id']; + // const cloneId = params['cloneid']; + + // if (this.descriptionTemplateId != null) { + // this.isNew = false; + // this.descriptionTemplateService.getSingleBlueprint(this.descriptionTemplateId).pipe(map(data => data as any)) + // .pipe(takeUntil(this._destroyed)) + // .subscribe(data => { + // this.descriptionTemplateEditor = new DescriptionTemplateEditor().fromModel(data); + // this.formGroup = this.descriptionTemplateEditor.buildForm(); + // this.buildSystemFields(); + // this.fillDescriptionTemplatesInMultAutocomplete(); + // if (this.descriptionTemplateEditor.status == DescriptionTemplateStatus.Finalized) { + // this.formGroup.disable(); + // this.viewOnly = true + // } + // // this.breadCrumbs = observableOf([{ + // // parentComponentName: 'DescriptionTemplateListingComponent', + // // label: this.language.instant('NAV-BAR.TEMPLATE'), + // // url: '/description-templates/' + this.descriptionTemplateId + // // }]); + // }); + // } else if (cloneId != null) { + // this.isClone = true; + // this.descriptionTemplateService.clone(cloneId).pipe(map(data => data as any), takeUntil(this._destroyed)) + // .subscribe( + // data => { + // this.descriptionTemplateEditor = new DescriptionTemplateEditor().fromModel(data); + // this.descriptionTemplateEditor.id = null; + // this.descriptionTemplateEditor.status = DescriptionTemplateStatus.Draft; + // this.formGroup = this.descriptionTemplateEditor.buildForm(); + // this.buildSystemFields(); + // this.fillDescriptionTemplatesInMultAutocomplete(); + // }, + // error => this.onCallbackError(error) + // ); + // } else { + // this.descriptionTemplateEditorModel = new DescriptionTemplateEditorModel(); + // this.descriptionTemplateEditor = new DescriptionTemplateEditor(); + // setTimeout(() => { + // // this.formGroup = this.descriptionTemplateModel.buildForm(); + // // this.addField(); + // this.descriptionTemplateEditor.status = DescriptionTemplateStatus.Draft; + // this.formGroup = this.descriptionTemplateEditor.buildForm(); + // }); + // // this.breadCrumbs = observableOf([{ + // // parentComponentName: 'DescriptionTemplateListingComponent', + // // label: this.language.instant('NAV-BAR.TEMPLATE'), + // // url: '/description-templates/' + this.descriptionTemplateId + // // }]); + // } + // }); + + // } + + // buildSystemFields() { + // const sections = this.sectionsArray().controls.length; + // for (let i = 0; i < sections; i++) { + // let systemFieldsInSection = new Array(); + // this.fieldsArray(i).controls.forEach((field) => { + // if ((field.get('category').value == FieldCategory.SYSTEM || field.get('category').value == DescriptionTemplateSectionFieldCategory.SYSTEM)) { + // systemFieldsInSection.push(this.fieldList.find(f => f.type == field.get('type').value).type); + // } + // }) + // this.systemFieldListPerSection.push(systemFieldsInSection); + // } + // } + + // fillDescriptionTemplatesInMultAutocomplete() { + // const sections = this.sectionsArray().controls.length; + // for (let i = 0; i < sections; i++) { + // let descriptionTemplatesInSection = new Array(); + // this.descriptionTemplatesArray(i).controls.forEach((template) => { + // descriptionTemplatesInSection.push({ id: template.value.descriptionTemplateId, label: template.value.label, description: "" }); + // }) + // this.descriptionTemplatesPerSection.push(descriptionTemplatesInSection); + // } + // } + + // checkForProfiles(event, sectionIndex: number) { + // if (event.checked === false) { + // this.descriptionTemplatesPerSection[sectionIndex] = new Array(); + // this.descriptionTemplatesArray(sectionIndex).clear(); + // } + // } + + + + // sectionsArray(): UntypedFormArray { + // //return this.descriptionTemplatesFormGroup.get('sections') as FormArray; + // return this.formGroup.get('definition').get('sections') as UntypedFormArray; + // } + + + // fieldsArray(sectionIndex: number): UntypedFormArray { + // return this.sectionsArray().at(sectionIndex).get('fields') as UntypedFormArray; + // } + + + + // removeField(sectionIndex: number, fieldIndex: number): void { + // this.fieldsArray(sectionIndex).removeAt(fieldIndex); + // } + + // systemFieldsArray(sectionIndex: number): UntypedFormArray { + // return this.sectionsArray().at(sectionIndex).get('systemFields') as UntypedFormArray; + // } + + // initSystemField(systemField?: SystemFieldType): UntypedFormGroup { + // return this.fb.group({ + // id: this.fb.control(Guid.create().toString()), + // type: this.fb.control(systemField), + // label: this.fb.control(''), + // placeholder: this.fb.control(''), + // description: this.fb.control(''), + // required: this.fb.control(true), + // ordinal: this.fb.control('') + // }); + // } + + // addSystemField(sectionIndex: number, systemField?: SystemFieldType): void { + // this.addField(sectionIndex, FieldCategory.SYSTEM, systemField); + // } + + // transfromEnumToString(type: SystemFieldType): string { + // return this.fieldList.find(f => f.type == type).label; + // } + + // selectedFieldType(type: SystemFieldType, sectionIndex: number): void { + // let index = this.systemFieldListPerSection[sectionIndex].indexOf(type); + // if (index == -1) { + // this.systemFieldListPerSection[sectionIndex].push(type); + // this.addSystemField(sectionIndex, type); + // } + // else { + // this.systemFieldListPerSection[sectionIndex].splice(index, 1); + // this.removeSystemField(sectionIndex, type); + // } + // } + + + + // descriptionTemplatesArray(sectionIndex: number): UntypedFormArray { + // return this.sectionsArray().at(sectionIndex).get('descriptionTemplates') as UntypedFormArray; + // } + + // addDescriptionTemplate(descriptionTemplate, sectionIndex: number): void { + // this.descriptionTemplatesArray(sectionIndex).push(this.fb.group({ + // label: this.fb.control(descriptionTemplate.value) + // })); + // } + + // removeDescriptionTemplate(sectionIndex: number, templateIndex: number): void { + // this.descriptionTemplatesArray(sectionIndex).removeAt(templateIndex); + // } + + // extraFieldsArray(sectionIndex: number): UntypedFormArray { + // return this.sectionsArray().at(sectionIndex).get('extraFields') as UntypedFormArray; + // } + + + + + + + + // getExtraFieldTypes(): Number[] { + // let keys: string[] = Object.keys(ExtraFieldType); + // keys = keys.slice(0, keys.length / 2); + // const values: Number[] = keys.map(Number); + // return values; + // } + + // getExtraFieldTypeValue(extraFieldType: ExtraFieldType): string { + // switch (extraFieldType) { + // case ExtraFieldType.TEXT: return 'Text'; + // case ExtraFieldType.RICH_TEXT: return 'Rich Text'; + // case ExtraFieldType.DATE: return 'Date'; + // case ExtraFieldType.NUMBER: return 'Number'; + // } + // } + + + + // moveItemInFormArray(formArray: UntypedFormArray, fromIndex: number, toIndex: number): void { + // const dir = toIndex > fromIndex ? 1 : -1; + + // const item = formArray.at(fromIndex); + // for (let i = fromIndex; i * dir < toIndex * dir; i = i + dir) { + // const current = formArray.at(i + dir); + // formArray.setControl(i, current); + // } + // formArray.setControl(toIndex, item); + // } + + // // clearForm(): void{ + // // this.descriptionTemplatesFormGroup.reset(); + // // } + + + + // // onPreviewTemplate(event, sectionIndex: number) { + // // const dialogRef = this.dialog.open(DatasetPreviewDialogComponent, { + // // width: '590px', + // // minHeight: '200px', + // // restoreFocus: false, + // // data: { + // // template: event + // // }, + // // panelClass: 'custom-modalbox' + // // }); + // // dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { + // // if (result) { + // // let blueprints = this.sectionsArray().at(sectionIndex).get('descriptionTemplates').value;//this.formGroup.get('blueprints').value; + // // const blueprint: DescriptionTemplatesInSectionEditor = new DescriptionTemplatesInSectionEditor(); + // // blueprint.id = Guid.create().toString(); + // // blueprint.descriptionTemplateId = event.id; + // // blueprint.label = event.label; + // // blueprints.push(blueprint.buildForm()); + // // this.sectionsArray().at(sectionIndex).get('descriptionTemplates').setValue(blueprints);//this.formGroup.get('blueprints').setValue(blueprints); + // // this.blueprintsAutoCompleteConfiguration = { + // // filterFn: this.filterProfiles.bind(this), + // // initialItems: (excludedItems: any[]) => this.filterProfiles('').pipe(map(result => result.filter(resultItem => (excludedItems || []).map(x => x.id).indexOf(resultItem.id) === -1))), + // // displayFn: (item) => item['label'], + // // titleFn: (item) => item['label'], + // // subtitleFn: (item) => item['description'], + // // popupItemActionIcon: 'visibility' + // // }; + // // } + // // }); + // // } + + + + // checkValidity() { + // this.formService.touchAllFormFields(this.formGroup); + // if (!this.isFormValid()) { return false; } + // let errorMessages = []; + // if (!this.hasTitle()) { + // errorMessages.push("Title should be set."); + // } + // if (!this.hasDescription()) { + // errorMessages.push("Description should be set."); + // } + // if (!this.hasDescriptionTemplates()) { + // errorMessages.push("At least one section should have description templates."); + // } + // if (errorMessages.length > 0) { + // this.showValidationErrorsDialog(undefined, errorMessages); + // return false; + // } + // return true; + // } + + // formSubmit(): void { + // if (this.checkValidity()) + // this.onSubmit(); + // } + + // public isFormValid() { + // return this.formGroup.valid; + // } + + // hasTitle(): boolean { + // const descriptionTemplate: DescriptionTemplatePersist = this.formGroup.value; + // return descriptionTemplate.definition.sections.some(section => section.fields.some(field => (field.category === DescriptionTemplateSectionFieldCategory.SYSTEM || field.category as unknown === DescriptionTemplateSectionFieldCategory.SYSTEM) && field.systemFieldType === DescriptionTemplateSystemFieldType.TEXT)); + // } + + // hasDescription(): boolean { + // const descriptionTemplate: DescriptionTemplatePersist = this.formGroup.value; + // return descriptionTemplate.definition.sections.some(section => section.fields.some(field => (field.category === DescriptionTemplateSectionFieldCategory.SYSTEM || field.category as unknown === DescriptionTemplateSectionFieldCategory.SYSTEM) && field.systemFieldType === DescriptionTemplateSystemFieldType.HTML_TEXT)); + // } + + // hasDescriptionTemplates(): boolean { + // const descriptionTemplate: DescriptionTemplatePersist = this.formGroup.value; + // return descriptionTemplate.definition.sections.some(section => section.hasTemplates == true); + // } + + // private showValidationErrorsDialog(projectOnly?: boolean, errmess?: string[]) { + + // const dialogRef = this.dialog.open(FormValidationErrorsDialogComponent, { + // disableClose: true, + // autoFocus: false, + // restoreFocus: false, + // data: { + // errorMessages: errmess, + // projectOnly: projectOnly + // }, + // }); + + // } + + // onSubmit(): void { + // this.descriptionTemplateService.createBlueprint(this.formGroup.value) + // .pipe(takeUntil(this._destroyed)) + // .subscribe( + // complete => this.onCallbackSuccess(), + // error => this.onCallbackError(error) + // ); + // } + + // onCallbackSuccess(): void { + // this.uiNotificationService.snackBarNotification(this.isNew ? this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-CREATION') : this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success); + // this.router.navigate(['/description-templates']); + // } + + // onCallbackError(errorResponse: any) { + // this.setErrorModel(errorResponse.error); + // this.formService.validateAllFormFields(this.formGroup); + // } + + // public setErrorModel(validationErrorModel: ValidationErrorModel) { + // Object.keys(validationErrorModel).forEach(item => { + // (this.descriptionTemplateEditor.validationErrorModel)[item] = (validationErrorModel)[item]; + // }); + // } + + public cancel(): void { + this.router.navigate(['/description-templates']); + } + + // // addField() { + // // (this.formGroup.get('definition').get('fields')).push(new DescriptionTemplateFieldEditorModel().buildForm()); + // // } + + // // removeField(index: number) { + // // (this.formGroup.get('definition').get('fields')).controls.splice(index, 1); + // // } + + // getDescriptionTemplateFieldDataTypeValues(): Number[] { + // let keys: string[] = Object.keys(DescriptionTemplateFieldDataType); + // keys = keys.slice(0, keys.length / 2); + // const values: Number[] = keys.map(Number); + // return values; + // } + + // getDescriptionTemplateFieldDataTypeWithLanguage(fieldType: DescriptionTemplateFieldDataType): string { + // let result = ''; + // this.language.get(this.enumUtils.toDescriptionTemplateFieldDataTypeString(fieldType)) + // .pipe(takeUntil(this._destroyed)) + // .subscribe((value: string) => { + // result = value; + // }); + // return result; + // } + + // getDescriptionTemplateFieldTypeValues(): Number[] { + // let keys: string[] = Object.keys(DescriptionTemplateType); + // keys = keys.slice(0, keys.length / 2); + // const values: Number[] = keys.map(Number); + // return values; + // } + + // getDescriptionTemplateFieldTypeWithLanguage(blueprintType: DescriptionTemplateType): string { + // let result = ''; + // this.language.get(this.enumUtils.toDescriptionTemplateTypeString(blueprintType)) + // .pipe(takeUntil(this._destroyed)) + // .subscribe((value: string) => { + // result = value; + // }); + // return result; + // } + + // delete() { + // this.dialog.open(ConfirmationDialogComponent, { + // data: { + // isDeleteConfirmation: true, + // confirmButton: this.language.instant('DESCRIPTION-TEMPLATE-EDITOR.CONFIRM-DELETE-DIALOG.CONFIRM-BUTTON'), + // cancelButton: this.language.instant("DESCRIPTION-TEMPLATE-EDITOR.CONFIRM-DELETE-DIALOG.CANCEL-BUTTON"), + // message: this.language.instant("DESCRIPTION-TEMPLATE-EDITOR.CONFIRM-DELETE-DIALOG.MESSAGE") + // } + // }) + // .afterClosed() + // .subscribe( + // confirmed => { + // if (confirmed) { + // if (this.formGroup.get('status').value == DescriptionTemplateStatus.Draft) { + // // this.formGroup.get('status').setValue(DescriptionTemplateStatus.Deleted); + // this.descriptionTemplateService.createBlueprint(this.formGroup.value) + // .pipe(takeUntil(this._destroyed)) + // .subscribe( + // complete => this.onCallbackSuccess(), + // error => this.onCallbackError(error) + // ); + // } + // else { + // // this.descriptionTemplateService.delete(this.descriptionTemplateId) + // // .pipe(takeUntil(this._destroyed)) + // // .subscribe( + // // complete => this.onCallbackSuccess(), + // // error => { + // // if (error.error.statusCode == 674) { + // // this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.UNSUCCESSFUL-DESCRIPTION-TEMPLATE-DELETE'), SnackBarNotificationLevel.Error); + // // } else { + // // this.uiNotificationService.snackBarNotification(this.language.instant(error.message), SnackBarNotificationLevel.Error); + // // } + // // } + // // ); + // } + // } + // } + // ) + + // } + +// finalize() { +// if (this.checkValidity()) { +// this.formGroup.get('status').setValue(DescriptionTemplateStatus.Finalized); +// this.formSubmit(); +// } +// } + +// downloadXML(): void { +// const blueprintId = this.formGroup.get('id').value; +// if (blueprintId == null) return; +// this.descriptionTemplateService.downloadXML(blueprintId) +// .pipe(takeUntil(this._destroyed)) +// .subscribe(response => { +// const blob = new Blob([response.body], { type: 'application/xml' }); +// const filename = this.fileUtils.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition')); + +// FileSaver.saveAs(blob, filename); +// }); +// } +} diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.model.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.model.ts new file mode 100644 index 000000000..843580008 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.model.ts @@ -0,0 +1,354 @@ +import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; +import { DescriptionTemplateStatus } from "@app/core/common/enum/description-template-status"; +import { DescriptionTemplate, DescriptionTemplateDefinition } from "@app/core/model/description-template/description-template"; +import { DescriptionTemplateDefinitionPersist, DescriptionTemplatePersist } from "@app/core/model/description-template/description-template-persist"; +import { BaseEditorModel } from "@common/base/base-form-editor-model"; +import { BackendErrorValidator } from "@common/forms/validation/custom-validator"; +import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model"; +import { Validation, ValidationContext } from "@common/forms/validation/validation-context"; +import { Guid } from "@common/types/guid"; + +export class DescriptionTemplateEditorModel extends BaseEditorModel { // implements DescriptionTemplatePersist { + label: string; + // definition: DescriptionTemplateDefinitionEditorModel; + status: DescriptionTemplateStatus = DescriptionTemplateStatus.Draft; + description: string; + permissions: string[]; + + public validationErrorModel: ValidationErrorModel = new ValidationErrorModel(); + protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + + constructor() { super(); } + + public fromModel(item: DescriptionTemplate): DescriptionTemplateEditorModel { + if (item) { + super.fromModel(item); + this.label = item.label; + this.status = item.status; + this.description = item.description; + //this.definition = new DescriptionTemplateDefinitionEditorModel().fromModel(item.definition); + } + return this; + } + + buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup { + if (context == null) { context = this.createValidationContext(); } + + return this.formBuilder.group({ + id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators], + label: [{ value: this.label, disabled: disabled }, context.getValidation('label').validators], + status: [{ value: this.status, disabled: disabled }, context.getValidation('status').validators], + // definition: this.definition.buildForm({ + // rootPath: `definition.` + // }), + hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators] + }); + } + + createValidationContext(): ValidationContext { + const baseContext: ValidationContext = new ValidationContext(); + const baseValidationArray: Validation[] = new Array(); + baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, 'id')] }); + baseValidationArray.push({ key: 'label', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'label')] }); + baseValidationArray.push({ key: 'status', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'status')] }); + baseValidationArray.push({ key: 'hash', validators: [] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} + +// export class DescriptionTemplateDefinitionEditorModel implements DescriptionTemplateDefinitionPersist { +// sections: DescriptionTemplateDefinitionSectionEditorModel[] = []; + +// protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + +// constructor( +// public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() +// ) { } + +// public fromModel(item: DescriptionTemplateDefinition): DescriptionTemplateDefinitionEditorModel { +// if (item) { +// if (item.sections) { item.sections.map(x => this.sections.push(new DescriptionTemplateDefinitionSectionEditorModel().fromModel(x))); } +// } +// return this; +// } + +// buildForm(params?: { +// context?: ValidationContext, +// disabled?: boolean, +// rootPath?: string +// }): UntypedFormGroup { +// let { context = null, disabled = false, rootPath } = params ?? {} +// if (context == null) { +// context = DescriptionTemplateDefinitionEditorModel.createValidationContext({ +// validationErrorModel: this.validationErrorModel, +// rootPath +// }); +// } + +// return this.formBuilder.group({ +// sections: this.formBuilder.array( +// (this.sections ?? []).map( +// (item, index) => new DescriptionTemplateDefinitionSectionEditorModel( +// this.validationErrorModel +// ).fromModel(item).buildForm({ +// rootPath: `sections[${index}].` +// }), context.getValidation('sections') +// ) +// ), +// }); +// } + +// static createValidationContext(params: { +// rootPath?: string, +// validationErrorModel: ValidationErrorModel +// }): ValidationContext { +// const { rootPath = '', validationErrorModel } = params; + +// const baseContext: ValidationContext = new ValidationContext(); +// const baseValidationArray: Validation[] = new Array(); +// baseValidationArray.push({ key: 'sections', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}sections`)] }); + +// baseContext.validation = baseValidationArray; +// return baseContext; +// } + +// } + +// export class DescriptionTemplateDefinitionSectionEditorModel implements DescriptionTemplateDefinitionSectionPersist { +// id: Guid; +// label: string; +// description: string; +// ordinal: number; +// fields: FieldInSectionEditorModel[] = []; +// hasTemplates: boolean; +// descriptionTemplates?: DescriptionTemplatesInSectionEditorModel[] = []; + +// protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + +// constructor( +// public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() +// ) { } + +// public fromModel(item: DescriptionTemplateDefinitionSection): DescriptionTemplateDefinitionSectionEditorModel { +// if (item) { +// this.id = item.id; +// this.label = item.label; +// this.description = item.description; +// this.ordinal = item.ordinal; +// this.hasTemplates = item.hasTemplates; +// if (item.fields) { item.fields.map(x => this.fields.push(new FieldInSectionEditorModel().fromModel(x))); } +// if (item.descriptionTemplates) { item.descriptionTemplates.map(x => this.descriptionTemplates.push(new DescriptionTemplatesInSectionEditorModel().fromModel(x))); } +// } +// return this; +// } + +// buildForm(params?: { +// context?: ValidationContext, +// disabled?: boolean, +// rootPath?: string +// }): UntypedFormGroup { +// let { context = null, disabled = false, rootPath } = params ?? {} +// if (context == null) { +// context = DescriptionTemplateDefinitionSectionEditorModel.createValidationContext({ +// validationErrorModel: this.validationErrorModel, +// rootPath +// }); +// } + +// return this.formBuilder.group({ +// id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators], +// label: [{ value: this.label, disabled: disabled }, context.getValidation('label').validators], +// ordinal: [{ value: this.ordinal, disabled: disabled }, context.getValidation('ordinal').validators], +// description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators], +// hasTemplates: [{ value: this.hasTemplates, disabled: disabled }, context.getValidation('hasTemplates').validators], +// fields: this.formBuilder.array( +// (this.fields ?? []).map( +// (item, index) => new FieldInSectionEditorModel( +// this.validationErrorModel +// ).fromModel(item).buildForm({ +// rootPath: `fields[${index}].` +// }), context.getValidation('fields') +// ) +// ), +// descriptionTemplates: this.formBuilder.array( +// (this.descriptionTemplates ?? []).map( +// (item, index) => new DescriptionTemplatesInSectionEditorModel( +// this.validationErrorModel +// ).fromModel(item).buildForm({ +// rootPath: `fields[${index}].` +// }), context.getValidation('descriptionTemplates') +// ) +// ) +// }); +// } + +// static createValidationContext(params: { +// rootPath?: string, +// validationErrorModel: ValidationErrorModel +// }): ValidationContext { +// const { rootPath = '', validationErrorModel } = params; + +// const baseContext: ValidationContext = new ValidationContext(); +// const baseValidationArray: Validation[] = new Array(); +// baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}id`)] }); +// baseValidationArray.push({ key: 'label', validators: [Validators.required,BackendErrorValidator(validationErrorModel, `${rootPath}label`)] }); +// baseValidationArray.push({ key: 'ordinal', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}ordinal`)] }); +// baseValidationArray.push({ key: 'description', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}description`)] }); +// baseValidationArray.push({ key: 'hasTemplates', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}hasTemplates`)] }); +// baseValidationArray.push({ key: 'fields', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}fields`)] }); +// baseValidationArray.push({ key: 'descriptionTemplates', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}descriptionTemplates`)] }); +// baseValidationArray.push({ key: 'hash', validators: [] }); + +// baseContext.validation = baseValidationArray; +// return baseContext; +// } + +// } + +// export class FieldInSectionEditorModel implements FieldInSectionPersist { +// public id: Guid; +// public category: DescriptionTemplateSectionFieldCategory; +// public dataType: DescriptionTemplateExtraFieldDataType; +// public systemFieldType: DescriptionTemplateSystemFieldType; +// public label: string; +// public placeholder: string; +// public description: string; +// public required: boolean = false; +// public ordinal: number; + +// protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + +// constructor( +// public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() +// ) { } + +// fromModel(item: FieldInSection): FieldInSectionEditorModel { +// this.id = item.id; +// this.category = item.category; +// this.dataType = item.dataType; +// this.systemFieldType = item.systemFieldType; +// this.label = item.label; +// this.placeholder = item.placeholder; +// this.description = item.description; +// this.required = item.required; +// this.ordinal = item.ordinal; +// return this; +// } + +// buildForm(params?: { +// context?: ValidationContext, +// disabled?: boolean, +// rootPath?: string +// }): UntypedFormGroup { +// let { context = null, disabled = false, rootPath } = params ?? {} +// if (context == null) { +// context = FieldInSectionEditorModel.createValidationContext({ +// validationErrorModel: this.validationErrorModel, +// rootPath +// }); +// } + +// return this.formBuilder.group({ +// id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators], + +// category: [{ value: this.category, disabled: disabled }, context.getValidation('category').validators], +// dataType: [{ value: this.dataType, disabled: disabled }, context.getValidation('dataType').validators], +// systemFieldType: [{ value: this.systemFieldType, disabled: disabled }, context.getValidation('systemFieldType').validators], +// label: [{ value: this.label, disabled: disabled }, context.getValidation('label').validators], +// placeholder: [{ value: this.placeholder, disabled: disabled }, context.getValidation('placeholder').validators], +// description: [{ value: this.description, disabled: disabled }, context.getValidation('description').validators], +// required: [{ value: this.required, disabled: disabled }, context.getValidation('required').validators], +// ordinal: [{ value: this.ordinal, disabled: disabled }, context.getValidation('ordinal').validators], + +// }); +// } + +// static createValidationContext(params: { +// rootPath?: string, +// validationErrorModel: ValidationErrorModel +// }): ValidationContext { +// const { rootPath = '', validationErrorModel } = params; + +// const baseContext: ValidationContext = new ValidationContext(); +// const baseValidationArray: Validation[] = new Array(); +// baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}id`)] }); + +// baseValidationArray.push({ key: 'category', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}category`)] }); +// baseValidationArray.push({ key: 'dataType', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}dataType`)] }); +// baseValidationArray.push({ key: 'systemFieldType', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}systemFieldType`)] }); +// baseValidationArray.push({ key: 'label', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}label`)] }); +// baseValidationArray.push({ key: 'placeholder', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}placeholder`)] }); +// baseValidationArray.push({ key: 'description', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}description`)] }); +// baseValidationArray.push({ key: 'required', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}required`)] }); +// baseValidationArray.push({ key: 'ordinal', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}ordinal`)] }); + +// baseContext.validation = baseValidationArray; +// return baseContext; +// } +// } + +// export class DescriptionTemplatesInSectionEditorModel implements DescriptionTemplatesInSectionPersist { +// id: Guid; +// descriptionTemplateId: Guid; +// label: string; +// minMultiplicity: number; +// maxMultiplicity: number; + +// protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); + +// constructor( +// public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() +// ) { } + +// fromModel(item: DescriptionTemplatesInSection): DescriptionTemplatesInSectionEditorModel { +// this.id = item.id; +// this.descriptionTemplateId = item.descriptionTemplateId; +// this.label = item.label; +// this.minMultiplicity = item.minMultiplicity; +// this.maxMultiplicity = item.maxMultiplicity; +// return this; +// } + +// buildForm(params?: { +// context?: ValidationContext, +// disabled?: boolean, +// rootPath?: string +// }): UntypedFormGroup { +// let { context = null, disabled = false, rootPath } = params ?? {} +// if (context == null) { +// context = DescriptionTemplatesInSectionEditorModel.createValidationContext({ +// validationErrorModel: this.validationErrorModel, +// rootPath +// }); +// } + +// return this.formBuilder.group({ +// id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators], +// descriptionTemplateId: [{ value: this.descriptionTemplateId, disabled: disabled }, context.getValidation('descriptionTemplateId').validators], +// label: [{ value: this.label, disabled: disabled }, context.getValidation('label').validators], +// minMultiplicity: [{ value: this.minMultiplicity, disabled: disabled }, context.getValidation('minMultiplicity').validators], +// maxMultiplicity: [{ value: this.maxMultiplicity, disabled: disabled }, context.getValidation('maxMultiplicity').validators], + +// }); +// } + +// static createValidationContext(params: { +// rootPath?: string, +// validationErrorModel: ValidationErrorModel +// }): ValidationContext { +// const { rootPath = '', validationErrorModel } = params; + +// const baseContext: ValidationContext = new ValidationContext(); +// const baseValidationArray: Validation[] = new Array(); +// baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}id`)] }); +// baseValidationArray.push({ key: 'descriptionTemplateId', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}descriptionTemplateId`)] }); +// baseValidationArray.push({ key: 'label', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}label`)] }); +// baseValidationArray.push({ key: 'minMultiplicity', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}minMultiplicity`)] }); +// baseValidationArray.push({ key: 'maxMultiplicity', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}maxMultiplicity`)] }); + +// baseContext.validation = baseValidationArray; +// return baseContext; +// } +// } \ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.resolver.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.resolver.ts new file mode 100644 index 000000000..60ee8ad1e --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.resolver.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { DescriptionTemplate, DescriptionTemplateBaseFieldData, DescriptionTemplateDefinition, DescriptionTemplateField, DescriptionTemplateFieldSet, DescriptionTemplateMultiplicity, DescriptionTemplatePage, DescriptionTemplateRule, DescriptionTemplateSection } from '@app/core/model/description-template/description-template'; +import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service'; +import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service'; +import { BaseEditorResolver } from '@common/base/base-editor.resolver'; +import { Guid } from '@common/types/guid'; +import { takeUntil, tap } from 'rxjs/operators'; +import { nameof } from 'ts-simple-nameof'; + +@Injectable() +export class DescriptionTemplateEditorResolver extends BaseEditorResolver { + + constructor(private descriptionTemplateService: DescriptionTemplateService, private breadcrumbService: BreadcrumbService) { + super(); + } + + public static lookupFields(): string[] { + return [ + ...BaseEditorResolver.lookupFields(), + nameof(x => x.id), + nameof(x => x.label), + nameof(x => x.status), + nameof(x => x.description), + nameof(x => x.status), + + [nameof(x => x.definition), nameof(x => x.pages), nameof(x => x.id)].join('.'), + [nameof(x => x.definition), nameof(x => x.pages), nameof(x => x.ordinal)].join('.'), + [nameof(x => x.definition), nameof(x => x.pages), nameof(x => x.title)].join('.'), + + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.id)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.ordinal)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.defaultVisibility)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.multiplicity)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.numbering)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.page)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.title)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.description)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.extendedDescription)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.ordinal)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.sections)].join('.'), // TODO: it is recursive here + + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.id)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.ordinal)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.numbering)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.title)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.description)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.extendedDescription)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.additionalInformation)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.hasCommentField)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.multiplicity), nameof(x => x.min)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.multiplicity), nameof(x => x.max)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.multiplicity), nameof(x => x.placeholder)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.multiplicity), nameof(x => x.tableView)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.id)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.ordinal)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.numbering)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.schematics)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.defaultValue)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.defaultValue)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.fieldType)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.includeInExport)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.validations)].join('.'), + + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.visibilityRules), nameof(x => x.target)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.visibilityRules), nameof(x => x.value)].join('.'), + [nameof(x => x.definition), nameof(x => x.sections), nameof(x => x.fieldSets), nameof(x => x.fields), nameof(x => x.data), nameof(x => x.label)].join('.'), + + nameof(x => x.createdAt), + nameof(x => x.hash), + nameof(x => x.isActive) + ] + } + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + + const fieldSets = [ + ...DescriptionTemplateEditorResolver.lookupFields() + ]; + const id = route.paramMap.get('id'); + const cloneid = route.paramMap.get('cloneid'); + if (id != null) { + return this.descriptionTemplateService.getSingle(Guid.parse(id), fieldSets).pipe(tap(x => this.breadcrumbService.addIdResolvedValue(x.id?.toString(), x.label)), takeUntil(this._destroyed)); + } else if (cloneid != null) { + return this.descriptionTemplateService.clone(Guid.parse(cloneid), fieldSets).pipe(tap(x => this.breadcrumbService.addIdResolvedValue(x.id?.toString(), x.label)), takeUntil(this._destroyed)); + } + } +} diff --git a/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.service.ts b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.service.ts new file mode 100644 index 000000000..7ff6e2b1c --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/editor/description-template-editor.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from "@angular/core"; +import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model"; + +@Injectable() +export class DescriptionTemplateEditorService { + private validationErrorModel: ValidationErrorModel; + + public setValidationErrorModel(validationErrorModel: ValidationErrorModel): void { + this.validationErrorModel = validationErrorModel; + } + + public getValidationErrorModel(): ValidationErrorModel { + return this.validationErrorModel; + } +} diff --git a/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.html b/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.html new file mode 100644 index 000000000..1bdb52aa0 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.html @@ -0,0 +1,125 @@ +
+
+ +
+
+

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

+ + +
+
+ +
+ +
+ +
+
+ + + + + + + + + +
+
+ + + + +
+
+ + {{item?.label | nullifyValue}} +
+
+ + + {{item?.description | nullifyValue}} +
+
+ + +
+
+ {{enumUtils.toDescriptionTemplateStatusString(item.status) | nullifyValue}} +
+
+
+ + + + {{'DESCRIPTION-TEMPLATE-LISTING.FIELDS.CREATED-AT' | translate}}: + + {{item?.createdAt | dateTimeFormatter : 'short' | nullifyValue}} + + +
+
+ + + {{'DESCRIPTION-TEMPLATE-LISTING.FIELDS.UPDATED-AT' | translate}}: + + {{item?.updatedAt | dateTimeFormatter : 'short' | nullifyValue}} + + + +
+
+
+ + +
+
+ {{enumUtils.toDescriptionTemplateStatusString(row.status) | nullifyValue}} +
+
+
+ + +
+
+ + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.scss b/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.scss new file mode 100644 index 000000000..5040654c7 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.scss @@ -0,0 +1,83 @@ +.mat-table { + margin-top: 47px; + border-radius: 4px; +} + +.description-template-listing { + margin-top: 1.3rem; + margin-left: 1rem; + margin-right: 2rem; + + .mat-header-row { + background: #f3f5f8; + } + + .mat-card { + margin: 16px 0; + padding: 0px; + } + + .mat-row { + cursor: pointer; + min-height: 4.5em; + } + + mat-row:hover { + background-color: #eef5f6; + } + + .mat-fab-bottom-right { + float: right; + z-index: 5; + } +} + +// PAGINATOR +:host ::ng-deep .mat-paginator-container { + flex-direction: row-reverse !important; + justify-content: space-between !important; + background-color: #f6f6f6; + align-items: center; +} + +.create-btn { + border-radius: 30px; + background-color: var(--secondary-color); + padding-left: 2em; + padding-right: 2em; + // color: #000; + + .button-text { + display: inline-block; + } +} + +.import-btn { + background: #ffffff 0% 0% no-repeat padding-box; + border-radius: 30px; + // color: var(--primary-color); + // border: 1px solid var(--primary-color); + padding-left: 2em; + padding-right: 2em; + color: #000; + border: 1px solid #000; +} + +.status-chip { + + border-radius: 20px; + padding-left: 1em; + padding-right: 1em; + padding-top: 0.2em; + font-size: .8em; +} + +.status-chip-finalized { + color: #568b5a; + background: #9dd1a1 0% 0% no-repeat padding-box; +} + +.status-chip-draft { + color: #00c4ff; + background: #d3f5ff 0% 0% no-repeat padding-box; +} diff --git a/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.ts b/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.ts new file mode 100644 index 000000000..18f92147d --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/listing/description-template-listing.component.ts @@ -0,0 +1,402 @@ + +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, Router } from '@angular/router'; +import { IsActive } from '@app/core/common/enum/is-active.enum'; +import { DescriptionTemplate } from '@app/core/model/description-template/description-template'; +import { AuthService } from '@app/core/services/auth/auth.service'; +import { MatomoService } from '@app/core/services/matomo/matomo-service'; +import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; +import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; +import { FileUtils } from '@app/core/services/utilities/file-utils.service'; +import { QueryParamsService } from '@app/core/services/utilities/query-params.service'; +import { BaseListingComponent } from '@common/base/base-listing-component'; +import { PipeService } from '@common/formatting/pipe.service'; +import { DataTableDateTimeFormatPipe } from '@common/formatting/pipes/date-time-format.pipe'; +import { QueryResult } from '@common/model/query-result'; +import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; +import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service'; +import { ColumnDefinition, ColumnsChangedEvent, HybridListingComponent, PageLoadEvent } from '@common/modules/hybrid-listing/hybrid-listing.component'; +import { Guid } from '@common/types/guid'; +import { TranslateService } from '@ngx-translate/core'; +import * as FileSaver from 'file-saver'; +import { Observable } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { nameof } from 'ts-simple-nameof'; +import { ImportDescriptionTemplateDialogComponent } from './import-dmp-blueprint/import-description-template.dialog.component'; +import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service'; +import { DescriptionTemplateStatus } from '@app/core/common/enum/description-template-status'; +import { DescriptionTemplateLookup } from '@app/core/query/description-template.lookup'; + + +@Component({ + selector: 'app-description-template-listing-component', + templateUrl: './description-template-listing.component.html', + styleUrls: ['./description-template-listing.component.scss'] +}) +export class DescriptionTemplateListingComponent extends BaseListingComponent implements OnInit { + publish = false; + userSettingsKey = { key: 'DescriptionTemplateListingUserSettings' }; + propertiesAvailableForOrder: ColumnDefinition[]; + descriptionTemplateStatuses = DescriptionTemplateStatus; + + @ViewChild('descriptionTemplateStatus', { static: true }) descriptionTemplateStatus?: TemplateRef; + @ViewChild('actions', { static: true }) actions?: TemplateRef; + @ViewChild(HybridListingComponent, { static: true }) hybridListingComponent: HybridListingComponent; + + private readonly lookupFields: string[] = [ + nameof(x => x.id), + nameof(x => x.label), + nameof(x => x.description), + nameof(x => x.status), + nameof(x => x.updatedAt), + nameof(x => x.createdAt), + nameof(x => x.hash), + nameof(x => x.isActive) + ]; + + rowIdentity = x => x.id; + + constructor( + protected router: Router, + protected route: ActivatedRoute, + protected uiNotificationService: UiNotificationService, + protected httpErrorHandlingService: HttpErrorHandlingService, + protected queryParamsService: QueryParamsService, + private descriptionTemplateService: DescriptionTemplateService, + public authService: AuthService, + private matomoService: MatomoService, + private pipeService: PipeService, + public enumUtils: EnumUtils, + private language: TranslateService, + private dialog: MatDialog, + private fileUtils: FileUtils + ) { + super(router, route, uiNotificationService, httpErrorHandlingService, queryParamsService); + // Lookup setup + // Default lookup values are defined in the user settings class. + this.lookup = this.initializeLookup(); + } + + ngOnInit() { + this.matomoService.trackPageView('Admin: DMP Templates'); + super.ngOnInit(); + } + + protected initializeLookup(): DescriptionTemplateLookup { + const lookup = new DescriptionTemplateLookup(); + lookup.metadata = { countAll: true }; + lookup.page = { offset: 0, size: this.ITEMS_PER_PAGE }; + lookup.isActive = [IsActive.Active]; + lookup.order = { items: [this.toDescSortField(nameof(x => x.createdAt))] }; + this.updateOrderUiFields(lookup.order); + + lookup.project = { + fields: this.lookupFields + }; + + return lookup; + } + + protected setupColumns() { + this.gridColumns.push(...[{ + prop: nameof(x => x.label), + sortable: true, + languageName: 'DESCRIPTION-TEMPLATE-LISTING.FIELDS.NAME' + }, + { + prop: nameof(x => x.description), + sortable: true, + languageName: 'DESCRIPTION-TEMPLATE-LISTING.FIELDS.DESCRIPTION' + }, + { + prop: nameof(x => x.status), + sortable: true, + languageName: 'DESCRIPTION-TEMPLATE-LISTING.FIELDS.STATUS', + cellTemplate: this.descriptionTemplateStatus + }, + { + prop: nameof(x => x.createdAt), + sortable: true, + languageName: 'DESCRIPTION-TEMPLATE-LISTING.FIELDS.CREATED-AT', + pipe: this.pipeService.getPipe(DataTableDateTimeFormatPipe).withFormat('short') + }, + { + prop: nameof(x => x.updatedAt), + sortable: true, + languageName: 'DESCRIPTION-TEMPLATE-LISTING.FIELDS.UPDATED-AT', + pipe: this.pipeService.getPipe(DataTableDateTimeFormatPipe).withFormat('short') + }, + { + alwaysShown: true, + cellTemplate: this.actions, + maxWidth: 120 + } + ]); + this.propertiesAvailableForOrder = this.gridColumns.filter(x => x.sortable); + } + + // + // Listing Component functions + // + onColumnsChanged(event: ColumnsChangedEvent) { + super.onColumnsChanged(event); + this.onColumnsChangedInternal(event.properties.map(x => x.toString())); + } + + private onColumnsChangedInternal(columns: string[]) { + // Here are defined the projection fields that always requested from the api. + const fields = new Set(this.lookupFields); + this.gridColumns.map(x => x.prop) + .filter(x => !columns?.includes(x as string)) + .forEach(item => { + fields.delete(item as string) + }); + this.lookup.project = { fields: [...fields] }; + this.onPageLoad({ offset: 0 } as PageLoadEvent); + } + + protected loadListing(): Observable> { + return this.descriptionTemplateService.query(this.lookup); + } + + public delete(id: Guid) { + if (id) { + const dialogRef = this.dialog.open(ConfirmationDialogComponent, { + data: { + isDeleteConfirmation: true, + message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'), + confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'), + cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL') + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { + if (result) { + this.descriptionTemplateService.delete(id).pipe(takeUntil(this._destroyed)) + .subscribe( + complete => this.onCallbackSuccess(), + error => this.onCallbackError(error) + ); + } + }); + } + } + + onCallbackSuccess(): void { + this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-DELETE'), SnackBarNotificationLevel.Success); + this.ngOnInit(); + } + + export(id: Guid): void { + this.descriptionTemplateService.downloadXML(id) + .pipe(takeUntil(this._destroyed)) + .subscribe(response => { + const blob = new Blob([response.body], { type: 'application/xml' }); + const filename = this.fileUtils.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition')); + FileSaver.saveAs(blob, filename); + }); + } + + import(): void { + const dialogRef = this.dialog.open(ImportDescriptionTemplateDialogComponent, { + restoreFocus: false, + data: { + message: this.language.instant('DESCRIPTION-TEMPLATE-LISTING.IMPORT.UPLOAD-XML-FILE-TITLE'), + confirmButton: this.language.instant('DESCRIPTION-TEMPLATE-LISTING.IMPORT.UPLOAD-XML'), + cancelButton: this.language.instant('DESCRIPTION-TEMPLATE-LISTING.IMPORT.UPLOAD-XML-FILE-CANCEL'), + name: '', + file: FileList, + sucsess: false + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(data => { + if (data && data.sucsess && data.name != null && data.file != null) { + this.descriptionTemplateService.uploadFile(data.file, data.name) + .pipe(takeUntil(this._destroyed)) + .subscribe(_ => { + this.uiNotificationService.snackBarNotification(this.language.instant('DESCRIPTION-TEMPLATE-LISTING.MESSAGES.TEMPLATE-UPLOAD-SUCCESS'), SnackBarNotificationLevel.Success); + this.ngOnInit(); + }, + error => { + this.uiNotificationService.snackBarNotification(error.message, SnackBarNotificationLevel.Error); + }); + } + }); + } +} + + + // ngOnInit() { + + + // refresh() { + // this.dataSource = new DatasetDataSource(this.descriptionTemplateService, this._paginator, this.sort, this.criteria); + // } + + // clone(id: string) { + // this.router.navigate(['description-templates/clone/' + id]); + // } + + // rowClick(rowId: String) { + // this.router.navigate(['description-templates/' + rowId]); + // } + + // downloadXML(descriptionTemplateId: string): void { + // this.descriptionTemplateService.downloadXML(descriptionTemplateId) + // .pipe(takeUntil(this._destroyed)) + // .subscribe(response => { + // const blob = new Blob([response.body], { type: 'application/xml' }); + // const filename = this.fileUtils.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition')); + // FileSaver.saveAs(blob, filename); + // }); + // } + + // deleteTemplate(id: string) { + // if (id) { + // this.dialog.open(ConfirmationDialogComponent, { + // data: { + // isDeleteConfirmation: true, + // confirmButton: this.languageService.instant('DESCRIPTION-TEMPLATE-EDITOR.CONFIRM-DELETE-DIALOG.CONFIRM-BUTTON'), + // cancelButton: this.languageService.instant("DESCRIPTION-TEMPLATE-EDITOR.CONFIRM-DELETE-DIALOG.CANCEL-BUTTON"), + // message: this.languageService.instant("DESCRIPTION-TEMPLATE-EDITOR.CONFIRM-DELETE-DIALOG.MESSAGE") + // } + // }) + // .afterClosed() + // .subscribe( + // confirmed => { + // if (confirmed) { + // this.descriptionTemplateService.delete(id) + // .pipe(takeUntil(this._destroyed)) + // .subscribe( + // complete => { + // this.uiNotificationService.snackBarNotification(this.languageService.instant('GENERAL.SNACK-BAR.SUCCESSFUL-DESCRIPTION-TEMPLATE-DELETE'), SnackBarNotificationLevel.Success); + // this.refresh(); + // }, + // error => { + // this.onCallbackError(error); + // if (error.error.statusCode == 674) { + // this.uiNotificationService.snackBarNotification(this.languageService.instant('GENERAL.SNACK-BAR.UNSUCCESSFUL-DESCRIPTION-TEMPLATE-DELETE'), SnackBarNotificationLevel.Error); + // } else { + // this.uiNotificationService.snackBarNotification(this.languageService.instant(error.message), SnackBarNotificationLevel.Error); + // } + // } + // ); + // } + // } + // ) + // } + // } + + // onCallbackError(errorResponse: HttpErrorResponse) { + // this.uiNotificationService.snackBarNotification(errorResponse.message, SnackBarNotificationLevel.Warning); + // } + + // getDefaultCriteria(): DescriptionTemplateCriteria { + // const defaultCriteria = new DescriptionTemplateCriteria(); + // return defaultCriteria; + // } + + // // makeItPublic(id: String) { + // // debugger; + // // this.datasetService.makeDatasetPublic(id).pipe(takeUntil(this._destroyed)).subscribe(); + // // } + + // parseStatus(value: number): string { + // const stringVal = value.toString() + // try { + // return this.statuses.find(status => status.value === stringVal).viewValue; + // } catch { + // return stringVal; + // } + // } + + // openDialog(): void { + // const dialogRef = this.dialog.open(DialodConfirmationUploadDescriptionTemplates, { + // restoreFocus: false, + // data: { + // message: this.languageService.instant('DESCRIPTION-TEMPLATE-LISTING.IMPORT.UPLOAD-XML-FILE-TITLE'), + // confirmButton: this.languageService.instant('DESCRIPTION-TEMPLATE-LISTING.IMPORT.UPLOAD-XML'), + // cancelButton: this.languageService.instant('DESCRIPTION-TEMPLATE-LISTING.IMPORT.UPLOAD-XML-FILE-CANCEL'), + // name: '', + // file: FileList, + // sucsess: false + // } + // }); + // dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(data => { + // if (data && data.sucsess && data.name != null && data.file != null) { + // this.descriptionTemplateService.uploadFile(data.file, data.name) + // .pipe(takeUntil(this._destroyed)) + // .subscribe(_ => { + // this.uiNotificationService.snackBarNotification(this.languageService.instant('DESCRIPTION-TEMPLATE-LISTING.MESSAGES.TEMPLATE-UPLOAD-SUCCESS'), SnackBarNotificationLevel.Success); + // this.refresh(); + // }, + // error => { + // this.uiNotificationService.snackBarNotification(error.message, SnackBarNotificationLevel.Error); + // }); + // } + // }); + // } + // getStatusClass(status: number): string { + + // if (status === 1) {//finalized + // return 'status-chip-finalized' + // } + // if (status === 0) { + // return 'status-chip-draft'; + // } + // return ''; + // } + // } + + // export class DatasetDataSource extends DataSource { + + // totalCount = 0; + + // constructor( + // private _service: descriptionTemplateService, + // private _paginator: MatPaginator, + // private _sort: MatSort, + // private _criteria: DescriptionTemplateCriteriaComponent + // ) { + // super(); + + // } + + // connect(): Observable { + // const displayDataChanges = [ + // this._paginator.page + // //this._sort.matSortChange + // ]; + + // return observableMerge(...displayDataChanges).pipe( + // startWith(null), + // switchMap(() => { + // const startIndex = this._paginator.pageIndex * this._paginator.pageSize; + // let fields: Array = new Array(); + // if (this._sort.active) { fields = this._sort.direction === 'asc' ? ['+' + this._sort.active] : ['-' + this._sort.active]; } + // const request = new DataTableRequest(startIndex, this._paginator.pageSize, { fields: fields }); + // request.criteria = this._criteria.criteria; + // return this._service.getPagedBlueprint(request); + // }), + // /*.catch((error: any) => { + // this._snackBar.openFromComponent(SnackBarNotificationComponent, { + // data: { message: 'GENERAL.SNACK-BAR.FORMS-BAD-REQUEST', language: this._languageService }, + // duration: 3000, + // extraClasses: ['snackbar-warning'] + // }); + // //this._criteria.criteria.onCallbackError(error); + // return Observable.of(null); + // })*/ + // map(result => { + // return result; + // }), + // map(result => { + // if (!result) { return []; } + // if (this._paginator.pageIndex === 0) { this.totalCount = result.totalCount; } + // return result.data; + // })); + // } + + // disconnect() { + // // No-op + // } diff --git a/dmp-frontend/src/app/ui/admin/description-template/listing/filters/description-template-listing-filters.component.html b/dmp-frontend/src/app/ui/admin/description-template/listing/filters/description-template-listing-filters.component.html new file mode 100644 index 000000000..8f4029f50 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/listing/filters/description-template-listing-filters.component.html @@ -0,0 +1,36 @@ +
+ + + + + +
+
+
+

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

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

{{ data.message }}

+
+
+ close +
+
+
+
+ + + {{ selectedFileName }} + + +
+
+
+
+ + +
+
+
+ + + +
+ +
+
+
+ +
+
+
diff --git a/dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.scss b/dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.scss new file mode 100644 index 000000000..c79fa2851 --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.scss @@ -0,0 +1,102 @@ +.hidden { + display: none; +} + + +.cancel-btn { + background: #ffffff 0% 0% no-repeat padding-box; + border: 1px solid #b5b5b5; + border-radius: 30px; +min-width: 101px; + height: 43px; + color: #212121; + font-weight: 500; +} + +.next-btn { + background: #ffffff 0% 0% no-repeat padding-box; + border: 1px solid var(--primary-color); + border-radius: 30px; + opacity: 1; +min-width: 101px; + height: 43px; + color: var(--primary-color); + font-weight: 500; +} + +.next-btn[disabled] { + width: 100px; + height: 43px; + background: #ffffff 0% 0% no-repeat padding-box; + border: 1px solid #b5b5b5; + border-radius: 30px; + opacity: 1; +} + +.next-btn:not([disabled]):hover { + background-color: var(--primary-color); + color: #ffffff; +} + +//ngx dropzone +.drop-file { + background-color: #fafafa; + border: 1px dashed #d1d1d1; + border-radius: 4px; + max-width: 480px; + height: 98px; + margin-top: 0.5rem; +} + +.file-preview { + height: auto !important; + width: auto !important; + max-width: 500px !important; + min-height: 1rem !important; + + background-color: #e0e0e0 !important; + background-image: none !important; + color: rgba(0, 0, 0, 0.87) !important; + font-weight: 500 !important; + border-radius: 24px !important; + line-height: 1.25 !important; +} + +.file-label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 14px !important; +} + +::ng-deep ngx-dropzone-remove-badge { + opacity: 1 !important; + margin-left: .5rem !important; + position: initial !important; +} + + +//attach file + +.attach-btn { + top: -20px; +} + +.attach-file { + width: 156px; + height: 44px; + color: #ffffff; + background: var(--primary-color) 0% 0% no-repeat padding-box; + box-shadow: 0px 3px 6px #1e202029; + border-radius: 30px; +} + +.attach-file:hover { + background-color: #ffffff; + border: 1px solid var(--primary-color); + color: var(--primary-color); +} + +.close-btn:hover{ + cursor: pointer; +} diff --git a/dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.ts b/dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.ts new file mode 100644 index 000000000..5246033be --- /dev/null +++ b/dmp-frontend/src/app/ui/admin/description-template/listing/import-dmp-blueprint/import-description-template.dialog.component.ts @@ -0,0 +1,61 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + + +@Component({ + templateUrl: './import-description-template.dialog.component.html', + styleUrls: ['./import-description-template.dialog.component.scss'] +}) +export class ImportDescriptionTemplateDialogComponent { + + sizeError = false; + selectFile = false; + maxFileSize: number = 1048576; + selectedFileName: string; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any + ) { } + + + selectXML(event) { + let file: FileList = null; + if (event.target && event.target.files) { + file = event.target.files; + } else if (event.addedFiles && event.addedFiles.length) { + file = event.addedFiles; + } + if (!file) return;//no select closed with cancel . no file selected + const size: number = file[0].size; // Get file size. + this.sizeError = size > this.maxFileSize; // Checks if file size is valid. + const formdata: FormData = new FormData(); + if (!this.sizeError) { + this.data.file = file; + this.selectFile = true; + this.selectedFileName = file[0].name; + } + this.data.name = file[0].name; + } + + cancel() { + this.data.sucsess = false; + this.dialogRef.close(this.data); + } + + confirm() { + this.data.name = this.data.name; + this.data.sucsess = true; + this.dialogRef.close(this.data); + } + + hasBlueprint(): boolean { + return (this.selectFile && !this.sizeError); + } + //remove selected file + onRemove() { + this.data.name = ""; + this.selectFile = false; + this.selectedFileName = ""; + } +} diff --git a/dmp-frontend/src/app/ui/admin/dmp-blueprint/listing/dmp-blueprint-listing.component.html b/dmp-frontend/src/app/ui/admin/dmp-blueprint/listing/dmp-blueprint-listing.component.html index 173ee15e3..447ae70fb 100644 --- a/dmp-frontend/src/app/ui/admin/dmp-blueprint/listing/dmp-blueprint-listing.component.html +++ b/dmp-frontend/src/app/ui/admin/dmp-blueprint/listing/dmp-blueprint-listing.component.html @@ -49,7 +49,7 @@
- {{enumUtils.toDescriptionTemplateTypeStatusString(item.status) | nullifyValue}} + {{enumUtils.toDmpBlueprintStatusString(item.status) | nullifyValue}}
@@ -78,7 +78,7 @@
- {{enumUtils.toDescriptionTemplateTypeStatusString(row.status) | nullifyValue}} + {{enumUtils.toDmpBlueprintStatusString(row.status) | nullifyValue}}
diff --git a/dmp-frontend/src/app/ui/misc/navigation/navigation.component.html b/dmp-frontend/src/app/ui/misc/navigation/navigation.component.html index 73803bc87..08e41f183 100644 --- a/dmp-frontend/src/app/ui/misc/navigation/navigation.component.html +++ b/dmp-frontend/src/app/ui/misc/navigation/navigation.component.html @@ -13,7 +13,7 @@ {{'NAV-BAR.USERS' | translate}} {{'NAV-BAR.DMP-BLUEPRINTS' | translate}} - {{'NAV-BAR.DATASETS-ADMIN' + {{'NAV-BAR.DESCRIPTION-TEMPLATES' | translate}} diff --git a/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts b/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts index 3f16356b5..2c67d929a 100644 --- a/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts +++ b/dmp-frontend/src/app/ui/sidebar/sidebar.component.ts @@ -51,7 +51,7 @@ export const PUBLIC_ROUTES: RouteInfo[] = [ export const ADMIN_ROUTES: RouteInfo[] = [ { path: '/dmp-blueprints', title: 'SIDE-BAR.DMP-BLUEPRINTS', icon: 'library_books' }, - { path: '/dataset-profiles', title: 'SIDE-BAR.DESCRIPTION-TEMPLATES', icon: 'library_books' }, + { path: '/description-templates', title: 'SIDE-BAR.DESCRIPTION-TEMPLATES', icon: 'library_books' }, { path: '/description-template-type', title: 'SIDE-BAR.DESCRIPTION-TEMPLATE-TYPES', icon: 'library_books' }, { path: '/users', title: 'SIDE-BAR.USERS', icon: 'people' }, { path: '/language-editor', title: 'SIDE-BAR.LANGUAGE-EDITOR', icon: 'language' }, @@ -59,7 +59,7 @@ export const ADMIN_ROUTES: RouteInfo[] = [ ]; export const DATASET_TEMPLATE_ROUTES: RouteInfo[] = [ - { path: '/dataset-profiles', title: 'SIDE-BAR.DESCRIPTION-TEMPLATES', icon: 'library_books' } + { path: '/description-templates', title: 'SIDE-BAR.DESCRIPTION-TEMPLATES', icon: 'library_books' } ]; export const INFO_ROUTES: RouteInfo[] = [ diff --git a/dmp-frontend/src/assets/i18n/en.json b/dmp-frontend/src/assets/i18n/en.json index 47bfb505e..79f4db20f 100644 --- a/dmp-frontend/src/assets/i18n/en.json +++ b/dmp-frontend/src/assets/i18n/en.json @@ -231,11 +231,14 @@ "OVERVIEW": "Overview", "HOME": "Board", "DESCRIPTION-TEMPLATE-TYPES": "Description Types", + "DESCRIPTION-TEMPLATES": "Description Templates", "NEW-DESCRIPTION-TEMPLATE-TYPE": "New", "EDIT-DESCRIPTION-TEMPLATE-TYPE": "Edit", "DMP-BLUEPRINTS": "DMP Blueprints", "NEW-DMP-BLUEPRINT": "New", - "EDIT-DMP-BLUEPRINT": "Edit" + "EDIT-DMP-BLUEPRINT": "Edit", + "NEW-DESCRIPTION-TEMPLATES": "New", + "EDIT-DESCRIPTION-TEMPLATES": "Edit" }, "COOKIE": { "MESSAGE": "This website uses cookies to enhance the user experience.", @@ -974,39 +977,47 @@ }, "EMPTY-LIST": "Nothing here yet." }, - "DATASET-PROFILE-LISTING": { + "DESCRIPTION-TEMPLATE-LISTING": { "TITLE": "Description Templates", - "COLUMNS": { + "CREATE-DESCRIPTION-TEMPLATE": "Create Description Template", + "FIELDS": { "NAME": "Name", - "REFERNCE": "Reference", - "GRANT": "Grant", - "URI": "Uri", - "ROLE": "Role", - "TEMPLATE": "Template", - "ORGANIZATION": "Organization", + "DESCRIPTION":"Description", "STATUS": "Status", - "VISITED": "Visited", - "EDITED": "Edited", - "DESCRIPTION": "Description", - "CREATED": "Created", - "ACTIONS": "Actions", - "DMP": "DMP", - "PROFILE": "Template", - "DATAREPOSITORIES": "Data Repositories", - "REGISTRIES": "Registries", - "SERVICES": "Services" + "UPDATED-AT": "Updated", + "CREATED-AT": "Created", + "PUBLISHED-AT": "Published" + }, + "FILTER": { + "TITLE": "Filters", + "IS-ACTIVE": "Is Active", + "CANCEL": "Cancel", + "APPLY-FILTERS": "Apply filters" + }, + "CONFIRM-DELETE-DIALOG": { + "MESSAGE": "Would you like to delete this Description Template?", + "CONFIRM-BUTTON": "Yes, delete", + "CANCEL-BUTTON": "No" }, "ACTIONS": { + "DELETE": "Delete", "EDIT": "Edit", - "MAKE-IT-PUBLIC": "Make it public", - "VIEW": "View", "CLONE": "Clone", + "DOWNLOAD-XML": "Download XML", "NEW-VERSION": "New Version", "NEW-VERSION-FROM-FILE": "New Version from File", - "VIEW-VERSIONS": "All Description Template Versions", - "DELETE": "Delete", - "CREATE-DESCRIPTION-TEMPLATE": "Create Description Template" - } + "VIEW-VERSIONS": "All Description Template Versions" + }, + "IMPORT": { + "UPLOAD-XML": "Import", + "UPLOAD-XML-FILE-TITLE": "Import Description Template", + "UPLOAD-XML-NAME": "Name Of Description Template", + "UPLOAD-XML-IMPORT": "File", + "UPLOAD-XML-FILE-CANCEL": "Cancel" + }, + "SUCCESSFUL-DELETE": "Successful Delete", + "UNSUCCESSFUL-DELETE": "This item could not be deleted.", + "TEMPLATE-UPLOAD-SUCCESS": "Template successfully uploaded" }, "DESCRIPTION-TEMPLATE-TYPE-LISTING": { "TITLE": "Description Types", @@ -1628,6 +1639,14 @@ "FINALIZED": "Finalized", "DRAFT": "Draft" }, + "DESCRIPTION-TEMPLATE-STATUS": { + "FINALIZED": "Finalized", + "DRAFT": "Draft" + }, + "DMP-BLUEPRINT-STATUS": { + "FINALIZED": "Finalized", + "DRAFT": "Draft" + }, "DMP-BLUEPRINT-SYSTEM-FIELD-TYPE": { "TEXT": "Title", "HTML_TEXT": "Description",