Merge branch 'master' into VM/RED-7340
This commit is contained in:
commit
40d9eb15cf
@ -2,67 +2,74 @@
|
||||
<div [translate]="'reports-screen.title'" class="heading-xl"></div>
|
||||
|
||||
<div [translate]="'reports-screen.setup'" class="description"></div>
|
||||
<div *ngIf="!isDocumine" [translate]="'reports-screen.description'" class="description"></div>
|
||||
|
||||
<div *ngIf="!isDocumine && placeholders$ | async as placeholders" class="placeholders">
|
||||
<div [translate]="'reports-screen.table-header.placeholders'" class="all-caps-label"></div>
|
||||
<div [translate]="'reports-screen.table-header.description'" class="all-caps-label"></div>
|
||||
<ng-container *ngFor="let placeholder of placeholders">
|
||||
<div class="placeholder">{{ placeholder.placeholder }}</div>
|
||||
<div
|
||||
[innerHTML]="placeholder.descriptionTranslation | translate: { attribute: placeholder.attributeName }"
|
||||
class="description"
|
||||
></div>
|
||||
</ng-container>
|
||||
</div>
|
||||
@if (!isDocumine) {
|
||||
<div [translate]="'reports-screen.description'" class="description"></div>
|
||||
}
|
||||
@if (!isDocumine && placeholders$ | async; as placeholders) {
|
||||
<div class="placeholders">
|
||||
<div [translate]="'reports-screen.table-header.placeholders'" class="all-caps-label"></div>
|
||||
<div [translate]="'reports-screen.table-header.description'" class="all-caps-label"></div>
|
||||
@for (placeholder of placeholders; track placeholder.placeholder) {
|
||||
<div class="placeholder">{{ placeholder.placeholder }}</div>
|
||||
<div
|
||||
[innerHTML]="placeholder.descriptionTranslation | translate: { attribute: placeholder.attributeName }"
|
||||
class="description"
|
||||
></div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div *ngIf="availableTemplates$ | async as availableTemplates" class="right-container" iqserHasScrollbar>
|
||||
<div class="header">
|
||||
<div [translate]="'reports-screen.report-documents'" class="heading"></div>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="fileInput.click()"
|
||||
*allow="roles.reportTemplates.upload; if: currentUser.isAdmin"
|
||||
[tooltip]="'reports-screen.upload-document' | translate"
|
||||
[attr.help-mode-key]="'upload_report'"
|
||||
[buttonId]="'upload_report'"
|
||||
icon="iqser:upload"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
(click)="fileInput.click()"
|
||||
*allow="roles.reportTemplates.upload; if: currentUser.isAdmin && !availableTemplates?.length"
|
||||
[translate]="'reports-screen.upload-document'"
|
||||
class="template upload-button"
|
||||
></div>
|
||||
|
||||
<div *ngFor="let template of availableTemplates" class="template">
|
||||
<div class="name">
|
||||
{{ template.fileName }} {{ template.multiFileReport ? ('reports-screen.multi-file-report' | translate) : '' }}
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<iqser-circle-button
|
||||
(action)="download(template)"
|
||||
*allow="roles.reportTemplates.download"
|
||||
[buttonId]="(template.fileName | snakeCase) + '-download-button'"
|
||||
[iconSize]="12"
|
||||
[size]="18"
|
||||
icon="iqser:download"
|
||||
></iqser-circle-button>
|
||||
@if (availableTemplates$ | async; as availableTemplates) {
|
||||
<div class="right-container" iqserHasScrollbar>
|
||||
<div class="header">
|
||||
<div [translate]="'reports-screen.report-documents'" class="heading"></div>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="deleteTemplate(template)"
|
||||
*allow="roles.reportTemplates.delete; if: currentUser.isAdmin"
|
||||
[buttonId]="(template.fileName | snakeCase) + '-delete-button'"
|
||||
[iconSize]="12"
|
||||
[size]="18"
|
||||
icon="iqser:trash"
|
||||
(action)="fileInput.click()"
|
||||
*allow="roles.reportTemplates.upload; if: currentUser.isAdmin"
|
||||
[tooltip]="'reports-screen.upload-document' | translate"
|
||||
[attr.help-mode-key]="'upload_report'"
|
||||
[buttonId]="'upload_report'"
|
||||
icon="iqser:upload"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
(click)="fileInput.click()"
|
||||
*allow="roles.reportTemplates.upload; if: currentUser.isAdmin && !availableTemplates?.length"
|
||||
[translate]="'reports-screen.upload-document'"
|
||||
class="template upload-button"
|
||||
></div>
|
||||
|
||||
@for (template of availableTemplates; track template.templateId) {
|
||||
<div [id]="template.fileName | snakeCase" class="template">
|
||||
<div class="name">
|
||||
{{ template.fileName }}
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<iqser-circle-button
|
||||
(action)="download(template)"
|
||||
*allow="roles.reportTemplates.download"
|
||||
[buttonId]="(template.fileName | snakeCase) + '-download-button'"
|
||||
[iconSize]="12"
|
||||
[size]="18"
|
||||
icon="iqser:download"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="deleteTemplate(template)"
|
||||
*allow="roles.reportTemplates.delete; if: currentUser.isAdmin"
|
||||
[buttonId]="(template.fileName | snakeCase) + '-delete-button'"
|
||||
[iconSize]="12"
|
||||
[size]="18"
|
||||
icon="iqser:trash"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<input #fileInput (change)="uploadTemplate($event)" hidden type="file" />
|
||||
|
||||
@ -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<Placeholder[]>([]);
|
||||
readonly availableTemplates$ = new BehaviorSubject<IReportTemplate[]>([]);
|
||||
readonly currentUser = getCurrentUser<User>();
|
||||
readonly roles = Roles;
|
||||
readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
readonly #translateService = inject(TranslateService);
|
||||
private readonly _fileInput = viewChild<ElementRef>('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<void> {
|
||||
#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<void> {
|
||||
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<void> {
|
||||
async #uploadTemplateForm(file: File, multiFileReport: boolean): Promise<void> {
|
||||
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')
|
||||
|
||||
@ -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<EditAnnotationDialogComponent, EditRedactionData, EditRedactResult>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
class="dialog-header heading-l"
|
||||
></div>
|
||||
|
||||
<div class="dialog-content redaction" [class.fixed-height]="isRedacted && isImage">
|
||||
<div class="iqser-input-group" *ngIf="!isImage && redactedTexts.length && !allRectangles">
|
||||
<div [class.fixed-height]="isRedacted && isImage" class="dialog-content redaction">
|
||||
<div *ngIf="!isImage && redactedTexts.length && !allRectangles" class="iqser-input-group">
|
||||
<redaction-selected-annotations-table
|
||||
[columns]="tableColumns"
|
||||
[data]="tableData"
|
||||
@ -15,7 +15,13 @@
|
||||
></redaction-selected-annotations-table>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!isManualRedaction" class="iqser-input-group w-450" [class.required]="!form.controls.type.disabled">
|
||||
<iqser-details-radio
|
||||
*ngIf="!isImage && annotations.length === 1"
|
||||
[options]="options"
|
||||
formControlName="option"
|
||||
></iqser-details-radio>
|
||||
|
||||
<div *ngIf="!isManualRedaction" [class.required]="!form.controls.type.disabled" class="iqser-input-group w-450">
|
||||
<label [translate]="'edit-redaction.dialog.content.type'"></label>
|
||||
|
||||
<mat-form-field>
|
||||
|
||||
@ -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<EditRedactionDialogComponent, EditRedactionData, EditRedactResult>
|
||||
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<RedactOrHintOption>[] | 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<string>({ value: sameSection ? this.annotations[0].section : null, disabled: this.isImported }),
|
||||
option: new FormControl<LegalBasisOption>(null),
|
||||
option: new FormControl<DetailsRadioOption<EditRedactionOption>>(this.options[0]),
|
||||
value: new FormControl<string>(this.allRectangles ? this.annotations[0].value : null),
|
||||
});
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<IRecategorizationRequest> = 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<IRecategorizationRequest> | 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()),
|
||||
);
|
||||
|
||||
@ -9,6 +9,7 @@ import type {
|
||||
DictionaryActions,
|
||||
IAddRedactionRequest,
|
||||
IBulkLocalRemoveRequest,
|
||||
IBulkRecategorizationRequest,
|
||||
ILegalBasisChangeRequest,
|
||||
IManualAddResponse,
|
||||
IRecategorizationRequest,
|
||||
@ -71,15 +72,16 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
||||
}
|
||||
|
||||
recategorizeRedactions(
|
||||
body: List<IRecategorizationRequest>,
|
||||
body: List<IRecategorizationRequest> | 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<IManualAddResponse> {
|
||||
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<IManualAddResponse> {
|
||||
return this._post(bulkLocal ? body[0] : body, `${bulkPath}/add/${dossierId}/${fileId}`).pipe(this.#log('Add', body));
|
||||
}
|
||||
|
||||
recategorize(body: List<IRecategorizationRequest>, 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<IRemoveRedactionRequest> | 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<ILegalBasisChangeRequest>, 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<IManualAddResponse> {
|
||||
);
|
||||
}
|
||||
|
||||
#recategorize(
|
||||
body: List<IRecategorizationRequest> | 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<IRemoveRedactionRequest> | 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);
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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<RedactOrHintOption>[] => {
|
||||
const options: DetailsRadioOption<RedactOrHintOption>[] = [
|
||||
export const getEditRedactionOptions = (): DetailsRadioOption<EditRedactionOption>[] => {
|
||||
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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<div *ngIf="dictionaries && selectedDictionary" class="dictionary-content">
|
||||
<div class="dictionaries">
|
||||
<div
|
||||
(click)="selectDictionary(dictionary)"
|
||||
(click)="selectDictionary(dictionary, undefined, true)"
|
||||
*ngFor="let dictionary of dictionaries"
|
||||
[class.active]="dictionary.label === selectedDictionary.label"
|
||||
class="dictionary"
|
||||
@ -33,44 +33,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="entries">
|
||||
<div class="header-wrapper">
|
||||
<div class="header-left">
|
||||
<div class="heading">
|
||||
<div class="flex-align-items-center">
|
||||
{{ selectedDictionary?.label }}
|
||||
<iqser-circle-button
|
||||
(action)="openEditDictionaryModal()"
|
||||
*ngIf="selectedDictionary.dossierDictionaryOnly && selectedDictionary.hasDictionary"
|
||||
[size]="20"
|
||||
[tooltip]="'edit-dossier-dialog.dictionary.edit-button-tooltip' | translate"
|
||||
class="p-left-8"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||
<ng-container *ngIf="activeEntryType === entryTypes.ENTRY || selectedDictionary.hint">
|
||||
{{
|
||||
'edit-dossier-dialog.dictionary.entries'
|
||||
| translate: { length: entriesToDisplay.length, hint: selectedDictionary.hint }
|
||||
}}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="activeEntryType === entryTypes.FALSE_POSITIVE && !selectedDictionary.hint">
|
||||
{{
|
||||
'edit-dossier-dialog.dictionary.false-positive-entries' | translate: { length: entriesToDisplay.length }
|
||||
}}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="activeEntryType === entryTypes.FALSE_RECOMMENDATION && !selectedDictionary.hint">
|
||||
{{
|
||||
'edit-dossier-dialog.dictionary.false-recommendation-entries'
|
||||
| translate: { length: entriesToDisplay.length }
|
||||
}}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="heading flex-align-items-center">
|
||||
{{ selectedDictionary?.label }}
|
||||
<iqser-circle-button
|
||||
(action)="openEditDictionaryModal()"
|
||||
*ngIf="selectedDictionary.dossierDictionaryOnly && selectedDictionary.hasDictionary"
|
||||
[size]="20"
|
||||
[tooltip]="'edit-dossier-dialog.dictionary.edit-button-tooltip' | translate"
|
||||
class="p-left-8"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
|
||||
<redaction-dictionary-manager
|
||||
@ -85,14 +57,14 @@
|
||||
[withFloatingActions]="false"
|
||||
>
|
||||
<ng-container slot="typeSwitch">
|
||||
<div *ngIf="!selectedDictionary.hint" [class.read-only]="!canEdit" class="header-right flex">
|
||||
<div class="flex">
|
||||
<iqser-icon-button
|
||||
(click)="selectEntryType(entryTypes.ENTRY)"
|
||||
(click)="selectEntryType(entryTypes.ENTRY, true)"
|
||||
[active]="activeEntryType === entryTypes.ENTRY"
|
||||
[label]="'edit-dossier-dialog.dictionary.to-redact' | translate: { count: selectedDictionary.entries.length }"
|
||||
></iqser-icon-button>
|
||||
<iqser-icon-button
|
||||
(click)="selectEntryType(entryTypes.FALSE_POSITIVE)"
|
||||
(click)="selectEntryType(entryTypes.FALSE_POSITIVE, true)"
|
||||
[active]="activeEntryType === entryTypes.FALSE_POSITIVE"
|
||||
[label]="
|
||||
'edit-dossier-dialog.dictionary.false-positives'
|
||||
@ -100,7 +72,7 @@
|
||||
"
|
||||
></iqser-icon-button>
|
||||
<iqser-icon-button
|
||||
(click)="selectEntryType(entryTypes.FALSE_RECOMMENDATION)"
|
||||
(click)="selectEntryType(entryTypes.FALSE_RECOMMENDATION, true)"
|
||||
[active]="activeEntryType === entryTypes.FALSE_RECOMMENDATION"
|
||||
[label]="
|
||||
'edit-dossier-dialog.dictionary.false-recommendations'
|
||||
|
||||
@ -11,13 +11,14 @@
|
||||
|
||||
.dictionaries {
|
||||
border-right: 1px solid var(--iqser-separator);
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
width: 200px;
|
||||
@include common-mixins.scroll-bar;
|
||||
|
||||
.dictionary {
|
||||
height: 40px;
|
||||
padding: 15px;
|
||||
border: 1px solid var(--iqser-separator);
|
||||
border-bottom: 1px solid var(--iqser-separator);
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
@ -43,33 +44,7 @@
|
||||
.entries {
|
||||
flex-grow: 1;
|
||||
padding: 16px 0 16px 12px;
|
||||
|
||||
.header-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
|
||||
.iqser-input-group {
|
||||
margin-left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.read-only {
|
||||
padding-right: 100px;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
|
||||
> div {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { CircleButtonComponent, IconButtonComponent, IqserDialog, LoadingService } from '@iqser/common-ui';
|
||||
import {
|
||||
CircleButtonComponent,
|
||||
ConfirmationDialogService,
|
||||
ConfirmOptions,
|
||||
IconButtonComponent,
|
||||
IqserDialog,
|
||||
LoadingService,
|
||||
} from '@iqser/common-ui';
|
||||
import { List } from '@iqser/common-ui/lib/utils';
|
||||
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, Dossier } from '@red/domain';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
@ -42,6 +49,14 @@ export class EditDossierDictionaryComponent implements OnInit {
|
||||
readonly entryTypes = DictionaryEntryTypes;
|
||||
@ViewChild(DictionaryManagerComponent, { static: false }) private readonly _dictionaryManager: DictionaryManagerComponent;
|
||||
|
||||
constructor(
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _iqserDialog: IqserDialog,
|
||||
private readonly _confirmationDialogService: ConfirmationDialogService,
|
||||
) {}
|
||||
|
||||
get changed(): boolean {
|
||||
return this._dictionaryManager?.editor.hasChanges;
|
||||
}
|
||||
@ -54,13 +69,6 @@ export class EditDossierDictionaryComponent implements OnInit {
|
||||
return this._dictionaryManager?.editor.hasChanges;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _iqserDialog: IqserDialog,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this._loadingService.start();
|
||||
this.canEdit = this._permissionsService.canEditDossierDictionary(this.dossier);
|
||||
@ -95,16 +103,23 @@ export class EditDossierDictionaryComponent implements OnInit {
|
||||
this._dictionaryManager.revert();
|
||||
}
|
||||
|
||||
selectDictionary(dictionary: Dictionary, entryType?: DictionaryEntryType) {
|
||||
async selectDictionary(dictionary: Dictionary, entryType?: DictionaryEntryType, checkForChanges = false) {
|
||||
if (checkForChanges && !(await this._checkForChanges()).continue) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedDictionary = dictionary;
|
||||
this.selectEntryType(entryType);
|
||||
await this.selectEntryType(entryType);
|
||||
}
|
||||
|
||||
selectEntryType(selectedEntryType: DictionaryEntryType) {
|
||||
this.activeEntryType = selectedEntryType ?? this.activeEntryType;
|
||||
const entryType = this.selectedDictionary.hint ? DictionaryEntryTypes.ENTRY : this.activeEntryType;
|
||||
async selectEntryType(selectedEntryType: DictionaryEntryType, checkForChanges = false) {
|
||||
if (checkForChanges && !(await this._checkForChanges()).continue) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (entryType) {
|
||||
this.activeEntryType = selectedEntryType ?? this.activeEntryType;
|
||||
|
||||
switch (this.activeEntryType) {
|
||||
case DictionaryEntryTypes.ENTRY: {
|
||||
this.entriesToDisplay = this.selectedDictionary.entries;
|
||||
break;
|
||||
@ -141,13 +156,32 @@ export class EditDossierDictionaryComponent implements OnInit {
|
||||
await this.#retrieveDictionaries();
|
||||
}
|
||||
|
||||
private async _checkForChanges(): Promise<{ continue: boolean }> {
|
||||
if (this.changed) {
|
||||
const dialogRef = this._confirmationDialogService.open({ disableConfirm: !this.valid });
|
||||
const result = await firstValueFrom(dialogRef.afterClosed());
|
||||
if (result === ConfirmOptions.CONFIRM) {
|
||||
this._loadingService.start();
|
||||
const { success } = await this.save();
|
||||
this._loadingService.stop();
|
||||
if (!success) {
|
||||
return { continue: false };
|
||||
}
|
||||
} else if (!result) {
|
||||
return { continue: false };
|
||||
}
|
||||
}
|
||||
|
||||
return { continue: true };
|
||||
}
|
||||
|
||||
async #updateDossierDictionary() {
|
||||
await this.#retrieveDictionaries();
|
||||
let dictionaryToSelect = this.dictionaries[0];
|
||||
if (this.selectedDictionary) {
|
||||
dictionaryToSelect = this.dictionaries.find(d => d.type === this.selectedDictionary.type);
|
||||
}
|
||||
this.selectDictionary(dictionaryToSelect, this.activeEntryType);
|
||||
await this.selectDictionary(dictionaryToSelect, this.activeEntryType);
|
||||
}
|
||||
|
||||
async #retrieveDictionaries() {
|
||||
|
||||
@ -13,7 +13,8 @@
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="editor.openFindPanel()"
|
||||
(action)="editor.toggleFindPanel()"
|
||||
[attr.aria-expanded]="_isSearchOpen()"
|
||||
[matTooltip]="'dictionary-overview.search' | translate"
|
||||
class="ml-8"
|
||||
icon="iqser:search"
|
||||
@ -92,6 +93,7 @@
|
||||
|
||||
<div class="editor-container">
|
||||
<redaction-editor
|
||||
[(isSearchOpen)]="_isSearchOpen"
|
||||
[canEdit]="canEdit"
|
||||
[diffEditorText]="diffEditorText"
|
||||
[initialEntries]="initialEntries"
|
||||
|
||||
@ -1,4 +1,15 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
signal,
|
||||
SimpleChanges,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, LoadingService } from '@iqser/common-ui';
|
||||
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, DictionaryType, Dossier, DossierTemplate, IDictionary } from '@red/domain';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
@ -77,6 +88,7 @@ export class DictionaryManagerComponent implements OnChanges, OnInit {
|
||||
selectDossierTemplate = { name: _('dictionary-overview.compare.select-dossier-template') } as DossierTemplate;
|
||||
compare = false;
|
||||
dictionaries: List<Dictionary> = this.#dictionaries;
|
||||
protected readonly _isSearchOpen = signal(false);
|
||||
protected initialDossierTemplateId: string;
|
||||
readonly #currentTab = window.location.href.split('/').pop();
|
||||
#dossierTemplate = this.dossierTemplatesService.all[0];
|
||||
|
||||
@ -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<List, EditorComponent>('revert') initialEntries: List;
|
||||
@Input() canEdit = false;
|
||||
readonly isSearchOpen = model.required<boolean>();
|
||||
/**
|
||||
* 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<void> {
|
||||
async toggleFindPanel(): Promise<void> {
|
||||
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[] = [];
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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.",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 835cb7820e2100ff1125939f4c2766f06e9c09a6
|
||||
Subproject commit 34387d49d29ba6449c1311cc1c5434b540398660
|
||||
@ -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[];
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user