Merge remote-tracking branch 'origin/master' into RED-9747

This commit is contained in:
Dan Percic 2024-08-14 15:37:28 +03:00
commit 747d8157b5
48 changed files with 428 additions and 272 deletions

View File

@ -16,7 +16,7 @@ import { CompositeRouteGuard, DEFAULT_REDIRECT_KEY, IqserPermissionsGuard, Iqser
import { TenantSelectComponent } from '@iqser/common-ui/lib/tenants';
import { doesNotHaveAnyRole, hasAnyRole, IqserAuthGuard } from '@iqser/common-ui/lib/users';
import { CustomRouteReuseStrategy } from '@iqser/common-ui/lib/utils';
import { ARCHIVE_ROUTE, BreadcrumbTypes, DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE, FILE_ID } from '@red/domain';
import { ARCHIVE_ROUTE, BreadcrumbTypes, DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIERS_ROUTE, FILE_ID } from '@red/domain';
import { RedRoleGuard } from '@users/red-role.guard';
import { Roles } from '@users/roles';
import { mainGuard } from '@utils/main.guard';
@ -81,7 +81,6 @@ const dossierTemplateIdRoutes: IqserRoutes = [
canActivate: [CompositeRouteGuard, loadArchivedDossiersGuard()],
data: {
routeGuards: [FeaturesGuard],
features: [DOSSIERS_ARCHIVE],
},
},
{

View File

@ -8,8 +8,7 @@ import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-se
import { filter, map, startWith } from 'rxjs/operators';
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';
import { ARCHIVE_ROUTE, DOSSIERS_ROUTE } from '@red/domain';
import { Roles } from '@users/roles';
import { REDDocumentViewer } from '../../modules/pdf-viewer/services/document-viewer.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@ -36,7 +35,6 @@ export class BaseScreenComponent {
{
text: this._translateService.instant('search.active-dossiers'),
icon: 'red:enter',
hide: () => !this._featuresService.isEnabled(DOSSIERS_ARCHIVE),
action: (query): void => this.#search(query, [], true),
},
{
@ -58,7 +56,6 @@ export class BaseScreenComponent {
private readonly _router: Router,
activatedRoute: ActivatedRoute,
private readonly _translateService: TranslateService,
private readonly _featuresService: FeaturesService,
readonly permissionsService: IqserPermissionsService,
readonly userService: UserService,
readonly userPreferenceService: UserPreferenceService,

View File

@ -1,6 +1,6 @@
import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Dictionary, LogEntryEngines, SuperTypes } from '@red/domain';
import { Dictionary, SuperTypes } from '@red/domain';
export const canUndo = (annotation: AnnotationWrapper, isApprover: boolean) => !isApprover && annotation.pending;
@ -16,7 +16,11 @@ export const canForceRedaction = (annotation: AnnotationWrapper, canAddRedaction
export const canAcceptRecommendation = (annotation: AnnotationWrapper) => annotation.isRecommendation && !annotation.pending;
export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) =>
annotation.canBeMarkedAsFalsePositive && !annotation.hasBeenResizedLocally && annotationEntity?.hasDictionary;
annotation.canBeMarkedAsFalsePositive &&
!annotation.hasBeenResizedLocally &&
!annotation.isRemovedLocally &&
!annotation.hasBeenForcedRedaction &&
annotationEntity?.hasDictionary;
export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean, autoAnalysisDisabled: boolean) =>
canAddRedaction &&

View File

@ -11,12 +11,6 @@
</mat-slide-toggle>
</div>
<div *ngIf="config.IS_DOCUMINE" class="iqser-input-group">
<mat-slide-toggle color="primary" formControlName="openScmDialogByDefault">
{{ 'preferences-screen.form.open-structured-view-by-default' | translate }}
</mat-slide-toggle>
</div>
<div *allow="roles.getTables" class="iqser-input-group">
<label [translate]="'preferences-screen.form.table-extraction-type'"></label>
<input formControlName="tableExtractionType" />
@ -37,6 +31,11 @@
{{ 'preferences-screen.form.help-mode-dialog' | translate }}
</mat-checkbox>
</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="overwriteFileOption">
{{ 'preferences-screen.form.overwrite-file-option' | translate }}
</mat-checkbox>
</div>
</ng-container>
</div>
</div>

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import {
@ -23,11 +23,11 @@ import { MatCheckbox } from '@angular/material/checkbox';
interface PreferencesForm {
// preferences
autoExpandFiltersOnActions: boolean;
openScmDialogByDefault: boolean;
tableExtractionType: string;
// warnings preferences
loadAllAnnotationsWarning: boolean;
helpModeDialog: boolean;
overwriteFileOption: boolean;
[k: string]: any;
}
@ -58,6 +58,12 @@ const Screens = {
],
})
export class PreferencesComponent extends BaseFormComponent implements OnInit {
readonly #formBuilder = inject(FormBuilder);
readonly #permissionsService = inject(IqserPermissionsService);
readonly #changeRef = inject(ChangeDetectorRef);
readonly #loadingService = inject(LoadingService);
readonly #userPreferenceService = inject(UserPreferenceService);
readonly form: FormGroup<AsControl<PreferencesForm>>;
readonly currentScreen: Screen;
readonly screens = Screens;
@ -66,81 +72,87 @@ export class PreferencesComponent extends BaseFormComponent implements OnInit {
readonly config = getConfig();
readonly isIqserDevMode = isIqserDevMode();
constructor(
route: ActivatedRoute,
readonly userPreferenceService: UserPreferenceService,
private readonly _formBuilder: FormBuilder,
private readonly _permissionsService: IqserPermissionsService,
private readonly _changeRef: ChangeDetectorRef,
private readonly _loadingService: LoadingService,
) {
get #isOverwriteFileOptionActive() {
return !(this.#userPreferenceService.getOverwriteFileOption() === 'undefined');
}
constructor(route: ActivatedRoute) {
super();
this.form = this._formBuilder.group({
this.form = this.#formBuilder.group({
// preferences
autoExpandFiltersOnActions: [this.userPreferenceService.getAutoExpandFiltersOnActions()],
openScmDialogByDefault: [this.userPreferenceService.getOpenScmDialogByDefault()],
tableExtractionType: [this.userPreferenceService.getTableExtractionType()],
autoExpandFiltersOnActions: [this.#userPreferenceService.getAutoExpandFiltersOnActions()],
tableExtractionType: [this.#userPreferenceService.getTableExtractionType()],
// warnings preferences
loadAllAnnotationsWarning: [this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)],
helpModeDialog: [this.userPreferenceService.getBool(KEYS.helpModeDialog)],
loadAllAnnotationsWarning: [this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)],
helpModeDialog: [this.#userPreferenceService.getBool(KEYS.helpModeDialog)],
overwriteFileOption: [this.#isOverwriteFileOptionActive],
});
if (!this._permissionsService.has(Roles.managePreferences)) {
if (!this.#permissionsService.has(Roles.managePreferences)) {
this.form.disable();
}
if (!this._permissionsService.has(Roles.getTables)) {
if (!this.#permissionsService.has(Roles.getTables)) {
this.form.controls.tableExtractionType.disable();
}
if (!this.#isOverwriteFileOptionActive) {
this.form.controls.overwriteFileOption.disable();
}
this.initialFormValue = this.form.getRawValue();
this.currentScreen = route.snapshot.data.screen;
}
ngOnInit() {
this._loadingService.stop();
this.#loadingService.stop();
}
async save(): Promise<any> {
if (this.form.controls.autoExpandFiltersOnActions.value !== this.userPreferenceService.getAutoExpandFiltersOnActions()) {
await this.userPreferenceService.toggleAutoExpandFiltersOnActions();
}
if (this.form.controls.openScmDialogByDefault.value !== this.userPreferenceService.getOpenScmDialogByDefault()) {
await this.userPreferenceService.toggleOpenScmDialogByDefault();
if (this.form.controls.autoExpandFiltersOnActions.value !== this.#userPreferenceService.getAutoExpandFiltersOnActions()) {
await this.#userPreferenceService.toggleAutoExpandFiltersOnActions();
}
if (this.form.controls.tableExtractionType.value !== this.userPreferenceService.getTableExtractionType()) {
await this.userPreferenceService.save(PreferencesKeys.tableExtractionType, this.form.controls.tableExtractionType.value);
if (this.form.controls.tableExtractionType.value !== this.#userPreferenceService.getTableExtractionType()) {
await this.#userPreferenceService.save(PreferencesKeys.tableExtractionType, this.form.controls.tableExtractionType.value);
}
if (
this.form.controls.loadAllAnnotationsWarning.value !==
this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)
this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)
) {
await this.userPreferenceService.save(
await this.#userPreferenceService.save(
PreferencesKeys.loadAllAnnotationsWarning,
String(this.form.controls.loadAllAnnotationsWarning.value),
);
}
if (this.form.controls.helpModeDialog.value !== this.userPreferenceService.getBool(KEYS.helpModeDialog)) {
await this.userPreferenceService.save(KEYS.helpModeDialog, String(this.form.controls.helpModeDialog.value));
if (this.form.controls.helpModeDialog.value !== this.#userPreferenceService.getBool(KEYS.helpModeDialog)) {
await this.#userPreferenceService.save(KEYS.helpModeDialog, String(this.form.controls.helpModeDialog.value));
}
await this.userPreferenceService.reload();
if (this.form.controls.overwriteFileOption.enabled && !this.form.controls.overwriteFileOption.value) {
await this.#userPreferenceService.saveOverwriteFileOption('undefined');
}
await this.#userPreferenceService.reload();
this.#patchValues();
this.initialFormValue = this.form.getRawValue();
this._changeRef.markForCheck();
this.#changeRef.markForCheck();
}
#patchValues() {
this.form.patchValue({
autoExpandFiltersOnActions: this.userPreferenceService.getAutoExpandFiltersOnActions(),
openScmDialogByDefault: this.userPreferenceService.getOpenScmDialogByDefault(),
tableExtractionType: this.userPreferenceService.getTableExtractionType(),
loadAllAnnotationsWarning: this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning),
helpModeDialog: this.userPreferenceService.getBool(KEYS.helpModeDialog),
autoExpandFiltersOnActions: this.#userPreferenceService.getAutoExpandFiltersOnActions(),
tableExtractionType: this.#userPreferenceService.getTableExtractionType(),
loadAllAnnotationsWarning: this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning),
helpModeDialog: this.#userPreferenceService.getBool(KEYS.helpModeDialog),
overwriteFileOption: this.#isOverwriteFileOptionActive,
});
if (!this.#isOverwriteFileOptionActive) {
this.form.controls.overwriteFileOption.disable();
}
}
}

View File

@ -6,9 +6,11 @@
<div class="table-header">Key</div>
<div class="table-header">Value</div>
<ng-container *ngFor="let entry of data.auditEntry.details | keyvalue : originalOrder">
<ng-container *ngFor="let entry of data.auditEntry.details | keyvalue: originalOrder">
<div class="bold">{{ entry.key | humanize }}</div>
<div>{{ entry.value }}</div>
<div>
<pre>{{ entry.value | json }}</pre>
</div>
</ng-container>
</div>
</div>

View File

@ -1,4 +1,4 @@
import { KeyValue, KeyValuePipe, NgForOf } from '@angular/common';
import { JsonPipe, KeyValue, KeyValuePipe, NgForOf } from '@angular/common';
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { BaseDialogComponent, CircleButtonComponent, HumanizePipe } from '@iqser/common-ui';
@ -17,7 +17,7 @@ type OrderFn = (a: KeyValue<string, string>, b: KeyValue<string, string>) => num
styleUrls: ['./audit-info-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [TranslateModule, NgForOf, KeyValuePipe, HumanizePipe, CircleButtonComponent],
imports: [TranslateModule, NgForOf, KeyValuePipe, HumanizePipe, CircleButtonComponent, JsonPipe],
})
export class AuditInfoDialogComponent extends BaseDialogComponent {
constructor(

View File

@ -19,7 +19,7 @@
</div>
</div>
<div class="content">
<div class="components-list" (cdkDropListDropped)="drop($event)" cdkDropList>
<div class="components-list">
<div class="header">
<div class="item-content">
<div class="all-caps-label">{{ 'component-definitions.columns.position' | translate }}</div>
@ -27,32 +27,37 @@
</div>
</div>
@for (component of componentDefinitions; track component) {
<div
class="list-item"
[class.selected]="selectedComponent?.id === component.id"
cdkDrag
(click)="selectComponent(component)"
>
<div class="item-content">
<div class="table-item-title heading">
<mat-icon cdkDragHandle class="draggable" svgIcon="red:draggable-dots"></mat-icon>
<span> {{ component.rank }} </span>
</div>
<div class="table-item-title heading">{{ component.displayName }}</div>
<div class="right-content">
@if (permissionsService.canEditEntities()) {
<iqser-circle-button
(action)="deleteComponent(component.id)"
[tooltip]="'trash.action.delete' | translate"
icon="iqser:trash"
></iqser-circle-button>
}
<mat-icon class="arrow-right" svgIcon="red:arrow-right"></mat-icon>
<div (cdkDropListDropped)="drop($event)" cdkDropList class="list-content">
@for (component of componentDefinitions; track component) {
<div
class="list-item"
[class.selected]="selectedComponent?.id === component.id"
cdkDrag
(click)="selectComponent(component)"
>
<div class="item-content">
<div class="table-item-title heading">
<mat-icon cdkDragHandle class="draggable" svgIcon="red:draggable-dots"></mat-icon>
<span> {{ component.rank }} </span>
</div>
<div class="table-item-title heading">{{ component.displayName }}</div>
<div class="right-content">
@if (permissionsService.canEditEntities()) {
<iqser-circle-button
[class]="'delete-component-definition'"
(action)="deleteComponent(component.id)"
[tooltip]="'trash.action.delete' | translate"
icon="iqser:trash"
></iqser-circle-button>
}
@if (selectedComponent?.id === component.id) {
<mat-icon class="arrow-right" svgIcon="red:arrow-right"></mat-icon>
}
</div>
</div>
</div>
</div>
}
}
</div>
</div>
@if (selectedComponent) {
<section class="dialog">

View File

@ -32,6 +32,10 @@
align-items: center;
gap: 5px;
iqser-circle-button {
visibility: hidden;
}
.arrow-right {
transform: scale(0.7);
}
@ -40,6 +44,7 @@
.content-container {
background-color: var(--iqser-grey-6);
height: 100vh;
.content-header {
display: flex;
@ -58,7 +63,7 @@
.content {
display: flex;
gap: 20px;
height: 100%;
overflow: hidden;
.components-list {
flex: 1;
@ -69,8 +74,15 @@
height: 30px;
}
.list-content {
overflow-y: auto;
height: calc(100vh - 30px);
outline: none;
}
.list-item {
height: 80px;
transition: background 0.3s ease;
}
.header,
@ -95,6 +107,10 @@
}
}
}
.list-item:hover > .item-content > .right-content > iqser-circle-button {
visibility: visible;
}
}
section {
@ -112,6 +128,9 @@
}
.cdk-drag-preview {
.list-item {
transition: background 0.3s ease;
}
.item-content {
@extend %item-content-style;
}

View File

@ -1,12 +1,5 @@
import { Component, OnInit, signal } from '@angular/core';
import {
BaseFormComponent,
CircleButtonComponent,
IconButtonComponent,
IqserDialog,
listingProvidersFactory,
LoadingService,
} from '@iqser/common-ui';
import { BaseFormComponent, CircleButtonComponent, IconButtonComponent, listingProvidersFactory, LoadingService } from '@iqser/common-ui';
import { ComponentDefinitionsService } from '@services/entity-services/component-definitions.service';
import { firstValueFrom } from 'rxjs';
import { getParam } from '@common-ui/utils';
@ -50,7 +43,6 @@ export default class ComponentDefinitionsComponent extends BaseFormComponent imp
constructor(
private readonly _loadingService: LoadingService,
private readonly _componentDefinitionsService: ComponentDefinitionsService,
private readonly _dialog: IqserDialog,
private readonly _formBuilder: FormBuilder,
private readonly _dialogService: AdminDialogService,
protected readonly permissionsService: PermissionsService,
@ -96,13 +88,15 @@ export default class ComponentDefinitionsComponent extends BaseFormComponent imp
}
async drop(event: CdkDragDrop<string>) {
moveItemInArray(this.#componentDefinitions(), event.previousIndex, event.currentIndex);
if (event.isPointerOverContainer) {
moveItemInArray(this.#componentDefinitions(), event.previousIndex, event.currentIndex);
const componentIds = this.#componentDefinitions().map(c => c.id);
const componentDefinitions = await firstValueFrom(
this._componentDefinitionsService.reorderComponentDefinitions(this.#dossierTemplateId, componentIds),
);
this.#componentDefinitions.set(componentDefinitions);
const componentIds = this.#componentDefinitions().map(c => c.id);
const componentDefinitions = await firstValueFrom(
this._componentDefinitionsService.reorderComponentDefinitions(this.#dossierTemplateId, componentIds),
);
this.#componentDefinitions.set(componentDefinitions);
}
}
async deleteComponent(componentId: string) {
@ -119,8 +113,8 @@ export default class ComponentDefinitionsComponent extends BaseFormComponent imp
#getForm() {
return this._formBuilder.group({
displayName: [this.selectedComponent.displayName, Validators.required],
description: [this.selectedComponent.description],
displayName: [this.selectedComponent?.displayName, Validators.required],
description: [this.selectedComponent?.description],
});
}

View File

@ -29,9 +29,8 @@
<div
class="row"
[class.disabled-file-options]="disabledFileOptions"
[matTooltip]="'add-edit-component-mapping.disabled-file-options' | translate"
[matTooltipDisabled]="!disabledFileOptions"
[matTooltipDisabled]="!form.get('encoding')?.disabled"
[matTooltipPosition]="'above'"
>
<div class="iqser-input-group required w-150">

View File

@ -18,14 +18,8 @@
font-size: 15px;
}
}
&.disabled-file-options {
opacity: 0.5;
pointer-events: auto;
max-width: 300px;
.iqser-input-group {
pointer-events: none;
}
}
}
.row:last-child {
max-width: 300px;
}

View File

@ -52,7 +52,6 @@ export class AddEditComponentMappingDialogComponent
{
protected readonly encodingTypeOptions = Object.keys(FileAttributeEncodingTypes);
protected readonly translations = fileAttributeEncodingTypesTranslations;
#fileChanged = false;
activeFile: File;
form!: UntypedFormGroup;
@ -73,13 +72,14 @@ export class AddEditComponentMappingDialogComponent
const file = new Blob([fileContent.body as Blob], { type: 'text/csv' });
this.form.get('file').setValue(file);
this.initialFormValue = this.form.getRawValue();
this.#disableEncodingAndDelimiter();
}
}
changeFile(file: File) {
this.#fileChanged = true;
this.form.get('file').setValue(file);
this.form.get('fileName').setValue(file?.name);
this.#enableEncodingAndDelimiter();
}
save() {
@ -96,7 +96,13 @@ export class AddEditComponentMappingDialogComponent
});
}
get disabledFileOptions() {
return this.initialFormValue?.file && !this.#fileChanged;
#disableEncodingAndDelimiter() {
this.form.get('encoding').disable();
this.form.get('delimiter').disable();
}
#enableEncodingAndDelimiter() {
this.form.get('encoding').enable();
this.form.get('delimiter').enable();
}
}

View File

@ -1,6 +1,5 @@
<div class="header-container">
<div [translate]="translations[type()]['title']" class="heading-l"></div>
<div [translate]="translations[type()]['warning-text']" class="error"></div>
<div [translate]="translations[this.type]['title']" class="heading-l"></div>
</div>
<div class="flex" style="height: 100%">

View File

@ -96,11 +96,13 @@ export class AdminSideNavComponent implements OnInit {
{
screen: 'entity-rules',
label: _('admin-side-nav.entity-rule-editor'),
helpModeKey: 'rule_editors',
show: (this.isIqserDevMode || this.canAccessRulesInDocumine) && this._permissionsService.has(Roles.rules.read),
},
{
screen: 'component-rules',
label: _('admin-side-nav.component-rule-editor'),
helpModeKey: 'rule_editors',
show:
this.isDocumine &&
(this.isIqserDevMode || this.canAccessRulesInDocumine) &&
@ -109,11 +111,13 @@ export class AdminSideNavComponent implements OnInit {
{
screen: 'component-mappings',
label: _('admin-side-nav.component-mappings'),
helpModeKey: 'component_mappings',
show: this.isDocumine,
},
{
screen: 'components',
label: _('admin-side-nav.components'),
helpModeKey: 'components_management',
show: this.isDocumine,
},
{

View File

@ -5,7 +5,6 @@ export const rulesScreenTranslations: Record<'ENTITY' | 'COMPONENT', Record<stri
'success.generic': _('entity-rules-screen.success.generic'),
'error.generic': _('entity-rules-screen.error.generic'),
title: _('entity-rules-screen.title'),
'warning-text': _('entity-rules-screen.warning-text'),
'errors-found': _('entity-rules-screen.errors-found'),
'warnings-found': _('entity-rules-screen.warnings-found'),
'save-changes': _('entity-rules-screen.save-changes'),
@ -15,7 +14,6 @@ export const rulesScreenTranslations: Record<'ENTITY' | 'COMPONENT', Record<stri
'success.generic': _('component-rules-screen.success.generic'),
'error.generic': _('component-rules-screen.error.generic'),
title: _('component-rules-screen.title'),
'warning-text': _('component-rules-screen.warning-text'),
'errors-found': _('component-rules-screen.errors-found'),
'warnings-found': _('entity-rules-screen.warnings-found'),
'save-changes': _('component-rules-screen.save-changes'),

View File

@ -7,7 +7,7 @@
>
<ng-container slot="right">
<redaction-file-download-btn
[attr.help-mode-key]="'download_dossier_in_dossier'"
[attr.help-mode-key]="isDocumine ? 'dossier_download_dossier' : 'download_dossier_in_dossier'"
[buttonId]="'download-files-btn'"
[disabled]="downloadFilesDisabled$ | async"
[dossier]="dossier"

View File

@ -1,6 +1,6 @@
<section>
<iqser-page-header [buttonConfigs]="buttonConfigs" [helpModeKey]="'dossier'">
<ng-container *ngIf="isArchiveEnabled" slot="beforeFilters">
<ng-container slot="beforeFilters">
<redaction-dossiers-type-switch></redaction-dossiers-type-switch>
</ng-container>
</iqser-page-header>

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { Dossier, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, DossierTemplate } from '@red/domain';
import { Dossier, DOSSIER_TEMPLATE_ID, DossierTemplate } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import {
ButtonConfig,
@ -19,7 +19,6 @@ import { UserPreferenceService } from '@users/user-preference.service';
import { SharedDialogService } from '@shared/services/dialog.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { OnAttach } from '@iqser/common-ui/lib/utils';
import { FeaturesService } from '@services/features.service';
import { DossiersTypeSwitchComponent } from '@shared/components/dossiers-type-switch/dossiers-type-switch.component';
import { TranslateModule } from '@ngx-translate/core';
import { DossiersListingDetailsComponent } from '../components/dossiers-listing-details/dossiers-listing-details.component';
@ -51,7 +50,6 @@ export class DossiersListingScreenComponent extends ListingComponent<Dossier> im
readonly buttonConfigs: ButtonConfig[];
readonly dossierTemplate: DossierTemplate;
readonly computeFilters$ = this._activeDossiersService.all$.pipe(tap(() => this._computeAllFilters()));
readonly isArchiveEnabled = this._featuresService.isEnabled(DOSSIERS_ARCHIVE);
@ViewChild('needsWorkFilterTemplate', {
read: TemplateRef,
static: true,
@ -68,7 +66,6 @@ export class DossiersListingScreenComponent extends ListingComponent<Dossier> im
private readonly _userPreferenceService: UserPreferenceService,
private readonly _loadingService: LoadingService,
readonly dossierTemplatesService: DossierTemplatesService,
private readonly _featuresService: FeaturesService,
) {
super();
const dossierTemplateId = router.routerState.snapshot.root.firstChild.firstChild.paramMap.get(DOSSIER_TEMPLATE_ID);

View File

@ -6,7 +6,7 @@
class="mt-6 mr-10"
></redaction-annotation-icon>
<div [class.flex-1]="!isDocumine">
<div class="flex-1">
<div>
<strong>{{ annotation.superTypeLabel | translate }}</strong>
&nbsp;

View File

@ -58,14 +58,17 @@
}
}
&:hover,
&.help-mode {
&:hover {
background-color: var(--iqser-annotation-hover);
::ng-deep .annotation-actions {
display: flex;
}
}
&.active {
background-color: var(--iqser-annotation-hover);
}
}
.hide-comments {

View File

@ -12,6 +12,8 @@
<iqser-circle-button
(action)="edit()"
[tooltip]="'component-management.actions.edit' | translate"
[attr.help-mode-key]="'editor_edit_component'"
[class.help-mode]="helpModeService.isHelpModeActive()"
icon="iqser:edit"
></iqser-circle-button>
@if (hasUpdatedValues) {
@ -26,17 +28,19 @@
<div cdkDrag class="editing-value">
<mat-icon
[class.hidden-button]="entry.componentValues.length === 1"
[attr.help-mode-key]="'change_component_order'"
cdkDragHandle
class="draggable"
svgIcon="red:draggable-dots"
></mat-icon>
<div class="iqser-input-group w-full">
<div [attr.help-mode-key]="'edit_component'" class="iqser-input-group w-full">
<textarea [id]="'value-input-' + $index" [(ngModel)]="value.value" rows="1" type="text"></textarea>
</div>
<iqser-circle-button
(action)="removeValue($index)"
[tooltip]="'component-management.actions.delete' | translate"
[class.hidden-button]="entry.componentValues.length === 1"
[attr.help-mode-key]="'remove_component_value'"
class="remove-value"
icon="iqser:trash"
></iqser-circle-button>
@ -56,6 +60,7 @@
<iqser-circle-button
(action)="undo()"
[tooltip]="'component-management.actions.undo' | translate"
[attr.help-mode-key]="'undo_component_change'"
class="undo-value"
icon="red:undo"
showDot
@ -64,6 +69,7 @@
<iqser-circle-button
(action)="add()"
[tooltip]="'component-management.actions.add' | translate"
[attr.help-mode-key]="'add_component_value'"
class="add-value"
icon="iqser:plus"
></iqser-circle-button>

View File

@ -29,6 +29,11 @@
iqser-circle-button {
visibility: hidden;
&.help-mode {
visibility: visible;
z-index: 600;
}
}
.changes-dot {

View File

@ -1,9 +1,9 @@
import { CdkDrag, CdkDragDrop, CdkDragHandle, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
import { KeyValuePipe, NgClass, NgForOf, NgIf } from '@angular/common';
import { AsyncPipe, KeyValuePipe, NgClass, NgForOf, NgIf } from '@angular/common';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatIcon } from '@angular/material/icon';
import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, IqserDialog } from '@iqser/common-ui';
import { CircleButtonComponent, HelpModeService, IconButtonComponent, IconButtonTypes, IqserDialog } from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { IComponentLogEntry, IComponentValue } from '@red/domain';
import { RevertValueDialogComponent } from '../../dialogs/docu-mine/revert-value-dialog/revert-value-dialog.component';
@ -27,6 +27,7 @@ import { FilePreviewStateService } from '../../services/file-preview-state.servi
NgForOf,
CdkDragHandle,
FormsModule,
AsyncPipe,
],
})
export class EditableStructuredComponentValueComponent implements OnInit {
@ -43,6 +44,7 @@ export class EditableStructuredComponentValueComponent implements OnInit {
selected = false;
constructor(
readonly helpModeService: HelpModeService,
private readonly _iqserDialog: IqserDialog,
private readonly _state: FilePreviewStateService,
) {}

View File

@ -44,34 +44,9 @@
</ng-template>
</ng-container>
<div *ngIf="multiSelectService.active()" class="multi-select">
<div class="selected-wrapper">
<iqser-round-checkbox
(click)="annotationManager.deselect()"
[indeterminate]="listingService.areSomeSelected$ | async"
type="with-bg"
></iqser-round-checkbox>
<span class="all-caps-label">{{ listingService.selectedLength$ | async }} selected </span>
<redaction-annotation-actions
*ngIf="listingService.areSomeSelected$ | async"
[alwaysVisible]="true"
[annotations]="listingService.selectedEntities$ | async"
[canPerformAnnotationActions]="state.isWritable()"
buttonType="primary"
tooltipPosition="above"
></redaction-annotation-actions>
</div>
<iqser-circle-button
(action)="multiSelectService.deactivate()"
[tooltip]="'file-preview.tabs.multi-select.close' | translate"
[type]="circleButtonTypes.primary"
icon="iqser:close"
tooltipPosition="before"
></iqser-circle-button>
</div>
@if (!isDocumine) {
<ng-container [ngTemplateOutlet]="multiSelectTemplate()"></ng-container>
}
<div class="annotations-wrapper" [class.documine-direction]="isDocumine">
<div [class.border-left]="isDocumine">
@ -158,11 +133,14 @@
</div>
</div>
@if (isDocumine) {
<ng-container [ngTemplateOutlet]="multiSelectTemplate()"></ng-container>
}
<div
#annotationsElement
(keydown)="preventKeyDefault($event)"
(keyup)="preventKeyDefault($event)"
[class.active-panel]="!pagesPanelActive"
[hidden]="excludedPagesService.shown()"
class="annotations"
id="annotations-list"
@ -246,7 +224,7 @@
</ng-template>
<ng-template #documineHeader>
<span [translate]="'annotations'"></span>
<span [translate]="'annotations'" [attr.help-mode-key]="'annotations_list'"></span>
<ng-container *ngTemplateOutlet="annotationsFilter"></ng-container>
</ng-template>
@ -254,7 +232,7 @@
<iqser-popup-filter
*ngIf="documentInfoService.hidden() || isDocumine"
[actionsTemplate]="annotationFilterActionTemplate"
[attr.help-mode-key]="'workload_filter'"
[attr.help-mode-key]="isDocumine ? 'filter_annotations' : 'workload_filter'"
[fileId]="state.file()?.id"
[primaryFiltersSlug]="'primaryFilters'"
[secondaryFiltersSlug]="'secondaryFilters'"
@ -264,3 +242,34 @@
<ng-template #documentInfo>
<redaction-document-info id="document-info"></redaction-document-info>
</ng-template>
<ng-template #multiSelect>
<div *ngIf="multiSelectService.active()" [class.documine-width]="isDocumine" class="multi-select">
<div class="selected-wrapper">
<iqser-round-checkbox
(click)="annotationManager.deselect()"
[indeterminate]="listingService.areSomeSelected$ | async"
type="with-bg"
></iqser-round-checkbox>
<span class="all-caps-label">{{ listingService.selectedLength$ | async }} selected </span>
<redaction-annotation-actions
*ngIf="listingService.areSomeSelected$ | async"
[alwaysVisible]="true"
[annotations]="listingService.selectedEntities$ | async"
[canPerformAnnotationActions]="state.isWritable()"
buttonType="primary"
tooltipPosition="above"
></redaction-annotation-actions>
</div>
<iqser-circle-button
(action)="multiSelectService.deactivate()"
[tooltip]="'file-preview.tabs.multi-select.close' | translate"
[type]="circleButtonTypes.primary"
icon="iqser:close"
tooltipPosition="before"
></iqser-circle-button>
</div>
</ng-template>

View File

@ -1,5 +1,16 @@
import { AsyncPipe, NgIf, NgTemplateOutlet } from '@angular/common';
import { ChangeDetectorRef, Component, computed, effect, ElementRef, HostListener, OnDestroy, OnInit, ViewChild } from '@angular/core';
import {
ChangeDetectorRef,
Component,
computed,
effect,
ElementRef,
HostListener,
OnDestroy,
OnInit,
TemplateRef,
viewChild,
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatIcon } from '@angular/material/icon';
import { MatTooltip } from '@angular/material/tooltip';
@ -20,7 +31,7 @@ import { AutoUnsubscribe, Debounce, IqserEventTarget } from '@iqser/common-ui/li
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ListItem } from '@models/file/list-item';
import { TranslateModule } from '@ngx-translate/core';
import { ComponentLogEntry, WorkflowFileStatuses } from '@red/domain';
import { WorkflowFileStatuses } from '@red/domain';
import { workloadTranslations } from '@translations/workload-translations';
import { UserPreferenceService } from '@users/user-preference.service';
import { getLocalStorageDataByFileId } from '@utils/local-storage';
@ -78,8 +89,9 @@ const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
],
})
export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
@ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
private readonly _annotationsElement = viewChild<ElementRef>('annotationsElement');
private readonly _quickNavigationElement = viewChild<ElementRef>('quickNavigation');
readonly multiSelectTemplate = viewChild<TemplateRef<any>>('multiSelect');
readonly #isIqserDevMode = this._userPreferenceService.isIqserDevMode;
protected readonly iconButtonTypes = IconButtonTypes;
protected readonly circleButtonTypes = CircleButtonTypes;
@ -94,6 +106,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
protected displayedPages: number[] = [];
protected pagesPanelActive = true;
protected enabledFilters = [];
#displayedPagesChanged = false;
constructor(
readonly filterService: FilterService,
@ -279,18 +292,20 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed'): void {
if (this._annotationsElement) {
const elements: HTMLElement[] = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`);
if (this._annotationsElement()) {
const elements: HTMLElement[] = this._annotationsElement().nativeElement.querySelectorAll(
`div[anotation-page-header="${page}"]`,
);
FileWorkloadComponent._scrollToFirstElement(elements, mode);
}
}
@Debounce()
scrollToSelectedAnnotation(): void {
if (this.listingService.selected.length === 0 || !this._annotationsElement) {
if (this.listingService.selected.length === 0 || !this._annotationsElement()) {
return;
}
const elements: HTMLElement[] = this._annotationsElement.nativeElement.querySelectorAll(
const elements: HTMLElement[] = this._annotationsElement().nativeElement.querySelectorAll(
`[annotation-id="${this._firstSelectedAnnotation?.id}"]`,
);
FileWorkloadComponent._scrollToFirstElement(elements);
@ -407,7 +422,8 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
): Map<number, AnnotationWrapper[]> {
const onlyPageWithAnnotations = this.viewModeService.onlyPagesWithAnnotations();
if (!primary || primary.length === 0) {
this.displayedPages = onlyPageWithAnnotations ? [] : this.#allPages;
const pages = onlyPageWithAnnotations ? [] : this.#allPages;
this.#setDisplayedPages(pages);
return;
}
@ -429,15 +445,16 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
this.enabledFilters = this.filterService.enabledFlatFilters;
if (this.enabledFilters.some(f => f.id === 'pages-without-annotations')) {
if (this.enabledFilters.length === 1 && !onlyPageWithAnnotations) {
this.displayedPages = this.#allPages.filter(page => !pagesThatDisplayAnnotations.includes(page));
const pages = this.#allPages.filter(page => !pagesThatDisplayAnnotations.includes(page));
this.#setDisplayedPages(pages);
} else {
this.displayedPages = [];
this.#setDisplayedPages([]);
}
this.displayedAnnotations.clear();
} else if (this.enabledFilters.length || onlyPageWithAnnotations || componentReferenceIds) {
this.displayedPages = pagesThatDisplayAnnotations;
this.#setDisplayedPages(pagesThatDisplayAnnotations);
} else {
this.displayedPages = this.#allPages;
this.#setDisplayedPages(this.#allPages);
}
this.displayedPages.sort((a, b) => a - b);
@ -511,8 +528,8 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
#scrollQuickNavigationToPage(page: number) {
if (this._quickNavigationElement) {
const elements: HTMLElement[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`);
if (this._quickNavigationElement()) {
const elements: HTMLElement[] = this._quickNavigationElement().nativeElement.querySelectorAll(`#quick-nav-page-${page}`);
FileWorkloadComponent._scrollToFirstElement(elements);
}
}
@ -533,9 +550,25 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}
#scrollToFirstAnnotationPage(annotations: Map<number, ListItem<AnnotationWrapper>[]>) {
if (this.isDocumine && annotations.size && !this.displayedPages.includes(this.pdf.currentPage())) {
if (this.isDocumine && annotations.size && this.#displayedPagesChanged && !this.displayedPages.includes(this.pdf.currentPage())) {
const page = annotations.keys().next().value;
this.pdf.navigateTo(page);
}
}
#setDisplayedPages(newDisplayedPages: number[]) {
if (!this.#samePages(newDisplayedPages)) {
this.displayedPages = newDisplayedPages;
this.#displayedPagesChanged = true;
return;
}
this.#displayedPagesChanged = false;
}
#samePages(newDisplayedPages: number[]) {
return (
this.displayedPages.length === newDisplayedPages.length &&
this.displayedPages.every((value, index) => value === newDisplayedPages[index])
);
}
}

View File

@ -1,9 +1,9 @@
<div class="components-header">
<span [translate]="'component-management.components'"></span>
<iqser-popup-filter [primaryFiltersSlug]="'componentLogFilters'"></iqser-popup-filter>
<span [translate]="'component-management.components'" [attr.help-mode-key]="'components_table'"></span>
<iqser-popup-filter [primaryFiltersSlug]="'componentLogFilters'" [attr.help-mode-key]="'filter_components'"></iqser-popup-filter>
</div>
<div *ngIf="displayedComponents$ | async as displayedComponents" class="components-container">
<div *ngIf="displayedComponents$ | async as displayedComponents" class="components-container" id="components-view">
<div class="component-row">
<div class="header">
<div class="component">{{ 'component-management.table-header.component' | translate }}</div>

View File

@ -1,4 +1,4 @@
<section class="dialog">
<section [attr.help-mode-key]="'edit_annotation_DIALOG'" class="dialog">
<form (submit)="save()" [formGroup]="form">
<div [translate]="'edit-redaction.dialog.title'" class="dialog-header heading-l"></div>
@ -52,6 +52,8 @@
</iqser-icon-button>
<div [translate]="'edit-redaction.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
<iqser-help-button></iqser-help-button>
</div>
</form>

View File

@ -1,6 +1,13 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
import { CircleButtonComponent, HasScrollbarDirective, IconButtonComponent, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
import {
CircleButtonComponent,
HasScrollbarDirective,
HelpButtonComponent,
IconButtonComponent,
IconButtonTypes,
IqserDialogComponent,
} from '@iqser/common-ui';
import { Dictionary, Dossier, SuperTypes } from '@red/domain';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { DictionaryService } from '@services/entity-services/dictionary.service';
@ -33,6 +40,7 @@ import { MatDialogClose } from '@angular/material/dialog';
IconButtonComponent,
CircleButtonComponent,
MatDialogClose,
HelpButtonComponent,
],
})
export class EditAnnotationDialogComponent

View File

@ -1,4 +1,4 @@
<section class="dialog">
<section [attr.help-mode-key]="'remove_annotation_DIALOG'" class="dialog">
<form (submit)="save()" [formGroup]="form">
<div
[innerHTML]="'remove-annotation.dialog.title' | translate: { count: data.redactions.length }"
@ -27,7 +27,7 @@
| translate
: {
text: text,
context: data.falsePositiveContext[idx]
context: data.falsePositiveContext[idx],
}
| replaceNbsp
}}
@ -60,6 +60,7 @@
</iqser-icon-button>
<div [translate]="'remove-annotation.dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
<iqser-help-button></iqser-help-button>
</div>
</form>

View File

@ -6,7 +6,14 @@ import { MatDialogClose } from '@angular/material/dialog';
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
import { ReplaceNbspPipe } from '@common-ui/pipes/replace-nbsp.pipe';
import { CircleButtonComponent, HasScrollbarDirective, IconButtonComponent, IconButtonTypes, IqserDialogComponent } from '@iqser/common-ui';
import {
CircleButtonComponent,
HasScrollbarDirective,
HelpButtonComponent,
IconButtonComponent,
IconButtonTypes,
IqserDialogComponent,
} from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { getRemoveRedactionOptions } from '../../../utils/dialog-options';
import { RemoveAnnotationData, RemoveAnnotationOption, RemoveAnnotationOptions, RemoveAnnotationResult } from '../../../utils/dialog-types';
@ -28,6 +35,7 @@ import { RemoveAnnotationData, RemoveAnnotationOption, RemoveAnnotationOptions,
CircleButtonComponent,
MatDialogClose,
NgIf,
HelpButtonComponent,
],
})
export class RemoveAnnotationDialogComponent extends IqserDialogComponent<

View File

@ -7,7 +7,7 @@ import {
SearchPositions,
TableColumnConfig,
} from '@iqser/common-ui';
import { combineLatest, Observable, of } from 'rxjs';
import { combineLatest, Observable } from 'rxjs';
import { debounceTime, map, startWith, switchMap, tap } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -16,7 +16,6 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { RouterHistoryService } from '@services/router-history.service';
import {
Dossier,
DOSSIERS_ARCHIVE,
DossierTemplate,
IMatchedDocument,
ISearchListItem,
@ -26,7 +25,6 @@ import {
} from '@red/domain';
import { FilesMapService } from '@services/files/files-map.service';
import { PlatformSearchService } from '@services/entity-services/platform-search.service';
import { FeaturesService } from '@services/features.service';
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { UserService } from '@users/user.service';
@ -68,7 +66,6 @@ export default class SearchScreenComponent extends ListingComponent<ISearchListI
private readonly _translateService: TranslateService,
private readonly _filesMapService: FilesMapService,
private readonly _platformSearchService: PlatformSearchService,
private readonly _featuresService: FeaturesService,
private readonly _dossierTemplateService: DossierTemplatesService,
private readonly _userService: UserService,
) {
@ -134,14 +131,8 @@ export default class SearchScreenComponent extends ListingComponent<ISearchListI
);
}
get #enabledArchive(): boolean {
return this._featuresService.isEnabled(DOSSIERS_ARCHIVE);
}
get #filtersChanged$(): Observable<[string[], WorkflowFileStatus, string, string[], boolean]> {
const onlyActiveDossiers$ = this.#enabledArchive
? this.filterService.getSingleFilter('onlyActiveDossiers').pipe(map(f => !!f.checked))
: of(true);
const onlyActiveDossiers$ = this.filterService.getSingleFilter('onlyActiveDossiers').pipe(map(f => !!f.checked));
const filterGroups$ = this.filterService.filterGroups$;
return combineLatest([filterGroups$, onlyActiveDossiers$]).pipe(
map(([groups, onlyActive]) => {
@ -225,9 +216,7 @@ export default class SearchScreenComponent extends ListingComponent<ISearchListI
this.filterService.addFilterGroups([dossierNameFilter, workflowStatusFilter, assigneeFilter]);
const onlyActiveLabel = this._translateService.instant('search-screen.filters.only-active');
if (this.#enabledArchive) {
this.filterService.addSingleFilter({ id: 'onlyActiveDossiers', label: onlyActiveLabel, checked: this.#routeOnlyActive });
}
this.filterService.addSingleFilter({ id: 'onlyActiveDossiers', label: onlyActiveLabel, checked: this.#routeOnlyActive });
}
#addTemplateFilter(templates: DossierTemplate[]) {

View File

@ -24,7 +24,7 @@
></iqser-circle-button>
<redaction-file-download-btn
[attr.help-mode-key]="'download_dossier'"
[attr.help-mode-key]="isDocumine ? 'template_download_dossier' : 'download_dossier'"
[buttonId]="'download-dossier-files-' + dossier.id"
[disabled]="downloadBtnDisabled"
[dossier]="dossier"

View File

@ -1,6 +1,6 @@
import { AsyncPipe, NgIf } from '@angular/common';
import { Component, Input, OnChanges } from '@angular/core';
import { CircleButtonComponent, IqserAllowDirective, IqserPermissionsService, largeDialogConfig } from '@iqser/common-ui';
import { CircleButtonComponent, getConfig, IqserAllowDirective, IqserPermissionsService, largeDialogConfig } from '@iqser/common-ui';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { TranslateModule } from '@ngx-translate/core';
import type { Dossier, File, User } from '@red/domain';
@ -23,6 +23,7 @@ import { DossiersDialogService } from '../../services/dossiers-dialog.service';
export class DossiersListingActionsComponent implements OnChanges {
readonly roles = Roles;
readonly currentUser = getCurrentUser<User>();
readonly isDocumine = getConfig().IS_DOCUMINE;
analysisForced: boolean;
files: File[];

View File

@ -128,7 +128,7 @@ export class FileActionsComponent implements OnChanges {
tooltipClass: 'small',
show: this.showDownload,
disabled: this.file.processingStatus === ProcessingFileStatuses.ERROR,
helpModeKey: 'download',
helpModeKey: this.#isDocumine ? 'download_document' : 'download',
},
{
id: 'btn-delete_file',

View File

@ -15,6 +15,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { HeadersConfiguration } from '@iqser/common-ui/lib/utils';
import { LicenseService } from '@services/license.service';
import { LicenseFeatures } from '../../admin/screens/license/utils/constants';
import { UserPreferenceService } from '@users/user-preference.service';
export interface ActiveUpload {
subscription: Subscription;
@ -43,6 +44,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
private readonly _errorMessageService: ErrorMessageService,
private readonly _licenseService: LicenseService,
private readonly _toaster: Toaster,
private readonly _userPreferenceService: UserPreferenceService,
) {
super();
const fileFetch$ = this.#fetchFiles$.pipe(
@ -76,7 +78,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
const maxSizeBytes = maxSizeMB * 1024 * 1024;
const dossierFiles = this._filesMapService.get(dossierId);
const supportMsOfficeFormats = this._licenseService.getFeature(LicenseFeatures.SUPPORT_MS_OFFICE_FORMATS)?.value as boolean;
let option: OverwriteFileOption = localStorage.getItem('overwriteFileOption') as OverwriteFileOption;
let option: OverwriteFileOption | 'undefined' = this._userPreferenceService.getOverwriteFileOption();
for (let idx = 0; idx < files.length; ++idx) {
const file = files[idx];
let currentOption = option;
@ -95,14 +97,14 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
}
}
} else if (dossierFiles.find(pf => pf.filename === file.file.name)) {
if (!option) {
if (option === 'undefined') {
const res = await this._dialogService.openOverwriteFileDialog(file.file.name);
if (res.cancel) {
return;
}
if (res.rememberChoice) {
localStorage.setItem('overwriteFileOption', res.option);
await this._userPreferenceService.saveOverwriteFileOption(res.option);
}
currentOption = res.option;

View File

@ -1,12 +1,11 @@
import { inject, Injectable } from '@angular/core';
import { ARCHIVE_ROUTE, Dossier, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain';
import { ARCHIVE_ROUTE, Dossier, DOSSIERS_ROUTE } from '@red/domain';
import { catchError, switchMap, tap } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ActiveDossiersService } from './active-dossiers.service';
import { DossiersService } from './dossiers.service';
import { FilesMapService } from '../files/files-map.service';
import { FeaturesService } from '../features.service';
import { Router } from '@angular/router';
@Injectable({ providedIn: 'root' })
@ -15,7 +14,6 @@ export class ArchivedDossiersService extends DossiersService {
protected readonly _defaultModelPath = 'archived-dossiers';
readonly #activeDossiersService = inject(ActiveDossiersService);
readonly #filesMapService = inject(FilesMapService);
readonly #featuresService = inject(FeaturesService);
readonly #router = inject(Router);
archive(dossiers: Dossier[]): Observable<unknown> {
@ -46,7 +44,7 @@ export class ArchivedDossiersService extends DossiersService {
}
loadAll(): Observable<Dossier[]> {
return this.#featuresService.isEnabled(DOSSIERS_ARCHIVE) ? super.loadAll() : of([]);
return super.loadAll();
}
#removeFromActiveDossiers(archivedDossiersIds: string[]): void {

View File

@ -10,21 +10,24 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { mapEach } from '@iqser/common-ui/lib/utils';
const CONFLICT_MSG = _('dossier-states-listing.error.conflict');
const GENERIC_MSG = _('dossier-states-listing.error.generic');
@Injectable({
providedIn: 'root',
})
export class DossierStatesService extends EntitiesService<IDossierState, DossierState> {
protected readonly _defaultModelPath = 'dossier-status';
protected readonly _entityClass = DossierState;
readonly #toaster = inject(Toaster);
readonly #dossierTemplatesService = inject(DossierTemplatesService);
readonly #dossierStatesMapService = inject(DossierStatesMapService);
protected readonly _defaultModelPath = 'dossier-status';
protected readonly _entityClass = DossierState;
async createOrUpdate(state: IDossierState) {
const showToast = (error: HttpErrorResponse) => {
this.#toaster.error(error.status === HttpStatusCode.Conflict ? CONFLICT_MSG : GENERIC_MSG);
if (error.status === HttpStatusCode.Conflict) {
this.#toaster.error(CONFLICT_MSG);
} else {
this.#toaster.rawError(error.error.message);
}
return EMPTY;
};

View File

@ -4,7 +4,6 @@ import {
DashboardStats,
Dictionary,
Dossier,
DOSSIERS_ARCHIVE,
DossierTemplate,
File,
IComment,
@ -13,7 +12,6 @@ import {
WorkflowFileStatus,
WorkflowFileStatuses,
} from '@red/domain';
import { FeaturesService } from '@services/features.service';
import { FilesMapService } from '@services/files/files-map.service';
import { Roles } from '@users/roles';
import { UserPreferenceService } from '@users/user-preference.service';
@ -27,7 +25,6 @@ export class PermissionsService {
constructor(
private readonly _userService: UserService,
private readonly _filesMapService: FilesMapService,
private readonly _featuresService: FeaturesService,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _iqserPermissionsService: IqserPermissionsService,
private readonly _dossierTemplatesService: DossierTemplatesService,
@ -290,12 +287,7 @@ export class PermissionsService {
}
canArchiveDossier(dossier: Dossier): boolean {
return (
this._iqserPermissionsService.has(Roles.dossiers.archived) &&
this._featuresService.isEnabled(DOSSIERS_ARCHIVE) &&
dossier.isActive &&
this.isOwner(dossier)
);
return this._iqserPermissionsService.has(Roles.dossiers.archived) && dossier.isActive && this.isOwner(dossier);
}
canEditDossier(dossier: Dossier): boolean {

View File

@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { IqserUserPreferenceService, ListingMode } from '@iqser/common-ui';
import { SystemDefaultOption, SystemDefaultType } from '../modules/account/utils/dialog-defaults';
import { RedactOrHintOption, RemoveRedactionOption } from '../modules/file-preview/utils/dialog-types';
import { OverwriteFileOption } from '@red/domain';
export const PreferencesKeys = {
dossierRecent: 'Dossier-Recent',
@ -10,7 +11,6 @@ export const PreferencesKeys = {
filesListingMode: 'Files-Listing-Mode',
autoExpandFiltersOnActions: 'Auto-Expand-Filters-On-Actions',
loadAllAnnotationsWarning: 'Load-All-Annotations-Warning',
openScmDialogByDefault: 'Open-Structured-Component-Management-By-Default',
tableExtractionType: 'Table-Extraction-Type',
addRedactionDefaultOption: 'Add-Redaction-Default',
addHintDefaultOption: 'Add-Hint-Default',
@ -22,6 +22,7 @@ export const PreferencesKeys = {
removeRedactionDefaultExtraOption: 'Remove-Redaction-Default-Extra',
removeRecommendationDefaultExtraOption: 'Remove-Recommendation-Default-Extra',
removeHintDefaultExtraOption: 'Remove-Hint-Default-Extra',
rememberOverwriteFileOption: 'Remember-Overwrite-File',
} as const;
@Injectable({
@ -65,15 +66,6 @@ export class UserPreferenceService extends IqserUserPreferenceService {
await this.save(PreferencesKeys.autoExpandFiltersOnActions, nextValue);
}
getOpenScmDialogByDefault(): boolean {
return this._getAttribute(PreferencesKeys.openScmDialogByDefault, 'false') === 'true';
}
async toggleOpenScmDialogByDefault(): Promise<void> {
const nextValue = (!this.getOpenScmDialogByDefault()).toString();
await this.save(PreferencesKeys.openScmDialogByDefault, nextValue);
}
getBool(key: string, defaultValue = 'false') {
return this._getAttribute(key, defaultValue) === 'true';
}
@ -179,4 +171,12 @@ export class UserPreferenceService extends IqserUserPreferenceService {
async saveRemoveRecommendationDefaultExtraOption(defaultOption: boolean | string): Promise<void> {
await this.save(PreferencesKeys.removeRecommendationDefaultExtraOption, defaultOption.toString());
}
getOverwriteFileOption(): OverwriteFileOption | 'undefined' {
return this._getAttribute(PreferencesKeys.rememberOverwriteFileOption, 'undefined') as OverwriteFileOption | 'undefined';
}
async saveOverwriteFileOption(preference: OverwriteFileOption | 'undefined') {
await this.save(PreferencesKeys.rememberOverwriteFileOption, preference);
}
}

View File

@ -1,8 +1,3 @@
{
"features": [
{
"name": "DOSSIERS_ARCHIVE",
"minVersion": "3.3.0"
}
]
"features": []
}

View File

@ -651,6 +651,11 @@
"documentKey": "workload_filter",
"overlappingElements": ["USER_MENU", "DOCUMENT_INFO"]
},
{
"elementKey": "filter_annotations",
"documentKey": "filter_annotations",
"overlappingElements": ["USER_MENU", "DOCUMENT_INFO"]
},
{
"elementKey": "workload_bulk_selection",
"documentKey": "workload_bulk_selection",
@ -692,5 +697,86 @@
{
"elementKey": "document_list_view",
"documentKey": "document_list"
},
{
"elementKey": "editor_edit_component",
"documentKey": "editor_edit_component",
"scrollableParentView": "COMPONENTS_VIEW"
},
{
"elementKey": "edit_component",
"documentKey": "edit_component"
},
{
"elementKey": "add_component_value",
"documentKey": "add_component_value"
},
{
"elementKey": "undo_component_change",
"documentKey": "undo_component_change"
},
{
"elementKey": "change_component_order",
"documentKey": "change_component_order"
},
{
"elementKey": "remove_component_value",
"documentKey": "remove_component_value"
},
{
"elementKey": "filter_components",
"documentKey": "filter_components"
},
{
"elementKey": "annotations_list",
"documentKey": "annotations_list"
},
{
"elementKey": "components_table",
"documentKey": "components_table"
},
{
"elementKey": "components_table",
"documentKey": "components_table"
},
{
"elementKey": "remove_annotation_DIALOG",
"documentKey": "remove_annotation"
},
{
"elementKey": "edit_annotation_DIALOG",
"documentKey": "edit_annotation"
},
{
"elementKey": "dossier_download_dossier",
"documentKey": "dossier_download_dossier",
"overlappingElements": ["USER_MENU"]
},
{
"elementKey": "dossier_download_document",
"documentKey": "dossier_download_document",
"scrollableParentView": "VIRTUAL_SCROLL"
},
{
"elementKey": "template_download_dossier",
"documentKey": "template_download_dossier",
"scrollableParentView": "VIRTUAL_SCROLL"
},
{
"elementKey": "editor_download_document",
"documentKey": "editor_download_document",
"overlappingElements": ["USER_MENU"]
},
{
"elementKey": "rule_editors",
"documentKey": "rule_editors"
},
{
"elementKey": "component_mappings",
"documentKey": "component_mappings"
},
{
"elementKey": "components_management",
"documentKey": "components_management"
}
]

View File

@ -587,8 +587,7 @@
"success": {
"generic": ""
},
"title": "",
"warning-text": ""
"title": ""
},
"configurations": "Konfiguration",
"confirm-archive-dossier": {
@ -1066,8 +1065,7 @@
"dossier-states": "{count, plural, one{Dossier-Status} other{Dossier-Status}}"
},
"error": {
"conflict": "Es gibt bereits einen Dossier-Status mit diesem Namen.",
"generic": "Speichern des Dossier-Status fehlgeschlagen."
"conflict": "Es gibt bereits einen Dossier-Status mit diesem Namen."
},
"no-data": {
"title": "Es wurde noch kein Dossier-Status angelegt."
@ -1360,7 +1358,6 @@
"generic": "Die Entitätsregeln wurden aktualisiert."
},
"title": "Entitätsregeln-Editor",
"warning-text": "Warnung: experimentelle Funktion!",
"warnings-found": "{warnings, plural, one{A warning} other{{warnings} warnings}} in Regeln gefunden"
},
"error": {
@ -2049,7 +2046,6 @@
"auto-expand-filters-on-action": "Filter ausgehend von meinen Aktionen automatisch anpassen",
"help-mode-dialog": "Dialog zur Aktivierung des Hilfemodus",
"load-all-annotations-warning": "Warnung bei gleichzeitigem Laden aller Annotationen in der Miniaturansicht",
"open-structured-view-by-default": "Strukturierte Komponentenansicht standardmäßig anzeigen",
"table-extraction-type": "Art der Tabellenextraktion"
},
"label": "Präferenzen",
@ -2587,4 +2583,4 @@
}
},
"yesterday": "Gestern"
}
}

View File

@ -587,8 +587,7 @@
"success": {
"generic": ""
},
"title": "",
"warning-text": ""
"title": ""
},
"configurations": "Configurations",
"confirm-archive-dossier": {
@ -1066,8 +1065,7 @@
"dossier-states": "{count, plural, one{Dossier state} other{Dossier states}}"
},
"error": {
"conflict": "Dossier state with this name already exists",
"generic": "Failed to save dossier state."
"conflict": "Dossier state with this name already exists"
},
"no-data": {
"title": "There are no dossier states."
@ -1351,7 +1349,6 @@
"generic": "Entity rules updated."
},
"title": "Entity rule editor",
"warning-text": "Warning: experimental feature!",
"warnings-found": "{warnings, plural, one{A warning} other{{warnings} warnings}} found in rules"
},
"entity": {
@ -2049,7 +2046,7 @@
"auto-expand-filters-on-action": "Auto-expand filters on my actions",
"help-mode-dialog": "Help mode activation dialog",
"load-all-annotations-warning": "Warning regarding simultaneous loading of all annotations in thumbnails",
"open-structured-view-by-default": "Display structured component management modal by default",
"overwrite-file-option": "Preferred action when re-uploading of an already existing file",
"table-extraction-type": "Table extraction type"
},
"label": "Preferences",
@ -2587,4 +2584,4 @@
}
},
"yesterday": "Yesterday"
}
}

View File

@ -587,8 +587,7 @@
"success": {
"generic": "Component rules updated!"
},
"title": "Component rule editor",
"warning-text": "Warning: experimental feature!"
"title": "Component rule editor"
},
"configurations": "Einstellungen",
"confirm-archive-dossier": {
@ -1066,8 +1065,7 @@
"dossier-states": "{count, plural, one{Dossier state} other{Dossier states}}"
},
"error": {
"conflict": "Dossier state with this name already exists!",
"generic": "Failed to save dossier state!"
"conflict": "Dossier state with this name already exists!"
},
"no-data": {
"title": "There are no dossier states."
@ -1360,7 +1358,6 @@
"generic": "Entity rules updated!"
},
"title": "Entity rule editor",
"warning-text": "Warning: experimental feature!",
"warnings-found": "{warnings, plural, one{A warning} other{{warnings} warnings}} found in rules"
},
"error": {
@ -2049,7 +2046,6 @@
"auto-expand-filters-on-action": "Auto expand filters on my actions",
"help-mode-dialog": "Help Mode Dialog",
"load-all-annotations-warning": "Warning regarding loading all annotations at once in file preview",
"open-structured-view-by-default": "Display Component View by default when opening a document",
"table-extraction-type": "Table extraction type"
},
"label": "Preferences",
@ -2587,4 +2583,4 @@
}
},
"yesterday": "Gestern"
}
}

View File

@ -122,7 +122,7 @@
"type": "Attribute type"
},
"save": "Save attribute",
"title": "{type, select, edit{Edit {name}} create{Add New} other{}} dossier attribute"
"title": "{type, select, edit{Edit {name}} create{Add new} other{}} dossier attribute"
},
"add-edit-dossier-state": {
"form": {
@ -178,7 +178,7 @@
"type": "Type"
},
"save": "Save attribute",
"title": "{type, select, edit{Edit {name}} create{Add New} other{}} file attribute"
"title": "{type, select, edit{Edit {name}} create{Add new} other{}} file attribute"
},
"add-edit-justification": {
"actions": {
@ -193,7 +193,7 @@
"reason": "Legal basis",
"reason-placeholder": "Enter legal basis"
},
"title": "{type, select, edit{Edit {name}} create{Add New} other{}} justification"
"title": "{type, select, edit{Edit {name}} create{Add new} other{}} justification"
},
"add-edit-user": {
"actions": {
@ -213,7 +213,7 @@
"reset-password": "Reset password",
"role": "Role"
},
"title": "{type, select, edit{Edit} create{Add New} other{}} user"
"title": "{type, select, edit{Edit} create{Add new} other{}} user"
},
"add-entity": {
"save": "Save entity",
@ -587,8 +587,7 @@
"success": {
"generic": "Component rules updated!"
},
"title": "Component rule editor",
"warning-text": "Warning: experimental feature!"
"title": "Component rule editor"
},
"configurations": "Configurations",
"confirm-archive-dossier": {
@ -1066,8 +1065,7 @@
"dossier-states": "{count, plural, one{Dossier state} other{Dossier states}}"
},
"error": {
"conflict": "Dossier state with this name already exists!",
"generic": "Failed to save dossier state!"
"conflict": "Dossier state with this name already exists!"
},
"no-data": {
"title": "There are no dossier states."
@ -1351,7 +1349,6 @@
"generic": "Entity rules updated!"
},
"title": "Entity rule editor",
"warning-text": "Warning: experimental feature!",
"warnings-found": "{warnings, plural, one{A warning} other{{warnings} warnings}} found in rules"
},
"entity": {
@ -2049,7 +2046,7 @@
"auto-expand-filters-on-action": "Auto expand filters on my actions",
"help-mode-dialog": "Help Mode Dialog",
"load-all-annotations-warning": "Warning regarding loading all annotations at once in file preview",
"open-structured-view-by-default": "Display Component View by default when opening a document",
"overwrite-file-option": "Preferred action when re-uploading of an already existing file",
"table-extraction-type": "Table extraction type"
},
"label": "Preferences",
@ -2587,4 +2584,4 @@
}
},
"yesterday": "Yesterday"
}
}

@ -1 +1 @@
Subproject commit 17943f2e8dcab28450e9102d6e8b7a0b5f7227db
Subproject commit 0ca5e7e2ad7ca2b788f7234d7a38a3b810d9b31a

View File

@ -1,5 +1,4 @@
export const DOSSIER_ID = 'dossierId';
export const DOSSIERS_ARCHIVE = 'DOSSIERS_ARCHIVE';
export const ARCHIVE_ROUTE = 'archive';
export const DOSSIERS_ROUTE = 'dossiers';