filestatus rework to facilitate smart sorting and filtering other minor issues fixed

This commit is contained in:
Timo Bejan 2020-11-04 23:36:07 +02:00
parent e86c36f3b6
commit d88ba3e9e3
23 changed files with 545 additions and 157 deletions

View File

@ -1,20 +1,16 @@
import { Component, Inject } from '@angular/core';
import {
FileStatus,
Project,
ProjectControllerService,
StatusControllerService
} from '@redaction/red-ui-http';
import { Project, ProjectControllerService, StatusControllerService } from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AppStateService } from '../../state/app-state.service';
import { UserService } from '../../user/user.service';
import { NotificationService, NotificationType } from '../../notification/notification.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper';
class DialogData {
type: 'file' | 'project';
project?: Project;
file?: FileStatus;
file?: FileStatusWrapper;
}
@Component({
@ -76,13 +72,14 @@ export class AssignOwnerDialogComponent {
const reviewerId = this.usersForm.get('singleUser').value;
await this._statusControllerService
.assignProjectOwner1(
.assignProjectOwner(
this._appStateService.activeProjectId,
this.data.file.fileId,
reviewerId
)
.toPromise();
this.data.file.currentReviewer = reviewerId;
this.data.file.reviewerName = this.userService.getNameForId(reviewerId);
this._notificationService.showToastNotification(
'Successfully assigned ' +
this.userService.getNameForId(reviewerId) +

View File

@ -1,7 +1,7 @@
import { Component, Inject, OnInit } from '@angular/core';
import { FileStatus } from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AppStateService } from '../../state/app-state.service';
import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper';
@Component({
selector: 'redaction-file-details-dialog',
@ -12,7 +12,7 @@ export class FileDetailsDialogComponent implements OnInit {
constructor(
private readonly _appStateService: AppStateService,
public dialogRef: MatDialogRef<FileDetailsDialogComponent>,
@Inject(MAT_DIALOG_DATA) public fileStatus: FileStatus
@Inject(MAT_DIALOG_DATA) public fileStatus: FileStatusWrapper
) {}
ngOnInit(): void {}

View File

@ -43,7 +43,9 @@ export class IconsModule {
'trash',
'user',
'check-alt',
'page'
'page',
'upload',
'undo'
];
for (const icon of icons) {

View File

@ -56,6 +56,24 @@
>
<mat-icon svgIcon="red:check-alt"></mat-icon>
</button>
<button
(click)="undoApproveOrUnderApproval($event)"
*ngIf="
isApprovedOrUnderApproval() &&
appStateService.isActiveProjectOwnerAndManager
"
[matTooltip]="
(appStateService.activeFile.status === 'APPROVED'
? 'project-overview.under-approval'
: 'project-overview.under-review'
) | translate
"
[matTooltipPosition]="'above'"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:undo"></mat-icon>
</button>
<button (click)="openFileDetailsDialog($event)" mat-icon-button>
<mat-icon svgIcon="red:info"></mat-icon>
</button>
@ -64,7 +82,7 @@
<button
(click)="downloadFile('REDACTED')"
color="primary"
class="download-btn"
class="custom-mini-fab"
mat-mini-fab
>
<mat-icon svgIcon="red:download"></mat-icon>

View File

@ -140,18 +140,3 @@ redaction-pdf-viewer {
}
}
}
::ng-deep .download-btn {
line-height: 16px !important;
width: 34px;
height: 34px;
.mat-button-wrapper {
line-height: 16px !important;
}
.mat-icon {
height: 16px !important;
width: 16px !important;
}
}

View File

@ -9,7 +9,6 @@ import {
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {
FileStatus,
ReanalysisControllerService,
ViewedPages,
ViewedPagesControllerService
@ -37,6 +36,7 @@ import { FilterModel } from '../../../common/filter/model/filter.model';
import { tap } from 'rxjs/operators';
import { NotificationService, NotificationType } from '../../../notification/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { FileStatusWrapper } from '../model/file-status.wrapper';
const KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
@ -124,7 +124,7 @@ export class FilePreviewScreenComponent implements OnInit {
this.viewedPages = viewedPages;
});
this._loadFileData().subscribe(() => {});
this.appStateService.fileStatusChanged.subscribe((fileStatus: FileStatus) => {
this.appStateService.fileStatusChanged.subscribe((fileStatus: FileStatusWrapper) => {
if (fileStatus.fileId === this.fileId) {
this._loadFileData().subscribe(() => {
this.viewReady = true;
@ -556,12 +556,12 @@ export class FilePreviewScreenComponent implements OnInit {
requestApprovalOrApproveFile($event: MouseEvent) {
$event.stopPropagation();
this._fileActionService.requestApprovalOrApproveFile().subscribe(() => {});
}
if (this.appStateService.activeFile.status === 'UNDER_REVIEW') {
this._fileActionService.setUnderApproval(this.appStateService.activeFile);
} else {
this._fileActionService.setApproved(this.appStateService.activeFile);
}
undoApproveOrUnderApproval($event: MouseEvent) {
$event.stopPropagation();
this._fileActionService.undoApproveOrUnderApproval().subscribe(() => {});
}
private _displayNewRuleToast() {

View File

@ -1,13 +1,9 @@
import {
FileStatus,
ManualRedactionEntry,
ManualRedactions,
RedactionLog
} from '@redaction/red-ui-http';
import { ManualRedactionEntry, ManualRedactions, RedactionLog } from '@redaction/red-ui-http';
import { FileStatusWrapper } from './file-status.wrapper';
export class FileDataModel {
constructor(
public fileStatus: FileStatus,
public fileStatus: FileStatusWrapper,
public annotatedFileData: Blob,
public redactedFileData: Blob,
public redactionLog: RedactionLog,
@ -18,10 +14,9 @@ export class FileDataModel {
return this.manualRedactions.entriesToAdd.filter(
(e) =>
e.status !== 'DECLINED' &&
!this.redactionLog.redactionLogEntry.find((r) => r.id === e.id)
// &&
// new Date(e.processedDate).getTime() >
// new Date(this.fileStatus.lastUpdated).getTime()
!this.redactionLog.redactionLogEntry.find((r) => r.id === e.id) &&
new Date(e.processedDate).getTime() >
new Date(this.fileStatus.lastProcessed).getTime()
);
}
}

View File

@ -0,0 +1,84 @@
import { FileStatus } from '@redaction/red-ui-http';
export class FileStatusWrapper {
constructor(public fileStatus: FileStatus, public reviewerName: string) {}
get lastProcessed() {
return this.fileStatus.lastProcessed;
}
get added() {
return this.fileStatus.added;
}
get allManualRedactionsApplied() {
return this.fileStatus.allManualRedactionsApplied;
}
get currentReviewer() {
return this.fileStatus.currentReviewer;
}
set currentReviewer(value: string) {
this.fileStatus.currentReviewer = value;
}
get dictionaryVersion() {
return this.fileStatus.dictionaryVersion;
}
get fileId() {
return this.fileStatus.fileId;
}
get filename() {
return this.fileStatus.filename;
}
get hasHints() {
return this.fileStatus.hasHints;
}
get hasRedactions() {
return this.fileStatus.hasRedactions;
}
get hasRequests() {
return this.fileStatus.hasRequests;
}
get lastUpdated() {
return this.fileStatus.lastUpdated;
}
get numberOfAnalyses() {
return this.fileStatus.numberOfAnalyses;
}
get projectId() {
return this.fileStatus.projectId;
}
get rulesVersion() {
return this.fileStatus.rulesVersion;
}
get status() {
return this.fileStatus.status;
}
get numberOfPages() {
return this.fileStatus.numberOfPages;
}
get uploader() {
return this.fileStatus.uploader;
}
get pages() {
if (this.fileStatus.status === 'ERROR') {
return -1;
}
return this.fileStatus.numberOfPages ? this.fileStatus.numberOfPages : 0;
}
}

View File

@ -12,12 +12,7 @@ import {
ViewChild
} from '@angular/core';
import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service';
import {
FileStatus,
ManualRedactionEntry,
ManualRedactions,
Rectangle
} from '@redaction/red-ui-http';
import { ManualRedactionEntry, Rectangle } from '@redaction/red-ui-http';
import WebViewer, { WebViewerInstance } from '@pdftron/webviewer';
import { TranslateService } from '@ngx-translate/core';
import { FileDownloadService } from '../service/file-download.service';
@ -25,6 +20,7 @@ import { ManualRedactionEntryWrapper } from '../model/manual-redaction-entry.wra
import { AppStateService } from '../../../state/app-state.service';
import { AnnotationWrapper } from '../model/annotation.wrapper';
import { ManualAnnotationService } from '../service/manual-annotation.service';
import { FileStatusWrapper } from '../model/file-status.wrapper';
export interface ViewerState {
displayMode?: any;
@ -45,7 +41,7 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
private _viewerState: ViewerState = null; // no initial state
@Input() fileData: Blob;
@Input() fileStatus: FileStatus;
@Input() fileStatus: FileStatusWrapper;
@Input() isReviewer = false;
@Output() fileReady = new EventEmitter();

View File

@ -4,6 +4,7 @@ import { AppStateService } from '../../../state/app-state.service';
import { UserService } from '../../../user/user.service';
import { StatusControllerService } from '@redaction/red-ui-http';
import { FileStatus } from '@redaction/red-ui-http';
import { FileStatusWrapper } from '../model/file-status.wrapper';
@Injectable({
providedIn: 'root'
@ -29,7 +30,7 @@ export class FileActionService {
);
} else {
this._statusControllerService
.assignProjectOwner1(
.assignProjectOwner(
this._appStateService.activeProjectId,
file ? file.fileId : this._appStateService.activeFileId,
this._userService.userId
@ -42,15 +43,46 @@ export class FileActionService {
}
}
setUnderApproval(fileStatus: FileStatus) {
this._statusControllerService
.setStatusUnderApproval(this._appStateService.activeProjectId, fileStatus.fileId)
.subscribe(() => {});
setUnderApproval(fileStatus: FileStatusWrapper) {
return this._statusControllerService.setStatusUnderApproval(
this._appStateService.activeProjectId,
fileStatus.fileId
);
}
setApproved(fileStatus: FileStatus) {
this._statusControllerService
.setStatusApproved(this._appStateService.activeProjectId, fileStatus.fileId)
.subscribe(() => {});
setApproved(fileStatus: FileStatusWrapper) {
return this._statusControllerService.setStatusApproved(
this._appStateService.activeProjectId,
fileStatus.fileId
);
}
setReview(fileStatus: FileStatusWrapper) {
return this._statusControllerService.setStatusUnderReview(
this._appStateService.activeProjectId,
fileStatus.fileId
);
}
requestApprovalOrApproveFile(fileStatusWrapper?: FileStatusWrapper) {
if (!fileStatusWrapper) {
fileStatusWrapper = this._appStateService.activeFile;
}
if (fileStatusWrapper.status === 'UNDER_REVIEW') {
return this.setUnderApproval(fileStatusWrapper);
} else {
return this.setApproved(fileStatusWrapper);
}
}
undoApproveOrUnderApproval(fileStatusWrapper?: FileStatusWrapper) {
if (!fileStatusWrapper) {
fileStatusWrapper = this._appStateService.activeFile;
}
if (fileStatusWrapper.status === 'APPROVED') {
return this.setUnderApproval(fileStatusWrapper);
} else {
return this.setReview(fileStatusWrapper);
}
}
}

View File

@ -223,7 +223,7 @@
<mat-icon svgIcon="red:document"></mat-icon>
<div>
<div class="heading">{{ totalPages }}</div>
<div translate="project-listing.stats.analyzed-pages.label"></div>
<div translate="project-listing.stats.analyzed-pages"></div>
</div>
</div>
@ -231,7 +231,7 @@
<mat-icon svgIcon="red:user"></mat-icon>
<div>
<div class="heading">{{ totalPeople }}</div>
<div translate="project-listing.stats.total-people.label"></div>
<div translate="project-listing.stats.total-people"></div>
</div>
</div>
</div>

View File

@ -1,9 +1,8 @@
@import '../../../assets/styles/red-mixins';
.add-project-btn {
mat-icon {
.mat-icon {
width: 14px;
margin-right: 10px;
color: $white;
}
}

View File

@ -41,19 +41,23 @@
<!-- <mat-icon svgIcon="red:document"></mat-icon>-->
<!-- </button>-->
</div>
<button
(click)="fileInput.click()"
color="primary"
mat-flat-button
translate="project-overview.upload-document"
></button>
<input
#fileInput
(change)="uploadFiles($event.target.files)"
class="file-upload-input"
multiple="true"
type="file"
/>
<div>
<button (click)="fileInput.click()" color="primary" class="custom-mini-fab" mat-mini-fab>
<mat-icon svgIcon="red:upload"></mat-icon>
</button>
<button [routerLink]="['/ui/projects/']" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>
</button>
<input
#fileInput
(change)="uploadFiles($event.target.files)"
class="file-upload-input"
multiple="true"
type="file"
/>
</div>
</div>
<div class="flex red-content-inner">
@ -114,9 +118,21 @@
></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="sortingComponent.toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="reviewerName"
label="project-overview.table-col-names.assigned-to"
></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="sortingComponent.toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="pages"
label="project-overview.table-col-names.pages"
></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="sortingComponent.toggleSort($event)"
[activeSortingOption]="sortingOption"
@ -176,12 +192,6 @@
translate="project-overview.new-rule.label"
></span>
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
{{ fileStatus.numberOfPages }}
</div>
</div>
</div>
<div>
@ -212,6 +222,11 @@
></redaction-initials-avatar>
</div>
<div *ngIf="!isError(fileStatus)" class="pages">
<mat-icon svgIcon="red:pages"></mat-icon>
{{ fileStatus.numberOfPages }}
</div>
<div [class.extend-cols]="isError(fileStatus)" class="status-container">
<div
*ngIf="isError(fileStatus)"
@ -306,6 +321,24 @@
>
<mat-icon svgIcon="red:check-alt"></mat-icon>
</button>
<button
(click)="undoApproveOrUnderApproval($event, fileStatus)"
*ngIf="
isApprovedOrUnderApproval(fileStatus) &&
appStateService.isActiveProjectOwnerAndManager
"
[matTooltip]="
(fileStatus.status === 'APPROVED'
? 'project-overview.under-approval'
: 'project-overview.under-review'
) | translate
"
[matTooltipPosition]="'above'"
color="accent"
mat-icon-button
>
<mat-icon svgIcon="red:undo"></mat-icon>
</button>
</div>
</div>
</div>

View File

@ -13,10 +13,10 @@
}
.grid-container {
grid-template-columns: 3fr 2fr 1fr 2fr auto;
grid-template-columns: 3fr 2fr 1fr 1fr 2fr auto;
&.bulk-select {
grid-template-columns: auto 3fr 2fr 1fr 2fr auto;
grid-template-columns: auto 3fr 2fr 1fr 1fr 2fr auto;
}
.table-item {
@ -29,7 +29,7 @@
}
.extend-cols {
grid-column-end: span 3;
grid-column-end: span 4;
align-items: flex-end;
}
@ -45,6 +45,23 @@
gap: 4px;
}
.pages {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 4px;
opacity: 0.7;
color: $grey-1;
font-size: 11px;
letter-spacing: 0;
line-height: 14px;
.mat-icon {
width: 10px;
height: 10px;
}
}
.status-container {
align-items: flex-end;
}

View File

@ -21,7 +21,7 @@ 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 { ActiveToast } from 'ngx-toastr';
import { FileStatusWrapper } from '../file/model/file-status.wrapper';
@Component({
selector: 'redaction-project-overview-screen',
@ -36,7 +36,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
public peopleFilters: FilterModel[];
public addedDateFilters: FilterModel[];
public displayedFiles: FileStatus[] = [];
public displayedFiles: FileStatusWrapper[] = [];
@ViewChild('projectDetailsComponent', { static: true })
private _projectDetailsComponent: ProjectDetailsComponent;
@ -85,17 +85,17 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
return this.userService.user;
}
public isPending(fileStatus: FileStatus) {
return fileStatus.status === FileStatus.StatusEnum.UNPROCESSED;
public isPending(fileStatusWrapper: FileStatusWrapper) {
return fileStatusWrapper.status === FileStatus.StatusEnum.UNPROCESSED;
}
public isError(fileStatus: FileStatus) {
return fileStatus.status === FileStatus.StatusEnum.ERROR;
public isError(fileStatusWrapper: FileStatusWrapper) {
return fileStatusWrapper.status === FileStatus.StatusEnum.ERROR;
}
public isProcessing(fileStatus: FileStatus) {
public isProcessing(fileStatusWrapper: FileStatusWrapper) {
return [FileStatus.StatusEnum.REPROCESS, FileStatus.StatusEnum.PROCESSING].includes(
fileStatus.status
fileStatusWrapper.status
);
}
@ -157,9 +157,10 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
this._computeAllFilters();
this._filterFiles();
this._projectDetailsComponent.calculateChartConfig();
this._changeDetectorRef.detectChanges();
}
public toggleFileSelected($event: MouseEvent, file: FileStatus) {
public toggleFileSelected($event: MouseEvent, file: FileStatusWrapper) {
$event.stopPropagation();
const idx = this._selectedFileIds.indexOf(file.fileId);
if (idx === -1) {
@ -186,35 +187,38 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
);
}
public isFileSelected(file: FileStatus) {
public isFileSelected(file: FileStatusWrapper) {
return this._selectedFileIds.indexOf(file.fileId) !== -1;
}
public openDeleteFileDialog($event: MouseEvent, fileStatus: FileStatus) {
public openDeleteFileDialog($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
this._dialogService.openDeleteFileDialog(
$event,
fileStatus.projectId,
fileStatus.fileId,
fileStatusWrapper.projectId,
fileStatusWrapper.fileId,
() => {
this._calculateData();
}
);
}
downloadFileRedactionReport($event: MouseEvent, file: FileStatus) {
downloadFileRedactionReport($event: MouseEvent, file: FileStatusWrapper) {
$event.stopPropagation();
this.appStateService.downloadFileRedactionReport(file);
}
public assignReviewer($event: MouseEvent, file: FileStatus) {
public assignReviewer($event: MouseEvent, file: FileStatusWrapper) {
$event.stopPropagation();
this._fileActionService.assignProjectReviewer(file, () => this.reloadProjects());
}
public reanalyseFile($event: MouseEvent, fileStatus: FileStatus) {
public reanalyseFile($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
$event.stopPropagation();
this._reanalysisControllerService
.reanalyzeFile(this.appStateService.activeProject.project.projectId, fileStatus.fileId)
.reanalyzeFile(
this.appStateService.activeProject.project.projectId,
fileStatusWrapper.fileId
)
.subscribe(() => {
this.reloadProjects();
});
@ -240,11 +244,11 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
this._uploadStatusOverlayService.openStatusOverlay();
}
public canOpenFile(fileStatus: FileStatus): boolean {
public canOpenFile(fileStatusWrapper: FileStatusWrapper): boolean {
return (
!this.isError(fileStatus) &&
!this.isProcessing(fileStatus) &&
this.appStateService.isReviewerOrOwner(fileStatus)
!this.isError(fileStatusWrapper) &&
!this.isProcessing(fileStatusWrapper) &&
this.appStateService.isReviewerOrOwner(fileStatusWrapper)
);
}
@ -253,7 +257,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
}
private _computeAllFilters() {
const allDistinctFileStatus = new Set<string>();
const allDistinctFileStatusWrapper = new Set<string>();
const allDistinctPeople = new Set<string>();
const allDistinctAddedDates = new Set<string>();
@ -264,7 +268,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
// File statuses
this.appStateService.activeProject.files.forEach((file) =>
allDistinctFileStatus.add(file.status)
allDistinctFileStatusWrapper.add(file.status)
);
// Added dates
@ -273,7 +277,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
);
this.statusFilters = [];
allDistinctFileStatus.forEach((status) => {
allDistinctFileStatusWrapper.forEach((status) => {
this.statusFilters.push({
key: status,
label: humanize(status)
@ -308,19 +312,19 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
const statusFilterMatched = this._checkFilter(
file,
this.statusFilters,
(f: FileStatus, filter: FilterModel) => f.status === filter.key
(f: FileStatusWrapper, filter: FilterModel) => f.status === filter.key
);
const peopleFilterMatched = this._checkFilter(
file,
this.peopleFilters,
(f: FileStatus, filter: FilterModel) => f.currentReviewer === filter.key
(f: FileStatusWrapper, filter: FilterModel) => f.currentReviewer === filter.key
);
const addedFilterMatched = this._checkFilter(
file,
this.addedDateFilters,
(f: FileStatus, filter: FilterModel) =>
(f: FileStatusWrapper, filter: FilterModel) =>
moment(f.added).format('DD/MM/YYYY') === filter.key
);
@ -333,7 +337,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
this._changeDetectorRef.detectChanges();
}
private _checkFilter(file: FileStatus, filters: FilterModel[], validate: Function) {
private _checkFilter(file: FileStatusWrapper, filters: FilterModel[], validate: Function) {
const hasChecked = filters.find((f) => f.checked);
if (!hasChecked) {
return true;
@ -348,21 +352,30 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
return filterMatched;
}
requestApprovalOrApproveFile($event: MouseEvent, fileStatus: FileStatus) {
requestApprovalOrApproveFile($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
$event.stopPropagation();
if (fileStatus.status === 'UNDER_REVIEW') {
this._fileActionService.setUnderApproval(fileStatus);
} else {
this._fileActionService.setApproved(fileStatus);
}
this._fileActionService.requestApprovalOrApproveFile(fileStatusWrapper).subscribe(() => {
this.reloadProjects();
});
}
isApprovedOrUnderApproval(fileStatus: FileStatus) {
return fileStatus.status === 'APPROVED' || fileStatus.status === 'UNDER_APPROVAL';
undoApproveOrUnderApproval($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
$event.stopPropagation();
this._fileActionService.undoApproveOrUnderApproval(fileStatusWrapper).subscribe(() => {
this.reloadProjects();
});
}
canApprove(fileStatus: FileStatus) {
return fileStatus.status === 'UNDER_REVIEW' || fileStatus.status === 'UNDER_APPROVAL';
isApprovedOrUnderApproval(fileStatusWrapper: FileStatusWrapper) {
return (
fileStatusWrapper.status === 'APPROVED' || fileStatusWrapper.status === 'UNDER_APPROVAL'
);
}
canApprove(fileStatusWrapper: FileStatusWrapper) {
return (
fileStatusWrapper.status === 'UNDER_REVIEW' ||
fileStatusWrapper.status === 'UNDER_APPROVAL'
);
}
}

View File

@ -20,11 +20,12 @@ import { download } from '../utils/file-download-utils';
import { humanize } from '../utils/functions';
import { AnnotationWrapper } from '../screens/file/model/annotation.wrapper';
import * as moment from 'moment';
import { FileStatusWrapper } from '../screens/file/model/file-status.wrapper';
export interface AppState {
projects: ProjectWrapper[];
activeProject: ProjectWrapper;
activeFile: FileStatus;
activeFile: FileStatusWrapper;
totalAnalysedPages?: number;
totalDocuments?: number;
totalPeople?: number;
@ -39,7 +40,7 @@ export class ProjectWrapper {
return this.files.find((f) => f.status === status);
}
constructor(public project: Project, public files: FileStatus[]) {}
constructor(public project: Project, public files: FileStatusWrapper[]) {}
get projectDate() {
return this.project.date;
@ -68,7 +69,7 @@ export class ProjectWrapper {
export class AppStateService {
private _appState: AppState;
private _dictionaryData: { [key: string]: TypeValue } = null;
public fileStatusChanged = new EventEmitter<FileStatus>();
public fileStatusChanged = new EventEmitter<FileStatusWrapper>();
constructor(
private readonly _router: Router,
@ -149,8 +150,8 @@ export class AppStateService {
return this._appState.activeFile?.currentReviewer === this._userService.userId;
}
get aggregatedFiles(): FileStatus[] {
const result: FileStatus[] = [];
get aggregatedFiles(): FileStatusWrapper[] {
const result: FileStatusWrapper[] = [];
this._appState.projects.forEach((p) => {
result.push(...p.files);
});
@ -169,7 +170,7 @@ export class AppStateService {
return this._appState.projects;
}
get activeFile(): FileStatus {
get activeFile(): FileStatusWrapper {
return this._appState.activeFile;
}
@ -235,7 +236,12 @@ export class AppStateService {
if (oldFile.fileId === file.fileId) {
// emit when analysis count changed
if (oldFile.lastUpdated !== file.lastUpdated) {
this.fileStatusChanged.emit(file);
this.fileStatusChanged.emit(
new FileStatusWrapper(
file,
this._userService.getNameForId(file.currentReviewer)
)
);
}
found = true;
break;
@ -243,11 +249,18 @@ export class AppStateService {
}
// emit for new file
if (!found) {
this.fileStatusChanged.emit(file);
this.fileStatusChanged.emit(
new FileStatusWrapper(
file,
this._userService.getNameForId(file.currentReviewer)
)
);
}
}
project.files = files;
project.files = files.map(
(f) => new FileStatusWrapper(f, this._userService.getNameForId(f.currentReviewer))
);
this._computeStats();
return files;
@ -386,7 +399,7 @@ export class AppStateService {
await this.reloadActiveProjectFiles();
}
downloadFileRedactionReport(file: FileStatus) {
downloadFileRedactionReport(file: FileStatusWrapper) {
this._fileUploadControllerService
.downloadRedactionReport({ fileIds: [file.fileId] }, true, 'response')
.subscribe((data) => {
@ -478,14 +491,14 @@ export class AppStateService {
this._appState.ruleVersion = result.rulesVersion;
}
isReviewerOrOwner(fileStatus: FileStatus) {
isReviewerOrOwner(fileStatus: FileStatusWrapper) {
return (
fileStatus.currentReviewer === this._userService.userId ||
this.isActiveProjectOwnerAndManager
);
}
fileNotUpToDateWithDictionary(fileStatus?: FileStatus) {
fileNotUpToDateWithDictionary(fileStatus?: FileStatusWrapper) {
if (!fileStatus) {
fileStatus = this.activeFile;
}

View File

@ -165,6 +165,7 @@
"project-overview": {
"under-approval": "Under Approval",
"approve": "Approve",
"under-review": "Under Review",
"no-files": "This Project contains no files yet. You can start your work by uploading some files!",
"new-rule": {
"label": "Outdated",
@ -193,6 +194,7 @@
"added-on": "Added on",
"needs-work": "Needs work",
"assigned-to": "Assigned to",
"pages": "Pages",
"status": "Status"
},
"upload-error": "Failed to upload file: {{name}}",

View File

@ -1,14 +1,7 @@
<svg height="16px" version="1.1" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"
>
<g fill="none" fill-rule="evenodd" id="Settings" stroke="none" stroke-width="1">
<g fill="currentColor" id="Data-Sources" transform="translate(-564.000000, -592.000000)">
<g id="Group-17" transform="translate(554.000000, 582.000000)">
<g id="Group-7-Copy">
<path
d="M25.3793879,17.3793879 L18.6206121,17.3793879 L18.6206121,10.6206121 C18.6206121,10.2775688 18.343034,10 18,10 C17.656966,10 17.3793879,10.2775781 17.3793879,10.6206121 L17.3793879,17.3793879 L10.6206121,17.3793879 C10.2775688,17.3793879 10,17.656966 10,18 C10,18.343034 10.2775781,18.6206121 10.6206121,18.6206121 L17.3793879,18.6206121 L17.3793879,25.3793879 C17.3793879,25.7224312 17.656966,26 18,26 C18.343034,26 18.6206121,25.7224219 18.6206121,25.3793879 L18.6206121,18.6206121 L25.3793879,18.6206121 C25.7224312,18.6206121 26,18.343034 26,18 C26,17.6581669 25.7224219,17.3793879 25.3793879,17.3793879 Z"
id="plus_icon"></path>
</g>
</g>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>plus_white</title>
<g id="plus_white" stroke="none" stroke-width="1" fill="currentColor" fill-rule="evenodd">
<polygon id="Path" fill-rule="nonzero" points="78 45 55 45 55 22 45 22 45 45 22 45 22 55 45 55 45 78 55 78 55 55 78 55"></polygon>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 469 B

View File

@ -0,0 +1,20 @@
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="438.536px" height="438.536px" viewBox="0 0 438.536 438.536"
style="enable-background:new 0 0 438.536 438.536;"
xml:space="preserve">
<g fill="currentColor">
<path d="M421.125,134.191c-11.608-27.03-27.217-50.347-46.819-69.949C354.7,44.639,331.384,29.033,304.353,17.42
C277.325,5.807,248.969,0.005,219.275,0.005c-27.978,0-55.052,5.277-81.227,15.843C111.879,26.412,88.61,41.305,68.243,60.531
l-37.12-36.835c-5.711-5.901-12.275-7.232-19.701-3.999C3.807,22.937,0,28.554,0,36.547v127.907c0,4.948,1.809,9.231,5.426,12.847
c3.619,3.617,7.902,5.426,12.85,5.426h127.907c7.996,0,13.61-3.807,16.846-11.421c3.234-7.423,1.903-13.988-3.999-19.701
l-39.115-39.398c13.328-12.563,28.553-22.222,45.683-28.98c17.131-6.757,35.021-10.138,53.675-10.138
c19.793,0,38.687,3.858,56.674,11.563c17.99,7.71,33.544,18.131,46.679,31.265c13.134,13.131,23.555,28.69,31.265,46.679
c7.703,17.987,11.56,36.875,11.56,56.674c0,19.798-3.856,38.686-11.56,56.672c-7.71,17.987-18.131,33.544-31.265,46.679
c-13.135,13.134-28.695,23.558-46.679,31.265c-17.987,7.707-36.881,11.561-56.674,11.561c-22.651,0-44.064-4.949-64.241-14.843
c-20.174-9.894-37.209-23.883-51.104-41.973c-1.331-1.902-3.521-3.046-6.567-3.429c-2.856,0-5.236,0.855-7.139,2.566
l-39.114,39.402c-1.521,1.53-2.33,3.478-2.426,5.853c-0.094,2.385,0.527,4.524,1.858,6.427
c20.749,25.125,45.871,44.587,75.373,58.382c29.502,13.798,60.625,20.701,93.362,20.701c29.694,0,58.05-5.808,85.078-17.416
c27.031-11.607,50.34-27.22,69.949-46.821c19.605-19.609,35.211-42.921,46.822-69.949s17.411-55.392,17.411-85.08
C438.536,189.569,432.732,161.22,421.125,134.191z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>upload</title>
<g id="upload" stroke="none" stroke-width="1" fill="currentColor" fill-rule="evenodd">
<path d="M35,35 L35,45 L15,45 L15,90 L85,90 L85,45 L65,45 L65,35 L95,35 L95,100 L5,100 L5,35 L35,35 Z M50,0 L70,20 L63,27 L55,19 L55,70 L45,70 L45,19 L37,27 L30,20 L50,0 Z" id="Combined-Shape" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 551 B

View File

@ -46,7 +46,23 @@
.mat-icon-button {
transition: background-color 0.25s ease-in-out;
&:hover {
background-color: $grey-2;
}
}
.custom-mini-fab {
line-height: 16px !important;
width: 34px !important;
height: 34px !important;
.mat-button-wrapper {
line-height: 16px !important;
}
.mat-icon {
height: 16px !important;
width: 16px !important;
}
}

View File

@ -63,28 +63,28 @@ export class StatusControllerService {
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public assignProjectOwner1(
public assignProjectOwner(
projectId: string,
fileId: string,
reviewerId: string,
observe?: 'body',
reportProgress?: boolean
): Observable<any>;
public assignProjectOwner1(
public assignProjectOwner(
projectId: string,
fileId: string,
reviewerId: string,
observe?: 'response',
reportProgress?: boolean
): Observable<HttpResponse<any>>;
public assignProjectOwner1(
public assignProjectOwner(
projectId: string,
fileId: string,
reviewerId: string,
observe?: 'events',
reportProgress?: boolean
): Observable<HttpEvent<any>>;
public assignProjectOwner1(
public assignProjectOwner(
projectId: string,
fileId: string,
reviewerId: string,
@ -93,19 +93,19 @@ export class StatusControllerService {
): Observable<any> {
if (projectId === null || projectId === undefined) {
throw new Error(
'Required parameter projectId was null or undefined when calling assignProjectOwner1.'
'Required parameter projectId was null or undefined when calling assignProjectOwner.'
);
}
if (fileId === null || fileId === undefined) {
throw new Error(
'Required parameter fileId was null or undefined when calling assignProjectOwner1.'
'Required parameter fileId was null or undefined when calling assignProjectOwner.'
);
}
if (reviewerId === null || reviewerId === undefined) {
throw new Error(
'Required parameter reviewerId was null or undefined when calling assignProjectOwner1.'
'Required parameter reviewerId was null or undefined when calling assignProjectOwner.'
);
}
@ -146,6 +146,87 @@ export class StatusControllerService {
);
}
/**
* Gets the status for a file.
* None
* @param projectId projectId
* @param fileId fileId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public getFileStatus(
projectId: string,
fileId: string,
observe?: 'body',
reportProgress?: boolean
): Observable<FileStatus>;
public getFileStatus(
projectId: string,
fileId: string,
observe?: 'response',
reportProgress?: boolean
): Observable<HttpResponse<FileStatus>>;
public getFileStatus(
projectId: string,
fileId: string,
observe?: 'events',
reportProgress?: boolean
): Observable<HttpEvent<FileStatus>>;
public getFileStatus(
projectId: string,
fileId: string,
observe: any = 'body',
reportProgress: boolean = false
): Observable<any> {
if (projectId === null || projectId === undefined) {
throw new Error(
'Required parameter projectId was null or undefined when calling getFileStatus.'
);
}
if (fileId === null || fileId === undefined) {
throw new Error(
'Required parameter fileId was null or undefined when calling getFileStatus.'
);
}
let headers = this.defaultHeaders;
// authentication (RED-OAUTH) required
if (this.configuration.accessToken) {
const accessToken =
typeof this.configuration.accessToken === 'function'
? this.configuration.accessToken()
: this.configuration.accessToken;
headers = headers.set('Authorization', 'Bearer ' + accessToken);
}
// to determine the Accept header
const httpHeaderAccepts: string[] = ['application/json'];
const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(
httpHeaderAccepts
);
if (httpHeaderAcceptSelected !== undefined) {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [];
return this.httpClient.request<FileStatus>(
'get',
`${this.basePath}/status/${encodeURIComponent(String(projectId))}/${encodeURIComponent(
String(fileId)
)}`,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
* Gets the status for all files in a project.
* None
@ -375,4 +456,85 @@ export class StatusControllerService {
}
);
}
/**
* Sets the status UNDER_REVIEW for a file.
* None
* @param projectId projectId
* @param fileId fileId
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public setStatusUnderReview(
projectId: string,
fileId: string,
observe?: 'body',
reportProgress?: boolean
): Observable<any>;
public setStatusUnderReview(
projectId: string,
fileId: string,
observe?: 'response',
reportProgress?: boolean
): Observable<HttpResponse<any>>;
public setStatusUnderReview(
projectId: string,
fileId: string,
observe?: 'events',
reportProgress?: boolean
): Observable<HttpEvent<any>>;
public setStatusUnderReview(
projectId: string,
fileId: string,
observe: any = 'body',
reportProgress: boolean = false
): Observable<any> {
if (projectId === null || projectId === undefined) {
throw new Error(
'Required parameter projectId was null or undefined when calling setStatusUnderReview.'
);
}
if (fileId === null || fileId === undefined) {
throw new Error(
'Required parameter fileId was null or undefined when calling setStatusUnderReview.'
);
}
let headers = this.defaultHeaders;
// authentication (RED-OAUTH) required
if (this.configuration.accessToken) {
const accessToken =
typeof this.configuration.accessToken === 'function'
? this.configuration.accessToken()
: this.configuration.accessToken;
headers = headers.set('Authorization', 'Bearer ' + accessToken);
}
// to determine the Accept header
const httpHeaderAccepts: string[] = [];
const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(
httpHeaderAccepts
);
if (httpHeaderAcceptSelected !== undefined) {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [];
return this.httpClient.request<any>(
'post',
`${this.basePath}/status/underreview/${encodeURIComponent(
String(projectId)
)}/${encodeURIComponent(String(fileId))}`,
{
withCredentials: this.configuration.withCredentials,
headers: headers,
observe: observe,
reportProgress: reportProgress
}
);
}
}

View File

@ -50,6 +50,10 @@ export interface FileStatus {
* Shows if any requests were found during the analysis.
*/
hasRequests?: boolean;
/**
* Shows the last date of a successful analysis.
*/
lastProcessed?: string;
/**
* Date and time when the file was last updated.
*/