Archived dossier overview WIP

This commit is contained in:
Adina Țeudan 2022-02-22 22:31:31 +02:00
parent 12d655a7b7
commit 0a05eb9a6c
24 changed files with 247 additions and 198 deletions

View File

@ -1,15 +1,15 @@
import { Injectable } from '@angular/core';
import { Injectable, Injector, ProviderToken } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { FilesService } from '@services/entity-services/files.service';
import { firstValueFrom } from 'rxjs';
import { DOSSIER_ID } from '@utils/constants';
import { DossiersService } from '@services/entity-services/dossiers.service';
@Injectable({ providedIn: 'root' })
export class DossierFilesGuard implements CanActivate {
constructor(
private readonly _dossiersService: ActiveDossiersService,
private readonly _injector: Injector,
private readonly _filesMapService: FilesMapService,
private readonly _filesService: FilesService,
private readonly _router: Router,
@ -17,9 +17,11 @@ export class DossierFilesGuard implements CanActivate {
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
const dossierId = route.paramMap.get(DOSSIER_ID);
const token: ProviderToken<DossiersService> = route.data.dossiersService;
const dossiersService: DossiersService = this._injector.get<DossiersService>(token);
if (!this._dossiersService.has(dossierId)) {
await this._router.navigate(['/main', 'dossiers']);
if (!dossiersService.has(dossierId)) {
await this._router.navigate(['/main', dossiersService.routerPath]);
return false;
}

View File

@ -2,6 +2,10 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { BreadcrumbTypes } from '@red/domain';
import { ArchivedDossiersScreenComponent } from './screens/archived-dossiers-screen/archived-dossiers-screen.component';
import { DOSSIER_ID } from '@utils/constants';
import { CompositeRouteGuard } from '@iqser/common-ui';
import { ARCHIVED_DOSSIERS_SERVICE } from '../../tokens';
import { DossierFilesGuard } from '../../guards/dossier-files-guard';
const routes: Routes = [
{
@ -10,6 +14,16 @@ const routes: Routes = [
component: ArchivedDossiersScreenComponent,
data: { breadcrumbs: [BreadcrumbTypes.archive] },
},
{
path: `:${DOSSIER_ID}`,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [DossierFilesGuard],
breadcrumbs: [BreadcrumbTypes.archive, BreadcrumbTypes.dossier],
dossiersService: ARCHIVED_DOSSIERS_SERVICE,
},
loadChildren: () => import('../dossier/screens/dossier-overview/dossier-overview.module').then(m => m.DossierOverviewModule),
},
];
@NgModule({

View File

@ -2,7 +2,6 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ArchivedDossiersScreenComponent } from './screens/archived-dossiers-screen/archived-dossiers-screen.component';
import { ArchiveRoutingModule } from './archive-routing.module';
import { CommonUiModule } from '@iqser/common-ui';
import { TableItemComponent } from './components/table-item/table-item.component';
import { ConfigService } from './services/config.service';
import { SharedModule } from '../shared/shared.module';
@ -12,7 +11,7 @@ const screens = [ArchivedDossiersScreenComponent];
@NgModule({
declarations: [...components, ...screens],
imports: [CommonModule, ArchiveRoutingModule, SharedModule, CommonUiModule],
imports: [CommonModule, ArchiveRoutingModule, SharedModule],
providers: [ConfigService],
})
export class ArchiveModule {}

View File

@ -5,10 +5,10 @@ import { PermissionsService } from '@services/permissions.service';
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
import { DictionaryService } from '@shared/services/dictionary.service';
import { CircleButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FormBuilder, FormGroup } from '@angular/forms';
import { firstValueFrom } from 'rxjs';
import { DossiersService } from '@services/entity-services/dossiers.service';
@Component({
selector: 'redaction-edit-dossier-dictionary',
@ -27,7 +27,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
@ViewChild(DictionaryManagerComponent, { static: false }) private readonly _dictionaryManager: DictionaryManagerComponent;
constructor(
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _dossiersService: DossiersService,
private readonly _dictionaryService: DictionaryService,
private readonly _permissionsService: PermissionsService,
private readonly _loadingService: LoadingService,

View File

@ -3,10 +3,10 @@ import { Dossier, DownloadFileType, IReportTemplate } from '@red/domain';
import { FormBuilder, FormGroup } from '@angular/forms';
import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { downloadTypesTranslations } from '../../../../../translations/download-types-translations';
import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service';
import { ReportTemplateService } from '@services/report-template.service';
import { PermissionsService } from '@services/permissions.service';
import { firstValueFrom } from 'rxjs';
import { DossiersService } from '@services/entity-services/dossiers.service';
@Component({
selector: 'redaction-edit-dossier-download-package',
@ -26,7 +26,7 @@ export class EditDossierDownloadPackageComponent implements OnInit, EditDossierS
@Input() dossier: Dossier;
constructor(
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _dossiersService: DossiersService,
private readonly _reportTemplateController: ReportTemplateService,
private readonly _formBuilder: FormBuilder,
private readonly _permissionsService: PermissionsService,
@ -88,7 +88,7 @@ export class EditDossierDownloadPackageComponent implements OnInit, EditDossierS
reportTemplateIds: this.form.get('reportTemplateIds').value,
};
const updatedDossier = await firstValueFrom(this._activeDossiersService.createOrUpdate(dossier));
const updatedDossier = await firstValueFrom(this._dossiersService.createOrUpdate(dossier));
return { success: !!updatedDossier };
}

View File

@ -10,18 +10,20 @@ import { EditDossierAttributesComponent } from './attributes/edit-dossier-attrib
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { EditDossierDeletedDocumentsComponent } from './deleted-documents/edit-dossier-deleted-documents.component';
import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { EditDossierTeamComponent } from './edit-dossier-team/edit-dossier-team.component';
import { PermissionsService } from '@services/permissions.service';
import { UserService } from '@services/user.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { dossiersServiceProvider } from '@services/entity-services/dossiers.service.provider';
type Section = 'dossierInfo' | 'downloadPackage' | 'dossierDictionary' | 'members' | 'dossierAttributes' | 'deletedDocuments';
@Component({
templateUrl: './edit-dossier-dialog.component.html',
styleUrls: ['./edit-dossier-dialog.component.scss'],
providers: [dossiersServiceProvider],
})
export class EditDossierDialogComponent extends BaseDialogComponent implements AfterViewInit {
readonly navItems: { key: Section; title?: string; sideNavTitle?: string }[];
@ -40,7 +42,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent implements A
constructor(
private readonly _toaster: Toaster,
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _activeDossiersService: DossiersService,
private readonly _changeRef: ChangeDetectorRef,
private readonly _loadingService: LoadingService,
private readonly _permissionsService: PermissionsService,

View File

@ -1,12 +1,12 @@
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { UserService } from '@services/user.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service';
import { Dossier, IDossierRequest } from '@red/domain';
import { AutoUnsubscribe } from '@iqser/common-ui';
import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { PermissionsService } from '@services/permissions.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
@Component({
selector: 'redaction-edit-dossier-team',
@ -28,7 +28,7 @@ export class EditDossierTeamComponent extends AutoUnsubscribe implements EditDos
constructor(
readonly userService: UserService,
private readonly _formBuilder: FormBuilder,
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _dossiersService: DossiersService,
private readonly _permissionsService: PermissionsService,
) {
super();
@ -91,7 +91,7 @@ export class EditDossierTeamComponent extends AutoUnsubscribe implements EditDos
ownerId: this.selectedOwnerId,
} as IDossierRequest;
const updatedDossier = await firstValueFrom(this._activeDossiersService.createOrUpdate(dossier));
const updatedDossier = await firstValueFrom(this._dossiersService.createOrUpdate(dossier));
return { success: !!updatedDossier };
}

View File

@ -10,13 +10,13 @@ import { MatDialogRef } from '@angular/material/dialog';
import { EditDossierDialogComponent } from '../edit-dossier-dialog.component';
import { ConfirmationDialogInput, IconButtonTypes, TitleColors, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
import { firstValueFrom } from 'rxjs';
import { DossierStateService } from '@services/entity-services/dossier-state.service';
import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
import { TranslateService } from '@ngx-translate/core';
import { DossiersService } from '@services/entity-services/dossiers.service';
@Component({
selector: 'redaction-edit-dossier-general-info',
@ -38,7 +38,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
readonly permissionsService: PermissionsService,
private readonly _dossierStateService: DossierStateService,
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _dossiersService: DossiersService,
private readonly _dossierStatsService: DossierStatsService,
private readonly _formBuilder: FormBuilder,
private readonly _dialogService: DossiersDialogService,
@ -120,7 +120,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
dossierStatusId: this.form.get('dossierStatusId').value,
} as IDossierRequest;
const updatedDossier = await firstValueFrom(this._activeDossiersService.createOrUpdate(dossier));
const updatedDossier = await firstValueFrom(this._dossiersService.createOrUpdate(dossier));
return { success: !!updatedDossier };
}
@ -139,7 +139,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
},
});
this._dialogService.openDialog('confirm', null, data, async () => {
await firstValueFrom(this._activeDossiersService.delete(this.dossier));
await firstValueFrom(this._dossiersService.delete(this.dossier));
this._editDossierDialogRef.close();
this._router.navigate(['main', 'dossiers']).then(() => this.#notifyDossierDeleted());
});

View File

@ -6,6 +6,7 @@ import { DossierFilesGuard } from '@guards/dossier-files-guard';
import { CompositeRouteGuard } from '@iqser/common-ui';
import { BreadcrumbTypes } from '@red/domain';
import { DOSSIER_ID, FILE_ID } from '@utils/constants';
import { ACTIVE_DOSSIERS_SERVICE } from '../../tokens';
const routes: Routes = [
{
@ -18,6 +19,7 @@ const routes: Routes = [
data: {
routeGuards: [DossierFilesGuard],
breadcrumbs: [BreadcrumbTypes.main, BreadcrumbTypes.dossier],
dossiersService: ACTIVE_DOSSIERS_SERVICE,
},
loadChildren: () => import('./screens/dossier-overview/dossier-overview.module').then(m => m.DossierOverviewModule),
},
@ -27,6 +29,7 @@ const routes: Routes = [
data: {
routeGuards: [DossierFilesGuard, FilePreviewGuard],
breadcrumbs: [BreadcrumbTypes.main, BreadcrumbTypes.dossier, BreadcrumbTypes.file],
dossiersService: ACTIVE_DOSSIERS_SERVICE,
},
loadChildren: () => import('./screens/file-preview-screen/file-preview.module').then(m => m.FilePreviewModule),
},

View File

@ -1,65 +1,33 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AddDossierDialogComponent } from './dialogs/add-dossier-dialog/add-dossier-dialog.component';
import { AssignReviewerApproverDialogComponent } from './dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component';
import { ManualAnnotationDialogComponent } from './dialogs/manual-redaction-dialog/manual-annotation-dialog.component';
import { ForceAnnotationDialogComponent } from './dialogs/force-redaction-dialog/force-annotation-dialog.component';
import { RemoveAnnotationsDialogComponent } from './dialogs/remove-annotations-dialog/remove-annotations-dialog.component';
import { DocumentInfoDialogComponent } from './dialogs/document-info-dialog/document-info-dialog.component';
import { SharedModule } from '@shared/shared.module';
import { DossiersRoutingModule } from './dossiers-routing.module';
import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module';
import { DossiersDialogService } from './services/dossiers-dialog.service';
import { ManualAnnotationService } from './services/manual-annotation.service';
import { AnnotationProcessingService } from './services/annotation-processing.service';
import { EditDossierDialogComponent } from './dialogs/edit-dossier-dialog/edit-dossier-dialog.component';
import { EditDossierGeneralInfoComponent } from './dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component';
import { EditDossierDownloadPackageComponent } from './dialogs/edit-dossier-dialog/download-package/edit-dossier-download-package.component';
import { EditDossierDictionaryComponent } from './dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component';
import { ChangeLegalBasisDialogComponent } from './dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component';
import { RecategorizeImageDialogComponent } from './dialogs/recategorize-image-dialog/recategorize-image-dialog.component';
import { EditDossierAttributesComponent } from './dialogs/edit-dossier-dialog/attributes/edit-dossier-attributes.component';
import { SearchScreenComponent } from './screens/search-screen/search-screen.component';
import { EditDossierDeletedDocumentsComponent } from './dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component';
import { OverlayModule } from '@angular/cdk/overlay';
import { SharedDossiersModule } from './shared/shared-dossiers.module';
import { ResizeAnnotationDialogComponent } from './dialogs/resize-annotation-dialog/resize-annotation-dialog.component';
import { EditDossierTeamComponent } from './dialogs/edit-dossier-dialog/edit-dossier-team/edit-dossier-team.component';
import { ConfirmArchiveDossierDialogComponent } from './dialogs/confirm-archive-dossier-dialog/confirm-archive-dossier-dialog.component';
const screens = [SearchScreenComponent];
const dialogs = [
AddDossierDialogComponent,
EditDossierDialogComponent,
ConfirmArchiveDossierDialogComponent,
ManualAnnotationDialogComponent,
ForceAnnotationDialogComponent,
RemoveAnnotationsDialogComponent,
ResizeAnnotationDialogComponent,
DocumentInfoDialogComponent,
AssignReviewerApproverDialogComponent,
ChangeLegalBasisDialogComponent,
RecategorizeImageDialogComponent,
];
const components = [
EditDossierGeneralInfoComponent,
EditDossierDownloadPackageComponent,
EditDossierDictionaryComponent,
EditDossierAttributesComponent,
EditDossierTeamComponent,
EditDossierDeletedDocumentsComponent,
...screens,
...dialogs,
];
const services = [DossiersDialogService, ManualAnnotationService, AnnotationProcessingService];
const components = [...screens, ...dialogs];
@NgModule({
declarations: [...components],
providers: [...services],
imports: [CommonModule, SharedModule, SharedDossiersModule, FileUploadDownloadModule, DossiersRoutingModule, OverlayModule],
})
export class DossiersModule {}

View File

@ -6,7 +6,6 @@ import { FilterService, ProgressBarConfigModel, shareLast, Toaster } from '@iqse
import { workflowFileStatusTranslations } from '../../../../translations/file-status-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { Dossier, DossierAttributeWithValue, DossierStats, IDossierRequest, StatusSorter, User } from '@red/domain';
import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service';
import { ActivatedRoute } from '@angular/router';
import { firstValueFrom, Observable } from 'rxjs';
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
@ -14,6 +13,7 @@ import { map, pluck, switchMap } from 'rxjs/operators';
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
import { FilesService } from '@services/entity-services/files.service';
import { DOSSIER_ID } from '@utils/constants';
import { DossiersService } from '@services/entity-services/dossiers.service';
@Component({
selector: 'redaction-dossier-details',
@ -37,19 +37,19 @@ export class DossierDetailsComponent {
statusConfig$: Observable<ProgressBarConfigModel[]>;
constructor(
readonly activeDossiersService: ActiveDossiersService,
readonly translateChartService: TranslateChartService,
readonly filterService: FilterService,
private readonly _dossiersService: DossiersService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _userService: UserService,
private readonly _filesService: FilesService,
private readonly _dossierStatsService: DossierStatsService,
private readonly _toaster: Toaster,
private readonly _dialogService: DossiersDialogService,
activatedRoute: ActivatedRoute,
private readonly _activatedRoute: ActivatedRoute,
) {
this.dossierId = activatedRoute.snapshot.paramMap.get(DOSSIER_ID);
this.dossier$ = this.activeDossiersService.getEntityChanged$(this.dossierId).pipe(shareLast());
this.dossierId = _activatedRoute.snapshot.paramMap.get(DOSSIER_ID);
this.dossier$ = this._dossiersService.getEntityChanged$(this.dossierId).pipe(shareLast());
this.dossierStats$ = this.dossier$.pipe(
pluck('dossierId'),
switchMap(dossierId => this._dossierStatsService.watch$(dossierId)),
@ -65,7 +65,7 @@ export class DossierDetailsComponent {
async assignOwner(user: User | string, dossier: Dossier) {
const owner = typeof user === 'string' ? this._userService.find(user) : user;
const dossierRequest: IDossierRequest = { ...dossier, ownerId: owner.id };
await firstValueFrom(this.activeDossiersService.createOrUpdate(dossierRequest));
await firstValueFrom(this._dossiersService.createOrUpdate(dossierRequest));
const ownerName = this._userService.getNameForId(owner.id);
const dossierName = dossier.dossierName;

View File

@ -1,8 +1,8 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { File } from '@red/domain';
import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service';
import { UserService } from '@services/user.service';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
@Component({
selector: 'redaction-file-workload',
@ -16,7 +16,7 @@ export class FileWorkloadComponent {
constructor(
readonly userService: UserService,
private readonly _dictionariesMapService: DictionariesMapService,
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _dossiersService: DossiersService,
) {}
get suggestionColor() {
@ -44,9 +44,7 @@ export class FileWorkloadComponent {
}
private _getDictionaryColor(type: string) {
return this._dictionariesMapService.getDictionaryColor(
type,
this._activeDossiersService.find(this.file.dossierId).dossierTemplateId,
);
const dossierTemplateId = this._dossiersService.find(this.file.dossierId).dossierTemplateId;
return this._dictionariesMapService.getDictionaryColor(type, dossierTemplateId);
}
}

View File

@ -164,7 +164,7 @@ export class ConfigService {
checkedRequiredFilters: () => NestedFilter[],
checkedNotRequiredFilters: () => NestedFilter[],
) {
const allDistinctWorkflowFileStatuses = new Set<string>();
const allDistinctWorkflowFileStatuses = new Set<WorkflowFileStatus>();
const allDistinctPeople = new Set<string>();
const allDistinctAddedDates = new Set<string>();
const allDistinctNeedsWork = new Set<string>();
@ -328,7 +328,9 @@ export class ConfigService {
}
_recentlyModifiedChecker = (file: File) =>
moment(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment());
moment(file.lastUpdated)
.add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS as number, 'hours')
.isAfter(moment());
_assignedToMeChecker = (file: File) => file.assignee === this._userService.currentUser.id;

View File

@ -9,7 +9,6 @@ import { DossierOverviewBulkActionsComponent } from './components/bulk-actions/d
import { DossierDetailsComponent } from './components/dossier-details/dossier-details.component';
import { DossierDetailsStatsComponent } from './components/dossier-details-stats/dossier-details-stats.component';
import { TableItemComponent } from './components/table-item/table-item.component';
import { ConfigService } from './config.service';
import { SharedDossiersModule } from '../../shared/shared-dossiers.module';
import { FileWorkloadComponent } from './components/table-item/file-workload/file-workload.component';
import { FileStatsComponent } from './components/file-stats/file-stats.component';
@ -18,7 +17,6 @@ import { ScreenHeaderComponent } from './components/screen-header/screen-header.
import { ViewModeSelectionComponent } from './components/view-mode-selection/view-mode-selection.component';
import { FileNameColumnComponent } from './components/table-item/file-name-column/file-name-column.component';
import { DateColumnComponent } from './components/table-item/date-column/date-column.component';
import { BulkActionsService } from './services/bulk-actions.service';
const routes: Routes = [
{
@ -46,7 +44,6 @@ const routes: Routes = [
FileNameColumnComponent,
DateColumnComponent,
],
providers: [ConfigService, BulkActionsService],
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, SharedDossiersModule, IqserIconsModule, TranslateModule],
})
export class DossierOverviewModule {}

View File

@ -38,18 +38,26 @@ import { PermissionsService } from '@services/permissions.service';
import { ActivatedRoute, Router } from '@angular/router';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { ConfigService } from '../config.service';
import { ActiveDossiersService } from '@services/entity-services/active-dossiers.service';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.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';
import { FilesService } from '@services/entity-services/files.service';
import { DOSSIER_ID } from '@utils/constants';
import { BulkActionsService } from '../services/bulk-actions.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { dossiersServiceProvider } from '@services/entity-services/dossiers.service.provider';
@Component({
templateUrl: './dossier-overview-screen.component.html',
styleUrls: ['./dossier-overview-screen.component.scss'],
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }],
providers: [
...DefaultListingServices,
ConfigService,
BulkActionsService,
{ provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) },
dossiersServiceProvider,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DossierOverviewScreenComponent extends ListingComponent<File> implements OnInit, OnDestroy, OnAttach {
@ -77,8 +85,8 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
constructor(
protected readonly _injector: Injector,
private readonly _router: Router,
private readonly _dossiersService: DossiersService,
private readonly _loadingService: LoadingService,
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _fileUploadService: FileUploadService,
private readonly _filesService: FilesService,
@ -89,16 +97,16 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
private readonly _userPreferenceService: UserPreferenceService,
private readonly _fileMapService: FilesMapService,
private readonly _errorService: ErrorService,
private readonly _route: ActivatedRoute,
readonly permissionsService: PermissionsService,
readonly configService: ConfigService,
readonly activatedRoute: ActivatedRoute,
) {
super(_injector);
this.dossierId = activatedRoute.snapshot.paramMap.get(DOSSIER_ID);
this.dossier$ = this._activeDossiersService
this.dossierId = _route.snapshot.paramMap.get(DOSSIER_ID);
this.dossier$ = this._dossiersService
.getEntityChanged$(this.dossierId)
.pipe(tap(dossier => (this.dossierTemplateId = dossier.dossierTemplateId)));
this.currentDossier = this._activeDossiersService.find(this.dossierId);
this.currentDossier = this._dossiersService.find(this.dossierId);
this._updateFileAttributes();
}
@ -133,7 +141,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
this._computeAllFilters();
});
this.addSubscription = this._activeDossiersService.dossierFileChanges$
this.addSubscription = this._dossiersService.dossierFileChanges$
.pipe(
filter(dossierId => dossierId === this.dossierId),
switchMap(dossierId => this._filesService.loadAll(dossierId)),
@ -176,7 +184,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
@HostListener('drop', ['$event'])
onDrop(event: DragEvent): void {
const currentDossier = this._activeDossiersService.find(this.dossierId);
const currentDossier = this._dossiersService.find(this.dossierId);
handleFileDrop(event, currentDossier, this._uploadFiles.bind(this));
}
@ -192,7 +200,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
}
private _setRemovableSubscriptions(): void {
this.addActiveScreenSubscription = this._activeDossiersService
this.addActiveScreenSubscription = this._dossiersService
.getEntityDeleted$(this.dossierId)
.pipe(tap(() => this._handleDeletedDossier()))
.subscribe();
@ -214,7 +222,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
}
private _loadEntitiesFromState() {
this.currentDossier = this._activeDossiersService.find(this.dossierId);
this.currentDossier = this._dossiersService.find(this.dossierId);
this._computeAllFilters();
}

View File

@ -10,6 +10,8 @@ import { FilePreviewStateService } from './services/file-preview-state.service';
import { PdfViewerDataService } from '../../services/pdf-viewer-data.service';
import { AnnotationReferencesService } from './services/annotation-references.service';
import { FilterService } from '@iqser/common-ui';
import { ManualAnnotationService } from '../../services/manual-annotation.service';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
export const filePreviewScreenProviders = [
FilterService,
@ -24,4 +26,6 @@ export const filePreviewScreenProviders = [
FilePreviewStateService,
PdfViewerDataService,
AnnotationReferencesService,
ManualAnnotationService,
AnnotationProcessingService,
];

View File

@ -2,16 +2,43 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FileAssignService } from './services/file-assign.service';
import { FileActionsComponent } from './components/file-actions/file-actions.component';
import { IqserIconsModule } from '@iqser/common-ui';
import { SharedModule } from '@shared/shared.module';
import { RedactionImportService } from './services/redaction-import.service';
import { DossiersDialogService } from '../services/dossiers-dialog.service';
import { DocumentInfoDialogComponent } from '../dialogs/document-info-dialog/document-info-dialog.component';
import { EditDossierDialogComponent } from '../dialogs/edit-dossier-dialog/edit-dossier-dialog.component';
import { AddDossierDialogComponent } from '../dialogs/add-dossier-dialog/add-dossier-dialog.component';
import { ConfirmArchiveDossierDialogComponent } from '../dialogs/confirm-archive-dossier-dialog/confirm-archive-dossier-dialog.component';
import { AssignReviewerApproverDialogComponent } from '../dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component';
import { EditDossierGeneralInfoComponent } from '../dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component';
import { EditDossierDownloadPackageComponent } from '../dialogs/edit-dossier-dialog/download-package/edit-dossier-download-package.component';
import { EditDossierDictionaryComponent } from '../dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component';
import { EditDossierAttributesComponent } from '../dialogs/edit-dossier-dialog/attributes/edit-dossier-attributes.component';
import { EditDossierTeamComponent } from '../dialogs/edit-dossier-dialog/edit-dossier-team/edit-dossier-team.component';
import { EditDossierDeletedDocumentsComponent } from '../dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component';
const components = [FileActionsComponent];
const components = [
FileActionsComponent,
DocumentInfoDialogComponent,
EditDossierGeneralInfoComponent,
EditDossierDownloadPackageComponent,
EditDossierDictionaryComponent,
EditDossierAttributesComponent,
EditDossierTeamComponent,
EditDossierDeletedDocumentsComponent,
];
const dialogs = [
EditDossierDialogComponent,
AddDossierDialogComponent,
ConfirmArchiveDossierDialogComponent,
AssignReviewerApproverDialogComponent,
];
const services = [DossiersDialogService, FileAssignService, RedactionImportService];
@NgModule({
declarations: [...components],
exports: [...components],
providers: [FileAssignService, RedactionImportService],
imports: [CommonModule, IqserIconsModule, SharedModule],
declarations: [...components, ...dialogs],
exports: [...components, ...dialogs],
providers: [...services],
imports: [CommonModule, SharedModule],
})
export class SharedDossiersModule {}

View File

@ -1,13 +1,14 @@
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { List } from '@iqser/common-ui';
import { ActivatedRouteSnapshot, IsActiveMatchOptions, NavigationEnd, Router } from '@angular/router';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { filter, pluck } from 'rxjs/operators';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { ActiveDossiersService } from './entity-services/active-dossiers.service';
import { TranslateService } from '@ngx-translate/core';
import { BreadcrumbTypes } from '@red/domain';
import { DOSSIER_ID, FILE_ID } from '@utils/constants';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { dossiersServiceResolver } from '@services/entity-services/dossiers.service.provider';
export type RouterLinkActiveOptions = { exact: boolean } | IsActiveMatchOptions;
export type BreadcrumbDisplayType = 'text' | 'dropdown';
@ -34,10 +35,10 @@ export class BreadcrumbsService {
private readonly _store$ = new BehaviorSubject<Breadcrumbs>([]);
constructor(
private readonly _injector: Injector,
private readonly _router: Router,
private readonly _translateService: TranslateService,
private readonly _filesMapService: FilesMapService,
private readonly _activeDossiersService: ActiveDossiersService,
) {
this.breadcrumbs$ = this._store$.asObservable();
@ -52,6 +53,10 @@ export class BreadcrumbsService {
return this._store$.value;
}
private get _dossiersService(): DossiersService {
return dossiersServiceResolver(this._injector);
}
private get _mainBreadcrumb(): Breadcrumb {
return {
name$: of(this._translateService.instant('top-bar.navigation-items.dossiers')),
@ -135,12 +140,13 @@ export class BreadcrumbsService {
}
private _addDossierBreadcrumb(route: ActivatedRouteSnapshot): void {
const dossiersService = this._dossiersService;
const dossierId = route.paramMap.get(DOSSIER_ID);
this._append({
name$: this._activeDossiersService.getEntityChanged$(dossierId).pipe(pluck('dossierName')),
name$: dossiersService.getEntityChanged$(dossierId).pipe(pluck('dossierName')),
type: 'text' as BreadcrumbDisplayType,
options: {
routerLink: ['/main', 'dossiers', dossierId],
routerLink: ['/main', dossiersService.routerPath, dossierId],
routerLinkActiveOptions: { exact: true },
clamp: true,
},
@ -150,11 +156,12 @@ export class BreadcrumbsService {
private _addFileBreadcrumb(route: ActivatedRouteSnapshot): void {
const dossierId = route.paramMap.get(DOSSIER_ID);
const fileId = route.paramMap.get(FILE_ID);
const dossiersService = this._dossiersService;
this._append({
name$: this._filesMapService.watch$(dossierId, fileId).pipe(pluck('filename')),
type: 'text' as BreadcrumbDisplayType,
options: {
routerLink: ['/main', 'dossiers', dossierId, 'file', fileId],
routerLink: ['/main', dossiersService.routerPath, dossierId, 'file', fileId],
clamp: true,
},
});

View File

@ -1,10 +1,9 @@
import { Injectable, Injector } from '@angular/core';
import { List, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui';
import { Dossier, DossierStats, IDossier, IDossierRequest } from '@red/domain';
import { catchError, filter, map, mapTo, pluck, switchMap, tap } from 'rxjs/operators';
import { firstValueFrom, forkJoin, Observable, of, Subject, throwError, timer } from 'rxjs';
import { List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
import { Dossier, IDossier } from '@red/domain';
import { catchError, switchMap, tap } from 'rxjs/operators';
import { firstValueFrom, Observable, of, timer } from 'rxjs';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
import { DossiersService } from '@services/entity-services/dossiers.service';
@ -13,78 +12,21 @@ export interface IDossiersStats {
totalAnalyzedPages: number;
}
interface DossierChange {
readonly dossierChanges: boolean;
readonly dossierId: string;
readonly fileChanges: boolean;
}
type DossierChanges = readonly DossierChange[];
interface ChangesDetails {
readonly dossierChanges: DossierChanges;
}
const DOSSIER_EXISTS_MSG = _('add-dossier-dialog.errors.dossier-already-exists');
const GENERIC_MSG = _('add-dossier-dialog.errors.generic');
@Injectable({
providedIn: 'root',
})
export class ActiveDossiersService extends DossiersService {
readonly dossierFileChanges$ = new Subject<string>();
constructor(private readonly _toaster: Toaster, protected readonly _injector: Injector) {
super(_injector, 'dossier');
constructor(protected readonly _injector: Injector) {
super(_injector, 'dossier', 'dossiers');
timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL)
.pipe(
switchMap(() => this.loadOnlyChanged()),
tap(changes => this.#emitFileChanges(changes)),
tap(changes => this._emitFileChanges(changes)),
)
.subscribe();
}
loadOnlyChanged(): Observable<DossierChanges> {
const removeIfNotFound = (id: string) =>
catchError((error: HttpErrorResponse) => {
if (error.status === HttpStatusCode.NotFound) {
this.remove(id);
return of([]);
}
return throwError(() => error);
});
const load = (changes: DossierChanges) =>
changes.map(change => this._load(change.dossierId).pipe(removeIfNotFound(change.dossierId)));
return this.hasChangesDetails$().pipe(
pluck('dossierChanges'),
switchMap(dossierChanges => forkJoin(load(dossierChanges)).pipe(mapTo(dossierChanges))),
tap(() => this._updateLastChanged()),
);
}
hasChangesDetails$(): Observable<ChangesDetails> {
const body = { value: this._lastCheckedForChanges.get('root') ?? '0' };
return this._post<ChangesDetails>(body, `${this._defaultModelPath}/changes/details`).pipe(
filter(changes => changes.dossierChanges.length > 0),
);
}
@Validate()
createOrUpdate(@RequiredParam() dossier: IDossierRequest): Observable<Dossier | undefined> {
const showToast = (error: HttpErrorResponse) => {
this._toaster.error(error.status === HttpStatusCode.Conflict ? DOSSIER_EXISTS_MSG : GENERIC_MSG);
return of(undefined);
};
return this._post(dossier).pipe(
switchMap(newDossier => this.loadAll().pipe(map(() => this.find(newDossier.dossierId)))),
catchError(showToast),
);
}
getDeleted(): Promise<IDossier[]> {
return firstValueFrom(this.getAll('deleted-dossiers'));
}
@ -118,16 +60,4 @@ export class ActiveDossiersService extends DossiersService {
#removeDossiers(dossiers: Dossier[]): void {
this.setEntities(this.all.filter(dossier => !dossiers.find(d => dossier.id === d.id)));
}
private _load(id: string, queryParams?: List<QueryParam>): Observable<DossierStats[]> {
return super._getOne([id], this._defaultModelPath, queryParams).pipe(
map(entity => new Dossier(entity)),
tap(dossier => this.replace(dossier)),
switchMap(dossier => this._dossierStatsService.getFor([dossier.dossierId])),
);
}
#emitFileChanges(dossierChanges: DossierChanges): void {
dossierChanges.filter(change => change.fileChanges).forEach(change => this.dossierFileChanges$.next(change.dossierId));
}
}

View File

@ -1,5 +1,4 @@
import { Injectable, Injector } from '@angular/core';
import { Toaster } from '@iqser/common-ui';
import { Dossier } from '@red/domain';
import { catchError, tap } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
@ -9,12 +8,8 @@ import { DossiersService } from '@services/entity-services/dossiers.service';
@Injectable({ providedIn: 'root' })
export class ArchivedDossiersService extends DossiersService {
constructor(
protected readonly _injector: Injector,
private readonly _toaster: Toaster,
private readonly _activeDossiersService: ActiveDossiersService,
) {
super(_injector, 'archived-dossiers');
constructor(protected readonly _injector: Injector, private readonly _activeDossiersService: ActiveDossiersService) {
super(_injector, 'archived-dossiers', 'archive');
}
archive(dossiers: Dossier[]): Observable<unknown> {
@ -26,14 +21,13 @@ export class ArchivedDossiersService extends DossiersService {
dossiers.map(d => d.id),
`${this._defaultModelPath}/archive`,
).pipe(
tap(() => this.#removeDossiers(dossiers)),
tap(() => this.#removeFromActiveDossiers(dossiers)),
catchError(showToast),
);
}
#removeDossiers(dossiers: Dossier[]): void {
this._activeDossiersService.setEntities(
this._activeDossiersService.all.filter(dossier => !dossiers.find(d => dossier.id === d.id)),
);
#removeFromActiveDossiers(dossiers: Dossier[]): void {
const remainingEntities = this._activeDossiersService.all.filter(dossier => !dossiers.find(d => dossier.id === d.id));
this._activeDossiersService.setEntities(remainingEntities);
}
}

View File

@ -0,0 +1,15 @@
import { ActivatedRoute } from '@angular/router';
import { Injector, ProviderToken } from '@angular/core';
import { DossiersService } from './dossiers.service';
export const dossiersServiceResolver = (injector: Injector) => {
const route = injector.get<ActivatedRoute>(ActivatedRoute);
const token: ProviderToken<DossiersService> = (route.firstChild || route).snapshot.data.dossiersService;
return injector.get<DossiersService>(token);
};
export const dossiersServiceProvider = {
provide: DossiersService,
useFactory: dossiersServiceResolver,
deps: [Injector],
};

View File

@ -1,25 +1,84 @@
import { EntitiesService, List, mapEach, shareLast } from '@iqser/common-ui';
import { Dossier, IDossier } from '@red/domain';
import { combineLatest, Observable } from 'rxjs';
import { filter, map, mapTo, switchMap, tap } from 'rxjs/operators';
import { EntitiesService, List, mapEach, QueryParam, RequiredParam, shareLast, Toaster, Validate } from '@iqser/common-ui';
import { Dossier, DossierStats, IDossier, IDossierRequest } from '@red/domain';
import { combineLatest, forkJoin, Observable, of, Subject, throwError } from 'rxjs';
import { catchError, filter, map, mapTo, pluck, switchMap, tap } from 'rxjs/operators';
import { Injector } from '@angular/core';
import { DossierStateService } from './dossier-state.service';
import { DossierStatsService } from './dossier-stats.service';
import { IDossiersStats } from '@services/entity-services/active-dossiers.service';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
interface DossierChange {
readonly dossierChanges: boolean;
readonly dossierId: string;
readonly fileChanges: boolean;
}
type DossierChanges = readonly DossierChange[];
interface ChangesDetails {
readonly dossierChanges: DossierChanges;
}
const DOSSIER_EXISTS_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(DossierStateService);
protected readonly _toaster = this._injector.get(Toaster);
protected constructor(protected readonly _injector: Injector, protected readonly _path: string) {
protected constructor(protected readonly _injector: Injector, protected readonly _path: string, readonly routerPath: string) {
super(_injector, Dossier, _path);
}
@Validate()
createOrUpdate(@RequiredParam() dossier: IDossierRequest): Observable<Dossier | undefined> {
const showToast = (error: HttpErrorResponse) => {
this._toaster.error(error.status === HttpStatusCode.Conflict ? DOSSIER_EXISTS_MSG : GENERIC_MSG);
return of(undefined);
};
return this._post(dossier, 'dossier').pipe(
switchMap(newDossier => this.loadAll().pipe(map(() => this.find(newDossier.dossierId)))),
catchError(showToast),
);
}
loadOnlyChanged(): Observable<DossierChanges> {
const removeIfNotFound = (id: string) =>
catchError((error: HttpErrorResponse) => {
if (error.status === HttpStatusCode.NotFound) {
this.remove(id);
return of([]);
}
return throwError(() => error);
});
const load = (changes: DossierChanges) =>
changes.map(change => this._load(change.dossierId).pipe(removeIfNotFound(change.dossierId)));
return this.hasChangesDetails$().pipe(
pluck('dossierChanges'),
switchMap(dossierChanges => forkJoin(load(dossierChanges)).pipe(mapTo(dossierChanges))),
tap(() => this._updateLastChanged()),
);
}
hasChangesDetails$(): Observable<ChangesDetails> {
const body = { value: this._lastCheckedForChanges.get('root') ?? '0' };
return this._post<ChangesDetails>(body, `${this._defaultModelPath}/changes/details`).pipe(
filter(changes => changes.dossierChanges.length > 0),
);
}
loadAll(): Observable<Dossier[]> {
const dossierIds = (dossiers: Dossier[]) => dossiers.map(d => d.id);
return this.getAll().pipe(
mapEach(entity => new Dossier(entity)),
mapEach(entity => new Dossier(entity, this.routerPath)),
/* Load stats before updating entities */
switchMap(dossiers => this._dossierStatsService.getFor(dossierIds(dossiers)).pipe(mapTo(dossiers))),
switchMap(dossiers => this._dossierStateService.loadAllForAllTemplates().pipe(mapTo(dossiers))),
@ -27,6 +86,18 @@ export abstract class DossiersService extends EntitiesService<Dossier, IDossier>
);
}
protected _emitFileChanges(dossierChanges: DossierChanges): void {
dossierChanges.filter(change => change.fileChanges).forEach(change => this.dossierFileChanges$.next(change.dossierId));
}
private _load(id: string, queryParams?: List<QueryParam>): Observable<DossierStats[]> {
return super._getOne([id], this._defaultModelPath, queryParams).pipe(
map(entity => new Dossier(entity, 'dossiers')),
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>();

View File

@ -1,13 +1,21 @@
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { UserService } from './user.service';
import { Dossier, File, IComment, IDossier } from '@red/domain';
import { ActiveDossiersService } from './entity-services/active-dossiers.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { ActivatedRoute } from '@angular/router';
import { dossiersServiceResolver } from '@services/entity-services/dossiers.service.provider';
@Injectable({
providedIn: 'root',
})
@Injectable({ providedIn: 'root' })
export class PermissionsService {
constructor(private readonly _userService: UserService, private readonly _activeDossiersService: ActiveDossiersService) {}
constructor(
private readonly _userService: UserService,
private readonly _route: ActivatedRoute,
private readonly _injector: Injector,
) {}
private get _dossiersService(): DossiersService {
return dossiersServiceResolver(this._injector);
}
isReviewerOrApprover(file: File): boolean {
const dossier = this._getDossier(file);
@ -228,6 +236,6 @@ export class PermissionsService {
}
private _getDossier(file: File): Dossier {
return this._activeDossiersService.find(file.dossierId);
return this._dossiersService.find(file.dossierId);
}
}

View File

@ -25,7 +25,7 @@ export class Dossier implements IDossier, IListable {
readonly archivedTime: string;
readonly hasReviewers: boolean;
constructor(dossier: IDossier) {
constructor(dossier: IDossier, readonly routerPath: string) {
this.dossierId = dossier.dossierId;
this.approverIds = dossier.approverIds;
this.date = dossier.date;
@ -53,7 +53,7 @@ export class Dossier implements IDossier, IListable {
}
get routerLink(): string {
return `/main/dossiers/${this.dossierId}`;
return `/main/${this.routerPath}/${this.dossierId}`;
}
get searchKey(): string {