added annotations to Plan editor too.

This commit is contained in:
Diamantis Tziotzios 2024-07-12 17:47:09 +03:00
parent 33c8f908e6
commit 0f9653cafc
36 changed files with 178 additions and 88 deletions

View File

@ -85,6 +85,8 @@ public final class Permission {
public static String UndoFinalizePlan = "UndoFinalizePlan"; public static String UndoFinalizePlan = "UndoFinalizePlan";
public static String AssignPlanUsers = "AssignPlanUsers"; public static String AssignPlanUsers = "AssignPlanUsers";
public static String InvitePlanUsers = "InvitePlanUsers"; public static String InvitePlanUsers = "InvitePlanUsers";
public static String AnnotatePlan = "AnnotatePlan";
//PlanBlueprint //PlanBlueprint
public static String BrowsePlanBlueprint = "BrowsePlanBlueprint"; public static String BrowsePlanBlueprint = "BrowsePlanBlueprint";
@ -107,7 +109,7 @@ public final class Permission {
//Description //Description
public static String BrowseDescription = "BrowseDescription"; public static String BrowseDescription = "BrowseDescription";
public static String ReviewDescription = "ReviewDescription"; public static String AnnotateDescription = "AnnotateDescription";
public static String EditDescription = "EditDescription"; public static String EditDescription = "EditDescription";
public static String FinalizeDescription = "FinalizeDescription"; public static String FinalizeDescription = "FinalizeDescription";
public static String DeleteDescription = "DeleteDescription"; public static String DeleteDescription = "DeleteDescription";

View File

@ -5,12 +5,15 @@ import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import org.opencdmp.commons.enums.IsActive; import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.data.DescriptionEntity; import org.opencdmp.data.DescriptionEntity;
import org.opencdmp.data.PlanEntity;
import org.opencdmp.data.PlanUserEntity; import org.opencdmp.data.PlanUserEntity;
import org.opencdmp.integrationevent.outbox.OutboxIntegrationEvent; import org.opencdmp.integrationevent.outbox.OutboxIntegrationEvent;
import org.opencdmp.integrationevent.outbox.OutboxService; import org.opencdmp.integrationevent.outbox.OutboxService;
import org.opencdmp.model.PlanUser; import org.opencdmp.model.PlanUser;
import org.opencdmp.model.description.Description; import org.opencdmp.model.description.Description;
import org.opencdmp.model.plan.Plan;
import org.opencdmp.query.DescriptionQuery; import org.opencdmp.query.DescriptionQuery;
import org.opencdmp.query.PlanQuery;
import org.opencdmp.query.PlanUserQuery; import org.opencdmp.query.PlanUserQuery;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@ -60,16 +63,18 @@ public class AnnotationEntityTouchedIntegrationEventHandlerImpl implements Annot
@Override @Override
public void handlePlan(UUID planId) { public void handlePlan(UUID planId) {
List<DescriptionEntity> descriptionEntities = this.queryFactory.query(DescriptionQuery.class).disableTracking().planIds(planId).collectAs(new BaseFieldSet().ensure(Description._id)); List<DescriptionEntity> descriptionEntities = this.queryFactory.query(DescriptionQuery.class).disableTracking().planIds(planId).collectAs(new BaseFieldSet().ensure(Description._id));
if (descriptionEntities == null || descriptionEntities.isEmpty()) return; PlanEntity planEntity = this.queryFactory.query(PlanQuery.class).disableTracking().ids(planId).firstAs(new BaseFieldSet().ensure(Plan._id, Plan._tenantId));
List<PlanUserEntity> planUsers = this.queryFactory.query(PlanUserQuery.class).disableTracking().planIds(planId).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(PlanUser._user)); List<PlanUserEntity> planUsers = this.queryFactory.query(PlanUserQuery.class).disableTracking().planIds(planId).isActives(IsActive.Active).collectAs(new BaseFieldSet().ensure(PlanUser._user));
AnnotationEntitiesTouchedIntegrationEvent event = new AnnotationEntitiesTouchedIntegrationEvent(); AnnotationEntitiesTouchedIntegrationEvent event = new AnnotationEntitiesTouchedIntegrationEvent();
event.setEvents(new ArrayList<>()); event.setEvents(new ArrayList<>());
event.getEvents().add(this.buildEventItem(planId, planUsers)); event.getEvents().add(this.buildEventItem(planId, planUsers));
for (DescriptionEntity description : descriptionEntities) event.getEvents().add(this.buildEventItem(description.getId(), planUsers)); if (descriptionEntities != null) {
for (DescriptionEntity description : descriptionEntities) event.getEvents().add(this.buildEventItem(description.getId(), planUsers));
this.handle(event, descriptionEntities.getFirst().getTenantId()); }
this.handle(event, planEntity.getTenantId());
} }
private AnnotationEntitiesTouchedIntegrationEvent.AnnotationEntityTouchedIntegrationEvent buildEventItem(UUID entityId, List<PlanUserEntity> planUsers){ private AnnotationEntitiesTouchedIntegrationEvent.AnnotationEntityTouchedIntegrationEvent buildEventItem(UUID entityId, List<PlanUserEntity> planUsers){

View File

@ -199,7 +199,7 @@ permissions:
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
ReviewDescription: AnnotateDescription:
roles: roles:
- Admin - Admin
- TenantAdmin - TenantAdmin
@ -604,6 +604,18 @@ permissions:
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
AnnotatePlan:
roles:
- Admin
- TenantAdmin
plan:
roles:
- Owner
- DescriptionContributor
- Reviewer
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# PlanBlueprint # PlanBlueprint
BrowsePlanBlueprint: BrowsePlanBlueprint:
roles: roles:

View File

@ -81,6 +81,7 @@ export enum AppPermission {
UndoFinalizePlan = "UndoFinalizePlan", UndoFinalizePlan = "UndoFinalizePlan",
AssignPlanUsers = "AssignPlanUsers", AssignPlanUsers = "AssignPlanUsers",
InvitePlanUsers = "InvitePlanUsers", InvitePlanUsers = "InvitePlanUsers",
AnnotatePlan = "AnnotatePlan",
//PlanBlueprint //PlanBlueprint
BrowsePlanBlueprint = "BrowsePlanBlueprint", BrowsePlanBlueprint = "BrowsePlanBlueprint",
@ -103,7 +104,7 @@ export enum AppPermission {
//Description //Description
BrowseDescription = "BrowseDescription", BrowseDescription = "BrowseDescription",
ReviewDescription = "ReviewDescription", AnnotateDescription = "AnnotateDescription",
EditDescription = "EditDescription", EditDescription = "EditDescription",
FinalizeDescription = "FinalizeDescription", FinalizeDescription = "FinalizeDescription",
DeleteDescription = "DeleteDescription", DeleteDescription = "DeleteDescription",

View File

@ -7,7 +7,7 @@
<mat-progress-bar color="primary" mode="indeterminate"></mat-progress-bar> <mat-progress-bar color="primary" mode="indeterminate"></mat-progress-bar>
</div> </div>
<div mat-dialog-content class="definition-content"> <div mat-dialog-content class="definition-content">
<app-description-form *ngIf="formGroup && formGroup.get('properties')" [propertiesFormGroup]="previewPropertiesFormGroup" [descriptionId]="descriptionId" [descriptionTemplate]="descriptionTemplate" [canReview]="false" [visibilityRulesService]="visibilityRulesService" [isNew]="true"></app-description-form> <app-description-form *ngIf="formGroup && formGroup.get('properties')" [propertiesFormGroup]="previewPropertiesFormGroup" [descriptionId]="descriptionId" [descriptionTemplate]="descriptionTemplate" [canAnnotate]="false" [visibilityRulesService]="visibilityRulesService" [isNew]="true"></app-description-form>
</div> </div>
<div mat-mat-dialog-actions *ngIf="formGroup"> <div mat-mat-dialog-actions *ngIf="formGroup">
<div class="col-auto d-flex pb-4 pt-2"> <div class="col-auto d-flex pb-4 pt-2">

View File

@ -123,7 +123,7 @@
</div> </div>
<div [id]="'preview_container'+ form.get('id').value" class="row"> <div [id]="'preview_container'+ form.get('id').value" class="row">
<div *ngIf="previewFieldSet && showPreview && firstField?.get('data')?.get('fieldType')?.value" class="col-12" [@fade-in-fast]> <div *ngIf="previewFieldSet && showPreview && firstField?.get('data')?.get('fieldType')?.value" class="col-12" [@fade-in-fast]>
<app-description-form-field-set class="w-100" [canReview]="false" [propertiesFormGroup]="previewPropertiesFormGroup" [fieldSet]="previewFieldSet" [visibilityRulesService]="visibilityRulesService" [numbering]="numbering" [hideAnnotations]="true" [validationErrorModel]="validationErrorModel"></app-description-form-field-set> <app-description-form-field-set class="w-100" [canAnnotate]="false" [propertiesFormGroup]="previewPropertiesFormGroup" [fieldSet]="previewFieldSet" [visibilityRulesService]="visibilityRulesService" [numbering]="numbering" [hideAnnotations]="true" [validationErrorModel]="validationErrorModel"></app-description-form-field-set>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1 +1 @@
<app-description-form *ngIf="previewPropertiesFormGroup" [canReview]="false" [propertiesFormGroup]="previewPropertiesFormGroup" [descriptionTemplate]="descriptionTemplate" [visibilityRulesService]="visibilityRulesService" [isNew]="true"></app-description-form> <app-description-form *ngIf="previewPropertiesFormGroup" [canAnnotate]="false" [propertiesFormGroup]="previewPropertiesFormGroup" [descriptionTemplate]="descriptionTemplate" [visibilityRulesService]="visibilityRulesService" [isNew]="true"></app-description-form>

View File

@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
import { Annotation } from '@annotation-service/core/model/annotation.model'; import { Annotation } from '@annotation-service/core/model/annotation.model';
import { AnnotationLookup } from '@annotation-service/core/query/annotation.lookup'; import { AnnotationLookup } from '@annotation-service/core/query/annotation.lookup';
import { AnnotationService } from '@annotation-service/services/http/annotation.service'; import { AnnotationService } from '@annotation-service/services/http/annotation.service';
import { AnnotationEntityType } from '@app/core/common/enum/annotation-entity-type';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { BaseService } from '@common/base/base.service'; import { BaseService } from '@common/base/base.service';
import { Guid } from '@common/types/guid'; import { Guid } from '@common/types/guid';
@ -15,9 +14,10 @@ import { nameof } from 'ts-simple-nameof';
@Injectable({ @Injectable({
providedIn: 'any', providedIn: 'any',
}) })
export class DescriptionFormAnnotationService extends BaseService { export class FormAnnotationService extends BaseService {
private entityId: Guid; private entityId: Guid;
private entityType: string;
private annotationsPerAnchor: Map<string, number>; private annotationsPerAnchor: Map<string, number>;
private annotationCountSubject: BehaviorSubject<Map<string, number>> = new BehaviorSubject<Map<string, number>>(null); private annotationCountSubject: BehaviorSubject<Map<string, number>> = new BehaviorSubject<Map<string, number>>(null);
private openAnnotationSubject: Subject<any> = new Subject<any>(); private openAnnotationSubject: Subject<any> = new Subject<any>();
@ -30,27 +30,28 @@ export class DescriptionFormAnnotationService extends BaseService {
super(); super();
} }
init(entityId: Guid) { init(entityId: Guid, entityType: string) {
this.entityId = entityId; this.entityId = entityId;
this.entityType = entityType;
this.refreshAnnotations(); this.refreshAnnotations();
} }
public getAnnotationCountObservable(): Observable<Map<string, number>> { public getAnnotationCountObservable(): Observable<Map<string, number>> {
return this.annotationCountSubject.asObservable(); return this.annotationCountSubject.asObservable();
} }
public getOpenAnnotationSubjectObservable(): Observable<string> { public getOpenAnnotationSubjectObservable(): Observable<string> {
return this.openAnnotationSubject.asObservable(); return this.openAnnotationSubject.asObservable();
} }
public οpenAnnotationDialog(next: any): void { public οpenAnnotationDialog(next: any): void {
this.openAnnotationSubject.next(next); this.openAnnotationSubject.next(next);
} }
public refreshAnnotations() { public refreshAnnotations() {
const lookup: AnnotationLookup = new AnnotationLookup(); const lookup: AnnotationLookup = new AnnotationLookup();
lookup.entityIds = [this.entityId]; lookup.entityIds = [this.entityId];
lookup.entityTypes = [AnnotationEntityType.Description]; lookup.entityTypes = [this.entityType];
lookup.project = { lookup.project = {
fields: [ fields: [
nameof<Annotation>(x => x.id), nameof<Annotation>(x => x.id),

View File

@ -177,7 +177,7 @@
[linkToScroll]="linkToScroll" [linkToScroll]="linkToScroll"
[validationErrorModel]="editorModel.validationErrorModel" [validationErrorModel]="editorModel.validationErrorModel"
[isNew]="isNew || isCopy" [isNew]="isNew || isCopy"
[canReview]="canReview" [canAnnotate]="canAnnotate"
[planUsers]="item?.plan?.planUsers ?? []" [planUsers]="item?.plan?.planUsers ?? []"
></app-description-form> ></app-description-form>
</div> </div>

View File

@ -44,7 +44,7 @@ import { TableOfContentsService } from './table-of-contents/services/table-of-co
import { TableOfContentsComponent } from './table-of-contents/table-of-contents.component'; import { TableOfContentsComponent } from './table-of-contents/table-of-contents.component';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service'; import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { DescriptionFormService } from './description-form/components/services/description-form.service'; import { DescriptionFormService } from './description-form/components/services/description-form.service';
import { DescriptionFormAnnotationService } from './description-form/description-form-annotation.service'; import { FormAnnotationService } from '../../annotations/annotation-dialog-component/form-annotation.service';
@Component({ @Component({
selector: 'app-description-editor-component', selector: 'app-description-editor-component',
@ -58,7 +58,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
isDeleted = false; isDeleted = false;
isCopy = false; isCopy = false;
canEdit = false; canEdit = false;
canReview = false; canAnnotate = false;
item: Description; item: Description;
fileTransformerEntityTypeEnum = FileTransformerEntityType; fileTransformerEntityTypeEnum = FileTransformerEntityType;
@ -110,7 +110,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private tableOfContentsService: TableOfContentsService, private tableOfContentsService: TableOfContentsService,
private descriptionFormService: DescriptionFormService, private descriptionFormService: DescriptionFormService,
private descriptionFormAnnotationService: DescriptionFormAnnotationService, private formAnnotationService: FormAnnotationService,
) { ) {
const descriptionLabel: string = route.snapshot.data['entity']?.label; const descriptionLabel: string = route.snapshot.data['entity']?.label;
if (descriptionLabel) { if (descriptionLabel) {
@ -194,7 +194,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
ngAfterViewInit(): void { ngAfterViewInit(): void {
if (this.scrollToField && this.anchorFieldsetId && this.anchorFieldsetId != '') { if (this.scrollToField && this.anchorFieldsetId && this.anchorFieldsetId != '') {
this.descriptionFormService.scrollingToAnchor(this.anchorFieldsetId); this.descriptionFormService.scrollingToAnchor(this.anchorFieldsetId);
if (this.openAnnotation) this.descriptionFormAnnotationService.οpenAnnotationDialog(this.anchorFieldsetId); if (this.openAnnotation) this.formAnnotationService.οpenAnnotationDialog(this.anchorFieldsetId);
} }
} }
@ -234,7 +234,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
buildForm() { buildForm() {
this.canEdit = this.permissionPerSection && this.permissionPerSection[this.item.planDescriptionTemplate.sectionId.toString()] && this.permissionPerSection[this.item.planDescriptionTemplate.sectionId.toString()].some(x => x === AppPermission.EditDescription); this.canEdit = this.permissionPerSection && this.permissionPerSection[this.item.planDescriptionTemplate.sectionId.toString()] && this.permissionPerSection[this.item.planDescriptionTemplate.sectionId.toString()].some(x => x === AppPermission.EditDescription);
this.canReview = this.permissionPerSection && this.permissionPerSection[this.item.planDescriptionTemplate.sectionId.toString()] && this.permissionPerSection[this.item.planDescriptionTemplate.sectionId.toString()].some(x => x === AppPermission.ReviewDescription); this.canAnnotate = this.permissionPerSection && this.permissionPerSection[this.item.planDescriptionTemplate.sectionId.toString()] && this.permissionPerSection[this.item.planDescriptionTemplate.sectionId.toString()].some(x => x === AppPermission.AnnotateDescription);
this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !this.canEdit, this.visibilityRulesService); this.formGroup = this.editorModel.buildForm(null, this.isDeleted || !this.canEdit, this.visibilityRulesService);
if (this.item.descriptionTemplate?.definition) this.visibilityRulesService.setContext(this.item.descriptionTemplate.definition, this.formGroup.get('properties')); if (this.item.descriptionTemplate?.definition) this.visibilityRulesService.setContext(this.item.descriptionTemplate.definition, this.formGroup.get('properties'));
if (this.item.descriptionTemplate?.definition) this.pageToFieldSetMap = this.mapPageToFieldSet(this.item.descriptionTemplate);; if (this.item.descriptionTemplate?.definition) this.pageToFieldSetMap = this.mapPageToFieldSet(this.item.descriptionTemplate);;

View File

@ -6,7 +6,7 @@
<app-description-form-field-set-title [fieldSet]="fieldSet" [path]="path" [isChild]="isChild" [hildeLink]="hideAnnotations" (copyLinkEvent)="copyLink(fieldSet.id)" [isAnchor]="isAnchor"></app-description-form-field-set-title> <app-description-form-field-set-title [fieldSet]="fieldSet" [path]="path" [isChild]="isChild" [hildeLink]="hideAnnotations" (copyLinkEvent)="copyLink(fieldSet.id)" [isAnchor]="isAnchor"></app-description-form-field-set-title>
</div> </div>
<div *ngIf="!hideAnnotations" class="col-auto" style="margin-top: 1rem;"> <div *ngIf="!hideAnnotations" class="col-auto" style="margin-top: 1rem;">
<button mat-icon-button class="col-auto annotation-icon" (click)="showAnnotations(fieldSet.id)" matTooltip="{{ 'DESCRIPTION-EDITOR.QUESTION.EXTENDED-DESCRIPTION.ANNOTATIONS' | translate }}" [disabled]="!canReview"> <button mat-icon-button class="col-auto annotation-icon" (click)="showAnnotations(fieldSet.id)" matTooltip="{{ 'DESCRIPTION-EDITOR.QUESTION.EXTENDED-DESCRIPTION.ANNOTATIONS' | translate }}" [disabled]="!canAnnotate">
<mat-icon [matBadge]="annotationsCount" [matBadgeHidden]="annotationsCount <= 0" matBadgeColor="warn">comment</mat-icon> <mat-icon [matBadge]="annotationsCount" [matBadgeHidden]="annotationsCount <= 0" matBadgeColor="warn">comment</mat-icon>
</button> </button>
</div> </div>

View File

@ -13,7 +13,7 @@ import { ValidationErrorModel } from '@common/forms/validation/error-model/valid
import { Guid } from '@common/types/guid'; import { Guid } from '@common/types/guid';
import { AnnotationDialogComponent } from '@app/ui/annotations/annotation-dialog-component/annotation-dialog.component'; import { AnnotationDialogComponent } from '@app/ui/annotations/annotation-dialog-component/annotation-dialog.component';
import { AnnotationEntityType } from '@app/core/common/enum/annotation-entity-type'; import { AnnotationEntityType } from '@app/core/common/enum/annotation-entity-type';
import { DescriptionFormAnnotationService } from '../../description-form-annotation.service'; import { FormAnnotationService } from '../../../../../annotations/annotation-dialog-component/form-annotation.service';
import { DescriptionPropertyDefinitionFieldSet } from '@app/core/model/description/description'; import { DescriptionPropertyDefinitionFieldSet } from '@app/core/model/description/description';
import { DescriptionFormService } from '../services/description-form.service'; import { DescriptionFormService } from '../services/description-form.service';
import { DescriptionTemplateFieldType } from '@app/core/common/enum/description-template-field-type'; import { DescriptionTemplateFieldType } from '@app/core/common/enum/description-template-field-type';
@ -33,7 +33,7 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
@Input() propertiesFormGroup: UntypedFormGroup; @Input() propertiesFormGroup: UntypedFormGroup;
@Input() descriptionId: Guid; @Input() descriptionId: Guid;
@Input() hideAnnotations: boolean = false; @Input() hideAnnotations: boolean = false;
@Input() canReview: boolean = false; @Input() canAnnotate: boolean = false;
@Input() numbering: string; @Input() numbering: string;
@Input() planUsers: PlanUser[] = []; @Input() planUsers: PlanUser[] = [];
@ -61,7 +61,7 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
private routerUtils: RouterUtilsService, private routerUtils: RouterUtilsService,
private dialog: MatDialog, private dialog: MatDialog,
private changeDetector: ChangeDetectorRef, private changeDetector: ChangeDetectorRef,
private descriptionFormAnnotationService: DescriptionFormAnnotationService, private formAnnotationService: FormAnnotationService,
private descriptionFormService: DescriptionFormService, private descriptionFormService: DescriptionFormService,
private uiNotificationService: UiNotificationService, private uiNotificationService: UiNotificationService,
private language: TranslateService, private language: TranslateService,
@ -72,7 +72,7 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
ngOnInit() { ngOnInit() {
this.descriptionFormService.getDetectChangesObservable().pipe(takeUntil(this._destroyed)).subscribe( _ => this.changeDetector.detectChanges() ); this.descriptionFormService.getDetectChangesObservable().pipe(takeUntil(this._destroyed)).subscribe( _ => this.changeDetector.detectChanges() );
this.descriptionFormAnnotationService.getAnnotationCountObservable().pipe(takeUntil(this._destroyed)).subscribe((annotationsPerAnchor: Map<string, number>) => { this.formAnnotationService.getAnnotationCountObservable().pipe(takeUntil(this._destroyed)).subscribe((annotationsPerAnchor: Map<string, number>) => {
const newCount = annotationsPerAnchor?.has(this.fieldSet.id) ? annotationsPerAnchor.get(this.fieldSet.id) : 0; const newCount = annotationsPerAnchor?.has(this.fieldSet.id) ? annotationsPerAnchor.get(this.fieldSet.id) : 0;
if (newCount != this.annotationsCount) { if (newCount != this.annotationsCount) {
this.annotationsCount = newCount; this.annotationsCount = newCount;
@ -80,7 +80,7 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
} }
}); });
this.descriptionFormAnnotationService.getOpenAnnotationSubjectObservable().pipe(takeUntil(this._destroyed)).subscribe((anchorFieldsetId: string) => { this.formAnnotationService.getOpenAnnotationSubjectObservable().pipe(takeUntil(this._destroyed)).subscribe((anchorFieldsetId: string) => {
if (anchorFieldsetId && anchorFieldsetId == this.fieldSet.id) this.showAnnotations(anchorFieldsetId); if (anchorFieldsetId && anchorFieldsetId == this.fieldSet.id) this.showAnnotations(anchorFieldsetId);
}); });
@ -191,7 +191,7 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
}); });
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(changesMade => { dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(changesMade => {
if (changesMade) { if (changesMade) {
this.descriptionFormAnnotationService.refreshAnnotations(); this.formAnnotationService.refreshAnnotations();
} }
}); });
} }

View File

@ -24,7 +24,7 @@
[validationErrorModel]="validationErrorModel" [validationErrorModel]="validationErrorModel"
[isChild]="false" [isChild]="false"
[hideAnnotations]="isNew" [hideAnnotations]="isNew"
[canReview]="canReview" [canAnnotate]="canAnnotate"
[planUsers]="planUsers" [planUsers]="planUsers"
></app-description-form-field-set> ></app-description-form-field-set>
</div> </div>
@ -35,7 +35,7 @@
<div *ngIf="section?.sections?.length > 0" class="col-12"> <div *ngIf="section?.sections?.length > 0" class="col-12">
<ng-container *ngFor="let innerSection of section.sections; let j = index;"> <ng-container *ngFor="let innerSection of section.sections; let j = index;">
<div class="row" *ngIf="visibilityRulesService.isVisibleMap[innerSection.id]"> <div class="row" *ngIf="visibilityRulesService.isVisibleMap[innerSection.id]">
<app-description-form-section class="col-12" [section]="innerSection" [canReview]="canReview" [path]="path+'.'+(j+1)" [pathName]="pathName+'.sections.'+j" (askedToScroll)="onAskedToScroll(null, $event)" [propertiesFormGroup]="propertiesFormGroup" [descriptionId]="descriptionId" [visibilityRulesService]="visibilityRulesService" [linkToScroll]="subsectionLinkToScroll"></app-description-form-section> <app-description-form-section class="col-12" [section]="innerSection" [canAnnotate]="canAnnotate" [path]="path+'.'+(j+1)" [pathName]="pathName+'.sections.'+j" (askedToScroll)="onAskedToScroll(null, $event)" [propertiesFormGroup]="propertiesFormGroup" [descriptionId]="descriptionId" [visibilityRulesService]="visibilityRulesService" [linkToScroll]="subsectionLinkToScroll"></app-description-form-section>
</div> </div>
</ng-container> </ng-container>
</div> </div>

View File

@ -20,7 +20,7 @@ import { PlanUser } from '@app/core/model/plan/plan';
export class DescriptionFormSectionComponent extends BaseComponent implements OnInit, OnChanges { export class DescriptionFormSectionComponent extends BaseComponent implements OnInit, OnChanges {
@Input() isNew: boolean = false; @Input() isNew: boolean = false;
@Input() canReview: boolean = false; @Input() canAnnotate: boolean = false;
@Input() section: DescriptionTemplateSection; @Input() section: DescriptionTemplateSection;
@Input() propertiesFormGroup: UntypedFormGroup; @Input() propertiesFormGroup: UntypedFormGroup;
@Input() visibilityRulesService: VisibilityRulesService; @Input() visibilityRulesService: VisibilityRulesService;

View File

@ -15,7 +15,7 @@
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-container *ngFor="let section of page.sections; let i = index;"> <ng-container *ngFor="let section of page.sections; let i = index;">
<div class="row" *ngIf="visibilityRulesService.isVisibleMap[section.id]"> <div class="row" *ngIf="visibilityRulesService.isVisibleMap[section.id]">
<app-description-form-section class="col-12" [section]="section" [canReview]="canReview" [path]="(z+1)+'.'+(i+1)" [pathName]="'pages.'+z+'.sections.'+i" [propertiesFormGroup]="propertiesFormGroup" [descriptionId]="descriptionId" [visibilityRulesService]="visibilityRulesService" (askedToScroll)="onAskedToScroll(expansionPanel, $event)" [linkToScroll]="linkToScroll" [validationErrorModel]="validationErrorModel" [isNew]="isNew" [planUsers]="planUsers"></app-description-form-section> <app-description-form-section class="col-12" [section]="section" [canAnnotate]="canAnnotate" [path]="(z+1)+'.'+(i+1)" [pathName]="'pages.'+z+'.sections.'+i" [propertiesFormGroup]="propertiesFormGroup" [descriptionId]="descriptionId" [visibilityRulesService]="visibilityRulesService" (askedToScroll)="onAskedToScroll(expansionPanel, $event)" [linkToScroll]="linkToScroll" [validationErrorModel]="validationErrorModel" [isNew]="isNew" [planUsers]="planUsers"></app-description-form-section>
</div> </div>
</ng-container> </ng-container>
</mat-expansion-panel> </mat-expansion-panel>

View File

@ -6,9 +6,10 @@ import { BaseComponent } from '@common/base/base.component';
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model'; import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
import { Guid } from '@common/types/guid'; import { Guid } from '@common/types/guid';
import { LinkToScroll } from '../table-of-contents/table-of-contents.component'; import { LinkToScroll } from '../table-of-contents/table-of-contents.component';
import { DescriptionFormAnnotationService } from './description-form-annotation.service'; import { FormAnnotationService } from '../../../annotations/annotation-dialog-component/form-annotation.service';
import { VisibilityRulesService } from './visibility-rules/visibility-rules.service'; import { VisibilityRulesService } from './visibility-rules/visibility-rules.service';
import { PlanUser } from '@app/core/model/plan/plan'; import { PlanUser } from '@app/core/model/plan/plan';
import { AnnotationEntityType } from '@app/core/common/enum/annotation-entity-type';
@Component({ @Component({
selector: 'app-description-form', selector: 'app-description-form',
@ -22,7 +23,7 @@ export class DescriptionFormComponent extends BaseComponent implements OnInit, O
@Input() visibilityRulesService: VisibilityRulesService; @Input() visibilityRulesService: VisibilityRulesService;
@Input() descriptionId: Guid; @Input() descriptionId: Guid;
@Input() isNew: boolean = false; @Input() isNew: boolean = false;
@Input() canReview: boolean = false; @Input() canAnnotate: boolean = false;
@Input() path: string; @Input() path: string;
@Input() datasetDescription: String; @Input() datasetDescription: String;
@Input() linkToScroll: LinkToScroll; @Input() linkToScroll: LinkToScroll;
@ -32,7 +33,7 @@ export class DescriptionFormComponent extends BaseComponent implements OnInit, O
@Output() formChanged: EventEmitter<any> = new EventEmitter(); @Output() formChanged: EventEmitter<any> = new EventEmitter();
constructor( constructor(
public descriptionFormAnnotationService: DescriptionFormAnnotationService, public formAnnotationService: FormAnnotationService,
) { ) {
super(); super();
@ -45,7 +46,7 @@ export class DescriptionFormComponent extends BaseComponent implements OnInit, O
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
this.init(); this.init();
if (this.descriptionId != null) { if (this.descriptionId != null) {
this.descriptionFormAnnotationService.init(this.descriptionId); this.formAnnotationService.init(this.descriptionId, AnnotationEntityType.Description);
} }
} }

View File

@ -13,7 +13,7 @@ import { DescriptionFormFieldSetComponent } from './components/form-field-set/fo
import { DescriptionFormFieldComponent } from './components/form-field/form-field.component'; import { DescriptionFormFieldComponent } from './components/form-field/form-field.component';
import { DescriptionFormSectionComponent } from './components/form-section/form-section.component'; import { DescriptionFormSectionComponent } from './components/form-section/form-section.component';
import { DescriptionFormComponent } from './description-form.component'; import { DescriptionFormComponent } from './description-form.component';
import { DescriptionFormAnnotationService } from './description-form-annotation.service'; import { FormAnnotationService } from '../../../annotations/annotation-dialog-component/form-annotation.service';
import { TagsFieldModule } from '@app/ui/tag/tags-field/tags-field.module'; import { TagsFieldModule } from '@app/ui/tag/tags-field/tags-field.module';
import { DescriptionFormService } from './components/services/description-form.service'; import { DescriptionFormService } from './components/services/description-form.service';
@ -43,7 +43,7 @@ import { DescriptionFormService } from './components/services/description-form.s
DescriptionFormFieldSetComponent DescriptionFormFieldSetComponent
], ],
providers: [ providers: [
DescriptionFormAnnotationService, FormAnnotationService,
DescriptionFormService, DescriptionFormService,
] ]
}) })

View File

@ -58,7 +58,7 @@ export class DescriptionEditorEntityResolver extends BaseEditorResolver {
[nameof<Description>(x => x.authorizationFlags), AppPermission.EditDescription].join('.'), [nameof<Description>(x => x.authorizationFlags), AppPermission.EditDescription].join('.'),
[nameof<Description>(x => x.authorizationFlags), AppPermission.DeleteDescription].join('.'), [nameof<Description>(x => x.authorizationFlags), AppPermission.DeleteDescription].join('.'),
[nameof<Description>(x => x.authorizationFlags), AppPermission.FinalizeDescription].join('.'), [nameof<Description>(x => x.authorizationFlags), AppPermission.FinalizeDescription].join('.'),
[nameof<Description>(x => x.authorizationFlags), AppPermission.ReviewDescription].join('.'), [nameof<Description>(x => x.authorizationFlags), AppPermission.AnnotateDescription].join('.'),
[nameof<Description>(x => x.planDescriptionTemplate), nameof<PlanDescriptionTemplate>(x => x.id)].join('.'), [nameof<Description>(x => x.planDescriptionTemplate), nameof<PlanDescriptionTemplate>(x => x.id)].join('.'),
[nameof<Description>(x => x.planDescriptionTemplate), nameof<PlanDescriptionTemplate>(x => x.sectionId)].join('.'), [nameof<Description>(x => x.planDescriptionTemplate), nameof<PlanDescriptionTemplate>(x => x.sectionId)].join('.'),

View File

@ -40,7 +40,7 @@ export class DescriptionEditorPermissionsResolver extends BaseEditorResolver {
const descriptionSectionPermissionResolverModel: DescriptionSectionPermissionResolver = { const descriptionSectionPermissionResolverModel: DescriptionSectionPermissionResolver = {
planId: description.plan.id, planId: description.plan.id,
sectionIds: [description.planDescriptionTemplate.sectionId], sectionIds: [description.planDescriptionTemplate.sectionId],
permissions: [AppPermission.EditDescription, AppPermission.DeleteDescription, AppPermission.FinalizeDescription, AppPermission.ReviewDescription] permissions: [AppPermission.EditDescription, AppPermission.DeleteDescription, AppPermission.FinalizeDescription, AppPermission.AnnotateDescription]
} }
return this.descriptionService.getDescriptionSectionPermissions(descriptionSectionPermissionResolverModel).pipe(takeUntil(this._destroyed)); return this.descriptionService.getDescriptionSectionPermissions(descriptionSectionPermissionResolverModel).pipe(takeUntil(this._destroyed));
})); }));
@ -62,7 +62,7 @@ export class DescriptionEditorPermissionsResolver extends BaseEditorResolver {
const descriptionSectionPermissionResolverModel: DescriptionSectionPermissionResolver = { const descriptionSectionPermissionResolverModel: DescriptionSectionPermissionResolver = {
planId: description.plan.id, planId: description.plan.id,
sectionIds: [description.planDescriptionTemplate.sectionId], sectionIds: [description.planDescriptionTemplate.sectionId],
permissions: [AppPermission.EditDescription, AppPermission.DeleteDescription, AppPermission.FinalizeDescription, AppPermission.ReviewDescription] permissions: [AppPermission.EditDescription, AppPermission.DeleteDescription, AppPermission.FinalizeDescription, AppPermission.AnnotateDescription]
} }
return this.descriptionService.getDescriptionSectionPermissions(descriptionSectionPermissionResolverModel).pipe(takeUntil(this._destroyed)); return this.descriptionService.getDescriptionSectionPermissions(descriptionSectionPermissionResolverModel).pipe(takeUntil(this._destroyed));
})); }));
@ -90,7 +90,7 @@ export class DescriptionEditorPermissionsResolver extends BaseEditorResolver {
const descriptionSectionPermissionResolverModel: DescriptionSectionPermissionResolver = { const descriptionSectionPermissionResolverModel: DescriptionSectionPermissionResolver = {
planId: description.plan.id, planId: description.plan.id,
sectionIds: [description.planDescriptionTemplate.sectionId], sectionIds: [description.planDescriptionTemplate.sectionId],
permissions: [AppPermission.EditDescription, AppPermission.DeleteDescription, AppPermission.FinalizeDescription, AppPermission.ReviewDescription] permissions: [AppPermission.EditDescription, AppPermission.DeleteDescription, AppPermission.FinalizeDescription, AppPermission.AnnotateDescription]
} }
return this.descriptionService.getDescriptionSectionPermissions(descriptionSectionPermissionResolverModel).pipe(takeUntil(this._destroyed)); return this.descriptionService.getDescriptionSectionPermissions(descriptionSectionPermissionResolverModel).pipe(takeUntil(this._destroyed));
})); }));

View File

@ -44,7 +44,7 @@
</div> </div>
</div> </div>
<div class="row mb-4 pb-3"> <div class="row mb-4 pb-3">
<div *ngIf="(canEdit || canReview) && isDraftDescription(description) && !isLocked; else previewButton" class="col-auto pr-0"> <div *ngIf="(canEdit || canAnnotate) && isDraftDescription(description) && !isLocked; else previewButton" class="col-auto pr-0">
<button (click)="editClicked(description)" mat-mini-fab class="mr-3 actions-btn" matTooltip="{{'DESCRIPTION-OVERVIEW.ACTIONS.EDIT' | translate}}" matTooltipPosition="above"> <button (click)="editClicked(description)" mat-mini-fab class="mr-3 actions-btn" matTooltip="{{'DESCRIPTION-OVERVIEW.ACTIONS.EDIT' | translate}}" matTooltipPosition="above">
<mat-icon class="mat-mini-fab-icon">create</mat-icon> <mat-icon class="mat-mini-fab-icon">create</mat-icon>
</button> </button>

View File

@ -70,7 +70,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
canEdit = false; canEdit = false;
canDelete = false; canDelete = false;
canFinalize = false; canFinalize = false;
canReview = false; canAnnotate = false;
canInvitePlanUsers = false; canInvitePlanUsers = false;
authorFocus: string; authorFocus: string;
@ -135,8 +135,8 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
this.canEdit = (this.authService.hasPermission(AppPermission.EditDescription) || this.canEdit = (this.authService.hasPermission(AppPermission.EditDescription) ||
this.description.authorizationFlags?.some(x => x === AppPermission.EditDescription)) && this.description.belongsToCurrentTenant != false; this.description.authorizationFlags?.some(x => x === AppPermission.EditDescription)) && this.description.belongsToCurrentTenant != false;
this.canReview = (this.authService.hasPermission(AppPermission.ReviewDescription) || this.canAnnotate = (this.authService.hasPermission(AppPermission.AnnotateDescription) ||
this.description.authorizationFlags?.some(x => x === AppPermission.ReviewDescription)) && this.description.belongsToCurrentTenant != false; this.description.authorizationFlags?.some(x => x === AppPermission.AnnotateDescription)) && this.description.belongsToCurrentTenant != false;
this.canFinalize = (this.authService.hasPermission(AppPermission.FinalizeDescription) || this.canFinalize = (this.authService.hasPermission(AppPermission.FinalizeDescription) ||
this.description.authorizationFlags?.some(x => x === AppPermission.FinalizeDescription)) && this.description.belongsToCurrentTenant != false; this.description.authorizationFlags?.some(x => x === AppPermission.FinalizeDescription)) && this.description.belongsToCurrentTenant != false;
@ -485,7 +485,7 @@ export class DescriptionOverviewComponent extends BaseComponent implements OnIni
[nameof<Description>(x => x.authorizationFlags), AppPermission.DeleteDescription].join('.'), [nameof<Description>(x => x.authorizationFlags), AppPermission.DeleteDescription].join('.'),
[nameof<Description>(x => x.authorizationFlags), AppPermission.FinalizeDescription].join('.'), [nameof<Description>(x => x.authorizationFlags), AppPermission.FinalizeDescription].join('.'),
[nameof<Description>(x => x.authorizationFlags), AppPermission.InvitePlanUsers].join('.'), [nameof<Description>(x => x.authorizationFlags), AppPermission.InvitePlanUsers].join('.'),
[nameof<Description>(x => x.authorizationFlags), AppPermission.ReviewDescription].join('.'), [nameof<Description>(x => x.authorizationFlags), AppPermission.AnnotateDescription].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.id)].join('.'), [nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.id)].join('.'),
[nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.label)].join('.'), [nameof<Description>(x => x.descriptionTemplate), nameof<DescriptionTemplate>(x => x.label)].join('.'),

View File

@ -10,7 +10,7 @@
<div class="subtitle">{{ formGroup.get('label').value }} <span *ngIf="isDirty()" class="changes">({{'PLAN-EDITOR.UNSAVED-CHANGES' | translate}})</span></div> <div class="subtitle">{{ formGroup.get('label').value }} <span *ngIf="isDirty()" class="changes">({{'PLAN-EDITOR.UNSAVED-CHANGES' | translate}})</span></div>
</div> </div>
<div class="ml-auto d-flex flex-row"> <div class="ml-auto d-flex flex-row">
<div *ngIf="formGroup.get('id').value" class="col-auto d-flex align-items-center"> <div *ngIf="formGroup.get('id').value" class="col-auto d-flex align-items-center">
<button [disabled]="isDirty()" [matTooltipDisabled]="!isDirty()" mat-raised-button class="plan-export-btn" type="button" [matMenuTriggerFor]="exportMenu" (click)="$event.stopPropagation();" [matTooltip]="'PLAN-EDITOR.ACTIONS.EXPORT.CAN-NOT-EXPORT' | translate"> <button [disabled]="isDirty()" [matTooltipDisabled]="!isDirty()" mat-raised-button class="plan-export-btn" type="button" [matMenuTriggerFor]="exportMenu" (click)="$event.stopPropagation();" [matTooltip]="'PLAN-EDITOR.ACTIONS.EXPORT.CAN-NOT-EXPORT' | translate">
{{ 'PLAN-EDITOR.ACTIONS.EXPORT.EXPORT' | translate }} {{ 'PLAN-EDITOR.ACTIONS.EXPORT.EXPORT' | translate }}
@ -24,7 +24,7 @@
</mat-menu> </mat-menu>
</div> </div>
<mat-divider *ngIf="formGroup.get('id').value && canEdit && ((!isLockedByUser && canEdit) || isLockedByUser || (hasReversableStatus() && !isLockedByUser))" [vertical]="true" class="ml-2 mr-2"></mat-divider> <mat-divider *ngIf="formGroup.get('id').value && canEdit && ((!isLockedByUser && canEdit) || isLockedByUser || (hasReversableStatus() && !isLockedByUser))" [vertical]="true" class="ml-2 mr-2"></mat-divider>
<div *ngIf="isDirty()" class="col-auto d-flex align-items-center"> <div *ngIf="isDirty()" class="col-auto d-flex align-items-center">
<button [disabled]="saving" type="button" mat-raised-button class="discard-btn mr-3" (click)="discardChanges()"> <button [disabled]="saving" type="button" mat-raised-button class="discard-btn mr-3" (click)="discardChanges()">
{{'PLAN-EDITOR.ACTIONS.DISCARD.DISCARD' | translate}} {{'PLAN-EDITOR.ACTIONS.DISCARD.DISCARD' | translate}}
@ -136,8 +136,8 @@
<div *ngIf="selectedBlueprint?.definition && this.step !== 0"> <div *ngIf="selectedBlueprint?.definition && this.step !== 0">
<ng-container *ngFor="let section of selectedBlueprint?.definition?.sections; let i=index"> <ng-container *ngFor="let section of selectedBlueprint?.definition?.sections; let i=index">
<li (click)="changeStep(i + 1)" [ngClass]="{'active': this.step === (i + 1), 'text-danger': hasErrors(section.id) }"><span class="d-flex align-items-center">{{section.label}} <li (click)="changeStep(i + 1)" [ngClass]="{'active': this.step === (i + 1), 'text-danger': hasErrors(section.id) }"><span class="d-flex align-items-center">{{section.label}}
<mat-icon *ngIf="section.description" class="ml-1 w-auto h-auto" style="font-size: 1rem; font-weight: 700;" [matTooltip]="section.description">info</mat-icon> <mat-icon *ngIf="section.description" class="ml-1 w-auto h-auto" style="font-size: 1rem; font-weight: 700;" [matTooltip]="section.description">info</mat-icon>
</span></li> </span></li>
<ol class="descriptionsInSection"> <ol class="descriptionsInSection">
<li *ngFor="let description of descriptionsInSection(section.id); let descriptionIndex = index" (click)="editDescription(description.id, false)" class="active-description"> <li *ngFor="let description of descriptionsInSection(section.id); let descriptionIndex = index" (click)="editDescription(description.id, false)" class="active-description">
<div class="d-flex flex-direction-row"> <div class="d-flex flex-direction-row">
@ -150,7 +150,7 @@
<ul *ngIf="item.id && section.hasTemplates && canEditSection(section.id) && !formGroup.disabled" class="add-description-option"> <ul *ngIf="item.id && section.hasTemplates && canEditSection(section.id) && !formGroup.disabled" class="add-description-option">
<li> <li>
<a class="add-description-action" [ngClass]="{'drag-handle-disabled': !hasDescriptionTemplates(section) || !hasValidMultiplicity(section)}" [routerLink]="hasDescriptionTemplates(section) && hasValidMultiplicity(section) ? this.routerUtils.generateUrl(['/descriptions/edit/', item.id, '/', section.id]) : null"> <a class="add-description-action" [ngClass]="{'drag-handle-disabled': !hasDescriptionTemplates(section) || !hasValidMultiplicity(section)}" [routerLink]="hasDescriptionTemplates(section) && hasValidMultiplicity(section) ? this.routerUtils.generateUrl(['/descriptions/edit/', item.id, '/', section.id]) : null">
<ng-container *ngIf="!hasDescriptionTemplates(section)" > <ng-container *ngIf="!hasDescriptionTemplates(section)">
<mat-icon [matTooltipDisabled]="hasDescriptionTemplates(section)" [matTooltip]="'PLAN-EDITOR.DESCRIPTION-TEMPLATES.EMPTY' | translate">add</mat-icon>{{'PLAN-EDITOR.ACTIONS.ADD-DESCRIPTION-IN-SECTION' | translate}} <mat-icon [matTooltipDisabled]="hasDescriptionTemplates(section)" [matTooltip]="'PLAN-EDITOR.DESCRIPTION-TEMPLATES.EMPTY' | translate">add</mat-icon>{{'PLAN-EDITOR.ACTIONS.ADD-DESCRIPTION-IN-SECTION' | translate}}
</ng-container> </ng-container>
<ng-container *ngIf="hasDescriptionTemplates(section)"> <ng-container *ngIf="hasDescriptionTemplates(section)">
@ -198,10 +198,21 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div *ngFor="let field of section.fields; let j=index"> <div *ngFor="let field of section.fields; let j=index">
<div class="heading" *ngIf="!field.label && field.category === planBlueprintSectionFieldCategoryEnum.System">{{i + 1}}.{{j + 1}} {{enumUtils.toPlanBlueprintSystemFieldTypeString(field.systemFieldType)}}<span *ngIf="field.required">*</span></div> <div class="row align-items-start">
<div class="heading" *ngIf="!field.label && field.category === planBlueprintSectionFieldCategoryEnum.ReferenceType">{{i + 1}}.{{j + 1}} {{field.referenceType.name}}<span *ngIf="field.required">*</span></div> <div class="col">
<div class="heading" *ngIf="field.label">{{i + 1}}.{{j + 1}} {{field.label}}<span *ngIf="field.required">*</span></div> <div class="heading" *ngIf="!field.label && field.category === planBlueprintSectionFieldCategoryEnum.System">{{i + 1}}.{{j + 1}} {{enumUtils.toPlanBlueprintSystemFieldTypeString(field.systemFieldType)}}<span *ngIf="field.required">*</span></div>
<div class="heading" *ngIf="!field.label && field.category === planBlueprintSectionFieldCategoryEnum.ReferenceType">{{i + 1}}.{{j + 1}} {{field.referenceType.name}}<span *ngIf="field.required">*</span></div>
<div class="heading" *ngIf="field.label">{{i + 1}}.{{j + 1}} {{field.label}}<span *ngIf="field.required">*</span></div>
</div>
<div *ngIf="!isNew" class="col-auto" style="margin-top: 1rem;">
<button mat-icon-button class="col-auto annotation-icon" (click)="showAnnotations(field.id)" matTooltip="{{ 'PLAN-EDITOR.ACTIONS.ANNOTATIONS' | translate }}" [disabled]="!canAnnotate(section.id)">
<mat-icon [matBadge]="annotationsPerAnchor.get(field.id)" [matBadgeHidden]="annotationsPerAnchor.get(field.id) <= 0" matBadgeColor="warn">comment</mat-icon>
</button>
</div>
</div>
<div *ngIf="field.description != null && field.description.length > 0" class="hint">{{field.description}}</div> <div *ngIf="field.description != null && field.description.length > 0" class="hint">{{field.description}}</div>
<div class="input-form"> <div class="input-form">
<div *ngIf="field.category === planBlueprintSectionFieldCategoryEnum.System"> <div *ngIf="field.category === planBlueprintSectionFieldCategoryEnum.System">
<div *ngIf="field.systemFieldType == planBlueprintSystemFieldTypeEnum.Title"> <div *ngIf="field.systemFieldType == planBlueprintSystemFieldTypeEnum.Title">
@ -298,15 +309,16 @@
</div> </div>
<div *ngIf="field.systemFieldType == planBlueprintSystemFieldTypeEnum.User"> <div *ngIf="field.systemFieldType == planBlueprintSystemFieldTypeEnum.User">
<div> <div>
<app-plan-user-field-component [form]="formGroup" [validationErrorModel]="editorModel.validationErrorModel" [sections]="selectedBlueprint.definition.sections" [viewOnly]="formGroup.disabled || !canEdit"></app-plan-user-field-component> </div> <app-plan-user-field-component [form]="formGroup" [validationErrorModel]="editorModel.validationErrorModel" [sections]="selectedBlueprint.definition.sections" [viewOnly]="formGroup.disabled || !canEdit"></app-plan-user-field-component>
</div>
</div> </div>
</div> </div>
<div *ngIf="field.category === planBlueprintSectionFieldCategoryEnum.ReferenceType"> <div *ngIf="field.category === planBlueprintSectionFieldCategoryEnum.ReferenceType">
<ng-container *ngIf="field.multipleSelect"> <ng-container *ngIf="field.multipleSelect">
<app-reference-field-component [form]="formGroup.get('properties').get('planBlueprintValues').get(field.id).get('references')" [dependencies]="formGroup.get('properties').get('planBlueprintValues')" [label]= "field.label" [placeholder]="field.placeholder && field.placeholder != '' ? field.placeholder : field.label" [referenceType]="field.referenceType" [multiple]="true"></app-reference-field-component> <app-reference-field-component [form]="formGroup.get('properties').get('planBlueprintValues').get(field.id).get('references')" [dependencies]="formGroup.get('properties').get('planBlueprintValues')" [label]="field.label" [placeholder]="field.placeholder && field.placeholder != '' ? field.placeholder : field.label" [referenceType]="field.referenceType" [multiple]="true"></app-reference-field-component>
</ng-container> </ng-container>
<ng-container *ngIf="!(field.multipleSelect)"> <ng-container *ngIf="!(field.multipleSelect)">
<app-reference-field-component [form]="formGroup.get('properties').get('planBlueprintValues').get(field.id).get('reference')" [dependencies]="formGroup.get('properties').get('planBlueprintValues')" [label]= "field.label" [placeholder]="field.placeholder && field.placeholder != '' ? field.placeholder : field.label" [referenceType]="field.referenceType" [multiple]="false"></app-reference-field-component> <app-reference-field-component [form]="formGroup.get('properties').get('planBlueprintValues').get(field.id).get('reference')" [dependencies]="formGroup.get('properties').get('planBlueprintValues')" [label]="field.label" [placeholder]="field.placeholder && field.placeholder != '' ? field.placeholder : field.label" [referenceType]="field.referenceType" [multiple]="false"></app-reference-field-component>
</ng-container> </ng-container>
</div> </div>
<div *ngIf="field.category === planBlueprintSectionFieldCategoryEnum.Extra"> <div *ngIf="field.category === planBlueprintSectionFieldCategoryEnum.Extra">
@ -372,4 +384,4 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,7 +4,12 @@ import { FormArray, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { AnnotationEntityType } from '@app/core/common/enum/annotation-entity-type';
import { DescriptionStatus } from '@app/core/common/enum/description-status'; import { DescriptionStatus } from '@app/core/common/enum/description-status';
import { FileTransformerEntityType } from '@app/core/common/enum/file-transformer-entity-type';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { LockTargetType } from '@app/core/common/enum/lock-target-type';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { PlanAccessType } from '@app/core/common/enum/plan-access-type'; import { PlanAccessType } from '@app/core/common/enum/plan-access-type';
import { PlanBlueprintFieldCategory } from '@app/core/common/enum/plan-blueprint-field-category'; import { PlanBlueprintFieldCategory } from '@app/core/common/enum/plan-blueprint-field-category';
import { PlanBlueprintExtraFieldDataType } from '@app/core/common/enum/plan-blueprint-field-type'; import { PlanBlueprintExtraFieldDataType } from '@app/core/common/enum/plan-blueprint-field-type';
@ -13,25 +18,24 @@ import { PlanBlueprintSystemFieldType } from '@app/core/common/enum/plan-bluepri
import { PlanStatus } from '@app/core/common/enum/plan-status'; import { PlanStatus } from '@app/core/common/enum/plan-status';
import { PlanUserRole } from '@app/core/common/enum/plan-user-role'; import { PlanUserRole } from '@app/core/common/enum/plan-user-role';
import { PlanUserType } from '@app/core/common/enum/plan-user-type'; import { PlanUserType } from '@app/core/common/enum/plan-user-type';
import { IsActive } from '@app/core/common/enum/is-active.enum';
import { LockTargetType } from '@app/core/common/enum/lock-target-type';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { DescriptionTemplate } from '@app/core/model/description-template/description-template'; import { DescriptionTemplate } from '@app/core/model/description-template/description-template';
import { DescriptionSectionPermissionResolver } from '@app/core/model/description/description'; import { DescriptionSectionPermissionResolver } from '@app/core/model/description/description';
import { PlanBlueprint, PlanBlueprintDefinitionSection, FieldInSection } from '@app/core/model/plan-blueprint/plan-blueprint';
import { Plan, PlanPersist } from '@app/core/model/plan/plan';
import { LanguageInfo } from '@app/core/model/language-info'; import { LanguageInfo } from '@app/core/model/language-info';
import { FieldInSection, PlanBlueprint, PlanBlueprintDefinitionSection } from '@app/core/model/plan-blueprint/plan-blueprint';
import { Plan, PlanPersist } from '@app/core/model/plan/plan';
import { AuthService } from '@app/core/services/auth/auth.service'; import { AuthService } from '@app/core/services/auth/auth.service';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service'; import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { LanguageInfoService } from '@app/core/services/culture/language-info-service'; import { LanguageInfoService } from '@app/core/services/culture/language-info-service';
import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service'; import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service';
import { DescriptionService } from '@app/core/services/description/description.service'; import { DescriptionService } from '@app/core/services/description/description.service';
import { PlanBlueprintService } from '@app/core/services/plan/plan-blueprint.service'; import { FileTransformerService } from '@app/core/services/file-transformer/file-transformer.service';
import { PlanService } from '@app/core/services/plan/plan.service';
import { LockService } from '@app/core/services/lock/lock.service'; import { LockService } from '@app/core/services/lock/lock.service';
import { LoggingService } from '@app/core/services/logging/logging-service'; import { LoggingService } from '@app/core/services/logging/logging-service';
import { AnalyticsService } from '@app/core/services/matomo/analytics-service'; import { AnalyticsService } from '@app/core/services/matomo/analytics-service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { PlanBlueprintService } from '@app/core/services/plan/plan-blueprint.service';
import { PlanService } from '@app/core/services/plan/plan.service';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { UserService } from '@app/core/services/user/user.service'; import { UserService } from '@app/core/services/user/user.service';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service'; import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { QueryParamsService } from '@app/core/services/utilities/query-params.service'; import { QueryParamsService } from '@app/core/services/utilities/query-params.service';
@ -39,6 +43,7 @@ import { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/mu
import { MultipleAutoCompleteCanRemoveItem } from '@app/library/auto-complete/multiple/multiple-auto-complete.component'; import { MultipleAutoCompleteCanRemoveItem } from '@app/library/auto-complete/multiple/multiple-auto-complete.component';
import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration'; import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration';
import { DescriptionTemplatePreviewDialogComponent } from '@app/ui/admin/description-template/description-template-preview/description-template-preview-dialog.component'; import { DescriptionTemplatePreviewDialogComponent } from '@app/ui/admin/description-template/description-template-preview/description-template-preview-dialog.component';
import { AnnotationDialogComponent } from '@app/ui/annotations/annotation-dialog-component/annotation-dialog.component';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service'; import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
import { BaseEditor } from '@common/base/base-editor'; import { BaseEditor } from '@common/base/base-editor';
import { FormService } from '@common/forms/form-service'; import { FormService } from '@common/forms/form-service';
@ -49,13 +54,11 @@ import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { map, takeUntil } from 'rxjs/operators'; import { map, takeUntil } from 'rxjs/operators';
import { PlanContactPrefillDialogComponent } from '../plan-contact-prefill-dialog/plan-contact-prefill-dialog.component'; import { PlanContactPrefillDialogComponent } from '../plan-contact-prefill-dialog/plan-contact-prefill-dialog.component';
import { PlanEditorModel, PlanFieldIndicator } from './plan-editor.model';
import { PlanEditorEntityResolver } from './resolvers/plan-editor-enitity.resolver';
import { PlanEditorService } from './plan-editor.service';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { FileTransformerService } from '@app/core/services/file-transformer/file-transformer.service';
import { FileTransformerEntityType } from '@app/core/common/enum/file-transformer-entity-type';
import { PlanFinalizeDialogComponent, PlanFinalizeDialogOutput } from '../plan-finalize-dialog/plan-finalize-dialog.component'; import { PlanFinalizeDialogComponent, PlanFinalizeDialogOutput } from '../plan-finalize-dialog/plan-finalize-dialog.component';
import { PlanEditorModel, PlanFieldIndicator } from './plan-editor.model';
import { PlanEditorService } from './plan-editor.service';
import { PlanEditorEntityResolver } from './resolvers/plan-editor-enitity.resolver';
import { FormAnnotationService } from '@app/ui/annotations/annotation-dialog-component/form-annotation.service';
@Component({ @Component({
selector: 'app-plan-editor', selector: 'app-plan-editor',
@ -72,6 +75,7 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
item: Plan; item: Plan;
selectedBlueprint: PlanBlueprint; selectedBlueprint: PlanBlueprint;
step: number = 0; step: number = 0;
annotationsPerAnchor: Map<string, number> = new Map<string, number>();
//Enums //Enums
descriptionStatusEnum = DescriptionStatus; descriptionStatusEnum = DescriptionStatus;
@ -147,6 +151,10 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
return this.isNew ? this.authService.hasPermission(AppPermission.NewPlan) : this.item.authorizationFlags?.some(x => x === AppPermission.EditPlan) || this.authService.hasPermission(AppPermission.EditPlan); return this.isNew ? this.authService.hasPermission(AppPermission.NewPlan) : this.item.authorizationFlags?.some(x => x === AppPermission.EditPlan) || this.authService.hasPermission(AppPermission.EditPlan);
} }
protected canAnnotate(id: Guid): boolean {
return this.permissionPerSection && this.permissionPerSection[id.toString()] && this.permissionPerSection[id.toString()].some(x => x === AppPermission.AnnotatePlan);
}
private hasPermission(permission: AppPermission): boolean { private hasPermission(permission: AppPermission): boolean {
return this.authService.hasPermission(permission) || this.item?.authorizationFlags?.some(x => x === permission) || this.editorModel?.permissions?.includes(permission); return this.authService.hasPermission(permission) || this.item?.authorizationFlags?.some(x => x === permission) || this.editorModel?.permissions?.includes(permission);
} }
@ -180,6 +188,7 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
private analyticsService: AnalyticsService, private analyticsService: AnalyticsService,
private breadcrumbService: BreadcrumbService, private breadcrumbService: BreadcrumbService,
public fileTransformerService: FileTransformerService, public fileTransformerService: FileTransformerService,
private formAnnotationService: FormAnnotationService,
) { ) {
const descriptionLabel: string = route.snapshot.data['entity']?.label; const descriptionLabel: string = route.snapshot.data['entity']?.label;
if (descriptionLabel) { if (descriptionLabel) {
@ -196,6 +205,16 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
this.permissionPerSection = this.route.snapshot.data['permissions'] as Map<Guid, string[]> ?? new Map<Guid, string[]>(); this.permissionPerSection = this.route.snapshot.data['permissions'] as Map<Guid, string[]> ?? new Map<Guid, string[]>();
super.ngOnInit(); super.ngOnInit();
if (this.isNew === false && this.step === 0) this.nextStep(); if (this.isNew === false && this.step === 0) this.nextStep();
this.formAnnotationService.init(this.item.id, AnnotationEntityType.Plan);
this.formAnnotationService.getAnnotationCountObservable().pipe(takeUntil(this._destroyed)).subscribe((annotationsPerAnchor: Map<string, number>) => {
this.annotationsPerAnchor = annotationsPerAnchor;
});
this.formAnnotationService.getOpenAnnotationSubjectObservable().pipe(takeUntil(this._destroyed)).subscribe((anchorId: string) => {
if (anchorId && anchorId == this.item.id?.toString()) this.showAnnotations(anchorId);
});
} }
getItem(itemId: Guid, successFunction: (item: Plan) => void) { getItem(itemId: Guid, successFunction: (item: Plan) => void) {
@ -687,7 +706,7 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
} }
}) })
if (descriptionTemplatesGroupIdsWithMaxMultitplicity.length > 0 && descriptionTemplatesGroupIds.length > 0){ if (descriptionTemplatesGroupIdsWithMaxMultitplicity.length > 0 && descriptionTemplatesGroupIds.length > 0) {
const descriptionTemplatesWithoutMaxMultiplicity = descriptionTemplatesGroupIds.filter(x => !descriptionTemplatesGroupIdsWithMaxMultitplicity.map(y => y).includes(x)) || []; const descriptionTemplatesWithoutMaxMultiplicity = descriptionTemplatesGroupIds.filter(x => !descriptionTemplatesGroupIdsWithMaxMultitplicity.map(y => y).includes(x)) || [];
if (descriptionTemplatesWithoutMaxMultiplicity.length > 0 && this.formGroup.pristine) return true; if (descriptionTemplatesWithoutMaxMultiplicity.length > 0 && this.formGroup.pristine) return true;
} }
@ -774,6 +793,30 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
} as MultipleAutoCompleteCanRemoveItem; } as MultipleAutoCompleteCanRemoveItem;
} }
//
//
// Annotations
//
//
showAnnotations(anchorId: string) {
const dialogRef = this.dialog.open(AnnotationDialogComponent, {
width: '40rem',
maxWidth: '90vw',
maxHeight: '90vh',
data: {
entityId: this.item.id,
anchor: anchorId,
entityType: AnnotationEntityType.Plan,
planUsers: this.item?.planUsers ?? []
}
});
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(changesMade => {
if (changesMade) {
this.formAnnotationService.refreshAnnotations();
}
});
}
// //
// //
// Misc // Misc

View File

@ -13,6 +13,7 @@ import { PlanEditorRoutingModule } from './plan-editor.routing';
import { PlanFormProgressIndicationModule } from './form-progress-indication/plan-form-progress-indication.module'; import { PlanFormProgressIndicationModule } from './form-progress-indication/plan-form-progress-indication.module';
import { PlanDeleteDialogModule } from '../plan-delete-dialog/plan-delete-dialog.module'; import { PlanDeleteDialogModule } from '../plan-delete-dialog/plan-delete-dialog.module';
import { PlanContactPrefillDialogModule } from '../plan-contact-prefill-dialog/plan-contact-prefill-dialog.module'; import { PlanContactPrefillDialogModule } from '../plan-contact-prefill-dialog/plan-contact-prefill-dialog.module';
import { AnnotationDialogModule } from '@app/ui/annotations/annotation-dialog-component/annotation-dialog.module';
@NgModule({ @NgModule({
imports: [ imports: [
@ -28,7 +29,8 @@ import { PlanContactPrefillDialogModule } from '../plan-contact-prefill-dialog/p
DragDropModule, DragDropModule,
PlanUserFieldModule, PlanUserFieldModule,
PlanFormProgressIndicationModule, PlanFormProgressIndicationModule,
PlanContactPrefillDialogModule PlanContactPrefillDialogModule,
AnnotationDialogModule
], ],
declarations: [ declarations: [
PlanEditorComponent, PlanEditorComponent,

View File

@ -34,7 +34,7 @@ export class PlanEditorPermissionsResolver extends BaseEditorResolver {
let descriptionSectionPermissionResolverModel: DescriptionSectionPermissionResolver = { let descriptionSectionPermissionResolverModel: DescriptionSectionPermissionResolver = {
planId: data.id, planId: data.id,
sectionIds: data?.blueprint?.definition?.sections?.map(x => x.id), sectionIds: data?.blueprint?.definition?.sections?.map(x => x.id),
permissions: [AppPermission.EditDescription, AppPermission.DeleteDescription] permissions: [AppPermission.EditDescription, AppPermission.DeleteDescription, AppPermission.AnnotatePlan]
}; };
return this.descriptionService.getDescriptionSectionPermissions(descriptionSectionPermissionResolverModel).pipe(takeUntil(this._destroyed)); return this.descriptionService.getDescriptionSectionPermissions(descriptionSectionPermissionResolverModel).pipe(takeUntil(this._destroyed));
} }

View File

@ -1735,7 +1735,8 @@
"SAVE-AND-CLOSE": "Save & Close", "SAVE-AND-CLOSE": "Save & Close",
"SAVE-AND-CONTINUE": "Save & Continue", "SAVE-AND-CONTINUE": "Save & Continue",
"REVERSE": "Undo Finalization", "REVERSE": "Undo Finalization",
"LOCKED": "Locked" "LOCKED": "Locked",
"ANNOTATIONS": "Comment"
}, },
"PLACEHOLDER": { "PLACEHOLDER": {
"DESCRIPTION": "Deskribapenarekin bete", "DESCRIPTION": "Deskribapenarekin bete",

View File

@ -1735,7 +1735,8 @@
"SAVE-AND-CLOSE": "Save & Close", "SAVE-AND-CLOSE": "Save & Close",
"SAVE-AND-CONTINUE": "Save & Continue", "SAVE-AND-CONTINUE": "Save & Continue",
"REVERSE": "Undo Finalization", "REVERSE": "Undo Finalization",
"LOCKED": "Locked" "LOCKED": "Locked",
"ANNOTATIONS": "Comment"
}, },
"PLACEHOLDER": { "PLACEHOLDER": {
"DESCRIPTION": "Fill with description", "DESCRIPTION": "Fill with description",

View File

@ -1735,7 +1735,8 @@
"SAVE-AND-CLOSE": "Save & Close", "SAVE-AND-CLOSE": "Save & Close",
"SAVE-AND-CONTINUE": "Save & Continue", "SAVE-AND-CONTINUE": "Save & Continue",
"REVERSE": "Undo Finalization", "REVERSE": "Undo Finalization",
"LOCKED": "Locked" "LOCKED": "Locked",
"ANNOTATIONS": "Comment"
}, },
"PLACEHOLDER": { "PLACEHOLDER": {
"DESCRIPTION": "Fill with description", "DESCRIPTION": "Fill with description",

View File

@ -1735,7 +1735,8 @@
"SAVE-AND-CLOSE": "Save & Close", "SAVE-AND-CLOSE": "Save & Close",
"SAVE-AND-CONTINUE": "Save & Continue", "SAVE-AND-CONTINUE": "Save & Continue",
"REVERSE": "Undo Finalization", "REVERSE": "Undo Finalization",
"LOCKED": "Locked" "LOCKED": "Locked",
"ANNOTATIONS": "Comment"
}, },
"PLACEHOLDER": { "PLACEHOLDER": {
"DESCRIPTION": "Rellenar con la descripción", "DESCRIPTION": "Rellenar con la descripción",

View File

@ -1735,7 +1735,8 @@
"SAVE-AND-CLOSE": "Save & Close", "SAVE-AND-CLOSE": "Save & Close",
"SAVE-AND-CONTINUE": "Save & Continue", "SAVE-AND-CONTINUE": "Save & Continue",
"REVERSE": "Undo Finalization", "REVERSE": "Undo Finalization",
"LOCKED": "Locked" "LOCKED": "Locked",
"ANNOTATIONS": "Comment"
}, },
"PLACEHOLDER": { "PLACEHOLDER": {
"DESCRIPTION": "Συμπληρώστε με περιγραφή", "DESCRIPTION": "Συμπληρώστε με περιγραφή",

View File

@ -1735,7 +1735,8 @@
"SAVE-AND-CLOSE": "Save & Close", "SAVE-AND-CLOSE": "Save & Close",
"SAVE-AND-CONTINUE": "Save & Continue", "SAVE-AND-CONTINUE": "Save & Continue",
"REVERSE": "Undo Finalization", "REVERSE": "Undo Finalization",
"LOCKED": "Locked" "LOCKED": "Locked",
"ANNOTATIONS": "Comment"
}, },
"PLACEHOLDER": { "PLACEHOLDER": {
"DESCRIPTION": "Dodajte opis", "DESCRIPTION": "Dodajte opis",

View File

@ -1735,7 +1735,8 @@
"SAVE-AND-CLOSE": "Save & Close", "SAVE-AND-CLOSE": "Save & Close",
"SAVE-AND-CONTINUE": "Save & Continue", "SAVE-AND-CONTINUE": "Save & Continue",
"REVERSE": "Undo Finalization", "REVERSE": "Undo Finalization",
"LOCKED": "Locked" "LOCKED": "Locked",
"ANNOTATIONS": "Comment"
}, },
"PLACEHOLDER": { "PLACEHOLDER": {
"DESCRIPTION": "Dodaj opis", "DESCRIPTION": "Dodaj opis",

View File

@ -1735,7 +1735,8 @@
"SAVE-AND-CLOSE": "Save & Close", "SAVE-AND-CLOSE": "Save & Close",
"SAVE-AND-CONTINUE": "Save & Continue", "SAVE-AND-CONTINUE": "Save & Continue",
"REVERSE": "Undo Finalization", "REVERSE": "Undo Finalization",
"LOCKED": "Locked" "LOCKED": "Locked",
"ANNOTATIONS": "Comment"
}, },
"PLACEHOLDER": { "PLACEHOLDER": {
"DESCRIPTION": "Preencha com a descrição", "DESCRIPTION": "Preencha com a descrição",

View File

@ -1735,7 +1735,8 @@
"SAVE-AND-CLOSE": "Save & Close", "SAVE-AND-CLOSE": "Save & Close",
"SAVE-AND-CONTINUE": "Save & Continue", "SAVE-AND-CONTINUE": "Save & Continue",
"REVERSE": "Undo Finalization", "REVERSE": "Undo Finalization",
"LOCKED": "Locked" "LOCKED": "Locked",
"ANNOTATIONS": "Comment"
}, },
"PLACEHOLDER": { "PLACEHOLDER": {
"DESCRIPTION": "Doplniť opis", "DESCRIPTION": "Doplniť opis",

View File

@ -1735,7 +1735,8 @@
"SAVE-AND-CLOSE": "Save & Close", "SAVE-AND-CLOSE": "Save & Close",
"SAVE-AND-CONTINUE": "Save & Continue", "SAVE-AND-CONTINUE": "Save & Continue",
"REVERSE": "Undo Finalization", "REVERSE": "Undo Finalization",
"LOCKED": "Locked" "LOCKED": "Locked",
"ANNOTATIONS": "Comment"
}, },
"PLACEHOLDER": { "PLACEHOLDER": {
"DESCRIPTION": "Dodajte opis", "DESCRIPTION": "Dodajte opis",

View File

@ -1735,7 +1735,8 @@
"SAVE-AND-CLOSE": "Save & Close", "SAVE-AND-CLOSE": "Save & Close",
"SAVE-AND-CONTINUE": "Save & Continue", "SAVE-AND-CONTINUE": "Save & Continue",
"REVERSE": "Undo Finalization", "REVERSE": "Undo Finalization",
"LOCKED": "Locked" "LOCKED": "Locked",
"ANNOTATIONS": "Comment"
}, },
"PLACEHOLDER": { "PLACEHOLDER": {
"DESCRIPTION": "Açıklamaları Doldur", "DESCRIPTION": "Açıklamaları Doldur",