use dialog to change dictionary when accepting a recommendation
This commit is contained in:
parent
65f879af8f
commit
a6ca95e7f0
@ -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<Dictionary[]> {
|
||||
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],
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="annotationActionsService.convertRecommendationToAnnotation($event, annotations, file, annotationsChanged)"
|
||||
(action)="acceptRecommendation($event)"
|
||||
*ngIf="annotationPermissions.canAcceptRecommendation"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltip]="'annotation-actions.accept-recommendation.label' | translate"
|
||||
|
||||
@ -8,6 +8,14 @@ import { UserService } from '@services/user.service';
|
||||
import { Dossier, File } from '@red/domain';
|
||||
import { Required } from '@iqser/common-ui';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import {
|
||||
AcceptRecommendationData,
|
||||
AcceptRecommendationDialogComponent,
|
||||
AcceptRecommendationReturnType,
|
||||
} from '../../dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { ManualAnnotationService } from '../../../../services/manual-annotation.service';
|
||||
|
||||
export const AnnotationButtonTypes = {
|
||||
dark: 'dark',
|
||||
@ -32,10 +40,12 @@ export class AnnotationActionsComponent implements OnChanges {
|
||||
annotationPermissions: AnnotationPermissions;
|
||||
|
||||
constructor(
|
||||
private readonly _manualAnnotationService: ManualAnnotationService,
|
||||
readonly annotationActionsService: AnnotationActionsService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _dialog: MatDialog,
|
||||
) {}
|
||||
|
||||
private _annotations: AnnotationWrapper[];
|
||||
@ -77,7 +87,7 @@ export class AnnotationActionsComponent implements OnChanges {
|
||||
this._setPermissions();
|
||||
}
|
||||
|
||||
removeOrSuggestRemoveAnnotation($event, removeFromDict: boolean) {
|
||||
removeOrSuggestRemoveAnnotation($event: MouseEvent, removeFromDict: boolean) {
|
||||
$event.stopPropagation();
|
||||
this.annotationActionsService.removeOrSuggestRemoveAnnotation(
|
||||
$event,
|
||||
@ -88,10 +98,32 @@ export class AnnotationActionsComponent implements OnChanges {
|
||||
);
|
||||
}
|
||||
|
||||
markAsFalsePositive($event) {
|
||||
markAsFalsePositive($event: MouseEvent) {
|
||||
this.annotationActionsService.markAsFalsePositive($event, this.annotations, this.file, this.annotationsChanged);
|
||||
}
|
||||
|
||||
acceptRecommendation($event: MouseEvent) {
|
||||
if (this.annotations.length > 1) {
|
||||
this.annotationActionsService.convertRecommendationToAnnotation($event, this.annotations, this.file, this.annotationsChanged);
|
||||
}
|
||||
|
||||
const dialogRef = this._dialog.open<AcceptRecommendationDialogComponent, AcceptRecommendationData, AcceptRecommendationReturnType>(
|
||||
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);
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
<section class="dialog">
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<div [translate]="'accept-recommendation-dialog.header'" class="dialog-header heading-l"></div>
|
||||
|
||||
<div class="dialog-content">
|
||||
<div class="iqser-input-group">
|
||||
<label translate="manual-annotation.dialog.content.text"></label>
|
||||
</div>
|
||||
{{ format(data.annotation.value) }}
|
||||
|
||||
<div class="iqser-input-group required w-400">
|
||||
<label translate="manual-annotation.dialog.content.dictionary"></label>
|
||||
|
||||
<mat-select formControlName="dictionary">
|
||||
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
|
||||
<mat-option
|
||||
*ngFor="let dictionary of possibleDictionaries"
|
||||
[matTooltip]="dictionary.description"
|
||||
[value]="dictionary.type"
|
||||
matTooltipPosition="after"
|
||||
>
|
||||
<span>
|
||||
{{ dictionary.label }}
|
||||
</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</div>
|
||||
|
||||
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
|
||||
<label translate="manual-annotation.dialog.content.comment"></label>
|
||||
<textarea formControlName="comment" name="comment" redactionHasScrollbar rows="4" type="text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="form.invalid" color="primary" mat-flat-button type="submit">
|
||||
{{ 'manual-annotation.dialog.actions.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
@ -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<AcceptRecommendationDialogComponent, AcceptRecommendationReturnType>,
|
||||
@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],
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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),
|
||||
|
||||
@ -184,11 +184,16 @@ export class AnnotationActionsService {
|
||||
annotations: AnnotationWrapper[],
|
||||
file: File,
|
||||
annotationsChanged: EventEmitter<AnnotationWrapper>,
|
||||
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<any>, annotation: AnnotationWrapper, annotationsChanged: EventEmitter<AnnotationWrapper>) {
|
||||
obs.subscribe(
|
||||
() => {
|
||||
private _processObsAndEmit(
|
||||
obs: Observable<unknown>,
|
||||
annotation: AnnotationWrapper,
|
||||
annotationsChanged: EventEmitter<AnnotationWrapper>,
|
||||
) {
|
||||
obs.subscribe({
|
||||
next: () => {
|
||||
annotationsChanged.emit(annotation);
|
||||
},
|
||||
() => {
|
||||
error: () => {
|
||||
annotationsChanged.emit();
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private _getFalsePositiveText(annotation: AnnotationWrapper) {
|
||||
|
||||
@ -60,9 +60,9 @@ export class ManualAnnotationService extends GenericService<IManualAddResponse>
|
||||
: 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<IManualAddResponse>
|
||||
positionClass: 'toast-file-preview',
|
||||
});
|
||||
},
|
||||
),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -89,15 +89,15 @@ export class ManualAnnotationService extends GenericService<IManualAddResponse>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -46,6 +46,30 @@ export class AppStateService {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
async getDictionariesOptions(dossierTemplateId: string, dossierId: string): Promise<Dictionary[]> {
|
||||
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'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user