Merge branch 'master' into VM/RED-2614

This commit is contained in:
Valentin 2021-12-15 10:30:30 +02:00
commit 4c076ffdaa
65 changed files with 303 additions and 259 deletions

View File

@ -1,4 +1,7 @@
{
"cli": {
"analytics": "4b8eed12-a1e6-4b7a-9ea2-925b27941271"
},
"version": 1,
"projects": {
"common-ui": {

View File

@ -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'],

View File

@ -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) {}
}

View File

@ -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';

View File

@ -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>

View File

@ -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>

View File

@ -8,6 +8,7 @@
[itemSize]="80"
[noDataText]="'downloads-list.no-data.title' | translate"
[selectionEnabled]="true"
[tableColumnConfigs]="tableColumnConfigs"
noDataIcon="iqser:download"
></iqser-table>
</div>

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -13,7 +13,6 @@ export class ManualRedactionEntryWrapper {
readonly quads: any,
readonly manualRedactionEntry: IManualRedactionEntry,
readonly type: ManualRedactionEntryType,
readonly annotationType: 'TEXT' | 'RECTANGLE' = 'TEXT',
readonly rectId?: string,
) {}
}

View File

@ -10,6 +10,7 @@ export interface RedactionLogEntryWrapper {
reference?: Array<string>;
startOffset?: number;
type?: string;
rectangle?: boolean;
color?: Array<number>;
dictionaryEntry?: boolean;

View File

@ -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>

View File

@ -25,6 +25,7 @@
[itemSize]="80"
[noDataIcon]="'iqser:document'"
[noDataText]="'audit-screen.no-data.title' | translate"
[tableColumnConfigs]="tableColumnConfigs"
[totalSize]="logs?.totalHits || 0"
>
</iqser-table>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -4,6 +4,7 @@
[itemSize]="80"
[noDataText]="'justifications-listing.no-data.title' | translate"
[selectionEnabled]="true"
[tableColumnConfigs]="tableColumnConfigs"
noDataIcon="iqser:document"
></iqser-table>

View File

@ -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>

View File

@ -37,6 +37,7 @@
[itemSize]="80"
[noMatchText]="'user-listing.no-match.title' | translate"
[selectionEnabled]="true"
[tableColumnConfigs]="tableColumnConfigs"
emptyColumnWidth="1fr"
></iqser-table>
</div>

View File

@ -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>

View File

@ -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,
});
}
}

View File

@ -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>

View File

@ -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) {

View File

@ -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());
});

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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] },
},
];

View File

@ -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>

View File

@ -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();

View File

@ -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>

View File

@ -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"

View File

@ -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();

View File

@ -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;
}

View File

@ -79,6 +79,7 @@
*ngIf="displayPdfViewer"
[annotations]="annotations"
[canPerformActions]="canPerformAnnotationActions$ | async"
[class.hidden]="!ready"
[dossier]="dossier"
[fileData]="fileData?.fileData"
[file]="file"

View File

@ -62,3 +62,7 @@
max-width: 400px;
width: 400px;
}
redaction-pdf-viewer.hidden {
visibility: hidden;
}

View File

@ -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();

View File

@ -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),

View File

@ -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]="[

View File

@ -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,

View File

@ -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;
}

View File

@ -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));

View File

@ -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

View File

@ -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 {

View File

@ -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],
});
}
}

View File

@ -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> {

View 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()

View File

@ -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 {

View File

@ -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();

View File

@ -41,7 +41,7 @@ export function getFirstRelevantTextPart(text, direction: 'FORWARD' | 'BACKWARD'
accumulator += char;
return spaceCount >= 2;
return spaceCount >= 3;
};
if (direction === 'FORWARD') {

View File

@ -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

View File

@ -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);

View File

@ -10,4 +10,6 @@ export interface IAddRedactionRequest {
reason?: string;
type?: string;
value?: string;
section?: string;
rectangle?: boolean;
}

View File

@ -16,4 +16,5 @@ export interface IManualRedactionEntry {
type?: string;
user?: string;
value?: string;
rectangle?: boolean;
}

View File

@ -14,6 +14,7 @@ export interface IRedactionLogEntry {
engines?: List<LogEntryEngine>;
excluded?: boolean;
hint?: boolean;
rectangle?: boolean;
id?: string;
image?: boolean;
imageHasTransparency?: boolean;

View File

@ -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;

View 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,
};

View File

@ -1,4 +1,5 @@
export * from './sorters/status-sorter';
export * from './breadcrumb-types';
export * from './types';
export * from './rules';
export * from './watermark';

View File

@ -1,6 +1,6 @@
{
"name": "redaction",
"version": "3.103.0",
"version": "3.105.0",
"private": true,
"license": "MIT",
"scripts": {

Binary file not shown.