361 lines
13 KiB
TypeScript
361 lines
13 KiB
TypeScript
import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
|
import {
|
|
FileStatus,
|
|
FileUploadControllerService,
|
|
ReanalysisControllerService,
|
|
StatusControllerService
|
|
} from '@redaction/red-ui-http';
|
|
import { NotificationService, NotificationType } from '../../notification/notification.service';
|
|
import { AppStateService } from '../../state/app-state.service';
|
|
import { FileDropOverlayService } from '../../upload/file-drop/service/file-drop-overlay.service';
|
|
import { FileUploadModel } from '../../upload/model/file-upload.model';
|
|
import { FileUploadService } from '../../upload/file-upload.service';
|
|
import { UploadStatusOverlayService } from '../../upload/upload-status-dialog/service/upload-status-overlay.service';
|
|
import { UserService } from '../../user/user.service';
|
|
import { humanize } from '../../utils/functions';
|
|
import { DialogService } from '../../dialogs/dialog.service';
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
import { FileActionService } from '../file/service/file-action.service';
|
|
import { FilterModel } from '../../common/filter/model/filter.model';
|
|
import * as moment from 'moment';
|
|
import { SortingComponent, SortingOption } from '../../components/sorting/sorting.component';
|
|
import { ProjectDetailsComponent } from './project-details/project-details.component';
|
|
import { FileStatusWrapper } from '../file/model/file-status.wrapper';
|
|
import {
|
|
annotationFilterChecker,
|
|
fileAddedFilterChecker,
|
|
getFilteredEntities,
|
|
keyChecker
|
|
} from '../../common/filter/utils/filter-utils';
|
|
|
|
@Component({
|
|
selector: 'redaction-project-overview-screen',
|
|
templateUrl: './project-overview-screen.component.html',
|
|
styleUrls: ['./project-overview-screen.component.scss']
|
|
})
|
|
export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
|
private _selectedFileIds: string[] = [];
|
|
public bulkSelectActive = false;
|
|
|
|
public statusFilters: FilterModel[];
|
|
public peopleFilters: FilterModel[];
|
|
// public addedDateFilters: FilterModel[];
|
|
public needsWorkFilters: FilterModel[];
|
|
|
|
public displayedFiles: FileStatusWrapper[] = [];
|
|
|
|
@ViewChild('projectDetailsComponent', { static: true })
|
|
private _projectDetailsComponent: ProjectDetailsComponent;
|
|
|
|
public sortingOption: SortingOption = { column: 'added', order: 'desc' };
|
|
|
|
constructor(
|
|
public readonly appStateService: AppStateService,
|
|
public readonly userService: UserService,
|
|
private readonly _activatedRoute: ActivatedRoute,
|
|
private readonly _fileUploadControllerService: FileUploadControllerService,
|
|
private readonly _statusControllerService: StatusControllerService,
|
|
private readonly _notificationService: NotificationService,
|
|
private readonly _dialogService: DialogService,
|
|
private readonly _fileActionService: FileActionService,
|
|
private readonly _fileUploadService: FileUploadService,
|
|
private readonly _uploadStatusOverlayService: UploadStatusOverlayService,
|
|
private readonly _reanalysisControllerService: ReanalysisControllerService,
|
|
private readonly _router: Router,
|
|
private readonly _changeDetectorRef: ChangeDetectorRef,
|
|
private readonly _translateService: TranslateService,
|
|
private readonly _fileDropOverlayService: FileDropOverlayService
|
|
) {
|
|
this._activatedRoute.params.subscribe((params) => {
|
|
this.appStateService.activateProject(params.projectId);
|
|
});
|
|
|
|
this.appStateService.fileStatusChanged.subscribe(() => {
|
|
this._calculateData();
|
|
});
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this._fileDropOverlayService.initFileDropHandling();
|
|
this._calculateData();
|
|
this._displayNewRuleToast();
|
|
}
|
|
|
|
ngOnDestroy(): void {
|
|
this._fileDropOverlayService.cleanupFileDropHandling();
|
|
}
|
|
|
|
public get user() {
|
|
return this.userService.user;
|
|
}
|
|
|
|
public isPending(fileStatusWrapper: FileStatusWrapper) {
|
|
return fileStatusWrapper.status === FileStatus.StatusEnum.UNPROCESSED;
|
|
}
|
|
|
|
public isError(fileStatusWrapper: FileStatusWrapper) {
|
|
return fileStatusWrapper.status === FileStatus.StatusEnum.ERROR;
|
|
}
|
|
|
|
public isProcessing(fileStatusWrapper: FileStatusWrapper) {
|
|
return [FileStatus.StatusEnum.REPROCESS, FileStatus.StatusEnum.PROCESSING].includes(
|
|
fileStatusWrapper.status
|
|
);
|
|
}
|
|
|
|
public toggleBulkSelect() {
|
|
this.bulkSelectActive = !this.bulkSelectActive;
|
|
}
|
|
|
|
private _displayNewRuleToast() {
|
|
// @ts-ignore
|
|
if (
|
|
!this.appStateService.activeProject.files.filter((file) =>
|
|
this.appStateService.fileNotUpToDateWithDictionary(file)
|
|
).length
|
|
) {
|
|
return;
|
|
}
|
|
|
|
this._notificationService.showToastNotification(
|
|
`${this._translateService.instant(
|
|
'project-overview.new-rule.toast.message-project'
|
|
)} <span class="pill">${this._translateService.instant(
|
|
'project-overview.new-rule.label'
|
|
)}</span>`,
|
|
null,
|
|
NotificationType.WARNING,
|
|
{
|
|
disableTimeOut: true,
|
|
positionClass: 'toast-top-left',
|
|
actions: [
|
|
{
|
|
title: this._translateService.instant(
|
|
'project-overview.new-rule.toast.actions.reanalyse-all'
|
|
),
|
|
action: () =>
|
|
this._reanalysisControllerService
|
|
.reanalyzeProject(
|
|
this.appStateService.activeProject.project.projectId
|
|
)
|
|
.toPromise()
|
|
.then(() => this.reloadProjects())
|
|
},
|
|
{
|
|
title: this._translateService.instant(
|
|
'project-overview.new-rule.toast.actions.later'
|
|
)
|
|
}
|
|
]
|
|
}
|
|
);
|
|
}
|
|
|
|
public reloadProjects() {
|
|
this.appStateService.getFiles().then(() => {
|
|
this._calculateData();
|
|
});
|
|
}
|
|
|
|
private _calculateData(): void {
|
|
this._computeAllFilters();
|
|
this._filterFiles();
|
|
this._projectDetailsComponent.calculateChartConfig();
|
|
this._changeDetectorRef.detectChanges();
|
|
}
|
|
|
|
public toggleFileSelected($event: MouseEvent, file: FileStatusWrapper) {
|
|
$event.stopPropagation();
|
|
const idx = this._selectedFileIds.indexOf(file.fileId);
|
|
if (idx === -1) {
|
|
this._selectedFileIds.push(file.fileId);
|
|
} else {
|
|
this._selectedFileIds.splice(idx, 1);
|
|
}
|
|
}
|
|
|
|
public toggleSelectAll() {
|
|
if (this.areAllFilesSelected()) {
|
|
this._selectedFileIds = [];
|
|
} else {
|
|
this._selectedFileIds = this.appStateService.activeProject.files.map(
|
|
(file) => file.fileId
|
|
);
|
|
}
|
|
}
|
|
|
|
public areAllFilesSelected() {
|
|
return (
|
|
this.appStateService.activeProject.files.length !== 0 &&
|
|
this._selectedFileIds.length === this.appStateService.activeProject.files.length
|
|
);
|
|
}
|
|
|
|
public isFileSelected(file: FileStatusWrapper) {
|
|
return this._selectedFileIds.indexOf(file.fileId) !== -1;
|
|
}
|
|
|
|
public openDeleteFileDialog($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
|
|
this._dialogService.openDeleteFileDialog(
|
|
$event,
|
|
fileStatusWrapper.projectId,
|
|
fileStatusWrapper.fileId,
|
|
() => {
|
|
this._calculateData();
|
|
}
|
|
);
|
|
}
|
|
|
|
downloadFileRedactionReport($event: MouseEvent, file: FileStatusWrapper) {
|
|
$event.stopPropagation();
|
|
this.appStateService.downloadFileRedactionReport(file);
|
|
}
|
|
|
|
public assignReviewer($event: MouseEvent, file: FileStatusWrapper) {
|
|
$event.stopPropagation();
|
|
this._fileActionService.assignProjectReviewer(file, () => this._calculateData());
|
|
}
|
|
|
|
public reanalyseFile($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
|
|
$event.stopPropagation();
|
|
this._reanalysisControllerService
|
|
.reanalyzeFile(
|
|
this.appStateService.activeProject.project.projectId,
|
|
fileStatusWrapper.fileId
|
|
)
|
|
.subscribe(() => {
|
|
this.reloadProjects();
|
|
});
|
|
}
|
|
|
|
public fileId(index, item) {
|
|
return item.fileId;
|
|
}
|
|
|
|
public uploadFiles(files: FileList | File[]) {
|
|
const uploadFiles: FileUploadModel[] = [];
|
|
for (let i = 0; i < files.length; i++) {
|
|
const file = files[i];
|
|
uploadFiles.push({
|
|
file: file,
|
|
progress: 0,
|
|
completed: false,
|
|
error: null
|
|
});
|
|
}
|
|
|
|
this._fileUploadService.uploadFiles(uploadFiles);
|
|
this._uploadStatusOverlayService.openStatusOverlay();
|
|
}
|
|
|
|
public canOpenFile(fileStatusWrapper: FileStatusWrapper): boolean {
|
|
return (
|
|
!this.isError(fileStatusWrapper) &&
|
|
!this.isProcessing(fileStatusWrapper) &&
|
|
this.appStateService.isReviewerOrOwner(fileStatusWrapper)
|
|
);
|
|
}
|
|
|
|
public sortingOptionChanged(option: SortingOption) {
|
|
this.sortingOption = option;
|
|
}
|
|
|
|
private _computeAllFilters() {
|
|
const allDistinctFileStatusWrapper = new Set<string>();
|
|
const allDistinctPeople = new Set<string>();
|
|
const allDistinctAddedDates = new Set<string>();
|
|
const allDistinctNeedsWork = new Set<string>();
|
|
|
|
// All people
|
|
this.appStateService.activeProject.project.memberIds.forEach((memberId) =>
|
|
allDistinctPeople.add(memberId)
|
|
);
|
|
|
|
// File statuses
|
|
this.appStateService.activeProject.files.forEach((file) =>
|
|
allDistinctFileStatusWrapper.add(file.status)
|
|
);
|
|
|
|
// Added dates
|
|
this.appStateService.activeProject.files.forEach((file) =>
|
|
allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY'))
|
|
);
|
|
|
|
// Needs work
|
|
this.appStateService.activeProject.files.forEach((file) => {
|
|
if (file.hasHints) allDistinctNeedsWork.add('hints');
|
|
if (file.hasRedactions) allDistinctNeedsWork.add('redactions');
|
|
if (file.hasRequests) allDistinctNeedsWork.add('requests');
|
|
});
|
|
|
|
this.statusFilters = [];
|
|
allDistinctFileStatusWrapper.forEach((status) => {
|
|
this.statusFilters.push({
|
|
key: status,
|
|
label: humanize(status)
|
|
});
|
|
});
|
|
|
|
this.peopleFilters = [];
|
|
allDistinctPeople.forEach((userId) => {
|
|
this.peopleFilters.push({
|
|
key: userId,
|
|
label: this.userService.getNameForId(userId)
|
|
});
|
|
});
|
|
|
|
this.needsWorkFilters = [];
|
|
allDistinctNeedsWork.forEach((type) => {
|
|
this.needsWorkFilters.push({
|
|
key: type,
|
|
label: this._translateService.instant('filter.' + type)
|
|
});
|
|
});
|
|
}
|
|
|
|
filtersChanged() {
|
|
this._filterFiles();
|
|
}
|
|
|
|
private _filterFiles() {
|
|
const filters = [
|
|
{ values: this.statusFilters, checker: keyChecker('status') },
|
|
{ values: this.peopleFilters, checker: keyChecker('currentReviewer') },
|
|
{ values: this.needsWorkFilters, checker: annotationFilterChecker, matchAll: true }
|
|
];
|
|
this.displayedFiles = getFilteredEntities(
|
|
this.appStateService.activeProject.files,
|
|
filters
|
|
);
|
|
this._changeDetectorRef.detectChanges();
|
|
}
|
|
|
|
requestApprovalOrApproveFile($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
|
|
$event.stopPropagation();
|
|
this._fileActionService.requestApprovalOrApproveFile(fileStatusWrapper).subscribe(() => {
|
|
this.reloadProjects();
|
|
});
|
|
}
|
|
|
|
undoApproveOrUnderApproval($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
|
|
$event.stopPropagation();
|
|
this._fileActionService.undoApproveOrUnderApproval(fileStatusWrapper).subscribe(() => {
|
|
this.reloadProjects();
|
|
});
|
|
}
|
|
|
|
isApprovedOrUnderApproval(fileStatusWrapper: FileStatusWrapper) {
|
|
return (
|
|
fileStatusWrapper.status === 'APPROVED' || fileStatusWrapper.status === 'UNDER_APPROVAL'
|
|
);
|
|
}
|
|
|
|
canApprove(fileStatusWrapper: FileStatusWrapper) {
|
|
return (
|
|
fileStatusWrapper.status === 'UNDER_REVIEW' ||
|
|
fileStatusWrapper.status === 'UNDER_APPROVAL'
|
|
);
|
|
}
|
|
}
|