Merge branch 'master' into VM/RED-6012
This commit is contained in:
commit
de7c3e9c36
@ -1,12 +1,12 @@
|
||||
{
|
||||
"root": true,
|
||||
"ignorePatterns": ["**/*"],
|
||||
"plugins": ["@nrwl/nx"],
|
||||
"plugins": ["@nx"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"rules": {
|
||||
"@nrwl/nx/enforce-module-boundaries": [
|
||||
"@nx/enforce-module-boundaries": [
|
||||
"error",
|
||||
{
|
||||
"enforceBuildableLibDependency": true,
|
||||
@ -35,15 +35,14 @@
|
||||
},
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"extends": ["plugin:@nrwl/nx/typescript"]
|
||||
"extends": ["plugin:@nx/typescript"]
|
||||
},
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"extends": [
|
||||
"plugin:@nrwl/nx/angular",
|
||||
"plugin:@nx/angular",
|
||||
"eslint:recommended",
|
||||
"plugin:@angular-eslint/recommended",
|
||||
"plugin:@angular-eslint/recommended--extra",
|
||||
"plugin:@angular-eslint/template/process-inline-templates",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:rxjs/recommended"
|
||||
@ -80,7 +79,7 @@
|
||||
"accessibility": "no-public"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/member-ordering": "error",
|
||||
"@typescript-eslint/member-ordering": "warn",
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{
|
||||
@ -183,10 +182,9 @@
|
||||
},
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"extends": ["plugin:@nrwl/nx/angular-template", "plugin:@angular-eslint/template/recommended"]
|
||||
"extends": ["plugin:@nx/angular-template", "plugin:@angular-eslint/template/recommended"]
|
||||
},
|
||||
{
|
||||
// https://github.com/angular-eslint/angular-eslint#notes-for-eslint-plugin-prettier-users
|
||||
"files": ["*.html"],
|
||||
"excludedFiles": ["*inline-template-*.component.html"],
|
||||
"extends": ["plugin:prettier/recommended"],
|
||||
|
||||
144
angular.json
144
angular.json
@ -1,144 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"projects": {
|
||||
"red-domain": {
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "library",
|
||||
"root": "libs/red-domain",
|
||||
"sourceRoot": "libs/red-domain/src",
|
||||
"prefix": "red"
|
||||
},
|
||||
"red-ui": {
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss",
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:class": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:guard": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:pipe": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:service": {
|
||||
"skipTests": true
|
||||
}
|
||||
},
|
||||
"root": "apps/red-ui",
|
||||
"sourceRoot": "apps/red-ui/src",
|
||||
"prefix": "redaction",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/apps/red-ui",
|
||||
"index": "apps/red-ui/src/index.html",
|
||||
"main": "apps/red-ui/src/main.ts",
|
||||
"polyfills": "apps/red-ui/src/polyfills.ts",
|
||||
"tsConfig": "apps/red-ui/tsconfig.json",
|
||||
"baseHref": "/ui/",
|
||||
"assets": [
|
||||
"apps/red-ui/src/favicon.ico",
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/@pdftron/webviewer/public/",
|
||||
"output": "/assets/wv-resources/"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/red-ui/src/assets/",
|
||||
"output": "/assets/"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "libs/common-ui/src/assets/",
|
||||
"output": "/assets/"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/monaco-editor",
|
||||
"output": "/assets/monaco-editor/"
|
||||
},
|
||||
"apps/red-ui/src/manifest.webmanifest"
|
||||
],
|
||||
"styles": ["apps/red-ui/src/styles.scss", "libs/common-ui/src/assets/styles/common-styles.scss"],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": ["./apps/red-ui/src/assets/styles", "./libs/common-ui/src/assets/styles"]
|
||||
},
|
||||
"scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js"],
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"buildOptimizer": false,
|
||||
"sourceMap": true,
|
||||
"optimization": false,
|
||||
"namedChunks": true
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "apps/red-ui/src/environments/environment.ts",
|
||||
"with": "apps/red-ui/src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": {
|
||||
"scripts": true,
|
||||
"styles": {
|
||||
"minify": true,
|
||||
"inlineCritical": false
|
||||
},
|
||||
"fonts": true
|
||||
},
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb",
|
||||
"maximumError": "20kb"
|
||||
}
|
||||
],
|
||||
"serviceWorker": true,
|
||||
"ngswConfigPath": "apps/red-ui/ngsw-config.json"
|
||||
}
|
||||
},
|
||||
"outputs": ["{options.outputPath}"]
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "red-ui:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "red-ui:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "red-ui:build"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
132
apps/red-ui/project.json
Normal file
132
apps/red-ui/project.json
Normal file
@ -0,0 +1,132 @@
|
||||
{
|
||||
"name": "red-ui",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"generators": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss",
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:class": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:guard": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:pipe": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:service": {
|
||||
"skipTests": true
|
||||
}
|
||||
},
|
||||
"sourceRoot": "apps/red-ui/src",
|
||||
"prefix": "redaction",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/apps/red-ui",
|
||||
"index": "apps/red-ui/src/index.html",
|
||||
"main": "apps/red-ui/src/main.ts",
|
||||
"polyfills": "apps/red-ui/src/polyfills.ts",
|
||||
"tsConfig": "apps/red-ui/tsconfig.json",
|
||||
"baseHref": "/ui/",
|
||||
"assets": [
|
||||
"apps/red-ui/src/favicon.ico",
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/@pdftron/webviewer/public/",
|
||||
"output": "/assets/wv-resources/"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/red-ui/src/assets/",
|
||||
"output": "/assets/"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "libs/common-ui/src/assets/",
|
||||
"output": "/assets/"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/monaco-editor",
|
||||
"output": "/assets/monaco-editor/"
|
||||
},
|
||||
"apps/red-ui/src/manifest.webmanifest"
|
||||
],
|
||||
"styles": ["apps/red-ui/src/styles.scss", "libs/common-ui/src/assets/styles/common-styles.scss"],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": ["./apps/red-ui/src/assets/styles", "./libs/common-ui/src/assets/styles"]
|
||||
},
|
||||
"scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js"],
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"buildOptimizer": false,
|
||||
"sourceMap": true,
|
||||
"optimization": false,
|
||||
"namedChunks": true
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "apps/red-ui/src/environments/environment.ts",
|
||||
"with": "apps/red-ui/src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": {
|
||||
"scripts": true,
|
||||
"styles": {
|
||||
"minify": true,
|
||||
"inlineCritical": false
|
||||
},
|
||||
"fonts": true
|
||||
},
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb",
|
||||
"maximumError": "20kb"
|
||||
}
|
||||
],
|
||||
"serviceWorker": true,
|
||||
"ngswConfigPath": "apps/red-ui/ngsw-config.json"
|
||||
}
|
||||
},
|
||||
"outputs": ["{options.outputPath}"]
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "red-ui:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "red-ui:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"executor": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "red-ui:build"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@ import {
|
||||
IqserAuthGuard,
|
||||
IqserPermissionsGuard,
|
||||
IqserRoutes,
|
||||
TenantResolveComponent,
|
||||
TenantSelectComponent,
|
||||
} from '@iqser/common-ui';
|
||||
import { RedRoleGuard } from '@users/red-role.guard';
|
||||
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
|
||||
@ -19,7 +19,7 @@ import { DossiersGuard } from '@guards/dossiers.guard';
|
||||
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
|
||||
import { FeaturesGuard } from '@guards/features-guard.service';
|
||||
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
|
||||
import { DossierTemplateExistsGuard } from '@guards/dossier-template-exists.guard';
|
||||
import { templateExistsWhenEnteringDossierList } from '@guards/dossier-template-exists.guard';
|
||||
import { DashboardGuard } from '@guards/dashboard-guard.service';
|
||||
import { TrashGuard } from '@guards/trash.guard';
|
||||
import { ARCHIVE_ROUTE, BreadcrumbTypes, DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE, FILE_ID } from '@red/domain';
|
||||
@ -180,9 +180,9 @@ const mainRoutes: IqserRoutes = [
|
||||
{
|
||||
path: `:${DOSSIER_TEMPLATE_ID}`,
|
||||
children: dossierTemplateIdRoutes,
|
||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard, templateExistsWhenEnteringDossierList()],
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard, DossierTemplatesGuard, DashboardGuard, DossierTemplateExistsGuard],
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard, DossierTemplatesGuard, DashboardGuard],
|
||||
permissions: {
|
||||
allow: [
|
||||
ROLES.any,
|
||||
@ -211,7 +211,7 @@ const routes: IqserRoutes = [
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
canActivate: [ifNotLoggedIn],
|
||||
component: TenantResolveComponent,
|
||||
component: TenantSelectComponent,
|
||||
},
|
||||
{
|
||||
path: ':tenant',
|
||||
@ -240,7 +240,7 @@ const routes: IqserRoutes = [
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled' })],
|
||||
imports: [RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled', bindToComponentInputs: true })],
|
||||
providers: [{ provide: RouteReuseStrategy, useExisting: CustomRouteReuseStrategy }],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Inject, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core';
|
||||
import { Component, inject, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
@ -35,12 +35,11 @@ export class AppComponent implements OnDestroy {
|
||||
/** RouterHistoryService needs to be injected for last dossiers screen to be updated on first app load */
|
||||
private readonly _routerHistoryService: RouterHistoryService,
|
||||
userPreferenceService: UserPreferenceService,
|
||||
@Inject(DOCUMENT) document: Document,
|
||||
renderer: Renderer2,
|
||||
private readonly _router: Router,
|
||||
route: ActivatedRoute,
|
||||
) {
|
||||
renderer.addClass(document.body, userPreferenceService.getTheme());
|
||||
renderer.addClass(inject(DOCUMENT).body, userPreferenceService.getTheme());
|
||||
loadCustomTheme();
|
||||
const sub = route.queryParamMap.subscribe(queryParams => this.#navigate(queryParams));
|
||||
this.#subscription.add(sub);
|
||||
|
||||
@ -67,6 +67,9 @@ import { RedRoleGuard } from '@users/red-role.guard';
|
||||
import { SkeletonTopBarComponent } from '@components/skeleton/skeleton-top-bar/skeleton-top-bar.component';
|
||||
import { DossierSkeletonComponent } from '@components/skeleton/dossier-skeleton/dossier-skeleton.component';
|
||||
import { SkeletonStatsComponent } from '@components/skeleton/skeleton-stats/skeleton-stats.component';
|
||||
import { UserMenuComponent } from '@components/user-menu/user-menu.component';
|
||||
import { TenantsMenuComponent } from '@components/tenants-menu/tenants-menu.component';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
|
||||
export const appModuleFactory = (config: AppConfig) => {
|
||||
@NgModule({
|
||||
@ -81,6 +84,8 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
SkeletonTopBarComponent,
|
||||
SkeletonStatsComponent,
|
||||
BaseScreenComponent,
|
||||
UserMenuComponent,
|
||||
TenantsMenuComponent,
|
||||
DownloadsListScreenComponent,
|
||||
],
|
||||
imports: [
|
||||
@ -163,6 +168,7 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
IqserListingModule,
|
||||
IconButtonComponent,
|
||||
TenantPipe,
|
||||
MatDividerModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
||||
@ -2,17 +2,17 @@
|
||||
<p *ngIf="!adminName && !adminUrl" class="heading-xl" translate="auth-error.heading"></p>
|
||||
<p
|
||||
*ngIf="adminName && adminUrl"
|
||||
[innerHTML]="'auth-error.heading-with-name-and-link' | translate: { adminName: adminName, adminUrl: adminUrl }"
|
||||
[innerHTML]="'auth-error.heading-with-name-and-link' | translate : { adminName: adminName, adminUrl: adminUrl }"
|
||||
class="heading-xl"
|
||||
></p>
|
||||
<p
|
||||
*ngIf="adminName && !adminUrl"
|
||||
[innerHTML]="'auth-error.heading-with-name' | translate: { adminName: adminName }"
|
||||
[innerHTML]="'auth-error.heading-with-name' | translate : { adminName: adminName }"
|
||||
class="heading-xl"
|
||||
></p>
|
||||
<p
|
||||
*ngIf="!adminName && adminUrl"
|
||||
[innerHTML]="'auth-error.heading-with-link' | translate: { adminName: adminName }"
|
||||
[innerHTML]="'auth-error.heading-with-link' | translate : { adminName: adminName }"
|
||||
class="heading-xl"
|
||||
></p>
|
||||
<a (click)="userService.logout()" translate="auth-error.logout"></a>
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
<redaction-spotlight-search
|
||||
*allow="roles.search; if: (isSearchScreen$ | async) === false && (currentUser.isUser || currentUser.isManager)"
|
||||
[actions]="searchActions"
|
||||
[placeholder]="'search.placeholder' | translate"
|
||||
[attr.help-mode-key]="'search_in_entire_application'"
|
||||
[placeholder]="'search.placeholder' | translate"
|
||||
></redaction-spotlight-search>
|
||||
|
||||
<iqser-help-button *deny="roles.getRss" [attr.help-mode-key]="'help_mode'" id="help-mode-button"></iqser-help-button>
|
||||
@ -36,18 +36,7 @@
|
||||
<iqser-user-button [attr.help-mode-key]="'open_usermenu'" [matMenuTriggerFor]="userMenu" id="userMenu"></iqser-user-button>
|
||||
|
||||
<mat-menu #userMenu="matMenu" xPosition="before">
|
||||
<div id="user-menu-items">
|
||||
<ng-container *ngFor="let item of userMenuItems; trackBy: trackByName">
|
||||
<a (click)="item.action?.()" *ngIf="item.show" [id]="item.id" [routerLink]="item.routerLink | tenant" mat-menu-item>
|
||||
{{ item.name | translate }}
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<button (click)="userService.logout()" id="logout" mat-menu-item>
|
||||
<mat-icon svgIcon="iqser:logout"></mat-icon>
|
||||
<span translate="top-bar.navigation-items.my-account.children.logout"> </span>
|
||||
</button>
|
||||
</div>
|
||||
<app-user-menu></app-user-menu>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,24 +5,14 @@ import { NavigationStart, Router } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { filter, map, startWith } from 'rxjs/operators';
|
||||
import { BASE_HREF, IqserPermissionsService, shareDistinctLast, TenantContextHolder, TenantsService } from '@iqser/common-ui';
|
||||
import { BASE_HREF, IqserPermissionsService, List, shareDistinctLast, TenantsService } 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 { ROLES } from '@users/roles';
|
||||
import { REDDocumentViewer } from '../../modules/pdf-viewer/services/document-viewer.service';
|
||||
|
||||
interface MenuItem {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly routerLink?: string;
|
||||
readonly show: boolean;
|
||||
readonly action?: () => void;
|
||||
readonly showDot?: () => boolean;
|
||||
}
|
||||
|
||||
const isNavigationStart = event => event instanceof NavigationStart;
|
||||
const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
|
||||
|
||||
@ -32,74 +22,41 @@ const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
|
||||
})
|
||||
export class BaseScreenComponent {
|
||||
readonly roles = ROLES;
|
||||
readonly tenantContext = inject(TenantsService);
|
||||
readonly documentViewer = inject(REDDocumentViewer);
|
||||
readonly currentUser = this.userService.currentUser;
|
||||
readonly userMenuItems: readonly MenuItem[] = [
|
||||
{
|
||||
id: 'account',
|
||||
name: _('top-bar.navigation-items.my-account.children.account'),
|
||||
routerLink: '/main/account',
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
id: 'admin',
|
||||
name: _('top-bar.navigation-items.my-account.children.admin'),
|
||||
routerLink: '/main/admin',
|
||||
show: (this.currentUser.isManager || this.currentUser.isUserAdmin) && this.permissionsService.has([ROLES.templates.read]),
|
||||
},
|
||||
{
|
||||
id: 'downloads',
|
||||
name: _('top-bar.navigation-items.my-account.children.downloads'),
|
||||
routerLink: '/main/downloads',
|
||||
show: this.currentUser.isUser && this.permissionsService.has(ROLES.readDownloadStatus),
|
||||
},
|
||||
{
|
||||
id: 'trash',
|
||||
name: _('top-bar.navigation-items.my-account.children.trash'),
|
||||
routerLink: '/main/trash',
|
||||
show: this.currentUser.isUser && this.permissionsService.has([ROLES.dossiers.read, ROLES.files.readStatus]),
|
||||
},
|
||||
{
|
||||
id: 'select-tenant',
|
||||
name: _('top-bar.navigation-items.my-account.children.select-tenant'),
|
||||
action: () => this.selectTenant(),
|
||||
show: this.tenantContext.hasMultipleTenants,
|
||||
},
|
||||
];
|
||||
readonly searchActions: readonly SpotlightSearchAction[] = [
|
||||
readonly searchActions: List<SpotlightSearchAction> = [
|
||||
{
|
||||
text: this._translateService.instant('search.this-dossier'),
|
||||
icon: 'red:enter',
|
||||
hide: (): boolean => this._hideSearchThisDossier,
|
||||
action: (query): void => this._searchThisDossier(query),
|
||||
hide: (): boolean => this.#hideSearchThisDossier,
|
||||
action: (query): void => this.#searchThisDossier(query),
|
||||
},
|
||||
{
|
||||
text: this._translateService.instant('search.active-dossiers'),
|
||||
icon: 'red:enter',
|
||||
hide: () => !this._featuresService.isEnabled(DOSSIERS_ARCHIVE),
|
||||
action: (query): void => this._search(query, [], true),
|
||||
action: (query): void => this.#search(query, [], true),
|
||||
},
|
||||
{
|
||||
text: this._translateService.instant('search.all-dossiers'),
|
||||
icon: 'red:enter',
|
||||
action: (query): void => this._search(query, []),
|
||||
action: (query): void => this.#search(query, []),
|
||||
},
|
||||
];
|
||||
private readonly _baseHref = inject(BASE_HREF);
|
||||
private readonly _navigationStart$ = this._router.events.pipe(
|
||||
readonly #baseHref = inject(BASE_HREF);
|
||||
readonly #navigationStart$ = this._router.events.pipe(
|
||||
filter(isNavigationStart),
|
||||
map((event: NavigationStart) => event.url),
|
||||
startWith(this._router.url),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
readonly isSearchScreen$ = this._navigationStart$.pipe(map(isSearchScreen));
|
||||
readonly isSearchScreen$ = this.#navigationStart$.pipe(map(isSearchScreen));
|
||||
|
||||
constructor(
|
||||
private readonly _router: Router,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _featuresService: FeaturesService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
protected readonly _tenantsService: TenantsService,
|
||||
readonly permissionsService: IqserPermissionsService,
|
||||
readonly userService: UserService,
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
@ -107,7 +64,7 @@ export class BaseScreenComponent {
|
||||
readonly breadcrumbsService: BreadcrumbsService,
|
||||
) {}
|
||||
|
||||
private get _hideSearchThisDossier() {
|
||||
get #hideSearchThisDossier() {
|
||||
const routerLink = this.breadcrumbsService.breadcrumbs[1]?.options?.routerLink;
|
||||
if (!routerLink) {
|
||||
return true;
|
||||
@ -117,25 +74,17 @@ export class BaseScreenComponent {
|
||||
return !isDossierOverview;
|
||||
}
|
||||
|
||||
trackByName(_index: number, item: MenuItem) {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
selectTenant() {
|
||||
window.open(window.location.origin + this._baseHref, '_blank');
|
||||
}
|
||||
|
||||
private _search(query: string, dossierIds: string[], onlyActive = false) {
|
||||
#search(query: string, dossierIds: string[], onlyActive = false) {
|
||||
const queryParams = { query, dossierIds: dossierIds.join(','), onlyActive };
|
||||
this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/search`], { queryParams }).then();
|
||||
this._router.navigate([`/${this._tenantsService.activeTenantId}/main/search`], { queryParams }).then();
|
||||
}
|
||||
|
||||
private _searchThisDossier(query: string) {
|
||||
#searchThisDossier(query: string) {
|
||||
const routerLink = this.breadcrumbsService.breadcrumbs[1]?.options?.routerLink;
|
||||
if (!routerLink) {
|
||||
return this._search(query, []);
|
||||
return this.#search(query, []);
|
||||
}
|
||||
const dossierId = routerLink[2];
|
||||
return this._search(query, [dossierId]);
|
||||
return this.#search(query, [dossierId]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mat-menu-item.notification {
|
||||
.mat-mdc-menu-item.notification {
|
||||
padding: 8px 26px 10px 8px;
|
||||
margin: 2px 0 0 0;
|
||||
height: fit-content;
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
::ng-deep.search-menu .mat-menu-content {
|
||||
::ng-deep.search-menu .mat-mdc-menu-content {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, HostListener, Input, ViewChild } fr
|
||||
import { debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||
import { MatLegacyMenuTrigger as MatMenuTrigger } from '@angular/material/legacy-menu';
|
||||
import { MatMenuTrigger } from '@angular/material/menu';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-spotlight-search [actions]',
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
<div id="tenants-menu-items">
|
||||
<ng-container *ngFor="let item of storedTenants(); trackBy: trackBy">
|
||||
<div class="label">{{ item.tenant.displayName }}</div>
|
||||
|
||||
<a (click)="selectTenant(item.tenant.tenantId)" [id]="item.tenant.tenantId" mat-menu-item>
|
||||
{{ item.email }}
|
||||
</a>
|
||||
<mat-divider class="pb-3 pt-3"></mat-divider>
|
||||
</ng-container>
|
||||
|
||||
<a (click)="selectTenant()" mat-menu-item>
|
||||
<span translate="top-bar.navigation-items.my-account.children.join-another-tenant"> </span>
|
||||
</a>
|
||||
</div>
|
||||
@ -0,0 +1,13 @@
|
||||
:host {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-family: var(--iqser-font-family);
|
||||
font-size: var(--iqser-font-size);
|
||||
background-color: var(--iqser-background);
|
||||
color: var(--iqser-disabled);
|
||||
padding-left: 17px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { BASE_HREF, IStoredTenant, TenantsService } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tenants-menu',
|
||||
templateUrl: './tenants-menu.component.html',
|
||||
styleUrls: ['./tenants-menu.component.scss'],
|
||||
})
|
||||
export class TenantsMenuComponent {
|
||||
readonly tenantsService = inject(TenantsService);
|
||||
|
||||
readonly storedTenants = computed(() => this.#getStoredTenants(this.tenantsService.activeTenant().tenantId));
|
||||
readonly #baseHref = inject(BASE_HREF);
|
||||
|
||||
trackBy(_index: number, item: IStoredTenant) {
|
||||
return item.tenant.tenantId;
|
||||
}
|
||||
|
||||
selectTenant(tenantId?: string) {
|
||||
const tenant = tenantId ? '/' + tenantId : '/';
|
||||
window.open(window.location.origin + this.#baseHref + tenant, '_blank');
|
||||
}
|
||||
|
||||
#getStoredTenants(activeTenantId: string) {
|
||||
const storedTenants = this.tenantsService.getStoredTenants();
|
||||
|
||||
return storedTenants.filter(t => t.tenant.tenantId !== activeTenantId);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
<div id="user-menu-items">
|
||||
<ng-container *ngFor="let item of userMenuItems; trackBy: trackBy">
|
||||
<a (click)="item.action?.()" *ngIf="item.show" [id]="item.id" [routerLink]="item.routerLink | tenant" mat-menu-item>
|
||||
{{ item.name | translate }}
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<a *ngIf="tenantsService.hasMultiple()" [id]="'switch-accounts'" [matMenuTriggerFor]="tenantsMenu" mat-menu-item>
|
||||
{{ 'top-bar.navigation-items.my-account.children.select-tenant' | translate }}
|
||||
</a>
|
||||
|
||||
<button (click)="userService.logout()" id="logout" mat-menu-item>
|
||||
<mat-icon svgIcon="iqser:logout"></mat-icon>
|
||||
<span translate="top-bar.navigation-items.my-account.children.logout"> </span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<mat-menu #tenantsMenu="matMenu" xPosition="before">
|
||||
<app-tenants-menu></app-tenants-menu>
|
||||
</mat-menu>
|
||||
@ -0,0 +1,3 @@
|
||||
:host {
|
||||
display: contents;
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { ROLES } from '@users/roles';
|
||||
import { getCurrentUser, IqserPermissionsService, List, TenantsService } from '@iqser/common-ui';
|
||||
import { User } from '@red/domain';
|
||||
import { UserService } from '@users/user.service';
|
||||
interface MenuItem {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly routerLink?: string;
|
||||
readonly show: boolean;
|
||||
readonly action?: () => void;
|
||||
readonly showDot?: () => boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-menu',
|
||||
templateUrl: './user-menu.component.html',
|
||||
styleUrls: ['./user-menu.component.scss'],
|
||||
})
|
||||
export class UserMenuComponent {
|
||||
readonly currentUser = getCurrentUser<User>();
|
||||
readonly tenantsService = inject(TenantsService);
|
||||
readonly userService = inject(UserService);
|
||||
readonly #permissionsService = inject(IqserPermissionsService);
|
||||
readonly userMenuItems: List<MenuItem> = [
|
||||
{
|
||||
id: 'account',
|
||||
name: _('top-bar.navigation-items.my-account.children.account'),
|
||||
routerLink: '/main/account',
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
id: 'admin',
|
||||
name: _('top-bar.navigation-items.my-account.children.admin'),
|
||||
routerLink: '/main/admin',
|
||||
show: (this.currentUser.isManager || this.currentUser.isUserAdmin) && this.#permissionsService.has([ROLES.templates.read]),
|
||||
},
|
||||
{
|
||||
id: 'downloads',
|
||||
name: _('top-bar.navigation-items.my-account.children.downloads'),
|
||||
routerLink: '/main/downloads',
|
||||
show: this.currentUser.isUser && this.#permissionsService.has(ROLES.readDownloadStatus),
|
||||
},
|
||||
{
|
||||
id: 'trash',
|
||||
name: _('top-bar.navigation-items.my-account.children.trash'),
|
||||
routerLink: '/main/trash',
|
||||
show: this.currentUser.isUser && this.#permissionsService.has([ROLES.dossiers.read, ROLES.files.readStatus]),
|
||||
},
|
||||
];
|
||||
|
||||
trackBy(_index: number, item: MenuItem) {
|
||||
return item.id;
|
||||
}
|
||||
}
|
||||
@ -7,13 +7,13 @@ import { DOSSIER_ID, DOSSIER_TEMPLATE_ID } from '@red/domain';
|
||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
import { TenantsService } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossierFilesGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _injector: Injector,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
private readonly _filesService: FilesService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
@ -34,7 +34,7 @@ export class DossierFilesGuard implements CanActivate {
|
||||
}
|
||||
|
||||
if (!dossiersService.has(dossierId)) {
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main`, dossierTemplateId]);
|
||||
await this._router.navigate([`/${this._tenantsService.activeTenantId}/main`, dossierTemplateId]);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -46,8 +46,8 @@ export class DossierFilesGuard implements CanActivate {
|
||||
const promises = [];
|
||||
|
||||
if (!this._dictionaryMapService.has(dossierId)) {
|
||||
const dictionary$ = this._dictionaryService.loadDossierDictionary(dossierTemplateId, dossierId);
|
||||
promises.push(firstValueFrom(dictionary$));
|
||||
const dictionaryPromise = this._dictionaryService.loadDossierDictionary(dossierTemplateId, dossierId);
|
||||
promises.push(dictionaryPromise);
|
||||
}
|
||||
|
||||
if (!this._filesMapService.has(dossierId)) {
|
||||
|
||||
@ -1,37 +1,31 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@red/domain';
|
||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
import { TenantsService } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossierTemplateExistsGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _dashboardStatsService: DashboardStatsService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _router: Router,
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
export function templateExistsWhenEnteringAdmin(): CanActivateFn {
|
||||
return async function (route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
const dossierTemplateId: string = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
const dossiersListView = !route.pathFromRoot.find(r => r.routeConfig?.path === 'admin');
|
||||
|
||||
if (dossiersListView) {
|
||||
const dossierTemplateStats = this._dashboardStatsService.find(dossierTemplateId);
|
||||
if (!dossierTemplateStats || dossierTemplateStats.isEmpty) {
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main`]);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
|
||||
if (!dossierTemplate) {
|
||||
await this._router.navigate([this._tenantContextHolder.currentTenant, 'main', 'admin', 'dossier-templates']);
|
||||
return false;
|
||||
}
|
||||
const dossierTemplate = inject(DashboardStatsService).find(dossierTemplateId);
|
||||
if (!dossierTemplate) {
|
||||
await inject(Router).navigate([inject(TenantsService).activeTenantId, 'main', 'admin', 'dossier-templates']);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function templateExistsWhenEnteringDossierList(): CanActivateFn {
|
||||
return async function (route: ActivatedRouteSnapshot) {
|
||||
const dossierTemplateId: string = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
|
||||
const dossierTemplateStats = inject(DashboardStatsService).find(dossierTemplateId);
|
||||
if (!dossierTemplateStats || dossierTemplateStats.isEmpty) {
|
||||
await inject(Router).navigate([inject(TenantsService).activeTenantId, 'main']);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@ -7,14 +7,14 @@ import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.se
|
||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||
import { ARCHIVE_ROUTE, DOSSIER_TEMPLATE_ID } from '@red/domain';
|
||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
import { TenantsService } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossiersGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _injector: Injector,
|
||||
private readonly _router: Router,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _dashboardStatsService: DashboardStatsService,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _archivedDossiersService: ArchivedDossiersService,
|
||||
@ -35,7 +35,7 @@ export class DossiersGuard implements CanActivate {
|
||||
const dossierTemplateStats = this._dashboardStatsService.find(dossierTemplateId);
|
||||
|
||||
if (isArchive && dossierTemplateStats?.numberOfArchivedDossiers === 0) {
|
||||
await this._router.navigate([this._tenantContextHolder.currentTenant, 'main', dossierTemplateId, 'dossiers']);
|
||||
await this._router.navigate([this._tenantsService.activeTenantId, 'main', dossierTemplateId, 'dossiers']);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -1,29 +1,21 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
import { TenantsService } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class EntityExistsGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
private readonly _router: Router,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
export function entityExistsGuard(): CanActivateFn {
|
||||
return async function (route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
const dossierTemplateId = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
const type = route.paramMap.get(ENTITY_TYPE);
|
||||
|
||||
if (!this._dictionariesMapService.get(dossierTemplateId, type)) {
|
||||
const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/${dossierTemplate.routerLink}/entities`]);
|
||||
if (!inject(DictionariesMapService).get(dossierTemplateId, type)) {
|
||||
const dossierTemplate = inject(DossierTemplatesService).find(dossierTemplateId);
|
||||
await inject(Router).navigate([`/${inject(TenantsService).activeTenantId}/${dossierTemplate.routerLink}/entities`]);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,27 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
|
||||
import { WatermarksMapService } from '@services/entity-services/watermarks-map.service';
|
||||
import { DOSSIER_TEMPLATE_ID, WATERMARK_ID } from '@red/domain';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
import { TenantsService } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class WatermarkExistsGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _watermarksMapService: WatermarksMapService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _router: Router,
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
export function watermarkExistsGuard(): CanActivateFn {
|
||||
return async function (route: ActivatedRouteSnapshot) {
|
||||
const dossierTemplateId = route.parent.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
const watermarkId = Number(route.paramMap.get(WATERMARK_ID));
|
||||
|
||||
if (this._watermarksMapService.get(dossierTemplateId, watermarkId)) {
|
||||
if (inject(WatermarksMapService).get(dossierTemplateId, watermarkId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await this._router.navigate([
|
||||
this._tenantContextHolder.currentTenant,
|
||||
await inject(Router).navigate([
|
||||
inject(TenantsService).activeTenantId,
|
||||
'main',
|
||||
'admin',
|
||||
'dossier-templates',
|
||||
@ -29,5 +22,5 @@ export class WatermarkExistsGuard implements CanActivate {
|
||||
'watermarks',
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
annotationDefaultColorConfig,
|
||||
annotationEntityColorConfig,
|
||||
AnnotationIconType,
|
||||
ChangeTypes,
|
||||
DefaultColors,
|
||||
Dictionary,
|
||||
Earmark,
|
||||
@ -23,6 +24,7 @@ import {
|
||||
} from '@red/domain';
|
||||
import { RedactionLogEntry } from '@models/file/redaction-log.entry';
|
||||
import { IListable, List } from '@iqser/common-ui';
|
||||
import { chronologicallyBy, timestampOf } from '../../modules/file-preview/services/file-data.service';
|
||||
|
||||
export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
||||
[x: string]: unknown;
|
||||
@ -360,6 +362,15 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
||||
private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntry) {
|
||||
if (redactionLogEntryWrapper.manualChanges?.length) {
|
||||
const lastRelevantManualChange = this._getLastRelevantManualChange(redactionLogEntryWrapper.manualChanges);
|
||||
const viableChanges = redactionLogEntryWrapper.changes.filter(c => c.analysisNumber > 1);
|
||||
const lastChange = viableChanges.sort(chronologicallyBy(x => x.dateTime)).at(-1);
|
||||
const lastChangeOccurredAfterLastManualChange =
|
||||
timestampOf(lastChange.dateTime) > timestampOf(lastRelevantManualChange.processedDate);
|
||||
|
||||
if (lastChangeOccurredAfterLastManualChange && lastChange.type === ChangeTypes.ADDED && redactionLogEntryWrapper.redacted) {
|
||||
annotationWrapper.superType = SuperTypes.Redaction;
|
||||
return;
|
||||
}
|
||||
|
||||
annotationWrapper.pending = !lastRelevantManualChange.processed;
|
||||
|
||||
@ -498,7 +509,7 @@ export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
||||
if (lastManualChange.processed) {
|
||||
switch (lastManualChange.annotationStatus) {
|
||||
case LogEntryStatuses.APPROVED:
|
||||
return redactionLogEntry.recommendation ? SuperTypes.Recommendation : SuperTypes.Hint;
|
||||
return redactionLogEntry.recommendation ? SuperTypes.Recommendation : SuperTypes.Skipped;
|
||||
case LogEntryStatuses.DECLINED:
|
||||
return isHintDictionary ? SuperTypes.Hint : SuperTypes.Skipped;
|
||||
case LogEntryStatuses.REQUESTED:
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
<input formControlName="lastName" name="lastName" type="text" />
|
||||
</div>
|
||||
|
||||
<div *ngIf="userPreferences.areDevFeaturesEnabled" class="iqser-input-group">
|
||||
<div *ngIf="devMode" class="iqser-input-group">
|
||||
<label [translate]="'top-bar.navigation-items.my-account.children.language.label'"></label>
|
||||
<mat-form-field>
|
||||
<mat-select formControlName="language">
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BaseFormComponent, IqserPermissionsService, LanguageService, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { IProfile } from '@red/domain';
|
||||
import {
|
||||
BaseFormComponent,
|
||||
getConfig,
|
||||
IqserPermissionsService,
|
||||
LanguageService,
|
||||
LoadingService,
|
||||
TenantsService,
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { AppConfig, IProfile } from '@red/domain';
|
||||
import { languagesTranslations } from '@translations/languages-translations';
|
||||
import { UserService } from '@users/user.service';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { ROLES } from '@users/roles';
|
||||
@ -14,7 +21,6 @@ import { UserProfileDialogService } from '../services/user-profile-dialog.servic
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-user-profile-screen',
|
||||
templateUrl: './user-profile-screen.component.html',
|
||||
styleUrls: ['./user-profile-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@ -22,15 +28,13 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
export class UserProfileScreenComponent extends BaseFormComponent implements OnInit {
|
||||
readonly translations = languagesTranslations;
|
||||
readonly devMode = this._userPreferenceService.areDevFeaturesEnabled;
|
||||
readonly changePasswordUrl: SafeResourceUrl;
|
||||
readonly changePasswordUrl: string;
|
||||
|
||||
#profileModel: IProfile;
|
||||
|
||||
constructor(
|
||||
domSanitizer: DomSanitizer,
|
||||
configService: ConfigService,
|
||||
private readonly _userService: UserService,
|
||||
readonly userPreferences: UserPreferenceService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dialogService: UserProfileDialogService,
|
||||
private readonly _formBuilder: UntypedFormBuilder,
|
||||
@ -43,7 +47,9 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
|
||||
) {
|
||||
super();
|
||||
this._loadingService.start();
|
||||
this.changePasswordUrl = domSanitizer.bypassSecurityTrustResourceUrl(`${configService.values.OAUTH_URL}/account/password`);
|
||||
const tenant = inject(TenantsService).activeTenantId;
|
||||
const realmUrl = `${getConfig<AppConfig>().OAUTH_URL}/realms/${tenant}`;
|
||||
this.changePasswordUrl = `${realmUrl}/account/password`;
|
||||
}
|
||||
|
||||
get languageChanged(): boolean {
|
||||
|
||||
@ -13,8 +13,8 @@ import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.
|
||||
import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component';
|
||||
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
|
||||
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
|
||||
import { DossierTemplateExistsGuard } from '@guards/dossier-template-exists.guard';
|
||||
import { EntityExistsGuard } from '@guards/entity-exists-guard.service';
|
||||
import { templateExistsWhenEnteringAdmin } from '@guards/dossier-template-exists.guard';
|
||||
import { entityExistsGuard } from '@guards/entity-exists-guard.service';
|
||||
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
|
||||
import { PermissionsGuard } from '@guards/permissions-guard';
|
||||
import { ROLES } from '@users/roles';
|
||||
@ -39,10 +39,10 @@ const dossierTemplateIdRoutes: IqserRoutes = [
|
||||
{
|
||||
path: `:${ENTITY_TYPE}`,
|
||||
component: BaseEntityScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
canActivate: [CompositeRouteGuard, entityExistsGuard()],
|
||||
loadChildren: () => import('./screens/entities/entities.module').then(m => m.EntitiesModule),
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard, EntityExistsGuard],
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -141,8 +141,7 @@ const dossierTemplatesRoutes: IqserRoutes = [
|
||||
{
|
||||
path: `:${DOSSIER_TEMPLATE_ID}`,
|
||||
children: dossierTemplateIdRoutes,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: { routeGuards: [DossierTemplateExistsGuard] },
|
||||
canActivate: [templateExistsWhenEnteringAdmin()],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { Router } from '@angular/router';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { AdminDialogService } from '../services/admin-dialog.service';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
import { getParam, LoadingService, TenantContextHolder } from '@iqser/common-ui';
|
||||
import { getParam, LoadingService, TenantsService } from '@iqser/common-ui';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
@ -27,7 +27,7 @@ export class BaseEntityScreenComponent {
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
) {
|
||||
const entity$ = dictionaryMapService.watch$(this.#dossierTemplateId, this.#entityType);
|
||||
@ -42,7 +42,7 @@ export class BaseEntityScreenComponent {
|
||||
this._loadingService.start();
|
||||
const dossierTemplate = this._dossierTemplatesService.find(this.#dossierTemplateId);
|
||||
await firstValueFrom(this._dictionaryService.deleteDictionaries([this.#entityType], this.#dossierTemplateId));
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/${dossierTemplate.routerLink}/entities`]);
|
||||
await this._router.navigate([`/${this._tenantsService.activeTenantId}/${dossierTemplate.routerLink}/entities`]);
|
||||
this._loadingService.stop();
|
||||
});
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<div class="dialog-content">
|
||||
<redaction-add-edit-entity
|
||||
#redactionAddEditEntity
|
||||
[dossierTemplateId]="dossierTemplateId"
|
||||
[dossierTemplateId]="data.dossierTemplateId"
|
||||
[entity]="null"
|
||||
[readOnly]="false"
|
||||
></redaction-add-edit-entity>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, inject, ViewChild } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { AddEditEntityComponent } from '@shared/components/add-edit-entity/add-edit-entity.component';
|
||||
import { BaseDialogComponent, IconButtonTypes } from '@iqser/common-ui';
|
||||
@ -16,14 +16,10 @@ interface DialogData {
|
||||
export class AddEntityDialogComponent extends BaseDialogComponent {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly roles = ROLES;
|
||||
readonly dossierTemplateId = this._data.dossierTemplateId;
|
||||
|
||||
readonly data = inject<DialogData>(MAT_DIALOG_DATA);
|
||||
@ViewChild(AddEditEntityComponent, { static: true }) private readonly _addEditEntityComponent: AddEditEntityComponent;
|
||||
|
||||
constructor(
|
||||
protected readonly _dialogRef: MatDialogRef<AddEntityDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) private readonly _data: DialogData,
|
||||
) {
|
||||
constructor(protected readonly _dialogRef: MatDialogRef<AddEntityDialogComponent>) {
|
||||
super(_dialogRef, false);
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<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>
|
||||
</ng-container>
|
||||
|
||||
@ -6,6 +6,11 @@ import { JustificationsService } from '@services/entity-services/justifications.
|
||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
interface DialogData {
|
||||
justification?: Justification;
|
||||
dossierTemplateId: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: './add-edit-justification-dialog.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@ -14,7 +19,7 @@ export class AddEditJustificationDialogComponent extends BaseDialogComponent {
|
||||
constructor(
|
||||
private readonly _justificationService: JustificationsService,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditJustificationDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) readonly data: { justification?: Justification; dossierTemplateId: string },
|
||||
@Inject(MAT_DIALOG_DATA) readonly data: DialogData,
|
||||
) {
|
||||
super(_dialogRef, !!data.justification);
|
||||
|
||||
|
||||
@ -30,12 +30,12 @@ const placeholderTypes: PlaceholderType[] = ['generalPlaceholders', 'fileAttribu
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ReportsScreenComponent implements OnInit {
|
||||
placeholders$ = new BehaviorSubject<Placeholder[]>([]);
|
||||
availableTemplates$ = new BehaviorSubject<IReportTemplate[]>([]);
|
||||
readonly placeholders$ = new BehaviorSubject<Placeholder[]>([]);
|
||||
readonly availableTemplates$ = new BehaviorSubject<IReportTemplate[]>([]);
|
||||
readonly currentUser = getCurrentUser<User>();
|
||||
readonly roles = ROLES;
|
||||
@ViewChild('fileInput') private readonly _fileInput: ElementRef;
|
||||
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
|
||||
@ViewChild('fileInput') private _fileInput: ElementRef;
|
||||
|
||||
constructor(
|
||||
private readonly _reportTemplateService: ReportTemplateService,
|
||||
@ -91,7 +91,7 @@ export class ReportsScreenComponent implements OnInit {
|
||||
},
|
||||
};
|
||||
|
||||
this._dialogService.openDialog('confirm', data, null, async result => {
|
||||
this._dialogService.openDialog('confirm', data, null, async (result: number) => {
|
||||
if (result) {
|
||||
const multiFileReport = result > 1;
|
||||
if (
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
import { ChangeDetectorRef, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, ElementRef, inject, OnInit, ViewChild } from '@angular/core';
|
||||
import WebViewer, { WebViewerInstance } from '@pdftron/webviewer';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import {
|
||||
AsControl,
|
||||
BASE_HREF_FN,
|
||||
BaseHrefFn,
|
||||
Debounce,
|
||||
getCurrentUser,
|
||||
getParam,
|
||||
IconButtonTypes,
|
||||
IqserPermissionsService,
|
||||
LoadingService,
|
||||
TenantContextHolder,
|
||||
TenantsService,
|
||||
Toaster,
|
||||
trackByFactory,
|
||||
} from '@iqser/common-ui';
|
||||
@ -85,9 +84,10 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
];
|
||||
readonly orientationOptions = ['DIAGONAL', 'HORIZONTAL', 'VERTICAL'];
|
||||
instance: WebViewerInstance;
|
||||
readonly #loaded$ = new BehaviorSubject(false);
|
||||
readonly loaded$ = this.#loaded$.asObservable().pipe(tap(() => setTimeout(() => this._changeDetectorRef.detectChanges())));
|
||||
readonly loaded$: Observable<boolean>;
|
||||
@ViewChild('viewer', { static: true }) private readonly _viewer: ElementRef<HTMLDivElement>;
|
||||
private readonly _convertPath = inject(BASE_HREF_FN);
|
||||
readonly #loaded$ = new BehaviorSubject(false);
|
||||
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
|
||||
readonly #watermarkId = Number(getParam(WATERMARK_ID));
|
||||
#watermark: Partial<IWatermark> = {};
|
||||
@ -104,15 +104,15 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
readonly permissionsService: IqserPermissionsService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _licenseService: LicenseService,
|
||||
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
||||
private readonly _watermarkService: WatermarkService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _router: Router,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
watermarksMapService: WatermarksMapService,
|
||||
) {
|
||||
this.loaded$ = this.#loaded$.asObservable().pipe(tap(() => setTimeout(() => this._changeDetectorRef.detectChanges())));
|
||||
const watermark$ = watermarksMapService.watch$(this.#dossierTemplateId, this.#watermarkId);
|
||||
const obs$: Observable<Partial<IWatermark>> = this.#watermarkId ? watermark$ : of(DEFAULT_WATERMARK);
|
||||
this.watermark$ = obs$.pipe(tap(watermark => this.#initForm(watermark)));
|
||||
@ -153,15 +153,15 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
return form;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.#loadViewer();
|
||||
}
|
||||
|
||||
@Debounce()
|
||||
async configChanged() {
|
||||
await this.#drawWatermark();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.#loadViewer();
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
const watermark: IWatermark = {
|
||||
id: this.#watermark.id,
|
||||
@ -179,7 +179,7 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
);
|
||||
if (!watermark.id) {
|
||||
await this._router.navigate([
|
||||
`/${this._tenantContextHolder.currentTenant}/main/admin/dossier-templates/${this.#dossierTemplateId}/watermarks/${
|
||||
`/${this._tenantsService.activeTenantId}/main/admin/dossier-templates/${this.#dossierTemplateId}/watermarks/${
|
||||
updatedWatermark.id
|
||||
}`,
|
||||
]);
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
} from '@iqser/common-ui';
|
||||
import { RedRoleGuard } from '@users/red-role.guard';
|
||||
import { WATERMARK_ID } from '@red/domain';
|
||||
import { WatermarkExistsGuard } from '@guards/watermark-exists.guard';
|
||||
import { watermarkExistsGuard } from '@guards/watermark-exists.guard';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { PaginatorComponent } from './paginator/paginator.component';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
@ -47,9 +47,9 @@ const routes: IqserRoutes = [
|
||||
{
|
||||
path: `:${WATERMARK_ID}`,
|
||||
component: WatermarkScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
canActivate: [CompositeRouteGuard, watermarkExistsGuard()],
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard, WatermarkExistsGuard],
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericService, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService } from '@iqser/common-ui';
|
||||
import { IAudit, IAuditResponse, IAuditSearchRequest, ICategory } from '@red/domain';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@ -11,8 +11,7 @@ export class AuditService extends GenericService<IAudit> {
|
||||
return super.getAll<ICategory[]>(`${this._defaultModelPath}/categories`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
searchAuditLog(@RequiredParam() body: IAuditSearchRequest): Observable<IAuditResponse> {
|
||||
searchAuditLog(body: IAuditSearchRequest): Observable<IAuditResponse> {
|
||||
return this._post(body, `${this._defaultModelPath}/search`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { filterEach, GenericService, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { filterEach, GenericService } from '@iqser/common-ui';
|
||||
import { forkJoin, Observable, of } from 'rxjs';
|
||||
import {
|
||||
IDigitalSignatureRequest,
|
||||
@ -14,18 +14,15 @@ import { catchError, map } from 'rxjs/operators';
|
||||
export class DigitalSignatureService extends GenericService<IDigitalSignatureRequest> {
|
||||
protected readonly _defaultModelPath = 'digital-signature';
|
||||
|
||||
@Validate()
|
||||
updateSignature(@RequiredParam() body: IPkcsDigitalSignatureRequest): Observable<unknown> {
|
||||
updateSignature(body: IPkcsDigitalSignatureRequest): Observable<unknown> {
|
||||
return this._put(body);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
saveSignature(@RequiredParam() body: IPkcsDigitalSignature): Observable<IPkcsDigitalSignatureRequest> {
|
||||
saveSignature(body: IPkcsDigitalSignature): Observable<IPkcsDigitalSignatureRequest> {
|
||||
return this._post(body);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
saveKmsSignature(@RequiredParam() body: IKmsDigitalSignature): Observable<IKmsDigitalSignatureRequest> {
|
||||
saveKmsSignature(body: IKmsDigitalSignature): Observable<IKmsDigitalSignatureRequest> {
|
||||
return this._post(body, `${this._defaultModelPath}/kms`);
|
||||
}
|
||||
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericService, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService } from '@iqser/common-ui';
|
||||
import { IRules } from '@red/domain';
|
||||
|
||||
@Injectable()
|
||||
export class RulesService extends GenericService<IRules> {
|
||||
protected readonly _defaultModelPath = 'rules';
|
||||
|
||||
@Validate()
|
||||
download(@RequiredParam() dossierTemplateId: string) {
|
||||
download(dossierTemplateId: string) {
|
||||
return this._getOne([dossierTemplateId]);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
uploadRules(@RequiredParam() body: IRules) {
|
||||
uploadRules(body: IRules) {
|
||||
return this._post<unknown>(body);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericService, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService } from '@iqser/common-ui';
|
||||
import { ISmtpConfiguration } from '@red/domain';
|
||||
|
||||
@Injectable()
|
||||
export class SmtpConfigService extends GenericService<unknown> {
|
||||
protected readonly _defaultModelPath = 'configuration';
|
||||
|
||||
@Validate()
|
||||
updateSMTPConfiguration(@RequiredParam() body: ISmtpConfiguration) {
|
||||
updateSMTPConfiguration(body: ISmtpConfiguration) {
|
||||
return this._post(body, `${this._defaultModelPath}/smtp`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
testSMTPConfiguration(@RequiredParam() body: ISmtpConfiguration) {
|
||||
testSMTPConfiguration(body: ISmtpConfiguration) {
|
||||
return this._post(body, `${this._defaultModelPath}/smtp/test`);
|
||||
}
|
||||
|
||||
|
||||
@ -5,14 +5,7 @@ import { adminSideNavTranslations } from '@translations/admin-side-nav-translati
|
||||
import { ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router';
|
||||
import { AdminSideNavType, AdminSideNavTypes, DOSSIER_TEMPLATE_ID, ENTITY_TYPE, User, WATERMARK_ID } from '@red/domain';
|
||||
import { ROLES } from '@users/roles';
|
||||
import {
|
||||
getCurrentUser,
|
||||
IqserHelpModeModule,
|
||||
IqserPermissionsService,
|
||||
SideNavComponent,
|
||||
TenantContextHolder,
|
||||
TenantPipe,
|
||||
} from '@iqser/common-ui';
|
||||
import { getCurrentUser, IqserHelpModeModule, IqserPermissionsService, SideNavComponent, TenantPipe } from '@iqser/common-ui';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NgForOf, NgIf } from '@angular/common';
|
||||
|
||||
@ -173,7 +166,6 @@ export class AdminSideNavComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private readonly _permissionsService: IqserPermissionsService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _route: ActivatedRoute,
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
) {}
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
getCurrentUser,
|
||||
IqserHelpModeModule,
|
||||
LoadingService,
|
||||
TenantContextHolder,
|
||||
TenantsService,
|
||||
} from '@iqser/common-ui';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
@ -23,17 +23,17 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
imports: [NgIf, IqserHelpModeModule, CircleButtonComponent, TranslateModule],
|
||||
})
|
||||
export class DossierTemplateActionsComponent implements OnInit {
|
||||
@Input() dossierTemplateId: string;
|
||||
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = getCurrentUser<User>();
|
||||
|
||||
@Input() dossierTemplateId: string;
|
||||
|
||||
constructor(
|
||||
private readonly _router: Router,
|
||||
private readonly _route: ActivatedRoute,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
) {}
|
||||
|
||||
@ -51,7 +51,7 @@ export class DossierTemplateActionsComponent implements OnInit {
|
||||
|
||||
const success = await firstValueFrom(this._dossierTemplatesService.delete([this.dossierTemplateId]));
|
||||
if (success) {
|
||||
await this._router.navigate([this._tenantContextHolder.currentTenant, 'main', 'admin']);
|
||||
await this._router.navigate([this._tenantsService.activeTenantId, 'main', 'admin']);
|
||||
}
|
||||
|
||||
this._loadingService.stop();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<iqser-page-header
|
||||
(closeAction)="router.navigate([tenantContextHolder.currentTenant + '/' + dossier.dossiersListRouterLink])"
|
||||
(closeAction)="router.navigate([tenantsService.activeTenantId + '/' + dossier.dossiersListRouterLink])"
|
||||
[actionConfigs]="actionConfigs"
|
||||
[helpModeKey]="'document'"
|
||||
[showCloseButton]="true"
|
||||
@ -7,20 +7,20 @@
|
||||
>
|
||||
<ng-container slot="right">
|
||||
<redaction-file-download-btn
|
||||
[attr.help-mode-key]="'edit_dossier_in_dossier'"
|
||||
[buttonId]="'download-files-btn'"
|
||||
[disabled]="downloadBtnDisabled$ | async"
|
||||
[dossier]="dossier"
|
||||
[files]="entitiesService.all$ | async"
|
||||
[attr.help-mode-key]="'edit_dossier_in_dossier'"
|
||||
></redaction-file-download-btn>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="downloadDossierAsCSV()"
|
||||
*ngIf="permissionsService.canDownloadCsvReport(dossier)"
|
||||
[attr.help-mode-key]="'edit_dossier_in_dossier'"
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[icon]="'iqser:csv'"
|
||||
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
|
||||
[attr.help-mode-key]="'edit_dossier_in_dossier'"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
@ -36,11 +36,11 @@
|
||||
<iqser-circle-button
|
||||
(action)="upload.emit()"
|
||||
*ngIf="permissionsService.canUploadFiles(dossier)"
|
||||
[attr.help-mode-key]="'edit_dossier_in_dossier'"
|
||||
[buttonId]="'upload-document-btn'"
|
||||
[icon]="'iqser:upload'"
|
||||
[tooltip]="'dossier-overview.header-actions.upload-document' | translate"
|
||||
[type]="circleButtonTypes.primary"
|
||||
[attr.help-mode-key]="'edit_dossier_in_dossier'"
|
||||
class="ml-14"
|
||||
></iqser-circle-button>
|
||||
</ng-container>
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
LoadingService,
|
||||
some,
|
||||
SortingService,
|
||||
TenantContextHolder,
|
||||
TenantsService,
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { Dossier, File, IFile } from '@red/domain';
|
||||
@ -44,7 +44,7 @@ export class DossierOverviewScreenHeaderComponent implements OnInit {
|
||||
readonly sortingService: SortingService<File>,
|
||||
readonly permissionsService: PermissionsService,
|
||||
readonly entitiesService: EntitiesService<IFile, File>,
|
||||
readonly tenantContextHolder: TenantContextHolder,
|
||||
readonly tenantsService: TenantsService,
|
||||
private readonly _reanalysisService: ReanalysisService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _primaryFileAttributeService: PrimaryFileAttributeService,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<div class="file-attribute">
|
||||
<div class="value">
|
||||
<div class="value" (click)="editFileAttribute($event)">
|
||||
<span *ngIf="!isDate; else date" class="clamp-3"> {{ fileAttributeValue || '-' }}</span>
|
||||
<ng-template #date>
|
||||
<span class="clamp-3"> {{ fileAttributeValue ? (fileAttributeValue | date : 'd MMM yyyy') : '-' }}</span>
|
||||
@ -24,23 +24,23 @@
|
||||
<mat-icon [svgIcon]="'iqser:edit'"></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #input>
|
||||
<div class="edit-input" stopPropagation>
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<iqser-dynamic-input
|
||||
(closedDatepicker)="closedDatepicker = $event"
|
||||
(keydown.escape)="close()"
|
||||
[formControlName]="fileAttribute.id"
|
||||
[id]="fileAttribute.id"
|
||||
[type]="fileAttribute.type"
|
||||
></iqser-dynamic-input>
|
||||
|
||||
<iqser-circle-button (action)="save()" [disabled]="disabled" [icon]="'iqser:check'" class="save"></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button (action)="close()" [icon]="'iqser:close'"></iqser-circle-button>
|
||||
</form>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-template #input>
|
||||
<div class="edit-input" stopPropagation>
|
||||
<form (submit)="save()" [formGroup]="form">
|
||||
<iqser-dynamic-input
|
||||
(closedDatepicker)="closedDatepicker = $event"
|
||||
(keydown.escape)="close()"
|
||||
[formControlName]="fileAttribute.id"
|
||||
[id]="fileAttribute.id"
|
||||
[type]="fileAttribute.type"
|
||||
></iqser-dynamic-input>
|
||||
|
||||
<iqser-circle-button (action)="save()" [disabled]="disabled" [icon]="'iqser:check'" class="save"></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button (action)="close()" [icon]="'iqser:close'"></iqser-circle-button>
|
||||
</form>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@ -59,8 +59,10 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
}
|
||||
|
||||
async editFileAttribute($event: MouseEvent): Promise<void> {
|
||||
$event.stopPropagation();
|
||||
this.#toggleEdit();
|
||||
if (!this.file.isInitialProcessing && this.permissionsService.canEditFileAttributes(this.file, this.dossier)) {
|
||||
$event.stopPropagation();
|
||||
this.#toggleEdit();
|
||||
}
|
||||
}
|
||||
|
||||
async save() {
|
||||
@ -139,7 +141,7 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
#focusOnEditInput(): void {
|
||||
setTimeout(() => {
|
||||
const input = document.getElementById(this.fileAttribute.id);
|
||||
input.focus();
|
||||
input?.focus();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ export class BulkActionsService {
|
||||
|
||||
async ocr(files: File[]) {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._reanalysisService.ocrFiles(files, files[0].dossierId));
|
||||
await this._reanalysisService.ocrFiles(files, files[0].dossierId);
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
@ -61,9 +61,7 @@ export class BulkActionsService {
|
||||
|
||||
async reanalyse(files: File[]) {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(
|
||||
this._reanalysisService.reanalyzeFilesForDossier(files, files[0].dossierId, { force: true, triggeredByUser: true }),
|
||||
);
|
||||
await this._reanalysisService.reanalyzeFilesForDossier(files, files[0].dossierId, { force: true, triggeredByUser: true });
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
@ -75,7 +73,7 @@ export class BulkActionsService {
|
||||
|
||||
async toggleAnalysis(files: File[], excluded: boolean) {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._reanalysisService.toggleAnalysis(files[0].dossierId, files, excluded));
|
||||
await this._reanalysisService.toggleAnalysis(files[0].dossierId, files, excluded);
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
[config]="dossiersChartConfig$ | async"
|
||||
[radius]="80"
|
||||
[strokeWidth]="15"
|
||||
[subtitles]="['dossier-template-stats.active-dossiers' | translate: { count: stats.numberOfActiveDossiers }]"
|
||||
[subtitles]="['dossier-template-stats.active-dossiers' | translate : { count: stats.numberOfActiveDossiers }]"
|
||||
filterKey="dossierStatesFilters"
|
||||
></redaction-donut-chart>
|
||||
|
||||
@ -29,10 +29,10 @@
|
||||
<div class="right-chart">
|
||||
<redaction-donut-chart
|
||||
[config]="documentsChartConfig$ | async"
|
||||
[helpModeKey]="'filter_dossier_list'"
|
||||
[radius]="80"
|
||||
[strokeWidth]="15"
|
||||
[subtitles]="['dossier-template-stats.total-documents' | translate]"
|
||||
[helpModeKey]="'filter_dossier_list'"
|
||||
filterKey="statusFilters"
|
||||
></redaction-donut-chart>
|
||||
</div>
|
||||
|
||||
@ -31,7 +31,7 @@ import {
|
||||
OnDetach,
|
||||
processFilters,
|
||||
shareDistinctLast,
|
||||
TenantContextHolder,
|
||||
TenantsService,
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
@ -126,7 +126,7 @@ export class FilePreviewScreenComponent
|
||||
private readonly _viewModeService: ViewModeService,
|
||||
private readonly _documentViewer: REDDocumentViewer,
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _dialogService: FilePreviewDialogService,
|
||||
private readonly _pageRotationService: PageRotationService,
|
||||
private readonly _viewerHeaderService: ViewerHeaderService,
|
||||
@ -310,8 +310,7 @@ export class FilePreviewScreenComponent
|
||||
this._subscribeToFileUpdates();
|
||||
|
||||
if (file?.analysisRequired && !file.excludedFromAutomaticAnalysis) {
|
||||
const reanalyzeFiles = this._reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true });
|
||||
await firstValueFrom(reanalyzeFiles);
|
||||
await this._reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true });
|
||||
}
|
||||
|
||||
this.pdfProxyService.configureElements();
|
||||
@ -772,7 +771,7 @@ export class FilePreviewScreenComponent
|
||||
|
||||
private _navigateToDossier() {
|
||||
this._logger.info('Navigating to ', this.state.dossier.dossierName);
|
||||
return this._router.navigate([`/${this._tenantContextHolder.currentTenant}${this.state.dossier.routerLink}`]);
|
||||
return this._router.navigate([`/${this._tenantsService.activeTenantId}${this.state.dossier.routerLink}`]);
|
||||
}
|
||||
|
||||
#highlightSelectedAnnotations(newAnnotations: AnnotationWrapper[]) {
|
||||
|
||||
@ -34,11 +34,11 @@ import { ViewedPagesMapService } from '@services/files/viewed-pages-map.service'
|
||||
|
||||
const DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes;
|
||||
|
||||
function timestampOf(value: string) {
|
||||
export function timestampOf(value: string) {
|
||||
return dayjs(value).valueOf();
|
||||
}
|
||||
|
||||
function chronologicallyBy<T>(property: (x: T) => string) {
|
||||
export function chronologicallyBy<T>(property: (x: T) => string) {
|
||||
return (a: T, b: T) => timestampOf(property(a)) - timestampOf(property(b));
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import type {
|
||||
ManualRedactionActions,
|
||||
} from '@red/domain';
|
||||
import { type AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { GenericService, IqserPermissionsService, List, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, IqserPermissionsService, List, Toaster } from '@iqser/common-ui';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { dictionaryActionsTranslations, manualRedactionActionsTranslations } from '@translations/annotation-actions-translations';
|
||||
@ -53,15 +53,13 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
||||
super();
|
||||
}
|
||||
|
||||
@Validate()
|
||||
async addComment(@RequiredParam() comment: string, @RequiredParam() annotationId: string, dossierId: string, fileId: string) {
|
||||
async addComment(comment: string, annotationId: string, dossierId: string, fileId: string) {
|
||||
const url = `${this._defaultModelPath}/comment/add/${dossierId}/${fileId}/${annotationId}`;
|
||||
const request = await firstValueFrom(this._post<{ commentId: string }>({ text: comment }, url));
|
||||
return request.commentId;
|
||||
}
|
||||
|
||||
@Validate()
|
||||
deleteComment(@RequiredParam() commentId: string, @RequiredParam() annotationId: string, dossierId: string, fileId: string) {
|
||||
deleteComment(commentId: string, annotationId: string, dossierId: string, fileId: string) {
|
||||
const url = `${this._defaultModelPath}/comment/undo/${dossierId}/${fileId}/${annotationId}/${commentId}`;
|
||||
return firstValueFrom(super.delete({}, url));
|
||||
}
|
||||
@ -180,116 +178,73 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
||||
}
|
||||
}
|
||||
|
||||
@Validate()
|
||||
add(@RequiredParam() body: List<IAddRedactionRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
add(body: List<IAddRedactionRequest>, dossierId: string, fileId: string) {
|
||||
return this._post(body, `${this.#bulkRedaction}/add/${dossierId}/${fileId}`).pipe(this.#log('Add', body));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
recategorize(
|
||||
@RequiredParam() body: List<IRecategorizationRequest>,
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
) {
|
||||
recategorize(body: List<IRecategorizationRequest>, dossierId: string, fileId: string) {
|
||||
return this._post(body, `${this.#bulkRedaction}/recategorize/${dossierId}/${fileId}`).pipe(this.#log('Recategorize', body));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
requestRecategorize(
|
||||
@RequiredParam() body: List<IRecategorizationRequest>,
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
) {
|
||||
requestRecategorize(body: List<IRecategorizationRequest>, dossierId: string, fileId: string) {
|
||||
return this._post(body, `${this.#bulkRequest}/recategorize/${dossierId}/${fileId}`).pipe(this.#log('Request recategorize', body));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
legalBasisChange(
|
||||
@RequiredParam() body: List<ILegalBasisChangeRequest>,
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
) {
|
||||
legalBasisChange(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string) {
|
||||
return this._post(body, `${this.#bulkRedaction}/legalBasisChange/${dossierId}/${fileId}`).pipe(
|
||||
this.#log('Legal basis change', body),
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
requestLegalBasisChange(
|
||||
@RequiredParam() body: List<ILegalBasisChangeRequest>,
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
) {
|
||||
requestLegalBasisChange(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string) {
|
||||
return this._post(body, `${this.#bulkRequest}/legalBasis/${dossierId}/${fileId}`).pipe(
|
||||
this.#log('Request legal basis change', body),
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
requestRemoveRedaction(
|
||||
@RequiredParam() body: List<IRemoveRedactionRequest>,
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
) {
|
||||
requestRemoveRedaction(body: List<IRemoveRedactionRequest>, dossierId: string, fileId: string) {
|
||||
return this._post(body, `${this.#bulkRequest}/remove/${dossierId}/${fileId}`).pipe(this.#log('Request remove', body));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
approve(@RequiredParam() annotationIds: List, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
approve(annotationIds: List, dossierId: string, fileId: string) {
|
||||
return this._post(annotationIds, `${this._defaultModelPath}/bulk/approve/${dossierId}/${fileId}`).pipe(
|
||||
this.#log('Approve', annotationIds),
|
||||
this.#showToast('approve'),
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
decline(@RequiredParam() annotationIds: List, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
decline(annotationIds: List, dossierId: string, fileId: string) {
|
||||
return this._post(annotationIds, `${this._defaultModelPath}/bulk/decline/${dossierId}/${fileId}`).pipe(
|
||||
this.#log('Decline', annotationIds),
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
undo(@RequiredParam() annotationIds: List, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
undo(annotationIds: List, dossierId: string, fileId: string) {
|
||||
const url = `${this._defaultModelPath}/bulk/undo/${dossierId}/${fileId}`;
|
||||
return super.delete(annotationIds, url).pipe(this.#log('Undo', annotationIds));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
remove(@RequiredParam() body: List<IRemoveRedactionRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
remove(body: List<IRemoveRedactionRequest>, dossierId: string, fileId: string) {
|
||||
return this._post(body, `${this.#bulkRedaction}/remove/${dossierId}/${fileId}`).pipe(this.#log('Remove', body));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
requestAdd(@RequiredParam() body: List<IAddRedactionRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
requestAdd(body: List<IAddRedactionRequest>, dossierId: string, fileId: string) {
|
||||
return this._post(body, `${this.#bulkRequest}/add/${dossierId}/${fileId}`).pipe(this.#log('Request add', body));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
forceRedaction(
|
||||
@RequiredParam() body: List<ILegalBasisChangeRequest>,
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
) {
|
||||
forceRedaction(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string) {
|
||||
return this._post(body, `${this.#bulkRedaction}/force/${dossierId}/${fileId}`).pipe(this.#log('Force redaction', body));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
forceRequest(
|
||||
@RequiredParam() body: List<ILegalBasisChangeRequest>,
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
) {
|
||||
forceRequest(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string) {
|
||||
return this._post(body, `${this.#bulkRequest}/force/${dossierId}/${fileId}`).pipe(this.#log('Request force redaction', body));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
resize(@RequiredParam() body: List<IResizeRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
resize(body: List<IResizeRequest>, dossierId: string, fileId: string) {
|
||||
return this._post(body, `${this.#bulkRedaction}/resize/${dossierId}/${fileId}`).pipe(this.#log('Resize', body));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
requestResize(@RequiredParam() body: List<IResizeRequest>, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
requestResize(body: List<IResizeRequest>, dossierId: string, fileId: string) {
|
||||
return this._post(body, `${this.#bulkRequest}/resize/${dossierId}/${fileId}`).pipe(this.#log('Request resize', body));
|
||||
}
|
||||
|
||||
@ -302,10 +257,10 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
||||
#showToast(action: ManualRedactionActions | DictionaryActions, isDictionary = false) {
|
||||
return tap({
|
||||
next: () => this._toaster.success(getMessage(action, isDictionary), { positionClass: 'toast-file-preview' }),
|
||||
error: (error: HttpErrorResponse) => {
|
||||
const isConflict = error.status === HttpStatusCode.Conflict;
|
||||
error: (error: unknown) => {
|
||||
const isConflict = (error as HttpErrorResponse).status === HttpStatusCode.Conflict;
|
||||
this._toaster.error(getMessage(action, isDictionary, true, isConflict), {
|
||||
error,
|
||||
error: error as HttpErrorResponse,
|
||||
positionClass: 'toast-file-preview',
|
||||
});
|
||||
},
|
||||
@ -315,10 +270,10 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
|
||||
#showAddToDictionaryToast(body: List<IAddRedactionRequest>, dictionaryLabel?: string) {
|
||||
return tap({
|
||||
next: () => this._toaster.success(getDictionaryMessage('add'), { positionClass: 'toast-file-preview' }),
|
||||
error: (error: HttpErrorResponse) => {
|
||||
const isConflict = error.status === HttpStatusCode.Conflict;
|
||||
error: (error: unknown) => {
|
||||
const isConflict = (error as HttpErrorResponse).status === HttpStatusCode.Conflict;
|
||||
this._toaster.error(getDictionaryMessage('add', true, isConflict), {
|
||||
error,
|
||||
error: error as HttpErrorResponse,
|
||||
params: {
|
||||
dictionaryName: dictionaryLabel,
|
||||
content: body[0].value,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectorRef, Inject, Injectable, NgZone } from '@angular/core';
|
||||
import { ChangeDetectorRef, inject, Injectable, NgZone } from '@angular/core';
|
||||
import { IHeaderElement, IManualRedactionEntry, ViewModes } from '@red/domain';
|
||||
import { Core } from '@pdftron/webviewer';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@ -9,7 +9,7 @@ import {
|
||||
} from '@models/file/manual-redaction-entry.wrapper';
|
||||
import { AnnotationDrawService } from '../../pdf-viewer/services/annotation-draw.service';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { BASE_HREF_FN, BaseHrefFn, IqserPermissionsService, isJustOne, shareDistinctLast } from '@iqser/common-ui';
|
||||
import { BASE_HREF_FN, IqserPermissionsService, isJustOne, shareDistinctLast } from '@iqser/common-ui';
|
||||
import { toPosition } from '../utils/pdf-calculation.utils';
|
||||
import { MultiSelectService } from './multi-select.service';
|
||||
import { FilePreviewStateService } from './file-preview-state.service';
|
||||
@ -48,8 +48,8 @@ export class PdfProxyService {
|
||||
shareDistinctLast(),
|
||||
);
|
||||
canPerformActions = true;
|
||||
|
||||
readonly canPerformAnnotationActions$: Observable<boolean>;
|
||||
private readonly _convertPath = inject(BASE_HREF_FN);
|
||||
readonly #visibilityOffIcon = this._convertPath('/assets/icons/general/visibility-off.svg');
|
||||
readonly #visibilityIcon = this._convertPath('/assets/icons/general/visibility.svg');
|
||||
readonly #falsePositiveIcon = this._convertPath('/assets/icons/general/pdftron-action-false-positive.svg');
|
||||
@ -59,7 +59,6 @@ export class PdfProxyService {
|
||||
readonly #addDictIcon = this._convertPath('/assets/icons/general/pdftron-action-add-dict.svg');
|
||||
|
||||
constructor(
|
||||
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _manualRedactionService: ManualRedactionService,
|
||||
private readonly _annotationsActionsService: AnnotationActionsService,
|
||||
@ -117,6 +116,145 @@ export class PdfProxyService {
|
||||
this._documentViewer.setRectangleToolStyles(color);
|
||||
}
|
||||
|
||||
private _configureRectangleAnnotationPopup(annotation: Annotation) {
|
||||
if (!this._pdf.isCompare || annotation.getPageNumber() % 2 === 1) {
|
||||
const addRectangleButton = {
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_RECTANGLE,
|
||||
img: this.#addRedactionIcon,
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
|
||||
onClick: () => this._addRectangleManualRedaction(),
|
||||
};
|
||||
|
||||
this._pdf.instance.UI.annotationPopup.add([addRectangleButton]);
|
||||
}
|
||||
}
|
||||
|
||||
private _addRectangleManualRedaction() {
|
||||
const activeAnnotation = this._annotationManager.selected[0];
|
||||
const activePage = activeAnnotation.getPageNumber();
|
||||
const quads = [this._annotationDrawService.annotationToQuads(activeAnnotation)];
|
||||
const manualRedactionEntry = this._getManualRedaction({ [activePage]: quads });
|
||||
this._cleanUpSelectionAndButtonState();
|
||||
|
||||
this.manualAnnotationRequested$.next({ manualRedactionEntry, type: 'REDACTION' });
|
||||
}
|
||||
|
||||
private _cleanUpSelectionAndButtonState() {
|
||||
this._viewerHeaderService.disable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]);
|
||||
this._viewerHeaderService.enable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]);
|
||||
}
|
||||
|
||||
private _configureTextPopup() {
|
||||
const popups: IHeaderElement[] = [];
|
||||
|
||||
if (!this._state.file.isApproved) {
|
||||
// Adding directly to the false-positive dict is only available in dev-mode
|
||||
if (this._userPreferenceService.areDevFeaturesEnabled) {
|
||||
popups.push({
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_FALSE_POSITIVE,
|
||||
img: this.#falsePositiveIcon,
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.FALSE_POSITIVE),
|
||||
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.FALSE_POSITIVE),
|
||||
});
|
||||
}
|
||||
|
||||
if (this._iqserPermissionsService.has(ROLES.redactions.write) || this._iqserPermissionsService.has(ROLES.redactions.request)) {
|
||||
popups.push({
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_REDACTION,
|
||||
img: this.#addRedactionIcon,
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
|
||||
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION),
|
||||
});
|
||||
|
||||
if (!this._iqserPermissionsService.has(ROLES.getRss)) {
|
||||
popups.push({
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_DICTIONARY,
|
||||
img: this.#addDictIcon,
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY),
|
||||
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._pdf.configureTextPopups(popups);
|
||||
|
||||
return this._handleCustomActions();
|
||||
}
|
||||
|
||||
private _addManualRedactionOfType(type: ManualRedactionEntryType) {
|
||||
const selectedQuads: Record<string, Quad[]> = this._pdf.documentViewer.getSelectedTextQuads();
|
||||
const text = this._documentViewer.selectedText;
|
||||
const manualRedactionEntry = this._getManualRedaction(selectedQuads, text, true);
|
||||
this.manualAnnotationRequested$.next({ manualRedactionEntry, type });
|
||||
}
|
||||
|
||||
private _handleCustomActions() {
|
||||
const isCurrentPageExcluded = this._state.file.isPageExcluded(this._pdf.currentPage);
|
||||
|
||||
if (this._viewModeService.viewMode === ViewModes.REDACTED) {
|
||||
this._viewerHeaderService.enable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
|
||||
} else {
|
||||
this._viewerHeaderService.disable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
|
||||
}
|
||||
|
||||
if (!isCurrentPageExcluded) {
|
||||
if (this.canPerformActions) {
|
||||
try {
|
||||
this._pdf.instance.UI.enableTools([AnnotationToolNames.AnnotationCreateRectangle]);
|
||||
} catch (e) {
|
||||
// happens
|
||||
}
|
||||
this._pdf.enable(TEXT_POPUPS_TO_TOGGLE);
|
||||
this._viewerHeaderService.enable(HEADER_ITEMS_TO_TOGGLE);
|
||||
|
||||
if (this._documentViewer.selectedText.length > 2) {
|
||||
this._pdf.enable([TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]);
|
||||
}
|
||||
} else {
|
||||
this._pdf.instance.UI.disableTools([AnnotationToolNames.AnnotationCreateRectangle]);
|
||||
this._pdf.disable(TEXT_POPUPS_TO_TOGGLE);
|
||||
this._viewerHeaderService.disable(HEADER_ITEMS_TO_TOGGLE);
|
||||
}
|
||||
} else {
|
||||
let textPopupElementsToDisable = [...TEXT_POPUPS_TO_TOGGLE];
|
||||
let headerElementsToDisable = [...HEADER_ITEMS_TO_TOGGLE];
|
||||
|
||||
if (this.canPerformActions) {
|
||||
this._pdf.enable(ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED);
|
||||
this._viewerHeaderService.enable(ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED as HeaderElementType[]);
|
||||
|
||||
textPopupElementsToDisable = textPopupElementsToDisable.filter(
|
||||
element => !ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED.includes(element),
|
||||
);
|
||||
headerElementsToDisable = headerElementsToDisable.filter(element => !ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED.includes(element));
|
||||
}
|
||||
|
||||
this._pdf.disable(textPopupElementsToDisable);
|
||||
this._viewerHeaderService.disable(headerElementsToDisable);
|
||||
}
|
||||
}
|
||||
|
||||
private _getManualRedaction(quads: Record<string, Quad[]>, text?: string, convertQuads = false): IManualRedactionEntry {
|
||||
const entry: IManualRedactionEntry = { positions: [] };
|
||||
|
||||
for (const key of Object.keys(quads)) {
|
||||
for (const quad of quads[key]) {
|
||||
const page = parseInt(key, 10);
|
||||
const pageHeight = this._documentViewer.getHeight(page);
|
||||
entry.positions.push(toPosition(page, pageHeight, convertQuads ? this._pdf.translateQuad(page, quad) : quad));
|
||||
}
|
||||
}
|
||||
|
||||
entry.value = text;
|
||||
entry.rectangle = !text;
|
||||
return entry;
|
||||
}
|
||||
|
||||
#deactivateMultiSelect() {
|
||||
this._multiSelectService.deactivate();
|
||||
this._annotationManager.deselect();
|
||||
@ -236,146 +374,7 @@ export class PdfProxyService {
|
||||
this._pdf.instance.UI.annotationPopup.add(actions);
|
||||
}
|
||||
|
||||
private _configureRectangleAnnotationPopup(annotation: Annotation) {
|
||||
if (!this._pdf.isCompare || annotation.getPageNumber() % 2 === 1) {
|
||||
const addRectangleButton = {
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_RECTANGLE,
|
||||
img: this.#addRedactionIcon,
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
|
||||
onClick: () => this._addRectangleManualRedaction(),
|
||||
};
|
||||
|
||||
this._pdf.instance.UI.annotationPopup.add([addRectangleButton]);
|
||||
}
|
||||
}
|
||||
|
||||
private _addRectangleManualRedaction() {
|
||||
const activeAnnotation = this._annotationManager.selected[0];
|
||||
const activePage = activeAnnotation.getPageNumber();
|
||||
const quads = [this._annotationDrawService.annotationToQuads(activeAnnotation)];
|
||||
const manualRedactionEntry = this._getManualRedaction({ [activePage]: quads });
|
||||
this._cleanUpSelectionAndButtonState();
|
||||
|
||||
this.manualAnnotationRequested$.next({ manualRedactionEntry, type: 'REDACTION' });
|
||||
}
|
||||
|
||||
private _cleanUpSelectionAndButtonState() {
|
||||
this._viewerHeaderService.disable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]);
|
||||
this._viewerHeaderService.enable([HeaderElements.SHAPE_TOOL_GROUP_BUTTON]);
|
||||
}
|
||||
|
||||
private _configureTextPopup() {
|
||||
const popups: IHeaderElement[] = [];
|
||||
|
||||
if (!this._state.file.isApproved) {
|
||||
// Adding directly to the false-positive dict is only available in dev-mode
|
||||
if (this._userPreferenceService.areDevFeaturesEnabled) {
|
||||
popups.push({
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_FALSE_POSITIVE,
|
||||
img: this.#falsePositiveIcon,
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.FALSE_POSITIVE),
|
||||
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.FALSE_POSITIVE),
|
||||
});
|
||||
}
|
||||
|
||||
if (this._iqserPermissionsService.has(ROLES.redactions.write) || this._iqserPermissionsService.has(ROLES.redactions.request)) {
|
||||
popups.push({
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_REDACTION,
|
||||
img: this.#addRedactionIcon,
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.REDACTION),
|
||||
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.REDACTION),
|
||||
});
|
||||
|
||||
if (!this._iqserPermissionsService.has(ROLES.getRss)) {
|
||||
popups.push({
|
||||
type: 'actionButton',
|
||||
dataElement: TextPopups.ADD_DICTIONARY,
|
||||
img: this.#addDictIcon,
|
||||
title: this.#getTitle(ManualRedactionEntryTypes.DICTIONARY),
|
||||
onClick: () => this._addManualRedactionOfType(ManualRedactionEntryTypes.DICTIONARY),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._pdf.configureTextPopups(popups);
|
||||
|
||||
return this._handleCustomActions();
|
||||
}
|
||||
|
||||
#getTitle(type: ManualRedactionEntryType) {
|
||||
return this._translateService.instant(this._manualRedactionService.getTitle(type, this._state.dossier));
|
||||
}
|
||||
|
||||
private _addManualRedactionOfType(type: ManualRedactionEntryType) {
|
||||
const selectedQuads: Record<string, Quad[]> = this._pdf.documentViewer.getSelectedTextQuads();
|
||||
const text = this._documentViewer.selectedText;
|
||||
const manualRedactionEntry = this._getManualRedaction(selectedQuads, text, true);
|
||||
this.manualAnnotationRequested$.next({ manualRedactionEntry, type });
|
||||
}
|
||||
|
||||
private _handleCustomActions() {
|
||||
const isCurrentPageExcluded = this._state.file.isPageExcluded(this._pdf.currentPage);
|
||||
|
||||
if (this._viewModeService.viewMode === ViewModes.REDACTED) {
|
||||
this._viewerHeaderService.enable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
|
||||
} else {
|
||||
this._viewerHeaderService.disable([HeaderElements.TOGGLE_READABLE_REDACTIONS]);
|
||||
}
|
||||
|
||||
if (!isCurrentPageExcluded) {
|
||||
if (this.canPerformActions) {
|
||||
try {
|
||||
this._pdf.instance.UI.enableTools([AnnotationToolNames.AnnotationCreateRectangle]);
|
||||
} catch (e) {
|
||||
// happens
|
||||
}
|
||||
this._pdf.enable(TEXT_POPUPS_TO_TOGGLE);
|
||||
this._viewerHeaderService.enable(HEADER_ITEMS_TO_TOGGLE);
|
||||
|
||||
if (this._documentViewer.selectedText.length > 2) {
|
||||
this._pdf.enable([TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]);
|
||||
}
|
||||
} else {
|
||||
this._pdf.instance.UI.disableTools([AnnotationToolNames.AnnotationCreateRectangle]);
|
||||
this._pdf.disable(TEXT_POPUPS_TO_TOGGLE);
|
||||
this._viewerHeaderService.disable(HEADER_ITEMS_TO_TOGGLE);
|
||||
}
|
||||
} else {
|
||||
let textPopupElementsToDisable = [...TEXT_POPUPS_TO_TOGGLE];
|
||||
let headerElementsToDisable = [...HEADER_ITEMS_TO_TOGGLE];
|
||||
|
||||
if (this.canPerformActions) {
|
||||
this._pdf.enable(ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED);
|
||||
this._viewerHeaderService.enable(ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED as HeaderElementType[]);
|
||||
|
||||
textPopupElementsToDisable = textPopupElementsToDisable.filter(
|
||||
element => !ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED.includes(element),
|
||||
);
|
||||
headerElementsToDisable = headerElementsToDisable.filter(element => !ALLOWED_ACTIONS_WHEN_PAGE_EXCLUDED.includes(element));
|
||||
}
|
||||
|
||||
this._pdf.disable(textPopupElementsToDisable);
|
||||
this._viewerHeaderService.disable(headerElementsToDisable);
|
||||
}
|
||||
}
|
||||
|
||||
private _getManualRedaction(quads: Record<string, Quad[]>, text?: string, convertQuads = false): IManualRedactionEntry {
|
||||
const entry: IManualRedactionEntry = { positions: [] };
|
||||
|
||||
for (const key of Object.keys(quads)) {
|
||||
for (const quad of quads[key]) {
|
||||
const page = parseInt(key, 10);
|
||||
const pageHeight = this._documentViewer.getHeight(page);
|
||||
entry.positions.push(toPosition(page, pageHeight, convertQuads ? this._pdf.translateQuad(page, quad) : quad));
|
||||
}
|
||||
}
|
||||
|
||||
entry.value = text;
|
||||
entry.rectangle = !text;
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
|
||||
import { MatLegacySlideToggleModule as MatSlideToggleModule } from '@angular/material/legacy-slide-toggle';
|
||||
import { MatLegacySliderModule as MatSliderModule } from '@angular/material/legacy-slider';
|
||||
import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { inject, Inject, Injectable, Injector } from '@angular/core';
|
||||
import { inject, Injectable, Injector } from '@angular/core';
|
||||
import WebViewer, { Core, WebViewerInstance, WebViewerOptions } from '@pdftron/webviewer';
|
||||
import { BASE_HREF_FN, BaseHrefFn, ErrorService, getConfig, shareDistinctLast } from '@iqser/common-ui';
|
||||
import { BASE_HREF_FN, ErrorService, getConfig, shareDistinctLast } from '@iqser/common-ui';
|
||||
import { AppConfig, File, IHeaderElement } from '@red/domain';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
@ -35,6 +35,7 @@ export class PdfViewer {
|
||||
compareMode$: Observable<boolean>;
|
||||
totalPages$: Observable<number>;
|
||||
|
||||
private readonly _convertPath = inject(BASE_HREF_FN);
|
||||
#instance: WebViewerInstance;
|
||||
readonly #licenseKey = inject(LicenseService).activeLicenseKey;
|
||||
readonly #config = getConfig<AppConfig>();
|
||||
@ -54,7 +55,6 @@ export class PdfViewer {
|
||||
private readonly _injector: Injector,
|
||||
private readonly _errorService: ErrorService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
||||
) {}
|
||||
|
||||
get instance() {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { BASE_HREF_FN, BaseHrefFn, bool } from '@iqser/common-ui';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { BASE_HREF_FN } from '@iqser/common-ui';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { HeaderElements } from '../../file-preview/utils/constants';
|
||||
@ -13,12 +13,12 @@ import Annotation = Core.Annotations.Annotation;
|
||||
@Injectable()
|
||||
export class ReadableRedactionsService {
|
||||
readonly active$: Observable<boolean>;
|
||||
private readonly _convertPath = inject(BASE_HREF_FN);
|
||||
readonly #enableIcon = this._convertPath('/assets/icons/general/redaction-preview.svg');
|
||||
readonly #disableIcon = this._convertPath('/assets/icons/general/redaction-final.svg');
|
||||
readonly #active$ = new BehaviorSubject<boolean>(true);
|
||||
|
||||
constructor(
|
||||
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
||||
private readonly _pdf: PdfViewer,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _annotationManager: REDAnnotationManager,
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { HeaderElements } from '../../file-preview/utils/constants';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BASE_HREF_FN, BaseHrefFn } from '@iqser/common-ui';
|
||||
import { BASE_HREF_FN } from '@iqser/common-ui';
|
||||
import { PdfViewer } from './pdf-viewer.service';
|
||||
import { REDDocumentViewer } from './document-viewer.service';
|
||||
|
||||
@Injectable()
|
||||
export class TooltipsService {
|
||||
private readonly _convertPath = inject(BASE_HREF_FN);
|
||||
readonly #enableIcon = this._convertPath('/assets/icons/general/pdftron-action-enable-tooltips.svg');
|
||||
readonly #disableIcon = this._convertPath('/assets/icons/general/pdftron-action-disable-tooltips.svg');
|
||||
|
||||
constructor(
|
||||
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
||||
private readonly _pdf: PdfViewer,
|
||||
private readonly _documentViewer: REDDocumentViewer,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { IHeaderElement, RotationTypes } from '@red/domain';
|
||||
import { HeaderElements, HeaderElementType } from '../../file-preview/utils/constants';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BASE_HREF_FN, BaseHrefFn } from '@iqser/common-ui';
|
||||
import { BASE_HREF_FN } from '@iqser/common-ui';
|
||||
import { TooltipsService } from './tooltips.service';
|
||||
import { PageRotationService } from './page-rotation.service';
|
||||
import { PdfViewer } from './pdf-viewer.service';
|
||||
@ -23,6 +23,7 @@ const divider: IHeaderElement = {
|
||||
export class ViewerHeaderService {
|
||||
readonly events$: Observable<ViewerEvent>;
|
||||
toggleLoadAnnotations$: Observable<boolean>;
|
||||
private readonly _convertPath = inject(BASE_HREF_FN);
|
||||
#buttons: Map<HeaderElementType, IHeaderElement>;
|
||||
readonly #config = new Map<HeaderElementType, boolean>([
|
||||
[HeaderElements.SHAPE_TOOL_GROUP_BUTTON, true],
|
||||
@ -40,7 +41,6 @@ export class ViewerHeaderService {
|
||||
readonly #events$ = new Subject<ViewerEvent>();
|
||||
|
||||
constructor(
|
||||
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _pdf: PdfViewer,
|
||||
@ -311,19 +311,6 @@ export class ViewerHeaderService {
|
||||
this.enable([HeaderElements.COMPARE_BUTTON]);
|
||||
}
|
||||
|
||||
#discardRotation(): void {
|
||||
this._rotationService.discardRotation();
|
||||
this.disable(ROTATION_ACTION_BUTTONS);
|
||||
}
|
||||
|
||||
#toggleRotationActionButtons() {
|
||||
if (this._rotationService.hasRotations) {
|
||||
this.enable(ROTATION_ACTION_BUTTONS);
|
||||
} else {
|
||||
this.disable(ROTATION_ACTION_BUTTONS);
|
||||
}
|
||||
}
|
||||
|
||||
private _closeCompareMode() {
|
||||
this._pdf.closeCompareMode();
|
||||
const { dossierId, fileId } = this._pdf;
|
||||
@ -353,4 +340,17 @@ export class ViewerHeaderService {
|
||||
private _isEnabled(key: HeaderElementType): boolean {
|
||||
return this.#config.get(key);
|
||||
}
|
||||
|
||||
#discardRotation(): void {
|
||||
this._rotationService.discardRotation();
|
||||
this.disable(ROTATION_ACTION_BUTTONS);
|
||||
}
|
||||
|
||||
#toggleRotationActionButtons() {
|
||||
if (this._rotationService.hasRotations) {
|
||||
this.enable(ROTATION_ACTION_BUTTONS);
|
||||
} else {
|
||||
this.disable(ROTATION_ACTION_BUTTONS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
<div [class.error]="isError" class="small-label">
|
||||
{{ date | date: 'd MMM yyyy' }}
|
||||
{{ date | date : 'd MMM yyyy' }}
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectorRef, Component, HostBinding, Injector, Input, OnChanges, Optional, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, HostBinding, Injector, Input, OnChanges, Optional, ViewChild } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { Action, ActionTypes, Dossier, File, ProcessingFileStatuses, User } from '@red/domain';
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
@ -10,8 +10,7 @@ import {
|
||||
IqserPermissionsService,
|
||||
IqserTooltipPositions,
|
||||
LoadingService,
|
||||
ScrollableParentView,
|
||||
TenantContextHolder,
|
||||
TenantsService,
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
@ -39,15 +38,16 @@ import { FileAttributesService } from '@services/entity-services/file-attributes
|
||||
styleUrls: ['./file-actions.component.scss'],
|
||||
})
|
||||
export class FileActionsComponent implements OnChanges {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = getCurrentUser<User>();
|
||||
|
||||
@Input() file: File;
|
||||
@Input() dossier: Dossier;
|
||||
@Input() type: 'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow';
|
||||
@Input() maxWidth: number;
|
||||
@Input() minWidth: number;
|
||||
@Input() fileActionsHelpModeKey: 'document_features_in_dossier' | 'editor_document_features' = 'document_features_in_dossier';
|
||||
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = getCurrentUser<User>();
|
||||
|
||||
toggleTooltip?: string;
|
||||
assignTooltip?: string;
|
||||
buttonType?: CircleButtonType;
|
||||
@ -78,8 +78,6 @@ export class FileActionsComponent implements OnChanges {
|
||||
tooltipPosition = IqserTooltipPositions.above;
|
||||
buttons: Action[];
|
||||
|
||||
scrollableParentView: ScrollableParentView;
|
||||
|
||||
@ViewChild(ExpandableFileActionsComponent)
|
||||
private readonly _expandableActionsComponent: ExpandableFileActionsComponent;
|
||||
|
||||
@ -89,7 +87,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _fileAssignService: FileAssignService,
|
||||
private readonly _reanalysisService: ReanalysisService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
@ -120,7 +118,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'delete-file-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._openDeleteFileDialog(),
|
||||
action: () => this.#openDeleteFileDialog(),
|
||||
tooltip: _('dossier-overview.delete.action'),
|
||||
icon: 'iqser:trash',
|
||||
show: this.showDelete,
|
||||
@ -128,7 +126,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'assign-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._assign(),
|
||||
action: () => this.#assign(),
|
||||
tooltip: this.assignTooltip,
|
||||
icon: 'red:assign',
|
||||
show: this.showAssign,
|
||||
@ -136,7 +134,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'assign-to-me-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._assignToMe(),
|
||||
action: () => this.#assignToMe(),
|
||||
tooltip: _('dossier-overview.assign-me'),
|
||||
icon: 'red:assign-me',
|
||||
show: this.showAssignToSelf,
|
||||
@ -144,7 +142,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'open-import-redactions-dialog-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._openImportRedactionsDialog(),
|
||||
action: () => this.#openImportRedactionsDialog(),
|
||||
tooltip: _('dossier-overview.import-redactions'),
|
||||
icon: 'red:import_redactions',
|
||||
show: this.showImportRedactions && !this._iqserPermissionsService.has(ROLES.getRss),
|
||||
@ -191,7 +189,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'set-file-under-approval-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._setFileUnderApproval(),
|
||||
action: () => this.#setFileUnderApproval(),
|
||||
tooltip: _('dossier-overview.under-approval'),
|
||||
icon: 'red:ready-for-approval',
|
||||
show: this.showUnderApproval,
|
||||
@ -199,7 +197,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'set-file-under-review-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._setFileUnderReview(),
|
||||
action: () => this.#setFileUnderReview(),
|
||||
tooltip: _('dossier-overview.under-review'),
|
||||
icon: 'red:undo',
|
||||
show: this.showUnderReview,
|
||||
@ -216,7 +214,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'toggle-automatic-analysis-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._toggleAutomaticAnalysis(),
|
||||
action: () => this.#toggleAutomaticAnalysis(),
|
||||
tooltip: _('dossier-overview.stop-auto-analysis'),
|
||||
icon: 'red:disable-analysis',
|
||||
show: this.canDisableAutoAnalysis,
|
||||
@ -224,7 +222,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'reanalyse-file-preview-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._reanalyseFile(),
|
||||
action: () => this.#reanalyseFile(),
|
||||
tooltip: _('file-preview.reanalyse-notification'),
|
||||
tooltipClass: 'small',
|
||||
icon: 'iqser:refresh',
|
||||
@ -234,7 +232,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'toggle-automatic-analysis-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._toggleAutomaticAnalysis(),
|
||||
action: () => this.#toggleAutomaticAnalysis(),
|
||||
tooltip: _('dossier-overview.start-auto-analysis'),
|
||||
buttonType: this.isFilePreview ? CircleButtonTypes.warn : CircleButtonTypes.default,
|
||||
icon: 'red:enable-analysis',
|
||||
@ -243,7 +241,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'set-under-approval-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._setFileUnderApproval(),
|
||||
action: () => this.#setFileUnderApproval(),
|
||||
tooltip: _('dossier-overview.under-approval'),
|
||||
icon: 'red:undo',
|
||||
show: this.showUndoApproval,
|
||||
@ -251,7 +249,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'ocr-file-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._ocrFile(),
|
||||
action: () => this.#ocrFile(),
|
||||
tooltip: _('dossier-overview.ocr-file'),
|
||||
icon: 'iqser:ocr',
|
||||
show: this.showOCR,
|
||||
@ -259,7 +257,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'reanalyse-file-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._reanalyseFile(),
|
||||
action: () => this.#reanalyseFile(),
|
||||
tooltip: _('dossier-overview.reanalyse.action'),
|
||||
icon: 'iqser:refresh',
|
||||
show: this.showReanalyseDossierOverview,
|
||||
@ -267,7 +265,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'toggle-analysis-btn',
|
||||
type: ActionTypes.toggle,
|
||||
action: () => this._toggleAnalysis(),
|
||||
action: () => this.#toggleAnalysis(),
|
||||
disabled: !this.canToggleAnalysis,
|
||||
tooltip: this.toggleTooltip,
|
||||
class: { 'mr-24': this.isDossierOverviewList },
|
||||
@ -280,7 +278,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this._setup();
|
||||
this.#setup();
|
||||
}
|
||||
|
||||
async setFileApproved() {
|
||||
@ -305,7 +303,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
|
||||
forceReanalysisAction($event: LongPressEvent) {
|
||||
this.analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled;
|
||||
this._setup();
|
||||
this.#setup();
|
||||
}
|
||||
|
||||
#showOCRConfirmationDialog(): Observable<boolean> {
|
||||
@ -319,11 +317,11 @@ export class FileActionsComponent implements OnChanges {
|
||||
return ref.afterClosed();
|
||||
}
|
||||
|
||||
private _openImportRedactionsDialog() {
|
||||
#openImportRedactionsDialog() {
|
||||
this._dialogService.openDialog('importRedactions', { dossierId: this.file.dossierId, fileId: this.file.fileId });
|
||||
}
|
||||
|
||||
private _openDeleteFileDialog() {
|
||||
#openDeleteFileDialog() {
|
||||
this._dialogService.openDialog(
|
||||
'confirm',
|
||||
{
|
||||
@ -335,7 +333,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
try {
|
||||
const dossier = this._activeDossiersService.find(this.file.dossierId);
|
||||
await firstValueFrom(this._fileManagementService.delete([this.file], this.file.dossierId));
|
||||
await this._injector.get(Router).navigate([`/${this._tenantContextHolder.currentTenant}${dossier.routerLink}`]);
|
||||
await this._injector.get(Router).navigate([`/${this._tenantsService.activeTenantId}${dossier.routerLink}`]);
|
||||
} catch (error) {
|
||||
this._injector.get(Toaster).error(_('error.http.generic'), { params: error });
|
||||
}
|
||||
@ -344,7 +342,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
);
|
||||
}
|
||||
|
||||
private _assign() {
|
||||
#assign() {
|
||||
const files = [this.file];
|
||||
const targetStatus = this.file.workflowStatus;
|
||||
const withCurrentUserAsDefault = true;
|
||||
@ -352,29 +350,29 @@ export class FileActionsComponent implements OnChanges {
|
||||
this._dialogService.openDialog('assignFile', { targetStatus, files, withCurrentUserAsDefault, withUnassignedOption });
|
||||
}
|
||||
|
||||
private async _assignToMe() {
|
||||
async #assignToMe() {
|
||||
await this._fileAssignService.assignToMe([this.file]);
|
||||
}
|
||||
|
||||
private async _reanalyseFile() {
|
||||
async #reanalyseFile() {
|
||||
const params: ReanalyzeQueryParams = {
|
||||
force: true,
|
||||
triggeredByUser: true,
|
||||
};
|
||||
await firstValueFrom(this._reanalysisService.reanalyzeFilesForDossier([this.file], this.file.dossierId, params));
|
||||
await this._reanalysisService.reanalyzeFilesForDossier([this.file], this.file.dossierId, params);
|
||||
}
|
||||
|
||||
private async _toggleAutomaticAnalysis() {
|
||||
async #toggleAutomaticAnalysis() {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._reanalysisService.toggleAutomaticAnalysis(this.file.dossierId, [this.file]));
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
private async _setFileUnderApproval() {
|
||||
async #setFileUnderApproval() {
|
||||
await this._fileAssignService.assignApprover(this.file, true);
|
||||
}
|
||||
|
||||
private async _ocrFile() {
|
||||
async #ocrFile() {
|
||||
if (this.file.lastManualChangeDate) {
|
||||
const confirm = await firstValueFrom(this.#showOCRConfirmationDialog());
|
||||
if (!confirm) {
|
||||
@ -388,21 +386,21 @@ export class FileActionsComponent implements OnChanges {
|
||||
viewerHeaderService.disable(ROTATION_ACTION_BUTTONS);
|
||||
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._reanalysisService.ocrFiles([this.file], this.file.dossierId));
|
||||
await this._reanalysisService.ocrFiles([this.file], this.file.dossierId);
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
private async _setFileUnderReview() {
|
||||
async #setFileUnderReview() {
|
||||
await this._fileAssignService.assignReviewer(this.file, true);
|
||||
}
|
||||
|
||||
private async _toggleAnalysis() {
|
||||
async #toggleAnalysis() {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._reanalysisService.toggleAnalysis(this.file.dossierId, [this.file], !this.file.excluded));
|
||||
await this._reanalysisService.toggleAnalysis(this.file.dossierId, [this.file], !this.file.excluded);
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
private _setup() {
|
||||
#setup() {
|
||||
this.isDossierOverviewList = this.type === 'dossier-overview-list';
|
||||
this.isDossierOverviewWorkflow = this.type === 'dossier-overview-workflow';
|
||||
this.isDossierOverview = this.type.startsWith('dossier-overview');
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||
{{ 'edit-dossier-dialog.dictionary.entries' | translate: { length: (dossierDictionary?.entries || []).length } }}
|
||||
{{ 'edit-dossier-dialog.dictionary.entries' | translate : { length: (dossierDictionary?.entries || []).length } }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -44,7 +44,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
||||
async ngOnInit() {
|
||||
this._loadingService.start();
|
||||
this.canEdit = this._permissionsService.canEditDossierDictionary(this.dossier);
|
||||
await this._updateDossierDictionary();
|
||||
await this.#updateDossierDictionary();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
||||
),
|
||||
);
|
||||
|
||||
await this._updateDossierDictionary();
|
||||
await this.#updateDossierDictionary();
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { success: false };
|
||||
@ -75,12 +75,12 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
||||
openEditDetailsDialog(): void {
|
||||
const data = { dictionary: this.dossierDictionary, dossierId: this.dossier.id, readOnly: !this.canEdit };
|
||||
this._dialogService.openDialog('editDictionaryDetails', data, async () => {
|
||||
await this._updateDossierDictionary();
|
||||
await this.#updateDossierDictionary();
|
||||
});
|
||||
}
|
||||
|
||||
private async _updateDossierDictionary() {
|
||||
async #updateDossierDictionary() {
|
||||
const { dossierId, dossierTemplateId } = this.dossier;
|
||||
this.dossierDictionary = await firstValueFrom(this._dictionaryService.loadDossierDictionary(dossierTemplateId, dossierId));
|
||||
this.dossierDictionary = await this._dictionaryService.loadDossierDictionary(dossierTemplateId, dossierId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
IconButtonTypes,
|
||||
IConfirmationDialogData,
|
||||
LoadingService,
|
||||
TenantContextHolder,
|
||||
TenantsService,
|
||||
TitleColors,
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
@ -35,10 +35,10 @@ import { dateWithoutTime } from '@utils/functions';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSectionInterface {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
|
||||
@Input() dossier: Dossier;
|
||||
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
|
||||
form: UntypedFormGroup;
|
||||
statusPlaceholder: string;
|
||||
hasDueDate: boolean;
|
||||
@ -55,7 +55,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
private readonly _formBuilder: UntypedFormBuilder,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _router: Router,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _editDossierDialogRef: MatDialogRef<EditDossierDialogComponent>,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _loadingService: LoadingService,
|
||||
@ -153,7 +153,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._trashService.deleteDossier(this.dossier));
|
||||
this._editDossierDialogRef.close();
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}${this.dossier.dossiersListRouterLink}`]);
|
||||
await this._router.navigate([`/${this._tenantsService.activeTenantId}${this.dossier.dossiersListRouterLink}`]);
|
||||
this._loadingService.stop();
|
||||
this._toaster.success(_('edit-dossier-dialog.delete-successful'), {
|
||||
params: {
|
||||
|
||||
@ -2,7 +2,7 @@ import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { Dossier, File, ProcessingFileStatuses } from '@red/domain';
|
||||
import { FileDownloadService } from '@upload-download/services/file-download.service';
|
||||
import { CircleButtonType, CircleButtonTypes, defaultDialogConfig, TenantContextHolder, Toaster } from '@iqser/common-ui';
|
||||
import { CircleButtonType, CircleButtonTypes, defaultDialogConfig, TenantsService, Toaster } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import {
|
||||
@ -31,7 +31,7 @@ export class FileDownloadBtnComponent implements OnChanges {
|
||||
|
||||
constructor(
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _fileDownloadService: FileDownloadService,
|
||||
private readonly _dialog: MatDialog,
|
||||
private readonly _toaster: Toaster,
|
||||
@ -62,7 +62,7 @@ export class FileDownloadBtnComponent implements OnChanges {
|
||||
await downloadRequest
|
||||
.then(() =>
|
||||
this._toaster.info(_('download-status.queued'), {
|
||||
params: { downloadHref: `/ui/${this._tenantContextHolder.currentTenant}/main/downloads` },
|
||||
params: { downloadHref: `/ui/${this._tenantsService.activeTenantId}/main/downloads` },
|
||||
}),
|
||||
)
|
||||
.catch(() => this._toaster.error(_('download-status.error')));
|
||||
|
||||
@ -83,6 +83,7 @@
|
||||
[diffEditorText]="diffEditorText"
|
||||
[initialEntries]="initialEntries"
|
||||
[showDiffEditor]="compare && showDiffEditor"
|
||||
(enableSaveButton)="onEnableSaveButton($event)"
|
||||
></redaction-editor>
|
||||
|
||||
<div *ngIf="compare && optionNotSelected" class="no-dictionary-selected">
|
||||
@ -96,6 +97,7 @@
|
||||
(action)="saveDictionary.emit()"
|
||||
[label]="'dictionary-overview.save-changes' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
[disabled]="!saveButtonEnabled()"
|
||||
icon="iqser:check"
|
||||
></iqser-icon-button>
|
||||
<div (click)="revert()" class="all-caps-label cancel" translate="dictionary-overview.revert-changes"></div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, signal, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { Debounce, IconButtonTypes, List, LoadingService } from '@iqser/common-ui';
|
||||
import { firstValueFrom, Observable, of } from 'rxjs';
|
||||
import { catchError, map, take, tap } from 'rxjs/operators';
|
||||
@ -27,6 +27,7 @@ const HELP_MODE_KEYS = {
|
||||
})
|
||||
export class DictionaryManagerComponent implements OnChanges {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly saveButtonEnabled = signal(true);
|
||||
|
||||
@Input() type: DictionaryType = 'dictionary';
|
||||
@Input() entityType?: string;
|
||||
@ -92,6 +93,7 @@ export class DictionaryManagerComponent implements OnChanges {
|
||||
|
||||
this._onDossierChanged(dossier.dossierTemplateId, dossier.id)
|
||||
.pipe(take(1))
|
||||
// eslint-disable-next-line rxjs/no-ignored-subscription
|
||||
.subscribe(entries => {
|
||||
this.diffEditorText = entries;
|
||||
this.showDiffEditor = true;
|
||||
@ -210,6 +212,10 @@ export class DictionaryManagerComponent implements OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
onEnableSaveButton(value: boolean) {
|
||||
this.saveButtonEnabled.set(value);
|
||||
}
|
||||
|
||||
private _applySearchDecorations() {
|
||||
this._searchDecorations = this.editor.codeEditor?.deltaDecorations(this._searchDecorations, []) || [];
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
*ngIf="!showDiffEditor"
|
||||
[(ngModel)]="value"
|
||||
[options]="editorOptions"
|
||||
(paste)="onPaste()"
|
||||
></ngx-monaco-editor>
|
||||
|
||||
<ngx-monaco-diff-editor
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||
import { Debounce, List, OnChange } from '@iqser/common-ui';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
|
||||
import { Debounce, List, LoadingService, OnChange } from '@iqser/common-ui';
|
||||
import { EditorThemeService } from '@services/editor-theme.service';
|
||||
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
|
||||
import ICodeEditor = monaco.editor.ICodeEditor;
|
||||
@ -31,6 +30,7 @@ export class EditorComponent implements OnInit, OnChanges {
|
||||
@Input() diffEditorText: string;
|
||||
@Input() @OnChange<List, EditorComponent>('revert') initialEntries: List;
|
||||
@Input() canEdit = false;
|
||||
@Output() enableSaveButton = new EventEmitter<boolean>();
|
||||
|
||||
/**
|
||||
* Used as [modified] input on diff editor
|
||||
@ -43,7 +43,7 @@ export class EditorComponent implements OnInit, OnChanges {
|
||||
private _diffEditor: IDiffEditor;
|
||||
private _decorations: string[] = [];
|
||||
|
||||
constructor(private readonly _userPreferenceService: UserPreferenceService, private readonly _editorThemeService: EditorThemeService) {}
|
||||
constructor(private readonly _loadingService: LoadingService, private readonly _editorThemeService: EditorThemeService) {}
|
||||
|
||||
get currentEntries(): string[] {
|
||||
return this.value.split('\n');
|
||||
@ -97,6 +97,8 @@ export class EditorComponent implements OnInit, OnChanges {
|
||||
const newDecorations = this.currentEntries.filter(entry => this._isNew(entry)).map(entry => this._getDecoration(entry));
|
||||
this._decorations = this.codeEditor.deltaDecorations(this._decorations, newDecorations);
|
||||
this.diffValue = this.value;
|
||||
this._loadingService.stop();
|
||||
this.enableSaveButton.emit(true);
|
||||
}
|
||||
|
||||
revert() {
|
||||
@ -105,6 +107,11 @@ export class EditorComponent implements OnInit, OnChanges {
|
||||
this._diffEditor?.getModifiedEditor().setValue(this.diffValue);
|
||||
}
|
||||
|
||||
onPaste() {
|
||||
this._loadingService.start();
|
||||
this.enableSaveButton.emit(false);
|
||||
}
|
||||
|
||||
private _defineThemes(): void {
|
||||
for (const theme of this._editorThemeService.themes) {
|
||||
(window as any).monaco.editor.defineTheme(theme, this._editorThemeService.configurations[theme]);
|
||||
|
||||
@ -10,7 +10,7 @@ mat-slide-toggle {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.mat-menu-item {
|
||||
.mat-mdc-menu-item {
|
||||
mat-icon {
|
||||
color: inherit;
|
||||
width: 14px;
|
||||
|
||||
@ -1,13 +1,6 @@
|
||||
import { Component, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { Action, ActionTypes, Dossier, File } from '@red/domain';
|
||||
import {
|
||||
CircleButtonType,
|
||||
defaultDialogConfig,
|
||||
IqserTooltipPosition,
|
||||
TenantContextHolder,
|
||||
Toaster,
|
||||
trackByFactory,
|
||||
} from '@iqser/common-ui';
|
||||
import { CircleButtonType, defaultDialogConfig, IqserTooltipPosition, TenantsService, Toaster, trackByFactory } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { FileDownloadService } from '@upload-download/services/file-download.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
@ -41,7 +34,7 @@ export class ExpandableFileActionsComponent implements OnChanges {
|
||||
|
||||
constructor(
|
||||
private readonly _fileDownloadService: FileDownloadService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _dialog: MatDialog,
|
||||
@ -106,7 +99,7 @@ export class ExpandableFileActionsComponent implements OnChanges {
|
||||
...result,
|
||||
});
|
||||
this._toaster.info(_('download-status.queued'), {
|
||||
params: { downloadHref: `/ui/${this._tenantContextHolder.currentTenant}/main/downloads` },
|
||||
params: { downloadHref: `/ui/${this._tenantsService.activeTenantId}/main/downloads` },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,6 @@
|
||||
</div>
|
||||
<div *ngIf="file.lastOCRTime" [matTooltipPosition]="'above'" [matTooltip]="'dossier-overview.ocr-performed' | translate">
|
||||
<mat-icon svgIcon="iqser:ocr"></mat-icon>
|
||||
{{ file.lastOCRTime | date: 'mediumDate' }}
|
||||
{{ file.lastOCRTime | date : 'mediumDate' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -3,10 +3,10 @@
|
||||
<mat-progress-bar
|
||||
[class.primary]="progressColor === 'primary'"
|
||||
[class.white]="progressColor === 'white'"
|
||||
[matTooltip]="numberOfPagesToOCR !== 0 ? numberOfOCRedPages + '/' + numberOfPagesToOCR : ''"
|
||||
[mode]="numberOfPagesToOCR === 0 ? 'indeterminate' : 'determinate'"
|
||||
[value]="(numberOfOCRedPages / numberOfPagesToOCR) * 100"
|
||||
[matTooltip]="numberOfPagesToOCR !== 0 ? numberOfOCRedPages + '/' + numberOfPagesToOCR : ''"
|
||||
class="ml-8 mr-8 w-100"
|
||||
></mat-progress-bar>
|
||||
|
||||
<span>{{ numberOfOCRedPages / numberOfPagesToOCR | percent: '0.0-0' }}</span>
|
||||
<span>{{ numberOfOCRedPages / numberOfPagesToOCR | percent : '0.0-0' }}</span>
|
||||
|
||||
@ -3,7 +3,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DOSSIER_TEMPLATE_ID, DownloadFileType, IDossierRequest, IDossierTemplate, IReportTemplate } from '@red/domain';
|
||||
import { UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { downloadTypesTranslations } from '@translations/download-types-translations';
|
||||
import { BaseDialogComponent, IconButtonTypes, IqserPermissionsService, SaveOptions, TenantContextHolder } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent, IconButtonTypes, IqserPermissionsService, SaveOptions, TenantsService } from '@iqser/common-ui';
|
||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { ReportTemplateService } from '@services/report-template.service';
|
||||
@ -42,7 +42,7 @@ export class AddDossierDialogComponent extends BaseDialogComponent implements On
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: IqserPermissionsService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _reportTemplateController: ReportTemplateService,
|
||||
@ -83,7 +83,7 @@ export class AddDossierDialogComponent extends BaseDialogComponent implements On
|
||||
this._loadingService.start();
|
||||
const savedDossier = await firstValueFrom(this._activeDossiersService.createOrUpdate(this._formToObject()));
|
||||
if (savedDossier) {
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}${savedDossier.routerLink}`]);
|
||||
await this._router.navigate([`/${this._tenantsService.activeTenantId}${savedDossier.routerLink}`]);
|
||||
if (options?.addMembers) {
|
||||
this._dialogService.openDialog('editDossier', {
|
||||
dossierId: savedDossier.id,
|
||||
|
||||
@ -3,14 +3,14 @@ import { DownloadStatus, IDownloadStatus, IDownloadStatusResponse, IPrepareDownl
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { EntitiesService, mapEach, RequiredParam, TenantContextHolder, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService, mapEach, TenantsService } from '@iqser/common-ui';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
|
||||
@Injectable()
|
||||
export class FileDownloadService extends EntitiesService<IDownloadStatus, DownloadStatus> {
|
||||
protected readonly _defaultModelPath = 'async/download';
|
||||
protected readonly _entityClass = DownloadStatus;
|
||||
protected readonly _tenantContext = inject(TenantContextHolder);
|
||||
protected readonly _tenantsService = inject(TenantsService);
|
||||
|
||||
constructor(private readonly _configService: ConfigService, private readonly _logger: NGXLogger) {
|
||||
super();
|
||||
@ -36,7 +36,7 @@ export class FileDownloadService extends EntitiesService<IDownloadStatus, Downlo
|
||||
const token = await this.generateToken(status.storageId);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = `${this._configService.values.API_URL}/async/download/with-ott/${token.value}?tenantId=${encodeURIComponent(
|
||||
this._tenantContext.currentTenant,
|
||||
this._tenantsService.activeTenantId,
|
||||
)}`;
|
||||
anchor.download = status.filename;
|
||||
anchor.target = '_blank';
|
||||
@ -46,19 +46,16 @@ export class FileDownloadService extends EntitiesService<IDownloadStatus, Downlo
|
||||
document.body.removeChild(anchor);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
prepareDownload(@RequiredParam() body: IPrepareDownloadRequest) {
|
||||
prepareDownload(body: IPrepareDownloadRequest) {
|
||||
return firstValueFrom(this._post(body, `${this._defaultModelPath}/prepare-option`));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
generateToken(@RequiredParam() storageId: string) {
|
||||
generateToken(storageId: string) {
|
||||
const request = this._post<{ value: string }>({ value: storageId }, `${this._defaultModelPath}/generate-ott`);
|
||||
return firstValueFrom(request);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
delete(@RequiredParam() body: IRemoveDownloadRequest): Observable<unknown> {
|
||||
delete(body: IRemoveDownloadRequest): Observable<unknown> {
|
||||
return super._post(body, `${this._defaultModelPath}/delete`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import { ConfigService } from '@services/config.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { IFileUploadResult, OverwriteFileOption, OverwriteFileOptions } from '@red/domain';
|
||||
import { isAcceptedFileType, isCsv, isZip } from '@utils/file-drop-utils';
|
||||
import { ErrorMessageService, GenericService, HeadersConfiguration, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { ErrorMessageService, GenericService, HeadersConfiguration, Toaster } from '@iqser/common-ui';
|
||||
import { FilesMapService } from '@services/files/files-map.service';
|
||||
import { switchMap, tap, throttleTime } from 'rxjs/operators';
|
||||
import { FilesService } from '@services/files/files.service';
|
||||
@ -164,8 +164,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
||||
}
|
||||
}
|
||||
|
||||
@Validate()
|
||||
uploadFileForm(@RequiredParam() dossierId: string, keepManualRedactions = false, file?: Blob) {
|
||||
uploadFileForm(dossierId: string, keepManualRedactions = false, file?: Blob) {
|
||||
const formParams = new FormData();
|
||||
|
||||
formParams.append('keepManualRedactions', keepManualRedactions.toString());
|
||||
@ -241,14 +240,17 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
||||
this._removeUpload(uploadFile);
|
||||
}
|
||||
},
|
||||
error: (err: HttpErrorResponse) => {
|
||||
error: (err: unknown) => {
|
||||
uploadFile.completed = true;
|
||||
uploadFile.error = {
|
||||
// Extract error message
|
||||
message: this._errorMessageService.getMessage(err, 'upload-status.error.generic'),
|
||||
message: this._errorMessageService.getMessage(err as HttpErrorResponse, 'upload-status.error.generic'),
|
||||
};
|
||||
this._removeUpload(uploadFile);
|
||||
if (uploadFile.retryCount < 5 && ![HttpStatusCode.BadRequest, HttpStatusCode.Conflict].includes(err.status)) {
|
||||
if (
|
||||
uploadFile.retryCount < 5 &&
|
||||
![HttpStatusCode.BadRequest, HttpStatusCode.Conflict].includes((err as HttpErrorResponse).status)
|
||||
) {
|
||||
uploadFile.retryCount += 1;
|
||||
this.scheduleUpload(uploadFile);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { EntitiesService, List, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService, List, mapEach, Toaster } from '@iqser/common-ui';
|
||||
import { DossierTemplate, IDossierTemplate } from '@red/domain';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { firstValueFrom, forkJoin, Observable, of } from 'rxjs';
|
||||
@ -75,8 +75,7 @@ export class DossierTemplatesService extends EntitiesService<IDossierTemplate, D
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
async createOrUpdate(@RequiredParam() body: IDossierTemplate) {
|
||||
async createOrUpdate(body: IDossierTemplate) {
|
||||
await firstValueFrom(this._post(body));
|
||||
return await firstValueFrom(this.loadAll());
|
||||
}
|
||||
|
||||
@ -8,17 +8,17 @@ import { DossiersService } from './dossiers.service';
|
||||
import { FilesMapService } from '../files/files-map.service';
|
||||
import { FeaturesService } from '../features.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
import { TenantsService } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ArchivedDossiersService extends DossiersService {
|
||||
protected readonly _defaultModelPath = 'archived-dossiers';
|
||||
readonly routerPath = ARCHIVE_ROUTE;
|
||||
protected readonly _defaultModelPath = 'archived-dossiers';
|
||||
|
||||
readonly #activeDossiersService = inject(ActiveDossiersService);
|
||||
readonly #filesMapService = inject(FilesMapService);
|
||||
readonly #featuresService = inject(FeaturesService);
|
||||
readonly #tenantContextHolder = inject(TenantContextHolder);
|
||||
readonly #tenantsService = inject(TenantsService);
|
||||
readonly #router = inject(Router);
|
||||
|
||||
archive(dossiers: Dossier[]): Observable<unknown> {
|
||||
@ -42,7 +42,7 @@ export class ArchivedDossiersService extends DossiersService {
|
||||
if (!this.#activeDossiersService.all.find(d => d.dossierTemplateId === dossierTemplateId)) {
|
||||
route = route.replace(DOSSIERS_ROUTE, ARCHIVE_ROUTE);
|
||||
}
|
||||
await this.#router.navigate([`/${this.#tenantContextHolder.currentTenant}${route}`]);
|
||||
await this.#router.navigate([`/${this.#tenantsService.activeTenantId}${route}`]);
|
||||
}),
|
||||
catchError(showArchiveFailedToast),
|
||||
);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { EntitiesService, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
/* eslint-disable @typescript-eslint/member-ordering */
|
||||
import { EntitiesService, mapEach, Toaster } from '@iqser/common-ui';
|
||||
import { Dossier, DossierStats, IDossier, IDossierChanges, IDossierRequest } from '@red/domain';
|
||||
import { Observable, of, Subject } from 'rxjs';
|
||||
import { catchError, map, switchMap, tap } from 'rxjs/operators';
|
||||
@ -17,11 +18,10 @@ export abstract class DossiersService extends EntitiesService<IDossier, Dossier>
|
||||
protected readonly _dossierStatsService = inject(DossierStatsService);
|
||||
protected readonly _dashboardStatsService = inject(DashboardStatsService);
|
||||
protected readonly _toaster = inject(Toaster);
|
||||
protected abstract readonly _defaultModelPath: string;
|
||||
protected readonly _entityClass = Dossier;
|
||||
protected abstract readonly _defaultModelPath: string;
|
||||
|
||||
@Validate()
|
||||
createOrUpdate(@RequiredParam() dossier: IDossierRequest): Observable<Dossier> {
|
||||
createOrUpdate(dossier: IDossierRequest): Observable<Dossier> {
|
||||
const showToast = (error: HttpErrorResponse) => {
|
||||
this._toaster.error(error.status === HttpStatusCode.Conflict ? CONFLICT_MSG : GENERIC_MSG);
|
||||
return of(null);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { EntitiesService, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService, mapEach } from '@iqser/common-ui';
|
||||
import { DefaultColors, DefaultColorType, IDefaultColors } from '@red/domain';
|
||||
import { forkJoin, Observable } from 'rxjs';
|
||||
import { map, switchMap, tap } from 'rxjs/operators';
|
||||
@ -29,11 +29,7 @@ export class DefaultColorsService extends EntitiesService<IDefaultColors, Defaul
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
update(
|
||||
@RequiredParam() newColor: Partial<Record<DefaultColorType, string>>,
|
||||
@RequiredParam() dossierTemplateId: string,
|
||||
): Observable<DefaultColors> {
|
||||
update(newColor: Partial<Record<DefaultColorType, string>>, dossierTemplateId: string): Observable<DefaultColors> {
|
||||
const oldColors = this.find(dossierTemplateId);
|
||||
const body = { ...oldColors, ...newColor };
|
||||
return this._post(body, `${this._defaultModelPath}/${dossierTemplateId}`).pipe(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { forkJoin, Observable, of, throwError, zip } from 'rxjs';
|
||||
import { EntitiesService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { firstValueFrom, forkJoin, Observable, throwError, zip } from 'rxjs';
|
||||
import { EntitiesService, List, QueryParam, Toaster } from '@iqser/common-ui';
|
||||
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, IDictionary, IUpdateDictionary, SuperTypes } from '@red/domain';
|
||||
import { catchError, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
@ -31,8 +31,8 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
/**
|
||||
* Retrieves all dictionary entries of an entry type
|
||||
*/
|
||||
@Validate()
|
||||
getForType(@RequiredParam() dossierTemplateId: string, @RequiredParam() type: string, dossierId?: string): Observable<IDictionary> {
|
||||
|
||||
getForType(dossierTemplateId: string, type: string, dossierId?: string): Observable<IDictionary> {
|
||||
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
||||
return this._getOne([type, dossierTemplateId], this._defaultModelPath, queryParams);
|
||||
}
|
||||
@ -40,12 +40,8 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
/**
|
||||
* Deletes entry types
|
||||
*/
|
||||
@Validate()
|
||||
deleteDictionaries(
|
||||
@RequiredParam() dictionaryIds: List,
|
||||
@RequiredParam() dossierTemplateId: string,
|
||||
dossierId?: string,
|
||||
): Observable<unknown> {
|
||||
|
||||
deleteDictionaries(dictionaryIds: List, dossierTemplateId: string, dossierId?: string): Observable<unknown> {
|
||||
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
||||
const url = `${this._defaultModelPath}/type/${dossierTemplateId}/delete`;
|
||||
return this._post(dictionaryIds, url, queryParams).pipe(
|
||||
@ -57,8 +53,8 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
/**
|
||||
* Retrieve all entry types
|
||||
*/
|
||||
@Validate()
|
||||
getAllDictionaries(@RequiredParam() dossierTemplateId: string, dossierId?: string) {
|
||||
|
||||
getAllDictionaries(dossierTemplateId: string, dossierId?: string) {
|
||||
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
||||
return this._getOne<{ types: IDictionary[] }>(['type', dossierTemplateId], this._defaultModelPath, queryParams);
|
||||
}
|
||||
@ -66,17 +62,12 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
/**
|
||||
* Updates colors, hint and caseInsensitive of an entry type.
|
||||
*/
|
||||
@Validate()
|
||||
updateDictionary(
|
||||
@RequiredParam() body: IUpdateDictionary,
|
||||
@RequiredParam() dossierTemplateId: string,
|
||||
@RequiredParam() type: string,
|
||||
dossierId?: string,
|
||||
): Observable<unknown> {
|
||||
|
||||
updateDictionary(body: IUpdateDictionary, dossierTemplateId: string, type: string, dossierId?: string): Observable<unknown> {
|
||||
const url = `${this._defaultModelPath}/type/${type}/${dossierTemplateId}`;
|
||||
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
||||
return this._post(body, url, queryParams).pipe(
|
||||
catchError((error: HttpErrorResponse) => this.#addUpdateDictionaryErrorToast(error)),
|
||||
catchError((error: unknown) => this.#addUpdateDictionaryErrorToast(error)),
|
||||
switchMap(() => this.loadDictionaryDataForDossierTemplate(dossierTemplateId)),
|
||||
switchMap(() => this._dossierTemplateStatsService.getFor([dossierTemplateId])),
|
||||
);
|
||||
@ -85,11 +76,11 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
/**
|
||||
* Creates entry type with colors, hint and caseInsensitive
|
||||
*/
|
||||
@Validate()
|
||||
addDictionary(@RequiredParam() dictionary: IDictionary, dossierId?: string): Observable<unknown> {
|
||||
|
||||
addDictionary(dictionary: IDictionary, dossierId?: string): Observable<unknown> {
|
||||
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
||||
return this._post(dictionary, `${this._defaultModelPath}/type`, queryParams).pipe(
|
||||
catchError((error: HttpErrorResponse) => this.#addUpdateDictionaryErrorToast(error)),
|
||||
catchError((error: unknown) => this.#addUpdateDictionaryErrorToast(error)),
|
||||
switchMap(() => this.loadDictionaryDataForDossierTemplate(dictionary.dossierTemplateId)),
|
||||
switchMap(() => this._dossierTemplateStatsService.getFor([dictionary.dossierTemplateId])),
|
||||
);
|
||||
@ -126,8 +117,8 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
this._toaster.success(_('dictionary-overview.success.generic'));
|
||||
}
|
||||
},
|
||||
error: error => {
|
||||
if (error.status === 400) {
|
||||
error: (error: unknown) => {
|
||||
if ((error as HttpErrorResponse).status === 400) {
|
||||
this._toaster.error(_('dictionary-overview.error.400'));
|
||||
} else {
|
||||
this._toaster.error(_('dictionary-overview.error.generic'));
|
||||
@ -179,23 +170,20 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
return possibleDictionaries;
|
||||
}
|
||||
|
||||
loadDossierDictionary(dossierTemplateId: string, dossierId: string): Observable<Dictionary> {
|
||||
return this.getForType(dossierTemplateId, 'dossier_redaction', dossierId).pipe(
|
||||
map(
|
||||
dictionary =>
|
||||
new Dictionary({
|
||||
...dictionary,
|
||||
type: 'dossier_redaction',
|
||||
}),
|
||||
),
|
||||
tap(dictionary => {
|
||||
this._dossierDictionariesMapService.set(dossierId, [dictionary]);
|
||||
}),
|
||||
catchError(() => {
|
||||
this._dossierDictionariesMapService.set(dossierId, []);
|
||||
return of(null);
|
||||
}),
|
||||
);
|
||||
async loadDossierDictionary(dossierTemplateId: string, dossierId: string): Promise<Dictionary> {
|
||||
const promise = firstValueFrom(this.getForType(dossierTemplateId, 'dossier_redaction', dossierId));
|
||||
const dict = await promise.catch(() => undefined);
|
||||
if (dict) {
|
||||
const dictionary = new Dictionary({
|
||||
...dict,
|
||||
type: 'dossier_redaction',
|
||||
});
|
||||
this._dossierDictionariesMapService.set(dossierId, [dictionary]);
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
this._dossierDictionariesMapService.set(dossierId, []);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
loadDictionaryData(dossierTemplatesIds: string[]): Observable<Dictionary[][]> {
|
||||
@ -227,21 +215,10 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
);
|
||||
}
|
||||
|
||||
#addUpdateDictionaryErrorToast(error: HttpErrorResponse): Observable<never> {
|
||||
if (error.status === HttpStatusCode.Conflict) {
|
||||
this._toaster.error(_('add-edit-entity.error.entity-already-exists'));
|
||||
} else if (error.status === HttpStatusCode.BadRequest) {
|
||||
this._toaster.error(_('add-edit-entity.error.invalid-color-or-rank'));
|
||||
} else {
|
||||
this._toaster.error(_('add-edit-entity.error.generic'));
|
||||
}
|
||||
return throwError(() => error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dictionary entries with entry type.
|
||||
*/
|
||||
@Validate()
|
||||
|
||||
private _addEntries(
|
||||
entries: List,
|
||||
dossierTemplateId: string,
|
||||
@ -261,7 +238,7 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
/**
|
||||
* Delete dictionary entries with entry type.
|
||||
*/
|
||||
@Validate()
|
||||
|
||||
private _deleteEntries(
|
||||
entries: List,
|
||||
dossierTemplateId: string,
|
||||
@ -275,4 +252,15 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
|
||||
const url = `${this._defaultModelPath}/delete/${type}/${dossierTemplateId}`;
|
||||
return this._post(entries, url, queryParams);
|
||||
}
|
||||
|
||||
#addUpdateDictionaryErrorToast(error: HttpErrorResponse | unknown): Observable<never> {
|
||||
if ((error as HttpErrorResponse).status === HttpStatusCode.Conflict) {
|
||||
this._toaster.error(_('add-edit-entity.error.entity-already-exists'));
|
||||
} else if ((error as HttpErrorResponse).status === HttpStatusCode.BadRequest) {
|
||||
this._toaster.error(_('add-edit-entity.error.invalid-color-or-rank'));
|
||||
} else {
|
||||
this._toaster.error(_('add-edit-entity.error.generic'));
|
||||
}
|
||||
return throwError(() => error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Dossier, DossierAttributeConfig, DossierAttributeWithValue, IDossierAttribute, IDossierAttributeConfig } from '@red/domain';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService, List, mapEach } from '@iqser/common-ui';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
@ -21,16 +21,14 @@ export class DossierAttributesService extends EntitiesService<IDossierAttributeC
|
||||
}));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
setAttributes(@RequiredParam() dossier: Dossier, @RequiredParam() dossierAttributeList: List<IDossierAttribute>) {
|
||||
setAttributes(dossier: Dossier, dossierAttributeList: List<IDossierAttribute>) {
|
||||
return this._post<{ dossierAttributeList?: List<IDossierAttribute> }>(
|
||||
{ dossierAttributeList },
|
||||
`${this._defaultModelPath}/set/${dossier.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
delete(@RequiredParam() ids: List, dossierTemplateId: string): Observable<unknown> {
|
||||
delete(ids: List, dossierTemplateId: string): Observable<unknown> {
|
||||
const queryParams = ids.map(id => ({ key: 'dossierAttributeIds', value: id }));
|
||||
return this._post(null, `${this._defaultModelPath}/config/delete/${dossierTemplateId}`, queryParams);
|
||||
}
|
||||
@ -43,21 +41,18 @@ export class DossierAttributesService extends EntitiesService<IDossierAttributeC
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
createOrUpdate(@RequiredParam() attributeConfig: IDossierAttributeConfig, dossierTemplateId: string) {
|
||||
createOrUpdate(attributeConfig: IDossierAttributeConfig, dossierTemplateId: string) {
|
||||
const url = `${this._defaultModelPath}/config/${dossierTemplateId}`;
|
||||
return firstValueFrom(this._post(attributeConfig, url));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
getConfig(@RequiredParam() dossierTemplateId: string): Observable<List<IDossierAttributeConfig>> {
|
||||
getConfig(dossierTemplateId: string): Observable<List<IDossierAttributeConfig>> {
|
||||
return this._getOne<{ dossierAttributeConfigs: List<IDossierAttributeConfig> }>(['config', dossierTemplateId]).pipe(
|
||||
map(res => res.dossierAttributeConfigs),
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
getAttributes(@RequiredParam() dossierId: string): Observable<List<IDossierAttribute>> {
|
||||
getAttributes(dossierId: string): Observable<List<IDossierAttribute>> {
|
||||
return this._getOne<{ dossierAttributeList?: List<IDossierAttribute> }>([dossierId]).pipe(map(res => res.dossierAttributeList));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { EntitiesService, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService, mapEach, Toaster } from '@iqser/common-ui';
|
||||
import { DossierState, IDossierState } from '@red/domain';
|
||||
import { EMPTY, firstValueFrom, forkJoin, Observable, switchMap } from 'rxjs';
|
||||
import { DossierTemplatesService } from '../dossier-templates/dossier-templates.service';
|
||||
@ -22,8 +22,7 @@ export class DossierStatesService extends EntitiesService<IDossierState, Dossier
|
||||
readonly #dossierTemplatesService = inject(DossierTemplatesService);
|
||||
readonly #dossierStatesMapService = inject(DossierStatesMapService);
|
||||
|
||||
@Validate()
|
||||
async createOrUpdate(@RequiredParam() state: IDossierState) {
|
||||
async createOrUpdate(state: IDossierState) {
|
||||
const showToast = (error: HttpErrorResponse) => {
|
||||
this.#toaster.error(error.status === HttpStatusCode.Conflict ? CONFLICT_MSG : GENERIC_MSG);
|
||||
return EMPTY;
|
||||
@ -37,8 +36,7 @@ export class DossierStatesService extends EntitiesService<IDossierState, Dossier
|
||||
return firstValueFrom(request);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
loadAllForTemplate(@RequiredParam() templateId: string) {
|
||||
loadAllForTemplate(templateId: string) {
|
||||
return this.loadAll(`${this._defaultModelPath}/dossier-template/${templateId}`).pipe(
|
||||
tap(states => this.#dossierStatesMapService.set(templateId, states)),
|
||||
);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { EntitiesService, List, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService, List } from '@iqser/common-ui';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
@ -10,17 +10,16 @@ export type FileAttributesConfigMap = Readonly<Record<string, IFileAttributesCon
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class FileAttributesService extends EntitiesService<IFileAttributeConfig, FileAttributeConfig> {
|
||||
readonly fileAttributesConfig$ = new BehaviorSubject<FileAttributesConfigMap>({});
|
||||
readonly isEditingFileAttribute$ = new BehaviorSubject(false);
|
||||
protected readonly _defaultModelPath = 'fileAttributes';
|
||||
protected readonly _entityClass = FileAttributeConfig;
|
||||
readonly fileAttributesConfig$ = new BehaviorSubject<FileAttributesConfigMap>({});
|
||||
|
||||
readonly isEditingFileAttribute$ = new BehaviorSubject(false);
|
||||
|
||||
/**
|
||||
* Get the file attributes that can be used at importing csv.
|
||||
*/
|
||||
@Validate()
|
||||
loadFileAttributesConfig(@RequiredParam() dossierTemplateId: string): Observable<IFileAttributesConfig> {
|
||||
|
||||
loadFileAttributesConfig(dossierTemplateId: string): Observable<IFileAttributesConfig> {
|
||||
const request$ = this._getOne<IFileAttributesConfig>(['config', dossierTemplateId]);
|
||||
return request$.pipe(
|
||||
tap(entities => entities.fileAttributeConfigs.sort((c1, c2) => c1.placeholder.localeCompare(c2.placeholder))),
|
||||
@ -41,8 +40,8 @@ export class FileAttributesService extends EntitiesService<IFileAttributeConfig,
|
||||
/**
|
||||
* Add or update a file attribute that can be used at importing csv.
|
||||
*/
|
||||
@Validate()
|
||||
setFileAttributesConfig(@RequiredParam() body: IFileAttributeConfig, @RequiredParam() dossierTemplateId: string) {
|
||||
|
||||
setFileAttributesConfig(body: IFileAttributeConfig, dossierTemplateId: string) {
|
||||
const url = `${this._defaultModelPath}/config/fileAttribute/${dossierTemplateId}`;
|
||||
return this._post<unknown>(body, url);
|
||||
}
|
||||
@ -50,8 +49,8 @@ export class FileAttributesService extends EntitiesService<IFileAttributeConfig,
|
||||
/**
|
||||
* Set file attributes base configuration and a list of file attributes,
|
||||
*/
|
||||
@Validate()
|
||||
setFileAttributeConfig(@RequiredParam() body: IFileAttributesConfig, @RequiredParam() dossierTemplateId: string) {
|
||||
|
||||
setFileAttributeConfig(body: IFileAttributesConfig, dossierTemplateId: string) {
|
||||
const url = `${this._defaultModelPath}/config/baseConfig/${dossierTemplateId}`;
|
||||
return this._put<unknown>(body, url);
|
||||
}
|
||||
@ -59,8 +58,8 @@ export class FileAttributesService extends EntitiesService<IFileAttributeConfig,
|
||||
/**
|
||||
* Set file attributes to an existing file
|
||||
*/
|
||||
@Validate()
|
||||
setFileAttributes(@RequiredParam() body: FileAttributes, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
|
||||
setFileAttributes(body: FileAttributes, dossierId: string, fileId: string) {
|
||||
const url = `${this._defaultModelPath}/set/${dossierId}/${fileId}`;
|
||||
return this._post<unknown>(body, url);
|
||||
}
|
||||
@ -68,8 +67,8 @@ export class FileAttributesService extends EntitiesService<IFileAttributeConfig,
|
||||
/**
|
||||
* Delete a specific file attribute.
|
||||
*/
|
||||
@Validate()
|
||||
deleteFileAttributes(@RequiredParam() body: List, @RequiredParam() dossierTemplateId: string) {
|
||||
|
||||
deleteFileAttributes(body: List, dossierTemplateId: string) {
|
||||
const url = `${this._defaultModelPath}/config/fileAttribute/${dossierTemplateId}/delete`;
|
||||
return this._post(body, url);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { EntitiesService, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService } from '@iqser/common-ui';
|
||||
import { ILegalBasis, Justification } from '@red/domain';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
@ -11,23 +11,19 @@ export class JustificationsService extends EntitiesService<ILegalBasis, Justific
|
||||
protected readonly _defaultModelPath = 'legalBasis';
|
||||
protected readonly _entityClass = Justification;
|
||||
|
||||
@Validate()
|
||||
createOrUpdate(@RequiredParam() justification: Justification, @RequiredParam() dossierTemplateId: string) {
|
||||
createOrUpdate(justification: Justification, dossierTemplateId: string) {
|
||||
return this._put(justification, `${this._defaultModelPath}/${dossierTemplateId}`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
loadAll(@RequiredParam() dossierTemplateId: string): Observable<Justification[]> {
|
||||
loadAll(dossierTemplateId: string): Observable<Justification[]> {
|
||||
return super.loadAll(`${this._defaultModelPath}/${dossierTemplateId}`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
delete(@RequiredParam() justificationNames: string[], @RequiredParam() dossierTemplateId: string): Observable<unknown> {
|
||||
delete(justificationNames: string[], dossierTemplateId: string): Observable<unknown> {
|
||||
return this._post(justificationNames, `${this._defaultModelPath}/${dossierTemplateId}/delete`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
getForDossierTemplate(@RequiredParam() dossierTemplateId: string): Observable<Justification[]> {
|
||||
getForDossierTemplate(dossierTemplateId: string): Observable<Justification[]> {
|
||||
return super
|
||||
.getAll(`${this._defaultModelPath}/${dossierTemplateId}`)
|
||||
.pipe(map((entities: ILegalBasis[]) => entities.map(entity => new Justification(entity))));
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { EntitiesService, List, mapEach, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService, List, mapEach, QueryParam, Toaster } from '@iqser/common-ui';
|
||||
import { Dossier, File, IDossier, IFile, TrashDossier, TrashFile, TrashItem } from '@red/domain';
|
||||
import { catchError, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { forkJoin, map, Observable, of } from 'rxjs';
|
||||
@ -44,8 +44,7 @@ export class TrashService extends EntitiesService<TrashItem, TrashItem> {
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
restore(@RequiredParam() items: TrashItem[]): Observable<unknown> {
|
||||
restore(items: TrashItem[]): Observable<unknown> {
|
||||
return this.#executeAction(
|
||||
items,
|
||||
(dossierIds: string[]) => this._restoreDossiers(dossierIds),
|
||||
@ -53,8 +52,7 @@ export class TrashService extends EntitiesService<TrashItem, TrashItem> {
|
||||
).pipe(tap(() => items.forEach(item => this.remove(item.id))));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
hardDelete(@RequiredParam() items: TrashItem[]): Observable<unknown> {
|
||||
hardDelete(items: TrashItem[]): Observable<unknown> {
|
||||
return this.#executeAction(
|
||||
items,
|
||||
(dossierIds: string[]) => this._hardDeleteDossiers(dossierIds),
|
||||
@ -104,6 +102,26 @@ export class TrashService extends EntitiesService<TrashItem, TrashItem> {
|
||||
);
|
||||
}
|
||||
|
||||
private _restoreDossiers(dossierIds: string[]): Observable<unknown> {
|
||||
return this._post(dossierIds, 'deleted-dossiers/restore').pipe(switchMap(() => this._activeDossiersService.loadAll()));
|
||||
}
|
||||
|
||||
private _hardDeleteDossiers(dossierIds: string[]): Observable<unknown> {
|
||||
const body = dossierIds.map<QueryParam>(id => ({ key: 'dossierId', value: id }));
|
||||
return this.delete(body, 'deleted-dossiers/hard-delete', body);
|
||||
}
|
||||
|
||||
private _hardDeleteFiles(dossierId: string, fileIds: List) {
|
||||
const queryParams = fileIds.map<QueryParam>(id => ({ key: 'fileIds', value: id }));
|
||||
return super
|
||||
.delete({}, `delete/hard-delete/${dossierId}`, queryParams)
|
||||
.pipe(switchMap(() => this._dossierStatsService.getFor([dossierId])));
|
||||
}
|
||||
|
||||
private _restoreFiles(dossierId: string, fileIds: List) {
|
||||
return this._post(fileIds, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
|
||||
}
|
||||
|
||||
#executeAction(items: TrashItem[], dossiersFn: (...args) => Observable<unknown>, filesFn: (...args) => Observable<unknown>) {
|
||||
const requests$ = [];
|
||||
|
||||
@ -121,24 +139,4 @@ export class TrashService extends EntitiesService<TrashItem, TrashItem> {
|
||||
|
||||
return forkJoin(requests$.map(r => r.pipe(take(1))));
|
||||
}
|
||||
|
||||
private _restoreDossiers(@RequiredParam() dossierIds: string[]): Observable<unknown> {
|
||||
return this._post(dossierIds, 'deleted-dossiers/restore').pipe(switchMap(() => this._activeDossiersService.loadAll()));
|
||||
}
|
||||
|
||||
private _hardDeleteDossiers(@RequiredParam() dossierIds: string[]): Observable<unknown> {
|
||||
const body = dossierIds.map<QueryParam>(id => ({ key: 'dossierId', value: id }));
|
||||
return this.delete(body, 'deleted-dossiers/hard-delete', body);
|
||||
}
|
||||
|
||||
private _hardDeleteFiles(@RequiredParam() dossierId: string, @RequiredParam() fileIds: List) {
|
||||
const queryParams = fileIds.map<QueryParam>(id => ({ key: 'fileIds', value: id }));
|
||||
return super
|
||||
.delete({}, `delete/hard-delete/${dossierId}`, queryParams)
|
||||
.pipe(switchMap(() => this._dossierStatsService.getFor([dossierId])));
|
||||
}
|
||||
|
||||
private _restoreFiles(@RequiredParam() dossierId: string, @RequiredParam() fileIds: List) {
|
||||
return this._post(fileIds, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { GenericService, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, mapEach, QueryParam } from '@iqser/common-ui';
|
||||
import { IWatermark, Watermark } from '@red/domain';
|
||||
import { firstValueFrom, forkJoin, Observable } from 'rxjs';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
@ -16,21 +16,18 @@ export class WatermarkService extends GenericService<IWatermark> {
|
||||
protected readonly _defaultModelPath = 'watermark';
|
||||
readonly #watermarksMapService = inject(WatermarksMapService);
|
||||
|
||||
@Validate()
|
||||
async deleteWatermark(@RequiredParam() dossierTemplateId: string, @RequiredParam() watermarkId: number) {
|
||||
async deleteWatermark(dossierTemplateId: string, watermarkId: number) {
|
||||
await firstValueFrom(super.delete(null, `${this._defaultModelPath}/${watermarkId}`));
|
||||
return firstValueFrom(this.loadForDossierTemplate(dossierTemplateId));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
async saveWatermark(@RequiredParam() body: IWatermark) {
|
||||
async saveWatermark(body: IWatermark) {
|
||||
const watermark = await firstValueFrom(this._post(body, `${this._defaultModelPath}`));
|
||||
await firstValueFrom(this.loadForDossierTemplate(watermark.dossierTemplateId));
|
||||
return this.#watermarksMapService.get(watermark.dossierTemplateId, watermark.id);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
loadForDossierTemplate(@RequiredParam() dossierTemplateId: string): Observable<Watermark[]> {
|
||||
loadForDossierTemplate(dossierTemplateId: string): Observable<Watermark[]> {
|
||||
const queryParams: QueryParam[] = [{ key: 'dossierTemplateId', value: dossierTemplateId }];
|
||||
return this.getAll(this._defaultModelPath, queryParams).pipe(
|
||||
mapEach(entity => new Watermark(entity)),
|
||||
@ -38,13 +35,11 @@ export class WatermarkService extends GenericService<IWatermark> {
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
loadAll(@RequiredParam() dossierTemplateIds: string[]): Observable<Watermark[]> {
|
||||
loadAll(dossierTemplateIds: string[]): Observable<Watermark[]> {
|
||||
return forkJoin(dossierTemplateIds.map(id => this.loadForDossierTemplate(id))).pipe(map(arrays => [].concat(...arrays)));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
async isWatermarkUsed(@RequiredParam() watermarkId: number) {
|
||||
async isWatermarkUsed(watermarkId: number) {
|
||||
const queryParams: QueryParam[] = [{ key: 'watermarkId', value: watermarkId }];
|
||||
const result = await firstValueFrom(this.getAll<IsUsedResponse>(`${this._defaultModelPath}/used`, queryParams));
|
||||
return result.value;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { GenericService, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, Toaster } from '@iqser/common-ui';
|
||||
import { Earmark, EarmarkOperation, EarmarkResponse } from '@red/domain';
|
||||
import { catchError, map, tap } from 'rxjs/operators';
|
||||
import { Observable, of } from 'rxjs';
|
||||
@ -13,8 +13,7 @@ export class EarmarksService extends GenericService<EarmarkResponse> {
|
||||
protected readonly _defaultModelPath = '';
|
||||
readonly #toaster = inject(Toaster);
|
||||
|
||||
@Validate()
|
||||
getEarmarks(@RequiredParam() dossierId: string, @RequiredParam() fileId: string): Observable<AnnotationWrapper[]> {
|
||||
getEarmarks(dossierId: string, fileId: string): Observable<AnnotationWrapper[]> {
|
||||
return this._http.get<{ highlights: Earmark[] }>(`/${this.#getPath(dossierId, fileId)}`).pipe(
|
||||
map(response => response.highlights),
|
||||
map(highlights => highlights.map(highlight => AnnotationWrapper.fromEarmark(highlight))),
|
||||
@ -23,13 +22,7 @@ export class EarmarksService extends GenericService<EarmarkResponse> {
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
performHighlightsAction(
|
||||
@RequiredParam() ids: string[],
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
@RequiredParam() operation: EarmarkOperation,
|
||||
) {
|
||||
performHighlightsAction(ids: string[], dossierId: string, fileId: string, operation: EarmarkOperation) {
|
||||
return this._post({ ids }, `${this.#getPath(dossierId, fileId)}/${operation}`).pipe(
|
||||
tap(() => this.#toaster.success(_('highlight-action-dialog.success'), { params: { operation } })),
|
||||
);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { GenericService, HeadersConfiguration, List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, HeadersConfiguration, List, QueryParam } from '@iqser/common-ui';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { HttpEvent, HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
@ -10,29 +10,21 @@ import { File, IPageRotationRequest } from '@red/domain';
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class FileManagementService extends GenericService<unknown> {
|
||||
readonly #filesService = inject(FilesService);
|
||||
protected readonly _defaultModelPath = '';
|
||||
readonly #filesService = inject(FilesService);
|
||||
|
||||
@Validate()
|
||||
delete(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string) {
|
||||
delete(files: List<File>, dossierId: string) {
|
||||
const fileIds = files.map(f => f.id);
|
||||
return super._post(fileIds, `delete/${dossierId}`).pipe(switchMap(() => this.#filesService.loadAll(dossierId)));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
rotatePage(@RequiredParam() body: IPageRotationRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
rotatePage(body: IPageRotationRequest, dossierId: string, fileId: string) {
|
||||
return this._post(body, `rotate/${dossierId}/${fileId}`);
|
||||
}
|
||||
|
||||
downloadOriginal(dossierId: string, fileId: string, observe?: 'events', indicator?: string): Observable<HttpEvent<Blob>>;
|
||||
downloadOriginal(dossierId: string, fileId: string, observe?: 'response', indicator?: string): Observable<HttpResponse<Blob>>;
|
||||
@Validate()
|
||||
downloadOriginal(
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
observe: 'events' | 'response' = 'events',
|
||||
indicator?: string,
|
||||
) {
|
||||
downloadOriginal(dossierId: string, fileId: string, observe: 'events' | 'response' = 'events', indicator?: string) {
|
||||
const queryParams: QueryParam[] = [{ key: 'inline', value: true }];
|
||||
|
||||
if (indicator) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { EntitiesService, isArray, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService, isArray, List, mapEach, QueryParam } from '@iqser/common-ui';
|
||||
import { File, IFile } from '@red/domain';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { UserService } from '@users/user.service';
|
||||
@ -45,41 +45,35 @@ export class FilesService extends EntitiesService<IFile, File> {
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
async setAssignee(@RequiredParam() files: File | List<File>, assigneeId: string) {
|
||||
async setAssignee(files: File | List<File>, assigneeId: string) {
|
||||
const _files = asList(files);
|
||||
const url = `${this._defaultModelPath}/set-assignee/${_files[0].dossierId}/bulk`;
|
||||
return this.#makePost(_files, url, [{ key: 'assigneeId', value: assigneeId }]);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
async setToNew(@RequiredParam() files: File | List<File>) {
|
||||
async setToNew(files: File | List<File>) {
|
||||
const _files = asList(files);
|
||||
return this.#makePost(_files, `${this._defaultModelPath}/new/${_files[0].dossierId}/bulk`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
async setUnderApproval(@RequiredParam() files: File | List<File>, assigneeId: string) {
|
||||
async setUnderApproval(files: File | List<File>, assigneeId: string) {
|
||||
const _files = asList(files);
|
||||
const url = `${this._defaultModelPath}/under-approval/${_files[0].dossierId}/bulk`;
|
||||
return this.#makePost(_files, url, [{ key: 'assigneeId', value: assigneeId }]);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
async setReviewer(@RequiredParam() files: File | List<File>, assigneeId: string) {
|
||||
async setReviewer(files: File | List<File>, assigneeId: string) {
|
||||
const _files = asList(files);
|
||||
const url = `${this._defaultModelPath}/under-review/${_files[0].dossierId}/bulk`;
|
||||
return this.#makePost(_files, url, [{ key: 'assigneeId', value: assigneeId }]);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
async setApproved(@RequiredParam() files: File | List<File>) {
|
||||
async setApproved(files: File | List<File>) {
|
||||
const _files = asList(files);
|
||||
return this.#makePost(_files, `${this._defaultModelPath}/approved/${_files[0].dossierId}/bulk`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
async setUnderReviewFor(@RequiredParam() files: File | List<File>) {
|
||||
async setUnderReviewFor(files: File | List<File>) {
|
||||
const _files = asList(files);
|
||||
return this.#makePost(_files, `${this._defaultModelPath}/under-review/${_files[0].dossierId}/bulk`);
|
||||
}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericService, HeadersConfiguration, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, HeadersConfiguration } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class RedactionImportService extends GenericService<void> {
|
||||
protected readonly _defaultModelPath = 'import-redactions';
|
||||
|
||||
@Validate()
|
||||
importRedactions(@RequiredParam() dossierId: string, @RequiredParam() fileId: string, file: Blob, pagesToImport: Set<number> | null) {
|
||||
importRedactions(dossierId: string, fileId: string, file: Blob, pagesToImport: Set<number> | null) {
|
||||
const formParams = new FormData();
|
||||
|
||||
if (file !== undefined) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericService, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, QueryParam } from '@iqser/common-ui';
|
||||
import { IRedactionLog, ISectionGrid } from '@red/domain';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
import { of } from 'rxjs';
|
||||
@ -10,8 +10,7 @@ import { of } from 'rxjs';
|
||||
export class RedactionLogService extends GenericService<unknown> {
|
||||
protected readonly _defaultModelPath = '';
|
||||
|
||||
@Validate()
|
||||
getRedactionLog(@RequiredParam() dossierId: string, @RequiredParam() fileId: string, withManualRedactions?: boolean) {
|
||||
getRedactionLog(dossierId: string, fileId: string, withManualRedactions?: boolean) {
|
||||
const queryParams: QueryParam[] = [];
|
||||
if (withManualRedactions) {
|
||||
queryParams.push({ key: 'withManualRedactions', value: withManualRedactions });
|
||||
@ -24,8 +23,7 @@ export class RedactionLogService extends GenericService<unknown> {
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
getSectionGrid(@RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
getSectionGrid(dossierId: string, fileId: string) {
|
||||
return this._getOne<ISectionGrid>([dossierId, fileId], 'sectionGrid');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericService, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, QueryParam } from '@iqser/common-ui';
|
||||
import { IRssData, IRssEntry, RssEntry } from '@red/domain';
|
||||
import { catchError, map, tap } from 'rxjs/operators';
|
||||
import { Observable, of } from 'rxjs';
|
||||
@ -10,8 +10,7 @@ import { saveAs } from 'file-saver';
|
||||
export class RssService extends GenericService<void> {
|
||||
protected readonly _defaultModelPath = 'import-redactions';
|
||||
|
||||
@Validate()
|
||||
getRSSData(@RequiredParam() dossierId: string, @RequiredParam() fileId: string): Observable<RssEntry> {
|
||||
getRSSData(dossierId: string, fileId: string): Observable<RssEntry> {
|
||||
const queryParams: QueryParam[] = [];
|
||||
queryParams.push({ key: 'fileId', value: fileId });
|
||||
|
||||
@ -22,8 +21,7 @@ export class RssService extends GenericService<void> {
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
getRSSExportData(@RequiredParam() dossierId: string, @RequiredParam() fileId: string): Observable<any> {
|
||||
getRSSExportData(dossierId: string, fileId: string): Observable<any> {
|
||||
const queryParams: QueryParam[] = [];
|
||||
queryParams.push({ key: 'fileId', value: fileId });
|
||||
|
||||
@ -33,21 +31,11 @@ export class RssService extends GenericService<void> {
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
override(
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
@RequiredParam() componentOverrides: Record<string, string>,
|
||||
): Observable<void> {
|
||||
override(dossierId: string, fileId: string, componentOverrides: Record<string, string>): Observable<void> {
|
||||
return this._post({ componentOverrides }, `rss/override/${dossierId}/${fileId}`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
revertOverride(
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
@RequiredParam() components: string[],
|
||||
): Observable<void> {
|
||||
revertOverride(dossierId: string, fileId: string, components: string[]): Observable<void> {
|
||||
return this._post({ components }, `rss/override/revert/${dossierId}/${fileId}`);
|
||||
}
|
||||
|
||||
@ -69,8 +57,7 @@ export class RssService extends GenericService<void> {
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
private _getRSSDataAsXML(@RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
private _getRSSDataAsXML(dossierId: string, fileId: string) {
|
||||
const queryParams: QueryParam[] = [];
|
||||
queryParams.push({ key: 'fileId', value: fileId });
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericService, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, mapEach } from '@iqser/common-ui';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { IViewedPage, IViewedPagesRequest, ViewedPage } from '@red/domain';
|
||||
import { firstValueFrom, of } from 'rxjs';
|
||||
@ -10,20 +10,17 @@ import { firstValueFrom, of } from 'rxjs';
|
||||
export class ViewedPagesService extends GenericService<unknown> {
|
||||
protected readonly _defaultModelPath = 'viewedPages';
|
||||
|
||||
@Validate()
|
||||
add(@RequiredParam() body: IViewedPagesRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
add(body: IViewedPagesRequest, dossierId: string, fileId: string) {
|
||||
const modelPath = `${this._defaultModelPath}/${dossierId}/${fileId}`;
|
||||
return firstValueFrom(this._post(body, modelPath));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
remove(@RequiredParam() dossierId: string, @RequiredParam() fileId: string, @RequiredParam() page: number) {
|
||||
remove(dossierId: string, fileId: string, page: number) {
|
||||
const modelPath = `${this._defaultModelPath}/${dossierId}/${fileId}/${page}`;
|
||||
return firstValueFrom(super.delete({}, modelPath));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
load(@RequiredParam() dossierId: string, @RequiredParam() fileId: string) {
|
||||
load(dossierId: string, fileId: string) {
|
||||
const request = this._getOne<{ pages?: IViewedPage[] }>([dossierId, fileId]).pipe(
|
||||
map(res => res.pages),
|
||||
catchError(() => of([] as IViewedPage[])),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericService, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService } from '@iqser/common-ui';
|
||||
import { IGeneralConfiguration } from '@red/domain';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@ -13,8 +13,7 @@ export class GeneralSettingsService extends GenericService<IGeneralConfiguration
|
||||
return this._getOne(['general']);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
updateGeneralConfigurations(@RequiredParam() body: IGeneralConfiguration) {
|
||||
updateGeneralConfigurations(body: IGeneralConfiguration) {
|
||||
return this._post<unknown>(body, `${this._defaultModelPath}/general`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericService, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, QueryParam } from '@iqser/common-ui';
|
||||
import { ILicense, ILicenseReport, ILicenseReportRequest, ILicenses } from '@red/domain';
|
||||
import { BehaviorSubject, firstValueFrom, Observable, of } from 'rxjs';
|
||||
import { catchError, filter, tap } from 'rxjs/operators';
|
||||
@ -128,8 +128,7 @@ export class LicenseService extends GenericService<ILicenseReport> {
|
||||
this.setSelectedLicense(this.activeLicense);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
getReport(@RequiredParam() body: ILicenseReportRequest, limit?: number, offset?: number) {
|
||||
getReport(body: ILicenseReportRequest, limit?: number, offset?: number) {
|
||||
const queryParams: QueryParam[] = [];
|
||||
if (limit) {
|
||||
queryParams.push({ key: 'limit', value: limit });
|
||||
|
||||
@ -1,15 +1,5 @@
|
||||
import { Inject, Injectable, OnDestroy } from '@angular/core';
|
||||
import {
|
||||
BASE_HREF,
|
||||
EntitiesService,
|
||||
getConfig,
|
||||
List,
|
||||
mapEach,
|
||||
QueryParam,
|
||||
RequiredParam,
|
||||
TenantContextHolder,
|
||||
Validate,
|
||||
} from '@iqser/common-ui';
|
||||
import { BASE_HREF, EntitiesService, getConfig, List, mapEach, QueryParam, TenantsService } from '@iqser/common-ui';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { EMPTY, firstValueFrom, iif, merge, Observable, of, Subscription, timer } from 'rxjs';
|
||||
import { AppConfig, Dossier, INotification, Notification, NotificationTypes } from '@red/domain';
|
||||
@ -40,7 +30,7 @@ export class NotificationsService extends EntitiesService<INotification, Notific
|
||||
constructor(
|
||||
@Inject(BASE_HREF) private readonly _baseHref: string,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _dossiersCacheService: DossiersCacheService,
|
||||
) {
|
||||
@ -57,8 +47,7 @@ export class NotificationsService extends EntitiesService<INotification, Notific
|
||||
this.#subscription.unsubscribe();
|
||||
}
|
||||
|
||||
@Validate()
|
||||
async toggleNotificationRead(@RequiredParam() body: List, @RequiredParam() setRead: boolean) {
|
||||
async toggleNotificationRead(body: List, setRead: boolean) {
|
||||
let queryParam: QueryParam;
|
||||
if (setRead !== undefined && setRead !== null) {
|
||||
queryParam = { key: 'setRead', value: setRead };
|
||||
@ -78,6 +67,41 @@ export class NotificationsService extends EntitiesService<INotification, Notific
|
||||
);
|
||||
}
|
||||
|
||||
private _new(notification: INotification) {
|
||||
const message = this._translate(notification, notificationsTranslations[notification.notificationType] as string);
|
||||
return new Notification(notification, message);
|
||||
}
|
||||
|
||||
private _translate(notification: INotification, translation: string): string {
|
||||
const fileId = notification.target.fileId;
|
||||
const dossierId = notification.target.dossierId;
|
||||
const dossier = this._dossiersCacheService.get(dossierId);
|
||||
const fileName = notification.target.fileName;
|
||||
const dossierName = notification.target?.dossierName ?? dossier?.dossierName;
|
||||
const downloadHref = `/ui/${this._tenantsService.activeTenantId}/main/downloads`;
|
||||
|
||||
return this._translateService.instant(translation, {
|
||||
fileHref: this._getFileHref(dossier, fileId),
|
||||
dossierHref: this._getDossierHref(dossier),
|
||||
dossierName: dossierName ?? this._translateService.instant(_('notifications.deleted-dossier')),
|
||||
fileName: fileName ?? this._translateService.instant(_('file')),
|
||||
user: this._getUsername(notification.userId),
|
||||
downloadHref: downloadHref,
|
||||
});
|
||||
}
|
||||
|
||||
private _getFileHref(dossier: Dossier, fileId: string): string {
|
||||
return dossier ? `${this._getDossierHref(dossier)}/file/${fileId}` : null;
|
||||
}
|
||||
|
||||
private _getDossierHref(dossier: Dossier): string {
|
||||
return dossier ? `${this._baseHref}/${this._tenantsService.activeTenantId}${dossier.routerLink}` : null;
|
||||
}
|
||||
|
||||
private _getUsername(userId: string | undefined) {
|
||||
return this._userService.getName(userId) || this._translateService.instant(_('unknown'));
|
||||
}
|
||||
|
||||
#initTimerAndChanges() {
|
||||
const timer$ = timer(0, CHANGED_CHECK_INTERVAL).pipe(
|
||||
switchMap(() => (this._dossiersCacheService.empty ? this._dossiersCacheService.load() : of(null))),
|
||||
@ -119,39 +143,4 @@ export class NotificationsService extends EntitiesService<INotification, Notific
|
||||
#loadNotificationsIfChanged(): Observable<Notification[]> {
|
||||
return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.loadAll(), EMPTY)));
|
||||
}
|
||||
|
||||
private _new(notification: INotification) {
|
||||
const message = this._translate(notification, notificationsTranslations[notification.notificationType] as string);
|
||||
return new Notification(notification, message);
|
||||
}
|
||||
|
||||
private _translate(notification: INotification, translation: string): string {
|
||||
const fileId = notification.target.fileId;
|
||||
const dossierId = notification.target.dossierId;
|
||||
const dossier = this._dossiersCacheService.get(dossierId);
|
||||
const fileName = notification.target.fileName;
|
||||
const dossierName = notification.target?.dossierName ?? dossier?.dossierName;
|
||||
const downloadHref = `/ui/${this._tenantContextHolder.currentTenant}/main/downloads`;
|
||||
|
||||
return this._translateService.instant(translation, {
|
||||
fileHref: this._getFileHref(dossier, fileId),
|
||||
dossierHref: this._getDossierHref(dossier),
|
||||
dossierName: dossierName ?? this._translateService.instant(_('notifications.deleted-dossier')),
|
||||
fileName: fileName ?? this._translateService.instant(_('file')),
|
||||
user: this._getUsername(notification.userId),
|
||||
downloadHref: downloadHref,
|
||||
});
|
||||
}
|
||||
|
||||
private _getFileHref(dossier: Dossier, fileId: string): string {
|
||||
return dossier ? `${this._getDossierHref(dossier)}/file/${fileId}` : null;
|
||||
}
|
||||
|
||||
private _getDossierHref(dossier: Dossier): string {
|
||||
return dossier ? `${this._baseHref}/${this._tenantContextHolder.currentTenant}${dossier.routerLink}` : null;
|
||||
}
|
||||
|
||||
private _getUsername(userId: string | undefined) {
|
||||
return this._userService.getName(userId) || this._translateService.instant(_('unknown'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, List, QueryParam, Toaster } from '@iqser/common-ui';
|
||||
import { Dossier, File, IPageExclusionRequest } from '@red/domain';
|
||||
import { catchError, switchMap, tap } from 'rxjs/operators';
|
||||
import { FilesService } from './files/files.service';
|
||||
@ -26,18 +26,15 @@ export class ReanalysisService extends GenericService<unknown> {
|
||||
super();
|
||||
}
|
||||
|
||||
@Validate()
|
||||
excludePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() file: File) {
|
||||
excludePages(body: IPageExclusionRequest, dossierId: string, file: File) {
|
||||
return this._post(body, `exclude-pages/${dossierId}/${file.id}`).pipe(switchMap(() => this._filesService.reload(dossierId, file)));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
includePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() file: File) {
|
||||
includePages(body: IPageExclusionRequest, dossierId: string, file: File) {
|
||||
return this._post(body, `include-pages/${dossierId}/${file.id}`).pipe(switchMap(() => this._filesService.reload(dossierId, file)));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
reanalyzeFilesForDossier(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string, params?: ReanalyzeQueryParams) {
|
||||
async reanalyzeFilesForDossier(files: List<File>, dossierId: string, params?: ReanalyzeQueryParams) {
|
||||
const fileIds = files.map(f => f.id);
|
||||
const queryParams: QueryParam[] = [];
|
||||
if (params?.force) {
|
||||
@ -47,24 +44,22 @@ export class ReanalysisService extends GenericService<unknown> {
|
||||
queryParams.push({ key: 'triggeredByUser', value: true });
|
||||
}
|
||||
|
||||
return this._post(fileIds, `reanalyze/${dossierId}/bulk`, queryParams).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
|
||||
await firstValueFrom(this._post(fileIds, `reanalyze/${dossierId}/bulk`, queryParams));
|
||||
return firstValueFrom(this._filesService.loadAll(dossierId));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
toggleAnalysis(@RequiredParam() dossierId: string, @RequiredParam() files: List<File>, excluded?: boolean) {
|
||||
async toggleAnalysis(dossierId: string, files: List<File>, excluded?: boolean) {
|
||||
const fileIds = files.map(f => f.id);
|
||||
const queryParams: QueryParam[] = [];
|
||||
if (excluded) {
|
||||
queryParams.push({ key: 'excluded', value: excluded });
|
||||
}
|
||||
|
||||
return this._post(fileIds, `toggle-analysis/${dossierId}/bulk`, queryParams).pipe(
|
||||
switchMap(() => this._filesService.loadAll(dossierId)),
|
||||
);
|
||||
await firstValueFrom(this._post(fileIds, `toggle-analysis/${dossierId}/bulk`, queryParams));
|
||||
return firstValueFrom(this._filesService.loadAll(dossierId));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
toggleAutomaticAnalysis(@RequiredParam() dossierId: string, @RequiredParam() files: File[]) {
|
||||
toggleAutomaticAnalysis(dossierId: string, files: File[]) {
|
||||
const fileIds = files.map(file => file.id);
|
||||
const excluded = !files[0].excludedFromAutomaticAnalysis;
|
||||
const queryParams: QueryParam[] = [{ key: 'excluded', value: excluded }];
|
||||
@ -82,14 +77,13 @@ export class ReanalysisService extends GenericService<unknown> {
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
ocrFiles(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string) {
|
||||
async ocrFiles(files: List<File>, dossierId: string) {
|
||||
const fileIds = files.map(f => f.id);
|
||||
return this._post(fileIds, `ocr/reanalyze/${dossierId}/bulk`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
|
||||
await firstValueFrom(this._post(fileIds, `ocr/reanalyze/${dossierId}/bulk`));
|
||||
return firstValueFrom(this._filesService.loadAll(dossierId));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
async reanalyzeDossier(@RequiredParam() dossier: Dossier, force?: boolean) {
|
||||
async reanalyzeDossier(dossier: Dossier, force?: boolean) {
|
||||
const { dossierId } = dossier;
|
||||
const queryParams: QueryParam[] = [];
|
||||
if (force) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GenericService, HeadersConfiguration, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, HeadersConfiguration } from '@iqser/common-ui';
|
||||
import { IPlaceholdersResponse, IReportTemplate } from '@red/domain';
|
||||
import { firstValueFrom, Observable, of } from 'rxjs';
|
||||
import { HttpResponse } from '@angular/common/http';
|
||||
@ -11,8 +11,7 @@ import { catchError, map } from 'rxjs/operators';
|
||||
export class ReportTemplateService extends GenericService<unknown> {
|
||||
protected readonly _defaultModelPath = 'templateUpload';
|
||||
|
||||
@Validate()
|
||||
uploadTemplateForm(@RequiredParam() dossierTemplateId: string, multiFileReport?: boolean, file?: Blob) {
|
||||
uploadTemplateForm(dossierTemplateId: string, multiFileReport?: boolean, file?: Blob) {
|
||||
const formParams = new FormData();
|
||||
|
||||
if (multiFileReport !== undefined) {
|
||||
@ -27,35 +26,26 @@ export class ReportTemplateService extends GenericService<unknown> {
|
||||
});
|
||||
}
|
||||
|
||||
@Validate()
|
||||
delete(@RequiredParam() dossierTemplateId: string, @RequiredParam() templateId: string) {
|
||||
delete(dossierTemplateId: string, templateId: string) {
|
||||
return super.delete({}, `${this._defaultModelPath}/${dossierTemplateId}/${templateId}`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
getAvailableReportTemplates(@RequiredParam() dossierTemplateId: string) {
|
||||
getAvailableReportTemplates(dossierTemplateId: string) {
|
||||
const request = this.getAll<IReportTemplate[]>(`${this._defaultModelPath}/${dossierTemplateId}`);
|
||||
return firstValueFrom(request.pipe(map(templates => this._sortAvailableReportTypes(templates))));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
getAvailablePlaceholders(@RequiredParam() dossierTemplateId: string) {
|
||||
getAvailablePlaceholders(dossierTemplateId: string) {
|
||||
return this._getOne<IPlaceholdersResponse>([dossierTemplateId], 'placeholders');
|
||||
}
|
||||
|
||||
@Validate()
|
||||
getTemplatesByPlaceholder(@RequiredParam() dossierTemplateId: string, @RequiredParam() attributeId: string) {
|
||||
getTemplatesByPlaceholder(dossierTemplateId: string, attributeId: string) {
|
||||
return this._post<IReportTemplate[]>({ value: attributeId }, `templates/${dossierTemplateId}`).pipe(catchError(() => of([])));
|
||||
}
|
||||
|
||||
downloadReportTemplate(dossierTemplateId: string, templateId: string, observe: 'response'): Observable<HttpResponse<Blob>>;
|
||||
downloadReportTemplate(dossierTemplateId: string, templateId: string, observe: 'body'): Observable<Blob>;
|
||||
@Validate()
|
||||
downloadReportTemplate(
|
||||
@RequiredParam() dossierTemplateId: string,
|
||||
@RequiredParam() templateId: string,
|
||||
observe: 'body' | 'response' = 'body',
|
||||
) {
|
||||
downloadReportTemplate(dossierTemplateId: string, templateId: string, observe: 'body' | 'response' = 'body') {
|
||||
return this._http.request('get', `/${this._defaultModelPath}/${dossierTemplateId}/${templateId}`, {
|
||||
responseType: 'blob',
|
||||
observe: observe,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
import { TenantsService } from '@iqser/common-ui';
|
||||
|
||||
const LAST_DOSSIERS_SCREEN = 'routerHistory_lastDossiersScreen';
|
||||
|
||||
@ -11,7 +11,7 @@ const LAST_DOSSIERS_SCREEN = 'routerHistory_lastDossiersScreen';
|
||||
export class RouterHistoryService {
|
||||
private _lastDossiersScreen = localStorage.getItem(LAST_DOSSIERS_SCREEN);
|
||||
|
||||
constructor(private readonly _router: Router, private readonly _tenantContextHolder: TenantContextHolder) {
|
||||
constructor(private readonly _router: Router, private readonly _tenantsService: TenantsService) {
|
||||
// eslint-disable-next-line rxjs/no-ignored-subscription
|
||||
this._router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => {
|
||||
if (event.url.includes('/dossiers') || event.url.includes('/archive')) {
|
||||
@ -23,7 +23,7 @@ export class RouterHistoryService {
|
||||
|
||||
navigateToLastDossiersScreen(): void {
|
||||
if (this._router.url === decodeURI(this._lastDossiersScreen)) {
|
||||
this._router.navigate(['/' + this._tenantContextHolder.currentTenant]);
|
||||
this._router.navigate(['/' + this._tenantsService.activeTenantId]);
|
||||
} else {
|
||||
const url = decodeURI(this._lastDossiersScreen).split('?')[0];
|
||||
// todo links
|
||||
|
||||
@ -1,28 +1,25 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||
import { UserService } from './user.service';
|
||||
import { IqserPermissionsService, IqserRoleGuard, TenantContextHolder } from '@iqser/common-ui';
|
||||
import { IqserPermissionsService, IqserRoleGuard } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class RedRoleGuard extends IqserRoleGuard {
|
||||
protected readonly _userService = inject(UserService);
|
||||
protected readonly _permissionsService = inject(IqserPermissionsService);
|
||||
protected readonly _tenantContextHolder = inject(TenantContextHolder);
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
|
||||
const currentUser = this._userService.currentUser;
|
||||
|
||||
if (!currentUser?.hasAnyRole) {
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/auth-error`]);
|
||||
await this._router.navigate([`/${this._tenantsService.activeTenantId}/auth-error`]);
|
||||
this._loadingService.stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
// we have at least 1 RED Role -> if it's not user he must be an admin
|
||||
if (currentUser.isUserAdmin && !currentUser.isAdmin && state.url.includes('admin') && !state.url.includes('users')) {
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/admin/users`]);
|
||||
await this._router.navigate([`/${this._tenantsService.activeTenantId}/main/admin/users`]);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -32,7 +29,7 @@ export class RedRoleGuard extends IqserRoleGuard {
|
||||
!currentUser.isUser &&
|
||||
!(state.url.includes('/main/admin/users') || state.url.includes('/main/account'))
|
||||
) {
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/admin/users`]);
|
||||
await this._router.navigate([`/${this._tenantsService.activeTenantId}/main/admin/users`]);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -41,9 +38,9 @@ export class RedRoleGuard extends IqserRoleGuard {
|
||||
return true;
|
||||
}
|
||||
if (!currentUser.isUser) {
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/admin`]);
|
||||
await this._router.navigate([`/${this._tenantsService.activeTenantId}/main/admin`]);
|
||||
} else {
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}`]);
|
||||
await this._router.navigate([`/${this._tenantsService.activeTenantId}`]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import { UserService } from '@users/user.service';
|
||||
import { SystemPreferencesService } from '@services/system-preferences.service';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { LicenseService } from '@services/license.service';
|
||||
import { BASE_HREF, IqserPermissionsService, LoadingService, TenantContextHolder } from '@iqser/common-ui';
|
||||
import { BASE_HREF, IqserPermissionsService, LoadingService, TenantsService } from '@iqser/common-ui';
|
||||
import { FeaturesService } from '@services/features.service';
|
||||
import { GeneralSettingsService } from '@services/general-settings.service';
|
||||
import { tap } from 'rxjs/operators';
|
||||
@ -32,7 +32,7 @@ export const mainResolver: ResolveFn<void> = async () => {
|
||||
|
||||
const systemPreferencesService = inject(SystemPreferencesService);
|
||||
const userPreferenceService = inject(UserPreferenceService);
|
||||
const tenantContextHolder = inject(TenantContextHolder);
|
||||
const tenantsService = inject(TenantsService);
|
||||
const licenseService = inject(LicenseService);
|
||||
const loadingService = inject(LoadingService);
|
||||
const configService = inject(ConfigService);
|
||||
@ -53,10 +53,11 @@ export const mainResolver: ResolveFn<void> = async () => {
|
||||
const lastDossierTemplate = userPreferenceService.getLastDossierTemplate();
|
||||
|
||||
if (lastDossierTemplate) {
|
||||
redirectToLastDossierTemplate(baseHref, tenantContextHolder.currentTenant, lastDossierTemplate);
|
||||
redirectToLastDossierTemplate(baseHref, tenantsService.activeTenantId, lastDossierTemplate);
|
||||
}
|
||||
|
||||
loadingService.stop();
|
||||
tenantsService.storeTenant(userService.currentUser.email);
|
||||
|
||||
logger.info('[ROUTES] Main resolver finished!');
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"ADMIN_CONTACT_NAME": null,
|
||||
"ADMIN_CONTACT_URL": null,
|
||||
"API_URL": "https://dev-09.iqser.cloud/redaction-gateway-v1",
|
||||
"API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1",
|
||||
"APP_NAME": "RedactManager",
|
||||
"AUTO_READ_TIME": 3,
|
||||
"BACKEND_APP_VERSION": "4.4.40",
|
||||
@ -11,7 +11,7 @@
|
||||
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
||||
"OAUTH_CLIENT_ID": "redaction",
|
||||
"OAUTH_IDP_HINT": null,
|
||||
"OAUTH_URL": "https://dev-09.iqser.cloud/auth",
|
||||
"OAUTH_URL": "https://dev-08.iqser.cloud/auth",
|
||||
"RECENT_PERIOD_IN_HOURS": 24,
|
||||
"SELECTION_MODE": "structural",
|
||||
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user