RED-7155, add redact recommandation modal.

This commit is contained in:
George 2023-08-02 18:05:44 +03:00
parent 599507e262
commit 62e60b253a
8 changed files with 277 additions and 29 deletions

View File

@ -0,0 +1,58 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
<div class="dialog-content redaction">
<div *ngIf="form.get('selectedText').value as selectedText" class="iqser-input-group w-450">
<label [translate]="'redact-text.dialog.content.selected-text'" class="selected-text"></label>
{{ selectedText }}
</div>
<iqser-details-radio
(extraOptionChanged)="extraOptionChanged($event)"
[options]="options"
formControlName="option"
></iqser-details-radio>
<div *ngIf="dictionaryRequest" class="iqser-input-group required w-450">
<label [translate]="'redact-text.dialog.content.type'"></label>
<mat-form-field>
<mat-select [placeholder]="'redact-text.dialog.content.type-placeholder' | translate" formControlName="dictionary">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
<mat-option
(click)="typeChanged()"
*ngFor="let dictionary of dictionaries"
[matTooltip]="dictionary.description"
[value]="dictionary.type"
matTooltipPosition="after"
>
<span> {{ dictionary.label }} </span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="!dictionaryRequest" class="iqser-input-group w-450">
<label [translate]="'redact-text.dialog.content.comment'"></label>
<textarea
[placeholder]="'redact-text.dialog.content.comment-placeholder' | translate"
formControlName="comment"
iqserHasScrollbar
name="comment"
rows="4"
type="text"
></textarea>
</div>
</div>
<div class="dialog-actions">
<iqser-icon-button [label]="'redact-text.dialog.actions.save' | translate" [submit]="true" [type]="iconButtonTypes.primary">
</iqser-icon-button>
<div [translate]="'redact-text.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
</div>
</form>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</section>

View File

@ -0,0 +1,170 @@
import { Component, OnInit } from '@angular/core';
import { DetailsRadioOption, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
import { Dictionary, Dossier, IAddRedactionRequest, SuperTypes } from '@red/domain';
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
import { Roles } from '@users/roles';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { getRedactOrHintOptions, RedactOrHintOption, RedactOrHintOptions } from '../../utils/dialog-options';
import { RedactRecommendationData, RedactRecommendationResult } from '../../utils/dialog-types';
import { JustificationsService } from '@services/entity-services/justifications.service';
import { tap } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { firstValueFrom } from 'rxjs';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
@Component({
templateUrl: './redact-recommendation-dialog.component.html',
})
export class RedactRecommendationDialogComponent
extends IqserDialogComponent<RedactRecommendationDialogComponent, RedactRecommendationData, RedactRecommendationResult>
implements OnInit
{
readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes;
readonly options: DetailsRadioOption<RedactOrHintOption>[];
readonly firstEntry: AnnotationWrapper;
readonly isMulti: boolean;
dictionaryRequest = false;
legalOptions: LegalBasisOption[] = [];
dictionaries: Dictionary[] = [];
form!: UntypedFormGroup;
#manualRedactionTypeExists = true;
#applyToAllDossiers: boolean;
readonly #dossier: Dossier;
constructor(
private readonly _justificationsService: JustificationsService,
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _dictionaryService: DictionaryService,
private readonly _formBuilder: FormBuilder,
) {
super();
this.#dossier = this._activeDossiersService.find(this.data.dossierId);
this.#applyToAllDossiers = this.data.applyToAllDossiers ?? true;
this.options = getRedactOrHintOptions(this.#dossier, false, false, this.#applyToAllDossiers, this.data.isApprover);
this.#manualRedactionTypeExists = this._dictionaryService.hasManualType(this.#dossier.dossierTemplateId);
this.firstEntry = this.data.annotations[0];
this.isMulti = this.data.annotations.length > 1;
this.form = this.#getForm();
this.form
.get('option')
.valueChanges.pipe(
tap((option: DetailsRadioOption<RedactOrHintOption>) => {
this.dictionaryRequest = option.value === RedactOrHintOptions.IN_DOSSIER;
this.#setDictionaries();
this.#resetValues();
}),
takeUntilDestroyed(),
)
.subscribe();
this.form.get('option').setValue(this.options[1]);
}
get displayedDictionaryLabel() {
const dictType = this.form.get('dictionary').value;
if (dictType) {
return this.dictionaries.find(d => d.type === dictType)?.label ?? null;
}
return null;
}
get disabled() {
return this.dictionaryRequest && !this.form.get('dictionary').value;
}
async ngOnInit(): Promise<void> {
this.#setDictionaries();
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.#dossier.dossierTemplateId));
this.legalOptions = data.map(lbm => ({
legalBasis: lbm.reason,
description: lbm.description,
label: lbm.name,
}));
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
this.#selectReason();
this.#resetValues();
}
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() {
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;
}
}
save(): void {
const redaction = this.#convertRecommendationToRedaction(this.firstEntry);
this.dialogRef.close({
redaction,
isMulti: this.isMulti,
});
}
#setDictionaries() {
this.dictionaries = this._dictionaryService.getRedactTextDictionaries(this.#dossier.dossierTemplateId, !this.#applyToAllDossiers);
}
#getForm(): UntypedFormGroup {
return this._formBuilder.group({
selectedText: this.isMulti ? null : this.firstEntry.value,
comment: [null],
dictionary: [null],
option: [null],
});
}
#selectReason() {
if (this.legalOptions.length === 1) {
this.form.get('reason').setValue(this.legalOptions[0]);
}
}
#convertRecommendationToRedaction(recommendation: AnnotationWrapper): IAddRedactionRequest {
const addRedactionRequest: IAddRedactionRequest = { ...recommendation };
addRedactionRequest.type = this.form.get('dictionary').value;
addRedactionRequest.value = this.form.get('selectedText').value;
const selectedType = this.dictionaries.find(d => d.type === addRedactionRequest.type);
if (selectedType) {
addRedactionRequest.addToDictionary = selectedType.hasDictionary;
} else {
addRedactionRequest.addToDictionary = this.dictionaryRequest && addRedactionRequest.type !== 'dossier_redaction';
}
const commentValue = this.form.get('comment').value;
addRedactionRequest.comment = commentValue ? { text: commentValue } : null;
addRedactionRequest.addToAllDossiers = this.data.isApprover && this.dictionaryRequest && this.#applyToAllDossiers;
return addRedactionRequest;
}
#resetValues() {
this.#applyToAllDossiers = this.data.applyToAllDossiers ?? true;
if (this.dictionaryRequest) {
this.form.get('dictionary').setValue(this.firstEntry.type);
return;
}
this.form.get('dictionary').setValue(this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null);
}
}

View File

@ -1,7 +1,6 @@
import { Component } from '@angular/core';
import { DetailsRadioOption, IconButtonTypes } from '@iqser/common-ui';
import { DetailsRadioOption, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
import { IqserDialogComponent } from '@iqser/common-ui';
import { PermissionsService } from '@services/permissions.service';
import { tap } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@ -18,6 +17,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
> {
readonly iconButtonTypes = IconButtonTypes;
readonly options: DetailsRadioOption<RemoveRedactionOption>[];
readonly isRecommendation = this.data.redaction.isRecommendation;
form!: UntypedFormGroup;
hint: boolean;

View File

@ -73,6 +73,7 @@ import { ResizeAnnotationDialogComponent } from './dialogs/docu-mine/resize-anno
import { EditAnnotationDialogComponent } from './dialogs/docu-mine/edit-annotation-dialog/edit-annotation-dialog.component';
import { EditRedactionDialogComponent } from './dialogs/edit-redaction-dialog/edit-redaction-dialog.component';
import { TablesService } from './services/tables.service';
import { RedactRecommendationDialogComponent } from './dialogs/redact-recommendation-dialog/redact-recommendation-dialog.component';
const routes: IqserRoutes = [
{
@ -102,6 +103,7 @@ const dialogs = [
AddAnnotationDialogComponent,
RemoveAnnotationDialogComponent,
ResizeAnnotationDialogComponent,
RedactRecommendationDialogComponent,
];
const components = [

View File

@ -14,13 +14,7 @@ import {
} from '@red/domain';
import { toPosition } from '../utils/pdf-calculation.utils';
import { AnnotationDrawService } from '../../pdf-viewer/services/annotation-draw.service';
import {
AcceptRecommendationData,
AcceptRecommendationDialogComponent,
AcceptRecommendationReturnType,
} from '../dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component';
import { defaultDialogConfig, getConfig } from '@iqser/common-ui';
import { filter } from 'rxjs/operators';
import { getConfig } from '@iqser/common-ui';
import { MatDialog } from '@angular/material/dialog';
import { FilePreviewStateService } from './file-preview-state.service';
import { FilePreviewDialogService } from './file-preview-dialog.service';
@ -32,7 +26,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, log } from '@iqser/common-ui/lib/utils';
import { List, log } from '@iqser/common-ui/lib/utils';
import { PermissionsService } from '@services/permissions.service';
import {
EditRedactionData,
@ -48,6 +42,7 @@ import { ResizeRedactionDialogComponent } from '../dialogs/resize-redaction-dial
import { ResizeAnnotationDialogComponent } from '../dialogs/docu-mine/resize-annotation-dialog/resize-annotation-dialog.component';
import { EditRedactionDialogComponent } from '../dialogs/edit-redaction-dialog/edit-redaction-dialog.component';
import { EditAnnotationDialogComponent } from '../dialogs/docu-mine/edit-annotation-dialog/edit-annotation-dialog.component';
import { RedactRecommendationDialogComponent } from '../dialogs/redact-recommendation-dialog/redact-recommendation-dialog.component';
@Injectable()
export class AnnotationActionsService {
@ -207,18 +202,19 @@ export class AnnotationActionsService {
async convertRecommendationToAnnotation(recommendations: AnnotationWrapper[]) {
const { dossierId, fileId } = this._state;
const dialogRef = this._dialog.open<AcceptRecommendationDialogComponent, AcceptRecommendationData, AcceptRecommendationReturnType>(
AcceptRecommendationDialogComponent,
{ ...defaultDialogConfig, autoFocus: true, data: { annotations: recommendations, dossierId } },
);
const dialogClosed = dialogRef.afterClosed().pipe(filter(value => !!value && !!value.annotations));
await firstValueFrom(dialogClosed).then(({ annotations, comment: commentText }) => {
if (isJustOne(annotations) && this._annotationManager.resizingAnnotationId === annotations[0].id) {
this.cancelResize(annotations[0]).then();
}
const comment = commentText ? { text: commentText } : undefined;
this.#processObsAndEmit(this._manualRedactionService.addRecommendation(annotations, dossierId, fileId, comment));
});
const data = this.#getRedactRecommendationDialogData(recommendations);
const dialog = this._iqserDialog.openDefault(RedactRecommendationDialogComponent, { data });
const result = await dialog.result();
console.log(result);
if (!result) {
return;
}
if (!result.isMulti && this._annotationManager.resizingAnnotationId === recommendations[0].id) {
this.cancelResize(recommendations[0]).then();
}
this.#processObsAndEmit(this._manualRedactionService.addRecommendation(recommendations, result.redaction, dossierId, fileId));
}
async resize(annotationWrapper: AnnotationWrapper) {
@ -472,4 +468,17 @@ export class AnnotationActionsService {
}
return this._iqserDialog.openDefault(EditRedactionDialogComponent, { data });
}
#getRedactRecommendationDialogData(annotations: AnnotationWrapper[]) {
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
const isApprover = this._permissionsService.isApprover(this._state.dossier());
const applyDictionaryUpdatesToAllDossiersByDefault = dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault;
return {
annotations,
dossierId: this._state.dossierId,
applyToAllDossiers: isApprover ? applyDictionaryUpdatesToAllDossiersByDefault : false,
isApprover,
};
}
}

View File

@ -65,15 +65,16 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
return firstValueFrom(super.delete({}, url));
}
addRecommendation(annotations: AnnotationWrapper[], dossierId: string, fileId: string, comment = { text: 'Accepted Recommendation' }) {
addRecommendation(annotations: AnnotationWrapper[], redaction: IAddRedactionRequest, dossierId: string, fileId: string) {
const recommendations: List<IAddRedactionRequest> = annotations.map(annotation => ({
addToDictionary: true,
addToDictionary: redaction.addToDictionary,
addToAllDossiers: redaction.addToAllDossiers,
sourceId: annotation.id,
value: annotation.value,
reason: annotation.legalBasis ?? 'Dictionary Request',
positions: annotation.positions,
type: annotation.recommendationType,
comment,
type: redaction.type,
comment: redaction.comment,
}));
return this.addAnnotation(recommendations, dossierId, fileId);
}

View File

@ -1,5 +1,5 @@
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
import { Dictionary, Dossier, File, IManualRedactionEntry } from '@red/domain';
import { Dictionary, Dossier, File, IAddRedactionRequest, IManualRedactionEntry } from '@red/domain';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { DetailsRadioOption } from '@iqser/common-ui';
import { RemoveRedactionOption } from './dialog-options';
@ -16,6 +16,7 @@ export interface EditRedactionData {
annotations: AnnotationWrapper[];
dossierId: string;
applyToAllDossiers: boolean;
isApprover?: boolean;
}
export type AddAnnotationData = RedactTextData;
@ -26,6 +27,13 @@ export interface RedactTextResult {
dictionary: Dictionary;
}
export type RedactRecommendationData = EditRedactionData;
export interface RedactRecommendationResult {
redaction: IAddRedactionRequest;
isMulti: boolean;
}
export interface EditRedactResult {
typeChanged: boolean;
legalBasis: string;

View File

@ -1,7 +1,7 @@
{
"ADMIN_CONTACT_NAME": null,
"ADMIN_CONTACT_URL": null,
"API_URL": "https://dom1.iqser.cloud",
"API_URL": "https://dan.iqser.cloud",
"APP_NAME": "RedactManager",
"IS_DOCUMINE": false,
"AUTO_READ_TIME": 3,
@ -12,7 +12,7 @@
"MAX_RETRIES_ON_SERVER_ERROR": 3,
"OAUTH_CLIENT_ID": "redaction",
"OAUTH_IDP_HINT": null,
"OAUTH_URL": "https://dom1.iqser.cloud/auth",
"OAUTH_URL": "https://dan.iqser.cloud/auth",
"RECENT_PERIOD_IN_HOURS": 24,
"SELECTION_MODE": "structural",
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",