extract file preview user management

This commit is contained in:
Dan Percic 2021-11-25 12:30:02 +02:00
parent d0f96990c7
commit aecdbcbe7b
10 changed files with 180 additions and 135 deletions

View File

@ -0,0 +1,46 @@
<iqser-status-bar [configs]="statusBarConfigs" [small]="true"></iqser-status-bar>
<div class="all-caps-label mr-16 ml-8">
{{ translations[file.workflowStatus] | translate }}
<span *ngIf="file.isUnderReview || file.isUnderApproval">{{ 'by' | translate }}:</span>
</div>
<redaction-initials-avatar
*ngIf="!editingReviewer"
[user]="file.assignee"
[withName]="!!file.assignee"
tooltipPosition="below"
></redaction-initials-avatar>
<div
(click)="editingReviewer = true"
*ngIf="!editingReviewer && canAssignReviewer"
class="assign-reviewer pointer"
translate="file-preview.assign-reviewer"
></div>
<redaction-assign-user-dropdown
(cancel)="editingReviewer = false"
(save)="assignReviewer(file, $event)"
*ngIf="editingReviewer"
[options]="usersOptions"
[value]="file.assignee"
></redaction-assign-user-dropdown>
<div *ngIf="!editingReviewer && canAssign" class="assign-actions-wrapper">
<iqser-circle-button
(action)="editingReviewer = true"
*ngIf="canAssignOrUnassign && !!file.assignee"
[tooltip]="assignTooltip"
icon="iqser:edit"
tooltipPosition="below"
></iqser-circle-button>
<iqser-circle-button
(action)="fileAssignService.assignToMe([file])"
*ngIf="canAssignToSelf"
[tooltip]="'file-preview.assign-me' | translate"
icon="red:assign-me"
tooltipPosition="below"
></iqser-circle-button>
</div>

View File

@ -0,0 +1,17 @@
:host {
display: contents;
}
.assign-reviewer {
margin-left: 6px;
text-decoration: underline;
}
.assign-actions-wrapper {
display: flex;
margin-left: 8px;
> *:not(:last-child) {
margin-right: 2px;
}
}

View File

@ -0,0 +1,101 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { Dossier, File, StatusBarConfigs, User } from '@red/domain';
import { List, LoadingService, Toaster } from '@iqser/common-ui';
import { PermissionsService } from '@services/permissions.service';
import { FileAssignService } from '../../../../shared/services/file-assign.service';
import { workflowFileStatusTranslations } from '../../../../translations/file-status-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { FilesService } from '@services/entity-services/files.service';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'redaction-user-management',
templateUrl: './user-management.component.html',
styleUrls: ['./user-management.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserManagementComponent implements OnChanges {
readonly translations = workflowFileStatusTranslations;
@Input() file: File;
@Input() dossier: Dossier;
editingReviewer = false;
statusBarConfigs: StatusBarConfigs;
canAssignToSelf = false;
canAssignUser = false;
canUnassignUser = false;
canAssign = false;
canAssignOrUnassign = false;
canAssignReviewer = false;
assignTooltip: string;
usersOptions: List;
constructor(
readonly fileAssignService: FileAssignService,
readonly permissionsService: PermissionsService,
readonly userService: UserService,
readonly filesService: FilesService,
readonly toaster: Toaster,
readonly loadingService: LoadingService,
readonly translateService: TranslateService,
) {}
private get _statusBarConfig(): StatusBarConfigs {
return [{ length: 1, color: this.file.workflowStatus }];
}
private get _assignOrChangeReviewerTooltip(): string {
return this.file.assignee
? this.translateService.instant('file-preview.change-reviewer')
: this.translateService.instant('file-preview.assign-reviewer');
}
private get _assignTooltip(): string {
return this.file.isUnderApproval
? this.translateService.instant('dossier-overview.assign-approver')
: this._assignOrChangeReviewerTooltip;
}
private get _canAssignReviewer(): boolean {
return !this.file.assignee && this.canAssignUser && this.dossier.hasReviewers;
}
private get _usersOptions(): List {
const unassignUser = this.canUnassignUser ? [undefined] : [];
return this.file.isUnderApproval ? [...this.dossier.approverIds, ...unassignUser] : [...this.dossier.memberIds, ...unassignUser];
}
ngOnChanges() {
this.canAssignToSelf = this.permissionsService.canAssignToSelf(this.file, this.dossier);
this.canAssignUser = this.permissionsService.canAssignUser(this.file, this.dossier);
this.canUnassignUser = this.permissionsService.canUnassignUser(this.file, this.dossier);
this.canAssignOrUnassign = this.canAssignUser || this.canUnassignUser;
this.canAssign = this.canAssignToSelf || this.canAssignOrUnassign;
this.statusBarConfigs = this._statusBarConfig;
this.canAssignReviewer = this._canAssignReviewer;
this.assignTooltip = this._assignTooltip;
this.usersOptions = this._usersOptions;
}
async assignReviewer(file: File, user: User | string) {
const assigneeId = typeof user === 'string' ? user : user?.id;
const reviewerName = this.userService.getNameForId(assigneeId);
const { dossierId, fileId, filename } = file;
this.loadingService.start();
if (!assigneeId) {
await this.filesService.setUnassigned([fileId], dossierId).toPromise();
} else if (file.isUnderReview) {
await this.filesService.setReviewerFor([fileId], dossierId, assigneeId).toPromise();
} else if (file.isUnderApproval) {
await this.filesService.setUnderApprovalFor([fileId], dossierId, assigneeId).toPromise();
}
this.loadingService.stop();
this.toaster.info(_('assignment.reviewer'), { params: { reviewerName, filename } });
this.editingReviewer = false;
}
}

View File

@ -7,57 +7,14 @@
</div>
<div class="flex-1 actions-container">
<iqser-status-bar [configs]="statusBarConfig(file)" [small]="true"></iqser-status-bar>
<div class="all-caps-label mr-16 ml-8">
{{ translations[file.workflowStatus] | translate }}
<span *ngIf="isUnderReviewOrApproval(file)">{{ 'by' | translate }}:</span>
</div>
<redaction-initials-avatar
*ngIf="!editingReviewer"
[user]="file.assignee"
[withName]="!!file.assignee"
tooltipPosition="below"
></redaction-initials-avatar>
<div
(click)="editingReviewer = true"
*ngIf="!editingReviewer && canAssignReviewer(file, dossier)"
class="assign-reviewer pointer"
translate="file-preview.assign-reviewer"
></div>
<redaction-assign-user-dropdown
(cancel)="editingReviewer = false"
(save)="assignReviewer(file, $event)"
*ngIf="editingReviewer"
[options]="usersOptions(file, dossier)"
[value]="file.assignee"
></redaction-assign-user-dropdown>
<div *ngIf="canAssign(file)" class="assign-actions-wrapper">
<iqser-circle-button
(action)="editingReviewer = true"
*ngIf="(permissionsService.canAssignUser(file) || permissionsService.canUnassignUser(file)) && !!file.assignee"
[tooltip]="assignTooltip(file)"
icon="iqser:edit"
tooltipPosition="below"
></iqser-circle-button>
<iqser-circle-button
(action)="fileAssignService.assignToMe([file])"
*ngIf="permissionsService.canAssignToSelf(file)"
[tooltip]="'file-preview.assign-me' | translate"
icon="red:assign-me"
tooltipPosition="below"
></iqser-circle-button>
</div>
<redaction-user-management [dossier]="dossier" [file]="file"></redaction-user-management>
<ng-container *ngIf="permissionsService.isApprover(dossier) && !!file.lastReviewer">
<div class="vertical-line"></div>
<div class="all-caps-label mr-16 ml-8" translate="file-preview.last-reviewer"></div>
<redaction-initials-avatar [user]="file.lastReviewer" [withName]="true"></redaction-initials-avatar>
</ng-container>
<div class="vertical-line"></div>
<redaction-file-actions

View File

@ -7,11 +7,6 @@
margin: 0 16px;
}
.assign-reviewer {
margin-left: 6px;
text-decoration: underline;
}
.actions-container {
display: flex;
justify-content: flex-end;
@ -54,15 +49,6 @@
}
}
.assign-actions-wrapper {
display: flex;
margin-left: 8px;
> *:not(:last-child) {
margin-right: 2px;
}
}
::ng-deep redaction-dictionary-annotation-icon {
margin-right: 8px;
}

View File

@ -18,23 +18,20 @@ import {
CircleButtonTypes,
Debounce,
FilterService,
List,
LoadingService,
OnAttach,
OnDetach,
processFilters,
shareDistinctLast,
Toaster,
} from '@iqser/common-ui';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ManualAnnotationResponse } from '@models/file/manual-annotation-response';
import { AnnotationData, FileDataModel } from '@models/file/file-data.model';
import { FileAssignService } from '../../shared/services/file-assign.service';
import { AnnotationDrawService } from '../../services/annotation-draw.service';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { Dossier, File, User, ViewMode, WorkflowFileStatus } from '@red/domain';
import { Dossier, File, ViewMode } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import { combineLatest, Observable, timer } from 'rxjs';
import { UserPreferenceService } from '@services/user-preference.service';
@ -45,9 +42,7 @@ import { FileWorkloadComponent } from './components/file-workload/file-workload.
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
import { clearStamps, stampPDFPage } from '@utils/page-stamper';
import { TranslateService } from '@ngx-translate/core';
import { workflowFileStatusTranslations } from '../../translations/file-status-translations';
import { handleFilterDelta } from '@utils/filter-utils';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FileActionsComponent } from '../../shared/components/file-actions/file-actions.component';
import { FilesService } from '@services/entity-services/files.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
@ -70,11 +65,9 @@ const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f'];
})
export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach {
readonly circleButtonTypes = CircleButtonTypes;
readonly translations = workflowFileStatusTranslations;
dialogRef: MatDialogRef<unknown>;
fullScreen = false;
editingReviewer = false;
shouldDeselectAnnotationsOnPageChange = true;
fileData: FileDataModel;
annotationData: AnnotationData;
@ -110,10 +103,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private readonly _activatedRoute: ActivatedRoute,
private readonly _dialogService: DossiersDialogService,
private readonly _router: Router,
private readonly _toaster: Toaster,
private readonly _annotationProcessingService: AnnotationProcessingService,
private readonly _annotationDrawService: AnnotationDrawService,
readonly fileAssignService: FileAssignService,
private readonly _fileDownloadService: PdfViewerDataService,
private readonly _filesService: FilesService,
private readonly _ngZone: NgZone,
@ -180,44 +171,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
);
}
assignTooltip(file: File): string {
return file.isUnderApproval
? this._translateService.instant('dossier-overview.assign-approver')
: this.assignOrChangeReviewerTooltip(file);
}
assignOrChangeReviewerTooltip(file: File): string {
return file.assignee
? this._translateService.instant('file-preview.change-reviewer')
: this._translateService.instant('file-preview.assign-reviewer');
}
statusBarConfig(file: File): [{ length: number; color: WorkflowFileStatus }] {
return [{ length: 1, color: file.workflowStatus }];
}
isUnderReviewOrApproval(file: File): boolean {
return file.isUnderReview || file.isUnderApproval;
}
canAssign(file: File): boolean {
return (
!this.editingReviewer &&
(this.permissionsService.canAssignUser(file) ||
this.permissionsService.canAssignToSelf(file) ||
this.permissionsService.canUnassignUser(file))
);
}
usersOptions(file: File, dossier: Dossier): List {
const unassignUser = this.permissionsService.canUnassignUser(file) ? [undefined] : [];
return file.isUnderApproval ? [...dossier.approverIds, ...unassignUser] : [...dossier.memberIds, ...unassignUser];
}
canAssignReviewer(file: File, dossier: Dossier): boolean {
return !file.assignee && this.permissionsService.canAssignUser(file) && dossier.hasReviewers;
}
async updateViewMode(): Promise<void> {
const annotations = this._getAnnotations(a => a.getCustomData('redacto-manager'));
const redactions = annotations.filter(a => a.getCustomData('redaction'));
@ -472,25 +425,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.excludedPagesService.hide();
}
async assignReviewer(file: File, user: User | string) {
const assigneeId = typeof user === 'string' ? user : user?.id;
const reviewerName = this._userService.getNameForId(assigneeId);
const { dossierId, fileId, filename } = file;
this._loadingService.start();
if (!assigneeId) {
await this._filesService.setUnassigned([fileId], dossierId).toPromise();
} else if (file.isUnderReview) {
await this._filesService.setReviewerFor([fileId], dossierId, assigneeId).toPromise();
} else if (file.isUnderApproval) {
await this._filesService.setUnderApprovalFor([fileId], dossierId, assigneeId).toPromise();
}
this._loadingService.stop();
this._toaster.info(_('assignment.reviewer'), { params: { reviewerName, filename } });
this.editingReviewer = false;
}
closeFullScreen() {
if (!!document.fullscreenElement && document.exitFullscreen) {
document.exitFullscreen().then();

View File

@ -19,6 +19,7 @@ import { TypeAnnotationIconComponent } from './components/type-annotation-icon/t
import { OverlayModule } from '@angular/cdk/overlay';
import { ViewSwitchComponent } from './components/view-switch/view-switch.component';
import { ViewModeService } from './services/view-mode.service';
import { UserManagementComponent } from './components/user-management/user-management.component';
const routes: Routes = [
{
@ -43,6 +44,7 @@ const routes: Routes = [
DocumentInfoComponent,
TypeAnnotationIconComponent,
ViewSwitchComponent,
UserManagementComponent,
],
imports: [
RouterModule.forChild(routes),

View File

@ -218,7 +218,6 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
this.showStatusBar = !this.file.isError && !this.file.isPending && this.isDossierOverviewList;
this.showAssignToSelf = this._permissionsService.canAssignToSelf(this.file) && this.isDossierOverview;
console.log(this.showAssignToSelf);
this.showAssign =
(this._permissionsService.canAssignUser(this.file) || this._permissionsService.canUnassignUser(this.file)) &&
this.isDossierOverview;

View File

@ -35,14 +35,12 @@ export class PermissionsService {
return (this.isOwner(dossier) && !file.isApproved) || file.isNew;
}
canAssignToSelf(file: File): boolean {
const dossier = this._getDossier(file);
canAssignToSelf(file: File, dossier = this._getDossier(file)): boolean {
const precondition = this.isDossierMember(dossier) && !this.isFileAssignee(file) && !file.isError && !file.isProcessing;
return precondition && (file.isNew || file.isUnderReview || (file.isUnderApproval && this.isApprover(dossier)));
}
canAssignUser(file: File): boolean {
const dossier = this._getDossier(file);
canAssignUser(file: File, dossier = this._getDossier(file)): boolean {
const precondition = !file.isProcessing && !file.isError && !file.isApproved && this.isApprover(dossier);
if (precondition) {
@ -56,13 +54,11 @@ export class PermissionsService {
return false;
}
canUnassignUser(file: File): boolean {
const dossier = this._getDossier(file);
canUnassignUser(file: File, dossier = this._getDossier(file)): boolean {
return (file.isUnderReview || file.isUnderApproval) && (this.isFileAssignee(file) || this.isApprover(dossier));
}
canSetUnderReview(file: File): boolean {
const dossier = this._getDossier(file);
canSetUnderReview(file: File, dossier = this._getDossier(file)): boolean {
return file.isUnderApproval && this.isApprover(dossier);
}

View File

@ -31,3 +31,10 @@ export const isProcessingStatuses: List<ProcessingFileStatus> = [
ProcessingFileStatuses.INDEXING,
ProcessingFileStatuses.PROCESSING,
] as const;
export interface StatusBarConfig {
readonly length: number;
readonly color: WorkflowFileStatus;
}
export type StatusBarConfigs = List<StatusBarConfig>;