Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ffdfe1029 | ||
|
|
4ecd8f34dd | ||
|
|
478a661ca4 | ||
|
|
e72ba642e2 | ||
|
|
d9820bd0fd | ||
|
|
fd96d827c1 | ||
|
|
2b499dbf57 | ||
|
|
a19692dae6 | ||
|
|
a5e41616cd | ||
|
|
23f5fbc615 | ||
|
|
b2a96433d3 | ||
|
|
c90b362e8e | ||
|
|
46a787ce71 | ||
|
|
2f38c2b491 | ||
|
|
392ac7c4b1 | ||
|
|
907245149a | ||
|
|
91d65750c1 | ||
|
|
d158d41a10 | ||
|
|
0095379584 | ||
|
|
26b0d36e7f | ||
|
|
a0bff7a4b5 | ||
|
|
cc215aed12 | ||
|
|
1d2351dba1 | ||
|
|
42fe183170 | ||
|
|
ae27842ceb | ||
|
|
4ef7ae71d4 | ||
|
|
964227811c | ||
|
|
2738b94913 | ||
|
|
ff129b64ca | ||
|
|
313eeef0ae | ||
|
|
9232d426a9 | ||
|
|
8a9ab32188 | ||
|
|
bda45f208f | ||
|
|
d9c607ffb3 | ||
|
|
400ad29bf2 | ||
|
|
eaf908bc7c | ||
|
|
7fbe0f8398 | ||
|
|
a2b8541e8e | ||
|
|
ed234ec5d6 | ||
|
|
9982b8c37f | ||
|
|
b6d2c7878f | ||
|
|
24cabac3fc | ||
|
|
a957cb6226 | ||
|
|
9deaa08d5a | ||
|
|
9d6e0f35cf | ||
|
|
55f62e7197 | ||
|
|
f0ce6aadb1 | ||
|
|
b314d6fb17 | ||
|
|
b1615b7309 | ||
|
|
6da85dae52 | ||
|
|
83afc3d578 | ||
|
|
ea7033b394 | ||
|
|
be143566a6 | ||
|
|
7920d3d5fd | ||
|
|
1240a1ef99 | ||
|
|
456ff25508 | ||
|
|
be5622ce9e | ||
|
|
0ab411b8ae | ||
|
|
0b31b731f5 | ||
|
|
f6eefde596 | ||
|
|
bbedf1e2cb | ||
|
|
aac3303101 | ||
|
|
9c1245b7e9 | ||
|
|
fe24b1ebdc | ||
|
|
f79519785c | ||
|
|
1a2ddcfaaa | ||
|
|
0f89b1954a | ||
|
|
504fc8aa43 | ||
|
|
5b4ae97503 | ||
|
|
e0b9cfcb05 | ||
|
|
791b584df9 | ||
|
|
152ce8d9b2 | ||
|
|
ad4c6c458d | ||
|
|
a008f8b5d1 | ||
|
|
a9719d8465 | ||
|
|
fada310046 | ||
|
|
12ae0f5fb2 | ||
|
|
24ecfbd81f | ||
|
|
562ae83f73 | ||
|
|
3512e51341 | ||
|
|
d1fe3b3181 | ||
|
|
aa8c2114bc | ||
|
|
a85a7e4298 | ||
|
|
d450c5c99f | ||
|
|
856563999b | ||
|
|
9b21e6872b | ||
|
|
1d6d9a97d4 | ||
|
|
7d72c78b11 | ||
|
|
7345991f03 | ||
|
|
cff765f478 | ||
|
|
26186e6bf7 |
@ -1,7 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouteReuseStrategy, RouterModule } from '@angular/router';
|
import { RouteReuseStrategy, RouterModule } from '@angular/router';
|
||||||
import { ifNotLoggedIn } from '@common-ui/tenants/guards/if-not-logged-in.guard';
|
import { ifNotLoggedIn } from '@common-ui/tenants/guards/if-not-logged-in.guard';
|
||||||
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
|
|
||||||
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
|
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
|
||||||
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
|
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
|
||||||
import { DashboardGuard } from '@guards/dashboard-guard.service';
|
import { DashboardGuard } from '@guards/dashboard-guard.service';
|
||||||
@ -23,6 +22,7 @@ import { Roles } from '@users/roles';
|
|||||||
import { mainGuard } from '@utils/main.guard';
|
import { mainGuard } from '@utils/main.guard';
|
||||||
import { webViewerLoadedGuard } from './modules/pdf-viewer/services/webviewer-loaded.guard';
|
import { webViewerLoadedGuard } from './modules/pdf-viewer/services/webviewer-loaded.guard';
|
||||||
import { ACTIVE_DOSSIERS_SERVICE } from './tokens';
|
import { ACTIVE_DOSSIERS_SERVICE } from './tokens';
|
||||||
|
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
|
||||||
|
|
||||||
const dossierTemplateIdRoutes: IqserRoutes = [
|
const dossierTemplateIdRoutes: IqserRoutes = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { BrowserModule } from '@angular/platform-browser';
|
|||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||||
import { GET_TENANT_FROM_PATH_FN, UI_ROOT } from '@common-ui/utils';
|
import { GET_TENANT_FROM_PATH_FN, UI_ROOT } from '@common-ui/utils';
|
||||||
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
|
|
||||||
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
|
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
|
||||||
import { BreadcrumbsComponent } from '@components/breadcrumbs/breadcrumbs.component';
|
import { BreadcrumbsComponent } from '@components/breadcrumbs/breadcrumbs.component';
|
||||||
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
|
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
|
||||||
@ -69,6 +68,7 @@ import { AppRoutingModule } from './app-routing.module';
|
|||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module';
|
import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module';
|
||||||
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
|
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
|
||||||
|
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
|
||||||
|
|
||||||
export const appModuleFactory = (config: AppConfig) => {
|
export const appModuleFactory = (config: AppConfig) => {
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@ -2,17 +2,17 @@
|
|||||||
<p *ngIf="!adminName && !adminUrl" class="heading-xl" translate="auth-error.heading"></p>
|
<p *ngIf="!adminName && !adminUrl" class="heading-xl" translate="auth-error.heading"></p>
|
||||||
<p
|
<p
|
||||||
*ngIf="adminName && adminUrl"
|
*ngIf="adminName && adminUrl"
|
||||||
[innerHTML]="'auth-error.heading-with-name-and-link' | translate : { adminName: adminName, adminUrl: adminUrl }"
|
[innerHTML]="'auth-error.heading-with-name-and-link' | translate: { adminName: adminName, adminUrl: adminUrl }"
|
||||||
class="heading-xl"
|
class="heading-xl"
|
||||||
></p>
|
></p>
|
||||||
<p
|
<p
|
||||||
*ngIf="adminName && !adminUrl"
|
*ngIf="adminName && !adminUrl"
|
||||||
[innerHTML]="'auth-error.heading-with-name' | translate : { adminName: adminName }"
|
[innerHTML]="'auth-error.heading-with-name' | translate: { adminName: adminName }"
|
||||||
class="heading-xl"
|
class="heading-xl"
|
||||||
></p>
|
></p>
|
||||||
<p
|
<p
|
||||||
*ngIf="!adminName && adminUrl"
|
*ngIf="!adminName && adminUrl"
|
||||||
[innerHTML]="'auth-error.heading-with-link' | translate : { adminName: adminName }"
|
[innerHTML]="'auth-error.heading-with-link' | translate: { adminName: adminName }"
|
||||||
class="heading-xl"
|
class="heading-xl"
|
||||||
></p>
|
></p>
|
||||||
<a (click)="userService.logout()" translate="auth-error.logout"></a>
|
<a (click)="userService.logout()" translate="auth-error.logout"></a>
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export class DossierFilesGuard implements CanActivate {
|
|||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
if (!this._dictionaryMapService.has(dossierId) && !this.isDocumine) {
|
if (!this._dictionaryMapService.has(dossierId) && !this.isDocumine) {
|
||||||
const dictionaryPromise = this._dictionaryService.loadDossierRedaction(dossierTemplateId, dossierId);
|
const dictionaryPromise = firstValueFrom(this._dictionaryService.loadDictionaryDataForDossier(dossierTemplateId, dossierId));
|
||||||
promises.push(dictionaryPromise);
|
promises.push(dictionaryPromise);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,10 @@ export const canForceRedaction = (annotation: AnnotationWrapper, canAddRedaction
|
|||||||
export const canAcceptRecommendation = (annotation: AnnotationWrapper) => annotation.isRecommendation && !annotation.pending;
|
export const canAcceptRecommendation = (annotation: AnnotationWrapper) => annotation.isRecommendation && !annotation.pending;
|
||||||
|
|
||||||
export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) =>
|
export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) =>
|
||||||
annotation.canBeMarkedAsFalsePositive && !annotation.hasBeenForcedRedaction && annotationEntity?.hasDictionary;
|
annotation.canBeMarkedAsFalsePositive &&
|
||||||
|
!annotation.hasBeenForcedRedaction &&
|
||||||
|
!annotation.hasBeenResizedLocally &&
|
||||||
|
annotationEntity?.hasDictionary;
|
||||||
|
|
||||||
export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean, autoAnalysisDisabled: boolean) =>
|
export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean, autoAnalysisDisabled: boolean) =>
|
||||||
canAddRedaction &&
|
canAddRedaction &&
|
||||||
@ -24,7 +27,8 @@ export const canRemoveFromDictionary = (annotation: AnnotationWrapper, autoAnaly
|
|||||||
annotation.isModifyDictionary &&
|
annotation.isModifyDictionary &&
|
||||||
(annotation.isRedacted || annotation.isSkipped || annotation.isHint || (annotation.isIgnoredHint && !annotation.isRuleBased)) &&
|
(annotation.isRedacted || annotation.isSkipped || annotation.isHint || (annotation.isIgnoredHint && !annotation.isRuleBased)) &&
|
||||||
(autoAnalysisDisabled || !annotation.pending) &&
|
(autoAnalysisDisabled || !annotation.pending) &&
|
||||||
!annotation.hasBeenResized;
|
!annotation.hasBeenResizedLocally &&
|
||||||
|
!(annotation.hasBeenForcedHint || annotation.hasBeenForcedRedaction);
|
||||||
|
|
||||||
export const canRemoveRedaction = (annotation: AnnotationWrapper, permissions: AnnotationPermissions) =>
|
export const canRemoveRedaction = (annotation: AnnotationWrapper, permissions: AnnotationPermissions) =>
|
||||||
(!annotation.isIgnoredHint || !annotation.isRuleBased) &&
|
(!annotation.isIgnoredHint || !annotation.isRuleBased) &&
|
||||||
@ -54,8 +58,15 @@ export const canResizeAnnotation = (
|
|||||||
(!annotation.isHint && hasDictionary && annotation.isRuleBased))) ||
|
(!annotation.isHint && hasDictionary && annotation.isRuleBased))) ||
|
||||||
annotation.isRecommendation;
|
annotation.isRecommendation;
|
||||||
|
|
||||||
|
export const canResizeInDictionary = (annotation: AnnotationWrapper, permissions: AnnotationPermissions) =>
|
||||||
|
permissions.canResizeAnnotation &&
|
||||||
|
annotation.isModifyDictionary &&
|
||||||
|
!annotation.hasBeenResizedLocally &&
|
||||||
|
!(annotation.hasBeenForcedHint || annotation.hasBeenForcedRedaction);
|
||||||
|
|
||||||
export const canEditAnnotation = (annotation: AnnotationWrapper) => (annotation.isRedacted || annotation.isSkipped) && !annotation.isImage;
|
export const canEditAnnotation = (annotation: AnnotationWrapper) => (annotation.isRedacted || annotation.isSkipped) && !annotation.isImage;
|
||||||
|
|
||||||
export const canEditHint = (annotation: AnnotationWrapper) => (annotation.isHint && !annotation.isRuleBased) || annotation.isIgnoredHint;
|
export const canEditHint = (annotation: AnnotationWrapper) =>
|
||||||
|
((annotation.isHint && !annotation.isRuleBased) || annotation.isIgnoredHint) && !annotation.isImage;
|
||||||
|
|
||||||
export const canEditImage = (annotation: AnnotationWrapper) => annotation.isImage;
|
export const canEditImage = (annotation: AnnotationWrapper) => annotation.isImage;
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
canRemoveOnlyHere,
|
canRemoveOnlyHere,
|
||||||
canRemoveRedaction,
|
canRemoveRedaction,
|
||||||
canResizeAnnotation,
|
canResizeAnnotation,
|
||||||
|
canResizeInDictionary,
|
||||||
canUndo,
|
canUndo,
|
||||||
} from './annotation-permissions.utils';
|
} from './annotation-permissions.utils';
|
||||||
import { AnnotationWrapper } from './annotation.wrapper';
|
import { AnnotationWrapper } from './annotation.wrapper';
|
||||||
@ -30,6 +31,7 @@ export class AnnotationPermissions {
|
|||||||
canForceRedaction = true;
|
canForceRedaction = true;
|
||||||
canChangeLegalBasis = true;
|
canChangeLegalBasis = true;
|
||||||
canResizeAnnotation = true;
|
canResizeAnnotation = true;
|
||||||
|
canResizeInDictionary = true;
|
||||||
canRecategorizeAnnotation = true;
|
canRecategorizeAnnotation = true;
|
||||||
canForceHint = true;
|
canForceHint = true;
|
||||||
canEditAnnotations = true;
|
canEditAnnotations = true;
|
||||||
@ -69,6 +71,7 @@ export class AnnotationPermissions {
|
|||||||
autoAnalysisDisabled,
|
autoAnalysisDisabled,
|
||||||
annotationEntity?.hasDictionary,
|
annotationEntity?.hasDictionary,
|
||||||
);
|
);
|
||||||
|
permissions.canResizeInDictionary = canResizeInDictionary(annotation, permissions);
|
||||||
permissions.canEditAnnotations = canEditAnnotation(annotation);
|
permissions.canEditAnnotations = canEditAnnotation(annotation);
|
||||||
permissions.canEditHints = canEditHint(annotation);
|
permissions.canEditHints = canEditHint(annotation);
|
||||||
permissions.canEditImages = canEditImage(annotation);
|
permissions.canEditImages = canEditImage(annotation);
|
||||||
@ -80,6 +83,7 @@ export class AnnotationPermissions {
|
|||||||
static reduce(permissions: AnnotationPermissions[]): AnnotationPermissions {
|
static reduce(permissions: AnnotationPermissions[]): AnnotationPermissions {
|
||||||
const result = new AnnotationPermissions();
|
const result = new AnnotationPermissions();
|
||||||
result.canResizeAnnotation = permissions.length === 1 && permissions[0].canResizeAnnotation;
|
result.canResizeAnnotation = permissions.length === 1 && permissions[0].canResizeAnnotation;
|
||||||
|
result.canResizeInDictionary = permissions.length === 1 && permissions[0].canResizeInDictionary;
|
||||||
result.canChangeLegalBasis = permissions.reduce((acc, next) => acc && next.canChangeLegalBasis, true);
|
result.canChangeLegalBasis = permissions.reduce((acc, next) => acc && next.canChangeLegalBasis, true);
|
||||||
result.canRecategorizeAnnotation = permissions.reduce((acc, next) => acc && next.canRecategorizeAnnotation, true);
|
result.canRecategorizeAnnotation = permissions.reduce((acc, next) => acc && next.canRecategorizeAnnotation, true);
|
||||||
result.canRemoveFromDictionary = permissions.reduce((acc, next) => acc && next.canRemoveFromDictionary, true);
|
result.canRemoveFromDictionary = permissions.reduce((acc, next) => acc && next.canRemoveFromDictionary, true);
|
||||||
|
|||||||
@ -25,7 +25,6 @@ import {
|
|||||||
SuperTypes,
|
SuperTypes,
|
||||||
} from '@red/domain';
|
} from '@red/domain';
|
||||||
import { annotationTypesTranslations } from '@translations/annotation-types-translations';
|
import { annotationTypesTranslations } from '@translations/annotation-types-translations';
|
||||||
import { log } from '@common-ui/utils';
|
|
||||||
|
|
||||||
export class AnnotationWrapper implements IListable {
|
export class AnnotationWrapper implements IListable {
|
||||||
id: string;
|
id: string;
|
||||||
@ -58,6 +57,7 @@ export class AnnotationWrapper implements IListable {
|
|||||||
isChangeLogEntry = false;
|
isChangeLogEntry = false;
|
||||||
engines: LogEntryEngine[] = [];
|
engines: LogEntryEngine[] = [];
|
||||||
hasBeenResized: boolean;
|
hasBeenResized: boolean;
|
||||||
|
hasBeenResizedLocally: boolean;
|
||||||
hasBeenRecategorized: boolean;
|
hasBeenRecategorized: boolean;
|
||||||
hasLegalBasisChanged: boolean;
|
hasLegalBasisChanged: boolean;
|
||||||
hasBeenForcedHint: boolean;
|
hasBeenForcedHint: boolean;
|
||||||
@ -65,12 +65,18 @@ export class AnnotationWrapper implements IListable {
|
|||||||
hasBeenRemovedByManualOverride: boolean;
|
hasBeenRemovedByManualOverride: boolean;
|
||||||
isRemoved = false;
|
isRemoved = false;
|
||||||
isRemovedLocally = false;
|
isRemovedLocally = false;
|
||||||
|
hiddenInWorkload = false;
|
||||||
lastManualChange: ManualRedactionType;
|
lastManualChange: ManualRedactionType;
|
||||||
|
entry: IEntityLogEntry;
|
||||||
|
|
||||||
get isRuleBased() {
|
get isRuleBased() {
|
||||||
return this.engines.includes(LogEntryEngines.RULE);
|
return this.engines.includes(LogEntryEngines.RULE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isDictBased() {
|
||||||
|
return this.engines.includes(LogEntryEngines.DICTIONARY);
|
||||||
|
}
|
||||||
|
|
||||||
get searchKey(): string {
|
get searchKey(): string {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
@ -89,7 +95,7 @@ export class AnnotationWrapper implements IListable {
|
|||||||
|
|
||||||
get canBeMarkedAsFalsePositive() {
|
get canBeMarkedAsFalsePositive() {
|
||||||
return (
|
return (
|
||||||
(this.isRecommendation || this.superType === SuperTypes.Redaction) &&
|
(this.isRecommendation || this.superType === SuperTypes.Redaction || (this.isSkipped && this.isDictBased)) &&
|
||||||
!this.isImage &&
|
!this.isImage &&
|
||||||
!this.imported &&
|
!this.imported &&
|
||||||
!this.pending &&
|
!this.pending &&
|
||||||
@ -218,7 +224,8 @@ export class AnnotationWrapper implements IListable {
|
|||||||
) {
|
) {
|
||||||
const annotationWrapper = new AnnotationWrapper();
|
const annotationWrapper = new AnnotationWrapper();
|
||||||
|
|
||||||
annotationWrapper.id = logEntry.id;
|
annotationWrapper.pending = logEntry.state === EntryStates.PENDING;
|
||||||
|
annotationWrapper.id = logEntry.id + (annotationWrapper.pending ? '-pending' : '');
|
||||||
annotationWrapper.isChangeLogEntry = logEntry.state === EntryStates.REMOVED || !!changeLogType;
|
annotationWrapper.isChangeLogEntry = logEntry.state === EntryStates.REMOVED || !!changeLogType;
|
||||||
annotationWrapper.type = logEntry.type;
|
annotationWrapper.type = logEntry.type;
|
||||||
annotationWrapper.value = logEntry.value;
|
annotationWrapper.value = logEntry.value;
|
||||||
@ -247,20 +254,20 @@ export class AnnotationWrapper implements IListable {
|
|||||||
annotationWrapper.section = logEntry.section;
|
annotationWrapper.section = logEntry.section;
|
||||||
annotationWrapper.reference = logEntry.reference || [];
|
annotationWrapper.reference = logEntry.reference || [];
|
||||||
annotationWrapper.hasBeenResized = !!logEntry.manualChanges?.find(c => c.manualRedactionType === ManualRedactionTypes.RESIZE);
|
annotationWrapper.hasBeenResized = !!logEntry.manualChanges?.find(c => c.manualRedactionType === ManualRedactionTypes.RESIZE);
|
||||||
|
annotationWrapper.hasBeenResizedLocally =
|
||||||
|
annotationWrapper.hasBeenResized && annotationWrapper.engines.includes(LogEntryEngines.MANUAL);
|
||||||
annotationWrapper.hasBeenRecategorized = !!logEntry.manualChanges?.find(
|
annotationWrapper.hasBeenRecategorized = !!logEntry.manualChanges?.find(
|
||||||
c => c.manualRedactionType === ManualRedactionTypes.RECATEGORIZE,
|
c => c.manualRedactionType === ManualRedactionTypes.RECATEGORIZE,
|
||||||
);
|
);
|
||||||
annotationWrapper.hasLegalBasisChanged = !!logEntry.manualChanges?.find(
|
annotationWrapper.hasLegalBasisChanged = !!logEntry.manualChanges?.find(
|
||||||
c => c.manualRedactionType === ManualRedactionTypes.LEGAL_BASIS_CHANGE,
|
c => c.manualRedactionType === ManualRedactionTypes.LEGAL_BASIS_CHANGE,
|
||||||
);
|
);
|
||||||
annotationWrapper.hasBeenForcedHint = !!logEntry.manualChanges?.find(
|
annotationWrapper.hasBeenForcedHint = !!logEntry.manualChanges?.find(c => c.manualRedactionType === ManualRedactionTypes.FORCE);
|
||||||
c => c.manualRedactionType === ManualRedactionTypes.FORCE_HINT,
|
|
||||||
);
|
|
||||||
annotationWrapper.hasBeenForcedRedaction = !!logEntry.manualChanges?.find(
|
annotationWrapper.hasBeenForcedRedaction = !!logEntry.manualChanges?.find(
|
||||||
c => c.manualRedactionType === ManualRedactionTypes.FORCE_REDACT,
|
c => c.manualRedactionType === ManualRedactionTypes.FORCE,
|
||||||
);
|
);
|
||||||
annotationWrapper.hasBeenRemovedByManualOverride = !!logEntry.manualChanges?.find(
|
annotationWrapper.hasBeenRemovedByManualOverride = !!logEntry.manualChanges?.find(
|
||||||
c => c.manualRedactionType === ManualRedactionTypes.REMOVE_LOCALLY,
|
c => c.manualRedactionType === ManualRedactionTypes.REMOVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
const content = this.#createContent(annotationWrapper, logEntry, isDocumine);
|
const content = this.#createContent(annotationWrapper, logEntry, isDocumine);
|
||||||
@ -269,19 +276,25 @@ export class AnnotationWrapper implements IListable {
|
|||||||
|
|
||||||
const lastRelevantManualChange = logEntry.manualChanges?.at(-1);
|
const lastRelevantManualChange = logEntry.manualChanges?.at(-1);
|
||||||
annotationWrapper.lastManualChange = lastRelevantManualChange?.manualRedactionType;
|
annotationWrapper.lastManualChange = lastRelevantManualChange?.manualRedactionType;
|
||||||
annotationWrapper.pending = lastRelevantManualChange && !lastRelevantManualChange.processed;
|
|
||||||
annotationWrapper.superType = SuperTypeMapper[logEntry.entryType][logEntry.state](logEntry);
|
annotationWrapper.superType = SuperTypeMapper[logEntry.entryType][logEntry.state](logEntry);
|
||||||
annotationWrapper.superTypeLabel = annotationTypesTranslations[annotationWrapper.superType];
|
annotationWrapper.superTypeLabel = annotationTypesTranslations[annotationWrapper.superType];
|
||||||
|
|
||||||
annotationWrapper.isRemoved = logEntry.state === EntryStates.REMOVED;
|
annotationWrapper.isRemoved = logEntry.state === EntryStates.REMOVED;
|
||||||
annotationWrapper.isRemovedLocally = lastRelevantManualChange?.manualRedactionType === ManualRedactionTypes.REMOVE_LOCALLY;
|
annotationWrapper.isRemovedLocally =
|
||||||
|
lastRelevantManualChange?.manualRedactionType === ManualRedactionTypes.REMOVE &&
|
||||||
|
logEntry.engines.includes(LogEntryEngines.MANUAL);
|
||||||
|
|
||||||
annotationWrapper.typeLabel = dictionary?.virtual ? undefined : dictionary?.label;
|
annotationWrapper.typeLabel = dictionary?.virtual ? undefined : dictionary?.label;
|
||||||
|
|
||||||
const colorKey = annotationEntityColorConfig[annotationWrapper.superType];
|
if (annotationWrapper.pending) {
|
||||||
const defaultColor = annotationDefaultColorConfig[annotationWrapper.superType];
|
annotationWrapper.color = defaultColors[annotationDefaultColorConfig.analysis] as string;
|
||||||
annotationWrapper.color = dictionary ? (dictionary[colorKey] as string) : (defaultColors[defaultColor] as string);
|
} else {
|
||||||
annotationWrapper['entry'] = logEntry;
|
const colorKey = annotationEntityColorConfig[annotationWrapper.superType];
|
||||||
|
const defaultColor = annotationDefaultColorConfig[annotationWrapper.superType];
|
||||||
|
annotationWrapper.color = dictionary ? (dictionary[colorKey] as string) : (defaultColors[defaultColor] as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
annotationWrapper.entry = logEntry;
|
||||||
|
|
||||||
return annotationWrapper;
|
return annotationWrapper;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group">
|
<div class="iqser-input-group">
|
||||||
<a [href]="changePasswordUrl" target="_blank"> {{ 'user-profile-screen.actions.change-password' | translate }}</a>
|
<a (click)="resetPassword()" target="_blank"> {{ 'user-profile-screen.actions.change-password' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="devMode" class="iqser-input-group">
|
<div *ngIf="devMode" class="iqser-input-group">
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { BaseFormComponent, getConfig, IqserPermissionsService, LanguageService, LoadingService, Toaster } from '@iqser/common-ui';
|
import { BaseFormComponent, IqserPermissionsService, LanguageService, LoadingService, Toaster } from '@iqser/common-ui';
|
||||||
import { TenantsService } from '@iqser/common-ui/lib/tenants';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { AppConfig, IProfile } from '@red/domain';
|
import { IProfile } from '@red/domain';
|
||||||
import { languagesTranslations } from '@translations/languages-translations';
|
import { languagesTranslations } from '@translations/languages-translations';
|
||||||
import { Roles } from '@users/roles';
|
import { Roles } from '@users/roles';
|
||||||
import { UserPreferenceService } from '@users/user-preference.service';
|
import { UserPreferenceService } from '@users/user-preference.service';
|
||||||
@ -22,10 +20,8 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
|
|||||||
#profileModel: IProfile;
|
#profileModel: IProfile;
|
||||||
readonly translations = languagesTranslations;
|
readonly translations = languagesTranslations;
|
||||||
readonly devMode = this._userPreferenceService.isIqserDevMode;
|
readonly devMode = this._userPreferenceService.isIqserDevMode;
|
||||||
readonly changePasswordUrl: string;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
domSanitizer: DomSanitizer,
|
|
||||||
private readonly _userService: UserService,
|
private readonly _userService: UserService,
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
private readonly _dialogService: UserProfileDialogService,
|
private readonly _dialogService: UserProfileDialogService,
|
||||||
@ -39,9 +35,6 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
const tenant = inject(TenantsService).activeTenantId;
|
|
||||||
const realmUrl = `${getConfig<AppConfig>().OAUTH_URL}/realms/${tenant}`;
|
|
||||||
this.changePasswordUrl = `${realmUrl}/account/password`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get languageChanged(): boolean {
|
get languageChanged(): boolean {
|
||||||
@ -115,6 +108,10 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async resetPassword() {
|
||||||
|
await this._userService.createResetPasswordAction();
|
||||||
|
}
|
||||||
|
|
||||||
private _getForm(): UntypedFormGroup {
|
private _getForm(): UntypedFormGroup {
|
||||||
return this._formBuilder.group({
|
return this._formBuilder.group({
|
||||||
email: ['', [Validators.required, Validators.email]],
|
email: ['', [Validators.required, Validators.email]],
|
||||||
|
|||||||
@ -34,11 +34,7 @@ export class AddEntityDialogComponent extends BaseDialogComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async save(): Promise<void> {
|
async save(): Promise<void> {
|
||||||
try {
|
await this._addEditEntityComponent.save();
|
||||||
await this._addEditEntityComponent.save();
|
this._dialogRef.close(true);
|
||||||
this._dialogRef.close(true);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,7 +104,12 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnI
|
|||||||
const from = this.form.get('from').value;
|
const from = this.form.get('from').value;
|
||||||
let to = this.form.get('to').value;
|
let to = this.form.get('to').value;
|
||||||
if (to) {
|
if (to) {
|
||||||
to = to.clone().add(1, 'd');
|
const hoursLeft = new Date(to).getHours();
|
||||||
|
const minutesLeft = new Date(to).getMinutes();
|
||||||
|
to = to
|
||||||
|
.clone()
|
||||||
|
.add(24 - hoursLeft - 1, 'h')
|
||||||
|
.add(60 - minutesLeft - 1);
|
||||||
}
|
}
|
||||||
const logsRequestBody: IAuditSearchRequest = {
|
const logsRequestBody: IAuditSearchRequest = {
|
||||||
pageSize: PAGE_SIZE,
|
pageSize: PAGE_SIZE,
|
||||||
|
|||||||
@ -7,5 +7,6 @@
|
|||||||
[initialEntries]="initialEntries$ | async"
|
[initialEntries]="initialEntries$ | async"
|
||||||
[isLeavingPage]="isLeavingPage"
|
[isLeavingPage]="isLeavingPage"
|
||||||
[selectedDictionaryType]="entityType"
|
[selectedDictionaryType]="entityType"
|
||||||
|
[activeEntryType]="activeEntryType"
|
||||||
[type]="type"
|
[type]="type"
|
||||||
></redaction-dictionary-manager>
|
></redaction-dictionary-manager>
|
||||||
|
|||||||
@ -79,4 +79,8 @@ export class DictionaryScreenComponent implements OnInit {
|
|||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get activeEntryType() {
|
||||||
|
return DICTIONARY_TO_ENTRY_TYPE_MAP[this.type];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,16 +37,8 @@ export class EntityInfoComponent {
|
|||||||
return !this._addEditEntityComponent || this._addEditEntityComponent.form?.invalid || !this._addEditEntityComponent.changed;
|
return !this._addEditEntityComponent || this._addEditEntityComponent.form?.invalid || !this._addEditEntityComponent.changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
get changed(): boolean {
|
|
||||||
return this._addEditEntityComponent.changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
async save(): Promise<void> {
|
async save(): Promise<void> {
|
||||||
try {
|
await this._addEditEntityComponent.save();
|
||||||
await this._addEditEntityComponent.save();
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
revert(): void {
|
revert(): void {
|
||||||
|
|||||||
@ -3,10 +3,11 @@ import { LicenseService } from '@services/license.service';
|
|||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { ChartDataset } from 'chart.js';
|
import { ChartDataset } from 'chart.js';
|
||||||
import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants';
|
import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants';
|
||||||
import { getLabelsFromLicense, getLineConfig } from '../../utils/functions';
|
import { getDataUntilCurrentMonth, getLabelsFromLicense, getLineConfig, isCurrentMonthAndYear } from '../../utils/functions';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { size } from '@iqser/common-ui/lib/utils';
|
import { size } from '@iqser/common-ui/lib/utils';
|
||||||
|
import { ILicenseData } from '@red/domain';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'red-license-analysis-capacity-usage',
|
selector: 'red-license-analysis-capacity-usage',
|
||||||
@ -35,7 +36,13 @@ export class LicenseAnalysisCapacityUsageComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#getCapacityDatasets(): ChartDataset[] {
|
#getCapacityDatasets(): ChartDataset[] {
|
||||||
const monthlyData = this.licenseService.selectedLicenseReport.monthlyData;
|
const monthlyData = [...this.licenseService.selectedLicenseReport.monthlyData];
|
||||||
|
const dataUntilCurrentMonth = getDataUntilCurrentMonth(monthlyData);
|
||||||
|
if (monthlyData.length === 1 || isCurrentMonthAndYear(monthlyData[0].startDate)) {
|
||||||
|
const empty = { analysedFilesBytes: null } as ILicenseData;
|
||||||
|
dataUntilCurrentMonth.splice(0, 0, empty);
|
||||||
|
monthlyData.splice(0, 0, empty);
|
||||||
|
}
|
||||||
|
|
||||||
const datasets: ChartDataset[] = [
|
const datasets: ChartDataset[] = [
|
||||||
{
|
{
|
||||||
@ -48,9 +55,11 @@ export class LicenseAnalysisCapacityUsageComponent {
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
data: monthlyData.map(
|
data: dataUntilCurrentMonth.map((month, monthIndex) =>
|
||||||
(month, monthIndex) =>
|
month.analysedFilesBytes
|
||||||
month.analysedFilesBytes + monthlyData.slice(0, monthIndex).reduce((acc, curr) => acc + curr.analysedFilesBytes, 0),
|
? month.analysedFilesBytes +
|
||||||
|
monthlyData.slice(0, monthIndex).reduce((acc, curr) => acc + (curr.analysedFilesBytes ?? 0), 0)
|
||||||
|
: 0,
|
||||||
),
|
),
|
||||||
label: this._translateService.instant('license-info-screen.analysis-capacity-usage.analyzed-cumulative'),
|
label: this._translateService.instant('license-info-screen.analysis-capacity-usage.analyzed-cumulative'),
|
||||||
yAxisID: 'y1',
|
yAxisID: 'y1',
|
||||||
|
|||||||
@ -3,10 +3,9 @@ import { LicenseService } from '@services/license.service';
|
|||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { ChartDataset } from 'chart.js';
|
import { ChartDataset } from 'chart.js';
|
||||||
import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants';
|
import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants';
|
||||||
import { getLabelsFromLicense, getLineConfig } from '../../utils/functions';
|
import { getDataUntilCurrentMonth, getLabelsFromLicense, getLineConfig, isCurrentMonthAndYear } from '../../utils/functions';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { addPaddingToArray } from '@utils/functions';
|
|
||||||
import { ILicenseData } from '@red/domain';
|
import { ILicenseData } from '@red/domain';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -35,10 +34,12 @@ export class LicensePageUsageComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#getPagesDatasets(): ChartDataset[] {
|
#getPagesDatasets(): ChartDataset[] {
|
||||||
const monthlyData = this.licenseService.selectedLicenseReport.monthlyData;
|
const monthlyData = [...this.licenseService.selectedLicenseReport.monthlyData];
|
||||||
if (monthlyData.length === 1) {
|
const dataUntilCurrentMonth = getDataUntilCurrentMonth(monthlyData);
|
||||||
const empty = monthlyData.map(() => ({ numberOfAnalyzedPages: 0 }) as ILicenseData)[0];
|
if (monthlyData.length === 1 || isCurrentMonthAndYear(monthlyData[0].startDate)) {
|
||||||
addPaddingToArray<ILicenseData>(monthlyData, empty);
|
const empty = { numberOfAnalyzedPages: null } as ILicenseData;
|
||||||
|
dataUntilCurrentMonth.splice(0, 0, empty);
|
||||||
|
monthlyData.splice(0, 0, empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -58,10 +59,11 @@ export class LicensePageUsageComponent {
|
|||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: monthlyData.map(
|
data: dataUntilCurrentMonth.map((month, monthIndex) =>
|
||||||
(month, monthIndex) =>
|
month.numberOfAnalyzedPages
|
||||||
month.numberOfAnalyzedPages +
|
? month.numberOfAnalyzedPages +
|
||||||
monthlyData.slice(0, monthIndex).reduce((acc, curr) => acc + curr.numberOfAnalyzedPages, 0),
|
monthlyData.slice(0, monthIndex).reduce((acc, curr) => acc + (curr.numberOfAnalyzedPages ?? 0), 0)
|
||||||
|
: 0,
|
||||||
),
|
),
|
||||||
label: this._translateService.instant('license-info-screen.page-usage.cumulative-pages'),
|
label: this._translateService.instant('license-info-screen.page-usage.cumulative-pages'),
|
||||||
yAxisID: 'y1',
|
yAxisID: 'y1',
|
||||||
|
|||||||
@ -3,10 +3,10 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { size } from '@iqser/common-ui/lib/utils';
|
import { size } from '@iqser/common-ui/lib/utils';
|
||||||
import { LicenseService } from '@services/license.service';
|
import { LicenseService } from '@services/license.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import type { DonutChartConfig, ILicenseReport } from '@red/domain';
|
import { DonutChartConfig, ILicenseData, ILicenseReport } from '@red/domain';
|
||||||
import { ChartDataset } from 'chart.js';
|
import { ChartDataset } from 'chart.js';
|
||||||
import { ChartBlack, ChartBlue, ChartGreen, ChartGrey, ChartRed } from '../../utils/constants';
|
import { ChartBlack, ChartBlue, ChartGreen, ChartGrey, ChartRed } from '../../utils/constants';
|
||||||
import { getLabelsFromLicense, getLineConfig } from '../../utils/functions';
|
import { getDataUntilCurrentMonth, getLabelsFromLicense, getLineConfig } from '../../utils/functions';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -60,7 +60,15 @@ export class LicenseRetentionCapacityComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#getDatasets(license: ILicenseReport): ChartDataset[] {
|
#getDatasets(license: ILicenseReport): ChartDataset[] {
|
||||||
const monthlyData = license.monthlyData;
|
const monthlyData = getDataUntilCurrentMonth(license.monthlyData);
|
||||||
|
if (monthlyData.length === 1) {
|
||||||
|
const empty = {
|
||||||
|
archivedFilesUploadedBytes: 0,
|
||||||
|
activeFilesUploadedBytes: 0,
|
||||||
|
trashFilesUploadedBytes: 0,
|
||||||
|
} as ILicenseData;
|
||||||
|
monthlyData.splice(0, 0, empty);
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -9,4 +9,5 @@ export enum LicenseFeatures {
|
|||||||
PDFTRON = 'pdftron',
|
PDFTRON = 'pdftron',
|
||||||
ANALYSIS_CAPACITY_BYTES = 'analysisCapacityBytes',
|
ANALYSIS_CAPACITY_BYTES = 'analysisCapacityBytes',
|
||||||
RETENTION_CAPACITY_BYTES = 'retentionCapacityBytes',
|
RETENTION_CAPACITY_BYTES = 'retentionCapacityBytes',
|
||||||
|
SUPPORT_MS_OFFICE_FORMATS = 'supportMSOfficeFormats',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { FillTarget } from 'chart.js';
|
import { FillTarget } from 'chart.js';
|
||||||
import { addPaddingToArray, hexToRgba } from '@utils/functions';
|
import { hexToRgba } from '@utils/functions';
|
||||||
import { ILicenseReport } from '@red/domain';
|
import { ILicenseData, ILicenseReport } from '@red/domain';
|
||||||
import { ComplexFillTarget } from 'chart.js/dist/types';
|
import { ComplexFillTarget } from 'chart.js/dist/types';
|
||||||
|
|
||||||
const monthNames = dayjs.monthsShort();
|
const monthNames = dayjs.monthsShort();
|
||||||
|
const currentMonth = dayjs(Date.now()).month();
|
||||||
|
const currentYear = dayjs(Date.now()).year();
|
||||||
|
|
||||||
export const verboseDate = (date: Dayjs) => `${monthNames[date.month()]} ${date.year()}`;
|
export const verboseDate = (date: Dayjs) => `${monthNames[date.month()]} ${date.year()}`;
|
||||||
|
|
||||||
@ -43,9 +45,17 @@ export const getLabelsFromLicense = (license: ILicenseReport) => {
|
|||||||
monthIterator = monthIterator.add(1, 'month');
|
monthIterator = monthIterator.add(1, 'month');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startMonth.month() === endMonth.month()) {
|
if (startMonth.month() === endMonth.month() || startMonth.month() === currentMonth) {
|
||||||
addPaddingToArray<string>(result, '');
|
result.splice(0, 0, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDataUntilCurrentMonth = (monthlyData: ILicenseData[]) => {
|
||||||
|
return monthlyData.filter(data => dayjs(data.startDate).month() <= currentMonth && dayjs(data.startDate).year() <= currentYear);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isCurrentMonthAndYear = (date: Date | string) => {
|
||||||
|
return dayjs(date).month() === currentMonth && dayjs(date).year() === currentYear;
|
||||||
|
};
|
||||||
|
|||||||
@ -1,26 +1,26 @@
|
|||||||
<ng-container *ngIf="configService.listingMode$ | async as mode">
|
<ng-container *ngIf="configService.listingMode$ | async as mode">
|
||||||
<div
|
<div
|
||||||
(click)="editFileAttribute($event)"
|
(click)="handleFieldClick($event)"
|
||||||
(mousedown)="handleClick($event)"
|
(mousedown)="handleClick($event)"
|
||||||
[ngClass]="{ 'workflow-attribute': mode === 'workflow', 'file-name-column': fileNameColumn }"
|
[ngClass]="{ 'workflow-attribute': mode === 'workflow', 'file-name-column': fileNameColumn }"
|
||||||
class="file-attribute"
|
class="file-attribute"
|
||||||
>
|
>
|
||||||
<div [ngClass]="{ 'workflow-value': mode === 'workflow' }" class="value">
|
<div [ngClass]="{ 'workflow-value': mode === 'workflow' }" class="value" *ngIf="!isInEditMode || mode === 'workflow'">
|
||||||
<mat-icon *ngIf="!fileAttribute.editable" [matTooltip]="'readonly' | translate" svgIcon="red:read-only"></mat-icon>
|
<mat-icon *ngIf="!fileAttribute.editable" [matTooltip]="'readonly' | translate" svgIcon="red:read-only"></mat-icon>
|
||||||
<div>
|
<span
|
||||||
|
*ngIf="!isDate; else date"
|
||||||
|
[style.max-width]="attributeValueWidth"
|
||||||
|
[matTooltip]="fileAttributeValue"
|
||||||
|
[ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }"
|
||||||
|
>
|
||||||
<b *ngIf="mode === 'workflow' && !isInEditMode"> {{ fileAttribute.label }}: </b>
|
<b *ngIf="mode === 'workflow' && !isInEditMode"> {{ fileAttribute.label }}: </b>
|
||||||
<span
|
{{ fileAttributeValue || '-' }}
|
||||||
*ngIf="!isDate; else date"
|
</span>
|
||||||
[matTooltip]="fileAttributeValue"
|
|
||||||
[ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }"
|
|
||||||
>
|
|
||||||
{{ fileAttributeValue || '-' }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<ng-template #date>
|
<ng-template #date>
|
||||||
<span [ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }">
|
<span [ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }">
|
||||||
{{ fileAttributeValue ? (fileAttributeValue | date: 'd MMM yyyy') : '-' }}</span
|
<b *ngIf="mode === 'workflow' && !isInEditMode"> {{ fileAttribute.label }}: </b>
|
||||||
>
|
{{ fileAttributeValue ? (fileAttributeValue | date: 'd MMM yyyy') : '-' }}
|
||||||
|
</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -37,7 +37,11 @@
|
|||||||
*ngIf="!isInEditMode; else input"
|
*ngIf="!isInEditMode; else input"
|
||||||
[attr.help-mode-key]="'edit_file_attributes'"
|
[attr.help-mode-key]="'edit_file_attributes'"
|
||||||
[class.help-mode-button]="helpModeService.isHelpModeActive$ | async"
|
[class.help-mode-button]="helpModeService.isHelpModeActive$ | async"
|
||||||
[ngClass]="{ 'workflow-edit-button': mode === 'workflow', 'action-buttons edit-button': !fileNameColumn }"
|
[ngClass]="{
|
||||||
|
'workflow-edit-button': mode === 'workflow',
|
||||||
|
'action-buttons edit-button': !fileNameColumn,
|
||||||
|
'filename-edit-button': fileNameColumn
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div [ngClass]="{ 'workflow-edit-icon': mode === 'workflow', 'edit-icon': !fileNameColumn }">
|
<div [ngClass]="{ 'workflow-edit-icon': mode === 'workflow', 'edit-icon': !fileNameColumn }">
|
||||||
<mat-icon [svgIcon]="'iqser:edit'"></mat-icon>
|
<mat-icon [svgIcon]="'iqser:edit'"></mat-icon>
|
||||||
@ -55,6 +59,8 @@
|
|||||||
<form (ngSubmit)="form.valid && save()" [formGroup]="form">
|
<form (ngSubmit)="form.valid && save()" [formGroup]="form">
|
||||||
<iqser-dynamic-input
|
<iqser-dynamic-input
|
||||||
(closedDatepicker)="closedDatepicker = $event"
|
(closedDatepicker)="closedDatepicker = $event"
|
||||||
|
[style.max-width]="editFieldWidth"
|
||||||
|
[style.min-width]="editFieldWidth"
|
||||||
(keydown.escape)="close()"
|
(keydown.escape)="close()"
|
||||||
[formControlName]="fileAttribute.id"
|
[formControlName]="fileAttribute.id"
|
||||||
[id]="fileAttribute.id"
|
[id]="fileAttribute.id"
|
||||||
@ -66,11 +72,17 @@
|
|||||||
(action)="save()"
|
(action)="save()"
|
||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
[icon]="'iqser:check'"
|
[icon]="'iqser:check'"
|
||||||
[size]="mode === 'workflow' ? 15 : 34"
|
[size]="mode === 'workflow' || fileNameColumn ? 15 : 34"
|
||||||
|
[ngClass]="{ 'file-name-btn': fileNameColumn }"
|
||||||
class="save"
|
class="save"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
|
|
||||||
<iqser-circle-button (action)="close()" [icon]="'iqser:close'" [size]="mode === 'workflow' ? 15 : 34"></iqser-circle-button>
|
<iqser-circle-button
|
||||||
|
(action)="close()"
|
||||||
|
[ngClass]="{ 'file-name-btn': fileNameColumn }"
|
||||||
|
[icon]="'iqser:close'"
|
||||||
|
[size]="mode === 'workflow' || fileNameColumn ? 15 : 34"
|
||||||
|
></iqser-circle-button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
&.file-name-column {
|
&.file-name-column {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
width: fit-content;
|
width: 100%;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
.workflow-value {
|
.workflow-value {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 90%;
|
width: 95%;
|
||||||
|
|
||||||
b {
|
b {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -81,6 +81,18 @@
|
|||||||
|
|
||||||
&.file-name-column-input {
|
&.file-name-column-input {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
form {
|
||||||
|
iqser-circle-button {
|
||||||
|
margin-left: 15px;
|
||||||
|
|
||||||
|
@media screen and (max-width: 1395px) {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.workflow-edit-input {
|
&.workflow-edit-input {
|
||||||
@ -99,10 +111,6 @@
|
|||||||
iqser-circle-button {
|
iqser-circle-button {
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: -8px;
|
margin-right: -8px;
|
||||||
}
|
}
|
||||||
@ -118,7 +126,6 @@
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-name-input,
|
|
||||||
.workflow-input {
|
.workflow-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
@ -143,6 +150,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-name-input {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.save {
|
.save {
|
||||||
margin-left: 7px;
|
margin-left: 7px;
|
||||||
}
|
}
|
||||||
@ -150,6 +161,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filename-edit-button:hover {
|
||||||
|
background-color: var(--iqser-grey-6);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.file-attribute:hover {
|
.file-attribute:hover {
|
||||||
&.workflow-attribute {
|
&.workflow-attribute {
|
||||||
background-color: var(--iqser-grey-6);
|
background-color: var(--iqser-grey-6);
|
||||||
@ -158,6 +174,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.workflow-value {
|
.workflow-value {
|
||||||
|
width: 90%;
|
||||||
|
|
||||||
b {
|
b {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: unset;
|
overflow: unset;
|
||||||
|
|||||||
@ -62,6 +62,16 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
|||||||
this.file.fileId !== this.fileAttributesService.fileEdit())) ||
|
this.file.fileId !== this.fileAttributesService.fileEdit())) ||
|
||||||
!this.fileAttributesService.openAttributeEdits().length,
|
!this.fileAttributesService.openAttributeEdits().length,
|
||||||
);
|
);
|
||||||
|
@Input() width?: number;
|
||||||
|
#widthFactor = window.innerWidth >= 1800 ? 0.85 : 0.7;
|
||||||
|
|
||||||
|
get editFieldWidth(): string {
|
||||||
|
return this.width ? `${this.width * this.#widthFactor}px` : 'unset';
|
||||||
|
}
|
||||||
|
|
||||||
|
get attributeValueWidth(): string {
|
||||||
|
return this.width ? `${this.width * 0.9}px` : 'unset';
|
||||||
|
}
|
||||||
|
|
||||||
get isDate(): boolean {
|
get isDate(): boolean {
|
||||||
return this.fileAttribute.type === FileAttributeConfigTypes.DATE;
|
return this.fileAttribute.type === FileAttributeConfigTypes.DATE;
|
||||||
@ -110,6 +120,15 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostListener('window:resize')
|
||||||
|
onResize() {
|
||||||
|
if (window.innerWidth >= 1800) {
|
||||||
|
this.#widthFactor = 0.85;
|
||||||
|
} else {
|
||||||
|
this.#widthFactor = 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Debounce(60)
|
@Debounce(60)
|
||||||
@HostListener('document:click', ['$event'])
|
@HostListener('document:click', ['$event'])
|
||||||
clickOutside($event: MouseEvent) {
|
clickOutside($event: MouseEvent) {
|
||||||
@ -131,7 +150,13 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
|||||||
this.#subscriptions.unsubscribe();
|
this.#subscriptions.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
async editFileAttribute($event: MouseEvent): Promise<void> {
|
handleFieldClick($event: MouseEvent) {
|
||||||
|
if (!this.fileNameColumn) {
|
||||||
|
this.editFileAttribute($event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editFileAttribute($event: MouseEvent) {
|
||||||
if (
|
if (
|
||||||
!this.file.isInitialProcessing &&
|
!this.file.isInitialProcessing &&
|
||||||
this.permissionsService.canEditFileAttributes(this.file, this.dossier) &&
|
this.permissionsService.canEditFileAttributes(this.file, this.dossier) &&
|
||||||
@ -143,9 +168,9 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
|||||||
...value,
|
...value,
|
||||||
{ attribute: this.fileAttribute.id, file: this.file.id },
|
{ attribute: this.fileAttribute.id, file: this.file.id },
|
||||||
]);
|
]);
|
||||||
this.#toggleEdit();
|
|
||||||
this.fileAttributesService.setFileEdit(this.file.fileId);
|
this.fileAttributesService.setFileEdit(this.file.fileId);
|
||||||
this.fileAttributesService.setOpenAttributeEdit(this.fileAttribute.id);
|
this.fileAttributesService.setOpenAttributeEdit(this.fileAttribute.id);
|
||||||
|
this.#toggleEdit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,9 +31,10 @@
|
|||||||
|
|
||||||
.file-name-cell:hover {
|
.file-name-cell:hover {
|
||||||
::ng-deep.file-name-column {
|
::ng-deep.file-name-column {
|
||||||
background: var(--iqser-grey-6);
|
.filename-edit-button {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
div {
|
|
||||||
mat-icon {
|
mat-icon {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import { merge, Observable } from 'rxjs';
|
|||||||
import { filter, skip, switchMap, tap } from 'rxjs/operators';
|
import { filter, skip, switchMap, tap } from 'rxjs/operators';
|
||||||
import { ConfigService } from '../config.service';
|
import { ConfigService } from '../config.service';
|
||||||
import { BulkActionsService } from '../services/bulk-actions.service';
|
import { BulkActionsService } from '../services/bulk-actions.service';
|
||||||
|
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './dossier-overview-screen.component.html',
|
templateUrl: './dossier-overview-screen.component.html',
|
||||||
@ -92,6 +93,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
|||||||
private readonly _fileDropOverlayService: FileDropOverlayService,
|
private readonly _fileDropOverlayService: FileDropOverlayService,
|
||||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||||
private readonly _dossierAttributesService: DossierAttributesService,
|
private readonly _dossierAttributesService: DossierAttributesService,
|
||||||
|
private readonly _dossiersCacheService: DossiersCacheService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.dossier$ = _dossiersService.getEntityChanged$(this.dossierId).pipe(tap(dossier => (this.#dossier = dossier)));
|
this.dossier$ = _dossiersService.getEntityChanged$(this.dossierId).pipe(tap(dossier => (this.#dossier = dossier)));
|
||||||
@ -121,7 +123,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
|||||||
|
|
||||||
get #dossierFilesChange$() {
|
get #dossierFilesChange$() {
|
||||||
return this._dossiersService.dossierFileChanges$.pipe(
|
return this._dossiersService.dossierFileChanges$.pipe(
|
||||||
filter(dossierId => dossierId === this.dossierId),
|
filter(dossierId => dossierId === this.dossierId && !!this._dossiersCacheService.get(dossierId)),
|
||||||
switchMap(dossierId => this._filesService.loadAll(dossierId)),
|
switchMap(dossierId => this._filesService.loadAll(dossierId)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -148,7 +148,7 @@ export class AnnotationActionsComponent implements OnChanges {
|
|||||||
|
|
||||||
acceptResize() {
|
acceptResize() {
|
||||||
if (this.resized) {
|
if (this.resized) {
|
||||||
return this.annotationActionsService.acceptResize(this.#annotations[0]);
|
return this.annotationActionsService.acceptResize(this.#annotations[0], this.annotationPermissions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,6 +171,10 @@ export class AnnotationActionsComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get #annotationChangesAllowed() {
|
get #annotationChangesAllowed() {
|
||||||
return !this.#isDocumine || !this._state.file().excludedFromAutomaticAnalysis;
|
return (!this.#isDocumine || !this._state.file().excludedFromAutomaticAnalysis) && !this.#somePending;
|
||||||
|
}
|
||||||
|
|
||||||
|
get #somePending() {
|
||||||
|
return this.#annotations.some(a => a.pending);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<strong>{{ annotation.superTypeLabel | translate }}</strong>
|
<strong>{{ annotation.superTypeLabel | translate }}</strong>
|
||||||
|
|
||||||
<strong *ngIf="pending" class="pending-analysis">
|
<strong *ngIf="annotation.pending" class="pending-analysis">
|
||||||
{{ 'annotation.pending' | translate }}
|
{{ 'annotation.pending' | translate }}
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -18,16 +18,4 @@ export class AnnotationCardComponent {
|
|||||||
@Input() isSelected = false;
|
@Input() isSelected = false;
|
||||||
|
|
||||||
constructor(readonly multiSelectService: MultiSelectService) {}
|
constructor(readonly multiSelectService: MultiSelectService) {}
|
||||||
|
|
||||||
get pending() {
|
|
||||||
return (
|
|
||||||
this.annotation.pending &&
|
|
||||||
((this.annotation.isModifyDictionary &&
|
|
||||||
!this.annotation.isRemovedLocally &&
|
|
||||||
!this.annotation.hasBeenForcedHint &&
|
|
||||||
(this.annotation.lastManualChange !== ManualRedactionTypes.LEGAL_BASIS_CHANGE &&
|
|
||||||
this.annotation.lastManualChange !== ManualRedactionTypes.RESIZE_LOCALLY)) ||
|
|
||||||
this.annotation.type === ImageCategory.SIGNATURE)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.popover {
|
.popover {
|
||||||
width: 260px;
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: var(--iqser-grey-1);
|
background-color: var(--iqser-grey-1);
|
||||||
|
|||||||
@ -14,11 +14,12 @@ interface Engine {
|
|||||||
readonly translateParams?: Record<string, any>;
|
readonly translateParams?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Engines = {
|
export const Engines = {
|
||||||
DICTIONARY: 'DICTIONARY',
|
DICTIONARY: 'DICTIONARY',
|
||||||
NER: 'NER',
|
NER: 'NER',
|
||||||
RULE: 'RULE',
|
RULE: 'RULE',
|
||||||
IMPORTED: 'IMPORTED',
|
IMPORTED: 'IMPORTED',
|
||||||
|
MANUAL: 'MANUAL',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type EngineName = keyof typeof Engines;
|
type EngineName = keyof typeof Engines;
|
||||||
@ -53,7 +54,7 @@ export class AnnotationDetailsComponent implements OnChanges {
|
|||||||
getChangesTooltip(): string | undefined {
|
getChangesTooltip(): string | undefined {
|
||||||
const changes = changesProperties.filter(key => this.annotation.item[key]);
|
const changes = changesProperties.filter(key => this.annotation.item[key]);
|
||||||
|
|
||||||
if (!changes.length) {
|
if (!changes.length || !this.annotation.item.engines?.includes(Engines.MANUAL)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
<div *ngIf="!annotation.item.isEarmark" class="actions-wrapper">
|
<div *ngIf="!annotation.item.isEarmark" class="actions-wrapper">
|
||||||
<div
|
<div
|
||||||
|
*ngIf="!annotation.item.pending"
|
||||||
(click)="showComments = !showComments"
|
(click)="showComments = !showComments"
|
||||||
[matTooltip]="'comments.comments' | translate: { count: annotation.item.numberOfComments }"
|
[matTooltip]="'comments.comments' | translate: { count: annotation.item.numberOfComments }"
|
||||||
class="comments-counter"
|
class="comments-counter"
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<redaction-annotation-wrapper
|
<redaction-annotation-wrapper
|
||||||
|
*ngIf="!annotation.item.hiddenInWorkload"
|
||||||
(click)="annotationClicked(annotation.item, $event)"
|
(click)="annotationClicked(annotation.item, $event)"
|
||||||
[annotation]="annotation"
|
[annotation]="annotation"
|
||||||
></redaction-annotation-wrapper>
|
></redaction-annotation-wrapper>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<div *ngFor="let comment of comments(); trackBy: trackBy" class="comment">
|
<div *ngFor="let comment of comments(); trackBy: trackBy" class="comment">
|
||||||
<div class="comment-details-wrapper">
|
<div class="comment-details-wrapper">
|
||||||
<div [matTooltipPosition]="'above'" [matTooltip]="comment.date | date: 'exactDate'" class="small-label">
|
<div [matTooltipPosition]="'above'" [matTooltip]="comment.date | date: 'exactDate'" class="small-label">
|
||||||
<strong> {{ comment.user | name }} </strong>
|
<strong> {{ comment.userId | name }} </strong>
|
||||||
{{ comment.date | date: 'sophisticatedDate' }}
|
{{ comment.date | date: 'sophisticatedDate' }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,7 @@ export class CommentsComponent implements OnChanges {
|
|||||||
text: value,
|
text: value,
|
||||||
id: commentId,
|
id: commentId,
|
||||||
annotationId: this.annotation.id,
|
annotationId: this.annotation.id,
|
||||||
user: this.currentUser.id,
|
userId: this.currentUser.id,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
this.input.reset();
|
this.input.reset();
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
<cdk-virtual-scroll-viewport [itemSize]="LIST_ITEM_SIZE" [ngStyle]="{ height: redactedTextsAreaHeight + 'px' }">
|
||||||
|
<ul>
|
||||||
|
<li *cdkVirtualFor="let value of values">{{ value }}</li>
|
||||||
|
</ul>
|
||||||
|
</cdk-virtual-scroll-viewport>
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
@use 'common-mixins';
|
||||||
|
|
||||||
|
cdk-virtual-scroll-viewport {
|
||||||
|
@include common-mixins.scroll-bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host ::ng-deep .cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 16px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
list-style-position: inside;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||||
|
import { NgStyle } from '@angular/common';
|
||||||
|
|
||||||
|
const LIST_ITEM_SIZE = 16;
|
||||||
|
const MAX_ITEMS_DISPLAY = 5;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'redaction-selected-annotations-list',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport, NgStyle],
|
||||||
|
templateUrl: './selected-annotations-list.component.html',
|
||||||
|
styleUrl: './selected-annotations-list.component.scss',
|
||||||
|
})
|
||||||
|
export class SelectedAnnotationsListComponent {
|
||||||
|
@Input({ required: true }) values: string[];
|
||||||
|
protected readonly LIST_ITEM_SIZE = LIST_ITEM_SIZE;
|
||||||
|
|
||||||
|
get redactedTextsAreaHeight() {
|
||||||
|
return this.values.length <= MAX_ITEMS_DISPLAY ? LIST_ITEM_SIZE * this.values.length : LIST_ITEM_SIZE * MAX_ITEMS_DISPLAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th *ngFor="let column of columns" [ngClass]="{ hide: !column.show, 'w-50': staticColumns }">
|
||||||
|
<label>{{ column.label }}</label>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody [ngStyle]="{ height: redactedTextsAreaHeight + 'px' }">
|
||||||
|
<tr *ngFor="let row of data">
|
||||||
|
<td *ngFor="let cell of row" [ngClass]="{ hide: !cell.show, bold: cell.bold, 'w-50': staticColumns }">
|
||||||
|
{{ cell.label }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
@use 'common-mixins';
|
||||||
|
|
||||||
|
table {
|
||||||
|
padding: 0 13px;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
border-spacing: 0;
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
padding-top: 2px;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: block;
|
||||||
|
@include common-mixins.scroll-bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
display: table;
|
||||||
|
|
||||||
|
th {
|
||||||
|
label {
|
||||||
|
opacity: 0.7;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
&:not(.w-50) {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
max-width: 0;
|
||||||
|
text-align: start;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
list-style-position: inside;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:last-child,
|
||||||
|
td:last-child {
|
||||||
|
max-width: 0;
|
||||||
|
width: 50%;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:nth-child(odd) {
|
||||||
|
td {
|
||||||
|
background-color: var(--iqser-alt-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-50 {
|
||||||
|
max-width: 0;
|
||||||
|
min-width: 50%;
|
||||||
|
|
||||||
|
&.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { NgClass, NgForOf, NgStyle } from '@angular/common';
|
||||||
|
|
||||||
|
export interface ValueColumn {
|
||||||
|
label: string;
|
||||||
|
show: boolean;
|
||||||
|
bold?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TABLE_ROW_SIZE = 18;
|
||||||
|
const MAX_ITEMS_DISPLAY = 10;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'redaction-selected-annotations-table',
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgForOf, NgClass, NgStyle],
|
||||||
|
templateUrl: './selected-annotations-table.component.html',
|
||||||
|
styleUrl: './selected-annotations-table.component.scss',
|
||||||
|
})
|
||||||
|
export class SelectedAnnotationsTableComponent {
|
||||||
|
@Input({ required: true }) columns: ValueColumn[];
|
||||||
|
@Input({ required: true }) data: ValueColumn[][];
|
||||||
|
@Input() staticColumns = false;
|
||||||
|
|
||||||
|
get redactedTextsAreaHeight() {
|
||||||
|
return this.data.length <= MAX_ITEMS_DISPLAY ? TABLE_ROW_SIZE * this.data.length : TABLE_ROW_SIZE * MAX_ITEMS_DISPLAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,32 +6,33 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<iqser-initials-avatar
|
<iqser-initials-avatar
|
||||||
*ngIf="!editingReviewer"
|
*ngIf="!state.isEditingReviewer()"
|
||||||
[id]="'assignee'"
|
[id]="'assignee'"
|
||||||
[user]="file.assignee"
|
[user]="file.assignee"
|
||||||
[withName]="!!file.assignee"
|
[withName]="!!file.assignee"
|
||||||
></iqser-initials-avatar>
|
></iqser-initials-avatar>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
(click)="editingReviewer = true"
|
(click)="state.isEditingReviewer.set(true)"
|
||||||
*ngIf="!editingReviewer && _canAssignReviewer()"
|
*ngIf="!state.isEditingReviewer() && _canAssignReviewer()"
|
||||||
[id]="'assign-reviewer'"
|
[id]="'assign-reviewer'"
|
||||||
[translate]="'file-preview.assign-reviewer'"
|
[translate]="'file-preview.assign-reviewer'"
|
||||||
class="assign-reviewer pointer"
|
class="assign-reviewer pointer"
|
||||||
|
iqserStopPropagation
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<redaction-assign-user-dropdown
|
<redaction-assign-user-dropdown
|
||||||
(cancel)="editingReviewer = false"
|
(cancel)="state.isEditingReviewer.set(false)"
|
||||||
(save)="assignReviewer(file, $event)"
|
(save)="assignReviewer(file, $event)"
|
||||||
*ngIf="editingReviewer"
|
*ngIf="state.isEditingReviewer()"
|
||||||
[options]="_usersOptions()"
|
[options]="_usersOptions()"
|
||||||
[value]="file.assignee"
|
[value]="file.assignee"
|
||||||
iqserStopPropagation
|
iqserStopPropagation
|
||||||
></redaction-assign-user-dropdown>
|
></redaction-assign-user-dropdown>
|
||||||
|
|
||||||
<div *ngIf="!editingReviewer && _canAssign()" class="assign-actions-wrapper">
|
<div *ngIf="!state.isEditingReviewer() && _canAssign()" class="assign-actions-wrapper">
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="editingReviewer = true"
|
(action)="state.isEditingReviewer.set(true)"
|
||||||
*ngIf="_canAssignOrUnassign() && !!file.assignee"
|
*ngIf="_canAssignOrUnassign() && !!file.assignee"
|
||||||
[attr.help-mode-key]="'editor_assign_user'"
|
[attr.help-mode-key]="'editor_assign_user'"
|
||||||
[buttonId]="'change-assignee'"
|
[buttonId]="'change-assignee'"
|
||||||
|
|||||||
@ -45,7 +45,6 @@ export class UserManagementComponent implements OnInit, OnDestroy {
|
|||||||
? _('file-preview.change-reviewer')
|
? _('file-preview.change-reviewer')
|
||||||
: _('file-preview.assign-reviewer');
|
: _('file-preview.assign-reviewer');
|
||||||
});
|
});
|
||||||
editingReviewer = false;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly fileAssignService: FileAssignService,
|
readonly fileAssignService: FileAssignService,
|
||||||
@ -76,7 +75,7 @@ export class UserManagementComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const translateParams = { reviewerName: this.userService.getName(assigneeId), filename: file.filename };
|
const translateParams = { reviewerName: this.userService.getName(assigneeId), filename: file.filename };
|
||||||
this.toaster.success(_('assignment.reviewer'), { params: translateParams });
|
this.toaster.success(_('assignment.reviewer'), { params: translateParams });
|
||||||
this.editingReviewer = false;
|
this.state.isEditingReviewer.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -90,21 +89,21 @@ export class UserManagementComponent implements OnInit, OnDestroy {
|
|||||||
@Bind()
|
@Bind()
|
||||||
handleViewerClick() {
|
handleViewerClick() {
|
||||||
this.ngZone.run(() => {
|
this.ngZone.run(() => {
|
||||||
this.editingReviewer = false;
|
this.state.isEditingReviewer.set(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:click')
|
@HostListener('document:click')
|
||||||
clickOutside() {
|
clickOutside() {
|
||||||
if (this.editingReviewer) {
|
if (this.state.isEditingReviewer()) {
|
||||||
this.editingReviewer = false;
|
this.state.isEditingReviewer.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:keyup', ['$event'])
|
@HostListener('document:keyup', ['$event'])
|
||||||
handleEsc($event: KeyboardEvent) {
|
handleEsc($event: KeyboardEvent) {
|
||||||
if ($event.key === 'Escape' && this.editingReviewer) {
|
if ($event.key === 'Escape' && this.state.isEditingReviewer()) {
|
||||||
this.editingReviewer = false;
|
this.state.isEditingReviewer.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -108,7 +108,7 @@ export class AddHintDialogComponent extends IqserDialogComponent<AddHintDialogCo
|
|||||||
|
|
||||||
#setDictionaries() {
|
#setDictionaries() {
|
||||||
this.dictionaries = this._dictionaryService.getAddHintDictionaries(
|
this.dictionaries = this._dictionaryService.getAddHintDictionaries(
|
||||||
this.#dossier.dossierTemplateId,
|
this.#dossier.dossierId,
|
||||||
!this.#applyToAllDossiers,
|
!this.#applyToAllDossiers,
|
||||||
this.dictionaryRequest,
|
this.dictionaryRequest,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,16 +3,9 @@
|
|||||||
<div [translate]="'edit-redaction.dialog.title'" class="dialog-header heading-l"></div>
|
<div [translate]="'edit-redaction.dialog.title'" class="dialog-header heading-l"></div>
|
||||||
|
|
||||||
<div class="dialog-content redaction">
|
<div class="dialog-content redaction">
|
||||||
<div *ngIf="showList">
|
<div class="iqser-input-group" *ngIf="showList">
|
||||||
<label [translate]="'edit-redaction.dialog.content.redacted-text'" class="selected-text"></label>
|
<label [translate]="'edit-redaction.dialog.content.redacted-text'" class="selected-text"></label>
|
||||||
<cdk-virtual-scroll-viewport
|
<redaction-selected-annotations-list [values]="redactedTexts"></redaction-selected-annotations-list>
|
||||||
[itemSize]="16"
|
|
||||||
[ngStyle]="{ height: redactedTexts.length <= 5 ? 16 * redactedTexts.length + 'px' : 80 + 'px' }"
|
|
||||||
>
|
|
||||||
<ul *cdkVirtualFor="let text of redactedTexts">
|
|
||||||
<li>{{ text }}</li>
|
|
||||||
</ul>
|
|
||||||
</cdk-virtual-scroll-viewport>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group required w-450">
|
<div class="iqser-input-group required w-450">
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
@use 'common-mixins';
|
|
||||||
|
|
||||||
cdk-virtual-scroll-viewport {
|
|
||||||
margin-top: 8px;
|
|
||||||
@include common-mixins.scroll-bar;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep .cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper {
|
|
||||||
max-width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
list-style-position: inside;
|
|
||||||
overflow: hidden;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
@ -7,18 +7,12 @@
|
|||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div class="dialog-content redaction" [class.fixed-height]="isRedacted && isImage">
|
<div class="dialog-content redaction" [class.fixed-height]="isRedacted && isImage">
|
||||||
<div *ngIf="!isImage">
|
<div class="iqser-input-group" *ngIf="!isImage && redactedTexts.length && !allRectangles">
|
||||||
<label [translate]="labelTranslation"></label>
|
<redaction-selected-annotations-table
|
||||||
<div *ngIf="!!redactedTexts.length && !allRectangles" class="iqser-input-group">
|
[columns]="tableColumns"
|
||||||
<cdk-virtual-scroll-viewport
|
[data]="tableData"
|
||||||
[itemSize]="16"
|
[staticColumns]="true"
|
||||||
[ngStyle]="{ height: redactedTexts.length <= 5 ? 16 * redactedTexts.length + 'px' : 80 + 'px' }"
|
></redaction-selected-annotations-table>
|
||||||
>
|
|
||||||
<ul *cdkVirtualFor="let text of redactedTexts">
|
|
||||||
<li>{{ text }}</li>
|
|
||||||
</ul>
|
|
||||||
</cdk-virtual-scroll-viewport>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!isManualRedaction" class="iqser-input-group w-450" [class.required]="!form.controls.type.disabled">
|
<div *ngIf="!isManualRedaction" class="iqser-input-group w-450" [class.required]="!form.controls.type.disabled">
|
||||||
|
|||||||
@ -1,29 +1,8 @@
|
|||||||
@use 'common-mixins';
|
|
||||||
|
|
||||||
.dialog-content {
|
.dialog-content {
|
||||||
|
padding-top: 8px;
|
||||||
|
|
||||||
&.fixed-height {
|
&.fixed-height {
|
||||||
height: 386px;
|
height: 386px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cdk-virtual-scroll-viewport {
|
|
||||||
margin-top: 8px;
|
|
||||||
@include common-mixins.scroll-bar;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep .cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper {
|
|
||||||
max-width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
list-style-position: inside;
|
|
||||||
overflow: hidden;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -9,9 +9,9 @@ import { firstValueFrom } from 'rxjs';
|
|||||||
import { getEditRedactionOptions, RedactOrHintOption } from '../../utils/dialog-options';
|
import { getEditRedactionOptions, RedactOrHintOption } from '../../utils/dialog-options';
|
||||||
import { EditRedactionData, EditRedactResult } from '../../utils/dialog-types';
|
import { EditRedactionData, EditRedactResult } from '../../utils/dialog-types';
|
||||||
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
|
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
|
||||||
import { editRedactionLabelsTranslations } from '@translations/redact-text-translations';
|
|
||||||
import { Roles } from '@users/roles';
|
import { Roles } from '@users/roles';
|
||||||
import { DialogHelpModeKeys } from '../../utils/constants';
|
import { DialogHelpModeKeys } from '../../utils/constants';
|
||||||
|
import { ValueColumn } from '../../components/selected-annotations-table/selected-annotations-table.component';
|
||||||
|
|
||||||
interface TypeSelectOptions {
|
interface TypeSelectOptions {
|
||||||
type: string;
|
type: string;
|
||||||
@ -31,15 +31,28 @@ export class EditRedactionDialogComponent
|
|||||||
readonly #applyToAllDossiers = this.data.applyToAllDossiers;
|
readonly #applyToAllDossiers = this.data.applyToAllDossiers;
|
||||||
readonly annotations = this.data.annotations;
|
readonly annotations = this.data.annotations;
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
readonly editRedactionLabelsTranslations = editRedactionLabelsTranslations;
|
|
||||||
readonly isModifyDictionary = this.annotations.every(annotation => annotation.isModifyDictionary);
|
readonly isModifyDictionary = this.annotations.every(annotation => annotation.isModifyDictionary);
|
||||||
readonly isImage = this.annotations.reduce((acc, next) => acc && next.isImage, true);
|
readonly isImage = this.annotations.reduce((acc, next) => acc && next.isImage, true);
|
||||||
readonly redactedTexts = !this.isImage ? this.annotations.map(annotation => annotation.value).filter(value => !!value) : null;
|
readonly redactedTexts = !this.isImage ? this.annotations.map(annotation => annotation.value).filter(value => !!value) : null;
|
||||||
readonly isManualRedaction = this.annotations.every(annotation => annotation.type === SuperTypes.ManualRedaction);
|
readonly isManualRedaction = this.annotations.some(annotation => annotation.type === SuperTypes.ManualRedaction);
|
||||||
readonly isHint = this.annotations.every(annotation => annotation.HINT || annotation.IMAGE_HINT);
|
readonly isHint = this.annotations.every(annotation => annotation.HINT || annotation.IMAGE_HINT);
|
||||||
readonly isRedacted = this.annotations.every(annotation => annotation.isRedacted);
|
readonly isRedacted = this.annotations.every(annotation => annotation.isRedacted);
|
||||||
readonly isImported: boolean = this.annotations.every(annotation => annotation.imported);
|
readonly isImported: boolean = this.annotations.every(annotation => annotation.imported);
|
||||||
readonly allRectangles = this.annotations.reduce((acc, a) => acc && a.AREA, true);
|
readonly allRectangles = this.annotations.reduce((acc, a) => acc && a.AREA, true);
|
||||||
|
readonly tableColumns = [
|
||||||
|
{
|
||||||
|
label: 'Value',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Type',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
readonly tableData: ValueColumn[][] = this.data.annotations.map(redaction => [
|
||||||
|
{ label: redaction.value, show: true, bold: true },
|
||||||
|
{ label: redaction.typeLabel, show: true },
|
||||||
|
]);
|
||||||
protected readonly roles = Roles;
|
protected readonly roles = Roles;
|
||||||
|
|
||||||
options: DetailsRadioOption<RedactOrHintOption>[] | undefined;
|
options: DetailsRadioOption<RedactOrHintOption>[] | undefined;
|
||||||
@ -81,9 +94,7 @@ export class EditRedactionDialogComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get redactBasedTypes() {
|
get redactBasedTypes() {
|
||||||
return this._dictionaryService
|
return this._dictionaryService.getRedactionTypes(this.#dossier.dossierTemplateId).map(dictionary => dictionary.type);
|
||||||
.getRedactTextDictionaries(this.#dossier.dossierTemplateId, !this.#applyToAllDossiers)
|
|
||||||
.map(dictionary => dictionary.type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isRedactBasedType() {
|
get isRedactBasedType() {
|
||||||
@ -106,20 +117,12 @@ export class EditRedactionDialogComponent
|
|||||||
return this.annotations.length > 1;
|
return this.annotations.length > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
get label() {
|
|
||||||
return this.allRectangles ? 'customRectangle' : this.isImported ? 'imported' : 'redactedText';
|
|
||||||
}
|
|
||||||
|
|
||||||
get labelTranslation() {
|
|
||||||
return editRedactionLabelsTranslations[this.label];
|
|
||||||
}
|
|
||||||
|
|
||||||
get helpButtonKey() {
|
get helpButtonKey() {
|
||||||
if (this.isHint) {
|
if (this.isHint) {
|
||||||
return DialogHelpModeKeys.HINT_EDIT
|
return DialogHelpModeKeys.HINT_EDIT;
|
||||||
}
|
}
|
||||||
if (this.someSkipped) {
|
if (this.someSkipped) {
|
||||||
return DialogHelpModeKeys.SKIPPED_EDIT
|
return DialogHelpModeKeys.SKIPPED_EDIT;
|
||||||
}
|
}
|
||||||
return DialogHelpModeKeys.REDACTION_EDIT;
|
return DialogHelpModeKeys.REDACTION_EDIT;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,11 @@
|
|||||||
<div *ngIf="isHintDialog" class="dialog-header heading-l" [translate]="'manual-annotation.dialog.header.force-hint'"></div>
|
<div *ngIf="isHintDialog" class="dialog-header heading-l" [translate]="'manual-annotation.dialog.header.force-hint'"></div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
|
<redaction-selected-annotations-table
|
||||||
|
[columns]="tableColumns"
|
||||||
|
[data]="tableData"
|
||||||
|
[staticColumns]="true"
|
||||||
|
></redaction-selected-annotations-table>
|
||||||
<div *ngIf="!isHintDialog && !isDocumine" class="iqser-input-group required w-400">
|
<div *ngIf="!isHintDialog && !isDocumine" class="iqser-input-group required w-400">
|
||||||
<label [translate]="'manual-annotation.dialog.content.reason'"></label>
|
<label [translate]="'manual-annotation.dialog.content.reason'"></label>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
.full-width {
|
.full-width {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { Dossier, ILegalBasisChangeRequest } from '@red/domain';
|
|||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { Roles } from '@users/roles';
|
import { Roles } from '@users/roles';
|
||||||
|
import { ValueColumn } from '../../components/selected-annotations-table/selected-annotations-table.component';
|
||||||
|
|
||||||
export interface LegalBasisOption {
|
export interface LegalBasisOption {
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -23,8 +24,24 @@ const DOCUMINE_LEGAL_BASIS = 'n-a.';
|
|||||||
})
|
})
|
||||||
export class ForceAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
|
export class ForceAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
|
||||||
readonly isDocumine = getConfig().IS_DOCUMINE;
|
readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||||
protected readonly roles = Roles;
|
|
||||||
|
readonly tableColumns = [
|
||||||
|
{
|
||||||
|
label: 'Value',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Type',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
readonly tableData: ValueColumn[][] = this._data.annotations.map(redaction => [
|
||||||
|
{ label: redaction.value, show: true, bold: true },
|
||||||
|
{ label: redaction.typeLabel, show: true },
|
||||||
|
]);
|
||||||
|
|
||||||
legalOptions: LegalBasisOption[] = [];
|
legalOptions: LegalBasisOption[] = [];
|
||||||
|
protected readonly roles = Roles;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _justificationsService: JustificationsService,
|
private readonly _justificationsService: JustificationsService,
|
||||||
@ -33,7 +50,7 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
|
|||||||
private readonly _data: { readonly dossier: Dossier; readonly hint: boolean; annotations: AnnotationWrapper[] },
|
private readonly _data: { readonly dossier: Dossier; readonly hint: boolean; annotations: AnnotationWrapper[] },
|
||||||
) {
|
) {
|
||||||
super(_dialogRef);
|
super(_dialogRef);
|
||||||
this.form = this._getForm();
|
this.form = this.#getForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
get isHintDialog() {
|
get isHintDialog() {
|
||||||
@ -66,17 +83,17 @@ export class ForceAnnotationDialogComponent extends BaseDialogComponent implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
this._dialogRef.close(this._createForceRedactionRequest());
|
this._dialogRef.close(this.#createForceRedactionRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getForm(): UntypedFormGroup {
|
#getForm(): UntypedFormGroup {
|
||||||
return this._formBuilder.group({
|
return this._formBuilder.group({
|
||||||
reason: this._data.hint ? ['Forced Hint'] : [null, !this.isDocumine ? Validators.required : null],
|
reason: this._data.hint ? ['Forced Hint'] : [null, !this.isDocumine ? Validators.required : null],
|
||||||
comment: [null],
|
comment: [null],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createForceRedactionRequest(): ILegalBasisChangeRequest {
|
#createForceRedactionRequest(): ILegalBasisChangeRequest {
|
||||||
const request: ILegalBasisChangeRequest = {};
|
const request: ILegalBasisChangeRequest = {};
|
||||||
|
|
||||||
request.legalBasis = !this.isDocumine ? this.form.get('reason').value.legalBasis : DOCUMINE_LEGAL_BASIS;
|
request.legalBasis = !this.isDocumine ? this.form.get('reason').value.legalBasis : DOCUMINE_LEGAL_BASIS;
|
||||||
|
|||||||
@ -78,7 +78,7 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
|
|||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.possibleDictionaries = this.isDictionaryRequest
|
this.possibleDictionaries = this.isDictionaryRequest
|
||||||
? await this._dictionaryService.getDictionariesOptions(this.#dossier.dossierTemplateId, this.#dossier.id)
|
? this._dictionaryService.getDictionariesOptions(this.#dossier.dossierTemplateId)
|
||||||
: this._dictionaryService.getRedactionTypes(this.#dossier.dossierTemplateId);
|
: this._dictionaryService.getRedactionTypes(this.#dossier.dossierTemplateId);
|
||||||
|
|
||||||
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.#dossier.dossierTemplateId));
|
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.#dossier.dossierTemplateId));
|
||||||
|
|||||||
@ -3,9 +3,12 @@
|
|||||||
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
|
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
|
||||||
|
|
||||||
<div class="dialog-content redaction">
|
<div class="dialog-content redaction">
|
||||||
<div *ngIf="form.controls.selectedText.value as selectedText" class="iqser-input-group w-450">
|
<div class="iqser-input-group">
|
||||||
<label [translate]="'redact-text.dialog.content.selected-text'" class="selected-text"></label>
|
<redaction-selected-annotations-table
|
||||||
{{ selectedText }}
|
[columns]="tableColumns"
|
||||||
|
[data]="tableData"
|
||||||
|
[staticColumns]="true"
|
||||||
|
></redaction-selected-annotations-table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<iqser-details-radio
|
<iqser-details-radio
|
||||||
@ -18,7 +21,7 @@
|
|||||||
<label [translate]="'redact-text.dialog.content.type'"></label>
|
<label [translate]="'redact-text.dialog.content.type'"></label>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select [placeholder]="'redact-text.dialog.content.type-placeholder' | translate" formControlName="dictionary">
|
<mat-select [placeholder]="'redact-text.dialog.content.unchanged' | translate" formControlName="dictionary">
|
||||||
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
|
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
|
||||||
<mat-option
|
<mat-option
|
||||||
(click)="typeChanged()"
|
(click)="typeChanged()"
|
||||||
@ -47,7 +50,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<iqser-icon-button [label]="'redact-text.dialog.actions.save' | translate" [submit]="true" [type]="iconButtonTypes.primary">
|
<iqser-icon-button
|
||||||
|
[disabled]="disabled"
|
||||||
|
[label]="'redact-text.dialog.actions.save' | translate"
|
||||||
|
[submit]="true"
|
||||||
|
[type]="iconButtonTypes.primary"
|
||||||
|
>
|
||||||
</iqser-icon-button>
|
</iqser-icon-button>
|
||||||
|
|
||||||
<div [translate]="'redact-text.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
|
<div [translate]="'redact-text.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
.redaction {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
@ -12,9 +12,11 @@ import { tap } from 'rxjs/operators';
|
|||||||
import { getRedactOrHintOptions, RedactOrHintOption, RedactOrHintOptions } from '../../utils/dialog-options';
|
import { getRedactOrHintOptions, RedactOrHintOption, RedactOrHintOptions } from '../../utils/dialog-options';
|
||||||
import { RedactRecommendationData, RedactRecommendationResult } from '../../utils/dialog-types';
|
import { RedactRecommendationData, RedactRecommendationResult } from '../../utils/dialog-types';
|
||||||
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
|
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
|
||||||
|
import { ValueColumn } from '../../components/selected-annotations-table/selected-annotations-table.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './redact-recommendation-dialog.component.html',
|
templateUrl: './redact-recommendation-dialog.component.html',
|
||||||
|
styleUrl: './redact-recommendation-dialog.component.scss',
|
||||||
})
|
})
|
||||||
export class RedactRecommendationDialogComponent
|
export class RedactRecommendationDialogComponent
|
||||||
extends IqserDialogComponent<RedactRecommendationDialogComponent, RedactRecommendationData, RedactRecommendationResult>
|
extends IqserDialogComponent<RedactRecommendationDialogComponent, RedactRecommendationData, RedactRecommendationResult>
|
||||||
@ -38,6 +40,21 @@ export class RedactRecommendationDialogComponent
|
|||||||
reason: [null],
|
reason: [null],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
readonly tableColumns = [
|
||||||
|
{
|
||||||
|
label: 'Value',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Type',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
readonly tableData: ValueColumn[][] = this.data.annotations.map(redaction => [
|
||||||
|
{ label: redaction.value, show: true, bold: true },
|
||||||
|
{ label: redaction.typeLabel, show: true },
|
||||||
|
]);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _justificationsService: JustificationsService,
|
private readonly _justificationsService: JustificationsService,
|
||||||
private readonly _dictionaryService: DictionaryService,
|
private readonly _dictionaryService: DictionaryService,
|
||||||
@ -68,7 +85,7 @@ export class RedactRecommendationDialogComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get disabled() {
|
get disabled() {
|
||||||
return this.dictionaryRequest && !this.form.controls.dictionary.value;
|
return !this.form.controls.dictionary.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
@ -114,7 +131,7 @@ export class RedactRecommendationDialogComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
#setDictionaries() {
|
#setDictionaries() {
|
||||||
this.dictionaries = this._dictionaryService.getRedactTextDictionaries(this.#dossier.dossierTemplateId, !this.#applyToAllDossiers);
|
this.dictionaries = this._dictionaryService.getRedactTextDictionaries(this.#dossier.dossierId, !this.#applyToAllDossiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
#selectReason() {
|
#selectReason() {
|
||||||
@ -143,9 +160,10 @@ export class RedactRecommendationDialogComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
#resetValues() {
|
#resetValues() {
|
||||||
|
const sameType = this.data.annotations.every(annotation => annotation.type === this.firstEntry.type);
|
||||||
this.#applyToAllDossiers = this.data.applyToAllDossiers ?? true;
|
this.#applyToAllDossiers = this.data.applyToAllDossiers ?? true;
|
||||||
if (this.dictionaryRequest) {
|
if (this.dictionaryRequest) {
|
||||||
this.form.controls.dictionary.setValue(this.firstEntry.type);
|
this.form.controls.dictionary.setValue(sameType ? this.firstEntry.type : null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.form.controls.dictionary.setValue(this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null);
|
this.form.controls.dictionary.setValue(this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null);
|
||||||
|
|||||||
@ -3,53 +3,56 @@
|
|||||||
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
|
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
|
||||||
|
|
||||||
<div class="dialog-content redaction">
|
<div class="dialog-content redaction">
|
||||||
<div class="iqser-input-group w-450 selected-text-group">
|
<div class="iqser-input-group w-full selected-text-group">
|
||||||
<label
|
|
||||||
[translate]="
|
|
||||||
initialText === form.controls.selectedText.value
|
|
||||||
? 'redact-text.dialog.content.selected-text'
|
|
||||||
: 'redact-text.dialog.content.text'
|
|
||||||
"
|
|
||||||
class="selected-text"
|
|
||||||
></label>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
[class.fixed-height-36]="dictionaryRequest"
|
[class.fixed-height-36]="dictionaryRequest"
|
||||||
[ngClass]="isEditingSelectedText ? 'flex relative' : 'flex-align-items-center'"
|
[ngClass]="isEditingSelectedText ? 'flex relative' : 'flex-align-items-center'"
|
||||||
>
|
>
|
||||||
<span *ngIf="!isEditingSelectedText" [innerHTML]="form.controls.selectedText.value"></span>
|
<div class="table">
|
||||||
|
<label>Value</label>
|
||||||
|
<div class="row">
|
||||||
|
<span
|
||||||
|
*ngIf="!isEditingSelectedText"
|
||||||
|
[innerHTML]="form.controls.selectedText.value"
|
||||||
|
[ngStyle]="{
|
||||||
|
'min-width': textWidth > maximumSelectedTextWidth ? '95%' : 'unset',
|
||||||
|
'max-width': textWidth > maximumSelectedTextWidth ? 0 : 'unset'
|
||||||
|
}"
|
||||||
|
></span>
|
||||||
|
<textarea
|
||||||
|
*ngIf="isEditingSelectedText"
|
||||||
|
[rows]="selectedTextRows"
|
||||||
|
[ngStyle]="{ width: maximumTextAreaWidth + 'px' }"
|
||||||
|
formControlName="selectedText"
|
||||||
|
iqserHasScrollbar
|
||||||
|
name="comment"
|
||||||
|
type="text"
|
||||||
|
></textarea>
|
||||||
|
|
||||||
<textarea
|
<iqser-circle-button
|
||||||
*ngIf="isEditingSelectedText"
|
(action)="toggleEditingSelectedText()"
|
||||||
[rows]="selectedTextRows"
|
*ngIf="dictionaryRequest"
|
||||||
class="w-full"
|
[showDot]="initialText !== form.get('selectedText').value && !isEditingSelectedText"
|
||||||
formControlName="selectedText"
|
[tooltip]="'redact-text.dialog.content.edit-text' | translate"
|
||||||
iqserHasScrollbar
|
[type]="isEditingSelectedText ? 'dark' : 'default'"
|
||||||
name="comment"
|
[size]="18"
|
||||||
type="text"
|
[iconSize]="13"
|
||||||
></textarea>
|
icon="iqser:edit"
|
||||||
|
tooltipPosition="below"
|
||||||
|
></iqser-circle-button>
|
||||||
|
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="toggleEditingSelectedText()"
|
(action)="undoTextChange()"
|
||||||
*ngIf="dictionaryRequest"
|
*ngIf="isEditingSelectedText"
|
||||||
[class.absolute]="isEditingSelectedText"
|
[showDot]="initialText !== form.get('selectedText').value"
|
||||||
[showDot]="initialText !== form.get('selectedText').value && !isEditingSelectedText"
|
[tooltip]="'redact-text.dialog.content.revert-text' | translate"
|
||||||
[tooltip]="'redact-text.dialog.content.edit-text' | translate"
|
[size]="18"
|
||||||
[type]="isEditingSelectedText ? 'dark' : 'default'"
|
[iconSize]="13"
|
||||||
class="edit-button"
|
icon="red:undo"
|
||||||
icon="iqser:edit"
|
tooltipPosition="below"
|
||||||
tooltipPosition="below"
|
></iqser-circle-button>
|
||||||
></iqser-circle-button>
|
</div>
|
||||||
|
</div>
|
||||||
<iqser-circle-button
|
|
||||||
(action)="undoTextChange()"
|
|
||||||
*ngIf="isEditingSelectedText"
|
|
||||||
[showDot]="initialText !== form.get('selectedText').value"
|
|
||||||
[tooltip]="'redact-text.dialog.content.revert-text' | translate"
|
|
||||||
class="absolute undo-button"
|
|
||||||
icon="red:undo"
|
|
||||||
tooltipPosition="below"
|
|
||||||
></iqser-circle-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -13,29 +13,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-button {
|
iqser-circle-button {
|
||||||
top: 0;
|
padding-left: 8px;
|
||||||
right: calc((0.5rem + 34px) * -1);
|
|
||||||
position: sticky;
|
|
||||||
}
|
|
||||||
|
|
||||||
.undo-button {
|
|
||||||
top: 0;
|
|
||||||
right: calc((0.5rem + 34px) * -2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-full {
|
.w-full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.absolute {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.relative {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fixed-height-36 {
|
.fixed-height-36 {
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
}
|
}
|
||||||
@ -43,3 +28,30 @@
|
|||||||
textarea {
|
textarea {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: calc(100% - 26px);
|
||||||
|
padding: 0 13px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
opacity: 0.7;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--iqser-alt-background);
|
||||||
|
min-width: 100%;
|
||||||
|
|
||||||
|
span {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
list-style-position: inside;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { getRedactOrHintOptions, RedactOrHintOption, RedactOrHintOptions } from
|
|||||||
import { RedactTextData, RedactTextResult } from '../../utils/dialog-types';
|
import { RedactTextData, RedactTextResult } from '../../utils/dialog-types';
|
||||||
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
|
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
|
||||||
|
|
||||||
const MAXIMUM_SELECTED_TEXT_WIDTH = 421;
|
const MAXIMUM_TEXT_AREA_WIDTH = 421;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './redact-text-dialog.component.html',
|
templateUrl: './redact-text-dialog.component.html',
|
||||||
@ -39,6 +39,20 @@ export class RedactTextDialogComponent
|
|||||||
readonly #dossier = inject(ActiveDossiersService).find(this.data.dossierId);
|
readonly #dossier = inject(ActiveDossiersService).find(this.data.dossierId);
|
||||||
readonly #manualRedactionTypeExists = inject(DictionaryService).hasManualType(this.#dossier.dossierTemplateId);
|
readonly #manualRedactionTypeExists = inject(DictionaryService).hasManualType(this.#dossier.dossierTemplateId);
|
||||||
#applyToAllDossiers = this.data.applyToAllDossiers ?? true;
|
#applyToAllDossiers = this.data.applyToAllDossiers ?? true;
|
||||||
|
readonly maximumTextAreaWidth = MAXIMUM_TEXT_AREA_WIDTH;
|
||||||
|
readonly maximumSelectedTextWidth = 567;
|
||||||
|
|
||||||
|
textWidth: number;
|
||||||
|
|
||||||
|
get defaultOption() {
|
||||||
|
const inDossierOption = this.options.find(option => option.value === RedactOrHintOptions.IN_DOSSIER);
|
||||||
|
this.dictionaryRequest = !!inDossierOption && !inDossierOption.disabled;
|
||||||
|
if (this.dictionaryRequest) {
|
||||||
|
this.#setDictionaries();
|
||||||
|
return inDossierOption;
|
||||||
|
}
|
||||||
|
return this.options[0];
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _justificationsService: JustificationsService,
|
private readonly _justificationsService: JustificationsService,
|
||||||
@ -49,7 +63,8 @@ export class RedactTextDialogComponent
|
|||||||
|
|
||||||
this.options = getRedactOrHintOptions(this.#dossier, this.#applyToAllDossiers, this.data.isApprover, this.data.isPageExcluded);
|
this.options = getRedactOrHintOptions(this.#dossier, this.#applyToAllDossiers, this.data.isApprover, this.data.isPageExcluded);
|
||||||
this.form = this.#getForm();
|
this.form = this.#getForm();
|
||||||
|
this.#setupValidators(this.dictionaryRequest ? RedactOrHintOptions.IN_DOSSIER : RedactOrHintOptions.ONLY_HERE);
|
||||||
|
this.textWidth = calcTextWidthInPixels(this.form.controls.selectedText.value);
|
||||||
this.form.controls.option.valueChanges
|
this.form.controls.option.valueChanges
|
||||||
.pipe(
|
.pipe(
|
||||||
tap((option: DetailsRadioOption<RedactOrHintOption>) => {
|
tap((option: DetailsRadioOption<RedactOrHintOption>) => {
|
||||||
@ -125,7 +140,7 @@ export class RedactTextDialogComponent
|
|||||||
this.isEditingSelectedText = !this.isEditingSelectedText;
|
this.isEditingSelectedText = !this.isEditingSelectedText;
|
||||||
if (this.isEditingSelectedText) {
|
if (this.isEditingSelectedText) {
|
||||||
const width = calcTextWidthInPixels(this.form.controls.selectedText.value);
|
const width = calcTextWidthInPixels(this.form.controls.selectedText.value);
|
||||||
this.selectedTextRows = Math.ceil(width / MAXIMUM_SELECTED_TEXT_WIDTH);
|
this.selectedTextRows = Math.ceil(width / MAXIMUM_TEXT_AREA_WIDTH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,16 +161,16 @@ export class RedactTextDialogComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
#setDictionaries() {
|
#setDictionaries() {
|
||||||
this.dictionaries = this._dictionaryService.getRedactTextDictionaries(this.#dossier.dossierTemplateId, !this.#applyToAllDossiers);
|
this.dictionaries = this._dictionaryService.getRedactTextDictionaries(this.#dossier.dossierId, !this.#applyToAllDossiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
#getForm(): FormGroup {
|
#getForm(): FormGroup {
|
||||||
return this._formBuilder.group({
|
return this._formBuilder.group({
|
||||||
selectedText: this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value,
|
selectedText: this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value,
|
||||||
reason: [null as LegalBasisOption, Validators.required],
|
reason: [null as LegalBasisOption],
|
||||||
comment: [null],
|
comment: [null],
|
||||||
dictionary: [this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null],
|
dictionary: [this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null],
|
||||||
option: this.options[0],
|
option: this.defaultOption,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,4 +217,6 @@ export class RedactTextDialogComponent
|
|||||||
}
|
}
|
||||||
this.form.controls.dictionary.setValue(this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null);
|
this.form.controls.dictionary.setValue(this.#manualRedactionTypeExists ? SuperTypes.ManualRedaction : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected readonly window = window;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,51 +1,38 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div
|
<div
|
||||||
*ngIf="data.redactions.length === 1"
|
[innerHTML]="(isBulk ? 'remove-redaction.dialog.title-bulk' : 'remove-redaction.dialog.title') | translate: typeTranslationArg"
|
||||||
[innerHTML]="
|
|
||||||
'remove-redaction.dialog.title' | translate: { type: hint ? 'hint' : recommendation ? 'recommendation' : 'redaction' }
|
|
||||||
"
|
|
||||||
class="dialog-header heading-l"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
*ngIf="data.redactions.length > 1"
|
|
||||||
[innerHTML]="
|
|
||||||
'remove-redaction.dialog.title-bulk' | translate: { type: hint ? 'hint' : recommendation ? 'recommendation' : 'redaction' }
|
|
||||||
"
|
|
||||||
class="dialog-header heading-l"
|
class="dialog-header heading-l"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div class="dialog-content redaction">
|
<div [ngStyle]="{ height: dialogContentHeight + redactedTextsAreaHeight + 'px' }" class="dialog-content redaction">
|
||||||
<div *ngIf="data.redactions.length > 1">
|
<div class="iqser-input-group">
|
||||||
<label
|
<redaction-selected-annotations-table
|
||||||
[translateParams]="{ type: hint ? 'hints' : recommendation ? 'recommendations' : 'redactions' }"
|
[columns]="tableColumns()"
|
||||||
[translate]="'remove-redaction.dialog.content.redacted-text'"
|
[data]="tableData()"
|
||||||
class="selected-text"
|
[staticColumns]="!hasFalsePositiveOption"
|
||||||
></label>
|
></redaction-selected-annotations-table>
|
||||||
|
|
||||||
<cdk-virtual-scroll-viewport
|
|
||||||
[itemSize]="16"
|
|
||||||
[ngStyle]="{ height: redactedTexts.length <= 5 ? 16 * redactedTexts.length + 'px' : 80 + 'px' }"
|
|
||||||
>
|
|
||||||
<ul *cdkVirtualFor="let text of redactedTexts; let idx = index">
|
|
||||||
<li>
|
|
||||||
{{
|
|
||||||
(isFalsePositive
|
|
||||||
? 'remove-redaction.dialog.content.list-item-false-positive'
|
|
||||||
: 'remove-redaction.dialog.content.list-item'
|
|
||||||
)
|
|
||||||
| translate
|
|
||||||
: {
|
|
||||||
text: text,
|
|
||||||
context: data.falsePositiveContext[idx]
|
|
||||||
}
|
|
||||||
| replaceNbsp
|
|
||||||
}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</cdk-virtual-scroll-viewport>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-template #singleSelected>
|
||||||
|
<span
|
||||||
|
[innerHTML]="
|
||||||
|
((recommendation
|
||||||
|
? 'remove-redaction.dialog.content.list-item-false-recommendation'
|
||||||
|
: isFalsePositive
|
||||||
|
? 'remove-redaction.dialog.content.list-item-false-positive'
|
||||||
|
: 'remove-redaction.dialog.content.list-item'
|
||||||
|
)
|
||||||
|
| translate
|
||||||
|
: {
|
||||||
|
text: redactedTexts[0],
|
||||||
|
type: data.redactions[0].typeLabel
|
||||||
|
}
|
||||||
|
| replaceNbsp) + (isFalsePositive ? data.falsePositiveContext[0] : '')
|
||||||
|
"
|
||||||
|
></span>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<iqser-details-radio [options]="options" formControlName="option"></iqser-details-radio>
|
<iqser-details-radio [options]="options" formControlName="option"></iqser-details-radio>
|
||||||
|
|
||||||
<div class="iqser-input-group w-450">
|
<div class="iqser-input-group w-450">
|
||||||
|
|||||||
@ -1,21 +1,4 @@
|
|||||||
@use 'common-mixins';
|
|
||||||
|
|
||||||
.dialog-content {
|
.dialog-content {
|
||||||
height: 408px;
|
padding-top: 8px;
|
||||||
}
|
padding-bottom: 35px;
|
||||||
|
|
||||||
cdk-virtual-scroll-viewport {
|
|
||||||
margin-top: 8px;
|
|
||||||
@include common-mixins.scroll-bar;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep .cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper {
|
|
||||||
max-width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
list-style-position: inside;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, computed } from '@angular/core';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
||||||
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
|
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||||
import { DetailsRadioOption, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
|
import { DetailsRadioOption, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
|
||||||
import { tap } from 'rxjs/operators';
|
import { getRemoveRedactionOptions, RemoveRedactionOption, RemoveRedactionOptions } from '../../utils/dialog-options';
|
||||||
import { getRemoveRedactionOptions, RemoveAnnotationOptions, RemoveRedactionOption } from '../../utils/dialog-options';
|
|
||||||
import { RemoveRedactionData, RemoveRedactionResult } from '../../utils/dialog-types';
|
import { RemoveRedactionData, RemoveRedactionResult } from '../../utils/dialog-types';
|
||||||
import { Roles } from '@users/roles';
|
import { Roles } from '@users/roles';
|
||||||
import { DialogHelpModeKeys } from '../../utils/constants';
|
import { DialogHelpModeKeys } from '../../utils/constants';
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ValueColumn } from '../../components/selected-annotations-table/selected-annotations-table.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './remove-redaction-dialog.component.html',
|
templateUrl: './remove-redaction-dialog.component.html',
|
||||||
@ -17,66 +18,88 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
|
|||||||
RemoveRedactionData,
|
RemoveRedactionData,
|
||||||
RemoveRedactionResult
|
RemoveRedactionResult
|
||||||
> {
|
> {
|
||||||
#applyToAllDossiers: boolean;
|
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
readonly options: DetailsRadioOption<RemoveRedactionOption>[];
|
readonly options: DetailsRadioOption<RemoveRedactionOption>[] = getRemoveRedactionOptions(this.data);
|
||||||
readonly recommendation: boolean;
|
readonly recommendation = this.data.redactions.every(redaction => redaction.isRecommendation);
|
||||||
readonly hint: boolean;
|
readonly hint = this.data.redactions[0].HINT;
|
||||||
readonly skipped: boolean;
|
readonly skipped = this.data.redactions.some(annotation => annotation.isSkipped);
|
||||||
readonly redactedTexts: string[];
|
readonly redactedTexts = this.data.redactions.map(annotation => annotation.value);
|
||||||
|
|
||||||
protected readonly roles = Roles;
|
protected readonly roles = Roles;
|
||||||
form!: UntypedFormGroup;
|
form: UntypedFormGroup = this._formBuilder.group({
|
||||||
|
comment: [null],
|
||||||
|
option: [this.defaultOption],
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly selectedOption = toSignal(this.form.get('option').valueChanges.pipe(map(value => value.value)));
|
||||||
|
readonly isFalsePositive = computed(() => this.selectedOption() === RemoveRedactionOptions.FALSE_POSITIVE);
|
||||||
|
readonly tableColumns = computed<ValueColumn[]>(() => [
|
||||||
|
{
|
||||||
|
label: 'Value',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Type',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Context',
|
||||||
|
show: this.isFalsePositive(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
readonly tableData = computed<ValueColumn[][]>(() =>
|
||||||
|
this.data.redactions.map((redaction, index) => [
|
||||||
|
{ label: redaction.value, show: true, bold: true },
|
||||||
|
{ label: redaction.typeLabel, show: true },
|
||||||
|
{ label: this.data.falsePositiveContext[index], show: this.isFalsePositive() },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
constructor(private readonly _formBuilder: FormBuilder) {
|
constructor(private readonly _formBuilder: FormBuilder) {
|
||||||
super();
|
super();
|
||||||
this.recommendation = this.data.redactions[0].isRecommendation;
|
|
||||||
this.hint = this.data.redactions[0].HINT;
|
|
||||||
this.skipped = this.data.redactions.some(annotation => annotation.isSkipped);
|
|
||||||
this.options = getRemoveRedactionOptions(this.data);
|
|
||||||
this.redactedTexts = this.data.redactions.map(annotation => annotation.value);
|
|
||||||
this.form = this.#getForm();
|
|
||||||
this.#applyToAllDossiers = this.data.applyToAllDossiers ?? true;
|
|
||||||
|
|
||||||
this.form
|
|
||||||
.get('option')
|
|
||||||
.valueChanges.pipe(
|
|
||||||
tap(() => {
|
|
||||||
for (const option of this.options) {
|
|
||||||
if (option.extraOption) {
|
|
||||||
option.extraOption.checked = this.#applyToAllDossiers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
takeUntilDestroyed(),
|
|
||||||
)
|
|
||||||
.subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
get isFalsePositive(): boolean {
|
|
||||||
return this.form.get('option').value.value === RemoveAnnotationOptions['FALSE' + '_POSITIVE'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get helpButtonKey() {
|
get helpButtonKey() {
|
||||||
if (this.hint) {
|
if (this.hint) {
|
||||||
return DialogHelpModeKeys.HINT_REMOVE
|
return DialogHelpModeKeys.HINT_REMOVE;
|
||||||
}
|
}
|
||||||
if (this.recommendation) {
|
if (this.recommendation) {
|
||||||
return DialogHelpModeKeys.RECOMMENDATION_REMOVE
|
return DialogHelpModeKeys.RECOMMENDATION_REMOVE;
|
||||||
}
|
}
|
||||||
if (this.skipped) {
|
if (this.skipped) {
|
||||||
return DialogHelpModeKeys.SKIPPED_REMOVE
|
return DialogHelpModeKeys.SKIPPED_REMOVE;
|
||||||
}
|
}
|
||||||
return DialogHelpModeKeys.REDACTION_REMOVE;
|
return DialogHelpModeKeys.REDACTION_REMOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasFalsePositiveOption() {
|
||||||
|
return !!this.options.find(option => option.value === RemoveRedactionOptions.FALSE_POSITIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultOption() {
|
||||||
|
const removeHereOption = this.options.find(option => option.value === RemoveRedactionOptions.ONLY_HERE);
|
||||||
|
if (!!removeHereOption && !removeHereOption.disabled) return removeHereOption;
|
||||||
|
return this.options[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
get typeTranslationArg() {
|
||||||
|
return { type: this.hint ? 'hint' : this.recommendation ? 'recommendation' : 'redaction' };
|
||||||
|
}
|
||||||
|
|
||||||
|
get isBulk() {
|
||||||
|
return this.data.redactions.length > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
get redactedTextsAreaHeight() {
|
||||||
|
return this.redactedTexts.length <= 10 ? 18 * this.redactedTexts.length : 180;
|
||||||
|
}
|
||||||
|
|
||||||
|
get dialogContentHeight() {
|
||||||
|
return this.options.length * 75 + 230;
|
||||||
|
}
|
||||||
|
|
||||||
save(): void {
|
save(): void {
|
||||||
this.close(this.form.getRawValue());
|
this.close(this.form.getRawValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
#getForm() {
|
|
||||||
return this._formBuilder.group({
|
|
||||||
comment: [null],
|
|
||||||
option: [this.options[0]],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,14 +7,18 @@
|
|||||||
|
|
||||||
<div class="dialog-content redaction">
|
<div class="dialog-content redaction">
|
||||||
<ng-container *ngIf="!redaction.isImage && !redaction.AREA">
|
<ng-container *ngIf="!redaction.isImage && !redaction.AREA">
|
||||||
<div class="iqser-input-group w-450">
|
<div class="flex-start">
|
||||||
<label [translate]="'resize-redaction.dialog.content.original-text'" class="selected-text"></label>
|
<label [translate]="'resize-redaction.dialog.content.original-text'"></label>
|
||||||
<span>{{ redaction.value }}</span>
|
<span class="multi-line-ellipsis"
|
||||||
|
><b>{{ redaction.value }}</b></span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group w-450">
|
<div class="flex-start">
|
||||||
<label [translate]="'resize-redaction.dialog.content.resized-text'" class="selected-text"></label>
|
<label [translate]="'resize-redaction.dialog.content.resized-text'"></label>
|
||||||
<span>{{ data.text }}</span>
|
<span class="multi-line-ellipsis"
|
||||||
|
><b>{{ data.text }}</b></span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
.multi-line-ellipsis {
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-start {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
padding: 0 13px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
opacity: 0.7;
|
||||||
|
font-weight: normal;
|
||||||
|
min-width: 15%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import { ResizeRedactionData, ResizeRedactionResult } from '../../utils/dialog-t
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './resize-redaction-dialog.component.html',
|
templateUrl: './resize-redaction-dialog.component.html',
|
||||||
|
styleUrls: ['./resize-redaction-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class ResizeRedactionDialogComponent extends IqserDialogComponent<
|
export class ResizeRedactionDialogComponent extends IqserDialogComponent<
|
||||||
ResizeRedactionDialogComponent,
|
ResizeRedactionDialogComponent,
|
||||||
@ -29,7 +30,14 @@ export class ResizeRedactionDialogComponent extends IqserDialogComponent<
|
|||||||
|
|
||||||
constructor(private readonly _formBuilder: FormBuilder) {
|
constructor(private readonly _formBuilder: FormBuilder) {
|
||||||
super();
|
super();
|
||||||
this.options = getResizeRedactionOptions(this.redaction, this.#dossier, false, this.#applyToAllDossiers, this.data.isApprover);
|
this.options = getResizeRedactionOptions(
|
||||||
|
this.redaction,
|
||||||
|
this.#dossier,
|
||||||
|
false,
|
||||||
|
this.#applyToAllDossiers,
|
||||||
|
this.data.isApprover,
|
||||||
|
this.data.permissions.canResizeInDictionary,
|
||||||
|
);
|
||||||
this.form = this.#getForm();
|
this.form = this.#getForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -94,7 +94,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right-container">
|
<div class="right-container">
|
||||||
<redaction-file-preview-right-container iqserDisableStopPropagation></redaction-file-preview-right-container>
|
<redaction-file-preview-right-container [iqserDisableStopPropagation]="state.isEditingReviewer()"></redaction-file-preview-right-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -70,6 +70,8 @@ import { DocumentUnloadedGuard } from './services/document-unloaded.guard';
|
|||||||
import { FilePreviewDialogService } from './services/file-preview-dialog.service';
|
import { FilePreviewDialogService } from './services/file-preview-dialog.service';
|
||||||
import { ManualRedactionService } from './services/manual-redaction.service';
|
import { ManualRedactionService } from './services/manual-redaction.service';
|
||||||
import { TablesService } from './services/tables.service';
|
import { TablesService } from './services/tables.service';
|
||||||
|
import { SelectedAnnotationsTableComponent } from './components/selected-annotations-table/selected-annotations-table.component';
|
||||||
|
import { SelectedAnnotationsListComponent } from './components/selected-annotations-list/selected-annotations-list.component';
|
||||||
|
|
||||||
const routes: IqserRoutes = [
|
const routes: IqserRoutes = [
|
||||||
{
|
{
|
||||||
@ -152,6 +154,8 @@ const components = [
|
|||||||
LogPipe,
|
LogPipe,
|
||||||
ReplaceNbspPipe,
|
ReplaceNbspPipe,
|
||||||
DisableStopPropagationDirective,
|
DisableStopPropagationDirective,
|
||||||
|
SelectedAnnotationsTableComponent,
|
||||||
|
SelectedAnnotationsListComponent,
|
||||||
],
|
],
|
||||||
providers: [FilePreviewDialogService, ManualRedactionService, DocumentUnloadedGuard, TablesService],
|
providers: [FilePreviewDialogService, ManualRedactionService, DocumentUnloadedGuard, TablesService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import {
|
|||||||
import { CommentsApiService } from '@services/comments-api.service';
|
import { CommentsApiService } from '@services/comments-api.service';
|
||||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { firstValueFrom, Observable, zip } from 'rxjs';
|
import { firstValueFrom, Observable } from 'rxjs';
|
||||||
import { getFirstRelevantTextPart } from '../../../utils';
|
import { getFirstRelevantTextPart } from '../../../utils';
|
||||||
import { AnnotationDrawService } from '../../pdf-viewer/services/annotation-draw.service';
|
import { AnnotationDrawService } from '../../pdf-viewer/services/annotation-draw.service';
|
||||||
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
|
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
|
||||||
@ -32,6 +32,7 @@ import { ResizeRedactionDialogComponent } from '../dialogs/resize-redaction-dial
|
|||||||
import { RemoveRedactionOptions } from '../utils/dialog-options';
|
import { RemoveRedactionOptions } from '../utils/dialog-options';
|
||||||
import {
|
import {
|
||||||
EditRedactionData,
|
EditRedactionData,
|
||||||
|
EditRedactResult,
|
||||||
RemoveRedactionData,
|
RemoveRedactionData,
|
||||||
RemoveRedactionPermissions,
|
RemoveRedactionPermissions,
|
||||||
RemoveRedactionResult,
|
RemoveRedactionResult,
|
||||||
@ -92,7 +93,7 @@ export class AnnotationActionsService {
|
|||||||
|
|
||||||
async editRedaction(annotations: AnnotationWrapper[]) {
|
async editRedaction(annotations: AnnotationWrapper[]) {
|
||||||
const { dossierId, dossierTemplateId, fileId, file } = this._state;
|
const { dossierId, dossierTemplateId, fileId, file } = this._state;
|
||||||
const isUnprocessed = annotations.every(annotation => annotation.pending);
|
const includeUnprocessed = annotations.every(annotation => this.#includeUnprocessed(annotation, true));
|
||||||
const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
|
const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
|
||||||
const data = {
|
const data = {
|
||||||
annotations,
|
annotations,
|
||||||
@ -101,47 +102,34 @@ export class AnnotationActionsService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const result = await this.#getEditRedactionDialog(data).result();
|
const result = await this.#getEditRedactionDialog(data).result();
|
||||||
|
|
||||||
const requests: Observable<unknown>[] = [];
|
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const recategorizeBody: List<IRecategorizationRequest> = annotations.map(annotation => {
|
||||||
!this.#isDocumine &&
|
const body = { annotationId: annotation.id, type: result.type ?? annotation.type };
|
||||||
(!annotations.every(annotation => annotation.legalBasis === result.legalBasis) ||
|
if (!this.#isDocumine) {
|
||||||
!annotations.every(annotation => annotation.section === result.section))
|
return {
|
||||||
) {
|
...body,
|
||||||
const changeLegalBasisBody = annotations.map(annotation => ({
|
legalBasis: result.legalBasis,
|
||||||
annotationId: annotation.id,
|
section: result.section ?? annotation.section,
|
||||||
legalBasis: result.legalBasis,
|
value: result.value ?? annotation.value,
|
||||||
section: result.section ?? annotation.section,
|
};
|
||||||
value: result.value ?? annotation.value,
|
}
|
||||||
}));
|
return body;
|
||||||
requests.push(
|
});
|
||||||
this._manualRedactionService.changeLegalBasis(
|
|
||||||
changeLegalBasisBody,
|
await this.#processObsAndEmit(
|
||||||
dossierId,
|
this._manualRedactionService
|
||||||
fileId,
|
.recategorizeRedactions(
|
||||||
file().excludedFromAutomaticAnalysis && isUnprocessed,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (result.type && !annotations.every(annotation => annotation.type === result.type)) {
|
|
||||||
const recategorizeBody: List<IRecategorizationRequest> = annotations.map(annotation => ({
|
|
||||||
annotationId: annotation.id,
|
|
||||||
type: result.type ?? annotation.type,
|
|
||||||
}));
|
|
||||||
requests.push(
|
|
||||||
this._manualRedactionService.recategorizeRedactions(
|
|
||||||
recategorizeBody,
|
recategorizeBody,
|
||||||
dossierId,
|
dossierId,
|
||||||
fileId,
|
fileId,
|
||||||
file().excludedFromAutomaticAnalysis && isUnprocessed,
|
this.#getChangedFields(annotations, result),
|
||||||
),
|
includeUnprocessed,
|
||||||
);
|
)
|
||||||
}
|
.pipe(log()),
|
||||||
|
);
|
||||||
|
|
||||||
if (result.comment) {
|
if (result.comment) {
|
||||||
try {
|
try {
|
||||||
@ -152,11 +140,6 @@ export class AnnotationActionsService {
|
|||||||
this._toaster.rawError(error.error.message);
|
this._toaster.rawError(error.error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!requests.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.#processObsAndEmit(zip(requests).pipe(log()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeRedaction(redactions: AnnotationWrapper[], permissions: AnnotationPermissions) {
|
async removeRedaction(redactions: AnnotationWrapper[], permissions: AnnotationPermissions) {
|
||||||
@ -167,7 +150,6 @@ export class AnnotationActionsService {
|
|||||||
};
|
};
|
||||||
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
|
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
|
||||||
const isApprover = this._permissionsService.isApprover(this._state.dossier());
|
const isApprover = this._permissionsService.isApprover(this._state.dossier());
|
||||||
const isUnprocessed = redactions.every(annotation => annotation.pending);
|
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
redactions,
|
redactions,
|
||||||
@ -190,7 +172,7 @@ export class AnnotationActionsService {
|
|||||||
) {
|
) {
|
||||||
this.#setAsFalsePositive(redactions, result);
|
this.#setAsFalsePositive(redactions, result);
|
||||||
} else {
|
} else {
|
||||||
this.#removeRedaction(redactions, result, this._state.file().excludedFromAutomaticAnalysis && isUnprocessed);
|
this.#removeRedaction(redactions, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,8 +222,9 @@ export class AnnotationActionsService {
|
|||||||
this._annotationManager.select(viewerAnnotation);
|
this._annotationManager.select(viewerAnnotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
async acceptResize(annotation: AnnotationWrapper): Promise<void> {
|
async acceptResize(annotation: AnnotationWrapper, permissions: AnnotationPermissions): Promise<void> {
|
||||||
const textAndPositions = await this.#extractTextAndPositions(annotation.id);
|
const textAndPositions = await this.#extractTextAndPositions(annotation.id);
|
||||||
|
const includeUnprocessed = this.#includeUnprocessed(annotation);
|
||||||
if (annotation.isRecommendation) {
|
if (annotation.isRecommendation) {
|
||||||
const recommendation = {
|
const recommendation = {
|
||||||
...annotation,
|
...annotation,
|
||||||
@ -263,12 +246,16 @@ export class AnnotationActionsService {
|
|||||||
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
|
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
|
||||||
const isUnprocessed = annotation.pending;
|
const isUnprocessed = annotation.pending;
|
||||||
|
|
||||||
const data = {
|
const data: ResizeRedactionData = {
|
||||||
redaction: annotation,
|
redaction: annotation,
|
||||||
text,
|
text,
|
||||||
applyToAllDossiers: isApprover && dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault,
|
applyToAllDossiers: isApprover && dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault,
|
||||||
isApprover,
|
isApprover,
|
||||||
dossierId: dossier.dossierId,
|
dossierId: dossier.dossierId,
|
||||||
|
permissions: {
|
||||||
|
canResizeAnnotation: permissions.canResizeAnnotation,
|
||||||
|
canResizeInDictionary: permissions.canResizeInDictionary,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await this.#getResizeRedactionDialog(data).result();
|
const result = await this.#getResizeRedactionDialog(data).result();
|
||||||
@ -289,12 +276,7 @@ export class AnnotationActionsService {
|
|||||||
await this.cancelResize(annotation);
|
await this.cancelResize(annotation);
|
||||||
|
|
||||||
const { fileId, dossierId, file } = this._state;
|
const { fileId, dossierId, file } = this._state;
|
||||||
const request = this._manualRedactionService.resize(
|
const request = this._manualRedactionService.resize([resizeRequest], dossierId, fileId, includeUnprocessed);
|
||||||
[resizeRequest],
|
|
||||||
dossierId,
|
|
||||||
fileId,
|
|
||||||
isUnprocessed && file().excludedFromAutomaticAnalysis,
|
|
||||||
);
|
|
||||||
return this.#processObsAndEmit(request);
|
return this.#processObsAndEmit(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,8 +431,9 @@ export class AnnotationActionsService {
|
|||||||
this.#processObsAndEmit(this._manualRedactionService.addAnnotation(requests, dossierId, fileId)).then();
|
this.#processObsAndEmit(this._manualRedactionService.addAnnotation(requests, dossierId, fileId)).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
#removeRedaction(redactions: AnnotationWrapper[], dialogResult: RemoveRedactionResult, includeUnprocessed = false) {
|
#removeRedaction(redactions: AnnotationWrapper[], dialogResult: RemoveRedactionResult) {
|
||||||
const removeFromDictionary = dialogResult.option.value === RemoveRedactionOptions.IN_DOSSIER;
|
const removeFromDictionary = dialogResult.option.value === RemoveRedactionOptions.IN_DOSSIER;
|
||||||
|
const includeUnprocessed = redactions.every(redaction => this.#includeUnprocessed(redaction, true));
|
||||||
const body = redactions.map(redaction => ({
|
const body = redactions.map(redaction => ({
|
||||||
annotationId: redaction.id,
|
annotationId: redaction.id,
|
||||||
comment: dialogResult.comment,
|
comment: dialogResult.comment,
|
||||||
@ -498,4 +481,37 @@ export class AnnotationActionsService {
|
|||||||
isApprover,
|
isApprover,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#getChangedFields(annotations: AnnotationWrapper[], result: EditRedactResult) {
|
||||||
|
const changedFields = [];
|
||||||
|
if (result.type && !annotations.every(annotation => annotation.type === result.type)) {
|
||||||
|
changedFields.push('type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#isDocumine) {
|
||||||
|
return { changes: changedFields.join(', ') };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.legalBasis && !annotations.every(annotation => annotation.legalBasis === result.legalBasis)) {
|
||||||
|
changedFields.push('reason');
|
||||||
|
}
|
||||||
|
if (typeof result.section === 'string' && !annotations.every(annotation => annotation.section === result.section)) {
|
||||||
|
changedFields.push('paragraph/location');
|
||||||
|
}
|
||||||
|
return { changes: changedFields.join(', ') };
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO this is temporary, based on RED-8950. Should be removed when a better solution will be found
|
||||||
|
#includeUnprocessed(annotation: AnnotationWrapper, isRemoveOrRecategorize = false) {
|
||||||
|
const processed = annotation.entry.manualChanges.at(-1)?.processed;
|
||||||
|
if (!processed) {
|
||||||
|
const autoAnalysisDisabled = this._state.file().excludedFromAutomaticAnalysis;
|
||||||
|
const addedLocallyWhileDisabled = annotation.manual;
|
||||||
|
if (autoAnalysisDisabled) {
|
||||||
|
return addedLocallyWhileDisabled;
|
||||||
|
}
|
||||||
|
return isRemoveOrRecategorize && addedLocallyWhileDisabled;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
} from '../utils/sort-by-page-rotation.utils';
|
} from '../utils/sort-by-page-rotation.utils';
|
||||||
import { FileDataService } from './file-data.service';
|
import { FileDataService } from './file-data.service';
|
||||||
import { FilePreviewStateService } from './file-preview-state.service';
|
import { FilePreviewStateService } from './file-preview-state.service';
|
||||||
|
import { Engines } from '../components/annotation-details/annotation-details.component';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AnnotationProcessingService {
|
export class AnnotationProcessingService {
|
||||||
@ -50,7 +51,8 @@ export class AnnotationProcessingService {
|
|||||||
label: _('filter-menu.redaction-changes'),
|
label: _('filter-menu.redaction-changes'),
|
||||||
checked: false,
|
checked: false,
|
||||||
topLevelFilter: true,
|
topLevelFilter: true,
|
||||||
checker: (annotation: AnnotationWrapper) => annotation?.hasRedactionChanges,
|
checker: (annotation: AnnotationWrapper) =>
|
||||||
|
annotation?.hasRedactionChanges && annotation?.engines?.includes(Engines.MANUAL),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'unseen-pages',
|
id: 'unseen-pages',
|
||||||
@ -124,7 +126,7 @@ export class AnnotationProcessingService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const filter of filters) {
|
for (const filter of filters) {
|
||||||
filter.children.sort((a, b) => a.id.localeCompare(b.id));
|
filter.children.sort((a, b) => a.id?.localeCompare(b.id));
|
||||||
handleCheckedValue(filter);
|
handleCheckedValue(filter);
|
||||||
if (filter.checked || filter.indeterminate) {
|
if (filter.checked || filter.indeterminate) {
|
||||||
filter.expanded = true;
|
filter.expanded = true;
|
||||||
|
|||||||
@ -3,7 +3,18 @@ import { toObservable } from '@angular/core/rxjs-interop';
|
|||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { EntitiesService, getConfig, Toaster } from '@iqser/common-ui';
|
import { EntitiesService, getConfig, Toaster } from '@iqser/common-ui';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { ChangeType, ChangeTypes, File, IEntityLog, IEntityLogEntry, SuperTypeMapper, ViewedPage, ViewMode, ViewModes } from '@red/domain';
|
import {
|
||||||
|
ChangeType,
|
||||||
|
ChangeTypes,
|
||||||
|
EntryStates,
|
||||||
|
File,
|
||||||
|
IEntityLog,
|
||||||
|
IEntityLogEntry,
|
||||||
|
SuperTypeMapper,
|
||||||
|
ViewedPage,
|
||||||
|
ViewMode,
|
||||||
|
ViewModes,
|
||||||
|
} from '@red/domain';
|
||||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||||
import { EarmarksService } from '@services/files/earmarks.service';
|
import { EarmarksService } from '@services/files/earmarks.service';
|
||||||
import { EntityLogService } from '@services/files/entity-log.service';
|
import { EntityLogService } from '@services/files/entity-log.service';
|
||||||
@ -167,7 +178,7 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
|||||||
|
|
||||||
async #convertData(entityLog: IEntityLog) {
|
async #convertData(entityLog: IEntityLog) {
|
||||||
const file = this._state.file();
|
const file = this._state.file();
|
||||||
const annotations: AnnotationWrapper[] = [];
|
let annotations: AnnotationWrapper[] = [];
|
||||||
const dictionaries = this._state.dictionaries;
|
const dictionaries = this._state.dictionaries;
|
||||||
const defaultColors = this._defaultColorsService.find(this._state.dossierTemplateId);
|
const defaultColors = this._defaultColorsService.find(this._state.dossierTemplateId);
|
||||||
let checkDictionary = true;
|
let checkDictionary = true;
|
||||||
@ -180,22 +191,6 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
|||||||
}
|
}
|
||||||
|
|
||||||
const canBeMappedToASuperType = !!SuperTypeMapper[entry.entryType][entry.state](entry);
|
const canBeMappedToASuperType = !!SuperTypeMapper[entry.entryType][entry.state](entry);
|
||||||
if (!canBeMappedToASuperType && this.#isIqserDevMode) {
|
|
||||||
this.#logger.error(
|
|
||||||
`[ENTITY_LOG] Entity ${entry.value} (${entry.entryType}, ${entry.state}) cannot be mapped to a super type!`,
|
|
||||||
entry,
|
|
||||||
);
|
|
||||||
this.#toaster.rawError(
|
|
||||||
`Skipping entity ${entry.value} (${entry.entryType}, ${entry.state}). It has unexpected state.
|
|
||||||
Check console for details.`,
|
|
||||||
{
|
|
||||||
timeOut: 10000,
|
|
||||||
easing: 'ease-in-out',
|
|
||||||
easeTime: 500,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canBeMappedToASuperType) {
|
if (!canBeMappedToASuperType) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -213,6 +208,15 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
|||||||
}
|
}
|
||||||
|
|
||||||
const changeType = this.#getChangeLogType(entry, file);
|
const changeType = this.#getChangeLogType(entry, file);
|
||||||
|
|
||||||
|
if (entry.state === EntryStates.PENDING) {
|
||||||
|
const originalAnnotation = this.#findOriginalAnnotation(annotations, entry);
|
||||||
|
if (originalAnnotation) {
|
||||||
|
entry.oldState = originalAnnotation.entry.state;
|
||||||
|
originalAnnotation.pending = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const annotation = AnnotationWrapper.fromData(
|
const annotation = AnnotationWrapper.fromData(
|
||||||
entry,
|
entry,
|
||||||
dictionary,
|
dictionary,
|
||||||
@ -223,6 +227,10 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
|||||||
);
|
);
|
||||||
|
|
||||||
annotations.push(annotation);
|
annotations.push(annotation);
|
||||||
|
|
||||||
|
if (annotation.pending) {
|
||||||
|
this.#createPendingAnnotations(annotation, annotations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return annotations;
|
return annotations;
|
||||||
@ -271,4 +279,28 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
|
|||||||
this._viewedPagesMapService.delete(this._state.fileId, viewedPage);
|
this._viewedPagesMapService.delete(this._state.fileId, viewedPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#createPendingAnnotations(annotation: AnnotationWrapper, annotations: AnnotationWrapper[]) {
|
||||||
|
for (let i = 0; i < annotation.positions.length; i++) {
|
||||||
|
const pendingAnnotation = {
|
||||||
|
...annotation,
|
||||||
|
id: `${annotation.id}-${i}`,
|
||||||
|
positions: [annotation.positions[i]],
|
||||||
|
hiddenInWorkload: true,
|
||||||
|
} as AnnotationWrapper;
|
||||||
|
annotations.push(pendingAnnotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#findOriginalAnnotation(annotations: AnnotationWrapper[], pendingEntry: IEntityLogEntry) {
|
||||||
|
for (let i = annotations.length - 1; i >= 0; i--) {
|
||||||
|
const annotation = annotations[i];
|
||||||
|
if (annotation.positions[0].page !== pendingEntry.positions[0].pageNumber) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (annotation.id === pendingEntry.id) {
|
||||||
|
return annotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { HttpEvent, HttpEventType, HttpProgressEvent, HttpResponse } from '@angular/common/http';
|
import { HttpEvent, HttpEventType, HttpProgressEvent, HttpResponse } from '@angular/common/http';
|
||||||
import { computed, effect, inject, Injectable, Signal } from '@angular/core';
|
import { computed, effect, inject, Injectable, signal, Signal } from '@angular/core';
|
||||||
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
||||||
import { LoadingService, wipeCache } from '@iqser/common-ui';
|
import { LoadingService, wipeCache } from '@iqser/common-ui';
|
||||||
import { getParam } from '@iqser/common-ui/lib/utils';
|
import { getParam } from '@iqser/common-ui/lib/utils';
|
||||||
@ -45,6 +45,7 @@ export class FilePreviewStateService {
|
|||||||
readonly dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
|
readonly dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
|
||||||
readonly fileId = getParam(FILE_ID);
|
readonly fileId = getParam(FILE_ID);
|
||||||
readonly updateExcludedPagesStyle = computed(() => this.file().excludedPages);
|
readonly updateExcludedPagesStyle = computed(() => this.file().excludedPages);
|
||||||
|
readonly isEditingReviewer = signal(false);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import type {
|
|||||||
import { dictionaryActionsTranslations, manualRedactionActionsTranslations } from '@translations/annotation-actions-translations';
|
import { dictionaryActionsTranslations, manualRedactionActionsTranslations } from '@translations/annotation-actions-translations';
|
||||||
import { Roles } from '@users/roles';
|
import { Roles } from '@users/roles';
|
||||||
import { NGXLogger } from 'ngx-logger';
|
import { NGXLogger } from 'ngx-logger';
|
||||||
import { EMPTY, of, OperatorFunction, pipe } from 'rxjs';
|
import { EMPTY, of, pipe } from 'rxjs';
|
||||||
import { catchError, tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
function getResponseType(error: boolean, isConflict: boolean) {
|
function getResponseType(error: boolean, isConflict: boolean) {
|
||||||
@ -56,18 +56,24 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
|||||||
value: annotation.value,
|
value: annotation.value,
|
||||||
reason: annotation.legalBasis ?? 'Dictionary Request',
|
reason: annotation.legalBasis ?? 'Dictionary Request',
|
||||||
positions: annotation.positions,
|
positions: annotation.positions,
|
||||||
type: redaction.type,
|
type: redaction.type ?? annotation.type,
|
||||||
comment: redaction.comment,
|
comment: redaction.comment,
|
||||||
}));
|
}));
|
||||||
return this.addAnnotation(recommendations, dossierId, fileId);
|
return this.addAnnotation(recommendations, dossierId, fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
changeLegalBasis(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string, includeUnprocessed = false) {
|
recategorizeRedactions(
|
||||||
return this.legalBasisChange(body, dossierId, fileId, includeUnprocessed).pipe(this.#showToast('change-legal-basis'));
|
body: List<IRecategorizationRequest>,
|
||||||
}
|
dossierId: string,
|
||||||
|
fileId: string,
|
||||||
recategorizeRedactions(body: List<IRecategorizationRequest>, dossierId: string, fileId: string, includeUnprocessed = false) {
|
successMessageParameters?: {
|
||||||
return this.recategorize(body, dossierId, fileId, includeUnprocessed).pipe(this.#showToast('change-type'));
|
[key: string]: string;
|
||||||
|
},
|
||||||
|
includeUnprocessed = false,
|
||||||
|
) {
|
||||||
|
return this.recategorize(body, dossierId, fileId, includeUnprocessed).pipe(
|
||||||
|
this.#showToast('recategorize-annotation', false, successMessageParameters),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addAnnotation(
|
addAnnotation(
|
||||||
@ -131,13 +137,6 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
legalBasisChange(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string, includeUnprocessed = false) {
|
|
||||||
return this._post(
|
|
||||||
body,
|
|
||||||
`${this.#bulkRedaction}/legalBasisChange/${dossierId}/${fileId}?includeUnprocessed=${includeUnprocessed}`,
|
|
||||||
).pipe(this.#log('Legal basis change', body));
|
|
||||||
}
|
|
||||||
|
|
||||||
undo(annotationIds: List, dossierId: string, fileId: string) {
|
undo(annotationIds: List, dossierId: string, fileId: string) {
|
||||||
const url = `${this._defaultModelPath}/bulk/undo/${dossierId}/${fileId}`;
|
const url = `${this._defaultModelPath}/bulk/undo/${dossierId}/${fileId}`;
|
||||||
return super.delete(annotationIds, url).pipe(this.#log('Undo', annotationIds));
|
return super.delete(annotationIds, url).pipe(this.#log('Undo', annotationIds));
|
||||||
@ -165,7 +164,11 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#showToast(action: ManualRedactionActions | DictionaryActions, isDictionary = false) {
|
#showToast(
|
||||||
|
action: ManualRedactionActions | DictionaryActions,
|
||||||
|
isDictionary = false,
|
||||||
|
successMessageParameters?: { [key: string]: string },
|
||||||
|
) {
|
||||||
return pipe(
|
return pipe(
|
||||||
catchError((error: unknown) => {
|
catchError((error: unknown) => {
|
||||||
const isConflict = (error as HttpErrorResponse).status === HttpStatusCode.Conflict;
|
const isConflict = (error as HttpErrorResponse).status === HttpStatusCode.Conflict;
|
||||||
@ -175,7 +178,12 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
|||||||
});
|
});
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
}),
|
}),
|
||||||
tap(() => this._toaster.success(getMessage(action, isDictionary), { positionClass: 'toast-file-preview' })),
|
tap(() =>
|
||||||
|
this._toaster.success(getMessage(action, isDictionary), {
|
||||||
|
params: successMessageParameters,
|
||||||
|
positionClass: 'toast-file-preview',
|
||||||
|
}),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export class PdfAnnotationActionsService {
|
|||||||
let acceptResizeButton: IHeaderElement;
|
let acceptResizeButton: IHeaderElement;
|
||||||
if (this.#annotationManager.annotationHasBeenResized) {
|
if (this.#annotationManager.annotationHasBeenResized) {
|
||||||
acceptResizeButton = this.#getButton('check', _('annotation-actions.resize-accept.label'), () =>
|
acceptResizeButton = this.#getButton('check', _('annotation-actions.resize-accept.label'), () =>
|
||||||
this.#annotationActionsService.acceptResize(firstAnnotation),
|
this.#annotationActionsService.acceptResize(firstAnnotation, permissions),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
acceptResizeButton = this.#getDisabledCheckButton();
|
acceptResizeButton = this.#getDisabledCheckButton();
|
||||||
|
|||||||
@ -396,8 +396,9 @@ export class PdfProxyService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const annotationChangesAllowed = !this.#isDocumine || !this._state.file().excludedFromAutomaticAnalysis;
|
const annotationChangesAllowed = !this.#isDocumine || !this._state.file().excludedFromAutomaticAnalysis;
|
||||||
|
const somePending = annotationWrappers.some(a => a.pending);
|
||||||
actions =
|
actions =
|
||||||
this._multiSelectService.inactive() && !this._documentViewer.selectedText.length
|
this._multiSelectService.inactive() && !this._documentViewer.selectedText.length && !somePending
|
||||||
? [...actions, ...this._pdfAnnotationActionsService.get(annotationWrappers, annotationChangesAllowed)]
|
? [...actions, ...this._pdfAnnotationActionsService.get(annotationWrappers, annotationChangesAllowed)]
|
||||||
: [];
|
: [];
|
||||||
this._pdf.instance.UI.annotationPopup.update(actions);
|
this._pdf.instance.UI.annotationPopup.update(actions);
|
||||||
|
|||||||
@ -113,6 +113,7 @@ export const getResizeRedactionOptions = (
|
|||||||
isRss: boolean,
|
isRss: boolean,
|
||||||
applyToAllDossiers: boolean,
|
applyToAllDossiers: boolean,
|
||||||
isApprover: boolean,
|
isApprover: boolean,
|
||||||
|
canResizeInDictionary: boolean,
|
||||||
): DetailsRadioOption<ResizeRedactionOption>[] => {
|
): DetailsRadioOption<ResizeRedactionOption>[] => {
|
||||||
const translations = resizeRedactionTranslations;
|
const translations = resizeRedactionTranslations;
|
||||||
const options: DetailsRadioOption<ResizeRedactionOption>[] = [
|
const options: DetailsRadioOption<ResizeRedactionOption>[] = [
|
||||||
@ -127,22 +128,23 @@ export const getResizeRedactionOptions = (
|
|||||||
if (isRss) {
|
if (isRss) {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
if (canResizeInDictionary) {
|
||||||
const dictBasedType = redaction.isModifyDictionary;
|
const dictBasedType = redaction.isModifyDictionary;
|
||||||
options.push({
|
options.push({
|
||||||
label: translations.inDossier.label,
|
label: translations.inDossier.label,
|
||||||
description: translations.inDossier.description,
|
description: translations.inDossier.description,
|
||||||
descriptionParams: { dossierName: dossier.dossierName },
|
descriptionParams: { dossierName: dossier.dossierName },
|
||||||
disabled: !dictBasedType || redaction.hasBeenRecategorized,
|
disabled: !dictBasedType || redaction.hasBeenRecategorized,
|
||||||
tooltip: !dictBasedType ? translations.inDossier.tooltip : null,
|
tooltip: !dictBasedType ? translations.inDossier.tooltip : null,
|
||||||
icon: FOLDER_ICON,
|
icon: FOLDER_ICON,
|
||||||
value: RedactOrHintOptions.IN_DOSSIER,
|
value: RedactOrHintOptions.IN_DOSSIER,
|
||||||
extraOption: {
|
extraOption: {
|
||||||
label: translations.inDossier.extraOptionLabel,
|
label: translations.inDossier.extraOptionLabel,
|
||||||
checked: applyToAllDossiers,
|
checked: applyToAllDossiers,
|
||||||
hidden: !isApprover,
|
hidden: !isApprover,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -153,6 +155,7 @@ export const getRemoveRedactionOptions = (
|
|||||||
const translations = isDocumine ? removeAnnotationTranslations : removeRedactionTranslations;
|
const translations = isDocumine ? removeAnnotationTranslations : removeRedactionTranslations;
|
||||||
const { permissions, redactions, applyToAllDossiers, isApprover, falsePositiveContext } = data;
|
const { permissions, redactions, applyToAllDossiers, isApprover, falsePositiveContext } = data;
|
||||||
const isBulk = redactions.length > 1;
|
const isBulk = redactions.length > 1;
|
||||||
|
const hasReasonChanged = redactions.some(r => r.hasLegalBasisChanged || r.hasBeenRecategorized);
|
||||||
|
|
||||||
const options: DetailsRadioOption<RemoveRedactionOption>[] = [];
|
const options: DetailsRadioOption<RemoveRedactionOption>[] = [];
|
||||||
if (permissions.canRemoveOnlyHere) {
|
if (permissions.canRemoveOnlyHere) {
|
||||||
@ -167,7 +170,7 @@ export const getRemoveRedactionOptions = (
|
|||||||
value: RemoveRedactionOptions.ONLY_HERE,
|
value: RemoveRedactionOptions.ONLY_HERE,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (permissions.canRemoveFromDictionary) {
|
if (permissions.canRemoveFromDictionary && !hasReasonChanged) {
|
||||||
options.push({
|
options.push({
|
||||||
label: isBulk ? translations.IN_DOSSIER.labelBulk : translations.IN_DOSSIER.label,
|
label: isBulk ? translations.IN_DOSSIER.labelBulk : translations.IN_DOSSIER.label,
|
||||||
description: isBulk ? translations.IN_DOSSIER.descriptionBulk : translations.IN_DOSSIER.description,
|
description: isBulk ? translations.IN_DOSSIER.descriptionBulk : translations.IN_DOSSIER.description,
|
||||||
@ -177,14 +180,14 @@ export const getRemoveRedactionOptions = (
|
|||||||
extraOption: !isDocumine
|
extraOption: !isDocumine
|
||||||
? {
|
? {
|
||||||
label: translations.IN_DOSSIER.extraOptionLabel,
|
label: translations.IN_DOSSIER.extraOptionLabel,
|
||||||
checked: applyToAllDossiers ?? true,
|
checked: redactions[0].isRecommendation && applyToAllDossiers,
|
||||||
hidden: !isApprover,
|
hidden: !isApprover,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (permissions.canMarkAsFalsePositive) {
|
if (permissions.canMarkAsFalsePositive) {
|
||||||
if (data.redactions[0].isRecommendation) {
|
if (redactions[0].isRecommendation) {
|
||||||
options.push({
|
options.push({
|
||||||
label: translations.DO_NOT_RECOMMEND.label,
|
label: translations.DO_NOT_RECOMMEND.label,
|
||||||
description: isBulk ? translations.DO_NOT_RECOMMEND.descriptionBulk : translations.DO_NOT_RECOMMEND.description,
|
description: isBulk ? translations.DO_NOT_RECOMMEND.descriptionBulk : translations.DO_NOT_RECOMMEND.description,
|
||||||
@ -198,7 +201,7 @@ export const getRemoveRedactionOptions = (
|
|||||||
extraOption: !isDocumine
|
extraOption: !isDocumine
|
||||||
? {
|
? {
|
||||||
label: translations.DO_NOT_RECOMMEND.extraOptionLabel,
|
label: translations.DO_NOT_RECOMMEND.extraOptionLabel,
|
||||||
checked: applyToAllDossiers ?? true,
|
checked: applyToAllDossiers,
|
||||||
hidden: !isApprover,
|
hidden: !isApprover,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
@ -217,8 +220,9 @@ export const getRemoveRedactionOptions = (
|
|||||||
extraOption: !isDocumine
|
extraOption: !isDocumine
|
||||||
? {
|
? {
|
||||||
label: translations.FALSE_POSITIVE.extraOptionLabel,
|
label: translations.FALSE_POSITIVE.extraOptionLabel,
|
||||||
checked: applyToAllDossiers ?? true,
|
checked: false,
|
||||||
hidden: !isApprover,
|
hidden: !isApprover,
|
||||||
|
description: translations.FALSE_POSITIVE.extraOptionDescription,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -46,6 +46,11 @@ export interface EditRedactResult {
|
|||||||
export type AddHintResult = RedactTextResult;
|
export type AddHintResult = RedactTextResult;
|
||||||
export type AddAnnotationResult = RedactTextResult;
|
export type AddAnnotationResult = RedactTextResult;
|
||||||
|
|
||||||
|
export interface ResizeRedactionPermissions {
|
||||||
|
canResizeAnnotation: boolean;
|
||||||
|
canResizeInDictionary: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ResizeAnnotationData {
|
export interface ResizeAnnotationData {
|
||||||
redaction: AnnotationWrapper;
|
redaction: AnnotationWrapper;
|
||||||
text: string;
|
text: string;
|
||||||
@ -55,6 +60,7 @@ export interface ResizeAnnotationData {
|
|||||||
export interface ResizeRedactionData extends ResizeAnnotationData {
|
export interface ResizeRedactionData extends ResizeAnnotationData {
|
||||||
applyToAllDossiers?: boolean;
|
applyToAllDossiers?: boolean;
|
||||||
isApprover?: boolean;
|
isApprover?: boolean;
|
||||||
|
permissions: ResizeRedactionPermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResizeAnnotationResult {
|
export interface ResizeAnnotationResult {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
|||||||
import { List } from '@iqser/common-ui/lib/utils';
|
import { List } from '@iqser/common-ui/lib/utils';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { Core } from '@pdftron/webviewer';
|
import { Core } from '@pdftron/webviewer';
|
||||||
import { IRectangle, ISectionRectangle, SuperTypes } from '@red/domain';
|
import { IPoint, IRectangle, ISectionRectangle, SuperTypes } from '@red/domain';
|
||||||
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
||||||
import { hexToRgb } from '@utils/functions';
|
import { hexToRgb } from '@utils/functions';
|
||||||
import { BoundingBox, Table } from '../../file-preview/services/tables.service';
|
import { BoundingBox, Table } from '../../file-preview/services/tables.service';
|
||||||
@ -128,6 +128,27 @@ export class AnnotationDrawService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (annotationWrapper.positions.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (annotationWrapper.pending && annotationWrapper.id.includes('pending')) {
|
||||||
|
const polylineAnnot = this._pdf.polyline();
|
||||||
|
polylineAnnot.ReadOnly = true;
|
||||||
|
polylineAnnot.StrokeColor = this.convertColor(annotationWrapper.color);
|
||||||
|
polylineAnnot.StrokeThickness = 2;
|
||||||
|
polylineAnnot.Id = annotationWrapper.id;
|
||||||
|
polylineAnnot.PageNumber = pageNumber;
|
||||||
|
polylineAnnot.Opacity = polylineAnnot.Id.endsWith('pending') ? 0 : 0.5;
|
||||||
|
|
||||||
|
const points = this.#computePolylinePoints(annotationWrapper.positions, annotationWrapper.pageNumber);
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
polylineAnnot.addPathPoint(points[i].x, points[i].y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return polylineAnnot;
|
||||||
|
}
|
||||||
|
|
||||||
if (annotationWrapper.superType === SuperTypes.TextHighlight) {
|
if (annotationWrapper.superType === SuperTypes.TextHighlight) {
|
||||||
const rectangleAnnot = this._pdf.rectangle();
|
const rectangleAnnot = this._pdf.rectangle();
|
||||||
const pageHeight = this._documentViewer.getHeight(pageNumber);
|
const pageHeight = this._documentViewer.getHeight(pageNumber);
|
||||||
@ -178,24 +199,59 @@ export class AnnotationDrawService {
|
|||||||
return annotation;
|
return annotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#computePolylinePoints(positions: IRectangle[], pageNumber: number): IPoint[] {
|
||||||
|
const pageHeight = this._documentViewer.getHeight(pageNumber);
|
||||||
|
const points = [this.#topLeft(positions[0], pageHeight)];
|
||||||
|
|
||||||
|
for (let i = 0; i < positions.length; i++) {
|
||||||
|
points.push(this.#topRight(positions[i], pageHeight));
|
||||||
|
points.push(this.#bottomRight(positions[i], pageHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = positions.length - 1; i >= 0; i--) {
|
||||||
|
points.push(this.#bottomLeft(positions[i], pageHeight));
|
||||||
|
points.push(this.#topLeft(positions[i], pageHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
points.push(this.#topRight(positions[0], pageHeight));
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
#rectanglesToQuads(positions: IRectangle[], pageNumber: number): Quad[] {
|
#rectanglesToQuads(positions: IRectangle[], pageNumber: number): Quad[] {
|
||||||
const pageHeight = this._documentViewer.getHeight(pageNumber);
|
const pageHeight = this._documentViewer.getHeight(pageNumber);
|
||||||
return positions.map(p => this.#rectangleToQuad(p, pageHeight));
|
return positions.map(p => this.#rectangleToQuad(p, pageHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
#rectangleToQuad(rectangle: IRectangle, pageHeight: number): Quad {
|
#rectangleToQuad(rectangle: IRectangle, pageHeight: number): Quad {
|
||||||
const x1 = rectangle.topLeft.x;
|
const { x: x1, y: y1 } = this.#topLeft(rectangle, pageHeight);
|
||||||
const y1 = pageHeight - (rectangle.topLeft.y + rectangle.height);
|
const { x: x2, y: y2 } = this.#topRight(rectangle, pageHeight);
|
||||||
|
const { x: x3, y: y3 } = this.#bottomRight(rectangle, pageHeight);
|
||||||
const x2 = rectangle.topLeft.x + rectangle.width;
|
const { x: x4, y: y4 } = this.#bottomLeft(rectangle, pageHeight);
|
||||||
const y2 = pageHeight - (rectangle.topLeft.y + rectangle.height);
|
|
||||||
|
|
||||||
const x3 = rectangle.topLeft.x + rectangle.width;
|
|
||||||
const y3 = pageHeight - rectangle.topLeft.y;
|
|
||||||
|
|
||||||
const x4 = rectangle.topLeft.x;
|
|
||||||
const y4 = pageHeight - rectangle.topLeft.y;
|
|
||||||
|
|
||||||
return this._pdf.quad(x1, y1, x2, y2, x3, y3, x4, y4);
|
return this._pdf.quad(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#topLeft(rectangle: IRectangle, pageHeight: number): IPoint {
|
||||||
|
const x = rectangle.topLeft.x;
|
||||||
|
const y = pageHeight - (rectangle.topLeft.y + rectangle.height);
|
||||||
|
return { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
|
#topRight(rectangle: IRectangle, pageHeight: number): IPoint {
|
||||||
|
const x = rectangle.topLeft.x + rectangle.width;
|
||||||
|
const y = pageHeight - (rectangle.topLeft.y + rectangle.height);
|
||||||
|
return { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
|
#bottomRight(rectangle: IRectangle, pageHeight: number): IPoint {
|
||||||
|
const x = rectangle.topLeft.x + rectangle.width;
|
||||||
|
const y = pageHeight - rectangle.topLeft.y;
|
||||||
|
return { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
|
#bottomLeft(rectangle: IRectangle, pageHeight: number): IPoint {
|
||||||
|
const x = rectangle.topLeft.x;
|
||||||
|
const y = pageHeight - rectangle.topLeft.y;
|
||||||
|
return { x, y };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,12 +140,16 @@ export class REDAnnotationManager {
|
|||||||
#listenForAnnotationSelected() {
|
#listenForAnnotationSelected() {
|
||||||
this.#manager.addEventListener('annotationSelected', (annotations: Annotation[], action: string) => {
|
this.#manager.addEventListener('annotationSelected', (annotations: Annotation[], action: string) => {
|
||||||
this.#logger.info('[PDF] Annotation selected: ', annotations, action);
|
this.#logger.info('[PDF] Annotation selected: ', annotations, action);
|
||||||
this.#annotationSelected$.next([annotations, action]);
|
|
||||||
|
|
||||||
const annotation = annotations.length === 1 ? annotations[0] : undefined;
|
const annotation = annotations.length === 1 ? annotations[0] : undefined;
|
||||||
if (annotation && annotation.ToolName === AnnotationToolNames.AnnotationCreateRectangle && action === 'deselected') {
|
if (
|
||||||
|
annotation &&
|
||||||
|
annotation.ToolName === AnnotationToolNames.AnnotationCreateRectangle &&
|
||||||
|
action === 'deselected' &&
|
||||||
|
!this.resizingAnnotationId
|
||||||
|
) {
|
||||||
this.delete(annotation.Id);
|
this.delete(annotation.Id);
|
||||||
}
|
}
|
||||||
|
this.#annotationSelected$.next([annotations, action]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -242,6 +242,10 @@ export class PdfViewer {
|
|||||||
return new this.#instance.Core.Annotations.TextHighlightAnnotation();
|
return new this.#instance.Core.Annotations.TextHighlightAnnotation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
polyline() {
|
||||||
|
return new this.#instance.Core.Annotations.PolylineAnnotation();
|
||||||
|
}
|
||||||
|
|
||||||
isTextHighlight(annotation: Annotation): annotation is TextHighlightAnnotation {
|
isTextHighlight(annotation: Annotation): annotation is TextHighlightAnnotation {
|
||||||
return annotation instanceof this.#instance.Core.Annotations.TextHighlightAnnotation;
|
return annotation instanceof this.#instance.Core.Annotations.TextHighlightAnnotation;
|
||||||
}
|
}
|
||||||
@ -323,15 +327,8 @@ export class PdfViewer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputElement = iframeWindow.document.getElementById('SearchPanel__input') as HTMLInputElement;
|
const clearSearchButton = iframeWindow.document.getElementsByClassName('clearSearch-button')[0] as HTMLButtonElement;
|
||||||
|
clearSearchButton?.click();
|
||||||
if (inputElement) {
|
|
||||||
setTimeout(() => (inputElement.value = ''), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!event.detail.isVisible) {
|
|
||||||
this.documentViewer.clearSearchResults();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -265,6 +265,7 @@ export class FileActionsComponent implements OnChanges {
|
|||||||
tooltip: _('dossier-overview.ocr-file'),
|
tooltip: _('dossier-overview.ocr-file'),
|
||||||
icon: 'iqser:ocr',
|
icon: 'iqser:ocr',
|
||||||
show: this.showOCR,
|
show: this.showOCR,
|
||||||
|
helpModeKey: 'automatic_text_recognition',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'reanalyse-file-btn',
|
id: 'reanalyse-file-btn',
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
<section class="dialog">
|
||||||
|
<div
|
||||||
|
[innerHTML]="'edit-dossier-dialog.dictionary.edit-dialog.title' | translate: { label: data.label }"
|
||||||
|
class="dialog-header heading-l"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<form (submit)="save()" [formGroup]="form">
|
||||||
|
<div class="dialog-content">
|
||||||
|
<div class="iqser-input-group w-300">
|
||||||
|
<mat-checkbox formControlName="addToDictionaryAction">
|
||||||
|
{{ 'edit-dossier-dialog.dictionary.edit-dialog.add-to-dictionary-action' | translate }}
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dialog-actions" *ngIf="data.canEdit">
|
||||||
|
<iqser-icon-button
|
||||||
|
[disabled]="!form.valid || !changed"
|
||||||
|
[label]="'edit-dossier-dialog.dictionary.edit-dialog.save' | translate"
|
||||||
|
[submit]="true"
|
||||||
|
[type]="iconButtonTypes.primary"
|
||||||
|
></iqser-icon-button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
[translate]="'edit-dossier-dialog.dictionary.edit-dialog.cancel'"
|
||||||
|
class="all-caps-label pointer cancel"
|
||||||
|
mat-dialog-close
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
||||||
|
</section>
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
import { Component, HostListener } from '@angular/core';
|
||||||
|
import { IconButtonTypes, IqserDialogComponent, LoadingService, Toaster } from '@iqser/common-ui';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
|
||||||
|
interface DialogData {
|
||||||
|
addToDictionaryAction: boolean;
|
||||||
|
label: string;
|
||||||
|
type: string;
|
||||||
|
dossierTemplateId: string;
|
||||||
|
dossierId: string;
|
||||||
|
canEdit: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReturnType {
|
||||||
|
addToDictionaryAction: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'redaction-edit-dictionary-dialog',
|
||||||
|
templateUrl: './edit-dictionary-dialog.component.html',
|
||||||
|
})
|
||||||
|
export class EditDictionaryDialogComponent extends IqserDialogComponent<EditDictionaryDialogComponent, DialogData, ReturnType> {
|
||||||
|
readonly form = this._formBuilder.group({
|
||||||
|
addToDictionaryAction: [{ value: this.data.addToDictionaryAction, disabled: !this.data.canEdit }],
|
||||||
|
});
|
||||||
|
readonly initialFormValue = this.form.getRawValue();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _formBuilder: FormBuilder,
|
||||||
|
private _dictionaryService: DictionaryService,
|
||||||
|
private _loadingService: LoadingService,
|
||||||
|
private _toaster: Toaster,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:keydown.Escape')
|
||||||
|
closeOnEscape(): void {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
async save() {
|
||||||
|
this._loadingService.start();
|
||||||
|
try {
|
||||||
|
await this._dictionaryService.updateFlag(
|
||||||
|
this.data.dossierTemplateId,
|
||||||
|
this.data.type,
|
||||||
|
this.data.dossierId,
|
||||||
|
this.form.controls.addToDictionaryAction.value,
|
||||||
|
);
|
||||||
|
this.close(this.form.value as ReturnType);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
this._toaster.error(_('edit-dossier-dialog.dictionary.edit-dialog.error.generic'));
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loadingService.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly iconButtonTypes = IconButtonTypes;
|
||||||
|
}
|
||||||
@ -39,7 +39,7 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
|
|||||||
if (!dayjs(attr.value).isSame(dayjs(this.currentAttrValue(attr)), 'day')) {
|
if (!dayjs(attr.value).isSame(dayjs(this.currentAttrValue(attr)), 'day')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (this._parseAttrValue(attr.value) !== this._parseAttrValue(this.currentAttrValue(attr))) {
|
} else if (this.#parseAttrValue(attr.value) !== this.#parseAttrValue(this.currentAttrValue(attr))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,8 +57,8 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
|
|||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
await this._loadAttributes();
|
await this.#loadAttributes();
|
||||||
this.form = this._getForm();
|
this.form = this.#getForm();
|
||||||
this.initialFormValue = this.form.getRawValue();
|
this.initialFormValue = this.form.getRawValue();
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
@ -74,7 +74,7 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
|
|||||||
try {
|
try {
|
||||||
await firstValueFrom(this._dossierAttributesService.setAttributes(this.dossier, dossierAttributeList));
|
await firstValueFrom(this._dossierAttributesService.setAttributes(this.dossier, dossierAttributeList));
|
||||||
await firstValueFrom(this._dossierAttributesService.loadAll(this.dossier.dossierTemplateId));
|
await firstValueFrom(this._dossierAttributesService.loadAll(this.dossier.dossierTemplateId));
|
||||||
await this._loadAttributes();
|
await this.#loadAttributes();
|
||||||
this.initialFormValue = this.form.getRawValue();
|
this.initialFormValue = this.form.getRawValue();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -84,7 +84,7 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileInputClick(attr: DossierAttributeWithValue) {
|
fileInputClick(attr: DossierAttributeWithValue) {
|
||||||
this._getFileInputById(attr.id).nativeElement.click();
|
this.#getFileInputById(attr.id).nativeElement.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
isSpecificType(attr: DossierAttributeWithValue, type: DossierAttributeConfigType): boolean {
|
isSpecificType(attr: DossierAttributeWithValue, type: DossierAttributeConfigType): boolean {
|
||||||
@ -105,11 +105,11 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
|
|||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
[attr.id]: result,
|
[attr.id]: result,
|
||||||
});
|
});
|
||||||
this._getFileInputById(attr.id).nativeElement.value = null;
|
this.#getFileInputById(attr.id).nativeElement.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
revert() {
|
revert() {
|
||||||
this._loadAttributes().then(() => this.form.reset(this.initialFormValue));
|
this.#loadAttributes().then(() => this.form.reset(this.initialFormValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
currentAttrValue(attr: DossierAttributeWithValue): string {
|
currentAttrValue(attr: DossierAttributeWithValue): string {
|
||||||
@ -122,21 +122,21 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getFileInputById(id: string): ElementRef {
|
#getFileInputById(id: string): ElementRef {
|
||||||
return this._fileInputs.find(el => el.nativeElement.id === id);
|
return this._fileInputs.find(el => el.nativeElement.name === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _parseAttrValue(value: any) {
|
#parseAttrValue(value: any) {
|
||||||
return [null, undefined, ''].includes(value) ? undefined : value;
|
return [null, undefined, ''].includes(value) ? undefined : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _loadAttributes() {
|
async #loadAttributes() {
|
||||||
this.attributes = await this._dossierAttributesService.getWithValues(this.dossier);
|
this.attributes = await this._dossierAttributesService.getWithValues(this.dossier);
|
||||||
this.customAttributes = this.attributes.filter(attr => !this.isSpecificType(attr, this.dossierAttributeConfigTypes.IMAGE));
|
this.customAttributes = this.attributes.filter(attr => !this.isSpecificType(attr, this.dossierAttributeConfigTypes.IMAGE));
|
||||||
this.imageAttributes = this.attributes.filter(attr => this.isSpecificType(attr, this.dossierAttributeConfigTypes.IMAGE));
|
this.imageAttributes = this.attributes.filter(attr => this.isSpecificType(attr, this.dossierAttributeConfigTypes.IMAGE));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getForm(): UntypedFormGroup {
|
#getForm(): UntypedFormGroup {
|
||||||
const controlsConfig = {};
|
const controlsConfig = {};
|
||||||
for (const attribute of this.attributes) {
|
for (const attribute of this.attributes) {
|
||||||
controlsConfig[attribute.id] = [{ value: attribute.value, disabled: this.disabled }];
|
controlsConfig[attribute.id] = [{ value: attribute.value, disabled: this.disabled }];
|
||||||
|
|||||||
@ -35,6 +35,14 @@
|
|||||||
<div class="heading">
|
<div class="heading">
|
||||||
<div class="flex-align-items-center">
|
<div class="flex-align-items-center">
|
||||||
{{ selectedDictionary?.label }}
|
{{ selectedDictionary?.label }}
|
||||||
|
<iqser-circle-button
|
||||||
|
*ngIf="selectedDictionary.dossierDictionaryOnly && selectedDictionary.hasDictionary"
|
||||||
|
(action)="openEditDictionaryModal()"
|
||||||
|
[size]="20"
|
||||||
|
[tooltip]="'edit-dossier-dialog.dictionary.edit-button-tooltip' | translate"
|
||||||
|
icon="iqser:edit"
|
||||||
|
class="p-left-8"
|
||||||
|
></iqser-circle-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="small-label stats-subtitle">
|
<div class="small-label stats-subtitle">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -72,3 +72,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-left-8 {
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||||
import { LoadingService } from '@iqser/common-ui';
|
import { IqserDialog, LoadingService } from '@iqser/common-ui';
|
||||||
import { List } from '@iqser/common-ui/lib/utils';
|
import { List } from '@iqser/common-ui/lib/utils';
|
||||||
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, Dossier } from '@red/domain';
|
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, Dossier } from '@red/domain';
|
||||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
|
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
|
|
||||||
import { EditDossierSaveResult } from '../edit-dossier-section.interface';
|
import { EditDossierSaveResult } from '../edit-dossier-section.interface';
|
||||||
|
import { EditDictionaryDialogComponent } from '../../edit-dictionary-dialog/edit-dictionary-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-edit-dossier-dictionary',
|
selector: 'redaction-edit-dossier-dictionary',
|
||||||
@ -17,6 +17,7 @@ import { EditDossierSaveResult } from '../edit-dossier-section.interface';
|
|||||||
export class EditDossierDictionaryComponent implements OnInit {
|
export class EditDossierDictionaryComponent implements OnInit {
|
||||||
@Input() dossier: Dossier;
|
@Input() dossier: Dossier;
|
||||||
canEdit = false;
|
canEdit = false;
|
||||||
|
canEditDictionaryFlag = false;
|
||||||
dictionaries: Dictionary[];
|
dictionaries: Dictionary[];
|
||||||
selectedDictionary: Dictionary;
|
selectedDictionary: Dictionary;
|
||||||
activeEntryType = DictionaryEntryTypes.ENTRY;
|
activeEntryType = DictionaryEntryTypes.ENTRY;
|
||||||
@ -40,12 +41,13 @@ export class EditDossierDictionaryComponent implements OnInit {
|
|||||||
private readonly _dictionaryService: DictionaryService,
|
private readonly _dictionaryService: DictionaryService,
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
private readonly _dialogService: DossiersDialogService,
|
private readonly _iqserDialog: IqserDialog,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
this.canEdit = this._permissionsService.canEditDossierDictionary(this.dossier);
|
this.canEdit = this._permissionsService.canEditDossierDictionary(this.dossier);
|
||||||
|
this.canEditDictionaryFlag = this._permissionsService.isOwner(this.dossier);
|
||||||
await this.#updateDossierDictionary();
|
await this.#updateDossierDictionary();
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
@ -101,19 +103,29 @@ export class EditDossierDictionaryComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #updateDossierDictionary() {
|
async openEditDictionaryModal() {
|
||||||
const { dossierId, dossierTemplateId } = this.dossier;
|
const { dossierId, dossierTemplateId } = this.dossier;
|
||||||
let dictionaryTypes = [
|
const result = await this._iqserDialog
|
||||||
...this._dictionaryService.getRedactTextDictionaries(dossierTemplateId, true),
|
.openDefault(EditDictionaryDialogComponent, {
|
||||||
...this._dictionaryService.getAddHintDictionaries(dossierTemplateId, false, true),
|
data: {
|
||||||
].map(d => d.type);
|
addToDictionaryAction: this.selectedDictionary.addToDictionaryAction,
|
||||||
dictionaryTypes = [...new Set(dictionaryTypes)];
|
label: this.selectedDictionary.label,
|
||||||
this.dictionaries = await firstValueFrom(
|
type: this.selectedDictionary.type,
|
||||||
this._dictionaryService.loadDictionaryEntriesByType(dictionaryTypes, dossierTemplateId, dossierId),
|
dossierId,
|
||||||
);
|
dossierTemplateId,
|
||||||
//TODO remove this when backend will send also the type
|
canEdit: this.canEditDictionaryFlag,
|
||||||
this.#setType(dictionaryTypes);
|
},
|
||||||
this.dictionaries = this.dictionaries.sort((a, b) => a.label.localeCompare(b.label));
|
})
|
||||||
|
.result();
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
this.selectedDictionary = { ...this.selectedDictionary, addToDictionaryAction: result.addToDictionaryAction } as Dictionary;
|
||||||
|
}
|
||||||
|
await this.#retrieveDictionaries();
|
||||||
|
}
|
||||||
|
|
||||||
|
async #updateDossierDictionary() {
|
||||||
|
await this.#retrieveDictionaries();
|
||||||
let dictionaryToSelect = this.dictionaries[0];
|
let dictionaryToSelect = this.dictionaries[0];
|
||||||
if (this.selectedDictionary) {
|
if (this.selectedDictionary) {
|
||||||
dictionaryToSelect = this.dictionaries.find(d => d.type === this.selectedDictionary.type);
|
dictionaryToSelect = this.dictionaries.find(d => d.type === this.selectedDictionary.type);
|
||||||
@ -121,6 +133,17 @@ export class EditDossierDictionaryComponent implements OnInit {
|
|||||||
this.selectDictionary(dictionaryToSelect, this.activeEntryType);
|
this.selectDictionary(dictionaryToSelect, this.activeEntryType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #retrieveDictionaries() {
|
||||||
|
const { dossierId, dossierTemplateId } = this.dossier;
|
||||||
|
const dictionaryTypes = [...new Set(this._dictionaryService.getDictionaries(dossierTemplateId).map(d => d.type))];
|
||||||
|
this.dictionaries = await firstValueFrom(
|
||||||
|
this._dictionaryService.loadDictionaryEntriesByType(dictionaryTypes, dossierTemplateId, dossierId),
|
||||||
|
);
|
||||||
|
//TODO remove this when backend will send also the type
|
||||||
|
this.#setType(dictionaryTypes);
|
||||||
|
this.dictionaries.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
|
}
|
||||||
|
|
||||||
//TODO remove this when backend will send also the type
|
//TODO remove this when backend will send also the type
|
||||||
#setType(dictionaryTypes: string[]) {
|
#setType(dictionaryTypes: string[]) {
|
||||||
for (let i = 0; i < this.dictionaries.length; i++) {
|
for (let i = 0; i < this.dictionaries.length; i++) {
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import { DossiersListingActionsComponent } from './components/dossiers-listing-a
|
|||||||
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
|
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
|
||||||
import { SideNavComponent, SmallChipComponent, StatusBarComponent } from '@iqser/common-ui/lib/shared';
|
import { SideNavComponent, SmallChipComponent, StatusBarComponent } from '@iqser/common-ui/lib/shared';
|
||||||
import { SelectComponent } from '@shared/components/select/select.component';
|
import { SelectComponent } from '@shared/components/select/select.component';
|
||||||
|
import { EditDictionaryDialogComponent } from './dialogs/edit-dictionary-dialog/edit-dictionary-dialog.component';
|
||||||
|
|
||||||
const components = [
|
const components = [
|
||||||
FileActionsComponent,
|
FileActionsComponent,
|
||||||
@ -40,6 +41,7 @@ const components = [
|
|||||||
FileActionsComponent,
|
FileActionsComponent,
|
||||||
DateColumnComponent,
|
DateColumnComponent,
|
||||||
DossiersListingActionsComponent,
|
DossiersListingActionsComponent,
|
||||||
|
EditDictionaryDialogComponent,
|
||||||
];
|
];
|
||||||
const dialogs = [EditDossierDialogComponent, AssignReviewerApproverDialogComponent];
|
const dialogs = [EditDossierDialogComponent, AssignReviewerApproverDialogComponent];
|
||||||
|
|
||||||
|
|||||||
@ -46,10 +46,6 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
get #isDossierRedaction(): boolean {
|
|
||||||
return this.entity?.type === 'dossier_redaction';
|
|
||||||
}
|
|
||||||
|
|
||||||
get #isSystemManaged(): boolean {
|
get #isSystemManaged(): boolean {
|
||||||
return !!this.entity?.systemManaged;
|
return !!this.entity?.systemManaged;
|
||||||
}
|
}
|
||||||
@ -132,28 +128,27 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
|
|||||||
hexColor: [this.entity?.hexColor, [Validators.required, Validators.minLength(7)]],
|
hexColor: [this.entity?.hexColor, [Validators.required, Validators.minLength(7)]],
|
||||||
recommendationHexColor: [this.entity?.recommendationHexColor, [Validators.required, Validators.minLength(7)]],
|
recommendationHexColor: [this.entity?.recommendationHexColor, [Validators.required, Validators.minLength(7)]],
|
||||||
skippedHexColor: [this.entity?.skippedHexColor, [Validators.required, Validators.minLength(7)]],
|
skippedHexColor: [this.entity?.skippedHexColor, [Validators.required, Validators.minLength(7)]],
|
||||||
|
type: [this.entity?.type],
|
||||||
|
description: [this.entity?.description],
|
||||||
|
rank: [{ value: this.entity?.rank, disabled: this.#isSystemManaged }, Validators.required],
|
||||||
|
hint: [{ value: !!this.entity?.hint, disabled: this.#isSystemManaged }],
|
||||||
|
hasDictionary: [
|
||||||
|
{
|
||||||
|
value: !!this.entity?.hasDictionary,
|
||||||
|
disabled: this.#isSystemManaged || (this.entity?.dossierDictionaryOnly && this.entity?.hasDictionary),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dossierDictionaryOnly: [{ value: !!this.entity?.dossierDictionaryOnly, disabled: this.#isSystemManaged || this.entity }],
|
||||||
|
caseSensitive: [{ value: !this.entity?.caseInsensitive, disabled: this.#isSystemManaged }],
|
||||||
|
manageEntriesInDictionaryEditorOnly: [
|
||||||
|
{
|
||||||
|
value: this.entity?.addToDictionaryAction,
|
||||||
|
disabled: this.#isSystemManaged,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.#isDossierRedaction) {
|
if (!this.entity?.hint) {
|
||||||
controlsConfig = {
|
|
||||||
...controlsConfig,
|
|
||||||
type: [this.entity?.type],
|
|
||||||
description: [this.entity?.description],
|
|
||||||
rank: [{ value: this.entity?.rank, disabled: this.#isSystemManaged }, Validators.required],
|
|
||||||
hint: [{ value: !!this.entity?.hint, disabled: this.#isSystemManaged }],
|
|
||||||
hasDictionary: [{ value: !!this.entity?.hasDictionary, disabled: this.#isSystemManaged }],
|
|
||||||
dossierDictionaryOnly: [{ value: !!this.entity?.dossierDictionaryOnly, disabled: this.#isSystemManaged || this.entity }],
|
|
||||||
caseSensitive: [{ value: !this.entity?.caseInsensitive, disabled: this.#isSystemManaged }],
|
|
||||||
manageEntriesInDictionaryEditorOnly: [
|
|
||||||
{
|
|
||||||
value: this.entity?.addToDictionaryAction,
|
|
||||||
disabled: this.#isSystemManaged && !this.#isDossierRedaction,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.entity?.hint && !this.#isDossierRedaction) {
|
|
||||||
controlsConfig = {
|
controlsConfig = {
|
||||||
...controlsConfig,
|
...controlsConfig,
|
||||||
defaultReason: [{ value: null, disabled: true }],
|
defaultReason: [{ value: null, disabled: true }],
|
||||||
@ -165,20 +160,18 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
|
|||||||
this.#initializeColors(form);
|
this.#initializeColors(form);
|
||||||
this.technicalName$ = form.get('label').valueChanges.pipe(map((value: string) => this.#toTechnicalName(value)));
|
this.technicalName$ = form.get('label').valueChanges.pipe(map((value: string) => this.#toTechnicalName(value)));
|
||||||
|
|
||||||
if (!this.#isDossierRedaction) {
|
form.get('hint').valueChanges.subscribe(isHint => {
|
||||||
form.get('hint').valueChanges.subscribe(isHint => {
|
if (isHint) {
|
||||||
if (isHint) {
|
REDACTION_FIELDS.forEach(field => form.removeControl(field));
|
||||||
REDACTION_FIELDS.forEach(field => form.removeControl(field));
|
} else {
|
||||||
} else {
|
form.addControl('defaultReason', new UntypedFormControl({ value: null, disabled: true }));
|
||||||
form.addControl('defaultReason', new UntypedFormControl({ value: null, disabled: true }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!this.entity) {
|
|
||||||
form.get('label').valueChanges.subscribe((label: string) => {
|
|
||||||
form.get('type').setValue(this.#toTechnicalName(label));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.entity) {
|
||||||
|
form.get('label').valueChanges.subscribe((label: string) => {
|
||||||
|
form.get('type').setValue(this.#toTechnicalName(label));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.readOnly) {
|
if (this.readOnly) {
|
||||||
@ -191,7 +184,10 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
|
|||||||
}
|
}
|
||||||
|
|
||||||
#toTechnicalName(value: string) {
|
#toTechnicalName(value: string) {
|
||||||
const existingTechnicalNames = this._dictionariesMapService.get(this.dossierTemplateId).map(dict => dict.type);
|
const existingTechnicalNames = this._dictionariesMapService
|
||||||
|
.get(this.dossierTemplateId)
|
||||||
|
.filter(dict => !dict.virtual)
|
||||||
|
.map(dict => dict.type);
|
||||||
const baseTechnicalName = toSnakeCase(value.trim());
|
const baseTechnicalName = toSnakeCase(value.trim());
|
||||||
let technicalName = baseTechnicalName.replaceAll(/[^A-Za-z0-9_-]/g, '');
|
let technicalName = baseTechnicalName.replaceAll(/[^A-Za-z0-9_-]/g, '');
|
||||||
if (!technicalName.length && baseTechnicalName.length) {
|
if (!technicalName.length && baseTechnicalName.length) {
|
||||||
@ -217,29 +213,21 @@ export class AddEditEntityComponent extends BaseFormComponent implements OnInit
|
|||||||
const hasDictionary = !!this.form.get('hasDictionary')?.value;
|
const hasDictionary = !!this.form.get('hasDictionary')?.value;
|
||||||
const dossierDictionaryOnly = !!this.form.get('dossierDictionaryOnly')?.value;
|
const dossierDictionaryOnly = !!this.form.get('dossierDictionaryOnly')?.value;
|
||||||
|
|
||||||
const entity = {
|
return {
|
||||||
...this.entity,
|
...this.entity,
|
||||||
label: this.form.get('label').value,
|
label: this.form.get('label').value,
|
||||||
hexColor: this.form.get('hexColor').value,
|
hexColor: this.form.get('hexColor').value,
|
||||||
recommendationHexColor: this.form.get('recommendationHexColor').value,
|
recommendationHexColor: this.form.get('recommendationHexColor').value,
|
||||||
skippedHexColor: this.form.get('skippedHexColor').value,
|
skippedHexColor: this.form.get('skippedHexColor').value,
|
||||||
dossierTemplateId: this.dossierTemplateId,
|
dossierTemplateId: this.dossierTemplateId,
|
||||||
|
type: this.form.get('type').value,
|
||||||
|
description: this.form.get('description').value,
|
||||||
|
hint: this.#isHint,
|
||||||
|
rank: this.form.get('rank').value,
|
||||||
|
caseInsensitive: !this.form.get('caseSensitive').value,
|
||||||
|
hasDictionary,
|
||||||
addToDictionaryAction,
|
addToDictionaryAction,
|
||||||
dossierDictionaryOnly,
|
dossierDictionaryOnly,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.entity?.type !== 'dossier_redaction') {
|
|
||||||
return {
|
|
||||||
...entity,
|
|
||||||
type: this.form.get('type').value,
|
|
||||||
description: this.form.get('description').value,
|
|
||||||
hint: this.#isHint,
|
|
||||||
rank: this.form.get('rank').value,
|
|
||||||
caseInsensitive: !this.form.get('caseSensitive').value,
|
|
||||||
hasDictionary,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -121,9 +121,11 @@ export class DictionaryManagerComponent implements OnChanges, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set selectedDictionary(dictionary: Dictionary) {
|
set selectedDictionary(dictionary: Dictionary) {
|
||||||
this.selectedDictionaryType = dictionary.type;
|
if (dictionary.type) {
|
||||||
this.#dictionary = dictionary;
|
this.selectedDictionaryType = dictionary.type;
|
||||||
this.#onDossierChanged(this.#dossier.dossierTemplateId).then(entries => this.#updateDiffEditorText(entries));
|
this.#dictionary = dictionary;
|
||||||
|
this.#onDossierChanged(this.#dossier.dossierTemplateId).then(entries => this.#updateDiffEditorText(entries));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get optionNotSelected() {
|
get optionNotSelected() {
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
<ng-container *ngIf="componentContext$ | async as ctx">
|
<div #filenameColumn>
|
||||||
<div>
|
<div
|
||||||
<div
|
[attr.help-mode-key]="'document_list'"
|
||||||
[attr.help-mode-key]="'document_list'"
|
[class.error]="file.isError"
|
||||||
[class.error]="file.isError"
|
[class.initial-processing]="file.isInitialProcessing || (ocrByDefault && !file.lastOCRTime)"
|
||||||
[class.initial-processing]="file.isInitialProcessing || (ocrByDefault && !file.lastOCRTime)"
|
[matTooltip]="file.filename"
|
||||||
[matTooltip]="file.filename"
|
class="table-item-title"
|
||||||
class="table-item-title"
|
matTooltipPosition="above"
|
||||||
matTooltipPosition="above"
|
>
|
||||||
>
|
{{ file.filename }}
|
||||||
{{ file.filename }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="componentContext$ | async as ctx">
|
||||||
<div *ngIf="ctx.primaryAttribute" class="primary-attribute">
|
<div *ngIf="ctx.primaryAttribute" class="primary-attribute">
|
||||||
<div class="small-label" *ngIf="file?.softDeletedTime; else editableFileAttribute">
|
<div class="small-label" *ngIf="file?.softDeletedTime; else editableFileAttribute">
|
||||||
<div>
|
<div>
|
||||||
@ -26,6 +26,7 @@
|
|||||||
[dossier]="dossier"
|
[dossier]="dossier"
|
||||||
[fileAttribute]="ctx.primaryAttribute"
|
[fileAttribute]="ctx.primaryAttribute"
|
||||||
[fileNameColumn]="true"
|
[fileNameColumn]="true"
|
||||||
|
[width]="width"
|
||||||
></redaction-file-attribute>
|
></redaction-file-attribute>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,14 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
Input,
|
||||||
|
OnChanges,
|
||||||
|
OnInit,
|
||||||
|
SimpleChanges,
|
||||||
|
ViewChild,
|
||||||
|
} from '@angular/core';
|
||||||
import { PrimaryFileAttributeService } from '@services/primary-file-attribute.service';
|
import { PrimaryFileAttributeService } from '@services/primary-file-attribute.service';
|
||||||
import { Dossier, File, IFileAttributeConfig, TrashFile } from '@red/domain';
|
import { Dossier, File, IFileAttributeConfig, TrashFile } from '@red/domain';
|
||||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||||
@ -18,15 +28,18 @@ interface FileNameColumnContext {
|
|||||||
})
|
})
|
||||||
export class FileNameColumnComponent extends ContextComponent<FileNameColumnContext> implements OnInit, OnChanges {
|
export class FileNameColumnComponent extends ContextComponent<FileNameColumnContext> implements OnInit, OnChanges {
|
||||||
readonly #reloadAttribute = new ReplaySubject<void>(1);
|
readonly #reloadAttribute = new ReplaySubject<void>(1);
|
||||||
|
@ViewChild('filenameColumn', { static: true }) filenameColumn: ElementRef;
|
||||||
@Input() file?: File | TrashFile;
|
@Input() file?: File | TrashFile;
|
||||||
@Input() dossier: Dossier;
|
@Input() dossier: Dossier;
|
||||||
@Input() dossierTemplateId: string;
|
@Input() dossierTemplateId: string;
|
||||||
ocrByDefault: boolean;
|
ocrByDefault: boolean;
|
||||||
|
width: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _fileAttributeService: FileAttributesService,
|
private readonly _fileAttributeService: FileAttributesService,
|
||||||
private readonly _primaryFileAttributeService: PrimaryFileAttributeService,
|
private readonly _primaryFileAttributeService: PrimaryFileAttributeService,
|
||||||
private readonly _dossierTemplateService: DossierTemplatesService,
|
private readonly _dossierTemplateService: DossierTemplatesService,
|
||||||
|
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -37,6 +50,10 @@ export class FileNameColumnComponent extends ContextComponent<FileNameColumnCont
|
|||||||
map(() => this.#findPrimaryAttribute()),
|
map(() => this.#findPrimaryAttribute()),
|
||||||
);
|
);
|
||||||
super._initContext({ primaryAttribute: primaryAttribute$ });
|
super._initContext({ primaryAttribute: primaryAttribute$ });
|
||||||
|
const _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
|
||||||
|
this.#updateItemWidth(entries[0]);
|
||||||
|
});
|
||||||
|
_observer.observe(this.filenameColumn.nativeElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
@ -57,4 +74,9 @@ export class FileNameColumnComponent extends ContextComponent<FileNameColumnCont
|
|||||||
const fileAttributes = this._fileAttributeService.getFileAttributeConfig(this.dossierTemplateId);
|
const fileAttributes = this._fileAttributeService.getFileAttributeConfig(this.dossierTemplateId);
|
||||||
return fileAttributes?.fileAttributeConfigs.find(a => a.primaryAttribute);
|
return fileAttributes?.fileAttributeConfigs.find(a => a.primaryAttribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#updateItemWidth(resizeObserverEntry: ResizeObserverEntry) {
|
||||||
|
this.width = resizeObserverEntry.contentRect.width;
|
||||||
|
this._changeDetectorRef.markForCheck();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form [formGroup]="form">
|
||||||
<div [translate]="'add-dossier-dialog.header-new'" class="dialog-header heading-l"></div>
|
<div [translate]="'add-dossier-dialog.header-new'" class="dialog-header heading-l"></div>
|
||||||
|
|
||||||
<div *ngIf="config.IS_DOCUMINE && !availableReportTypes.length" class="inline-dialog-toast toast-warning">
|
<div *ngIf="config.IS_DOCUMINE && !availableReportTypes.length" class="inline-dialog-toast toast-warning">
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { interval, Subject, Subscription } from 'rxjs';
|
|||||||
import { ConfigService } from '@services/config.service';
|
import { ConfigService } from '@services/config.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { IFileUploadResult, OverwriteFileOption, OverwriteFileOptions } from '@red/domain';
|
import { IFileUploadResult, OverwriteFileOption, OverwriteFileOptions } from '@red/domain';
|
||||||
import { isAcceptedFileType, isCsv, isZip } from '@utils/file-drop-utils';
|
import { isAcceptedFileType, isCsv, isDocument, isZip } from '@utils/file-drop-utils';
|
||||||
import { ErrorMessageService, GenericService, Toaster } from '@iqser/common-ui';
|
import { ErrorMessageService, GenericService, Toaster } from '@iqser/common-ui';
|
||||||
import { FilesMapService } from '@services/files/files-map.service';
|
import { FilesMapService } from '@services/files/files-map.service';
|
||||||
import { switchMap, tap, throttleTime } from 'rxjs/operators';
|
import { switchMap, tap, throttleTime } from 'rxjs/operators';
|
||||||
@ -13,6 +13,8 @@ import { FilesService } from '@services/files/files.service';
|
|||||||
import { UploadDownloadDialogService } from './upload-download-dialog.service';
|
import { UploadDownloadDialogService } from './upload-download-dialog.service';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { HeadersConfiguration } from '@iqser/common-ui/lib/utils';
|
import { HeadersConfiguration } from '@iqser/common-ui/lib/utils';
|
||||||
|
import { LicenseService } from '@services/license.service';
|
||||||
|
import { LicenseFeatures } from '../../admin/screens/license/utils/constants';
|
||||||
|
|
||||||
export interface ActiveUpload {
|
export interface ActiveUpload {
|
||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
@ -39,6 +41,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
|||||||
private readonly _configService: ConfigService,
|
private readonly _configService: ConfigService,
|
||||||
private readonly _dialogService: UploadDownloadDialogService,
|
private readonly _dialogService: UploadDownloadDialogService,
|
||||||
private readonly _errorMessageService: ErrorMessageService,
|
private readonly _errorMessageService: ErrorMessageService,
|
||||||
|
private readonly _licenseService: LicenseService,
|
||||||
private readonly _toaster: Toaster,
|
private readonly _toaster: Toaster,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@ -72,6 +75,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
|||||||
const maxSizeMB = this._configService.values.MAX_FILE_SIZE_MB;
|
const maxSizeMB = this._configService.values.MAX_FILE_SIZE_MB;
|
||||||
const maxSizeBytes = maxSizeMB * 1024 * 1024;
|
const maxSizeBytes = maxSizeMB * 1024 * 1024;
|
||||||
const dossierFiles = this._filesMapService.get(dossierId);
|
const dossierFiles = this._filesMapService.get(dossierId);
|
||||||
|
const supportMsOfficeFormats = this._licenseService.getFeature(LicenseFeatures.SUPPORT_MS_OFFICE_FORMATS)?.value as boolean;
|
||||||
let option: OverwriteFileOption = localStorage.getItem('overwriteFileOption') as OverwriteFileOption;
|
let option: OverwriteFileOption = localStorage.getItem('overwriteFileOption') as OverwriteFileOption;
|
||||||
for (let idx = 0; idx < files.length; ++idx) {
|
for (let idx = 0; idx < files.length; ++idx) {
|
||||||
const file = files[idx];
|
const file = files[idx];
|
||||||
@ -115,7 +119,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isAcceptedFileType(file)) {
|
if (!isAcceptedFileType(file, supportMsOfficeFormats)) {
|
||||||
file.completed = true;
|
file.completed = true;
|
||||||
file.error = {
|
file.error = {
|
||||||
message: this._translateService.instant('upload-status.error.file-type'),
|
message: this._translateService.instant('upload-status.error.file-type'),
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { inject, Injectable, OnDestroy } from '@angular/core';
|
|||||||
import { DashboardStatsService } from '../dossier-templates/dashboard-stats.service';
|
import { DashboardStatsService } from '../dossier-templates/dashboard-stats.service';
|
||||||
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
||||||
import { List } from '@iqser/common-ui/lib/utils';
|
import { List } from '@iqser/common-ui/lib/utils';
|
||||||
|
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class DossiersChangesService extends GenericService<Dossier> implements OnDestroy {
|
export class DossiersChangesService extends GenericService<Dossier> implements OnDestroy {
|
||||||
@ -18,6 +19,7 @@ export class DossiersChangesService extends GenericService<Dossier> implements O
|
|||||||
readonly #archivedDossiersService = inject(ArchivedDossiersService);
|
readonly #archivedDossiersService = inject(ArchivedDossiersService);
|
||||||
readonly #dashboardStatsService = inject(DashboardStatsService);
|
readonly #dashboardStatsService = inject(DashboardStatsService);
|
||||||
readonly #logger = inject(NGXLogger);
|
readonly #logger = inject(NGXLogger);
|
||||||
|
readonly #dossierCache = inject(DossiersCacheService);
|
||||||
protected readonly _defaultModelPath = 'dossier';
|
protected readonly _defaultModelPath = 'dossier';
|
||||||
|
|
||||||
loadOnlyChanged(): Observable<IDossierChanges> {
|
loadOnlyChanged(): Observable<IDossierChanges> {
|
||||||
@ -72,6 +74,7 @@ export class DossiersChangesService extends GenericService<Dossier> implements O
|
|||||||
}
|
}
|
||||||
|
|
||||||
#load(id: string): Observable<DossierStats[]> {
|
#load(id: string): Observable<DossierStats[]> {
|
||||||
|
if (!this.#dossierCache.get(id)) return of([]);
|
||||||
const queryParams: List<QueryParam> = [{ key: 'includeArchived', value: true }];
|
const queryParams: List<QueryParam> = [{ key: 'includeArchived', value: true }];
|
||||||
return super._getOne([id], this._defaultModelPath, queryParams).pipe(
|
return super._getOne([id], this._defaultModelPath, queryParams).pipe(
|
||||||
map(entity => new Dossier(entity)),
|
map(entity => new Dossier(entity)),
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { EntitiesService, QueryParam, Toaster } from '@iqser/common-ui';
|
import { EntitiesService, QueryParam, Toaster } from '@iqser/common-ui';
|
||||||
import { List } from '@iqser/common-ui/lib/utils';
|
import { List } from '@iqser/common-ui/lib/utils';
|
||||||
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, IDictionary, IUpdateDictionary, SuperTypes } from '@red/domain';
|
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, IDictionary, IUpdateDictionary, SuperTypes } from '@red/domain';
|
||||||
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
|
import { firstValueFrom, forkJoin, Observable } from 'rxjs';
|
||||||
import { FALLBACK_COLOR } from '@utils/constants';
|
import { map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { firstValueFrom, forkJoin, Observable, throwError } from 'rxjs';
|
|
||||||
import { catchError, map, switchMap, tap } from 'rxjs/operators';
|
|
||||||
import { IMAGE_CATEGORIES } from '../../modules/file-preview/utils/constants';
|
import { IMAGE_CATEGORIES } from '../../modules/file-preview/utils/constants';
|
||||||
import { DictionariesMapService } from './dictionaries-map.service';
|
import { DictionariesMapService } from './dictionaries-map.service';
|
||||||
import { DossierTemplateStatsService } from './dossier-template-stats.service';
|
import { DossierTemplateStatsService } from './dossier-template-stats.service';
|
||||||
@ -25,7 +23,6 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
private readonly _toaster: Toaster,
|
private readonly _toaster: Toaster,
|
||||||
private readonly _dossierTemplateStatsService: DossierTemplateStatsService,
|
private readonly _dossierTemplateStatsService: DossierTemplateStatsService,
|
||||||
private readonly _dictionariesMapService: DictionariesMapService,
|
private readonly _dictionariesMapService: DictionariesMapService,
|
||||||
private readonly _dossierDictionariesMapService: DossierDictionariesMapService,
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -49,8 +46,13 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAllDictionaries(dossierTemplateId: string, dossierId?: string) {
|
getAllDictionaries(dossierTemplateId: string, dossierId?: string) {
|
||||||
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
const queryParams = [];
|
||||||
return this._getOne<{ types: IDictionary[] }>(['type', dossierTemplateId], this._defaultModelPath, queryParams);
|
if (dossierId) {
|
||||||
|
queryParams.push({ key: 'dossierId', value: dossierId });
|
||||||
|
}
|
||||||
|
return this._getOne<{ types: IDictionary[] }>(['type', dossierTemplateId], this._defaultModelPath, queryParams).pipe(
|
||||||
|
map(typesResponse => typesResponse.types.map(type => new Dictionary(type))),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,7 +63,6 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
const url = `${this._defaultModelPath}/type/${type}/${dossierTemplateId}`;
|
const url = `${this._defaultModelPath}/type/${type}/${dossierTemplateId}`;
|
||||||
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
||||||
const request = this._post(body, url, queryParams).pipe(
|
const request = this._post(body, url, queryParams).pipe(
|
||||||
catchError((error: unknown) => this.#addUpdateDictionaryErrorToast(error)),
|
|
||||||
switchMap(() => this.loadDictionaryDataForDossierTemplate(dossierTemplateId)),
|
switchMap(() => this.loadDictionaryDataForDossierTemplate(dossierTemplateId)),
|
||||||
switchMap(() => this._dossierTemplateStatsService.getFor([dossierTemplateId])),
|
switchMap(() => this._dossierTemplateStatsService.getFor([dossierTemplateId])),
|
||||||
);
|
);
|
||||||
@ -69,10 +70,19 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
return await firstValueFrom(request);
|
return await firstValueFrom(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateFlag(dossierTemplateId: string, type: string, dossierId: string, addToDictionary: boolean): Promise<unknown> {
|
||||||
|
const url = `${this._defaultModelPath}/updateFlag/${type}/${dossierTemplateId}/${dossierId}`;
|
||||||
|
const queryParams = [{ key: 'addToDictionary', value: addToDictionary }];
|
||||||
|
const request = this._post(null, url, queryParams).pipe(
|
||||||
|
switchMap(() => this.loadDictionaryDataForDossier(dossierTemplateId, dossierId)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return await firstValueFrom(request);
|
||||||
|
}
|
||||||
|
|
||||||
async add(dictionary: IDictionary, dossierId?: string): Promise<unknown> {
|
async add(dictionary: IDictionary, dossierId?: string): Promise<unknown> {
|
||||||
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
||||||
const request = this._post(dictionary, `${this._defaultModelPath}/type`, queryParams).pipe(
|
const request = this._post(dictionary, `${this._defaultModelPath}/type`, queryParams).pipe(
|
||||||
catchError((error: unknown) => this.#addUpdateDictionaryErrorToast(error)),
|
|
||||||
switchMap(() => this.loadDictionaryDataForDossierTemplate(dictionary.dossierTemplateId)),
|
switchMap(() => this.loadDictionaryDataForDossierTemplate(dictionary.dossierTemplateId)),
|
||||||
switchMap(() => this._dossierTemplateStatsService.getFor([dictionary.dossierTemplateId])),
|
switchMap(() => this._dossierTemplateStatsService.getFor([dictionary.dossierTemplateId])),
|
||||||
);
|
);
|
||||||
@ -137,9 +147,14 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
return !!this._dictionariesMapService.get(dossierTemplateId).find(e => e.type === type && !e.virtual);
|
return !!this._dictionariesMapService.get(dossierTemplateId).find(e => e.type === type && !e.virtual);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRedactTextDictionaries(dossierTemplateId: string, dossierDictionaryOnly: boolean): Dictionary[] {
|
getDictionaries(dossierTemplateId: string) {
|
||||||
return this._dictionariesMapService
|
return this._dictionariesMapService
|
||||||
.get(dossierTemplateId)
|
.get(dossierTemplateId)
|
||||||
|
.filter(d => d.model['typeId'] && (d.dossierDictionaryOnly || d.addToDictionaryAction));
|
||||||
|
}
|
||||||
|
|
||||||
|
getRedactTextDictionaries(dossierId: string, dossierDictionaryOnly: boolean): Dictionary[] {
|
||||||
|
return this.#extractDossierLevelTypes(dossierId)
|
||||||
.filter(d => d.model['typeId'] && !d.hint && d.addToDictionaryAction && (dossierDictionaryOnly || !d.dossierDictionaryOnly))
|
.filter(d => d.model['typeId'] && !d.hint && d.addToDictionaryAction && (dossierDictionaryOnly || !d.dossierDictionaryOnly))
|
||||||
.sort((a, b) => a.label.localeCompare(b.label));
|
.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
}
|
}
|
||||||
@ -157,10 +172,9 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
.sort((a, b) => a.label.localeCompare(b.label));
|
.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
}
|
}
|
||||||
|
|
||||||
getAddHintDictionaries(dossierTemplateId: string, dossierDictionaryOnly: boolean, dictionaryRequest: boolean): Dictionary[] {
|
getAddHintDictionaries(dossierId: string, dossierDictionaryOnly: boolean, dictionaryRequest: boolean): Dictionary[] {
|
||||||
const dictionaries: Dictionary[] = [];
|
const dictionaries: Dictionary[] = [];
|
||||||
|
this.#extractDossierLevelTypes(dossierId).forEach((d: Dictionary) => {
|
||||||
this._dictionariesMapService.get(dossierTemplateId).forEach((d: Dictionary) => {
|
|
||||||
if (d.hint) {
|
if (d.hint) {
|
||||||
if (dictionaryRequest) {
|
if (dictionaryRequest) {
|
||||||
if (d.hasDictionary && d.addToDictionaryAction) {
|
if (d.hasDictionary && d.addToDictionaryAction) {
|
||||||
@ -182,51 +196,17 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRedactionTypes(dossierTemplateId: string): Dictionary[] {
|
getRedactionTypes(dossierTemplateId: string): Dictionary[] {
|
||||||
const possibleDictionaries: Dictionary[] = [];
|
return this._dictionariesMapService
|
||||||
|
.get(dossierTemplateId)
|
||||||
for (const dictionary of this._dictionariesMapService.get(dossierTemplateId)) {
|
.filter(dictionary => !dictionary.virtual && !dictionary.hint && !dictionary.systemManaged)
|
||||||
if (!dictionary.virtual && !dictionary.hint && !dictionary.systemManaged) {
|
.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
possibleDictionaries.push(dictionary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDictionariesOptions(dossierTemplateId: string, dossierId: string): Promise<Dictionary[]> {
|
getDictionariesOptions(dossierTemplateId: string): Dictionary[] {
|
||||||
const possibleDictionaries: Dictionary[] = [];
|
return this._dictionariesMapService
|
||||||
|
.get(dossierTemplateId)
|
||||||
const dossierDictionary: Dictionary = this._dossierDictionariesMapService.get(dossierId, 'dossier_redaction');
|
.filter(dictionary => !dictionary.virtual && dictionary.addToDictionaryAction)
|
||||||
|
.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
for (const dictionary of this._dictionariesMapService.get(dossierTemplateId)) {
|
|
||||||
if (!dictionary.virtual && dictionary.addToDictionaryAction) {
|
|
||||||
possibleDictionaries.push(dictionary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dossierDictionary?.addToDictionaryAction) {
|
|
||||||
possibleDictionaries.push(dossierDictionary);
|
|
||||||
}
|
|
||||||
|
|
||||||
possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
|
|
||||||
|
|
||||||
return possibleDictionaries;
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadDossierRedaction(dossierTemplateId: string, dossierId: string): Promise<Dictionary> {
|
|
||||||
const promise = this.getForType(dossierTemplateId, 'dossier_redaction', dossierId);
|
|
||||||
const dict = await promise.catch(() => undefined);
|
|
||||||
if (dict) {
|
|
||||||
const dictionary = new Dictionary({
|
|
||||||
...dict,
|
|
||||||
type: 'dossier_redaction',
|
|
||||||
});
|
|
||||||
this._dossierDictionariesMapService.set(dossierId, [dictionary]);
|
|
||||||
return dictionary;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._dossierDictionariesMapService.set(dossierId, []);
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDictionaryData(dossierTemplatesIds: string[]): Observable<Dictionary[][]> {
|
loadDictionaryData(dossierTemplatesIds: string[]): Observable<Dictionary[][]> {
|
||||||
@ -239,33 +219,16 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
|
|
||||||
loadDictionaryDataForDossierTemplate(dossierTemplateId: string): Observable<Dictionary[]> {
|
loadDictionaryDataForDossierTemplate(dossierTemplateId: string): Observable<Dictionary[]> {
|
||||||
return this.getAllDictionaries(dossierTemplateId).pipe(
|
return this.getAllDictionaries(dossierTemplateId).pipe(
|
||||||
map(typesResponse => typesResponse.types.map(type => new Dictionary(type))),
|
|
||||||
map(dictionaries => {
|
|
||||||
let manualTypeExists = false;
|
|
||||||
for (const dictionary of dictionaries) {
|
|
||||||
if (dictionary.type === SuperTypes.ManualRedaction) {
|
|
||||||
manualTypeExists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!manualTypeExists) {
|
|
||||||
dictionaries.push(
|
|
||||||
new Dictionary(
|
|
||||||
{
|
|
||||||
hexColor: FALLBACK_COLOR,
|
|
||||||
type: SuperTypes.ManualRedaction,
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dictionaries;
|
|
||||||
}),
|
|
||||||
tap(dictionaries => this._dictionariesMapService.set(dossierTemplateId, dictionaries)),
|
tap(dictionaries => this._dictionariesMapService.set(dossierTemplateId, dictionaries)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadDictionaryDataForDossier(dossierTemplateId: string, dossierId: string): Observable<Dictionary[]> {
|
||||||
|
return this.getAllDictionaries(dossierTemplateId, dossierId).pipe(
|
||||||
|
tap(dictionaries => this._dictionariesMapService.set(dossierId, dictionaries)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
loadDictionaryEntriesByType(types: string[], dossierTemplateId: string, dossierId: string): Observable<Dictionary[]> {
|
loadDictionaryEntriesByType(types: string[], dossierTemplateId: string, dossierId: string): Observable<Dictionary[]> {
|
||||||
const queryParams = [{ key: 'dossierId', value: dossierId }];
|
const queryParams = [{ key: 'dossierId', value: dossierId }];
|
||||||
const requests = [];
|
const requests = [];
|
||||||
@ -303,14 +266,7 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
|||||||
return firstValueFrom(this._post(entries, url, queryParams));
|
return firstValueFrom(this._post(entries, url, queryParams));
|
||||||
}
|
}
|
||||||
|
|
||||||
#addUpdateDictionaryErrorToast(error: HttpErrorResponse | unknown): Observable<never> {
|
#extractDossierLevelTypes(dossierId: string) {
|
||||||
if ((error as HttpErrorResponse).status === HttpStatusCode.Conflict) {
|
return this._dictionariesMapService.get(dossierId).filter(dictionary => dictionary.model['typeId']?.includes(dossierId));
|
||||||
this._toaster.error(_('add-edit-entity.error.entity-already-exists'));
|
|
||||||
} else if ((error as HttpErrorResponse).status === HttpStatusCode.BadRequest) {
|
|
||||||
this._toaster.error(_('add-edit-entity.error.invalid-color-or-rank'));
|
|
||||||
} else {
|
|
||||||
this._toaster.error(_('add-edit-entity.error.generic'));
|
|
||||||
}
|
|
||||||
return throwError(() => error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -115,10 +115,7 @@ export class TrashService extends EntitiesService<TrashItem, TrashItem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _hardDeleteFiles(dossierId: string, fileIds: List) {
|
private _hardDeleteFiles(dossierId: string, fileIds: List) {
|
||||||
const queryParams = fileIds.map<QueryParam>(id => ({ key: 'fileIds', value: id }));
|
return super._post(fileIds, `delete/hard-delete/${dossierId}`).pipe(switchMap(() => this._dossierStatsService.getFor([dossierId])));
|
||||||
return super
|
|
||||||
.delete({}, `delete/hard-delete/${dossierId}`, queryParams)
|
|
||||||
.pipe(switchMap(() => this._dossierStatsService.getFor([dossierId])));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _restoreFiles(dossierId: string, fileIds: List) {
|
private _restoreFiles(dossierId: string, fileIds: List) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { GenericService } from '@iqser/common-ui';
|
import { GenericService, isIqserDevMode, Toaster } from '@iqser/common-ui';
|
||||||
import { IEntityLog, ISectionGrid } from '@red/domain';
|
import { EntryStates, IEntityLog, IEntityLogEntry, ISectionGrid } from '@red/domain';
|
||||||
import { firstValueFrom, of } from 'rxjs';
|
import { firstValueFrom, of } from 'rxjs';
|
||||||
import { catchError } from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -9,11 +9,14 @@ import { catchError } from 'rxjs/operators';
|
|||||||
})
|
})
|
||||||
export class EntityLogService extends GenericService<unknown> {
|
export class EntityLogService extends GenericService<unknown> {
|
||||||
protected readonly _defaultModelPath = '';
|
protected readonly _defaultModelPath = '';
|
||||||
|
readonly #isIqserDevMode = isIqserDevMode();
|
||||||
|
readonly #toaster = inject(Toaster);
|
||||||
|
|
||||||
async getEntityLog(dossierId: string, fileId: string) {
|
async getEntityLog(dossierId: string, fileId: string) {
|
||||||
const queryParams = [{ key: 'includeUnprocessed', value: true }];
|
const queryParams = [{ key: 'includeUnprocessed', value: true }];
|
||||||
const entityLog$ = this._getOne<IEntityLog>([dossierId, fileId], 'entityLog', queryParams);
|
const entityLog$ = this._getOne<IEntityLog>([dossierId, fileId], 'entityLog', queryParams);
|
||||||
const entityLog = await firstValueFrom(entityLog$.pipe(catchError(() => of({} as IEntityLog))));
|
const entityLog = await firstValueFrom(entityLog$.pipe(catchError(() => of({} as IEntityLog))));
|
||||||
|
entityLog.entityLogEntry = this.#filterInvalidEntries(entityLog.entityLogEntry);
|
||||||
entityLog.entityLogEntry.sort((a, b) => a.positions[0].pageNumber - b.positions[0].pageNumber);
|
entityLog.entityLogEntry.sort((a, b) => a.positions[0].pageNumber - b.positions[0].pageNumber);
|
||||||
return entityLog;
|
return entityLog;
|
||||||
}
|
}
|
||||||
@ -21,4 +24,16 @@ export class EntityLogService extends GenericService<unknown> {
|
|||||||
getSectionGrid(dossierId: string, fileId: string) {
|
getSectionGrid(dossierId: string, fileId: string) {
|
||||||
return this._getOne<ISectionGrid>([dossierId, fileId], 'sectionGrid');
|
return this._getOne<ISectionGrid>([dossierId, fileId], 'sectionGrid');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#filterInvalidEntries(entityLogEntry: IEntityLogEntry[]) {
|
||||||
|
return entityLogEntry.filter(entry => {
|
||||||
|
entry.positions = entry.positions?.filter(p => !!p.rectangle?.length);
|
||||||
|
const hasPositions = !!entry.positions?.length;
|
||||||
|
const isRemoved = entry.state === EntryStates.REMOVED;
|
||||||
|
if (!hasPositions) {
|
||||||
|
this.#toaster.devInfo(`Entry ${entry.id} was skipped because it has no position`);
|
||||||
|
}
|
||||||
|
return hasPositions && !isRemoved;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -307,10 +307,7 @@ export class PermissionsService {
|
|||||||
|
|
||||||
canEditDossierDictionary(dossier: Dossier): boolean {
|
canEditDossierDictionary(dossier: Dossier): boolean {
|
||||||
return (
|
return (
|
||||||
dossier.isActive &&
|
dossier.isActive && this.isDossierMember(dossier) && this._iqserPermissionsService.has(Roles.dossiers.dictionaryEntries.write)
|
||||||
this._dossierDictionariesMapService.get(dossier.id).length > 0 &&
|
|
||||||
this.isDossierMember(dossier) &&
|
|
||||||
this._iqserPermissionsService.has(Roles.dossiers.dictionaryEntries.write)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,7 +338,7 @@ export class PermissionsService {
|
|||||||
canDeleteComment(comment: IComment, file: File, dossier: Dossier) {
|
canDeleteComment(comment: IComment, file: File, dossier: Dossier) {
|
||||||
return (
|
return (
|
||||||
this._iqserPermissionsService.has(Roles.comments.delete) &&
|
this._iqserPermissionsService.has(Roles.comments.delete) &&
|
||||||
(comment.user === this.#userId || this.isApprover(dossier)) &&
|
(comment.userId === this.#userId || this.isApprover(dossier)) &&
|
||||||
!file.isApproved
|
!file.isApproved
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,10 +28,6 @@ export const manualRedactionActionsTranslations: Record<ManualRedactionActions,
|
|||||||
error: _('annotation-actions.message.manual-redaction.add.error'),
|
error: _('annotation-actions.message.manual-redaction.add.error'),
|
||||||
success: _('annotation-actions.message.manual-redaction.add.success'),
|
success: _('annotation-actions.message.manual-redaction.add.success'),
|
||||||
},
|
},
|
||||||
'change-legal-basis': {
|
|
||||||
error: _('annotation-actions.message.manual-redaction.change-legal-basis.error'),
|
|
||||||
success: _('annotation-actions.message.manual-redaction.change-legal-basis.success'),
|
|
||||||
},
|
|
||||||
'force-redaction': {
|
'force-redaction': {
|
||||||
error: _('annotation-actions.message.manual-redaction.force-redaction.error'),
|
error: _('annotation-actions.message.manual-redaction.force-redaction.error'),
|
||||||
success: _('annotation-actions.message.manual-redaction.force-redaction.success'),
|
success: _('annotation-actions.message.manual-redaction.force-redaction.success'),
|
||||||
@ -44,9 +40,9 @@ export const manualRedactionActionsTranslations: Record<ManualRedactionActions,
|
|||||||
error: _('annotation-actions.message.manual-redaction.recategorize-image.error'),
|
error: _('annotation-actions.message.manual-redaction.recategorize-image.error'),
|
||||||
success: _('annotation-actions.message.manual-redaction.recategorize-image.success'),
|
success: _('annotation-actions.message.manual-redaction.recategorize-image.success'),
|
||||||
},
|
},
|
||||||
'change-type': {
|
'recategorize-annotation': {
|
||||||
error: _('annotation-actions.message.manual-redaction.change-type.error'),
|
error: _('annotation-actions.message.manual-redaction.recategorize-annotation.error'),
|
||||||
success: _('annotation-actions.message.manual-redaction.change-type.success'),
|
success: _('annotation-actions.message.manual-redaction.recategorize-annotation.success'),
|
||||||
},
|
},
|
||||||
undo: {
|
undo: {
|
||||||
error: _('annotation-actions.message.manual-redaction.undo.error'),
|
error: _('annotation-actions.message.manual-redaction.undo.error'),
|
||||||
|
|||||||
@ -6,6 +6,7 @@ export interface DialogOption {
|
|||||||
description: string;
|
description: string;
|
||||||
descriptionBulk?: string;
|
descriptionBulk?: string;
|
||||||
extraOptionLabel?: string;
|
extraOptionLabel?: string;
|
||||||
|
extraOptionDescription?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const redactTextTranslations: Record<'onlyHere' | 'inDossier', DialogOption> = {
|
export const redactTextTranslations: Record<'onlyHere' | 'inDossier', DialogOption> = {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export const removeRedactionTranslations: { [key in RemoveRedactionOption]: Dial
|
|||||||
description: _('remove-redaction.dialog.content.options.false-positive.description'),
|
description: _('remove-redaction.dialog.content.options.false-positive.description'),
|
||||||
descriptionBulk: _('remove-redaction.dialog.content.options.false-positive.description-bulk'),
|
descriptionBulk: _('remove-redaction.dialog.content.options.false-positive.description-bulk'),
|
||||||
extraOptionLabel: _('remove-redaction.dialog.content.options.false-positive.extraOptionLabel'),
|
extraOptionLabel: _('remove-redaction.dialog.content.options.false-positive.extraOptionLabel'),
|
||||||
|
extraOptionDescription: _('remove-redaction.dialog.content.options.false-positive.extraOptionDescription'),
|
||||||
},
|
},
|
||||||
DO_NOT_RECOMMEND: {
|
DO_NOT_RECOMMEND: {
|
||||||
label: _('remove-redaction.dialog.content.options.do-not-recommend.label'),
|
label: _('remove-redaction.dialog.content.options.do-not-recommend.label'),
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
|
||||||
|
export const selectTenantTranslations: { [key in string]: string } = {
|
||||||
|
IS_LOGGED_OUT: _('tenant-resolve.header.youre-logged-out'),
|
||||||
|
NO_ROLE_LOG_OUT: _('tenant-resolve.header.no-role-log-out'),
|
||||||
|
} as const;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user