fix file actions issues, live update on dossier change

This commit is contained in:
Dan Percic 2021-11-15 15:01:44 +02:00
parent 9ad4e2400a
commit 1249e41089
15 changed files with 90 additions and 93 deletions

View File

@ -18,7 +18,7 @@
translate="top-bar.navigation-items.dossiers"
></a>
<ng-container *ngIf="dossiersService.activeDossier$ | async as dossier">
<ng-container *ngIf="activeDossier$ | async as dossier">
<mat-icon svgIcon="iqser:arrow-right"></mat-icon>
<a

View File

@ -8,7 +8,7 @@ import { FileDownloadService } from '@upload-download/services/file-download.ser
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 { filter, map, startWith, switchMap } from 'rxjs/operators';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { shareDistinctLast } from '@iqser/common-ui';
@ -71,6 +71,7 @@ export class BaseScreenComponent {
action: (query): void => 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),

View File

@ -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<File, PageIndicatorComponent>('handlePageRead')
file: File;
@Input()
@OnChange<boolean, PageIndicatorComponent>('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) {

View File

@ -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<AssignReviewerApproverDialogComponent, boolean>,
@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();

View File

@ -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<ChangeLegalBasisDialogComponent>,
@Inject(MAT_DIALOG_DATA) public annotations: AnnotationWrapper[],
readonly dialogRef: MatDialogRef<ChangeLegalBasisDialogComponent>,
@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<LegalBasisOption>(lbm => ({
legalBasis: lbm.reason,
description: lbm.description,
label: lbm.name,

View File

@ -362,7 +362,8 @@ export class ConfigService {
};
private _underApprovalFn = (reloadDossiers: () => Promise<void>) => 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();

View File

@ -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<File> 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<File> imple
async ngOnInit(): Promise<void> {
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<File> 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<File> imple
this._loadingService.start();
await this._appStateService.getFiles(this.currentDossier);
}
this.entitiesService.setEntities(this._fileMapService.get(this.dossierId));
}
private async _uploadFiles(files: FileUploadModel[]) {

View File

@ -7,7 +7,7 @@
[subtitle]="'dossier-listing.stats.charts.dossiers' | translate"
></redaction-simple-doughnut-chart>
<div *ngIf="dossiersService.stats$ | async as stats" class="dossier-stats-container">
<div *ngIf="dossiersService.generalStats$ | async as stats" class="dossier-stats-container">
<div class="dossier-stats-item">
<mat-icon svgIcon="red:needs-work"></mat-icon>
<div>

View File

@ -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');

View File

@ -47,7 +47,7 @@
<!-- download redacted file-->
<redaction-file-download-btn
[dossier]="dossiersService.activeDossier$ | async"
[dossier]="dossier$ | async"
[files]="[file]"
[tooltipClass]="'small'"
[tooltipPosition]="tooltipPosition"

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { File, FileStatus } from '@red/domain';
import { Dossier, File, FileStatus } from '@red/domain';
import { AppStateService } from '@state/app-state.service';
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
import {
@ -8,6 +8,7 @@ import {
CircleButtonType,
CircleButtonTypes,
ConfirmationDialogInput,
List,
LoadingService,
OnChange,
Required,
@ -22,6 +23,8 @@ import { FileActionService } from '../../services/file-action.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { FileManagementService } from '../../services/file-management.service';
import { FilesMapService } from '@services/entity-services/files-map.service';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Component({
selector: 'redaction-file-actions',
@ -41,7 +44,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
@Input() @Required() type: 'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow';
@Output() readonly actionPerformed = new EventEmitter<string>();
statusBarConfig?: readonly StatusBarConfig<FileStatus>[];
dossier$: Observable<Dossier>;
statusBarConfig?: List<StatusBarConfig<FileStatus>>;
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) {

View File

@ -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'])

View File

@ -22,9 +22,9 @@ const GENERIC_MGS = _('add-dossier-dialog.errors.generic');
providedIn: 'root',
})
export class DossiersService extends EntitiesService<Dossier, IDossier> {
readonly stats$ = this.all$.pipe(switchMap(entities => this._generalStats$(entities)));
readonly activeDossier$: Observable<Dossier | undefined>;
private readonly _activeDossier$ = new BehaviorSubject<Dossier | undefined>(undefined);
readonly generalStats$ = this.all$.pipe(switchMap(entities => this._generalStats$(entities)));
readonly activeDossierId$: Observable<string | undefined>;
private readonly _activeDossierId$ = new BehaviorSubject<string | undefined>(undefined);
constructor(
private readonly _router: Router,
@ -34,43 +34,37 @@ export class DossiersService extends EntitiesService<Dossier, IDossier> {
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) {

View File

@ -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;

View File

@ -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;
}