From 62e60b253a94ea362b0196600752d2da93f11c47 Mon Sep 17 00:00:00 2001 From: George Date: Wed, 2 Aug 2023 18:05:44 +0300 Subject: [PATCH] RED-7155, add redact recommandation modal. --- ...edact-recommendation-dialog.component.html | 58 ++++++ .../redact-recommendation-dialog.component.ts | 170 ++++++++++++++++++ .../remove-redaction-dialog.component.ts | 4 +- .../file-preview/file-preview.module.ts | 2 + .../services/annotation-actions.service.ts | 49 ++--- .../services/manual-redaction.service.ts | 9 +- .../file-preview/utils/dialog-types.ts | 10 +- apps/red-ui/src/assets/config/config.json | 4 +- 8 files changed, 277 insertions(+), 29 deletions(-) create mode 100644 apps/red-ui/src/app/modules/file-preview/dialogs/redact-recommendation-dialog/redact-recommendation-dialog.component.html create mode 100644 apps/red-ui/src/app/modules/file-preview/dialogs/redact-recommendation-dialog/redact-recommendation-dialog.component.ts diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-recommendation-dialog/redact-recommendation-dialog.component.html b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-recommendation-dialog/redact-recommendation-dialog.component.html new file mode 100644 index 000000000..936ed16ae --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-recommendation-dialog/redact-recommendation-dialog.component.html @@ -0,0 +1,58 @@ +
+
+
+ +
+
+ + {{ selectedText }} +
+ + + +
+ + + + + {{ displayedDictionaryLabel }} + + {{ dictionary.label }} + + + +
+ +
+ + +
+
+ +
+ + + +
+
+
+ + +
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-recommendation-dialog/redact-recommendation-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-recommendation-dialog/redact-recommendation-dialog.component.ts new file mode 100644 index 000000000..dbeb80b71 --- /dev/null +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-recommendation-dialog/redact-recommendation-dialog.component.ts @@ -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 + implements OnInit +{ + readonly roles = Roles; + readonly iconButtonTypes = IconButtonTypes; + readonly options: DetailsRadioOption[]; + 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) => { + 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 { + 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): 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); + } +} diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts index 27e37a39d..6ffd20905 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts @@ -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[]; + readonly isRecommendation = this.data.redaction.isRecommendation; form!: UntypedFormGroup; hint: boolean; diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts b/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts index 086827b0e..5d988dc88 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts @@ -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 = [ diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts index 9a7f5d81f..1b7a8c416 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts @@ -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, - { ...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, + }; + } } diff --git a/apps/red-ui/src/app/modules/file-preview/services/manual-redaction.service.ts b/apps/red-ui/src/app/modules/file-preview/services/manual-redaction.service.ts index 4becd01eb..c69052d07 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/manual-redaction.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/manual-redaction.service.ts @@ -65,15 +65,16 @@ export class ManualRedactionService extends GenericService { 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 = 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); } diff --git a/apps/red-ui/src/app/modules/file-preview/utils/dialog-types.ts b/apps/red-ui/src/app/modules/file-preview/utils/dialog-types.ts index 03ae0df7c..808e4fc20 100644 --- a/apps/red-ui/src/app/modules/file-preview/utils/dialog-types.ts +++ b/apps/red-ui/src/app/modules/file-preview/utils/dialog-types.ts @@ -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; diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index a1bd20386..9734775ed 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -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",