diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts index 5f813eca6..593e776ab 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts @@ -7,7 +7,7 @@ import { ManualAnnotationService } from '../../services/manual-annotation.servic import { ManualAnnotationResponse } from '@models/file/manual-annotation-response'; import { PermissionsService } from '@services/permissions.service'; import { JustificationsService } from '@services/entity-services/justifications.service'; -import { Dictionary, Dossier, File, IAddRedactionRequest } from '@red/domain'; +import { Dictionary, Dossier, File, IAddRedactionRequest, IManualAddResponse } from '@red/domain'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { DictionaryService } from '@shared/services/dictionary.service'; import { BaseDialogComponent } from '@iqser/common-ui'; @@ -75,7 +75,10 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme async ngOnInit() { super.ngOnInit(); - this.possibleDictionaries = await this._getPossibleDictionaries(); + this.possibleDictionaries = await this._appStateService.getDictionariesOptions( + this._dossier.dossierTemplateId, + this._dossier.dossierId, + ); const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this._dossier.dossierTemplateId)); this.legalOptions = data.map(lbm => ({ legalBasis: lbm.reason, @@ -89,7 +92,8 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme save() { this._enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry); this._manualAnnotationService.addAnnotation(this.data.manualRedactionEntryWrapper.manualRedactionEntry, this.data.file).subscribe( - response => this._dialogRef.close(new ManualAnnotationResponse(this.data.manualRedactionEntryWrapper, response)), + (response: IManualAddResponse) => + this._dialogRef.close(new ManualAnnotationResponse(this.data.manualRedactionEntryWrapper, response)), () => this._dialogRef.close(), ); } @@ -102,31 +106,6 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme ); } - private async _getPossibleDictionaries(): Promise { - const possibleDictionaries: Dictionary[] = []; - const dossier = this._dossier; - - const dossierDictionary = await firstValueFrom( - this._dictionaryService.getForType(dossier.dossierTemplateId, 'dossier_redaction', dossier.dossierId), - ); - - for (const key of Object.keys(this._appStateService.dictionaryData[dossier.dossierTemplateId])) { - const dictionaryData = this._appStateService.getDictionary(key, dossier.dossierTemplateId); - if (!dictionaryData.virtual && dictionaryData.addToDictionaryAction) { - possibleDictionaries.push(dictionaryData); - } - } - - if (dossierDictionary.addToDictionaryAction) { - // TODO fix this in the backend - possibleDictionaries.push(new Dictionary({ ...dossierDictionary, type: 'dossier_redaction' })); - } - - possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label)); - - return possibleDictionaries; - } - private _getForm(): FormGroup { return this._formBuilder.group({ section: [null], diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-actions/annotation-actions.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-actions/annotation-actions.component.html index c3d7d4599..27d8236ef 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-actions/annotation-actions.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-actions/annotation-actions.component.html @@ -42,7 +42,7 @@ > 1) { + this.annotationActionsService.convertRecommendationToAnnotation($event, this.annotations, this.file, this.annotationsChanged); + } + + const dialogRef = this._dialog.open( + AcceptRecommendationDialogComponent, + { autoFocus: true, data: { annotation: this.annotations[0], file: this.file } }, + ); + const dialogClosed = dialogRef.afterClosed().pipe(filter(value => !!value && !!value.annotation)); + dialogClosed.subscribe(({ annotation, comment: commentText }) => { + const comment = commentText ? { text: commentText } : undefined; + this.annotationActionsService.convertRecommendationToAnnotation( + $event, + [annotation], + this.file, + this.annotationsChanged, + comment, + ); + }); + } + hideAnnotation($event: MouseEvent) { $event.stopPropagation(); this.viewer.Core.annotationManager.hideAnnotations(this.viewerAnnotations); diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.html new file mode 100644 index 000000000..34bd8655a --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.html @@ -0,0 +1,43 @@ +
+
+
+ +
+
+ +
+ {{ format(data.annotation.value) }} + +
+ + + + {{ displayedDictionaryLabel }} + + + {{ dictionary.label }} + + + +
+ +
+ + +
+
+ +
+ +
+
+ + +
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts new file mode 100644 index 000000000..0c62e03fd --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts @@ -0,0 +1,94 @@ +import { Component, Inject, Injector, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { AppStateService } from '@state/app-state.service'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { PermissionsService } from '@services/permissions.service'; +import { Dictionary, Dossier, File } from '@red/domain'; +import { DossiersService } from '@services/entity-services/dossiers.service'; +import { BaseDialogComponent } from '@iqser/common-ui'; +import { DictionaryService } from '@shared/services/dictionary.service'; +import { ManualAnnotationService } from '../../../../services/manual-annotation.service'; +import { AnnotationWrapper } from '../../../../../../models/file/annotation.wrapper'; + +export interface AcceptRecommendationData { + readonly annotation: AnnotationWrapper; + readonly file: File; +} + +export interface AcceptRecommendationReturnType { + readonly annotation: AnnotationWrapper; + readonly comment: string; +} + +@Component({ + templateUrl: './accept-recommendation-dialog.component.html', +}) +export class AcceptRecommendationDialogComponent extends BaseDialogComponent implements OnInit { + isDocumentAdmin: boolean; + + possibleDictionaries: Dictionary[] = []; + + private readonly _dossier: Dossier; + + constructor( + private readonly _appStateService: AppStateService, + private readonly _formBuilder: FormBuilder, + private readonly _manualAnnotationService: ManualAnnotationService, + private readonly _permissionsService: PermissionsService, + private readonly _dossiersService: DossiersService, + private readonly _dictionaryService: DictionaryService, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) readonly data: AcceptRecommendationData, + ) { + super(_injector, _dialogRef); + this._dossier = this._dossiersService.find(this.data.file.dossierId); + this.isDocumentAdmin = this._permissionsService.isApprover(this._dossier); + this.form = this._getForm(); + this.initialFormValue = this.form.getRawValue(); + } + + get displayedDictionaryLabel() { + const dictType = this.form.get('dictionary').value as string; + if (dictType && this.possibleDictionaries.length) { + return this.possibleDictionaries.find(d => d.type === dictType).label; + } + return null; + } + + async ngOnInit() { + super.ngOnInit(); + this.possibleDictionaries = await this._appStateService.getDictionariesOptions( + this._dossier.dossierTemplateId, + this._dossier.dossierId, + ); + this.form.patchValue({ + dictionary: this.possibleDictionaries.find(dict => dict.type === this.data.annotation.recommendationType).type, + }); + } + + save() { + const recommendationType = this.form.get('dictionary').value; + const annotation = Object.assign({}, this.data.annotation); + annotation.recommendationType = recommendationType; + this._dialogRef.close({ + annotation, + comment: this.form.get('comment').value as string, + }); + } + + format(value: string) { + return value.replace( + // eslint-disable-next-line no-control-regex,max-len + /([^\s\d-]{2,})[-\u00AD]\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]/gi, + '$1', + ); + } + + private _getForm(): FormGroup { + return this._formBuilder.group({ + dictionary: [Validators.required], + comment: [null], + }); + } +} diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview.module.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview.module.ts index 6e9c902ae..bd73b422f 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview.module.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview.module.ts @@ -19,6 +19,7 @@ import { TypeAnnotationIconComponent } from './components/type-annotation-icon/t import { OverlayModule } from '@angular/cdk/overlay'; import { ViewSwitchComponent } from './components/view-switch/view-switch.component'; import { UserManagementComponent } from './components/user-management/user-management.component'; +import { AcceptRecommendationDialogComponent } from './dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component'; const routes: Routes = [ { @@ -44,6 +45,7 @@ const routes: Routes = [ TypeAnnotationIconComponent, ViewSwitchComponent, UserManagementComponent, + AcceptRecommendationDialogComponent, ], imports: [ RouterModule.forChild(routes), diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-actions.service.ts index e02e796cd..0a33bd58c 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/services/annotation-actions.service.ts @@ -184,11 +184,16 @@ export class AnnotationActionsService { annotations: AnnotationWrapper[], file: File, annotationsChanged: EventEmitter, + comment?: { text: string }, ) { $event?.stopPropagation(); annotations.forEach(annotation => { - this._processObsAndEmit(this._manualAnnotationService.addRecommendation(annotation, file), annotation, annotationsChanged); + this._processObsAndEmit( + this._manualAnnotationService.addRecommendation(annotation, file, comment), + annotation, + annotationsChanged, + ); }); } @@ -486,15 +491,19 @@ export class AnnotationActionsService { return this._dossiersService.find(file.dossierId); } - private _processObsAndEmit(obs: Observable, annotation: AnnotationWrapper, annotationsChanged: EventEmitter) { - obs.subscribe( - () => { + private _processObsAndEmit( + obs: Observable, + annotation: AnnotationWrapper, + annotationsChanged: EventEmitter, + ) { + obs.subscribe({ + next: () => { annotationsChanged.emit(annotation); }, - () => { + error: () => { annotationsChanged.emit(); }, - ); + }); } private _getFalsePositiveText(annotation: AnnotationWrapper) { diff --git a/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts b/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts index e633cfb30..4bd24fd9e 100644 --- a/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts @@ -60,9 +60,9 @@ export class ManualAnnotationService extends GenericService : this[this.CONFIG[mode]](body, secondParam, file.dossierId, file.id); return obs.pipe( - tap( - () => this._toaster.success(this._getMessage(mode, modifyDictionary), { positionClass: 'toast-file-preview' }), - (error: HttpErrorResponse) => { + tap({ + next: () => this._toaster.success(this._getMessage(mode, modifyDictionary), { positionClass: 'toast-file-preview' }), + error: (error: HttpErrorResponse) => { const isConflict = error.status === HttpStatusCode.Conflict; this._toaster.error(this._getMessage(mode, modifyDictionary, true, isConflict), { error, @@ -73,7 +73,7 @@ export class ManualAnnotationService extends GenericService positionClass: 'toast-file-preview', }); }, - ), + }), ); } @@ -89,15 +89,15 @@ export class ManualAnnotationService extends GenericService return super.delete({}, url); } - addRecommendation(annotation: AnnotationWrapper, file: File) { + addRecommendation(annotation: AnnotationWrapper, file: File, comment = { text: 'Accepted Recommendation' }) { const manualRedactionEntry: IAddRedactionRequest = {}; manualRedactionEntry.addToDictionary = true; // set the ID as reason, so we can hide the suggestion - manualRedactionEntry.reason = annotation.id; + manualRedactionEntry.reason = annotation.annotationId; manualRedactionEntry.value = annotation.value; manualRedactionEntry.positions = annotation.positions; manualRedactionEntry.type = annotation.recommendationType; - manualRedactionEntry.comment = { text: 'Accepted Recommendation' }; + manualRedactionEntry.comment = comment; return this.addAnnotation(manualRedactionEntry, file); } diff --git a/apps/red-ui/src/app/state/app-state.service.ts b/apps/red-ui/src/app/state/app-state.service.ts index 4eb1bc5f6..8a313372b 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -46,6 +46,30 @@ export class AppStateService { : undefined; } + async getDictionariesOptions(dossierTemplateId: string, dossierId: string): Promise { + const possibleDictionaries: Dictionary[] = []; + + const dossierDictionary = await firstValueFrom( + this._dictionaryService.getForType(dossierTemplateId, 'dossier_redaction', dossierId), + ); + + for (const key of Object.keys(this.dictionaryData[dossierTemplateId])) { + const dictionaryData = this.getDictionary(key, dossierTemplateId); + if (!dictionaryData.virtual && dictionaryData.addToDictionaryAction) { + possibleDictionaries.push(dictionaryData); + } + } + + if (dossierDictionary.addToDictionaryAction) { + // TODO fix this in the backend + possibleDictionaries.push(new Dictionary({ ...dossierDictionary, type: 'dossier_redaction' })); + } + + possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label)); + + return possibleDictionaries; + } + getDictionaryColor(type: string, dossierTemplateId: string) { return !this._dictionaryData ? '#cccccc'