Refactored file actions & event emitters

This commit is contained in:
Adina Țeudan 2021-11-22 21:11:53 +02:00
parent 85d7c59c3a
commit dd319dfee9
24 changed files with 309 additions and 382 deletions

View File

@ -1,11 +1,11 @@
<div *ngIf="!excludePages" class="right-title heading" translate="file-preview.tabs.annotations.label">
<div *ngIf="!showExcludedPages" class="right-title heading" translate="file-preview.tabs.annotations.label">
<div>
<div
(click)="multiSelectActive = true"
*ngIf="!multiSelectActive && !isReadOnly"
class="all-caps-label primary pointer"
translate="file-preview.tabs.annotations.select"
iqserHelpMode="bulk-select-annotations"
translate="file-preview.tabs.annotations.select"
></div>
<iqser-popup-filter
[actionsTemplate]="annotationFilterActionTemplate"
@ -16,10 +16,10 @@
</div>
</div>
<div *ngIf="excludePages" class="right-title heading" translate="file-preview.tabs.exclude-pages.label">
<div *ngIf="showExcludedPages" class="right-title heading" translate="file-preview.tabs.exclude-pages.label">
<div>
<iqser-circle-button
(action)="actionPerformed.emit('view-exclude-pages')"
(action)="toggleViewExcludedPages.emit()"
[tooltip]="'file-preview.tabs.exclude-pages.close' | translate"
icon="iqser:close"
tooltipPosition="before"
@ -111,11 +111,11 @@
</div>
<div style="overflow: hidden; width: 100%">
<ng-container *ngIf="!excludePages">
<ng-container *ngIf="!showExcludedPages">
<div [attr.anotation-page-header]="activeViewerPage" [class.padding-left-0]="currentPageIsExcluded" class="page-separator">
<span *ngIf="!!activeViewerPage" class="flex-align-items-center">
<iqser-circle-button
(action)="viewExcludePages()"
(action)="toggleViewExcludedPages.emit()"
*ngIf="currentPageIsExcluded"
[tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize"
icon="red:exclude-pages"
@ -161,7 +161,8 @@
>
<ng-container *ngIf="currentPageIsExcluded">
{{ 'file-preview.tabs.annotations.page-is' | translate }}
<a (click)="viewExcludePages()" class="with-underline" translate="file-preview.excluded-from-redaction"></a
<a (click)="toggleViewExcludedPages.emit()" class="with-underline"
translate="file-preview.excluded-from-redaction"></a
>.
</ng-container>
</iqser-empty-state>
@ -201,8 +202,8 @@
</ng-container>
<redaction-page-exclusion
(actionPerformed)="actionPerformed.emit($event)"
*ngIf="excludePages"
(excludePages)="excludePages.emit()"
*ngIf="showExcludedPages"
[file]="file"
></redaction-page-exclusion>
</div>

View File

@ -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<unknown>;
@Input() viewer: WebViewerInstance;
@Output() readonly shouldDeselectAnnotationsOnPageChangeChange = new EventEmitter<boolean>();
@ -54,10 +54,11 @@ export class FileWorkloadComponent {
@Output() readonly selectPage = new EventEmitter<number>();
@Output() readonly toggleSkipped = new EventEmitter<MouseEvent>();
@Output() readonly annotationsChanged = new EventEmitter<AnnotationWrapper>();
@Output() readonly actionPerformed = new EventEmitter<string>();
@Output() readonly excludePages = new EventEmitter<void>();
displayedPages: number[] = [];
pagesPanelActive = true;
readonly displayedAnnotations$: Observable<Map<number, AnnotationWrapper[]>>;
@Output() @Required() readonly toggleViewExcludedPages = new EventEmitter<void>();
private _annotations$ = new BehaviorSubject<AnnotationWrapper[]>([]);
@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[],

View File

@ -12,7 +12,7 @@ import { ReanalysisService } from '@services/reanalysis.service';
})
export class PageExclusionComponent implements OnChanges {
@Input() file: File;
@Output() readonly actionPerformed = new EventEmitter<string>();
@Output() readonly excludePages = new EventEmitter<void>();
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();
}
}

View File

@ -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';
@ -28,9 +27,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<AssignReviewerApproverDialogComponent, boolean>,
@Inject(MAT_DIALOG_DATA) readonly data: DialogData,
@ -68,36 +67,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
@ -119,6 +88,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

View File

@ -1,4 +1,4 @@
<ng-container (longPress)="forceReanalysisAction($event)" *ngIf="listingService.selectedLength$ | async" redactionLongPress>
<ng-container (longPress)="forceReanalysisAction($event)" *ngIf="selectedFiles.length" redactionLongPress>
<iqser-circle-button
(action)="delete()"
*ngIf="canDelete"

View File

@ -1,11 +1,9 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { Dossier, File } from '@red/domain';
import { FileAssignService } from '../../../../shared/services/file-assign.service';
import { Observable } from 'rxjs';
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
import { CircleButtonTypes, ConfirmationDialogInput, ListingService, LoadingService } from '@iqser/common-ui';
import { CircleButtonTypes, ConfirmationDialogInput, LoadingService, Required } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { LongPressEvent } from '@shared/directives/long-press.directive';
@ -22,14 +20,11 @@ import { FilesService } from '@services/entity-services/files.service';
})
export class DossierOverviewBulkActionsComponent {
readonly circleButtonTypes = CircleButtonTypes;
@Input() dossier: Dossier;
@Output() readonly reload = new EventEmitter();
@Input() @Required() dossier: Dossier;
@Input() @Required() selectedFiles: File[];
analysisForced: boolean;
constructor(
private readonly _appStateService: AppStateService,
private readonly _dialogService: DossiersDialogService,
private readonly _fileManagementService: FileManagementService,
private readonly _reanalysisService: ReanalysisService,
@ -37,15 +32,10 @@ export class DossierOverviewBulkActionsComponent {
private readonly _fileAssignService: FileAssignService,
private readonly _loadingService: LoadingService,
private readonly _translateService: TranslateService,
readonly listingService: ListingService<File>,
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<void> {
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<any>) {
this._loadingService.start();
obs.subscribe().add(() => {
this.reload.emit();
this._loadingService.stop();
});
this._dialogService.openDialog('assignFile', null, data);
}
}

View File

@ -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<File>,
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() {

View File

@ -55,7 +55,6 @@
</div>
<redaction-file-actions
(actionPerformed)="calculateData.emit($event)"
*ngIf="!file.isProcessing"
[file]="file"
class="mr-4"

View File

@ -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<string>();
}

View File

@ -14,7 +14,6 @@
</div>
<redaction-file-actions
(actionPerformed)="actionPerformed.emit($event)"
*ngIf="!file.isProcessing"
[file]="file"
type="dossier-overview-workflow"

View File

@ -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<string>();
}

View File

@ -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<File, WorkflowFileStatus> {
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<ActionConfig> {
return [
{
@ -106,47 +145,6 @@ export class ConfigService {
];
}
workflowConfig(reloadDossiers: () => Promise<void>): WorkflowConfig<File, WorkflowFileStatus> {
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<void>) => 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<void>) => (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<void>) => 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<void>) => 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();
};
}

View File

@ -1,7 +1,6 @@
<ng-container *ngIf="dossier$ | async as dossier">
<section (longPress)="forceReanalysisAction($event)" redactionLongPress>
<redaction-screen-header
(actionPerformed)="actionPerformed($event)"
[analysisForced]="analysisForced"
[dossier]="dossier"
></redaction-screen-header>
@ -54,12 +53,12 @@
</section>
<ng-template #bulkActions>
<redaction-dossier-overview-bulk-actions (reload)="reloadFiles()" [dossier]="dossier"></redaction-dossier-overview-bulk-actions>
<redaction-dossier-overview-bulk-actions [dossier]="dossier"
[selectedFiles]="this.listingService.selected"></redaction-dossier-overview-bulk-actions>
</ng-template>
<ng-template #tableItemTemplate let-file="entity">
<redaction-table-item
(calculateData)="actionPerformed($event)"
[displayedAttributes]="displayedAttributes"
[dossierTemplateId]="dossier.dossierTemplateId"
[file]="file"
@ -74,5 +73,5 @@
<input #fileInput (change)="uploadFiles($event.target['files'])" class="file-upload-input" multiple="true" type="file" />
<ng-template #workflowItemTemplate let-entity="entity">
<redaction-workflow-item (actionPerformed)="actionPerformed($event, entity)" [file]="entity"></redaction-workflow-item>
<redaction-workflow-item [file]="entity"></redaction-workflow-item>
</ng-template>

View File

@ -11,7 +11,6 @@ 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';
@ -65,7 +64,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
analysisForced: boolean;
displayedInFileListAttributes: IFileAttributeConfig[] = [];
displayedAttributes: IFileAttributeConfig[] = [];
readonly workflowConfig: WorkflowConfig<File, WorkflowFileStatus> = this.configService.workflowConfig(() => this.reloadFiles());
readonly workflowConfig: WorkflowConfig<File, WorkflowFileStatus> = this.configService.workflowConfig;
readonly dossier$: Observable<Dossier>;
readonly dossierId: string;
currentDossier: Dossier;
@ -80,7 +79,6 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> 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<File> 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;
@ -161,7 +143,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
.pipe(
switchMap(() => this._filesService.hasChanges$(this.dossierId)),
filter(changed => changed),
switchMap(() => this.reloadFiles()),
switchMap(() => this._reloadFiles()),
)
.subscribe();
@ -202,11 +184,6 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> 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<File> 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();

View File

@ -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<void> {
$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();
}
}

View File

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

View File

@ -93,9 +93,11 @@
<redaction-file-actions
#fileActions
(actionPerformed)="fileActionPerformed($event)"
(ocredFile)="ocredFile()"
(toggleViewDocumentInfo)="toggleViewDocumentInfo()"
(toggleViewExcludedPages)="toggleViewExcludedPages()"
[activeDocumentInfo]="viewDocumentInfo"
[activeExcludePages]="excludePages"
[activeExcludePages]="showExcludedPages"
[file]="file"
type="file-preview"
></redaction-file-actions>
@ -156,36 +158,38 @@
<div class="right-container">
<iqser-empty-state
*ngIf="file.excluded && !viewDocumentInfo && !excludePages"
*ngIf="file.excluded && !viewDocumentInfo && !showExcludedPages"
[horizontalPadding]="40"
[text]="'file-preview.tabs.is-excluded' | translate"
icon="red:needs-work"
></iqser-empty-state>
<redaction-document-info
(closeDocumentInfoView)="viewDocumentInfo = false"
(closeDocumentInfoView)="toggleViewDocumentInfo()"
*ngIf="viewDocumentInfo"
[file]="fileData.file"
></redaction-document-info>
<redaction-file-workload
#fileWorkloadComponent
(actionPerformed)="fileActionPerformed($event)"
(annotationsChanged)="annotationsChangedByReviewAction($event)"
(deselectAnnotations)="deselectAnnotations($event)"
(excludePages)="excludePages()"
(selectAnnotations)="selectAnnotations($event)"
(selectPage)="selectPage($event)"
(toggleSkipped)="toggleSkipped($event)"
(toggleViewExcludedPages)="toggleViewExcludedPages()"
*ngIf="!file.excluded"
[(shouldDeselectAnnotationsOnPageChange)]="shouldDeselectAnnotationsOnPageChange"
[activeViewerPage]="activeViewerPage"
[annotationActionsTemplate]="annotationActionsTemplate"
[annotations]="annotations"
[dialogRef]="dialogRef"
[excludePages]="excludePages"
[dossier]="dossier"
[file]="file"
[hideSkipped]="hideSkipped"
[selectedAnnotations]="selectedAnnotations"
[showExcludedPages]="showExcludedPages"
[viewedPages]="fileData?.viewedPages"
[viewer]="activeViewer"
></redaction-file-workload>

View File

@ -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<void> {
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;
}
@ -619,13 +594,15 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.addSubscription = timer(0, 5000)
.pipe(switchMap(() => 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();

View File

@ -62,7 +62,7 @@
></redaction-file-download-btn>
<iqser-circle-button
(action)="toggleViewDocumentInfo()"
(action)="toggleViewDocumentInfo.emit()"
*ngIf="showDocumentInfo"
[attr.aria-expanded]="activeDocumentInfo"
[tooltip]="'file-preview.document-info' | translate"
@ -71,7 +71,7 @@
></iqser-circle-button>
<iqser-circle-button
(action)="toggleExcludePages()"
(action)="toggleViewExcludedPages.emit()"
*ngIf="showExcludePages"
[attr.aria-expanded]="activeExcludePages"
[showDot]="!!file.excludedPages?.length"

View File

@ -22,6 +22,7 @@ import { DossiersService } from '@services/entity-services/dossiers.service';
import { FileManagementService } from '@services/entity-services/file-management.service';
import { FilesService } from '@services/entity-services/files.service';
import { ReanalysisService } from '@services/reanalysis.service';
import { Router } from '@angular/router';
@Component({
selector: 'redaction-file-actions',
@ -37,7 +38,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
@Input() activeDocumentInfo: boolean;
@Input() activeExcludePages: boolean;
@Input() @Required() type: 'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow';
@Output() readonly actionPerformed = new EventEmitter<string>();
@Output() readonly ocredFile = new EventEmitter<void>();
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<void>();
@Output() readonly toggleViewExcludedPages = new EventEmitter<void>();
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) {
@ -121,8 +117,6 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
.catch(error => {
this._toaster.error(_('error.http.generic'), { params: error });
});
await this._filesService.loadAll(this.file.dossierId).toPromise();
this.actionPerformed.emit('delete');
this._loadingService.stop();
},
);
@ -131,39 +125,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 }, () => {
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) {
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 +167,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();
}
setFileUnderReview($event: MouseEvent, ignoreDialogChanges = false) {
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);
});
async setFileUnderReview($event: MouseEvent, ignoreDialogChanges = false) {
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 +188,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 +223,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();
}
}

View File

@ -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<void>((resolve, reject) => {
const atLeastOneFileHasReviewer = files.reduce((acc, fs) => acc || !!fs.currentReviewer, false);
if (atLeastOneFileHasReviewer) {
@ -26,48 +29,43 @@ 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());
}
});
}
assignReviewer($event: MouseEvent, file: File, callback?: Function, ignoreChanged = false): void {
this._assignFile('reviewer', $event, file, callback, ignoreChanged);
async assignReviewer($event: MouseEvent, file: File, ignoreChanged = false): Promise<void> {
await this._assignFile('reviewer', $event, file, ignoreChanged);
}
assignApprover($event: MouseEvent, file: File, callback?: Function, ignoreChanged = false): void {
this._assignFile('approver', $event, file, callback, ignoreChanged);
async assignApprover($event: MouseEvent, file: File, ignoreChanged = false): Promise<void> {
await this._assignFile('approver', $event, file, ignoreChanged);
}
private _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<void> {
const dossier = this._dossiersService.find(file.dossierId);
const userIds = this._getUserIds(mode, dossier);
if (userIds.length === 1 || userIds.includes(this._userService.currentUser.id)) {
if (userIds.length === 1) {
$event?.stopPropagation(); // event$ is null when called from workflow view
const userId = userIds.length === 1 ? userIds[0] : this._userService.currentUser.id;
this._makeAssignFileRequest(userId, mode, [file]).then(async () => {
if (callback) {
await callback();
}
});
await this._makeAssignFileRequest(userId, mode, [file]);
} else {
const data = { mode, files: [file], ignoreChanged };
this._dialogService.openDialog('assignFile', $event, data, async () => {
if (callback) {
await callback();
}
});
this._dialogService.openDialog('assignFile', $event, data);
}
}
private async _makeAssignFileRequest(userId: string, mode: 'reviewer' | 'approver', files: File[]) {
this._loadingService.start();
try {
if (mode === 'reviewer') {
await this._filesService
@ -89,22 +87,21 @@ export class FileAssignService {
} 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<any> {
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()));
}
}

View File

@ -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) {

View File

@ -7,7 +7,6 @@ import { FilesMapService } from '@services/entity-services/files-map.service';
import { map, mapTo, switchMap, tap } from 'rxjs/operators';
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
@Injectable({
providedIn: 'root',
})

View File

@ -14,22 +14,22 @@ export class ReanalysisService extends GenericService<unknown> {
@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(switchMap(() => 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(switchMap(() => 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(switchMap(() => this._filesService.loadAll(dossierId)));
}
@Validate()