diff --git a/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen/reports-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen/reports-screen.component.html index e3f5c58a5..888d5b903 100644 --- a/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen/reports-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen/reports-screen.component.html @@ -2,67 +2,74 @@
-
- -
-
-
- -
{{ placeholder.placeholder }}
-
-
-
+ @if (!isDocumine) { +
+ } + @if (!isDocumine && placeholders$ | async; as placeholders) { +
+
+
+ @for (placeholder of placeholders; track placeholder.placeholder) { +
{{ placeholder.placeholder }}
+
+ } +
+ } -
-
-
- - -
- -
- -
-
- {{ template.fileName }} {{ template.multiFileReport ? ('reports-screen.multi-file-report' | translate) : '' }} -
- -
- +@if (availableTemplates$ | async; as availableTemplates) { +
+
+
+ +
+ + @for (template of availableTemplates; track template.templateId) { +
+
+ {{ template.fileName }} +
+ +
+ + + +
+
+ }
-
+} diff --git a/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen/reports-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen/reports-screen.component.ts index edc4dfa90..814fbc2da 100644 --- a/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen/reports-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen/reports-screen.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, inject, OnInit, viewChild } from '@angular/core'; import { DOSSIER_TEMPLATE_ID, IPlaceholdersResponse, IReportTemplate, User } from '@red/domain'; import { download } from '@utils/file-download-utils'; import { @@ -23,8 +23,8 @@ import { BehaviorSubject, firstValueFrom } from 'rxjs'; import { Roles } from '@users/roles'; import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { getParam } from '@iqser/common-ui/lib/utils'; -import { AsyncPipe, NgForOf, NgIf } from '@angular/common'; -import { TranslateModule } from '@ngx-translate/core'; +import { AsyncPipe } from '@angular/common'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { SnakeCasePipe } from '@common-ui/pipes/snake-case.pipe'; interface Placeholder { @@ -42,15 +42,16 @@ const placeholderTypes: PlaceholderType[] = ['generalPlaceholders', 'fileAttribu styleUrls: ['./reports-screen.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [HasScrollbarDirective, NgIf, NgForOf, AsyncPipe, TranslateModule, CircleButtonComponent, IqserAllowDirective, SnakeCasePipe], + imports: [HasScrollbarDirective, AsyncPipe, TranslateModule, CircleButtonComponent, IqserAllowDirective, SnakeCasePipe], }) export default class ReportsScreenComponent implements OnInit { - @ViewChild('fileInput') private readonly _fileInput: ElementRef; readonly placeholders$ = new BehaviorSubject([]); readonly availableTemplates$ = new BehaviorSubject([]); readonly currentUser = getCurrentUser(); readonly roles = Roles; readonly isDocumine = getConfig().IS_DOCUMINE; + readonly #translateService = inject(TranslateService); + private readonly _fileInput = viewChild('fileInput'); readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID); constructor( @@ -63,8 +64,8 @@ export default class ReportsScreenComponent implements OnInit { async ngOnInit() { this._loadingService.start(); - await this._loadReportTemplates(); - await this._loadPlaceholders(); + await this.#loadReportTemplates(); + await this.#loadPlaceholders(); this._loadingService.stop(); } @@ -84,14 +85,14 @@ export default class ReportsScreenComponent implements OnInit { deleteTemplate(template: IReportTemplate) { this._dialogService.openDialog('confirm', null, () => { - this._loadingService.loadWhile(this._deleteTemplate(template)); + this._loadingService.loadWhile(this.#deleteTemplate(template)); }); } uploadTemplate($event) { const file: File = $event.target.files[0]; - if (!this._isValidFile(file)) { + if (!this.#isValidFile(file)) { this._toaster.error(_('reports-screen.invalid-upload')); return; } @@ -115,27 +116,31 @@ export default class ReportsScreenComponent implements OnInit { template => template.fileName === file.name && template.multiFileReport === multiFileReport, ) ) { - await this._openOverwriteConfirmationDialog(file, multiFileReport); + await this.#openOverwriteConfirmationDialog(file, multiFileReport); } else { - await this._uploadTemplateForm(file, multiFileReport); + await this.#uploadTemplateForm(file, multiFileReport); } } }); - this._fileInput.nativeElement.value = null; + this._fileInput().nativeElement.value = null; } - private _getAttributeName(placeholder: string): string { + #getAttributeName(placeholder: string): string { return removeBraces(placeholder).split('.').pop(); } - private _getPlaceholderDescriptionTranslation(type: PlaceholderType, placeholder: string): string { + #getPlaceholderDescriptionTranslation(type: PlaceholderType, placeholder: string): string { return type === 'generalPlaceholders' ? generalPlaceholdersDescriptionsTranslations[removeBraces(placeholder)] : placeholdersDescriptionsTranslations[type]; } - private async _openOverwriteConfirmationDialog(file: File, multiFileReport: boolean): Promise { + #getTemplateFilename(template: IReportTemplate): string { + return `${template.fileName} ${template.multiFileReport ? this.#translateService.instant(_('reports-screen.multi-file-report')) : ''}`.trim(); + } + + async #openOverwriteConfirmationDialog(file: File, multiFileReport: boolean): Promise { const data: IConfirmationDialogData = { title: _('confirmation-dialog.report-template-same-name.title'), question: _('confirmation-dialog.report-template-same-name.question'), @@ -148,29 +153,34 @@ export default class ReportsScreenComponent implements OnInit { this._dialogService.openDialog('confirm', data, null, async result => { if (result) { - await this._uploadTemplateForm(file, multiFileReport); + await this.#uploadTemplateForm(file, multiFileReport); } }); } - private async _uploadTemplateForm(file: File, multiFileReport: boolean): Promise { + async #uploadTemplateForm(file: File, multiFileReport: boolean): Promise { this._loadingService.start(); await firstValueFrom(this._reportTemplateService.uploadTemplateForm(this.#dossierTemplateId, multiFileReport, file)); - await this._loadReportTemplates(); + await this.#loadReportTemplates(); this._loadingService.stop(); } - private async _deleteTemplate(template: IReportTemplate) { + async #deleteTemplate(template: IReportTemplate) { await firstValueFrom(this._reportTemplateService.delete(template.dossierTemplateId, template.templateId)); - await this._loadReportTemplates(); + await this.#loadReportTemplates(); } - private async _loadReportTemplates() { + async #loadReportTemplates() { const reportTemplates = await this._reportTemplateService.getAvailableReportTemplates(this.#dossierTemplateId); - this.availableTemplates$.next(reportTemplates); + this.availableTemplates$.next( + reportTemplates.map(template => ({ + ...template, + fileName: this.#getTemplateFilename(template), + })), + ); } - private async _loadPlaceholders() { + async #loadPlaceholders() { const placeholdersResponse: IPlaceholdersResponse = await firstValueFrom( this._reportTemplateService.getAvailablePlaceholders(this.#dossierTemplateId), ); @@ -178,25 +188,25 @@ export default class ReportsScreenComponent implements OnInit { placeholderTypes.flatMap(type => placeholdersResponse[type].map(placeholder => ({ placeholder, - descriptionTranslation: this._getPlaceholderDescriptionTranslation(type, placeholder), - attributeName: this._getAttributeName(placeholder), + descriptionTranslation: this.#getPlaceholderDescriptionTranslation(type, placeholder), + attributeName: this.#getAttributeName(placeholder), })), ), ); } - private _isValidFile(file: File): boolean { - return this._isExcelFile(file) || this._isWordFile(file); + #isValidFile(file: File): boolean { + return this.#isExcelFile(file) || this.#isWordFile(file); } - private _isExcelFile(file: File): boolean { + #isExcelFile(file: File): boolean { return ( file.type?.toLowerCase() === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || file.name.toLowerCase().endsWith('.xlsx') ); } - private _isWordFile(file: File): boolean { + #isWordFile(file: File): boolean { return ( file.type?.toLowerCase() === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || file.name.toLowerCase().endsWith('.docx') diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/docu-mine/edit-annotation-dialog/edit-annotation-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/docu-mine/edit-annotation-dialog/edit-annotation-dialog.component.ts index 58745f45b..156c920ce 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/docu-mine/edit-annotation-dialog/edit-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/docu-mine/edit-annotation-dialog/edit-annotation-dialog.component.ts @@ -8,7 +8,7 @@ import { IconButtonTypes, IqserDialogComponent, } from '@iqser/common-ui'; -import { Dictionary, Dossier, SuperTypes } from '@red/domain'; +import { Dictionary, Dossier } from '@red/domain'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { DictionaryService } from '@services/entity-services/dictionary.service'; import { Roles } from '@users/roles'; @@ -47,12 +47,12 @@ export class EditAnnotationDialogComponent extends IqserDialogComponent implements OnInit { - readonly #dossier: Dossier; readonly roles = Roles; readonly iconButtonTypes = IconButtonTypes; readonly redactedTexts: string[]; dictionaries: Dictionary[] = []; form: UntypedFormGroup; + readonly #dossier: Dossier; constructor( private readonly _activeDossiersService: ActiveDossiersService, @@ -60,7 +60,7 @@ export class EditAnnotationDialogComponent private readonly _formBuilder: FormBuilder, ) { super(); - this.#dossier = _activeDossiersService.find(this.data.dossierId); + this.#dossier = this._activeDossiersService.find(this.data.dossierId); const annotations = this.data.annotations; this.redactedTexts = annotations.map(annotation => annotation.value); this.form = this.#getForm(); @@ -83,10 +83,6 @@ export class EditAnnotationDialogComponent this.#setTypes(); } - reasonChanged() { - this.form.patchValue({ reason: this.dictionaries.find(d => d.type === SuperTypes.ManualRedaction) }); - } - save(): void { const value = this.form.value; this.dialogRef.close({ @@ -106,8 +102,4 @@ export class EditAnnotationDialogComponent type: [sameType ? this.data.annotations[0].type : null], }); } - - #allRectangles() { - return this.data.annotations.reduce((acc, a) => acc && a.AREA, true); - } } diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/edit-redaction-dialog/edit-redaction-dialog.component.html b/apps/red-ui/src/app/modules/file-preview/dialogs/edit-redaction-dialog/edit-redaction-dialog.component.html index 00afb75cc..a1f9c2ce9 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/edit-redaction-dialog/edit-redaction-dialog.component.html +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/edit-redaction-dialog/edit-redaction-dialog.component.html @@ -6,8 +6,8 @@ class="dialog-header heading-l" >
-
-
+
+
-
+ + +
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/edit-redaction-dialog/edit-redaction-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/edit-redaction-dialog/edit-redaction-dialog.component.ts index 44aa3110c..2191aff10 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/edit-redaction-dialog/edit-redaction-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/edit-redaction-dialog/edit-redaction-dialog.component.ts @@ -5,7 +5,6 @@ import { MatDialogClose } from '@angular/material/dialog'; import { MatFormField } from '@angular/material/form-field'; import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select'; import { MatTooltip } from '@angular/material/tooltip'; -import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option'; import { CircleButtonComponent, HasScrollbarDirective, @@ -26,10 +25,11 @@ import { SelectedAnnotationsTableComponent, ValueColumn, } from '../../components/selected-annotations-table/selected-annotations-table.component'; -import { DialogHelpModeKeys } from '../../utils/constants'; import { getEditRedactionOptions } from '../../utils/dialog-options'; -import { EditRedactionData, EditRedactResult, RedactOrHintOption } from '../../utils/dialog-types'; +import { EditRedactionData, EditRedactionOption, EditRedactResult } from '../../utils/dialog-types'; import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component'; +import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component'; +import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option'; interface TypeSelectOptions { type: string; @@ -58,18 +58,16 @@ interface TypeSelectOptions { HelpButtonComponent, MatDialogClose, HasScrollbarDirective, + DetailsRadioComponent, ], }) export class EditRedactionDialogComponent extends IqserDialogComponent implements OnInit { - readonly #dossier = inject(ActiveDossiersService).find(this.data.dossierId); - readonly #applyToAllDossiers = this.data.applyToAllDossiers; - protected readonly roles = Roles; + readonly ignoredKeys = ['option', 'comment']; readonly annotations = this.data.annotations; readonly iconButtonTypes = IconButtonTypes; - readonly isModifyDictionary = this.annotations.every(annotation => annotation.isModifyDictionary); readonly isImage = this.annotations.reduce((acc, next) => acc && next.isImage, true); readonly redactedTexts = !this.isImage ? this.annotations.map(annotation => annotation.value).filter(value => !!value) : null; readonly isManualRedaction = this.annotations.some(annotation => annotation.type === SuperTypes.ManualRedaction); @@ -82,13 +80,15 @@ export class EditRedactionDialogComponent { label: redaction.value, bold: true }, { label: redaction.typeLabel }, ]); - options: DetailsRadioOption[] | undefined; + options = getEditRedactionOptions(); legalOptions: LegalBasisOption[] = []; dictionaries: Dictionary[] = []; typeSelectOptions: TypeSelectOptions[] = []; readonly form = this.#getForm(); hasTypeChanged = false; initialReasonDisabled = this.someSkipped; + protected readonly roles = Roles; + readonly #dossier = inject(ActiveDossiersService).find(this.data.dossierId); constructor( private readonly _justificationsService: JustificationsService, @@ -144,16 +144,6 @@ export class EditRedactionDialogComponent return this.annotations.length > 1; } - get helpButtonKey() { - if (this.isHint) { - return DialogHelpModeKeys.HINT_EDIT; - } - if (this.someSkipped) { - return DialogHelpModeKeys.SKIPPED_EDIT; - } - return DialogHelpModeKeys.REDACTION_EDIT; - } - get sameType() { return this.annotations.every(annotation => annotation.type === this.annotations[0].type); } @@ -179,7 +169,6 @@ export class EditRedactionDialogComponent typeChanged() { const selectedDictionaryType = this.form.controls.type.value; - this.#setOptions(selectedDictionaryType); const initialReason = this.form.get('type').value === this.initialFormValue.type && !this.initialReasonDisabled; if (this.redactBasedTypes.includes(selectedDictionaryType) || initialReason) { @@ -193,7 +182,7 @@ export class EditRedactionDialogComponent } else { this.form.controls.reason.disable(); } - this.form.patchValue({ reason: null, option: null }); + this.form.patchValue({ reason: null }); } save() { @@ -206,6 +195,7 @@ export class EditRedactionDialogComponent comment: value.comment, type: value.type, value: this.allRectangles ? value.value : null, + option: value.option.value, }); } @@ -230,22 +220,6 @@ export class EditRedactionDialogComponent } } - #setOptions(type: string, reasonChanged = false) { - const selectedDictionary = this.dictionaries.find(d => d.type === type); - this.options = getEditRedactionOptions( - this.#dossier.dossierName, - this.#applyToAllDossiers, - !!selectedDictionary?.dossierDictionaryOnly, - this.isModifyDictionary, - ); - this.form.patchValue( - { - option: !this.isModifyDictionary || reasonChanged ? this.options[0] : this.options[1], - }, - { emitEvent: false }, - ); - } - #getForm() { const sameSection = this.annotations.every(annotation => annotation.section === this.annotations[0].section); return new FormGroup({ @@ -256,7 +230,7 @@ export class EditRedactionDialogComponent disabled: this.isImported, }), section: new FormControl({ value: sameSection ? this.annotations[0].section : null, disabled: this.isImported }), - option: new FormControl(null), + option: new FormControl>(this.options[0]), value: new FormControl(this.allRectangles ? this.annotations[0].value : 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 972fd64ba..1d637a41b 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 @@ -24,7 +24,6 @@ import { SelectedAnnotationsTableComponent, ValueColumn, } from '../../components/selected-annotations-table/selected-annotations-table.component'; -import { DialogHelpModeKeys } from '../../utils/constants'; import { getRemoveRedactionOptions } from '../../utils/dialog-options'; import { RemoveRedactionData, 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 05b6ae67f..bd89af035 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 @@ -9,6 +9,7 @@ import { DictionaryEntryTypes, EarmarkOperation, type IBulkLocalRemoveRequest, + IBulkRecategorizationRequest, ILegalBasisChangeRequest, IRecategorizationRequest, IRectangle, @@ -34,6 +35,7 @@ import { EditRedactionData, EditRedactResult, ForceAnnotationOptions, + RedactOrHintOptions, RemoveRedactionData, RemoveRedactionOptions, RemoveRedactionPermissions, @@ -108,13 +110,11 @@ export class AnnotationActionsService { } async editRedaction(annotations: AnnotationWrapper[]) { - const { dossierId, dossierTemplateId, fileId, file } = this._state; + const { dossierId, fileId } = this._state; const includeUnprocessed = annotations.every(annotation => this.#includeUnprocessed(annotation, true)); - const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId); const data = { annotations, dossierId, - applyToAllDossiers: dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault, }; const result = await this.#getEditRedactionDialog(data).result(); @@ -122,22 +122,38 @@ export class AnnotationActionsService { return; } - const recategorizeBody: List = annotations.map(annotation => { - const body: IRecategorizationRequest = { - annotationId: annotation.id, - type: result.type ?? annotation.type, - comment: result.comment, - }; - if (!this.#isDocumine) { - return { - ...body, - legalBasis: result.legalBasis, - section: result.section ?? annotation.section, - value: result.value ?? annotation.value, + let recategorizeBody: List | IBulkRecategorizationRequest; + + if (result.option === RedactOrHintOptions.ONLY_HERE) { + recategorizeBody = annotations.map(annotation => { + const body: IRecategorizationRequest = { + annotationId: annotation.id, + type: result.type ?? annotation.type, + comment: result.comment, }; - } - return body; - }); + if (!this.#isDocumine) { + return { + ...body, + legalBasis: result.legalBasis, + section: result.section ?? annotation.section, + value: result.value ?? annotation.value, + }; + } + return body; + }); + } else { + const originTypes = annotations.map(a => a.type); + const originLegalBases = annotations.map(a => a.legalBasis); + recategorizeBody = { + value: annotations[0].value, + type: result.type, + legalBasis: result.legalBasis, + section: result.section, + originTypes, + originLegalBases, + rectangle: false, + }; + } await this.#processObsAndEmit( this._manualRedactionService @@ -147,6 +163,7 @@ export class AnnotationActionsService { fileId, this.#getChangedFields(annotations, result), includeUnprocessed, + result.option === RedactOrHintOptions.IN_DOCUMENT, ) .pipe(log()), ); 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 36504f8db..0ed5aa351 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 @@ -9,6 +9,7 @@ import type { DictionaryActions, IAddRedactionRequest, IBulkLocalRemoveRequest, + IBulkRecategorizationRequest, ILegalBasisChangeRequest, IManualAddResponse, IRecategorizationRequest, @@ -71,15 +72,16 @@ export class ManualRedactionService extends GenericService { } recategorizeRedactions( - body: List, + body: List | IBulkRecategorizationRequest, dossierId: string, fileId: string, successMessageParameters?: { [key: string]: string; }, includeUnprocessed = false, + bulkLocal = false, ) { - return this.recategorize(body, dossierId, fileId, includeUnprocessed).pipe( + return this.#recategorize(body, dossierId, fileId, includeUnprocessed, bulkLocal).pipe( this.#showToast('recategorize-annotation', false, successMessageParameters), ); } @@ -118,7 +120,7 @@ export class ManualRedactionService extends GenericService { includeUnprocessed = false, bulkLocal = false, ) { - return this.remove(body, dossierId, fileId, includeUnprocessed, bulkLocal).pipe( + return this.#remove(body, dossierId, fileId, includeUnprocessed, bulkLocal).pipe( this.#showToast(!isHint ? 'remove' : 'remove-hint', removeFromDictionary), ); } @@ -141,30 +143,11 @@ export class ManualRedactionService extends GenericService { return this._post(bulkLocal ? body[0] : body, `${bulkPath}/add/${dossierId}/${fileId}`).pipe(this.#log('Add', body)); } - recategorize(body: List, dossierId: string, fileId: string, includeUnprocessed = false) { - return this._post(body, `${this.#bulkRedaction}/recategorize/${dossierId}/${fileId}?includeUnprocessed=${includeUnprocessed}`).pipe( - this.#log('Recategorize', body), - ); - } - undo(annotationIds: List, dossierId: string, fileId: string) { const url = `${this._defaultModelPath}/bulk/undo/${dossierId}/${fileId}`; return super.delete(annotationIds, url).pipe(this.#log('Undo', annotationIds)); } - remove( - body: List | IBulkLocalRemoveRequest, - dossierId: string, - fileId: string, - includeUnprocessed = false, - bulkLocal = false, - ) { - const bulkPath = bulkLocal ? this.#bulkLocal : this.#bulkRedaction; - return this._post(body, `${bulkPath}/remove/${dossierId}/${fileId}?includeUnprocessed=${includeUnprocessed}`).pipe( - this.#log('Remove', body), - ); - } - forceRedaction(body: List, dossierId: string, fileId: string) { return this._post(body, `${this.#bulkRedaction}/force/${dossierId}/${fileId}`).pipe(this.#log('Force redaction', body)); } @@ -175,6 +158,32 @@ export class ManualRedactionService extends GenericService { ); } + #recategorize( + body: List | IBulkRecategorizationRequest, + dossierId: string, + fileId: string, + includeUnprocessed = false, + bulkLocal = false, + ) { + const bulkPath = bulkLocal ? this.#bulkLocal : this.#bulkRedaction; + return this._post(body, `${bulkPath}/recategorize/${dossierId}/${fileId}?includeUnprocessed=${includeUnprocessed}`).pipe( + this.#log('Recategorize', body), + ); + } + + #remove( + body: List | IBulkLocalRemoveRequest, + dossierId: string, + fileId: string, + includeUnprocessed = false, + bulkLocal = false, + ) { + const bulkPath = bulkLocal ? this.#bulkLocal : this.#bulkRedaction; + return this._post(body, `${bulkPath}/remove/${dossierId}/${fileId}?includeUnprocessed=${includeUnprocessed}`).pipe( + this.#log('Remove', body), + ); + } + #log(action: string, body: unknown) { return tap(response => { this._logger.info(`[MANUAL-REDACTIONS] ${action} `, body, response); diff --git a/apps/red-ui/src/app/modules/file-preview/utils/constants.ts b/apps/red-ui/src/app/modules/file-preview/utils/constants.ts index 57787e87b..1b1590059 100644 --- a/apps/red-ui/src/app/modules/file-preview/utils/constants.ts +++ b/apps/red-ui/src/app/modules/file-preview/utils/constants.ts @@ -19,16 +19,6 @@ export const ActionsHelpModeKeys = { 'hint-image': 'hint', } as const; -export const DialogHelpModeKeys = { - REDACTION_EDIT: 'redaction_edit', - REDACTION_REMOVE: 'redaction_remove', - SKIPPED_EDIT: 'skipped_edit', - SKIPPED_REMOVE: 'skipped_remove', - RECOMMENDATION_REMOVE: 'recommendation_remove', - HINT_EDIT: 'hint_edit', - HINT_REMOVE: 'hint_remove', -} as const; - export const ALL_HOTKEYS: List = ['Escape', 'F', 'f', 'ArrowUp', 'ArrowDown', 'H', 'h'] as const; export const HeaderElements = { diff --git a/apps/red-ui/src/app/modules/file-preview/utils/dialog-options.ts b/apps/red-ui/src/app/modules/file-preview/utils/dialog-options.ts index 74e0a7720..8f5830e0f 100644 --- a/apps/red-ui/src/app/modules/file-preview/utils/dialog-options.ts +++ b/apps/red-ui/src/app/modules/file-preview/utils/dialog-options.ts @@ -7,6 +7,7 @@ import { removeAnnotationTranslations } from '@translations/remove-annotation-tr import { removeRedactionTranslations } from '@translations/remove-redaction-translations'; import { resizeRedactionTranslations } from '@translations/resize-redaction-translations'; import { + EditRedactionOption, ForceAnnotationOption, RectangleRedactOption, RectangleRedactOptions, @@ -25,36 +26,21 @@ const DOCUMENT_ICON = 'iqser:document'; const FOLDER_ICON = 'red:folder'; const REMOVE_FROM_DICT_ICON = 'red:remove-from-dict'; -export const getEditRedactionOptions = ( - dossierName: string, - applyToAllDossiers: boolean, - dossierDictionaryOnly: boolean, - isModifyDictionary: boolean, -): DetailsRadioOption[] => { - const options: DetailsRadioOption[] = [ +export const getEditRedactionOptions = (): DetailsRadioOption[] => { + return [ { label: editRedactionTranslations.onlyHere.label, description: editRedactionTranslations.onlyHere.description, icon: PIN_ICON, - value: ResizeOptions.ONLY_HERE, + value: RedactOrHintOptions.ONLY_HERE, + }, + { + label: editRedactionTranslations.inDocument.label, + description: editRedactionTranslations.inDocument.description, + icon: DOCUMENT_ICON, + value: RedactOrHintOptions.IN_DOCUMENT, }, ]; - if (isModifyDictionary) { - options.push({ - label: editRedactionTranslations.inDossier.label, - description: editRedactionTranslations.inDossier.description, - descriptionParams: { dossierName: dossierName }, - icon: FOLDER_ICON, - value: ResizeOptions.IN_DOSSIER, - additionalCheck: { - label: editRedactionTranslations.inDossier.extraOptionLabel, - checked: applyToAllDossiers, - hidden: dossierDictionaryOnly, - disabled: true, - }, - }); - } - return options; }; export const getRedactOrHintOptions = ( @@ -74,7 +60,7 @@ export const getRedactOrHintOptions = ( label: translations.onlyHere.label, description: translations.onlyHere.description, icon: PIN_ICON, - value: ResizeOptions.ONLY_HERE, + value: RedactOrHintOptions.ONLY_HERE, }); } @@ -87,7 +73,7 @@ export const getRedactOrHintOptions = ( label: redactTextTranslations.inDocument.label, description: redactTextTranslations.inDocument.description, icon: DOCUMENT_ICON, - value: ResizeOptions.IN_DOCUMENT, + value: RedactOrHintOptions.IN_DOCUMENT, }); } @@ -96,7 +82,7 @@ export const getRedactOrHintOptions = ( description: translations.inDossier.description, descriptionParams: { dossierName: dossier.dossierName }, icon: FOLDER_ICON, - value: ResizeOptions.IN_DOSSIER, + value: RedactOrHintOptions.IN_DOSSIER, disabled: isPageExcluded, additionalCheck: { label: translations.inDossier.extraOptionLabel, @@ -145,7 +131,7 @@ export const getResizeRedactionOptions = ( label: translations.onlyHere.label, description: translations.onlyHere.description, icon: PIN_ICON, - value: RedactOrHintOptions.ONLY_HERE, + value: ResizeOptions.ONLY_HERE, }, ]; @@ -161,8 +147,8 @@ export const getResizeRedactionOptions = ( disabled: !dictBasedType || redaction.hasBeenRecategorized, tooltip: !dictBasedType ? translations.inDossier.tooltip : null, icon: FOLDER_ICON, - value: RedactOrHintOptions.IN_DOSSIER, - additionalCheck: { + value: ResizeOptions.IN_DOSSIER, + extraOption: { label: translations.inDossier.extraOptionLabel, checked: applyToAllDossiers, hidden: !isApprover, 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 284469ae9..94aff4a00 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 @@ -3,6 +3,13 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; import { Dictionary, Dossier, File, IAddRedactionRequest, IManualRedactionEntry } from '@red/domain'; +export const EditRedactionOptions = { + ONLY_HERE: 'ONLY_HERE', + IN_DOCUMENT: 'IN_DOCUMENT', +} as const; + +export type EditRedactionOption = keyof typeof EditRedactionOptions; + export const RedactOrHintOptions = { ONLY_HERE: 'ONLY_HERE', IN_DOCUMENT: 'IN_DOCUMENT', @@ -52,7 +59,6 @@ export interface RedactTextData { export interface EditRedactionData { annotations: AnnotationWrapper[]; dossierId: string; - applyToAllDossiers: boolean; isApprover?: boolean; } @@ -65,7 +71,9 @@ export interface RedactTextResult { bulkLocal?: boolean; } -export type RedactRecommendationData = EditRedactionData; +export type RedactRecommendationData = EditRedactionData & { + applyToAllDossiers: boolean; +}; export interface RedactRecommendationResult { redaction: IAddRedactionRequest; @@ -79,6 +87,7 @@ export interface EditRedactResult { comment: string; type: string; value: string; + option: EditRedactionOption; } export type AddHintResult = RedactTextResult; diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html index 7d5b0684e..d2ad69a42 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html @@ -1,7 +1,7 @@
-
-
-
-
- {{ selectedDictionary?.label }} - -
-
-
- - - {{ - 'edit-dossier-dialog.dictionary.entries' - | translate: { length: entriesToDisplay.length, hint: selectedDictionary.hint } - }} - - - {{ - 'edit-dossier-dialog.dictionary.false-positive-entries' | translate: { length: entriesToDisplay.length } - }} - - - {{ - 'edit-dossier-dialog.dictionary.false-recommendation-entries' - | translate: { length: entriesToDisplay.length } - }} - -
-
-
-
+
+ {{ selectedDictionary?.label }} +
-
+
= this.#dictionaries; + protected readonly _isSearchOpen = signal(false); protected initialDossierTemplateId: string; readonly #currentTab = window.location.href.split('/').pop(); #dossierTemplate = this.dossierTemplatesService.all[0]; diff --git a/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts index 45a452d89..e2538f236 100644 --- a/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { Component, Input, model, OnChanges, OnInit, SimpleChanges, untracked } from '@angular/core'; import { LoadingService } from '@iqser/common-ui'; import { EditorThemeService } from '@services/editor-theme.service'; import { Subject } from 'rxjs'; @@ -39,6 +39,7 @@ export class EditorComponent implements OnInit, OnChanges { @Input() diffEditorText: string; @Input() @OnChange('revert') initialEntries: List; @Input() canEdit = false; + readonly isSearchOpen = model.required(); /** * Used as [modified] input on diff editor * Shouldn't be updated when editing in diff editor. @@ -84,9 +85,14 @@ export class EditorComponent implements OnInit, OnChanges { return this.currentEntries.length; } - async openFindPanel(): Promise { + async toggleFindPanel(): Promise { + const isFindPanelOpen = untracked(this.isSearchOpen); const editor = this.showDiffEditor ? this._diffEditor.getOriginalEditor() : this.codeEditor; - await editor.getAction('actions.find').run(); + if (isFindPanelOpen) { + await (editor.getContribution('editor.contrib.findController') as any).closeFindWidget(); + } else { + await editor.getAction('actions.find').run(); + } } onPaste(event: ClipboardEvent) { @@ -127,11 +133,13 @@ export class EditorComponent implements OnInit, OnChanges { this._diffEditor.getModifiedEditor().onDidChangeModelContent(() => { this.value = this._diffEditor.getModel().modified.getValue(); }); + this._initializeFindWidget(editor.getOriginalEditor()); this.#setTheme(); } onCodeEditorInit(editor: MonacoStandaloneCodeEditor): void { this.codeEditor = editor; + this._initializeFindWidget(editor); this.#setTheme(); } @@ -143,6 +151,15 @@ export class EditorComponent implements OnInit, OnChanges { this._editorTextChanged$.next(this.value); } + private _initializeFindWidget(editor: MonacoStandaloneCodeEditor): void { + this.isSearchOpen.set(false); + (editor.getContribution('editor.contrib.findController') as any).getState().onFindReplaceStateChange(event => { + if (event.isRevealed) { + this.isSearchOpen.update(v => !v); + } + }); + } + #getDecorations(newText: string) { const currentEntries = newText.split('\n'); const newDecorations: IModelDeltaDecoration[] = []; diff --git a/apps/red-ui/src/app/translations/redact-text-translations.ts b/apps/red-ui/src/app/translations/redact-text-translations.ts index 2b334471b..69c774c54 100644 --- a/apps/red-ui/src/app/translations/redact-text-translations.ts +++ b/apps/red-ui/src/app/translations/redact-text-translations.ts @@ -26,20 +26,13 @@ export const redactTextTranslations: Record<'onlyHere' | 'inDocument' | 'inDossi }, } as const; -export const editRedactionTranslations: Record<'onlyHere' | 'inDossier', DialogOption> = { +export const editRedactionTranslations: Record<'onlyHere' | 'inDocument', DialogOption> = { onlyHere: { label: _('edit-redaction.dialog.content.options.only-here.label'), description: _('edit-redaction.dialog.content.options.only-here.description'), }, - inDossier: { - label: _('edit-redaction.dialog.content.options.in-dossier.label'), - description: _('edit-redaction.dialog.content.options.in-dossier.description'), - extraOptionLabel: _('edit-redaction.dialog.content.options.in-dossier.extraOptionLabel'), + inDocument: { + label: _('edit-redaction.dialog.content.options.in-document.label'), + description: _('edit-redaction.dialog.content.options.in-document.description'), }, } as const; - -export const editRedactionLabelsTranslations = { - redactedText: _('edit-redaction.dialog.content.redacted-text'), - customRectangle: _('edit-redaction.dialog.content.custom-rectangle'), - imported: _('edit-redaction.dialog.content.imported'), -} as const; diff --git a/apps/red-ui/src/app/utils/main.guard.ts b/apps/red-ui/src/app/utils/main.guard.ts index 0c8874d5c..7c4db96bd 100644 --- a/apps/red-ui/src/app/utils/main.guard.ts +++ b/apps/red-ui/src/app/utils/main.guard.ts @@ -27,8 +27,6 @@ export function mainGuard(): AsyncGuard { const logger = inject(NGXLogger); logger.info('[ROUTES] Main resolver started...'); - console.log('main guard'); - const router = inject(Router); inject(FeaturesService).loadConfig(); if (inject(IqserPermissionsService).has(Roles.dossiers.read)) { diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index eabffcfef..0af7bd06f 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -1234,11 +1234,8 @@ "save": "Speichern", "title": "{label} bearbeiten" }, - "entries": "{length} {length, plural, one{Eintrag} other{Einträge}}", "entries-count": "", - "false-positive-entries": "{length} {length, plural, one{Falsch-Positiver} other{Falsch-Positive}}", "false-positives": "Falsch-Positive ({count})", - "false-recommendation-entries": "{length} {length, plural, one{falsche Empfehlung} other{falsche Empfehlungen}}", "false-recommendations": "Falsche Empfehlungen ({count})", "to-redact": "Schwärzungen ({count})" }, @@ -1284,14 +1281,11 @@ "content": { "comment": "Kommentar", "comment-placeholder": "Bemerkungen oder Notizen hinzufügen...", - "custom-rectangle": "Bereichsschwärzung", - "imported": "Importierte Schwärzung", "legal-basis": "Rechtsgrundlage", "options": { - "in-dossier": { - "description": "Schwärzung in jedem Dokument in {dossierName} bearbeiten.", - "extraOptionLabel": "In alle aktiven und zukünftigen Dossiers übernehmen", - "label": "Typ in Dossier ändern" + "in-document": { + "description": "", + "label": "" }, "only-here": { "description": "Bearbeiten Sie die Schwärzung nur an dieser Stelle im Dokument.", diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index ab30f44eb..ef8b3e852 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -1234,11 +1234,8 @@ "save": "Save", "title": "Edit {label}" }, - "entries": "{length} {length, plural, one{entry} other{entries}} to redact", "entries-count": "{count} {count, plural, one{entry} other{entries}}", - "false-positive-entries": "{length} false positive {length, plural, one{entry} other{entries}}", "false-positives": "False positives ({count})", - "false-recommendation-entries": "{length} false recommendation {length, plural, one{entry} other{entries}}", "false-recommendations": "False recommendations ({count})", "to-redact": "Entries ({count})" }, @@ -1284,18 +1281,15 @@ "content": { "comment": "Comment", "comment-placeholder": "Add remarks or notes...", - "custom-rectangle": "Custom Rectangle", - "imported": "Imported Redaction", "legal-basis": "Legal basis", "options": { - "in-dossier": { - "description": "Edit redaction in every document in {dossierName}.", - "extraOptionLabel": "Apply to all active and future dossiers", - "label": "Change type in dossier" + "in-document": { + "description": "Edit redaction of all linked occurrences of the term in this document.", + "label": "Change in document" }, "only-here": { "description": "Edit redaction only at this position in this document.", - "label": "Change type only here" + "label": "Change only here" } }, "reason": "Reason", diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index dd2b7ee47..0371da1b1 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -1234,11 +1234,8 @@ "save": "", "title": "" }, - "entries": "{length} {length, plural, one{entry} other{entries}} to {hint, select, true{annotate} other{redact}}", "entries-count": "", - "false-positive-entries": "{length} false positive {length, plural, one{entry} other{entries}}", "false-positives": "False positives ({count})", - "false-recommendation-entries": "{length} false recommendation {length, plural, one{entry} other{entries}}", "false-recommendations": "False recommendations ({count})", "to-redact": "To redact ({count})" }, @@ -1284,13 +1281,10 @@ "content": { "comment": "Comment", "comment-placeholder": "Add remarks or mentions...", - "custom-rectangle": "", - "imported": "", "legal-basis": "", "options": { - "in-dossier": { + "in-document": { "description": "", - "extraOptionLabel": "", "label": "" }, "only-here": { diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 96f6e6184..5b507aa30 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -1234,11 +1234,8 @@ "save": "", "title": "" }, - "entries": "{length} {length, plural, one{entry} other{entries}} to {hint, select, true{annotate} other{redact}}", "entries-count": "{count} {count, plural, one{entry} other{entries}}", - "false-positive-entries": "{length} false positive {length, plural, one{entry} other{entries}}", "false-positives": "False positives ({count})", - "false-recommendation-entries": "{length} false recommendation {length, plural, one{entry} other{entries}}", "false-recommendations": "False recommendations ({count})", "to-redact": "To redact ({count})" }, @@ -1284,13 +1281,10 @@ "content": { "comment": "Comment", "comment-placeholder": "Add remarks or mentions...", - "custom-rectangle": "", - "imported": "", "legal-basis": "", "options": { - "in-dossier": { + "in-document": { "description": "", - "extraOptionLabel": "", "label": "" }, "only-here": { diff --git a/libs/common-ui b/libs/common-ui index 835cb7820..34387d49d 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit 835cb7820e2100ff1125939f4c2766f06e9c09a6 +Subproject commit 34387d49d29ba6449c1311cc1c5434b540398660 diff --git a/libs/red-domain/src/lib/redaction-log/recategorization.request.ts b/libs/red-domain/src/lib/redaction-log/recategorization.request.ts index 5892fd7a4..815d28acf 100644 --- a/libs/red-domain/src/lib/redaction-log/recategorization.request.ts +++ b/libs/red-domain/src/lib/redaction-log/recategorization.request.ts @@ -6,3 +6,18 @@ export interface IRecategorizationRequest { readonly section?: string; readonly value?: string; } + +export interface IBulkRecategorizationRequest { + readonly value: string; + readonly type: string; + readonly legalBasis: string; + readonly section: string; + readonly originTypes: string[]; + readonly originLegalBases: string[]; + readonly rectangle: boolean; + readonly position?: { + readonly rectangle: number[]; + readonly pageNumber: number; + }; + readonly pageNumbers?: number[]; +}