diff --git a/apps/red-ui/src/app/components/notifications/notifications.component.ts b/apps/red-ui/src/app/components/notifications/notifications.component.ts index 79b71d79c..f2812d947 100644 --- a/apps/red-ui/src/app/components/notifications/notifications.component.ts +++ b/apps/red-ui/src/app/components/notifications/notifications.component.ts @@ -1,12 +1,11 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@shared/pipes/date.pipe'; -import { AppStateService } from '@state/app-state.service'; import { UserService } from '@services/user.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { NotificationsService } from '@services/notifications.service'; import { Notification } from '@red/domain'; -import { distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'; +import { distinctUntilChanged, map, switchMapTo, tap } from 'rxjs/operators'; import { BehaviorSubject, Observable, timer } from 'rxjs'; import { AutoUnsubscribe, CHANGED_CHECK_INTERVAL, List, shareLast } from '@iqser/common-ui'; @@ -33,7 +32,6 @@ export class NotificationsComponent extends AutoUnsubscribe implements OnInit { private readonly _translateService: TranslateService, private readonly _userService: UserService, private readonly _notificationsService: NotificationsService, - private readonly _appStateService: AppStateService, private readonly _dossiersService: DossiersService, private readonly _datePipe: DatePipe, ) { @@ -43,17 +41,6 @@ export class NotificationsComponent extends AutoUnsubscribe implements OnInit { this.hasUnreadNotifications$ = this._hasUnreadNotifications$; } - async ngOnInit(): Promise { - await this._loadData(); - - this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL) - .pipe( - switchMap(() => this._notificationsService.getNotificationsIfChanged(INCLUDE_SEEN)), - tap(notifications => this._notifications$.next(notifications)), - ) - .subscribe(); - } - private get _hasUnreadNotifications$(): Observable { return this.notifications$.pipe( map(notifications => notifications.filter(n => !n.readDate).length > 0), @@ -62,6 +49,17 @@ export class NotificationsComponent extends AutoUnsubscribe implements OnInit { ); } + async ngOnInit(): Promise { + await this._loadData(); + + this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL) + .pipe( + switchMapTo(this._notificationsService.getNotificationsIfChanged(INCLUDE_SEEN)), + tap(notifications => this._notifications$.next(notifications)), + ) + .subscribe(); + } + async markRead($event, notifications: List = this._notifications$.getValue().map(n => n.id), isRead = true): Promise { $event.stopPropagation(); await this._notificationsService.toggleNotificationRead(notifications, isRead).toPromise(); diff --git a/apps/red-ui/src/app/guards/dossier-files-guard.ts b/apps/red-ui/src/app/guards/dossier-files-guard.ts index 61861a0a6..67d764cd8 100644 --- a/apps/red-ui/src/app/guards/dossier-files-guard.ts +++ b/apps/red-ui/src/app/guards/dossier-files-guard.ts @@ -3,7 +3,6 @@ import { ActivatedRouteSnapshot, CanActivate, CanDeactivate, Router, RouterState import { DossiersService } from '@services/entity-services/dossiers.service'; import { BreadcrumbsService } from '@services/breadcrumbs.service'; import { pluck } from 'rxjs/operators'; -import { AppStateService } from '@state/app-state.service'; import { FilesMapService } from '@services/entity-services/files-map.service'; import { FilesService } from '@services/entity-services/files.service'; @@ -11,7 +10,6 @@ import { FilesService } from '@services/entity-services/files.service'; export class DossierFilesGuard implements CanActivate, CanDeactivate { constructor( private readonly _dossiersService: DossiersService, - private readonly _appStateService: AppStateService, private readonly _breadcrumbsService: BreadcrumbsService, private readonly _filesMapService: FilesMapService, private readonly _filesService: FilesService, diff --git a/apps/red-ui/src/app/guards/dossiers.guard.ts b/apps/red-ui/src/app/guards/dossiers.guard.ts index d9c5c9938..db39ed378 100644 --- a/apps/red-ui/src/app/guards/dossiers.guard.ts +++ b/apps/red-ui/src/app/guards/dossiers.guard.ts @@ -2,13 +2,11 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, CanDeactivate, Router, RouterStateSnapshot } from '@angular/router'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { BreadcrumbsService } from '@services/breadcrumbs.service'; -import { AppStateService } from '@state/app-state.service'; @Injectable({ providedIn: 'root' }) export class DossiersGuard implements CanActivate, CanDeactivate { constructor( private readonly _dossiersService: DossiersService, - private readonly _appStateService: AppStateService, private readonly _breadcrumbsService: BreadcrumbsService, private readonly _router: Router, ) {} diff --git a/apps/red-ui/src/app/guards/file-preview.guard.ts b/apps/red-ui/src/app/guards/file-preview.guard.ts index 8672f0d5f..6d8fe5440 100644 --- a/apps/red-ui/src/app/guards/file-preview.guard.ts +++ b/apps/red-ui/src/app/guards/file-preview.guard.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, CanDeactivate, Router, RouterStateSnapshot } from '@angular/router'; import { FilesMapService } from '@services/entity-services/files-map.service'; -import { AppStateService } from '@state/app-state.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { BreadcrumbsService } from '@services/breadcrumbs.service'; import { pluck } from 'rxjs/operators'; @@ -11,7 +10,6 @@ export class FilePreviewGuard implements CanActivate, CanDeactivate { constructor( private readonly _filesMapService: FilesMapService, private readonly _dossiersService: DossiersService, - private readonly _appStateService: AppStateService, private readonly _breadcrumbsService: BreadcrumbsService, private readonly _router: Router, ) {} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts index bffaa60fe..c963908a1 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts @@ -1,6 +1,5 @@ import { Component, Inject } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { AppStateService } from '@state/app-state.service'; import { FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { fileAttributeTypesTranslations } from '../../translations/file-attribute-types-translations'; @@ -19,7 +18,6 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent { translations = fileAttributeTypesTranslations; constructor( - private readonly _appStateService: AppStateService, private readonly _formBuilder: FormBuilder, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) @@ -28,18 +26,6 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent { super(); } - private _getForm(fileAttribute: IFileAttributeConfig): FormGroup { - return this._formBuilder.group({ - label: [fileAttribute?.label, Validators.required], - csvColumnHeader: [fileAttribute?.csvColumnHeader], - type: [fileAttribute?.type || FileAttributeConfigTypes.TEXT, Validators.required], - readonly: [fileAttribute ? !fileAttribute.editable : false], - primaryAttribute: [fileAttribute?.primaryAttribute], - filterable: [fileAttribute?.filterable], - displayedInFileList: [fileAttribute?.displayedInFileList], - }); - } - get changed(): boolean { if (!this.fileAttribute) { return true; @@ -66,4 +52,16 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent { }; this.dialogRef.close(fileAttribute); } + + private _getForm(fileAttribute: IFileAttributeConfig): FormGroup { + return this._formBuilder.group({ + label: [fileAttribute?.label, Validators.required], + csvColumnHeader: [fileAttribute?.csvColumnHeader], + type: [fileAttribute?.type || FileAttributeConfigTypes.TEXT, Validators.required], + readonly: [fileAttribute ? !fileAttribute.editable : false], + primaryAttribute: [fileAttribute?.primaryAttribute], + filterable: [fileAttribute?.filterable], + displayedInFileList: [fileAttribute?.displayedInFileList], + }); + } } diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts index 5ad935999..66bb09192 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts @@ -8,7 +8,6 @@ import { LoadingService, TableColumnConfig, } from '@iqser/common-ui'; -import { AppStateService } from '@state/app-state.service'; import { AdminDialogService } from '../../services/admin-dialog.service'; import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service'; import { dossierAttributeTypesTranslations } from '../../translations/dossier-attribute-types-translations'; @@ -40,7 +39,6 @@ export class DossierAttributesListingScreenComponent extends ListingComponent +
-
+
- +
{{ 'file-preview.tabs.annotations.page-is' | translate }} - . @@ -201,8 +205,8 @@
diff --git a/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.ts index ea23c01ed..1741d5210 100644 --- a/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.ts @@ -43,7 +43,7 @@ export class FileWorkloadComponent { @Input() @Required() file!: File; @Input() @Required() dossier!: Dossier; @Input() hideSkipped: boolean; - @Input() excludePages: boolean; + @Input() showExcludedPages: boolean; @Input() annotationActionsTemplate: TemplateRef; @Input() viewer: WebViewerInstance; @Output() readonly shouldDeselectAnnotationsOnPageChangeChange = new EventEmitter(); @@ -54,10 +54,11 @@ export class FileWorkloadComponent { @Output() readonly selectPage = new EventEmitter(); @Output() readonly toggleSkipped = new EventEmitter(); @Output() readonly annotationsChanged = new EventEmitter(); - @Output() readonly actionPerformed = new EventEmitter(); + @Output() readonly excludePages = new EventEmitter(); displayedPages: number[] = []; pagesPanelActive = true; readonly displayedAnnotations$: Observable>; + @Output() @Required() readonly toggleViewExcludedPages = new EventEmitter(); private _annotations$ = new BehaviorSubject([]); @ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef; @ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef; @@ -244,10 +245,6 @@ export class FileWorkloadComponent { this.selectPage.emit(this._nextPageWithAnnotations()); } - viewExcludePages(): void { - this.actionPerformed.emit('view-exclude-pages'); - } - private _filterAnnotations( annotations: AnnotationWrapper[], primary: INestedFilter[], diff --git a/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts b/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts index 122980e03..81df44ccb 100644 --- a/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts @@ -12,7 +12,7 @@ import { ReanalysisService } from '@services/reanalysis.service'; }) export class PageExclusionComponent implements OnChanges { @Input() file: File; - @Output() readonly actionPerformed = new EventEmitter(); + @Output() readonly excludePages = new EventEmitter(); excludedPagesRanges: IPageRange[] = []; @ViewChild(InputWithActionComponent) private readonly _inputComponent: InputWithActionComponent; @@ -66,7 +66,7 @@ export class PageExclusionComponent implements OnChanges { ) .toPromise(); this._inputComponent.reset(); - this.actionPerformed.emit('exclude-pages'); + this.excludePages.emit(); } catch (e) { this._toaster.error(_('file-preview.tabs.exclude-pages.error')); this._loadingService.stop(); @@ -85,6 +85,6 @@ export class PageExclusionComponent implements OnChanges { ) .toPromise(); this._inputComponent.reset(); - this.actionPerformed.emit('exclude-pages'); + this.excludePages.emit(); } } 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 ae4634caf..e9a1dc5c5 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 @@ -1,8 +1,7 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { AppStateService } from '@state/app-state.service'; import { UserService } from '@services/user.service'; -import { Toaster } from '@iqser/common-ui'; +import { LoadingService, Toaster } from '@iqser/common-ui'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Dossier, File } from '@red/domain'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; @@ -29,9 +28,9 @@ export class AssignReviewerApproverDialogComponent { readonly userService: UserService, private readonly _toaster: Toaster, private readonly _formBuilder: FormBuilder, - private readonly _appStateService: AppStateService, private readonly _dossiersService: DossiersService, private readonly _filesService: FilesService, + private readonly _loadingService: LoadingService, readonly permissionsService: PermissionsService, private readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) readonly data: DialogData, @@ -69,36 +68,6 @@ export class AssignReviewerApproverDialogComponent { return this.data.files.reduce((prev, file) => prev && this.permissionsService.canUnassignUser(file), true); } - isOwner(userId: string): boolean { - return userId === this.selectedUser; - } - - async save() { - try { - if (this.data.mode === 'reviewer') { - await this._filesService - .setReviewerFor( - this.data.files.map(f => f.fileId), - this.dossier.id, - this.selectedUser, - ) - .toPromise(); - } else { - await this._filesService - .setUnderApprovalFor( - this.data.files.map(f => f.fileId), - this.dossier.id, - this.selectedUser, - ) - .toPromise(); - } - } catch (error) { - this._toaster.error(_('error.http.generic'), { params: error }); - } - - this._dialogRef.close(true); - } - /** Initialize the form with: * the id of the current reviewer of the files list if there is only one reviewer for all of them; * or the id of the current user @@ -123,6 +92,38 @@ export class AssignReviewerApproverDialogComponent { return user; } + isOwner(userId: string): boolean { + return userId === this.selectedUser; + } + + async save() { + this._loadingService.start(); + try { + if (this.data.mode === 'reviewer') { + await this._filesService + .setReviewerFor( + this.data.files.map(f => f.fileId), + this.dossier.id, + this.selectedUser, + ) + .toPromise(); + } else { + await this._filesService + .setUnderApprovalFor( + this.data.files.map(f => f.fileId), + this.dossier.id, + this.selectedUser, + ) + .toPromise(); + } + } catch (error) { + this._toaster.error(_('error.http.generic'), { params: error }); + } + this._loadingService.stop(); + + this._dialogRef.close(true); + } + private _getForm(): FormGroup { return this._formBuilder.group({ // Allow a null reviewer if a previous reviewer exists (= it's not the first assignment) & current user is allowed to unassign diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.ts index c094fccc5..fe136cdaf 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.ts @@ -1,7 +1,6 @@ import { Component, Inject, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { Dossier, IFile, IFileAttributeConfig } from '@red/domain'; -import { AppStateService } from '@state/app-state.service'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { FileAttributesService } from '@services/entity-services/file-attributes.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; @@ -17,7 +16,6 @@ export class DocumentInfoDialogComponent implements OnInit { private readonly _dossier: Dossier; constructor( - private readonly _appStateService: AppStateService, private readonly _dossiersService: DossiersService, private readonly _formBuilder: FormBuilder, private readonly _fileAttributesService: FileAttributesService, @@ -34,6 +32,16 @@ export class DocumentInfoDialogComponent implements OnInit { this.form = this._getForm(); } + async saveDocumentInfo() { + const attributeIdToValue = { + ...this.data.fileAttributes?.attributeIdToValue, + ...this.form.getRawValue(), + }; + await this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.data.dossierId, this.data.fileId).toPromise(); + this.data.fileAttributes = { attributeIdToValue }; + this.dialogRef.close(true); + } + private _getForm(): FormGroup { return this._formBuilder.group( this.attributes.reduce( @@ -45,14 +53,4 @@ export class DocumentInfoDialogComponent implements OnInit { ), ); } - - async saveDocumentInfo() { - const attributeIdToValue = { - ...this.data.fileAttributes?.attributeIdToValue, - ...this.form.getRawValue(), - }; - await this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.data.dossierId, this.data.fileId).toPromise(); - this.data.fileAttributes = { attributeIdToValue }; - this.dialogRef.close(true); - } } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts index 75851073a..5494a637d 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts @@ -10,7 +10,6 @@ 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 { AppStateService } from '@state/app-state.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { TeamMembersManagerComponent } from '../../components/team-members-manager/team-members-manager.component'; import { Observable } from 'rxjs'; @@ -38,7 +37,6 @@ export class EditDossierDialogComponent { constructor( private readonly _toaster: Toaster, - private readonly _appStateService: AppStateService, private readonly _dossiersService: DossiersService, private readonly _changeRef: ChangeDetectorRef, private readonly _dialogRef: MatDialogRef, diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.html index 0dd90ce37..73b1f5890 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/bulk-actions/dossier-overview-bulk-actions.component.html @@ -1,4 +1,4 @@ - + , private readonly _userPreferenceService: UserPreferenceService, private readonly _filesService: FilesService, ) {} - get selectedFiles(): File[] { - return this.listingService.selected; - } - get allSelectedFilesCanBeAssignedIntoSameState() { const allFilesAreUnderReviewOrUnassigned = this.selectedFiles.reduce( (acc, file) => acc && (file.isUnderReview || file.isUnassigned), @@ -131,51 +121,58 @@ export class DossierOverviewBulkActionsComponent { this.dossier.dossierId, ) .toPromise(); - this.reload.emit(); this._loadingService.stop(); }, ); } - setToUnderApproval() { + async setToUnderApproval() { // If more than 1 approver - show dialog and ask who to assign if (this.dossier.approverIds.length > 1) { this._assignFiles('approver', true); } else { - this._performBulkAction( - this._filesService.setUnderApprovalFor( + this._loadingService.start(); + await this._filesService + .setUnderApprovalFor( this.selectedFiles.map(f => f.id), this.dossier.id, this.dossier.approverIds[0], - ), - ); + ) + .toPromise(); + this._loadingService.stop(); } } - reanalyse() { + async reanalyse() { + this._loadingService.start(); const fileIds = this.selectedFiles.filter(file => file.analysisRequired).map(file => file.fileId); - this._performBulkAction(this._reanalysisService.reanalyzeFilesForDossier(fileIds, this.dossier.id)); + await this._reanalysisService.reanalyzeFilesForDossier(fileIds, this.dossier.id).toPromise(); + this._loadingService.stop(); } - ocr() { - this._performBulkAction( - this._reanalysisService.ocrFiles( + async ocr() { + this._loadingService.start(); + await this._reanalysisService + .ocrFiles( this.selectedFiles.map(f => f.fileId), this.dossier.id, - ), - ); + ) + .toPromise(); + this._loadingService.stop(); } - setToUnderReview() { - this._performBulkAction( - this._filesService.setUnderReviewFor( + async setToUnderReview() { + this._loadingService.start(); + await this._filesService + .setUnderReviewFor( this.selectedFiles.map(f => f.id), this.dossier.id, - ), - ); + ) + .toPromise(); + this._loadingService.stop(); } - approveDocuments() { + async approveDocuments(): Promise { const foundUpdatedFile = this.selectedFiles.find(file => file.hasUpdates); if (foundUpdatedFile) { this._dialogService.openDialog( @@ -185,31 +182,31 @@ export class DossierOverviewBulkActionsComponent { title: _('confirmation-dialog.approve-multiple-files.title'), question: _('confirmation-dialog.approve-multiple-files.question'), }), - () => { - this._performBulkAction( - this._filesService.setApprovedFor( + async () => { + this._loadingService.start(); + await this._filesService + .setApprovedFor( this.selectedFiles.map(f => f.id), this.dossier.id, - ), - ); + ) + .toPromise(); + this._loadingService.stop(); }, ); } else { - this._performBulkAction( - this._filesService.setApprovedFor( + this._loadingService.start(); + await this._filesService + .setApprovedFor( this.selectedFiles.map(f => f.id), this.dossier.id, - ), - ); + ) + .toPromise(); + this._loadingService.stop(); } } - assignToMe() { - this._fileAssignService.assignToMe(this.selectedFiles).then(() => { - this._loadingService.start(); - this.reload.emit(); - this._loadingService.stop(); - }); + async assignToMe() { + await this._fileAssignService.assignToMe(this.selectedFiles); } assign() { @@ -219,18 +216,6 @@ export class DossierOverviewBulkActionsComponent { private _assignFiles(mode: 'reviewer' | 'approver', ignoreChanged = false) { const data = { mode, files: this.selectedFiles, ignoreChanged }; - this._dialogService.openDialog('assignFile', null, data, () => { - this._loadingService.start(); - this.reload.emit(); - this._loadingService.stop(); - }); - } - - private _performBulkAction(obs: Observable) { - this._loadingService.start(); - obs.subscribe().add(() => { - this.reload.emit(); - this._loadingService.stop(); - }); + this._dialogService.openDialog('assignFile', null, data); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/screen-header/screen-header.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/screen-header/screen-header.component.ts index c31056276..193eaefc8 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/screen-header/screen-header.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/screen-header/screen-header.component.ts @@ -1,6 +1,15 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output, TemplateRef } from '@angular/core'; import { RouterHistoryService } from '@services/router-history.service'; -import { ActionConfig, CircleButtonTypes, EntitiesService, List, ListingService, SortingService, Toaster } from '@iqser/common-ui'; +import { + ActionConfig, + CircleButtonTypes, + EntitiesService, + List, + ListingService, + LoadingService, + SortingService, + Toaster, +} from '@iqser/common-ui'; import { Dossier, File } from '@red/domain'; import { PermissionsService } from '@services/permissions.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; @@ -34,6 +43,7 @@ export class ScreenHeaderComponent implements OnInit { readonly entitiesService: EntitiesService, readonly routerHistoryService: RouterHistoryService, private readonly _reanalysisService: ReanalysisService, + private readonly _loadingService: LoadingService, ) {} ngOnInit() { @@ -41,13 +51,14 @@ export class ScreenHeaderComponent implements OnInit { } async reanalyseDossier() { + this._loadingService.start(); try { await this._reanalysisService.reanalyzeDossier(this.dossier.dossierId, true).toPromise(); - this.actionPerformed.emit('reload'); this._toaster.success(_('dossier-overview.reanalyse-dossier.success')); } catch (e) { this._toaster.error(_('dossier-overview.reanalyse-dossier.error')); } + this._loadingService.stop(); } exportFilesAsCSV() { diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/table-item/table-item.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/table-item/table-item.component.html index a0a4be767..deff79925 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/table-item/table-item.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/table-item/table-item.component.html @@ -54,11 +54,5 @@ >
- +
diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/table-item/table-item.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/table-item/table-item.component.ts index 97c1df33a..33df01b32 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/table-item/table-item.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/table-item/table-item.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { File, IFileAttributeConfig } from '@red/domain'; import { Required } from '@iqser/common-ui'; @@ -12,5 +12,4 @@ export class TableItemComponent { @Input() @Required() file!: File; @Input() @Required() displayedAttributes!: IFileAttributeConfig[]; @Input() dossierTemplateId: string; - @Output() readonly calculateData = new EventEmitter(); } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/workflow-item/workflow-item.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/workflow-item/workflow-item.component.html index 6148644a7..fc2681153 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/workflow-item/workflow-item.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/workflow-item/workflow-item.component.html @@ -13,10 +13,5 @@
- +
diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/workflow-item/workflow-item.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/workflow-item/workflow-item.component.ts index 5fcbfd743..fffcf980d 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/workflow-item/workflow-item.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/workflow-item/workflow-item.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { File } from '@red/domain'; @Component({ @@ -9,5 +9,4 @@ import { File } from '@red/domain'; }) export class WorkflowItemComponent { @Input() file: File; - @Output() readonly actionPerformed = new EventEmitter(); } 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 2b429730d..6e30166ab 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 @@ -15,7 +15,6 @@ import { import { File, IFileAttributeConfig, StatusSorter, WorkflowFileStatus, WorkflowFileStatuses } from '@red/domain'; import { workflowFileStatusTranslations } from '../../translations/file-status-translations'; import { FileAssignService } from '../../shared/services/file-assign.service'; -import { AppStateService } from '@state/app-state.service'; import { PermissionsService } from '@services/permissions.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { TranslateService } from '@ngx-translate/core'; @@ -38,7 +37,6 @@ export class ConfigService { private readonly _fileAssignService: FileAssignService, private readonly _filesService: FilesService, private readonly _loadingService: LoadingService, - private readonly _appStateService: AppStateService, private readonly _dossiersService: DossiersService, private readonly _permissionsService: PermissionsService, private readonly _translateService: TranslateService, @@ -57,6 +55,47 @@ export class ConfigService { this._listingMode$.next(listingMode); } + get workflowConfig(): WorkflowConfig { + return { + columnIdentifierFn: entity => entity.workflowStatus, + itemVersionFn: (entity: File) => `${entity.lastUpdated}-${entity.numberOfAnalyses}`, + columns: [ + { + label: workflowFileStatusTranslations[WorkflowFileStatuses.UNASSIGNED], + key: WorkflowFileStatuses.UNASSIGNED, + enterFn: this._unassignFn, + enterPredicate: (file: File) => this._permissionsService.canUnassignUser(file), + color: '#D3D5DA', + }, + { + label: workflowFileStatusTranslations[WorkflowFileStatuses.UNDER_REVIEW], + enterFn: this._underReviewFn, + enterPredicate: (file: File) => + this._permissionsService.canSetUnderReview(file) || + this._permissionsService.canAssignToSelf(file) || + this._permissionsService.canAssignUser(file), + key: WorkflowFileStatuses.UNDER_REVIEW, + color: '#FDBD00', + }, + { + label: workflowFileStatusTranslations[WorkflowFileStatuses.UNDER_APPROVAL], + enterFn: this._underApprovalFn, + enterPredicate: (file: File) => + this._permissionsService.canSetUnderApproval(file) || this._permissionsService.canUndoApproval(file), + key: WorkflowFileStatuses.UNDER_APPROVAL, + color: '#374C81', + }, + { + label: workflowFileStatusTranslations[WorkflowFileStatuses.APPROVED], + enterFn: this._approveFn, + enterPredicate: (file: File) => this._permissionsService.isReadyForApproval(file) && file.canBeApproved, + key: WorkflowFileStatuses.APPROVED, + color: '#48C9F7', + }, + ], + }; + } + actionConfig(dossierId: string): List { return [ { @@ -106,47 +145,6 @@ export class ConfigService { ]; } - workflowConfig(reloadDossiers: () => Promise): WorkflowConfig { - return { - columnIdentifierFn: entity => entity.workflowStatus, - itemVersionFn: (entity: File) => `${entity.lastUpdated}-${entity.numberOfAnalyses}`, - columns: [ - { - label: workflowFileStatusTranslations[WorkflowFileStatuses.UNASSIGNED], - key: WorkflowFileStatuses.UNASSIGNED, - enterFn: this._unassignFn(reloadDossiers), - enterPredicate: (file: File) => this._permissionsService.canUnassignUser(file), - color: '#D3D5DA', - }, - { - label: workflowFileStatusTranslations[WorkflowFileStatuses.UNDER_REVIEW], - enterFn: this._underReviewFn(reloadDossiers), - enterPredicate: (file: File) => - this._permissionsService.canSetUnderReview(file) || - this._permissionsService.canAssignToSelf(file) || - this._permissionsService.canAssignUser(file), - key: WorkflowFileStatuses.UNDER_REVIEW, - color: '#FDBD00', - }, - { - label: workflowFileStatusTranslations[WorkflowFileStatuses.UNDER_APPROVAL], - enterFn: this._underApprovalFn(reloadDossiers), - enterPredicate: (file: File) => - this._permissionsService.canSetUnderApproval(file) || this._permissionsService.canUndoApproval(file), - key: WorkflowFileStatuses.UNDER_APPROVAL, - color: '#374C81', - }, - { - label: workflowFileStatusTranslations[WorkflowFileStatuses.APPROVED], - enterFn: this._approveFn(reloadDossiers), - enterPredicate: (file: File) => this._permissionsService.isReadyForApproval(file) && file.canBeApproved, - key: WorkflowFileStatuses.APPROVED, - color: '#48C9F7', - }, - ], - }; - } - filterGroups( entities: File[], fileAttributeConfigs: IFileAttributeConfig[], @@ -341,7 +339,7 @@ export class ConfigService { { id: 'assigned-to-me', label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'), - checker: this._recentlyModifiedChecker, + checker: this._assignedToMeChecker, disabled: entities.filter(this._assignedToMeChecker).length === 0, }, { @@ -363,36 +361,34 @@ export class ConfigService { this._dialogService.openDialog('editDossier', $event, { dossierId }); } - private _unassignFn = (reloadDossiers: () => Promise) => async (file: File) => { + private _unassignFn = async (file: File) => { this._loadingService.start(); if (file.isUnderReview) { await this._filesService.setReviewerFor([file.fileId], file.dossierId, null).toPromise(); } else if (file.isUnderApproval) { await this._filesService.setUnderApprovalFor([file.fileId], file.dossierId, null).toPromise(); } - this._loadingService.loadWhile(reloadDossiers()); + this._loadingService.stop(); }; - private _underReviewFn = (reloadDossiers: () => Promise) => (file: File) => { - this._fileAssignService.assignReviewer(null, file, () => this._loadingService.loadWhile(reloadDossiers()), true); + private _underReviewFn = async (file: File) => { + await this._fileAssignService.assignReviewer(null, file, true); }; - private _underApprovalFn = (reloadDossiers: () => Promise) => async (file: File) => { + private _underApprovalFn = async (file: File) => { const dossier = this._dossiersService.find(file.dossierId); if (dossier.approverIds.length > 1) { - this._fileAssignService.assignApprover(null, file, () => this._loadingService.loadWhile(reloadDossiers()), true); + await this._fileAssignService.assignApprover(null, file, true); } else { this._loadingService.start(); await this._filesService.setUnderApprovalFor([file.id], dossier.dossierId, dossier.approverIds[0]).toPromise(); - await reloadDossiers(); this._loadingService.stop(); } }; - private _approveFn = (reloadDossiers: () => Promise) => async (file: File) => { + private _approveFn = async (file: File) => { this._loadingService.start(); await this._filesService.setApprovedFor([file.id], file.dossierId).toPromise(); - await reloadDossiers(); this._loadingService.stop(); }; } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.html index 973b7739c..24d08766a 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.html @@ -1,10 +1,6 @@
- +
@@ -54,12 +50,14 @@
- + - + 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 724fbb019..8ceb18eda 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 @@ -11,14 +11,13 @@ import { ViewChild, } from '@angular/core'; import { Dossier, DossierAttributeWithValue, File, IFileAttributeConfig, WorkflowFileStatus } from '@red/domain'; -import { AppStateService } from '@state/app-state.service'; import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service'; import { FileUploadModel } from '@upload-download/model/file-upload.model'; import { FileUploadService } from '@upload-download/services/file-upload.service'; import { StatusOverlayService } from '@upload-download/services/status-overlay.service'; import * as moment from 'moment'; import { Observable, timer } from 'rxjs'; -import { filter, switchMap, tap } from 'rxjs/operators'; +import { filter, switchMapTo, tap } from 'rxjs/operators'; import { convertFiles, Files, handleFileDrop } from '@utils/index'; import { CHANGED_CHECK_INTERVAL, @@ -65,7 +64,7 @@ export class DossierOverviewScreenComponent extends ListingComponent imple analysisForced: boolean; displayedInFileListAttributes: IFileAttributeConfig[] = []; displayedAttributes: IFileAttributeConfig[] = []; - readonly workflowConfig: WorkflowConfig = this.configService.workflowConfig(() => this.reloadFiles()); + readonly workflowConfig: WorkflowConfig = this.configService.workflowConfig; readonly dossier$: Observable; readonly dossierId: string; currentDossier: Dossier; @@ -80,7 +79,6 @@ export class DossierOverviewScreenComponent extends ListingComponent imple private readonly _router: Router, readonly permissionsService: PermissionsService, private readonly _loadingService: LoadingService, - private readonly _appStateService: AppStateService, private readonly _dossiersService: DossiersService, private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _appConfigService: AppConfigService, @@ -124,22 +122,6 @@ export class DossierOverviewScreenComponent extends ListingComponent imple return this.filterService.getGroup('quickFilters')?.filters.filter(f => !f.required && f.checked); } - async actionPerformed(action?: string, file?: File) { - if (['assign-reviewer', 'reload'].includes(action)) { - return this.reloadFiles(); - } - - if (action === 'upload') { - return this._fileInput.nativeElement.click(); - } - - this._loadEntitiesFromState(); - - if (action === 'navigate') { - await this._router.navigate([file.routerLink]); - } - } - disabledFn = (file: File) => file.excluded; lastOpenedFn = (file: File) => this._userPreferenceService.getLastOpenedFileForDossier(file.dossierId) === file.id; @@ -159,9 +141,9 @@ export class DossierOverviewScreenComponent extends ListingComponent imple this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL) .pipe( - switchMap(() => this._filesService.hasChanges$(this.dossierId)), + switchMapTo(this._filesService.hasChanges$(this.dossierId)), filter(changed => changed), - switchMap(() => this.reloadFiles()), + switchMapTo(this._reloadFiles()), ) .subscribe(); @@ -202,11 +184,6 @@ export class DossierOverviewScreenComponent extends ListingComponent imple this.analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled; } - async reloadFiles() { - await this._filesService.loadAll(this.dossierId).toPromise(); - this._computeAllFilters(); - } - @HostListener('drop', ['$event']) onDrop(event: DragEvent): void { const currentDossier = this._dossiersService.find(this.dossierId); @@ -227,6 +204,11 @@ export class DossierOverviewScreenComponent extends ListingComponent imple recentlyModifiedChecker = (file: File) => moment(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment()); + private async _reloadFiles() { + await this._filesService.loadAll(this.dossierId).toPromise(); + this._computeAllFilters(); + } + private _loadEntitiesFromState() { this.currentDossier = this._dossiersService.find(this.dossierId); this._computeAllFilters(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.ts index 611d05fdb..6b347473f 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-actions/dossiers-listing-actions.component.ts @@ -8,7 +8,6 @@ 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 { ReanalysisService } from '@services/reanalysis.service'; -import { switchMapTo, tap } from 'rxjs/operators'; import { DossiersService } from '@services/entity-services/dossiers.service'; @Component({ @@ -66,7 +65,6 @@ export class DossiersListingActionsComponent implements OnChanges { async reanalyseDossier($event: MouseEvent, id: string): Promise { $event.stopPropagation(); - const reanalysis$ = this._reanalysisService.reanalyzeDossier(id).pipe(switchMapTo(this._dossiersService.loadAll())); - await reanalysis$.pipe(tap(() => this.actionPerformed.emit())).toPromise(); + await this._reanalysisService.reanalyzeDossier(id).toPromise(); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/config.service.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/config.service.ts index d78c92cfc..ce0711162 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/config.service.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/config.service.ts @@ -8,7 +8,6 @@ import { UserService } from '@services/user.service'; import { workflowFileStatusTranslations } from '../../translations/file-status-translations'; import { dossierMemberChecker, dossierTemplateChecker, RedactionFilterSorter } from '@utils/index'; import { workloadTranslations } from '../../translations/workload-translations'; -import { AppStateService } from '@state/app-state.service'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; import { DossierStatsService } from '@services/entity-services/dossier-stats.service'; @@ -18,7 +17,6 @@ export class ConfigService { private readonly _translateService: TranslateService, private readonly _userPreferenceService: UserPreferenceService, private readonly _userService: UserService, - private readonly _appStateService: AppStateService, private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dossierStatsService: DossierStatsService, ) {} @@ -44,38 +42,6 @@ export class ConfigService { _otherChecker = (dw: Dossier) => !dw.memberIds.includes(this._currentUser.id); - private _quickFilters(entities: Dossier[]): NestedFilter[] { - const myDossiersLabel = this._translateService.instant('dossier-listing.quick-filters.my-dossiers'); - const filters = [ - { - id: 'my-dossiers', - label: myDossiersLabel, - checker: this._myDossiersChecker, - disabled: entities.filter(this._myDossiersChecker).length === 0, - }, - { - id: 'to-approve', - label: this._translateService.instant('dossier-listing.quick-filters.to-approve'), - checker: this._toApproveChecker, - disabled: entities.filter(this._toApproveChecker).length === 0, - }, - { - id: 'to-review', - label: this._translateService.instant('dossier-listing.quick-filters.to-review'), - checker: this._toReviewChecker, - disabled: entities.filter(this._toReviewChecker).length === 0, - }, - { - id: 'other', - label: this._translateService.instant('dossier-listing.quick-filters.other'), - checker: this._otherChecker, - disabled: entities.filter(this._otherChecker).length === 0, - }, - ].map(filter => new NestedFilter(filter)); - - return filters.filter(f => f.label === myDossiersLabel || this._userPreferenceService.areDevFeaturesEnabled); - } - buttonsConfig(addDossier: () => void): ButtonConfig[] { return [ { @@ -214,6 +180,38 @@ export class ConfigService { return filterGroups; } + private _quickFilters(entities: Dossier[]): NestedFilter[] { + const myDossiersLabel = this._translateService.instant('dossier-listing.quick-filters.my-dossiers'); + const filters = [ + { + id: 'my-dossiers', + label: myDossiersLabel, + checker: this._myDossiersChecker, + disabled: entities.filter(this._myDossiersChecker).length === 0, + }, + { + id: 'to-approve', + label: this._translateService.instant('dossier-listing.quick-filters.to-approve'), + checker: this._toApproveChecker, + disabled: entities.filter(this._toApproveChecker).length === 0, + }, + { + id: 'to-review', + label: this._translateService.instant('dossier-listing.quick-filters.to-review'), + checker: this._toReviewChecker, + disabled: entities.filter(this._toReviewChecker).length === 0, + }, + { + id: 'other', + label: this._translateService.instant('dossier-listing.quick-filters.other'), + checker: this._otherChecker, + disabled: entities.filter(this._otherChecker).length === 0, + }, + ].map(filter => new NestedFilter(filter)); + + return filters.filter(f => f.label === myDossiersLabel || this._userPreferenceService.areDevFeaturesEnabled); + } + private _dossierStatusChecker = (dossier: Dossier, filter: INestedFilter) => { const stats = this._dossierStatsService.get(dossier.dossierId); return stats?.fileCountPerWorkflowStatus[filter.id]; 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 873d0e2ea..0b0219eff 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 @@ -1,5 +1,4 @@ import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; -import { AppStateService } from '@state/app-state.service'; import { Dossier } from '@red/domain'; import { UserService } from '@services/user.service'; import { PermissionsService } from '@services/permissions.service'; @@ -21,7 +20,7 @@ import { ConfigService } from '../config.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { FilesService } from '@services/entity-services/files.service'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; -import { switchMap, tap } from 'rxjs/operators'; +import { switchMapTo, tap } from 'rxjs/operators'; @Component({ templateUrl: './dossiers-listing-screen.component.html', @@ -50,7 +49,6 @@ export class DossiersListingScreenComponent extends ListingComponent im protected readonly _injector: Injector, private readonly _userService: UserService, readonly permissionsService: PermissionsService, - private readonly _appStateService: AppStateService, private readonly _dossiersService: DossiersService, private readonly _dialogService: DossiersDialogService, private readonly _translateChartService: TranslateChartService, @@ -70,7 +68,7 @@ export class DossiersListingScreenComponent extends ListingComponent im this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL) .pipe( - switchMap(() => this._dossiersService.loadAllIfChanged()), + switchMapTo(this._dossiersService.loadAllIfChanged()), tap(() => this.computeAllFilters()), ) .subscribe(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html index f8724f975..99bb129ec 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html @@ -93,9 +93,11 @@ @@ -156,36 +158,38 @@
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts index f27fac5b5..ead9075ae 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts @@ -81,7 +81,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni hideSkipped = false; displayPDFViewer = false; viewDocumentInfo = false; - excludePages = false; + showExcludedPages = false; @ViewChild(PdfViewerComponent) readonly viewerComponent: PdfViewerComponent; @ViewChild('fileActions') fileActions: FileActionsComponent; readonly dossierId: string; @@ -271,7 +271,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni const file = this._filesMapService.get(this.dossierId, this.fileId); if (file?.analysisRequired) { - this.fileActions.reanalyseFile(); + await this.fileActions.reanalyseFile(); } this.displayPDFViewer = true; @@ -367,7 +367,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni response.manualRedactionEntryWrapper.rectId, ); this._instance.Core.annotationManager.deleteAnnotation(annotation); - await this._filesService.reload(this.dossierId, this.fileId); + await this._filesService.reload(this.dossierId, this.fileId).toPromise(); const distinctPages = manualRedactionEntryWrapper.manualRedactionEntry.positions .map(p => p.page) .filter((item, pos, self) => self.indexOf(item) === pos); @@ -459,58 +459,33 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni await this._cleanupAndRedrawManualAnnotationsForEntirePage(annotation?.pageNumber || this.activeViewerPage); } - async fileActionPerformed(action: string) { - this.editingReviewer = false; + ocredFile(): void { + this._updateCanPerformActions(); + this._reloadFileOnReanalysis = true; + } - switch (action) { - case 'enable-analysis': - case 'disable-analysis': - this._loadingService.start(); - // the trigger will disable it later - break; + async excludePages(): Promise { + this._loadingService.start(); + await this._loadFileData(true); + this._cleanupAndRedrawManualAnnotations$(); + await this._stampPDF(); + this._loadingService.stop(); + } - case 'delete': - return this._router.navigate([this.dossiersService.find(this.dossierId).routerLink]); + toggleViewExcludedPages(): void { + this.showExcludedPages = !this.showExcludedPages; + this._workloadComponent.multiSelectActive = false; + this.viewDocumentInfo = false; + } - case 'reanalyse': - await this._loadFileData(true); - this._updateCanPerformActions(); - await this._filesService.loadAll(this.dossierId).toPromise(); - return; - - case 'exclude-pages': - await this._filesService.loadAll(this.dossierId).toPromise(); - await this._loadFileData(true); - this._cleanupAndRedrawManualAnnotations$(); - await this._stampPDF(); - this._loadingService.stop(); - return; - - case 'view-document-info': - this.viewDocumentInfo = !this.viewDocumentInfo; - return; - - case 'view-exclude-pages': - this.excludePages = !this.excludePages; - this._workloadComponent.multiSelectActive = false; - this.viewDocumentInfo = false; - return; - - case 'ocr-file': - this._updateCanPerformActions(); - this._reloadFileOnReanalysis = true; - return; - - default: - this._updateCanPerformActions(); - } + toggleViewDocumentInfo(): void { + this.viewDocumentInfo = !this.viewDocumentInfo; + this._workloadComponent.multiSelectActive = false; + this.showExcludedPages = false; } async assignToMe(file: File) { - await this._fileAssignService.assignToMe([file], async () => { - await this._filesService.reload(this.dossierId, this.fileId); - this._updateCanPerformActions(); - }); + await this._fileAssignService.assignToMe([file]); } async assignReviewer(file: File, user: User | string) { @@ -518,11 +493,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni const reviewerName = this.userService.getNameForId(reviewerId); const { dossierId, fileId, filename } = file; + this._loadingService.start(); await this._filesService.setReviewerFor([fileId], dossierId, reviewerId).toPromise(); + this._loadingService.stop(); this._toaster.info(_('assignment.reviewer'), { params: { reviewerName, filename } }); - await this._filesService.reload(this.dossierId, this.fileId); - this._updateCanPerformActions(); this.editingReviewer = false; } @@ -617,15 +592,17 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni private _subscribeToFileUpdates(): void { this.addSubscription = timer(0, 5000) - .pipe(switchMap(() => this._filesService.reload(this.dossierId, this.fileId))) + .pipe(switchMapTo(this._filesService.reload(this.dossierId, this.fileId))) .subscribe(); + this.addSubscription = this.file$.subscribe(() => { + this._updateCanPerformActions(); + }); this.addSubscription = this._filesMapService.fileReanalysed$ .pipe(filter(file => file.fileId === this.fileId)) .subscribe(async () => { await this._loadFileData(!this._reloadFileOnReanalysis); this._reloadFileOnReanalysis = false; this._loadingService.stop(); - this._updateCanPerformActions(); this._cleanupAndRedrawManualAnnotations$(); }); } @@ -679,7 +656,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni const currentPageAnnotations = this.annotations.filter(a => a.pageNumber === page); const currentPageAnnotationIds = currentPageAnnotations.map(a => a.id); - await this._filesService.reload(this.dossierId, this.fileId); + await this._filesService.reload(this.dossierId, this.fileId).toPromise(); this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise(); this.rebuildFilters(); diff --git a/apps/red-ui/src/app/modules/dossier/services/annotation-actions.service.ts b/apps/red-ui/src/app/modules/dossier/services/annotation-actions.service.ts index acc4b9e9a..ba57ebe6f 100644 --- a/apps/red-ui/src/app/modules/dossier/services/annotation-actions.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/annotation-actions.service.ts @@ -11,7 +11,6 @@ import { BASE_HREF } from '../../../tokens'; import { UserService } from '@services/user.service'; import { Core, WebViewerInstance } from '@pdftron/webviewer'; import { Dossier, IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain'; -import { AppStateService } from '../../../state/app-state.service'; import { toPosition } from '../utils/pdf-calculation.utils'; import { AnnotationDrawService } from './annotation-draw.service'; import { translateQuads } from '../../../utils'; @@ -22,7 +21,6 @@ export class AnnotationActionsService { constructor( @Inject(BASE_HREF) private readonly _baseHref: string, private readonly _ngZone: NgZone, - private readonly _appStateService: AppStateService, private readonly _userService: UserService, private readonly _permissionsService: PermissionsService, private readonly _manualAnnotationService: ManualAnnotationService, 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 7955ceb8f..3b7168251 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 @@ -62,7 +62,7 @@ > (); + @Output() readonly ocredFile = new EventEmitter(); toggleTooltip?: string; assignTooltip?: string; @@ -63,6 +64,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, isDossierOverviewWorkflow = false; isFilePreview = false; tooltipPosition: IqserTooltipPosition; + @Output() readonly toggleViewDocumentInfo = new EventEmitter(); + @Output() readonly toggleViewExcludedPages = new EventEmitter(); constructor( readonly permissionsService: PermissionsService, @@ -77,6 +80,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, private readonly _toaster: Toaster, private readonly _userPreferenceService: UserPreferenceService, private readonly _reanalysisService: ReanalysisService, + private readonly _router: Router, ) { super(); } @@ -93,16 +97,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, this.setup(); } - toggleViewDocumentInfo() { - this.actionPerformed.emit('view-document-info'); - } - - toggleExcludePages() { - this.actionPerformed.emit('view-exclude-pages'); - } - openDocument() { - this.actionPerformed.emit('navigate'); + this._router.navigate([this.file.routerLink]).then(); } openDeleteFileDialog($event: MouseEvent) { @@ -115,14 +111,13 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, }), async () => { this._loadingService.start(); - await this._fileManagementService - .delete([this.file.fileId], this.file.dossierId) - .toPromise() - .catch(error => { - this._toaster.error(_('error.http.generic'), { params: error }); - }); - await this._filesService.loadAll(this.file.dossierId).toPromise(); - this.actionPerformed.emit('delete'); + try { + const dossier = this.dossiersService.find(this.file.dossierId); + await this._fileManagementService.delete([this.file.fileId], this.file.dossierId).toPromise(); + await this._router.navigate([dossier.routerLink]); + } catch (error) { + this._toaster.error(_('error.http.generic'), { params: error }); + } this._loadingService.stop(); }, ); @@ -131,39 +126,24 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, assign($event: MouseEvent) { const mode = this.file.isUnderApproval ? 'approver' : 'reviewer'; const files = [this.file]; - this._dialogService.openDialog('assignFile', $event, { mode, files, withCurrentUserAsDefault: true }, () => { - this.actionPerformed.emit('assign-reviewer'); - }); + this._dialogService.openDialog('assignFile', $event, { mode, files }); } async assignToMe($event: MouseEvent) { $event.stopPropagation(); - - await this._fileAssignService.assignToMe([this.file], () => { - this.reloadFiles('reanalyse'); - }); + await this._fileAssignService.assignToMe([this.file]); } - reanalyseFile($event?: MouseEvent) { + async reanalyseFile($event?: MouseEvent) { if ($event) { $event.stopPropagation(); } - this.addSubscription = this._reanalysisService - .reanalyzeFilesForDossier([this.file.fileId], this.file.dossierId, true) - .subscribe(() => { - this.reloadFiles('reanalyse'); - }); + await this._reanalysisService.reanalyzeFilesForDossier([this.file.fileId], this.file.dossierId, true).toPromise(); } async setFileUnderApproval($event: MouseEvent) { $event.stopPropagation(); - const dossier = this.dossiersService.find(this.file.dossierId); - if (dossier.approverIds.length > 1) { - await this._fileAssignService.assignApprover($event, this.file, () => this.reloadFiles('assign-reviewer'), true); - } else { - await this._filesService.setUnderApprovalFor([this.file.id], dossier.id, dossier.approverIds[0]).toPromise(); - this.reloadFiles('set-under-approval'); - } + await this._fileAssignService.assignApprover($event, this.file, true); } async setFileApproved($event: MouseEvent) { @@ -188,27 +168,20 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, async ocrFile($event: MouseEvent) { $event.stopPropagation(); + this._loadingService.start(); await this._reanalysisService.ocrFiles([this.file.fileId], this.file.dossierId).toPromise(); - this.reloadFiles('ocr-file'); + this.ocredFile.emit(); + this._loadingService.stop(); } async setFileUnderReview($event: MouseEvent, ignoreDialogChanges = false) { - await this._fileAssignService.assignReviewer($event, this.file, () => this.reloadFiles('assign-reviewer'), ignoreDialogChanges); - } - - reloadFiles(action: string) { - this._filesService - .loadAll(this.file.dossierId) - .toPromise() - .then(() => { - this.actionPerformed.emit(action); - }); + await this._fileAssignService.assignReviewer($event, this.file, ignoreDialogChanges); } async toggleAnalysis() { + this._loadingService.start(); await this._reanalysisService.toggleAnalysis(this.file.dossierId, this.file.fileId, !this.file.excluded).toPromise(); - await this._filesService.loadAll(this.file.dossierId).toPromise(); - this.actionPerformed.emit(this.file?.excluded ? 'enable-analysis' : 'disable-analysis'); + this._loadingService.stop(); } forceReanalysisAction($event: LongPressEvent) { @@ -216,9 +189,9 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, } setup() { - this.isDossierOverview = this.type.startsWith('dossier-overview-list'); this.isDossierOverviewList = this.type === 'dossier-overview-list'; this.isDossierOverviewWorkflow = this.type === 'dossier-overview-workflow'; + this.isDossierOverview = this.type.startsWith('dossier-overview'); this.isFilePreview = this.type === 'file-preview'; this.tooltipPosition = this.isFilePreview ? 'below' : 'above'; @@ -251,7 +224,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy, } private async _setFileApproved() { + this._loadingService.start(); await this._filesService.setApprovedFor([this.file.id], this.file.dossierId).toPromise(); - this.reloadFiles('set-approved'); + this._loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/dossier/shared/services/file-assign.service.ts b/apps/red-ui/src/app/modules/dossier/shared/services/file-assign.service.ts index bb61fe900..9e48b02b6 100644 --- a/apps/red-ui/src/app/modules/dossier/shared/services/file-assign.service.ts +++ b/apps/red-ui/src/app/modules/dossier/shared/services/file-assign.service.ts @@ -4,8 +4,10 @@ import { Dossier, File } from '@red/domain'; import { DossiersDialogService } from '../../services/dossiers-dialog.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { FilesService } from '@services/entity-services/files.service'; -import { ConfirmationDialogInput, Toaster } from '@iqser/common-ui'; +import { ConfirmationDialogInput, LoadingService, Toaster } from '@iqser/common-ui'; import { DossiersService } from '@services/entity-services/dossiers.service'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; @Injectable() export class FileAssignService { @@ -14,10 +16,11 @@ export class FileAssignService { private readonly _userService: UserService, private readonly _filesService: FilesService, private readonly _dossiersService: DossiersService, + private readonly _loadingService: LoadingService, private readonly _toaster: Toaster, ) {} - async assignToMe(files: File[], callback?: Function) { + async assignToMe(files: File[]) { return new Promise((resolve, reject) => { const atLeastOneFileHasReviewer = files.reduce((acc, fs) => acc || !!fs.currentReviewer, false); if (atLeastOneFileHasReviewer) { @@ -26,27 +29,29 @@ export class FileAssignService { question: _('confirmation-dialog.assign-file-to-me.question'), }); this._dialogService.openDialog('confirm', null, data, () => { - this._assignReviewerToCurrentUser(files, callback) + this._assignReviewerToCurrentUser(files) + .toPromise() .then(() => resolve()) .catch(() => reject()); }); } else { - this._assignReviewerToCurrentUser(files, callback) + this._assignReviewerToCurrentUser(files) + .toPromise() .then(() => resolve()) .catch(() => reject()); } }); } - async assignReviewer($event: MouseEvent, file: File, callback?: Function, ignoreChanged = false): Promise { - await this._assignFile('reviewer', $event, file, callback, ignoreChanged); + async assignReviewer($event: MouseEvent, file: File, ignoreChanged = false): Promise { + await this._assignFile('reviewer', $event, file, ignoreChanged); } - async assignApprover($event: MouseEvent, file: File, callback?: Function, ignoreChanged = false): Promise { - await this._assignFile('approver', $event, file, callback, ignoreChanged); + async assignApprover($event: MouseEvent, file: File, ignoreChanged = false): Promise { + await this._assignFile('approver', $event, file, ignoreChanged); } - private async _assignFile(mode: 'reviewer' | 'approver', $event: MouseEvent, file: File, callback?: Function, ignoreChanged = false) { + private async _assignFile(mode: 'reviewer' | 'approver', $event: MouseEvent, file: File, ignoreChanged = false): Promise { $event?.stopPropagation(); const currentUserId = this._userService.currentUser.id; @@ -54,40 +59,28 @@ export class FileAssignService { const eligibleUsersIds = this._getUserIds(mode, currentDossier); if (file.isUnassigned) { - await this._makeAssignFileRequest(currentUserId, mode, [file], callback); + await this._makeAssignFileRequest(currentUserId, mode, [file]); } else if (file.currentReviewer === currentUserId) { if (eligibleUsersIds.includes(currentUserId)) { - await this._makeAssignFileRequest(currentUserId, mode, [file], callback); + await this._makeAssignFileRequest(currentUserId, mode, [file]); } else if (eligibleUsersIds.length === 1) { - await this._makeAssignFileRequest(eligibleUsersIds[0], mode, [file], callback); + await this._makeAssignFileRequest(eligibleUsersIds[0], mode, [file]); } else { - const files = [file]; - this._dialogService.openDialog('assignFile', null, { mode, files, ignoreChanged }, async () => { - if (callback) { - await callback(); - } - }); + const data = { mode, files: [file], ignoreChanged }; + this._dialogService.openDialog('assignFile', null, data); } } else { if (eligibleUsersIds.length === 1) { - await this._makeAssignFileRequest(eligibleUsersIds[0], mode, [file], callback); + await this._makeAssignFileRequest(eligibleUsersIds[0], mode, [file]); } else { - const files = [file]; - this._dialogService.openDialog( - 'assignFile', - null, - { mode, files, ignoreChanged, withCurrentUserAsDefault: true }, - async () => { - if (callback) { - await callback(); - } - }, - ); + const data = { mode, files: [file], ignoreChanged, withCurrentUserAsDefault: true }; + this._dialogService.openDialog('assignFile', null, data); } } } - private async _makeAssignFileRequest(userId: string, mode: 'reviewer' | 'approver', files: File[], callback?: Function) { + private async _makeAssignFileRequest(userId: string, mode: 'reviewer' | 'approver', files: File[]) { + this._loadingService.start(); try { if (mode === 'reviewer') { await this._filesService @@ -106,28 +99,24 @@ export class FileAssignService { ) .toPromise(); } - if (callback) { - await callback(); - } } catch (error) { this._toaster.error(_('error.http.generic'), { params: error }); } + this._loadingService.stop(); } private _getUserIds(mode: 'reviewer' | 'approver', dossier: Dossier) { return mode === 'approver' ? dossier.approverIds : dossier.memberIds; } - private async _assignReviewerToCurrentUser(files: File[], callback?: Function) { - await this._filesService + private _assignReviewerToCurrentUser(files: File[]): Observable { + this._loadingService.start(); + return this._filesService .setReviewerFor( files.map(f => f.fileId), files[0].dossierId, this._userService.currentUser.id, ) - .toPromise(); - if (callback) { - await callback(); - } + .pipe(tap(() => this._loadingService.stop())); } } diff --git a/apps/red-ui/src/app/services/entity-services/file-management.service.ts b/apps/red-ui/src/app/services/entity-services/file-management.service.ts index fd254a2e1..d1f0d6c68 100644 --- a/apps/red-ui/src/app/services/entity-services/file-management.service.ts +++ b/apps/red-ui/src/app/services/entity-services/file-management.service.ts @@ -2,29 +2,38 @@ import { GenericService, HeadersConfiguration, List, QueryParam, RequiredParam, import { Injectable, Injector } from '@angular/core'; import { HttpHeaders, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; +import { switchMap, switchMapTo } from 'rxjs/operators'; +import { FilesService } from '@services/entity-services/files.service'; +import { DossierStatsService } from '@services/entity-services/dossier-stats.service'; @Injectable({ providedIn: 'root', }) export class FileManagementService extends GenericService { - constructor(protected readonly _injector: Injector) { + constructor( + protected readonly _injector: Injector, + private readonly _filesService: FilesService, + private readonly _dossierStatsService: DossierStatsService, + ) { super(_injector, ''); } @Validate() - delete(@RequiredParam() body: List, @RequiredParam() dossierId: string) { - return super._post(body, `delete/${dossierId}`); + delete(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string) { + return super._post(fileIds, `delete/${dossierId}`).pipe(switchMapTo(this._filesService.loadAll(dossierId))); } @Validate() hardDelete(@RequiredParam() dossierId: string, @RequiredParam() fileIds: List) { const queryParams = fileIds.map(id => ({ key: 'fileIds', value: id })); - return super.delete({}, `delete/hard-delete/${dossierId}`, queryParams); + return super + .delete({}, `delete/hard-delete/${dossierId}`, queryParams) + .pipe(switchMapTo(this._dossierStatsService.getFor([dossierId]))); } @Validate() restore(@RequiredParam() body: List, @RequiredParam() dossierId: string) { - return this._post(body, `delete/restore/${dossierId}`); + return this._post(body, `delete/restore/${dossierId}`).pipe(switchMapTo(this._filesService.loadAll(dossierId))); } downloadOriginalFile(dossierId: string, fileId: string, observe?: 'body', inline?: boolean, indicator?: string): Observable; diff --git a/apps/red-ui/src/app/services/entity-services/files-map.service.ts b/apps/red-ui/src/app/services/entity-services/files-map.service.ts index 1402a468a..a8f69b6a4 100644 --- a/apps/red-ui/src/app/services/entity-services/files-map.service.ts +++ b/apps/red-ui/src/app/services/entity-services/files-map.service.ts @@ -37,23 +37,36 @@ export class FilesMapService { return entities.forEach(entity => this._entityChanged$.next(entity)); } + const reanalysedEntities = []; + const changedEntities = []; + // Keep old object references for unchanged entities const newEntities = entities.map(newEntity => { const oldEntity = this.get(key, newEntity.id); if (oldEntity?.lastProcessed !== newEntity.lastProcessed) { - this.fileReanalysed$.next(newEntity); + reanalysedEntities.push(newEntity); } if (newEntity.isEqual(oldEntity)) { return oldEntity; } - this._entityChanged$.next(newEntity); + changedEntities.push(newEntity); return newEntity; }); this._map.get(key).next(newEntities); + + // Emit observables only after entities have been updated + + for (const file of reanalysedEntities) { + this.fileReanalysed$.next(file); + } + + for (const file of changedEntities) { + this._entityChanged$.next(file); + } } replace(entity: File) { diff --git a/apps/red-ui/src/app/services/entity-services/files.service.ts b/apps/red-ui/src/app/services/entity-services/files.service.ts index 55e31a480..70b5d978e 100644 --- a/apps/red-ui/src/app/services/entity-services/files.service.ts +++ b/apps/red-ui/src/app/services/entity-services/files.service.ts @@ -3,9 +3,9 @@ import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/ import { File, IFile } from '@red/domain'; import { Observable } from 'rxjs'; import { UserService } from '../user.service'; -import { FileAttributesService } from '@services/entity-services/file-attributes.service'; import { FilesMapService } from '@services/entity-services/files-map.service'; -import { tap } from 'rxjs/operators'; +import { map, mapTo, switchMap, switchMapTo, tap } from 'rxjs/operators'; +import { DossierStatsService } from '@services/entity-services/dossier-stats.service'; @Injectable({ providedIn: 'root', @@ -14,38 +14,31 @@ export class FilesService extends EntitiesService { constructor( protected readonly _injector: Injector, private readonly _userService: UserService, - private readonly _fileAttributesService: FileAttributesService, private readonly _filesMapService: FilesMapService, + private readonly _dossierStatsService: DossierStatsService, ) { super(_injector, File, 'status'); } + /** Reload dossier files + stats. */ loadAll(dossierId: string) { const files$ = this.getFor(dossierId).pipe(mapEach(file => new File(file, this._userService.getNameForId(file.currentReviewer)))); - return files$.pipe(tap(files => this._filesMapService.set(dossierId, files))); + const loadStats$ = files$.pipe(switchMap(files => this._dossierStatsService.getFor([dossierId]).pipe(mapTo(files)))); + return loadStats$.pipe(tap(files => this._filesMapService.set(dossierId, files))); } - getOne(dossierId: string, fileId: string): Observable { - return super._getOne([dossierId, fileId]); - } - - async reload(dossierId: string, fileId: string): Promise { - const oldFile = this._filesMapService.get(dossierId, fileId); - - if (!oldFile) { - return null; - } - - const rawFile = await this.getOne(dossierId, fileId).toPromise(); - const newFile = new File(rawFile, this._userService.getNameForId(rawFile.currentReviewer)); - this._filesMapService.replace(newFile); - return newFile; + reload(dossierId: string, fileId: string): Observable { + return super._getOne([dossierId, fileId]).pipe( + map(file => new File(file, this._userService.getNameForId(file.currentReviewer))), + switchMap(file => this._dossierStatsService.getFor([dossierId]).pipe(mapTo(file))), + tap(file => this._filesMapService.replace(file)), + ); } @Validate() setUnderApprovalFor(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, approverId: string) { const url = `${this._defaultModelPath}/under-approval/${dossierId}/bulk`; - return this._post(fileIds, url, [{ key: 'approverId', value: approverId }]); + return this._post(fileIds, url, [{ key: 'approverId', value: approverId }]).pipe(switchMapTo(this.loadAll(dossierId))); } /** @@ -54,7 +47,7 @@ export class FilesService extends EntitiesService { @Validate() setReviewerFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string, reviewerId: string) { const url = `${this._defaultModelPath}/set-reviewer/${dossierId}/bulk`; - return this._post(filesIds, url, [{ key: 'reviewerId', value: reviewerId }]); + return this._post(filesIds, url, [{ key: 'reviewerId', value: reviewerId }]).pipe(switchMapTo(this.loadAll(dossierId))); } /** @@ -62,7 +55,9 @@ export class FilesService extends EntitiesService { */ @Validate() setApprovedFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string) { - return this._post(filesIds, `${this._defaultModelPath}/approved/${dossierId}/bulk`); + return this._post(filesIds, `${this._defaultModelPath}/approved/${dossierId}/bulk`).pipe( + switchMapTo(this.loadAll(dossierId)), + ); } /** @@ -70,7 +65,9 @@ export class FilesService extends EntitiesService { */ @Validate() setUnderReviewFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string) { - return this._post(filesIds, `${this._defaultModelPath}/under-review/${dossierId}/bulk`); + return this._post(filesIds, `${this._defaultModelPath}/under-review/${dossierId}/bulk`).pipe( + switchMapTo(this.loadAll(dossierId)), + ); } /** diff --git a/apps/red-ui/src/app/services/reanalysis.service.ts b/apps/red-ui/src/app/services/reanalysis.service.ts index d08bfdfd6..32c4811a2 100644 --- a/apps/red-ui/src/app/services/reanalysis.service.ts +++ b/apps/red-ui/src/app/services/reanalysis.service.ts @@ -1,33 +1,35 @@ import { Injectable, Injector } from '@angular/core'; import { GenericService, List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; import { IPageExclusionRequest } from '@red/domain'; +import { switchMap, switchMapTo } from 'rxjs/operators'; +import { FilesService } from './entity-services/files.service'; @Injectable({ providedIn: 'root', }) export class ReanalysisService extends GenericService { - constructor(protected readonly _injector: Injector) { + constructor(protected readonly _injector: Injector, private readonly _filesService: FilesService) { super(_injector, ''); } @Validate() excludePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) { - return this._post(body, `exclude-pages/${dossierId}/${fileId}`); + return this._post(body, `exclude-pages/${dossierId}/${fileId}`).pipe(switchMapTo(this._filesService.reload(dossierId, fileId))); } @Validate() includePages(@RequiredParam() body: IPageExclusionRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) { - return this._post(body, `include-pages/${dossierId}/${fileId}`); + return this._post(body, `include-pages/${dossierId}/${fileId}`).pipe(switchMapTo(this._filesService.reload(dossierId, fileId))); } @Validate() - reanalyzeFilesForDossier(@RequiredParam() body: List, @RequiredParam() dossierId: string, force?: boolean) { + reanalyzeFilesForDossier(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, force?: boolean) { const queryParams: QueryParam[] = []; if (force) { queryParams.push({ key: 'force', value: force }); } - return this._post(body, `reanalyze/${dossierId}/bulk`, queryParams); + return this._post(fileIds, `reanalyze/${dossierId}/bulk`, queryParams).pipe(switchMapTo(this._filesService.loadAll(dossierId))); } @Validate() @@ -37,12 +39,14 @@ export class ReanalysisService extends GenericService { queryParams.push({ key: 'excluded', value: excluded }); } - return this._post({}, `toggle-analysis/${dossierId}/${fileId}`, queryParams); + return this._post({}, `toggle-analysis/${dossierId}/${fileId}`, queryParams).pipe( + switchMapTo(this._filesService.loadAll(dossierId)), + ); } @Validate() ocrFiles(@RequiredParam() body: List, @RequiredParam() dossierId: string) { - return this._post(body, `ocr/reanalyze/${dossierId}/bulk`); + return this._post(body, `ocr/reanalyze/${dossierId}/bulk`).pipe(switchMapTo(this._filesService.loadAll(dossierId))); } @Validate() @@ -52,6 +56,6 @@ export class ReanalysisService extends GenericService { queryParams.push({ key: 'force', value: force }); } - return this._post({}, `reanalyze/${dossierId}`, queryParams); + return this._post({}, `reanalyze/${dossierId}`, queryParams).pipe(switchMapTo(this._filesService.loadAll(dossierId))); } } diff --git a/libs/common-ui b/libs/common-ui index 5d6e3053f..4621e9548 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit 5d6e3053fe6a92eb7bc9fe3e44934bd3c6f5943d +Subproject commit 4621e95481fc48b682c46d75d94b5af66426368e diff --git a/package.json b/package.json index 014a24032..26f9e1387 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redaction", - "version": "3.53.0", + "version": "3.54.0", "private": true, "license": "MIT", "scripts": { diff --git a/paligo-theme.tar.gz b/paligo-theme.tar.gz index 1ce7bdf00..1f097b50e 100644 Binary files a/paligo-theme.tar.gz and b/paligo-theme.tar.gz differ