Merge branch 'master' into VM/RED-3370
This commit is contained in:
commit
af1aaa16b4
@ -16,7 +16,6 @@
|
||||
"@components/**",
|
||||
"@guards/**",
|
||||
"@i18n/**",
|
||||
"@state/**",
|
||||
"@utils/**",
|
||||
"@models/**",
|
||||
"@environments/**",
|
||||
|
||||
@ -6,8 +6,8 @@ import { BaseScreenComponent } from '@components/base-screen/base-screen.compone
|
||||
import { RouteReuseStrategy, RouterModule, Routes } from '@angular/router';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
|
||||
import { AppStateGuard } from '@state/app-state.guard';
|
||||
import { DossiersGuard } from '@guards/dossiers.guard';
|
||||
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -36,7 +36,7 @@ const routes: Routes = [
|
||||
loadChildren: () => import('./modules/dossier/dossiers.module').then(m => m.DossiersModule),
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard, DossiersGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard, DossierTemplatesGuard, DossiersGuard],
|
||||
requiredRoles: ['RED_USER', 'RED_MANAGER'],
|
||||
},
|
||||
},
|
||||
@ -51,7 +51,7 @@ const routes: Routes = [
|
||||
],
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
import { Component, ViewContainerRef } from '@angular/core';
|
||||
import { Component, OnInit, ViewContainerRef } from '@angular/core';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
})
|
||||
export class AppComponent {
|
||||
export class AppComponent implements OnInit {
|
||||
// ViewContainerRef needs to be injected for the color picker to work
|
||||
// RouterHistoryService needs to be injected for last dossiers screen to be updated on first app load
|
||||
constructor(public viewContainerRef: ViewContainerRef, private readonly _routerHistoryService: RouterHistoryService) {}
|
||||
constructor(
|
||||
public viewContainerRef: ViewContainerRef,
|
||||
private readonly _routerHistoryService: RouterHistoryService,
|
||||
private readonly _userService: UserService,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this._userService.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { NavigationStart, Router } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { FileDownloadService } from '@upload-download/services/file-download.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
@ -34,13 +32,11 @@ export class BaseScreenComponent {
|
||||
name: _('top-bar.navigation-items.my-account.children.account'),
|
||||
routerLink: '/main/account',
|
||||
show: true,
|
||||
action: this.appStateService.reset,
|
||||
},
|
||||
{
|
||||
name: _('top-bar.navigation-items.my-account.children.admin'),
|
||||
routerLink: '/main/admin',
|
||||
show: this.currentUser.isManager || this.currentUser.isUserAdmin,
|
||||
action: this.appStateService.reset,
|
||||
},
|
||||
{
|
||||
name: _('top-bar.navigation-items.my-account.children.downloads'),
|
||||
@ -75,7 +71,6 @@ export class BaseScreenComponent {
|
||||
readonly isSearchScreen$ = this._navigationStart$.pipe(map(isSearchScreen));
|
||||
|
||||
constructor(
|
||||
readonly appStateService: AppStateService,
|
||||
readonly userService: UserService,
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
readonly titleService: Title,
|
||||
|
||||
21
apps/red-ui/src/app/guards/dictionary-exists.guard.ts
Normal file
21
apps/red-ui/src/app/guards/dictionary-exists.guard.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { DICTIONARY_TYPE, DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
import { DictionariesMapService } from '../services/entity-services/dictionaries-map.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DictionaryExistsGuard implements CanActivate {
|
||||
constructor(private readonly _dictionariesMapService: DictionariesMapService, private readonly _router: Router) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
const dossierTemplateId: string = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
const type: string = route.paramMap.get(DICTIONARY_TYPE);
|
||||
|
||||
if (!this._dictionariesMapService.get(dossierTemplateId, type)) {
|
||||
await this._router.navigate(['main', 'admin', 'dossier-templates', dossierTemplateId, 'dictionaries']);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DOSSIER_ID } from '@utils/constants';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossierFilesGuard implements CanActivate {
|
||||
@ -15,7 +16,7 @@ export class DossierFilesGuard implements CanActivate {
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
const dossierId = route.paramMap.get('dossierId');
|
||||
const dossierId = route.paramMap.get(DOSSIER_ID);
|
||||
|
||||
if (!this._dossiersService.has(dossierId)) {
|
||||
await this._router.navigate(['/main', 'dossiers']);
|
||||
|
||||
20
apps/red-ui/src/app/guards/dossier-template-exists.guard.ts
Normal file
20
apps/red-ui/src/app/guards/dossier-template-exists.guard.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossierTemplateExistsGuard implements CanActivate {
|
||||
constructor(private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _router: Router) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
const dossierTemplateId: string = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
|
||||
if (!this._dossierTemplatesService.find(dossierTemplateId)) {
|
||||
await this._router.navigate(['main', 'admin', 'dossier-templates']);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
18
apps/red-ui/src/app/guards/dossier-templates.guard.ts
Normal file
18
apps/red-ui/src/app/guards/dossier-templates.guard.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate } from '@angular/router';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossierTemplatesGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
) {}
|
||||
|
||||
async canActivate(): Promise<boolean> {
|
||||
await firstValueFrom(this._dossierTemplatesService.loadAll());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,11 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
import { CanActivate } from '@angular/router';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossiersGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _router: Router,
|
||||
) {}
|
||||
constructor(private readonly _dossiersService: DossiersService) {}
|
||||
|
||||
async canActivate(): Promise<boolean> {
|
||||
await firstValueFrom(this._dossiersService.loadAll());
|
||||
|
||||
@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { DOSSIER_ID, FILE_ID } from '@utils/constants';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class FilePreviewGuard implements CanActivate {
|
||||
@ -12,8 +13,8 @@ export class FilePreviewGuard implements CanActivate {
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
const dossierId = route.paramMap.get('dossierId');
|
||||
const fileId = route.paramMap.get('fileId');
|
||||
const dossierId = route.paramMap.get(DOSSIER_ID);
|
||||
const fileId = route.paramMap.get(FILE_ID);
|
||||
|
||||
const dossier = this._dossiersService.find(dossierId);
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ export class FileDataModel {
|
||||
private readonly _file: File,
|
||||
private _redactionLog: IRedactionLog,
|
||||
public viewedPages?: IViewedPage[],
|
||||
private _dictionaryData?: { [p: string]: Dictionary },
|
||||
private _dictionaryData?: Dictionary[],
|
||||
private _areDevFeaturesEnabled?: boolean,
|
||||
) {
|
||||
this._buildAllAnnotations();
|
||||
@ -83,7 +83,8 @@ export class FileDataModel {
|
||||
// copy the redactionLog Entry
|
||||
|
||||
const changeLogValues = this.#getChangeLogValues(redactionLogEntry);
|
||||
if (!this._dictionaryData[redactionLogEntry.type]) {
|
||||
const dictionaryData = this._dictionaryData.find(dict => dict.type === redactionLogEntry.type);
|
||||
if (!dictionaryData) {
|
||||
this.missingTypes.add(redactionLogEntry.type);
|
||||
return;
|
||||
}
|
||||
@ -94,7 +95,7 @@ export class FileDataModel {
|
||||
changeLogValues.isChangeLogEntry,
|
||||
changeLogValues.hidden,
|
||||
this.redactionLog.legalBasis,
|
||||
!!this._dictionaryData[redactionLogEntry.type]?.hint,
|
||||
!!dictionaryData?.hint,
|
||||
);
|
||||
|
||||
if (
|
||||
|
||||
@ -3,7 +3,6 @@ import { RouterModule } from '@angular/router';
|
||||
import { CompositeRouteGuard } from '@iqser/common-ui';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
import { RedRoleGuard } from '../auth/red-role.guard';
|
||||
import { AppStateGuard } from '@state/app-state.guard';
|
||||
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
|
||||
|
||||
const routes = [
|
||||
@ -13,7 +12,7 @@ const routes = [
|
||||
component: BaseAccountScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
loadChildren: () => import('./screens/user-profile/user-profile.module').then(m => m.UserProfileModule),
|
||||
},
|
||||
@ -22,7 +21,7 @@ const routes = [
|
||||
component: BaseAccountScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
requiredRoles: ['RED_USER'],
|
||||
},
|
||||
loadChildren: () => import('./screens/notifications/notifications.module').then(m => m.NotificationsModule),
|
||||
|
||||
@ -2,7 +2,6 @@ import { NgModule } from '@angular/core';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
import { CompositeRouteGuard } from '@iqser/common-ui';
|
||||
import { RedRoleGuard } from '../auth/red-role.guard';
|
||||
import { AppStateGuard } from '@state/app-state.guard';
|
||||
import { DictionaryListingScreenComponent } from './screens/dictionary-listing/dictionary-listing-screen.component';
|
||||
import { DictionaryOverviewScreenComponent } from './screens/dictionary-overview/dictionary-overview-screen.component';
|
||||
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
|
||||
@ -18,6 +17,10 @@ import { TrashScreenComponent } from './screens/trash/trash-screen.component';
|
||||
import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component';
|
||||
import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component';
|
||||
import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component';
|
||||
import { DossierTemplatesGuard } from '../../guards/dossier-templates.guard';
|
||||
import { DICTIONARY_TYPE, DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
import { DossierTemplateExistsGuard } from '../../guards/dossier-template-exists.guard';
|
||||
import { DictionaryExistsGuard } from '../../guards/dictionary-exists.guard';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'dossier-templates', pathMatch: 'full' },
|
||||
@ -29,7 +32,7 @@ const routes: Routes = [
|
||||
component: BaseAdminScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
loadChildren: () =>
|
||||
import('./screens/dossier-templates-listing/dossier-templates-listing.module').then(
|
||||
@ -37,7 +40,7 @@ const routes: Routes = [
|
||||
),
|
||||
},
|
||||
{
|
||||
path: ':dossierTemplateId',
|
||||
path: `:${DOSSIER_TEMPLATE_ID}`,
|
||||
children: [
|
||||
{
|
||||
path: 'info',
|
||||
@ -53,16 +56,16 @@ const routes: Routes = [
|
||||
component: DictionaryListingScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':dictionary',
|
||||
path: `:${DICTIONARY_TYPE}`,
|
||||
component: DictionaryOverviewScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
canDeactivate: [PendingChangesGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard, DictionaryExistsGuard],
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -73,7 +76,7 @@ const routes: Routes = [
|
||||
canActivate: [CompositeRouteGuard],
|
||||
canDeactivate: [PendingChangesGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
loadChildren: () => import('./screens/rules/rules.module').then(m => m.RulesModule),
|
||||
},
|
||||
@ -82,7 +85,7 @@ const routes: Routes = [
|
||||
component: FileAttributesListingScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -90,7 +93,7 @@ const routes: Routes = [
|
||||
component: BaseDossierTemplateScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
loadChildren: () => import('./screens/watermark/watermark.module').then(m => m.WatermarkModule),
|
||||
},
|
||||
@ -99,7 +102,7 @@ const routes: Routes = [
|
||||
component: BaseDossierTemplateScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
loadChildren: () => import('./screens/reports/reports.module').then(m => m.ReportsModule),
|
||||
},
|
||||
@ -108,7 +111,7 @@ const routes: Routes = [
|
||||
component: DossierAttributesListingScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -116,7 +119,7 @@ const routes: Routes = [
|
||||
component: DefaultColorsScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -127,10 +130,13 @@ const routes: Routes = [
|
||||
},
|
||||
{ path: '', redirectTo: 'info', pathMatch: 'full' },
|
||||
],
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: { routeGuards: [DossierTemplateExistsGuard] },
|
||||
},
|
||||
],
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard, DossierTemplatesGuard],
|
||||
requiredRoles: ['RED_MANAGER', 'RED_ADMIN'],
|
||||
},
|
||||
},
|
||||
@ -139,7 +145,7 @@ const routes: Routes = [
|
||||
component: UserListingScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
requiredRoles: ['RED_USER_ADMIN'],
|
||||
},
|
||||
},
|
||||
@ -148,7 +154,7 @@ const routes: Routes = [
|
||||
component: LicenseInformationScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
requiredRoles: ['RED_ADMIN'],
|
||||
},
|
||||
},
|
||||
@ -157,7 +163,7 @@ const routes: Routes = [
|
||||
component: DigitalSignatureScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
requiredRoles: ['RED_ADMIN'],
|
||||
},
|
||||
},
|
||||
@ -166,7 +172,7 @@ const routes: Routes = [
|
||||
component: AuditScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
requiredRoles: ['RED_ADMIN'],
|
||||
},
|
||||
},
|
||||
@ -176,7 +182,7 @@ const routes: Routes = [
|
||||
canActivate: [CompositeRouteGuard],
|
||||
canDeactivate: [PendingChangesGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
requiredRoles: ['RED_ADMIN'],
|
||||
},
|
||||
},
|
||||
@ -185,7 +191,7 @@ const routes: Routes = [
|
||||
component: TrashScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
requiredRoles: ['RED_MANAGER'],
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { Component, HostBinding, Input, OnInit } from '@angular/core';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { adminSideNavTranslations } from '../translations/admin-side-nav-translations';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DICTIONARY_TYPE } from '@utils/constants';
|
||||
|
||||
type Type = 'settings' | 'dossierTemplates';
|
||||
|
||||
@ -68,7 +69,7 @@ export class AdminSideNavComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private readonly _userService: UserService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
) {}
|
||||
|
||||
@ -77,6 +78,6 @@ export class AdminSideNavComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.prefix = this._appStateService.activeDictionaryType ? '../../' : '../';
|
||||
this.prefix = this._route.snapshot.paramMap.get(DICTIONARY_TYPE) ? '../../' : '../';
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,31 +1,22 @@
|
||||
<div class="menu flex-2 visible-lg breadcrumbs-container">
|
||||
<a
|
||||
*ngIf="root || !!dossierTemplatesService.activeDossierTemplate"
|
||||
[routerLink]="'/main/admin/dossier-templates'"
|
||||
class="breadcrumb"
|
||||
translate="dossier-templates.label"
|
||||
></a>
|
||||
|
||||
<mat-icon svgIcon="iqser:arrow-right"></mat-icon>
|
||||
|
||||
<ng-container *ngIf="dossierTemplate$ | async as dossierTemplate">
|
||||
<a [class.active]="!appStateService.activeDictionaryType" [routerLink]="dossierTemplate.routerLink" class="breadcrumb ml-0">
|
||||
<a
|
||||
*ngIf="root || dossierTemplate"
|
||||
[routerLink]="'/main/admin/dossier-templates'"
|
||||
class="breadcrumb"
|
||||
translate="dossier-templates.label"
|
||||
></a>
|
||||
|
||||
<mat-icon svgIcon="iqser:arrow-right"></mat-icon>
|
||||
|
||||
<a [class.active]="(activeDictionary$ | async) === undefined" [routerLink]="dossierTemplate.routerLink" class="breadcrumb ml-0">
|
||||
{{ dossierTemplate.name }}
|
||||
</a>
|
||||
|
||||
<ng-container *ngIf="appStateService.activeDictionary">
|
||||
<ng-container *ngIf="activeDictionary$ | async as activeDictionary">
|
||||
<mat-icon svgIcon="iqser:arrow-right"></mat-icon>
|
||||
<a
|
||||
[routerLink]="
|
||||
'/main/admin/dossier-templates/' +
|
||||
dossierTemplate.dossierTemplateId +
|
||||
'/dictionaries/' +
|
||||
appStateService.activeDictionaryType
|
||||
"
|
||||
class="breadcrumb ml-0"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
{{ appStateService.activeDictionary.label }}
|
||||
<a [routerLink]="activeDictionary.routerLink" class="breadcrumb ml-0" routerLinkActive="active">
|
||||
{{ activeDictionary.label }}
|
||||
</a>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DossierTemplate } from '@red/domain';
|
||||
import { Dictionary, DossierTemplate } from '@red/domain';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-template-breadcrumbs',
|
||||
@ -16,17 +15,22 @@ import { DossierTemplate } from '@red/domain';
|
||||
export class DossierTemplateBreadcrumbsComponent {
|
||||
@Input() root = false;
|
||||
readonly dossierTemplate$: Observable<DossierTemplate>;
|
||||
readonly activeDictionary$: Observable<Dictionary>;
|
||||
|
||||
constructor(
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
readonly permissionService: PermissionsService,
|
||||
readonly appStateService: AppStateService,
|
||||
readonly dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
this.dossierTemplate$ = _route.paramMap.pipe(
|
||||
map(params => params.get('dossierTemplateId')),
|
||||
switchMap((dossierTemplateId: string) => this.dossierTemplatesService.getEntityChanged$(dossierTemplateId)),
|
||||
map(params => params.get(DOSSIER_TEMPLATE_ID)),
|
||||
switchMap((dossierTemplateId: string) => this._dossierTemplatesService.getEntityChanged$(dossierTemplateId)),
|
||||
);
|
||||
this.activeDictionary$ = _route.paramMap.pipe(
|
||||
map(params => [params.get(DOSSIER_TEMPLATE_ID), params.get('dictionary')]),
|
||||
switchMap(([dossierTemplateId, dictionary]: [string, string]) =>
|
||||
dictionary ? this._dictionariesMapService.watch$(dossierTemplateId, dictionary) : of(undefined),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,13 +5,12 @@ import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { BaseDialogComponent, shareDistinctLast, Toaster } from '@iqser/common-ui';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { toSnakeCase } from '@utils/functions';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
import { Dictionary, IDictionary } from '@red/domain';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-add-edit-dictionary-dialog',
|
||||
@ -34,7 +33,7 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
readonly userService: UserService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
protected readonly _injector: Injector,
|
||||
@ -66,28 +65,17 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
|
||||
async save(): Promise<void> {
|
||||
const dictionary = this._formToObject();
|
||||
let observable: Observable<unknown>;
|
||||
const dossierTemplateId = this._data.dossierTemplateId;
|
||||
|
||||
if (this.dictionary) {
|
||||
// edit mode
|
||||
observable = this._dictionaryService.updateDictionary(dictionary, dossierTemplateId, dictionary.type);
|
||||
await firstValueFrom(this._dictionaryService.updateDictionary(dictionary, dossierTemplateId, dictionary.type));
|
||||
} else {
|
||||
// create mode
|
||||
observable = this._dictionaryService.addDictionary({ ...dictionary, dossierTemplateId });
|
||||
await firstValueFrom(this._dictionaryService.addDictionary({ ...dictionary, dossierTemplateId }));
|
||||
}
|
||||
|
||||
return firstValueFrom(observable)
|
||||
.then(() => this._dialogRef.close(true))
|
||||
.catch(error => {
|
||||
if (error.status === HttpStatusCode.Conflict) {
|
||||
this._toaster.error(_('add-edit-dictionary.error.dictionary-already-exists'));
|
||||
} else if (error.status === HttpStatusCode.BadRequest) {
|
||||
this._toaster.error(_('add-edit-dictionary.error.invalid-color-or-rank'));
|
||||
} else {
|
||||
this._toaster.error(_('add-edit-dictionary.error.generic'));
|
||||
}
|
||||
});
|
||||
this._dialogRef.close(true);
|
||||
}
|
||||
|
||||
private _getForm(dictionary: Dictionary): FormGroup {
|
||||
@ -103,7 +91,7 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||
}
|
||||
|
||||
private _toTechnicalName(value: string) {
|
||||
const existingTechnicalNames = Object.keys(this._appStateService.dictionaryData[this._dossierTemplateId]);
|
||||
const existingTechnicalNames = this._dictionariesMapService.get(this._dossierTemplateId).map(dict => dict.type);
|
||||
const baseTechnicalName = toSnakeCase(value.trim());
|
||||
let technicalName = baseTechnicalName;
|
||||
let suffix = 1;
|
||||
|
||||
@ -25,7 +25,7 @@ export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditDossierAttributeDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
readonly data: { readonly dossierAttribute: IDossierAttributeConfig },
|
||||
readonly data: { readonly dossierAttribute: IDossierAttributeConfig; dossierTemplateId: string },
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this._getForm(this.dossierAttribute);
|
||||
@ -37,7 +37,7 @@ export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(this.form.getRawValue())) {
|
||||
for (const key of Object.keys(this.form.getRawValue() as IDossierAttributeConfig)) {
|
||||
if (this.dossierAttribute[key] !== this.form.get(key).value) {
|
||||
return true;
|
||||
}
|
||||
@ -55,7 +55,7 @@ export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent
|
||||
...this.form.getRawValue(),
|
||||
};
|
||||
|
||||
this._dossierAttributesService.createOrUpdate(attribute).subscribe(
|
||||
this._dossierAttributesService.createOrUpdate(attribute, this.data.dossierTemplateId).subscribe(
|
||||
() => {
|
||||
this._dialogRef.close(true);
|
||||
},
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { Component, Inject, Injector } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import * as moment from 'moment';
|
||||
@ -9,8 +8,9 @@ import { downloadTypesTranslations } from '../../../../translations/download-typ
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { BaseDialogComponent, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DownloadFileType, IDossierTemplate } from '@red/domain';
|
||||
import { DossierTemplate, DownloadFileType, IDossierTemplate } from '@red/domain';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
import { DictionaryService } from '../../../shared/services/dictionary.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
@ -25,22 +25,24 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
||||
key: type,
|
||||
label: downloadTypesTranslations[type],
|
||||
}));
|
||||
readonly dossierTemplate: DossierTemplate;
|
||||
private _previousValidFrom: Moment;
|
||||
private _previousValidTo: Moment;
|
||||
private _lastValidFrom: Moment;
|
||||
private _lastValidTo: Moment;
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditDossierTemplateDialogComponent>,
|
||||
private readonly _loadingService: LoadingService,
|
||||
@Inject(MAT_DIALOG_DATA) readonly dossierTemplate: IDossierTemplate,
|
||||
@Inject(MAT_DIALOG_DATA) readonly dossierTemplateId: string,
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
this.dossierTemplate = this._dossierTemplatesService.find(this.dossierTemplateId);
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
this.hasValidFrom = !!this.dossierTemplate?.validFrom;
|
||||
@ -80,8 +82,7 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
||||
validFrom: this.hasValidFrom ? this.form.get('validFrom').value : null,
|
||||
validTo: this.hasValidTo ? this.form.get('validTo').value : null,
|
||||
} as IDossierTemplate;
|
||||
await this._dossierTemplatesService.createOrUpdate(dossierTemplate).toPromise();
|
||||
await this._appStateService.loadDictionaryData();
|
||||
await firstValueFrom(this._dossierTemplatesService.createOrUpdate(dossierTemplate));
|
||||
this._dialogRef.close(true);
|
||||
} catch (error: any) {
|
||||
const message =
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { FileAttributeEncodingTypes, IFileAttributesConfig } from '../../../../../../../../libs/red-domain/src';
|
||||
import { FileAttributeEncodingTypes, IFileAttributesConfig } from '@red/domain';
|
||||
import { fileAttributeEncodingTypesTranslations } from '../../translations/file-attribute-encoding-types-translations';
|
||||
import { BaseDialogComponent, Toaster } from '../../../../../../../../libs/common-ui/src';
|
||||
import { DossierTemplatesService } from '../../../../services/entity-services/dossier-templates.service';
|
||||
import { BaseDialogComponent, Toaster } from '@iqser/common-ui';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { FileAttributesService } from '../../../../services/entity-services/file-attributes.service';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './file-attributes-configurations-dialog.component.html',
|
||||
@ -15,10 +15,9 @@ import { FileAttributesService } from '../../../../services/entity-services/file
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FileAttributesConfigurationsDialogComponent extends BaseDialogComponent {
|
||||
private readonly _configuration: IFileAttributesConfig = this._data;
|
||||
|
||||
readonly encodingTypeOptions = Object.keys(FileAttributeEncodingTypes);
|
||||
readonly translations = fileAttributeEncodingTypesTranslations;
|
||||
private readonly _configuration: IFileAttributesConfig = this._data.config;
|
||||
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
@ -27,7 +26,7 @@ export class FileAttributesConfigurationsDialogComponent extends BaseDialogCompo
|
||||
private readonly _toaster: Toaster,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<FileAttributesConfigurationsDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) private _data: IFileAttributesConfig,
|
||||
@Inject(MAT_DIALOG_DATA) private _data: { config: IFileAttributesConfig; dossierTemplateId: string },
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
this.form = this._getForm();
|
||||
@ -50,12 +49,7 @@ export class FileAttributesConfigurationsDialogComponent extends BaseDialogCompo
|
||||
this._configuration.encoding = this.form.get('encodingType').value;
|
||||
|
||||
try {
|
||||
await firstValueFrom(
|
||||
this._fileAttributesService.setFileAttributeConfig(
|
||||
this._configuration,
|
||||
this._dossierTemplatesService.activeDossierTemplateId,
|
||||
),
|
||||
);
|
||||
await firstValueFrom(this._fileAttributesService.setFileAttributeConfig(this._configuration, this._data.dossierTemplateId));
|
||||
this._toaster.success(_('file-attributes-configurations.update.success'));
|
||||
} catch (e) {
|
||||
this._toaster.error(_('file-attributes-configurations.update.error'));
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector } from '@angular/core';
|
||||
import { DefaultColorType, IColors } from '@red/domain';
|
||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||
import {
|
||||
@ -16,6 +15,8 @@ import { UserService } from '@services/user.service';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
interface ListItem extends IListable {
|
||||
readonly key: string;
|
||||
@ -28,7 +29,7 @@ interface ListItem extends IListable {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DefaultColorsScreenComponent) }],
|
||||
})
|
||||
export class DefaultColorsScreenComponent extends ListingComponent<ListItem> implements OnInit {
|
||||
export class DefaultColorsScreenComponent extends ListingComponent<ListItem> {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
readonly translations = defaultColorsTranslations;
|
||||
@ -38,21 +39,19 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> imp
|
||||
{ label: _('default-colors-screen.table-col-names.color'), class: 'flex-center' },
|
||||
];
|
||||
private _colorsObj: IColors;
|
||||
readonly #dossierTemplateId: string;
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
super(_injector);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await this._loadColors();
|
||||
this.#dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
}
|
||||
|
||||
openEditColorDialog($event: MouseEvent, color: { key: DefaultColorType | string; value: string }) {
|
||||
@ -62,18 +61,17 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> imp
|
||||
{
|
||||
colors: this._colorsObj,
|
||||
colorKey: color.key,
|
||||
dossierTemplateId: this._dossierTemplatesService.activeDossierTemplateId,
|
||||
dossierTemplateId: this.#dossierTemplateId,
|
||||
},
|
||||
async () => {
|
||||
await this._loadColors();
|
||||
await this._appStateService.refreshDossierTemplate(this._dossierTemplatesService.activeDossierTemplateId);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private async _loadColors() {
|
||||
this._loadingService.start();
|
||||
const data = await firstValueFrom(this._appStateService.loadColors(this._dossierTemplatesService.activeDossierTemplateId));
|
||||
const data = await firstValueFrom(this._dictionaryService.getColors(this.#dossierTemplateId));
|
||||
this._colorsObj = data;
|
||||
const entities = Object.keys(data)
|
||||
.map(key => ({
|
||||
@ -82,7 +80,7 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> imp
|
||||
searchKey: key,
|
||||
value: data[key],
|
||||
}))
|
||||
.filter(entry => entry.id !== 'dossierTemplateId');
|
||||
.filter(entry => entry.id !== DOSSIER_TEMPLATE_ID);
|
||||
this.entitiesService.setEntities(entities);
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Component, forwardRef, Injector, OnInit } from '@angular/core';
|
||||
import { Component, forwardRef, Injector } from '@angular/core';
|
||||
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
CircleButtonTypes,
|
||||
@ -20,13 +19,15 @@ import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dictionary-listing-screen.component.html',
|
||||
styleUrls: ['./dictionary-listing-screen.component.scss'],
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DictionaryListingScreenComponent) }],
|
||||
})
|
||||
export class DictionaryListingScreenComponent extends ListingComponent<Dictionary> implements OnInit {
|
||||
export class DictionaryListingScreenComponent extends ListingComponent<Dictionary> {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
@ -38,14 +39,14 @@ export class DictionaryListingScreenComponent extends ListingComponent<Dictionar
|
||||
];
|
||||
chartData: DoughnutChartConfig[] = [];
|
||||
readonly templateStats$: Observable<DossierTemplateStats>;
|
||||
templateStats: DossierTemplateStats;
|
||||
readonly #dossierTemplateId: string;
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
@ -54,13 +55,10 @@ export class DictionaryListingScreenComponent extends ListingComponent<Dictionar
|
||||
) {
|
||||
super(_injector);
|
||||
_loadingService.start();
|
||||
const dossierTemplateId = _route.snapshot.paramMap.get('dossierTemplateId');
|
||||
this.templateStats = this._dossierTemplateStatsService.get(dossierTemplateId);
|
||||
this.templateStats$ = this._dossierTemplateStatsService.watch$(dossierTemplateId).pipe(tap(stats => (this.templateStats = stats)));
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this._loadDictionaryData(false);
|
||||
this.#dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
this.templateStats$ = this._dossierTemplateStatsService
|
||||
.watch$(this.#dossierTemplateId)
|
||||
.pipe(tap(templateStats => this._loadDictionaryData(templateStats)));
|
||||
}
|
||||
|
||||
openDeleteDictionariesDialog($event?: MouseEvent, types = this.listingService.selected) {
|
||||
@ -69,45 +67,30 @@ export class DictionaryListingScreenComponent extends ListingComponent<Dictionar
|
||||
await firstValueFrom(
|
||||
this._dictionaryService.deleteDictionaries(
|
||||
types.map(t => t.type),
|
||||
this._dossierTemplatesService.activeDossierTemplateId,
|
||||
this.#dossierTemplateId,
|
||||
),
|
||||
);
|
||||
await this._loadDictionaryData();
|
||||
this._loadingService.stop();
|
||||
});
|
||||
}
|
||||
|
||||
openAddEditDictionaryDialog($event?: MouseEvent, dictionary?: Dictionary) {
|
||||
this._dialogService.openDialog(
|
||||
'addEditDictionary',
|
||||
$event,
|
||||
{
|
||||
dictionary,
|
||||
dossierTemplateId: this._dossierTemplatesService.activeDossierTemplateId,
|
||||
},
|
||||
async () => {
|
||||
this._loadingService.start();
|
||||
await this._loadDictionaryData();
|
||||
this._loadingService.stop();
|
||||
},
|
||||
);
|
||||
this._dialogService.openDialog('addEditDictionary', $event, {
|
||||
dictionary,
|
||||
dossierTemplateId: this.#dossierTemplateId,
|
||||
});
|
||||
}
|
||||
|
||||
private async _loadDictionaryData(refresh = true): Promise<void> {
|
||||
if (refresh) {
|
||||
await this._appStateService.loadDictionaryData();
|
||||
}
|
||||
|
||||
const appStateDictionaryData = this._appStateService.dictionaryData[this._dossierTemplatesService.activeDossierTemplateId];
|
||||
const entities = Object.values(appStateDictionaryData).filter(d => !d.virtual);
|
||||
private _loadDictionaryData(templateStats: DossierTemplateStats): void {
|
||||
const entities: Dictionary[] = this._dictionariesMapService.get(this.#dossierTemplateId).filter(d => !d.virtual);
|
||||
this.entitiesService.setEntities(entities);
|
||||
|
||||
this._calculateData();
|
||||
this._calculateData(templateStats);
|
||||
}
|
||||
|
||||
private _calculateData(): void {
|
||||
private _calculateData(templateStats: DossierTemplateStats): void {
|
||||
this.chartData = this.allEntities.map(dict => ({
|
||||
value: this.templateStats.dictionarySummary(dict.type).entriesCount ?? 0,
|
||||
value: templateStats.dictionarySummary(dict.type).entriesCount ?? 0,
|
||||
color: dict.hexColor,
|
||||
label: dict.label,
|
||||
key: dict.type,
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<section>
|
||||
<section *ngIf="dictionary$ | async as dictionary">
|
||||
<div class="page-header">
|
||||
<redaction-dossier-template-breadcrumbs></redaction-dossier-template-breadcrumbs>
|
||||
|
||||
<div class="actions">
|
||||
<iqser-circle-button
|
||||
(action)="openDeleteDictionaryDialog($event)"
|
||||
(action)="openDeleteDictionaryDialog(dictionary, $event)"
|
||||
*ngIf="currentUser.isAdmin"
|
||||
[tooltip]="'dictionary-overview.action.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
@ -13,7 +13,7 @@
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="openEditDictionaryDialog($event)"
|
||||
(action)="openEditDictionaryDialog(dictionary, $event)"
|
||||
*ngIf="currentUser.isAdmin"
|
||||
[tooltip]="'dictionary-overview.action.edit' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
@ -22,7 +22,7 @@
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="download()"
|
||||
(action)="download(dictionary)"
|
||||
[tooltip]="'dictionary-overview.action.download' | translate"
|
||||
icon="iqser:download"
|
||||
tooltipPosition="below"
|
||||
@ -55,11 +55,11 @@
|
||||
|
||||
<redaction-dictionary-manager
|
||||
#dictionaryManager
|
||||
(saveDictionary)="save()"
|
||||
(saveDictionary)="save(dictionary)"
|
||||
[canEdit]="currentUser.isAdmin"
|
||||
[isLeavingPage]="isLeavingPage"
|
||||
[filterByDossierTemplate]="true"
|
||||
[initialEntries]="initialEntries"
|
||||
[isLeavingPage]="isLeavingPage"
|
||||
></redaction-dictionary-manager>
|
||||
|
||||
<div *ngIf="!!dictionary" class="right-container">
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { saveAs } from 'file-saver';
|
||||
@ -8,22 +7,25 @@ import { DictionaryManagerComponent } from '@shared/components/dictionary-manage
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
import { CircleButtonTypes, LoadingService } from '@iqser/common-ui';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { Dictionary } from '@red/domain';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { firstValueFrom, Observable, of } from 'rxjs';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dictionary-overview-screen.component.html',
|
||||
styleUrls: ['./dictionary-overview-screen.component.scss'],
|
||||
})
|
||||
export class DictionaryOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
export class DictionaryOverviewScreenComponent {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
|
||||
initialEntries: string[] = [];
|
||||
dictionary: Dictionary;
|
||||
isLeavingPage = false;
|
||||
|
||||
readonly dictionary$: Observable<Dictionary>;
|
||||
|
||||
@ViewChild('dictionaryManager', { static: false })
|
||||
private readonly _dictionaryManager: DictionaryManagerComponent;
|
||||
@ViewChild('fileInput') private readonly _fileInput: ElementRef;
|
||||
@ -33,66 +35,51 @@ export class DictionaryOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
private readonly _userService: UserService,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
protected readonly _translateService: TranslateService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
) {}
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
this.dictionary$ = _route.paramMap.pipe(
|
||||
map(params => [params.get(DOSSIER_TEMPLATE_ID), params.get('dictionary')]),
|
||||
switchMap(([dossierTemplateId, dictionary]: [string, string]) =>
|
||||
dossierTemplateId ? this._dictionariesMapService.watch$(dossierTemplateId, dictionary) : of(undefined),
|
||||
),
|
||||
switchMap(dictionary => this._loadEntries(dictionary)),
|
||||
);
|
||||
}
|
||||
|
||||
get changed() {
|
||||
return this._dictionaryManager.editor.hasChanges;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._appStateService.activateDictionary(this._activatedRoute.snapshot.params.dictionary);
|
||||
this.dictionary = this._appStateService.activeDictionary;
|
||||
await this._loadEntries();
|
||||
}
|
||||
|
||||
openEditDictionaryDialog($event: any) {
|
||||
this._dialogService.openDialog(
|
||||
'addEditDictionary',
|
||||
$event,
|
||||
{
|
||||
dictionary: this.dictionary,
|
||||
dossierTemplateId: this.dictionary.dossierTemplateId,
|
||||
},
|
||||
async () => {
|
||||
this._loadingService.start();
|
||||
await this._appStateService.loadDictionaryData();
|
||||
this.dictionary = this._appStateService.activeDictionary;
|
||||
this._loadingService.stop();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
openDeleteDictionaryDialog($event?: MouseEvent) {
|
||||
$event?.stopPropagation();
|
||||
|
||||
this._dialogService.openDialog('confirm', $event, null, async () => {
|
||||
await firstValueFrom(this._dictionaryService.deleteDictionaries([this.dictionary.type], this.dictionary.dossierTemplateId));
|
||||
await this._appStateService.loadDictionaryData();
|
||||
await this._router.navigate([
|
||||
'/main',
|
||||
'admin',
|
||||
'dossier-templates',
|
||||
this._dossierTemplatesService.activeDossierTemplateId,
|
||||
'dictionaries',
|
||||
]);
|
||||
openEditDictionaryDialog(dictionary: Dictionary, $event: MouseEvent) {
|
||||
this._dialogService.openDialog('addEditDictionary', $event, {
|
||||
dictionary,
|
||||
dossierTemplateId: dictionary.dossierTemplateId,
|
||||
});
|
||||
}
|
||||
|
||||
download(): void {
|
||||
openDeleteDictionaryDialog(dictionary: Dictionary, $event?: MouseEvent) {
|
||||
$event?.stopPropagation();
|
||||
|
||||
this._dialogService.openDialog('confirm', $event, null, async () => {
|
||||
await firstValueFrom(this._dictionaryService.deleteDictionaries([dictionary.type], dictionary.dossierTemplateId));
|
||||
await this._router.navigate(['/main', 'admin', 'dossier-templates', dictionary.dossierTemplateId, 'dictionaries']);
|
||||
});
|
||||
}
|
||||
|
||||
download(dictionary: Dictionary): void {
|
||||
const content = this._dictionaryManager.editor.value;
|
||||
const blob = new Blob([content], {
|
||||
type: 'text/plain;charset=utf-8',
|
||||
});
|
||||
saveAs(blob, `${this.dictionary.label}.txt`);
|
||||
saveAs(blob, `${dictionary.label}.txt`);
|
||||
}
|
||||
|
||||
upload($event): void {
|
||||
const file = $event.target.files[0];
|
||||
const file: File = $event.target.files[0];
|
||||
const fileReader = new FileReader();
|
||||
|
||||
if (file) {
|
||||
@ -115,24 +102,18 @@ export class DictionaryOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
save() {
|
||||
async save(dictionary: Dictionary) {
|
||||
const entries = this._dictionaryManager.editor?.currentEntries;
|
||||
|
||||
this._loadingService.start();
|
||||
this._dictionaryService
|
||||
.saveEntries(entries, this.initialEntries, this.dictionary.dossierTemplateId, this.dictionary.type, null)
|
||||
.subscribe(
|
||||
async () => {
|
||||
await this._loadEntries();
|
||||
},
|
||||
() => {
|
||||
this._loadingService.stop();
|
||||
},
|
||||
try {
|
||||
await firstValueFrom(
|
||||
this._dictionaryService.saveEntries(entries, this.initialEntries, dictionary.dossierTemplateId, dictionary.type, null),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._appStateService.reset();
|
||||
await this._loadEntries(dictionary);
|
||||
} catch (e) {
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private _overwrite(fileContent: string): void {
|
||||
@ -148,24 +129,16 @@ export class DictionaryOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
this._dictionaryManager.editor.value = currentEntries.join('\n');
|
||||
}
|
||||
|
||||
private async _loadEntries() {
|
||||
private async _loadEntries(dictionary: Dictionary) {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._dictionaryService.getForType(this.dictionary.dossierTemplateId, this.dictionary.type))
|
||||
.then(
|
||||
data => {
|
||||
this._loadingService.stop();
|
||||
this.initialEntries = [...data.entries].sort((str1, str2) =>
|
||||
str1.localeCompare(str2, undefined, { sensitivity: 'accent' }),
|
||||
);
|
||||
},
|
||||
() => {
|
||||
this._loadingService.stop();
|
||||
this.initialEntries = [];
|
||||
},
|
||||
)
|
||||
.catch(() => {
|
||||
this._loadingService.stop();
|
||||
this.initialEntries = [];
|
||||
});
|
||||
try {
|
||||
const data = await firstValueFrom(this._dictionaryService.getForType(dictionary.dossierTemplateId, dictionary.type));
|
||||
this._loadingService.stop();
|
||||
this.initialEntries = [...data.entries].sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' }));
|
||||
} catch (e) {
|
||||
this._loadingService.stop();
|
||||
this.initialEntries = [];
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,9 +14,10 @@ import { dossierAttributeTypesTranslations } from '../../translations/dossier-at
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DossierAttributeConfig, IDossierAttributeConfig } from '@red/domain';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ReportTemplateService } from '../../../../services/report-template.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-attributes-listing-screen.component.html',
|
||||
@ -38,17 +39,19 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
||||
{ label: _('dossier-attributes-listing.table-col-names.placeholder'), width: '2fr' },
|
||||
{ label: _('dossier-attributes-listing.table-col-names.type'), sortByKey: 'type' },
|
||||
];
|
||||
readonly #dossierTemplateId: string;
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dossierAttributesService: DossierAttributesService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _reportTemplateService: ReportTemplateService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
super(_injector);
|
||||
this.#dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
@ -56,7 +59,7 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
||||
}
|
||||
|
||||
async openConfirmDeleteAttributeDialog($event: MouseEvent, dossierAttribute?: DossierAttributeConfig) {
|
||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
const dossierTemplateId = this.#dossierTemplateId;
|
||||
const resp = await firstValueFrom(
|
||||
this._reportTemplateService.getTemplatesByPlaceholder(dossierTemplateId, dossierAttribute.placeholder),
|
||||
);
|
||||
@ -64,13 +67,13 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
||||
this._dialogService.openDialog('deleteAttribute', $event, { attribute: dossierAttribute, templates: resp }, async () => {
|
||||
this._loadingService.start();
|
||||
const ids = dossierAttribute ? [dossierAttribute.id] : this.listingService.selected.map(item => item.id);
|
||||
await firstValueFrom(this._dossierAttributesService.delete(ids));
|
||||
await firstValueFrom(this._dossierAttributesService.delete(ids, dossierTemplateId));
|
||||
await this._loadData();
|
||||
});
|
||||
}
|
||||
|
||||
openAddEditAttributeDialog($event: MouseEvent, dossierAttribute?: IDossierAttributeConfig) {
|
||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
const dossierTemplateId = this.#dossierTemplateId;
|
||||
|
||||
this._dialogService.openDialog('addEditDossierAttribute', $event, { dossierAttribute, dossierTemplateId }, async () =>
|
||||
this._loadData(),
|
||||
@ -79,7 +82,7 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
||||
|
||||
private async _loadData() {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._dossierAttributesService.loadAll());
|
||||
await firstValueFrom(this._dossierAttributesService.loadAll(this.#dossierTemplateId));
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { AdminDialogService } from '../../../services/admin-dialog.service';
|
||||
import { DossierTemplate } from '@red/domain';
|
||||
@ -17,7 +16,6 @@ import { UserService } from '@services/user.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
@ -48,7 +46,6 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
readonly routerHistoryService: RouterHistoryService,
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
@ -68,13 +65,6 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
|
||||
}
|
||||
|
||||
private async _deleteTemplates(templateIds = this.listingService.selected.map(d => d.dossierTemplateId)) {
|
||||
await firstValueFrom(this._dossierTemplatesService.delete(templateIds)).catch(error => {
|
||||
if (error.status === HttpStatusCode.Conflict) {
|
||||
this._toaster.error(_('dossier-templates-listing.error.conflict'));
|
||||
} else {
|
||||
this._toaster.error(_('dossier-templates-listing.error.generic'));
|
||||
}
|
||||
});
|
||||
await this._appStateService.loadDictionaryData();
|
||||
await firstValueFrom(this._dossierTemplatesService.delete(templateIds));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, forwardRef, Injector, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||
import {
|
||||
CircleButtonTypes,
|
||||
@ -15,10 +14,12 @@ import { UserService } from '@services/user.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { FileAttributeConfig, IFileAttributeConfig, IFileAttributesConfig } from '@red/domain';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ReportTemplateService } from '../../../../services/report-template.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
templateUrl: './file-attributes-listing-screen.component.html',
|
||||
@ -51,19 +52,21 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
];
|
||||
private _existingConfiguration: IFileAttributesConfig;
|
||||
@ViewChild('fileInput') private _fileInput: ElementRef;
|
||||
readonly #dossierTemplateId: string;
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _fileAttributesService: FileAttributesService,
|
||||
private readonly _reportTemplateService: ReportTemplateService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
super(_injector);
|
||||
this.#dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
}
|
||||
|
||||
private get _numberOfDisplayedAttrs(): number {
|
||||
@ -81,7 +84,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
openAddEditAttributeDialog($event: MouseEvent, fileAttribute?: IFileAttributeConfig) {
|
||||
const data = {
|
||||
fileAttribute,
|
||||
dossierTemplateId: this._dossierTemplatesService.activeDossierTemplateId,
|
||||
dossierTemplateId: this.#dossierTemplateId,
|
||||
numberOfDisplayedAttrs: this._numberOfDisplayedAttrs,
|
||||
numberOfFilterableAttrs: this._numberOfFilterableAttrs,
|
||||
};
|
||||
@ -91,7 +94,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
}
|
||||
|
||||
async openConfirmDeleteAttributeDialog($event: MouseEvent, fileAttribute?: FileAttributeConfig) {
|
||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
const dossierTemplateId = this.#dossierTemplateId;
|
||||
const resp = await firstValueFrom(
|
||||
this._reportTemplateService.getTemplatesByPlaceholder(dossierTemplateId, fileAttribute.placeholder),
|
||||
);
|
||||
@ -108,7 +111,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
),
|
||||
);
|
||||
}
|
||||
await this._appStateService.refreshDossierTemplate(dossierTemplateId);
|
||||
await this._dossierTemplatesService.refreshDossierTemplate(dossierTemplateId);
|
||||
await this._loadData();
|
||||
});
|
||||
}
|
||||
@ -122,7 +125,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
null,
|
||||
{
|
||||
csv,
|
||||
dossierTemplateId: this._dossierTemplatesService.activeDossierTemplateId,
|
||||
dossierTemplateId: this.#dossierTemplateId,
|
||||
existingConfiguration: this._existingConfiguration,
|
||||
},
|
||||
async () => this._loadData(),
|
||||
@ -130,13 +133,14 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
}
|
||||
|
||||
openConfigurationsDialog($event: MouseEvent) {
|
||||
this._dialogService.openDialog('fileAttributesConfigurations', $event, this._existingConfiguration);
|
||||
this._dialogService.openDialog('fileAttributesConfigurations', $event, {
|
||||
config: this._existingConfiguration,
|
||||
dossierTemplateId: this.#dossierTemplateId,
|
||||
});
|
||||
}
|
||||
|
||||
private async _createNewFileAttributeAndRefreshView(newValue: IFileAttributeConfig): Promise<void> {
|
||||
await firstValueFrom(
|
||||
this._fileAttributesService.setFileAttributesConfig(newValue, this._dossierTemplatesService.activeDossierTemplateId),
|
||||
).catch(error => {
|
||||
await firstValueFrom(this._fileAttributesService.setFileAttributesConfig(newValue, this.#dossierTemplateId)).catch(error => {
|
||||
if (error.status === HttpStatusCode.Conflict) {
|
||||
this._toaster.error(_('file-attributes-listing.error.conflict'));
|
||||
} else {
|
||||
@ -144,7 +148,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
}
|
||||
this._loadingService.stop();
|
||||
});
|
||||
await this._appStateService.refreshDossierTemplate(this._dossierTemplatesService.activeDossierTemplateId);
|
||||
await this._dossierTemplatesService.refreshDossierTemplate(this.#dossierTemplateId);
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
@ -152,9 +156,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
this._loadingService.start();
|
||||
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this._fileAttributesService.getFileAttributesConfig(this._dossierTemplatesService.activeDossierTemplateId),
|
||||
);
|
||||
const response = await firstValueFrom(this._fileAttributesService.loadFileAttributesConfig(this.#dossierTemplateId));
|
||||
this._existingConfiguration = response;
|
||||
const fileAttributeConfig = response?.fileAttributeConfigs.map(item => new FileAttributeConfig(item)) || [];
|
||||
this.entitiesService.setEntities(fileAttributeConfig);
|
||||
|
||||
@ -6,6 +6,7 @@ import { DossierTemplate, DossierTemplateStats } from '@red/domain';
|
||||
import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service';
|
||||
import { AdminDialogService } from '../../../services/admin-dialog.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-template-info-screen.component.html',
|
||||
@ -23,12 +24,12 @@ export class DossierTemplateInfoScreenComponent {
|
||||
private readonly _route: ActivatedRoute,
|
||||
readonly permissionsService: PermissionsService,
|
||||
) {
|
||||
const dossierTemplateId = _route.snapshot.paramMap.get('dossierTemplateId');
|
||||
const dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
this.dossierTemplate$ = this._dossierTemplatesService.getEntityChanged$(dossierTemplateId);
|
||||
this.dossierTemplateStats$ = this._dossierTemplateStatsService.watch$(dossierTemplateId);
|
||||
}
|
||||
|
||||
openEditDossierTemplateDialog($event: MouseEvent, dossierTemplate: DossierTemplate) {
|
||||
this._dialogService.openDialog('addEditDossierTemplate', $event, dossierTemplate);
|
||||
this._dialogService.openDialog('addEditDossierTemplate', $event, dossierTemplate.id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<section class="dialog">
|
||||
<div
|
||||
[translateParams]="{
|
||||
type: justification ? 'edit' : 'create',
|
||||
name: justification?.name
|
||||
type: data.justification ? 'edit' : 'create',
|
||||
name: data.justification?.name
|
||||
}"
|
||||
[translate]="'add-edit-justification.title'"
|
||||
class="dialog-header heading-l"
|
||||
@ -47,9 +47,9 @@
|
||||
{{ 'add-edit-justification.actions.save' | translate }}
|
||||
</button>
|
||||
|
||||
<div class="all-caps-label cancel" translate="add-edit-justification.actions.cancel" (click)="close()"></div>
|
||||
<div (click)="close()" class="all-caps-label cancel" translate="add-edit-justification.actions.cancel"></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||
</section>
|
||||
|
||||
@ -14,7 +14,6 @@ import { firstValueFrom } from 'rxjs';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AddEditJustificationDialogComponent extends BaseDialogComponent {
|
||||
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _justificationService: JustificationsService,
|
||||
@ -22,7 +21,7 @@ export class AddEditJustificationDialogComponent extends BaseDialogComponent {
|
||||
private readonly _loadingService: LoadingService,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditJustificationDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public justification: Justification,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { justification?: Justification; dossierTemplateId: string },
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
|
||||
@ -31,10 +30,10 @@ export class AddEditJustificationDialogComponent extends BaseDialogComponent {
|
||||
}
|
||||
|
||||
async save() {
|
||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
const dossierTemplateId = this.data.dossierTemplateId;
|
||||
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._justificationService.createOrUpdate(this.form.getRawValue(), dossierTemplateId));
|
||||
await firstValueFrom(this._justificationService.createOrUpdate(this.form.getRawValue() as Justification, dossierTemplateId));
|
||||
await firstValueFrom(this._justificationService.loadAll(dossierTemplateId));
|
||||
this._loadingService.stop();
|
||||
this._dialogRef.close(true);
|
||||
@ -42,9 +41,9 @@ export class AddEditJustificationDialogComponent extends BaseDialogComponent {
|
||||
|
||||
private _getForm(): FormGroup {
|
||||
return this._formBuilder.group({
|
||||
name: [{ value: this.justification?.name, disabled: !!this.justification }, Validators.required],
|
||||
reason: [this.justification?.reason, Validators.required],
|
||||
description: [this.justification?.description, Validators.required],
|
||||
name: [{ value: this.data.justification?.name, disabled: !!this.data.justification }, Validators.required],
|
||||
reason: [this.data.justification?.reason, Validators.required],
|
||||
description: [this.data.justification?.description, Validators.required],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ export class JustificationsDialogService extends DialogService<DialogType> {
|
||||
super(_dialog);
|
||||
}
|
||||
|
||||
confirmDelete(justifications: Justification[]) {
|
||||
confirmDelete(justifications: Justification[], dossierTemplateId: string) {
|
||||
const data = new ConfirmationDialogInput({
|
||||
title: _('confirmation-dialog.delete-justification.title'),
|
||||
titleColor: TitleColors.WARN,
|
||||
@ -50,7 +50,6 @@ export class JustificationsDialogService extends DialogService<DialogType> {
|
||||
});
|
||||
this.openDialog('confirm', null, data, async () => {
|
||||
this._loadingService.start();
|
||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
const justificationIds = justifications.map(j => j.id);
|
||||
await firstValueFrom(this._justificationService.delete(justificationIds, dossierTemplateId));
|
||||
await firstValueFrom(this._justificationService.loadAll(dossierTemplateId));
|
||||
|
||||
@ -11,11 +11,12 @@ import {
|
||||
} from '@iqser/common-ui';
|
||||
import { Justification } from '@red/domain';
|
||||
import { JustificationsService } from '@services/entity-services/justifications.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { JustificationsDialogService } from '../justifications-dialog.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-justifications-screen',
|
||||
@ -38,30 +39,32 @@ export class JustificationsScreenComponent extends ListingComponent<Justificatio
|
||||
{ label: _('justifications-listing.table-col-names.reason') },
|
||||
{ label: _('justifications-listing.table-col-names.description'), width: '2fr' },
|
||||
];
|
||||
readonly #dossierTemplateId: string;
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _justificationService: JustificationsService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dialogService: JustificationsDialogService,
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
readonly userService: UserService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
super(_injector);
|
||||
this.#dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._justificationService.loadAll(this._dossierTemplatesService.activeDossierTemplateId));
|
||||
await firstValueFrom(this._justificationService.loadAll(this.#dossierTemplateId));
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
openAddJustificationDialog(): void {
|
||||
this._dialogService.openDialog('addEditJustification', null, null);
|
||||
this._dialogService.openDialog('addEditJustification', null, { justification: null, dossierTemplateId: this.#dossierTemplateId });
|
||||
}
|
||||
|
||||
openConfirmDeleteDialog() {
|
||||
this._dialogService.confirmDelete(this.listingService.selected);
|
||||
this._dialogService.confirmDelete(this.listingService.selected, this.#dossierTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import { CircleButtonTypes, ListingService, LoadingService } from '@iqser/common
|
||||
import { JustificationsDialogService } from '../justifications-dialog.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-table-item',
|
||||
@ -14,6 +16,7 @@ import { UserPreferenceService } from '@services/user-preference.service';
|
||||
export class TableItemComponent {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
@Input() justification: Justification;
|
||||
readonly #dossierTemplateId: string;
|
||||
|
||||
constructor(
|
||||
private readonly _dialogService: JustificationsDialogService,
|
||||
@ -21,13 +24,19 @@ export class TableItemComponent {
|
||||
private readonly _listingService: ListingService<Justification>,
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
readonly userService: UserService,
|
||||
) {}
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
this.#dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
}
|
||||
|
||||
openEditJustificationDialog() {
|
||||
this._dialogService.openDialog('addEditJustification', null, this.justification);
|
||||
this._dialogService.openDialog('addEditJustification', null, {
|
||||
justification: this.justification,
|
||||
dossierTemplateId: this.#dossierTemplateId,
|
||||
});
|
||||
}
|
||||
|
||||
openConfirmDeleteDialog() {
|
||||
this._dialogService.confirmDelete([this.justification]);
|
||||
this._dialogService.confirmDelete([this.justification], this.#dossierTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ import { AdminDialogService } from '../../../services/admin-dialog.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { ReportTemplateService } from '@services/report-template.service';
|
||||
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
interface Placeholder {
|
||||
placeholder: string;
|
||||
@ -32,6 +34,7 @@ const placeholderTypes: PlaceholderType[] = ['generalPlaceholders', 'fileAttribu
|
||||
export class ReportsScreenComponent implements OnInit {
|
||||
placeholders$ = new BehaviorSubject<Placeholder[]>([]);
|
||||
availableTemplates$ = new BehaviorSubject<IReportTemplate[]>([]);
|
||||
readonly #dossierTemplateId: string;
|
||||
|
||||
@ViewChild('fileInput') private _fileInput: ElementRef;
|
||||
|
||||
@ -41,8 +44,11 @@ export class ReportsScreenComponent implements OnInit {
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
readonly permissionsService: PermissionsService,
|
||||
) {}
|
||||
) {
|
||||
this.#dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._loadingService.start();
|
||||
@ -93,8 +99,6 @@ export class ReportsScreenComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
|
||||
if (this.availableTemplates$.value.some(template => template.fileName === file.name)) {
|
||||
const data = new ConfirmationDialogInput({
|
||||
title: _('confirmation-dialog.report-template-same-name.title'),
|
||||
@ -108,17 +112,17 @@ export class ReportsScreenComponent implements OnInit {
|
||||
|
||||
this._dialogService.openDialog('confirm', null, data, null, async result => {
|
||||
if (result) {
|
||||
await this._openConfirmationDialog(file, dossierTemplateId);
|
||||
await this._openConfirmationDialog(file);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await this._openConfirmationDialog(file, dossierTemplateId);
|
||||
await this._openConfirmationDialog(file);
|
||||
}
|
||||
|
||||
this._fileInput.nativeElement.value = null;
|
||||
}
|
||||
|
||||
private async _openConfirmationDialog(file: File, dossierTemplateId: string) {
|
||||
private async _openConfirmationDialog(file: File) {
|
||||
if (this._isExcelFile(file)) {
|
||||
const data = new ConfirmationDialogInput({
|
||||
title: _('confirmation-dialog.upload-report-template.title'),
|
||||
@ -132,12 +136,12 @@ export class ReportsScreenComponent implements OnInit {
|
||||
});
|
||||
this._dialogService.openDialog('confirm', null, data, null, async result => {
|
||||
if (result) {
|
||||
await firstValueFrom(this._reportTemplateService.uploadTemplateForm(dossierTemplateId, result > 1, file));
|
||||
await firstValueFrom(this._reportTemplateService.uploadTemplateForm(this.#dossierTemplateId, result > 1, file));
|
||||
await this._loadReportTemplates();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await firstValueFrom(this._reportTemplateService.uploadTemplateForm(dossierTemplateId, false, file));
|
||||
await firstValueFrom(this._reportTemplateService.uploadTemplateForm(this.#dossierTemplateId, false, file));
|
||||
await this._loadReportTemplates();
|
||||
}
|
||||
}
|
||||
@ -149,15 +153,13 @@ export class ReportsScreenComponent implements OnInit {
|
||||
|
||||
private async _loadReportTemplates() {
|
||||
this.availableTemplates$.next(
|
||||
await firstValueFrom(
|
||||
this._reportTemplateService.getAvailableReportTemplates(this._dossierTemplatesService.activeDossierTemplateId),
|
||||
),
|
||||
await firstValueFrom(this._reportTemplateService.getAvailableReportTemplates(this.#dossierTemplateId)),
|
||||
);
|
||||
}
|
||||
|
||||
private async _loadPlaceholders() {
|
||||
const placeholdersResponse: IPlaceholdersResponse = await firstValueFrom(
|
||||
this._reportTemplateService.getAvailablePlaceholders(this._dossierTemplatesService.activeDossierTemplateId),
|
||||
this._reportTemplateService.getAvailablePlaceholders(this.#dossierTemplateId),
|
||||
);
|
||||
this.placeholders$.next(
|
||||
placeholderTypes.flatMap(type =>
|
||||
|
||||
@ -4,9 +4,10 @@ import { Debounce, IconButtonTypes, LoadingService, Toaster } from '@iqser/commo
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { RulesService } from '../../../services/rules.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
import ICodeEditor = monaco.editor.ICodeEditor;
|
||||
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
|
||||
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
|
||||
@ -36,16 +37,19 @@ export class RulesScreenComponent implements OnInit {
|
||||
|
||||
private _codeEditor: ICodeEditor;
|
||||
private _decorations: string[] = [];
|
||||
readonly #dossierTemplateId: string;
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _rulesService: RulesService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _toaster: Toaster,
|
||||
protected readonly _translateService: TranslateService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
) {}
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
this.#dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
}
|
||||
|
||||
set isLeavingPage(isLeaving: boolean) {
|
||||
this.isLeaving = isLeaving;
|
||||
@ -95,7 +99,7 @@ export class RulesScreenComponent implements OnInit {
|
||||
await firstValueFrom(
|
||||
this._rulesService.uploadRules({
|
||||
rules: this._codeEditor.getModel().getValue(),
|
||||
dossierTemplateId: this._dossierTemplatesService.activeDossierTemplateId,
|
||||
dossierTemplateId: this.#dossierTemplateId,
|
||||
}),
|
||||
).then(
|
||||
async () => {
|
||||
@ -125,7 +129,7 @@ export class RulesScreenComponent implements OnInit {
|
||||
}
|
||||
|
||||
upload($event): void {
|
||||
const file = $event.target.files[0];
|
||||
const file: File = $event.target.files[0];
|
||||
const fileReader = new FileReader();
|
||||
|
||||
if (file) {
|
||||
@ -152,7 +156,7 @@ export class RulesScreenComponent implements OnInit {
|
||||
|
||||
private async _initialize() {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._rulesService.download(this._dossierTemplatesService.activeDossierTemplateId)).then(
|
||||
await firstValueFrom(this._rulesService.download(this.#dossierTemplateId)).then(
|
||||
rules => {
|
||||
this.currentLines = this.initialLines = rules.rules.split('\n');
|
||||
this.revert();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectorRef, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, ElementRef, Inject, ViewChild } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import WebViewer, { WebViewerInstance } from '@pdftron/webviewer';
|
||||
import { environment } from '@environments/environment';
|
||||
@ -11,7 +11,10 @@ import { stampPDFPage } from '@utils/page-stamper';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { WatermarkService } from '@shared/services/watermark.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { firstValueFrom, Observable, of, switchMap } from 'rxjs';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
export const DEFAULT_WATERMARK: IWatermark = {
|
||||
text: null,
|
||||
@ -27,9 +30,10 @@ export const DEFAULT_WATERMARK: IWatermark = {
|
||||
templateUrl: './watermark-screen.component.html',
|
||||
styleUrls: ['./watermark-screen.component.scss'],
|
||||
})
|
||||
export class WatermarkScreenComponent implements OnInit {
|
||||
export class WatermarkScreenComponent {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly form: FormGroup = this._getForm();
|
||||
readonly #dossierTemplateId: string;
|
||||
private _instance: WebViewerInstance;
|
||||
private _watermark: IWatermark = {};
|
||||
@ViewChild('viewer', { static: true })
|
||||
@ -45,8 +49,10 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
this._loadingService.start();
|
||||
this.#dossierTemplateId = this._route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
}
|
||||
|
||||
get changed(): boolean {
|
||||
@ -61,8 +67,8 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
return false;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._loadWatermark();
|
||||
async ngOnInit(): Promise<void> {
|
||||
await firstValueFrom(this._loadWatermark());
|
||||
}
|
||||
|
||||
@Debounce()
|
||||
@ -70,26 +76,27 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
await this._drawWatermark();
|
||||
}
|
||||
|
||||
save() {
|
||||
const watermark = {
|
||||
...this.form.getRawValue(),
|
||||
};
|
||||
|
||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
async save(): Promise<void> {
|
||||
const watermark: IWatermark = this.form.getRawValue();
|
||||
|
||||
const observable = watermark.text
|
||||
? this._watermarkService.saveWatermark(watermark, dossierTemplateId)
|
||||
: this._watermarkService.deleteWatermark(dossierTemplateId);
|
||||
? this._watermarkService.saveWatermark(watermark, this.#dossierTemplateId)
|
||||
: this._watermarkService.deleteWatermark(this.#dossierTemplateId);
|
||||
|
||||
firstValueFrom(observable).then(
|
||||
() => {
|
||||
this._loadWatermark();
|
||||
this._toaster.success(
|
||||
watermark.text ? _('watermark-screen.action.change-success') : _('watermark-screen.action.delete-success'),
|
||||
);
|
||||
},
|
||||
() => this._toaster.error(_('watermark-screen.action.error')),
|
||||
);
|
||||
try {
|
||||
await firstValueFrom(
|
||||
observable.pipe(
|
||||
switchMap(() => this._loadWatermark()),
|
||||
tap(() => {
|
||||
this._toaster.success(
|
||||
watermark.text ? _('watermark-screen.action.change-success') : _('watermark-screen.action.delete-success'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
this._toaster.error(_('watermark-screen.action.error'));
|
||||
}
|
||||
}
|
||||
|
||||
async revert() {
|
||||
@ -106,18 +113,14 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private _loadWatermark() {
|
||||
this._watermarkService.getWatermark(this._dossierTemplatesService.activeDossierTemplateId).subscribe(
|
||||
watermark => {
|
||||
private _loadWatermark(): Observable<IWatermark> {
|
||||
return this._watermarkService.getWatermark(this.#dossierTemplateId).pipe(
|
||||
catchError(() => of(DEFAULT_WATERMARK)),
|
||||
tap(watermark => {
|
||||
this._watermark = watermark;
|
||||
this.form.setValue({ ...this._watermark });
|
||||
this.form.setValue({ ...watermark });
|
||||
this._loadViewer();
|
||||
},
|
||||
() => {
|
||||
this._watermark = DEFAULT_WATERMARK;
|
||||
this.form.setValue({ ...this._watermark });
|
||||
this._loadViewer();
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -132,7 +135,7 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
isReadOnly: true,
|
||||
backendType: 'ems',
|
||||
},
|
||||
this._viewer.nativeElement,
|
||||
this._viewer.nativeElement as HTMLElement,
|
||||
).then(instance => {
|
||||
this._instance = instance;
|
||||
|
||||
@ -162,12 +165,12 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
const pdfNet = this._instance.Core.PDFNet;
|
||||
const document = await this._instance.Core.documentViewer.getDocument().getPDFDoc();
|
||||
|
||||
const text = this.form.get('text').value || '';
|
||||
const fontSize = this.form.get('fontSize').value;
|
||||
const fontType = this.form.get('fontType').value;
|
||||
const text: string = this.form.get('text').value || '';
|
||||
const fontSize: number = this.form.get('fontSize').value;
|
||||
const fontType: string = this.form.get('fontType').value;
|
||||
const orientation: WatermarkOrientation = this.form.get('orientation').value;
|
||||
const opacity = this.form.get('opacity').value;
|
||||
const color = this.form.get('hexColor').value;
|
||||
const opacity: number = this.form.get('opacity').value;
|
||||
const color: string = this.form.get('hexColor').value;
|
||||
|
||||
await stampPDFPage(document, pdfNet, text, fontSize, fontType, orientation, opacity, color, [1]);
|
||||
this._instance.Core.documentViewer.refreshAll();
|
||||
|
||||
@ -11,7 +11,6 @@ import { ConfirmDeleteUsersDialogComponent } from '../dialogs/confirm-delete-use
|
||||
import { FileAttributesCsvImportDialogComponent } from '../dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component';
|
||||
import { AddEditDossierAttributeDialogComponent } from '../dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component';
|
||||
import { ConfirmationDialogComponent, DialogConfig, DialogService, largeDialogConfig } from '@iqser/common-ui';
|
||||
import { AddEditJustificationDialogComponent } from '../screens/justifications/add-edit-justification-dialog/add-edit-justification-dialog.component';
|
||||
import { UploadDictionaryDialogComponent } from '../dialogs/upload-dictionary-dialog/upload-dictionary-dialog.component';
|
||||
import { FileAttributesConfigurationsDialogComponent } from '../dialogs/file-attributes-configurations-dialog/file-attributes-configurations-dialog.component';
|
||||
|
||||
@ -28,7 +27,6 @@ type DialogType =
|
||||
| 'smtpAuthConfig'
|
||||
| 'addEditDossierTemplate'
|
||||
| 'addEditDossierAttribute'
|
||||
| 'addEditJustification'
|
||||
| 'uploadDictionary';
|
||||
|
||||
@Injectable()
|
||||
@ -81,10 +79,6 @@ export class AdminDialogService extends DialogService<DialogType> {
|
||||
component: AddEditDossierAttributeDialogComponent,
|
||||
dialogConfig: { autoFocus: true },
|
||||
},
|
||||
addEditJustification: {
|
||||
component: AddEditJustificationDialogComponent,
|
||||
dialogConfig: { autoFocus: true },
|
||||
},
|
||||
uploadDictionary: {
|
||||
component: UploadDictionaryDialogComponent,
|
||||
},
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AdminDialogService } from '../../../services/admin-dialog.service';
|
||||
import { CircleButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DictionaryService } from '../../../../shared/services/dictionary.service';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-template-actions',
|
||||
@ -22,44 +21,30 @@ export class DossierTemplateActionsComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private readonly _router: Router,
|
||||
private readonly _route: ActivatedRoute,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
) {}
|
||||
|
||||
get dossierTemplate() {
|
||||
return this._dossierTemplatesService.find(this.dossierTemplateId);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.dossierTemplateId ??= this._dossierTemplatesService.activeDossierTemplateId;
|
||||
this.dossierTemplateId ??= this._route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
}
|
||||
|
||||
openEditDossierTemplateDialog($event: any) {
|
||||
this._dialogService.openDialog('addEditDossierTemplate', $event, this.dossierTemplate);
|
||||
openEditDossierTemplateDialog($event: MouseEvent) {
|
||||
this._dialogService.openDialog('addEditDossierTemplate', $event, this.dossierTemplateId);
|
||||
}
|
||||
|
||||
openDeleteDossierTemplateDialog($event?: MouseEvent) {
|
||||
this._dialogService.openDialog('confirm', $event, null, async () => {
|
||||
this._loadingService.start();
|
||||
|
||||
await firstValueFrom(this._dossierTemplatesService.delete([this.dossierTemplateId]))
|
||||
.then(async () => {
|
||||
await firstValueFrom(this._dossierTemplatesService.loadAll());
|
||||
await this._appStateService.loadDictionaryData();
|
||||
await this._router.navigate(['main', 'admin']);
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.status === HttpStatusCode.Conflict) {
|
||||
this._toaster.error(_('dossier-templates-listing.error.conflict'));
|
||||
} else {
|
||||
this._toaster.error(_('dossier-templates-listing.error.generic'));
|
||||
}
|
||||
this._loadingService.stop();
|
||||
});
|
||||
await firstValueFrom(this._dossierTemplatesService.delete([this.dossierTemplateId]));
|
||||
await this._router.navigate(['main', 'admin']);
|
||||
|
||||
this._loadingService.stop();
|
||||
});
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { ReportTemplateService } from '@services/report-template.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
templateUrl: './add-dossier-dialog.component.html',
|
||||
@ -129,7 +130,7 @@ export class AddDossierDialogComponent extends BaseDialogComponent {
|
||||
dossierName: this.form.get('dossierName').value,
|
||||
description: this.form.get('description').value,
|
||||
dueDate: this.hasDueDate ? this.form.get('dueDate').value : undefined,
|
||||
dossierTemplateId: this.form.get('dossierTemplateId').value,
|
||||
dossierTemplateId: this.form.get(DOSSIER_TEMPLATE_ID).value,
|
||||
downloadFileTypes: this.form.get('downloadFileTypes').value,
|
||||
reportTemplateIds: this.form.get('reportTemplateIds').value,
|
||||
watermarkEnabled: this.form.get('watermarkEnabled').value,
|
||||
|
||||
@ -34,7 +34,7 @@ export class DocumentInfoDialogComponent extends BaseDialogComponent implements
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
this.attributes = (
|
||||
await firstValueFrom(this._fileAttributesService.getFileAttributesConfig(this._dossier.dossierTemplateId))
|
||||
await firstValueFrom(this._fileAttributesService.loadFileAttributesConfig(this._dossier.dossierTemplateId))
|
||||
).fileAttributeConfigs.filter(attr => attr.editable);
|
||||
this.form = this._getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
|
||||
@ -14,6 +14,7 @@ import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-edit-dossier-general-info',
|
||||
@ -94,7 +95,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
watermarkEnabled: this.form.get('watermarkEnabled').value,
|
||||
watermarkPreviewEnabled: this.form.get('watermarkPreviewEnabled').value,
|
||||
dueDate: this.hasDueDate ? this.form.get('dueDate').value : undefined,
|
||||
dossierTemplateId: this.form.get('dossierTemplateId').value,
|
||||
dossierTemplateId: this.form.get(DOSSIER_TEMPLATE_ID).value,
|
||||
} as IDossierRequest;
|
||||
try {
|
||||
await firstValueFrom(this._dossiersService.createOrUpdate(dossier));
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
||||
import { ManualAnnotationService } from '../../services/manual-annotation.service';
|
||||
@ -33,7 +32,6 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
|
||||
private readonly _dossier: Dossier;
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _justificationsService: JustificationsService,
|
||||
private readonly _manualAnnotationService: ManualAnnotationService,
|
||||
@ -77,7 +75,7 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
|
||||
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
this.possibleDictionaries = await this._appStateService.getDictionariesOptions(
|
||||
this.possibleDictionaries = await this._dictionaryService.getDictionariesOptions(
|
||||
this._dossier.dossierTemplateId,
|
||||
this._dossier.dossierId,
|
||||
);
|
||||
|
||||
@ -5,6 +5,7 @@ import { FilePreviewGuard } from '@guards/file-preview.guard';
|
||||
import { DossierFilesGuard } from '@guards/dossier-files-guard';
|
||||
import { CompositeRouteGuard } from '@iqser/common-ui';
|
||||
import { BreadcrumbTypes } from '@red/domain';
|
||||
import { DOSSIER_ID, FILE_ID } from '@utils/constants';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -12,7 +13,7 @@ const routes: Routes = [
|
||||
component: SearchScreenComponent,
|
||||
},
|
||||
{
|
||||
path: ':dossierId',
|
||||
path: `:${DOSSIER_ID}`,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [DossierFilesGuard],
|
||||
@ -21,7 +22,7 @@ const routes: Routes = [
|
||||
loadChildren: () => import('./screens/dossier-overview/dossier-overview.module').then(m => m.DossierOverviewModule),
|
||||
},
|
||||
{
|
||||
path: ':dossierId/file/:fileId',
|
||||
path: `:${DOSSIER_ID}/file/:${FILE_ID}`,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [DossierFilesGuard, FilePreviewGuard],
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { TranslateChartService } from '@services/translate-chart.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
@ -14,6 +13,7 @@ import { DossierStatsService } from '@services/entity-services/dossier-stats.ser
|
||||
import { pluck, switchMap } from 'rxjs/operators';
|
||||
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { DOSSIER_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-details',
|
||||
@ -35,7 +35,6 @@ export class DossierDetailsComponent {
|
||||
readonly dossierStats$: Observable<DossierStats>;
|
||||
|
||||
constructor(
|
||||
readonly appStateService: AppStateService,
|
||||
readonly dossiersService: DossiersService,
|
||||
readonly translateChartService: TranslateChartService,
|
||||
readonly filterService: FilterService,
|
||||
@ -47,7 +46,7 @@ export class DossierDetailsComponent {
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
activatedRoute: ActivatedRoute,
|
||||
) {
|
||||
this.dossierId = activatedRoute.snapshot.paramMap.get('dossierId');
|
||||
this.dossierId = activatedRoute.snapshot.paramMap.get(DOSSIER_ID);
|
||||
this.dossier$ = this.dossiersService.getEntityChanged$(this.dossierId).pipe(shareLast());
|
||||
this.dossierStats$ = this.dossier$.pipe(
|
||||
pluck('dossierId'),
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { File } from '@red/domain';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { UserService } from '../../../../../../../services/user.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-file-workload',
|
||||
@ -14,8 +14,8 @@ export class FileWorkloadComponent {
|
||||
@Input() file: File;
|
||||
|
||||
constructor(
|
||||
public readonly userService: UserService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
readonly userService: UserService,
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
) {}
|
||||
|
||||
@ -44,6 +44,6 @@ export class FileWorkloadComponent {
|
||||
}
|
||||
|
||||
private _getDictionaryColor(type: string) {
|
||||
return this._appStateService.getDictionaryColor(type, this._dossiersService.find(this.file.dossierId).dossierTemplateId);
|
||||
return this._dictionariesMapService.getDictionaryColor(type, this._dossiersService.find(this.file.dossierId).dossierTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ import { LongPressEvent } from '@shared/directives/long-press.directive';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { DOSSIER_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-overview-screen.component.html',
|
||||
@ -93,7 +94,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
readonly activatedRoute: ActivatedRoute,
|
||||
) {
|
||||
super(_injector);
|
||||
this.dossierId = activatedRoute.snapshot.paramMap.get('dossierId');
|
||||
this.dossierId = activatedRoute.snapshot.paramMap.get(DOSSIER_ID);
|
||||
this.dossier$ = this._dossiersService
|
||||
.getEntityChanged$(this.dossierId)
|
||||
.pipe(tap(dossier => (this.dossierTemplateId = dossier.dossierTemplateId)));
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { Dossier, DossierStats } from '@red/domain';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-workload-column',
|
||||
@ -12,17 +12,17 @@ export class DossierWorkloadColumnComponent {
|
||||
@Input() dossier: Dossier;
|
||||
@Input() dossierStats: DossierStats;
|
||||
|
||||
constructor(private readonly _appStateService: AppStateService) {}
|
||||
constructor(private readonly _dictionariesMapService: DictionariesMapService) {}
|
||||
|
||||
get suggestionColor() {
|
||||
return this._appStateService.getDictionaryColor('suggestion', this.dossier.dossierTemplateId);
|
||||
return this._dictionariesMapService.getDictionaryColor('suggestion', this.dossier.dossierTemplateId);
|
||||
}
|
||||
|
||||
get hintColor() {
|
||||
return this._appStateService.getDictionaryColor('hint', this.dossier.dossierTemplateId);
|
||||
return this._dictionariesMapService.getDictionaryColor('hint', this.dossier.dossierTemplateId);
|
||||
}
|
||||
|
||||
get redactionColor() {
|
||||
return this._appStateService.getDictionaryColor('redaction', this.dossier.dossierTemplateId);
|
||||
return this._dictionariesMapService.getDictionaryColor('redaction', this.dossier.dossierTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-type-annotation-icon',
|
||||
@ -15,7 +15,7 @@ export class TypeAnnotationIconComponent implements OnChanges {
|
||||
color: string;
|
||||
type: 'square' | 'rhombus' | 'circle' | 'hexagon';
|
||||
|
||||
constructor(private _appStateService: AppStateService, readonly screenStateService: FilePreviewStateService) {}
|
||||
constructor(private _dictionariesMapService: DictionariesMapService, readonly screenStateService: FilePreviewStateService) {}
|
||||
|
||||
private get _dossierTemplateId(): string {
|
||||
return this.screenStateService.dossierTemplateId;
|
||||
@ -29,9 +29,9 @@ export class TypeAnnotationIconComponent implements OnChanges {
|
||||
const { isSuggestion, isRecommendation, isSkipped, isDeclinedSuggestion, isHint, isIgnoredHint } = this.annotation;
|
||||
|
||||
if (this.annotation.isSuperTypeBasedColor) {
|
||||
this.color = this._appStateService.getDictionaryColor(this.annotation.superType, this._dossierTemplateId);
|
||||
this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.superType, this._dossierTemplateId);
|
||||
} else {
|
||||
this.color = this._appStateService.getDictionaryColor(this.annotation.type, this._dossierTemplateId);
|
||||
this.color = this._dictionariesMapService.getDictionaryColor(this.annotation.type, this._dossierTemplateId);
|
||||
}
|
||||
|
||||
this.type =
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { Dictionary, Dossier } from '@red/domain';
|
||||
@ -31,7 +30,6 @@ export class AcceptRecommendationDialogComponent extends BaseDialogComponent imp
|
||||
private readonly _dossier: Dossier;
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _manualAnnotationService: ManualAnnotationService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
@ -58,7 +56,7 @@ export class AcceptRecommendationDialogComponent extends BaseDialogComponent imp
|
||||
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
this.possibleDictionaries = await this._appStateService.getDictionariesOptions(
|
||||
this.possibleDictionaries = await this._dictionaryService.getDictionariesOptions(
|
||||
this._dossier.dossierTemplateId,
|
||||
this._dossier.dossierId,
|
||||
);
|
||||
|
||||
@ -142,8 +142,8 @@ export class AnnotationActionsService {
|
||||
annotation,
|
||||
dossierId,
|
||||
fileId,
|
||||
removeFromDictionary,
|
||||
result.comment,
|
||||
removeFromDictionary,
|
||||
),
|
||||
annotation,
|
||||
annotationsChanged,
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Core, WebViewerInstance } from '@pdftron/webviewer';
|
||||
import { hexToRgb } from '@utils/functions';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
@ -11,12 +10,13 @@ import { environment } from '@environments/environment';
|
||||
import { IRectangle, ISectionGrid, ISectionRectangle } from '@red/domain';
|
||||
import { SkippedService } from './skipped.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
|
||||
@Injectable()
|
||||
export class AnnotationDrawService {
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _redactionLogService: RedactionLogService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
@ -50,13 +50,13 @@ export class AnnotationDrawService {
|
||||
case 'hint':
|
||||
case 'redaction':
|
||||
case 'recommendation':
|
||||
color = this._appStateService.getDictionaryColor(dictionary, dossierTemplateId);
|
||||
color = this._dictionariesMapService.getDictionaryColor(dictionary, dossierTemplateId);
|
||||
break;
|
||||
case 'skipped':
|
||||
color = this._appStateService.getDictionaryColor(superType, dossierTemplateId);
|
||||
color = this._dictionariesMapService.getDictionaryColor(superType, dossierTemplateId);
|
||||
break;
|
||||
default:
|
||||
color = this._appStateService.getDictionaryColor(superType, dossierTemplateId);
|
||||
color = this._dictionariesMapService.getDictionaryColor(superType, dossierTemplateId);
|
||||
break;
|
||||
}
|
||||
return color;
|
||||
|
||||
@ -2,13 +2,14 @@ import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, firstValueFrom, Observable, pairwise, switchMap } from 'rxjs';
|
||||
import { FileDataModel } from '@models/file/file-data.model';
|
||||
import { Dossier, File } from '@red/domain';
|
||||
import { DossiersService } from '../../../../../services/entity-services/dossiers.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { FilesMapService } from '../../../../../services/entity-services/files-map.service';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { PermissionsService } from '../../../../../services/permissions.service';
|
||||
import { boolFactory, shareLast } from '@iqser/common-ui';
|
||||
import { filter, startWith } from 'rxjs/operators';
|
||||
import { FileManagementService } from '../../../../../services/entity-services/file-management.service';
|
||||
import { FileManagementService } from '@services/entity-services/file-management.service';
|
||||
import { DOSSIER_ID, FILE_ID } from '@utils/constants';
|
||||
|
||||
@Injectable()
|
||||
export class FilePreviewStateService {
|
||||
@ -32,8 +33,8 @@ export class FilePreviewStateService {
|
||||
activatedRoute: ActivatedRoute,
|
||||
private readonly _fileManagementService: FileManagementService,
|
||||
) {
|
||||
this.fileId = activatedRoute.snapshot.paramMap.get('fileId');
|
||||
this.dossierId = activatedRoute.snapshot.paramMap.get('dossierId');
|
||||
this.fileId = activatedRoute.snapshot.paramMap.get(FILE_ID);
|
||||
this.dossierId = activatedRoute.snapshot.paramMap.get(DOSSIER_ID);
|
||||
this.dossierTemplateId = dossiersService.find(this.dossierId).dossierTemplateId;
|
||||
|
||||
this.dossier$ = dossiersService.getEntityChanged$(this.dossierId);
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import {
|
||||
Dossier,
|
||||
IAddRedactionRequest,
|
||||
@ -20,6 +19,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
|
||||
@Injectable()
|
||||
export class ManualAnnotationService extends GenericService<IManualAddResponse> {
|
||||
@ -28,7 +28,7 @@ export class ManualAnnotationService extends GenericService<IManualAddResponse>
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
@ -74,8 +74,10 @@ export class ManualAnnotationService extends GenericService<IManualAddResponse>
|
||||
this._toaster.error(this._getMessage(mode, modifyDictionary, true, isConflict), {
|
||||
error,
|
||||
params: {
|
||||
dictionaryName: this._appStateService.getDictionary(body.type, this._dossier(dossierId).dossierTemplateId)
|
||||
.label,
|
||||
dictionaryName: this._dictionariesMapService.getDictionary(
|
||||
body.type as string,
|
||||
this._dossier(dossierId).dossierTemplateId,
|
||||
).label,
|
||||
content: body.value,
|
||||
},
|
||||
positionClass: 'toast-file-preview',
|
||||
@ -199,8 +201,8 @@ export class ManualAnnotationService extends GenericService<IManualAddResponse>
|
||||
annotationWrapper: AnnotationWrapper,
|
||||
dossierId: string,
|
||||
fileId: string,
|
||||
removeFromDictionary: boolean = false,
|
||||
comment: string,
|
||||
removeFromDictionary: boolean = false,
|
||||
) {
|
||||
let mode: AnnotationActionMode,
|
||||
body: any,
|
||||
|
||||
@ -3,14 +3,14 @@ import { forkJoin, Observable, of } from 'rxjs';
|
||||
import { catchError, map, tap } from 'rxjs/operators';
|
||||
import { FileDataModel } from '@models/file/file-data.model';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { File, IRedactionLog, IViewedPage } from '@red/domain';
|
||||
import { Dictionary, File, IRedactionLog, IViewedPage } from '@red/domain';
|
||||
import { RedactionLogService } from './redaction-log.service';
|
||||
import { ViewedPagesService } from '@services/entity-services/viewed-pages.service';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { FilePreviewStateService } from '../screens/file-preview-screen/services/file-preview-state.service';
|
||||
import { Toaster } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
|
||||
@Injectable()
|
||||
export class PdfViewerDataService {
|
||||
@ -18,10 +18,10 @@ export class PdfViewerDataService {
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _redactionLogService: RedactionLogService,
|
||||
private readonly _viewedPagesService: ViewedPagesService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _stateService: FilePreviewStateService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
) {}
|
||||
|
||||
loadRedactionLogFor(dossierId: string, fileId: string) {
|
||||
@ -37,12 +37,8 @@ export class PdfViewerDataService {
|
||||
|
||||
return forkJoin([redactionLog$, viewedPages$]).pipe(
|
||||
map((data: [redactionLog: IRedactionLog, viewedPages: IViewedPage[]]) => {
|
||||
const fileDataModel = new FileDataModel(
|
||||
newFile,
|
||||
...data,
|
||||
this._appStateService.dictionaryData[this._stateService.dossierTemplateId],
|
||||
this._userPreferenceService.areDevFeaturesEnabled,
|
||||
);
|
||||
const dictionaries: Dictionary[] = this._dictionariesMapService.get(this._stateService.dossierTemplateId);
|
||||
const fileDataModel = new FileDataModel(newFile, ...data, dictionaries, this._userPreferenceService.areDevFeaturesEnabled);
|
||||
if (fileDataModel.missingTypes.size > 0) {
|
||||
this._toaster.error(_('error.missing-types'), {
|
||||
disableTimeOut: true,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dictionary-annotation-icon',
|
||||
@ -14,12 +14,12 @@ export class DictionaryAnnotationIconComponent implements OnChanges {
|
||||
label: string;
|
||||
type: 'square' | 'circle';
|
||||
|
||||
constructor(private readonly _appStateService: AppStateService) {}
|
||||
constructor(private readonly _dictionariesMapService: DictionariesMapService) {}
|
||||
|
||||
ngOnChanges(): void {
|
||||
if (this.dictionaryKey) {
|
||||
const dictionary = this._appStateService.getDictionary(this.dictionaryKey, this.dossierTemplateId);
|
||||
this.color = this._appStateService.getDictionaryColor(this.dictionaryKey, this.dossierTemplateId);
|
||||
const dictionary = this._dictionariesMapService.getDictionary(this.dictionaryKey, this.dossierTemplateId);
|
||||
this.color = this._dictionariesMapService.getDictionaryColor(this.dictionaryKey, this.dossierTemplateId);
|
||||
this.type = dictionary.hint ? 'circle' : 'square';
|
||||
this.label = this.dictionaryKey[0].toUpperCase();
|
||||
}
|
||||
|
||||
@ -7,8 +7,8 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { EditorComponent } from '@shared/components/editor/editor.component';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
|
||||
import FindMatch = monaco.editor.FindMatch;
|
||||
|
||||
@ -46,8 +46,8 @@ export class DictionaryManagerComponent implements OnChanges {
|
||||
|
||||
constructor(
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
readonly dossiersService: DossiersService,
|
||||
readonly appStateService: AppStateService,
|
||||
readonly dossierTemplatesService: DossierTemplatesService,
|
||||
) {}
|
||||
|
||||
@ -101,9 +101,8 @@ export class DictionaryManagerComponent implements OnChanges {
|
||||
this.diffEditorText = '';
|
||||
return;
|
||||
}
|
||||
const entries =
|
||||
this._dictionary.entries ??
|
||||
this.appStateService.dictionaryData[this._dictionary.dossierTemplateId][this._dictionary.type].entries;
|
||||
const entries: List =
|
||||
this._dictionary.entries ?? this._dictionariesMapService.get(this._dictionary.dossierTemplateId, this._dictionary.type).entries;
|
||||
|
||||
if (entries.length) {
|
||||
this.diffEditorText = this._toString([...entries]);
|
||||
@ -125,11 +124,11 @@ export class DictionaryManagerComponent implements OnChanges {
|
||||
});
|
||||
}
|
||||
|
||||
get _dictionaries() {
|
||||
get _dictionaries(): Dictionary[] {
|
||||
if (!this._dossierTemplate || this._dossierTemplate.name === this.selectDossierTemplate.name) {
|
||||
return;
|
||||
}
|
||||
return Object.values(this.appStateService.dictionaryData[this.dossierTemplate?.dossierTemplateId]).filter(dict => !dict.virtual);
|
||||
return this._dictionariesMapService.get(this.dossierTemplate?.dossierTemplateId).filter(dict => !dict.virtual);
|
||||
}
|
||||
|
||||
get dossierTemplateIsNotSelected() {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { INestedFilter, Required } from '@iqser/common-ui';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-type-filter',
|
||||
@ -23,13 +23,13 @@ export class TypeFilterComponent implements OnChanges {
|
||||
];
|
||||
private _needsAnalysisKeys = ['add-dictionary', 'remove-only-here', 'change-legal-basis', 'analysis'];
|
||||
|
||||
constructor(private readonly _appStateService: AppStateService) {}
|
||||
constructor(private readonly _dictionariesMapService: DictionariesMapService) {}
|
||||
|
||||
isSuggestion = (key: string) => this._suggestionsKeys.includes(key);
|
||||
|
||||
needsAnalysis = (key: string) => this._needsAnalysisKeys.includes(key);
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.dictionaryColor = this._appStateService.getDictionaryColor(this.filter.id, this.dossierTemplateId);
|
||||
this.dictionaryColor = this._dictionariesMapService.getDictionaryColor(this.filter.id, this.dossierTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { Dossier, DossierAttributeConfig, DossierAttributeWithValue, IDossierAttribute, IDossierAttributeConfig } from '@red/domain';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
|
||||
@ -9,7 +8,7 @@ import { map, tap } from 'rxjs/operators';
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DossierAttributesService extends EntitiesService<DossierAttributeConfig, IDossierAttributeConfig> {
|
||||
constructor(private readonly _dossierTemplatesService: DossierTemplatesService, protected readonly _injector: Injector) {
|
||||
constructor(protected readonly _injector: Injector) {
|
||||
super(_injector, DossierAttributeConfig, 'dossier-attributes');
|
||||
}
|
||||
|
||||
@ -32,12 +31,12 @@ export class DossierAttributesService extends EntitiesService<DossierAttributeCo
|
||||
}
|
||||
|
||||
@Validate()
|
||||
delete(@RequiredParam() ids: List, dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId): Observable<unknown> {
|
||||
delete(@RequiredParam() 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);
|
||||
}
|
||||
|
||||
loadAll(dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId): Observable<DossierAttributeConfig[]> {
|
||||
loadAll(dossierTemplateId: string): Observable<DossierAttributeConfig[]> {
|
||||
return this.getConfig(dossierTemplateId).pipe(
|
||||
map(entities => entities ?? []),
|
||||
mapEach(entity => new DossierAttributeConfig(entity)),
|
||||
@ -48,7 +47,7 @@ export class DossierAttributesService extends EntitiesService<DossierAttributeCo
|
||||
@Validate()
|
||||
createOrUpdate(
|
||||
@RequiredParam() attributeConfig: IDossierAttributeConfig,
|
||||
dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId,
|
||||
dossierTemplateId: string,
|
||||
): Observable<IDossierAttributeConfig> {
|
||||
return this._post(attributeConfig, `${this._defaultModelPath}/config/${dossierTemplateId}`);
|
||||
}
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { firstValueFrom, forkJoin, Observable, of, throwError } from 'rxjs';
|
||||
import { EntitiesService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { Dictionary, IColors, IDictionary, IUpdateDictionary } from '@red/domain';
|
||||
import { mapTo, switchMap, tap } from 'rxjs/operators';
|
||||
import { catchError, map, mapTo, switchMap, tap } from 'rxjs/operators';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { FALLBACK_COLOR } from '@utils/constants';
|
||||
import { hexToRgb } from '@utils/functions';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||
|
||||
const MIN_WORD_LENGTH = 2;
|
||||
|
||||
@ -15,7 +20,9 @@ export class DictionaryService extends EntitiesService<Dictionary, IDictionary>
|
||||
constructor(
|
||||
private readonly _toaster: Toaster,
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _fileAttributesService: FileAttributesService,
|
||||
private readonly _dossierTemplateStatsService: DossierTemplateStatsService,
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
) {
|
||||
super(_injector, Dictionary, 'dictionary');
|
||||
}
|
||||
@ -37,7 +44,12 @@ export class DictionaryService extends EntitiesService<Dictionary, IDictionary>
|
||||
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
||||
const url = `${this._defaultModelPath}/type/${dossierTemplateId}/delete`;
|
||||
return this._post<unknown>(body, url, queryParams).pipe(
|
||||
switchMap(dictionaries => this._dossierTemplateStatsService.getFor([dossierTemplateId]).pipe(mapTo(dictionaries))),
|
||||
switchMap(dictionaries =>
|
||||
forkJoin([
|
||||
this._dossierTemplateStatsService.getFor([dossierTemplateId]),
|
||||
this.loadDictionaryDataForDossierTemplate(dossierTemplateId),
|
||||
]).pipe(mapTo(dictionaries)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -55,7 +67,22 @@ export class DictionaryService extends EntitiesService<Dictionary, IDictionary>
|
||||
*/
|
||||
@Validate()
|
||||
getColors(@RequiredParam() dossierTemplateId: string) {
|
||||
return this._getOne<IColors>([dossierTemplateId], 'color');
|
||||
return this._getOne<IColors>([dossierTemplateId], 'color').pipe(
|
||||
catchError(() =>
|
||||
of({
|
||||
analysisColor: FALLBACK_COLOR,
|
||||
dictionaryRequestColor: FALLBACK_COLOR,
|
||||
defaultColor: FALLBACK_COLOR,
|
||||
manualRedactionColor: FALLBACK_COLOR,
|
||||
notRedacted: FALLBACK_COLOR,
|
||||
requestAdd: FALLBACK_COLOR,
|
||||
previewColor: FALLBACK_COLOR,
|
||||
requestRemove: FALLBACK_COLOR,
|
||||
updatedColor: FALLBACK_COLOR,
|
||||
dossierTemplateId: dossierTemplateId,
|
||||
} as IColors),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,11 +94,17 @@ export class DictionaryService extends EntitiesService<Dictionary, IDictionary>
|
||||
@RequiredParam() dossierTemplateId: string,
|
||||
@RequiredParam() type: string,
|
||||
dossierId?: string,
|
||||
) {
|
||||
): Observable<Dictionary> {
|
||||
const url = `${this._defaultModelPath}/type/${type}/${dossierTemplateId}`;
|
||||
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
||||
return this._post(body, url, queryParams).pipe(
|
||||
switchMap(dictionary => this._dossierTemplateStatsService.getFor([dossierTemplateId]).pipe(mapTo(dictionary))),
|
||||
catchError((error: HttpErrorResponse) => this.#addUpdateDictionaryErrorToast(error)),
|
||||
switchMap(dictionary =>
|
||||
forkJoin([
|
||||
this._dossierTemplateStatsService.getFor([dossierTemplateId]),
|
||||
this.loadDictionaryDataForDossierTemplate(dossierTemplateId),
|
||||
]).pipe(map(() => this._dictionariesMapService.get(dictionary.dossierTemplateId, dictionary.type))),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -79,18 +112,27 @@ export class DictionaryService extends EntitiesService<Dictionary, IDictionary>
|
||||
* Set system colors for redaction
|
||||
*/
|
||||
@Validate()
|
||||
setColors(@RequiredParam() body: IColors, @RequiredParam() dossierTemplateId: string) {
|
||||
return this._post(body, `color/${dossierTemplateId}`);
|
||||
setColors(@RequiredParam() body: IColors, @RequiredParam() dossierTemplateId: string): Observable<Dictionary[]> {
|
||||
return this._post(body, `color/${dossierTemplateId}`).pipe(
|
||||
switchMap(() => this.loadDictionaryDataForDossierTemplate(dossierTemplateId)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates entry type with colors, hint and caseInsensitive
|
||||
*/
|
||||
@Validate()
|
||||
addDictionary(@RequiredParam() dictionary: IDictionary, dossierId?: string) {
|
||||
addDictionary(@RequiredParam() dictionary: IDictionary, dossierId?: string): Observable<Dictionary> {
|
||||
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined;
|
||||
return this._post(dictionary, `${this._defaultModelPath}/type`, queryParams).pipe(
|
||||
switchMap(() => this._dossierTemplateStatsService.getFor([dictionary.dossierTemplateId])),
|
||||
catchError((error: HttpErrorResponse) => this.#addUpdateDictionaryErrorToast(error)),
|
||||
switchMap(() =>
|
||||
forkJoin([
|
||||
this._dossierTemplateStatsService.getFor([dictionary.dossierTemplateId]),
|
||||
this.loadDictionaryDataForDossierTemplate(dictionary.dossierTemplateId),
|
||||
]),
|
||||
),
|
||||
map(() => this._dictionariesMapService.get(dictionary.dossierTemplateId, dictionary.type)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -136,6 +178,108 @@ export class DictionaryService extends EntitiesService<Dictionary, IDictionary>
|
||||
}
|
||||
}
|
||||
|
||||
async getDictionariesOptions(dossierTemplateId: string, dossierId: string): Promise<Dictionary[]> {
|
||||
const possibleDictionaries: Dictionary[] = [];
|
||||
|
||||
const dossierDictionary = await firstValueFrom(this.getForType(dossierTemplateId, 'dossier_redaction', dossierId));
|
||||
|
||||
for (const dictionary of this._dictionariesMapService.get(dossierTemplateId)) {
|
||||
if (!dictionary.virtual && dictionary.addToDictionaryAction) {
|
||||
possibleDictionaries.push(dictionary as Dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
if (dossierDictionary.addToDictionaryAction) {
|
||||
// TODO fix this in the backend
|
||||
possibleDictionaries.push(new Dictionary({ ...dossierDictionary, type: 'dossier_redaction' }));
|
||||
}
|
||||
|
||||
possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return possibleDictionaries;
|
||||
}
|
||||
|
||||
loadDictionaryData(dossierTemplatesIds: string[]): Observable<Dictionary[][]> {
|
||||
const observables: Observable<Dictionary[]>[] = [];
|
||||
for (const dossierTemplateId of dossierTemplatesIds) {
|
||||
observables.push(this.loadDictionaryDataForDossierTemplate(dossierTemplateId));
|
||||
}
|
||||
return forkJoin(observables);
|
||||
}
|
||||
|
||||
loadDictionaryDataForDossierTemplate(dossierTemplateId: string): Observable<Dictionary[]> {
|
||||
const types$: Observable<Dictionary[]> = this.getAllDictionaries(dossierTemplateId).pipe(
|
||||
map(typesResponse => typesResponse.types.map(type => new Dictionary(type))),
|
||||
);
|
||||
|
||||
const virtualTypes$: Observable<Dictionary[]> = this.getColors(dossierTemplateId).pipe(
|
||||
tap(colors => {
|
||||
for (const key of Object.keys(colors)) {
|
||||
const color = colors[key];
|
||||
try {
|
||||
const rgbValue = hexToRgb(color);
|
||||
if (!rgbValue) {
|
||||
colors[key] = FALLBACK_COLOR;
|
||||
}
|
||||
} catch (e) {
|
||||
colors[key] = FALLBACK_COLOR;
|
||||
}
|
||||
}
|
||||
}),
|
||||
map(colors => {
|
||||
const virtualTypes = [
|
||||
{
|
||||
hexColor: colors.manualRedactionColor || FALLBACK_COLOR,
|
||||
type: 'dossier_redaction',
|
||||
hint: false,
|
||||
recommendation: false,
|
||||
},
|
||||
{ hexColor: colors.notRedacted || FALLBACK_COLOR, type: 'declined-suggestion' },
|
||||
{ hexColor: colors.manualRedactionColor || FALLBACK_COLOR, type: 'manual' },
|
||||
{ hexColor: colors.ignoredHintColor || FALLBACK_COLOR, type: 'ignored-hint' },
|
||||
{ hexColor: colors.manualRedactionColor || FALLBACK_COLOR, type: 'manual-redaction' },
|
||||
// dictionary actions
|
||||
{ hexColor: '#c5d3eb', type: 'recommendation' },
|
||||
{ hexColor: colors.analysisColor || FALLBACK_COLOR, type: 'remove-only-here' },
|
||||
// generic suggestions
|
||||
{ hexColor: colors.requestAdd || FALLBACK_COLOR, type: 'suggestion' },
|
||||
{ hexColor: colors.requestAdd || FALLBACK_COLOR, type: 'suggestion-add' },
|
||||
// add suggestions
|
||||
{ hexColor: colors.requestAdd || FALLBACK_COLOR, type: 'suggestion-change-legal-basis' },
|
||||
{ hexColor: colors.requestAdd || FALLBACK_COLOR, type: 'suggestion-recategorize-image' },
|
||||
{ hexColor: colors.dictionaryRequestColor || FALLBACK_COLOR, type: 'suggestion-add-dictionary' },
|
||||
{ hexColor: colors.dictionaryRequestColor || FALLBACK_COLOR, type: 'suggestion-resize' },
|
||||
{ hexColor: colors.requestRemove || FALLBACK_COLOR, type: 'suggestion-remove' },
|
||||
{ hexColor: colors.dictionaryRequestColor || FALLBACK_COLOR, type: 'suggestion-remove-dictionary' },
|
||||
{ hexColor: colors.notRedacted || FALLBACK_COLOR, type: 'skipped' },
|
||||
{ hexColor: colors.defaultColor || FALLBACK_COLOR, type: 'default' },
|
||||
{ hexColor: colors.requestAdd || FALLBACK_COLOR, type: 'add' },
|
||||
{ hexColor: colors.analysisColor || FALLBACK_COLOR, type: 'analysis' },
|
||||
{ hexColor: '#fa98f7', type: 'hint', hint: true },
|
||||
{ hexColor: colors.manualRedactionColor || FALLBACK_COLOR, type: 'redaction' },
|
||||
{ hexColor: colors.updatedColor || FALLBACK_COLOR, type: 'updated' },
|
||||
];
|
||||
|
||||
return virtualTypes.map(config => new Dictionary(config, true));
|
||||
}),
|
||||
);
|
||||
|
||||
return forkJoin([types$, virtualTypes$])
|
||||
.pipe(map(([types, virtualTypes]: Dictionary[][]) => [...types, ...virtualTypes]))
|
||||
.pipe(tap(dictionaries => this._dictionariesMapService.set(dossierTemplateId, dictionaries)));
|
||||
}
|
||||
|
||||
#addUpdateDictionaryErrorToast(error: HttpErrorResponse): Observable<never> {
|
||||
if (error.status === HttpStatusCode.Conflict) {
|
||||
this._toaster.error(_('add-edit-dictionary.error.dictionary-already-exists'));
|
||||
} else if (error.status === HttpStatusCode.BadRequest) {
|
||||
this._toaster.error(_('add-edit-dictionary.error.invalid-color-or-rank'));
|
||||
} else {
|
||||
this._toaster.error(_('add-edit-dictionary.error.generic'));
|
||||
}
|
||||
return throwError(() => error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dictionary entries with entry type.
|
||||
*/
|
||||
|
||||
@ -7,6 +7,7 @@ import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BreadcrumbTypes } from '@red/domain';
|
||||
import { DOSSIER_ID, FILE_ID } from '@utils/constants';
|
||||
|
||||
export type RouterLinkActiveOptions = { exact: boolean } | IsActiveMatchOptions;
|
||||
|
||||
@ -82,7 +83,7 @@ export class BreadcrumbsService {
|
||||
}
|
||||
|
||||
private _addDossierBreadcrumb(route: ActivatedRouteSnapshot): void {
|
||||
const dossierId = route.paramMap.get('dossierId');
|
||||
const dossierId = route.paramMap.get(DOSSIER_ID);
|
||||
this._append({
|
||||
name$: this._dossiersService.getEntityChanged$(dossierId).pipe(pluck('dossierName')),
|
||||
routerLink: ['/main', 'dossiers', dossierId],
|
||||
@ -91,8 +92,8 @@ export class BreadcrumbsService {
|
||||
}
|
||||
|
||||
private _addFileBreadcrumb(route: ActivatedRouteSnapshot): void {
|
||||
const dossierId = route.paramMap.get('dossierId');
|
||||
const fileId = route.paramMap.get('fileId');
|
||||
const dossierId = route.paramMap.get(DOSSIER_ID);
|
||||
const fileId = route.paramMap.get(FILE_ID);
|
||||
this._append({
|
||||
name$: this._filesMapService.watch$(dossierId, fileId).pipe(pluck('filename')),
|
||||
routerLink: ['/main', 'dossiers', dossierId, 'file', fileId],
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Dictionary, IDictionary } from '@red/domain';
|
||||
import { EntitiesMapService } from '@iqser/common-ui';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DictionariesMapService extends EntitiesMapService<Dictionary, IDictionary> {
|
||||
constructor() {
|
||||
super(DOSSIER_TEMPLATE_ID);
|
||||
}
|
||||
|
||||
/** If the type is not found, it returns the 'default' type. */
|
||||
getDictionary(type: string, dossierTemplateId: string): Dictionary | undefined {
|
||||
return this.get(dossierTemplateId, type) || this.get(dossierTemplateId, 'default');
|
||||
}
|
||||
|
||||
getDictionaryColor(type: string, dossierTemplateId: string) {
|
||||
return !this.get(dossierTemplateId) ? '#cccccc' : this.getDictionary(type, dossierTemplateId).hexColor;
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,13 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { StatsService } from '@iqser/common-ui';
|
||||
import { DossierStats, IDossierStats } from '@red/domain';
|
||||
import { DOSSIER_ID } from '@utils/constants';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DossierStatsService extends StatsService<DossierStats, IDossierStats> {
|
||||
constructor(protected readonly _injector: Injector) {
|
||||
super(_injector, 'dossierId', DossierStats, 'dossier-stats');
|
||||
super(_injector, DOSSIER_ID, DossierStats, 'dossier-stats');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { StatsService } from '@iqser/common-ui';
|
||||
import { DossierTemplateStats, IDossierTemplateStats } from '@red/domain';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DossierTemplateStatsService extends StatsService<DossierTemplateStats, IDossierTemplateStats> {
|
||||
constructor(protected readonly _injector: Injector) {
|
||||
super(_injector, 'dossierTemplateId', DossierTemplateStats, 'dossier-template-stats');
|
||||
super(_injector, DOSSIER_TEMPLATE_ID, DossierTemplateStats, 'dossier-template-stats');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,79 +1,75 @@
|
||||
import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService, List, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { DossierTemplate, IDossierTemplate } from '@red/domain';
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { BehaviorSubject, forkJoin, Observable } from 'rxjs';
|
||||
import { forkJoin, Observable, throwError } from 'rxjs';
|
||||
import { FileAttributesService } from './file-attributes.service';
|
||||
import { ActivationEnd, Router } from '@angular/router';
|
||||
import { currentComponentRoute } from '@utils/functions';
|
||||
import { mapTo, switchMap, tap } from 'rxjs/operators';
|
||||
import { catchError, mapTo, switchMap, tap } from 'rxjs/operators';
|
||||
import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service';
|
||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
|
||||
const DOSSIER_TEMPLATE_CONFLICT_MSG = _('dossier-templates-listing.error.conflict');
|
||||
const GENERIC_MSG = _('dossier-templates-listing.error.generic');
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DossierTemplatesService extends EntitiesService<DossierTemplate, IDossierTemplate> {
|
||||
readonly activeDossierTemplate$: Observable<DossierTemplate | undefined>;
|
||||
private readonly _activeDossierTemplate$ = new BehaviorSubject<DossierTemplate | undefined>(undefined);
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _fileAttributesService: FileAttributesService,
|
||||
private readonly _router: Router,
|
||||
private readonly _dossierTemplateStatsService: DossierTemplateStatsService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
) {
|
||||
super(_injector, DossierTemplate, 'dossier-template');
|
||||
this.activeDossierTemplate$ = this._activeDossierTemplate$.asObservable();
|
||||
|
||||
_router.events.pipe(currentComponentRoute).subscribe((event: ActivationEnd) => {
|
||||
const dossierTemplateId = event.snapshot.paramMap.get('dossierTemplateId');
|
||||
const sameIdAsCurrentActive = dossierTemplateId === this._activeDossierTemplate$.getValue()?.dossierTemplateId;
|
||||
|
||||
if (sameIdAsCurrentActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dossierTemplateId === null || dossierTemplateId === undefined) {
|
||||
return this._activeDossierTemplate$.next(undefined);
|
||||
}
|
||||
|
||||
if (!this.has(dossierTemplateId)) {
|
||||
this._activeDossierTemplate$.next(undefined);
|
||||
return this._router.navigate(['/main/admin/dossier-templates']).then();
|
||||
}
|
||||
|
||||
this._activeDossierTemplate$.next(this.find(dossierTemplateId));
|
||||
});
|
||||
}
|
||||
|
||||
get activeDossierTemplate(): DossierTemplate | undefined {
|
||||
return this._activeDossierTemplate$.value;
|
||||
}
|
||||
|
||||
get activeDossierTemplateId(): string | undefined {
|
||||
return this._activeDossierTemplate$.value?.dossierTemplateId;
|
||||
}
|
||||
|
||||
loadAll(): Observable<DossierTemplate[]> {
|
||||
const getAttributes = (entities: DossierTemplate[]) => entities.map(e => this._fileAttributesService.getFileAttributesConfig(e.id));
|
||||
const getAttributes = (entities: DossierTemplate[]) =>
|
||||
entities.map(e => this._fileAttributesService.loadFileAttributesConfig(e.id));
|
||||
const dossierTemplateIds = (templates: DossierTemplate[]) => templates.map(d => d.id);
|
||||
|
||||
return this.getAll().pipe(
|
||||
mapEach(entity => new DossierTemplate(entity)),
|
||||
/* Load stats before updating entities */
|
||||
switchMap(templates =>
|
||||
forkJoin([this._dossierTemplateStatsService.getFor(dossierTemplateIds(templates)), ...getAttributes(templates)]).pipe(
|
||||
mapTo(templates),
|
||||
),
|
||||
forkJoin([
|
||||
this._dossierTemplateStatsService.getFor(dossierTemplateIds(templates)),
|
||||
...getAttributes(templates),
|
||||
this._dictionaryService.loadDictionaryData(dossierTemplateIds(templates)),
|
||||
]).pipe(mapTo(templates)),
|
||||
),
|
||||
tap(templates => this.setEntities(templates)),
|
||||
);
|
||||
}
|
||||
|
||||
delete(body: List): Observable<unknown> {
|
||||
return super._post(body, `${this._defaultModelPath}/delete`).pipe(switchMap(() => this.loadAll()));
|
||||
const showToast = (error: HttpErrorResponse) => {
|
||||
if (error.status === HttpStatusCode.Conflict) {
|
||||
this._toaster.error(DOSSIER_TEMPLATE_CONFLICT_MSG);
|
||||
} else {
|
||||
this._toaster.error(GENERIC_MSG);
|
||||
}
|
||||
return throwError(() => error);
|
||||
};
|
||||
|
||||
return super._post(body, `${this._defaultModelPath}/delete`).pipe(
|
||||
catchError(showToast),
|
||||
switchMap(() => this.loadAll()),
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
createOrUpdate(@RequiredParam() body: IDossierTemplate) {
|
||||
return this._post(body).pipe(switchMap(() => this.loadAll()));
|
||||
}
|
||||
|
||||
refreshDossierTemplate(dossierTemplateId: string): Observable<any> {
|
||||
return forkJoin([
|
||||
this._fileAttributesService.loadFileAttributesConfig(dossierTemplateId),
|
||||
this._dictionaryService.loadDictionaryDataForDossierTemplate(dossierTemplateId),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ interface ChangesDetails {
|
||||
}
|
||||
|
||||
const DOSSIER_EXISTS_MSG = _('add-dossier-dialog.errors.dossier-already-exists');
|
||||
const GENERIC_MGS = _('add-dossier-dialog.errors.generic');
|
||||
const GENERIC_MSG = _('add-dossier-dialog.errors.generic');
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@ -72,7 +72,7 @@ export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
||||
@Validate()
|
||||
createOrUpdate(@RequiredParam() dossier: IDossierRequest): Observable<Dossier | undefined> {
|
||||
const showToast = (error: HttpErrorResponse) => {
|
||||
this._toaster.error(error.status === HttpStatusCode.Conflict ? DOSSIER_EXISTS_MSG : GENERIC_MGS);
|
||||
this._toaster.error(error.status === HttpStatusCode.Conflict ? DOSSIER_EXISTS_MSG : GENERIC_MSG);
|
||||
return throwError(error);
|
||||
};
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ export class FileAttributesService extends EntitiesService<FileAttributeConfig,
|
||||
* Get the file attributes that can be used at importing csv.
|
||||
*/
|
||||
@Validate()
|
||||
getFileAttributesConfig(@RequiredParam() dossierTemplateId: string): Observable<IFileAttributesConfig> {
|
||||
loadFileAttributesConfig(@RequiredParam() 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))),
|
||||
|
||||
@ -1,82 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||
import { File, IFile } from '@red/domain';
|
||||
import { filter, startWith } from 'rxjs/operators';
|
||||
import { RequiredParam, shareLast, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesMapService } from '@iqser/common-ui';
|
||||
import { DOSSIER_ID } from '@utils/constants';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class FilesMapService {
|
||||
private readonly _entityChanged$ = new Subject<File>();
|
||||
private readonly _entityDeleted$ = new Subject<File>();
|
||||
private readonly _map = new Map<string, BehaviorSubject<File[]>>();
|
||||
|
||||
get$(dossierId: string) {
|
||||
if (!this._map.has(dossierId)) {
|
||||
this._map.set(dossierId, new BehaviorSubject<File[]>([]));
|
||||
}
|
||||
|
||||
return this._map.get(dossierId).asObservable();
|
||||
}
|
||||
|
||||
has(dossierId: string) {
|
||||
return this._map.has(dossierId);
|
||||
}
|
||||
|
||||
get(key: string): File[];
|
||||
get(key: string, id: string): File | undefined;
|
||||
get(key: string, id?: string): File | File[] | undefined {
|
||||
const value = this._map.get(key)?.value;
|
||||
if (!id) {
|
||||
return value ?? [];
|
||||
}
|
||||
return value?.find(item => item.id === id);
|
||||
}
|
||||
|
||||
set(key: string, entities: File[]): void {
|
||||
if (!this._map.has(key)) {
|
||||
this._map.set(key, new BehaviorSubject<File[]>(entities));
|
||||
return entities.forEach(entity => this._entityChanged$.next(entity));
|
||||
}
|
||||
|
||||
const changedEntities: File[] = [];
|
||||
const deletedEntities = this.get(key).filter(oldEntity => !entities.find(newEntity => newEntity.id === oldEntity.id));
|
||||
|
||||
// Keep old object references for unchanged entities
|
||||
const newEntities = entities.map(newEntity => {
|
||||
const oldEntity = this.get(key, newEntity.id);
|
||||
|
||||
if (newEntity.isEqual(oldEntity)) {
|
||||
return oldEntity;
|
||||
}
|
||||
|
||||
changedEntities.push(newEntity);
|
||||
return newEntity;
|
||||
});
|
||||
|
||||
this._map.get(key).next(newEntities);
|
||||
|
||||
// Emit observables only after entities have been updated
|
||||
for (const file of changedEntities) {
|
||||
this._entityChanged$.next(file);
|
||||
}
|
||||
|
||||
for (const file of deletedEntities) {
|
||||
this._entityDeleted$.next(file);
|
||||
}
|
||||
}
|
||||
|
||||
replace(entities: File[]) {
|
||||
const dossierId = entities[0].dossierId;
|
||||
const entityIds = entities.map(entity => entity.id);
|
||||
const existingFiles = this.get(dossierId).filter(file => entityIds.includes(file.fileId));
|
||||
entities = entities.filter(entity => {
|
||||
const existingFile = existingFiles.find(file => file.id === entity.id);
|
||||
return existingFile.lastUpdated !== entity.lastUpdated;
|
||||
});
|
||||
if (entities.length) {
|
||||
const all = this.get(dossierId).filter(file => !entities.map(entity => entity.id).includes(file.id));
|
||||
this.set(dossierId, [...all, ...entities]);
|
||||
}
|
||||
export class FilesMapService extends EntitiesMapService<File, IFile> {
|
||||
constructor() {
|
||||
super(DOSSIER_ID);
|
||||
}
|
||||
|
||||
replaceFiles(files: File[], property: keyof IFile, generateValue: Function) {
|
||||
@ -86,17 +16,4 @@ export class FilesMapService {
|
||||
);
|
||||
this.replace(newFiles);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
watch$(@RequiredParam() key: string, @RequiredParam() entityId: string): Observable<File> {
|
||||
return this._entityChanged$.pipe(
|
||||
filter(entity => entity.id === entityId),
|
||||
startWith(this.get(key, entityId)),
|
||||
shareLast(),
|
||||
);
|
||||
}
|
||||
|
||||
watchDeleted$(entityId: string): Observable<File> {
|
||||
return this._entityDeleted$.pipe(filter(entity => entity.id === entityId));
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import jwt_decode from 'jwt-decode';
|
||||
import { ICreateUserRequest, IMyProfileUpdateRequest, IProfileUpdateRequest, IResetPasswordRequest, IUser, User } from '@red/domain';
|
||||
import { wipeCaches } from '@redaction/red-cache';
|
||||
import { BASE_HREF } from '../tokens';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { BehaviorSubject, firstValueFrom, Observable } from 'rxjs';
|
||||
import { EntitiesService, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@ -36,6 +36,12 @@ export class UserService extends EntitiesService<User, IUser> {
|
||||
return this.all.filter(user => user.isUser || user.isManager);
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (this.currentUser.isUserAdmin || this.currentUser.isUser || this.currentUser.isAdmin) {
|
||||
await firstValueFrom(this.loadAll());
|
||||
}
|
||||
}
|
||||
|
||||
logout() {
|
||||
wipeCaches().then();
|
||||
this._keycloakService.logout(window.location.origin + this._baseHref).then();
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { AppStateService } from './app-state.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AppStateGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _router: Router,
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
if (this._userService.currentUser.isUserAdmin) {
|
||||
await firstValueFrom(this._userService.loadAll());
|
||||
}
|
||||
|
||||
if (this._userService.currentUser.isUser || this._userService.currentUser.isAdmin) {
|
||||
await firstValueFrom(this._userService.loadAll());
|
||||
await this._dossierTemplatesService.loadAllIfEmpty();
|
||||
await this._appStateService.loadDictionaryDataIfNecessary();
|
||||
}
|
||||
|
||||
const { dossierTemplateId, type } = route.params;
|
||||
|
||||
if (dossierTemplateId && !this._dossierTemplatesService.find(dossierTemplateId)) {
|
||||
await this._router.navigate(['main', 'admin', 'dossier-templates']);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type && !this._appStateService.dictionaryData[dossierTemplateId][type]) {
|
||||
await this._router.navigate(['main', 'admin', 'dossier-templates', dossierTemplateId]);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1,371 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Dictionary, DossierTemplate, IColors } from '@red/domain';
|
||||
import { Router } from '@angular/router';
|
||||
import { firstValueFrom, forkJoin, Observable, of } from 'rxjs';
|
||||
import { catchError, map, tap } from 'rxjs/operators';
|
||||
import { FALLBACK_COLOR, hexToRgb } from '@utils/functions';
|
||||
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
|
||||
export interface AppState {
|
||||
activeDictionaryType?: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AppStateService {
|
||||
private _appState: AppState = {};
|
||||
|
||||
constructor(
|
||||
private readonly _router: Router,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _fileAttributesService: FileAttributesService,
|
||||
) {}
|
||||
|
||||
private _dictionaryData?: { [key: string]: { [key: string]: Dictionary } };
|
||||
|
||||
get dictionaryData(): { [key: string]: { [key: string]: Dictionary } } | undefined {
|
||||
return this._dictionaryData;
|
||||
}
|
||||
|
||||
get dossierTemplates(): DossierTemplate[] {
|
||||
return this._dossierTemplatesService.all;
|
||||
}
|
||||
|
||||
get activeDictionaryType(): string | undefined {
|
||||
return this._appState.activeDictionaryType;
|
||||
}
|
||||
|
||||
get activeDictionary(): Dictionary | undefined {
|
||||
const activeDossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
||||
return activeDossierTemplateId && this.activeDictionaryType && this.dictionaryData && this.dictionaryData[activeDossierTemplateId]
|
||||
? this.dictionaryData[activeDossierTemplateId][this.activeDictionaryType]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
async getDictionariesOptions(dossierTemplateId: string, dossierId: string): Promise<Dictionary[]> {
|
||||
const possibleDictionaries: Dictionary[] = [];
|
||||
|
||||
const dossierDictionary = await firstValueFrom(
|
||||
this._dictionaryService.getForType(dossierTemplateId, 'dossier_redaction', dossierId),
|
||||
);
|
||||
|
||||
for (const key of Object.keys(this.dictionaryData[dossierTemplateId])) {
|
||||
const dictionaryData = this.getDictionary(key, dossierTemplateId);
|
||||
if (!dictionaryData.virtual && dictionaryData.addToDictionaryAction) {
|
||||
possibleDictionaries.push(dictionaryData);
|
||||
}
|
||||
}
|
||||
|
||||
if (dossierDictionary.addToDictionaryAction) {
|
||||
// TODO fix this in the backend
|
||||
possibleDictionaries.push(new Dictionary({ ...dossierDictionary, type: 'dossier_redaction' }));
|
||||
}
|
||||
|
||||
possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return possibleDictionaries;
|
||||
}
|
||||
|
||||
getDictionaryColor(type: string, dossierTemplateId: string) {
|
||||
return !this._dictionaryData
|
||||
? '#cccccc'
|
||||
: this._dictionaryData[dossierTemplateId][type]?.hexColor || this._dictionaryData[dossierTemplateId]['default'].hexColor;
|
||||
}
|
||||
|
||||
getDictionary(key: string, dossierTemplateId: string): Dictionary | undefined {
|
||||
if (!dossierTemplateId) {
|
||||
dossierTemplateId = this.dossierTemplates.length > 0 ? this.dossierTemplates[0].dossierTemplateId : undefined;
|
||||
}
|
||||
if (!dossierTemplateId || !this._dictionaryData) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const data = this._dictionaryData[dossierTemplateId][key];
|
||||
return data ? data : this._dictionaryData[dossierTemplateId]['default'];
|
||||
}
|
||||
|
||||
activateDictionary(dictionaryType: string) {
|
||||
if (this._dossierTemplatesService.activeDossierTemplate) {
|
||||
this._appState.activeDictionaryType = dictionaryType;
|
||||
if (!this.activeDictionary) {
|
||||
this._appState.activeDictionaryType = null;
|
||||
this._router.navigate([this._dossierTemplatesService.activeDossierTemplate.routerLink]).then();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._appState.activeDictionaryType = null;
|
||||
}
|
||||
|
||||
async refreshDossierTemplate(dossierTemplateId: string) {
|
||||
const dossierTemplate = await firstValueFrom(this._dossierTemplatesService.get(dossierTemplateId));
|
||||
|
||||
await firstValueFrom(this._fileAttributesService.getFileAttributesConfig(dossierTemplateId));
|
||||
|
||||
const newDossierTemplate = new DossierTemplate(dossierTemplate);
|
||||
this._dossierTemplatesService.replace(newDossierTemplate);
|
||||
|
||||
await this.refreshDossierTemplateDictionaryData(dossierTemplateId);
|
||||
}
|
||||
|
||||
async loadDictionaryDataIfNecessary() {
|
||||
if (!this._dictionaryData) {
|
||||
await this.loadDictionaryData();
|
||||
} else {
|
||||
return this._dictionaryData;
|
||||
}
|
||||
}
|
||||
|
||||
async loadDictionaryData(): Promise<void> {
|
||||
const observables = [];
|
||||
for (const dossierTemplate of this.dossierTemplates) {
|
||||
observables.push(this._getDictionaryDataForDossierTemplate$(dossierTemplate.dossierTemplateId));
|
||||
}
|
||||
const result = await firstValueFrom(forkJoin(observables));
|
||||
|
||||
const dictionaryData = {};
|
||||
for (let i = 0; i < this.dossierTemplates.length; i++) {
|
||||
dictionaryData[this.dossierTemplates[i].dossierTemplateId] = result[i];
|
||||
}
|
||||
|
||||
this._dictionaryData = dictionaryData;
|
||||
}
|
||||
|
||||
async refreshDossierTemplateDictionaryData(dossierTemplateId: string) {
|
||||
this._dictionaryData[dossierTemplateId] = await firstValueFrom(this._getDictionaryDataForDossierTemplate$(dossierTemplateId));
|
||||
}
|
||||
|
||||
loadColors(dossierTemplateId: string) {
|
||||
return this._dictionaryService.getColors(dossierTemplateId).pipe(
|
||||
catchError(() =>
|
||||
of({
|
||||
analysisColor: FALLBACK_COLOR,
|
||||
dictionaryRequestColor: FALLBACK_COLOR,
|
||||
defaultColor: FALLBACK_COLOR,
|
||||
manualRedactionColor: FALLBACK_COLOR,
|
||||
notRedacted: FALLBACK_COLOR,
|
||||
requestAdd: FALLBACK_COLOR,
|
||||
previewColor: FALLBACK_COLOR,
|
||||
requestRemove: FALLBACK_COLOR,
|
||||
updatedColor: FALLBACK_COLOR,
|
||||
dossierTemplateId: dossierTemplateId,
|
||||
} as IColors),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private _getDictionaryDataForDossierTemplate$(dossierTemplateId: string): Observable<{ [key: string]: any }> {
|
||||
const dictionaryData: { [key: string]: any } = {};
|
||||
|
||||
const typeObs = this._dictionaryService.getAllDictionaries(dossierTemplateId).pipe(
|
||||
tap(typesResponse => {
|
||||
for (const type of typesResponse.types) {
|
||||
dictionaryData[type.type] = new Dictionary(type);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const colorsObs = this.loadColors(dossierTemplateId).pipe(
|
||||
tap(colors => {
|
||||
for (const key of Object.keys(colors)) {
|
||||
const color = colors[key];
|
||||
try {
|
||||
const rgbValue = hexToRgb(color);
|
||||
if (!rgbValue) {
|
||||
colors[key] = FALLBACK_COLOR;
|
||||
}
|
||||
} catch (e) {
|
||||
colors[key] = FALLBACK_COLOR;
|
||||
}
|
||||
}
|
||||
|
||||
dictionaryData['dossier_redaction'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.manualRedactionColor || FALLBACK_COLOR,
|
||||
type: 'dossier_redaction',
|
||||
hint: false,
|
||||
recommendation: false,
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['declined-suggestion'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.notRedacted || FALLBACK_COLOR,
|
||||
type: 'declined-suggestion',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['manual'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.manualRedactionColor || FALLBACK_COLOR,
|
||||
type: 'manual',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['ignored-hint'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.ignoredHintColor || FALLBACK_COLOR,
|
||||
type: 'ignored-hint',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['manual-redaction'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.manualRedactionColor || FALLBACK_COLOR,
|
||||
type: 'manual-redaction',
|
||||
},
|
||||
true,
|
||||
);
|
||||
// dictionary actions
|
||||
dictionaryData['recommendation'] = new Dictionary(
|
||||
{
|
||||
hexColor: '#c5d3eb',
|
||||
type: 'recommendation',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['remove-only-here'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.analysisColor || FALLBACK_COLOR,
|
||||
type: 'remove-only-here',
|
||||
},
|
||||
true,
|
||||
);
|
||||
// generic suggestions
|
||||
dictionaryData['suggestion'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.requestAdd || FALLBACK_COLOR,
|
||||
type: 'suggestion',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['suggestion-add'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.requestAdd || FALLBACK_COLOR,
|
||||
type: 'suggestion-add',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
// add suggestions
|
||||
dictionaryData['suggestion-change-legal-basis'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.requestAdd || FALLBACK_COLOR,
|
||||
type: 'suggestion-change-legal-basis',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['suggestion-recategorize-image'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.requestAdd || FALLBACK_COLOR,
|
||||
type: 'suggestion-recategorize-image',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['suggestion-add-dictionary'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.dictionaryRequestColor || FALLBACK_COLOR,
|
||||
type: 'suggestion-add',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['suggestion-resize'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.dictionaryRequestColor || FALLBACK_COLOR,
|
||||
type: 'suggestion-resize',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['suggestion-remove'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.requestRemove || FALLBACK_COLOR,
|
||||
type: 'suggestion-add',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['suggestion-remove-dictionary'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.dictionaryRequestColor || FALLBACK_COLOR,
|
||||
type: 'suggestion-add',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['skipped'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.notRedacted || FALLBACK_COLOR,
|
||||
type: 'skipped',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['default'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.defaultColor || FALLBACK_COLOR,
|
||||
type: 'default',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['add'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.requestAdd || FALLBACK_COLOR,
|
||||
type: 'add',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['analysis'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.analysisColor || FALLBACK_COLOR,
|
||||
type: 'analysis',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['hint'] = new Dictionary(
|
||||
{
|
||||
hexColor: '#fa98f7',
|
||||
type: 'hint',
|
||||
hint: true,
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['redaction'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.manualRedactionColor || FALLBACK_COLOR,
|
||||
type: 'redaction',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
dictionaryData['updated'] = new Dictionary(
|
||||
{
|
||||
hexColor: colors.updatedColor || FALLBACK_COLOR,
|
||||
type: 'updated',
|
||||
},
|
||||
true,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
return forkJoin([typeObs, colorsObs]).pipe(map(() => dictionaryData));
|
||||
}
|
||||
}
|
||||
@ -1 +1,6 @@
|
||||
export const CHANGED_CHECK_INTERVAL = 5000;
|
||||
export const FALLBACK_COLOR = '#CCCCCC';
|
||||
export const DOSSIER_ID = 'dossierId';
|
||||
export const FILE_ID = 'fileId';
|
||||
export const DOSSIER_TEMPLATE_ID = 'dossierTemplateId';
|
||||
export const DICTIONARY_TYPE = 'dictionary';
|
||||
|
||||
@ -8,8 +8,6 @@ export const currentComponentRoute = filter(
|
||||
event => event instanceof ActivationEnd && !!event.snapshot.component && event.snapshot.component !== BaseScreenComponent,
|
||||
);
|
||||
|
||||
export const FALLBACK_COLOR = '#CCCCCC';
|
||||
|
||||
export function groupBy(xs: List<unknown>, key: string) {
|
||||
return xs.reduce((rv, x) => {
|
||||
(rv[x[key]] = rv[x[key]] || []).push(x);
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit d9d8dcc7f419dbcecef062110dc41b57899056f7
|
||||
Subproject commit 333bd099140f20deab75278bd02d20d3fdddae88
|
||||
@ -1,7 +1,7 @@
|
||||
import { IListable, List } from '@iqser/common-ui';
|
||||
import { Entity, List } from '@iqser/common-ui';
|
||||
import { IDictionary } from './dictionary';
|
||||
|
||||
export class Dictionary implements IDictionary, IListable {
|
||||
export class Dictionary extends Entity<IDictionary> implements IDictionary {
|
||||
readonly addToDictionaryAction: boolean;
|
||||
readonly caseInsensitive: boolean;
|
||||
readonly description: string;
|
||||
@ -15,6 +15,7 @@ export class Dictionary implements IDictionary, IListable {
|
||||
readonly type: string;
|
||||
|
||||
constructor(dictionary: IDictionary, readonly virtual = false) {
|
||||
super(dictionary);
|
||||
this.addToDictionaryAction = !!dictionary.addToDictionaryAction;
|
||||
this.caseInsensitive = !!dictionary.caseInsensitive;
|
||||
this.description = dictionary.description ?? '';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "redaction",
|
||||
"version": "3.228.0",
|
||||
"version": "3.229.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
Binary file not shown.
@ -27,7 +27,6 @@
|
||||
"@redaction/red-cache": ["libs/red-cache/src/index.ts"],
|
||||
"@services/*": ["apps/red-ui/src/app/services/*"],
|
||||
"@shared/*": ["apps/red-ui/src/app/modules/shared/*"],
|
||||
"@state/*": ["apps/red-ui/src/app/state/*"],
|
||||
"@upload-download/*": ["apps/red-ui/src/app/modules/upload-download/*"],
|
||||
"@utils/*": ["apps/red-ui/src/app/utils/*"]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user