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.title'" class="heading-xl"></div>
|
||||||
|
|
||||||
<div [translate]="'reports-screen.setup'" class="description"></div>
|
<div [translate]="'reports-screen.setup'" class="description"></div>
|
||||||
<div *ngIf="!isDocumine" [translate]="'reports-screen.description'" class="description"></div>
|
@if (!isDocumine) {
|
||||||
|
<div [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>
|
@if (!isDocumine && placeholders$ | async; as placeholders) {
|
||||||
<div [translate]="'reports-screen.table-header.description'" class="all-caps-label"></div>
|
<div class="placeholders">
|
||||||
<ng-container *ngFor="let placeholder of placeholders">
|
<div [translate]="'reports-screen.table-header.placeholders'" class="all-caps-label"></div>
|
||||||
<div class="placeholder">{{ placeholder.placeholder }}</div>
|
<div [translate]="'reports-screen.table-header.description'" class="all-caps-label"></div>
|
||||||
<div
|
@for (placeholder of placeholders; track placeholder.placeholder) {
|
||||||
[innerHTML]="placeholder.descriptionTranslation | translate: { attribute: placeholder.attributeName }"
|
<div class="placeholder">{{ placeholder.placeholder }}</div>
|
||||||
class="description"
|
<div
|
||||||
></div>
|
[innerHTML]="placeholder.descriptionTranslation | translate: { attribute: placeholder.attributeName }"
|
||||||
</ng-container>
|
class="description"
|
||||||
</div>
|
></div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="availableTemplates$ | async as availableTemplates" class="right-container" iqserHasScrollbar>
|
@if (availableTemplates$ | async; as availableTemplates) {
|
||||||
<div class="header">
|
<div class="right-container" iqserHasScrollbar>
|
||||||
<div [translate]="'reports-screen.report-documents'" class="heading"></div>
|
<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>
|
|
||||||
|
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="deleteTemplate(template)"
|
(action)="fileInput.click()"
|
||||||
*allow="roles.reportTemplates.delete; if: currentUser.isAdmin"
|
*allow="roles.reportTemplates.upload; if: currentUser.isAdmin"
|
||||||
[buttonId]="(template.fileName | snakeCase) + '-delete-button'"
|
[tooltip]="'reports-screen.upload-document' | translate"
|
||||||
[iconSize]="12"
|
[attr.help-mode-key]="'upload_report'"
|
||||||
[size]="18"
|
[buttonId]="'upload_report'"
|
||||||
icon="iqser:trash"
|
icon="iqser:upload"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
}
|
||||||
|
|
||||||
<input #fileInput (change)="uploadTemplate($event)" hidden type="file" />
|
<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 { DOSSIER_TEMPLATE_ID, IPlaceholdersResponse, IReportTemplate, User } from '@red/domain';
|
||||||
import { download } from '@utils/file-download-utils';
|
import { download } from '@utils/file-download-utils';
|
||||||
import {
|
import {
|
||||||
@ -23,8 +23,8 @@ import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
|||||||
import { Roles } from '@users/roles';
|
import { Roles } from '@users/roles';
|
||||||
import { getCurrentUser } from '@iqser/common-ui/lib/users';
|
import { getCurrentUser } from '@iqser/common-ui/lib/users';
|
||||||
import { getParam } from '@iqser/common-ui/lib/utils';
|
import { getParam } from '@iqser/common-ui/lib/utils';
|
||||||
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
import { AsyncPipe } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
import { SnakeCasePipe } from '@common-ui/pipes/snake-case.pipe';
|
import { SnakeCasePipe } from '@common-ui/pipes/snake-case.pipe';
|
||||||
|
|
||||||
interface Placeholder {
|
interface Placeholder {
|
||||||
@ -42,15 +42,16 @@ const placeholderTypes: PlaceholderType[] = ['generalPlaceholders', 'fileAttribu
|
|||||||
styleUrls: ['./reports-screen.component.scss'],
|
styleUrls: ['./reports-screen.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [HasScrollbarDirective, NgIf, NgForOf, AsyncPipe, TranslateModule, CircleButtonComponent, IqserAllowDirective, SnakeCasePipe],
|
imports: [HasScrollbarDirective, AsyncPipe, TranslateModule, CircleButtonComponent, IqserAllowDirective, SnakeCasePipe],
|
||||||
})
|
})
|
||||||
export default class ReportsScreenComponent implements OnInit {
|
export default class ReportsScreenComponent implements OnInit {
|
||||||
@ViewChild('fileInput') private readonly _fileInput: ElementRef;
|
|
||||||
readonly placeholders$ = new BehaviorSubject<Placeholder[]>([]);
|
readonly placeholders$ = new BehaviorSubject<Placeholder[]>([]);
|
||||||
readonly availableTemplates$ = new BehaviorSubject<IReportTemplate[]>([]);
|
readonly availableTemplates$ = new BehaviorSubject<IReportTemplate[]>([]);
|
||||||
readonly currentUser = getCurrentUser<User>();
|
readonly currentUser = getCurrentUser<User>();
|
||||||
readonly roles = Roles;
|
readonly roles = Roles;
|
||||||
readonly isDocumine = getConfig().IS_DOCUMINE;
|
readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||||
|
readonly #translateService = inject(TranslateService);
|
||||||
|
private readonly _fileInput = viewChild<ElementRef>('fileInput');
|
||||||
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
|
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -63,8 +64,8 @@ export default class ReportsScreenComponent implements OnInit {
|
|||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
await this._loadReportTemplates();
|
await this.#loadReportTemplates();
|
||||||
await this._loadPlaceholders();
|
await this.#loadPlaceholders();
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,14 +85,14 @@ export default class ReportsScreenComponent implements OnInit {
|
|||||||
|
|
||||||
deleteTemplate(template: IReportTemplate) {
|
deleteTemplate(template: IReportTemplate) {
|
||||||
this._dialogService.openDialog('confirm', null, () => {
|
this._dialogService.openDialog('confirm', null, () => {
|
||||||
this._loadingService.loadWhile(this._deleteTemplate(template));
|
this._loadingService.loadWhile(this.#deleteTemplate(template));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadTemplate($event) {
|
uploadTemplate($event) {
|
||||||
const file: File = $event.target.files[0];
|
const file: File = $event.target.files[0];
|
||||||
|
|
||||||
if (!this._isValidFile(file)) {
|
if (!this.#isValidFile(file)) {
|
||||||
this._toaster.error(_('reports-screen.invalid-upload'));
|
this._toaster.error(_('reports-screen.invalid-upload'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -115,27 +116,31 @@ export default class ReportsScreenComponent implements OnInit {
|
|||||||
template => template.fileName === file.name && template.multiFileReport === multiFileReport,
|
template => template.fileName === file.name && template.multiFileReport === multiFileReport,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
await this._openOverwriteConfirmationDialog(file, multiFileReport);
|
await this.#openOverwriteConfirmationDialog(file, multiFileReport);
|
||||||
} else {
|
} 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();
|
return removeBraces(placeholder).split('.').pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getPlaceholderDescriptionTranslation(type: PlaceholderType, placeholder: string): string {
|
#getPlaceholderDescriptionTranslation(type: PlaceholderType, placeholder: string): string {
|
||||||
return type === 'generalPlaceholders'
|
return type === 'generalPlaceholders'
|
||||||
? generalPlaceholdersDescriptionsTranslations[removeBraces(placeholder)]
|
? generalPlaceholdersDescriptionsTranslations[removeBraces(placeholder)]
|
||||||
: placeholdersDescriptionsTranslations[type];
|
: 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 = {
|
const data: IConfirmationDialogData = {
|
||||||
title: _('confirmation-dialog.report-template-same-name.title'),
|
title: _('confirmation-dialog.report-template-same-name.title'),
|
||||||
question: _('confirmation-dialog.report-template-same-name.question'),
|
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 => {
|
this._dialogService.openDialog('confirm', data, null, async result => {
|
||||||
if (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();
|
this._loadingService.start();
|
||||||
await firstValueFrom(this._reportTemplateService.uploadTemplateForm(this.#dossierTemplateId, multiFileReport, file));
|
await firstValueFrom(this._reportTemplateService.uploadTemplateForm(this.#dossierTemplateId, multiFileReport, file));
|
||||||
await this._loadReportTemplates();
|
await this.#loadReportTemplates();
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _deleteTemplate(template: IReportTemplate) {
|
async #deleteTemplate(template: IReportTemplate) {
|
||||||
await firstValueFrom(this._reportTemplateService.delete(template.dossierTemplateId, template.templateId));
|
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);
|
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(
|
const placeholdersResponse: IPlaceholdersResponse = await firstValueFrom(
|
||||||
this._reportTemplateService.getAvailablePlaceholders(this.#dossierTemplateId),
|
this._reportTemplateService.getAvailablePlaceholders(this.#dossierTemplateId),
|
||||||
);
|
);
|
||||||
@ -178,25 +188,25 @@ export default class ReportsScreenComponent implements OnInit {
|
|||||||
placeholderTypes.flatMap(type =>
|
placeholderTypes.flatMap(type =>
|
||||||
placeholdersResponse[type].map(placeholder => ({
|
placeholdersResponse[type].map(placeholder => ({
|
||||||
placeholder,
|
placeholder,
|
||||||
descriptionTranslation: this._getPlaceholderDescriptionTranslation(type, placeholder),
|
descriptionTranslation: this.#getPlaceholderDescriptionTranslation(type, placeholder),
|
||||||
attributeName: this._getAttributeName(placeholder),
|
attributeName: this.#getAttributeName(placeholder),
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isValidFile(file: File): boolean {
|
#isValidFile(file: File): boolean {
|
||||||
return this._isExcelFile(file) || this._isWordFile(file);
|
return this.#isExcelFile(file) || this.#isWordFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isExcelFile(file: File): boolean {
|
#isExcelFile(file: File): boolean {
|
||||||
return (
|
return (
|
||||||
file.type?.toLowerCase() === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
file.type?.toLowerCase() === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
||||||
file.name.toLowerCase().endsWith('.xlsx')
|
file.name.toLowerCase().endsWith('.xlsx')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isWordFile(file: File): boolean {
|
#isWordFile(file: File): boolean {
|
||||||
return (
|
return (
|
||||||
file.type?.toLowerCase() === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
|
file.type?.toLowerCase() === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
|
||||||
file.name.toLowerCase().endsWith('.docx')
|
file.name.toLowerCase().endsWith('.docx')
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
IconButtonTypes,
|
IconButtonTypes,
|
||||||
IqserDialogComponent,
|
IqserDialogComponent,
|
||||||
} from '@iqser/common-ui';
|
} 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 { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||||
import { Roles } from '@users/roles';
|
import { Roles } from '@users/roles';
|
||||||
@ -47,12 +47,12 @@ export class EditAnnotationDialogComponent
|
|||||||
extends IqserDialogComponent<EditAnnotationDialogComponent, EditRedactionData, EditRedactResult>
|
extends IqserDialogComponent<EditAnnotationDialogComponent, EditRedactionData, EditRedactResult>
|
||||||
implements OnInit
|
implements OnInit
|
||||||
{
|
{
|
||||||
readonly #dossier: Dossier;
|
|
||||||
readonly roles = Roles;
|
readonly roles = Roles;
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
readonly redactedTexts: string[];
|
readonly redactedTexts: string[];
|
||||||
dictionaries: Dictionary[] = [];
|
dictionaries: Dictionary[] = [];
|
||||||
form: UntypedFormGroup;
|
form: UntypedFormGroup;
|
||||||
|
readonly #dossier: Dossier;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _activeDossiersService: ActiveDossiersService,
|
private readonly _activeDossiersService: ActiveDossiersService,
|
||||||
@ -60,7 +60,7 @@ export class EditAnnotationDialogComponent
|
|||||||
private readonly _formBuilder: FormBuilder,
|
private readonly _formBuilder: FormBuilder,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.#dossier = _activeDossiersService.find(this.data.dossierId);
|
this.#dossier = this._activeDossiersService.find(this.data.dossierId);
|
||||||
const annotations = this.data.annotations;
|
const annotations = this.data.annotations;
|
||||||
this.redactedTexts = annotations.map(annotation => annotation.value);
|
this.redactedTexts = annotations.map(annotation => annotation.value);
|
||||||
this.form = this.#getForm();
|
this.form = this.#getForm();
|
||||||
@ -83,10 +83,6 @@ export class EditAnnotationDialogComponent
|
|||||||
this.#setTypes();
|
this.#setTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
reasonChanged() {
|
|
||||||
this.form.patchValue({ reason: this.dictionaries.find(d => d.type === SuperTypes.ManualRedaction) });
|
|
||||||
}
|
|
||||||
|
|
||||||
save(): void {
|
save(): void {
|
||||||
const value = this.form.value;
|
const value = this.form.value;
|
||||||
this.dialogRef.close({
|
this.dialogRef.close({
|
||||||
@ -106,8 +102,4 @@ export class EditAnnotationDialogComponent
|
|||||||
type: [sameType ? this.data.annotations[0].type : null],
|
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"
|
class="dialog-header heading-l"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div class="dialog-content redaction" [class.fixed-height]="isRedacted && isImage">
|
<div [class.fixed-height]="isRedacted && isImage" class="dialog-content redaction">
|
||||||
<div class="iqser-input-group" *ngIf="!isImage && redactedTexts.length && !allRectangles">
|
<div *ngIf="!isImage && redactedTexts.length && !allRectangles" class="iqser-input-group">
|
||||||
<redaction-selected-annotations-table
|
<redaction-selected-annotations-table
|
||||||
[columns]="tableColumns"
|
[columns]="tableColumns"
|
||||||
[data]="tableData"
|
[data]="tableData"
|
||||||
@ -15,7 +15,13 @@
|
|||||||
></redaction-selected-annotations-table>
|
></redaction-selected-annotations-table>
|
||||||
</div>
|
</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>
|
<label [translate]="'edit-redaction.dialog.content.type'"></label>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { MatDialogClose } from '@angular/material/dialog';
|
|||||||
import { MatFormField } from '@angular/material/form-field';
|
import { MatFormField } from '@angular/material/form-field';
|
||||||
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
|
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
|
|
||||||
import {
|
import {
|
||||||
CircleButtonComponent,
|
CircleButtonComponent,
|
||||||
HasScrollbarDirective,
|
HasScrollbarDirective,
|
||||||
@ -26,10 +25,11 @@ import {
|
|||||||
SelectedAnnotationsTableComponent,
|
SelectedAnnotationsTableComponent,
|
||||||
ValueColumn,
|
ValueColumn,
|
||||||
} from '../../components/selected-annotations-table/selected-annotations-table.component';
|
} from '../../components/selected-annotations-table/selected-annotations-table.component';
|
||||||
import { DialogHelpModeKeys } from '../../utils/constants';
|
|
||||||
import { getEditRedactionOptions } from '../../utils/dialog-options';
|
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 { 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 {
|
interface TypeSelectOptions {
|
||||||
type: string;
|
type: string;
|
||||||
@ -58,18 +58,16 @@ interface TypeSelectOptions {
|
|||||||
HelpButtonComponent,
|
HelpButtonComponent,
|
||||||
MatDialogClose,
|
MatDialogClose,
|
||||||
HasScrollbarDirective,
|
HasScrollbarDirective,
|
||||||
|
DetailsRadioComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class EditRedactionDialogComponent
|
export class EditRedactionDialogComponent
|
||||||
extends IqserDialogComponent<EditRedactionDialogComponent, EditRedactionData, EditRedactResult>
|
extends IqserDialogComponent<EditRedactionDialogComponent, EditRedactionData, EditRedactResult>
|
||||||
implements OnInit
|
implements OnInit
|
||||||
{
|
{
|
||||||
readonly #dossier = inject(ActiveDossiersService).find(this.data.dossierId);
|
readonly ignoredKeys = ['option', 'comment'];
|
||||||
readonly #applyToAllDossiers = this.data.applyToAllDossiers;
|
|
||||||
protected readonly roles = Roles;
|
|
||||||
readonly annotations = this.data.annotations;
|
readonly annotations = this.data.annotations;
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
readonly isModifyDictionary = this.annotations.every(annotation => annotation.isModifyDictionary);
|
|
||||||
readonly isImage = this.annotations.reduce((acc, next) => acc && next.isImage, true);
|
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 redactedTexts = !this.isImage ? this.annotations.map(annotation => annotation.value).filter(value => !!value) : null;
|
||||||
readonly isManualRedaction = this.annotations.some(annotation => annotation.type === SuperTypes.ManualRedaction);
|
readonly isManualRedaction = this.annotations.some(annotation => annotation.type === SuperTypes.ManualRedaction);
|
||||||
@ -82,13 +80,15 @@ export class EditRedactionDialogComponent
|
|||||||
{ label: redaction.value, bold: true },
|
{ label: redaction.value, bold: true },
|
||||||
{ label: redaction.typeLabel },
|
{ label: redaction.typeLabel },
|
||||||
]);
|
]);
|
||||||
options: DetailsRadioOption<RedactOrHintOption>[] | undefined;
|
options = getEditRedactionOptions();
|
||||||
legalOptions: LegalBasisOption[] = [];
|
legalOptions: LegalBasisOption[] = [];
|
||||||
dictionaries: Dictionary[] = [];
|
dictionaries: Dictionary[] = [];
|
||||||
typeSelectOptions: TypeSelectOptions[] = [];
|
typeSelectOptions: TypeSelectOptions[] = [];
|
||||||
readonly form = this.#getForm();
|
readonly form = this.#getForm();
|
||||||
hasTypeChanged = false;
|
hasTypeChanged = false;
|
||||||
initialReasonDisabled = this.someSkipped;
|
initialReasonDisabled = this.someSkipped;
|
||||||
|
protected readonly roles = Roles;
|
||||||
|
readonly #dossier = inject(ActiveDossiersService).find(this.data.dossierId);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _justificationsService: JustificationsService,
|
private readonly _justificationsService: JustificationsService,
|
||||||
@ -144,16 +144,6 @@ export class EditRedactionDialogComponent
|
|||||||
return this.annotations.length > 1;
|
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() {
|
get sameType() {
|
||||||
return this.annotations.every(annotation => annotation.type === this.annotations[0].type);
|
return this.annotations.every(annotation => annotation.type === this.annotations[0].type);
|
||||||
}
|
}
|
||||||
@ -179,7 +169,6 @@ export class EditRedactionDialogComponent
|
|||||||
|
|
||||||
typeChanged() {
|
typeChanged() {
|
||||||
const selectedDictionaryType = this.form.controls.type.value;
|
const selectedDictionaryType = this.form.controls.type.value;
|
||||||
this.#setOptions(selectedDictionaryType);
|
|
||||||
|
|
||||||
const initialReason = this.form.get('type').value === this.initialFormValue.type && !this.initialReasonDisabled;
|
const initialReason = this.form.get('type').value === this.initialFormValue.type && !this.initialReasonDisabled;
|
||||||
if (this.redactBasedTypes.includes(selectedDictionaryType) || initialReason) {
|
if (this.redactBasedTypes.includes(selectedDictionaryType) || initialReason) {
|
||||||
@ -193,7 +182,7 @@ export class EditRedactionDialogComponent
|
|||||||
} else {
|
} else {
|
||||||
this.form.controls.reason.disable();
|
this.form.controls.reason.disable();
|
||||||
}
|
}
|
||||||
this.form.patchValue({ reason: null, option: null });
|
this.form.patchValue({ reason: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
@ -206,6 +195,7 @@ export class EditRedactionDialogComponent
|
|||||||
comment: value.comment,
|
comment: value.comment,
|
||||||
type: value.type,
|
type: value.type,
|
||||||
value: this.allRectangles ? value.value : null,
|
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() {
|
#getForm() {
|
||||||
const sameSection = this.annotations.every(annotation => annotation.section === this.annotations[0].section);
|
const sameSection = this.annotations.every(annotation => annotation.section === this.annotations[0].section);
|
||||||
return new FormGroup({
|
return new FormGroup({
|
||||||
@ -256,7 +230,7 @@ export class EditRedactionDialogComponent
|
|||||||
disabled: this.isImported,
|
disabled: this.isImported,
|
||||||
}),
|
}),
|
||||||
section: new FormControl<string>({ value: sameSection ? this.annotations[0].section : null, 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),
|
value: new FormControl<string>(this.allRectangles ? this.annotations[0].value : null),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import {
|
|||||||
SelectedAnnotationsTableComponent,
|
SelectedAnnotationsTableComponent,
|
||||||
ValueColumn,
|
ValueColumn,
|
||||||
} from '../../components/selected-annotations-table/selected-annotations-table.component';
|
} from '../../components/selected-annotations-table/selected-annotations-table.component';
|
||||||
import { DialogHelpModeKeys } from '../../utils/constants';
|
|
||||||
import { getRemoveRedactionOptions } from '../../utils/dialog-options';
|
import { getRemoveRedactionOptions } from '../../utils/dialog-options';
|
||||||
import {
|
import {
|
||||||
RemoveRedactionData,
|
RemoveRedactionData,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
DictionaryEntryTypes,
|
DictionaryEntryTypes,
|
||||||
EarmarkOperation,
|
EarmarkOperation,
|
||||||
type IBulkLocalRemoveRequest,
|
type IBulkLocalRemoveRequest,
|
||||||
|
IBulkRecategorizationRequest,
|
||||||
ILegalBasisChangeRequest,
|
ILegalBasisChangeRequest,
|
||||||
IRecategorizationRequest,
|
IRecategorizationRequest,
|
||||||
IRectangle,
|
IRectangle,
|
||||||
@ -34,6 +35,7 @@ import {
|
|||||||
EditRedactionData,
|
EditRedactionData,
|
||||||
EditRedactResult,
|
EditRedactResult,
|
||||||
ForceAnnotationOptions,
|
ForceAnnotationOptions,
|
||||||
|
RedactOrHintOptions,
|
||||||
RemoveRedactionData,
|
RemoveRedactionData,
|
||||||
RemoveRedactionOptions,
|
RemoveRedactionOptions,
|
||||||
RemoveRedactionPermissions,
|
RemoveRedactionPermissions,
|
||||||
@ -108,13 +110,11 @@ export class AnnotationActionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async editRedaction(annotations: AnnotationWrapper[]) {
|
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 includeUnprocessed = annotations.every(annotation => this.#includeUnprocessed(annotation, true));
|
||||||
const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
|
|
||||||
const data = {
|
const data = {
|
||||||
annotations,
|
annotations,
|
||||||
dossierId,
|
dossierId,
|
||||||
applyToAllDossiers: dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await this.#getEditRedactionDialog(data).result();
|
const result = await this.#getEditRedactionDialog(data).result();
|
||||||
@ -122,22 +122,38 @@ export class AnnotationActionsService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recategorizeBody: List<IRecategorizationRequest> = annotations.map(annotation => {
|
let recategorizeBody: List<IRecategorizationRequest> | IBulkRecategorizationRequest;
|
||||||
const body: IRecategorizationRequest = {
|
|
||||||
annotationId: annotation.id,
|
if (result.option === RedactOrHintOptions.ONLY_HERE) {
|
||||||
type: result.type ?? annotation.type,
|
recategorizeBody = annotations.map(annotation => {
|
||||||
comment: result.comment,
|
const body: IRecategorizationRequest = {
|
||||||
};
|
annotationId: annotation.id,
|
||||||
if (!this.#isDocumine) {
|
type: result.type ?? annotation.type,
|
||||||
return {
|
comment: result.comment,
|
||||||
...body,
|
|
||||||
legalBasis: result.legalBasis,
|
|
||||||
section: result.section ?? annotation.section,
|
|
||||||
value: result.value ?? annotation.value,
|
|
||||||
};
|
};
|
||||||
}
|
if (!this.#isDocumine) {
|
||||||
return body;
|
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(
|
await this.#processObsAndEmit(
|
||||||
this._manualRedactionService
|
this._manualRedactionService
|
||||||
@ -147,6 +163,7 @@ export class AnnotationActionsService {
|
|||||||
fileId,
|
fileId,
|
||||||
this.#getChangedFields(annotations, result),
|
this.#getChangedFields(annotations, result),
|
||||||
includeUnprocessed,
|
includeUnprocessed,
|
||||||
|
result.option === RedactOrHintOptions.IN_DOCUMENT,
|
||||||
)
|
)
|
||||||
.pipe(log()),
|
.pipe(log()),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import type {
|
|||||||
DictionaryActions,
|
DictionaryActions,
|
||||||
IAddRedactionRequest,
|
IAddRedactionRequest,
|
||||||
IBulkLocalRemoveRequest,
|
IBulkLocalRemoveRequest,
|
||||||
|
IBulkRecategorizationRequest,
|
||||||
ILegalBasisChangeRequest,
|
ILegalBasisChangeRequest,
|
||||||
IManualAddResponse,
|
IManualAddResponse,
|
||||||
IRecategorizationRequest,
|
IRecategorizationRequest,
|
||||||
@ -71,15 +72,16 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
recategorizeRedactions(
|
recategorizeRedactions(
|
||||||
body: List<IRecategorizationRequest>,
|
body: List<IRecategorizationRequest> | IBulkRecategorizationRequest,
|
||||||
dossierId: string,
|
dossierId: string,
|
||||||
fileId: string,
|
fileId: string,
|
||||||
successMessageParameters?: {
|
successMessageParameters?: {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
},
|
},
|
||||||
includeUnprocessed = false,
|
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),
|
this.#showToast('recategorize-annotation', false, successMessageParameters),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -118,7 +120,7 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
|||||||
includeUnprocessed = false,
|
includeUnprocessed = false,
|
||||||
bulkLocal = 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),
|
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));
|
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) {
|
undo(annotationIds: List, dossierId: string, fileId: string) {
|
||||||
const url = `${this._defaultModelPath}/bulk/undo/${dossierId}/${fileId}`;
|
const url = `${this._defaultModelPath}/bulk/undo/${dossierId}/${fileId}`;
|
||||||
return super.delete(annotationIds, url).pipe(this.#log('Undo', annotationIds));
|
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) {
|
forceRedaction(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string) {
|
||||||
return this._post(body, `${this.#bulkRedaction}/force/${dossierId}/${fileId}`).pipe(this.#log('Force redaction', body));
|
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) {
|
#log(action: string, body: unknown) {
|
||||||
return tap(response => {
|
return tap(response => {
|
||||||
this._logger.info(`[MANUAL-REDACTIONS] ${action} `, body, response);
|
this._logger.info(`[MANUAL-REDACTIONS] ${action} `, body, response);
|
||||||
|
|||||||
@ -19,16 +19,6 @@ export const ActionsHelpModeKeys = {
|
|||||||
'hint-image': 'hint',
|
'hint-image': 'hint',
|
||||||
} as const;
|
} 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 ALL_HOTKEYS: List = ['Escape', 'F', 'f', 'ArrowUp', 'ArrowDown', 'H', 'h'] as const;
|
||||||
|
|
||||||
export const HeaderElements = {
|
export const HeaderElements = {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { removeAnnotationTranslations } from '@translations/remove-annotation-tr
|
|||||||
import { removeRedactionTranslations } from '@translations/remove-redaction-translations';
|
import { removeRedactionTranslations } from '@translations/remove-redaction-translations';
|
||||||
import { resizeRedactionTranslations } from '@translations/resize-redaction-translations';
|
import { resizeRedactionTranslations } from '@translations/resize-redaction-translations';
|
||||||
import {
|
import {
|
||||||
|
EditRedactionOption,
|
||||||
ForceAnnotationOption,
|
ForceAnnotationOption,
|
||||||
RectangleRedactOption,
|
RectangleRedactOption,
|
||||||
RectangleRedactOptions,
|
RectangleRedactOptions,
|
||||||
@ -25,36 +26,21 @@ const DOCUMENT_ICON = 'iqser:document';
|
|||||||
const FOLDER_ICON = 'red:folder';
|
const FOLDER_ICON = 'red:folder';
|
||||||
const REMOVE_FROM_DICT_ICON = 'red:remove-from-dict';
|
const REMOVE_FROM_DICT_ICON = 'red:remove-from-dict';
|
||||||
|
|
||||||
export const getEditRedactionOptions = (
|
export const getEditRedactionOptions = (): DetailsRadioOption<EditRedactionOption>[] => {
|
||||||
dossierName: string,
|
return [
|
||||||
applyToAllDossiers: boolean,
|
|
||||||
dossierDictionaryOnly: boolean,
|
|
||||||
isModifyDictionary: boolean,
|
|
||||||
): DetailsRadioOption<RedactOrHintOption>[] => {
|
|
||||||
const options: DetailsRadioOption<RedactOrHintOption>[] = [
|
|
||||||
{
|
{
|
||||||
label: editRedactionTranslations.onlyHere.label,
|
label: editRedactionTranslations.onlyHere.label,
|
||||||
description: editRedactionTranslations.onlyHere.description,
|
description: editRedactionTranslations.onlyHere.description,
|
||||||
icon: PIN_ICON,
|
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 = (
|
export const getRedactOrHintOptions = (
|
||||||
@ -74,7 +60,7 @@ export const getRedactOrHintOptions = (
|
|||||||
label: translations.onlyHere.label,
|
label: translations.onlyHere.label,
|
||||||
description: translations.onlyHere.description,
|
description: translations.onlyHere.description,
|
||||||
icon: PIN_ICON,
|
icon: PIN_ICON,
|
||||||
value: ResizeOptions.ONLY_HERE,
|
value: RedactOrHintOptions.ONLY_HERE,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +73,7 @@ export const getRedactOrHintOptions = (
|
|||||||
label: redactTextTranslations.inDocument.label,
|
label: redactTextTranslations.inDocument.label,
|
||||||
description: redactTextTranslations.inDocument.description,
|
description: redactTextTranslations.inDocument.description,
|
||||||
icon: DOCUMENT_ICON,
|
icon: DOCUMENT_ICON,
|
||||||
value: ResizeOptions.IN_DOCUMENT,
|
value: RedactOrHintOptions.IN_DOCUMENT,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +82,7 @@ export const getRedactOrHintOptions = (
|
|||||||
description: translations.inDossier.description,
|
description: translations.inDossier.description,
|
||||||
descriptionParams: { dossierName: dossier.dossierName },
|
descriptionParams: { dossierName: dossier.dossierName },
|
||||||
icon: FOLDER_ICON,
|
icon: FOLDER_ICON,
|
||||||
value: ResizeOptions.IN_DOSSIER,
|
value: RedactOrHintOptions.IN_DOSSIER,
|
||||||
disabled: isPageExcluded,
|
disabled: isPageExcluded,
|
||||||
additionalCheck: {
|
additionalCheck: {
|
||||||
label: translations.inDossier.extraOptionLabel,
|
label: translations.inDossier.extraOptionLabel,
|
||||||
@ -145,7 +131,7 @@ export const getResizeRedactionOptions = (
|
|||||||
label: translations.onlyHere.label,
|
label: translations.onlyHere.label,
|
||||||
description: translations.onlyHere.description,
|
description: translations.onlyHere.description,
|
||||||
icon: PIN_ICON,
|
icon: PIN_ICON,
|
||||||
value: RedactOrHintOptions.ONLY_HERE,
|
value: ResizeOptions.ONLY_HERE,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -161,8 +147,8 @@ export const getResizeRedactionOptions = (
|
|||||||
disabled: !dictBasedType || redaction.hasBeenRecategorized,
|
disabled: !dictBasedType || redaction.hasBeenRecategorized,
|
||||||
tooltip: !dictBasedType ? translations.inDossier.tooltip : null,
|
tooltip: !dictBasedType ? translations.inDossier.tooltip : null,
|
||||||
icon: FOLDER_ICON,
|
icon: FOLDER_ICON,
|
||||||
value: RedactOrHintOptions.IN_DOSSIER,
|
value: ResizeOptions.IN_DOSSIER,
|
||||||
additionalCheck: {
|
extraOption: {
|
||||||
label: translations.inDossier.extraOptionLabel,
|
label: translations.inDossier.extraOptionLabel,
|
||||||
checked: applyToAllDossiers,
|
checked: applyToAllDossiers,
|
||||||
hidden: !isApprover,
|
hidden: !isApprover,
|
||||||
|
|||||||
@ -3,6 +3,13 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
|||||||
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
||||||
import { Dictionary, Dossier, File, IAddRedactionRequest, IManualRedactionEntry } from '@red/domain';
|
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 = {
|
export const RedactOrHintOptions = {
|
||||||
ONLY_HERE: 'ONLY_HERE',
|
ONLY_HERE: 'ONLY_HERE',
|
||||||
IN_DOCUMENT: 'IN_DOCUMENT',
|
IN_DOCUMENT: 'IN_DOCUMENT',
|
||||||
@ -52,7 +59,6 @@ export interface RedactTextData {
|
|||||||
export interface EditRedactionData {
|
export interface EditRedactionData {
|
||||||
annotations: AnnotationWrapper[];
|
annotations: AnnotationWrapper[];
|
||||||
dossierId: string;
|
dossierId: string;
|
||||||
applyToAllDossiers: boolean;
|
|
||||||
isApprover?: boolean;
|
isApprover?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +71,9 @@ export interface RedactTextResult {
|
|||||||
bulkLocal?: boolean;
|
bulkLocal?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RedactRecommendationData = EditRedactionData;
|
export type RedactRecommendationData = EditRedactionData & {
|
||||||
|
applyToAllDossiers: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export interface RedactRecommendationResult {
|
export interface RedactRecommendationResult {
|
||||||
redaction: IAddRedactionRequest;
|
redaction: IAddRedactionRequest;
|
||||||
@ -79,6 +87,7 @@ export interface EditRedactResult {
|
|||||||
comment: string;
|
comment: string;
|
||||||
type: string;
|
type: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
option: EditRedactionOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddHintResult = RedactTextResult;
|
export type AddHintResult = RedactTextResult;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<div *ngIf="dictionaries && selectedDictionary" class="dictionary-content">
|
<div *ngIf="dictionaries && selectedDictionary" class="dictionary-content">
|
||||||
<div class="dictionaries">
|
<div class="dictionaries">
|
||||||
<div
|
<div
|
||||||
(click)="selectDictionary(dictionary)"
|
(click)="selectDictionary(dictionary, undefined, true)"
|
||||||
*ngFor="let dictionary of dictionaries"
|
*ngFor="let dictionary of dictionaries"
|
||||||
[class.active]="dictionary.label === selectedDictionary.label"
|
[class.active]="dictionary.label === selectedDictionary.label"
|
||||||
class="dictionary"
|
class="dictionary"
|
||||||
@ -33,44 +33,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="entries">
|
<div class="entries">
|
||||||
<div class="header-wrapper">
|
<div class="heading flex-align-items-center">
|
||||||
<div class="header-left">
|
{{ selectedDictionary?.label }}
|
||||||
<div class="heading">
|
<iqser-circle-button
|
||||||
<div class="flex-align-items-center">
|
(action)="openEditDictionaryModal()"
|
||||||
{{ selectedDictionary?.label }}
|
*ngIf="selectedDictionary.dossierDictionaryOnly && selectedDictionary.hasDictionary"
|
||||||
<iqser-circle-button
|
[size]="20"
|
||||||
(action)="openEditDictionaryModal()"
|
[tooltip]="'edit-dossier-dialog.dictionary.edit-button-tooltip' | translate"
|
||||||
*ngIf="selectedDictionary.dossierDictionaryOnly && selectedDictionary.hasDictionary"
|
class="p-left-8"
|
||||||
[size]="20"
|
icon="iqser:edit"
|
||||||
[tooltip]="'edit-dossier-dialog.dictionary.edit-button-tooltip' | translate"
|
></iqser-circle-button>
|
||||||
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>
|
</div>
|
||||||
|
|
||||||
<redaction-dictionary-manager
|
<redaction-dictionary-manager
|
||||||
@ -85,14 +57,14 @@
|
|||||||
[withFloatingActions]="false"
|
[withFloatingActions]="false"
|
||||||
>
|
>
|
||||||
<ng-container slot="typeSwitch">
|
<ng-container slot="typeSwitch">
|
||||||
<div *ngIf="!selectedDictionary.hint" [class.read-only]="!canEdit" class="header-right flex">
|
<div class="flex">
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(click)="selectEntryType(entryTypes.ENTRY)"
|
(click)="selectEntryType(entryTypes.ENTRY, true)"
|
||||||
[active]="activeEntryType === entryTypes.ENTRY"
|
[active]="activeEntryType === entryTypes.ENTRY"
|
||||||
[label]="'edit-dossier-dialog.dictionary.to-redact' | translate: { count: selectedDictionary.entries.length }"
|
[label]="'edit-dossier-dialog.dictionary.to-redact' | translate: { count: selectedDictionary.entries.length }"
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(click)="selectEntryType(entryTypes.FALSE_POSITIVE)"
|
(click)="selectEntryType(entryTypes.FALSE_POSITIVE, true)"
|
||||||
[active]="activeEntryType === entryTypes.FALSE_POSITIVE"
|
[active]="activeEntryType === entryTypes.FALSE_POSITIVE"
|
||||||
[label]="
|
[label]="
|
||||||
'edit-dossier-dialog.dictionary.false-positives'
|
'edit-dossier-dialog.dictionary.false-positives'
|
||||||
@ -100,7 +72,7 @@
|
|||||||
"
|
"
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(click)="selectEntryType(entryTypes.FALSE_RECOMMENDATION)"
|
(click)="selectEntryType(entryTypes.FALSE_RECOMMENDATION, true)"
|
||||||
[active]="activeEntryType === entryTypes.FALSE_RECOMMENDATION"
|
[active]="activeEntryType === entryTypes.FALSE_RECOMMENDATION"
|
||||||
[label]="
|
[label]="
|
||||||
'edit-dossier-dialog.dictionary.false-recommendations'
|
'edit-dossier-dialog.dictionary.false-recommendations'
|
||||||
|
|||||||
@ -11,13 +11,14 @@
|
|||||||
|
|
||||||
.dictionaries {
|
.dictionaries {
|
||||||
border-right: 1px solid var(--iqser-separator);
|
border-right: 1px solid var(--iqser-separator);
|
||||||
overflow-y: scroll;
|
overflow-y: auto;
|
||||||
|
width: 200px;
|
||||||
@include common-mixins.scroll-bar;
|
@include common-mixins.scroll-bar;
|
||||||
|
|
||||||
.dictionary {
|
.dictionary {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border: 1px solid var(--iqser-separator);
|
border-bottom: 1px solid var(--iqser-separator);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -43,33 +44,7 @@
|
|||||||
.entries {
|
.entries {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 16px 0 16px 12px;
|
padding: 16px 0 16px 12px;
|
||||||
|
overflow: hidden;
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,12 @@
|
|||||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
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 { List } from '@iqser/common-ui/lib/utils';
|
||||||
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, Dossier } from '@red/domain';
|
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, Dossier } from '@red/domain';
|
||||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||||
@ -42,6 +49,14 @@ export class EditDossierDictionaryComponent implements OnInit {
|
|||||||
readonly entryTypes = DictionaryEntryTypes;
|
readonly entryTypes = DictionaryEntryTypes;
|
||||||
@ViewChild(DictionaryManagerComponent, { static: false }) private readonly _dictionaryManager: DictionaryManagerComponent;
|
@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 {
|
get changed(): boolean {
|
||||||
return this._dictionaryManager?.editor.hasChanges;
|
return this._dictionaryManager?.editor.hasChanges;
|
||||||
}
|
}
|
||||||
@ -54,13 +69,6 @@ export class EditDossierDictionaryComponent implements OnInit {
|
|||||||
return this._dictionaryManager?.editor.hasChanges;
|
return this._dictionaryManager?.editor.hasChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly _dictionaryService: DictionaryService,
|
|
||||||
private readonly _permissionsService: PermissionsService,
|
|
||||||
private readonly _loadingService: LoadingService,
|
|
||||||
private readonly _iqserDialog: IqserDialog,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
this.canEdit = this._permissionsService.canEditDossierDictionary(this.dossier);
|
this.canEdit = this._permissionsService.canEditDossierDictionary(this.dossier);
|
||||||
@ -95,16 +103,23 @@ export class EditDossierDictionaryComponent implements OnInit {
|
|||||||
this._dictionaryManager.revert();
|
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.selectedDictionary = dictionary;
|
||||||
this.selectEntryType(entryType);
|
await this.selectEntryType(entryType);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectEntryType(selectedEntryType: DictionaryEntryType) {
|
async selectEntryType(selectedEntryType: DictionaryEntryType, checkForChanges = false) {
|
||||||
this.activeEntryType = selectedEntryType ?? this.activeEntryType;
|
if (checkForChanges && !(await this._checkForChanges()).continue) {
|
||||||
const entryType = this.selectedDictionary.hint ? DictionaryEntryTypes.ENTRY : this.activeEntryType;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (entryType) {
|
this.activeEntryType = selectedEntryType ?? this.activeEntryType;
|
||||||
|
|
||||||
|
switch (this.activeEntryType) {
|
||||||
case DictionaryEntryTypes.ENTRY: {
|
case DictionaryEntryTypes.ENTRY: {
|
||||||
this.entriesToDisplay = this.selectedDictionary.entries;
|
this.entriesToDisplay = this.selectedDictionary.entries;
|
||||||
break;
|
break;
|
||||||
@ -141,13 +156,32 @@ export class EditDossierDictionaryComponent implements OnInit {
|
|||||||
await this.#retrieveDictionaries();
|
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() {
|
async #updateDossierDictionary() {
|
||||||
await this.#retrieveDictionaries();
|
await this.#retrieveDictionaries();
|
||||||
let dictionaryToSelect = this.dictionaries[0];
|
let dictionaryToSelect = this.dictionaries[0];
|
||||||
if (this.selectedDictionary) {
|
if (this.selectedDictionary) {
|
||||||
dictionaryToSelect = this.dictionaries.find(d => d.type === this.selectedDictionary.type);
|
dictionaryToSelect = this.dictionaries.find(d => d.type === this.selectedDictionary.type);
|
||||||
}
|
}
|
||||||
this.selectDictionary(dictionaryToSelect, this.activeEntryType);
|
await this.selectDictionary(dictionaryToSelect, this.activeEntryType);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #retrieveDictionaries() {
|
async #retrieveDictionaries() {
|
||||||
|
|||||||
@ -13,7 +13,8 @@
|
|||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
|
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="editor.openFindPanel()"
|
(action)="editor.toggleFindPanel()"
|
||||||
|
[attr.aria-expanded]="_isSearchOpen()"
|
||||||
[matTooltip]="'dictionary-overview.search' | translate"
|
[matTooltip]="'dictionary-overview.search' | translate"
|
||||||
class="ml-8"
|
class="ml-8"
|
||||||
icon="iqser:search"
|
icon="iqser:search"
|
||||||
@ -92,6 +93,7 @@
|
|||||||
|
|
||||||
<div class="editor-container">
|
<div class="editor-container">
|
||||||
<redaction-editor
|
<redaction-editor
|
||||||
|
[(isSearchOpen)]="_isSearchOpen"
|
||||||
[canEdit]="canEdit"
|
[canEdit]="canEdit"
|
||||||
[diffEditorText]="diffEditorText"
|
[diffEditorText]="diffEditorText"
|
||||||
[initialEntries]="initialEntries"
|
[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 { CircleButtonComponent, IconButtonComponent, IconButtonTypes, LoadingService } from '@iqser/common-ui';
|
||||||
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, DictionaryType, Dossier, DossierTemplate, IDictionary } from '@red/domain';
|
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, DictionaryType, Dossier, DossierTemplate, IDictionary } from '@red/domain';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
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;
|
selectDossierTemplate = { name: _('dictionary-overview.compare.select-dossier-template') } as DossierTemplate;
|
||||||
compare = false;
|
compare = false;
|
||||||
dictionaries: List<Dictionary> = this.#dictionaries;
|
dictionaries: List<Dictionary> = this.#dictionaries;
|
||||||
|
protected readonly _isSearchOpen = signal(false);
|
||||||
protected initialDossierTemplateId: string;
|
protected initialDossierTemplateId: string;
|
||||||
readonly #currentTab = window.location.href.split('/').pop();
|
readonly #currentTab = window.location.href.split('/').pop();
|
||||||
#dossierTemplate = this.dossierTemplatesService.all[0];
|
#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 { LoadingService } from '@iqser/common-ui';
|
||||||
import { EditorThemeService } from '@services/editor-theme.service';
|
import { EditorThemeService } from '@services/editor-theme.service';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
@ -39,6 +39,7 @@ export class EditorComponent implements OnInit, OnChanges {
|
|||||||
@Input() diffEditorText: string;
|
@Input() diffEditorText: string;
|
||||||
@Input() @OnChange<List, EditorComponent>('revert') initialEntries: List;
|
@Input() @OnChange<List, EditorComponent>('revert') initialEntries: List;
|
||||||
@Input() canEdit = false;
|
@Input() canEdit = false;
|
||||||
|
readonly isSearchOpen = model.required<boolean>();
|
||||||
/**
|
/**
|
||||||
* Used as [modified] input on diff editor
|
* Used as [modified] input on diff editor
|
||||||
* Shouldn't be updated when editing in 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;
|
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;
|
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) {
|
onPaste(event: ClipboardEvent) {
|
||||||
@ -127,11 +133,13 @@ export class EditorComponent implements OnInit, OnChanges {
|
|||||||
this._diffEditor.getModifiedEditor().onDidChangeModelContent(() => {
|
this._diffEditor.getModifiedEditor().onDidChangeModelContent(() => {
|
||||||
this.value = this._diffEditor.getModel().modified.getValue();
|
this.value = this._diffEditor.getModel().modified.getValue();
|
||||||
});
|
});
|
||||||
|
this._initializeFindWidget(editor.getOriginalEditor());
|
||||||
this.#setTheme();
|
this.#setTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
onCodeEditorInit(editor: MonacoStandaloneCodeEditor): void {
|
onCodeEditorInit(editor: MonacoStandaloneCodeEditor): void {
|
||||||
this.codeEditor = editor;
|
this.codeEditor = editor;
|
||||||
|
this._initializeFindWidget(editor);
|
||||||
this.#setTheme();
|
this.#setTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +151,15 @@ export class EditorComponent implements OnInit, OnChanges {
|
|||||||
this._editorTextChanged$.next(this.value);
|
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) {
|
#getDecorations(newText: string) {
|
||||||
const currentEntries = newText.split('\n');
|
const currentEntries = newText.split('\n');
|
||||||
const newDecorations: IModelDeltaDecoration[] = [];
|
const newDecorations: IModelDeltaDecoration[] = [];
|
||||||
|
|||||||
@ -26,20 +26,13 @@ export const redactTextTranslations: Record<'onlyHere' | 'inDocument' | 'inDossi
|
|||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const editRedactionTranslations: Record<'onlyHere' | 'inDossier', DialogOption> = {
|
export const editRedactionTranslations: Record<'onlyHere' | 'inDocument', DialogOption> = {
|
||||||
onlyHere: {
|
onlyHere: {
|
||||||
label: _('edit-redaction.dialog.content.options.only-here.label'),
|
label: _('edit-redaction.dialog.content.options.only-here.label'),
|
||||||
description: _('edit-redaction.dialog.content.options.only-here.description'),
|
description: _('edit-redaction.dialog.content.options.only-here.description'),
|
||||||
},
|
},
|
||||||
inDossier: {
|
inDocument: {
|
||||||
label: _('edit-redaction.dialog.content.options.in-dossier.label'),
|
label: _('edit-redaction.dialog.content.options.in-document.label'),
|
||||||
description: _('edit-redaction.dialog.content.options.in-dossier.description'),
|
description: _('edit-redaction.dialog.content.options.in-document.description'),
|
||||||
extraOptionLabel: _('edit-redaction.dialog.content.options.in-dossier.extraOptionLabel'),
|
|
||||||
},
|
},
|
||||||
} as const;
|
} 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);
|
const logger = inject(NGXLogger);
|
||||||
logger.info('[ROUTES] Main resolver started...');
|
logger.info('[ROUTES] Main resolver started...');
|
||||||
|
|
||||||
console.log('main guard');
|
|
||||||
|
|
||||||
const router = inject(Router);
|
const router = inject(Router);
|
||||||
inject(FeaturesService).loadConfig();
|
inject(FeaturesService).loadConfig();
|
||||||
if (inject(IqserPermissionsService).has(Roles.dossiers.read)) {
|
if (inject(IqserPermissionsService).has(Roles.dossiers.read)) {
|
||||||
|
|||||||
@ -1234,11 +1234,8 @@
|
|||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"title": "{label} bearbeiten"
|
"title": "{label} bearbeiten"
|
||||||
},
|
},
|
||||||
"entries": "{length} {length, plural, one{Eintrag} other{Einträge}}",
|
|
||||||
"entries-count": "",
|
"entries-count": "",
|
||||||
"false-positive-entries": "{length} {length, plural, one{Falsch-Positiver} other{Falsch-Positive}}",
|
|
||||||
"false-positives": "Falsch-Positive ({count})",
|
"false-positives": "Falsch-Positive ({count})",
|
||||||
"false-recommendation-entries": "{length} {length, plural, one{falsche Empfehlung} other{falsche Empfehlungen}}",
|
|
||||||
"false-recommendations": "Falsche Empfehlungen ({count})",
|
"false-recommendations": "Falsche Empfehlungen ({count})",
|
||||||
"to-redact": "Schwärzungen ({count})"
|
"to-redact": "Schwärzungen ({count})"
|
||||||
},
|
},
|
||||||
@ -1284,14 +1281,11 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"comment": "Kommentar",
|
"comment": "Kommentar",
|
||||||
"comment-placeholder": "Bemerkungen oder Notizen hinzufügen...",
|
"comment-placeholder": "Bemerkungen oder Notizen hinzufügen...",
|
||||||
"custom-rectangle": "Bereichsschwärzung",
|
|
||||||
"imported": "Importierte Schwärzung",
|
|
||||||
"legal-basis": "Rechtsgrundlage",
|
"legal-basis": "Rechtsgrundlage",
|
||||||
"options": {
|
"options": {
|
||||||
"in-dossier": {
|
"in-document": {
|
||||||
"description": "Schwärzung in jedem Dokument in {dossierName} bearbeiten.",
|
"description": "",
|
||||||
"extraOptionLabel": "In alle aktiven und zukünftigen Dossiers übernehmen",
|
"label": ""
|
||||||
"label": "Typ in Dossier ändern"
|
|
||||||
},
|
},
|
||||||
"only-here": {
|
"only-here": {
|
||||||
"description": "Bearbeiten Sie die Schwärzung nur an dieser Stelle im Dokument.",
|
"description": "Bearbeiten Sie die Schwärzung nur an dieser Stelle im Dokument.",
|
||||||
|
|||||||
@ -1234,11 +1234,8 @@
|
|||||||
"save": "Save",
|
"save": "Save",
|
||||||
"title": "Edit {label}"
|
"title": "Edit {label}"
|
||||||
},
|
},
|
||||||
"entries": "{length} {length, plural, one{entry} other{entries}} to redact",
|
|
||||||
"entries-count": "{count} {count, plural, one{entry} other{entries}}",
|
"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-positives": "False positives ({count})",
|
||||||
"false-recommendation-entries": "{length} false recommendation {length, plural, one{entry} other{entries}}",
|
|
||||||
"false-recommendations": "False recommendations ({count})",
|
"false-recommendations": "False recommendations ({count})",
|
||||||
"to-redact": "Entries ({count})"
|
"to-redact": "Entries ({count})"
|
||||||
},
|
},
|
||||||
@ -1284,18 +1281,15 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"comment": "Comment",
|
"comment": "Comment",
|
||||||
"comment-placeholder": "Add remarks or notes...",
|
"comment-placeholder": "Add remarks or notes...",
|
||||||
"custom-rectangle": "Custom Rectangle",
|
|
||||||
"imported": "Imported Redaction",
|
|
||||||
"legal-basis": "Legal basis",
|
"legal-basis": "Legal basis",
|
||||||
"options": {
|
"options": {
|
||||||
"in-dossier": {
|
"in-document": {
|
||||||
"description": "Edit redaction in every document in {dossierName}.",
|
"description": "Edit redaction of all linked occurrences of the term in this document.",
|
||||||
"extraOptionLabel": "Apply to all active and future dossiers",
|
"label": "Change in document"
|
||||||
"label": "Change type in dossier"
|
|
||||||
},
|
},
|
||||||
"only-here": {
|
"only-here": {
|
||||||
"description": "Edit redaction only at this position in this document.",
|
"description": "Edit redaction only at this position in this document.",
|
||||||
"label": "Change type only here"
|
"label": "Change only here"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reason": "Reason",
|
"reason": "Reason",
|
||||||
|
|||||||
@ -1234,11 +1234,8 @@
|
|||||||
"save": "",
|
"save": "",
|
||||||
"title": ""
|
"title": ""
|
||||||
},
|
},
|
||||||
"entries": "{length} {length, plural, one{entry} other{entries}} to {hint, select, true{annotate} other{redact}}",
|
|
||||||
"entries-count": "",
|
"entries-count": "",
|
||||||
"false-positive-entries": "{length} false positive {length, plural, one{entry} other{entries}}",
|
|
||||||
"false-positives": "False positives ({count})",
|
"false-positives": "False positives ({count})",
|
||||||
"false-recommendation-entries": "{length} false recommendation {length, plural, one{entry} other{entries}}",
|
|
||||||
"false-recommendations": "False recommendations ({count})",
|
"false-recommendations": "False recommendations ({count})",
|
||||||
"to-redact": "To redact ({count})"
|
"to-redact": "To redact ({count})"
|
||||||
},
|
},
|
||||||
@ -1284,13 +1281,10 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"comment": "Comment",
|
"comment": "Comment",
|
||||||
"comment-placeholder": "Add remarks or mentions...",
|
"comment-placeholder": "Add remarks or mentions...",
|
||||||
"custom-rectangle": "",
|
|
||||||
"imported": "",
|
|
||||||
"legal-basis": "",
|
"legal-basis": "",
|
||||||
"options": {
|
"options": {
|
||||||
"in-dossier": {
|
"in-document": {
|
||||||
"description": "",
|
"description": "",
|
||||||
"extraOptionLabel": "",
|
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"only-here": {
|
"only-here": {
|
||||||
|
|||||||
@ -1234,11 +1234,8 @@
|
|||||||
"save": "",
|
"save": "",
|
||||||
"title": ""
|
"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}}",
|
"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-positives": "False positives ({count})",
|
||||||
"false-recommendation-entries": "{length} false recommendation {length, plural, one{entry} other{entries}}",
|
|
||||||
"false-recommendations": "False recommendations ({count})",
|
"false-recommendations": "False recommendations ({count})",
|
||||||
"to-redact": "To redact ({count})"
|
"to-redact": "To redact ({count})"
|
||||||
},
|
},
|
||||||
@ -1284,13 +1281,10 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"comment": "Comment",
|
"comment": "Comment",
|
||||||
"comment-placeholder": "Add remarks or mentions...",
|
"comment-placeholder": "Add remarks or mentions...",
|
||||||
"custom-rectangle": "",
|
|
||||||
"imported": "",
|
|
||||||
"legal-basis": "",
|
"legal-basis": "",
|
||||||
"options": {
|
"options": {
|
||||||
"in-dossier": {
|
"in-document": {
|
||||||
"description": "",
|
"description": "",
|
||||||
"extraOptionLabel": "",
|
|
||||||
"label": ""
|
"label": ""
|
||||||
},
|
},
|
||||||
"only-here": {
|
"only-here": {
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit 835cb7820e2100ff1125939f4c2766f06e9c09a6
|
Subproject commit 34387d49d29ba6449c1311cc1c5434b540398660
|
||||||
@ -6,3 +6,18 @@ export interface IRecategorizationRequest {
|
|||||||
readonly section?: string;
|
readonly section?: string;
|
||||||
readonly value?: 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