From 1249e4108947c17deacf873200482dfa0befdf98 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 15 Nov 2021 15:01:44 +0200 Subject: [PATCH] fix file actions issues, live update on dossier change --- .../base-screen/base-screen.component.html | 2 +- .../base-screen/base-screen.component.ts | 3 +- .../page-indicator.component.ts | 14 +++-- ...sign-reviewer-approver-dialog.component.ts | 18 +++--- .../change-legal-basis-dialog.component.ts | 17 ++---- .../dossier-overview/config.service.ts | 3 +- .../dossier-overview-screen.component.ts | 11 +++- .../dossiers-listing-details.component.html | 2 +- .../dossiers-listing-screen.component.ts | 2 - .../file-actions/file-actions.component.html | 2 +- .../file-actions/file-actions.component.ts | 57 ++++++++++--------- .../file-drop/file-drop.component.ts | 3 +- .../entity-services/dossiers.service.ts | 30 ++++------ apps/red-ui/src/app/state/app-state.guard.ts | 7 ++- .../red-ui/src/app/state/app-state.service.ts | 12 ++-- 15 files changed, 90 insertions(+), 93 deletions(-) diff --git a/apps/red-ui/src/app/components/base-screen/base-screen.component.html b/apps/red-ui/src/app/components/base-screen/base-screen.component.html index 2c927f27c..6d51a9cf3 100644 --- a/apps/red-ui/src/app/components/base-screen/base-screen.component.html +++ b/apps/red-ui/src/app/components/base-screen/base-screen.component.html @@ -18,7 +18,7 @@ translate="top-bar.navigation-items.dossiers" > - + this._search(query), }, ]; + readonly activeDossier$ = this.dossiersService.activeDossierId$.pipe(switchMap(id => this.dossiersService.getEntityChanged$(id))); private readonly _navigationStart$ = this._router.events.pipe( filter(isNavigationStart), map((event: NavigationStart) => event.url), diff --git a/apps/red-ui/src/app/modules/dossier/components/page-indicator/page-indicator.component.ts b/apps/red-ui/src/app/modules/dossier/components/page-indicator/page-indicator.component.ts index 8ead6fb90..cc63a24e3 100644 --- a/apps/red-ui/src/app/modules/dossier/components/page-indicator/page-indicator.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/page-indicator/page-indicator.component.ts @@ -1,10 +1,10 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core'; import { PermissionsService } from '@services/permissions.service'; import { ConfigService } from '@services/config.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { ViewedPagesService } from '../../shared/services/viewed-pages.service'; import { File, IViewedPage } from '@red/domain'; -import { AutoUnsubscribe, OnChange } from '@iqser/common-ui'; +import { AutoUnsubscribe } from '@iqser/common-ui'; import { FilesMapService } from '@services/entity-services/files-map.service'; @Component({ @@ -13,14 +13,12 @@ import { FilesMapService } from '@services/entity-services/files-map.service'; styleUrls: ['./page-indicator.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy { +export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy, OnChanges { @Input() - @OnChange('handlePageRead') file: File; @Input() - @OnChange('handlePageRead') - active: boolean; + active = false; @Input() showDottedIcon = false; @Input() number: number; @@ -54,6 +52,10 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy } } + ngOnChanges() { + this.handlePageRead(); + } + async toggleReadState() { if (this._permissionService.canMarkPagesAsViewed(this.file)) { if (this.read) { diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts index 7e382b21d..9d139a483 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts @@ -4,7 +4,7 @@ import { AppStateService } from '@state/app-state.service'; import { UserService } from '@services/user.service'; import { Toaster } from '@iqser/common-ui'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Dossier, File } from '@red/domain'; +import { File } from '@red/domain'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { FilesService } from '@services/entity-services/files.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; @@ -12,7 +12,6 @@ import { PermissionsService } from '@services/permissions.service'; class DialogData { mode: 'approver' | 'reviewer'; - dossierId: string; files?: File[]; ignoreChanged?: boolean; } @@ -24,7 +23,6 @@ class DialogData { export class AssignReviewerApproverDialogComponent { usersForm: FormGroup; searchForm: FormGroup; - readonly dossier: Dossier | undefined; constructor( readonly userService: UserService, @@ -37,7 +35,6 @@ export class AssignReviewerApproverDialogComponent { private readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) readonly data: DialogData, ) { - this.dossier = this._dossiersService.find(data.dossierId); this._loadData(); } @@ -47,9 +44,8 @@ export class AssignReviewerApproverDialogComponent { get singleUsersSelectOptions() { const unassignUser = this._canUnassignFiles ? [undefined] : []; - return this.data.mode === 'approver' - ? [...this.dossier.approverIds, ...unassignUser] - : [...this.dossier.memberIds, ...unassignUser]; + const dossier = this._dossiersService.activeDossier; + return this.data.mode === 'approver' ? [...dossier.approverIds, ...unassignUser] : [...dossier.memberIds, ...unassignUser]; } get changed(): boolean { @@ -75,14 +71,14 @@ export class AssignReviewerApproverDialogComponent { } async save() { - try { - const selectedUser = this.selectedSingleUser; + const selectedUser = this.selectedSingleUser; + try { if (this.data.mode === 'reviewer') { await this._filesService .setReviewerFor( this.data.files.map(f => f.fileId), - this.data.dossierId, + this._dossiersService.activeDossierId, selectedUser, ) .toPromise(); @@ -90,7 +86,7 @@ export class AssignReviewerApproverDialogComponent { await this._filesService .setUnderApprovalFor( this.data.files.map(f => f.fileId), - this.data.dossierId, + this._dossiersService.activeDossierId, selectedUser, ) .toPromise(); diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.ts index cd1573fa1..c789b445d 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.ts @@ -1,9 +1,7 @@ import { Component, Inject, OnInit } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { AppStateService } from '@state/app-state.service'; import { PermissionsService } from '@services/permissions.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { JustificationsService } from '@services/entity-services/justifications.service'; @@ -24,14 +22,12 @@ export class ChangeLegalBasisDialogComponent implements OnInit { legalOptions: LegalBasisOption[] = []; constructor( - private readonly _translateService: TranslateService, private readonly _justificationsService: JustificationsService, - private readonly _appStateService: AppStateService, private readonly _dossiersService: DossiersService, private readonly _permissionsService: PermissionsService, private readonly _formBuilder: FormBuilder, - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public annotations: AnnotationWrapper[], + readonly dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) readonly annotations: AnnotationWrapper[], ) {} get changed(): boolean { @@ -39,19 +35,18 @@ export class ChangeLegalBasisDialogComponent implements OnInit { } async ngOnInit() { - this.isDocumentAdmin = this._permissionsService.isApprover(); + const dossier = this._dossiersService.activeDossier; + this.isDocumentAdmin = this._permissionsService.isApprover(dossier); this.legalBasisForm = this._formBuilder.group({ reason: [null, Validators.required], comment: this.isDocumentAdmin ? [null] : [null, Validators.required], }); - const data = await this._justificationsService - .getForDossierTemplate(this._dossiersService.activeDossier.dossierTemplateId) - .toPromise(); + const data = await this._justificationsService.getForDossierTemplate(dossier.dossierTemplateId).toPromise(); this.legalOptions = data - .map(lbm => ({ + .map(lbm => ({ legalBasis: lbm.reason, description: lbm.description, label: lbm.name, diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/config.service.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/config.service.ts index dcdafc357..51b2dacf1 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/config.service.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/config.service.ts @@ -362,7 +362,8 @@ export class ConfigService { }; private _underApprovalFn = (reloadDossiers: () => Promise) => async (file: File) => { - if (this._dossiersService.activeDossier.approverIds.length > 1) { + const dossier = this._dossiersService.find(file.dossierId); + if (dossier.approverIds.length > 1) { this._fileActionService.assignFile('approver', null, file, () => this._loadingService.loadWhile(reloadDossiers()), true); } else { this._loadingService.start(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts index 382483ad3..bc6d06d25 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts @@ -54,6 +54,7 @@ import { saveAsCSV } from '@utils/csv-utils'; import { FilesService } from '@services/entity-services/files.service'; import { FilesMapService } from '@services/entity-services/files-map.service'; import { ReanalysisService } from '@services/reanalysis.service'; +import { DossierStatsService } from '@services/entity-services/dossier-stats.service'; @Component({ templateUrl: './dossier-overview-screen.component.html', @@ -108,6 +109,7 @@ export class DossierOverviewScreenComponent extends ListingComponent imple private readonly _userPreferenceService: UserPreferenceService, private readonly _filesService: FilesService, private readonly _fileMapService: FilesMapService, + private readonly _dossierStatsService: DossierStatsService, activatedRoute: ActivatedRoute, ) { super(_injector); @@ -157,6 +159,12 @@ export class DossierOverviewScreenComponent extends ListingComponent imple async ngOnInit(): Promise { await this._loadEntitiesFromState(); + + this.addSubscription = this._fileMapService + .get$(this.dossierId) + .pipe(tap(files => this.entitiesService.setEntities(files))) + .subscribe(); + try { this._fileDropOverlayService.initFileDropHandling(); @@ -220,6 +228,7 @@ export class DossierOverviewScreenComponent extends ListingComponent imple async reloadFiles() { const files = await this._appStateService.getFiles(this.currentDossier); + await this._dossierStatsService.getFor([this.dossierId]).toPromise(); this.entitiesService.setEntities(files); await this.calculateData(); } @@ -305,8 +314,6 @@ export class DossierOverviewScreenComponent extends ListingComponent imple this._loadingService.start(); await this._appStateService.getFiles(this.currentDossier); } - - this.entitiesService.setEntities(this._fileMapService.get(this.dossierId)); } private async _uploadFiles(files: FileUploadModel[]) { diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.html index 81fc4b41c..ffcf42195 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.html @@ -7,7 +7,7 @@ [subtitle]="'dossier-listing.stats.charts.dossiers' | translate" > -
+
diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts index bfe5c87e3..4bbfaad62 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts @@ -63,7 +63,6 @@ export class DossiersListingScreenComponent private readonly _filesService: FilesService, ) { super(_injector); - this._appStateService.reset(); } ngOnInit(): void { @@ -82,7 +81,6 @@ export class DossiersListingScreenComponent } ngOnAttach(): void { - this._appStateService.reset(); this.ngOnInit(); this.ngAfterViewInit(); this._tableComponent.scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth'); diff --git a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.html b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.html index 90a51c1e5..7136bddfd 100644 --- a/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.html +++ b/apps/red-ui/src/app/modules/dossier/shared/components/file-actions/file-actions.component.html @@ -47,7 +47,7 @@ (); - statusBarConfig?: readonly StatusBarConfig[]; + dossier$: Observable; + statusBarConfig?: List>; tooltipPosition?: 'below' | 'above'; toggleTooltip?: string; assignTooltip?: string; @@ -105,14 +109,11 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD ngOnInit(): void { this.setup(); + this.dossier$ = this.dossiersService.getEntityChanged$(this.file.dossierId).pipe(tap(() => this.setup())); this.addSubscription = this._filesMapService.watch$(this.file.dossierId, this.file.fileId).subscribe(file => { this.file = file; this.setup(); }); - - this.addSubscription = this.dossiersService.getEntityChanged$(this.file.dossierId).subscribe(() => { - this.setup(); - }); } toggleViewDocumentInfo() { @@ -175,41 +176,41 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD }); } - setFileUnderApproval($event: MouseEvent) { + async setFileUnderApproval($event: MouseEvent) { $event.stopPropagation(); - if (this.dossiersService.activeDossier.approverIds.length > 1) { + const dossier = this.dossiersService.find(this.file.dossierId); + if (dossier.approverIds.length > 1) { this._fileActionService.assignFile('approver', $event, this.file, () => this.reloadFiles('assign-reviewer'), true); } else { - this.addSubscription = this._fileActionService.setFilesUnderApproval([this.file]).subscribe(() => { - this.reloadFiles('set-under-approval'); - }); + await this._fileActionService.setFilesUnderApproval([this.file]).toPromise(); + this.reloadFiles('set-under-approval'); } } async setFileApproved($event: MouseEvent) { $event.stopPropagation(); - if (this.file.hasUpdates) { - this._dialogService.openDialog( - 'confirm', - $event, - new ConfirmationDialogInput({ - title: _('confirmation-dialog.approve-file.title'), - question: _('confirmation-dialog.approve-file.question'), - }), - async () => { - await this._setFileApproved(); - }, - ); - } else { + if (!this.file.hasUpdates) { await this._setFileApproved(); + return; } + + this._dialogService.openDialog( + 'confirm', + $event, + new ConfirmationDialogInput({ + title: _('confirmation-dialog.approve-file.title'), + question: _('confirmation-dialog.approve-file.question'), + }), + async () => { + await this._setFileApproved(); + }, + ); } - ocrFile($event: MouseEvent) { + async ocrFile($event: MouseEvent) { $event.stopPropagation(); - this.addSubscription = this._fileActionService.ocrFiles([this.file]).subscribe(() => { - this.reloadFiles('ocr-file'); - }); + await this._fileActionService.ocrFiles([this.file]).toPromise(); + this.reloadFiles('ocr-file'); } setFileUnderReview($event: MouseEvent, ignoreDialogChanges = false) { diff --git a/apps/red-ui/src/app/modules/upload-download/file-drop/file-drop.component.ts b/apps/red-ui/src/app/modules/upload-download/file-drop/file-drop.component.ts index 4df48a628..62c42a1dd 100644 --- a/apps/red-ui/src/app/modules/upload-download/file-drop/file-drop.component.ts +++ b/apps/red-ui/src/app/modules/upload-download/file-drop/file-drop.component.ts @@ -35,7 +35,8 @@ export class FileDropComponent { @HostListener('drop', ['$event']) onDrop(event: DragEvent) { - handleFileDrop(event, this._dossiersService.activeDossier, this.uploadFiles.bind(this)); + const dossier = this._dossiersService.find(this._dossiersService.activeDossierId); + handleFileDrop(event, dossier, this.uploadFiles.bind(this)); } @HostListener('dragover', ['$event']) diff --git a/apps/red-ui/src/app/services/entity-services/dossiers.service.ts b/apps/red-ui/src/app/services/entity-services/dossiers.service.ts index c8c417701..95016a254 100644 --- a/apps/red-ui/src/app/services/entity-services/dossiers.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dossiers.service.ts @@ -22,9 +22,9 @@ const GENERIC_MGS = _('add-dossier-dialog.errors.generic'); providedIn: 'root', }) export class DossiersService extends EntitiesService { - readonly stats$ = this.all$.pipe(switchMap(entities => this._generalStats$(entities))); - readonly activeDossier$: Observable; - private readonly _activeDossier$ = new BehaviorSubject(undefined); + readonly generalStats$ = this.all$.pipe(switchMap(entities => this._generalStats$(entities))); + readonly activeDossierId$: Observable; + private readonly _activeDossierId$ = new BehaviorSubject(undefined); constructor( private readonly _router: Router, @@ -34,43 +34,37 @@ export class DossiersService extends EntitiesService { private readonly _dossierStatsService: DossierStatsService, ) { super(_injector, Dossier, 'dossier'); - this.activeDossier$ = this._activeDossier$.asObservable(); + this.activeDossierId$ = this._activeDossierId$.asObservable(); _router.events.pipe(currentComponentRoute).subscribe((event: ActivationEnd) => { const dossierId = event.snapshot.paramMap.get('dossierId'); - const sameIdAsCurrentActive = dossierId === this._activeDossier$.getValue()?.dossierId; + const sameIdAsCurrentActive = dossierId === this._activeDossierId$.getValue(); if (sameIdAsCurrentActive) { return; } if (dossierId === null || dossierId === undefined) { - return this._activeDossier$.next(undefined); + return this._activeDossierId$.next(undefined); } if (!this.has(dossierId)) { - this._activeDossier$.next(undefined); + this._activeDossierId$.next(undefined); return this._router.navigate(['/main/dossiers']).then(); } - this._activeDossier$.next(this.find(dossierId)); - this.updateDossierDictionary(this.activeDossier.dossierTemplateId, dossierId).then(); + this._activeDossierId$.next(dossierId); + const dossier = this.find(dossierId); + this.updateDossierDictionary(dossier.dossierTemplateId, dossierId).then(); }); } get activeDossier(): Dossier | undefined { - return this._activeDossier$.value; + return this.find(this.activeDossierId); } get activeDossierId(): string | undefined { - return this._activeDossier$.value?.dossierId; - } - - replace(newDossier: Dossier) { - super.replace(newDossier); - if (newDossier.dossierId === this.activeDossierId) { - this._activeDossier$.next(newDossier); - } + return this._activeDossierId$.value; } async updateDossierDictionary(dossierTemplateId: string, dossierId: string) { diff --git a/apps/red-ui/src/app/state/app-state.guard.ts b/apps/red-ui/src/app/state/app-state.guard.ts index 23a7474c0..1acfb429e 100644 --- a/apps/red-ui/src/app/state/app-state.guard.ts +++ b/apps/red-ui/src/app/state/app-state.guard.ts @@ -36,11 +36,16 @@ export class AppStateGuard implements CanActivate { const { dossierId, fileId, dossierTemplateId, type } = route.params; - if (dossierId && !this._dossiersService.find(dossierId)) { + const dossier = this._dossiersService.find(dossierId); + if (dossierId && !dossier) { await this._router.navigate(['main', 'dossiers']); return false; } + if (fileId && this._filesMapService.get(dossierId).length === 0) { + await this._appStateService.getFiles(dossier); + } + if (fileId && !this._filesMapService.get(dossierId, fileId)) { await this._router.navigate(['main', 'dossiers', dossierId]); return false; diff --git a/apps/red-ui/src/app/state/app-state.service.ts b/apps/red-ui/src/app/state/app-state.service.ts index 9deb63428..e9ede4a0c 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -3,7 +3,7 @@ import { Dictionary, Dossier, DossierTemplate, File, IColors, IDossier } from '@ import { ActivationEnd, Router } from '@angular/router'; import { UserService } from '@services/user.service'; import { forkJoin, Observable, of } from 'rxjs'; -import { catchError, filter, first, map, tap } from 'rxjs/operators'; +import { catchError, first, map, tap } from 'rxjs/operators'; import { currentComponentRoute, FALLBACK_COLOR, hexToRgb } from '@utils/functions'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { UserPreferenceService } from '@services/user-preference.service'; @@ -49,12 +49,6 @@ export class AppStateService { return (this._appState.activeFileId = undefined); } - await _dossiersService.activeDossier$ - .pipe( - filter(dossier => !!dossier), - first(), - ) - .toPromise(); const dossierId = event.snapshot.paramMap.get('dossierId'); return this.activateFile(dossierId, fileId); }); @@ -159,6 +153,7 @@ export class AppStateService { async getFiles(dossier = this._dossiersService.activeDossier) { const files = await this._filesService.getFor(dossier.id).toPromise(); + await this._dossierStatsService.getFor([dossier.id]).toPromise(); const fileAttributes = this._fileAttributesService.getFileAttributeConfig(dossier.dossierTemplateId); const newFiles = files.map(iFile => new File(iFile, this._userService.getNameForId(iFile.currentReviewer), fileAttributes)); @@ -171,7 +166,8 @@ export class AppStateService { } async activateFile(dossierId: string, fileId: string) { - if (this._dossiersService.activeDossierId === dossierId && this.activeFileId === fileId) { + const activeDossierId = await this._dossiersService.activeDossierId$.pipe(first()).toPromise(); + if (activeDossierId === dossierId && this.activeFileId === fileId) { return; }