diff --git a/apps/red-ui/src/app/guards/dictionary-exists.guard.ts b/apps/red-ui/src/app/guards/dictionary-exists.guard.ts deleted file mode 100644 index 4f7377cba..000000000 --- a/apps/red-ui/src/app/guards/dictionary-exists.guard.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; -import { DICTIONARY_TYPE, DOSSIER_TEMPLATE_ID } from '@utils/constants'; -import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; - -@Injectable({ providedIn: 'root' }) -export class DictionaryExistsGuard implements CanActivate { - constructor(private readonly _dictionariesMapService: DictionariesMapService, private readonly _router: Router) {} - - async canActivate(route: ActivatedRouteSnapshot): Promise { - const dossierTemplateId: string = route.paramMap.get(DOSSIER_TEMPLATE_ID); - const type: string = route.paramMap.get(DICTIONARY_TYPE); - - if (!this._dictionariesMapService.get(dossierTemplateId, type)) { - await this._router.navigate(['main', 'admin', 'dossier-templates', dossierTemplateId, 'dictionaries']); - return false; - } - - return true; - } -} diff --git a/apps/red-ui/src/app/guards/dossier-templates.guard.ts b/apps/red-ui/src/app/guards/dossier-templates.guard.ts index 4d0995f44..9a163ee8f 100644 --- a/apps/red-ui/src/app/guards/dossier-templates.guard.ts +++ b/apps/red-ui/src/app/guards/dossier-templates.guard.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { CanActivate } from '@angular/router'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; import { firstValueFrom } from 'rxjs'; -import { DictionaryService } from '@shared/services/dictionary.service'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; @Injectable({ providedIn: 'root' }) export class DossierTemplatesGuard implements CanActivate { diff --git a/apps/red-ui/src/app/guards/entity-exists-guard.service.ts b/apps/red-ui/src/app/guards/entity-exists-guard.service.ts new file mode 100644 index 000000000..f363829ff --- /dev/null +++ b/apps/red-ui/src/app/guards/entity-exists-guard.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; +import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants'; +import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; +import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; + +@Injectable({ providedIn: 'root' }) +export class EntityExistsGuard implements CanActivate { + constructor( + private readonly _dictionariesMapService: DictionariesMapService, + private readonly _router: Router, + private readonly _dossierTemplatesService: DossierTemplatesService, + ) {} + + async canActivate(route: ActivatedRouteSnapshot): Promise { + const dossierTemplateId: string = route.paramMap.get(DOSSIER_TEMPLATE_ID); + const type: string = route.paramMap.get(ENTITY_TYPE); + + if (!this._dictionariesMapService.get(dossierTemplateId, type)) { + const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId); + await this._router.navigate([`${dossierTemplate.routerLink}/entities`]); + return false; + } + + return true; + } +} diff --git a/apps/red-ui/src/app/models/file/annotation.permissions.ts b/apps/red-ui/src/app/models/file/annotation.permissions.ts index 428b69a9b..2beb6e759 100644 --- a/apps/red-ui/src/app/models/file/annotation.permissions.ts +++ b/apps/red-ui/src/app/models/file/annotation.permissions.ts @@ -1,5 +1,5 @@ import { AnnotationWrapper } from './annotation.wrapper'; -import { User } from '@red/domain'; +import { Dictionary, User } from '@red/domain'; import { isArray } from 'lodash-es'; export class AnnotationPermissions { @@ -16,7 +16,7 @@ export class AnnotationPermissions { canRecategorizeImage = true; canForceHint = true; - static forUser(isApprover: boolean, user: User, annotations: AnnotationWrapper | AnnotationWrapper[]) { + static forUser(isApprover: boolean, user: User, annotations: AnnotationWrapper | AnnotationWrapper[], entities: Dictionary[]) { if (!isArray(annotations)) { annotations = [annotations]; } @@ -34,7 +34,8 @@ export class AnnotationPermissions { permissions.canForceRedaction = annotation.isSkipped && !annotation.isFalsePositive && !annotation.pending; permissions.canAcceptRecommendation = annotation.isRecommendation && !annotation.pending; - permissions.canMarkAsFalsePositive = annotation.canBeMarkedAsFalsePositive && !annotation.imported && !annotation.pending; + const annotationEntity = entities.find(entity => entity.type === annotation.type); + permissions.canMarkAsFalsePositive = annotation.canBeMarkedAsFalsePositive && annotationEntity.hasDictionary; permissions.canRemoveOrSuggestToRemoveOnlyHere = (annotation.isRedacted || annotation.isHint) && !annotation.pending; permissions.canRemoveOrSuggestToRemoveFromDictionary = diff --git a/apps/red-ui/src/app/models/file/annotation.wrapper.ts b/apps/red-ui/src/app/models/file/annotation.wrapper.ts index 0113c05bb..9d5a99980 100644 --- a/apps/red-ui/src/app/models/file/annotation.wrapper.ts +++ b/apps/red-ui/src/app/models/file/annotation.wrapper.ts @@ -79,7 +79,7 @@ export class AnnotationWrapper implements Record { } get canBeMarkedAsFalsePositive() { - return (this.isRecommendation || this.superType === SuperTypes.Redaction) && !this.isImage; + return (this.isRecommendation || this.superType === SuperTypes.Redaction) && !this.isImage && !this.imported && !this.pending; } get isSuperTypeBasedColor() { @@ -294,7 +294,7 @@ export class AnnotationWrapper implements Record { private static _handleRecommendations(annotationWrapper: AnnotationWrapper, redactionLogEntry: RedactionLogEntry) { if (annotationWrapper.superType === SuperTypes.Recommendation) { - annotationWrapper.recommendationType = redactionLogEntry.type.substr('recommendation_'.length); + annotationWrapper.recommendationType = redactionLogEntry.type; } } diff --git a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts index 785351d42..0f1446a68 100644 --- a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts @@ -2,8 +2,7 @@ import { NgModule } from '@angular/core'; import { AuthGuard } from '../auth/auth.guard'; import { CompositeRouteGuard } from '@iqser/common-ui'; import { RedRoleGuard } from '../auth/red-role.guard'; -import { DictionaryListingScreenComponent } from './screens/dictionary-listing/dictionary-listing-screen.component'; -import { DictionaryOverviewScreenComponent } from './screens/dictionary-overview/dictionary-overview-screen.component'; +import { EntitiesListingScreenComponent } from './screens/entities-listing/entities-listing-screen.component'; import { PendingChangesGuard } from '@guards/can-deactivate.guard'; import { FileAttributesListingScreenComponent } from './screens/file-attributes-listing/file-attributes-listing-screen.component'; import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component'; @@ -18,12 +17,13 @@ import { GeneralConfigScreenComponent } from './screens/general-config/general-c import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component'; import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component'; import { DossierTemplatesGuard } from '@guards/dossier-templates.guard'; -import { DICTIONARY_TYPE, DOSSIER_TEMPLATE_ID } from '@utils/constants'; +import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants'; import { DossierTemplateExistsGuard } from '@guards/dossier-template-exists.guard'; -import { DictionaryExistsGuard } from '@guards/dictionary-exists.guard'; +import { EntityExistsGuard } from '@guards/entity-exists-guard.service'; import { DossierStatesListingScreenComponent } from './screens/dossier-states-listing/dossier-states-listing-screen.component'; import { DossiersGuard } from '@guards/dossiers.guard'; import { ACTIVE_DOSSIERS_SERVICE } from '../../tokens'; +import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component'; const routes: Routes = [ { path: '', redirectTo: 'dossier-templates', pathMatch: 'full' }, @@ -52,23 +52,23 @@ const routes: Routes = [ loadChildren: () => import('./screens/info/dossier-template-info.module').then(m => m.DossierTemplateInfoModule), }, { - path: 'dictionaries', + path: 'entities', children: [ { path: '', - component: DictionaryListingScreenComponent, + component: EntitiesListingScreenComponent, canActivate: [CompositeRouteGuard], data: { routeGuards: [AuthGuard, RedRoleGuard], }, }, { - path: `:${DICTIONARY_TYPE}`, - component: DictionaryOverviewScreenComponent, + path: `:${ENTITY_TYPE}`, + component: BaseEntityScreenComponent, canActivate: [CompositeRouteGuard], - canDeactivate: [PendingChangesGuard], + loadChildren: () => import('./screens/entities/entities.module').then(m => m.EntitiesModule), data: { - routeGuards: [AuthGuard, RedRoleGuard, DictionaryExistsGuard], + routeGuards: [AuthGuard, RedRoleGuard, EntityExistsGuard], }, }, ], diff --git a/apps/red-ui/src/app/modules/admin/admin-side-nav/admin-side-nav.component.html b/apps/red-ui/src/app/modules/admin/admin-side-nav/admin-side-nav.component.html index 4338e7eea..b23f4cb39 100644 --- a/apps/red-ui/src/app/modules/admin/admin-side-nav/admin-side-nav.component.html +++ b/apps/red-ui/src/app/modules/admin/admin-side-nav/admin-side-nav.component.html @@ -2,6 +2,7 @@ + + +
+
+ + + + +
+ diff --git a/apps/red-ui/src/app/modules/admin/base-entity-screen/base-entity-screen.component.ts b/apps/red-ui/src/app/modules/admin/base-entity-screen/base-entity-screen.component.ts new file mode 100644 index 000000000..7f1796ce7 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/base-entity-screen/base-entity-screen.component.ts @@ -0,0 +1,49 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants'; +import { ActivatedRoute, Router } from '@angular/router'; +import { firstValueFrom, Observable } from 'rxjs'; +import { AdminDialogService } from '../services/admin-dialog.service'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; +import { UserService } from '@services/user.service'; +import { LoadingService } from '@iqser/common-ui'; +import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; +import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; +import { map } from 'rxjs/operators'; + +@Component({ + templateUrl: './base-entity-screen.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class BaseEntityScreenComponent { + readonly currentUser = this._userService.currentUser; + readonly disabledItems$: Observable; + readonly #dossierTemplateId: string; + readonly #entityType: string; + + constructor( + private readonly _route: ActivatedRoute, + private readonly _userService: UserService, + private readonly _loadingService: LoadingService, + private readonly _dialogService: AdminDialogService, + private readonly _dictionaryService: DictionaryService, + private readonly _dictionaryMapService: DictionariesMapService, + private readonly _dossierTemplatesService: DossierTemplatesService, + private readonly _router: Router, + ) { + this.#dossierTemplateId = this._route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID); + this.#entityType = this._route.snapshot.paramMap.get(ENTITY_TYPE); + this.disabledItems$ = this._dictionaryMapService + .watch$(this.#dossierTemplateId, this.#entityType) + .pipe(map(entity => (entity.hasDictionary ? [] : ['dictionary', 'false-positive', 'false-recommendations']))); + } + + openDeleteDictionariesDialog() { + this._dialogService.openDialog('confirm', null, null, async () => { + this._loadingService.start(); + const dossierTemplate = this._dossierTemplatesService.find(this.#dossierTemplateId); + await firstValueFrom(this._dictionaryService.deleteDictionaries([this.#entityType], this.#dossierTemplateId)); + await this._router.navigate([`${dossierTemplate.routerLink}/entities`]); + this._loadingService.stop(); + }); + } +} diff --git a/apps/red-ui/src/app/modules/admin/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component.ts b/apps/red-ui/src/app/modules/admin/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component.ts index f77417356..da0e9ac58 100644 --- a/apps/red-ui/src/app/modules/admin/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component.ts +++ b/apps/red-ui/src/app/modules/admin/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component.ts @@ -5,7 +5,7 @@ import { map, switchMap } from 'rxjs/operators'; import { ActivatedRoute } from '@angular/router'; import { Dictionary, DossierTemplate } from '@red/domain'; import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; -import { DOSSIER_TEMPLATE_ID } from '@utils/constants'; +import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants'; @Component({ selector: 'redaction-dossier-template-breadcrumbs', @@ -27,7 +27,7 @@ export class DossierTemplateBreadcrumbsComponent { switchMap((dossierTemplateId: string) => this._dossierTemplatesService.getEntityChanged$(dossierTemplateId)), ); this.activeDictionary$ = _route.paramMap.pipe( - map(params => [params.get(DOSSIER_TEMPLATE_ID), params.get('dictionary')]), + map(params => [params.get(DOSSIER_TEMPLATE_ID), params.get(ENTITY_TYPE)]), switchMap(([dossierTemplateId, dictionary]: [string, string]) => dictionary ? this._dictionariesMapService.watch$(dossierTemplateId, dictionary) : of(undefined), ), diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.html deleted file mode 100644 index 7acf0cf23..000000000 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.html +++ /dev/null @@ -1,105 +0,0 @@ -
-
- {{ dialogHeader }} -
- -
-
-
- -
{{ dictionary?.type || (technicalName$ | async) || '-' }}
-
- -
- - {{ dictionary.label }} -
- -
-
- - - -
- -
- - -
- -
- - -
- -
-
-
- -
- - -
- -
- - - {{ 'add-edit-dictionary.form.redaction' | translate }} - - - {{ 'add-edit-dictionary.form.hint' | translate }} - - -
- -
- - {{ 'add-edit-dictionary.form.case-sensitive' | translate }} - -
- -
- - {{ 'add-edit-dictionary.form.add-to-dictionary-action' | translate }} - -
-
- -
- -
-
- - -
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts deleted file mode 100644 index 54354afa3..000000000 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { firstValueFrom, Observable } from 'rxjs'; -import { BaseDialogComponent, LoadingService, shareDistinctLast, Toaster } from '@iqser/common-ui'; -import { TranslateService } from '@ngx-translate/core'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { toSnakeCase } from '@utils/functions'; -import { DictionaryService } from '@shared/services/dictionary.service'; -import { Dictionary, IDictionary } from '@red/domain'; -import { UserService } from '@services/user.service'; -import { map } from 'rxjs/operators'; -import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; - -@Component({ - selector: 'redaction-add-edit-dictionary-dialog', - templateUrl: './add-edit-dictionary-dialog.component.html', - styleUrls: ['./add-edit-dictionary-dialog.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AddEditDictionaryDialogComponent extends BaseDialogComponent { - readonly dictionary = this._data.dictionary; - readonly canEditLabel$ = this._canEditLabel$; - readonly technicalName$: Observable; - readonly dialogHeader = this._translateService.instant('add-edit-dictionary.title', { - type: this._data.dictionary ? 'edit' : 'create', - name: this._data.dictionary?.label, - }); - readonly hasColor$: Observable; - private readonly _dossierTemplateId = this._data.dossierTemplateId; - - constructor( - readonly userService: UserService, - private readonly _toaster: Toaster, - private readonly _formBuilder: FormBuilder, - private readonly _dictionariesMapService: DictionariesMapService, - private readonly _translateService: TranslateService, - private readonly _dictionaryService: DictionaryService, - private readonly _loadingService: LoadingService, - protected readonly _injector: Injector, - protected readonly _dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) - private readonly _data: { readonly dictionary: Dictionary; readonly dossierTemplateId: string }, - ) { - super(_injector, _dialogRef, !!_data.dictionary); - this.form = this._getForm(this.dictionary); - this.initialFormValue = this.form.getRawValue(); - this.hasColor$ = this._colorEmpty$; - this.technicalName$ = this.form.get('label').valueChanges.pipe(map(value => this._toTechnicalName(value))); - } - - private get _canEditLabel$() { - return this.userService.currentUser$.pipe( - map(user => user.isAdmin || !this._data.dictionary), - shareDistinctLast(), - ); - } - - private get _colorEmpty$() { - return this.form.get('hexColor').valueChanges.pipe(map((value: string) => !value || value?.length === 0)); - } - - getDictCaseSensitive(dictionary: Dictionary): boolean { - return dictionary ? !dictionary.caseInsensitive : false; - } - - async save(): Promise { - this._loadingService.start(); - const dictionary = this._formToObject(); - const dossierTemplateId = this._data.dossierTemplateId; - - try { - if (this.dictionary) { - // edit mode - await firstValueFrom(this._dictionaryService.updateDictionary(dictionary, dossierTemplateId, dictionary.type)); - this._toaster.success(_('add-edit-dictionary.success.edit')); - } else { - // create mode - await firstValueFrom(this._dictionaryService.addDictionary({ ...dictionary, dossierTemplateId })); - this._toaster.success(_('add-edit-dictionary.success.create')); - } - this._dialogRef.close(true); - } catch (e) {} - - this._loadingService.stop(); - } - - private _getForm(dictionary: Dictionary): FormGroup { - return this._formBuilder.group({ - label: [dictionary?.label, [Validators.required, Validators.minLength(3)]], - description: [dictionary?.description], - rank: [dictionary?.rank, Validators.required], - hexColor: [dictionary?.hexColor, [Validators.required, Validators.minLength(7)]], - hint: [!!dictionary?.hint], - addToDictionaryAction: [!!dictionary?.addToDictionaryAction], - caseSensitive: [this.getDictCaseSensitive(dictionary)], - }); - } - - private _toTechnicalName(value: string) { - const existingTechnicalNames = this._dictionariesMapService.get(this._dossierTemplateId).map(dict => dict.type); - const baseTechnicalName = toSnakeCase(value.trim()); - let technicalName = baseTechnicalName; - let suffix = 1; - while (existingTechnicalNames.includes(technicalName)) { - technicalName = [baseTechnicalName, suffix++].join('_'); - } - return technicalName; - } - - private _formToObject(): IDictionary { - return { - type: this.dictionary?.type || this._toTechnicalName(this.form.get('label').value), - label: this.form.get('label').value, - caseInsensitive: !this.form.get('caseSensitive').value, - description: this.form.get('description').value, - hexColor: this.form.get('hexColor').value, - hint: this.form.get('hint').value, - rank: this.form.get('rank').value, - addToDictionaryAction: this.form.get('addToDictionaryAction').value, - dossierTemplateId: this._data.dossierTemplateId, - }; - } -} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts index 0c043682c..6e0c1e4d4 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts @@ -8,7 +8,7 @@ import { BaseDialogComponent, LoadingService, Toaster } from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { DossierTemplate, DownloadFileType, IDossierTemplate } from '@red/domain'; import { HttpStatusCode } from '@angular/common/http'; -import { DictionaryService } from '@shared/services/dictionary.service'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; import { firstValueFrom } from 'rxjs'; import dayjs, { Dayjs } from 'dayjs'; diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-entity-dialog/add-entity-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/add-entity-dialog/add-entity-dialog.component.html new file mode 100644 index 000000000..9b107f290 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-entity-dialog/add-entity-dialog.component.html @@ -0,0 +1,19 @@ +
+
+ {{ dialogHeader }} +
+ +
+
+ +
+
+ +
+ +
+ + +
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/add-entity-dialog/add-entity-dialog.component.scss similarity index 100% rename from apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.scss rename to apps/red-ui/src/app/modules/admin/dialogs/add-entity-dialog/add-entity-dialog.component.scss diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-entity-dialog/add-entity-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-entity-dialog/add-entity-dialog.component.ts new file mode 100644 index 000000000..efecbc48d --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-entity-dialog/add-entity-dialog.component.ts @@ -0,0 +1,64 @@ +import { ChangeDetectionStrategy, Component, Inject, Injector, ViewChild } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { BaseDialogComponent } from '@iqser/common-ui'; +import { TranslateService } from '@ngx-translate/core'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; +import { Dictionary } from '@red/domain'; +import { UserService } from '@services/user.service'; +import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; +import { AddEditEntityComponent } from '../../shared/components/add-edit-entity/add-edit-entity.component'; + +@Component({ + templateUrl: './add-entity-dialog.component.html', + styleUrls: ['./add-entity-dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AddEntityDialogComponent extends BaseDialogComponent { + readonly entity = this._data.dictionary; + readonly dialogHeader = this._translateService.instant('add-entity.title'); + readonly dossierTemplateId = this._data.dossierTemplateId; + + @ViewChild(AddEditEntityComponent) private readonly _addEditEntityComponent: AddEditEntityComponent; + + constructor( + readonly userService: UserService, + private readonly _formBuilder: FormBuilder, + private readonly _dictionariesMapService: DictionariesMapService, + private readonly _translateService: TranslateService, + private readonly _dictionaryService: DictionaryService, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) + private readonly _data: { readonly dictionary: Dictionary; readonly dossierTemplateId: string }, + ) { + super(_injector, _dialogRef, !!_data.dictionary); + this.form = this._getForm(this.entity); + this.initialFormValue = this.form.getRawValue(); + } + + async save(): Promise { + try { + await this._addEditEntityComponent.save(); + this._dialogRef.close(true); + } catch (e) { + console.error(e); + } + } + + private _getForm(entity: Dictionary): FormGroup { + return this._formBuilder.group({ + type: [entity?.label], + label: [entity?.label, [Validators.required, Validators.minLength(3)]], + description: [entity?.description], + rank: [entity?.rank, Validators.required], + hexColor: [entity?.hexColor, [Validators.required, Validators.minLength(7)]], + hint: [!!entity?.hint], + addToDictionaryAction: [!!entity?.addToDictionaryAction], + caseSensitive: [entity ? !entity.caseInsensitive : false], + recommendationHexColor: [this.entity?.recommendationHexColor, [Validators.required, Validators.minLength(7)]], + hasDictionary: [this.entity?.hasDictionary], + defaultReason: [{ value: null, disabled: true }], + }); + } +} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts index d10739d47..7c89dd81a 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts @@ -6,7 +6,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; import { defaultColorsTranslations } from '../../translations/default-colors-translations'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { DictionaryService } from '@shared/services/dictionary.service'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; import { firstValueFrom } from 'rxjs'; interface IEditColorData { diff --git a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts index f48122ed9..d93f3e956 100644 --- a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts @@ -12,7 +12,7 @@ import { import { defaultColorsTranslations } from '../../translations/default-colors-translations'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { UserService } from '@services/user.service'; -import { DictionaryService } from '@shared/services/dictionary.service'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; import { firstValueFrom } from 'rxjs'; import { ActivatedRoute } from '@angular/router'; diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts deleted file mode 100644 index f4cf0426c..000000000 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Component, forwardRef, Injector } from '@angular/core'; -import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; -import { TranslateService } from '@ngx-translate/core'; -import { - CircleButtonTypes, - DefaultListingServices, - IconButtonTypes, - ListingComponent, - LoadingService, - TableColumnConfig, -} from '@iqser/common-ui'; -import { AdminDialogService } from '../../services/admin-dialog.service'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { UserService } from '@services/user.service'; -import { DictionaryService } from '@shared/services/dictionary.service'; -import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; -import { Dictionary, DossierTemplateStats } from '@red/domain'; -import { firstValueFrom, Observable } from 'rxjs'; -import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service'; -import { ActivatedRoute } from '@angular/router'; -import { tap } from 'rxjs/operators'; -import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; -import { DOSSIER_TEMPLATE_ID } from '@utils/constants'; - -@Component({ - templateUrl: './dictionary-listing-screen.component.html', - styleUrls: ['./dictionary-listing-screen.component.scss'], - providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DictionaryListingScreenComponent) }], -}) -export class DictionaryListingScreenComponent extends ListingComponent { - readonly iconButtonTypes = IconButtonTypes; - readonly circleButtonTypes = CircleButtonTypes; - readonly currentUser = this._userService.currentUser; - readonly tableHeaderLabel = _('dictionary-listing.table-header.title'); - readonly tableColumnConfigs: TableColumnConfig[] = [ - { label: _('dictionary-listing.table-col-names.type'), sortByKey: 'searchKey', width: '2fr' }, - { label: _('dictionary-listing.table-col-names.rank'), sortByKey: 'rank', class: 'flex-center' }, - { label: _('dictionary-listing.table-col-names.hint-redaction'), class: 'flex-center' }, - ]; - chartData: DoughnutChartConfig[] = []; - readonly templateStats$: Observable; - readonly #dossierTemplateId: string; - - constructor( - protected readonly _injector: Injector, - private readonly _userService: UserService, - private readonly _loadingService: LoadingService, - private readonly _dossierTemplatesService: DossierTemplatesService, - private readonly _dictionariesMapService: DictionariesMapService, - private readonly _dialogService: AdminDialogService, - private readonly _translateService: TranslateService, - private readonly _dictionaryService: DictionaryService, - private readonly _dossierTemplateStatsService: DossierTemplateStatsService, - private readonly _route: ActivatedRoute, - ) { - super(_injector); - _loadingService.start(); - this.#dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID); - this.templateStats$ = this._dossierTemplateStatsService - .watch$(this.#dossierTemplateId) - .pipe(tap(templateStats => this._loadDictionaryData(templateStats))); - } - - openDeleteDictionariesDialog($event?: MouseEvent, types = this.listingService.selected) { - this._dialogService.openDialog('confirm', $event, null, async () => { - this._loadingService.start(); - await firstValueFrom( - this._dictionaryService.deleteDictionaries( - types.map(t => t.type), - this.#dossierTemplateId, - ), - ); - this._loadingService.stop(); - }); - } - - openAddEditDictionaryDialog($event?: MouseEvent, dictionary?: Dictionary) { - this._dialogService.openDialog('addEditDictionary', $event, { - dictionary, - dossierTemplateId: this.#dossierTemplateId, - }); - } - - private _loadDictionaryData(templateStats: DossierTemplateStats): void { - const entities: Dictionary[] = this._dictionariesMapService.get(this.#dossierTemplateId).filter(d => !d.virtual); - this.entitiesService.setEntities(entities); - - this._calculateData(templateStats); - } - - private _calculateData(templateStats: DossierTemplateStats): void { - this.chartData = this.allEntities.map(dict => ({ - value: templateStats.dictionarySummary(dict.type).entriesCount ?? 0, - color: dict.hexColor, - label: dict.label, - key: dict.type, - })); - this.chartData.sort((a, b) => (a.label < b.label ? -1 : 1)); - this._loadingService.stop(); - } -} diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.html deleted file mode 100644 index 43915aff7..000000000 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.html +++ /dev/null @@ -1,101 +0,0 @@ -
- - -
-
- - - - - -
-
-
-
- {{ dictionary.label }} -
-
-
-
- - {{ dictionaryManager.initialEntries?.length }} -
-
-
-
- - {{ 'dictionary-listing.case-sensitive' | translate }} -
-
- -
- -
- {{ (dictionary.hint ? 'hint' : 'redaction') | translate }} -
-
- -
-
-
{{ dictionary.description }}
-
-
-
-
diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts deleted file mode 100644 index 48de2b29f..000000000 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { Component, ElementRef, ViewChild } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; -import { saveAs } from 'file-saver'; -import { AdminDialogService } from '../../services/admin-dialog.service'; -import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component'; -import { DictionaryService } from '@shared/services/dictionary.service'; -import { CircleButtonTypes, LoadingService } from '@iqser/common-ui'; -import { UserService } from '@services/user.service'; -import { Dictionary } from '@red/domain'; -import { firstValueFrom, Observable, of } from 'rxjs'; -import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; -import { map, switchMap } from 'rxjs/operators'; -import { DOSSIER_TEMPLATE_ID } from '@utils/constants'; - -@Component({ - templateUrl: './dictionary-overview-screen.component.html', - styleUrls: ['./dictionary-overview-screen.component.scss'], -}) -export class DictionaryOverviewScreenComponent { - readonly circleButtonTypes = CircleButtonTypes; - readonly currentUser = this._userService.currentUser; - - initialEntries: string[] = []; - isLeavingPage = false; - - readonly dictionary$: Observable; - - @ViewChild('dictionaryManager', { static: false }) - private readonly _dictionaryManager: DictionaryManagerComponent; - @ViewChild('fileInput') private readonly _fileInput: ElementRef; - - constructor( - private readonly _router: Router, - private readonly _userService: UserService, - private readonly _activatedRoute: ActivatedRoute, - private readonly _loadingService: LoadingService, - private readonly _dialogService: AdminDialogService, - protected readonly _translateService: TranslateService, - private readonly _dictionaryService: DictionaryService, - private readonly _dictionariesMapService: DictionariesMapService, - private readonly _route: ActivatedRoute, - ) { - this.dictionary$ = _route.paramMap.pipe( - map(params => [params.get(DOSSIER_TEMPLATE_ID), params.get('dictionary')]), - switchMap(([dossierTemplateId, dictionary]: [string, string]) => - dossierTemplateId ? this._dictionariesMapService.watch$(dossierTemplateId, dictionary) : of(undefined), - ), - switchMap(dictionary => this._loadEntries(dictionary)), - ); - } - - get changed() { - return this._dictionaryManager.editor.hasChanges; - } - - openEditDictionaryDialog(dictionary: Dictionary, $event: MouseEvent) { - this._dialogService.openDialog('addEditDictionary', $event, { - dictionary, - dossierTemplateId: dictionary.dossierTemplateId, - }); - } - - openDeleteDictionaryDialog(dictionary: Dictionary, $event?: MouseEvent) { - $event?.stopPropagation(); - - this._dialogService.openDialog('confirm', $event, null, async () => { - await firstValueFrom(this._dictionaryService.deleteDictionaries([dictionary.type], dictionary.dossierTemplateId)); - await this._router.navigate(['/main', 'admin', 'dossier-templates', dictionary.dossierTemplateId, 'dictionaries']); - }); - } - - download(dictionary: Dictionary): void { - const content = this._dictionaryManager.editor.value; - const blob = new Blob([content], { - type: 'text/plain;charset=utf-8', - }); - saveAs(blob, `${dictionary.label}.txt`); - } - - upload($event): void { - const file: File = $event.target.files[0]; - const fileReader = new FileReader(); - - if (file) { - fileReader.onload = () => { - const fileContent = fileReader.result as string; - if (this._dictionaryManager.editor.value) { - this._dialogService.openDialog('uploadDictionary', null, null, ({ option }) => { - if (option === 'overwrite') { - this._overwrite(fileContent); - } else if (option === 'merge') { - this._merge(fileContent); - } - }); - } else { - this._overwrite(fileContent); - } - this._fileInput.nativeElement.value = null; - }; - fileReader.readAsText(file); - } - } - - async save(dictionary: Dictionary) { - const entries = this._dictionaryManager.editor?.currentEntries; - - this._loadingService.start(); - try { - await firstValueFrom( - this._dictionaryService.saveEntries(entries, this.initialEntries, dictionary.dossierTemplateId, dictionary.type, null), - ); - await this._loadEntries(dictionary); - } catch (e) { - this._loadingService.stop(); - } - } - - private _overwrite(fileContent: string): void { - this._dictionaryManager.editor.value = fileContent; - } - - private _merge(fileContent: string): void { - const currentEntries = this._dictionaryManager.editor.value.split('\n'); - fileContent - .split('\n') - .filter(entry => !currentEntries.includes(entry)) - .forEach(entry => currentEntries.push(entry)); - this._dictionaryManager.editor.value = currentEntries.join('\n'); - } - - private async _loadEntries(dictionary: Dictionary) { - this._loadingService.start(); - try { - const data = await firstValueFrom(this._dictionaryService.getForType(dictionary.dossierTemplateId, dictionary.type)); - this._loadingService.stop(); - this.initialEntries = [...data.entries].sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' })); - } catch (e) { - this._loadingService.stop(); - this.initialEntries = []; - } - return dictionary; - } -} diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-templates-listing/table-item/table-item.component.html b/apps/red-ui/src/app/modules/admin/screens/dossier-templates-listing/table-item/table-item.component.html index 55fce9070..f40f487cd 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-templates-listing/table-item/table-item.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-templates-listing/table-item/table-item.component.html @@ -5,7 +5,7 @@
- {{ 'dossier-templates-listing.dictionaries' | translate: { length: stats.numberOfDictionaries } }} + {{ 'dossier-templates-listing.entities' | translate: { length: stats.numberOfDictionaries } }}
diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.html similarity index 56% rename from apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.html rename to apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.html index 20cb45626..f4370e0d0 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.html @@ -21,40 +21,28 @@
- -
- -
@@ -64,13 +52,13 @@
@@ -87,16 +75,6 @@
{{ dict.label }}
-
-
- - {{ templateStats.dictionarySummary(dict.type)?.entriesCount || 0 }} -
-
- - {{ 'dictionary-listing.case-sensitive' | translate }} -
-
@@ -109,17 +87,21 @@
-
+
+ {{ templateStats.dictionarySummary(dict.type)?.entriesCount || 0 }} +
+ +
diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.scss b/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.scss similarity index 58% rename from apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.scss rename to apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.scss index 218de10d3..67f009904 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.scss +++ b/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.scss @@ -14,19 +14,6 @@ } .dict-name { - z-index: 1; max-width: 100%; } } - -.right-container { - display: flex; - width: 353px; - min-width: 353px; - justify-content: center; - padding: 50px 20px 30px 20px; - - &.has-scrollbar:hover { - padding-right: 9px; - } -} diff --git a/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.ts new file mode 100644 index 000000000..99e439b02 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/entities-listing/entities-listing-screen.component.ts @@ -0,0 +1,76 @@ +import { Component, forwardRef, Injector } from '@angular/core'; +import { + CircleButtonTypes, + DefaultListingServices, + IconButtonTypes, + ListingComponent, + LoadingService, + TableColumnConfig, +} from '@iqser/common-ui'; +import { AdminDialogService } from '../../services/admin-dialog.service'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; +import { Dictionary, DossierTemplateStats } from '@red/domain'; +import { firstValueFrom, Observable } from 'rxjs'; +import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service'; +import { ActivatedRoute } from '@angular/router'; +import { tap } from 'rxjs/operators'; +import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; +import { DOSSIER_TEMPLATE_ID } from '@utils/constants'; +import { PermissionsService } from '@services/permissions.service'; + +@Component({ + templateUrl: './entities-listing-screen.component.html', + styleUrls: ['./entities-listing-screen.component.scss'], + providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => EntitiesListingScreenComponent) }], +}) +export class EntitiesListingScreenComponent extends ListingComponent { + readonly iconButtonTypes = IconButtonTypes; + readonly circleButtonTypes = CircleButtonTypes; + readonly tableHeaderLabel = _('entities-listing.table-header.title'); + readonly tableColumnConfigs: TableColumnConfig[] = [ + { label: _('entities-listing.table-col-names.type'), sortByKey: 'searchKey', width: '2fr' }, + { label: _('entities-listing.table-col-names.rank'), sortByKey: 'rank', class: 'flex-center' }, + { label: _('entities-listing.table-col-names.hint-redaction'), class: 'flex-center' }, + { label: _('entities-listing.table-col-names.dictionary-entries') }, + ]; + readonly templateStats$: Observable; + readonly #dossierTemplateId: string; + + constructor( + protected readonly _injector: Injector, + private readonly _loadingService: LoadingService, + private readonly _dictionariesMapService: DictionariesMapService, + private readonly _dialogService: AdminDialogService, + private readonly _dictionaryService: DictionaryService, + private readonly _dossierTemplateStatsService: DossierTemplateStatsService, + private readonly _route: ActivatedRoute, + readonly permissionsService: PermissionsService, + ) { + super(_injector); + this.#dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID); + this.templateStats$ = this._dossierTemplateStatsService.watch$(this.#dossierTemplateId).pipe(tap(() => this._loadDictionaryData())); + } + + openDeleteEntitiesDialog($event?: MouseEvent, types = this.listingService.selected) { + this._dialogService.openDialog('confirm', $event, null, async () => { + this._loadingService.start(); + await firstValueFrom( + this._dictionaryService.deleteDictionaries( + types.map(t => t.type), + this.#dossierTemplateId, + ), + ); + this._loadingService.stop(); + }); + } + + openAddEntityDialog($event?: MouseEvent) { + this._dialogService.openDialog('addEntity', $event, { dossierTemplateId: this.#dossierTemplateId }); + } + + private _loadDictionaryData(): void { + const entities: Dictionary[] = this._dictionariesMapService.get(this.#dossierTemplateId).filter(d => !d.virtual); + this.entitiesService.setEntities(entities); + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/entities/entities.module.ts b/apps/red-ui/src/app/modules/admin/screens/entities/entities.module.ts new file mode 100644 index 000000000..52fd524dd --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/entities/entities.module.ts @@ -0,0 +1,38 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { SharedModule } from '@shared/shared.module'; +import { DictionaryScreenComponent } from './screens/dictionary/dictionary-screen.component'; +import { PendingChangesGuard } from '@guards/can-deactivate.guard'; +import { InfoComponent } from './screens/info/info.component'; +import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'; +import { SharedAdminModule } from '../../shared/shared-admin.module'; + +const routes = [ + { path: '', redirectTo: 'info', pathMatch: 'full' }, + { + path: 'info', + component: InfoComponent, + }, + { + path: 'dictionary', + component: DictionaryScreenComponent, + canDeactivate: [PendingChangesGuard], + }, + { + path: 'false-positive', + component: DictionaryScreenComponent, + canDeactivate: [PendingChangesGuard], + }, + { + path: 'false-recommendations', + component: DictionaryScreenComponent, + canDeactivate: [PendingChangesGuard], + }, +]; + +@NgModule({ + declarations: [DictionaryScreenComponent, InfoComponent], + imports: [RouterModule.forChild(routes), SharedAdminModule, CommonModule, SharedModule, MonacoEditorModule], +}) +export class EntitiesModule {} diff --git a/apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.html new file mode 100644 index 000000000..30e79e8e4 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.html @@ -0,0 +1,9 @@ + diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.scss b/apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.scss similarity index 83% rename from apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.scss rename to apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.scss index 1078732a2..2200ae685 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.scss +++ b/apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.scss @@ -1,7 +1,6 @@ -.right-container { - width: 353px; - min-width: 353px; - padding: 24px; +:host { + display: flex; + flex: 1; } .dictionary-header { diff --git a/apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.ts new file mode 100644 index 000000000..4a4cea930 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/entities/screens/dictionary/dictionary-screen.component.ts @@ -0,0 +1,79 @@ +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; +import { List, LoadingService } from '@iqser/common-ui'; +import { UserService } from '@services/user.service'; +import { BehaviorSubject, firstValueFrom } from 'rxjs'; +import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants'; +import { DICTIONARY_TO_ENTRY_TYPE_MAP, DICTIONARY_TYPE_KEY_MAP, DictionaryType } from '@red/domain'; + +@Component({ + templateUrl: './dictionary-screen.component.html', + styleUrls: ['./dictionary-screen.component.scss'], +}) +export class DictionaryScreenComponent implements OnInit { + readonly currentUser = this._userService.currentUser; + initialEntries$ = new BehaviorSubject([]); + isLeavingPage = false; + readonly type: DictionaryType; + readonly #dossierTemplateId: string; + readonly #entityType: string; + @ViewChild('dictionaryManager', { static: false }) + private readonly _dictionaryManager: DictionaryManagerComponent; + @ViewChild('fileInput') private readonly _fileInput: ElementRef; + + constructor( + private readonly _userService: UserService, + private readonly _loadingService: LoadingService, + private readonly _dictionaryService: DictionaryService, + private readonly _route: ActivatedRoute, + ) { + this.#dossierTemplateId = _route.parent.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID); + this.#entityType = _route.parent.snapshot.paramMap.get(ENTITY_TYPE); + this.type = this._route.snapshot.routeConfig.path as DictionaryType; + } + + get changed() { + return this._dictionaryManager.editor.hasChanges; + } + + async ngOnInit(): Promise { + await this._loadEntries(); + } + + async save() { + const entries = this._dictionaryManager.editor?.currentEntries; + + this._loadingService.start(); + try { + await firstValueFrom( + this._dictionaryService.saveEntries( + entries, + this.initialEntries$.value, + this.#dossierTemplateId, + this.#entityType, + null, + true, + DICTIONARY_TO_ENTRY_TYPE_MAP[this.type], + ), + ); + await this._loadEntries(); + } catch (e) { + this._loadingService.stop(); + } + } + + private async _loadEntries() { + this._loadingService.start(); + try { + const data = await firstValueFrom(this._dictionaryService.getForType(this.#dossierTemplateId, this.#entityType)); + const entries: List = data[DICTIONARY_TYPE_KEY_MAP[this.type]]; + this.initialEntries$.next([...entries].sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' }))); + this._loadingService.stop(); + } catch (e) { + this._loadingService.stop(); + this.initialEntries$.next([]); + } + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.html b/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.html new file mode 100644 index 000000000..1a8618ba9 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.html @@ -0,0 +1,24 @@ +
+
+
+
+ +
+ + {{ 'readonly' | translate }} +
+
+ +
+ +
+ +
+ + +
+
+
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.scss b/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.scss new file mode 100644 index 000000000..6e7689711 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.scss @@ -0,0 +1,28 @@ +@use 'common-mixins'; + +:host { + display: flex; + flex-grow: 1; + overflow: hidden; +} + +.dialog-header { + padding-right: 32px; + display: flex; + justify-content: space-between; +} + +.content-container { + flex: 1; + display: flex; + padding: 30px; + overflow: auto; + @include common-mixins.scroll-bar; + background-color: var(--iqser-grey-2); + justify-content: center; + + .dialog { + width: 100%; + min-height: unset; + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.ts b/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.ts new file mode 100644 index 000000000..b2fc313c9 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/entities/screens/info/info.component.ts @@ -0,0 +1,87 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewChild } from '@angular/core'; +import { BaseFormComponent } from '@iqser/common-ui'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Dictionary } from '@red/domain'; +import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; +import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants'; +import { ActivatedRoute } from '@angular/router'; +import { UserService } from '@services/user.service'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; +import { PermissionsService } from '@services/permissions.service'; +import { AddEditEntityComponent } from '../../../../shared/components/add-edit-entity/add-edit-entity.component'; + +@Component({ + selector: 'redaction-info', + templateUrl: './info.component.html', + styleUrls: ['./info.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class InfoComponent extends BaseFormComponent { + readonly currentUser = this._userService.currentUser; + entity: Dictionary; + readonly dossierTemplateId: string; + @ViewChild(AddEditEntityComponent) private readonly _addEditEntityComponent: AddEditEntityComponent; + + constructor( + private readonly _formBuilder: FormBuilder, + private readonly _dictionariesMapService: DictionariesMapService, + private readonly _route: ActivatedRoute, + private readonly _userService: UserService, + private readonly _dictionaryService: DictionaryService, + private readonly _changeRef: ChangeDetectorRef, + readonly permissionsService: PermissionsService, + ) { + super(); + this.dossierTemplateId = this._route.parent.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID); + this.form = this._initializeForm(); + } + + async save(): Promise { + try { + await this._addEditEntityComponent.save(); + this.initialFormValue = this.form.getRawValue(); + this._changeRef.markForCheck(); + } catch (e) { + console.error(e); + } + } + + revert(): void { + this.form = this._initializeForm(); + } + + private _initializeForm(): FormGroup { + const entityType = this._route.parent.snapshot.paramMap.get(ENTITY_TYPE); + this.entity = this._dictionariesMapService.getDictionary(entityType, this.dossierTemplateId); + + const controlsConfig = { + type: [this.entity.type], + label: [ + { + value: this.entity.label, + disabled: !this.currentUser.isAdmin, + }, + [Validators.required, Validators.minLength(3)], + ], + description: [this.entity.description], + rank: [this.entity.rank, Validators.required], + hexColor: [this.entity.hexColor, [Validators.required, Validators.minLength(7)]], + recommendationHexColor: [this.entity.recommendationHexColor, [Validators.required, Validators.minLength(7)]], + hint: [this.entity.hint], + hasDictionary: [this.entity.hasDictionary], + }; + + if (!this.entity.hint) { + Object.assign(controlsConfig, { + defaultReason: [{ value: null, disabled: true }], + caseSensitive: [!this.entity.caseInsensitive], + addToDictionaryAction: [this.entity.addToDictionaryAction], + }); + } + const form = this._formBuilder.group(controlsConfig); + + this.initialFormValue = form.getRawValue(); + + return form; + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/info/info-screen/dossier-template-info-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/info/info-screen/dossier-template-info-screen.component.html index 4c7c16ecd..bd356f4a6 100644 --- a/apps/red-ui/src/app/modules/admin/screens/info/info-screen/dossier-template-info-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/info/info-screen/dossier-template-info-screen.component.html @@ -18,7 +18,7 @@
- {{ 'dossier-template-info-screen.dictionaries' | translate: { count: stats.numberOfDictionaries } }} + {{ 'dossier-template-info-screen.entities' | translate: { count: stats.numberOfDictionaries } }}
diff --git a/apps/red-ui/src/app/modules/admin/screens/rules/rules.module.ts b/apps/red-ui/src/app/modules/admin/screens/rules/rules.module.ts index d83e1a860..76350779f 100644 --- a/apps/red-ui/src/app/modules/admin/screens/rules/rules.module.ts +++ b/apps/red-ui/src/app/modules/admin/screens/rules/rules.module.ts @@ -4,7 +4,7 @@ import { RouterModule } from '@angular/router'; import { SharedModule } from '@shared/shared.module'; import { RulesScreenComponent } from './rules-screen/rules-screen.component'; import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'; -import { PendingChangesGuard } from '../../../../guards/can-deactivate.guard'; +import { PendingChangesGuard } from '@guards/can-deactivate.guard'; const routes = [{ path: '', component: RulesScreenComponent, canDeactivate: [PendingChangesGuard] }]; diff --git a/apps/red-ui/src/app/modules/admin/screens/watermark/watermark-screen/watermark-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/watermark/watermark-screen/watermark-screen.component.html index 549beee22..92df155d7 100644 --- a/apps/red-ui/src/app/modules/admin/screens/watermark/watermark-screen/watermark-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/watermark/watermark-screen/watermark-screen.component.html @@ -56,7 +56,7 @@
{ component: ConfirmationDialogComponent, dialogConfig: { disableClose: false }, }, - addEditDictionary: { - component: AddEditDictionaryDialogComponent, + addEntity: { + component: AddEntityDialogComponent, dialogConfig: { autoFocus: true }, }, editColor: { diff --git a/apps/red-ui/src/app/modules/admin/shared/components/add-edit-entity/add-edit-entity.component.html b/apps/red-ui/src/app/modules/admin/shared/components/add-edit-entity/add-edit-entity.component.html new file mode 100644 index 000000000..7b5c6a8b3 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/shared/components/add-edit-entity/add-edit-entity.component.html @@ -0,0 +1,116 @@ +
+
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+
+ +
+ + +
+ +
+
+
+ +
+ +
{{ this.form.get('type').value || '-' }}
+ +
+ +
+ + + {{ 'add-edit-entity.form.redaction' | translate }} + + + {{ 'add-edit-entity.form.hint' | translate }} + + +
+ +
+ + +
+ +
+ + +
+ +
+ + {{ 'add-edit-entity.form.has-dictionary' | translate }} + +
+ +
+ + {{ 'add-edit-entity.form.case-sensitive' | translate }} + +
+ +
+ + {{ 'add-edit-entity.form.add-to-dictionary-action' | translate }} + +
+
diff --git a/apps/red-ui/src/app/modules/admin/shared/components/add-edit-entity/add-edit-entity.component.scss b/apps/red-ui/src/app/modules/admin/shared/components/add-edit-entity/add-edit-entity.component.scss new file mode 100644 index 000000000..38569f36f --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/shared/components/add-edit-entity/add-edit-entity.component.scss @@ -0,0 +1,11 @@ +.row { + display: flex; + + > *:not(:last-child) { + margin-right: 16px; + } + + .iqser-input-group { + margin-top: 0; + } +} diff --git a/apps/red-ui/src/app/modules/admin/shared/components/add-edit-entity/add-edit-entity.component.ts b/apps/red-ui/src/app/modules/admin/shared/components/add-edit-entity/add-edit-entity.component.ts new file mode 100644 index 000000000..eb9bd66df --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/shared/components/add-edit-entity/add-edit-entity.component.ts @@ -0,0 +1,122 @@ +import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { Dictionary, IDictionary } from '@red/domain'; +import { FormControl, FormGroup } from '@angular/forms'; +import { map, startWith } from 'rxjs/operators'; +import { firstValueFrom, Observable } from 'rxjs'; +import { toSnakeCase } from '@utils/functions'; +import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; +import { PermissionsService } from '@services/permissions.service'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; +import { LoadingService, Toaster } from '@iqser/common-ui'; + +const REDACTION_FIELDS = ['defaultReason', 'caseSensitive', 'addToDictionaryAction']; + +@Component({ + selector: 'redaction-add-edit-entity [entity] [dossierTemplateId]', + templateUrl: './add-edit-entity.component.html', + styleUrls: ['./add-edit-entity.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AddEditEntityComponent implements OnInit, OnChanges { + @Input() dossierTemplateId: string; + @Input() entity: Dictionary; + @Input() form: FormGroup; + + hasHexColor$: Observable; + hasRecommendationHexColor$: Observable; + technicalName$: Observable; + + constructor( + private readonly _dictionariesMapService: DictionariesMapService, + private readonly _permissionsService: PermissionsService, + private readonly _dictionaryService: DictionaryService, + private readonly _toaster: Toaster, + private readonly _loadingService: LoadingService, + ) {} + + ngOnInit() { + this.hasHexColor$ = this._colorEmpty$('hexColor'); + this.hasRecommendationHexColor$ = this._colorEmpty$('recommendationHexColor'); + this.technicalName$ = this.form.get('label').valueChanges.pipe(map((value: string) => this._toTechnicalName(value))); + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.form) { + this.form.get('hint').valueChanges.subscribe(isHint => { + if (isHint) { + REDACTION_FIELDS.forEach(field => this.form.removeControl(field)); + } else { + this.form.addControl('addToDictionaryAction', new FormControl(this.entity?.addToDictionaryAction)); + this.form.addControl('caseSensitive', new FormControl(!this.entity?.caseInsensitive)); + this.form.addControl('defaultReason', new FormControl({ value: null, disabled: true })); + } + }); + + if (!this.entity) { + this.form.get('label').valueChanges.subscribe((label: string) => { + this.form.get('type').setValue(this._toTechnicalName(label)); + }); + } + + if (!this._permissionsService.canEditEntities()) { + this.form.disable(); + } + } + } + + async save(): Promise { + this._loadingService.start(); + const dictionary = this._formToObject(); + + try { + if (this.entity) { + // edit mode + await firstValueFrom(this._dictionaryService.updateDictionary(dictionary, this.dossierTemplateId, dictionary.type)); + this._toaster.success(_('add-edit-entity.success.edit')); + } else { + // create mode + await firstValueFrom(this._dictionaryService.addDictionary({ ...dictionary, dossierTemplateId: this.dossierTemplateId })); + this._toaster.success(_('add-edit-entity.success.create')); + } + this._loadingService.stop(); + } catch (e) { + this._loadingService.stop(); + throw e; + } + } + + private _toTechnicalName(value: string) { + const existingTechnicalNames = this._dictionariesMapService.get(this.dossierTemplateId).map(dict => dict.type); + const baseTechnicalName = toSnakeCase(value.trim()); + let technicalName = baseTechnicalName; + let suffix = 1; + while (existingTechnicalNames.includes(technicalName)) { + technicalName = [baseTechnicalName, suffix++].join('_'); + } + return technicalName; + } + + private _colorEmpty$(field: string) { + return this.form.get(field).valueChanges.pipe( + startWith(this.form.get(field).value), + map((value: string) => !value || value?.length === 0), + ); + } + + private _formToObject(): IDictionary { + return { + type: this.form.get('type').value, + label: this.form.get('label').value, + caseInsensitive: !this.form.get('caseSensitive').value, + description: this.form.get('description').value, + hexColor: this.form.get('hexColor').value, + recommendationHexColor: this.form.get('recommendationHexColor').value, + hint: this.form.get('hint').value, + rank: this.form.get('rank').value, + addToDictionaryAction: !!this.form.get('addToDictionaryAction').value, + dossierTemplateId: this.dossierTemplateId, + hasDictionary: !!this.form.get('hasDictionary').value, + }; + } +} diff --git a/apps/red-ui/src/app/modules/admin/shared/components/dossier-template-actions/dossier-template-actions.component.ts b/apps/red-ui/src/app/modules/admin/shared/components/dossier-template-actions/dossier-template-actions.component.ts index 74d178ba6..4977eb83c 100644 --- a/apps/red-ui/src/app/modules/admin/shared/components/dossier-template-actions/dossier-template-actions.component.ts +++ b/apps/red-ui/src/app/modules/admin/shared/components/dossier-template-actions/dossier-template-actions.component.ts @@ -5,7 +5,7 @@ import { CircleButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; import { UserService } from '@services/user.service'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; import { firstValueFrom } from 'rxjs'; -import { DictionaryService } from '@shared/services/dictionary.service'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DOSSIER_TEMPLATE_ID } from '@utils/constants'; @Component({ diff --git a/apps/red-ui/src/app/modules/admin/shared/shared-admin.module.ts b/apps/red-ui/src/app/modules/admin/shared/shared-admin.module.ts index 09552f814..32bdf41f4 100644 --- a/apps/red-ui/src/app/modules/admin/shared/shared-admin.module.ts +++ b/apps/red-ui/src/app/modules/admin/shared/shared-admin.module.ts @@ -2,13 +2,15 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; import { DossierTemplateActionsComponent } from './components/dossier-template-actions/dossier-template-actions.component'; +import { AddEditEntityComponent } from './components/add-edit-entity/add-edit-entity.component'; +import { ColorPickerModule } from 'ngx-color-picker'; -const components = [DossierTemplateActionsComponent]; +const components = [DossierTemplateActionsComponent, AddEditEntityComponent]; @NgModule({ declarations: [...components], exports: [...components], providers: [], - imports: [CommonModule, SharedModule], + imports: [CommonModule, SharedModule, ColorPickerModule], }) export class SharedAdminModule {} diff --git a/apps/red-ui/src/app/modules/admin/translations/admin-side-nav-translations.ts b/apps/red-ui/src/app/modules/admin/translations/admin-side-nav-translations.ts index ac6364a98..aeb936b41 100644 --- a/apps/red-ui/src/app/modules/admin/translations/admin-side-nav-translations.ts +++ b/apps/red-ui/src/app/modules/admin/translations/admin-side-nav-translations.ts @@ -1,6 +1,8 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { AdminSideNavType } from '@red/domain'; -export const adminSideNavTranslations = { +export const adminSideNavTranslations: { [key in AdminSideNavType]: string } = { settings: _('admin-side-nav.settings'), dossierTemplates: _('admin-side-nav.dossier-templates'), + entities: _('admin-side-nav.entities'), } as const; diff --git a/apps/red-ui/src/app/modules/archive/archive-routing.module.ts b/apps/red-ui/src/app/modules/archive/archive-routing.module.ts index ae8f5b006..3e1c8f3ca 100644 --- a/apps/red-ui/src/app/modules/archive/archive-routing.module.ts +++ b/apps/red-ui/src/app/modules/archive/archive-routing.module.ts @@ -6,7 +6,7 @@ import { DOSSIER_ID, FILE_ID } from '@utils/constants'; import { CompositeRouteGuard } from '@iqser/common-ui'; import { ARCHIVED_DOSSIERS_SERVICE } from '../../tokens'; import { DossierFilesGuard } from '@guards/dossier-files-guard'; -import { FilePreviewGuard } from '../../guards/file-preview.guard'; +import { FilePreviewGuard } from '@guards/file-preview.guard'; const routes: Routes = [ { diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html index fe48130cc..966c5faf9 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html @@ -13,7 +13,7 @@
- {{ 'add-edit-dictionary.form.add-to-dictionary-action' | translate }} + {{ 'edit-dossier-dialog.dictionary.add-to-dictionary-action' | translate }}
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts index 4cda2fe4c..b1c2e7a8c 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts @@ -3,7 +3,7 @@ import { Dossier, IDictionary } from '@red/domain'; import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-dossier-section.interface'; import { PermissionsService } from '@services/permissions.service'; import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component'; -import { DictionaryService } from '@shared/services/dictionary.service'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; import { CircleButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { FormBuilder, FormGroup } from '@angular/forms'; @@ -85,6 +85,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa const dictionary: IDictionary = { ...this.dossierDictionary, type: 'dossier_redaction', + hasDictionary: true, addToDictionaryAction: this.form.get('addToDictionaryAction').value, }; await firstValueFrom( diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html index 946d699e7..8e477fcbf 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html @@ -17,7 +17,7 @@ {{ activeNavItem.title | translate }}
-
+
{{ 'readonly' | translate }}
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.scss b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.scss index 1e1c0fbaa..3e11434a9 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.scss +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.scss @@ -42,14 +42,8 @@ redaction-edit-dossier-dictionary { margin-top: 6px; } -.read-only { - display: flex; +.read-only-indicator { position: absolute; right: 32px; top: 108px; - - mat-icon { - height: 14px; - width: 14px; - } } diff --git a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.scss b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.scss index 68682cd6a..759479c3d 100644 --- a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.scss +++ b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.scss @@ -28,7 +28,3 @@ iqser-status-bar { margin-left: 2px; } - -.spinning-icon { - margin: 0 12px 0 11px; -} diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts index f6359d91f..eb5b07569 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.ts @@ -10,6 +10,7 @@ import { FilePreviewStateService } from '../../services/file-preview-state.servi import { HelpModeService, ScrollableParentView, ScrollableParentViews } from '@iqser/common-ui'; import { PdfViewer } from '../../services/pdf-viewer.service'; import { FileDataService } from '../../services/file-data.service'; +import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; export const AnnotationButtonTypes = { dark: 'dark', @@ -41,6 +42,7 @@ export class AnnotationActionsComponent implements OnChanges { private readonly _state: FilePreviewStateService, private readonly _permissionsService: PermissionsService, private readonly _fileDataService: FileDataService, + private readonly _dictionariesMapService: DictionariesMapService, ) {} private _annotations: AnnotationWrapper[]; @@ -131,6 +133,7 @@ export class AnnotationActionsComponent implements OnChanges { this._permissionsService.isApprover(dossier), this._userService.currentUser, this.annotations, + this._dictionariesMapService.get(dossier.dossierTemplateId), ); } } diff --git a/apps/red-ui/src/app/modules/file-preview/components/type-annotation-icon/type-annotation-icon.component.ts b/apps/red-ui/src/app/modules/file-preview/components/type-annotation-icon/type-annotation-icon.component.ts index 2e5f280b1..0bc6e7af0 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/type-annotation-icon/type-annotation-icon.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/type-annotation-icon/type-annotation-icon.component.ts @@ -30,10 +30,9 @@ export class TypeAnnotationIconComponent implements OnChanges { if (isHighlight) { this.color = this.annotation.color; - } else if (this.annotation.isSuperTypeBasedColor) { - this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.superType, this._dossierTemplateId); } else { - this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.type, this._dossierTemplateId); + const type = this.annotation.isSuperTypeBasedColor ? this.annotation.superType : this.annotation.type; + this.color = this._dictionariesMapService.getDictionaryColor(type, this._dossierTemplateId, this.annotation.isRecommendation); } this.type = diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts index d232e5ba9..73323ca4c 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component.ts @@ -5,7 +5,7 @@ import { PermissionsService } from '@services/permissions.service'; import { Dictionary, Dossier } from '@red/domain'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { BaseDialogComponent } from '@iqser/common-ui'; -import { DictionaryService } from '@shared/services/dictionary.service'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; import { ManualAnnotationService } from '@services/manual-annotation.service'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts index 1395d4911..fabc6793a 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts @@ -7,7 +7,7 @@ import { PermissionsService } from '@services/permissions.service'; import { JustificationsService } from '@services/entity-services/justifications.service'; import { Dictionary, Dossier, IAddRedactionRequest } from '@red/domain'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; -import { DictionaryService } from '@shared/services/dictionary.service'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; import { BaseDialogComponent, CircleButtonTypes } from '@iqser/common-ui'; import { firstValueFrom } from 'rxjs'; diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts index 0b17ec4d7..b43c1a620 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview-screen.component.ts @@ -44,7 +44,7 @@ import { filePreviewScreenProviders } from './file-preview-providers'; import { ManualAnnotationService } from '@services/manual-annotation.service'; import { DossiersService } from '@services/dossiers/dossiers.service'; import { PageRotationService } from './services/page-rotation.service'; -import { ComponentCanDeactivate } from '../../guards/can-deactivate.guard'; +import { ComponentCanDeactivate } from '@guards/can-deactivate.guard'; import { PdfViewer } from './services/pdf-viewer.service'; import { FilePreviewDialogService } from './services/file-preview-dialog.service'; import { FileDataService } from './services/file-data.service'; diff --git a/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts b/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts index 48f2aeb02..2a923dab7 100644 --- a/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts +++ b/apps/red-ui/src/app/modules/file-preview/file-preview.module.ts @@ -24,7 +24,7 @@ import { AcceptRecommendationDialogComponent } from './dialogs/accept-recommenda import { AnnotationCardComponent } from './components/annotation-card/annotation-card.component'; import { AnnotationReferencesPageIndicatorComponent } from './components/annotation-references-page-indicator/annotation-references-page-indicator.component'; import { HighlightsSeparatorComponent } from './components/highlights-separator/highlights-separator.component'; -import { PendingChangesGuard } from '../../guards/can-deactivate.guard'; +import { PendingChangesGuard } from '@guards/can-deactivate.guard'; import { ManualAnnotationDialogComponent } from './dialogs/manual-redaction-dialog/manual-annotation-dialog.component'; import { ForceAnnotationDialogComponent } from './dialogs/force-redaction-dialog/force-annotation-dialog.component'; import { RemoveAnnotationsDialogComponent } from './dialogs/remove-annotations-dialog/remove-annotations-dialog.component'; diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts index e941e0603..843e3ecf5 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-actions.service.ts @@ -9,7 +9,7 @@ import { AnnotationPermissions } from '@models/file/annotation.permissions'; import { BASE_HREF } from '../../../tokens'; import { UserService } from '@services/user.service'; import { Core } from '@pdftron/webviewer'; -import { Dossier, IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain'; +import { DictionaryEntryTypes, Dossier, IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain'; import { toPosition } from '../../dossier/utils/pdf-calculation.utils'; import { AnnotationDrawService } from './annotation-draw.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; @@ -24,6 +24,7 @@ import { MatDialog } from '@angular/material/dialog'; import { FilePreviewStateService } from './file-preview-state.service'; import { PdfViewer } from './pdf-viewer.service'; import { FilePreviewDialogService } from './file-preview-dialog.service'; +import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; import Quad = Core.Math.Quad; @Injectable() @@ -41,6 +42,7 @@ export class AnnotationActionsService { private readonly _annotationDrawService: AnnotationDrawService, private readonly _activeDossiersService: ActiveDossiersService, private readonly _screenStateService: FilePreviewStateService, + private readonly _dictionariesMapService: DictionariesMapService, ) {} private get _dossier(): Dossier { @@ -223,6 +225,7 @@ export class AnnotationActionsService { this._permissionsService.isApprover(dossier), this._userService.currentUser, annotation, + this._dictionariesMapService.get(dossier.dossierTemplateId), ), })); @@ -500,13 +503,17 @@ export class AnnotationActionsService { ) { $event?.stopPropagation(); - const falsePositiveRequest: IAddRedactionRequest = {}; - falsePositiveRequest.reason = annotation.id; - falsePositiveRequest.value = text; - falsePositiveRequest.type = 'false_positive'; - falsePositiveRequest.positions = annotation.positions; - falsePositiveRequest.addToDictionary = true; - falsePositiveRequest.comment = { text: 'False Positive' }; + const falsePositiveRequest: IAddRedactionRequest = { + reason: annotation.id, + value: text, + type: annotation.type, + positions: annotation.positions, + addToDictionary: true, + comment: { text: 'False Positive' }, + dictionaryEntryType: annotation.isRecommendation + ? DictionaryEntryTypes.FALSE_RECOMMENDATION + : DictionaryEntryTypes.FALSE_POSITIVE, + }; const { dossierId, fileId } = this._screenStateService; this._processObsAndEmit( diff --git a/apps/red-ui/src/app/modules/file-preview/services/annotation-draw.service.ts b/apps/red-ui/src/app/modules/file-preview/services/annotation-draw.service.ts index 37a7dc010..a73ef55e3 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/annotation-draw.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/annotation-draw.service.ts @@ -46,9 +46,11 @@ export class AnnotationDrawService { switch (superType) { case SuperTypes.Hint: case SuperTypes.Redaction: - case SuperTypes.Recommendation: color = this._dictionariesMapService.getDictionaryColor(dictionary, this._state.dossierTemplateId); break; + case SuperTypes.Recommendation: + color = this._dictionariesMapService.getDictionaryColor(dictionary, this._state.dossierTemplateId, true); + break; case SuperTypes.Skipped: color = this._dictionariesMapService.getDictionaryColor(superType, this._state.dossierTemplateId); break; diff --git a/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts b/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts index 224c950b4..92df9d108 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/file-data.service.ts @@ -15,13 +15,13 @@ import { BehaviorSubject, firstValueFrom, iif, Observable, Subject } from 'rxjs' import { RedactionLogEntry } from '../../../models/file/redaction-log.entry'; import { Injectable } from '@angular/core'; import { FilePreviewStateService } from './file-preview-state.service'; -import { ViewedPagesService } from '../../../services/entity-services/viewed-pages.service'; -import { UserPreferenceService } from '../../../services/user-preference.service'; -import { DictionariesMapService } from '../../../services/entity-services/dictionaries-map.service'; +import { ViewedPagesService } from '@services/entity-services/viewed-pages.service'; +import { UserPreferenceService } from '@services/user-preference.service'; +import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; -import { PermissionsService } from '../../../services/permissions.service'; +import { PermissionsService } from '@services/permissions.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { shareDistinctLast, shareLast, Toaster } from '../../../../../../../libs/common-ui/src'; +import { shareDistinctLast, shareLast, Toaster } from '@iqser/common-ui'; import { RedactionLogService } from '../../dossier/services/redaction-log.service'; import { TextHighlightService } from '../../dossier/services/text-highlight.service'; import { ViewModeService } from './view-mode.service'; diff --git a/apps/red-ui/src/app/modules/file-preview/services/pdf-viewer.service.ts b/apps/red-ui/src/app/modules/file-preview/services/pdf-viewer.service.ts index f961db56e..472d5ce78 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/pdf-viewer.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/pdf-viewer.service.ts @@ -10,7 +10,7 @@ import { DISABLED_HOTKEYS } from '../shared/constants'; import { Observable, Subject } from 'rxjs'; import { NGXLogger } from 'ngx-logger'; import { tap } from 'rxjs/operators'; -import { shareLast } from '../../../../../../../libs/common-ui/src'; +import { shareLast } from '@iqser/common-ui'; import Annotation = Core.Annotations.Annotation; @Injectable() diff --git a/apps/red-ui/src/app/modules/search/search-screen/search-screen.component.html b/apps/red-ui/src/app/modules/search/search-screen/search-screen.component.html index b63970902..9a90d6016 100644 --- a/apps/red-ui/src/app/modules/search/search-screen/search-screen.component.html +++ b/apps/red-ui/src/app/modules/search/search-screen/search-screen.component.html @@ -76,7 +76,7 @@
- + {{ item.dossierName }}
diff --git a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html index 8050b4264..4dfa09fe4 100644 --- a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html +++ b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html @@ -42,7 +42,7 @@
- + {{ selectDictionary.label | translate }} {{ dictionary.label }} diff --git a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts index e8d4ea708..02ec11ae4 100644 --- a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts @@ -1,10 +1,10 @@ -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; -import { Debounce, IconButtonTypes, List } from '@iqser/common-ui'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; +import { Debounce, IconButtonTypes, List, LoadingService } from '@iqser/common-ui'; import { firstValueFrom, Observable, of } from 'rxjs'; import { catchError, map, take, tap } from 'rxjs/operators'; -import { Dictionary, Dossier, DossierTemplate } from '@red/domain'; +import { Dictionary, DICTIONARY_TYPE_KEY_MAP, DictionaryType, Dossier, DossierTemplate } from '@red/domain'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { DictionaryService } from '@shared/services/dictionary.service'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; import { EditorComponent } from '@shared/components/editor/editor.component'; @@ -22,6 +22,7 @@ const SMOOTH_SCROLL = 0; export class DictionaryManagerComponent implements OnChanges { readonly iconButtonTypes = IconButtonTypes; + @Input() type: DictionaryType = 'dictionary'; @Input() withFloatingActions = true; @Input() filterByDossierTemplate = false; @Input() initialEntries: List; @@ -47,6 +48,8 @@ export class DictionaryManagerComponent implements OnChanges { constructor( private readonly _dictionaryService: DictionaryService, private readonly _dictionariesMapService: DictionariesMapService, + private readonly _loadingService: LoadingService, + private readonly _changeRef: ChangeDetectorRef, readonly activeDossiersService: ActiveDossiersService, readonly dossierTemplatesService: DossierTemplatesService, ) {} @@ -60,7 +63,7 @@ export class DictionaryManagerComponent implements OnChanges { set dossierTemplate(value) { this._dossierTemplate = value; this.dictionaries = this._dictionaries; - this._dictionary = this.selectDictionary; + this._compareDictionary = this.selectDictionary; this.showDiffEditor = false; } @@ -87,14 +90,15 @@ export class DictionaryManagerComponent implements OnChanges { }); } - private _dictionary = this.selectDictionary; + private _compareDictionary = this.selectDictionary; - get dictionary() { - return this._dictionary; + get compareDictionary() { + return this._compareDictionary; } - set dictionary(dictionary: Dictionary) { - this._dictionary = dictionary; + set compareDictionary(dictionary: Dictionary) { + this._loadingService.start(); + this._compareDictionary = dictionary; if (dictionary.label === this.selectDictionary.label) { this.showDiffEditor = false; @@ -102,7 +106,8 @@ export class DictionaryManagerComponent implements OnChanges { return; } const entries: List = - this._dictionary.entries ?? this._dictionariesMapService.get(this._dictionary.dossierTemplateId, this._dictionary.type).entries; + this._compareDictionary.getEntries(this.type) ?? + this._dictionariesMapService.get(this._compareDictionary.dossierTemplateId, this._compareDictionary.type).getEntries(this.type); if (entries.length) { this.diffEditorText = this._toString([...entries]); @@ -111,16 +116,20 @@ export class DictionaryManagerComponent implements OnChanges { } firstValueFrom( - this._dictionaryService.getForType(this._dictionary.dossierTemplateId, this._dictionary.type).pipe( - tap(values => (this._dictionary.entries = [...values.entries] ?? [])), + this._dictionaryService.getForType(this._compareDictionary.dossierTemplateId, this._compareDictionary.type).pipe( + tap(values => { + this._compareDictionary.setEntries([...values[DICTIONARY_TYPE_KEY_MAP[this.type]]] ?? [], this.type); + }), catchError(() => { - this._dictionary.entries = []; + this._compareDictionary.setEntries([], this.type); return of({}); }), ), ).then(() => { - this.diffEditorText = this._toString([...this._dictionary.entries]); + this.diffEditorText = this._toString([...(this._compareDictionary.getEntries(this.type) as string[])]); this.showDiffEditor = true; + this._changeRef.markForCheck(); + this._loadingService.stop(); }); } @@ -137,7 +146,7 @@ export class DictionaryManagerComponent implements OnChanges { get optionNotSelected() { if (this.filterByDossierTemplate) { - return this.selectDictionary.label === this._dictionary.label; + return this.selectDictionary.label === this._compareDictionary.label; } return this.dossier.dossierName === this.selectDossier.dossierName; } @@ -207,7 +216,6 @@ export class DictionaryManagerComponent implements OnChanges { private _onDossierChanged(dossierTemplateId: string, dossierId?: string, type = 'dossier_redaction'): Observable { const dictionary$ = this._dictionaryService.getForType(dossierTemplateId, type, dossierId); - return dictionary$.pipe(map(data => this._toString([...data.entries]))); } diff --git a/apps/red-ui/src/app/services/entity-services/dictionaries-map.service.ts b/apps/red-ui/src/app/services/entity-services/dictionaries-map.service.ts index 7f09cbf9f..f828d0326 100644 --- a/apps/red-ui/src/app/services/entity-services/dictionaries-map.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dictionaries-map.service.ts @@ -14,7 +14,18 @@ export class DictionariesMapService extends EntitiesMapService * Retrieves all dictionary entries of an entry type */ @Validate() - getForType(@RequiredParam() dossierTemplateId: string, @RequiredParam() type: string, dossierId?: string) { + getForType(@RequiredParam() dossierTemplateId: string, @RequiredParam() type: string, dossierId?: string): Observable { const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined; return this._getOne([type, dossierTemplateId], this._defaultModelPath, queryParams); } @@ -144,6 +144,7 @@ export class DictionaryService extends EntitiesService type: string, dossierId: string, showToast = true, + dictionaryEntryType = DictionaryEntryTypes.ENTRY, ): Observable { let entriesToAdd = []; entries.forEach(currentEntry => { @@ -156,13 +157,13 @@ export class DictionaryService extends EntitiesService // can add at least 1 - block UI let obs: Observable; if (entriesToAdd.length > 0) { - obs = this._addEntry(entriesToAdd, dossierTemplateId, type, dossierId, true); + obs = this._addEntries(entriesToAdd, dossierTemplateId, type, dictionaryEntryType, dossierId); } else { - obs = this._deleteEntries(initialEntries, dossierTemplateId, type, dossierId); + obs = this._deleteEntries(initialEntries, dossierTemplateId, type, dictionaryEntryType, dossierId); } return obs.pipe( - switchMap(dictionary => this._dossierTemplateStatsService.getFor([dossierTemplateId]).pipe(mapTo(dictionary))), + switchMap(dictionary => this._dossierTemplateStatsService.getFor([dossierTemplateId]).pipe(map(() => dictionary))), tap( () => { if (showToast) { @@ -216,7 +217,7 @@ export class DictionaryService extends EntitiesService const virtualTypes$: Observable = this.getColors(dossierTemplateId).pipe( tap(colors => { for (const key of Object.keys(colors)) { - const color = colors[key]; + const color: string = colors[key]; try { const rgbValue = hexToRgb(color); if (!rgbValue) { @@ -273,11 +274,11 @@ export class DictionaryService extends EntitiesService #addUpdateDictionaryErrorToast(error: HttpErrorResponse): Observable { if (error.status === HttpStatusCode.Conflict) { - this._toaster.error(_('add-edit-dictionary.error.dictionary-already-exists')); + this._toaster.error(_('add-edit-entity.error.entity-already-exists')); } else if (error.status === HttpStatusCode.BadRequest) { - this._toaster.error(_('add-edit-dictionary.error.invalid-color-or-rank')); + this._toaster.error(_('add-edit-entity.error.invalid-color-or-rank')); } else { - this._toaster.error(_('add-edit-dictionary.error.generic')); + this._toaster.error(_('add-edit-entity.error.generic')); } return throwError(() => error); } @@ -286,19 +287,20 @@ export class DictionaryService extends EntitiesService * Add dictionary entries with entry type. */ @Validate() - private _addEntry( - @RequiredParam() body: List, - @RequiredParam() dossierTemplateId: string, - @RequiredParam() type: string, - dossierId?: string, - removeCurrent?: boolean, + private _addEntries( + entries: List, + dossierTemplateId: string, + type: string, + dictionaryEntryType: DictionaryEntryType, + dossierId: string, ) { const queryParams: List = [ { key: 'dossierId', value: dossierId }, - { key: 'removeCurrent', value: removeCurrent }, + { key: 'dictionaryEntryType', value: dictionaryEntryType }, + { key: 'removeCurrent', value: true }, ]; const url = `${this._defaultModelPath}/${type}/${dossierTemplateId}`; - return this._post(body, url, queryParams); + return this._post(entries, url, queryParams); } /** @@ -306,13 +308,16 @@ export class DictionaryService extends EntitiesService */ @Validate() private _deleteEntries( - @RequiredParam() body: List, - @RequiredParam() dossierTemplateId: string, - @RequiredParam() type: string, - @RequiredParam() dossierId?: string, + entries: List, + dossierTemplateId: string, + type: string, + dictionaryEntryType: DictionaryEntryType, + dossierId: string, ) { - const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined; + const queryParams = dossierId + ? [{ key: 'dossierId', value: dossierId }] + : [{ key: 'dictionaryEntryType', value: dictionaryEntryType }]; const url = `${this._defaultModelPath}/delete/${type}/${dossierTemplateId}`; - return this._post(body, url, queryParams); + return this._post(entries, url, queryParams); } } diff --git a/apps/red-ui/src/app/services/entity-services/dossier-templates.service.ts b/apps/red-ui/src/app/services/entity-services/dossier-templates.service.ts index 9547bfa4a..0b645589e 100644 --- a/apps/red-ui/src/app/services/entity-services/dossier-templates.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dossier-templates.service.ts @@ -1,13 +1,13 @@ import { EntitiesService, List, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { DossierTemplate, IDossierTemplate } from '@red/domain'; import { Injectable, Injector } from '@angular/core'; -import { forkJoin, Observable, of, throwError } from 'rxjs'; +import { forkJoin, Observable, of } from 'rxjs'; import { FileAttributesService } from './file-attributes.service'; import { catchError, mapTo, switchMap, tap } from 'rxjs/operators'; import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service'; import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { DictionaryService } from '@shared/services/dictionary.service'; +import { DictionaryService } from '@services/entity-services/dictionary.service'; const DOSSIER_TEMPLATE_CONFLICT_MSG = _('dossier-templates-listing.error.conflict'); const GENERIC_MSG = _('dossier-templates-listing.error.generic'); diff --git a/apps/red-ui/src/app/services/manual-annotation.service.ts b/apps/red-ui/src/app/services/manual-annotation.service.ts index fece80bf6..f3294ed5c 100644 --- a/apps/red-ui/src/app/services/manual-annotation.service.ts +++ b/apps/red-ui/src/app/services/manual-annotation.service.ts @@ -101,14 +101,15 @@ export class ManualAnnotationService extends GenericService } addRecommendation(annotation: AnnotationWrapper, dossierId: string, fileId: string, comment = { text: 'Accepted Recommendation' }) { - const manualRedactionEntry: IAddRedactionRequest = {}; - manualRedactionEntry.addToDictionary = true; - // set the ID as reason, so we can hide the suggestion - manualRedactionEntry.reason = annotation.annotationId; - manualRedactionEntry.value = annotation.value; - manualRedactionEntry.positions = annotation.positions; - manualRedactionEntry.type = annotation.recommendationType; - manualRedactionEntry.comment = comment; + const manualRedactionEntry: IAddRedactionRequest = { + addToDictionary: true, + // set the ID as reason, so we can hide the suggestion + reason: annotation.annotationId, + value: annotation.value, + positions: annotation.positions, + type: annotation.recommendationType, + comment: comment, + }; return this.addAnnotation(manualRedactionEntry, dossierId, fileId); } diff --git a/apps/red-ui/src/app/services/permissions.service.ts b/apps/red-ui/src/app/services/permissions.service.ts index fa6bbd797..b60cf3156 100644 --- a/apps/red-ui/src/app/services/permissions.service.ts +++ b/apps/red-ui/src/app/services/permissions.service.ts @@ -13,6 +13,10 @@ export class PermissionsService { private readonly _featuresService: FeaturesService, ) {} + canEditEntities(user = this._userService.currentUser): boolean { + return user.isAdmin; + } + canPerformDossierStatesActions(user = this._userService.currentUser): boolean { return user.isAdmin; } diff --git a/apps/red-ui/src/app/utils/constants.ts b/apps/red-ui/src/app/utils/constants.ts index bf535d4d8..1f808d01d 100644 --- a/apps/red-ui/src/app/utils/constants.ts +++ b/apps/red-ui/src/app/utils/constants.ts @@ -3,5 +3,5 @@ export const FALLBACK_COLOR = '#CCCCCC'; export const DOSSIER_ID = 'dossierId'; export const FILE_ID = 'fileId'; export const DOSSIER_TEMPLATE_ID = 'dossierTemplateId'; -export const DICTIONARY_TYPE = 'dictionary'; +export const ENTITY_TYPE = 'entity'; export const DOSSIERS_ARCHIVE = 'DOSSIERS_ARCHIVE'; diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 684210fa7..d8966dc0b 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -1,7 +1,7 @@ { "ADMIN_CONTACT_NAME": null, "ADMIN_CONTACT_URL": null, - "API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1", + "API_URL": "https://dev-05.iqser.cloud/redaction-gateway-v1", "APP_NAME": "RedactManager", "AUTO_READ_TIME": 3, "BACKEND_APP_VERSION": "4.4.40", @@ -17,7 +17,7 @@ "MAX_RETRIES_ON_SERVER_ERROR": 3, "OAUTH_CLIENT_ID": "redaction", "OAUTH_IDP_HINT": null, - "OAUTH_URL": "https://dev-08.iqser.cloud/auth/realms/redaction", + "OAUTH_URL": "https://dev-05.iqser.cloud/auth/realms/redaction", "RECENT_PERIOD_IN_HOURS": 24, "SELECTION_MODE": "structural", "MANUAL_BASE_URL": "https://docs.redactmanager.com/preview" diff --git a/apps/red-ui/src/assets/i18n/de.json b/apps/red-ui/src/assets/i18n/de.json index 3127d1417..6566faaaa 100644 --- a/apps/red-ui/src/assets/i18n/de.json +++ b/apps/red-ui/src/assets/i18n/de.json @@ -36,35 +36,6 @@ }, "header-new": "Dossier erstellen" }, - "add-edit-dictionary": { - "error": { - "dictionary-already-exists": "Ein Wörterbuch mit diesem Namen existiert bereits!", - "generic": "Wörterbuch konnte nicht gespeichert werden!", - "invalid-color-or-rank": "Ungültige Farbe oder Rang! Der Rang wird bereits von einem anderen Wörterbuch verwendet oder die Farbe ist kein gültiger Hex-Farbcode!" - }, - "form": { - "add-to-dictionary-action": "Anwender können Einträge hinzufügen", - "case-sensitive": "Groß-/Kleinschreibung berücksichtigen", - "color": "Hex-Farbcode", - "color-placeholder": "#", - "description": "Beschreibung", - "description-placeholder": "Beschreibung eingeben", - "hint": "Hinweis", - "name": "Name des Wörterbuches", - "name-hint": "Kann nach dem Speichern nicht mehr bearbeitet werden.", - "name-placeholder": "Namen eingeben", - "rank": "Rang", - "rank-placeholder": "1000", - "redaction": "Schwärzung", - "technical-name": "Technischer Name" - }, - "save": "Wörterbuch speichern", - "success": { - "create": "", - "edit": "" - }, - "title": "{type, select, edit{Wörterbuch {name} bearbeiten} create{Wörterbuch erstellen} other{}}" - }, "add-edit-dossier-attribute": { "error": { "generic": "Attribut konnte nicht gespeichert werden!" @@ -106,6 +77,38 @@ "save": "Dossier-Vorlage speichern", "title": "{type, select, edit{Dossier-Vorlage {name} bearbeiten} create{Dossier-Vorlage erstellen} other{}}" }, + "add-edit-entity": { + "error": { + "entity-already-exists": "", + "generic": "", + "invalid-color-or-rank": "" + }, + "form": { + "add-to-dictionary-action": "", + "case-sensitive": "", + "color": "", + "color-placeholder": "", + "default-reason": "", + "default-reason-placeholder": "", + "description": "", + "description-placeholder": "", + "has-dictionary": "", + "hint": "", + "name": "", + "name-placeholder": "", + "rank": "", + "rank-placeholder": "", + "recommendation-color": "", + "recommendation-color-placeholder": "", + "redaction": "", + "technical-name": "", + "technical-name-hint": "" + }, + "success": { + "create": "", + "edit": "" + } + }, "add-edit-file-attribute": { "form": { "column-header": "CSV-Spaltenüberschrift", @@ -158,9 +161,32 @@ }, "title": "{type, select, edit{Benutzer bearbeiten} create{Neuen Benutzer hinzufügen} other{}}" }, + "add-entity": { + "save": "Wörterbuch speichern", + "title": "Wörterbuch erstellen" + }, "admin-side-nav": { + "audit": "", + "configurations": "", + "default-colors": "", + "dictionary": "", + "digital-signature": "", + "dossier-attributes": "", + "dossier-states": "", + "dossier-template-info": "", "dossier-templates": "Dossier-Vorlage", - "settings": "Einstellungen" + "entities": "", + "entity-info": "", + "false-positive": "", + "false-recommendations": "", + "file-attributes": "", + "justifications": "", + "license-information": "", + "reports": "", + "rule-editor": "", + "settings": "Einstellungen", + "user-management": "", + "watermark": "" }, "annotation-actions": { "accept-recommendation": { @@ -321,6 +347,9 @@ "suggestion-resize": "Vorgeschlagene Größenänderung", "text-highlight": "" }, + "annotation": { + "pending": "" + }, "archived-dossiers-listing": { "no-data": { "title": "" @@ -549,7 +578,6 @@ } }, "content": "Begründung", - "default-colors": "Farbeinstellungen", "default-colors-screen": { "action": { "edit": "Farbe bearbeiten" @@ -575,57 +603,14 @@ } }, "dev-mode": "DEV", - "dictionaries": "Wörterbücher", "dictionary": "Wörterbuch", - "dictionary-listing": { - "action": { - "delete": "Wörterbuch löschen", - "edit": "Wörterbuch bearbeiten" - }, - "add-new": "Neues Wörterbuch", - "bulk": { - "delete": "Ausgewählte Wörterbücher löschen" - }, - "case-sensitive": "Klein-/Großschreibung berücksichtigen", - "no-data": { - "action": "Neues Wörterbuch", - "title": "Es gibt noch keine Wörterbücher." - }, - "no-match": { - "title": "Die ausgewählten Filter treffen auf kein Wörterbuch zu." - }, - "search": "Suche ...", - "stats": { - "charts": { - "entries": "Einträge", - "types": "Typen" - } - }, - "table-col-names": { - "hint-redaction": "Hinweis/Schwärzung", - "rank": "Rang", - "type": "Typ" - }, - "table-header": { - "title": "{length} {length, plural, one{Wörterbuch} other{Wörterbücher}}" - } - }, "dictionary-overview": { - "action": { - "delete": "Wörterbuch löschen", - "download": "Wörterbuch herunterladen", - "edit": "Wörterbuch bearbeiten", - "upload": "Wörterbuch hochladen" - }, "compare": { "compare": "Vergleichen", "select-dictionary": "Wörterbuch auswählen", "select-dossier": "Dossier auswählen", "select-dossier-template": "Dossiervorlage auswählen" }, - "dictionary-details": { - "description": "Beschreibung" - }, "error": { "entries-too-short": "Einige Einträge im Wörterbuch unterschreiten die Mindestlänge von 2 Zeichen. Diese sind rot markiert.", "generic": "Es ist ein Fehler aufgetreten ... Das Wörterbuch konnte nicht aktualisiert werden!" @@ -686,7 +671,6 @@ "number": "Nummer", "text": "Text" }, - "dossier-attributes": "Dossier-Attribut", "dossier-attributes-listing": { "action": { "delete": "Attribut löschen", @@ -880,7 +864,6 @@ "under-review": "In Review", "upload-files": "Sie können Dateien überall per Drag and Drop platzieren..." }, - "dossier-states": "", "dossier-states-listing": { "action": { "delete": "", @@ -910,12 +893,11 @@ "title": "" } }, - "dossier-template-info": "", "dossier-template-info-screen": { "created-by": "", "created-on": "", "description": "", - "dictionaries": "", + "entities": "", "entries": "", "modified-on": "", "valid-from": "", @@ -930,7 +912,7 @@ "bulk": { "delete": "Ausgewählte Dossier-Vorlagen löschen" }, - "dictionaries": "{length} {length, plural, one{dictionary} other{dictionaries}}", + "entities": "{length} {length, plural, one{entity} other{entities}}", "error": { "conflict": "Dieses DossierTemplate kann nicht gelöscht werden! Zumindest auf Dossier wird diese Vorlage verwendet!", "generic": "Dieses DossierTemplate kann nicht gelöscht werden!" @@ -1026,6 +1008,7 @@ "change-successful": "Dossier wurde aktualisiert.", "delete-successful": "Dossier wurde gelöscht.", "dictionary": { + "add-to-dictionary-action": "", "display-name": { "cancel": "Abbrechen", "edit": "Anzeigenamen bearbeiten", @@ -1072,6 +1055,42 @@ }, "side-nav-title": "Konfiguration" }, + "entities-listing": { + "action": { + "delete": "Wörterbuch löschen", + "edit": "Wörterbuch bearbeiten" + }, + "add-new": "Neues Wörterbuch", + "bulk": { + "delete": "Ausgewählte Wörterbücher löschen" + }, + "no-data": { + "action": "Neues Wörterbuch", + "title": "Es gibt noch keine Wörterbücher." + }, + "no-match": { + "title": "Die ausgewählten Filter treffen auf kein Wörterbuch zu." + }, + "search": "Suche ...", + "table-col-names": { + "dictionary-entries": "", + "hint-redaction": "Hinweis/Schwärzung", + "rank": "Rang", + "type": "Typ" + }, + "table-header": { + "title": "{length} {length, plural, one{Wörterbuch} other{Wörterbücher}}" + } + }, + "entity": { + "info": { + "actions": { + "revert": "", + "save": "" + }, + "heading": "" + } + }, "error": { "deleted-entity": { "dossier": { @@ -1108,7 +1127,6 @@ "number": "Nummer", "text": "Freier Text" }, - "file-attributes": "Datei-Attribute", "file-attributes-configurations": { "cancel": "", "form": { @@ -1297,6 +1315,7 @@ "approved": "Genehmigt", "deleted": "Gelöscht", "error": "Reanalyse erforderlich", + "full-processing": "", "full-reprocess": "Wird analysiert", "image-analyzing": "Bildanalyse", "indexing": "Wird analysiert", @@ -1417,7 +1436,6 @@ "success": "" }, "highlights": "", - "hint": "Hinweis", "image-category": { "formula": "Formel", "image": "Bild", @@ -1428,7 +1446,6 @@ "unassigned": "Unbekannt", "you": "Sie" }, - "justifications": "Begründungen", "justifications-listing": { "actions": { "delete": "Begründung löschen", @@ -1511,20 +1528,6 @@ } } }, - "months": { - "apr": "Apr.", - "aug": "August", - "dec": "Dez.", - "feb": "Feb.", - "jan": "Jan.", - "jul": "Jul.", - "jun": "März", - "mar": "März", - "may": "Nov.", - "nov": "Nov.", - "oct": "Okt.", - "sep": "Sept." - }, "notification": { "assign-approver": "Sie wurden dem Dokument {fileName} im Dossier {dossierName} als Genehmiger zugewiesen!", "assign-reviewer": "Sie wurden dem Dokument {fileName} im Dossier {dossierName} als Reviewer zugewiesen!", @@ -1623,6 +1626,10 @@ "processed": "", "processing": "" }, + "processing": { + "basic": "", + "ocr": "" + }, "readonly": "Lesemodus", "readonly-archived": "", "recategorize-image-dialog": { @@ -1637,7 +1644,6 @@ }, "header": "Bildtypen bearbeiten" }, - "redaction": "Schwärzung", "references": "", "remove-annotations-dialog": { "cancel": "Abbrechen", @@ -1657,7 +1663,6 @@ "report-type": { "label": "{length} {length, plural, one{Berichtstyp} other{Berichtstypen}}" }, - "reports": "Berichte", "reports-screen": { "description": "", "descriptions": { @@ -1733,7 +1738,6 @@ "red-user-admin": "Benutzer-Admin", "regular": "Regulär" }, - "rule-editor": "Regel-Editor", "rules-screen": { "error": { "generic": "Es ist ein Fehler aufgetreten ... Die Regeln konnten nicht aktualisiert werden!" @@ -1915,7 +1919,6 @@ "view-as": "Ansicht als:", "workflow": "Arbeitsablauf" }, - "watermark": "Wasserzeichen", "watermark-screen": { "action": { "change-success": "Das Wasserzeichen wurde aktualisiert!", @@ -1926,6 +1929,7 @@ }, "form": { "color": "Farbe", + "color-placeholder": "", "font-size": "Schriftgröße", "font-type": "Schriftart", "opacity": "Deckkraft", diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 202091afb..45a25d508 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -36,35 +36,6 @@ }, "header-new": "Create Dossier" }, - "add-edit-dictionary": { - "error": { - "dictionary-already-exists": "Dictionary with this name already exists!", - "generic": "Failed to save dictionary!", - "invalid-color-or-rank": "Invalid color or rank! Rank is already used by another dictionary or the color is not a valid hexColor!" - }, - "form": { - "add-to-dictionary-action": "Enable 'Add to dictionary'", - "case-sensitive": "Case Sensitive", - "color": "Hex Color", - "color-placeholder": "#", - "description": "Description", - "description-placeholder": "Enter Description", - "hint": "Hint", - "name": "Display Name", - "name-hint": "Cannot be edited after saving.", - "name-placeholder": "Enter Name", - "rank": "Rank", - "rank-placeholder": "1000", - "redaction": "Redaction", - "technical-name": "Technical Name" - }, - "save": "Save Dictionary", - "success": { - "create": "Dictionary added!", - "edit": "Dictionary updated!" - }, - "title": "{type, select, edit{Edit {name}} create{Create} other{}} Dictionary" - }, "add-edit-dossier-attribute": { "error": { "generic": "Failed to save attribute!" @@ -106,6 +77,38 @@ "save": "Save Dossier Template", "title": "{type, select, edit{Edit {name}} create{Create} other{}} Dossier Template" }, + "add-edit-entity": { + "error": { + "entity-already-exists": "Entity with this name already exists!", + "generic": "Failed to save entity!", + "invalid-color-or-rank": "Invalid color or rank! Rank is already used by another entity or the color is not a valid hexColor!" + }, + "form": { + "add-to-dictionary-action": "Enable 'Add to dictionary'", + "case-sensitive": "Case Sensitive", + "color": "Hex Color", + "color-placeholder": "#", + "default-reason": "Default Reason", + "default-reason-placeholder": "No Default Reason", + "description": "Description", + "description-placeholder": "Enter Description", + "has-dictionary": "Has dictionary", + "hint": "Hint", + "name": "Display Name", + "name-placeholder": "Enter Name", + "rank": "Rank", + "rank-placeholder": "1000", + "recommendation-color": "Recommendation Hex Color", + "recommendation-color-placeholder": "#", + "redaction": "Redaction", + "technical-name": "Technical Name", + "technical-name-hint": "{type, select, edit{Autogenerated based on the initial display name.} create{Autogenerates based on the display name and cannot be edited after saving.} other{}}" + }, + "success": { + "create": "Entity added!", + "edit": "Entity updated!" + } + }, "add-edit-file-attribute": { "form": { "column-header": "CSV Column Header", @@ -158,12 +161,32 @@ }, "title": "{type, select, edit{Edit} create{Add New} other{}} User" }, - "admin-side-nav": { - "dossier-templates": "Dossier Templates", - "settings": "Settings" + "add-entity": { + "save": "Save Entity", + "title": "Create Entity" }, - "annotation": { - "pending": "(Pending Analysis)" + "admin-side-nav": { + "audit": "Audit", + "configurations": "Configurations", + "default-colors": "Default Colors", + "dictionary": "Dictionary", + "digital-signature": "Digital Signature", + "dossier-attributes": "Dossier Attributes", + "dossier-states": "Dossier States", + "dossier-template-info": "Info", + "dossier-templates": "Dossier Templates", + "entities": "Entities", + "entity-info": "Info", + "false-positive": "False Positive", + "false-recommendations": "False Recommendations", + "file-attributes": "File Attributes", + "justifications": "Justifications", + "license-information": "License Information", + "reports": "Reports", + "rule-editor": "Rule Editor", + "settings": "Settings", + "user-management": "User Management", + "watermark": "Watermark" }, "annotation-actions": { "accept-recommendation": { @@ -324,6 +347,9 @@ "suggestion-resize": "Suggested Resize", "text-highlight": "Highlight" }, + "annotation": { + "pending": "(Pending Analysis)" + }, "archived-dossiers-listing": { "no-data": { "title": "No archived dossiers." @@ -552,7 +578,6 @@ } }, "content": "Reason", - "default-colors": "Default Colors", "default-colors-screen": { "action": { "edit": "Edit Color" @@ -578,57 +603,14 @@ } }, "dev-mode": "DEV", - "dictionaries": "Dictionaries", "dictionary": "Dictionary", - "dictionary-listing": { - "action": { - "delete": "Delete Dictionary", - "edit": "Edit Dictionary" - }, - "add-new": "New Dictionary", - "bulk": { - "delete": "Delete Selected Dictionaries" - }, - "case-sensitive": "Case Sensitive", - "no-data": { - "action": "New Dictionary", - "title": "There are no dictionaries yet." - }, - "no-match": { - "title": "No dictionaries match your current filters." - }, - "search": "Search...", - "stats": { - "charts": { - "entries": "Entries", - "types": "Types" - } - }, - "table-col-names": { - "hint-redaction": "Hint/Redaction", - "rank": "Rank", - "type": "Type" - }, - "table-header": { - "title": "{length} {length, plural, one{dictionary} other{dictionaries}}" - } - }, "dictionary-overview": { - "action": { - "delete": "Delete Dictionary", - "download": "Download Dictionary", - "edit": "Edit Dictionary", - "upload": "Upload Dictionary" - }, "compare": { "compare": "Compare", "select-dictionary": "Select Dictionary", "select-dossier": "Select Dossier", "select-dossier-template": "Select Dossier Template" }, - "dictionary-details": { - "description": "Description" - }, "error": { "entries-too-short": "Some entries of the dictionary are below the minimum length of 2. These are highlighted with red!", "generic": "Something went wrong... Dictionary update failed!" @@ -689,7 +671,6 @@ "number": "Number", "text": "Free Text" }, - "dossier-attributes": "Dossier Attributes", "dossier-attributes-listing": { "action": { "delete": "Delete Attribute", @@ -883,7 +864,6 @@ "under-review": "Under Review", "upload-files": "Drag & drop files anywhere..." }, - "dossier-states": "Dossier States", "dossier-states-listing": { "action": { "delete": "Delete State", @@ -913,12 +893,11 @@ "title": "{length} dossier {length, plural, one{state} other{states}}" } }, - "dossier-template-info": "Info", "dossier-template-info-screen": { "created-by": "Created by", "created-on": "Created on: {date}", "description": "Description", - "dictionaries": "{count} {count, plural, one{dictionary} other{dictionaries}}", + "entities": "{count} {count, plural, one{entity} other{entities}}", "entries": "{count} {count, plural, one{entry} other{entries}}", "modified-on": "Modified on: {date}", "valid-from": "Valid from: {date}", @@ -933,7 +912,7 @@ "bulk": { "delete": "Delete Selected Dossier Templates" }, - "dictionaries": "{length} {length, plural, one{dictionary} other{dictionaries}}", + "entities": "{length} {length, plural, one{entity} other{entities}}", "error": { "conflict": "Cannot delete this DossierTemplate! At least one Dossier uses this template!", "generic": "Cannot delete this DossierTemplate!" @@ -1029,6 +1008,7 @@ "change-successful": "Dossier {dossierName} was updated.", "delete-successful": "Dossier {dossierName} was deleted.", "dictionary": { + "add-to-dictionary-action": "Available for add to dictionary", "display-name": { "cancel": "Cancel", "edit": "Edit Display Name", @@ -1075,6 +1055,42 @@ }, "side-nav-title": "Configurations" }, + "entities-listing": { + "action": { + "delete": "Delete Entity", + "edit": "Edit Entity" + }, + "add-new": "New Entity", + "bulk": { + "delete": "Delete Selected Entities" + }, + "no-data": { + "action": "New Entity", + "title": "There are no entities yet." + }, + "no-match": { + "title": "No entities match your current filters." + }, + "search": "Search...", + "table-col-names": { + "dictionary-entries": "Dictionary entries", + "hint-redaction": "Hint/Redaction", + "rank": "Rank", + "type": "Name" + }, + "table-header": { + "title": "{length} {length, plural, one{entity} other{entities}}" + } + }, + "entity": { + "info": { + "actions": { + "revert": "Revert", + "save": "Save Changes" + }, + "heading": "Edit Entity" + } + }, "error": { "deleted-entity": { "dossier": { @@ -1111,7 +1127,6 @@ "number": "Number", "text": "Free Text" }, - "file-attributes": "File Attributes", "file-attributes-configurations": { "cancel": "Cancel", "form": { @@ -1295,17 +1310,13 @@ "only-managers": "Enabling / disabling is permitted only for managers" } }, - "processing": { - "ocr": "OCR", - "basic": "Processing" - }, "file-status": { "analyse": "Analyzing", "approved": "Approved", "deleted": "Deleted", "error": "Re-processing required", - "full-reprocess": "Processing", "full-processing": "Processing", + "full-reprocess": "Processing", "image-analyzing": "Image Analyzing", "indexing": "Processing", "initial-processing": "Initial processing...", @@ -1425,7 +1436,6 @@ "success": "{operation, select, CONVERT{Converting} REMOVE{Removing} other{}} highlights in progress..." }, "highlights": "{color} - {length} {length, plural, one{highlight} other{highlights}}", - "hint": "Hint", "image-category": { "formula": "Formula", "image": "Image", @@ -1436,7 +1446,6 @@ "unassigned": "Unassigned", "you": "You" }, - "justifications": "Justifications", "justifications-listing": { "actions": { "delete": "Delete Justification", @@ -1519,20 +1528,6 @@ } } }, - "months": { - "apr": "Apr.", - "aug": "Aug.", - "dec": "Dec.", - "feb": "Feb.", - "jan": "Jan.", - "jul": "Jul.", - "jun": "Jun.", - "mar": "Mar.", - "may": "May", - "nov": "Nov.", - "oct": "Oct.", - "sep": "Sep." - }, "notification": { "assign-approver": "You have been assigned as approver for {fileName} in dossier: {dossierName}!", "assign-reviewer": "You have been assigned as reviewer for {fileName} in dossier: {dossierName}!", @@ -1631,6 +1626,10 @@ "processed": "Processed", "processing": "Processing" }, + "processing": { + "basic": "Processing", + "ocr": "OCR" + }, "readonly": "Read only", "readonly-archived": "Read only (archived)", "recategorize-image-dialog": { @@ -1645,7 +1644,6 @@ }, "header": "Edit Image Type" }, - "redaction": "Redaction", "references": "{count} {count, plural, one{reference} other{references}}", "remove-annotations-dialog": { "cancel": "Cancel", @@ -1665,7 +1663,6 @@ "report-type": { "label": "{length} report {length, plural, one{type} other{types}}" }, - "reports": "Reports", "reports-screen": { "description": "Below, you will find a list of placeholders for dossier- and document-specific information. You can include these placeholders in your report templates.", "descriptions": { @@ -1741,7 +1738,6 @@ "red-user-admin": "Users Admin", "regular": "Regular" }, - "rule-editor": "Rule Editor", "rules-screen": { "error": { "generic": "Something went wrong... Rules update failed!" @@ -1923,7 +1919,6 @@ "view-as": "View as:", "workflow": "Workflow" }, - "watermark": "Watermark", "watermark-screen": { "action": { "change-success": "Watermark updated!", @@ -1934,6 +1929,7 @@ }, "form": { "color": "Color", + "color-placeholder": "#", "font-size": "Font Size", "font-type": "Font Type", "opacity": "Opacity", diff --git a/libs/common-ui b/libs/common-ui index 08737703e..22434a262 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit 08737703e46fd0a8366bdf0c4241772d7ac8e2bb +Subproject commit 22434a2627d71f7cddc8bdd7d9adab9762c41e4c diff --git a/libs/red-domain/src/lib/dictionaries/dictionary.model.ts b/libs/red-domain/src/lib/dictionaries/dictionary.model.ts index 492905cff..24c06b959 100644 --- a/libs/red-domain/src/lib/dictionaries/dictionary.model.ts +++ b/libs/red-domain/src/lib/dictionaries/dictionary.model.ts @@ -1,32 +1,44 @@ import { Entity, List } from '@iqser/common-ui'; import { IDictionary } from './dictionary'; +import { DICTIONARY_TYPE_KEY_MAP, DictionaryType } from '../redaction-log'; export class Dictionary extends Entity implements IDictionary { readonly addToDictionaryAction: boolean; readonly caseInsensitive: boolean; readonly description: string; readonly dossierTemplateId?: string; - entries: List; readonly hexColor?: string; + readonly recommendationHexColor?: string; readonly hint: boolean; readonly label: string; readonly rank?: number; readonly recommendation: boolean; readonly type: string; + readonly typeId?: string; + readonly hasDictionary?: boolean; - constructor(dictionary: IDictionary, readonly virtual = false) { - super(dictionary); - this.addToDictionaryAction = !!dictionary.addToDictionaryAction; - this.caseInsensitive = !!dictionary.caseInsensitive; - this.description = dictionary.description ?? ''; - this.dossierTemplateId = dictionary.dossierTemplateId; - this.entries = dictionary.entries ?? []; - this.hexColor = dictionary.hexColor; - this.hint = !!dictionary.hint; - this.label = dictionary.label ?? dictionary.type; - this.rank = dictionary.rank; - this.recommendation = !!dictionary.recommendation; - this.type = dictionary.type; + entries: List; + falsePositiveEntries: List; + falseRecommendationEntries: List; + + constructor(entity: IDictionary, readonly virtual = false) { + super(entity); + this.addToDictionaryAction = !!entity.addToDictionaryAction; + this.caseInsensitive = !!entity.caseInsensitive; + this.description = entity.description ?? ''; + this.dossierTemplateId = entity.dossierTemplateId; + this.entries = entity.entries ?? []; + this.falsePositiveEntries = entity.falsePositiveEntries ?? []; + this.falseRecommendationEntries = entity.falseRecommendationEntries ?? []; + this.hexColor = entity.hexColor; + this.recommendationHexColor = entity.recommendationHexColor; + this.hint = !!entity.hint; + this.label = entity.label ?? entity.type; + this.rank = entity.rank; + this.recommendation = !!entity.recommendation; + this.type = entity.type; + this.typeId = entity.typeId; + this.hasDictionary = entity.hasDictionary; } get id(): string { @@ -38,6 +50,14 @@ export class Dictionary extends Entity implements IDictionary { } get routerLink(): string { - return `/main/admin/dossier-templates/${this.dossierTemplateId}/dictionaries/${this.type}`; + return `/main/admin/dossier-templates/${this.dossierTemplateId}/entities/${this.type}`; + } + + setEntries(entries: List, type: DictionaryType): void { + this[DICTIONARY_TYPE_KEY_MAP[type]] = entries; + } + + getEntries(type: DictionaryType): List { + return this[DICTIONARY_TYPE_KEY_MAP[type]]; } } diff --git a/libs/red-domain/src/lib/dictionaries/dictionary.ts b/libs/red-domain/src/lib/dictionaries/dictionary.ts index c32ce22aa..5f0e6b205 100644 --- a/libs/red-domain/src/lib/dictionaries/dictionary.ts +++ b/libs/red-domain/src/lib/dictionaries/dictionary.ts @@ -24,10 +24,13 @@ export interface IDictionary { * The nonnull entry type. */ readonly type: string; + readonly typeId?: string; /** * The list of dictionary entries of an entry type. */ readonly entries?: List; + readonly falsePositiveEntries?: List; + readonly falseRecommendationEntries?: List; /** * The value of color must be a correct hex color */ @@ -48,4 +51,8 @@ export interface IDictionary { * True if the type just for recommendations, not for redaction, default is false. */ readonly recommendation?: boolean; + + readonly recommendationHexColor?: string; + + readonly hasDictionary?: boolean; } diff --git a/libs/red-domain/src/lib/dossiers/dossier.model.ts b/libs/red-domain/src/lib/dossiers/dossier.model.ts index 79f613570..8d3eb5db6 100644 --- a/libs/red-domain/src/lib/dossiers/dossier.model.ts +++ b/libs/red-domain/src/lib/dossiers/dossier.model.ts @@ -58,7 +58,11 @@ export class Dossier implements IDossier, IListable, IRouterPath { } get isActive(): boolean { - return this.softDeletedTime == null && this.archivedTime == null; + return !this.isSoftDeleted && !this.isArchived; + } + + get isArchived(): boolean { + return this.archivedTime !== null; } get isSoftDeleted(): boolean { diff --git a/libs/red-domain/src/lib/files/types.ts b/libs/red-domain/src/lib/files/types.ts index dae596fff..9ce6398e3 100644 --- a/libs/red-domain/src/lib/files/types.ts +++ b/libs/red-domain/src/lib/files/types.ts @@ -39,6 +39,7 @@ export const isProcessingStatuses: List = [ ProcessingFileStatuses.INDEXING, ProcessingFileStatuses.PROCESSING, ProcessingFileStatuses.ANALYSE, + ProcessingFileStatuses.FULL_PROCESSING, ] as const; export const isFullProcessingStatuses: List = [ diff --git a/libs/red-domain/src/lib/redaction-log/add-redaction.request.ts b/libs/red-domain/src/lib/redaction-log/add-redaction.request.ts index 40040a583..da708bad8 100644 --- a/libs/red-domain/src/lib/redaction-log/add-redaction.request.ts +++ b/libs/red-domain/src/lib/redaction-log/add-redaction.request.ts @@ -1,9 +1,11 @@ import { IRectangle } from '../geometry'; import { List } from '@iqser/common-ui'; +import { DictionaryEntryType } from './dictionary-entry-types'; export interface IAddRedactionRequest { addToDictionary?: boolean; addToDossierDictionary?: boolean; + dictionaryEntryType?: DictionaryEntryType; comment?: { text: string }; legalBasis?: string; positions?: List; diff --git a/libs/red-domain/src/lib/redaction-log/dictionary-entry-types.ts b/libs/red-domain/src/lib/redaction-log/dictionary-entry-types.ts new file mode 100644 index 000000000..f7917e815 --- /dev/null +++ b/libs/red-domain/src/lib/redaction-log/dictionary-entry-types.ts @@ -0,0 +1,21 @@ +export type DictionaryEntryType = 'ENTRY' | 'FALSE_POSITIVE' | 'FALSE_RECOMMENDATION'; + +export const DictionaryEntryTypes = { + ENTRY: 'ENTRY' as DictionaryEntryType, + FALSE_POSITIVE: 'FALSE_POSITIVE' as DictionaryEntryType, + FALSE_RECOMMENDATION: 'FALSE_RECOMMENDATION' as DictionaryEntryType, +}; + +export type DictionaryType = 'dictionary' | 'false-positive' | 'false-recommendations'; + +export const DICTIONARY_TYPE_KEY_MAP: { [key in DictionaryType]: 'entries' | 'falsePositiveEntries' | 'falseRecommendationEntries' } = { + dictionary: 'entries', + 'false-positive': 'falsePositiveEntries', + 'false-recommendations': 'falseRecommendationEntries', +}; + +export const DICTIONARY_TO_ENTRY_TYPE_MAP: { [key in DictionaryType]: DictionaryEntryType } = { + dictionary: DictionaryEntryTypes.ENTRY, + 'false-positive': DictionaryEntryTypes.FALSE_POSITIVE, + 'false-recommendations': DictionaryEntryTypes.FALSE_RECOMMENDATION, +}; diff --git a/libs/red-domain/src/lib/redaction-log/index.ts b/libs/red-domain/src/lib/redaction-log/index.ts index 04deded25..746bdbb73 100644 --- a/libs/red-domain/src/lib/redaction-log/index.ts +++ b/libs/red-domain/src/lib/redaction-log/index.ts @@ -11,3 +11,4 @@ export * from './approve-request'; export * from './image-recategorization.request'; export * from './resize.request'; export * from './manual-change'; +export * from './dictionary-entry-types'; diff --git a/libs/red-domain/src/lib/shared/admin-side-nav-types.ts b/libs/red-domain/src/lib/shared/admin-side-nav-types.ts new file mode 100644 index 000000000..0739b42c5 --- /dev/null +++ b/libs/red-domain/src/lib/shared/admin-side-nav-types.ts @@ -0,0 +1,7 @@ +export type AdminSideNavType = 'settings' | 'dossierTemplates' | 'entities'; + +export const AdminSideNavTypes = { + settings: 'settings' as AdminSideNavType, + dossierTemplates: 'dossierTemplates' as AdminSideNavType, + entities: 'entities' as AdminSideNavType, +}; diff --git a/libs/red-domain/src/lib/shared/index.ts b/libs/red-domain/src/lib/shared/index.ts index 57a45f6fd..71892cfa0 100644 --- a/libs/red-domain/src/lib/shared/index.ts +++ b/libs/red-domain/src/lib/shared/index.ts @@ -9,3 +9,4 @@ export * from './view-mode'; export * from './expandable-file-actions'; export * from './pdf.types'; export * from './logger-config'; +export * from './admin-side-nav-types';