UI updates
This commit is contained in:
parent
08979ec31c
commit
dc73f615fa
@ -64,7 +64,6 @@ import { ManualAnnotationDialogComponent } from './dialogs/manual-redaction-dial
|
||||
import { ToastComponent } from './components/toast/toast.component';
|
||||
import { FilterComponent } from './common/filter/filter.component';
|
||||
import { AppInfoComponent } from './screens/app-info/app-info.component';
|
||||
import { SortingComponent } from './components/sorting/sorting.component';
|
||||
import { TableColNameComponent } from './components/table-col-name/table-col-name.component';
|
||||
import { ProjectDetailsComponent } from './screens/project-overview-screen/project-details/project-details.component';
|
||||
import { PageIndicatorComponent } from './screens/file/page-indicator/page-indicator.component';
|
||||
@ -77,6 +76,7 @@ import { FileActionsComponent } from './common/file-actions/file-actions.compone
|
||||
import { TypeAnnotationIconComponent } from './components/type-annotation-icon/type-annotation-icon.component';
|
||||
import { TypeFilterComponent } from './components/type-filter/type-filter.component';
|
||||
import { DictionaryAnnotationIconComponent } from './components/dictionary-annotation-icon/dictionary-annotation-icon.component';
|
||||
import { BulkActionsComponent } from './screens/project-overview-screen/bulk-actions/bulk-actions.component';
|
||||
|
||||
export function HttpLoaderFactory(httpClient: HttpClient) {
|
||||
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
|
||||
@ -108,7 +108,6 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
|
||||
ToastComponent,
|
||||
FilterComponent,
|
||||
AppInfoComponent,
|
||||
SortingComponent,
|
||||
TableColNameComponent,
|
||||
ProjectDetailsComponent,
|
||||
PageIndicatorComponent,
|
||||
@ -121,7 +120,9 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
|
||||
FileActionsComponent,
|
||||
TypeAnnotationIconComponent,
|
||||
TypeFilterComponent,
|
||||
DictionaryAnnotationIconComponent
|
||||
DictionaryAnnotationIconComponent,
|
||||
BulkActionsComponent,
|
||||
FileActionsComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
||||
@ -52,7 +52,7 @@ export class FileActionsComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
openDeleteFileDialog($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
|
||||
this._dialogService.openDeleteFileDialog($event, fileStatusWrapper.projectId, fileStatusWrapper.fileId, () => {
|
||||
this._dialogService.openDeleteFilesDialog($event, fileStatusWrapper.projectId, [fileStatusWrapper.fileId], () => {
|
||||
this.actionPerformed.emit('delete');
|
||||
});
|
||||
}
|
||||
|
||||
@ -81,13 +81,17 @@ export class PermissionsService {
|
||||
return fileStatus.status === 'APPROVED';
|
||||
}
|
||||
|
||||
canApprove(fileStatus?: FileStatusWrapper) {
|
||||
canSetUnderReview(fileStatus?: FileStatusWrapper) {
|
||||
if (!fileStatus) {
|
||||
fileStatus = this._appStateService.activeFile;
|
||||
}
|
||||
return fileStatus.status === 'UNDER_APPROVAL' && this.isManagerAndOwner();
|
||||
}
|
||||
|
||||
canApprove(fileStatus?: FileStatusWrapper) {
|
||||
return this.canSetUnderReview && !fileStatus.hasRequests;
|
||||
}
|
||||
|
||||
canSetUnderApproval(fileStatus?: FileStatusWrapper) {
|
||||
if (!fileStatus) {
|
||||
fileStatus = this._appStateService.activeFile;
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
<mat-form-field appearance="none" class="red-select">
|
||||
<mat-select
|
||||
[(ngModel)]="activeOption"
|
||||
panelClass="red-select-panel"
|
||||
(selectionChange)="dropdownSelect()"
|
||||
>
|
||||
<mat-option *ngFor="let option of sortingOptions" [value]="option">
|
||||
{{ option.label | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@ -1,92 +0,0 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
|
||||
export class SortingOption {
|
||||
label?: string;
|
||||
order: 'asc' | 'desc';
|
||||
column: string;
|
||||
}
|
||||
|
||||
const SORTING_OPTIONS: { [key: string]: SortingOption[] } = {
|
||||
'project-listing': [
|
||||
{ label: 'sorting.recent', order: 'desc', column: 'projectDate' },
|
||||
{ label: 'sorting.alphabetically', order: 'asc', column: 'project.projectName' }
|
||||
],
|
||||
'project-overview': [
|
||||
{ label: 'sorting.recent', order: 'desc', column: 'added' },
|
||||
{ label: 'sorting.alphabetically', order: 'asc', column: 'filename' },
|
||||
{ label: 'sorting.number-of-pages', order: 'asc', column: 'numberOfPages' },
|
||||
{ label: 'sorting.number-of-analyses', order: 'desc', column: 'numberOfAnalyses' }
|
||||
]
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-sorting',
|
||||
templateUrl: './sorting.component.html',
|
||||
styleUrls: ['./sorting.component.scss']
|
||||
})
|
||||
export class SortingComponent implements OnInit {
|
||||
@Input() initialOption: SortingOption;
|
||||
|
||||
@Input()
|
||||
private type: 'project-overview' | 'project-listing';
|
||||
|
||||
@Output()
|
||||
private optionChanged = new EventEmitter<SortingOption>();
|
||||
|
||||
public sortingOptions: SortingOption[];
|
||||
public activeOption: SortingOption;
|
||||
|
||||
constructor() {}
|
||||
|
||||
public ngOnInit(): void {
|
||||
if (this.initialOption) {
|
||||
this.setOption(this.initialOption);
|
||||
}
|
||||
}
|
||||
|
||||
private _addCustomOption(option: Partial<SortingOption>) {
|
||||
const customOption = {
|
||||
label: 'sorting.custom',
|
||||
column: option.column,
|
||||
order: option.order
|
||||
};
|
||||
this.sortingOptions.push(customOption);
|
||||
this.activeOption = customOption;
|
||||
}
|
||||
|
||||
private _resetOptions() {
|
||||
if (this.activeOption?.label !== 'sorting.custom') {
|
||||
this.sortingOptions = [...SORTING_OPTIONS[this.type]];
|
||||
}
|
||||
}
|
||||
|
||||
public dropdownSelect() {
|
||||
this._resetOptions();
|
||||
this.optionChanged.emit(this.activeOption);
|
||||
}
|
||||
|
||||
public setOption(option: { column: string; order: 'asc' | 'desc' }) {
|
||||
if (!this.sortingOptions) {
|
||||
this._resetOptions();
|
||||
}
|
||||
const existingOption = this.sortingOptions.find(
|
||||
(o) => o.column === option.column && o.order === option.order
|
||||
);
|
||||
if (existingOption) {
|
||||
this.activeOption = existingOption;
|
||||
this._resetOptions();
|
||||
} else {
|
||||
this._addCustomOption(option);
|
||||
}
|
||||
}
|
||||
|
||||
public toggleSort(column: string) {
|
||||
if (this.activeOption.column === column) {
|
||||
const currentOrder = this.activeOption.order;
|
||||
this.setOption({ column, order: currentOrder === 'asc' ? 'desc' : 'asc' });
|
||||
} else {
|
||||
this.setOption({ column, order: 'asc' });
|
||||
}
|
||||
this.optionChanged.emit(this.activeOption);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { SortingOption } from '../sorting/sorting.component';
|
||||
import { SortingOption } from '../../utils/sorting.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-table-col-name',
|
||||
@ -19,16 +19,8 @@ export class TableColNameComponent implements OnInit {
|
||||
ngOnInit(): void {}
|
||||
|
||||
public get arrowColor(): { up: string; down: string } {
|
||||
const up =
|
||||
this.activeSortingOption.order === 'desc' &&
|
||||
this.activeSortingOption.column === this.column
|
||||
? 'primary'
|
||||
: 'accent';
|
||||
const down =
|
||||
this.activeSortingOption.order === 'asc' &&
|
||||
this.activeSortingOption.column === this.column
|
||||
? 'primary'
|
||||
: 'accent';
|
||||
const up = this.activeSortingOption.order === 'desc' && this.activeSortingOption.column === this.column ? 'primary' : 'accent';
|
||||
const down = this.activeSortingOption.order === 'asc' && this.activeSortingOption.column === this.column ? 'primary' : 'accent';
|
||||
return { up, down };
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper'
|
||||
class DialogData {
|
||||
type: 'file' | 'project';
|
||||
project?: Project;
|
||||
file?: FileStatusWrapper;
|
||||
files?: FileStatusWrapper[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
@ -44,9 +44,13 @@ export class AssignOwnerDialogComponent {
|
||||
}
|
||||
|
||||
if (this.data.type === 'file') {
|
||||
const file = this.data.file;
|
||||
const uniqueReviewers = new Set<string>();
|
||||
for (const file of this.data.files) {
|
||||
uniqueReviewers.add(file.currentReviewer);
|
||||
}
|
||||
const singleUser = uniqueReviewers.size === 1 ? uniqueReviewers.values().next().value : [];
|
||||
this.usersForm = this._formBuilder.group({
|
||||
singleUser: [file?.currentReviewer]
|
||||
singleUser: [singleUser]
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -61,47 +65,35 @@ export class AssignOwnerDialogComponent {
|
||||
project.ownerId = ownerId;
|
||||
await this._appStateService.addOrUpdateProject(project);
|
||||
this._notificationService.showToastNotification(
|
||||
'Successfully assigned ' +
|
||||
this.userService.getNameForId(ownerId) +
|
||||
' to project: ' +
|
||||
project.projectName
|
||||
'Successfully assigned ' + this.userService.getNameForId(ownerId) + ' to project: ' + project.projectName
|
||||
);
|
||||
}
|
||||
|
||||
if (this.data.type === 'file') {
|
||||
const reviewerId = this.usersForm.get('singleUser').value;
|
||||
|
||||
await this._statusControllerService
|
||||
.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) +
|
||||
' to file: ' +
|
||||
this.data.file.filename
|
||||
const promises = this.data.files.map((file) =>
|
||||
this._statusControllerService.assignProjectOwner(this._appStateService.activeProjectId, file.fileId, reviewerId).toPromise()
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
for (const file of this.data.files) {
|
||||
file.currentReviewer = reviewerId;
|
||||
file.reviewerName = this.userService.getNameForId(reviewerId);
|
||||
this._notificationService.showToastNotification(
|
||||
'Successfully assigned ' + this.userService.getNameForId(reviewerId) + ' to file: ' + file.filename
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this._notificationService.showToastNotification(
|
||||
'Failed: ' + error.error.message,
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._notificationService.showToastNotification('Failed: ' + error.error.message, null, NotificationType.ERROR);
|
||||
}
|
||||
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
get singleUsersSelectOptions() {
|
||||
return this.data.type === 'file'
|
||||
? this._appStateService.activeProject.project.memberIds
|
||||
: this.userService.managerUsers.map((m) => m.userId);
|
||||
return this.data.type === 'file' ? this._appStateService.activeProject.project.memberIds : this.userService.managerUsers.map((m) => m.userId);
|
||||
}
|
||||
|
||||
get multiUsersSelectOptions() {
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FileDetailsDialogComponent } from './file-details-dialog/file-details-dialog.component';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import {
|
||||
FileStatus,
|
||||
FileUploadControllerService,
|
||||
ManualRedactionControllerService,
|
||||
Project
|
||||
} from '@redaction/red-ui-http';
|
||||
import { FileStatus, FileUploadControllerService, ManualRedactionControllerService, Project } from '@redaction/red-ui-http';
|
||||
import { ConfirmationDialogComponent } from '../common/confirmation-dialog/confirmation-dialog.component';
|
||||
import { NotificationService, NotificationType } from '../notification/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@ -38,10 +33,7 @@ export class DialogService {
|
||||
private readonly _manualRedactionControllerService: ManualRedactionControllerService
|
||||
) {}
|
||||
|
||||
public openFileDetailsDialog(
|
||||
$event: MouseEvent,
|
||||
file: FileStatus
|
||||
): MatDialogRef<FileDetailsDialogComponent> {
|
||||
public openFileDetailsDialog($event: MouseEvent, file: FileStatus): MatDialogRef<FileDetailsDialogComponent> {
|
||||
$event.stopPropagation();
|
||||
return this._dialog.open(FileDetailsDialogComponent, {
|
||||
...dialogConfig,
|
||||
@ -49,41 +41,32 @@ export class DialogService {
|
||||
});
|
||||
}
|
||||
|
||||
public openDeleteFileDialog(
|
||||
$event: MouseEvent,
|
||||
projectId: string,
|
||||
fileId: string,
|
||||
cb?: Function
|
||||
): MatDialogRef<ConfirmationDialogComponent> {
|
||||
$event.stopPropagation();
|
||||
public openDeleteFilesDialog($event: MouseEvent, projectId: string, fileIds: string[], cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
|
||||
$event?.stopPropagation();
|
||||
|
||||
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
|
||||
|
||||
ref.afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
const file = this._appStateService.getFileById(projectId, fileId);
|
||||
this._fileUploadControllerService.deleteFile(file.projectId, file.fileId).subscribe(
|
||||
async () => {
|
||||
const promises = fileIds
|
||||
.map((fileId) => this._appStateService.getFileById(projectId, fileId))
|
||||
.map((file) => this._fileUploadControllerService.deleteFile(file.projectId, file.fileId).toPromise());
|
||||
|
||||
Promise.all(promises)
|
||||
.then(async () => {
|
||||
await this._appStateService.reloadActiveProjectFiles();
|
||||
if (cb) cb();
|
||||
},
|
||||
() => {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('delete-file-error', file),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
this._notificationService.showToastNotification(this._translateService.instant('delete-files-error'), null, NotificationType.ERROR);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
public openManualRedactionDialog(
|
||||
$event: ManualRedactionEntryWrapper,
|
||||
cb?: Function
|
||||
): MatDialogRef<ManualAnnotationDialogComponent> {
|
||||
public openManualRedactionDialog($event: ManualRedactionEntryWrapper, cb?: Function): MatDialogRef<ManualAnnotationDialogComponent> {
|
||||
const ref = this._dialog.open(ManualAnnotationDialogComponent, {
|
||||
...dialogConfig,
|
||||
autoFocus: true,
|
||||
@ -99,34 +82,24 @@ export class DialogService {
|
||||
return ref;
|
||||
}
|
||||
|
||||
public openAcceptSuggestionModal(
|
||||
$event: MouseEvent,
|
||||
annotation: AnnotationWrapper,
|
||||
callback?: Function
|
||||
): MatDialogRef<ConfirmationDialogComponent> {
|
||||
public openAcceptSuggestionModal($event: MouseEvent, annotation: AnnotationWrapper, callback?: Function): MatDialogRef<ConfirmationDialogComponent> {
|
||||
$event.stopPropagation();
|
||||
|
||||
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
|
||||
ref.afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
this._manualAnnotationService
|
||||
.approveRequest(annotation.id)
|
||||
.subscribe((acceptResult) => {
|
||||
if (callback) {
|
||||
callback(acceptResult);
|
||||
}
|
||||
});
|
||||
this._manualAnnotationService.approveRequest(annotation.id).subscribe((acceptResult) => {
|
||||
if (callback) {
|
||||
callback(acceptResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
public openRejectSuggestionModal(
|
||||
$event: MouseEvent,
|
||||
annotation: AnnotationWrapper,
|
||||
rejectCallback: () => void
|
||||
): MatDialogRef<ConfirmationDialogComponent> {
|
||||
public openRejectSuggestionModal($event: MouseEvent, annotation: AnnotationWrapper, rejectCallback: () => void): MatDialogRef<ConfirmationDialogComponent> {
|
||||
$event.stopPropagation();
|
||||
|
||||
const ref = this._dialog.open(ConfirmationDialogComponent, {
|
||||
@ -145,10 +118,7 @@ export class DialogService {
|
||||
return ref;
|
||||
}
|
||||
|
||||
public openEditProjectDialog(
|
||||
$event: MouseEvent,
|
||||
project: Project
|
||||
): MatDialogRef<AddEditProjectDialogComponent> {
|
||||
public openEditProjectDialog($event: MouseEvent, project: Project): MatDialogRef<AddEditProjectDialogComponent> {
|
||||
$event.stopPropagation();
|
||||
return this._dialog.open(AddEditProjectDialogComponent, {
|
||||
...dialogConfig,
|
||||
@ -157,11 +127,7 @@ export class DialogService {
|
||||
});
|
||||
}
|
||||
|
||||
public openDeleteProjectDialog(
|
||||
$event: MouseEvent,
|
||||
project: Project,
|
||||
cb?: Function
|
||||
): MatDialogRef<ConfirmationDialogComponent> {
|
||||
public openDeleteProjectDialog($event: MouseEvent, project: Project, cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
|
||||
$event.stopPropagation();
|
||||
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
|
||||
ref.afterClosed().subscribe(async (result) => {
|
||||
@ -173,11 +139,7 @@ export class DialogService {
|
||||
return ref;
|
||||
}
|
||||
|
||||
public openAssignProjectMembersAndOwnerDialog(
|
||||
$event: MouseEvent,
|
||||
project: Project,
|
||||
cb?: Function
|
||||
): MatDialogRef<AssignOwnerDialogComponent> {
|
||||
public openAssignProjectMembersAndOwnerDialog($event: MouseEvent, project: Project, cb?: Function): MatDialogRef<AssignOwnerDialogComponent> {
|
||||
$event?.stopPropagation();
|
||||
const ref = this._dialog.open(AssignOwnerDialogComponent, {
|
||||
...dialogConfig,
|
||||
@ -189,13 +151,27 @@ export class DialogService {
|
||||
return ref;
|
||||
}
|
||||
|
||||
public openAssignFileReviewerDialog(
|
||||
file: FileStatus,
|
||||
cb?: Function
|
||||
): MatDialogRef<AssignOwnerDialogComponent> {
|
||||
public openAssignFileReviewerDialog(file: FileStatus, cb?: Function): MatDialogRef<AssignOwnerDialogComponent> {
|
||||
const ref = this._dialog.open(AssignOwnerDialogComponent, {
|
||||
...dialogConfig,
|
||||
data: { type: 'file', file: file }
|
||||
data: { type: 'file', files: [file] }
|
||||
});
|
||||
|
||||
ref.afterClosed().subscribe(() => {
|
||||
if (cb) cb();
|
||||
});
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
public openBulkAssignFileReviewerDialog(fileIds: string[], cb?: Function): MatDialogRef<AssignOwnerDialogComponent> {
|
||||
const projectId = this._appStateService.activeProject.project.projectId;
|
||||
const ref = this._dialog.open(AssignOwnerDialogComponent, {
|
||||
...dialogConfig,
|
||||
data: {
|
||||
type: 'file',
|
||||
files: fileIds.map((fileId) => this._appStateService.getFileById(projectId, fileId))
|
||||
}
|
||||
});
|
||||
|
||||
ref.afterClosed().subscribe(() => {
|
||||
@ -218,11 +194,7 @@ export class DialogService {
|
||||
return ref;
|
||||
}
|
||||
|
||||
openRemoveAnnotationModal(
|
||||
$event: MouseEvent,
|
||||
annotation: AnnotationWrapper,
|
||||
callback: () => void
|
||||
) {
|
||||
openRemoveAnnotationModal($event: MouseEvent, annotation: AnnotationWrapper, callback: () => void) {
|
||||
$event.stopPropagation();
|
||||
|
||||
const ref = this._dialog.open(ConfirmationDialogComponent, {
|
||||
@ -232,13 +204,11 @@ export class DialogService {
|
||||
|
||||
ref.afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
this._manualAnnotationService
|
||||
.removeOrSuggestRemoveAnnotation(annotation)
|
||||
.subscribe(() => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
this._manualAnnotationService.removeOrSuggestRemoveAnnotation(annotation).subscribe(() => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -36,6 +36,8 @@ export class IconsModule {
|
||||
'pages',
|
||||
'plus',
|
||||
'preview',
|
||||
'radio-indeterminate',
|
||||
'radio-selected',
|
||||
'refresh',
|
||||
'report',
|
||||
'secret',
|
||||
@ -51,11 +53,7 @@ export class IconsModule {
|
||||
];
|
||||
|
||||
for (const icon of icons) {
|
||||
iconRegistry.addSvgIconInNamespace(
|
||||
'red',
|
||||
icon,
|
||||
sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/general/${icon}.svg`)
|
||||
);
|
||||
iconRegistry.addSvgIconInNamespace('red', icon, sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/general/${icon}.svg`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,33 +40,19 @@
|
||||
|
||||
<div class="flex red-content-inner">
|
||||
<div class="left-container">
|
||||
<div class="grid-container bulk-select">
|
||||
<div class="header-item span-5">
|
||||
<div class="select-all-container">
|
||||
<div class="select-oval always-visible" [class.active]="areAllProjectsSelected()" (click)="toggleSelectAll()"></div>
|
||||
<span class="all-caps-label">
|
||||
{{ 'project-listing.table-header.title' | translate: { length: displayedProjects.length || 0 } }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<redaction-sorting
|
||||
#sortingComponent
|
||||
[initialOption]="sortingOption"
|
||||
(optionChanged)="sortingOptionChanged($event)"
|
||||
type="project-listing"
|
||||
></redaction-sorting>
|
||||
</div>
|
||||
<div class="grid-container">
|
||||
<div class="header-item span-4">
|
||||
<span class="all-caps-label">
|
||||
{{ 'project-listing.table-header.title' | translate: { length: displayedProjects.length || 0 } }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="select-oval-placeholder"></div>
|
||||
|
||||
<redaction-table-col-name
|
||||
label="project-listing.table-col-names.name"
|
||||
column="project.projectName"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[withSort]="true"
|
||||
(toggleSort)="sortingComponent.toggleSort($event)"
|
||||
(toggleSort)="sortingService.toggleSort('project-listing', $event)"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name label="project-listing.table-col-names.needs-work"></redaction-table-col-name>
|
||||
@ -83,10 +69,6 @@
|
||||
class="table-item"
|
||||
[class.pointer]="canOpenProject(pw)"
|
||||
>
|
||||
<div class="pr-0">
|
||||
<div class="select-oval" [class.active]="isProjectSelected(pw)" (click)="toggleProjectSelected($event, pw)"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="table-item-title">
|
||||
{{ pw.project.projectName }}
|
||||
|
||||
@ -12,10 +12,6 @@
|
||||
|
||||
.grid-container {
|
||||
grid-template-columns: 2fr 1fr 1fr auto;
|
||||
|
||||
&.bulk-select {
|
||||
grid-template-columns: auto 2fr 1fr 1fr auto;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-subtitle {
|
||||
@ -25,13 +21,6 @@
|
||||
.status-container {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
.active {
|
||||
font-weight: 600;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-fixed-container {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { Project } from '@redaction/red-ui-http';
|
||||
import { AppStateService } from '../../state/app-state.service';
|
||||
import { UserService } from '../../user/user.service';
|
||||
@ -7,7 +7,6 @@ import { groupBy, humanize } from '../../utils/functions';
|
||||
import { DialogService } from '../../dialogs/dialog.service';
|
||||
import { FilterModel } from '../../common/filter/model/filter.model';
|
||||
import * as moment from 'moment';
|
||||
import { SortingOption } from '../../components/sorting/sorting.component';
|
||||
import {
|
||||
annotationFilterChecker,
|
||||
dueDateChecker,
|
||||
@ -17,6 +16,7 @@ import {
|
||||
RedactionFilterSorter
|
||||
} from '../../common/filter/utils/filter-utils';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { SortingOption, SortingService } from '../../utils/sorting.service';
|
||||
import { PermissionsService } from '../../common/service/permissions.service';
|
||||
import { ProjectWrapper } from '../../state/model/project.wrapper';
|
||||
|
||||
@ -41,7 +41,6 @@ export class ProjectListingScreenComponent implements OnInit {
|
||||
};
|
||||
|
||||
public displayedProjects: ProjectWrapper[] = [];
|
||||
public sortingOption: SortingOption = { column: 'projectDate', order: 'desc' };
|
||||
|
||||
constructor(
|
||||
public readonly appStateService: AppStateService,
|
||||
@ -49,7 +48,8 @@ export class ProjectListingScreenComponent implements OnInit {
|
||||
public readonly permissionsService: PermissionsService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _dialogService: DialogService,
|
||||
private readonly _translateService: TranslateService
|
||||
private readonly _translateService: TranslateService,
|
||||
public readonly sortingService: SortingService
|
||||
) {}
|
||||
|
||||
public ngOnInit(): void {
|
||||
@ -78,6 +78,10 @@ export class ProjectListingScreenComponent implements OnInit {
|
||||
return this.userService.user;
|
||||
}
|
||||
|
||||
public get sortingOption(): SortingOption {
|
||||
return this.sortingService.getSortingOption('project-listing');
|
||||
}
|
||||
|
||||
public get activeProjects() {
|
||||
return this.appStateService.allProjects.reduce((i, p) => i + (p.project.status === Project.StatusEnum.ACTIVE ? 1 : 0), 0);
|
||||
}
|
||||
@ -123,10 +127,6 @@ export class ProjectListingScreenComponent implements OnInit {
|
||||
this._dialogService.openAssignProjectMembersAndOwnerDialog($event, project);
|
||||
}
|
||||
|
||||
public sortingOptionChanged(option: SortingOption) {
|
||||
this.sortingOption = option;
|
||||
}
|
||||
|
||||
public getProjectStatusConfig(pw: ProjectWrapper) {
|
||||
const obj = pw.files.reduce((acc, file) => {
|
||||
const status = file.status;
|
||||
@ -236,32 +236,6 @@ export class ProjectListingScreenComponent implements OnInit {
|
||||
this._changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
public toggleProjectSelected($event: MouseEvent, pw: ProjectWrapper) {
|
||||
$event.stopPropagation();
|
||||
const idx = this._selectedProjectIds.indexOf(pw.project.projectId);
|
||||
if (idx === -1) {
|
||||
this._selectedProjectIds.push(pw.project.projectId);
|
||||
} else {
|
||||
this._selectedProjectIds.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public toggleSelectAll() {
|
||||
if (this.areAllProjectsSelected()) {
|
||||
this._selectedProjectIds = [];
|
||||
} else {
|
||||
this._selectedProjectIds = this.appStateService.allProjects.map((pw) => pw.project.projectId);
|
||||
}
|
||||
}
|
||||
|
||||
public areAllProjectsSelected(): boolean {
|
||||
return this.appStateService.allProjects.length !== 0 && this._selectedProjectIds.length === this.appStateService.allProjects.length;
|
||||
}
|
||||
|
||||
public isProjectSelected(pw: ProjectWrapper): boolean {
|
||||
return this._selectedProjectIds.indexOf(pw.project.projectId) !== -1;
|
||||
}
|
||||
|
||||
openEditProjectDialog($event: MouseEvent, project: Project) {
|
||||
this._dialogService.openEditProjectDialog($event, project);
|
||||
}
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
<ng-container *ngIf="areSomeFilesSelected">
|
||||
<button
|
||||
(click)="delete()"
|
||||
*ngIf="canDelete"
|
||||
[matTooltip]="'project-overview.bulk.delete' | translate"
|
||||
class="dark"
|
||||
color="accent"
|
||||
mat-icon-button
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<mat-icon svgIcon="red:trash"></mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
(click)="assign()"
|
||||
*ngIf="canAssign"
|
||||
[matTooltip]="'project-overview.bulk.assign' | translate"
|
||||
class="dark"
|
||||
color="accent"
|
||||
mat-icon-button
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<mat-icon svgIcon="red:assign"></mat-icon>
|
||||
</button>
|
||||
|
||||
<div [matTooltip]="reanalyseTooltip | translate" matTooltipPosition="above">
|
||||
<button (click)="reanalyse()" *ngIf="canReanalyse()" [disabled]="reanalyseDisabled" class="dark" color="accent" mat-icon-button>
|
||||
<mat-icon svgIcon="red:refresh"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
(click)="approveDocuments()"
|
||||
*ngIf="canApprove"
|
||||
[matTooltip]="'project-overview.approve' | translate"
|
||||
class="dark"
|
||||
color="accent"
|
||||
mat-icon-button
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<mat-icon svgIcon="red:check-alt"></mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
(click)="setToUnderApproval()"
|
||||
*ngIf="canSetToUnderApproval"
|
||||
[matTooltip]="'project-overview.under-approval' | translate"
|
||||
class="dark"
|
||||
color="accent"
|
||||
mat-icon-button
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<mat-icon svgIcon="red:check-alt"></mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
(click)="setToUnderReview()"
|
||||
*ngIf="canSetToUnderReview"
|
||||
[matTooltip]="'project-overview.under-review' | translate"
|
||||
class="dark"
|
||||
color="accent"
|
||||
mat-icon-button
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<mat-icon svgIcon="red:refresh"></mat-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
@ -0,0 +1,3 @@
|
||||
:host {
|
||||
display: flex;
|
||||
}
|
||||
@ -0,0 +1,134 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { AppStateService } from '../../../state/app-state.service';
|
||||
import { UserService } from '../../../user/user.service';
|
||||
import { ReanalysisControllerService } from '@redaction/red-ui-http';
|
||||
import { DialogService } from '../../../dialogs/dialog.service';
|
||||
import { PermissionsService } from '../../../common/service/permissions.service';
|
||||
import { FileStatusWrapper } from '../../file/model/file-status.wrapper';
|
||||
import { FileActionService } from '../../file/service/file-action.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-bulk-actions',
|
||||
templateUrl: './bulk-actions.component.html',
|
||||
styleUrls: ['./bulk-actions.component.scss']
|
||||
})
|
||||
export class BulkActionsComponent {
|
||||
@Input() private selectedFileIds: string[];
|
||||
@Output() private reload = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _dialogService: DialogService,
|
||||
private readonly _reanalysisControllerService: ReanalysisControllerService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _fileActionService: FileActionService
|
||||
) {}
|
||||
|
||||
private get selectedFiles(): FileStatusWrapper[] {
|
||||
return this.selectedFileIds.map((fileId) => this._appStateService.getFileById(this._appStateService.activeProject.project.projectId, fileId));
|
||||
}
|
||||
|
||||
private get _hasOutdatedDocuments() {
|
||||
return this.selectedFiles.filter((file) => this._permissionsService.fileRequiresReanalysis(file)).length > 0;
|
||||
}
|
||||
|
||||
public get areAllFilesSelected() {
|
||||
return this._appStateService.activeProject.files.length !== 0 && this.selectedFileIds.length === this._appStateService.activeProject.files.length;
|
||||
}
|
||||
|
||||
public get areSomeFilesSelected() {
|
||||
return this.selectedFileIds.length > 0;
|
||||
}
|
||||
|
||||
public get canDelete() {
|
||||
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canDeleteFile(file), true);
|
||||
}
|
||||
|
||||
public get canAssign() {
|
||||
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canAssignReviewer(file), true);
|
||||
}
|
||||
|
||||
public canReanalyse() {
|
||||
return this._permissionsService.isProjectMember();
|
||||
}
|
||||
|
||||
public get reanalyseDisabled() {
|
||||
return !this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canReanalyseFile(file), true);
|
||||
}
|
||||
|
||||
public get fileStatuses() {
|
||||
return this.selectedFiles.map((file) => file.fileStatus.status);
|
||||
}
|
||||
|
||||
public get reanalyseTooltip() {
|
||||
if (!this.reanalyseDisabled) {
|
||||
return 'project-overview.bulk.reanalyse';
|
||||
}
|
||||
|
||||
if (!this._hasOutdatedDocuments) {
|
||||
return 'project-overview.bulk.reanalyse-error-outdated';
|
||||
}
|
||||
|
||||
return 'project-overview.bulk.reanalyse-error-assign';
|
||||
}
|
||||
|
||||
public delete() {
|
||||
this._dialogService.openDeleteFilesDialog(null, this._appStateService.activeProject.project.projectId, this.selectedFileIds, () => {
|
||||
this.reload.emit();
|
||||
this.selectedFileIds.splice(0, this.selectedFileIds.length);
|
||||
});
|
||||
}
|
||||
|
||||
public assign() {
|
||||
this._dialogService.openBulkAssignFileReviewerDialog(this.selectedFileIds);
|
||||
}
|
||||
|
||||
public reanalyse() {
|
||||
const promises = this.selectedFiles
|
||||
.filter((file) => this._permissionsService.fileRequiresReanalysis(file))
|
||||
.map((file) => this._reanalysisControllerService.reanalyzeFile(this._appStateService.activeProject.project.projectId, file.fileId).toPromise());
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
this.reload.emit();
|
||||
});
|
||||
}
|
||||
|
||||
// Under review
|
||||
public get canSetToUnderReview() {
|
||||
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canSetUnderReview(file), true);
|
||||
}
|
||||
public setToUnderReview() {
|
||||
const promises = this.selectedFiles.map((file) => this._fileActionService.setFileUnderReview(file).toPromise());
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
this.reload.emit();
|
||||
});
|
||||
}
|
||||
|
||||
// Under approval
|
||||
public get canSetToUnderApproval() {
|
||||
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canSetUnderApproval(file), true);
|
||||
}
|
||||
public setToUnderApproval() {
|
||||
const promises = this.selectedFiles.map((file) => this._fileActionService.setFileUnderApproval(file).toPromise());
|
||||
console.log(promises.length);
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
this.reload.emit();
|
||||
console.log('done');
|
||||
});
|
||||
}
|
||||
|
||||
// Approve
|
||||
public get canApprove() {
|
||||
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canApprove(file), true);
|
||||
}
|
||||
public approveDocuments() {
|
||||
const promises = this.selectedFiles.map((file) => this._fileActionService.setFileApproved(file).toPromise());
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
this.reload.emit();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -44,32 +44,39 @@
|
||||
<input #fileInput (change)="uploadFiles($event.target['files'])" class="file-upload-input" multiple="true" type="file" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
tmp
|
||||
<div class="flex red-content-inner">
|
||||
<div class="left-container">
|
||||
<div class="grid-container bulk-select">
|
||||
<div class="header-item span-7">
|
||||
<div class="select-all-container">
|
||||
<div (click)="toggleSelectAll()" [class.active]="areAllFilesSelected()" class="select-oval always-visible"></div>
|
||||
<span class="all-caps-label">
|
||||
{{ 'project-overview.table-header.title' | translate: { length: displayedFiles.length || 0 } }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<redaction-sorting
|
||||
#sortingComponent
|
||||
[initialOption]="sortingOption"
|
||||
(optionChanged)="sortingOptionChanged($event)"
|
||||
type="project-overview"
|
||||
></redaction-sorting>
|
||||
<div
|
||||
(click)="toggleSelectAll()"
|
||||
[class.active]="areAllFilesSelected"
|
||||
class="select-oval always-visible"
|
||||
*ngIf="!areAllFilesSelected && !areSomeFilesSelected"
|
||||
></div>
|
||||
<mat-icon *ngIf="areAllFilesSelected" (click)="toggleSelectAll()" class="selection-icon active" svgIcon="red:radio-selected"></mat-icon>
|
||||
<mat-icon
|
||||
*ngIf="areSomeFilesSelected && !areAllFilesSelected"
|
||||
(click)="toggleSelectAll()"
|
||||
class="selection-icon"
|
||||
svgIcon="red:radio-indeterminate"
|
||||
></mat-icon>
|
||||
</div>
|
||||
|
||||
<span class="all-caps-label">
|
||||
{{ 'project-overview.table-header.title' | translate: { length: displayedFiles.length || 0 } }}
|
||||
</span>
|
||||
|
||||
<redaction-bulk-actions [selectedFileIds]="selectedFileIds" (reload)="reloadProjects()"></redaction-bulk-actions>
|
||||
</div>
|
||||
|
||||
<!-- Table column names-->
|
||||
<div class="select-oval-placeholder"></div>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="sortingComponent.toggleSort($event)"
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[withSort]="true"
|
||||
column="filename"
|
||||
@ -77,7 +84,7 @@
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="sortingComponent.toggleSort($event)"
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[withSort]="true"
|
||||
column="added"
|
||||
@ -87,7 +94,7 @@
|
||||
<redaction-table-col-name label="project-overview.table-col-names.needs-work"></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="sortingComponent.toggleSort($event)"
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[withSort]="true"
|
||||
column="reviewerName"
|
||||
@ -95,7 +102,7 @@
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="sortingComponent.toggleSort($event)"
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[withSort]="true"
|
||||
column="pages"
|
||||
@ -103,7 +110,7 @@
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="sortingComponent.toggleSort($event)"
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[withSort]="true"
|
||||
class="flex-end"
|
||||
@ -120,7 +127,13 @@
|
||||
class="table-item"
|
||||
>
|
||||
<div class="pr-0">
|
||||
<div (click)="toggleFileSelected($event, fileStatus)" [class.active]="isFileSelected(fileStatus)" class="select-oval"></div>
|
||||
<div (click)="toggleFileSelected($event, fileStatus)" *ngIf="!isFileSelected(fileStatus)" class="select-oval"></div>
|
||||
<mat-icon
|
||||
class="selection-icon active"
|
||||
*ngIf="isFileSelected(fileStatus)"
|
||||
(click)="toggleFileSelected($event, fileStatus)"
|
||||
svgIcon="red:radio-selected"
|
||||
></mat-icon>
|
||||
</div>
|
||||
|
||||
<div matTooltipPosition="above" [matTooltip]="'[' + fileStatus.status + '] ' + fileStatus.filename">
|
||||
|
||||
@ -1,13 +1,6 @@
|
||||
@import '../../../assets/styles/red-variables';
|
||||
@import '../../../assets/styles/red-mixins';
|
||||
|
||||
.actions {
|
||||
.active {
|
||||
font-weight: 600;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
.file-upload-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -12,12 +12,13 @@ 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 { SortingOption } from '../../components/sorting/sorting.component';
|
||||
import { ProjectDetailsComponent } from './project-details/project-details.component';
|
||||
import { FileStatusWrapper } from '../file/model/file-status.wrapper';
|
||||
import { annotationFilterChecker, getFilteredEntities, keyChecker, RedactionFilterSorter } from '../../common/filter/utils/filter-utils';
|
||||
import { SortingOption, SortingService } from '../../utils/sorting.service';
|
||||
import { PermissionsService } from '../../common/service/permissions.service';
|
||||
import { UserService } from '../../user/user.service';
|
||||
import { FileStatus } from '@redaction/red-ui-http';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-project-overview-screen',
|
||||
@ -25,11 +26,10 @@ import { UserService } from '../../user/user.service';
|
||||
styleUrls: ['./project-overview-screen.component.scss']
|
||||
})
|
||||
export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
private _selectedFileIds: string[] = [];
|
||||
|
||||
statusFilters: FilterModel[];
|
||||
peopleFilters: FilterModel[];
|
||||
needsWorkFilters: FilterModel[];
|
||||
public selectedFileIds: string[] = [];
|
||||
public statusFilters: FilterModel[];
|
||||
public peopleFilters: FilterModel[];
|
||||
public needsWorkFilters: FilterModel[];
|
||||
|
||||
displayedFiles: FileStatusWrapper[] = [];
|
||||
|
||||
@ -41,12 +41,11 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('projectDetailsComponent', { static: false })
|
||||
private _projectDetailsComponent: ProjectDetailsComponent;
|
||||
|
||||
sortingOption: SortingOption = { column: 'added', order: 'desc' };
|
||||
|
||||
constructor(
|
||||
public readonly appStateService: AppStateService,
|
||||
public readonly userService: UserService,
|
||||
private readonly _sortingService: SortingService,
|
||||
public readonly permissionsService: PermissionsService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _dialogService: DialogService,
|
||||
@ -111,6 +110,26 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
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 get sortingOption(): SortingOption {
|
||||
return this._sortingService.getSortingOption('project-overview');
|
||||
}
|
||||
|
||||
public toggleSort($event) {
|
||||
this._sortingService.toggleSort('project-overview', $event);
|
||||
}
|
||||
|
||||
reloadProjects() {
|
||||
this.appStateService.getFiles().then(() => {
|
||||
this.calculateData();
|
||||
@ -126,31 +145,35 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
|
||||
toggleFileSelected($event: MouseEvent, file: FileStatusWrapper) {
|
||||
$event.stopPropagation();
|
||||
const idx = this._selectedFileIds.indexOf(file.fileId);
|
||||
const idx = this.selectedFileIds.indexOf(file.fileId);
|
||||
if (idx === -1) {
|
||||
this._selectedFileIds.push(file.fileId);
|
||||
this.selectedFileIds.push(file.fileId);
|
||||
} else {
|
||||
this._selectedFileIds.splice(idx, 1);
|
||||
this.selectedFileIds.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
toggleSelectAll() {
|
||||
if (this.areAllFilesSelected()) {
|
||||
this._selectedFileIds = [];
|
||||
public toggleSelectAll() {
|
||||
if (this.areAllFilesSelected) {
|
||||
this.selectedFileIds = [];
|
||||
} else {
|
||||
this._selectedFileIds = this.appStateService.activeProject.files.map((file) => file.fileId);
|
||||
this.selectedFileIds = this.appStateService.activeProject.files.map((file) => file.fileId);
|
||||
}
|
||||
}
|
||||
|
||||
areAllFilesSelected() {
|
||||
return this.appStateService.activeProject.files.length !== 0 && this._selectedFileIds.length === this.appStateService.activeProject.files.length;
|
||||
public get areAllFilesSelected() {
|
||||
return this.appStateService.activeProject.files.length !== 0 && this.selectedFileIds.length === this.appStateService.activeProject.files.length;
|
||||
}
|
||||
|
||||
isFileSelected(file: FileStatusWrapper) {
|
||||
return this._selectedFileIds.indexOf(file.fileId) !== -1;
|
||||
public get areSomeFilesSelected() {
|
||||
return this.selectedFileIds.length > 0;
|
||||
}
|
||||
|
||||
fileId(index, item) {
|
||||
public isFileSelected(file: FileStatusWrapper) {
|
||||
return this.selectedFileIds.indexOf(file.fileId) !== -1;
|
||||
}
|
||||
|
||||
public fileId(index, item) {
|
||||
return item.fileId;
|
||||
}
|
||||
|
||||
@ -170,10 +193,6 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
this._uploadStatusOverlayService.openStatusOverlay();
|
||||
}
|
||||
|
||||
sortingOptionChanged(option: SortingOption) {
|
||||
this.sortingOption = option;
|
||||
}
|
||||
|
||||
private _computeAllFilters() {
|
||||
const allDistinctFileStatusWrapper = new Set<string>();
|
||||
const allDistinctPeople = new Set<string>();
|
||||
@ -215,7 +234,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
|
||||
allDistinctPeople.forEach((userId) => {
|
||||
this.peopleFilters.push({
|
||||
key: userId,
|
||||
label: userId ? this._userService.getNameForId(userId) : this._translateService.instant('initials-avatar.unassigned')
|
||||
label: userId ? this.userService.getNameForId(userId) : this._translateService.instant('initials-avatar.unassigned')
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
import { NotificationService, NotificationType } from '../notification/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { UserService, UserWrapper } from '../user/user.service';
|
||||
import { forkJoin, of, timer } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { download } from '../utils/file-download-utils';
|
||||
|
||||
31
apps/red-ui/src/app/utils/sorting.service.ts
Normal file
31
apps/red-ui/src/app/utils/sorting.service.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
export class SortingOption {
|
||||
order: 'asc' | 'desc';
|
||||
column: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SortingService {
|
||||
private _options: { [key: string]: SortingOption } = {
|
||||
'project-listing': { column: 'project.projectName', order: 'asc' },
|
||||
'project-overview': { column: 'filename', order: 'asc' }
|
||||
};
|
||||
|
||||
constructor() {}
|
||||
|
||||
public toggleSort(screen: 'project-listing' | 'project-overview', column: string) {
|
||||
if (this._options[screen].column === column) {
|
||||
const currentOrder = this._options[screen].order;
|
||||
this._options[screen].order = currentOrder === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
this._options[screen] = { column, order: 'asc' };
|
||||
}
|
||||
}
|
||||
|
||||
public getSortingOption(screen: 'project-listing' | 'project-overview') {
|
||||
return this._options[screen];
|
||||
}
|
||||
}
|
||||
@ -209,6 +209,7 @@
|
||||
},
|
||||
"upload-error": "Failed to upload file: {{name}}",
|
||||
"delete-file-error": "Failed to delete file: {{filename}}",
|
||||
"delete-files-error": "Failed to delete files.",
|
||||
"reanalyse": {
|
||||
"action": "Reanalyze File"
|
||||
},
|
||||
@ -242,7 +243,15 @@
|
||||
},
|
||||
"header": "Project Overview",
|
||||
"upload-document": "Upload Document",
|
||||
"no-project": "Requested project: {{projectId}} does not exist! <a href='/ui/projects'>Back to Project Listing. <a/>"
|
||||
"no-project": "Requested project: {{projectId}} does not exist! <a href='/ui/projects'>Back to Project Listing. <a/>",
|
||||
"bulk": {
|
||||
"delete": "Delete documents",
|
||||
"assign": "Assign reviewer",
|
||||
"change-state": "Change state",
|
||||
"reanalyse": "Reanalyse documents",
|
||||
"reanalyse-error-outdated": "No outdated documents have been selected.",
|
||||
"reanalyse-error-member-assign": "Not all selected documents are assigned to you."
|
||||
}
|
||||
},
|
||||
"file-preview": {
|
||||
"show-redacted-view": "Show Redacted Preview",
|
||||
|
||||
13
apps/red-ui/src/assets/icons/general/radio-indeterminate.svg
Normal file
13
apps/red-ui/src/assets/icons/general/radio-indeterminate.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>BACD0033-A5B4-40C6-9A6E-99CC587814E4</title>
|
||||
<g id="Bulk-Actions" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="01.-Bulk-Actions" transform="translate(-10.000000, -127.000000)">
|
||||
<rect fill="none" x="0" y="0" width="1440" height="980"></rect>
|
||||
<polygon id="Rectangle" fill="none" points="0 112 1086 112 1086 162 0 162"></polygon>
|
||||
<g id="intermediary_selection" transform="translate(10.000000, 127.000000)" fill="currentColor">
|
||||
<path d="M10,0 C15.5228475,0 20,4.4771525 20,10 C20,15.5228475 15.5228475,20 10,20 C4.4771525,20 0,15.5228475 0,10 C0,4.4771525 4.4771525,0 10,0 Z M14,8.9 L6,8.9 C5.44771525,8.9 5,9.34771525 5,9.9 L5,9.9 L5,10.1 C5,10.6522847 5.44771525,11.1 6,11.1 L6,11.1 L14,11.1 C14.5522847,11.1 15,10.6522847 15,10.1 L15,10.1 L15,9.9 C15,9.34771525 14.5522847,8.9 14,8.9 L14,8.9 Z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
15
apps/red-ui/src/assets/icons/general/radio-selected.svg
Normal file
15
apps/red-ui/src/assets/icons/general/radio-selected.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>53931886-242F-46D3-AD2D-DBADB674ACDB</title>
|
||||
<g id="Bulk-Actions" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="01.-Bulk-Actions" transform="translate(-10.000000, -209.000000)">
|
||||
<rect fill="none" x="0" y="0" width="1440" height="980"></rect>
|
||||
<g id="Entry" transform="translate(0.000000, 194.000000)" fill="none">
|
||||
<polygon id="Rectangle" points="0 0 1086 0 1086 50 0 50"></polygon>
|
||||
</g>
|
||||
<g id="Radio_selected" transform="translate(10.000000, 209.000000)" fill="currentColor">
|
||||
<path d="M10,0 C15.5228475,0 20,4.4771525 20,10 C20,15.5228475 15.5228475,20 10,20 C4.4771525,20 0,15.5228475 0,10 C0,4.4771525 4.4771525,0 10,0 Z M13.6545842,6.07220062 L8.50054373,11.3199291 L6.58289039,9.38262084 C6.34328715,9.14059736 5.95224003,9.14059736 5.71263679,9.38262084 L5.06605523,10.0357335 C4.82983344,10.2743414 4.82983344,10.6586767 5.06605523,10.8972846 L8.06804104,13.9295935 C8.30826653,14.1722455 8.70055256,14.1715218 8.93988111,13.9279851 L15.1747747,7.58346184 C15.4094976,7.34461174 15.4087908,6.96150162 15.1731882,6.72351919 L14.5266067,6.07040651 C14.2863095,5.82768204 13.8938806,5.82848943 13.6545842,6.07220062 Z" id="radio_selected"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -66,4 +66,8 @@
|
||||
&:hover:not(.warn):not(.primary) {
|
||||
background-color: $grey-2;
|
||||
}
|
||||
|
||||
&.dark:hover {
|
||||
background-color: $grey-4;
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
}
|
||||
|
||||
&.white-dark {
|
||||
border: 1px solid #e2e4e9;
|
||||
border: 1px solid $grey-4;
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,6 +135,7 @@
|
||||
.select-oval {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
border: 1px solid $grey-5;
|
||||
background-color: $white;
|
||||
@ -145,9 +146,10 @@
|
||||
&.always-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
.selection-icon {
|
||||
width: 20px !important;
|
||||
color: $primary;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
height: 50px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
gap: 16px;
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
@ -20,7 +20,7 @@
|
||||
&.span-7 {
|
||||
grid-column-end: span 7;
|
||||
}
|
||||
&.span-5 {
|
||||
grid-column-end: span 5;
|
||||
&.span-4 {
|
||||
grid-column-end: span 4;
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,12 +173,7 @@ body {
|
||||
|
||||
.select-all-container {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
|
||||
.select-oval {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.pr-0 {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "redaction",
|
||||
"version": "0.0.137",
|
||||
"version": "0.0.138",
|
||||
"license": "MIT",
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user