RED-7069, add unified edit dialog.

This commit is contained in:
George 2023-07-27 18:31:50 +03:00
parent 7cc0f4e551
commit 293366dc84
15 changed files with 189 additions and 143 deletions

View File

@ -34,7 +34,7 @@
></iqser-circle-button>
<iqser-circle-button
(action)="annotationActionsService.changeLegalBasis(annotations)"
(action)="annotationActionsService.editRedaction(annotations)"
*ngIf="annotationPermissions.canChangeLegalBasis"
[tooltipPosition]="tooltipPosition"
[tooltip]="'annotation-actions.edit-reason.label' | translate"

View File

@ -1,9 +1,12 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div [translate]="'edit-redaction.dialog.title'" class="dialog-header heading-l"></div>
<div
[translate]="isImage ? 'edit-redaction.dialog.title-edit-image' : 'edit-redaction.dialog.title-edit-text'"
class="dialog-header heading-l"
></div>
<div class="dialog-content redaction">
<div *ngIf="singleEntry" class="iqser-input-group w-450">
<div *ngIf="redactedText" class="iqser-input-group w-450">
<label [translate]="'edit-redaction.dialog.content.redacted-text'" class="selected-text"></label>
{{ redactedText }}
</div>
@ -12,7 +15,7 @@
<label [translate]="'edit-redaction.dialog.content.type'"></label>
<mat-form-field>
<mat-select formControlName="dictionary">
<mat-select formControlName="type">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
<mat-option
(click)="typeChanged()"
@ -27,11 +30,7 @@
</mat-form-field>
</div>
<iqser-details-radio
(extraOptionChanged)="extraOptionChanged($event)"
[options]="options"
formControlName="option"
></iqser-details-radio>
<iqser-details-radio *ngIf="options?.length" [options]="options" formControlName="option"></iqser-details-radio>
<div class="iqser-input-group required w-450">
<label [translate]="'edit-redaction.dialog.content.reason'"></label>
@ -54,7 +53,7 @@
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
</div>
<div class="iqser-input-group w-400">
<div class="iqser-input-group w-450">
<label [translate]="'edit-redaction.dialog.content.section'"></label>
<input formControlName="section" name="section" type="text" />
</div>

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { DetailsRadioOption, IconButtonTypes, IqserDialogComponent, IqserPermissionsService } from '@iqser/common-ui';
import { Dictionary, Dossier, IAddRedactionRequest } from '@red/domain';
import { Dictionary, Dossier, SuperTypes } from '@red/domain';
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
import { Roles } from '@users/roles';
import { firstValueFrom } from 'rxjs';
@ -9,27 +9,30 @@ import { ActiveDossiersService } from '@services/dossiers/active-dossiers.servic
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { getEditRedactionOptions, RedactOrHintOption } from '../../utils/dialog-options';
import { EditRedactionData, RedactTextResult } from '../../utils/dialog-types';
import { EditRedactionData, EditRedactResult } from '../../utils/dialog-types';
import { IMAGE_CATEGORIES } from '../../utils/constants';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { tap } from 'rxjs/operators';
@Component({
templateUrl: './edit-redaction-dialog.component.html',
})
export class EditRedactionDialogComponent
extends IqserDialogComponent<EditRedactionDialogComponent, EditRedactionData, RedactTextResult>
extends IqserDialogComponent<EditRedactionDialogComponent, EditRedactionData, EditRedactResult>
implements OnInit
{
readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes;
readonly options: DetailsRadioOption<RedactOrHintOption>[];
readonly singleEntry: boolean;
readonly redactedText: string;
readonly isImage: boolean;
readonly initialReason: string;
readonly initialType: string;
options: DetailsRadioOption<RedactOrHintOption>[] | undefined;
legalOptions: LegalBasisOption[] = [];
dictionaries: Dictionary[] = [];
form!: UntypedFormGroup;
#manualRedactionTypeExists = true;
#applyToAllDossiers: boolean;
readonly #dossier: Dossier;
constructor(
@ -40,21 +43,35 @@ export class EditRedactionDialogComponent
private readonly _formBuilder: FormBuilder,
) {
super();
console.log(this.data.annotations);
this.#dossier = _activeDossiersService.find(this.data.dossierId);
this.#applyToAllDossiers = true;
this.#manualRedactionTypeExists = this._dictionaryService.hasManualType(this.#dossier.dossierTemplateId);
this.options = getEditRedactionOptions(this.#dossier, this.#applyToAllDossiers, true);
this.#applyToAllDossiers = this.data.applyToAllDossiers;
const annotations = this.data.annotations;
const firstEntry = annotations[0];
this.isImage = IMAGE_CATEGORIES.includes(firstEntry.type);
this.redactedText = annotations.length === 1 && !this.isImage ? firstEntry.value : null;
this.initialReason = firstEntry.legalBasis;
this.initialType = firstEntry.type;
this.form = this.#getForm();
this.singleEntry = this.data.annotations.length === 1;
this.redactedText = this.data?.annotations[0].value;
console.log(this.form.value);
this.form.valueChanges
.pipe(
tap(value => {
const reasonChanged = this.initialReason !== value.reason.legalBasis;
const typeChanged = this.initialType !== value.type;
if (typeChanged || reasonChanged) {
this.#setOptions(value.type, reasonChanged);
}
}),
takeUntilDestroyed(),
)
// eslint-disable-next-line rxjs/no-ignored-subscription
.subscribe();
}
get displayedDictionaryLabel() {
const dictType = this.form.get('dictionary').value;
if (dictType) {
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
const selectedDictionaryType = this.form.get('type').value;
if (selectedDictionaryType) {
return this.dictionaries.find(d => d.type === selectedDictionaryType)?.label ?? null;
}
return null;
}
@ -64,8 +81,8 @@ export class EditRedactionDialogComponent
}
async ngOnInit(): Promise<void> {
this.#setDictionaries();
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.#dossier.dossierTemplateId));
this.#setTypes();
const data = await firstValueFrom(this._justificationsService.loadAll(this.#dossier.dossierTemplateId));
this.legalOptions = data.map(lbm => ({
legalBasis: lbm.reason,
description: lbm.description,
@ -79,42 +96,49 @@ export class EditRedactionDialogComponent
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
}
extraOptionChanged(option: DetailsRadioOption<RedactOrHintOption>): void {
this.#applyToAllDossiers = option.extraOption.checked;
this.#setDictionaries();
if (this.#applyToAllDossiers && this.form.get('dictionary').value) {
const selectedDictionaryLabel = this.form.get('dictionary').value;
const selectedDictionary = this.dictionaries.find(d => d.type === selectedDictionaryLabel);
if (!selectedDictionary) {
this.form.get('dictionary').setValue(null);
}
}
typeChanged() {
const selectedDictionaryType = this.form.get('type').value;
this.#setOptions(selectedDictionaryType);
}
typeChanged() {
console.log(this.form.value);
if (!this.#applyToAllDossiers) {
const selectedDictionaryType = this.form.get('dictionary').value;
const selectedDictionary = this.dictionaries.find(d => d.type === selectedDictionaryType);
this.options[1].extraOption.disabled = selectedDictionary.dossierDictionaryOnly;
}
reasonChanged() {
this.form.patchValue({ reason: this.dictionaries.find(d => d.type === SuperTypes.ManualRedaction) });
}
save(): void {
this.#enhanceManualRedaction(this.data.annotations[0].manualRedactionEntry);
const redaction = this.data.annotations[0].manualRedactionEntry;
const value = this.form.value;
this.dialogRef.close({
redaction,
dictionary: this.dictionaries.find(d => d.type === this.form.get('dictionary').value),
typeChanged: this.initialType !== value.type,
legalBasis: value.reason.legalBasis,
section: value.section,
comment: value.comment,
type: value.type,
value: this.#allRectangles() ? this.data.annotations[0].value : null,
});
}
#setDictionaries() {
this.dictionaries = this._dictionaryService.getRedactTextDictionaries(
this.#dossier.dossierTemplateId,
!this.#applyToAllDossiers,
null,
#setTypes() {
this.dictionaries = this._dictionaryService.getEditableRedactionTypes(this.#dossier.dossierTemplateId, this.isImage);
}
#setOptions(type: string, reasonChanged = false) {
const selectedDictionary = this.dictionaries.find(d => d.type === type);
const isManualRedaction = type === SuperTypes.ManualRedaction;
this.options = getEditRedactionOptions(
this.#dossier.dossierName,
this.#applyToAllDossiers,
selectedDictionary.dossierDictionaryOnly,
this.isImage,
isManualRedaction,
);
this.form.patchValue(
{
option:
this.isImage || selectedDictionary.dossierDictionaryOnly || isManualRedaction || reasonChanged
? this.options[0]
: this.options[1],
},
{ emitEvent: false },
);
}
@ -122,36 +146,13 @@ export class EditRedactionDialogComponent
return this._formBuilder.group({
reason: [null],
comment: [null],
dictionary: [this.data.annotations[0].type],
type: [this.data.annotations[0].type],
section: [this.data.annotations[0].section],
option: [this.options[0]],
option: [null, { disabled: true }],
});
}
#enhanceManualRedaction(addRedactionRequest: IAddRedactionRequest) {
addRedactionRequest.type = this.form.get('dictionary').value;
addRedactionRequest.section = null;
addRedactionRequest.value = this.form.get('selectedText').value;
const legalOption: LegalBasisOption = this.form.get('reason').value;
if (legalOption) {
addRedactionRequest.reason = legalOption.description;
addRedactionRequest.legalBasis = legalOption.legalBasis;
}
const selectedType = this.dictionaries.find(d => d.type === addRedactionRequest.type);
if (selectedType) {
addRedactionRequest.addToDictionary = selectedType.hasDictionary;
} else {
addRedactionRequest.addToDictionary = addRedactionRequest.type !== 'dossier_redaction';
}
if (!addRedactionRequest.reason) {
addRedactionRequest.reason = 'Dictionary Request';
}
const commentValue = this.form.get('comment').value;
addRedactionRequest.comment = commentValue ? { text: commentValue } : null;
addRedactionRequest.addToAllDossiers = this.#applyToAllDossiers;
#allRectangles() {
return this.data.annotations.reduce((acc, a) => acc && a.rectangle, true);
}
}

View File

@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { DetailsRadioOption, IconButtonTypes, IqserPermissionsService } from '@iqser/common-ui';
import { DetailsRadioOption, IconButtonTypes, IqserDialogComponent, IqserPermissionsService } from '@iqser/common-ui';
import { Dictionary, Dossier, IAddRedactionRequest, SuperTypes } from '@red/domain';
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
import { Roles } from '@users/roles';
@ -10,7 +10,6 @@ import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-d
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { tap } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { IqserDialogComponent } from '@iqser/common-ui';
import { getRedactOrHintOptions, RedactOrHintOption, RedactOrHintOptions } from '../../utils/dialog-options';
import { RedactTextData, RedactTextResult } from '../../utils/dialog-types';
@ -123,11 +122,7 @@ export class RedactTextDialogComponent
}
#setDictionaries() {
this.dictionaries = this._dictionaryService.getRedactTextDictionaries(
this.#dossier.dossierTemplateId,
!this.#applyToAllDossiers,
this.dictionaryRequest,
);
this.dictionaries = this._dictionaryService.getRedactTextDictionaries(this.#dossier.dossierTemplateId, !this.#applyToAllDossiers);
}
#getForm(): UntypedFormGroup {

View File

@ -1,7 +1,7 @@
import { inject, Injectable } from '@angular/core';
import { Injectable } from '@angular/core';
import { ManualRedactionService } from './manual-redaction.service';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { firstValueFrom, Observable } from 'rxjs';
import { firstValueFrom, from, Observable, zip } from 'rxjs';
import { getFirstRelevantTextPart } from '../../../utils';
import { Core } from '@pdftron/webviewer';
import {
@ -32,7 +32,7 @@ import { REDDocumentViewer } from '../../pdf-viewer/services/document-viewer.ser
import { RemoveRedactionDialogComponent } from '../dialogs/remove-redaction-dialog/remove-redaction-dialog.component';
import { IqserDialog } from '@common-ui/dialog/iqser-dialog.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { isJustOne, List } from '@iqser/common-ui/lib/utils';
import { isJustOne, List, log } from '@iqser/common-ui/lib/utils';
import { PermissionsService } from '@services/permissions.service';
import {
RemoveRedactionData,
@ -50,7 +50,6 @@ import { EditRedactionDialogComponent } from '../dialogs/edit-redaction-dialog/e
@Injectable()
export class AnnotationActionsService {
readonly #isDocumine;
readonly #iqserDialog = inject(IqserDialog);
constructor(
private readonly _manualRedactionService: ManualRedactionService,
@ -107,25 +106,42 @@ export class AnnotationActionsService {
});
}
changeLegalBasis(annotations: AnnotationWrapper[]) {
const { dossierId, fileId } = this._state;
console.log('we here ', annotations);
this._iqserDialog.openDefault(EditRedactionDialogComponent, { data: { annotations, dossierId } });
// this._dialogService.openDialog(
// 'changeLegalBasis',
// { annotations, dossier: this._state.dossier() },
// (data: { comment: string; legalBasis: string; section: string; value: string }) => {
// const body = annotations.map(annotation => ({
// annotationId: annotation.id,
// comment: data.comment,
// legalBasis: data.legalBasis,
// section: data.section,
// value: data.value,
// }));
//
// this.#processObsAndEmit(this._manualRedactionService.changeLegalBasis(body, dossierId, fileId)).then();
// },
// );
async editRedaction(annotations: AnnotationWrapper[]) {
const { dossierId, dossierTemplateId, fileId } = this._state;
const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
const data = {
annotations,
dossierId,
applyToAllDossiers: dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault,
};
const result = await this._iqserDialog.openDefault(EditRedactionDialogComponent, { data }).result();
const requests: Observable<unknown>[] = [];
const changeLegalBasisBody = annotations.map(annotation => ({
annotationId: annotation.id,
legalBasis: result.legalBasis,
section: result.section,
value: result.value,
}));
requests.push(this._manualRedactionService.changeLegalBasis(changeLegalBasisBody, dossierId, fileId));
if (result.typeChanged) {
const recategorizeBody: List<IRecategorizationRequest> = annotations.map(({ id }) => ({
annotationId: id,
type: result.type,
}));
console.log({ recategorizeBody });
requests.push(this._manualRedactionService.recategorizeRedactions(recategorizeBody, dossierId, fileId));
}
if (result.comment) {
requests.push(
from(Promise.all(annotations.map(a => this._manualRedactionService.addComment(result.comment, a.id, dossierId, fileId)))),
);
}
await this.#processObsAndEmit(zip(requests).pipe(log()));
}
async removeRedaction(redaction: AnnotationWrapper, permissions) {
@ -166,7 +182,7 @@ export class AnnotationActionsService {
type,
comment,
}));
this.#processObsAndEmit(this._manualRedactionService.recategorizeImage(body, dossierId, fileId)).then();
this.#processObsAndEmit(this._manualRedactionService.recategorizeRedactions(body, dossierId, fileId)).then();
});
}

View File

@ -82,7 +82,7 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
return this.legalBasisChange(body, dossierId, fileId).pipe(this.#showToast('change-legal-basis'));
}
recategorizeImage(body: List<IRecategorizationRequest>, dossierId: string, fileId: string) {
recategorizeRedactions(body: List<IRecategorizationRequest>, dossierId: string, fileId: string) {
return this.recategorize(body, dossierId, fileId).pipe(this.#showToast('recategorize-image'));
}

View File

@ -53,14 +53,14 @@ export class PdfAnnotationActionsService {
if (permissions.canChangeLegalBasis) {
const editButton = this.#getButton('edit', _('annotation-actions.edit-reason.label'), () =>
this.#annotationActionsService.changeLegalBasis(annotations),
this.#annotationActionsService.editRedaction(annotations),
);
availableActions.push(editButton);
}
if (permissions.canRecategorizeImage) {
const recategorizeButton = this.#getButton('thumb-down', _('annotation-actions.recategorize-image'), () =>
this.#annotationActionsService.recategorizeImages(annotations),
this.#annotationActionsService.editRedaction(annotations),
);
availableActions.push(recategorizeButton);
}

View File

@ -1,5 +1,6 @@
import { List, ValuesOf } from '@iqser/common-ui/lib/utils';
export const IMAGE_CATEGORIES: readonly string[] = ['signature', 'logo', 'formula', 'image'];
export const ActionsHelpModeKeys = {
redaction: 'redaction',
'manual-redaction': 'redaction',

View File

@ -33,29 +33,37 @@ export type RemoveRedactionOption = keyof typeof RemoveRedactionOptions;
export type RemoveAnnotationOption = RemoveRedactionOption;
export const getEditRedactionOptions = (
dossier: Dossier,
dossierName: string,
applyToAllDossiers: boolean,
isApprover: boolean,
): DetailsRadioOption<RedactOrHintOption>[] => [
{
label: editRedactionTranslations.onlyHere.label,
description: editRedactionTranslations.onlyHere.description,
icon: PIN_ICON,
value: ResizeOptions.ONLY_HERE,
},
{
label: editRedactionTranslations.inDossier.label,
description: editRedactionTranslations.inDossier.description,
descriptionParams: { dossierName: dossier.dossierName },
icon: FOLDER_ICON,
value: ResizeOptions.IN_DOSSIER,
extraOption: {
label: editRedactionTranslations.inDossier.extraOptionLabel,
checked: applyToAllDossiers,
hidden: !isApprover,
dossierDictionaryOnly: boolean,
isImage: boolean,
isManualRedaction: boolean,
): DetailsRadioOption<RedactOrHintOption>[] => {
const options: DetailsRadioOption<RedactOrHintOption>[] = [
{
label: editRedactionTranslations.onlyHere.label,
description: editRedactionTranslations.onlyHere.description,
icon: PIN_ICON,
value: ResizeOptions.ONLY_HERE,
},
},
];
];
if (!isImage && !isManualRedaction) {
options.push({
label: editRedactionTranslations.inDossier.label,
description: editRedactionTranslations.inDossier.description,
descriptionParams: { dossierName: dossierName },
icon: FOLDER_ICON,
value: ResizeOptions.IN_DOSSIER,
extraOption: {
label: editRedactionTranslations.inDossier.extraOptionLabel,
checked: applyToAllDossiers,
hidden: dossierDictionaryOnly,
disabled: true,
},
});
}
return options;
};
export const getRedactOrHintOptions = (
dossier: Dossier,

View File

@ -15,6 +15,7 @@ export interface RedactTextData {
export interface EditRedactionData {
annotations: AnnotationWrapper[];
dossierId: string;
applyToAllDossiers: boolean;
}
export type AddAnnotationData = RedactTextData;
@ -25,6 +26,15 @@ export interface RedactTextResult {
dictionary: Dictionary;
}
export interface EditRedactResult {
typeChanged: boolean;
legalBasis: string;
section: string;
comment: string;
type: string;
value: string;
}
export type AddHintResult = RedactTextResult;
export type AddAnnotationResult = RedactTextResult;

View File

@ -10,6 +10,7 @@ import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { FALLBACK_COLOR } from '@utils/constants';
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
import { List } from '@iqser/common-ui/lib/utils';
import { IMAGE_CATEGORIES } from '../../modules/file-preview/utils/constants';
const MIN_WORD_LENGTH = 2;
const IMAGE_TYPES = ['image', 'formula', 'ocr'];
@ -147,7 +148,7 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
return !!this._dictionariesMapService.get(dossierTemplateId).find(e => e.type === SuperTypes.ManualRedaction && !e.virtual);
}
getRedactTextDictionaries(dossierTemplateId: string, dossierDictionaryOnly: boolean, dictionaryRequest: boolean): Dictionary[] {
getRedactTextDictionaries(dossierTemplateId: string, dossierDictionaryOnly: boolean): Dictionary[] {
const dictionaries: Dictionary[] = [];
this._dictionariesMapService.get(dossierTemplateId).forEach((d: Dictionary) => {
@ -162,6 +163,17 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
return dictionaries.sort((a, b) => a.label.localeCompare(b.label));
}
getEditableRedactionTypes(dossierTemplateId: string, isImage: boolean): Dictionary[] {
return this._dictionariesMapService
.get(dossierTemplateId)
.filter(d =>
isImage
? IMAGE_CATEGORIES.includes(d.type)
: (d.hasDictionary && !d.virtual && !d.systemManaged) || d.type === SuperTypes.ManualRedaction,
)
.sort((a, b) => a.label.localeCompare(b.label));
}
getAddHintDictionaries(dossierTemplateId: string, dossierDictionaryOnly: boolean, dictionaryRequest: boolean): Dictionary[] {
const dictionaries: Dictionary[] = [];

View File

@ -1230,7 +1230,8 @@
"section": "",
"type": ""
},
"title": ""
"title-edit-image": "",
"title-edit-text": ""
}
},
"entities-listing": {

View File

@ -1230,7 +1230,8 @@
"section": "Paragraph / Location",
"type": "Type"
},
"title": "Edit Redaction"
"title-edit-image": "Edit image redaction",
"title-edit-text": "Edit redaction"
}
},
"entities-listing": {

View File

@ -1230,7 +1230,8 @@
"section": "",
"type": ""
},
"title": ""
"title-edit-image": "",
"title-edit-text": ""
}
},
"entities-listing": {

View File

@ -1230,7 +1230,8 @@
"section": "",
"type": ""
},
"title": ""
"title-edit-image": "",
"title-edit-text": ""
}
},
"entities-listing": {