From be9b8df4684ad040a814256644546a0e1c467eef Mon Sep 17 00:00:00 2001 From: Diamantis Tziotzios Date: Mon, 15 Apr 2024 18:19:56 +0300 Subject: [PATCH] added progress indication at DMP Editor. --- ...escription-base-fields-editor.component.ts | 37 +++-- .../dmp-editor.component.html | 4 +- .../dmp-editor.component.ts | 41 +++--- .../dmp-editor-blueprint/dmp-editor.model.ts | 126 ++++++++++++------ .../dmp-editor-blueprint/dmp-editor.module.ts | 12 +- ...mp-form-progress-indication.component.html | 5 + ...mp-form-progress-indication.component.scss | 16 +++ .../dmp-form-progress-indication.component.ts | 104 +++++++++++++++ .../dmp-form-progress-indication.module.ts | 18 +++ .../dmp-user-field.component.html | 4 +- .../dmp-user-field.component.ts | 6 +- 11 files changed, 284 insertions(+), 89 deletions(-) create mode 100644 dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.component.html create mode 100644 dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.component.scss create mode 100644 dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.component.ts create mode 100644 dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.module.ts diff --git a/dmp-frontend/src/app/ui/description/editor/description-base-fields-editor/description-base-fields-editor.component.ts b/dmp-frontend/src/app/ui/description/editor/description-base-fields-editor/description-base-fields-editor.component.ts index c5a76d3b0..44b8ba390 100644 --- a/dmp-frontend/src/app/ui/description/editor/description-base-fields-editor/description-base-fields-editor.component.ts +++ b/dmp-frontend/src/app/ui/description/editor/description-base-fields-editor/description-base-fields-editor.component.ts @@ -1,19 +1,16 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; +import { DescriptionStatus } from '@app/core/common/enum/description-status'; +import { DescriptionTemplateVersionStatus } from '@app/core/common/enum/description-template-version-status'; import { IsActive } from '@app/core/common/enum/is-active.enum'; import { DescriptionTemplate } from '@app/core/model/description-template/description-template'; import { Description } from '@app/core/model/description/description'; -import { DmpBlueprintDefinitionSection } from '@app/core/model/dmp-blueprint/dmp-blueprint'; -import { Dmp, DmpDescriptionTemplate } from '@app/core/model/dmp/dmp'; -import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service'; +import { DmpDescriptionTemplate } from '@app/core/model/dmp/dmp'; import { DescriptionService } from '@app/core/services/description/description.service'; -import { DmpBlueprintService } from '@app/core/services/dmp/dmp-blueprint.service'; import { BaseComponent } from '@common/base/base.component'; import { takeUntil } from 'rxjs/operators'; import { DeprecatedDescriptionTemplateDialog } from './dialog-description-template/deprecated-description-template-dialog.component'; -import { DescriptionTemplateVersionStatus } from '@app/core/common/enum/description-template-version-status'; -import { DescriptionStatus } from '@app/core/common/enum/description-status'; @Component({ selector: 'app-description-base-fields-editor-component', @@ -47,15 +44,17 @@ export class DescriptionBaseFieldsEditorComponent extends BaseComponent { const currentVersionsOfDescriptionTemplates = dmpDescriptionTemplates.map(x => x.currentDescriptionTemplate); this.availableDescriptionTemplates.push(...currentVersionsOfDescriptionTemplates); - const isPreviousVersion: boolean = this.description.descriptionTemplate.versionStatus === DescriptionTemplateVersionStatus.Previous; - if (isPreviousVersion === true) { - if (this.description.status === DescriptionStatus.Draft) { - this.openDeprecatedDescriptionTemplateDialog(); + if (this.description?.descriptionTemplate != null) { + const isPreviousVersion: boolean = this.description.descriptionTemplate.versionStatus === DescriptionTemplateVersionStatus.Previous; + if (isPreviousVersion === true) { + if (this.description.status === DescriptionStatus.Draft) { + this.openDeprecatedDescriptionTemplateDialog(); + } else { + this.availableDescriptionTemplates.push(this.description.descriptionTemplate); + } } else { this.availableDescriptionTemplates.push(this.description.descriptionTemplate); } - } else { - this.availableDescriptionTemplates.push(this.description.descriptionTemplate); } } @@ -67,19 +66,19 @@ export class DescriptionBaseFieldsEditorComponent extends BaseComponent { }); dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe( result => { - if(result) { + if (result) { this.descriptionService.updateDescriptionTemplate({ id: this.description.id, hash: this.description.hash }) - .subscribe( - result => { - this.refresh.emit(result); - }, - error => console.error(error)); + .subscribe( + result => { + this.refresh.emit(result); + }, + error => console.error(error)); } else { this.availableDescriptionTemplates.push(this.description.descriptionTemplate); } - }); + }); } } diff --git a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.html b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.html index c903e861c..66a6bb93c 100644 --- a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.html +++ b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.html @@ -111,13 +111,13 @@ chevron_left
{{'DMP-EDITOR.ACTIONS.PREVIOUS-STEP' | translate}}
-
+
{{'DMP-EDITOR.ACTIONS.NEXT-STEP' | translate}}
chevron_right
- +
diff --git a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.ts b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.ts index 078d541d8..5348b035f 100644 --- a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.ts +++ b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.component.ts @@ -88,7 +88,7 @@ export class DmpEditorComponent extends BaseEditor implemen filterFn: (searchQuery: string, data?: any) => this.dmpBlueprintService.query(this.dmpBlueprintService.buildAutocompleteLookup(searchQuery, null, null, [DmpBlueprintStatus.Finalized])).pipe(map(x => x.items)), getSelectedItem: (selectedItem: any) => this.dmpBlueprintService.query(this.dmpBlueprintService.buildAutocompleteLookup(null, null, [selectedItem])).pipe(map(x => x.items[0])), displayFn: (item: DmpBlueprint) => item.label, - subtitleFn: (item: DmpBlueprint) => this.language.instant('DMP-EDITOR.FIELDS.DMP-BLUEPRINT-VERSION') + ' '+ item.version, + subtitleFn: (item: DmpBlueprint) => this.language.instant('DMP-EDITOR.FIELDS.DMP-BLUEPRINT-VERSION') + ' ' + item.version, titleFn: (item: DmpBlueprint) => item.label, valueAssign: (item: DmpBlueprint) => item.id, }; @@ -174,14 +174,14 @@ export class DmpEditorComponent extends BaseEditor implemen try { this.editorModel = data ? new DmpEditorModel().fromModel(data) : new DmpEditorModel(); if (data) { - if(data.descriptions){ + if (data.descriptions) { if (data.status == DmpStatus.Finalized) { data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status === DescriptionStatus.Finalized); } else { data.descriptions = data.descriptions.filter(x => x.isActive === IsActive.Active && x.status !== DescriptionStatus.Canceled); } } - if(data.dmpDescriptionTemplates){ + if (data.dmpDescriptionTemplates) { data.dmpDescriptionTemplates = data.dmpDescriptionTemplates.filter(x => x.isActive === IsActive.Active); } @@ -304,6 +304,11 @@ export class DmpEditorComponent extends BaseEditor implemen // Steps // // + + get maxSteps(): number { + return this.item?.blueprint?.definition?.sections?.length ?? 0; + } + changeStep(index: number) { this.step = index; this.resetScroll(); @@ -389,7 +394,8 @@ export class DmpEditorComponent extends BaseEditor implemen DmpEditorModel.reApplyPropertiesValidators( { formGroup: this.formGroup, - validationErrorModel: this.editorModel.validationErrorModel + validationErrorModel: this.editorModel.validationErrorModel, + blueprint: this.item.blueprint } ); this.formGroup.get('properties').get('contacts').markAsDirty(); @@ -404,7 +410,8 @@ export class DmpEditorComponent extends BaseEditor implemen DmpEditorModel.reApplyPropertiesValidators( { formGroup: this.formGroup, - validationErrorModel: this.editorModel.validationErrorModel + validationErrorModel: this.editorModel.validationErrorModel, + blueprint: this.item.blueprint } ); this.formGroup.get('properties').get('contacts').markAsDirty(); @@ -455,32 +462,32 @@ export class DmpEditorComponent extends BaseEditor implemen }); } - canAddDescription(section: DmpBlueprintDefinitionSection ): boolean{ - if(section.hasTemplates){ - if (section.descriptionTemplates?.length > 0){ + canAddDescription(section: DmpBlueprintDefinitionSection): boolean { + if (section.hasTemplates) { + if (section.descriptionTemplates?.length > 0) { const descriptions = this.descriptionsInSection(section.id) - if (this.item.dmpDescriptionTemplates.filter(x => x.sectionId == section.id).length > descriptions.map(x => x.dmpDescriptionTemplate).length){ + if (this.item.dmpDescriptionTemplates.filter(x => x.sectionId == section.id).length > descriptions.map(x => x.dmpDescriptionTemplate).length) { return true; } - let multiplicityValidResults :boolean[] = []; + let multiplicityValidResults: boolean[] = []; section.descriptionTemplates.forEach(sectionDescriptionTemplate => { - if (sectionDescriptionTemplate.maxMultiplicity != null){ + if (sectionDescriptionTemplate.maxMultiplicity != null) { const count = descriptions.filter(x => x.dmpDescriptionTemplate.descriptionTemplateGroupId == sectionDescriptionTemplate.descriptionTemplateGroupId).length || 0; if (count >= sectionDescriptionTemplate.maxMultiplicity) multiplicityValidResults.push(false); else multiplicityValidResults.push(true); - }else{ + } else { multiplicityValidResults.push(true); } }) - if(multiplicityValidResults.includes(true)) return true + if (multiplicityValidResults.includes(true)) return true else return false; - }else{ + } else { return true; } - }else{ + } else { return false; } } @@ -504,10 +511,10 @@ export class DmpEditorComponent extends BaseEditor implemen dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(groupId => { if (groupId) { let data = this.formGroup.get('descriptionTemplates').get(sectionId.toString()).value as Guid[]; - if (data){ + if (data) { data.push(groupId); this.formGroup.get('descriptionTemplates').get(sectionId.toString()).patchValue(data); - } else{ + } else { this.formGroup.get('descriptionTemplates').get(sectionId.toString()).patchValue([groupId]); } } diff --git a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.model.ts b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.model.ts index 86c991402..9559ca651 100644 --- a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.model.ts +++ b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.model.ts @@ -1,11 +1,12 @@ -import { FormArray, FormControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; +import { FormArray, FormControl, UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; import { DmpAccessType } from "@app/core/common/enum/dmp-access-type"; +import { DmpBlueprintFieldCategory } from "@app/core/common/enum/dmp-blueprint-field-category"; import { DmpContactType } from "@app/core/common/enum/dmp-contact-type"; import { DmpStatus } from "@app/core/common/enum/dmp-status"; import { DmpUserRole } from "@app/core/common/enum/dmp-user-role"; import { DmpUserType } from "@app/core/common/enum/dmp-user-type"; import { IsActive } from "@app/core/common/enum/is-active.enum"; -import { DmpBlueprint } from "@app/core/model/dmp-blueprint/dmp-blueprint"; +import { DmpBlueprint, FieldInSection } from "@app/core/model/dmp-blueprint/dmp-blueprint"; import { Dmp, DmpBlueprintValue, DmpBlueprintValuePersist, DmpContact, DmpContactPersist, DmpDescriptionTemplate, DmpDescriptionTemplatePersist, DmpPersist, DmpProperties, DmpPropertiesPersist, DmpReferenceDataPersist, DmpReferencePersist, DmpUser, DmpUserPersist } from "@app/core/model/dmp/dmp"; import { DmpReference } from "@app/core/model/dmp/dmp-reference"; import { ReferencePersist } from "@app/core/model/reference/reference"; @@ -103,8 +104,8 @@ export class DmpEditorModel extends BaseEditorModel implements DmpPersist { const descriptionTemplatesFormGroup = this.formBuilder.group({}); (this.descriptionTemplates ?? []).filter(x => x?.sectionId).map(x => x.sectionId).map( - (item, index) => descriptionTemplatesFormGroup.addControl(item.toString(), - new FormControl(this.descriptionTemplates?.filter(x => x.sectionId === item)?.filter(x => x.descriptionTemplateGroupId).map(x => x.descriptionTemplateGroupId) || [], context.getValidation('descriptionTemplates').validators)) + (item, index) => descriptionTemplatesFormGroup.addControl(item.toString(), + new FormControl(this.descriptionTemplates?.filter(x => x.sectionId === item)?.filter(x => x.descriptionTemplateGroupId).map(x => x.descriptionTemplateGroupId) || [], context.getValidation('descriptionTemplates').validators)) ); // // buildForm({ // // rootPath: `descriptionTemplates[${index}].` @@ -120,13 +121,16 @@ export class DmpEditorModel extends BaseEditorModel implements DmpPersist { 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: 'properties', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'properties')] }); + baseValidationArray.push({ key: 'status', validators: [BackendErrorValidator(this.validationErrorModel, 'status')] }); + baseValidationArray.push({ key: 'properties', validators: [BackendErrorValidator(this.validationErrorModel, 'properties')] }); baseValidationArray.push({ key: 'description', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'description')] }); - baseValidationArray.push({ key: 'language', validators: [BackendErrorValidator(this.validationErrorModel, 'language')] }); - baseValidationArray.push({ key: 'blueprint', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'blueprint')] }); - baseValidationArray.push({ key: 'accessType', validators: [BackendErrorValidator(this.validationErrorModel, 'accessType')] }); + baseValidationArray.push({ key: 'language', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'language')] }); + baseValidationArray.push({ key: 'blueprint', validators: [BackendErrorValidator(this.validationErrorModel, 'blueprint')] }); + baseValidationArray.push({ key: 'accessType', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'accessType')] }); baseValidationArray.push({ key: 'descriptionTemplates', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'descriptionTemplates')] }); + + baseValidationArray.push({ key: 'dmpDescriptionValidator', validators: [] }); + baseValidationArray.push({ key: 'users', validators: [BackendErrorValidator(this.validationErrorModel, `users`)] }); baseValidationArray.push({ key: 'hash', validators: [] }); @@ -143,6 +147,7 @@ export class DmpEditorModel extends BaseEditorModel implements DmpPersist { static reApplyPropertiesValidators(params: { formGroup: UntypedFormGroup, validationErrorModel: ValidationErrorModel, + blueprint: DmpBlueprint }): void { const { formGroup, validationErrorModel } = params; @@ -150,20 +155,36 @@ export class DmpEditorModel extends BaseEditorModel implements DmpPersist { DmpPropertiesEditorModel.reapplyValidators({ formGroup: control as UntypedFormGroup, rootPath: `properties.`, - validationErrorModel: validationErrorModel + validationErrorModel: validationErrorModel, + blueprint: params.blueprint }); + } + + static reApplyDescriptionTemplateValidators(params: { + formGroup: UntypedFormGroup, + validationErrorModel: ValidationErrorModel, + }): void { + + const { formGroup, validationErrorModel } = params; const descriptionTemplates = formGroup?.get('descriptionTemplates') as UntypedFormGroup; const keys = Object.keys(descriptionTemplates.value as Object); keys.forEach((key) => { const control = descriptionTemplates?.get(key); - DmpBlueprintValueEditorModel.reapplyValidators({ + DmpDescriptionTemplateEditorModel.reapplyValidators({ formGroup: control as UntypedFormGroup, rootPath: `descriptionTemplates[${key}].`, validationErrorModel: validationErrorModel }) }); + } + static reApplyUsersValidators(params: { + formGroup: UntypedFormGroup, + validationErrorModel: ValidationErrorModel, + }): void { + + const { formGroup, validationErrorModel } = params; (formGroup.get('users') as FormArray).controls?.forEach( (control, index) => DmpUserEditorModel.reapplyValidators({ formGroup: control as UntypedFormGroup, @@ -186,14 +207,16 @@ export class DmpPropertiesEditorModel implements DmpPropertiesPersist { fromModel(item: DmpProperties, dmpReferences: DmpReference[], dmpBlueprint: DmpBlueprint): DmpPropertiesEditorModel { - + dmpBlueprint.definition.sections.forEach(section => { section.fields?.forEach(field => { - this.dmpBlueprintValues.set(field.id, new DmpBlueprintValueEditorModel(this.validationErrorModel).fromModel( - { - fieldId: field.id, - fieldValue: item?.dmpBlueprintValues?.find(x => x.fieldId == field.id)?.fieldValue, - }, dmpReferences)); + if (field.category !== DmpBlueprintFieldCategory.System) { + this.dmpBlueprintValues.set(field.id, new DmpBlueprintValueEditorModel(this.validationErrorModel).fromModel( + { + fieldId: field.id, + fieldValue: item?.dmpBlueprintValues?.find(x => x.fieldId == field.id)?.fieldValue, + }, dmpReferences, field)); + } }); }); if (item?.contacts) { item.contacts.map(x => this.contacts.push(new DmpContactEditorModel(this.validationErrorModel).fromModel(x))); } @@ -228,8 +251,8 @@ export class DmpPropertiesEditorModel implements DmpPropertiesPersist { const dmpBlueprintValuesFormGroup = this.formBuilder.group({}); this.dmpBlueprintValues.forEach((value, key) => dmpBlueprintValuesFormGroup.addControl(key.toString(), value.buildForm({ - rootPath: `${rootPath}dmpBlueprintValues[${key}].` - })), context.getValidation('dmpBlueprintValues') + rootPath: `${rootPath}dmpBlueprintValues[${key}].` + })), context.getValidation('dmpBlueprintValues') ) formGroup.addControl('dmpBlueprintValues', dmpBlueprintValuesFormGroup); @@ -254,7 +277,8 @@ export class DmpPropertiesEditorModel implements DmpPropertiesPersist { static reapplyValidators(params: { formGroup: UntypedFormGroup, validationErrorModel: ValidationErrorModel, - rootPath: string + rootPath: string, + blueprint: DmpBlueprint }): void { const { formGroup, rootPath, validationErrorModel } = params; @@ -266,9 +290,10 @@ export class DmpPropertiesEditorModel implements DmpPropertiesPersist { DmpBlueprintValueEditorModel.reapplyValidators({ formGroup: control as UntypedFormGroup, rootPath: `${rootPath}dmpBlueprintValues[${key}].`, - validationErrorModel: validationErrorModel + validationErrorModel: validationErrorModel, + isRequired: params.blueprint.definition.sections.flatMap(x => x.fields).find(x => x.id.toString() == key).required }) - }); + }); (formGroup.get('contacts') as FormArray).controls?.forEach( (control, index) => DmpContactEditorModel.reapplyValidators({ @@ -284,6 +309,8 @@ export class DmpBlueprintValueEditorModel implements DmpBlueprintValuePersist { fieldId: Guid; fieldValue: string; references: DmpReferencePersist[] = []; + isRequired: boolean = false; + category: DmpBlueprintFieldCategory; protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); @@ -291,7 +318,7 @@ export class DmpBlueprintValueEditorModel implements DmpBlueprintValuePersist { public validationErrorModel: ValidationErrorModel = new ValidationErrorModel() ) { } - fromModel(item: DmpBlueprintValue, dmpReferences: DmpReference[]): DmpBlueprintValueEditorModel { + fromModel(item: DmpBlueprintValue, dmpReferences: DmpReference[], field: FieldInSection): DmpBlueprintValueEditorModel { this.fieldId = item.fieldId; this.fieldValue = item.fieldValue; this.references = dmpReferences?.filter(x => x.data.blueprintFieldId == this.fieldId && x.isActive == IsActive.Active).map(x => { @@ -311,6 +338,10 @@ export class DmpBlueprintValueEditorModel implements DmpBlueprintValuePersist { } }); + this.isRequired = field.required; + if (this.isRequired) console.log(field); + this.category = field.category; + return this; } @@ -323,28 +354,39 @@ export class DmpBlueprintValueEditorModel implements DmpBlueprintValuePersist { if (context == null) { context = DmpBlueprintValueEditorModel.createValidationContext({ validationErrorModel: this.validationErrorModel, - rootPath + rootPath, + isRequired: this.isRequired }); } - return this.formBuilder.group({ + const formGroup = this.formBuilder.group({ fieldId: [{ value: this.fieldId, disabled: disabled }, context.getValidation('fieldId').validators], - fieldValue: [{ value: this.fieldValue, disabled: disabled }, context.getValidation('fieldValue').validators], - references: [{ value: this.references?.map(x => x.reference), disabled: disabled }, context.getValidation('references').validators], }); + switch (this.category) { + case DmpBlueprintFieldCategory.ReferenceType: + formGroup.addControl('references', new FormControl({ value: this.references?.map(x => x.reference), disabled: disabled }, context.getValidation('references').validators)); + break; + case DmpBlueprintFieldCategory.System: + case DmpBlueprintFieldCategory.Extra: + formGroup.addControl('fieldValue', new FormControl({ value: this.fieldValue, disabled: disabled }, context.getValidation('fieldValue').validators)); + break; + } + + return formGroup; } static createValidationContext(params: { rootPath?: string, - validationErrorModel: ValidationErrorModel + validationErrorModel: ValidationErrorModel, + isRequired: boolean, }): ValidationContext { const { rootPath = '', validationErrorModel } = params; const baseContext: ValidationContext = new ValidationContext(); const baseValidationArray: Validation[] = new Array(); baseValidationArray.push({ key: 'fieldId', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}fieldId`)] }); - baseValidationArray.push({ key: 'fieldValue', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}fieldValue`)] }); - baseValidationArray.push({ key: 'references', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}references`)] }); + baseValidationArray.push({ key: 'fieldValue', validators: params.isRequired ? [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}fieldValue`)] : [BackendErrorValidator(validationErrorModel, `${rootPath}fieldValue`)] }); + baseValidationArray.push({ key: 'references', validators: params.isRequired ? [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}references`)] : [BackendErrorValidator(validationErrorModel, `${rootPath}references`)] }); baseContext.validation = baseValidationArray; return baseContext; @@ -353,13 +395,15 @@ export class DmpBlueprintValueEditorModel implements DmpBlueprintValuePersist { static reapplyValidators(params: { formGroup: UntypedFormGroup, validationErrorModel: ValidationErrorModel, - rootPath: string + rootPath: string, + isRequired: boolean }): void { const { formGroup, rootPath, validationErrorModel } = params; const context = DmpBlueprintValueEditorModel.createValidationContext({ rootPath, - validationErrorModel + validationErrorModel, + isRequired: params.isRequired }); ['fieldId', 'fieldValue', 'references'].forEach(keyField => { @@ -375,7 +419,7 @@ export class DmpContactEditorModel implements DmpContactPersist { firstName: string; lastName: string; email: string; - contactType: DmpContactType= DmpContactType.Internal; + contactType: DmpContactType = DmpContactType.Internal; protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); @@ -384,11 +428,11 @@ export class DmpContactEditorModel implements DmpContactPersist { ) { } fromModel(item: DmpContact): DmpContactEditorModel { - if(item?.user?.id) this.userId = item.user.id; + if (item?.user?.id) this.userId = item.user.id; this.firstName = item.firstName; this.lastName = item.lastName; - this.email = item.email; - this.contactType = (item == null || this.userId != null) ? DmpContactType.Internal : DmpContactType.External; + this.email = item.email; + this.contactType = (item == null || this.userId != null) ? DmpContactType.Internal : DmpContactType.External; return this; } @@ -457,7 +501,7 @@ export class DmpUserEditorModel implements DmpUserPersist { user: Guid; role: DmpUserRole; email: string; - userType: DmpUserType= DmpUserType.Internal; + userType: DmpUserType = DmpUserType.Internal; sectionId: Guid; protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder(); @@ -467,10 +511,10 @@ export class DmpUserEditorModel implements DmpUserPersist { ) { } fromModel(item: DmpUser): DmpUserEditorModel { - if(item?.user?.id) this.user = item.user.id; + if (item?.user?.id) this.user = item.user.id; this.role = item.role; // TODO this.email = item.email; - this.userType = (item == null || this.user != null) ? DmpUserType.Internal : DmpUserType.External; + this.userType = (item == null || this.user != null) ? DmpUserType.Internal : DmpUserType.External; this.sectionId = item.sectionId; return this; @@ -623,7 +667,7 @@ export class DmpDescriptionTemplateEditorModel implements DmpDescriptionTemplate if (context == null) { context = DmpDescriptionTemplateEditorModel.createValidationContext({ validationErrorModel: this.validationErrorModel, - rootPath + rootPath: rootPath, }); } @@ -635,7 +679,7 @@ export class DmpDescriptionTemplateEditorModel implements DmpDescriptionTemplate static createValidationContext(params: { rootPath?: string, - validationErrorModel: ValidationErrorModel + validationErrorModel: ValidationErrorModel, }): ValidationContext { const { rootPath = '', validationErrorModel } = params; diff --git a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.module.ts b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.module.ts index 7a0957885..80b7e8fe4 100644 --- a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.module.ts +++ b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/dmp-editor.module.ts @@ -1,15 +1,16 @@ +import { DragDropModule } from '@angular/cdk/drag-drop'; import { NgModule } from '@angular/core'; import { FormattingModule } from '@app/core/formatting.module'; +import { AutoCompleteModule } from '@app/library/auto-complete/auto-complete.module'; import { RichTextEditorModule } from '@app/library/rich-text-editor/rich-text-editor.module'; +import { ReferenceFieldModule } from '@app/ui/reference/reference-field/reference-field.module'; import { CommonFormsModule } from '@common/forms/common-forms.module'; import { ConfirmationDialogModule } from '@common/modules/confirmation-dialog/confirmation-dialog.module'; import { CommonUiModule } from '@common/ui/common-ui.module'; +import { DmpUserFieldModule } from '../dmp-user-field/dmp-user-field.module'; import { DmpEditorComponent } from './dmp-editor.component'; import { DmpEditorRoutingModule } from './dmp-editor.routing'; -import { AutoCompleteModule } from '@app/library/auto-complete/auto-complete.module'; -import { ReferenceFieldModule } from '@app/ui/reference/reference-field/reference-field.module'; -import { DragDropModule } from '@angular/cdk/drag-drop'; -import { DmpUserFieldModule } from '../dmp-user-field/dmp-user-field.module'; +import { DmpFormProgressIndicationModule } from './form-progress-indication/dmp-form-progress-indication.module'; @NgModule({ imports: [ @@ -22,7 +23,8 @@ import { DmpUserFieldModule } from '../dmp-user-field/dmp-user-field.module'; AutoCompleteModule, ReferenceFieldModule, DragDropModule, - DmpUserFieldModule + DmpUserFieldModule, + DmpFormProgressIndicationModule ], declarations: [ DmpEditorComponent, diff --git a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.component.html b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.component.html new file mode 100644 index 000000000..68f919097 --- /dev/null +++ b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.component.html @@ -0,0 +1,5 @@ +
+
{{progressSoFar}} {{'GENERAL.PREPOSITIONS.OF' | translate}} {{total}}
+ +
{{value}}%
+
diff --git a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.component.scss b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.component.scss new file mode 100644 index 000000000..a15510639 --- /dev/null +++ b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.component.scss @@ -0,0 +1,16 @@ +.percentage { + color: #212121; + opacity: 0.7; + font-weight: 400; + font-size: 0.875rem; +} + +.progress-bar { + border-radius: 20px; + height: 11px; + +} + +::ng-deep .mat-progress-bar .mat-progress-bar-fill::after { + border-radius: 20px !important; +} diff --git a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.component.ts b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.component.ts new file mode 100644 index 000000000..f38f5c58d --- /dev/null +++ b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.component.ts @@ -0,0 +1,104 @@ +import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { AbstractControl, UntypedFormArray, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; +import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service'; +import { BaseComponent } from '@common/base/base.component'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'app-dmp-form-progress-indication', + templateUrl: './dmp-form-progress-indication.component.html', + styleUrls: ['./dmp-form-progress-indication.component.scss'] +}) +export class DmpFormProgressIndicationComponent extends BaseComponent implements OnInit, OnChanges { + @Input() formGroup: UntypedFormGroup; + + @Input() public progressValueAccuracy = 2; + progressSoFar: number; + total: number; + percent: number; + + constructor(private visibilityRulesService: VisibilityRulesService) { super(); } + + public value = 0; + ngOnInit() { + this.init(); + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.formGroup) { + this.init(); + } + } + + init() { + setTimeout(() => { this.calculateValueForProgressbar(); }); + this.formGroup + .valueChanges + .pipe(takeUntil(this._destroyed)) + .subscribe(control => { + setTimeout(() => { this.calculateValueForProgressbar(); }); + }); + } + + calculateValueForProgressbar() { + this.progressSoFar = this.countFormControlsValidForProgress(this.formGroup); + this.total = this.countFormControlsRequiredFieldsForTotal(this.formGroup); + this.percent = (this.progressSoFar / this.total) * 100; + this.value = Number.parseFloat(this.percent.toPrecision(this.progressValueAccuracy)); + } + + countFormControlsValidForProgress(formControl: AbstractControl): number { + let valueCurrent = 0; + if (formControl instanceof UntypedFormControl) { + if (this.controlRequired(formControl) && this.controlEnabled(formControl) && formControl.valid) { + valueCurrent++; + } + } else if (formControl instanceof UntypedFormGroup) { + Object.keys(formControl.controls).forEach(item => { + const control = formControl.get(item); + valueCurrent = valueCurrent + this.countFormControlsValidForProgress(control); + }); + } else if (formControl instanceof UntypedFormArray) { + formControl.controls.forEach(item => { + valueCurrent = valueCurrent + this.countFormControlsValidForProgress(item); + }); + } + return valueCurrent; + } + + countFormControlsRequiredFieldsForTotal(formControl: AbstractControl, checkVisibility = false): number { + let valueCurrent = 0; + if (formControl instanceof UntypedFormControl) { + if (this.controlRequired(formControl) && this.controlEnabled(formControl)) { + valueCurrent++; + } + } else if (formControl instanceof UntypedFormGroup) { + if (!checkVisibility || (!formControl.get('id')?.value || (this.visibilityRulesService.isVisibleMap[formControl.get('id').value] ?? true))) { + Object.keys(formControl.controls).forEach(item => { + const control = formControl.get(item); + valueCurrent = valueCurrent + this.countFormControlsRequiredFieldsForTotal(control, checkVisibility); + }); + } + } else if (formControl instanceof UntypedFormArray) { + formControl.controls.forEach(item => { + valueCurrent = valueCurrent + this.countFormControlsRequiredFieldsForTotal(item, checkVisibility); + }); + } + return valueCurrent; + } + + controlRequired(formControl: AbstractControl) { + if (formControl.validator) { + const validator = formControl.validator({} as AbstractControl); + if (validator && validator.required) { + return true; + } + } else { return false } + } + + controlEnabled(formControl: AbstractControl) { + if (formControl.enabled) { + return true; + } else { return false } + } +} diff --git a/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.module.ts b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.module.ts new file mode 100644 index 000000000..3c004582a --- /dev/null +++ b/dmp-frontend/src/app/ui/dmp/dmp-editor-blueprint/form-progress-indication/dmp-form-progress-indication.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { CommonFormsModule } from '@common/forms/common-forms.module'; +import { CommonUiModule } from '@common/ui/common-ui.module'; +import { DmpFormProgressIndicationComponent } from './dmp-form-progress-indication.component'; + +@NgModule({ + imports: [ + CommonUiModule, + CommonFormsModule + ], + declarations: [ + DmpFormProgressIndicationComponent + ], + exports: [ + DmpFormProgressIndicationComponent + ] +}) +export class DmpFormProgressIndicationModule { } diff --git a/dmp-frontend/src/app/ui/dmp/dmp-user-field/dmp-user-field.component.html b/dmp-frontend/src/app/ui/dmp/dmp-user-field/dmp-user-field.component.html index 0fe457109..e82c4b574 100644 --- a/dmp-frontend/src/app/ui/dmp/dmp-user-field/dmp-user-field.component.html +++ b/dmp-frontend/src/app/ui/dmp/dmp-user-field/dmp-user-field.component.html @@ -43,7 +43,7 @@
- {{'DMP-EDITOR.FIELDS.USER' | translate}}* + {{'DMP-EDITOR.FIELDS.USER' | translate}} {{user.get('user').getError('backendError').message}} {{'GENERAL.VALIDATION.REQUIRED' | translate}} @@ -51,7 +51,7 @@
- {{'DMP-EDITOR.FIELDS.EMAIL' | translate}}* + {{'DMP-EDITOR.FIELDS.EMAIL' | translate}} {{user.get('email').getError('backendError').message}} {{'GENERAL.VALIDATION.REQUIRED' | translate}} diff --git a/dmp-frontend/src/app/ui/dmp/dmp-user-field/dmp-user-field.component.ts b/dmp-frontend/src/app/ui/dmp/dmp-user-field/dmp-user-field.component.ts index 298f18318..c8e49fa56 100644 --- a/dmp-frontend/src/app/ui/dmp/dmp-user-field/dmp-user-field.component.ts +++ b/dmp-frontend/src/app/ui/dmp/dmp-user-field/dmp-user-field.component.ts @@ -57,10 +57,10 @@ export class DmpUserFieldComponent extends BaseComponent implements OnInit { removeUser(userIndex: number): void { (this.form.get('users') as FormArray).removeAt(userIndex); - DmpEditorModel.reApplyPropertiesValidators( + DmpEditorModel.reApplyUsersValidators( { formGroup: this.form, - validationErrorModel: this.validationErrorModel + validationErrorModel: this.validationErrorModel, } ); this.form.get('users').markAsDirty(); @@ -78,7 +78,7 @@ export class DmpUserFieldComponent extends BaseComponent implements OnInit { moveItemInArray(usersFormArray.controls, event.previousIndex, event.currentIndex); usersFormArray.updateValueAndValidity(); - DmpEditorModel.reApplyPropertiesValidators( + DmpEditorModel.reApplyUsersValidators( { formGroup: this.form, validationErrorModel: this.validationErrorModel