Pull request #363: RED-3796
Merge in RED/ui from RED-3796 to master * commit '19f6b057e748b5aacbd5722cb8df340e72cf1c2b': RED-3796: common-ui RED-3796: Redirect to last opened template RED-3796: Fixed provider, filter dossiers, initialize add dossier RED-3796: Working downloads & search RED-3796: Dossiers cache for notifications RED-3796: Empty dossier template checks RED-3796: Dossier template stats RED-3796: Use dossier template stats endpoint RED-3796: Removed routerPath RED-3796: Navigation WIP (no files) RED-3796: Dashboard #2 (empty dossier template) RED-3796: Dashboard #1
This commit is contained in:
commit
a73feab16d
@ -6,83 +6,107 @@ 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 { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
|
||||
import { DossiersGuard } from '@guards/dossiers.guard';
|
||||
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
|
||||
import { FeaturesGuard } from '@guards/features-guard.service';
|
||||
import { DOSSIERS_ARCHIVE } from '@utils/constants';
|
||||
import { ARCHIVE_ROUTE, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@utils/constants';
|
||||
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
|
||||
import { DossierTemplateExistsGuard } from '@guards/dossier-template-exists.guard';
|
||||
import { DashboardGuard } from '@guards/dashboard-guard.service';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'main/dossiers',
|
||||
redirectTo: 'main',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'main',
|
||||
component: BaseScreenComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'dashboard',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'account',
|
||||
loadChildren: () => import('./modules/account/account.module').then(m => m.AccountModule),
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
loadChildren: () => import('./modules/admin/admin.module').then(m => m.AdminModule),
|
||||
},
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadChildren: () => import('./modules/dashboard/dashboard.module').then(m => m.DashboardModule),
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, DossierTemplatesGuard, DashboardGuard],
|
||||
requiredRoles: ['RED_USER', 'RED_MANAGER'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'downloads',
|
||||
component: DownloadsListScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
loadChildren: () => import('./modules/search/search.module').then(m => m.SearchModule),
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
requiredRoles: ['RED_USER', 'RED_MANAGER'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: `:${DOSSIER_TEMPLATE_ID}`,
|
||||
children: [
|
||||
{
|
||||
path: `${DOSSIERS_ROUTE}`,
|
||||
loadChildren: () => import('./modules/dossier/dossiers.module').then(m => m.DossiersModule),
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [DossiersGuard],
|
||||
dossiersService: ACTIVE_DOSSIERS_SERVICE,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: `${ARCHIVE_ROUTE}`,
|
||||
loadChildren: () => import('./modules/archive/archive.module').then(m => m.ArchiveModule),
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [FeaturesGuard, DossiersGuard],
|
||||
dossiersService: ARCHIVED_DOSSIERS_SERVICE,
|
||||
features: [DOSSIERS_ARCHIVE],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: `${DOSSIERS_ROUTE}`,
|
||||
pathMatch: 'full',
|
||||
},
|
||||
],
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, DossierTemplatesGuard, DashboardGuard, DossierTemplateExistsGuard],
|
||||
requiredRoles: ['RED_USER', 'RED_MANAGER'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'auth-error',
|
||||
component: AuthErrorComponent,
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
{
|
||||
path: 'main/account',
|
||||
component: BaseScreenComponent,
|
||||
loadChildren: () => import('./modules/account/account.module').then(m => m.AccountModule),
|
||||
},
|
||||
{
|
||||
path: 'main/admin',
|
||||
component: BaseScreenComponent,
|
||||
loadChildren: () => import('./modules/admin/admin.module').then(m => m.AdminModule),
|
||||
},
|
||||
{
|
||||
path: 'main/dossiers',
|
||||
component: BaseScreenComponent,
|
||||
loadChildren: () => import('./modules/dossier/dossiers.module').then(m => m.DossiersModule),
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, DossierTemplatesGuard, DossiersGuard],
|
||||
requiredRoles: ['RED_USER', 'RED_MANAGER'],
|
||||
dossiersService: ACTIVE_DOSSIERS_SERVICE,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'main/archive',
|
||||
component: BaseScreenComponent,
|
||||
loadChildren: () => import('./modules/archive/archive.module').then(m => m.ArchiveModule),
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [FeaturesGuard, AuthGuard, RedRoleGuard, DossierTemplatesGuard, DossiersGuard],
|
||||
requiredRoles: ['RED_USER', 'RED_MANAGER'],
|
||||
dossiersService: ARCHIVED_DOSSIERS_SERVICE,
|
||||
features: [DOSSIERS_ARCHIVE],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'main/downloads',
|
||||
component: BaseScreenComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: DownloadsListScreenComponent,
|
||||
},
|
||||
],
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'main/search',
|
||||
component: BaseScreenComponent,
|
||||
loadChildren: () => import('./modules/search/search.module').then(m => m.SearchModule),
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, DossiersGuard],
|
||||
requiredRoles: ['RED_USER', 'RED_MANAGER'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: 'main/dossiers',
|
||||
redirectTo: 'main',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
];
|
||||
|
||||
@ -163,6 +163,7 @@ const components = [AppComponent, AuthErrorComponent, NotificationsComponent, Sp
|
||||
multi: true,
|
||||
useFactory: configurationInitializer,
|
||||
deps: [
|
||||
BASE_HREF,
|
||||
KeycloakService,
|
||||
Title,
|
||||
ConfigService,
|
||||
|
||||
@ -7,12 +7,12 @@
|
||||
<redaction-breadcrumbs></redaction-breadcrumbs>
|
||||
</div>
|
||||
|
||||
<div class="logo">
|
||||
<a [routerLink]="['/']" class="logo">
|
||||
<iqser-hidden-action (action)="userPreferenceService.toggleDevFeatures()">
|
||||
<iqser-logo icon="red:logo"></iqser-logo>
|
||||
</iqser-hidden-action>
|
||||
<div class="app-name">{{ titleService.getTitle() }}</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="actions flex-2">
|
||||
<div class="buttons">
|
||||
|
||||
@ -7,10 +7,10 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { filter, map, startWith } from 'rxjs/operators';
|
||||
import { HelpModeService, shareDistinctLast } from '@iqser/common-ui';
|
||||
import { shareDistinctLast } from '@iqser/common-ui';
|
||||
import { BreadcrumbsService } from '@services/breadcrumbs.service';
|
||||
import { FeaturesService } from '@services/features.service';
|
||||
import { DOSSIERS_ARCHIVE } from '@utils/constants';
|
||||
import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@utils/constants';
|
||||
|
||||
interface MenuItem {
|
||||
readonly id: string;
|
||||
@ -91,7 +91,6 @@ export class BaseScreenComponent {
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
readonly titleService: Title,
|
||||
readonly breadcrumbsService: BreadcrumbsService,
|
||||
readonly helpModeService: HelpModeService,
|
||||
) {}
|
||||
|
||||
private get _hideSearchThisDossier() {
|
||||
@ -100,7 +99,7 @@ export class BaseScreenComponent {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isDossierOverview = (routerLink.includes('dossiers') || routerLink.includes('archive')) && routerLink.length === 3;
|
||||
const isDossierOverview = (routerLink.includes(DOSSIERS_ROUTE) || routerLink.includes(ARCHIVE_ROUTE)) && routerLink.length === 3;
|
||||
return !isDossierOverview;
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import { Notification } from '@red/domain';
|
||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { shareLast } from '@iqser/common-ui';
|
||||
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
|
||||
|
||||
interface NotificationsGroup {
|
||||
date: string;
|
||||
@ -21,7 +22,11 @@ export class NotificationsComponent {
|
||||
readonly hasUnreadNotifications$: Observable<boolean>;
|
||||
readonly groupedNotifications$: Observable<NotificationsGroup[]>;
|
||||
|
||||
constructor(private readonly _notificationsService: NotificationsService, private readonly _datePipe: DatePipe) {
|
||||
constructor(
|
||||
private readonly _notificationsService: NotificationsService,
|
||||
private readonly _datePipe: DatePipe,
|
||||
private readonly _dossiersCacheService: DossiersCacheService,
|
||||
) {
|
||||
this.groupedNotifications$ = this._notificationsService.all$.pipe(map(notifications => this._groupNotifications(notifications)));
|
||||
this.hasUnreadNotifications$ = this._hasUnreadNotifications$;
|
||||
}
|
||||
|
||||
14
apps/red-ui/src/app/guards/dashboard-guard.service.ts
Normal file
14
apps/red-ui/src/app/guards/dashboard-guard.service.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate } from '@angular/router';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DashboardGuard implements CanActivate {
|
||||
constructor(private readonly _dashboardStatsService: DashboardStatsService) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
await firstValueFrom(this._dashboardStatsService.loadAll());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
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';
|
||||
import { DOSSIER_ID, DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@ -17,16 +17,17 @@ export class DossierFilesGuard implements CanActivate {
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
const dossierId = route.paramMap.get(DOSSIER_ID);
|
||||
const dossierTemplateId = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
const token: ProviderToken<DossiersService> = route.data.dossiersService;
|
||||
const dossiersService: DossiersService = this._injector.get<DossiersService>(token);
|
||||
|
||||
if (!dossiersService.has(dossierId)) {
|
||||
await this._router.navigate(['/main', dossiersService.routerPath]);
|
||||
await this._router.navigate(['/main', dossierTemplateId]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._filesMapService.has(dossierId)) {
|
||||
await firstValueFrom(this._filesService.loadAll(dossierId, dossiersService.routerPath));
|
||||
await firstValueFrom(this._filesService.loadAll(dossierId));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
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';
|
||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossierTemplateExistsGuard implements CanActivate {
|
||||
constructor(private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _router: Router) {}
|
||||
constructor(private readonly _dashboardStatsService: DashboardStatsService, 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']);
|
||||
const dossiersListView = !route.pathFromRoot.find(r => r.routeConfig?.path === 'admin');
|
||||
const dossierTemplateStats = this._dashboardStatsService.find(dossierTemplateId);
|
||||
if (!dossierTemplateStats || (dossiersListView && dossierTemplateStats.isEmpty)) {
|
||||
const routerPath = dossiersListView ? [''] : ['main', 'admin', 'dossier-templates'];
|
||||
await this._router.navigate(routerPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate } from '@angular/router';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossierTemplatesGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
) {}
|
||||
constructor(private readonly _dossierTemplatesService: DossierTemplatesService) {}
|
||||
|
||||
async canActivate(): Promise<boolean> {
|
||||
await firstValueFrom(this._dossierTemplatesService.loadAll());
|
||||
|
||||
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class EntityExistsGuard implements CanActivate {
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import { AdminDialogService } from '../services/admin-dialog.service';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { LoadingService } from '@iqser/common-ui';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
@ -23,7 +23,7 @@ export class AddEditDossierStateDialogComponent extends BaseDialogComponent {
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _dossierStateService: DossierStatesService,
|
||||
private readonly _dossierStatesService: DossierStatesService,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddEditDossierStateDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) readonly data: DialogData,
|
||||
@ -45,7 +45,7 @@ export class AddEditDossierStateDialogComponent extends BaseDialogComponent {
|
||||
};
|
||||
this._loadingService.start();
|
||||
try {
|
||||
await firstValueFrom(this._dossierStateService.createOrUpdate(dossierState));
|
||||
await firstValueFrom(this._dossierStatesService.createOrUpdate(dossierState));
|
||||
this._toaster.success(_('add-edit-dossier-state.success'), { params: { type: this.type } });
|
||||
this._dialogRef.close();
|
||||
} catch (e) {}
|
||||
|
||||
@ -3,7 +3,7 @@ import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/fo
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
|
||||
import { downloadTypesTranslations } from '../../../../translations/download-types-translations';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { BaseDialogComponent, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DossierTemplate, DownloadFileType, IDossierTemplate } from '@red/domain';
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DossierTemplatesService } from '../../../../services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplate, IDossierTemplate } from '../../../../../../../../libs/red-domain/src';
|
||||
import { LoadingService, Toaster } from '../../../../../../../../libs/common-ui/src';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { DossierTemplate } from '@red/domain';
|
||||
import { LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
|
||||
@ -11,8 +11,8 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
styleUrls: ['./clone-dossier-template-dialog.component.scss'],
|
||||
})
|
||||
export class CloneDossierTemplateDialogComponent {
|
||||
private readonly _dossierTemplate: DossierTemplate;
|
||||
nameOfClonedDossierTemplate: string;
|
||||
private readonly _dossierTemplate: DossierTemplate;
|
||||
|
||||
constructor(
|
||||
private readonly _toaster: Toaster,
|
||||
@ -25,6 +25,17 @@ export class CloneDossierTemplateDialogComponent {
|
||||
this.nameOfClonedDossierTemplate = this._getCloneName();
|
||||
}
|
||||
|
||||
async save() {
|
||||
this._loadingService.start();
|
||||
try {
|
||||
await firstValueFrom(this._dossierTemplatesService.clone(this.dossierTemplateId, this.nameOfClonedDossierTemplate));
|
||||
this._dialogRef.close(true);
|
||||
} catch (error: any) {
|
||||
this._toaster.error(_('clone-dossier-template.error.generic'), { error });
|
||||
}
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
private _getCloneName(): string | null {
|
||||
const templateName = this._dossierTemplate.name.trim();
|
||||
|
||||
@ -47,15 +58,4 @@ export class CloneDossierTemplateDialogComponent {
|
||||
}
|
||||
return `Clone of ${nameOfClonedTemplate}`;
|
||||
}
|
||||
|
||||
async save() {
|
||||
this._loadingService.start();
|
||||
try {
|
||||
await firstValueFrom(this._dossierTemplatesService.clone(this.dossierTemplateId, this.nameOfClonedDossierTemplate));
|
||||
this._dialogRef.close(true);
|
||||
} catch (error: any) {
|
||||
this._toaster.error(_('clone-dossier-template.error.generic'), { error });
|
||||
}
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ export class ConfirmDeleteDossierStateDialogComponent {
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _dossierStateService: DossierStatesService,
|
||||
private readonly _dossierStatesService: DossierStatesService,
|
||||
private readonly _dialogRef: MatDialogRef<ConfirmDeleteDossierStateDialogComponent>,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _archivedDossiersService: ArchivedDossiersService,
|
||||
@ -55,7 +55,7 @@ export class ConfirmDeleteDossierStateDialogComponent {
|
||||
|
||||
async save(): Promise<void> {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._dossierStateService.deleteState(this.data.toBeDeletedState, this.replaceDossierStatusId));
|
||||
await firstValueFrom(this._dossierStatesService.deleteState(this.data.toBeDeletedState, this.replaceDossierStatusId));
|
||||
await firstValueFrom(
|
||||
forkJoin([this._activeDossiersService.loadAll().pipe(take(1)), this._archivedDossiersService.loadAll().pipe(take(1))]),
|
||||
);
|
||||
|
||||
@ -4,7 +4,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { FileAttributeEncodingTypes, IFileAttributesConfig } from '@red/domain';
|
||||
import { fileAttributeEncodingTypesTranslations } from '../../translations/file-attribute-encoding-types-translations';
|
||||
import { BaseDialogComponent, Toaster } from '@iqser/common-ui';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/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';
|
||||
|
||||
@ -13,7 +13,7 @@ import { defaultColorsTranslations } from '../../translations/default-colors-tra
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
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 { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
|
||||
@ -28,7 +28,7 @@ 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 { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { DossierTemplate, DossierTemplateStats } from '@red/domain';
|
||||
|
||||
@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Justification } from '@red/domain';
|
||||
import { JustificationsService } from '@services/entity-services/justifications.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { BaseDialogComponent, LoadingService } from '@iqser/common-ui';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
} from '@iqser/common-ui';
|
||||
import { AddEditJustificationDialogComponent } from './add-edit-justification-dialog/add-edit-justification-dialog.component';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { JustificationsService } from '@services/entity-services/justifications.service';
|
||||
import { Justification } from '@red/domain';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
import { removeBraces } from '@utils/functions';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { AdminDialogService } from '../../../services/admin-dialog.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { ReportTemplateService } from '@services/report-template.service';
|
||||
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
@ -18,7 +18,7 @@ import { TrashItem } from '@red/domain';
|
||||
import { TrashService } from '@services/entity-services/trash.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './trash-screen.component.html',
|
||||
|
||||
@ -3,7 +3,7 @@ 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 { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
@ -37,6 +37,7 @@ export class DossierTemplateActionsComponent implements OnInit {
|
||||
openEditDossierTemplateDialog($event: MouseEvent) {
|
||||
this._dialogService.openDialog('addEditDossierTemplate', $event, this.dossierTemplateId);
|
||||
}
|
||||
|
||||
openCloneDossierTemplateDialog($event: MouseEvent) {
|
||||
this._dialogService.openDialog('cloneDossierTemplate', $event, this.dossierTemplateId);
|
||||
}
|
||||
|
||||
@ -12,14 +12,14 @@ const routes: Routes = [
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
component: ArchivedDossiersScreenComponent,
|
||||
data: { breadcrumbs: [BreadcrumbTypes.archive] },
|
||||
data: { breadcrumbs: [BreadcrumbTypes.dossierTemplate] },
|
||||
},
|
||||
{
|
||||
path: `:${DOSSIER_ID}`,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [DossierFilesGuard],
|
||||
breadcrumbs: [BreadcrumbTypes.archive, BreadcrumbTypes.dossier],
|
||||
breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier],
|
||||
dossiersService: ARCHIVED_DOSSIERS_SERVICE,
|
||||
},
|
||||
loadChildren: () => import('../dossier-overview/dossier-overview.module').then(m => m.DossierOverviewModule),
|
||||
@ -29,7 +29,7 @@ const routes: Routes = [
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [DossierFilesGuard],
|
||||
breadcrumbs: [BreadcrumbTypes.archive, BreadcrumbTypes.dossier, BreadcrumbTypes.file],
|
||||
breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier, BreadcrumbTypes.file],
|
||||
dossiersService: ARCHIVED_DOSSIERS_SERVICE,
|
||||
},
|
||||
loadChildren: () => import('../file-preview/file-preview.module').then(m => m.FilePreviewModule),
|
||||
|
||||
@ -22,7 +22,7 @@ export class TableItemComponent implements OnChanges {
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.dossier) {
|
||||
this.#ngOnChanges$.next(this.dossier.dossierId);
|
||||
this.#ngOnChanges$.next(this.dossier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
<section>
|
||||
<iqser-page-header></iqser-page-header>
|
||||
<iqser-page-header>
|
||||
<ng-container slot="beforeFilters">
|
||||
<redaction-dossiers-type-switch></redaction-dossiers-type-switch>
|
||||
</ng-container>
|
||||
</iqser-page-header>
|
||||
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
|
||||
@ -1,32 +1,45 @@
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit } from '@angular/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DefaultListingServicesTmp, EntitiesService, ListingComponent } from '@iqser/common-ui';
|
||||
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
|
||||
import { DefaultListingServices, ListingComponent } from '@iqser/common-ui';
|
||||
import { Dossier } from '@red/domain';
|
||||
import { ConfigService } from '../../services/config.service';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-archived-dossiers-screen',
|
||||
templateUrl: './archived-dossiers-screen.component.html',
|
||||
styleUrls: ['./archived-dossiers-screen.component.scss'],
|
||||
providers: [
|
||||
...DefaultListingServicesTmp,
|
||||
{ provide: EntitiesService, useExisting: ArchivedDossiersService },
|
||||
{ provide: ListingComponent, useExisting: forwardRef(() => ArchivedDossiersScreenComponent) },
|
||||
],
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => ArchivedDossiersScreenComponent) }],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ArchivedDossiersScreenComponent extends ListingComponent<Dossier> implements OnInit {
|
||||
readonly tableColumnConfigs = this._configService.tableConfig;
|
||||
readonly tableHeaderLabel = _('archived-dossiers-listing.table-header.title');
|
||||
private readonly _dossierTemplateId: string;
|
||||
|
||||
constructor(protected readonly _injector: Injector, private readonly _configService: ConfigService) {
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _configService: ConfigService,
|
||||
private readonly _archivedDossiersService: ArchivedDossiersService,
|
||||
private readonly _router: Router,
|
||||
) {
|
||||
super(_injector);
|
||||
this._dossierTemplateId = this._router.routerState.snapshot.root.firstChild.firstChild.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
this._router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||
console.log(this._dossierTemplateId);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.addSubscription = this.entitiesService.all$.pipe(tap(() => this._computeAllFilters())).subscribe();
|
||||
this.addSubscription = this._archivedDossiersService.all$
|
||||
.pipe(
|
||||
tap(dossiers => console.log(dossiers.map(d => d.dossierTemplateId))),
|
||||
tap(dossiers => this.entitiesService.setEntities(dossiers.filter(d => d.dossierTemplateId === this._dossierTemplateId))),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private _computeAllFilters() {
|
||||
|
||||
@ -5,7 +5,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { dossierMemberChecker, dossierTemplateChecker } from '@utils/index';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
|
||||
@Injectable()
|
||||
export class ConfigService {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, Router } from '@angular/router';
|
||||
import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
@ -19,12 +19,12 @@ export class AuthGuard extends KeycloakAuthGuard {
|
||||
super(_router, _keycloak);
|
||||
}
|
||||
|
||||
async isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
|
||||
async isAccessAllowed(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
if (!this.authenticated) {
|
||||
const kcIdpHint = route.queryParamMap.get('kc_idp_hint');
|
||||
await this._keycloak.login({
|
||||
idpHint: kcIdpHint ?? this._configService.values.OAUTH_IDP_HINT,
|
||||
redirectUri: window.location.origin + this._baseHref + state.url,
|
||||
redirectUri: window.location.href,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -49,18 +49,16 @@ export class RedRoleGuard implements CanActivate {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._userService.currentUser.isUser && state.url.startsWith('/main/dossiers')) {
|
||||
this._router.navigate(['/main/admin']);
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
return;
|
||||
}
|
||||
if (route.data.requiredRoles) {
|
||||
if (this._userService.hasAnyRole(route.data.requiredRoles)) {
|
||||
obs.next(true);
|
||||
obs.complete();
|
||||
} else {
|
||||
this._router.navigate(['/main/dossiers']);
|
||||
if (!this._userService.currentUser.isUser) {
|
||||
this._router.navigate(['/main/admin']);
|
||||
} else {
|
||||
this._router.navigate(['/']);
|
||||
}
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
}
|
||||
|
||||
@ -0,0 +1,77 @@
|
||||
<a
|
||||
*ngIf="stats as dossierTemplate"
|
||||
[class.empty]="dossierTemplate.isEmpty"
|
||||
[routerLink]="dossierTemplate.isEmpty ? null : ['..', dossierTemplate.dossierTemplateId]"
|
||||
class="dialog"
|
||||
>
|
||||
<ng-container *ngIf="!dossierTemplate.isEmpty; else empty">
|
||||
<div class="flex-2">
|
||||
<div class="heading mb-6">{{ dossierTemplate.name }}</div>
|
||||
<div class="stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:archive"></mat-icon>
|
||||
<span
|
||||
[innerHTML]="
|
||||
'dossier-template-stats.archived-dossiers' | translate: { count: dossierTemplate.numberOfArchivedDossiers }
|
||||
"
|
||||
></span>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="iqser:trash"></mat-icon>
|
||||
<span
|
||||
[innerHTML]="
|
||||
'dossier-template-stats.deleted-dossiers' | translate: { count: dossierTemplate.numberOfDeletedDossiers }
|
||||
"
|
||||
></span>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="red:user"></mat-icon>
|
||||
<span [innerHTML]="'dossier-template-stats.total-people' | translate: { count: dossierTemplate.numberOfPeople }"></span>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="iqser:trash"></mat-icon>
|
||||
<span
|
||||
[innerHTML]="'dossier-template-stats.analyzed-pages' | translate: { count: dossierTemplate.numberOfPages }"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-3">
|
||||
<redaction-simple-doughnut-chart
|
||||
[config]="translateChartService.translateDossierStates(dossierTemplate.dossiersChartData, dossierTemplate.id)"
|
||||
[radius]="63"
|
||||
[strokeWidth]="15"
|
||||
[subtitle]="'dossier-template-stats.active-dossiers' | translate: { count: dossierTemplate.numberOfActiveDossiers }"
|
||||
direction="row"
|
||||
totalType="sum"
|
||||
></redaction-simple-doughnut-chart>
|
||||
</div>
|
||||
<div class="flex-3">
|
||||
<redaction-simple-doughnut-chart
|
||||
[config]="translateChartService.translateWorkflowStatus(dossierTemplate.documentsChartData)"
|
||||
[radius]="63"
|
||||
[strokeWidth]="15"
|
||||
[subtitle]="'dossier-template-stats.total-documents' | translate"
|
||||
direction="row"
|
||||
totalType="sum"
|
||||
></redaction-simple-doughnut-chart>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #empty>
|
||||
<div class="text-muted">
|
||||
<div class="heading mb-8">
|
||||
{{ dossierTemplate.name }}
|
||||
</div>
|
||||
<div>
|
||||
{{ 'dashboard.empty-template.description' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
<iqser-icon-button
|
||||
(action)="newDossier()"
|
||||
[label]="'dashboard.empty-template.new-dossier' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="iqser:plus"
|
||||
></iqser-icon-button>
|
||||
</ng-template>
|
||||
</a>
|
||||
@ -0,0 +1,53 @@
|
||||
@use 'common-mixins';
|
||||
|
||||
.dialog {
|
||||
@include common-mixins.clear-a;
|
||||
flex-direction: row;
|
||||
max-width: unset;
|
||||
min-height: unset;
|
||||
margin: 0 0 16px 0;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&.empty {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&:not(.empty) {
|
||||
&:hover {
|
||||
background-color: var(--iqser-grey-2);
|
||||
|
||||
.heading {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
&:not(:first-child) {
|
||||
justify-content: center;
|
||||
border-left: 1px solid var(--iqser-separator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats-subtitle {
|
||||
flex-direction: column;
|
||||
|
||||
> div {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
min-height: 14px;
|
||||
min-width: 14px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { DashboardStats } from '@red/domain';
|
||||
import { IconButtonTypes } from '@iqser/common-ui';
|
||||
import { DossiersDialogService } from '../../../dossier/shared/services/dossiers-dialog.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { TranslateChartService } from '@services/translate-chart.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-template-stats [stats]',
|
||||
templateUrl: './template-stats.component.html',
|
||||
styleUrls: ['./template-stats.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TemplateStatsComponent {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
|
||||
@Input() stats: DashboardStats;
|
||||
|
||||
constructor(
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _translateService: TranslateService,
|
||||
readonly translateChartService: TranslateChartService,
|
||||
) {}
|
||||
|
||||
newDossier(): void {
|
||||
this._dialogService.openDialog('addDossier', null, { dossierTemplateId: this.stats.dossierTemplateId });
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
<div class="container">
|
||||
<div
|
||||
[translateParams]="{ name: currentUser.firstName || currentUser.name }"
|
||||
[translate]="'dashboard.greeting.title'"
|
||||
class="heading-xl mb-8"
|
||||
></div>
|
||||
|
||||
<div class="mb-32" translate="dashboard.greeting.subtitle"></div>
|
||||
|
||||
<redaction-template-stats *ngFor="let dossierTemplate of stats$ | async" [stats]="dossierTemplate"></redaction-template-stats>
|
||||
</div>
|
||||
@ -0,0 +1,11 @@
|
||||
:host {
|
||||
align-items: center;
|
||||
background-color: var(--iqser-grey-2);
|
||||
|
||||
.container {
|
||||
padding: 32px;
|
||||
width: 900px;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dashboard-screen',
|
||||
templateUrl: './dashboard-screen.component.html',
|
||||
styleUrls: ['./dashboard-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DashboardScreenComponent implements OnInit {
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
readonly stats$ = this._dashboardStatsService.all$;
|
||||
|
||||
constructor(
|
||||
private readonly _userService: UserService,
|
||||
private readonly _dashboardStatsService: DashboardStatsService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this._userPreferenceService.saveLastDossierTemplate(null);
|
||||
}
|
||||
}
|
||||
24
apps/red-ui/src/app/modules/dashboard/dashboard.module.ts
Normal file
24
apps/red-ui/src/app/modules/dashboard/dashboard.module.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { DashboardScreenComponent } from './dashboard-screen/dashboard-screen.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { TemplateStatsComponent } from './components/template-stats/template-stats.component';
|
||||
import { SharedDossiersModule } from '../dossier/shared/shared-dossiers.module';
|
||||
import { BreadcrumbTypes } from '@red/domain';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '',
|
||||
component: DashboardScreenComponent,
|
||||
data: {
|
||||
breadcrumbs: [BreadcrumbTypes.dashboard],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [DashboardScreenComponent, TemplateStatsComponent],
|
||||
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, SharedDossiersModule],
|
||||
})
|
||||
export class DashboardModule {}
|
||||
@ -1,10 +1,10 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { Dossier, DossierAttributeWithValue, DossierStats } from '@red/domain';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
|
||||
import { DossiersDialogService } from '../../../dossier/services/dossiers-dialog.service';
|
||||
import { DossiersDialogService } from '../../../dossier/shared/services/dossiers-dialog.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-details-stats',
|
||||
@ -28,14 +28,14 @@ export class DossierDetailsStatsComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.dossierStats$ = this._dossierStatsService.watch$(this.dossier.dossierId);
|
||||
this.dossierStats$ = this._dossierStatsService.watch$(this.dossier.id);
|
||||
this.dossierTemplateName = this._dossierTemplatesService.find(this.dossier.dossierTemplateId)?.name || '-';
|
||||
}
|
||||
|
||||
openEditDossierDialog(section: string): void {
|
||||
const data = { dossierId: this.dossier.dossierId, section };
|
||||
const data = { dossierId: this.dossier.id, section };
|
||||
this._dialogService.openDialog('editDossier', null, data, async () => {
|
||||
await firstValueFrom(this._filesService.loadAll(this.dossier.dossierId, this.dossier.routerPath));
|
||||
await firstValueFrom(this._filesService.loadAll(this.dossier.id));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
|
||||
import { map, pluck, switchMap } from 'rxjs/operators';
|
||||
import { DossiersDialogService } from '../../../dossier/services/dossiers-dialog.service';
|
||||
import { DossiersDialogService } from '../../../dossier/shared/services/dossiers-dialog.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { DOSSIER_ID } from '@utils/constants';
|
||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||
@ -80,7 +80,7 @@ export class DossierDetailsComponent {
|
||||
key: status,
|
||||
}));
|
||||
documentsChartData.sort((a, b) => StatusSorter.byStatus(a.key, b.key));
|
||||
return this.translateChartService.translateStatus(documentsChartData);
|
||||
return this.translateChartService.translateLabels(documentsChartData);
|
||||
}
|
||||
|
||||
#calculateStatusConfig(stats: DossierStats): ProgressBarConfigModel[] {
|
||||
|
||||
@ -1,49 +1,51 @@
|
||||
<iqser-page-header
|
||||
(closeAction)="router.navigate(['/main', dossiersService.routerPath])"
|
||||
(closeAction)="router.navigate([dossier.dossiersListRouterLink])"
|
||||
[actionConfigs]="actionConfigs"
|
||||
[helpModeKey]="'document'"
|
||||
[showCloseButton]="true"
|
||||
[viewModeSelection]="viewModeSelection"
|
||||
>
|
||||
<redaction-file-download-btn
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[dossier]="dossier"
|
||||
[files]="entitiesService.all$ | async"
|
||||
iqserHelpMode="edit_dossier_in_dossier"
|
||||
tooltipPosition="below"
|
||||
></redaction-file-download-btn>
|
||||
<ng-container slot="right">
|
||||
<redaction-file-download-btn
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[dossier]="dossier"
|
||||
[files]="entitiesService.all$ | async"
|
||||
iqserHelpMode="edit_dossier_in_dossier"
|
||||
tooltipPosition="below"
|
||||
></redaction-file-download-btn>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="downloadDossierAsCSV()"
|
||||
*ngIf="permissionsService.canDownloadCsvReport(dossier)"
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
|
||||
icon="iqser:csv"
|
||||
iqserHelpMode="edit_dossier_in_dossier"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="downloadDossierAsCSV()"
|
||||
*ngIf="permissionsService.canDownloadCsvReport(dossier)"
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
|
||||
icon="iqser:csv"
|
||||
iqserHelpMode="edit_dossier_in_dossier"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="reanalyseDossier()"
|
||||
*ngIf="permissionsService.displayReanalyseBtn(dossier)"
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[tooltipClass]="'small warn'"
|
||||
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate"
|
||||
[type]="circleButtonTypes.warn"
|
||||
icon="iqser:refresh"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="reanalyseDossier()"
|
||||
*ngIf="permissionsService.displayReanalyseBtn(dossier)"
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[tooltipClass]="'small warn'"
|
||||
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate"
|
||||
[type]="circleButtonTypes.warn"
|
||||
icon="iqser:refresh"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="upload.emit()"
|
||||
*ngIf="permissionsService.canUploadFiles(dossier)"
|
||||
[tooltip]="'dossier-overview.header-actions.upload-document' | translate"
|
||||
[type]="circleButtonTypes.primary"
|
||||
class="ml-14"
|
||||
icon="iqser:upload"
|
||||
iqserHelpMode="edit_dossier_in_dossier"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="upload.emit()"
|
||||
*ngIf="permissionsService.canUploadFiles(dossier)"
|
||||
[tooltip]="'dossier-overview.header-actions.upload-document' | translate"
|
||||
[type]="circleButtonTypes.primary"
|
||||
class="ml-14"
|
||||
icon="iqser:upload"
|
||||
iqserHelpMode="edit_dossier_in_dossier"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
</ng-container>
|
||||
</iqser-page-header>
|
||||
|
||||
<ng-template #viewModeSelection>
|
||||
|
||||
@ -51,7 +51,7 @@ export class DossierOverviewScreenHeaderComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.actionConfigs = this.configService.actionConfig(this.dossier.dossierId, this.listingService.areSomeSelected$);
|
||||
this.actionConfigs = this.configService.actionConfig(this.dossier.id, this.listingService.areSomeSelected$);
|
||||
}
|
||||
|
||||
async reanalyseDossier() {
|
||||
|
||||
@ -17,7 +17,7 @@ import { PermissionsService } from '@services/permissions.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DossiersDialogService } from '../dossier/services/dossiers-dialog.service';
|
||||
import { DossiersDialogService } from '../dossier/shared/services/dossiers-dialog.service';
|
||||
import { annotationFilterChecker, RedactionFilterSorter } from '../../utils';
|
||||
import { workloadTranslations } from '../dossier/translations/workload-translations';
|
||||
import { ConfigService as AppConfigService } from '@services/config.service';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, ElementRef, forwardRef, HostListener, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { Component, ElementRef, forwardRef, HostListener, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { Dossier, DossierAttributeWithValue, File, IFileAttributeConfig, WorkflowFileStatus } from '@red/domain';
|
||||
import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service';
|
||||
import { FileUploadModel } from '@upload-download/model/file-upload.model';
|
||||
@ -28,7 +28,7 @@ import { PermissionsService } from '@services/permissions.service';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
import { ConfigService } from '../config.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
@ -126,7 +126,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
||||
get #dossierFilesChange$() {
|
||||
return this._dossiersService.dossierFileChanges$.pipe(
|
||||
filter(dossierId => dossierId === this.dossierId),
|
||||
switchMap(dossierId => this._filesService.loadAll(dossierId, this._dossiersService.routerPath)),
|
||||
switchMap(dossierId => this._filesService.loadAll(dossierId)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Dossier, File } from '@red/domain';
|
||||
import { DossiersDialogService } from '../../dossier/services/dossiers-dialog.service';
|
||||
import { DossiersDialogService } from '../../dossier/shared/services/dossiers-dialog.service';
|
||||
import { ConfirmationDialogInput, LoadingService } from '@iqser/common-ui';
|
||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
|
||||
@ -1,21 +1,27 @@
|
||||
import { Component, Injector } from '@angular/core';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DownloadFileType, IDossierRequest, IDossierTemplate, IReportTemplate } from '@red/domain';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { downloadTypesTranslations } from '../../../../translations/download-types-translations';
|
||||
import { BaseDialogComponent, IconButtonTypes, SaveOptions } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent, IconButtonTypes, LoadingService, SaveOptions } from '@iqser/common-ui';
|
||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { ReportTemplateService } from '@services/report-template.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
import dayjs from 'dayjs';
|
||||
import { Router } from '@angular/router';
|
||||
import { DossiersDialogService } from '../../shared/services/dossiers-dialog.service';
|
||||
|
||||
interface DialogData {
|
||||
readonly dossierTemplateId?: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: './add-dossier-dialog.component.html',
|
||||
styleUrls: ['./add-dossier-dialog.component.scss'],
|
||||
})
|
||||
export class AddDossierDialogComponent extends BaseDialogComponent {
|
||||
export class AddDossierDialogComponent extends BaseDialogComponent implements OnInit {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
|
||||
hasDueDate = false;
|
||||
@ -33,8 +39,12 @@ export class AddDossierDialogComponent extends BaseDialogComponent {
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _reportTemplateController: ReportTemplateService,
|
||||
private readonly _router: Router,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
protected readonly _injector: Injector,
|
||||
protected readonly _dialogRef: MatDialogRef<AddDossierDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) readonly data: DialogData,
|
||||
) {
|
||||
super(_injector, _dialogRef);
|
||||
this._getDossierTemplates();
|
||||
@ -58,13 +68,26 @@ export class AddDossierDialogComponent extends BaseDialogComponent {
|
||||
return this.form.invalid;
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.dossierTemplateChanged(this.form.get('dossierTemplateId').value);
|
||||
}
|
||||
|
||||
reportTemplateValueMapper = (reportTemplate: IReportTemplate) => reportTemplate.templateId;
|
||||
|
||||
async save(options?: SaveOptions) {
|
||||
this._loadingService.start();
|
||||
const savedDossier = await firstValueFrom(this._activeDossiersService.createOrUpdate(this._formToObject()));
|
||||
if (savedDossier) {
|
||||
this._dialogRef.close({ dossier: savedDossier, addMembers: options?.addMembers });
|
||||
await this._router.navigate([savedDossier.routerLink]);
|
||||
if (options?.addMembers) {
|
||||
this._dialogService.openDialog('editDossier', null, {
|
||||
dossierId: savedDossier.id,
|
||||
section: 'members',
|
||||
});
|
||||
}
|
||||
this._dialogRef.close(savedDossier);
|
||||
}
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
async dossierTemplateChanged(dossierTemplateId) {
|
||||
@ -98,7 +121,7 @@ export class AddDossierDialogComponent extends BaseDialogComponent {
|
||||
return this._formBuilder.group(
|
||||
{
|
||||
dossierName: [null, Validators.required],
|
||||
dossierTemplateId: [null, Validators.required],
|
||||
dossierTemplateId: [this.data?.dossierTemplateId, Validators.required],
|
||||
downloadFileTypes: [null],
|
||||
reportTemplateIds: [null],
|
||||
description: [null],
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, ChangeDetectorRef, Component, Inject, Injector, ViewChild } from '@angular/core';
|
||||
import { AfterViewInit, Component, Inject, Injector, ViewChild } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Dossier } from '@red/domain';
|
||||
import { EditDossierGeneralInfoComponent } from './general-info/edit-dossier-general-info.component';
|
||||
@ -48,8 +48,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent implements A
|
||||
|
||||
constructor(
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _activeDossiersService: DossiersService,
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _userService: UserService,
|
||||
@ -62,7 +61,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent implements A
|
||||
},
|
||||
) {
|
||||
super(_injector, _dialogRef, true);
|
||||
this.dossier$ = this._activeDossiersService.getEntityChanged$(_data.dossierId).pipe(
|
||||
this.dossier$ = this._dossiersService.getEntityChanged$(_data.dossierId).pipe(
|
||||
tap(dossier => {
|
||||
this._dossier = dossier;
|
||||
this._initializeNavItems();
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<mat-form-field floatLabel="always">
|
||||
<mat-label>{{ 'assign-dossier-owner.dialog.single-user' | translate }}</mat-label>
|
||||
<mat-select formControlName="owner" id="editDossierOwnerSelect">
|
||||
<mat-option *ngFor="let userId of ownersSelectOptions; let index = index" [value]="userId" [id]="'mat-option-' + index">
|
||||
<mat-option *ngFor="let userId of ownersSelectOptions; let index = index" [id]="'mat-option-' + index" [value]="userId">
|
||||
{{ userId | name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
@ -11,12 +11,12 @@
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="selectedApproversList.length">
|
||||
<div class="all-caps-label mt-16" translate="assign-dossier-owner.dialog.approvers" id="approversLabel"></div>
|
||||
<div class="all-caps-label mt-16" id="approversLabel" translate="assign-dossier-owner.dialog.approvers"></div>
|
||||
<redaction-team-members
|
||||
(remove)="toggleSelected($event)"
|
||||
[canAdd]="false"
|
||||
[canRemove]="hasOwner && !disabled"
|
||||
[dossierId]="dossier.dossierId"
|
||||
[dossierId]="dossier.id"
|
||||
[largeSpacing]="true"
|
||||
[memberIds]="selectedApproversList"
|
||||
[perLine]="13"
|
||||
@ -25,12 +25,12 @@
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="(selectedReviewers$ | async)?.length">
|
||||
<div class="all-caps-label mt-16" translate="assign-dossier-owner.dialog.reviewers" id="reviewersLabel"></div>
|
||||
<div class="all-caps-label mt-16" id="reviewersLabel" translate="assign-dossier-owner.dialog.reviewers"></div>
|
||||
<redaction-team-members
|
||||
(remove)="toggleSelected($event)"
|
||||
[canAdd]="false"
|
||||
[canRemove]="hasOwner && !disabled"
|
||||
[dossierId]="dossier.dossierId"
|
||||
[dossierId]="dossier.id"
|
||||
[largeSpacing]="true"
|
||||
[memberIds]="selectedReviewers$ | async"
|
||||
[perLine]="13"
|
||||
|
||||
@ -2,14 +2,14 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Dossier, IDossierRequest, IDossierTemplate } from '@red/domain';
|
||||
import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-dossier-section.interface';
|
||||
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
|
||||
import { DossiersDialogService } from '../../../shared/services/dossiers-dialog.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { EditDossierDialogComponent } from '../edit-dossier-dialog.component';
|
||||
import { ConfirmationDialogInput, ConfirmOptions, IconButtonTypes, LoadingService, TitleColors, Toaster } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
@ -139,7 +139,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._trashService.deleteDossier(this.dossier));
|
||||
this._editDossierDialogRef.close();
|
||||
await this._router.navigate(['main', 'dossiers']);
|
||||
await this._router.navigate([this.dossier.dossiersListRouterLink]);
|
||||
this._loadingService.stop();
|
||||
this._toaster.success(_('edit-dossier-dialog.delete-successful'), { params: this.dossier });
|
||||
});
|
||||
@ -161,7 +161,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
if (result === ConfirmOptions.CONFIRM) {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._archivedDossiersService.archive([this.dossier]));
|
||||
await this._router.navigate(['main', 'dossiers']);
|
||||
await this._router.navigate([this.dossier.dossiersListRouterLink]);
|
||||
this._toaster.success(_('dossier-listing.archive.archive-succeeded'), { params: this.dossier });
|
||||
this._editDossierDialogRef.close();
|
||||
this._loadingService.stop();
|
||||
@ -187,7 +187,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
dossierTemplateId: [
|
||||
{
|
||||
value: this.dossier.dossierTemplateId,
|
||||
disabled: this._dossierStatsService.get(this.dossier.dossierId).hasFiles || !this.dossier.isActive,
|
||||
disabled: this._dossierStatsService.get(this.dossier.id).hasFiles || !this.dossier.isActive,
|
||||
},
|
||||
Validators.required,
|
||||
],
|
||||
|
||||
@ -12,7 +12,7 @@ const routes: Routes = [
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [DossierFilesGuard],
|
||||
breadcrumbs: [BreadcrumbTypes.main, BreadcrumbTypes.dossier],
|
||||
breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier],
|
||||
dossiersService: ACTIVE_DOSSIERS_SERVICE,
|
||||
},
|
||||
loadChildren: () => import('../dossier-overview/dossier-overview.module').then(m => m.DossierOverviewModule),
|
||||
@ -22,7 +22,7 @@ const routes: Routes = [
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [DossierFilesGuard],
|
||||
breadcrumbs: [BreadcrumbTypes.main, BreadcrumbTypes.dossier, BreadcrumbTypes.file],
|
||||
breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier, BreadcrumbTypes.file],
|
||||
dossiersService: ACTIVE_DOSSIERS_SERVICE,
|
||||
},
|
||||
loadChildren: () => import('../file-preview/file-preview.module').then(m => m.FilePreviewModule),
|
||||
@ -31,7 +31,7 @@ const routes: Routes = [
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
loadChildren: () => import('../dossiers-listing/dossiers-listing.module').then(m => m.DossiersListingModule),
|
||||
data: { breadcrumbs: [BreadcrumbTypes.main] },
|
||||
data: { breadcrumbs: [BreadcrumbTypes.dossierTemplate] },
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Input, OnChanges, Optional, ViewChild } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { Action, ActionTypes, Dossier, File } from '@red/domain';
|
||||
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
import {
|
||||
CircleButtonType,
|
||||
CircleButtonTypes,
|
||||
@ -24,7 +24,6 @@ import { ExcludedPagesService } from '../../../../file-preview/services/excluded
|
||||
import { DocumentInfoService } from '../../../../file-preview/services/document-info.service';
|
||||
import { ExpandableFileActionsComponent } from '@shared/components/expandable-file-actions/expandable-file-actions.component';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { RedactionImportService } from '../../services/redaction-import.service';
|
||||
import { PageRotationService } from '../../../../file-preview/services/page-rotation.service';
|
||||
|
||||
@Component({
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { AddDossierDialogComponent } from '../dialogs/add-dossier-dialog/add-dossier-dialog.component';
|
||||
import { EditDossierDialogComponent } from '../dialogs/edit-dossier-dialog/edit-dossier-dialog.component';
|
||||
import { AssignReviewerApproverDialogComponent } from '../dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component';
|
||||
import { AddDossierDialogComponent } from '../../dialogs/add-dossier-dialog/add-dossier-dialog.component';
|
||||
import { EditDossierDialogComponent } from '../../dialogs/edit-dossier-dialog/edit-dossier-dialog.component';
|
||||
import { AssignReviewerApproverDialogComponent } from '../../dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component';
|
||||
import { ConfirmationDialogComponent, DialogConfig, DialogService, largeDialogConfig } from '@iqser/common-ui';
|
||||
import { ImportRedactionsDialogComponent } from '../../file-preview/dialogs/import-redactions-dialog/import-redactions-dialog';
|
||||
import { ImportRedactionsDialogComponent } from '../../../file-preview/dialogs/import-redactions-dialog/import-redactions-dialog';
|
||||
|
||||
type DialogType = 'confirm' | 'editDossier' | 'addDossier' | 'assignFile' | 'importRedactions';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { Dossier, File } from '@red/domain';
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
import { DossiersDialogService } from './dossiers-dialog.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { ConfirmationDialogInput, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
|
||||
@ -4,7 +4,7 @@ import { FileAssignService } from './services/file-assign.service';
|
||||
import { FileActionsComponent } from './components/file-actions/file-actions.component';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { RedactionImportService } from './services/redaction-import.service';
|
||||
import { DossiersDialogService } from '../services/dossiers-dialog.service';
|
||||
import { DossiersDialogService } from './services/dossiers-dialog.service';
|
||||
import { EditDossierDialogComponent } from '../dialogs/edit-dossier-dialog/edit-dossier-dialog.component';
|
||||
import { AddDossierDialogComponent } from '../dialogs/add-dossier-dialog/add-dossier-dialog.component';
|
||||
import { AssignReviewerApproverDialogComponent } from '../dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<div (longPress)="forceReanalysisAction($event)" class="action-buttons" redactionLongPress>
|
||||
<iqser-circle-button
|
||||
(action)="openEditDossierDialog($event, dossier.dossierId)"
|
||||
(action)="openEditDossierDialog($event, dossier.id)"
|
||||
*ngIf="currentUser.isUser"
|
||||
[icon]="currentUser.isManager ? 'iqser:edit' : 'red:info'"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
|
||||
@ -3,7 +3,7 @@ import { PermissionsService } from '@services/permissions.service';
|
||||
import { CircleButtonTypes, List, ScrollableParentView, ScrollableParentViews, StatusBarConfig } from '@iqser/common-ui';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { Dossier, DossierStats, File } from '@red/domain';
|
||||
import { DossiersDialogService } from '../../../dossier/services/dossiers-dialog.service';
|
||||
import { DossiersDialogService } from '../../../dossier/shared/services/dossiers-dialog.service';
|
||||
import { LongPressEvent } from '@shared/directives/long-press.directive';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
@ -44,7 +44,7 @@ export class DossiersListingActionsComponent implements OnChanges {
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.files = this.filesMapService.get(this.dossier.dossierId);
|
||||
this.files = this.filesMapService.get(this.dossier.id);
|
||||
this.displayReanalyseBtn = this.permissionsService.displayReanalyseBtn(this.dossier) && this.analysisForced;
|
||||
}
|
||||
|
||||
|
||||
@ -1,25 +1,24 @@
|
||||
<div>
|
||||
<div *ngIf="stats$ | async as stats">
|
||||
<redaction-simple-doughnut-chart
|
||||
*ngIf="dossiersChartData$ | async as config"
|
||||
[config]="config"
|
||||
[config]="dossiersChartData$ | async"
|
||||
[radius]="80"
|
||||
[strokeWidth]="15"
|
||||
[subtitle]="'dossier-listing.stats.charts.dossiers' | translate: { count: activeDossiersService.all.length }"
|
||||
[subtitle]="'dossier-template-stats.active-dossiers' | translate: { count: stats.numberOfActiveDossiers }"
|
||||
></redaction-simple-doughnut-chart>
|
||||
|
||||
<div *ngIf="activeDossiersService.generalStats$ | async as stats" class="dossier-stats-container">
|
||||
<div class="dossier-stats-container">
|
||||
<div class="dossier-stats-item">
|
||||
<mat-icon svgIcon="red:needs-work"></mat-icon>
|
||||
<div>
|
||||
<div class="heading">{{ stats.totalAnalyzedPages | number }}</div>
|
||||
<div [translateParams]="{ count: stats.totalAnalyzedPages }" [translate]="'dossier-listing.stats.analyzed-pages'"></div>
|
||||
<div class="heading">{{ stats.numberOfPages | number }}</div>
|
||||
<div [translateParams]="{ count: stats.numberOfPages }" [translate]="'dossier-listing.stats.analyzed-pages'"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dossier-stats-item">
|
||||
<mat-icon svgIcon="red:user"></mat-icon>
|
||||
<div>
|
||||
<div class="heading">{{ stats.totalPeople }}</div>
|
||||
<div class="heading">{{ stats.numberOfPeople }}</div>
|
||||
<div translate="dossier-listing.stats.total-people"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -28,10 +27,9 @@
|
||||
|
||||
<div class="right-chart">
|
||||
<redaction-simple-doughnut-chart
|
||||
*ngIf="documentsChartData$ | async as config"
|
||||
[config]="config"
|
||||
[config]="documentsChartData$ | async"
|
||||
[radius]="80"
|
||||
[strokeWidth]="15"
|
||||
[subtitle]="'dossier-listing.stats.charts.total-documents' | translate"
|
||||
[subtitle]="'dossier-template-stats.total-documents' | translate"
|
||||
></redaction-simple-doughnut-chart>
|
||||
</div>
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { FilterService, mapEach } from '@iqser/common-ui';
|
||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { DossierStats, FileCountPerWorkflowStatus, StatusSorter } from '@red/domain';
|
||||
import { workflowFileStatusTranslations } from '../../../../translations/file-status-translations';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TranslateChartService } from '@services/translate-chart.service';
|
||||
import { filter, map, switchMap } from 'rxjs/operators';
|
||||
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
import { DashboardStats } from '@red/domain';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossiers-listing-details',
|
||||
@ -18,59 +15,20 @@ import { DossierStatesMapService } from '@services/entity-services/dossier-state
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DossiersListingDetailsComponent {
|
||||
readonly stats$: Observable<DashboardStats>;
|
||||
readonly documentsChartData$: Observable<DoughnutChartConfig[]>;
|
||||
readonly dossiersChartData$: Observable<DoughnutChartConfig[]>;
|
||||
|
||||
constructor(
|
||||
readonly filterService: FilterService,
|
||||
readonly activeDossiersService: ActiveDossiersService,
|
||||
private readonly _dossierStatsMap: DossierStatsService,
|
||||
private readonly _dashboardStatsService: DashboardStatsService,
|
||||
private readonly _translateChartService: TranslateChartService,
|
||||
private readonly _dossierStatesMapService: DossierStatesMapService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
) {
|
||||
this.documentsChartData$ = this.activeDossiersService.all$.pipe(
|
||||
mapEach(dossier => _dossierStatsMap.watch$(dossier.dossierId)),
|
||||
switchMap(stats$ => combineLatest(stats$)),
|
||||
filter(stats => !stats.some(s => s === undefined)),
|
||||
map(stats => this._toChartData(stats)),
|
||||
const dossierTemplateId: string = this._route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
this.stats$ = this._dashboardStatsService.getEntityChanged$(dossierTemplateId);
|
||||
this.dossiersChartData$ = this.stats$.pipe(
|
||||
map(s => this._translateChartService.translateDossierStates(s.dossiersChartData, dossierTemplateId)),
|
||||
);
|
||||
|
||||
this.dossiersChartData$ = this.activeDossiersService.all$.pipe(map(() => this._toDossierChartData()));
|
||||
}
|
||||
|
||||
private _toDossierChartData(): DoughnutChartConfig[] {
|
||||
const configArray: DoughnutChartConfig[] = this._dossierStatesMapService.stats;
|
||||
const undefinedStateLength =
|
||||
this.activeDossiersService.all.length - configArray.map(v => v.value).reduce((acc, val) => acc + val, 0);
|
||||
configArray.push({
|
||||
value: undefinedStateLength,
|
||||
label: this._translateService.instant('edit-dossier-dialog.general-info.form.dossier-state.placeholder'),
|
||||
color: '#E2E4E9',
|
||||
});
|
||||
|
||||
return configArray;
|
||||
}
|
||||
|
||||
private _toChartData(stats: DossierStats[]) {
|
||||
const chartData: FileCountPerWorkflowStatus = {};
|
||||
stats.forEach(stat => {
|
||||
const statuses: FileCountPerWorkflowStatus = stat.fileCountPerWorkflowStatus;
|
||||
Object.keys(statuses).forEach(status => {
|
||||
chartData[status] = chartData[status] ? (chartData[status] as number) + (statuses[status] as number) : statuses[status];
|
||||
});
|
||||
});
|
||||
|
||||
const documentsChartData = Object.keys(chartData).map(
|
||||
status =>
|
||||
({
|
||||
value: chartData[status],
|
||||
color: status,
|
||||
label: workflowFileStatusTranslations[status],
|
||||
key: status,
|
||||
} as DoughnutChartConfig),
|
||||
);
|
||||
documentsChartData.sort((a, b) => StatusSorter.byStatus(a.key, b.key));
|
||||
return this._translateChartService.translateStatus(documentsChartData);
|
||||
this.documentsChartData$ = this.stats$.pipe(map(s => this._translateChartService.translateWorkflowStatus(s.documentsChartData)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ export class TableItemComponent implements OnChanges {
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.dossier) {
|
||||
this.#ngOnChanges$.next(this.dossier.dossierId);
|
||||
this.#ngOnChanges$.next(this.dossier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,11 +6,11 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { workflowFileStatusTranslations } from '../../translations/file-status-translations';
|
||||
import { dossierMemberChecker, dossierStateChecker, dossierTemplateChecker, RedactionFilterSorter } from '@utils/index';
|
||||
import { dossierMemberChecker, dossierStateChecker, RedactionFilterSorter } from '../../utils';
|
||||
import { workloadTranslations } from '../dossier/translations/workload-translations';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
|
||||
import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service';
|
||||
import { DossiersDialogService } from '../dossier/shared/services/dossiers-dialog.service';
|
||||
|
||||
@Injectable()
|
||||
export class ConfigService {
|
||||
@ -18,9 +18,9 @@ export class ConfigService {
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dossierStatsService: DossierStatsService,
|
||||
private readonly _dossierStatesMapService: DossierStatesMapService,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
) {}
|
||||
|
||||
get tableConfig(): TableColumnConfig<Dossier>[] {
|
||||
@ -38,19 +38,11 @@ export class ConfigService {
|
||||
return this._userService.currentUser;
|
||||
}
|
||||
|
||||
_myDossiersChecker = (dw: Dossier) => dw.ownerId === this._currentUser.id;
|
||||
|
||||
_toApproveChecker = (dw: Dossier) => dw.approverIds.includes(this._currentUser.id);
|
||||
|
||||
_toReviewChecker = (dw: Dossier) => dw.memberIds.includes(this._currentUser.id);
|
||||
|
||||
_otherChecker = (dw: Dossier) => !dw.memberIds.includes(this._currentUser.id);
|
||||
|
||||
buttonsConfig(addDossier: () => void): ButtonConfig[] {
|
||||
buttonsConfig(dossierTemplateId: string): ButtonConfig[] {
|
||||
return [
|
||||
{
|
||||
label: _('dossier-listing.add-new'),
|
||||
action: addDossier,
|
||||
action: () => this._openAddDossierDialog(dossierTemplateId),
|
||||
hide: !this._currentUser.isManager,
|
||||
icon: 'iqser:plus',
|
||||
type: 'primary',
|
||||
@ -63,7 +55,6 @@ export class ConfigService {
|
||||
const allDistinctFileStatus = new Set<string>();
|
||||
const allDistinctPeople = new Set<string>();
|
||||
const allDistinctNeedsWork = new Set<string>();
|
||||
const allDistinctDossierTemplates = new Set<string>();
|
||||
const allDistinctDossierStates = new Set<string>();
|
||||
|
||||
const stateToTemplateMap = new Map<string, string>();
|
||||
@ -72,7 +63,6 @@ export class ConfigService {
|
||||
|
||||
entities?.forEach(entry => {
|
||||
entry.memberIds.forEach(f => allDistinctPeople.add(f));
|
||||
allDistinctDossierTemplates.add(entry.dossierTemplateId);
|
||||
if (entry.dossierStatusId) {
|
||||
allDistinctDossierStates.add(entry.dossierStatusId);
|
||||
stateToTemplateMap.set(entry.dossierStatusId, entry.dossierTemplateId);
|
||||
@ -166,23 +156,6 @@ export class ConfigService {
|
||||
matchAll: true,
|
||||
});
|
||||
|
||||
const dossierTemplateFilters = [...allDistinctDossierTemplates].map(
|
||||
id =>
|
||||
new NestedFilter({
|
||||
id: id,
|
||||
label: this._dossierTemplatesService.find(id)?.name || '-',
|
||||
}),
|
||||
);
|
||||
|
||||
filterGroups.push({
|
||||
slug: 'dossierTemplateFilters',
|
||||
label: this._translateService.instant('filters.dossier-templates'),
|
||||
icon: 'red:template',
|
||||
hide: dossierTemplateFilters.length <= 1,
|
||||
filters: dossierTemplateFilters,
|
||||
checker: dossierTemplateChecker,
|
||||
});
|
||||
|
||||
filterGroups.push({
|
||||
slug: 'quickFilters',
|
||||
filters: this._quickFilters(entities),
|
||||
@ -208,6 +181,18 @@ export class ConfigService {
|
||||
return filterGroups;
|
||||
}
|
||||
|
||||
private _myDossiersChecker = (dw: Dossier) => dw.ownerId === this._currentUser.id;
|
||||
|
||||
private _toApproveChecker = (dw: Dossier) => dw.approverIds.includes(this._currentUser.id);
|
||||
|
||||
private _toReviewChecker = (dw: Dossier) => dw.memberIds.includes(this._currentUser.id);
|
||||
|
||||
private _otherChecker = (dw: Dossier) => !dw.memberIds.includes(this._currentUser.id);
|
||||
|
||||
private _openAddDossierDialog(dossierTemplateId: string): void {
|
||||
this._dialogService.openDialog('addDossier', null, { dossierTemplateId });
|
||||
}
|
||||
|
||||
private _quickFilters(entities: Dossier[]): NestedFilter[] {
|
||||
const myDossiersLabel = this._translateService.instant('dossier-listing.quick-filters.my-dossiers');
|
||||
const filters = [
|
||||
@ -242,12 +227,12 @@ export class ConfigService {
|
||||
}
|
||||
|
||||
private _dossierStatusChecker = (dossier: Dossier, filter: INestedFilter) => {
|
||||
const stats = this._dossierStatsService.get(dossier.dossierId);
|
||||
const stats = this._dossierStatsService.get(dossier.id);
|
||||
return stats?.fileCountPerWorkflowStatus[filter.id];
|
||||
};
|
||||
|
||||
private _annotationFilterChecker = (dossier: Dossier, filter: INestedFilter) => {
|
||||
const stats = this._dossierStatsService.get(dossier.dossierId);
|
||||
const stats = this._dossierStatsService.get(dossier.id);
|
||||
switch (filter.id) {
|
||||
// case 'analysis': {
|
||||
// return stats.reanalysisRequired;
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
<section>
|
||||
<iqser-page-header [buttonConfigs]="buttonConfigs" [helpModeKey]="'dossier'"></iqser-page-header>
|
||||
<iqser-page-header [buttonConfigs]="buttonConfigs" [helpModeKey]="'dossier'">
|
||||
<ng-container slot="beforeFilters">
|
||||
<redaction-dossiers-type-switch></redaction-dossiers-type-switch>
|
||||
</ng-container>
|
||||
</iqser-page-header>
|
||||
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
@ -25,7 +29,7 @@
|
||||
</section>
|
||||
|
||||
<ng-template #needsWorkFilterTemplate let-filter="filter">
|
||||
<redaction-type-filter [dossierTemplateId]="defaultDossierTemplateId" [filter]="filter"></redaction-type-filter>
|
||||
<redaction-type-filter [dossierTemplateId]="dossierTemplateId" [filter]="filter"></redaction-type-filter>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #tableItemTemplate let-dossier="entity">
|
||||
|
||||
@ -2,32 +2,28 @@ import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, Templ
|
||||
import { Dossier } from '@red/domain';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { TranslateChartService } from '@services/translate-chart.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { DossiersDialogService } from '../../dossier/services/dossiers-dialog.service';
|
||||
import { DefaultListingServicesTmp, EntitiesService, ListingComponent, OnAttach, TableComponent } from '@iqser/common-ui';
|
||||
import { ButtonConfig, DefaultListingServices, ListingComponent, OnAttach, TableComponent } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { ConfigService } from '../config.service';
|
||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { DossiersDialogService } from '../../dossier/shared/services/dossiers-dialog.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossiers-listing-screen.component.html',
|
||||
styleUrls: ['./dossiers-listing-screen.component.scss'],
|
||||
providers: [
|
||||
...DefaultListingServicesTmp,
|
||||
{ provide: EntitiesService, useExisting: ActiveDossiersService },
|
||||
{ provide: ListingComponent, useExisting: forwardRef(() => DossiersListingScreenComponent) },
|
||||
],
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossiersListingScreenComponent) }],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DossiersListingScreenComponent extends ListingComponent<Dossier> implements OnInit, OnAttach {
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
readonly tableColumnConfigs = this._configService.tableConfig;
|
||||
readonly tableHeaderLabel = _('dossier-listing.table-header.title');
|
||||
readonly buttonConfigs = this._configService.buttonsConfig(() => this.openAddDossierDialog());
|
||||
readonly buttonConfigs: ButtonConfig[];
|
||||
readonly dossierTemplateId: string;
|
||||
@ViewChild('needsWorkFilterTemplate', {
|
||||
read: TemplateRef,
|
||||
static: true,
|
||||
@ -36,44 +32,37 @@ export class DossiersListingScreenComponent extends ListingComponent<Dossier> im
|
||||
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<Dossier>;
|
||||
|
||||
constructor(
|
||||
private readonly _router: Router,
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _userService: UserService,
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _translateChartService: TranslateChartService,
|
||||
private readonly _configService: ConfigService,
|
||||
private readonly _filesService: FilesService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _router: Router,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
) {
|
||||
super(_injector);
|
||||
this.dossierTemplateId = this._router.routerState.snapshot.root.firstChild.firstChild.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
this._router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||
this.buttonConfigs = this._configService.buttonsConfig(this.dossierTemplateId);
|
||||
}
|
||||
|
||||
get defaultDossierTemplateId(): string {
|
||||
return this._dossierTemplatesService.all[0].id;
|
||||
openAddDossierDialog(): void {
|
||||
this._dialogService.openDialog('addDossier', null, { dossierTemplateId: this.dossierTemplateId });
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.addSubscription = this._activeDossiersService.all$.pipe(tap(() => this._computeAllFilters())).subscribe();
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this._userPreferenceService.saveLastDossierTemplate(this.dossierTemplateId);
|
||||
this.addSubscription = this.entitiesService.all$.pipe(tap(() => this._computeAllFilters())).subscribe();
|
||||
this.addSubscription = this._activeDossiersService.all$
|
||||
.pipe(tap(dossiers => this.entitiesService.setEntities(dossiers.filter(d => d.dossierTemplateId === this.dossierTemplateId))))
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnAttach(): void {
|
||||
this._tableComponent?.scrollToLastIndex();
|
||||
}
|
||||
|
||||
openAddDossierDialog(): void {
|
||||
this._dialogService.openDialog('addDossier', null, null, async (addResponse: { dossier: Dossier; addMembers: boolean }) => {
|
||||
await this._router.navigate([addResponse.dossier.routerLink]);
|
||||
if (addResponse.addMembers) {
|
||||
this._dialogService.openDialog('editDossier', null, {
|
||||
dossierId: addResponse.dossier.dossierId,
|
||||
section: 'members',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _computeAllFilters() {
|
||||
const filterGroups = this._configService.filterGroups(this.entitiesService.all, this._needsWorkFilterTemplate);
|
||||
this.filterService.addFilterGroups(filterGroups);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { DocumentInfoService } from '../../services/document-info.service';
|
||||
import { combineLatest, Observable, switchMap } from 'rxjs';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
|
||||
@ -54,10 +54,7 @@ export class AcceptRecommendationDialogComponent extends BaseDialogComponent imp
|
||||
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
this.possibleDictionaries = await this._dictionaryService.getDictionariesOptions(
|
||||
this._dossier.dossierTemplateId,
|
||||
this._dossier.dossierId,
|
||||
);
|
||||
this.possibleDictionaries = await this._dictionaryService.getDictionariesOptions(this._dossier.dossierTemplateId, this._dossier.id);
|
||||
this.form.patchValue({
|
||||
dictionary: this.possibleDictionaries.find(dict => dict.type === this.data.annotations[0].recommendationType).type,
|
||||
});
|
||||
|
||||
@ -76,10 +76,7 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
|
||||
|
||||
async ngOnInit() {
|
||||
super.ngOnInit();
|
||||
this.possibleDictionaries = await this._dictionaryService.getDictionariesOptions(
|
||||
this._dossier.dossierTemplateId,
|
||||
this._dossier.dossierId,
|
||||
);
|
||||
this.possibleDictionaries = await this._dictionaryService.getDictionariesOptions(this._dossier.dossierTemplateId, this._dossier.id);
|
||||
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this._dossier.dossierTemplateId));
|
||||
this.legalOptions = data.map(lbm => ({
|
||||
legalBasis: lbm.reason,
|
||||
|
||||
@ -427,7 +427,7 @@ export class AnnotationActionsService {
|
||||
value: text,
|
||||
};
|
||||
|
||||
this._processObsAndEmit(this._manualRedactionService.resizeOrSuggestResize([resizeRequest], data.dossier.dossierId, fileId));
|
||||
this._processObsAndEmit(this._manualRedactionService.resizeOrSuggestResize([resizeRequest], data.dossier.id, fileId));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, merge, Observable } from 'rxjs';
|
||||
import { shareLast } from '@iqser/common-ui';
|
||||
import { filter, map, startWith, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { File, IFileAttributeConfig } from '@red/domain';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { combineLatest, firstValueFrom, from, merge, Observable, of, pairwise, Subject, switchMap } from 'rxjs';
|
||||
import { Dossier, File } from '@red/domain';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { boolFactory } from '@iqser/common-ui';
|
||||
@ -39,8 +39,9 @@ export class FilePreviewStateService {
|
||||
private readonly _filesService: FilesService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _fileManagementService: FileManagementService,
|
||||
private readonly _router: Router,
|
||||
) {
|
||||
const dossiersService = dossiersServiceResolver(_injector);
|
||||
const dossiersService = dossiersServiceResolver(_injector, _router);
|
||||
|
||||
this.fileId = route.snapshot.paramMap.get(FILE_ID);
|
||||
this.dossierId = route.snapshot.paramMap.get(DOSSIER_ID);
|
||||
@ -81,7 +82,7 @@ export class FilePreviewStateService {
|
||||
#dossierFilesChange$() {
|
||||
return this._dossiersService.dossierFileChanges$.pipe(
|
||||
filter(dossierId => dossierId === this.dossierId),
|
||||
switchMap(dossierId => this._filesService.loadAll(dossierId, this._dossiersService.routerPath)),
|
||||
switchMap(dossierId => this._filesService.loadAll(dossierId)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -20,10 +20,9 @@ import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { Dossier, IMatchedDocument, ISearchListItem, ISearchResponse } from '@red/domain';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { PlatformSearchService } from '@services/entity-services/platform-search.service';
|
||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
|
||||
import { FeaturesService } from '@services/features.service';
|
||||
import { DOSSIERS_ARCHIVE } from '@utils/constants';
|
||||
import { DossiersCacheService } from '../../../services/dossiers/dossiers-cache.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './search-screen.component.html',
|
||||
@ -66,8 +65,7 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _archivedDossiersService: ArchivedDossiersService,
|
||||
private readonly _dossiersCacheService: DossiersCacheService,
|
||||
readonly routerHistoryService: RouterHistoryService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
@ -83,13 +81,12 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
|
||||
const checked = dossierIds.includes(id);
|
||||
return new NestedFilter({ id, label: dossierName, checked });
|
||||
};
|
||||
const allDossiers = [...this._activeDossiersService.all, ...this._archivedDossiersService.all];
|
||||
const dossierNameFilter: IFilterGroup = {
|
||||
slug: 'dossiers',
|
||||
label: this._translateService.instant('search-screen.filters.by-dossier'),
|
||||
filterceptionPlaceholder: this._translateService.instant('search-screen.filters.search-placeholder'),
|
||||
icon: 'red:folder',
|
||||
filters: allDossiers.map(dossierToFilter),
|
||||
filters: this._dossiersCacheService.all.map(dossierToFilter),
|
||||
checker: keyChecker('dossierId'),
|
||||
};
|
||||
this.filterService.addFilterGroups([dossierNameFilter]);
|
||||
@ -166,10 +163,11 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
|
||||
}: IMatchedDocument): ISearchListItem {
|
||||
const file = this._filesMapService.get(dossierId, fileId);
|
||||
if (!file) {
|
||||
console.error('Missing file');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const dossier = (dossierArchived ? this._archivedDossiersService : this._activeDossiersService).find(dossierId);
|
||||
const dossier = this._dossiersCacheService.get(dossierId);
|
||||
|
||||
return {
|
||||
id: fileId,
|
||||
|
||||
@ -6,7 +6,7 @@ import { Dictionary, DICTIONARY_TYPE_KEY_MAP, DictionaryType, Dossier, DossierTe
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { EditorComponent } from '@shared/components/editor/editor.component';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
|
||||
@ -82,7 +82,7 @@ export class DictionaryManagerComponent implements OnChanges {
|
||||
return;
|
||||
}
|
||||
|
||||
this._onDossierChanged(dossier.dossierTemplateId, dossier.dossierId)
|
||||
this._onDossierChanged(dossier.dossierTemplateId, dossier.id)
|
||||
.pipe(take(1))
|
||||
.subscribe(entries => {
|
||||
this.diffEditorText = entries;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { Dossier, DossierStats } from '@red/domain';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { List } from '@iqser/common-ui';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
<a [routerLinkActive]="'active'" [routerLink]="['..', DOSSIERS_ROUTE]" class="red-tab">
|
||||
{{ 'dossiers-type-switch.active' | translate }}
|
||||
</a>
|
||||
|
||||
<a [routerLinkActive]="'active'" [routerLink]="['..', ARCHIVE_ROUTE]" class="red-tab">
|
||||
{{ 'dossiers-type-switch.archive' | translate }}
|
||||
</a>
|
||||
|
||||
<div class="separator"></div>
|
||||
@ -0,0 +1,11 @@
|
||||
:host {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.separator {
|
||||
background-color: var(--iqser-separator);
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
margin-left: 8px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { ARCHIVE_ROUTE, DOSSIERS_ROUTE } from '@utils/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossiers-type-switch',
|
||||
templateUrl: './dossiers-type-switch.component.html',
|
||||
styleUrls: ['./dossiers-type-switch.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class DossiersTypeSwitchComponent {
|
||||
readonly DOSSIERS_ROUTE = DOSSIERS_ROUTE;
|
||||
readonly ARCHIVE_ROUTE = ARCHIVE_ROUTE;
|
||||
}
|
||||
@ -47,6 +47,7 @@
|
||||
> div {
|
||||
border-radius: 4px;
|
||||
padding: 3px 8px;
|
||||
width: 100%;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Input, OnChanges, OnInit } from '@angular/core';
|
||||
import { Component, Input, OnChanges, OnInit, Optional } from '@angular/core';
|
||||
import { Color } from '@red/domain';
|
||||
import { FilterService, INestedFilter } from '@iqser/common-ui';
|
||||
import { Observable, of } from 'rxjs';
|
||||
@ -30,17 +30,20 @@ export class SimpleDoughnutChartComponent implements OnChanges, OnInit {
|
||||
filtersEnabled: boolean;
|
||||
|
||||
chartData: any[] = [];
|
||||
perimeter: number;
|
||||
cx = 0;
|
||||
cy = 0;
|
||||
size = 0;
|
||||
|
||||
filters$: Observable<INestedFilter[]>;
|
||||
|
||||
constructor(readonly filterService: FilterService) {
|
||||
this.filterService.filterGroups$.subscribe(() => {
|
||||
this.filtersEnabled = !!this.filterService.filterGroups.find(g => g.slug === this.filterKey);
|
||||
});
|
||||
constructor(@Optional() readonly filterService: FilterService) {
|
||||
if (filterService) {
|
||||
this.filterService.filterGroups$.subscribe(() => {
|
||||
this.filtersEnabled = !!this.filterService.filterGroups.find(g => g.slug === this.filterKey);
|
||||
});
|
||||
} else {
|
||||
this.filtersEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
get circumference(): number {
|
||||
@ -56,7 +59,7 @@ export class SimpleDoughnutChartComponent implements OnChanges, OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.filters$ = this.filterService.getFilterModels$(this.filterKey) ?? of([]);
|
||||
this.filters$ = (this.filtersEnabled && this.filterService.getFilterModels$(this.filterKey)) ?? of([]);
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
@ -67,7 +70,7 @@ export class SimpleDoughnutChartComponent implements OnChanges, OnInit {
|
||||
}
|
||||
|
||||
filterChecked$(key: string): Observable<boolean> {
|
||||
return this.filters$.pipe(map(all => all?.find(e => e.id === key)?.checked));
|
||||
return this.filtersEnabled ? this.filters$.pipe(map(all => all?.find(e => e.id === key)?.checked)) : of(false);
|
||||
}
|
||||
|
||||
calculateChartData() {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
|
||||
import { CircleButtonTypes, List } from '@iqser/common-ui';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DossiersDialogService } from '../../../dossier/services/dossiers-dialog.service';
|
||||
import { DossiersDialogService } from '../../../dossier/shared/services/dossiers-dialog.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-team-members',
|
||||
|
||||
@ -31,6 +31,9 @@ import { FileNameColumnComponent } from '@shared/components/file-name-column/fil
|
||||
import { DossierNameColumnComponent } from '@shared/components/dossier-name-column/dossier-name-column.component';
|
||||
import { MAT_DATE_FORMATS } from '@angular/material/core';
|
||||
import { DragDropFileUploadDirective } from '@shared/directives/drag-drop-file-upload.directive';
|
||||
import { DossiersTypeSwitchComponent } from '@shared/components/dossiers-type-switch/dossiers-type-switch.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
const buttons = [FileDownloadBtnComponent, UserButtonComponent];
|
||||
|
||||
@ -51,6 +54,7 @@ const components = [
|
||||
DossierNameColumnComponent,
|
||||
FileStatsComponent,
|
||||
FileNameColumnComponent,
|
||||
DossiersTypeSwitchComponent,
|
||||
|
||||
...buttons,
|
||||
];
|
||||
@ -61,7 +65,7 @@ const modules = [MatConfigModule, ScrollingModule, IconsModule, FormsModule, Rea
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components, ...utils, EditorComponent],
|
||||
imports: [CommonModule, ...modules, MonacoEditorModule],
|
||||
imports: [CommonModule, ...modules, MonacoEditorModule, TranslateModule, RouterModule],
|
||||
exports: [...modules, ...components, ...utils],
|
||||
providers: [
|
||||
{
|
||||
|
||||
@ -42,7 +42,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
||||
super(_injector, 'upload');
|
||||
const fileFetch$ = this._fetchFiles$.pipe(
|
||||
throttleTime(250),
|
||||
switchMap(dossierId => this._filesService.loadAll(dossierId, 'dossiers')),
|
||||
switchMap(dossierId => this._filesService.loadAll(dossierId)),
|
||||
);
|
||||
this._subscriptions.add(fileFetch$.subscribe());
|
||||
const interval$ = interval(2500).pipe(tap(() => this._handleUploads()));
|
||||
|
||||
@ -6,10 +6,11 @@ import { filter, pluck } from 'rxjs/operators';
|
||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BreadcrumbTypes } from '@red/domain';
|
||||
import { DOSSIER_ID, DOSSIERS_ARCHIVE, FILE_ID } from '@utils/constants';
|
||||
import { DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, FILE_ID } from '@utils/constants';
|
||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||
import { dossiersServiceResolver } from '@services/entity-services/dossiers.service.provider';
|
||||
import { FeaturesService } from '@services/features.service';
|
||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||
|
||||
export type RouterLinkActiveOptions = { exact: boolean } | IsActiveMatchOptions;
|
||||
export type BreadcrumbDisplayType = 'text' | 'dropdown';
|
||||
@ -40,6 +41,7 @@ export class BreadcrumbsService {
|
||||
private readonly _router: Router,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
private readonly _dashboardStatsService: DashboardStatsService,
|
||||
private readonly _featuresService: FeaturesService,
|
||||
) {
|
||||
this.breadcrumbs$ = this._store$.asObservable();
|
||||
@ -47,7 +49,7 @@ export class BreadcrumbsService {
|
||||
this._router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
|
||||
const root = this._router.routerState.snapshot.root;
|
||||
this._clear();
|
||||
this._addBreadcrumbs(root.firstChild);
|
||||
this._addBreadcrumbs(root);
|
||||
});
|
||||
}
|
||||
|
||||
@ -56,26 +58,15 @@ export class BreadcrumbsService {
|
||||
}
|
||||
|
||||
private get _dossiersService(): DossiersService {
|
||||
return dossiersServiceResolver(this._injector);
|
||||
return dossiersServiceResolver(this._injector, this._router);
|
||||
}
|
||||
|
||||
private get _mainBreadcrumb(): Breadcrumb {
|
||||
private get _dashboardBreadcrumb(): Breadcrumb {
|
||||
return {
|
||||
name$: of(this._translateService.instant('top-bar.navigation-items.dossiers')),
|
||||
name$: of(this._translateService.instant('top-bar.navigation-items.dashboard')),
|
||||
type: 'text' as BreadcrumbDisplayType,
|
||||
options: {
|
||||
routerLink: ['/main', 'dossiers'],
|
||||
routerLinkActiveOptions: { exact: true },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private get _archiveBreadcrumb(): Breadcrumb {
|
||||
return {
|
||||
name$: of(this._translateService.instant('top-bar.navigation-items.archived-dossiers')),
|
||||
type: 'text' as BreadcrumbDisplayType,
|
||||
options: {
|
||||
routerLink: ['/main', 'archive'],
|
||||
routerLink: ['/main', 'dashboard'],
|
||||
routerLinkActiveOptions: { exact: true },
|
||||
},
|
||||
};
|
||||
@ -89,81 +80,92 @@ export class BreadcrumbsService {
|
||||
this._store$.next([]);
|
||||
}
|
||||
|
||||
private _addBreadcrumbs(route: ActivatedRouteSnapshot) {
|
||||
private _addBreadcrumbs(route: ActivatedRouteSnapshot, params: Record<string, string> = {}) {
|
||||
if (route.firstChild) {
|
||||
this._addBreadcrumbs(route.firstChild);
|
||||
this._addBreadcrumbs(route.firstChild, { ...params, ...route.params });
|
||||
return;
|
||||
}
|
||||
|
||||
const breadcrumbs = route.data.breadcrumbs || [];
|
||||
|
||||
if (breadcrumbs.length === 1 && this._featuresService.isEnabled(DOSSIERS_ARCHIVE)) {
|
||||
if (breadcrumbs[0] === BreadcrumbTypes.main) {
|
||||
this._addMainDropdownBreadcrumb('active');
|
||||
if (breadcrumbs[0] === BreadcrumbTypes.dossierTemplate) {
|
||||
this._addDossierTemplateDropdown(params);
|
||||
return;
|
||||
}
|
||||
if (breadcrumbs[0] === BreadcrumbTypes.archive) {
|
||||
this._addMainDropdownBreadcrumb('archived');
|
||||
if (breadcrumbs[0] === BreadcrumbTypes.dashboard) {
|
||||
this._append(this._dashboardBreadcrumb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const breadcrumb of breadcrumbs) {
|
||||
switch (breadcrumb) {
|
||||
case BreadcrumbTypes.main:
|
||||
this._append(this._mainBreadcrumb);
|
||||
break;
|
||||
case BreadcrumbTypes.archive:
|
||||
this._append(this._archiveBreadcrumb);
|
||||
case BreadcrumbTypes.dossierTemplate:
|
||||
this._append(this._dossierTemplateBreadcrumb(params));
|
||||
break;
|
||||
case BreadcrumbTypes.dossier:
|
||||
this._addDossierBreadcrumb(route);
|
||||
this._addDossierBreadcrumb(params);
|
||||
break;
|
||||
case BreadcrumbTypes.file:
|
||||
this._addFileBreadcrumb(route);
|
||||
this._addFileBreadcrumb(params);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _addMainDropdownBreadcrumb(type: 'active' | 'archived'): void {
|
||||
const activeDossiers: Breadcrumb = this._mainBreadcrumb;
|
||||
const archivedDossiers: Breadcrumb = this._archiveBreadcrumb;
|
||||
const activeOption = type === 'active' ? activeDossiers : archivedDossiers;
|
||||
private _addDossierTemplateDropdown(params: Record<string, string>): void {
|
||||
const breadcrumbs = this._dashboardStatsService.all
|
||||
.filter(dt => !dt.isEmpty)
|
||||
.map(dt => this._dossierTemplateBreadcrumb({ dossierTemplateId: dt.id }));
|
||||
|
||||
const activeOption = breadcrumbs.find(b => b.options.routerLink[1] === params[DOSSIER_TEMPLATE_ID]);
|
||||
|
||||
this._append({
|
||||
name$: activeOption.name$,
|
||||
type: 'dropdown' as BreadcrumbDisplayType,
|
||||
options: {
|
||||
options: [activeDossiers, archivedDossiers],
|
||||
options: breadcrumbs,
|
||||
activeOption,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _addDossierBreadcrumb(route: ActivatedRouteSnapshot): void {
|
||||
private _dossierTemplateBreadcrumb(params: Record<string, string>): Breadcrumb {
|
||||
const dossierTemplateId: string = params[DOSSIER_TEMPLATE_ID];
|
||||
return {
|
||||
name$: this._dashboardStatsService.getEntityChanged$(dossierTemplateId).pipe(pluck('name')),
|
||||
type: 'text' as BreadcrumbDisplayType,
|
||||
options: {
|
||||
routerLink: ['/main', dossierTemplateId, this._dossiersService.routerPath],
|
||||
routerLinkActiveOptions: { exact: true },
|
||||
clamp: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private _addDossierBreadcrumb(params: Record<string, string>): void {
|
||||
const dossiersService = this._dossiersService;
|
||||
const dossierId = route.paramMap.get(DOSSIER_ID);
|
||||
const dossierId: string = params[DOSSIER_ID];
|
||||
this._append({
|
||||
name$: dossiersService.getEntityChanged$(dossierId).pipe(pluck('dossierName')),
|
||||
type: 'text' as BreadcrumbDisplayType,
|
||||
options: {
|
||||
routerLink: ['/main', dossiersService.routerPath, dossierId],
|
||||
routerLink: [dossiersService.find(dossierId).routerLink],
|
||||
routerLinkActiveOptions: { exact: true },
|
||||
clamp: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _addFileBreadcrumb(route: ActivatedRouteSnapshot): void {
|
||||
const dossierId = route.paramMap.get(DOSSIER_ID);
|
||||
const fileId = route.paramMap.get(FILE_ID);
|
||||
const dossiersService = this._dossiersService;
|
||||
private _addFileBreadcrumb(params: Record<string, string>): void {
|
||||
const dossierId: string = params[DOSSIER_ID];
|
||||
const fileId: string = params[FILE_ID];
|
||||
this._append({
|
||||
name$: this._filesMapService.watch$(dossierId, fileId).pipe(pluck('filename')),
|
||||
type: 'text' as BreadcrumbDisplayType,
|
||||
options: {
|
||||
routerLink: ['/main', dossiersService.routerPath, dossierId, 'file', fileId],
|
||||
routerLink: [this._filesMapService.get(dossierId, fileId).routerLink],
|
||||
clamp: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import { EntitiesService, mapEach } from '@iqser/common-ui';
|
||||
import { DashboardStats, IDashboardStats } from '@red/domain';
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, switchMap, tap } from 'rxjs/operators';
|
||||
import { DossierStatesService } from '@services/entity-services/dossier-states.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DashboardStatsService extends EntitiesService<DashboardStats, IDashboardStats> {
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _dossierStatesService: DossierStatesService,
|
||||
private readonly _logger: NGXLogger,
|
||||
) {
|
||||
super(_injector, DashboardStats, 'dossier-template/stats');
|
||||
}
|
||||
|
||||
loadAll(): Observable<DashboardStats[]> {
|
||||
return this.getAll(this._defaultModelPath).pipe(
|
||||
mapEach(entity => new DashboardStats(entity)),
|
||||
switchMap(entities => this._dossierStatesService.loadAllForAllTemplates().pipe(map(() => entities))),
|
||||
tap(entities => entities.sort((a, b) => (a.numberOfActiveDossiers > 0 && b.numberOfActiveDossiers === 0 ? -1 : 1))),
|
||||
tap(entities => this.setEntities(entities)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -2,12 +2,12 @@ import { EntitiesService, List, mapEach, RequiredParam, Toaster, Validate } from
|
||||
import { DossierTemplate, IDossierTemplate } from '@red/domain';
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { forkJoin, Observable, of } from 'rxjs';
|
||||
import { FileAttributesService } from './file-attributes.service';
|
||||
import { FileAttributesService } from '../entity-services/file-attributes.service';
|
||||
import { catchError, mapTo, switchMap, tap } from 'rxjs/operators';
|
||||
import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service';
|
||||
import { DossierTemplateStatsService } from '../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 '@services/entity-services/dictionary.service';
|
||||
import { DictionaryService } from '../entity-services/dictionary.service';
|
||||
|
||||
const DOSSIER_TEMPLATE_CONFLICT_MSG = _('dossier-templates-listing.error.conflict');
|
||||
const GENERIC_MSG = _('dossier-templates-listing.error.generic');
|
||||
@ -1,21 +1,25 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { switchMap, tap } from 'rxjs/operators';
|
||||
import { timer } from 'rxjs';
|
||||
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
||||
import { CHANGED_CHECK_INTERVAL, DOSSIERS_ROUTE } from '@utils/constants';
|
||||
import { DossiersService } from './dossiers.service';
|
||||
|
||||
export interface IDossiersStats {
|
||||
totalPeople: number;
|
||||
totalAnalyzedPages: number;
|
||||
}
|
||||
import { Observable, timer } from 'rxjs';
|
||||
import { switchMap, tap } from 'rxjs/operators';
|
||||
import { Dossier } from '@red/domain';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ActiveDossiersService extends DossiersService {
|
||||
constructor(protected readonly _injector: Injector) {
|
||||
super(_injector, 'dossier', 'dossiers');
|
||||
private _initializedRefresh = false;
|
||||
|
||||
constructor(protected readonly _injector: Injector) {
|
||||
super(_injector, 'dossier', DOSSIERS_ROUTE);
|
||||
}
|
||||
|
||||
initializeRefresh() {
|
||||
if (this._initializedRefresh) {
|
||||
return;
|
||||
}
|
||||
this._initializedRefresh = true;
|
||||
timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL)
|
||||
.pipe(
|
||||
switchMap(() => this.loadOnlyChanged()),
|
||||
@ -24,7 +28,8 @@ export class ActiveDossiersService extends DossiersService {
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
getCountWithState(dossierStatusId: string): number {
|
||||
return this.all.filter(dossier => dossier.dossierStatusId === dossierStatusId).length;
|
||||
loadAll(): Observable<Dossier[]> {
|
||||
this.initializeRefresh();
|
||||
return super.loadAll();
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import { ActiveDossiersService } from './active-dossiers.service';
|
||||
import { DossiersService } from './dossiers.service';
|
||||
import { FilesMapService } from '../entity-services/files-map.service';
|
||||
import { FeaturesService } from '@services/features.service';
|
||||
import { DOSSIERS_ARCHIVE } from '@utils/constants';
|
||||
import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE } from '@utils/constants';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ArchivedDossiersService extends DossiersService {
|
||||
@ -17,7 +17,7 @@ export class ArchivedDossiersService extends DossiersService {
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
private readonly _featuresService: FeaturesService,
|
||||
) {
|
||||
super(_injector, 'archived-dossiers', 'archive');
|
||||
super(_injector, 'archived-dossiers', ARCHIVE_ROUTE);
|
||||
}
|
||||
|
||||
archive(dossiers: Dossier[]): Observable<unknown> {
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
import { EventEmitter, Injectable } from '@angular/core';
|
||||
import { ActiveDossiersService } from './active-dossiers.service';
|
||||
import { ArchivedDossiersService } from './archived-dossiers.service';
|
||||
import { firstValueFrom, forkJoin, merge } from 'rxjs';
|
||||
import { map, skip, take } from 'rxjs/operators';
|
||||
import { Dossier } from '@red/domain';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DossiersCacheService {
|
||||
readonly changed$ = new EventEmitter<void>();
|
||||
private _dossiers: Dossier[] = JSON.parse(localStorage.getItem('dossiers')) || [];
|
||||
|
||||
constructor(
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _archivedDossiersService: ArchivedDossiersService,
|
||||
) {
|
||||
// Skip 1 to avoid clearing the cache when the dossier services are initialized
|
||||
merge(_activeDossiersService.all$.pipe(skip(1)), _archivedDossiersService.all$.pipe(skip(1))).subscribe(() => {
|
||||
this.set();
|
||||
});
|
||||
}
|
||||
|
||||
get empty(): boolean {
|
||||
return !localStorage.getItem('dossiers');
|
||||
}
|
||||
|
||||
get all(): Dossier[] {
|
||||
return this._dossiers;
|
||||
}
|
||||
|
||||
async load(): Promise<void> {
|
||||
await firstValueFrom(
|
||||
forkJoin([this._activeDossiersService.loadAll().pipe(take(1)), this._archivedDossiersService.loadAll().pipe(take(1))]).pipe(
|
||||
map(list => list.flat()),
|
||||
),
|
||||
);
|
||||
this.set();
|
||||
}
|
||||
|
||||
set(): void {
|
||||
const dossiers = [this._activeDossiersService.all, this._archivedDossiersService.all].flat();
|
||||
this._dossiers = dossiers;
|
||||
localStorage.setItem('dossiers', JSON.stringify(dossiers));
|
||||
this.changed$.emit();
|
||||
}
|
||||
|
||||
get(dossierId: string) {
|
||||
return this._dossiers.find(dossier => dossier.id === dossierId);
|
||||
}
|
||||
}
|
||||
@ -1,23 +1,21 @@
|
||||
import { EntitiesService, List, mapEach, QueryParam, RequiredParam, shareLast, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService, List, mapEach, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { Dossier, DossierStats, IDossier, IDossierChanges, IDossierRequest } from '@red/domain';
|
||||
import { combineLatest, forkJoin, Observable, of, Subject, throwError } from 'rxjs';
|
||||
import { forkJoin, Observable, of, Subject, throwError } from 'rxjs';
|
||||
import { catchError, filter, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { Injector } from '@angular/core';
|
||||
import { DossierStatesService } from '../entity-services/dossier-states.service';
|
||||
import { DossierStatsService } from './dossier-stats.service';
|
||||
import { IDossiersStats } from './active-dossiers.service';
|
||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||
|
||||
const CONFLICT_MSG = _('add-dossier-dialog.errors.dossier-already-exists');
|
||||
const GENERIC_MSG = _('add-dossier-dialog.errors.generic');
|
||||
|
||||
export abstract class DossiersService extends EntitiesService<Dossier, IDossier> {
|
||||
readonly dossierFileChanges$ = new Subject<string>();
|
||||
readonly generalStats$ = this.all$.pipe(switchMap(entities => this.#generalStats$(entities)));
|
||||
protected readonly _dossierStatsService = this._injector.get(DossierStatsService);
|
||||
protected readonly _dossierStateService = this._injector.get(DossierStatesService);
|
||||
protected readonly _dashboardStatsService = this._injector.get(DashboardStatsService);
|
||||
protected readonly _toaster = this._injector.get(Toaster);
|
||||
protected readonly _logger = this._injector.get(NGXLogger);
|
||||
|
||||
@ -61,10 +59,10 @@ export abstract class DossiersService extends EntitiesService<Dossier, IDossier>
|
||||
loadAll(): Observable<Dossier[]> {
|
||||
const dossierIds = (dossiers: Dossier[]) => dossiers.map(d => d.id);
|
||||
return this.getAll().pipe(
|
||||
mapEach(entity => new Dossier(entity, this.routerPath)),
|
||||
mapEach(entity => new Dossier(entity)),
|
||||
/* Load stats before updating entities */
|
||||
switchMap(dossiers => this._dossierStatsService.getFor(dossierIds(dossiers)).pipe(map(() => dossiers))),
|
||||
switchMap(dossiers => this._dossierStateService.loadAllForAllTemplates().pipe(map(() => dossiers))),
|
||||
switchMap(dossiers => this._dashboardStatsService.loadAll().pipe(map(() => dossiers))),
|
||||
tap(dossiers => this.setEntities(dossiers)),
|
||||
);
|
||||
}
|
||||
@ -81,33 +79,9 @@ export abstract class DossiersService extends EntitiesService<Dossier, IDossier>
|
||||
private _load(id: string): Observable<DossierStats[]> {
|
||||
const queryParams: List<QueryParam> = [{ key: 'includeArchived', value: this._path === 'archived-dossiers' }];
|
||||
return super._getOne([id], this._defaultModelPath, queryParams).pipe(
|
||||
map(entity => new Dossier(entity, 'dossiers')),
|
||||
map(entity => new Dossier(entity)),
|
||||
tap(dossier => this.replace(dossier)),
|
||||
switchMap(dossier => this._dossierStatsService.getFor([dossier.dossierId])),
|
||||
);
|
||||
}
|
||||
|
||||
#computeStats(entities: List<Dossier>): IDossiersStats {
|
||||
let totalAnalyzedPages = 0;
|
||||
const totalPeople = new Set<string>();
|
||||
|
||||
entities.forEach(dossier => {
|
||||
dossier.memberIds?.forEach(m => totalPeople.add(m));
|
||||
totalAnalyzedPages += this._dossierStatsService.get(dossier.dossierId).numberOfPages;
|
||||
});
|
||||
|
||||
return {
|
||||
totalPeople: totalPeople.size,
|
||||
totalAnalyzedPages,
|
||||
};
|
||||
}
|
||||
|
||||
#generalStats$(entities: List<Dossier>): Observable<IDossiersStats> {
|
||||
const stats$ = entities.map(entity => this._dossierStatsService.watch$(entity.dossierId));
|
||||
return combineLatest(stats$).pipe(
|
||||
filter(stats => stats.every(s => !!s)),
|
||||
map(() => this.#computeStats(entities)),
|
||||
shareLast(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { Injectable, Injector } from '@angular/core';
|
||||
import { EntitiesService, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { DossierState, IDossierState } from '@red/domain';
|
||||
import { EMPTY, forkJoin, Observable, switchMap } from 'rxjs';
|
||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { catchError, defaultIfEmpty, tap } from 'rxjs/operators';
|
||||
import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service';
|
||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Router } from '@angular/router';
|
||||
import { Injector, ProviderToken } from '@angular/core';
|
||||
import { DossiersService } from '../dossiers/dossiers.service';
|
||||
|
||||
/** Usage in components or services is only allowed in guards or in constructors.
|
||||
* Otherwise, it causes errors on navigation to other screens if the component is reused. */
|
||||
export const dossiersServiceResolver = (injector: Injector) => {
|
||||
let route: ActivatedRoute = injector.get<ActivatedRoute>(ActivatedRoute);
|
||||
export const dossiersServiceResolver = (injector: Injector, router: Router) => {
|
||||
let route = router.routerState.root;
|
||||
while (route.firstChild) {
|
||||
route = route.firstChild;
|
||||
}
|
||||
@ -16,5 +16,5 @@ export const dossiersServiceResolver = (injector: Injector) => {
|
||||
export const dossiersServiceProvider = {
|
||||
provide: DossiersService,
|
||||
useFactory: dossiersServiceResolver,
|
||||
deps: [Injector],
|
||||
deps: [Injector, Router],
|
||||
};
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { GenericService, HeadersConfiguration, IRouterPath, List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, HeadersConfiguration, List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
|
||||
import { IPageRotationRequest } from '@red/domain';
|
||||
import { File, IPageRotationRequest } from '@red/domain';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@ -20,10 +20,9 @@ export class FileManagementService extends GenericService<unknown> {
|
||||
}
|
||||
|
||||
@Validate()
|
||||
delete(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string) {
|
||||
delete(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string) {
|
||||
const fileIds = files.map(f => f.id);
|
||||
const routerPath: string = files[0].routerPath;
|
||||
return super._post(fileIds, `delete/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId, routerPath)));
|
||||
return super._post(fileIds, `delete/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
|
||||
@ -12,11 +12,7 @@ export class FilesMapService extends EntitiesMapService<File, IFile> {
|
||||
replaceFiles(files: File[], property: keyof IFile, generateValue: Function) {
|
||||
const newFiles = files.map(
|
||||
file =>
|
||||
new File(
|
||||
{ ...file, [property]: generateValue(file[property]), lastUpdated: new Date().toISOString() },
|
||||
file.reviewerName,
|
||||
file.routerPath,
|
||||
),
|
||||
new File({ ...file, [property]: generateValue(file[property]), lastUpdated: new Date().toISOString() }, file.reviewerName),
|
||||
);
|
||||
this.replace(newFiles);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { EntitiesService, IRouterPath, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { File, IFile } from '@red/domain';
|
||||
import { Observable } from 'rxjs';
|
||||
import { UserService } from '../user.service';
|
||||
@ -23,9 +23,9 @@ export class FilesService extends EntitiesService<File, IFile> {
|
||||
}
|
||||
|
||||
/** Reload dossier files + stats. */
|
||||
loadAll(dossierId: string, routerPath: string) {
|
||||
loadAll(dossierId: string) {
|
||||
const files$ = this.getFor(dossierId).pipe(
|
||||
mapEach(file => new File(file, this._userService.getNameForId(file.assignee), routerPath)),
|
||||
mapEach(file => new File(file, this._userService.getNameForId(file.assignee))),
|
||||
tap(() => this._logger.info('[FILE] Loaded')),
|
||||
);
|
||||
const loadStats$ = files$.pipe(switchMap(files => this._dossierStatsService.getFor([dossierId]).pipe(map(() => files))));
|
||||
@ -34,7 +34,7 @@ export class FilesService extends EntitiesService<File, IFile> {
|
||||
|
||||
reload(dossierId: string, file: File): Observable<boolean> {
|
||||
return super._getOne([dossierId, file.id]).pipe(
|
||||
map(_file => new File(_file, this._userService.getNameForId(_file.assignee), file.routerPath)),
|
||||
map(_file => new File(_file, this._userService.getNameForId(_file.assignee))),
|
||||
switchMap(_file => this._dossierStatsService.getFor([dossierId]).pipe(map(() => _file))),
|
||||
map(_file => this._filesMapService.replace([_file])),
|
||||
tap(() => this._logger.info('[FILE] Reloaded')),
|
||||
@ -42,56 +42,46 @@ export class FilesService extends EntitiesService<File, IFile> {
|
||||
}
|
||||
|
||||
@Validate()
|
||||
setUnassigned(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string) {
|
||||
setUnassigned(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string) {
|
||||
const url = `${this._defaultModelPath}/set-assignee/${dossierId}/bulk`;
|
||||
const fileIds = files.map(f => f.id);
|
||||
const routerPath: string = files[0].routerPath;
|
||||
return this._post<unknown>(fileIds, url).pipe(switchMap(() => this.loadAll(dossierId, routerPath)));
|
||||
return this._post<unknown>(fileIds, url).pipe(switchMap(() => this.loadAll(dossierId)));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
setToNewFor(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string) {
|
||||
setToNewFor(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string) {
|
||||
const url = `${this._defaultModelPath}/new/${dossierId}/bulk`;
|
||||
const fileIds = files.map(f => f.id);
|
||||
const routerPath: string = files[0].routerPath;
|
||||
return this._post<unknown>(fileIds, url).pipe(switchMap(() => this.loadAll(dossierId, routerPath)));
|
||||
return this._post<unknown>(fileIds, url).pipe(switchMap(() => this.loadAll(dossierId)));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
setUnderApprovalFor(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string, assigneeId: string) {
|
||||
setUnderApprovalFor(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string, assigneeId: string) {
|
||||
const url = `${this._defaultModelPath}/under-approval/${dossierId}/bulk`;
|
||||
const fileIds = files.map(f => f.id);
|
||||
const routerPath: string = files[0].routerPath;
|
||||
return this._post<unknown>(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(
|
||||
switchMap(() => this.loadAll(dossierId, routerPath)),
|
||||
);
|
||||
return this._post<unknown>(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(switchMap(() => this.loadAll(dossierId)));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
setReviewerFor(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string, assigneeId: string) {
|
||||
setReviewerFor(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string, assigneeId: string) {
|
||||
const url = `${this._defaultModelPath}/under-review/${dossierId}/bulk`;
|
||||
const fileIds = files.map(f => f.id);
|
||||
const routerPath: string = files[0].routerPath;
|
||||
return this._post<unknown>(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(
|
||||
switchMap(() => this.loadAll(dossierId, routerPath)),
|
||||
);
|
||||
return this._post<unknown>(fileIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(switchMap(() => this.loadAll(dossierId)));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
setApprovedFor(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string) {
|
||||
setApprovedFor(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string) {
|
||||
const fileIds = files.map(f => f.id);
|
||||
const routerPath: string = files[0].routerPath;
|
||||
return this._post<unknown>(fileIds, `${this._defaultModelPath}/approved/${dossierId}/bulk`).pipe(
|
||||
switchMap(() => this.loadAll(dossierId, routerPath)),
|
||||
switchMap(() => this.loadAll(dossierId)),
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
setUnderReviewFor(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string) {
|
||||
setUnderReviewFor(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string) {
|
||||
const fileIds = files.map(f => f.id);
|
||||
const routerPath: string = files[0].routerPath;
|
||||
return this._post<unknown>(fileIds, `${this._defaultModelPath}/under-review/${dossierId}/bulk`).pipe(
|
||||
switchMap(() => this.loadAll(dossierId, routerPath)),
|
||||
switchMap(() => this.loadAll(dossierId)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,20 +2,17 @@ import { Injectable, Injector } from '@angular/core';
|
||||
import { GenericService } from '@iqser/common-ui';
|
||||
import { Dossier, IMatchedDocument, ISearchInput, ISearchRequest, ISearchResponse } from '@red/domain';
|
||||
import { Observable, of, zip } from 'rxjs';
|
||||
import { mapTo, switchMap } from 'rxjs/operators';
|
||||
import { ActiveDossiersService } from '../dossiers/active-dossiers.service';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { FilesMapService } from './files-map.service';
|
||||
import { FilesService } from './files.service';
|
||||
import { ArchivedDossiersService } from '../dossiers/archived-dossiers.service';
|
||||
import { DossiersService } from '../dossiers/dossiers.service';
|
||||
import { DossiersCacheService } from '../dossiers/dossiers-cache.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PlatformSearchService extends GenericService<ISearchResponse> {
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _filesService: FilesService,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _archivedDossiersService: ArchivedDossiersService,
|
||||
private readonly _dossiersCacheService: DossiersCacheService,
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
) {
|
||||
super(_injector, 'search-v2');
|
||||
@ -42,20 +39,19 @@ export class PlatformSearchService extends GenericService<ISearchResponse> {
|
||||
return this._post(body).pipe(switchMap(searchValue => this._loadMissingFiles$(searchValue)));
|
||||
}
|
||||
|
||||
private _dossiersWithMissingFiles(searchResponse: ISearchResponse, service: DossiersService): Dossier[] {
|
||||
const documentsOfType = searchResponse.matchedDocuments.filter(document => service.has(document.dossierId));
|
||||
private _dossiersWithMissingFiles(searchResponse: ISearchResponse): Dossier[] {
|
||||
const documentsOfType = searchResponse.matchedDocuments.filter(document => this._dossiersCacheService.get(document.dossierId));
|
||||
const fileNotLoaded = ({ dossierId, fileId }: IMatchedDocument) => !this._filesMapService.get(dossierId, fileId);
|
||||
const dossiersWithNotLoadedFiles = documentsOfType.filter(fileNotLoaded).map(document => document.dossierId);
|
||||
return Array.from(new Set(dossiersWithNotLoadedFiles)).map(dossierId => service.find(dossierId));
|
||||
return Array.from(new Set(dossiersWithNotLoadedFiles)).map(dossierId => this._dossiersCacheService.get(dossierId));
|
||||
}
|
||||
|
||||
private _loadMissingFiles$(searchResponse: ISearchResponse): Observable<ISearchResponse> {
|
||||
const services = [this._activeDossiersService, this._archivedDossiersService];
|
||||
const dossiers = services.map(service => this._dossiersWithMissingFiles(searchResponse, service)).flat();
|
||||
return dossiers.length ? this._loadFilesFor$(dossiers).pipe(mapTo(searchResponse)) : of(searchResponse);
|
||||
const dossiers = this._dossiersWithMissingFiles(searchResponse);
|
||||
return dossiers.length ? this._loadFilesFor$(dossiers).pipe(map(() => searchResponse)) : of(searchResponse);
|
||||
}
|
||||
|
||||
private _loadFilesFor$(dossiers: Dossier[]) {
|
||||
return zip(...dossiers.map(dossier => this._filesService.loadAll(dossier.id, dossier.routerPath)));
|
||||
return zip(...dossiers.map(dossier => this._filesService.loadAll(dossier.id)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,11 +12,6 @@ import { flatMap } from 'lodash-es';
|
||||
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
|
||||
import { FilesService } from '@services/entity-services/files.service';
|
||||
|
||||
export interface IDossiersStats {
|
||||
totalPeople: number;
|
||||
totalAnalyzedPages: number;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
@ -39,7 +34,7 @@ export class TrashService extends GenericService<TrashItem> {
|
||||
this._toaster.error(_('dossier-listing.delete.delete-failed'), { params: dossier });
|
||||
return of({});
|
||||
};
|
||||
return this.delete(dossier.dossierId, 'dossier').pipe(
|
||||
return this.delete(dossier.id, 'dossier').pipe(
|
||||
switchMap(() => this._activeDossiersService.loadAll()),
|
||||
catchError(showToast),
|
||||
);
|
||||
@ -81,7 +76,7 @@ export class TrashService extends GenericService<TrashItem> {
|
||||
getFiles(dossierIds = this._activeDossiersService.all.map(d => d.id)): Observable<TrashFile[]> {
|
||||
return this._post<Record<string, IFile[]>>(dossierIds, 'status/softdeleted').pipe(
|
||||
map(res => flatMap(Object.values(res))),
|
||||
mapEach(file => new File(file, this._userService.getNameForId(file.assignee), this._activeDossiersService.routerPath)),
|
||||
mapEach(file => new File(file, this._userService.getNameForId(file.assignee))),
|
||||
mapEach(file => {
|
||||
const dossier = this._activeDossiersService.find(file.dossierId);
|
||||
return new TrashFile(
|
||||
@ -133,6 +128,6 @@ export class TrashService extends GenericService<TrashItem> {
|
||||
}
|
||||
|
||||
private _restoreFiles(@RequiredParam() dossierId: string, @RequiredParam() fileIds: List) {
|
||||
return this._post(fileIds, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId, 'dossiers')));
|
||||
return this._post(fileIds, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,12 +6,11 @@ import { Dossier, INotification, Notification, NotificationTypes } from '@red/do
|
||||
import { map, switchMap, tap } from 'rxjs/operators';
|
||||
import { notificationsTranslations } from '../translations/notifications-translations';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { ActiveDossiersService } from './dossiers/active-dossiers.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import dayjs from 'dayjs';
|
||||
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
||||
import { BASE_HREF } from '../tokens';
|
||||
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
|
||||
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
|
||||
|
||||
const INCLUDE_SEEN = false;
|
||||
|
||||
@ -23,19 +22,22 @@ export class NotificationsService extends EntitiesService<Notification, INotific
|
||||
@Inject(BASE_HREF) private readonly _baseHref: string,
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _archivedDossiersService: ArchivedDossiersService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _dossiersCacheService: DossiersCacheService,
|
||||
) {
|
||||
super(_injector, Notification, 'notification');
|
||||
|
||||
timer(0, CHANGED_CHECK_INTERVAL)
|
||||
.pipe(
|
||||
switchMap(() => (this._activeDossiersService.all.length ? of(null) : this._activeDossiersService.loadAll())),
|
||||
switchMap(() => (this._archivedDossiersService.all.length ? of(null) : this._archivedDossiersService.loadAll())),
|
||||
switchMap(() => (this._dossiersCacheService.empty ? this._dossiersCacheService.load() : of(null))),
|
||||
switchMap(() => this.#loadNotificationsIfChanged()),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// Rebuild notifications when cached dossiers are updated
|
||||
this._dossiersCacheService.changed$.subscribe(() => {
|
||||
this.setEntities(this.all.map(e => this._new(e)));
|
||||
});
|
||||
}
|
||||
|
||||
@Validate()
|
||||
@ -75,7 +77,7 @@ export class NotificationsService extends EntitiesService<Notification, INotific
|
||||
private _translate(notification: INotification, translation: string): string {
|
||||
const fileId = notification.target.fileId;
|
||||
const dossierId = notification.target.dossierId;
|
||||
const dossier = this._activeDossiersService.find(dossierId) || this._archivedDossiersService.find(dossierId);
|
||||
const dossier = this._dossiersCacheService.get(dossierId);
|
||||
const fileName = notification.target.fileName;
|
||||
|
||||
return this._translateService.instant(translation, {
|
||||
|
||||
@ -33,7 +33,7 @@ export class PermissionsService {
|
||||
}
|
||||
|
||||
displayReanalyseBtn(dossier: Dossier): boolean {
|
||||
return this.isApprover(dossier) && !!this._filesMapService.get(dossier.dossierId).find(f => f.analysisRequired);
|
||||
return this.isApprover(dossier) && !!this._filesMapService.get(dossier.id).find(f => f.analysisRequired);
|
||||
}
|
||||
|
||||
canUploadFiles(dossier: Dossier): boolean {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { GenericService, IRouterPath, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { GenericService, List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
|
||||
import { Dossier, File, IPageExclusionRequest } from '@red/domain';
|
||||
import { catchError, switchMap, tap } from 'rxjs/operators';
|
||||
import { FilesService } from './entity-services/files.service';
|
||||
@ -36,9 +36,8 @@ export class ReanalysisService extends GenericService<unknown> {
|
||||
}
|
||||
|
||||
@Validate()
|
||||
reanalyzeFilesForDossier(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string, params?: ReanalyzeQueryParams) {
|
||||
reanalyzeFilesForDossier(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string, params?: ReanalyzeQueryParams) {
|
||||
const fileIds = files.map(f => f.id);
|
||||
const routerPath: string = files[0].routerPath;
|
||||
const queryParams: QueryParam[] = [];
|
||||
if (params?.force) {
|
||||
queryParams.push({ key: 'force', value: true });
|
||||
@ -47,22 +46,19 @@ export class ReanalysisService extends GenericService<unknown> {
|
||||
queryParams.push({ key: 'triggeredByUser', value: true });
|
||||
}
|
||||
|
||||
return this._post(fileIds, `reanalyze/${dossierId}/bulk`, queryParams).pipe(
|
||||
switchMap(() => this._filesService.loadAll(dossierId, routerPath)),
|
||||
);
|
||||
return this._post(fileIds, `reanalyze/${dossierId}/bulk`, queryParams).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
toggleAnalysis(@RequiredParam() dossierId: string, @RequiredParam() files: List<IRouterPath>, excluded?: boolean) {
|
||||
toggleAnalysis(@RequiredParam() dossierId: string, @RequiredParam() files: List<File>, excluded?: boolean) {
|
||||
const fileIds = files.map(f => f.id);
|
||||
const routerPath: string = files[0].routerPath;
|
||||
const queryParams: QueryParam[] = [];
|
||||
if (excluded) {
|
||||
queryParams.push({ key: 'excluded', value: excluded });
|
||||
}
|
||||
|
||||
return this._post(fileIds, `toggle-analysis/${dossierId}/bulk`, queryParams).pipe(
|
||||
switchMap(() => this._filesService.loadAll(dossierId, routerPath)),
|
||||
switchMap(() => this._filesService.loadAll(dossierId)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -86,24 +82,19 @@ export class ReanalysisService extends GenericService<unknown> {
|
||||
}
|
||||
|
||||
@Validate()
|
||||
ocrFiles(@RequiredParam() files: List<IRouterPath>, @RequiredParam() dossierId: string) {
|
||||
ocrFiles(@RequiredParam() files: List<File>, @RequiredParam() dossierId: string) {
|
||||
const fileIds = files.map(f => f.id);
|
||||
const routerPath: string = files[0].routerPath;
|
||||
return this._post(fileIds, `ocr/reanalyze/${dossierId}/bulk`).pipe(
|
||||
switchMap(() => this._filesService.loadAll(dossierId, routerPath)),
|
||||
);
|
||||
return this._post(fileIds, `ocr/reanalyze/${dossierId}/bulk`).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
|
||||
}
|
||||
|
||||
@Validate()
|
||||
reanalyzeDossier(@RequiredParam() dossier: Dossier, force?: boolean) {
|
||||
const { dossierId, routerPath } = dossier;
|
||||
const { dossierId } = dossier;
|
||||
const queryParams: QueryParam[] = [];
|
||||
if (force) {
|
||||
queryParams.push({ key: 'force', value: force });
|
||||
}
|
||||
|
||||
return this._post({}, `reanalyze/${dossierId}`, queryParams).pipe(
|
||||
switchMap(() => this._filesService.loadAll(dossierId, routerPath)),
|
||||
);
|
||||
return this._post({}, `reanalyze/${dossierId}`, queryParams).pipe(switchMap(() => this._filesService.loadAll(dossierId)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,11 +6,11 @@ import { filter } from 'rxjs/operators';
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class RouterHistoryService {
|
||||
private _lastDossiersScreen = '/main/dossiers';
|
||||
private _lastDossiersScreen = '/';
|
||||
|
||||
constructor(private readonly _router: Router) {
|
||||
this._router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => {
|
||||
if (event.url.startsWith('/main/dossiers')) {
|
||||
if (event.url.includes('/dossiers') || event.url.includes('/archive')) {
|
||||
this._lastDossiersScreen = event.url;
|
||||
}
|
||||
});
|
||||
@ -18,7 +18,7 @@ export class RouterHistoryService {
|
||||
|
||||
navigateToLastDossiersScreen(): void {
|
||||
if (this._router.url === this._lastDossiersScreen) {
|
||||
this._router.navigate(['/main/dossiers']);
|
||||
this._router.navigate(['/']);
|
||||
} else {
|
||||
this._router.navigate([this._lastDossiersScreen]);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user