Merge branch 'master' into VM/RED-2614
This commit is contained in:
commit
4c076ffdaa
@ -1,4 +1,7 @@
|
||||
{
|
||||
"cli": {
|
||||
"analytics": "4b8eed12-a1e6-4b7a-9ea2-925b27941271"
|
||||
},
|
||||
"version": 1,
|
||||
"projects": {
|
||||
"common-ui": {
|
||||
|
||||
@ -35,7 +35,6 @@ const routes: Routes = [
|
||||
component: BaseScreenComponent,
|
||||
loadChildren: () => import('./modules/dossier/dossiers.module').then(m => m.DossiersModule),
|
||||
canActivate: [CompositeRouteGuard],
|
||||
canDeactivate: [DossiersGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard, DossiersGuard],
|
||||
requiredRoles: ['RED_USER', 'RED_MANAGER'],
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Component, ViewContainerRef } from '@angular/core';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-root',
|
||||
@ -6,6 +7,7 @@ import { Component, ViewContainerRef } from '@angular/core';
|
||||
styleUrls: ['./app.component.scss'],
|
||||
})
|
||||
export class AppComponent {
|
||||
// View container ref needs to be injected for the color picker to work
|
||||
constructor(public viewContainerRef: ViewContainerRef) {}
|
||||
// ViewContainerRef needs to be injected for the color picker to work
|
||||
// RouterHistoryService needs to be injected for last dossiers screen to be updated on first app load
|
||||
constructor(public viewContainerRef: ViewContainerRef, private readonly _routerHistoryService: RouterHistoryService) {}
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common
|
||||
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
|
||||
import { ApiPathInterceptor } from '@utils/api-path-interceptor';
|
||||
import { MissingTranslationHandler, TranslateCompiler, TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { languageInitializer } from '@i18n/language.initializer';
|
||||
import { LanguageService } from '@i18n/language.service';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<redaction-breadcrumbs iqserHelpMode="navigate-in-breadcrumbs"></redaction-breadcrumbs>
|
||||
</div>
|
||||
|
||||
<div class="center">
|
||||
<div class="logo">
|
||||
<iqser-hidden-action (action)="userPreferenceService.toggleDevFeatures()">
|
||||
<iqser-logo icon="red:logo"></iqser-logo>
|
||||
</iqser-hidden-action>
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
<ng-container *ngIf="breadcrumbsService.breadcrumbs$ | async as breadcrumbs">
|
||||
<a
|
||||
*ngIf="breadcrumbs.length === 0 || (breadcrumbsService.showGoBack$ | async) === true; else items"
|
||||
class="breadcrumb back"
|
||||
redactionNavigateLastDossiersScreen
|
||||
>
|
||||
<a *ngIf="breadcrumbs.length === 0; else items" class="breadcrumb back" redactionNavigateLastDossiersScreen>
|
||||
<mat-icon svgIcon="iqser:expand"></mat-icon>
|
||||
{{ 'top-bar.navigation-items.back' | translate }}
|
||||
</a>
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
[itemSize]="80"
|
||||
[noDataText]="'downloads-list.no-data.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
noDataIcon="iqser:download"
|
||||
></iqser-table>
|
||||
</div>
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, CanDeactivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { BreadcrumbsService } from '@services/breadcrumbs.service';
|
||||
import { pluck } from 'rxjs/operators';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossierFilesGuard implements CanActivate, CanDeactivate<unknown> {
|
||||
export class DossierFilesGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _breadcrumbsService: BreadcrumbsService,
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
private readonly _filesService: FilesService,
|
||||
private readonly _router: Router,
|
||||
@ -27,28 +24,6 @@ export class DossierFilesGuard implements CanActivate, CanDeactivate<unknown> {
|
||||
if (!this._filesMapService.has(dossierId)) {
|
||||
await this._filesService.loadAll(dossierId).toPromise();
|
||||
}
|
||||
|
||||
this._breadcrumbsService.append({
|
||||
name$: this._dossiersService.getEntityChanged$(dossierId).pipe(pluck('dossierName')),
|
||||
routerLink: ['/main', 'dossiers', dossierId],
|
||||
routerLinkActiveOptions: { exact: true },
|
||||
});
|
||||
|
||||
this._breadcrumbsService.hideGoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
canDeactivate(
|
||||
component: unknown,
|
||||
currentRoute: ActivatedRouteSnapshot,
|
||||
currentState: RouterStateSnapshot,
|
||||
nextState?: RouterStateSnapshot,
|
||||
) {
|
||||
const dossierId = currentRoute.paramMap.get('dossierId');
|
||||
this._breadcrumbsService.remove(['/main', 'dossiers', dossierId]);
|
||||
if (!nextState.url.startsWith('/main/dossiers')) {
|
||||
this._breadcrumbsService.showGoBack();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,39 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, CanDeactivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { BreadcrumbsService } from '@services/breadcrumbs.service';
|
||||
import { of } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossiersGuard implements CanActivate, CanDeactivate<unknown> {
|
||||
export class DossiersGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _breadcrumbsService: BreadcrumbsService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _router: Router,
|
||||
) {}
|
||||
|
||||
async canActivate(): Promise<boolean> {
|
||||
await this._dossiersService.loadAll().toPromise();
|
||||
this._breadcrumbsService.hideGoBack();
|
||||
this._breadcrumbsService.append({
|
||||
name$: of(this._translateService.instant('top-bar.navigation-items.dossiers')),
|
||||
routerLink: ['/main', 'dossiers'],
|
||||
routerLinkActiveOptions: { exact: true },
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
canDeactivate(
|
||||
component: unknown,
|
||||
currentRoute: ActivatedRouteSnapshot,
|
||||
currentState: RouterStateSnapshot,
|
||||
nextState?: RouterStateSnapshot,
|
||||
) {
|
||||
if (!nextState.url.startsWith('/main/dossiers')) {
|
||||
this._breadcrumbsService.showGoBack();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, CanDeactivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { BreadcrumbsService } from '@services/breadcrumbs.service';
|
||||
import { pluck } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class FilePreviewGuard implements CanActivate, CanDeactivate<unknown> {
|
||||
export class FilePreviewGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _breadcrumbsService: BreadcrumbsService,
|
||||
private readonly _router: Router,
|
||||
) {}
|
||||
|
||||
@ -25,32 +22,6 @@ export class FilePreviewGuard implements CanActivate, CanDeactivate<unknown> {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._breadcrumbsService.append({
|
||||
name$: this._filesMapService.watch$(dossierId, fileId).pipe(pluck('filename')),
|
||||
routerLink: ['/main', 'dossiers', dossierId, 'file', fileId],
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
canDeactivate(
|
||||
component: unknown,
|
||||
currentRoute: ActivatedRouteSnapshot,
|
||||
currentState: RouterStateSnapshot,
|
||||
nextState?: RouterStateSnapshot,
|
||||
) {
|
||||
const dossierId = currentRoute.paramMap.get('dossierId');
|
||||
const fileId = currentRoute.paramMap.get('fileId');
|
||||
this._breadcrumbsService.remove(['/main', 'dossiers', dossierId, 'file', fileId]);
|
||||
|
||||
if (!nextState.url.includes('dossiers/')) {
|
||||
this._breadcrumbsService.remove(['/main', 'dossiers', dossierId]);
|
||||
}
|
||||
|
||||
if (!nextState.url.startsWith('/main/dossiers')) {
|
||||
this._breadcrumbsService.showGoBack();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, CanDeactivate } from '@angular/router';
|
||||
import { BreadcrumbsService } from '@services/breadcrumbs.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class GoBackGuard implements CanActivate, CanDeactivate<unknown> {
|
||||
constructor(private readonly _breadcrumbsService: BreadcrumbsService) {}
|
||||
|
||||
canActivate(): boolean {
|
||||
this._breadcrumbsService.showGoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
canDeactivate() {
|
||||
this._breadcrumbsService.hideGoBack();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -42,6 +42,8 @@ export class AnnotationWrapper {
|
||||
legalBasisValue: string;
|
||||
legalBasisChangeValue?: string;
|
||||
resizing?: boolean;
|
||||
rectangle?: boolean;
|
||||
section?: string;
|
||||
|
||||
manual?: boolean;
|
||||
|
||||
@ -217,6 +219,8 @@ export class AnnotationWrapper {
|
||||
annotationWrapper.comments = redactionLogEntry.comments || [];
|
||||
annotationWrapper.manual = redactionLogEntry.manual;
|
||||
annotationWrapper.engines = redactionLogEntry.engines;
|
||||
annotationWrapper.section = redactionLogEntry.section;
|
||||
annotationWrapper.rectangle = redactionLogEntry.rectangle;
|
||||
|
||||
this._createContent(annotationWrapper, redactionLogEntry);
|
||||
this._setSuperType(annotationWrapper, redactionLogEntry);
|
||||
|
||||
@ -13,7 +13,6 @@ export class ManualRedactionEntryWrapper {
|
||||
readonly quads: any,
|
||||
readonly manualRedactionEntry: IManualRedactionEntry,
|
||||
readonly type: ManualRedactionEntryType,
|
||||
readonly annotationType: 'TEXT' | 'RECTANGLE' = 'TEXT',
|
||||
readonly rectId?: string,
|
||||
) {}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ export interface RedactionLogEntryWrapper {
|
||||
reference?: Array<string>;
|
||||
startOffset?: number;
|
||||
type?: string;
|
||||
rectangle?: boolean;
|
||||
|
||||
color?: Array<number>;
|
||||
dictionaryEntry?: boolean;
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
[itemSize]="50"
|
||||
[noDataText]="'file-attributes-csv-import.no-data.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
emptyColumnWidth="auto"
|
||||
noDataIcon="red:attribute"
|
||||
></iqser-table>
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
[itemSize]="80"
|
||||
[noDataIcon]="'iqser:document'"
|
||||
[noDataText]="'audit-screen.no-data.title' | translate"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[totalSize]="logs?.totalHits || 0"
|
||||
>
|
||||
</iqser-table>
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
|
||||
|
||||
<div class="content-container">
|
||||
<iqser-table [itemSize]="80" emptyColumnWidth="2fr"></iqser-table>
|
||||
<iqser-table [itemSize]="80" [tableColumnConfigs]="tableColumnConfigs" emptyColumnWidth="2fr"></iqser-table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
[noMatchText]="'dictionary-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[showNoDataButton]="currentUser.isAdmin"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
emptyColumnWidth="1fr"
|
||||
noDataIcon="red:dictionary"
|
||||
></iqser-table>
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
[noMatchText]="'dossier-attributes-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[showNoDataButton]="currentUser.isAdmin"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
emptyColumnWidth="1fr"
|
||||
noDataIcon="red:attribute"
|
||||
></iqser-table>
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
[noDataText]="'dossier-templates-listing.no-data.title' | translate"
|
||||
[noMatchText]="'dossier-templates-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
noDataIcon="red:template"
|
||||
></iqser-table>
|
||||
</div>
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
[noDataText]="'file-attributes-listing.no-data.title' | translate"
|
||||
[noMatchText]="'file-attributes-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
emptyColumnWidth="1fr"
|
||||
noDataIcon="red:attribute"
|
||||
></iqser-table>
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
[itemSize]="80"
|
||||
[noDataText]="'justifications-listing.no-data.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
noDataIcon="iqser:document"
|
||||
></iqser-table>
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
[noDataText]="'trash.no-data.title' | translate"
|
||||
[noMatchText]="'trash.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableItemClasses]="{ disabled: disabledFn }"
|
||||
noDataIcon="red:template"
|
||||
></iqser-table>
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
[itemSize]="80"
|
||||
[noMatchText]="'user-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
emptyColumnWidth="1fr"
|
||||
></iqser-table>
|
||||
</div>
|
||||
|
||||
@ -21,6 +21,16 @@
|
||||
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
||||
</div>
|
||||
|
||||
<div class="iqser-input-group w-400">
|
||||
<label translate="change-legal-basis-dialog.content.section"></label>
|
||||
<input formControlName="section" name="section" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="iqser-input-group w-400" *ngIf="this.allRectangles">
|
||||
<label translate="change-legal-basis-dialog.content.classification"></label>
|
||||
<input formControlName="classification" name="classification" type="text" />
|
||||
</div>
|
||||
|
||||
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
|
||||
<label translate="change-legal-basis-dialog.content.comment"></label>
|
||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
||||
|
||||
@ -31,7 +31,15 @@ export class ChangeLegalBasisDialogComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
get changed(): boolean {
|
||||
return this.form.get('reason').value.legalBasis !== this._data.annotations[0].legalBasis;
|
||||
return (
|
||||
this.form.get('reason').value.legalBasis !== this._data.annotations[0].legalBasis ||
|
||||
this.form.get('section').value !== this._data.annotations[0].section ||
|
||||
this.form.get('classification').value !== this._data.annotations[0].value
|
||||
);
|
||||
}
|
||||
|
||||
get allRectangles(): boolean {
|
||||
return this._data.annotations.reduce((acc, a) => acc && a.rectangle, true);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -55,13 +63,17 @@ export class ChangeLegalBasisDialogComponent implements OnInit {
|
||||
return this._formBuilder.group({
|
||||
reason: [null, Validators.required],
|
||||
comment: this.isDocumentAdmin ? [null] : [null, Validators.required],
|
||||
classification: [this.allRectangles ? this._data.annotations[0].value : null],
|
||||
section: [this._data.annotations[0].section],
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
this.dialogRef.close({
|
||||
legalBasis: this.form.get('reason').value.legalBasis,
|
||||
section: this.form.get('section').value,
|
||||
comment: this.form.get('comment').value,
|
||||
value: this.form.get('classification').value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
[itemSize]="50"
|
||||
[noDataText]="'edit-dossier-dialog.deleted-documents.no-data.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableItemClasses]="{ disabled: disabledFn }"
|
||||
noDataIcon="iqser:document"
|
||||
></iqser-table>
|
||||
|
||||
@ -124,17 +124,13 @@ export class EditDossierDialogComponent extends BaseDialogComponent {
|
||||
return this.activeComponent?.disabled;
|
||||
}
|
||||
|
||||
afterSave() {
|
||||
this._toaster.success(_('edit-dossier-dialog.change-successful'), { params: { dossierName: this._dossierName } });
|
||||
}
|
||||
|
||||
async save(closeAfterSave: boolean = false) {
|
||||
this._loadingService.start();
|
||||
const result = await this.activeComponent.save();
|
||||
this._loadingService.stop();
|
||||
|
||||
if (result.success) {
|
||||
this.afterSave();
|
||||
this._toaster.success(_('edit-dossier-dialog.change-successful'), { params: { dossierName: this._dossierName } });
|
||||
}
|
||||
|
||||
if (result.success && closeAfterSave) {
|
||||
|
||||
@ -116,7 +116,6 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
});
|
||||
this._dialogService.openDialog('confirm', null, data, async () => {
|
||||
await this._dossiersService.delete(this.dossier).toPromise();
|
||||
this._editDossierDialogRef.componentInstance.afterSave();
|
||||
this._editDossierDialogRef.close();
|
||||
this._router.navigate(['main', 'dossiers']).then(() => this._notifyDossierDeleted());
|
||||
});
|
||||
|
||||
@ -3,19 +3,37 @@
|
||||
<div [translate]="title" class="dialog-header heading-l"></div>
|
||||
|
||||
<div class="dialog-content">
|
||||
<ng-container *ngIf="data.manualRedactionEntryWrapper.annotationType === 'TEXT'">
|
||||
<ng-container *ngIf="!data.manualRedactionEntryWrapper.manualRedactionEntry.rectangle">
|
||||
<div class="iqser-input-group">
|
||||
<label translate="manual-annotation.dialog.content.text"></label>
|
||||
</div>
|
||||
{{ format(data.manualRedactionEntryWrapper.manualRedactionEntry.value) }}
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="data.manualRedactionEntryWrapper.annotationType === 'RECTANGLE'">
|
||||
<ng-container *ngIf="data.manualRedactionEntryWrapper.manualRedactionEntry.rectangle">
|
||||
<div class="iqser-input-group">
|
||||
<label translate="manual-annotation.dialog.content.rectangle"></label>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="!isFalsePositiveRequest && isDictionaryRequest" class="iqser-input-group required w-400">
|
||||
<label translate="manual-annotation.dialog.content.dictionary"></label>
|
||||
|
||||
<mat-select formControlName="dictionary">
|
||||
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
|
||||
<mat-option
|
||||
*ngFor="let dictionary of possibleDictionaries"
|
||||
[matTooltip]="dictionary.description"
|
||||
[value]="dictionary.type"
|
||||
matTooltipPosition="after"
|
||||
>
|
||||
<span>
|
||||
{{ dictionary.label }}
|
||||
</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!isDictionaryRequest" class="iqser-input-group required w-400">
|
||||
<label translate="manual-annotation.dialog.content.reason"></label>
|
||||
<mat-select
|
||||
@ -39,27 +57,19 @@
|
||||
<input [value]="redactionForm.get('reason').value?.legalBasis" disabled type="text" />
|
||||
</div>
|
||||
|
||||
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
|
||||
<label translate="manual-annotation.dialog.content.comment"></label>
|
||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
||||
<div *ngIf="data.manualRedactionEntryWrapper.manualRedactionEntry.rectangle" class="iqser-input-group w-400">
|
||||
<label translate="manual-annotation.dialog.content.section"></label>
|
||||
<input formControlName="section" name="section" type="text" />
|
||||
</div>
|
||||
|
||||
<div *ngIf="isDictionaryRequest && !isFalsePositiveRequest" class="iqser-input-group required w-300">
|
||||
<label translate="manual-annotation.dialog.content.dictionary"></label>
|
||||
<div *ngIf="data.manualRedactionEntryWrapper.manualRedactionEntry.rectangle" class="iqser-input-group w-400">
|
||||
<label translate="manual-annotation.dialog.content.classification"></label>
|
||||
<input formControlName="classification" name="classification" type="text" />
|
||||
</div>
|
||||
|
||||
<mat-select formControlName="dictionary">
|
||||
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
|
||||
<mat-option
|
||||
*ngFor="let dictionary of redactionDictionaries"
|
||||
[matTooltip]="dictionary.description"
|
||||
[value]="dictionary.type"
|
||||
matTooltipPosition="after"
|
||||
>
|
||||
<span>
|
||||
{{ dictionary.label }}
|
||||
</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
|
||||
<label translate="manual-annotation.dialog.content.comment"></label>
|
||||
<textarea formControlName="comment" name="comment" redactionHasScrollbar rows="4" type="text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
||||
isDictionaryRequest: boolean;
|
||||
isFalsePositiveRequest: boolean;
|
||||
|
||||
redactionDictionaries: Dictionary[] = [];
|
||||
possibleDictionaries: Dictionary[] = [];
|
||||
legalOptions: LegalBasisOption[] = [];
|
||||
|
||||
private readonly _dossier: Dossier;
|
||||
@ -51,7 +51,7 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
||||
|
||||
this.redactionForm = this._getForm();
|
||||
|
||||
this.redactionDictionaries = this._redactionDictionaries;
|
||||
this.possibleDictionaries = this._possibleDictionaries;
|
||||
}
|
||||
|
||||
get title() {
|
||||
@ -61,24 +61,25 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
||||
get displayedDictionaryLabel() {
|
||||
const dictType = this.redactionForm.get('dictionary').value;
|
||||
if (dictType) {
|
||||
return this.redactionDictionaries.find(d => d.type === dictType).label;
|
||||
return this.possibleDictionaries.find(d => d.type === dictType).label;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private get _redactionDictionaries(): Dictionary[] {
|
||||
const redactionDictionaries: Dictionary[] = [];
|
||||
private get _possibleDictionaries(): Dictionary[] {
|
||||
const possibleDictionaries: Dictionary[] = [];
|
||||
const dossier = this._dossier;
|
||||
|
||||
for (const key of Object.keys(this._appStateService.dictionaryData[dossier.dossierTemplateId])) {
|
||||
const dictionaryData = this._appStateService.getDictionary(key, dossier.dossierTemplateId);
|
||||
if (!dictionaryData.virtual && dictionaryData.addToDictionaryAction) {
|
||||
redactionDictionaries.push(dictionaryData);
|
||||
possibleDictionaries.push(dictionaryData);
|
||||
}
|
||||
}
|
||||
redactionDictionaries.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return redactionDictionaries;
|
||||
possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return possibleDictionaries;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -111,11 +112,13 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
section: [null],
|
||||
reason: this.isDictionaryRequest ? [null] : [null, Validators.required],
|
||||
dictionary: this.isDictionaryRequest
|
||||
? [this.isFalsePositiveRequest ? 'false_positive' : null, Validators.required]
|
||||
: ['manual', Validators.required],
|
||||
comment: this.isDocumentAdmin ? [null] : [null, Validators.required],
|
||||
classification: ['non-readable content'],
|
||||
});
|
||||
}
|
||||
|
||||
@ -133,5 +136,9 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
||||
}
|
||||
const commentValue = this.redactionForm.get('comment').value;
|
||||
addRedactionRequest.comment = commentValue ? { text: commentValue } : null;
|
||||
addRedactionRequest.section = this.redactionForm.get('section').value;
|
||||
addRedactionRequest.value = addRedactionRequest.rectangle
|
||||
? this.redactionForm.get('classification').value
|
||||
: addRedactionRequest.value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,22 +3,20 @@ import { RouterModule, Routes } from '@angular/router';
|
||||
import { SearchScreenComponent } from './screens/search-screen/search-screen.component';
|
||||
import { FilePreviewGuard } from '@guards/file-preview.guard';
|
||||
import { DossierFilesGuard } from '@guards/dossier-files-guard';
|
||||
import { GoBackGuard } from '@guards/go-back-guard.service';
|
||||
import { CompositeRouteGuard } from '@iqser/common-ui';
|
||||
import { BreadcrumbTypes } from '@red/domain';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'search',
|
||||
component: SearchScreenComponent,
|
||||
canActivate: [GoBackGuard],
|
||||
canDeactivate: [GoBackGuard],
|
||||
},
|
||||
{
|
||||
path: ':dossierId',
|
||||
canActivate: [CompositeRouteGuard],
|
||||
canDeactivate: [DossierFilesGuard],
|
||||
data: {
|
||||
routeGuards: [DossierFilesGuard],
|
||||
breadcrumbs: [BreadcrumbTypes.main, BreadcrumbTypes.dossier],
|
||||
},
|
||||
loadChildren: () => import('./screens/dossier-overview/dossier-overview.module').then(m => m.DossierOverviewModule),
|
||||
},
|
||||
@ -27,14 +25,15 @@ const routes: Routes = [
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [DossierFilesGuard, FilePreviewGuard],
|
||||
breadcrumbs: [BreadcrumbTypes.main, BreadcrumbTypes.dossier, BreadcrumbTypes.file],
|
||||
},
|
||||
canDeactivate: [FilePreviewGuard],
|
||||
loadChildren: () => import('./screens/file-preview-screen/file-preview.module').then(m => m.FilePreviewModule),
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
loadChildren: () => import('./screens/dossiers-listing/dossiers-listing.module').then(m => m.DossiersListingModule),
|
||||
data: { breadcrumbs: [BreadcrumbTypes.main] },
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
[noMatchText]="'dossier-overview.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[showNoDataButton]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableItemClasses]="{ disabled: disabledFn, 'last-opened': lastOpenedFn }"
|
||||
helpModeKey="document-list"
|
||||
></iqser-table>
|
||||
|
||||
@ -17,7 +17,7 @@ import { FileUploadService } from '@upload-download/services/file-upload.service
|
||||
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
|
||||
import * as moment from 'moment';
|
||||
import { Observable, timer } from 'rxjs';
|
||||
import { filter, switchMap, tap } from 'rxjs/operators';
|
||||
import { filter, skip, switchMap, tap } from 'rxjs/operators';
|
||||
import { convertFiles, Files, handleFileDrop } from '@utils/index';
|
||||
import {
|
||||
CircleButtonTypes,
|
||||
@ -73,6 +73,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
|
||||
@ViewChild('fileInput', { static: true }) private readonly _fileInput: ElementRef;
|
||||
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<Dossier>;
|
||||
private _fileAttributeConfigs: IFileAttributeConfig[];
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
@ -99,19 +100,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
.getEntityChanged$(this.dossierId)
|
||||
.pipe(tap(dossier => (this.dossierTemplateId = dossier.dossierTemplateId)));
|
||||
this.currentDossier = this._dossiersService.find(this.dossierId);
|
||||
|
||||
this.fileAttributeConfigs = this._fileAttributesService.getFileAttributeConfig(
|
||||
this.currentDossier.dossierTemplateId,
|
||||
)?.fileAttributeConfigs;
|
||||
this.tableColumnConfigs = this.configService.tableConfig(this.displayedAttributes);
|
||||
}
|
||||
|
||||
private _fileAttributeConfigs: IFileAttributeConfig[];
|
||||
|
||||
set fileAttributeConfigs(value: IFileAttributeConfig[]) {
|
||||
this._fileAttributeConfigs = value || [];
|
||||
this.displayedInFileListAttributes = this._fileAttributeConfigs.filter(config => config.displayedInFileList);
|
||||
this.displayedAttributes = this.displayedInFileListAttributes.filter(c => c.displayedInFileList);
|
||||
this._updateFileAttributes();
|
||||
}
|
||||
|
||||
get checkedRequiredFilters(): NestedFilter[] {
|
||||
@ -123,6 +112,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
}
|
||||
|
||||
disabledFn = (file: File) => file.excluded;
|
||||
|
||||
lastOpenedFn = (file: File) => this._userPreferenceService.getLastOpenedFileForDossier(file.dossierId) === file.id;
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
@ -150,11 +140,15 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
this._computeAllFilters();
|
||||
});
|
||||
|
||||
this.addSubscription = this._dossierTemplatesService.entityChanged$.subscribe(() => {
|
||||
this.fileAttributeConfigs = this._fileAttributesService.getFileAttributeConfig(
|
||||
this.currentDossier.dossierTemplateId,
|
||||
)?.fileAttributeConfigs;
|
||||
});
|
||||
this.addSubscription = this._dossierTemplatesService
|
||||
.getEntityChanged$(this.currentDossier.dossierTemplateId)
|
||||
.pipe(
|
||||
skip(1),
|
||||
tap(() => {
|
||||
this._updateFileAttributes();
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
try {
|
||||
this.dossierAttributes = await this._dossierAttributesService.getWithValues(this.currentDossier);
|
||||
@ -170,14 +164,12 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
async ngOnAttach() {
|
||||
await this.ngOnInit();
|
||||
ngOnAttach() {
|
||||
this._fileDropOverlayService.initFileDropHandling(this.dossierId);
|
||||
this._tableComponent?.scrollToLastIndex();
|
||||
}
|
||||
|
||||
ngOnDetach() {
|
||||
this.ngOnDestroy();
|
||||
}
|
||||
ngOnDetach() {}
|
||||
|
||||
forceReanalysisAction($event: LongPressEvent) {
|
||||
this.analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled;
|
||||
@ -203,6 +195,15 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
recentlyModifiedChecker = (file: File) =>
|
||||
moment(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment());
|
||||
|
||||
private _updateFileAttributes(): void {
|
||||
this._fileAttributeConfigs =
|
||||
this._fileAttributesService.getFileAttributeConfig(this.currentDossier.dossierTemplateId)?.fileAttributeConfigs || [];
|
||||
this.displayedInFileListAttributes = this._fileAttributeConfigs.filter(config => config.displayedInFileList);
|
||||
this.displayedAttributes = this.displayedInFileListAttributes.filter(c => c.displayedInFileList);
|
||||
this.tableColumnConfigs = this.configService.tableConfig(this.displayedAttributes);
|
||||
this._computeAllFilters();
|
||||
}
|
||||
|
||||
private async _reloadFiles() {
|
||||
await this._filesService.loadAll(this.dossierId).toPromise();
|
||||
this._computeAllFilters();
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
[noDataText]="'dossier-listing.no-data.title' | translate"
|
||||
[noMatchText]="'dossier-listing.no-match.title' | translate"
|
||||
[showNoDataButton]="currentUser.isManager"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
helpModeKey="dossier-list"
|
||||
noDataIcon="red:folder"
|
||||
></iqser-table>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
<div>
|
||||
<iqser-circle-button
|
||||
(action)="edit()"
|
||||
*ngIf="permissionsService.canEditFileAttributes(dossier, file)"
|
||||
[tooltip]="'file-preview.tabs.document-info.edit' | translate"
|
||||
icon="iqser:edit"
|
||||
tooltipPosition="before"
|
||||
|
||||
@ -5,6 +5,7 @@ import { AutoUnsubscribe } from '@iqser/common-ui';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DocumentInfoService } from '../../services/document-info.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PermissionsService } from '../../../../../../services/permissions.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-document-info [file] [dossier]',
|
||||
@ -22,6 +23,7 @@ export class DocumentInfoComponent extends AutoUnsubscribe implements OnInit {
|
||||
constructor(
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
readonly permissionsService: PermissionsService,
|
||||
readonly documentInfoService: DocumentInfoService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@ -240,7 +240,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
// this will auto select rectangle after drawing
|
||||
if (annotations.length === 1 && annotations[0].ToolName === 'AnnotationCreateRectangle') {
|
||||
this.annotationManager.selectAnnotations(annotations);
|
||||
annotations[0].enableRotationControl();
|
||||
annotations[0].disableRotationControl();
|
||||
}
|
||||
});
|
||||
|
||||
@ -495,12 +495,10 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
const activeAnnotation = this.annotationManager.getSelectedAnnotations()[0];
|
||||
const activePage = activeAnnotation.getPageNumber();
|
||||
const quads = [this._annotationDrawService.annotationToQuads(activeAnnotation, this.instance)];
|
||||
const manualRedaction = this._getManualRedaction({ [activePage]: quads }, 'Rectangle');
|
||||
const manualRedaction = this._getManualRedaction({ [activePage]: quads });
|
||||
this._cleanUpSelectionAndButtonState();
|
||||
|
||||
this.manualAnnotationRequested.emit(
|
||||
new ManualRedactionEntryWrapper(quads, manualRedaction, 'REDACTION', 'RECTANGLE', activeAnnotation.Id),
|
||||
);
|
||||
this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(quads, manualRedaction, 'REDACTION', activeAnnotation.Id));
|
||||
}
|
||||
|
||||
private _cleanUpSelectionAndButtonState() {
|
||||
@ -619,7 +617,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
|
||||
private _getManualRedaction(
|
||||
quads: Readonly<Record<string, Core.Math.Quad[]>>,
|
||||
text: string,
|
||||
text?: string,
|
||||
convertQuads = false,
|
||||
): IManualRedactionEntry {
|
||||
const entry: IManualRedactionEntry = { positions: [] };
|
||||
@ -633,6 +631,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
entry.value = text;
|
||||
entry.rectangle = !text;
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
@ -79,6 +79,7 @@
|
||||
*ngIf="displayPdfViewer"
|
||||
[annotations]="annotations"
|
||||
[canPerformActions]="canPerformAnnotationActions$ | async"
|
||||
[class.hidden]="!ready"
|
||||
[dossier]="dossier"
|
||||
[fileData]="fileData?.fileData"
|
||||
[file]="file"
|
||||
|
||||
@ -62,3 +62,7 @@
|
||||
max-width: 400px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
redaction-pdf-viewer.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@ -1,14 +1,4 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
HostListener,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, HostListener, NgZone, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { Core, WebViewerInstance } from '@pdftron/webviewer';
|
||||
@ -64,7 +54,6 @@ const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f'];
|
||||
templateUrl: './file-preview-screen.component.html',
|
||||
styleUrls: ['./file-preview-screen.component.scss'],
|
||||
providers: [FilterService, ExcludedPagesService, ViewModeService, MultiSelectService, DocumentInfoService],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
@ -84,6 +73,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
readonly viewDocumentInfo$: Observable<boolean>;
|
||||
readonly file$: Observable<File>;
|
||||
readonly fileId: string;
|
||||
ready = false;
|
||||
private _instance: WebViewerInstance;
|
||||
private _lastPage: string;
|
||||
private _reloadFileOnReanalysis = false;
|
||||
@ -177,13 +167,16 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
async updateViewMode(): Promise<void> {
|
||||
const annotations = this._getAnnotations(a => a.getCustomData('redacto-manager'));
|
||||
const ocrAnnotationIds = this.annotationData.allAnnotations.filter(a => a.isOCR).map(a => a.id);
|
||||
const annotations = this._getAnnotations(a => a.getCustomData('redact-manager'));
|
||||
const redactions = annotations.filter(a => a.getCustomData('redaction'));
|
||||
|
||||
switch (this.viewModeService.viewMode) {
|
||||
case 'STANDARD': {
|
||||
this._setAnnotationsColor(redactions, 'annotationColor');
|
||||
const standardEntries = annotations.filter(a => a.getCustomData('changeLogRemoved') === 'false');
|
||||
const standardEntries = annotations
|
||||
.filter(a => a.getCustomData('changeLogRemoved') === 'false')
|
||||
.filter(a => !ocrAnnotationIds.includes(a.Id));
|
||||
const nonStandardEntries = annotations.filter(a => a.getCustomData('changeLogRemoved') === 'true');
|
||||
this._show(standardEntries);
|
||||
this._hide(nonStandardEntries);
|
||||
@ -225,10 +218,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
await this.ngOnInit();
|
||||
this._lastPage = previousRoute.queryParams.page;
|
||||
this._changeDetectorRef.markForCheck();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.ready = false;
|
||||
this._loadingService.start();
|
||||
await this.userPreferenceService.saveLastOpenedFileForDossier(this.dossierId, this.fileId);
|
||||
this._subscribeToFileUpdates();
|
||||
@ -403,6 +396,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
async viewerReady($event: WebViewerInstance) {
|
||||
this._instance = $event;
|
||||
this.ready = true;
|
||||
await this._stampPDF();
|
||||
await this._cleanupAndRedrawManualAnnotations();
|
||||
this._setExcludedPageStyles();
|
||||
|
||||
@ -27,9 +27,9 @@ export class DocumentInfoService {
|
||||
|
||||
fileAttributes$(fileId: string, dossierId: string, dossierTemplateId: string) {
|
||||
const getAttributes = () => this._fileAttributesService.getFileAttributeConfig(dossierTemplateId).fileAttributeConfigs;
|
||||
const dossierTemplateChange$ = this._dossierTemplatesService.entityChanged$.pipe(
|
||||
filter(template => template.dossierTemplateId === dossierTemplateId),
|
||||
);
|
||||
const dossierTemplateChange$ = this._dossierTemplatesService
|
||||
.getEntityChanged$(dossierTemplateId)
|
||||
.pipe(filter(template => template.dossierTemplateId === dossierTemplateId));
|
||||
const fileChange$ = this._filesMapService.watch$(dossierId, fileId);
|
||||
return merge(dossierTemplateChange$, fileChange$).pipe(
|
||||
map(getAttributes),
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
[itemSize]="85"
|
||||
[noDataText]="'search-screen.no-data' | translate"
|
||||
[noMatchText]="'search-screen.no-match' | translate"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
noDataIcon="iqser:search"
|
||||
></iqser-table>
|
||||
</div>
|
||||
@ -55,6 +56,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<redaction-initials-avatar [user]="item.assignee" [withName]="true"></redaction-initials-avatar>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<iqser-status-bar
|
||||
[configs]="[
|
||||
|
||||
@ -35,6 +35,7 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
|
||||
readonly tableHeaderLabel = _('search-screen.table-header');
|
||||
readonly tableColumnConfigs: TableColumnConfig<ISearchListItem>[] = [
|
||||
{ label: _('search-screen.cols.document'), width: '2fr' },
|
||||
{ label: _('search-screen.cols.assignee') },
|
||||
{ label: _('search-screen.cols.status') },
|
||||
{ label: _('search-screen.cols.dossier') },
|
||||
{ label: _('search-screen.cols.pages'), width: 'auto' },
|
||||
@ -134,6 +135,7 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
|
||||
unmatched: unmatchedTerms || null,
|
||||
highlights,
|
||||
status: file.workflowStatus,
|
||||
assignee: file.assignee,
|
||||
numberOfPages: file.numberOfPages,
|
||||
dossierName: this._dossiersService.find(dossierId).dossierName,
|
||||
filename: file.filename,
|
||||
|
||||
@ -88,10 +88,17 @@ export class AnnotationActionsService {
|
||||
'changeLegalBasis',
|
||||
$event,
|
||||
{ annotations, dossier: this._dossier(file) },
|
||||
(data: { comment: string; legalBasis: string }) => {
|
||||
(data: { comment: string; legalBasis: string; section: string; value: string }) => {
|
||||
annotations.forEach(annotation => {
|
||||
this._processObsAndEmit(
|
||||
this._manualAnnotationService.changeLegalBasis(annotation.annotationId, file, data.legalBasis, data.comment),
|
||||
this._manualAnnotationService.changeLegalBasis(
|
||||
annotation.annotationId,
|
||||
file,
|
||||
data.section,
|
||||
data.value,
|
||||
data.legalBasis,
|
||||
data.comment,
|
||||
),
|
||||
annotation,
|
||||
annotationsChanged,
|
||||
);
|
||||
@ -383,7 +390,7 @@ export class AnnotationActionsService {
|
||||
}
|
||||
|
||||
updateHiddenAnnotation(annotations: AnnotationWrapper[], viewerAnnotations: Annotation[], hidden: boolean) {
|
||||
const annotationId = (viewerAnnotations[0] as any).Dx;
|
||||
const annotationId = viewerAnnotations[0].Id;
|
||||
const annotationToBeUpdated = annotations.find((a: AnnotationWrapper) => a.annotationId === annotationId);
|
||||
annotationToBeUpdated.hidden = hidden;
|
||||
}
|
||||
|
||||
@ -180,7 +180,7 @@ export class AnnotationDrawService {
|
||||
(hideSkipped && annotationWrapper.isSkipped) ||
|
||||
annotationWrapper.isOCR ||
|
||||
annotationWrapper.hidden;
|
||||
annotation.setCustomData('redacto-manager', 'true');
|
||||
annotation.setCustomData('redact-manager', 'true');
|
||||
annotation.setCustomData('redaction', String(annotationWrapper.isRedacted));
|
||||
annotation.setCustomData('skipped', String(annotationWrapper.isSkipped));
|
||||
annotation.setCustomData('changeLog', String(annotationWrapper.isChangeLogEntry));
|
||||
|
||||
@ -102,11 +102,11 @@ export class ManualAnnotationService extends GenericService<IManualAddResponse>
|
||||
}
|
||||
|
||||
// /manualRedaction/request/legalBasis
|
||||
changeLegalBasis(annotationId: string, file: File, legalBasis: string, comment?: string) {
|
||||
changeLegalBasis(annotationId: string, file: File, section: string, value: string, legalBasis: string, comment?: string) {
|
||||
const mode: AnnotationActionMode = this._permissionsService.isApprover(this._dossier(file))
|
||||
? 'change-legal-basis'
|
||||
: 'request-change-legal-basis';
|
||||
return this._makeRequest(mode, file, { annotationId, legalBasis, comment });
|
||||
return this._makeRequest(mode, file, { annotationId, legalBasis, comment, section, value });
|
||||
}
|
||||
|
||||
// this wraps
|
||||
|
||||
@ -21,7 +21,6 @@ import {
|
||||
ConfirmationDialogInput,
|
||||
IqserTooltipPosition,
|
||||
LoadingService,
|
||||
Required,
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
@ -40,7 +39,7 @@ import { DocumentInfoService } from '../../../screens/file-preview-screen/servic
|
||||
import { ExpandableFileActionsComponent } from '@shared/components/expandable-file-actions/expandable-file-actions.component';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-file-actions',
|
||||
selector: 'redaction-file-actions [file] [type]',
|
||||
templateUrl: './file-actions.component.html',
|
||||
styleUrls: ['./file-actions.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@ -49,8 +48,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
|
||||
@Input() @Required() file: File;
|
||||
@Input() @Required() type: 'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow';
|
||||
@Input() file: File;
|
||||
@Input() type: 'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow';
|
||||
@Input() maxWidth: number;
|
||||
@Output() readonly ocredFile = new EventEmitter<void>();
|
||||
|
||||
@ -58,28 +57,30 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
|
||||
assignTooltip?: string;
|
||||
buttonType?: CircleButtonType;
|
||||
|
||||
showUndoApproval: boolean;
|
||||
showAssignToSelf: boolean;
|
||||
showAssign: boolean;
|
||||
showDelete: boolean;
|
||||
showOCR: boolean;
|
||||
canReanalyse: boolean;
|
||||
showUnderReview: boolean;
|
||||
showUnderApproval: boolean;
|
||||
showApprove: boolean;
|
||||
canToggleAnalysis: boolean;
|
||||
showStatusBar: boolean;
|
||||
showOpenDocument: boolean;
|
||||
showReanalyseFilePreview: boolean;
|
||||
showReanalyseDossierOverview: boolean;
|
||||
analysisForced: boolean;
|
||||
showUndoApproval = false;
|
||||
showAssignToSelf = false;
|
||||
showAssign = false;
|
||||
showDelete = false;
|
||||
showOCR = false;
|
||||
canReanalyse = false;
|
||||
showUnderReview = false;
|
||||
showUnderApproval = false;
|
||||
showApprove = false;
|
||||
canToggleAnalysis = false;
|
||||
showStatusBar = false;
|
||||
showOpenDocument = false;
|
||||
showReanalyseFilePreview = false;
|
||||
showReanalyseDossierOverview = false;
|
||||
analysisForced = false;
|
||||
isDossierOverview = false;
|
||||
isDossierOverviewList = false;
|
||||
isDossierOverviewWorkflow = false;
|
||||
isFilePreview = false;
|
||||
tooltipPosition: IqserTooltipPosition;
|
||||
buttons: Action[];
|
||||
@ViewChild(ExpandableFileActionsComponent) _expandableActionsComponent: ExpandableFileActionsComponent;
|
||||
|
||||
@ViewChild(ExpandableFileActionsComponent)
|
||||
private readonly _expandableActionsComponent: ExpandableFileActionsComponent;
|
||||
|
||||
constructor(
|
||||
@Optional() private readonly _excludedPagesService: ExcludedPagesService,
|
||||
@ -100,8 +101,9 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
|
||||
super();
|
||||
}
|
||||
|
||||
@HostBinding('class.keep-visible') get expanded() {
|
||||
return this._expandableActionsComponent?.expanded;
|
||||
@HostBinding('class.keep-visible')
|
||||
get expanded() {
|
||||
return !!this._expandableActionsComponent?.expanded;
|
||||
}
|
||||
|
||||
private get _toggleTooltip(): string {
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { List, shareDistinctLast } from '@iqser/common-ui';
|
||||
import { IsActiveMatchOptions } from '@angular/router';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { List } from '@iqser/common-ui';
|
||||
import { ActivatedRouteSnapshot, IsActiveMatchOptions, NavigationEnd, Router } from '@angular/router';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { filter, pluck } from 'rxjs/operators';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BreadcrumbTypes } from '@red/domain';
|
||||
|
||||
export type RouterLinkActiveOptions = { exact: boolean } | IsActiveMatchOptions;
|
||||
|
||||
@ -18,40 +23,79 @@ export type Breadcrumbs = List<Breadcrumb>;
|
||||
})
|
||||
export class BreadcrumbsService {
|
||||
readonly breadcrumbs$: Observable<Breadcrumbs>;
|
||||
readonly showGoBack$: Observable<boolean>;
|
||||
private readonly _showGoBack$ = new BehaviorSubject<boolean>(false);
|
||||
private readonly _store$ = new BehaviorSubject<Breadcrumbs>([]);
|
||||
|
||||
constructor() {
|
||||
constructor(
|
||||
private readonly _router: Router,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
) {
|
||||
this.breadcrumbs$ = this._store$.asObservable();
|
||||
this.showGoBack$ = this._showGoBack$.asObservable().pipe(shareDistinctLast());
|
||||
|
||||
this._router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
|
||||
const root = this._router.routerState.snapshot.root;
|
||||
this._clear();
|
||||
this._addBreadcrumbs(root.firstChild);
|
||||
});
|
||||
}
|
||||
|
||||
get breadcrumbs() {
|
||||
return this._store$.value;
|
||||
}
|
||||
|
||||
append(breadcrumb: Breadcrumb) {
|
||||
const existing = this._store$.value.find(item => item.routerLink.toString() === breadcrumb.routerLink.toString());
|
||||
if (existing) {
|
||||
this.remove(existing.routerLink);
|
||||
}
|
||||
private _append(breadcrumb: Breadcrumb) {
|
||||
this._store$.next([...this._store$.value, breadcrumb]);
|
||||
}
|
||||
|
||||
clear() {
|
||||
private _clear() {
|
||||
this._store$.next([]);
|
||||
}
|
||||
|
||||
remove(routerLink: string[]) {
|
||||
this._store$.next(this._store$.value.filter(item => item.routerLink.toString() !== routerLink.toString()));
|
||||
private _addBreadcrumbs(route: ActivatedRouteSnapshot) {
|
||||
if (route.firstChild) {
|
||||
this._addBreadcrumbs(route.firstChild);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const breadcrumb of route.data.breadcrumbs || []) {
|
||||
switch (breadcrumb) {
|
||||
case BreadcrumbTypes.main:
|
||||
this._addMainBreadcrumb();
|
||||
break;
|
||||
case BreadcrumbTypes.dossier:
|
||||
this._addDossierBreadcrumb(route);
|
||||
break;
|
||||
case BreadcrumbTypes.file:
|
||||
this._addFileBreadcrumb(route);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showGoBack() {
|
||||
this._showGoBack$.next(true);
|
||||
private _addMainBreadcrumb(): void {
|
||||
this._append({
|
||||
name$: of(this._translateService.instant('top-bar.navigation-items.dossiers')),
|
||||
routerLink: ['/main', 'dossiers'],
|
||||
routerLinkActiveOptions: { exact: true },
|
||||
});
|
||||
}
|
||||
|
||||
hideGoBack() {
|
||||
this._showGoBack$.next(false);
|
||||
private _addDossierBreadcrumb(route: ActivatedRouteSnapshot): void {
|
||||
const dossierId = route.paramMap.get('dossierId');
|
||||
this._append({
|
||||
name$: this._dossiersService.getEntityChanged$(dossierId).pipe(pluck('dossierName')),
|
||||
routerLink: ['/main', 'dossiers', dossierId],
|
||||
routerLinkActiveOptions: { exact: true },
|
||||
});
|
||||
}
|
||||
|
||||
private _addFileBreadcrumb(route: ActivatedRouteSnapshot): void {
|
||||
const dossierId = route.paramMap.get('dossierId');
|
||||
const fileId = route.paramMap.get('fileId');
|
||||
this._append({
|
||||
name$: this._filesMapService.watch$(dossierId, fileId).pipe(pluck('filename')),
|
||||
routerLink: ['/main', 'dossiers', dossierId, 'file', fileId],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,8 +70,11 @@ export class FilesMapService {
|
||||
}
|
||||
|
||||
replace(entity: File) {
|
||||
const all = this.get(entity.dossierId).filter(file => file.fileId !== entity.fileId);
|
||||
this.set(entity.dossierId, [...all, entity]);
|
||||
const existingFile = this.get(entity.dossierId).find(file => file.fileId === entity.fileId);
|
||||
if (existingFile.lastUpdated !== entity.lastUpdated) {
|
||||
const all = this.get(entity.dossierId).filter(file => file.fileId !== entity.fileId);
|
||||
this.set(entity.dossierId, [...all, entity]);
|
||||
}
|
||||
}
|
||||
|
||||
watch$(key: string, entityId: string): Observable<File> {
|
||||
|
||||
@ -2,7 +2,6 @@ import { Injectable, Injector } from '@angular/core';
|
||||
import { GenericService, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { IGeneralConfiguration } from '@red/domain';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@ -13,7 +12,7 @@ export class GeneralSettingsService extends GenericService<IGeneralConfiguration
|
||||
}
|
||||
|
||||
getGeneralConfigurations() {
|
||||
return this._userService.currentUser?.hasAnyREDRoles ? this._getOne(['general']) : of({ displayName: 'RedactManager' });
|
||||
return this._getOne(['general']);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
|
||||
@ -18,8 +18,12 @@ export class PermissionsService {
|
||||
return this.isApprover(dossier);
|
||||
}
|
||||
|
||||
canEditFileAttributes(dossier: Dossier, file: File): boolean {
|
||||
return ((file.isUnderReview || file.isNew) && this.isDossierMember(dossier)) || (file.isUnderApproval && this.isApprover(dossier));
|
||||
}
|
||||
|
||||
canToggleAnalysis(file: File): boolean {
|
||||
return this.isReviewerOrApprover(file) && (file.isNew || file.isUnderReview || file.isUnderApproval);
|
||||
return this.isFileAssignee(file) && (file.isNew || file.isUnderReview || file.isUnderApproval);
|
||||
}
|
||||
|
||||
canReanalyseFile(file: File | File[]): boolean {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { catchError, filter, mergeMapTo, take, tap } from 'rxjs/operators';
|
||||
import { catchError, filter, mergeMapTo, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { of } from 'rxjs';
|
||||
import { from, of, throwError } from 'rxjs';
|
||||
import { KeycloakEventType, KeycloakService } from 'keycloak-angular';
|
||||
import { GeneralSettingsService } from '@services/general-settings.service';
|
||||
import { LanguageService } from '@i18n/language.service';
|
||||
@ -19,14 +19,16 @@ export function configurationInitializer(
|
||||
keycloakService.keycloakEvents$
|
||||
.pipe(
|
||||
filter(event => event.type === KeycloakEventType.OnReady),
|
||||
switchMap(() => from(keycloakService.isLoggedIn())),
|
||||
switchMap(loggedIn => (!loggedIn ? throwError('Not Logged In') : of({}))),
|
||||
mergeMapTo(generalSettingsService.getGeneralConfigurations()),
|
||||
tap(configuration => configService.updateDisplayName(configuration.displayName)),
|
||||
tap(() => userPreferenceService.reload()),
|
||||
tap(() => languageService.chooseAndSetInitialLanguage()),
|
||||
catchError(() => {
|
||||
title.setTitle('RedactManager');
|
||||
return of({});
|
||||
}),
|
||||
tap(() => userPreferenceService.reload()),
|
||||
tap(() => languageService.chooseAndSetInitialLanguage()),
|
||||
take(1),
|
||||
)
|
||||
.toPromise();
|
||||
|
||||
@ -41,7 +41,7 @@ export function getFirstRelevantTextPart(text, direction: 'FORWARD' | 'BACKWARD'
|
||||
|
||||
accumulator += char;
|
||||
|
||||
return spaceCount >= 2;
|
||||
return spaceCount >= 3;
|
||||
};
|
||||
|
||||
if (direction === 'FORWARD') {
|
||||
|
||||
@ -359,6 +359,8 @@
|
||||
"comment": "Comment",
|
||||
"legalBasis": "Legal Basis",
|
||||
"reason": "Select redaction reason",
|
||||
"section": "Paragraph / Location",
|
||||
"classification": "Value / Classification",
|
||||
"reason-placeholder": "Select a reason..."
|
||||
},
|
||||
"header": "Edit Redaction Reason"
|
||||
@ -1258,6 +1260,8 @@
|
||||
"dictionary": "Dictionary",
|
||||
"legalBasis": "Legal Basis",
|
||||
"reason": "Reason",
|
||||
"section": "Paragraph / Location",
|
||||
"classification": "Value / Classification",
|
||||
"reason-placeholder": "Select a reason ...",
|
||||
"rectangle": "Custom Rectangle",
|
||||
"text": "Selected text:"
|
||||
@ -1488,6 +1492,7 @@
|
||||
"cols": {
|
||||
"document": "Document",
|
||||
"dossier": "Dossier",
|
||||
"assignee": "Assignee",
|
||||
"pages": "Pages",
|
||||
"status": "Status"
|
||||
},
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit a82a08cd89e33325a789f5b433cd80adc8aa5e59
|
||||
Subproject commit caf4838be63574740e5380c74ad96fe21f7a456b
|
||||
@ -104,7 +104,7 @@ export class File extends Entity<IFile> implements IFile {
|
||||
this.isNew = this.workflowStatus === WorkflowFileStatuses.NEW;
|
||||
this.isUnderReview = this.workflowStatus === WorkflowFileStatuses.UNDER_REVIEW;
|
||||
this.isUnderApproval = this.workflowStatus === WorkflowFileStatuses.UNDER_APPROVAL;
|
||||
this.canBeApproved = !this.analysisRequired && !this.hasSuggestions;
|
||||
this.canBeApproved = !this.analysisRequired && !this.hasSuggestions && !this.isProcessing && !this.isError;
|
||||
this.canBeOpened = !this.isError && !this.isPending && this.numberOfAnalyses > 0;
|
||||
this.canBeOCRed = !this.excluded && !this.lastOCRTime && (this.isNew || this.isUnderReview || this.isUnderApproval);
|
||||
|
||||
|
||||
@ -10,4 +10,6 @@ export interface IAddRedactionRequest {
|
||||
reason?: string;
|
||||
type?: string;
|
||||
value?: string;
|
||||
section?: string;
|
||||
rectangle?: boolean;
|
||||
}
|
||||
|
||||
@ -16,4 +16,5 @@ export interface IManualRedactionEntry {
|
||||
type?: string;
|
||||
user?: string;
|
||||
value?: string;
|
||||
rectangle?: boolean;
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ export interface IRedactionLogEntry {
|
||||
engines?: List<LogEntryEngine>;
|
||||
excluded?: boolean;
|
||||
hint?: boolean;
|
||||
rectangle?: boolean;
|
||||
id?: string;
|
||||
image?: boolean;
|
||||
imageHasTransparency?: boolean;
|
||||
|
||||
@ -3,6 +3,7 @@ import { IListable, List } from '@iqser/common-ui';
|
||||
export interface ISearchListItem extends IListable {
|
||||
readonly dossierId: string;
|
||||
readonly filename: string;
|
||||
readonly assignee: string;
|
||||
readonly unmatched: List | null;
|
||||
readonly highlights: Record<string, List>;
|
||||
readonly routerLink: string;
|
||||
|
||||
7
libs/red-domain/src/lib/shared/breadcrumb-types.ts
Normal file
7
libs/red-domain/src/lib/shared/breadcrumb-types.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export type BreadcrumbType = 'main' | 'dossier' | 'file';
|
||||
|
||||
export const BreadcrumbTypes = {
|
||||
main: 'main' as BreadcrumbType,
|
||||
dossier: 'dossier' as BreadcrumbType,
|
||||
file: 'file' as BreadcrumbType,
|
||||
};
|
||||
@ -1,4 +1,5 @@
|
||||
export * from './sorters/status-sorter';
|
||||
export * from './breadcrumb-types';
|
||||
export * from './types';
|
||||
export * from './rules';
|
||||
export * from './watermark';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "redaction",
|
||||
"version": "3.103.0",
|
||||
"version": "3.105.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user