diff --git a/apps/red-ui/src/app/app-routing.module.ts b/apps/red-ui/src/app/app-routing.module.ts index ccf478b4b..64ff6c3d7 100644 --- a/apps/red-ui/src/app/app-routing.module.ts +++ b/apps/red-ui/src/app/app-routing.module.ts @@ -22,6 +22,7 @@ import { CustomRouteReuseStrategy } from '@iqser/common-ui/lib/utils'; import { ifLoggedIn } from '@guards/if-logged-in.guard'; import { ifNotLoggedIn } from '@guards/if-not-logged-in.guard'; import { TenantSelectComponent } from '@iqser/common-ui/lib/tenants'; +import { editAttributeGuard } from '@guards/edit-attribute.guard'; const dossierTemplateIdRoutes: IqserRoutes = [ { @@ -37,6 +38,7 @@ const dossierTemplateIdRoutes: IqserRoutes = [ { path: `:${DOSSIER_ID}`, canActivate: [CompositeRouteGuard, IqserPermissionsGuard], + canDeactivate: [editAttributeGuard], data: { routeGuards: [DossierFilesGuard], breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier], @@ -115,7 +117,6 @@ const mainRoutes: IqserRoutes = [ Roles.any, Roles.templates.read, Roles.fileAttributes.readConfig, - Roles.watermarks.read, Roles.dictionaryTypes.read, Roles.colors.read, Roles.states.read, @@ -179,7 +180,6 @@ const mainRoutes: IqserRoutes = [ Roles.any, Roles.templates.read, Roles.fileAttributes.readConfig, - Roles.watermarks.read, Roles.dictionaryTypes.read, Roles.colors.read, Roles.states.read, diff --git a/apps/red-ui/src/app/app.component.ts b/apps/red-ui/src/app/app.component.ts index a66f9a467..4e78439d4 100644 --- a/apps/red-ui/src/app/app.component.ts +++ b/apps/red-ui/src/app/app.component.ts @@ -1,13 +1,10 @@ -import { Component, inject, Renderer2, ViewContainerRef } from '@angular/core'; +import { Component, Renderer2, ViewContainerRef } from '@angular/core'; import { RouterHistoryService } from '@services/router-history.service'; -import { DOCUMENT } from '@angular/common'; import { UserPreferenceService } from '@users/user-preference.service'; -import { getConfig, IqserPermissionsService } from '@iqser/common-ui'; +import { getConfig } from '@iqser/common-ui'; import { AppConfig } from '@red/domain'; import { NavigationEnd, Router } from '@angular/router'; -import { filter, map, switchMap, take, tap } from 'rxjs/operators'; -import { Roles } from '@users/roles'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { filter, map, switchMap, take } from 'rxjs/operators'; function loadCustomTheme() { const cssFileName = getConfig().THEME; @@ -37,9 +34,8 @@ export class AppComponent { userPreferenceService: UserPreferenceService, renderer: Renderer2, private readonly _router: Router, - iqserPermissionsService: IqserPermissionsService, ) { - renderer.addClass(inject(DOCUMENT).body, userPreferenceService.getTheme()); + renderer.addClass(document.body, userPreferenceService.getTheme()); loadCustomTheme(); const removeQueryParams = _router.events.pipe( @@ -51,13 +47,9 @@ export class AppComponent { ); removeQueryParams.subscribe(); - const changeFavicon = iqserPermissionsService.has$(Roles.getRss).pipe( - tap(hasRss => { - const faviconUrl = hasRss ? 'assets/icons/documine-logo.ico' : 'favicon.ico'; - document.getElementById('favicon').setAttribute('href', faviconUrl); - }), - ); - changeFavicon.pipe(takeUntilDestroyed()).subscribe(); + if (getConfig().IS_DOCUMINE) { + document.getElementById('favicon').setAttribute('href', 'assets/icons/documine-logo.ico'); + } } #removeKeycloakQueryParams() { diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index 944d621cd..f1b55a336 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -131,11 +131,17 @@ export const appModuleFactory = (config: AppConfig) => { features: { ANNOTATIONS: { color: 'aqua', - enabled: true, + enabled: false, level: NgxLoggerLevel.DEBUG, }, FILTERS: { - enabled: true, + enabled: false, + }, + TENANTS: { + enabled: false, + }, + ROUTES: { + enabled: false, }, PDF: { enabled: true, diff --git a/apps/red-ui/src/app/components/base-screen/base-screen.component.html b/apps/red-ui/src/app/components/base-screen/base-screen.component.html index c50d2e68e..b0a9e9658 100644 --- a/apps/red-ui/src/app/components/base-screen/base-screen.component.html +++ b/apps/red-ui/src/app/components/base-screen/base-screen.component.html @@ -11,7 +11,10 @@ diff --git a/apps/red-ui/src/app/components/base-screen/base-screen.component.ts b/apps/red-ui/src/app/components/base-screen/base-screen.component.ts index f37002b13..d1e8a888f 100644 --- a/apps/red-ui/src/app/components/base-screen/base-screen.component.ts +++ b/apps/red-ui/src/app/components/base-screen/base-screen.component.ts @@ -6,7 +6,7 @@ import { Title } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action'; import { filter, map, startWith } from 'rxjs/operators'; -import { IqserPermissionsService } from '@iqser/common-ui'; +import { getConfig, IqserPermissionsService } from '@iqser/common-ui'; import { BreadcrumbsService } from '@services/breadcrumbs.service'; import { FeaturesService } from '@services/features.service'; import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain'; @@ -33,6 +33,7 @@ export class BaseScreenComponent { readonly roles = Roles; readonly documentViewer = inject(REDDocumentViewer); readonly currentUser = this.userService.currentUser; + readonly config = getConfig(); readonly searchActions: List = [ { text: this._translateService.instant('search.this-dossier'), diff --git a/apps/red-ui/src/app/components/skeleton/skeleton-top-bar/skeleton-top-bar.component.html b/apps/red-ui/src/app/components/skeleton/skeleton-top-bar/skeleton-top-bar.component.html index e83646bf0..ffc2d86fd 100644 --- a/apps/red-ui/src/app/components/skeleton/skeleton-top-bar/skeleton-top-bar.component.html +++ b/apps/red-ui/src/app/components/skeleton/skeleton-top-bar/skeleton-top-bar.component.html @@ -4,7 +4,7 @@ diff --git a/apps/red-ui/src/app/components/skeleton/skeleton-top-bar/skeleton-top-bar.component.ts b/apps/red-ui/src/app/components/skeleton/skeleton-top-bar/skeleton-top-bar.component.ts index 1ac32cbd5..936ef0af5 100644 --- a/apps/red-ui/src/app/components/skeleton/skeleton-top-bar/skeleton-top-bar.component.ts +++ b/apps/red-ui/src/app/components/skeleton/skeleton-top-bar/skeleton-top-bar.component.ts @@ -1,5 +1,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Title } from '@angular/platform-browser'; +import { getConfig } from '@iqser/common-ui'; @Component({ selector: 'redaction-skeleton-top-bar', @@ -8,5 +9,7 @@ import { Title } from '@angular/platform-browser'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class SkeletonTopBarComponent { + readonly config = getConfig(); + constructor(readonly titleService: Title) {} } diff --git a/apps/red-ui/src/app/guards/edit-attribute.guard.ts b/apps/red-ui/src/app/guards/edit-attribute.guard.ts new file mode 100644 index 000000000..ea4027e88 --- /dev/null +++ b/apps/red-ui/src/app/guards/edit-attribute.guard.ts @@ -0,0 +1,7 @@ +import { CanDeactivateFn } from '@angular/router'; +import { inject } from '@angular/core'; +import { FileAttributesService } from '@services/entity-services/file-attributes.service'; +import { DossierOverviewScreenComponent } from '../modules/dossier-overview/screen/dossier-overview-screen.component'; + +export const editAttributeGuard: CanDeactivateFn = () => + !inject(FileAttributesService).isEditingFileAttribute(); diff --git a/apps/red-ui/src/app/models/file/annotation-permissions.utils.ts b/apps/red-ui/src/app/models/file/annotation-permissions.utils.ts index a6a5d31d5..f158e3354 100644 --- a/apps/red-ui/src/app/models/file/annotation-permissions.utils.ts +++ b/apps/red-ui/src/app/models/file/annotation-permissions.utils.ts @@ -5,50 +5,40 @@ import { Dictionary } from '@red/domain'; export const canUndo = (annotation: AnnotationWrapper, isApprover: boolean) => !isApprover && (annotation.isSuggestion || annotation.pending); -export const canAcceptSuggestion = (annotation: AnnotationWrapper, isApprover: boolean, canProcessManualRedaction: boolean) => { - const can = isApprover && (annotation.isSuggestion || annotation.isDeclinedSuggestion); - return annotation.isSuggestionAdd || annotation.isSuggestionRemoveDictionary ? can && canProcessManualRedaction : can; -}; +export const canForceHint = (annotation: AnnotationWrapper, canAddRedaction: boolean) => + canAddRedaction && annotation.isIgnoredHint && !annotation.pending; -export const canRejectSuggestion = (annotation: AnnotationWrapper, isApprover: boolean, canProcessManualRedaction: boolean) => { - const can = isApprover && annotation.isSuggestion; - return annotation.isSuggestionAdd || annotation.isSuggestionRemoveDictionary ? can && canProcessManualRedaction : can; -}; - -export const canForceHint = (annotation: AnnotationWrapper, canAddOrRequestRedaction: boolean) => - canAddOrRequestRedaction && annotation.isIgnoredHint && !annotation.pending; - -export const canForceRedaction = (annotation: AnnotationWrapper, canAddOrRequestRedaction: boolean) => - canAddOrRequestRedaction && annotation.isSkipped && !annotation.isFalsePositive && !annotation.pending; +export const canForceRedaction = (annotation: AnnotationWrapper, canAddRedaction: boolean) => + canAddRedaction && annotation.isSkipped && !annotation.isFalsePositive && !annotation.pending; export const canAcceptRecommendation = (annotation: AnnotationWrapper) => annotation.isRecommendation && !annotation.pending; export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) => annotation.canBeMarkedAsFalsePositive && annotationEntity.hasDictionary; -export const canRemoveOrSuggestToRemoveOnlyHere = (annotation: AnnotationWrapper, canAddOrRequestRedaction: boolean) => - canAddOrRequestRedaction && !annotation.pending && (annotation.isRedacted || (annotation.isHint && !annotation.isImage)); +export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean) => + canAddRedaction && !annotation.pending && (annotation.isRedacted || (annotation.isHint && !annotation.isImage)); -export const canRemoveOrSuggestToRemoveFromDictionary = (annotation: AnnotationWrapper) => +export const canRemoveFromDictionary = (annotation: AnnotationWrapper) => annotation.isModifyDictionary && (annotation.isRedacted || annotation.isSkipped || annotation.isHint) && !annotation.pending && !annotation.hasBeenResized; -export const canRemoveOrSuggestToRemoveRedaction = (annotations: AnnotationWrapper[], permissions: AnnotationPermissions) => +export const canRemoveRedaction = (annotations: AnnotationWrapper[], permissions: AnnotationPermissions) => annotations.length === 1 && - (permissions.canRemoveOrSuggestToRemoveOnlyHere || - permissions.canRemoveOrSuggestToRemoveFromDictionary || - permissions.canMarkAsFalsePositive); + (permissions.canRemoveOnlyHere || permissions.canRemoveFromDictionary || permissions.canMarkAsFalsePositive); -export const canChangeLegalBasis = (annotation: AnnotationWrapper, canAddOrRequestRedaction: boolean) => - canAddOrRequestRedaction && annotation.isRedacted && !annotation.pending; +export const canChangeLegalBasis = (annotation: AnnotationWrapper, canAddRedaction: boolean) => + canAddRedaction && annotation.isRedacted && !annotation.pending; -export const canRecategorizeImage = (annotation: AnnotationWrapper) => - ((annotation.isImage && !annotation.isSuggestion) || annotation.isSuggestionRecategorizeImage) && !annotation.pending; +export const canRecategorizeImage = (annotation: AnnotationWrapper, canRecategorize: boolean) => + canRecategorize && + ((annotation.isImage && !annotation.isSuggestion) || annotation.isSuggestionRecategorizeImage) && + !annotation.pending; -export const canResizeAnnotation = (annotation: AnnotationWrapper, canAddOrRequestRedaction: boolean) => - canAddOrRequestRedaction && +export const canResizeAnnotation = (annotation: AnnotationWrapper, canAddRedaction: boolean) => + canAddRedaction && (((annotation.isRedacted || annotation.isImage) && !annotation.isSuggestion) || annotation.isSuggestionResize || annotation.isRecommendation) && 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 06736546c..6a48a50dc 100644 --- a/apps/red-ui/src/app/models/file/annotation.permissions.ts +++ b/apps/red-ui/src/app/models/file/annotation.permissions.ts @@ -5,16 +5,14 @@ import { IqserPermissionsService } from '@iqser/common-ui'; import { Roles } from '@users/roles'; import { canAcceptRecommendation, - canAcceptSuggestion, canChangeLegalBasis, canForceHint, canForceRedaction, canMarkAsFalsePositive, canRecategorizeImage, - canRejectSuggestion, - canRemoveOrSuggestToRemoveFromDictionary, - canRemoveOrSuggestToRemoveOnlyHere, - canRemoveOrSuggestToRemoveRedaction, + canRemoveFromDictionary, + canRemoveOnlyHere, + canRemoveRedaction, canResizeAnnotation, canUndo, } from './annotation-permissions.utils'; @@ -23,11 +21,9 @@ export class AnnotationPermissions { canUndo = true; canAcceptRecommendation = true; canMarkAsFalsePositive = true; - canRemoveOrSuggestToRemoveOnlyHere = true; - canRemoveOrSuggestToRemoveFromDictionary = true; - canRemoveOrSuggestToRemoveRedaction = true; - canAcceptSuggestion = true; - canRejectSuggestion = true; + canRemoveOnlyHere = true; + canRemoveFromDictionary = true; + canRemoveRedaction = true; canForceRedaction = true; canChangeLegalBasis = true; canResizeAnnotation = true; @@ -46,27 +42,23 @@ export class AnnotationPermissions { const summedPermissions: AnnotationPermissions = new AnnotationPermissions(); const canAddRedaction = permissionsService.has(Roles.redactions.write); - const canRequestRedaction = permissionsService.has(Roles.redactions.request); - const canProcessManualRedaction = permissionsService.has(Roles.redactions.processManualRequest); - const canAddOrRequestRedaction = canAddRedaction || canRequestRedaction; + const canDoAnyApproverAction = canAddRedaction && isApprover; for (const annotation of annotations) { const permissions: AnnotationPermissions = new AnnotationPermissions(); const annotationEntity = entities.find(entity => entity.type === annotation.type); permissions.canUndo = canUndo(annotation, isApprover); - permissions.canAcceptSuggestion = canAcceptSuggestion(annotation, isApprover, canProcessManualRedaction); - permissions.canRejectSuggestion = canRejectSuggestion(annotation, isApprover, canProcessManualRedaction); - permissions.canForceHint = canForceHint(annotation, canAddOrRequestRedaction); - permissions.canForceRedaction = canForceRedaction(annotation, canAddOrRequestRedaction); + permissions.canForceHint = canForceHint(annotation, canDoAnyApproverAction); + permissions.canForceRedaction = canForceRedaction(annotation, canDoAnyApproverAction); permissions.canAcceptRecommendation = canAcceptRecommendation(annotation); permissions.canMarkAsFalsePositive = canMarkAsFalsePositive(annotation, annotationEntity); - permissions.canRemoveOrSuggestToRemoveOnlyHere = canRemoveOrSuggestToRemoveOnlyHere(annotation, canAddOrRequestRedaction); - permissions.canRemoveOrSuggestToRemoveFromDictionary = canRemoveOrSuggestToRemoveFromDictionary(annotation); - permissions.canRemoveOrSuggestToRemoveRedaction = canRemoveOrSuggestToRemoveRedaction(annotations, permissions); - permissions.canChangeLegalBasis = canChangeLegalBasis(annotation, canAddOrRequestRedaction); - permissions.canRecategorizeImage = canRecategorizeImage(annotation); - permissions.canResizeAnnotation = canResizeAnnotation(annotation, canAddOrRequestRedaction); + permissions.canRemoveOnlyHere = canRemoveOnlyHere(annotation, canAddRedaction); + permissions.canRemoveFromDictionary = canRemoveFromDictionary(annotation); + permissions.canRemoveRedaction = canRemoveRedaction(annotations, permissions); + permissions.canChangeLegalBasis = canChangeLegalBasis(annotation, canDoAnyApproverAction); + permissions.canRecategorizeImage = canRecategorizeImage(annotation, canDoAnyApproverAction); + permissions.canResizeAnnotation = canResizeAnnotation(annotation, canDoAnyApproverAction); summedPermissions._merge(permissions); } 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 2fb56a236..79cfdfd2d 100644 --- a/apps/red-ui/src/app/models/file/annotation.wrapper.ts +++ b/apps/red-ui/src/app/models/file/annotation.wrapper.ts @@ -371,6 +371,9 @@ export class AnnotationWrapper implements IListable { if (redactionLogEntry.reason?.toLowerCase() === 'false positive') { return annotationTypesTranslations[SuggestionAddFalsePositive]; } + if (annotation.superType === SuperTypes.ManualRedaction && annotation.hintDictionary) { + return _('annotation-type.manual-hint'); + } return annotationTypesTranslations[annotation.superType]; } diff --git a/apps/red-ui/src/app/modules/account/account.module.ts b/apps/red-ui/src/app/modules/account/account.module.ts index 7f35e0d7a..fac249986 100644 --- a/apps/red-ui/src/app/modules/account/account.module.ts +++ b/apps/red-ui/src/app/modules/account/account.module.ts @@ -6,7 +6,7 @@ import { AccountSideNavComponent } from './account-side-nav/account-side-nav.com import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component'; import { NotificationPreferencesService } from './services/notification-preferences.service'; import { TranslateModule } from '@ngx-translate/core'; -import { IconButtonComponent, IqserHelpModeModule } from '@iqser/common-ui'; +import { IconButtonComponent, IqserAllowDirective, IqserHelpModeModule } from '@iqser/common-ui'; import { PreferencesComponent } from './screens/preferences/preferences.component'; import { SideNavComponent } from '@iqser/common-ui/lib/shared'; @@ -20,6 +20,7 @@ import { SideNavComponent } from '@iqser/common-ui/lib/shared'; IqserHelpModeModule, IconButtonComponent, SideNavComponent, + IqserAllowDirective, ], providers: [NotificationPreferencesService], }) diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html index 281579968..b883c12df 100644 --- a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html +++ b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html @@ -13,7 +13,7 @@
- + (); - constructor( - private readonly _toaster: Toaster, - private readonly _formBuilder: UntypedFormBuilder, - private readonly _loadingService: LoadingService, - private readonly _notificationPreferencesService: NotificationPreferencesService, - private readonly _changeRef: ChangeDetectorRef, - ) { + readonly #toaster = inject(Toaster); + readonly #formBuilder = inject(UntypedFormBuilder); + readonly #loadingService = inject(LoadingService); + readonly #notificationPreferencesService = inject(NotificationPreferencesService); + readonly #cdRef = inject(ChangeDetectorRef); + readonly #iqserPermissionsService = inject(IqserPermissionsService); + readonly #isRss = this.#iqserPermissionsService.has(Roles.getRss); + + constructor() { super(); } async ngOnInit(): Promise { - await this._initializeForm(); + await this.#initializeForm(); + } + + getRssFilteredSettings(settings: string[]) { + return settings.filter(s => (this.#isRss ? !RSS_EXCLUDED_SETTINGS.includes(s) : true)); } isCategoryActive(category: string) { @@ -66,19 +75,19 @@ export class NotificationsScreenComponent extends BaseFormComponent implements O } async save() { - this._loadingService.start(); + this.#loadingService.start(); try { - await firstValueFrom(this._notificationPreferencesService.update(this.form.value)); + await firstValueFrom(this.#notificationPreferencesService.update(this.form.value)); } catch (e) { - this._toaster.error(_('notifications-screen.error.generic')); + this.#toaster.error(_('notifications-screen.error.generic')); } this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue())); - this._changeRef.markForCheck(); - this._loadingService.stop(); + this.#cdRef.markForCheck(); + this.#loadingService.stop(); } - private _getForm(): UntypedFormGroup { - return this._formBuilder.group({ + #getForm(): UntypedFormGroup { + return this.#formBuilder.group({ inAppNotificationsEnabled: [undefined], emailNotificationsEnabled: [undefined], emailNotificationType: [undefined], @@ -87,14 +96,14 @@ export class NotificationsScreenComponent extends BaseFormComponent implements O }); } - private async _initializeForm() { - this._loadingService.start(); + async #initializeForm() { + this.#loadingService.start(); - this.form = this._getForm(); - const notificationPreferences = await firstValueFrom(this._notificationPreferencesService.get()); + this.form = this.#getForm(); + const notificationPreferences = await firstValueFrom(this.#notificationPreferencesService.get()); this.form.patchValue(notificationPreferences); this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue())); - this._loadingService.stop(); + this.#loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/account/screens/preferences/preferences.component.html b/apps/red-ui/src/app/modules/account/screens/preferences/preferences.component.html index 42199b989..26f5d5bfa 100644 --- a/apps/red-ui/src/app/modules/account/screens/preferences/preferences.component.html +++ b/apps/red-ui/src/app/modules/account/screens/preferences/preferences.component.html @@ -1,4 +1,4 @@ -
+
@@ -13,6 +13,11 @@ {{ 'preferences-screen.form.show-suggestions-in-preview' | translate }}
+
+ + {{ 'preferences-screen.form.open-structured-view-by-default' | translate }} + +

{{ 'preferences-screen.warnings-subtitle' | translate }}

@@ -38,7 +43,6 @@ (action)="save()" [disabled]="!valid || !changed" [label]="'preferences-screen.actions.save' | translate" - [submit]="true" [type]="iconButtonTypes.primary" >
diff --git a/apps/red-ui/src/app/modules/account/screens/preferences/preferences.component.ts b/apps/red-ui/src/app/modules/account/screens/preferences/preferences.component.ts index 765aeeeec..c7fb30560 100644 --- a/apps/red-ui/src/app/modules/account/screens/preferences/preferences.component.ts +++ b/apps/red-ui/src/app/modules/account/screens/preferences/preferences.component.ts @@ -10,6 +10,7 @@ interface PreferencesForm { // preferences autoExpandFiltersOnActions: boolean; displaySuggestionsInPreview: boolean; + openStructuredComponentManagementDialogByDefault: boolean; // warnings preferences unapprovedSuggestionsWarning: boolean; loadAllAnnotationsWarning: boolean; @@ -35,6 +36,7 @@ export class PreferencesComponent extends BaseFormComponent { readonly currentScreen: Screen; readonly screens = Screens; initialFormValue: PreferencesForm; + readonly roles = Roles; constructor( readonly userPreferenceService: UserPreferenceService, @@ -48,6 +50,9 @@ export class PreferencesComponent extends BaseFormComponent { // preferences autoExpandFiltersOnActions: [this.userPreferenceService.getAutoExpandFiltersOnActions()], displaySuggestionsInPreview: [this.userPreferenceService.getDisplaySuggestionsInPreview()], + openStructuredComponentManagementDialogByDefault: [ + this.userPreferenceService.getOpenStructuredComponentManagementDialogByDefault(), + ], // warnings preferences unapprovedSuggestionsWarning: [this.userPreferenceService.getUnapprovedSuggestionsWarning()], loadAllAnnotationsWarning: [this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)], @@ -68,6 +73,12 @@ export class PreferencesComponent extends BaseFormComponent { if (this.form.controls.displaySuggestionsInPreview.value !== this.userPreferenceService.getDisplaySuggestionsInPreview()) { await this.userPreferenceService.toggleDisplaySuggestionsInPreview(); } + if ( + this.form.controls.openStructuredComponentManagementDialogByDefault.value !== + this.userPreferenceService.getOpenStructuredComponentManagementDialogByDefault() + ) { + await this.userPreferenceService.toggleOpenStructuredComponentManagementDialogByDefault(); + } if (this.form.controls.unapprovedSuggestionsWarning.value !== this.userPreferenceService.getUnapprovedSuggestionsWarning()) { await this.userPreferenceService.toggleUnapprovedSuggestionsWarning(); } @@ -83,12 +94,20 @@ export class PreferencesComponent extends BaseFormComponent { } await this.userPreferenceService.reload(); - this.form.patchValue({ - autoExpandFiltersOnActions: this.userPreferenceService.getAutoExpandFiltersOnActions(), - displaySuggestionsInPreview: this.userPreferenceService.getDisplaySuggestionsInPreview(), - unapprovedSuggestionsWarning: this.userPreferenceService.getUnapprovedSuggestionsWarning(), - }); + this.#patchValues(); + this.initialFormValue = this.form.getRawValue(); this._changeRef.markForCheck(); } + + #patchValues() { + this.form.patchValue({ + autoExpandFiltersOnActions: this.userPreferenceService.getAutoExpandFiltersOnActions(), + displaySuggestionsInPreview: this.userPreferenceService.getDisplaySuggestionsInPreview(), + openStructuredComponentManagementDialogByDefault: + this.userPreferenceService.getOpenStructuredComponentManagementDialogByDefault(), + unapprovedSuggestionsWarning: this.userPreferenceService.getUnapprovedSuggestionsWarning(), + loadAllAnnotationsWarning: this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning), + }); + } } diff --git a/apps/red-ui/src/app/modules/admin/admin.module.ts b/apps/red-ui/src/app/modules/admin/admin.module.ts index 67eff6d7f..fc8df4e81 100644 --- a/apps/red-ui/src/app/modules/admin/admin.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin.module.ts @@ -56,6 +56,7 @@ import { AuditInfoDialogComponent } from './dialogs/audit-info-dialog/audit-info import { DossierTemplateActionsComponent } from './shared/components/dossier-template-actions/dossier-template-actions.component'; import { IqserUsersModule } from '@iqser/common-ui/lib/users'; import { TenantPipe } from '@iqser/common-ui/lib/tenants'; +import { SelectComponent } from '@shared/components/select/select.component'; const dialogs = [ AddEditCloneDossierTemplateDialogComponent, @@ -123,6 +124,7 @@ const components = [ IqserAllowDirective, IqserDenyDirective, TenantPipe, + SelectComponent, ], }) export class AdminModule {} diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.ts index 0faf36246..fb9a9e314 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/chart/chart.component.ts @@ -9,10 +9,11 @@ import { ChartConfiguration, ChartDataset } from 'chart.js'; export class ChartComponent implements OnChanges { @Input({ required: true }) datasets: ChartDataset[]; @Input({ required: true }) labels: string[]; - @Input() ticksCallback?: (value: number) => string; + @Input() valueFormatter?: (value: number) => string; @Input() secondaryAxis = false; @Input() yAxisLabel?: string; @Input() yAxisLabelRight?: string; + @Input() reverseLegend = false; chartData: ChartConfiguration['data']; chartOptions: ChartConfiguration<'line'>['options'] = {}; @@ -34,7 +35,7 @@ export class ChartComponent implements OnChanges { scales: { y: { stacked: true, - ticks: { callback: this.ticksCallback ? (value: number) => this.ticksCallback(value) : undefined }, + ticks: { callback: this.valueFormatter ? (value: number) => this.valueFormatter(value) : undefined }, title: { display: !!this.yAxisLabel, text: this.yAxisLabel, @@ -52,10 +53,10 @@ export class ChartComponent implements OnChanges { }, }, plugins: { - legend: { position: 'right' }, + legend: { position: 'right', reverse: this.reverseLegend }, tooltip: { callbacks: { - label: this.ticksCallback ? item => `${item.dataset.label}: ${this.ticksCallback(item.parsed.y)}` : undefined, + label: this.valueFormatter ? item => `${item.dataset.label}: ${this.valueFormatter(item.parsed.y)}` : undefined, }, }, }, diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.html b/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.html index 87bd0b86b..821f0489e 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.html @@ -42,6 +42,7 @@ *ngIf="data$ | async as data" [datasets]="data.datasets" [labels]="data.labels" - [ticksCallback]="formatSize" + [reverseLegend]="true" + [valueFormatter]="formatSize" >
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.ts index 9b45d3aa0..4795e4b96 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-storage/license-storage.component.ts @@ -5,7 +5,7 @@ import { LicenseService } from '@services/license.service'; import { map, tap } from 'rxjs/operators'; import type { DonutChartConfig, ILicenseReport } from '@red/domain'; import { ChartDataset } from 'chart.js'; -import { ChartBlue, ChartGreen, ChartGrey, ChartRed } from '../../utils/constants'; +import { ChartBlack, ChartBlue, ChartGreen, ChartGrey, ChartRed } from '../../utils/constants'; import { getLabelsFromLicense, getLineConfig } from '../../utils/functions'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; @@ -71,21 +71,27 @@ export class LicenseStorageComponent { { data: monthlyData.flatMap(d => d.activeFilesUploadedBytes), label: this._translateService.instant('license-info-screen.storage.active-documents'), - ...getLineConfig(ChartGreen, 'origin'), + ...getLineConfig(ChartGreen, false, 'origin'), stack: 'storage', }, { data: monthlyData.flatMap(d => d.archivedFilesUploadedBytes), label: this._translateService.instant('license-info-screen.storage.archived-documents'), - ...getLineConfig(ChartBlue, '-1'), + ...getLineConfig(ChartBlue, false, '-1'), stack: 'storage', }, { data: monthlyData.flatMap(d => d.trashFilesUploadedBytes), label: this._translateService.instant('license-info-screen.storage.trash-documents'), - ...getLineConfig(ChartRed, '-1'), + ...getLineConfig(ChartRed, false, '-1'), stack: 'storage', }, + { + data: monthlyData.flatMap(d => d.activeFilesUploadedBytes + d.archivedFilesUploadedBytes + d.trashFilesUploadedBytes), + label: this._translateService.instant('license-info-screen.storage.all-documents'), + ...getLineConfig(ChartBlack, true, false), + borderWidth: 2, + }, ]; } } diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.html b/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.html index dec058ea6..9aeee0700 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.html @@ -35,7 +35,7 @@ [datasets]="data.datasets" [labels]="data.labels" [secondaryAxis]="true" - [yAxisLabelRight]="'license-info-screen.chart.total-pages' | translate" - [yAxisLabel]="'license-info-screen.chart.pages-per-month' | translate" + [yAxisLabelRight]="'license-info-screen.pages.total-pages' | translate" + [yAxisLabel]="'license-info-screen.pages.pages-per-month' | translate" >
diff --git a/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.ts b/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.ts index 8e10ee3c8..0da63b972 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/components/license-usage/license-usage.component.ts @@ -5,6 +5,7 @@ import type { ILicenseReport } from '@red/domain'; import { ChartDataset } from 'chart.js'; import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants'; import { getLabelsFromLicense, getLineConfig } from '../../utils/functions'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'red-license-usage', @@ -21,7 +22,7 @@ export class LicenseUsageComponent { })), ); - constructor(readonly licenseService: LicenseService) {} + constructor(readonly licenseService: LicenseService, private readonly _translateService: TranslateService) {} getAnalysisPercentageOfLicense() { const totalLicensedNumberOfPages = this.licenseService.totalLicensedNumberOfPages; @@ -35,16 +36,16 @@ export class LicenseUsageComponent { return [ { data: monthlyData.flatMap(d => d.numberOfAnalyzedPages), - label: 'Pages per Month', + label: this._translateService.instant('license-info-screen.pages.pages-per-month'), type: 'bar', backgroundColor: ChartBlue, borderColor: ChartBlue, order: 2, }, { - data: monthlyData.flatMap(() => 200000), - label: 'Total Pages', - ...getLineConfig(ChartRed, false), + data: monthlyData.flatMap(() => this.licenseService.totalLicensedNumberOfPages), + label: this._translateService.instant('license-info-screen.pages.total-pages'), + ...getLineConfig(ChartRed, true, false), yAxisID: 'y1', order: 1, }, @@ -54,10 +55,10 @@ export class LicenseUsageComponent { month.numberOfAnalyzedPages + monthlyData.slice(0, monthIndex).reduce((acc, curr) => acc + curr.numberOfAnalyzedPages, 0), ), - label: 'Cumulative Pages', + label: this._translateService.instant('license-info-screen.pages.cumulative'), yAxisID: 'y1', order: 1, - ...getLineConfig(ChartGreen, false), + ...getLineConfig(ChartGreen, true, false), }, ]; } diff --git a/apps/red-ui/src/app/modules/admin/screens/license/utils/constants.ts b/apps/red-ui/src/app/modules/admin/screens/license/utils/constants.ts index 24c1bde59..2b7c99a2a 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/utils/constants.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/utils/constants.ts @@ -2,3 +2,4 @@ export const ChartRed = '#dd4d50'; export const ChartGreen = '#5ce594'; export const ChartBlue = '#0389ec'; export const ChartGrey = '#ccced3'; // grey-5 +export const ChartBlack = '#283241'; // grey-1 diff --git a/apps/red-ui/src/app/modules/admin/screens/license/utils/functions.ts b/apps/red-ui/src/app/modules/admin/screens/license/utils/functions.ts index efa5d0aad..1711451bc 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license/utils/functions.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license/utils/functions.ts @@ -10,6 +10,7 @@ export const verboseDate = (date: Dayjs) => `${monthNames[date.month()]} ${date. export const getLineConfig: ( color: string, + displayLine: boolean, target: FillTarget, ) => { type: 'line'; @@ -17,9 +18,9 @@ export const getLineConfig: ( backgroundColor: string; pointBackgroundColor: string; fill: ComplexFillTarget; -} = (color, target) => ({ +} = (color, displayLine, target) => ({ type: 'line', - borderColor: hexToRgba(color, 1), + borderColor: hexToRgba(color, displayLine ? 1 : 0), backgroundColor: hexToRgba(color, 1), pointBackgroundColor: hexToRgba(color, 1), fill: { @@ -27,6 +28,7 @@ export const getLineConfig: ( above: hexToRgba(color, 0.3), below: hexToRgba(color, 0.3), }, + pointStyle: false, }); export const getLabelsFromMonthlyData = (monthlyData: ILicenseData[]) => monthlyData.map(data => verboseDate(dayjs(data.startDate))); diff --git a/apps/red-ui/src/app/modules/admin/shared/components/admin-side-nav/admin-side-nav.component.ts b/apps/red-ui/src/app/modules/admin/shared/components/admin-side-nav/admin-side-nav.component.ts index b78a3cf3d..bae2b5a66 100644 --- a/apps/red-ui/src/app/modules/admin/shared/components/admin-side-nav/admin-side-nav.component.ts +++ b/apps/red-ui/src/app/modules/admin/shared/components/admin-side-nav/admin-side-nav.component.ts @@ -107,7 +107,7 @@ export class AdminSideNavComponent implements OnInit { screen: 'watermarks', label: _('admin-side-nav.watermarks'), helpModeKey: 'watermarks', - show: true, + show: this._permissionsService.has(Roles.watermarks.read), }, { screen: 'file-attributes', diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/file-attribute/file-attribute.component.html b/apps/red-ui/src/app/modules/dossier-overview/components/file-attribute/file-attribute.component.html index b4c80aaa1..e27032dd8 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/file-attribute/file-attribute.component.html +++ b/apps/red-ui/src/app/modules/dossier-overview/components/file-attribute/file-attribute.component.html @@ -40,7 +40,7 @@
- + this.fileAttributesService.isEditingFileAttribute() && + !!this.file && + !!this.fileAttribute && (this.fileAttribute.id !== this.fileAttributesService.openAttributeEdit() || this.file.fileId !== this.fileAttributesService.fileEdit()), ); @@ -71,6 +74,7 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr return this.file.fileAttributes.attributeIdToValue[this.fileAttribute.id]; } + @Debounce(50) @HostListener('document:click') clickOutside() { if (this.isInEditMode && this.closedDatepicker) { @@ -85,6 +89,7 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr async editFileAttribute($event: MouseEvent): Promise { if (!this.file.isInitialProcessing && this.permissionsService.canEditFileAttributes(this.file, this.dossier)) { $event.stopPropagation(); + this.fileAttributesService.openAttributeEdits.mutate(value => value.push(this.fileAttribute.id)); this.#toggleEdit(); this.fileAttributesService.setFileEdit(this.file.fileId); this.fileAttributesService.setOpenAttributeEdit(this.fileAttribute.id); @@ -116,6 +121,13 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr if (this.isInEditMode) { this.form = this.#getForm(); this.#toggleEdit(); + this.fileAttributesService.openAttributeEdits.mutate(value => { + for (let index = 0; index < value.length; index++) { + if (value[index] === this.fileAttribute.id) { + value.splice(index, 1); + } + } + }); } } @@ -133,16 +145,16 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr const fileAttributes = this.file.fileAttributes.attributeIdToValue; Object.keys(fileAttributes).forEach(key => { const attrValue = fileAttributes[key]; - config[key] = [ - dayjs(attrValue, 'YYYY-MM-DD', true).isValid() ? dayjs(attrValue).toDate() : attrValue, - Validators.pattern(/^(\s+\S+\s*)*(?!\s).*$/), - ]; + config[key] = [dayjs(attrValue, 'YYYY-MM-DD', true).isValid() ? dayjs(attrValue).toDate() : attrValue]; + }); + return this._formBuilder.group(config, { + validators: control => + !control.get(this.fileAttribute.id).value?.trim().length && !this.fileAttributeValue ? { emptyString: true } : null, }); - return this._formBuilder.group(config); } #formatAttributeValue(attrValue) { - return this.isDate ? attrValue && dayjs(attrValue).format('YYYY-MM-DD') : attrValue.replaceAll(/\s\s+/g, ' '); + return this.isDate ? attrValue && dayjs(attrValue).format('YYYY-MM-DD') : attrValue.trim().replaceAll(/\s\s+/g, ' '); } #toggleEdit(): void { @@ -153,7 +165,6 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr } this.isInEditMode = !this.isInEditMode; - this.fileAttributesService.isEditingFileAttribute.set(this.isInEditMode); if (this.isInEditMode) { this.#focusOnEditInput(); diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/screen-header/dossier-overview-screen-header.component.html b/apps/red-ui/src/app/modules/dossier-overview/components/screen-header/dossier-overview-screen-header.component.html index 8ff1d20b9..9db020284 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/screen-header/dossier-overview-screen-header.component.html +++ b/apps/red-ui/src/app/modules/dossier-overview/components/screen-header/dossier-overview-screen-header.component.html @@ -47,5 +47,5 @@ - + diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html index fe9b4f66d..94ccbfaba 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-actions/annotation-actions.component.html @@ -51,15 +51,6 @@ icon="iqser:check" > - - implements OnChanges, OnInit { +export class PageIndicatorComponent implements OnChanges { readonly #config = getConfig(); + readonly #logger = inject(NGXLogger); @Input({ required: true }) number: number; @Input() showDottedIcon = false; @Input() activeSelection = false; @Input() read = false; @Output() readonly pageSelected = new EventEmitter(); pageReadTimeout: number = null; + readonly assigneeChanged$: Observable; constructor( private readonly _viewedPagesService: ViewedPagesService, @@ -38,38 +35,45 @@ export class PageIndicatorComponent extends ContextComponent prevFile.assignee !== currFile.assignee), + tap(assigneeChanged => assigneeChanged && this.handlePageRead()), + startWith(true), + map(() => true), + ); + + effect(() => { + if (this.isActive) { + this.handlePageRead(); + } + }); } get isActive() { return this.number === this._pdf.currentPage(); } - ngOnInit() { - const assigneeChanged$ = this._state.file$.pipe( - pairwise(), - map(([prevFile, currFile]) => prevFile.assignee !== currFile.assignee), - tap(assigneeChanged => assigneeChanged && this.handlePageRead()), - ); - super._initContext({ assigneeChanged: assigneeChanged$ }); - } - ngOnChanges() { this.handlePageRead(); } async toggleReadState() { - if (this._permissionService.canMarkPagesAsViewed(this._state.file())) { - if (this.read) { - await this.#markPageUnread(); - } else { - await this.#markPageRead(); - } + if (!this._permissionService.canMarkPagesAsViewed(this._state.file())) { + this.#logger.info('[PAGES] Cannot toggle read state'); + return; + } + + if (this.read) { + await this.#markPageUnread(); + } else { + await this.#markPageRead(); } } handlePageRead(): void { if (!this._permissionService.canMarkPagesAsViewed(this._state.file())) { + this.#logger.info('[PAGES] Cannot mark pages as read'); return; } @@ -87,6 +91,8 @@ export class PageIndicatorComponent extends ContextComponent -
+
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts index 4755d984e..fdb1e990a 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/redact-text-dialog/redact-text-dialog.component.ts @@ -9,11 +9,7 @@ import { ActiveDossiersService } from '@services/dossiers/active-dossiers.servic import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component'; import { DictionaryService } from '@services/entity-services/dictionary.service'; -import { - ManualRedactionEntryType, - ManualRedactionEntryTypes, - ManualRedactionEntryWrapper, -} from '@models/file/manual-redaction-entry.wrapper'; +import { ManualRedactionEntryType, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; import { redactTextTranslations } from '@translations/redact-text-translations'; import { RedactTextOption, RedactTextOptions } from './redact-text-options'; import { tap } from 'rxjs/operators'; @@ -29,6 +25,7 @@ interface RedactTextData { file: File; applyToAllDossiers: boolean; isApprover: boolean; + hint: boolean; } interface DialogResult { @@ -59,6 +56,7 @@ export class RedactTextDialogComponent readonly #translations = redactTextTranslations; readonly #dossier: Dossier; + readonly #isRss = this._iqserPermissionsService.has(Roles.getRss); readonly hint: boolean; constructor( @@ -71,7 +69,7 @@ export class RedactTextDialogComponent super(); this.#dossier = _activeDossiersService.find(this.data.dossierId); this.type = this.data.manualRedactionEntryWrapper.type; - this.hint = this.type === ManualRedactionEntryTypes.HINT; + this.hint = this.data.hint; this.#applyToAllDossiers = this.data.applyToAllDossiers ?? true; this.#manualRedactionTypeExists = this._dictionaryService.hasManualType(this.#dossier.dossierTemplateId); this.options = this.#options(); @@ -83,6 +81,7 @@ export class RedactTextDialogComponent .valueChanges.pipe( tap((option: DetailsRadioOption) => { this.dictionaryRequest = option.value === RedactTextOptions.IN_DOSSIER; + this.#setDictionaries(); this.#resetValues(); }), takeUntilDestroyed(), @@ -99,19 +98,14 @@ export class RedactTextDialogComponent } get disabled() { - if (this.dictionaryRequest || this.hint) { + if (this.dictionaryRequest || this.hint || this.#isRss) { return !this.form.get('dictionary').value; } return !this.form.get('reason').value; } async ngOnInit(): Promise { - this.dictionaries = this._dictionaryService.getPossibleDictionaries( - this.#dossier.dossierTemplateId, - this.hint, - !this.#applyToAllDossiers, - ); - + this.#setDictionaries(); const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.#dossier.dossierTemplateId)); this.legalOptions = data.map(lbm => ({ legalBasis: lbm.reason, @@ -122,16 +116,13 @@ export class RedactTextDialogComponent this.#selectReason(); this.#formatSelectedTextValue(); + this.#resetValues(); } extraOptionChanged(option: DetailsRadioOption): void { this.#applyToAllDossiers = option.extraOption.checked; - this.dictionaries = this._dictionaryService.getPossibleDictionaries( - this.#dossier.dossierTemplateId, - this.hint, - !this.#applyToAllDossiers, - ); + this.#setDictionaries(); if (this.#applyToAllDossiers && this.form.get('dictionary').value) { const selectedDictionaryLabel = this.form.get('dictionary').value; const selectedDictionary = this.dictionaries.find(d => d.type === selectedDictionaryLabel); @@ -155,10 +146,18 @@ export class RedactTextDialogComponent this.dialogRef.close({ redaction, dictionary: this.dictionaries.find(d => d.type === this.form.get('dictionary').value), - applyToAllDossiers: this.dictionaryRequest ? this.#applyToAllDossiers : null, }); } + #setDictionaries() { + this.dictionaries = this._dictionaryService.getPossibleDictionaries( + this.#dossier.dossierTemplateId, + this.hint, + !this.#applyToAllDossiers, + this.dictionaryRequest, + ); + } + #getForm(): UntypedFormGroup { return this._formBuilder.group({ selectedText: this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value, @@ -194,12 +193,12 @@ export class RedactTextDialogComponent addRedactionRequest.legalBasis = legalOption.legalBasis; } - if (this._iqserPermissionsService.has(Roles.getRss)) { - const selectedType = this.dictionaries.find(d => d.type === addRedactionRequest.type); + const selectedType = this.dictionaries.find(d => d.type === addRedactionRequest.type); + + if (selectedType) { addRedactionRequest.addToDictionary = selectedType.hasDictionary; } else { addRedactionRequest.addToDictionary = this.dictionaryRequest && addRedactionRequest.type !== 'dossier_redaction'; - addRedactionRequest.addToDossierDictionary = this.dictionaryRequest && addRedactionRequest.type === 'dossier_redaction'; } if (!addRedactionRequest.reason) { @@ -223,7 +222,7 @@ export class RedactTextDialogComponent value: RedactTextOptions.ONLY_HERE, }, ]; - if (!this._iqserPermissionsService.has(Roles.getRss)) { + if (!this.#isRss) { options.push({ label: this.#translations[this.type].inDossier.label, description: this.#translations[this.type].inDossier.description, @@ -242,8 +241,10 @@ export class RedactTextDialogComponent #resetValues() { this.#applyToAllDossiers = this.data.applyToAllDossiers ?? true; - this.options[1].extraOption.checked = this.#applyToAllDossiers; - if (this.dictionaryRequest) { + if (!this.#isRss) { + this.options[1].extraOption.checked = this.#applyToAllDossiers; + } + if (this.dictionaryRequest || this.hint) { this.form.get('reason').setValue(null); this.form.get('dictionary').setValue(null); return; diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts index e783a0b4e..4c133e169 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.component.ts @@ -16,8 +16,8 @@ const FOLDER_ICON = 'red:folder'; const REMOVE_FROM_DICT_ICON = 'red:remove-from-dict'; export interface RemoveRedactionPermissions { - canRemoveOrSuggestToRemoveOnlyHere: boolean; - canRemoveOrSuggestToRemoveFromDictionary: boolean; + canRemoveOnlyHere: boolean; + canRemoveFromDictionary: boolean; canMarkAsFalsePositive: boolean; } @@ -49,6 +49,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< readonly options: DetailsRadioOption[]; form!: UntypedFormGroup; + hint: boolean; readonly #redaction: AnnotationWrapper; readonly #permissions: RemoveRedactionPermissions; @@ -58,6 +59,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< constructor(private readonly _formBuilder: FormBuilder, private readonly _permissionsService: PermissionsService) { super(); this.#redaction = this.data.redaction; + this.hint = this.#redaction.hint; this.#permissions = this.data.permissions; this.options = this.#options(); this.form = this.#getForm(); @@ -91,20 +93,20 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent< #options() { const options: DetailsRadioOption[] = []; - if (this.#permissions.canRemoveOrSuggestToRemoveOnlyHere) { + if (this.#permissions.canRemoveOnlyHere) { options.push({ label: this.#translations.ONLY_HERE.label, description: this.#translations.ONLY_HERE.description, - descriptionParams: { value: this.#redaction.value }, + descriptionParams: { type: this.hint ? 'hint' : 'redact', value: this.#redaction.value }, icon: PIN_ICON, value: RemoveRedactionOptions.ONLY_HERE, }); } - if (this.#permissions.canRemoveOrSuggestToRemoveFromDictionary) { + if (this.#permissions.canRemoveFromDictionary) { options.push({ label: this.#translations.IN_DOSSIER.label, description: this.#translations.IN_DOSSIER.description, - descriptionParams: { value: this.#redaction.value }, + descriptionParams: { type: this.hint ? 'hint' : 'redact', value: this.#redaction.value }, icon: FOLDER_ICON, value: RemoveRedactionOptions.IN_DOSSIER, extraOption: { diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html index 5cdedd715..556a20194 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/remove-redaction-dialog/remove-redaction-dialog.html @@ -1,6 +1,9 @@
-
+
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.html b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.html index f4a705dd6..58c569cbc 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.html +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.html @@ -4,10 +4,7 @@
-
Component
-
Value
-
Transformation
-
Annotations
+
{{ getHeaderCellTranslation(cell) | translate }}
{{ entry.key }}
@@ -80,6 +77,13 @@ >
+ Display by default when opening documents
diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.scss b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.scss index cf04f8aa6..7c1268089 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.scss +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.scss @@ -63,3 +63,7 @@ ul { .output-data > div:nth-child(8n + 12) { background: var(--iqser-grey-8); } + +.ml-auto { + margin-left: auto; +} diff --git a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.ts b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.ts index 7b9204110..6277a31cc 100644 --- a/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/dialogs/rss-dialog/rss-dialog.component.ts @@ -17,8 +17,11 @@ interface RssData { }) export class RssDialogComponent extends BaseDialogComponent implements OnInit { readonly circleButtonTypes = CircleButtonTypes; - readonly rssData = signal(undefined); + readonly openStructuredComponentManagementDialogByDefault = signal( + this.userPreferences.getOpenStructuredComponentManagementDialogByDefault(), + ); + readonly tableHeaderCells = ['component', 'value', 'transformation-rule', 'annotation-references'] as const; constructor( protected readonly _dialogRef: MatDialogRef, @@ -34,6 +37,10 @@ export class RssDialogComponent extends BaseDialogComponent implements OnInit { await this.#loadData(); } + getHeaderCellTranslation(cell: string) { + return `rss-dialog.table-header.${cell}`; + } + originalOrder = (): number => 0; exportJSON() { @@ -56,6 +63,14 @@ export class RssDialogComponent extends BaseDialogComponent implements OnInit { return this.exportJSON(); } + async toggleOpenStructuredComponentManagementDialogByDefault() { + await this.userPreferences.toggleOpenStructuredComponentManagementDialogByDefault(); + await this.userPreferences.reload(); + this.openStructuredComponentManagementDialogByDefault.set( + this.userPreferences.getOpenStructuredComponentManagementDialogByDefault(), + ); + } + async undo(originalKey: string) { this._loadingService.start(); await firstValueFrom(this._rssService.revertOverride(this.data.file.dossierId, this.data.file.fileId, [originalKey])); 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 7f122e60a..f7450a28c 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 @@ -25,7 +25,7 @@ import { Toaster, } from '@iqser/common-ui'; import { MatDialog } from '@angular/material/dialog'; -import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; +import { ManualRedactionEntryTypes, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.service'; import { AnnotationProcessingService } from './services/annotation-processing.service'; @@ -317,6 +317,8 @@ export class FilePreviewScreenComponent this.pdfProxyService.configureElements(); this.#restoreOldFilters(); document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener); + + this.#openRssDialogIfDefault(); } ngAfterViewInit() { @@ -342,7 +344,7 @@ export class FilePreviewScreenComponent result.annotations.map(w => w.manualRedactionEntry).filter(e => e.positions[0].page <= file.numberOfPages), this.dossierId, this.fileId, - result.dictionary?.label, + { dictionaryLabel: result.dictionary?.label }, ); const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file))); @@ -355,13 +357,16 @@ export class FilePreviewScreenComponent const file = this.state.file(); const dossierTemplate = this._dossierTemplatesService.find(this.state.dossierTemplateId); const isApprover = this.permissionsService.isApprover(this.state.dossier()); + const applyDictionaryUpdatesToAllDossiersByDefault = dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault; + const hint = manualRedactionEntryWrapper.type === ManualRedactionEntryTypes.HINT; const ref = this._iqserDialog.openDefault(RedactTextDialogComponent, { data: { manualRedactionEntryWrapper, dossierId: this.dossierId, file, - applyToAllDossiers: isApprover ? dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault : false, + applyToAllDossiers: isApprover ? applyDictionaryUpdatesToAllDossiersByDefault : false, isApprover, + hint, }, }); @@ -370,13 +375,10 @@ export class FilePreviewScreenComponent return; } - const add$ = this._manualRedactionService.addAnnotation([result.redaction], this.dossierId, this.fileId, result.dictionary?.label); - - if (isApprover && result.applyToAllDossiers !== null) { - const { ...body } = dossierTemplate; - body.applyDictionaryUpdatesToAllDossiersByDefault = result.applyToAllDossiers; - await this._dossierTemplatesService.createOrUpdate(body); - } + const add$ = this._manualRedactionService.addAnnotation([result.redaction], this.dossierId, this.fileId, { + hint, + dictionaryLabel: result.dictionary?.label, + }); const addAndReload$ = add$.pipe( tap(() => this._documentViewer.clearSelection()), @@ -831,4 +833,13 @@ export class FilePreviewScreenComponent } }); } + + #openRssDialogIfDefault() { + if ( + this.permissionsService.canViewRssDialog() && + this.userPreferenceService.getOpenStructuredComponentManagementDialogByDefault() + ) { + this.openRSSView(); + } + } } 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 19397e5d2..ee381b2cf 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 @@ -123,8 +123,8 @@ export class AnnotationActionsService { async removeOrSuggestRemoveRedaction(redaction: AnnotationWrapper, permissions) { const removePermissions: RemoveRedactionPermissions = { - canRemoveOrSuggestToRemoveOnlyHere: permissions.canRemoveOrSuggestToRemoveOnlyHere, - canRemoveOrSuggestToRemoveFromDictionary: permissions.canRemoveOrSuggestToRemoveFromDictionary, + canRemoveOnlyHere: permissions.canRemoveOnlyHere, + canRemoveFromDictionary: permissions.canRemoveFromDictionary, canMarkAsFalsePositive: permissions.canMarkAsFalsePositive, }; const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId); @@ -149,12 +149,6 @@ export class AnnotationActionsService { } else { this.#removeRedaction(redaction, result); } - - if (isApprover && result.option.extraOption) { - const { ...body } = dossierTemplate; - body.applyDictionaryUpdatesToAllDossiersByDefault = result.option.extraOption.checked; - await this._dossierTemplatesService.createOrUpdate(body); - } } } diff --git a/apps/red-ui/src/app/modules/file-preview/services/manual-redaction.service.ts b/apps/red-ui/src/app/modules/file-preview/services/manual-redaction.service.ts index 1f4d040ce..09da1da9e 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/manual-redaction.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/manual-redaction.service.ts @@ -94,8 +94,15 @@ export class ManualRedactionService extends GenericService { return this.requestRecategorize(body, dossierId, fileId).pipe(this.#showToast('request-image-recategorization')); } - addAnnotation(requests: List, dossierId: string, fileId: string, dictionaryLabel?: string) { - const toast = requests[0].addToDictionary ? this.#showAddToDictionaryToast(requests, dictionaryLabel) : this.#showToast('add'); + addAnnotation( + requests: List, + dossierId: string, + fileId: string, + options?: { hint?: boolean; dictionaryLabel?: string }, + ) { + const toast = requests[0].addToDictionary + ? this.#showAddToDictionaryToast(requests, options?.dictionaryLabel) + : this.#showToast(options?.hint ? 'force-hint' : 'add'); const canAddRedaction = this._iqaerPermissionsService.has(Roles.redactions.write); if (canAddRedaction) { return this.add(requests, dossierId, fileId).pipe(toast); diff --git a/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts b/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts index 410899cb4..fe2ee792d 100644 --- a/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/file-preview/services/pdf-annotation-actions.service.ts @@ -63,15 +63,6 @@ export class PdfAnnotationActionsService { availableActions.push(recategorizeButton); } - // if (permissions.canRemoveOrSuggestToRemoveFromDictionary) { - // const removeFromDictButton = this.#getButton( - // 'remove-from-dict', - // _('annotation-actions.remove-annotation.remove-from-dict'), - // () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, true), - // ); - // availableActions.push(removeFromDictButton); - // } - if (permissions.canAcceptRecommendation) { const acceptRecommendationButton = this.#getButton('check', _('annotation-actions.accept-recommendation.label'), () => this.#annotationActionsService.convertRecommendationToAnnotation(annotations), @@ -79,27 +70,6 @@ export class PdfAnnotationActionsService { availableActions.push(acceptRecommendationButton); } - if (permissions.canAcceptSuggestion) { - const acceptSuggestionButton = this.#getButton('check', _('annotation-actions.accept-suggestion.label'), () => - this.#annotationActionsService.acceptSuggestion(annotations), - ); - availableActions.push(acceptSuggestionButton); - } - - if (permissions.canUndo) { - const undoButton = this.#getButton('undo', _('annotation-actions.undo'), () => - this.#annotationActionsService.undoDirectAction(annotations), - ); - availableActions.push(undoButton); - } - - // if (permissions.canMarkAsFalsePositive) { - // const markAsFalsePositiveButton = this.#getButton('thumb-down', _('annotation-actions.remove-annotation.false-positive'), () => - // this.#annotationActionsService.markAsFalsePositive(annotations), - // ); - // availableActions.push(markAsFalsePositiveButton); - // } - if (permissions.canForceRedaction) { const forceRedactionButton = this.#getButton('thumb-up', _('annotation-actions.force-redaction.label'), () => this.#annotationActionsService.forceAnnotation(annotations), @@ -114,27 +84,11 @@ export class PdfAnnotationActionsService { availableActions.push(forceHintButton); } - if (permissions.canRejectSuggestion) { - const rejectSuggestionButton = this.#getButton('close', _('annotation-actions.reject-suggestion'), () => - this.#annotationActionsService.rejectSuggestion(annotations), - ); - availableActions.push(rejectSuggestionButton); - } - - // if (permissions.canRemoveOrSuggestToRemoveOnlyHere) { - // const removeOrSuggestToRemoveOnlyHereButton = this.#getButton( - // 'trash', - // _('annotation-actions.remove-annotation.only-here'), - // () => this.#annotationActionsService.removeOrSuggestRemoveAnnotation(annotations, false), - // ); - // availableActions.push(removeOrSuggestToRemoveOnlyHereButton); - // } - - if (permissions.canRemoveOrSuggestToRemoveRedaction) { - const removeOrSuggestToRemoveButton = this.#getButton('trash', _('annotation-actions.remove-annotation.remove-redaction'), () => + if (permissions.canRemoveRedaction) { + const removeRedactionButton = this.#getButton('trash', _('annotation-actions.remove-annotation.remove-redaction'), () => this.#annotationActionsService.removeOrSuggestRemoveRedaction(annotations[0], permissions), ); - availableActions.push(removeOrSuggestToRemoveButton); + availableActions.push(removeRedactionButton); } return availableActions; @@ -158,21 +112,13 @@ export class PdfAnnotationActionsService { canResize: permissions.length === 1 && permissions[0].canResizeAnnotation, canChangeLegalBasis: permissions.reduce((acc, next) => acc && next.canChangeLegalBasis, true), canRecategorizeImage: permissions.reduce((acc, next) => acc && next.canRecategorizeImage, true), - canRemoveOrSuggestToRemoveFromDictionary: permissions.reduce( - (acc, next) => acc && next.canRemoveOrSuggestToRemoveFromDictionary, - true, - ), + canRemoveFromDictionary: permissions.reduce((acc, next) => acc && next.canRemoveFromDictionary, true), canAcceptRecommendation: permissions.reduce((acc, next) => acc && next.canAcceptRecommendation, true), - canAcceptSuggestion: permissions.reduce((acc, next) => acc && next.canAcceptSuggestion, true), - canUndo: - this.#iqserPermissionsService.has(Roles.redactions.deleteManual) && - permissions.reduce((acc, next) => acc && next.canUndo, true), canMarkAsFalsePositive: permissions.reduce((acc, next) => acc && next.canMarkAsFalsePositive, true), canForceRedaction: permissions.reduce((acc, next) => acc && next.canForceRedaction, true), canForceHint: permissions.reduce((acc, next) => acc && next.canForceHint, true), - canRejectSuggestion: permissions.reduce((acc, next) => acc && next.canRejectSuggestion, true), - canRemoveOrSuggestToRemoveOnlyHere: permissions.reduce((acc, next) => acc && next.canRemoveOrSuggestToRemoveOnlyHere, true), - canRemoveOrSuggestToRemoveRedaction: permissions.reduce((acc, next) => acc && next.canRemoveOrSuggestToRemoveRedaction, true), + canRemoveOnlyHere: permissions.reduce((acc, next) => acc && next.canRemoveOnlyHere, true), + canRemoveRedaction: permissions.reduce((acc, next) => acc && next.canRemoveRedaction, true), }; } } diff --git a/apps/red-ui/src/app/modules/icons/icons.module.ts b/apps/red-ui/src/app/modules/icons/icons.module.ts index 54c90a5a9..d0b98b06e 100644 --- a/apps/red-ui/src/app/modules/icons/icons.module.ts +++ b/apps/red-ui/src/app/modules/icons/icons.module.ts @@ -36,6 +36,7 @@ export class IconsModule { 'dictionary', 'denied', 'disable-analysis', + 'documine-logo', 'double-chevron-right', 'enable-analysis', 'enter', diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-manager.service.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-manager.service.ts index e0f0def1e..004d735a9 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-manager.service.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-manager.service.ts @@ -136,7 +136,7 @@ export class REDAnnotationManager { #autoSelectRectangleAfterCreation() { this.#manager.addEventListener('annotationChanged', (annotations: Annotation[], action: string, options) => { - this.#logger.info('[PDF] Annotations changed: ', annotations, action, options); + this.#logger.info('[ANNOTATIONS] Annotations changed: ', annotations, action, options); // when a rectangle is drawn, // it returns one annotation with tool name 'AnnotationCreateRectangle; // this will auto select rectangle after drawing diff --git a/apps/red-ui/src/app/modules/pdf-viewer/services/webviewer-loaded.guard.ts b/apps/red-ui/src/app/modules/pdf-viewer/services/webviewer-loaded.guard.ts index 6278b91af..44d915163 100644 --- a/apps/red-ui/src/app/modules/pdf-viewer/services/webviewer-loaded.guard.ts +++ b/apps/red-ui/src/app/modules/pdf-viewer/services/webviewer-loaded.guard.ts @@ -29,8 +29,9 @@ export function webViewerLoadedGuard(): CanActivateFn | ResolveFn { try { instance = await pdf.init(inject(DOCUMENT).getElementById('viewer')); } catch (e) { - logger.warn('[PDF] WebViewerGuard error: ', e, 'redirecting to', state.url); - return router.navigateByUrl(state.url); + const redirectUrl = state.url.split('/').slice(0, -2).join('/'); + logger.warn('[PDF] WebViewerGuard error: ', e, 'redirecting to', redirectUrl); + return router.navigateByUrl(redirectUrl); // redirect to dissier page } annotationManager.init(instance.Core.annotationManager); diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/download-package/edit-dossier-download-package.component.html b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/download-package/edit-dossier-download-package.component.html index ea60f8cc7..6026d1705 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/download-package/edit-dossier-download-package.component.html +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/download-package/edit-dossier-download-package.component.html @@ -18,26 +18,26 @@ formControlName="downloadFileTypes" >
+ +

{{ 'dossier-watermark-selector.heading' | translate }}

+ + + -

{{ 'dossier-watermark-selector.heading' | translate }}

- - - - - - + + +
-

diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-team/edit-dossier-team.component.ts b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-team/edit-dossier-team.component.ts index ad1fa12a5..234e314ad 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-team/edit-dossier-team.component.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-team/edit-dossier-team.component.ts @@ -44,7 +44,7 @@ export class EditDossierTeamComponent implements EditDossierSectionInterface, On } get changed() { - const { owner, members, approvers } = this.form.value; + const { owner, members, approvers } = this.form.getRawValue(); if (this.dossier.ownerId !== owner) { return true; @@ -66,7 +66,7 @@ export class EditDossierTeamComponent implements EditDossierSectionInterface, On } this.#updateLists(); } - const { owner, members, approvers } = this.form.value; + const { owner, members, approvers } = this.form.getRawValue(); const dossier = { ...this.dossier, memberIds: members, @@ -129,7 +129,7 @@ export class EditDossierTeamComponent implements EditDossierSectionInterface, On const possibleMembers = this.#userService.all.filter(user => user.isUser || user.isManager); this.membersSelectOptions = possibleMembers .filter(user => this.#userService.getName(user.id).toLowerCase().includes(value.toLowerCase())) - .filter(user => this.form.value.owner !== user.id) + .filter(user => this.form.getRawValue().owner !== user.id) .map(user => user.id) .sort((a, b) => this.#userService.getName(a).localeCompare(this.#userService.getName(b))); } diff --git a/apps/red-ui/src/app/modules/shared-dossiers/shared-dossiers.module.ts b/apps/red-ui/src/app/modules/shared-dossiers/shared-dossiers.module.ts index 885cfbd15..6cc42f3b7 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/shared-dossiers.module.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/shared-dossiers.module.ts @@ -28,6 +28,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { DossiersListingActionsComponent } from './components/dossiers-listing-actions/dossiers-listing-actions.component'; import { IqserUsersModule } from '@iqser/common-ui/lib/users'; import { SideNavComponent, SmallChipComponent, StatusBarComponent } from '@iqser/common-ui/lib/shared'; +import { SelectComponent } from '@shared/components/select/select.component'; const components = [ FileActionsComponent, @@ -65,6 +66,7 @@ const dialogs = [EditDossierDialogComponent, AssignReviewerApproverDialogCompone DynamicInputComponent, IqserAllowDirective, IqserDenyDirective, + SelectComponent, ], }) export class SharedDossiersModule {} diff --git a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts index c55441d88..46c66374a 100644 --- a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts @@ -39,7 +39,7 @@ export class FileDownloadBtnComponent implements OnChanges { } async downloadFiles() { - const ref = this._dialog.open(DownloadDialogComponent, { + const ref = this._dialog.openDefault(DownloadDialogComponent, { data: { dossier: this.dossier, files: this.files }, }); const result = await ref.result(); diff --git a/apps/red-ui/src/app/modules/shared/components/expandable-file-actions/expandable-file-actions.component.ts b/apps/red-ui/src/app/modules/shared/components/expandable-file-actions/expandable-file-actions.component.ts index ff8759052..1a2d83f63 100644 --- a/apps/red-ui/src/app/modules/shared/components/expandable-file-actions/expandable-file-actions.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/expandable-file-actions/expandable-file-actions.component.ts @@ -79,7 +79,7 @@ export class ExpandableFileActionsComponent implements OnChanges { } async #downloadFiles(files: File[], dossier: Dossier) { - const ref = this._dialog.open(DownloadDialogComponent, { + const ref = this._dialog.openDefault(DownloadDialogComponent, { data: { dossier, files }, }); const result = await ref.result(); diff --git a/apps/red-ui/src/app/modules/shared/components/select/select.component.ts b/apps/red-ui/src/app/modules/shared/components/select/select.component.ts index be661e398..5d2ac6076 100644 --- a/apps/red-ui/src/app/modules/shared/components/select/select.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/select/select.component.ts @@ -1,11 +1,16 @@ import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, HostBinding, Input, TemplateRef, ViewChild } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { MatChipListbox, MatChipSelectionChange } from '@angular/material/chips'; +import { MatChipListbox, MatChipSelectionChange, MatChipsModule } from '@angular/material/chips'; +import { StopPropagationDirective } from '@iqser/common-ui'; +import { NgForOf, NgTemplateOutlet } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { MatIconModule } from '@angular/material/icon'; @Component({ selector: 'redaction-select', templateUrl: './select.component.html', styleUrls: ['./select.component.scss'], + standalone: true, providers: [ { provide: NG_VALUE_ACCESSOR, @@ -13,16 +18,17 @@ import { MatChipListbox, MatChipSelectionChange } from '@angular/material/chips' multi: true, }, ], + imports: [StopPropagationDirective, NgTemplateOutlet, TranslateModule, NgForOf, MatChipsModule, MatIconModule], }) export class SelectComponent implements AfterViewInit, ControlValueAccessor { + private _value: any[] = []; + private _onChange: (value: any[]) => void; @Input() optionTemplate?: TemplateRef<{ option: any }>; @Input() label: string; @Input() options: any[]; @Input() disabled = false; @Input() multiple = true; @ViewChild(MatChipListbox) chipList: MatChipListbox; - private _value: any[] = []; - private _onChange: (value: any[]) => void; constructor(private readonly _changeDetector: ChangeDetectorRef, private readonly _elementRef: ElementRef) {} diff --git a/apps/red-ui/src/app/modules/shared/dialogs/download-dialog/download-dialog.component.html b/apps/red-ui/src/app/modules/shared/dialogs/download-dialog/download-dialog.component.html index d4fb8cfe1..ae00db31b 100644 --- a/apps/red-ui/src/app/modules/shared/dialogs/download-dialog/download-dialog.component.html +++ b/apps/red-ui/src/app/modules/shared/dialogs/download-dialog/download-dialog.component.html @@ -24,7 +24,7 @@ formControlName="downloadFileTypes" > -
+
{ + readonly #logger = inject(NGXLogger); readonly iconButtonTypes = IconButtonTypes; - readonly downloadTypes: { key: DownloadFileType; label: string }[] = this.#formDownloadTypes; + readonly hasApprovedFiles: boolean; + readonly downloadTypes: { key: DownloadFileType; label: string }[]; readonly availableReportTypes = this.#availableReportTypes; readonly form = this.#getForm(); + initialFormValue = this.form.getRawValue(); readonly roles = Roles; @@ -37,6 +59,8 @@ export class DownloadDialogComponent extends IqserDialogComponent file.workflowStatus === WorkflowFileStatuses.APPROVED); + this.downloadTypes = this.#formDownloadTypes; } get reportTypesLength() { @@ -52,12 +76,12 @@ export class DownloadDialogComponent extends IqserDialogComponent type === DownloadFileTypes.REDACTED) && - !this.data.files.some(file => file.workflowStatus === WorkflowFileStatuses.APPROVED) - ); + const hasReportTypes = this.form.controls.reportTemplateIds.value.length; + const downloadFileTypes = this.form.controls.downloadFileTypes.value; + const onlyRedactedVersionSelected = + downloadFileTypes.length && downloadFileTypes.every(type => type === DownloadFileTypes.REDACTED); + + return !hasReportTypes || (onlyRedactedVersionSelected && !this.hasApprovedFiles); } override get valid() { @@ -77,7 +101,7 @@ export class DownloadDialogComponent extends IqserDialogComponent { - if (!this.data.files.some(file => file.workflowStatus === WorkflowFileStatuses.APPROVED)) { + if (!this.hasApprovedFiles) { return type.key !== DownloadFileTypes.REDACTED; } return true; @@ -86,7 +110,7 @@ export class DownloadDialogComponent extends IqserDialogComponent { - if (!this.data.files.some(file => file.workflowStatus === WorkflowFileStatuses.APPROVED)) { + if (!this.hasApprovedFiles) { return type !== DownloadFileTypes.REDACTED; } return true; @@ -99,18 +123,20 @@ export class DownloadDialogComponent extends IqserDialogComponent reportTemplate.templateId; - close() { + override close() { const result: DownloadDialogResult = { reportTemplateIds: this.form.controls.reportTemplateIds.value, downloadFileTypes: this.form.controls.downloadFileTypes.value, redactionPreviewColor: this.form.controls.redactionPreviewColor.value, }; + this.#logger.info('[DOWNLOAD] Closing with result', result); + if (result.reportTemplateIds.length === 0) { - return this.dialogRef.close(); + return super.close(); } - this.dialogRef.close(result); + super.close(result); } #hasReportTemplateOrDownloadType(control: AbstractControl) { diff --git a/apps/red-ui/src/app/modules/shared/shared.module.ts b/apps/red-ui/src/app/modules/shared/shared.module.ts index 5da21e2b1..b1919ca5b 100644 --- a/apps/red-ui/src/app/modules/shared/shared.module.ts +++ b/apps/red-ui/src/app/modules/shared/shared.module.ts @@ -17,7 +17,6 @@ import { IqserHelpModeModule, StopPropagationDirective, } from '@iqser/common-ui'; -import { SelectComponent } from './components/select/select.component'; import { NavigateLastDossiersScreenDirective } from './directives/navigate-last-dossiers-screen.directive'; import { DictionaryManagerComponent } from './components/dictionary-manager/dictionary-manager.component'; import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'; @@ -43,17 +42,16 @@ import { AddEditEntityComponent } from './components/add-edit-entity/add-edit-en import { ColorPickerModule } from 'ngx-color-picker'; import { WatermarkSelectorComponent } from './components/dossier-watermark-selector/watermark-selector.component'; import { OcrProgressBarComponent } from './components/ocr-progress-bar/ocr-progress-bar.component'; -import { DownloadDialogComponent } from './dialogs/download-dialog/download-dialog.component'; import { CustomDateAdapter } from '@shared/CustomDateAdapter'; import { IqserUsersModule } from '@iqser/common-ui/lib/users'; import { SmallChipComponent } from '@iqser/common-ui/lib/shared'; +import { SelectComponent } from '@shared/components/select/select.component'; const buttons = [FileDownloadBtnComponent]; const components = [ PaginationComponent, AnnotationIconComponent, - SelectComponent, DictionaryManagerComponent, AssignUserDropdownComponent, TypeFilterComponent, @@ -82,7 +80,7 @@ const modules = [MatConfigModule, ScrollingModule, IconsModule, FormsModule, Rea const deleteThisWhenAllComponentsAreStandalone = [DonutChartComponent]; @NgModule({ - declarations: [...components, ...utils, EditorComponent, DownloadDialogComponent], + declarations: [...components, ...utils, EditorComponent], imports: [ CommonModule, ...modules, @@ -99,6 +97,7 @@ const deleteThisWhenAllComponentsAreStandalone = [DonutChartComponent]; HasScrollbarDirective, IqserAllowDirective, IqserDenyDirective, + SelectComponent, ], exports: [...modules, ...components, ...utils, ...deleteThisWhenAllComponentsAreStandalone], providers: [ diff --git a/apps/red-ui/src/app/services/entity-services/dictionary.service.ts b/apps/red-ui/src/app/services/entity-services/dictionary.service.ts index e8a9dee5c..35681d847 100644 --- a/apps/red-ui/src/app/services/entity-services/dictionary.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dictionary.service.ts @@ -12,6 +12,7 @@ import { DossierDictionariesMapService } from '@services/entity-services/dossier import { List } from '@iqser/common-ui/lib/utils'; const MIN_WORD_LENGTH = 2; +const IMAGE_TYPES = ['image', 'formula', 'ocr']; @Injectable({ providedIn: 'root', @@ -146,7 +147,12 @@ export class DictionaryService extends EntitiesService return !!this._dictionariesMapService.get(dossierTemplateId).find(e => e.type === SuperTypes.ManualRedaction && !e.virtual); } - getPossibleDictionaries(dossierTemplateId: string, hintTypes: boolean, dossierDictionaryOnly = false): Dictionary[] { + getPossibleDictionaries( + dossierTemplateId: string, + hintTypes: boolean, + dossierDictionaryOnly = false, + dictionaryRequest = false, + ): Dictionary[] { const possibleDictionaries: Dictionary[] = []; this._dictionariesMapService.get(dossierTemplateId).forEach((d: Dictionary) => { @@ -157,8 +163,17 @@ export class DictionaryService extends EntitiesService ) { possibleDictionaries.push(d); } - } else if ((d.hint && d.hasDictionary && d.addToDictionaryAction) || (dossierDictionaryOnly && d.dossierDictionaryOnly)) { - possibleDictionaries.push(d); + } else if ( + (d.hint && d.hasDictionary && d.addToDictionaryAction && d.type) || + (dictionaryRequest && dossierDictionaryOnly && d.dossierDictionaryOnly) + ) { + if (!dictionaryRequest) { + if (!IMAGE_TYPES.includes(d.type)) { + possibleDictionaries.push(d); + } + } else { + possibleDictionaries.push(d); + } } }); diff --git a/apps/red-ui/src/app/services/entity-services/file-attributes.service.ts b/apps/red-ui/src/app/services/entity-services/file-attributes.service.ts index 0f74df8a5..356328cf4 100644 --- a/apps/red-ui/src/app/services/entity-services/file-attributes.service.ts +++ b/apps/red-ui/src/app/services/entity-services/file-attributes.service.ts @@ -1,5 +1,5 @@ import { EntitiesService } from '@iqser/common-ui'; -import { Injectable, signal } from '@angular/core'; +import { computed, Injectable, signal } from '@angular/core'; import { BehaviorSubject, Observable, of } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; import { FileAttributeConfig, FileAttributes, IFileAttributeConfig, IFileAttributesConfig } from '@red/domain'; @@ -12,8 +12,9 @@ export type FileAttributesConfigMap = Readonly { readonly fileAttributesConfig$ = new BehaviorSubject({}); - readonly isEditingFileAttribute = signal(false); + readonly isEditingFileAttribute = computed(() => this.openAttributeEdits().length > 0); + readonly openAttributeEdits = signal([]); readonly openAttributeEdit = signal(''); readonly fileEdit = signal(''); diff --git a/apps/red-ui/src/app/services/entity-services/watermark.service.ts b/apps/red-ui/src/app/services/entity-services/watermark.service.ts index a88897203..f7072ad87 100644 --- a/apps/red-ui/src/app/services/entity-services/watermark.service.ts +++ b/apps/red-ui/src/app/services/entity-services/watermark.service.ts @@ -1,10 +1,11 @@ import { inject, Injectable } from '@angular/core'; -import { GenericService, QueryParam } from '@iqser/common-ui'; +import { GenericService, IqserPermissionsService, QueryParam } from '@iqser/common-ui'; import { IWatermark, Watermark } from '@red/domain'; -import { firstValueFrom, forkJoin, Observable } from 'rxjs'; +import { firstValueFrom, forkJoin, Observable, of } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { WatermarksMapService } from '@services/entity-services/watermarks-map.service'; import { mapEach } from '@iqser/common-ui/lib/utils'; +import { Roles } from '@users/roles'; interface IsUsedResponse { value: boolean; @@ -14,9 +15,11 @@ interface IsUsedResponse { providedIn: 'root', }) export class WatermarkService extends GenericService { - readonly #watermarksMapService = inject(WatermarksMapService); protected readonly _defaultModelPath = 'watermark'; + readonly #watermarksMapService = inject(WatermarksMapService); + readonly #allowed = inject(IqserPermissionsService).has(Roles.watermarks.read); + async deleteWatermark(dossierTemplateId: string, watermarkId: number) { await firstValueFrom(super.delete(null, `${this._defaultModelPath}/${watermarkId}`)); return firstValueFrom(this.loadForDossierTemplate(dossierTemplateId)); @@ -37,6 +40,9 @@ export class WatermarkService extends GenericService { } loadAll(dossierTemplateIds: string[]): Observable { + if (!this.#allowed) { + return of([]); + } return forkJoin(dossierTemplateIds.map(id => this.loadForDossierTemplate(id))).pipe(map(arrays => [].concat(...arrays))); } diff --git a/apps/red-ui/src/app/services/files/files.service.ts b/apps/red-ui/src/app/services/files/files.service.ts index 34ffe30f2..36244ec74 100644 --- a/apps/red-ui/src/app/services/files/files.service.ts +++ b/apps/red-ui/src/app/services/files/files.service.ts @@ -31,7 +31,7 @@ export class FilesService extends EntitiesService { loadAll(dossierId: string) { const files$ = this.getFor(dossierId).pipe( mapEach(file => new File(file, this._userService.getName(file.assignee))), - tap(() => this._logger.info('[FILE] Loaded')), + tap(file => this._logger.info('[FILE] Loaded', file)), ); const loadStats$ = files$.pipe(switchMap(files => this._dossierStatsService.getFor([dossierId]).pipe(map(() => files)))); return loadStats$.pipe(tap(files => this._filesMapService.set(dossierId, files))); @@ -41,7 +41,7 @@ export class FilesService extends EntitiesService { const _file = await firstValueFrom(super._getOne([dossierId, file.id])); const reloadedFile = new File(_file, this._userService.getName(_file.assignee)); await firstValueFrom(this._dossierStatsService.getFor([dossierId])); - this._logger.info('[FILE] Reloaded'); + this._logger.info('[FILE] Reloaded', reloadedFile); return this._filesMapService.replace(dossierId, [reloadedFile]); } diff --git a/apps/red-ui/src/app/services/permissions.service.ts b/apps/red-ui/src/app/services/permissions.service.ts index 1040f8cf8..8eade5337 100644 --- a/apps/red-ui/src/app/services/permissions.service.ts +++ b/apps/red-ui/src/app/services/permissions.service.ts @@ -354,6 +354,10 @@ export class PermissionsService { return this._iqserPermissionsService.has(Roles.rules.write) && this.isAdmin(); } + canViewRssDialog() { + return this._iqserPermissionsService.has(Roles.getRss); + } + #canDeleteEntity(entity: Dictionary): boolean { return !entity.systemManaged; } diff --git a/apps/red-ui/src/app/users/user-preference.service.ts b/apps/red-ui/src/app/users/user-preference.service.ts index 7f4ed1082..4f8423e91 100644 --- a/apps/red-ui/src/app/users/user-preference.service.ts +++ b/apps/red-ui/src/app/users/user-preference.service.ts @@ -10,6 +10,7 @@ export const PreferencesKeys = { displaySuggestionsInPreview: 'Display-Suggestions-In-Preview', unapprovedSuggestionsWarning: 'Unapproved-Suggestions-Warning', loadAllAnnotationsWarning: 'Load-All-Annotations-Warning', + openStructuredComponentManagementDialogByDefault: 'Open-Structured-Component-Management-By-Default', } as const; @Injectable({ @@ -53,6 +54,15 @@ export class UserPreferenceService extends IqserUserPreferenceService { await this.save(PreferencesKeys.autoExpandFiltersOnActions, nextValue); } + getOpenStructuredComponentManagementDialogByDefault(): boolean { + return this._getAttribute(PreferencesKeys.openStructuredComponentManagementDialogByDefault, 'false') === 'true'; + } + + async toggleOpenStructuredComponentManagementDialogByDefault(): Promise { + const nextValue = (!this.getOpenStructuredComponentManagementDialogByDefault()).toString(); + await this.save(PreferencesKeys.openStructuredComponentManagementDialogByDefault, nextValue); + } + getDisplaySuggestionsInPreview(): boolean { return this._getAttribute(PreferencesKeys.displaySuggestionsInPreview, 'false') === 'true'; } diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 3e5de8134..f96939231 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -1,8 +1,9 @@ { "ADMIN_CONTACT_NAME": null, "ADMIN_CONTACT_URL": null, - "API_URL": "https://dev-04.iqser.cloud", - "APP_NAME": "RedactManager", + "API_URL": "https://dan.iqser.cloud", + "APP_NAME": "RedactManager very very very very long", + "IS_DOCUMINE": false, "AUTO_READ_TIME": 3, "BACKEND_APP_VERSION": "4.4.40", "EULA_URL": "EULA_URL", @@ -11,7 +12,7 @@ "MAX_RETRIES_ON_SERVER_ERROR": 3, "OAUTH_CLIENT_ID": "redaction", "OAUTH_IDP_HINT": null, - "OAUTH_URL": "https://dev-04.iqser.cloud/auth", + "OAUTH_URL": "https://dan.iqser.cloud/auth", "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/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index 47c314791..4a120e99a 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -368,6 +368,7 @@ "declined-suggestion": "Abgelehnter Vorschlag", "hint": "Hinweis", "ignored-hint": "Ignorierter Hinweis", + "manual-hint": "", "manual-redaction": "Manuelle Schwärzung", "recommendation": "Empfehlung", "redaction": "Schwärzung", @@ -1642,10 +1643,6 @@ }, "license-info-screen": { "backend-version": "Backend-Version der Anwendung", - "chart": { - "pages-per-month": "Seiten pro Monat", - "total-pages": "Gesamtzahl der Seiten" - }, "copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)", "copyright-claim-title": "Copyright", "current-analyzed": "In aktuellem Lizenzzeitraum analysierte Seiten", @@ -1666,6 +1663,11 @@ "licensing-details": "Lizenzdetails", "licensing-period": "Laufzeit der Lizenz", "ocr-analyzed-pages": "Mit OCR konvertierte Seiten", + "pages": { + "cumulative": "Seiten insgesamt", + "pages-per-month": "Seiten pro Monat", + "total-pages": "Gesamtzahl der Seiten" + }, "status": { "active": "Aktiv", "inactive": "" @@ -1864,6 +1866,7 @@ "form": { "auto-expand-filters-on-action": "", "load-all-annotations-warning": "", + "open-structured-view-by-default": "", "show-suggestions-in-preview": "", "unapproved-suggestions-warning": "" }, diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index ad03facbb..3fbb132a2 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -368,6 +368,7 @@ "declined-suggestion": "Declined Suggestion", "hint": "Hint", "ignored-hint": "Ignored Hint", + "manual-hint": "Manual Hint", "manual-redaction": "Manual Redaction", "recommendation": "Recommendation", "redaction": "Redaction", @@ -1642,10 +1643,6 @@ }, "license-info-screen": { "backend-version": "Backend Application Version", - "chart": { - "pages-per-month": "Pages per Month", - "total-pages": "Total Pages" - }, "copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)", "copyright-claim-title": "Copyright Claim", "current-analyzed": "Analyzed Pages in Licensing Period", @@ -1666,6 +1663,11 @@ "licensing-details": "Licensing Details", "licensing-period": "Licensing Period", "ocr-analyzed-pages": "OCR Processed Pages in Licensing Period", + "pages": { + "cumulative": "Cumulative Pages", + "pages-per-month": "Pages per Month", + "total-pages": "Total Pages" + }, "status": { "active": "Active", "inactive": "Inactive" @@ -1864,6 +1866,7 @@ "form": { "auto-expand-filters-on-action": "Auto-expand filters on my actions", "load-all-annotations-warning": "Warning regarding loading all annotations at once in file preview", + "open-structured-view-by-default": "Display structured component management modal by default", "show-suggestions-in-preview": "Display suggestions in document preview", "unapproved-suggestions-warning": "Warning regarding unapproved suggestions in document Preview mode" }, @@ -1959,11 +1962,11 @@ "label": "False positive" }, "in-dossier": { - "description": "Do not redact \"{value}\" in any document of the current dossier.", - "label": "Remove in dossier" + "description": "Do not {type} \"{value}\" in any document of the current dossier.", + "label": "Remove from dossier" }, "only-here": { - "description": "Do not redact \"{value}\" at this position in the current document.", + "description": "Do not {type} \"{value}\" at this position in the current document.", "label": "Remove here" }, "redact": { @@ -1976,7 +1979,7 @@ } } }, - "title": "Remove redaction" + "title": "Remove {type}" } }, "report-type": { diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index 3d272400d..99bfe9ccc 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -208,9 +208,6 @@ "accept-recommendation": { "label": "Empfehlung annehmen" }, - "accept-suggestion": { - "label": "Genehmigen und zum Wörterbuch hinzufügen" - }, "convert-highlights": { "label": "" }, @@ -328,7 +325,6 @@ } }, "recategorize-image": "neu kategorisieren", - "reject-suggestion": "Vorschlag ablehnen", "remove-annotation": { "remove-redaction": "" }, @@ -368,6 +364,7 @@ "declined-suggestion": "Abgelehnter Vorschlag", "hint": "Hinweis", "ignored-hint": "Ignorierter Hinweis", + "manual-hint": "", "manual-redaction": "Manuelle Schwärzung", "recommendation": "Empfehlung", "redaction": "Schwärzung", @@ -1642,10 +1639,6 @@ }, "license-info-screen": { "backend-version": "Backend-Version der Anwendung", - "chart": { - "pages-per-month": "Seiten pro Monat", - "total-pages": "Gesamtzahl der Seiten" - }, "copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)", "copyright-claim-title": "Copyright", "current-analyzed": "In aktuellem Lizenzzeitraum analysierte Seiten", @@ -1666,6 +1659,11 @@ "licensing-details": "Lizenzdetails", "licensing-period": "Laufzeit der Lizenz", "ocr-analyzed-pages": "Mit OCR konvertierte Seiten", + "pages": { + "cumulative": "Seiten insgesamt", + "pages-per-month": "Seiten pro Monat", + "total-pages": "Gesamtzahl der Seiten" + }, "status": { "active": "Aktiv", "inactive": "" @@ -1864,6 +1862,7 @@ "form": { "auto-expand-filters-on-action": "", "load-all-annotations-warning": "", + "open-structured-view-by-default": "", "show-suggestions-in-preview": "", "unapproved-suggestions-warning": "" }, @@ -2075,6 +2074,12 @@ "undo": "" }, "annotations": "", + "table-header": { + "annotation": "", + "component": "", + "transformation": "", + "value": "" + }, "title": "" }, "rules-screen": { diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 5b8fc7d75..85aa28668 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -208,9 +208,6 @@ "accept-recommendation": { "label": "Accept Recommendation" }, - "accept-suggestion": { - "label": "Approve Suggestion" - }, "convert-highlights": { "label": "Convert Selected Earmarks" }, @@ -258,8 +255,8 @@ }, "manual-redaction": { "add": { - "error": "Failed to save component: {error}", - "success": "Component added!" + "error": "Failed to save annotation: {error}", + "success": "Annotation added!" }, "approve": { "error": "Failed to approve suggestion: {error}", @@ -290,8 +287,8 @@ "success": "Hint removed!" }, "remove": { - "error": "Failed to remove component: {error}", - "success": "Component removed!" + "error": "Failed to remove annotation: {error}", + "success": "Annotation removed!" }, "request-change-legal-basis": { "error": "Failed to request annotation reason change: {error}", @@ -328,7 +325,6 @@ } }, "recategorize-image": "Recategorize", - "reject-suggestion": "Reject Suggestion", "remove-annotation": { "remove-redaction": "Remove" }, @@ -368,9 +364,10 @@ "declined-suggestion": "Declined Suggestion", "hint": "Hint", "ignored-hint": "Ignored Hint", - "manual-redaction": "Manual Component", + "manual-hint": "Manual Hint", + "manual-redaction": "Manual Annotation", "recommendation": "Recommendation", - "redaction": "Component", + "redaction": "Annotation", "skipped": "Skipped", "suggestion-add": "Suggested component", "suggestion-add-dictionary": "Suggested add to Dictionary", @@ -1424,7 +1421,7 @@ "text-highlights": "Earmarks", "text-highlights-tooltip": "Shows all text-earmarks and allows removing or importing them as components", "toggle-analysis": { - "disable": "Disable component", + "disable": "Disable extraction", "enable": "Enable for component", "only-managers": "Enabling / disabling is permitted only for managers" } @@ -1642,10 +1639,6 @@ }, "license-info-screen": { "backend-version": "Backend Application Version", - "chart": { - "pages-per-month": "Pages per Month", - "total-pages": "Total Pages" - }, "copyright-claim-text": "Copyright © 2020 - {currentYear} knecon AG (powered by IQSER)", "copyright-claim-title": "Copyright Claim", "current-analyzed": "Analyzed Pages in Licensing Period", @@ -1666,6 +1659,11 @@ "licensing-details": "Licensing Details", "licensing-period": "Licensing Period", "ocr-analyzed-pages": "OCR Processed Pages in Licensing Period", + "pages": { + "cumulative": "Cumulative Pages", + "pages-per-month": "Pages per Month", + "total-pages": "Total Pages" + }, "status": { "active": "Active", "inactive": "Inactive" @@ -1716,7 +1714,7 @@ "force-hint": "Force Hint", "force-redaction": "Force Component", "hint": "Add hint", - "redact": "Redact", + "redact": "Annotation", "redaction": "Redaction" } } @@ -1724,9 +1722,9 @@ "minutes": "minutes", "no-active-license": "Invalid or corrupt license – Please contact your administrator", "notification": { - "assign-approver": "You have been assigned as approver for {fileHref, select, null{{fileName}} other{{fileName}}} in dossier: {dossierHref, select, null{{dossierName}} other{{dossierName}}}!", + "assign-approver": "You have been assigned to {fileHref, select, null{{fileName}} other{{fileName}}} in dossier: {dossierHref, select, null{{dossierName}} other{{dossierName}}}!", "assign-reviewer": "You have been assigned as reviewer for {fileHref, select, null{{fileName}} other{{fileName}}} in dossier: {dossierHref, select, null{{dossierName}} other{{dossierName}}}!", - "document-approved": " {fileHref, select, null{{fileName}} other{{fileName}}} has been approved!", + "document-approved": " {fileHref, select, null{{fileName}} other{{fileName}}} has been moved to Done!", "dossier-deleted": "Dossier: {dossierName} has been deleted!", "dossier-owner-deleted": "The owner of dossier: {dossierName} has been deleted!", "dossier-owner-removed": "You have been removed as dossier owner from {dossierHref, select, null{{dossierName}} other{{dossierName}}}!", @@ -1754,9 +1752,9 @@ }, "options-title": "Choose on which action you want to be notified", "options": { - "ASSIGN_APPROVER": "When I am assigned to a document as Approver", + "ASSIGN_APPROVER": "When I am assigned to a document", "ASSIGN_REVIEWER": "When I am assigned to a document as Reviewer", - "DOCUMENT_APPROVED": "When the document status changes to Approved (only for dossier owners)", + "DOCUMENT_APPROVED": "When the document status changes to Done (only for dossier owners)", "DOCUMENT_UNDER_APPROVAL": "When the document status changes to Under Approval", "DOCUMENT_UNDER_REVIEW": "When the document status changes to In Progress", "DOSSIER_DELETED": "When a dossier was deleted", @@ -1864,6 +1862,7 @@ "form": { "auto-expand-filters-on-action": "Auto expand filters on my actions", "load-all-annotations-warning": "Warning regarding loading all annotations at once in file preview", + "open-structured-view-by-default": "Display structured component management modal by default", "show-suggestions-in-preview": "Display suggestions in document preview", "unapproved-suggestions-warning": "Warning regarding unapproved suggestions in document Preview mode" }, @@ -1959,11 +1958,11 @@ "label": "False positive" }, "in-dossier": { - "description": "Do not redact \"{value}\" in any document of the current dossier.", - "label": "Remove in dossier" + "description": "Do not {type} \"{value}\" in any document of the current dossier.", + "label": "Remove from dossier" }, "only-here": { - "description": "Do not redact \"{value}\" at this position in the current document.", + "description": "Do not {type} \"{value}\" at this position in the current document.", "label": "Remove here" }, "redact": { @@ -1976,7 +1975,7 @@ } } }, - "title": "Remove redaction" + "title": "Remove {type}" } }, "report-type": { @@ -2075,6 +2074,12 @@ "undo": "Undo to: {value}" }, "annotations": "{type} found on {pageCount, plural, one{page} other{pages}} {pages} by rule #{ruleNumber}", + "table-header": { + "annotation-references": "Annotation references", + "component": "Component", + "transformation-rule": "Transformation rule", + "value": "Value" + }, "title": "Structured Component Management" }, "rules-screen": { diff --git a/apps/red-ui/src/assets/icons/documine-logo.ico b/apps/red-ui/src/assets/icons/documine-logo.ico index e5947b475..0b233e850 100644 Binary files a/apps/red-ui/src/assets/icons/documine-logo.ico and b/apps/red-ui/src/assets/icons/documine-logo.ico differ diff --git a/apps/red-ui/src/assets/icons/general/documine-logo.svg b/apps/red-ui/src/assets/icons/general/documine-logo.svg new file mode 100644 index 000000000..64a5dbe47 --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/documine-logo.svg @@ -0,0 +1,19 @@ + + + + + + + + + + \ No newline at end of file diff --git a/docker/red-ui/docker-entrypoint.sh b/docker/red-ui/docker-entrypoint.sh index a4f900adc..9177a469b 100755 --- a/docker/red-ui/docker-entrypoint.sh +++ b/docker/red-ui/docker-entrypoint.sh @@ -4,6 +4,7 @@ ADMIN_CONTACT_NAME="${ADMIN_CONTACT_NAME:-}" ADMIN_CONTACT_URL="${ADMIN_CONTACT_URL:-}" API_URL="${API_URL:-}" APP_NAME="${APP_NAME:-}" +IS_DOCUMINE="${IS_DOCUMINE:-}" AUTO_READ_TIME="${AUTO_READ_TIME:-1.5}" BACKEND_APP_VERSION="${BACKEND_APP_VERSION:-4.7.0}" EULA_URL="${EULA_URL:-}" @@ -25,13 +26,12 @@ BASE_TRANSLATIONS_DIRECTORY="${BASE_TRANSLATIONS_DIRECTORY:-/assets/i18n/redact/ THEME="${THEME:-theme-template}" WATERMARK_PREVIEW_PAPER_FORMAT="${WATERMARK_PREVIEW_PAPER_FORMAT:-a4}" - - echo '{ "ADMIN_CONTACT_NAME":"'"$ADMIN_CONTACT_NAME"'", "ADMIN_CONTACT_URL":"'"$ADMIN_CONTACT_URL"'", "API_URL":"'"$API_URL"'", "APP_NAME":"'"$APP_NAME"'", + "IS_DOCUMINE":"'"$IS_DOCUMINE"'", "AUTO_READ_TIME":"'"$AUTO_READ_TIME"'", "BACKEND_APP_VERSION":"'"$BACKEND_APP_VERSION"'", "EULA_URL":"'"$EULA_URL:"'", @@ -51,7 +51,7 @@ echo '{ "AVAILABLE_OLD_NOTIFICATIONS_MINUTES":"'"$AVAILABLE_OLD_NOTIFICATIONS_MINUTES"'", "NOTIFICATIONS_THRESHOLD":"'"$NOTIFICATIONS_THRESHOLD"'", "WATERMARK_PREVIEW_PAPER_FORMAT":"'"$WATERMARK_PREVIEW_PAPER_FORMAT"'" - }' > /usr/share/nginx/html/ui/assets/config/config.json + }' >/usr/share/nginx/html/ui/assets/config/config.json echo 'Env variables: ' cat /usr/share/nginx/html/ui/assets/config/config.json diff --git a/libs/common-ui b/libs/common-ui index a8f5fb2e2..e69f8b3f0 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit a8f5fb2e25cd1f150c1099d387b4f9dece3b922c +Subproject commit e69f8b3f0df8701bbc3dbc79e2239aaad3acc889 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 95e06eece..189d16a88 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 @@ -4,7 +4,6 @@ import { DictionaryEntryType } from './dictionary-entry-types'; export interface IAddRedactionRequest { addToDictionary?: boolean; - addToDossierDictionary?: boolean; dictionaryEntryType?: DictionaryEntryType; comment?: { text: string }; legalBasis?: string; diff --git a/libs/red-domain/src/lib/redaction-log/manual-redaction-entry.ts b/libs/red-domain/src/lib/redaction-log/manual-redaction-entry.ts index a8a9abf9d..92712e2f7 100644 --- a/libs/red-domain/src/lib/redaction-log/manual-redaction-entry.ts +++ b/libs/red-domain/src/lib/redaction-log/manual-redaction-entry.ts @@ -3,7 +3,6 @@ import { LogEntryStatus } from './types'; export interface IManualRedactionEntry { addToDictionary?: boolean; - addToDossierDictionary?: boolean; annotationId?: string; fileId?: string; legalBasis?: string; diff --git a/libs/red-domain/src/lib/shared/app-config.ts b/libs/red-domain/src/lib/shared/app-config.ts index 343b6b097..a4c788e31 100644 --- a/libs/red-domain/src/lib/shared/app-config.ts +++ b/libs/red-domain/src/lib/shared/app-config.ts @@ -1,4 +1,4 @@ -import { IqserAppConfig } from '@iqser/common-ui/lib/utils'; +import { IqserAppConfig } from '@common-ui/utils'; export interface AppConfig extends IqserAppConfig { readonly ADMIN_CONTACT_NAME: string; diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..39a2b6e9a --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ] +} diff --git a/tsconfig.json b/tsconfig.json index 8f7ec4bd3..1899620a9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,7 @@ "@i18n/*": ["apps/red-ui/src/app/i18n/*"], "@iqser/common-ui": ["libs/common-ui/src/index.ts"], "@iqser/common-ui/*": ["libs/common-ui/src/*"], + "@common-ui/*": ["libs/common-ui/src/lib/*"], "@models/*": ["apps/red-ui/src/app/models/*"], "@red/domain": ["libs/red-domain/src/index.ts"], "@services/*": ["apps/red-ui/src/app/services/*"],