Project overview file status

This commit is contained in:
Adina Țeudan 2020-10-28 01:46:56 +02:00
parent 1e510f24c1
commit dfe82287d9
9 changed files with 302 additions and 236 deletions

View File

@ -25,7 +25,7 @@
[style]="'height: ' + size + 'px; width: ' + size + 'px; padding: ' + strokeWidth + 'px;'"
>
<div class="heading-xl">{{ dataTotal }}</div>
<div class="projects-text mt-5">{{ subtitle | translate }}</div>
<div class="mt-5">{{ subtitle | translate }}</div>
</div>
<div class="breakdown-container">

View File

@ -19,6 +19,10 @@
justify-content: center;
align-items: center;
box-sizing: border-box;
> div {
text-align: center;
}
}
.breakdown-container {

View File

@ -89,7 +89,7 @@
[class.pointer]="canOpenProject(pw)"
>
<div>
<div class="table-item-title table-item-title--large">
<div class="table-item-title">
{{ pw.project.projectName }}
</div>
<div class="small-label stats-subtitle">

View File

@ -123,14 +123,14 @@
<div
class="table-item"
[class.pointer]="canOpenFile(fileStatus.status)"
[class.pointer]="canOpenFile(fileStatus)"
*ngFor="
let fileStatus of appStateService.activeProject.files
| sortBy: sortingOption.order:sortingOption.column;
trackBy: fileId
"
[routerLink]="
canOpenFile(fileStatus.status)
canOpenFile(fileStatus)
? ['/ui/projects/' + activeProject.projectId + '/file/' + fileStatus.fileId]
: []
"
@ -144,7 +144,11 @@
</div>
<div [matTooltip]="'[' + fileStatus.status + '] ' + fileStatus.filename">
<div class="table-item-title table-item-title--large">
<div
class="table-item-title"
[class.disabled]="isPending(fileStatus) || isProcessing(fileStatus)"
[class.error]="isError(fileStatus)"
>
{{ fileStatus.filename }}
</div>
<div class="small-label stats-subtitle">
@ -155,11 +159,13 @@
</div>
</div>
<div class="small-label">
{{ fileStatus.added | date: 'd MMM. yyyy, hh:mm a' }}
<div>
<div class="small-label" [class.error]="isError(fileStatus)">
{{ fileStatus.added | date: 'd MMM. yyyy, hh:mm a' }}
</div>
</div>
<div class="needs-work">
<div class="needs-work" *ngIf="!isError(fileStatus)">
<redaction-annotation-icon
[typeValue]="appStateService.getDictionaryTypeValue('redaction')"
></redaction-annotation-icon>
@ -168,23 +174,38 @@
></redaction-annotation-icon>
</div>
<div class="assigned-to">
<div class="assigned-to" *ngIf="!isError(fileStatus)">
<redaction-initials-avatar
withName="true"
[userId]="fileStatus.currentReviewer"
></redaction-initials-avatar>
</div>
<div class="status-container">
<div class="status-container" [class.extend-cols]="isError(fileStatus)">
<div
*ngIf="isError(fileStatus)"
class="small-label error"
translate="project-overview.file-listing.file-entry.file-error.label"
></div>
<div
class="small-label"
*ngIf="isPending(fileStatus)"
translate="project-overview.file-listing.file-entry.file-pending.label"
></div>
<div
class="small-label"
*ngIf="isProcessing(fileStatus)"
translate="processing"
></div>
<redaction-status-bar
*ngIf="
!isPending(fileStatus) &&
!isProcessing(fileStatus) &&
!isError(fileStatus)
"
[config]="[
{
color:
fileStatus.status === 'PROCESSED'
? 'finished'
: fileStatus.status === 'ERROR'
? 'under-approval'
: 'under-review',
color: fileStatus.status,
length: 1
}
]"

View File

@ -39,8 +39,21 @@
grid-template-columns: auto 3fr 2fr 1fr 2fr auto;
.table-item {
.disabled {
color: $grey-7;
}
.error {
color: $red-1;
}
.extend-cols {
grid-column-end: span 3;
align-items: flex-end;
}
.table-item-title {
//line-height: 80px;
max-width: 25vw;
}
.needs-work {
@ -50,6 +63,10 @@
justify-content: flex-start;
gap: 4px;
}
.status-container {
align-items: flex-end;
}
}
}

View File

@ -1,234 +1,247 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {
FileStatus,
FileUploadControllerService,
ReanalysisControllerService,
StatusControllerService
FileStatus,
FileUploadControllerService,
ReanalysisControllerService,
StatusControllerService
} from '@redaction/red-ui-http';
import {NotificationService} 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 {SortingOption} from '../../utils/types';
import {DoughnutChartConfig} from '../../components/simple-doughnut-chart/simple-doughnut-chart.component';
import {groupBy} from '../../utils/functions';
import {DialogService} from '../../dialogs/dialog.service';
import { NotificationService } 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 { SortingOption } from '../../utils/types';
import { DoughnutChartConfig } from '../../components/simple-doughnut-chart/simple-doughnut-chart.component';
import { groupBy } from '../../utils/functions';
import { DialogService } from '../../dialogs/dialog.service';
@Component({
selector: 'redaction-project-overview-screen',
templateUrl: './project-overview-screen.component.html',
styleUrls: ['./project-overview-screen.component.scss']
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[] = [];
private _selectedFileIds: string[] = [];
public sortingOptions: SortingOption[] = [
{label: 'project-overview.sorting.recent.label', order: 'desc', column: 'lastUpdated'},
{label: 'project-overview.sorting.oldest.label', order: 'asc', column: 'lastUpdated'},
{
label: 'project-overview.sorting.alphabetically.label',
order: 'asc',
column: 'filename'
},
{
label: 'project-overview.sorting.number-of-pages.label',
order: 'asc',
column: 'numberOfPages'
},
{
label: 'project-overview.sorting.number-of-analyses.label',
order: 'desc',
column: 'numberOfAnalyses'
public sortingOptions: SortingOption[] = [
{ label: 'project-overview.sorting.recent.label', order: 'desc', column: 'lastUpdated' },
{ label: 'project-overview.sorting.oldest.label', order: 'asc', column: 'lastUpdated' },
{
label: 'project-overview.sorting.alphabetically.label',
order: 'asc',
column: 'filename'
},
{
label: 'project-overview.sorting.number-of-pages.label',
order: 'asc',
column: 'numberOfPages'
},
{
label: 'project-overview.sorting.number-of-analyses.label',
order: 'desc',
column: 'numberOfAnalyses'
}
];
public sortingOption: SortingOption = this.sortingOptions[0];
public documentsChartData: DoughnutChartConfig[] = [];
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 _fileUploadService: FileUploadService,
private readonly _uploadStatusOverlayService: UploadStatusOverlayService,
private readonly _reanalysisControllerService: ReanalysisControllerService,
private readonly _router: Router,
private readonly _fileDropOverlayService: FileDropOverlayService
) {
this._activatedRoute.params.subscribe((params) => {
this.appStateService.activateProject(params.projectId);
});
this.appStateService.fileStatusChanged.subscribe(() => {
this._calculateChartConfig();
});
}
];
public sortingOption: SortingOption = this.sortingOptions[0];
public documentsChartData: DoughnutChartConfig[] = [];
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 _fileUploadService: FileUploadService,
private readonly _uploadStatusOverlayService: UploadStatusOverlayService,
private readonly _reanalysisControllerService: ReanalysisControllerService,
private readonly _router: Router,
private readonly _fileDropOverlayService: FileDropOverlayService
) {
this._activatedRoute.params.subscribe((params) => {
this.appStateService.activateProject(params.projectId);
});
this.appStateService.fileStatusChanged.subscribe(() => {
this._calculateChartConfig();
});
}
ngOnInit(): void {
this._fileDropOverlayService.initFileDropHandling();
this._calculateChartConfig();
}
ngOnDestroy(): void {
this._fileDropOverlayService.cleanupFileDropHandling();
}
public get activeProject() {
return this.appStateService.activeProject.project;
}
public get user() {
return this.userService.user;
}
public get displayMembers() {
return this.activeProject.memberIds.slice(0, 6);
}
public get overflowCount() {
return this.activeProject.memberIds.length > 6
? this.activeProject.memberIds.length - 6
: 0;
}
private _reloadProjects() {
this.appStateService.loadAllProjects().then(() => {
this._calculateChartConfig();
});
}
private _calculateChartConfig() {
const groups = groupBy(this.appStateService.activeProject.files, 'status');
this.documentsChartData = [];
for (const key of Object.keys(groups)) {
this.documentsChartData.push({value: groups[key].length, color: key, label: key});
}
}
public toggleFileSelected($event: MouseEvent, file: FileStatus) {
$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: FileStatus) {
return this._selectedFileIds.indexOf(file.fileId) !== -1;
}
public openDeleteFileDialog($event: MouseEvent, fileStatus: FileStatus) {
this._dialogService.openDeleteFileDialog(
$event,
fileStatus.projectId,
fileStatus.fileId,
() => {
ngOnInit(): void {
this._fileDropOverlayService.initFileDropHandling();
this._calculateChartConfig();
}
);
}
downloadFileRedactionReport($event: MouseEvent, file: FileStatus) {
$event.stopPropagation();
this.appStateService.downloadFileRedactionReport(file);
}
downloadRedactionReport($event: MouseEvent) {
$event.stopPropagation();
this.appStateService.downloadRedactionReport();
}
public openEditProjectDialog($event: MouseEvent) {
this._dialogService.openEditProjectDialog(
$event,
this.appStateService.activeProject.project
);
}
public openDeleteProjectDialog($event: MouseEvent) {
this._dialogService.openDeleteProjectDialog(
$event,
this.appStateService.activeProject.project,
() => {
this._router.navigate(['/ui/projects']);
}
);
}
public openAssignProjectMembersDialog() {
this._dialogService.openAssignProjectMembersAndOwnerDialog(null, this.activeProject, () => {
this._reloadProjects();
});
}
public openAssignFileReviewerDialog($event: MouseEvent, file: FileStatus) {
this._dialogService.openAssignFileReviewerDialog($event, file, () => {
this._reloadProjects();
});
}
public reanalyseFile($event: MouseEvent, fileStatus: FileStatus) {
$event.stopPropagation();
this._reanalysisControllerService
.reanalyzeFile(this.appStateService.activeProject.project.projectId, fileStatus.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();
}
ngOnDestroy(): void {
this._fileDropOverlayService.cleanupFileDropHandling();
}
public canOpenFile(fileStatus: FileStatus): boolean {
// TODO check correct condition for this
return fileStatus === 'PROCESSING' || fileStatus === 'REVIEWED' || true;
}
public get activeProject() {
return this.appStateService.activeProject.project;
}
public toggleSortByAddedOn() {
const sortedByRecent: boolean = this.sortingOption === this.sortingOptions[0];
this.sortingOption = sortedByRecent ? this.sortingOptions[1] : this.sortingOptions[0];
}
public get user() {
return this.userService.user;
}
public get displayMembers() {
return this.activeProject.memberIds.slice(0, 6);
}
public get overflowCount() {
return this.activeProject.memberIds.length > 6
? this.activeProject.memberIds.length - 6
: 0;
}
public isPending(fileStatus: FileStatus) {
return fileStatus.status === FileStatus.StatusEnum.UNPROCESSED;
}
public isError(fileStatus: FileStatus) {
return fileStatus.status === FileStatus.StatusEnum.ERROR;
}
public isProcessing(fileStatus: FileStatus) {
return [FileStatus.StatusEnum.REPROCESS, FileStatus.StatusEnum.PROCESSING].includes(
fileStatus.status
);
}
private _reloadProjects() {
this.appStateService.loadAllProjects().then(() => {
this._calculateChartConfig();
});
}
private _calculateChartConfig() {
const groups = groupBy(this.appStateService.activeProject.files, 'status');
this.documentsChartData = [];
for (const key of Object.keys(groups)) {
this.documentsChartData.push({ value: groups[key].length, color: key, label: key });
}
}
public toggleFileSelected($event: MouseEvent, file: FileStatus) {
$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: FileStatus) {
return this._selectedFileIds.indexOf(file.fileId) !== -1;
}
public openDeleteFileDialog($event: MouseEvent, fileStatus: FileStatus) {
this._dialogService.openDeleteFileDialog(
$event,
fileStatus.projectId,
fileStatus.fileId,
() => {
this._calculateChartConfig();
}
);
}
downloadFileRedactionReport($event: MouseEvent, file: FileStatus) {
$event.stopPropagation();
this.appStateService.downloadFileRedactionReport(file);
}
downloadRedactionReport($event: MouseEvent) {
$event.stopPropagation();
this.appStateService.downloadRedactionReport();
}
public openEditProjectDialog($event: MouseEvent) {
this._dialogService.openEditProjectDialog(
$event,
this.appStateService.activeProject.project
);
}
public openDeleteProjectDialog($event: MouseEvent) {
this._dialogService.openDeleteProjectDialog(
$event,
this.appStateService.activeProject.project,
() => {
this._router.navigate(['/ui/projects']);
}
);
}
public openAssignProjectMembersDialog() {
this._dialogService.openAssignProjectMembersAndOwnerDialog(null, this.activeProject, () => {
this._reloadProjects();
});
}
public openAssignFileReviewerDialog($event: MouseEvent, file: FileStatus) {
this._dialogService.openAssignFileReviewerDialog($event, file, () => {
this._reloadProjects();
});
}
public reanalyseFile($event: MouseEvent, fileStatus: FileStatus) {
$event.stopPropagation();
this._reanalysisControllerService
.reanalyzeFile(this.appStateService.activeProject.project.projectId, fileStatus.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(fileStatus: FileStatus): boolean {
// TODO check correct condition for this
return !this.isError(fileStatus) && (fileStatus.status === 'PROCESSING' || true);
}
public toggleSortByAddedOn() {
const sortedByRecent: boolean = this.sortingOption === this.sortingOptions[0];
this.sortingOption = sortedByRecent ? this.sortingOptions[1] : this.sortingOptions[0];
}
}

View File

@ -393,6 +393,15 @@
},
"last-updated": {
"label": "Last updated: {{lastUpdated}}"
},
"file-pending": {
"label": "Pending..."
},
"file-processing": {
"label": "Processing..."
},
"file-error": {
"label": "The file has encountered an error and cannot be opened."
}
}
},

View File

@ -32,6 +32,7 @@ body {
.right-fixed-container {
border-left: 1px solid $grey-4;
background: $white;
height: 100%;
width: $right-container-inside-width;
padding: $right-container-padding;

View File

@ -7,6 +7,7 @@ $grey-3: #aaacb3;
$grey-4: #e2e4e9;
$grey-5: #d3d5da;
$grey-6: #f0f1f4;
$grey-7: #9398a0;
$blue-1: #4875f7;
$blue-2: #48c9f7;