Update workflow UI

This commit is contained in:
Adina Țeudan 2021-12-01 21:30:31 +02:00
parent bf859b1f91
commit 9845267cc5
23 changed files with 438 additions and 258 deletions

View File

@ -4,12 +4,12 @@ import { File } from '@red/domain';
import { DossiersService } from '@services/entity-services/dossiers.service';
@Component({
selector: 'redaction-file-workload-column',
templateUrl: './file-workload-column.component.html',
styleUrls: ['./file-workload-column.component.scss'],
selector: 'redaction-file-workload',
templateUrl: './file-workload.component.html',
styleUrls: ['./file-workload.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileWorkloadColumnComponent {
export class FileWorkloadComponent {
@Input() file: File;
constructor(private readonly _appStateService: AppStateService, private readonly _dossiersService: DossiersService) {}

View File

@ -17,7 +17,7 @@
<ng-container *ngIf="!file.isError">
<div class="cell">
<redaction-file-workload-column [file]="file"></redaction-file-workload-column>
<redaction-file-workload [file]="file"></redaction-file-workload>
</div>
<div class="user-column cell">

View File

@ -13,5 +13,18 @@
</div>
</div>
<redaction-file-actions *ngIf="!file.isProcessing" [file]="file" type="dossier-overview-workflow"></redaction-file-actions>
<div *ngFor="let config of displayedAttributes" class="small-label mt-4">
{{ file.fileAttributes.attributeIdToValue[config.id] || '-' }}
</div>
<redaction-file-workload [file]="file"></redaction-file-workload>
<div class="file-actions">
<redaction-file-actions
*ngIf="!file.isProcessing"
[file]="file"
[maxWidth]="width - 20"
type="dossier-overview-workflow"
></redaction-file-actions>
</div>
</div>

View File

@ -1,7 +1,7 @@
@use 'common-mixins';
.workflow-item {
padding: 10px;
padding: 10px 10px 8px 10px;
> div {
display: flex;
@ -23,12 +23,31 @@
}
}
redaction-file-actions {
redaction-file-workload {
margin-top: 10px;
display: block;
min-height: 16px;
}
.file-actions {
margin-top: 8px;
min-height: 34px;
overflow: hidden;
}
redaction-file-actions:not(.keep-visible) {
display: none;
}
&:hover .filename {
text-decoration: underline;
}
&:hover redaction-file-actions {
display: block;
display: initial;
}
}
.mt-4 {
margin-top: 4px;
}

View File

@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { File } from '@red/domain';
import { File, IFileAttributeConfig } from '@red/domain';
import { Required } from '@iqser/common-ui';
@Component({
selector: 'redaction-workflow-item',
@ -8,5 +9,7 @@ import { File } from '@red/domain';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WorkflowItemComponent {
@Input() file: File;
@Input() @Required() file!: File;
@Input() @Required() displayedAttributes!: IFileAttributeConfig[];
@Input() width: number;
}

View File

@ -27,6 +27,7 @@ import { ConfigService as AppConfigService } from '@services/config.service';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { FilesService } from '@services/entity-services/files.service';
import { BehaviorSubject, Observable } from 'rxjs';
import { noop } from 'lodash';
@Injectable()
export class ConfigService {
@ -63,8 +64,8 @@ export class ConfigService {
{
label: workflowFileStatusTranslations[WorkflowFileStatuses.NEW],
key: WorkflowFileStatuses.NEW,
enterFn: this._unassignFn,
enterPredicate: (file: File) => this._permissionsService.canUnassignUser(file),
enterFn: noop,
enterPredicate: () => false,
color: '#D3D5DA',
},
{
@ -362,12 +363,6 @@ export class ConfigService {
this._dialogService.openDialog('editDossier', $event, { dossierId });
}
private _unassignFn = async (file: File) => {
this._loadingService.start();
await this._filesService.setUnassigned([file.fileId], file.dossierId).toPromise();
this._loadingService.stop();
};
private _underReviewFn = async (file: File) => {
await this._fileAssignService.assignReviewer(null, file, true);
};

View File

@ -11,7 +11,7 @@ import { DossierDetailsStatsComponent } from './components/dossier-details-stats
import { TableItemComponent } from './components/table-item/table-item.component';
import { ConfigService } from './config.service';
import { SharedDossiersModule } from '../../shared/shared-dossiers.module';
import { FileWorkloadColumnComponent } from './components/table-item/file-workload-column/file-workload-column.component';
import { FileWorkloadComponent } from './components/table-item/file-workload/file-workload.component';
import { FileStatsComponent } from './components/file-stats/file-stats.component';
import { WorkflowItemComponent } from './components/workflow-item/workflow-item.component';
import { ScreenHeaderComponent } from './components/screen-header/screen-header.component';
@ -36,7 +36,7 @@ const routes: Routes = [
DossierOverviewBulkActionsComponent,
DossierDetailsComponent,
DossierDetailsStatsComponent,
FileWorkloadColumnComponent,
FileWorkloadComponent,
TableItemComponent,
FileStatsComponent,
WorkflowItemComponent,

View File

@ -34,14 +34,13 @@
[addElementIcon]="'iqser:upload'"
[config]="workflowConfig"
[itemClasses]="{ disabled: disabledFn }"
[itemHeight]="'56px'"
[itemTemplate]="workflowItemTemplate"
[noDataButtonIcon]="'iqser:upload'"
[noDataButtonLabel]="'dossier-overview.no-data.action' | translate"
[noDataIcon]="'iqser:document'"
[noDataText]="'dossier-overview.no-data.title' | translate"
[showNoDataButton]="true"
addElementColumn="UNASSIGNED"
addElementColumn="NEW"
></iqser-workflow>
</div>
@ -76,6 +75,6 @@
<input #fileInput (change)="uploadFiles($event.target['files'])" class="file-upload-input" multiple="true" type="file" />
<ng-template #workflowItemTemplate let-entity="entity">
<redaction-workflow-item [file]="entity"></redaction-workflow-item>
<ng-template #workflowItemTemplate let-entity="entity" let-itemWidth="itemWidth">
<redaction-workflow-item [displayedAttributes]="displayedAttributes" [file]="entity" [width]="itemWidth"></redaction-workflow-item>
</ng-template>

View File

@ -44,7 +44,6 @@ import { DossiersDialogService } from '../../services/dossiers-dialog.service';
import { clearStamps, stampPDFPage } from '@utils/page-stamper';
import { TranslateService } from '@ngx-translate/core';
import { handleFilterDelta } from '@utils/filter-utils';
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';
import { FileManagementService } from '@services/entity-services/file-management.service';
@ -55,6 +54,7 @@ import { ExcludedPagesService } from './services/excluded-pages.service';
import { ViewModeService } from './services/view-mode.service';
import { MultiSelectService } from './services/multi-select.service';
import { DocumentInfoService } from './services/document-info.service';
import { ReanalysisService } from '../../../../services/reanalysis.service';
import Annotation = Core.Annotations.Annotation;
import PDFNet = Core.PDFNet;
@ -78,7 +78,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
hideSkipped = false;
displayPDFViewer = false;
@ViewChild(PdfViewerComponent) readonly viewerComponent: PdfViewerComponent;
@ViewChild('fileActions') fileActions: FileActionsComponent;
readonly dossierId: string;
readonly canPerformAnnotationActions$: Observable<boolean>;
readonly dossier$: Observable<Dossier>;
@ -117,6 +116,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private readonly _translateService: TranslateService,
private readonly _filesMapService: FilesMapService,
private readonly _dossiersService: DossiersService,
private readonly _reanalysisService: ReanalysisService,
readonly excludedPagesService: ExcludedPagesService,
readonly viewModeService: ViewModeService,
readonly multiSelectService: MultiSelectService,
@ -239,7 +239,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
const file = this._filesMapService.get(this.dossierId, this.fileId);
if (file?.analysisRequired) {
await this.fileActions.reanalyseFile();
await this._reanalysisService.reanalyzeFilesForDossier([this.fileId], this.dossierId, true).toPromise();
}
this.displayPDFViewer = true;

View File

@ -16,153 +16,12 @@
</ng-container>
<ng-template #actions (longPress)="forceReanalysisAction($event)" redactionLongPress>
<div *ngIf="file" class="file-actions">
<iqser-circle-button
(action)="openDocument()"
*ngIf="showOpenDocument"
<div class="file-actions">
<redaction-expandable-file-actions
[actions]="buttons"
[buttonType]="buttonType"
[maxWidth]="maxWidth"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.open-document' | translate"
[type]="buttonType"
icon="iqser:collapse"
></iqser-circle-button>
<iqser-circle-button
(action)="openDeleteFileDialog($event)"
*ngIf="showDelete"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.delete.action' | translate"
[type]="buttonType"
icon="iqser:trash"
></iqser-circle-button>
<iqser-circle-button
(action)="assign($event)"
*ngIf="showAssign"
[tooltipPosition]="tooltipPosition"
[tooltip]="assignTooltip | translate"
[type]="buttonType"
icon="red:assign"
></iqser-circle-button>
<iqser-circle-button
(action)="assignToMe($event)"
*ngIf="showAssignToSelf"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.assign-me' | translate"
[type]="buttonType"
icon="red:assign-me"
></iqser-circle-button>
<!-- download redacted file-->
<redaction-file-download-btn
[files]="[file]"
[tooltipClass]="'small'"
[tooltipPosition]="tooltipPosition"
[type]="buttonType"
></redaction-file-download-btn>
<iqser-circle-button
(action)="documentInfoService.toggle()"
*ngIf="documentInfoService"
[attr.aria-expanded]="documentInfoService.shown$ | async"
[tooltip]="'file-preview.document-info' | translate"
icon="red:status-info"
tooltipPosition="below"
></iqser-circle-button>
<iqser-circle-button
(action)="excludedPagesService.toggle()"
*ngIf="excludedPagesService"
[attr.aria-expanded]="excludedPagesService.shown$ | async"
[showDot]="!!file.excludedPages?.length"
[tooltip]="'file-preview.exclude-pages' | translate"
icon="red:exclude-pages"
tooltipPosition="below"
></iqser-circle-button>
<!-- Ready for approval-->
<iqser-circle-button
(action)="setFileUnderApproval($event)"
*ngIf="showUnderApproval"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.under-approval' | translate"
[type]="buttonType"
icon="red:ready-for-approval"
></iqser-circle-button>
<!-- Back to review -->
<iqser-circle-button
(action)="setFileUnderReview($event, true)"
*ngIf="showUnderReview"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.under-review' | translate"
[type]="buttonType"
icon="red:undo"
></iqser-circle-button>
<!-- Approved-->
<iqser-circle-button
(action)="setFileApproved($event)"
*ngIf="showApprove"
[disabled]="!file.canBeApproved"
[tooltipPosition]="tooltipPosition"
[tooltip]="file.canBeApproved ? ('dossier-overview.approve' | translate) : ('dossier-overview.approve-disabled' | translate)"
[type]="buttonType"
icon="red:approved"
></iqser-circle-button>
<!-- Back to approval -->
<iqser-circle-button
(action)="setFileUnderApproval($event)"
*ngIf="showUndoApproval"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.under-approval' | translate"
[type]="buttonType"
icon="red:undo"
></iqser-circle-button>
<iqser-circle-button
(action)="ocrFile($event)"
*ngIf="showOCR"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.ocr-file' | translate"
[type]="buttonType"
icon="iqser:ocr"
></iqser-circle-button>
<!-- reanalyse file preview -->
<iqser-circle-button
(action)="reanalyseFile($event)"
*ngIf="canReanalyse && isFilePreview && analysisForced"
[tooltip]="'file-preview.reanalyse-notification' | translate"
[type]="circleButtonTypes.warn"
icon="iqser:refresh"
tooltipClass="warn small"
tooltipPosition="below"
></iqser-circle-button>
<!-- reanalyse file listing -->
<iqser-circle-button
(action)="reanalyseFile($event)"
*ngIf="canReanalyse && isDossierOverview && analysisForced"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.reanalyse.action' | translate"
[type]="circleButtonTypes.dark"
icon="iqser:refresh"
></iqser-circle-button>
<!-- exclude from redaction -->
<div class="iqser-input-group">
<mat-slide-toggle
(change)="toggleAnalysis()"
(click)="$event.stopPropagation()"
[checked]="!file?.excluded"
[class.mr-24]="isDossierOverviewList"
[disabled]="!canToggleAnalysis"
[matTooltipPosition]="tooltipPosition"
[matTooltip]="toggleTooltip | translate"
color="primary"
></mat-slide-toggle>
</div>
></redaction-expandable-file-actions>
</div>
</ng-template>

View File

@ -3,9 +3,7 @@
.file-actions {
display: flex;
overflow-y: auto;
color: variables.$grey-1;
@include common-mixins.no-scroll-bar;
> *:not(:last-child) {
margin-right: 2px;
@ -27,14 +25,6 @@ iqser-status-bar {
margin-left: 2px;
}
mat-slide-toggle {
height: 34px;
width: 34px;
line-height: 33px;
margin-left: 8px;
margin-right: 5px;
}
.spinning-icon {
margin: 0 12px 0 11px;
}

View File

@ -1,4 +1,16 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Optional, Output } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
HostBinding,
Input,
OnChanges,
OnDestroy,
OnInit,
Optional,
Output,
ViewChild,
} from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { File } from '@red/domain';
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
@ -25,6 +37,8 @@ import { Router } from '@angular/router';
import { ExcludedPagesService } from '../../../screens/file-preview-screen/services/excluded-pages.service';
import { tap } from 'rxjs/operators';
import { DocumentInfoService } from '../../../screens/file-preview-screen/services/document-info.service';
import { Action, ActionTypes } from '@shared/components/expandable-file-actions/types';
import { ExpandableFileActionsComponent } from '@shared/components/expandable-file-actions/expandable-file-actions.component';
@Component({
selector: 'redaction-file-actions',
@ -36,8 +50,9 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
@Input() file: File;
@Input() @Required() file: File;
@Input() @Required() type: 'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow';
@Input() maxWidth: number;
@Output() readonly ocredFile = new EventEmitter<void>();
toggleTooltip?: string;
@ -56,16 +71,20 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
canToggleAnalysis: boolean;
showStatusBar: boolean;
showOpenDocument: boolean;
showReanalyseFilePreview: boolean;
showReanalyseDossierOverview: boolean;
analysisForced: boolean;
isDossierOverview = false;
isDossierOverviewList = false;
isDossierOverviewWorkflow = false;
isFilePreview = false;
tooltipPosition: IqserTooltipPosition;
buttons: Action[];
@ViewChild(ExpandableFileActionsComponent) _expandableActionsComponent: ExpandableFileActionsComponent;
constructor(
@Optional() readonly excludedPagesService: ExcludedPagesService,
@Optional() readonly documentInfoService: DocumentInfoService,
@Optional() private readonly _excludedPagesService: ExcludedPagesService,
@Optional() private readonly _documentInfoService: DocumentInfoService,
private readonly _permissionsService: PermissionsService,
private readonly _dossiersService: DossiersService,
private readonly _dialogService: DossiersDialogService,
@ -82,6 +101,10 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
super();
}
@HostBinding('class.keep-visible') get expanded() {
return this._expandableActionsComponent?.expanded;
}
private get _toggleTooltip(): string {
if (!this.currentUser.isManager) {
return _('file-preview.toggle-analysis.only-managers');
@ -90,6 +113,124 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
return this.file?.excluded ? _('file-preview.toggle-analysis.enable') : _('file-preview.toggle-analysis.disable');
}
private get _buttons(): Action[] {
return [
{
type: ActionTypes.circleBtn,
action: () => this._openDocument(),
tooltip: _('dossier-overview.open-document'),
icon: 'iqser:collapse',
show: this.showOpenDocument,
},
{
type: ActionTypes.circleBtn,
action: $event => this._openDeleteFileDialog($event),
tooltip: _('dossier-overview.delete.action'),
icon: 'iqser:trash',
show: this.showDelete,
},
{
type: ActionTypes.circleBtn,
action: $event => this._assign($event),
tooltip: this.assignTooltip,
icon: 'red:assign',
show: this.showAssign,
},
{
type: ActionTypes.circleBtn,
action: $event => this._assignToMe($event),
tooltip: _('dossier-overview.assign-me'),
icon: 'red:assign-me',
show: this.showAssignToSelf,
},
{
type: ActionTypes.downloadBtn,
show: true,
files: [this.file],
tooltipClass: 'small',
disabled: !this._permissionsService.canDownloadFiles([this.file]),
},
{
type: ActionTypes.circleBtn,
action: () => this._documentInfoService.toggle(),
tooltip: _('file-preview.document-info'),
ariaExpanded: this._documentInfoService?.shown$,
icon: 'red:status-info',
show: !!this._documentInfoService,
},
{
type: ActionTypes.circleBtn,
action: () => this._excludedPagesService.toggle(),
tooltip: _('file-preview.exclude-pages'),
ariaExpanded: this._excludedPagesService?.shown$,
showDot: !!this.file.excludedPages?.length,
icon: 'red:exclude-pages',
show: !!this._excludedPagesService,
},
{
type: ActionTypes.circleBtn,
action: $event => this._setFileUnderApproval($event),
tooltip: _('dossier-overview.under-approval'),
icon: 'red:ready-for-approval',
show: this.showUnderApproval,
},
{
type: ActionTypes.circleBtn,
action: $event => this._setFileUnderReview($event),
tooltip: _('dossier-overview.under-review'),
icon: 'red:undo',
show: this.showUnderReview,
},
{
type: ActionTypes.circleBtn,
action: $event => this.setFileApproved($event),
tooltip: this.file.canBeApproved ? _('dossier-overview.approve') : _('dossier-overview.approve-disabled'),
icon: 'red:approved',
disabled: !this.file.canBeApproved,
show: this.showApprove,
},
{
type: ActionTypes.circleBtn,
action: $event => this._setFileUnderApproval($event),
tooltip: _('dossier-overview.under-approval'),
icon: 'red:undo',
show: this.showUndoApproval,
},
{
type: ActionTypes.circleBtn,
action: $event => this._ocrFile($event),
tooltip: _('dossier-overview.ocr-file'),
icon: 'iqser:ocr',
show: this.showOCR,
},
{
type: ActionTypes.circleBtn,
action: $event => this._reanalyseFile($event),
tooltip: _('file-preview.reanalyse-notification'),
buttonType: CircleButtonTypes.warn,
tooltipClass: 'warn small',
icon: 'iqser:refresh',
show: this.showReanalyseFilePreview,
},
{
type: ActionTypes.circleBtn,
action: $event => this._reanalyseFile($event),
tooltip: _('dossier-overview.reanalyse.action'),
icon: 'iqser:refresh',
show: this.showReanalyseDossierOverview,
},
{
type: ActionTypes.toggle,
action: () => this._toggleAnalysis(),
disabled: !this.canToggleAnalysis,
tooltip: this.toggleTooltip,
class: { 'mr-24': this.isDossierOverviewList },
checked: !this.file.excluded,
show: true,
},
].filter(btn => btn.show);
}
ngOnInit() {
this._dossiersService.getEntityChanged$(this.file.dossierId).pipe(tap(() => this._setup()));
}
@ -98,57 +239,6 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
this._setup();
}
openDocument() {
this._router.navigate([this.file.routerLink]).then();
}
openDeleteFileDialog($event: MouseEvent) {
this._dialogService.openDialog(
'confirm',
$event,
new ConfirmationDialogInput({
title: _('confirmation-dialog.delete-file.title'),
question: _('confirmation-dialog.delete-file.question'),
}),
async () => {
this._loadingService.start();
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();
},
);
}
assign($event: MouseEvent) {
const mode = this.file.isUnderApproval ? 'approver' : 'reviewer';
const files = [this.file];
const withCurrentUserAsDefault = true;
const withUnassignedOption = true;
this._dialogService.openDialog('assignFile', $event, { mode, files, withCurrentUserAsDefault, withUnassignedOption });
}
async assignToMe($event: MouseEvent) {
$event.stopPropagation();
await this._fileAssignService.assignToMe([this.file]);
}
async reanalyseFile($event?: MouseEvent) {
if ($event) {
$event.stopPropagation();
}
await this._reanalysisService.reanalyzeFilesForDossier([this.file.fileId], this.file.dossierId, true).toPromise();
}
async setFileUnderApproval($event: MouseEvent) {
$event.stopPropagation();
await this._fileAssignService.assignApprover($event, this.file, true);
}
async setFileApproved($event: MouseEvent) {
$event.stopPropagation();
if (!this.file.hasUpdates) {
@ -169,7 +259,62 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
);
}
async ocrFile($event: MouseEvent) {
forceReanalysisAction($event: LongPressEvent) {
this.analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled;
}
private _openDocument() {
this._router.navigate([this.file.routerLink]).then();
}
private _openDeleteFileDialog($event: MouseEvent) {
this._dialogService.openDialog(
'confirm',
$event,
new ConfirmationDialogInput({
title: _('confirmation-dialog.delete-file.title'),
question: _('confirmation-dialog.delete-file.question'),
}),
async () => {
this._loadingService.start();
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();
},
);
}
private _assign($event: MouseEvent) {
const mode = this.file.isUnderApproval ? 'approver' : 'reviewer';
const files = [this.file];
const withCurrentUserAsDefault = true;
const withUnassignedOption = true;
this._dialogService.openDialog('assignFile', $event, { mode, files, withCurrentUserAsDefault, withUnassignedOption });
}
private async _assignToMe($event: MouseEvent) {
$event.stopPropagation();
await this._fileAssignService.assignToMe([this.file]);
}
private async _reanalyseFile($event?: MouseEvent) {
if ($event) {
$event.stopPropagation();
}
await this._reanalysisService.reanalyzeFilesForDossier([this.file.fileId], this.file.dossierId, true).toPromise();
}
private async _setFileUnderApproval($event: MouseEvent) {
$event.stopPropagation();
await this._fileAssignService.assignApprover($event, this.file, true);
}
private async _ocrFile($event: MouseEvent) {
$event.stopPropagation();
this._loadingService.start();
await this._reanalysisService.ocrFiles([this.file.fileId], this.file.dossierId).toPromise();
@ -177,20 +322,16 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
this._loadingService.stop();
}
async setFileUnderReview($event: MouseEvent, ignoreDialogChanges = false) {
await this._fileAssignService.assignReviewer($event, this.file, ignoreDialogChanges);
private async _setFileUnderReview($event: MouseEvent) {
await this._fileAssignService.assignReviewer($event, this.file, true);
}
async toggleAnalysis() {
private async _toggleAnalysis() {
this._loadingService.start();
await this._reanalysisService.toggleAnalysis(this.file.dossierId, this.file.fileId, !this.file.excluded).toPromise();
this._loadingService.stop();
}
forceReanalysisAction($event: LongPressEvent) {
this.analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled;
}
private _setup() {
this.isDossierOverviewList = this.type === 'dossier-overview-list';
this.isDossierOverviewWorkflow = this.type === 'dossier-overview-workflow';
@ -221,6 +362,11 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
this.isDossierOverview;
this.showOpenDocument = this.file.canBeOpened && this.isDossierOverviewWorkflow;
this.showReanalyseFilePreview = this.canReanalyse && this.isFilePreview && this.analysisForced;
this.showReanalyseDossierOverview = this.canReanalyse && this.isDossierOverview && this.analysisForced;
this.buttons = this._buttons;
}
private async _setFileApproved() {

View File

@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { File } from '@red/domain';
import { FileDownloadService } from '@upload-download/services/file-download.service';
import { CircleButtonType, CircleButtonTypes, List, Toaster } from '@iqser/common-ui';
import { CircleButtonType, CircleButtonTypes, Toaster } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -15,7 +15,7 @@ export type MenuState = 'OPEN' | 'CLOSED';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileDownloadBtnComponent {
@Input() files: List<File>;
@Input() files: File[];
@Input() tooltipPosition: 'above' | 'below' | 'before' | 'after' = 'above';
@Input() type: CircleButtonType = CircleButtonTypes.default;
@Input() tooltipClass: string;
@ -29,7 +29,7 @@ export class FileDownloadBtnComponent {
) {}
get canDownloadFiles() {
return this.files.length > 0 && this.files.reduce((acc, file) => acc && this._permissionsService.canDownloadFiles(file), true);
return this._permissionsService.canDownloadFiles(this.files);
}
get tooltip() {

View File

@ -0,0 +1,64 @@
<ng-container *ngFor="let btn of displayedButtons">
<iqser-circle-button
(action)="btn.action($event)"
*ngIf="btn.type === 'circleBtn'"
[attr.aria-expanded]="btn.ariaExpanded && btn.ariaExpanded | async"
[disabled]="btn.disabled"
[icon]="btn.icon"
[showDot]="btn.showDot"
[tooltipClass]="btn.tooltipClass"
[tooltipPosition]="tooltipPosition"
[tooltip]="btn.tooltip | translate"
[type]="btn.buttonType || buttonType"
></iqser-circle-button>
<!-- download redacted file-->
<redaction-file-download-btn
*ngIf="btn.type === 'downloadBtn'"
[files]="btn.files"
[tooltipClass]="btn.tooltipClass"
[tooltipPosition]="tooltipPosition"
[type]="buttonType"
></redaction-file-download-btn>
<!-- exclude from redaction -->
<div *ngIf="btn.type === 'toggle'" class="iqser-input-group">
<mat-slide-toggle
(change)="btn.action()"
(click)="$event.stopPropagation()"
[checked]="btn.checked"
[disabled]="btn.disabled"
[matTooltipPosition]="tooltipPosition"
[matTooltip]="btn.tooltip | translate"
[ngClass]="btn.class"
color="primary"
></mat-slide-toggle>
</div>
</ng-container>
<iqser-circle-button
(menuClosed)="expanded = false"
(menuOpened)="expanded = true"
*ngIf="hiddenButtons.length > 0"
[attr.aria-expanded]="expanded"
[icon]="'iqser:plus'"
[matMenuTriggerFor]="hiddenButtonsMenu"
[type]="buttonType"
></iqser-circle-button>
<mat-menu #hiddenButtonsMenu="matMenu" class="padding-bottom-8">
<button (click)="btn.action($event)" *ngFor="let btn of hiddenButtons" [disabled]="btn.disabled" mat-menu-item>
<ng-container *ngIf="btn.type === 'circleBtn'">
<mat-icon [svgIcon]="btn.icon"></mat-icon>
{{ btn.tooltip | translate }}
</ng-container>
<ng-container *ngIf="btn.type === 'downloadBtn'">
<mat-icon svgIcon="iqser:download"></mat-icon>
{{ 'dossier-overview.download-file' | translate }}
</ng-container>
<ng-container *ngIf="btn.type === 'toggle'">
<mat-slide-toggle [checked]="btn.checked" [disabled]="btn.disabled" class="ml-0" color="primary"></mat-slide-toggle>
{{ btn.tooltip | translate }}
</ng-container>
</button>
</mat-menu>

View File

@ -0,0 +1,27 @@
:host {
display: contents;
}
mat-slide-toggle {
height: 34px;
width: 34px;
line-height: 33px;
margin-left: 8px;
margin-right: 5px;
}
.mat-menu-item {
mat-icon {
color: inherit;
width: 14px;
height: 14px;
}
&[disabled] {
color: rgba(var(--iqser-accent-rgb), 0.3);
}
}
.ml-0 {
margin-left: 0;
}

View File

@ -0,0 +1,38 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Action } from './types';
import { CircleButtonType, IqserTooltipPosition } from '@iqser/common-ui';
@Component({
selector: 'redaction-expandable-file-actions',
templateUrl: './expandable-file-actions.component.html',
styleUrls: ['./expandable-file-actions.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExpandableFileActionsComponent implements OnChanges {
@Input() maxWidth: number;
@Input() actions: Action[];
@Input() buttonType: CircleButtonType;
@Input() tooltipPosition: IqserTooltipPosition;
displayedButtons: Action[];
hiddenButtons: Action[];
expanded = false;
ngOnChanges(changes: SimpleChanges) {
if (changes.actions || changes.maxWidth) {
if (this.maxWidth) {
const count = Math.floor(this.maxWidth / 36);
if (count >= this.actions.length) {
this.displayedButtons = [...this.actions];
this.hiddenButtons = [];
} else {
this.displayedButtons = this.actions.slice(0, count - 1);
this.hiddenButtons = this.actions.slice(count - 1);
}
} else {
this.displayedButtons = [...this.actions];
this.hiddenButtons = [];
}
}
}
}

View File

@ -0,0 +1,27 @@
import { Observable } from 'rxjs';
import { CircleButtonType } from '@iqser/common-ui';
import { File } from '@red/domain';
export type ActionType = 'circleBtn' | 'downloadBtn' | 'toggle';
export const ActionTypes = {
circleBtn: 'circleBtn' as ActionType,
downloadBtn: 'downloadBtn' as ActionType,
toggle: 'toggle' as ActionType,
};
export interface Action {
action?: Function;
tooltip?: string;
icon?: string;
show?: boolean;
ariaExpanded?: Observable<boolean>;
showDot?: boolean;
disabled?: boolean;
buttonType?: CircleButtonType;
tooltipClass?: string;
checked?: boolean;
class?: { [key: string]: boolean };
files?: File[];
type: ActionType;
}

View File

@ -25,6 +25,7 @@ import { NamePipe } from './pipes/name.pipe';
import { TypeFilterComponent } from './components/type-filter/type-filter.component';
import { TeamMembersComponent } from './components/team-members/team-members.component';
import { EditorComponent } from './components/editor/editor.component';
import { ExpandableFileActionsComponent } from './components/expandable-file-actions/expandable-file-actions.component';
const buttons = [FileDownloadBtnComponent, UserButtonComponent];
@ -39,6 +40,7 @@ const components = [
AssignUserDropdownComponent,
TypeFilterComponent,
TeamMembersComponent,
ExpandableFileActionsComponent,
...buttons,
];

View File

@ -99,13 +99,12 @@ export class PermissionsService {
return (file.isUnderReview || file.isUnderApproval) && this.isFileAssignee(file);
}
canDownloadFiles(file: File): boolean {
const dossier = this._getDossier(file);
if (!dossier) {
canDownloadFiles(files: File[]): boolean {
if (files.length === 0) {
return false;
}
return file.isApproved && this.isApprover(dossier);
const dossier = this._getDossier(files[0]);
return this.isApprover(dossier) && files.reduce((prev, file) => prev && file.isApproved, true);
}
canDeleteDossier(dossier: Dossier): boolean {

@ -1 +1 @@
Subproject commit 0a861bf60d47504a0c8cba019671da05dcdb3a28
Subproject commit 4a27531b8e52cea707909f5d93f68b303e587945

View File

@ -45,7 +45,6 @@ export class File extends Entity<IFile> implements IFile {
readonly hintsOnly: boolean;
readonly hasNone: boolean;
readonly isNew: boolean;
// readonly isUnassigned: boolean;
readonly isError: boolean;
readonly isProcessing: boolean;
readonly isApproved: boolean;