diff --git a/apps/red-ui/src/app/common/filter/utils/filter-utils.ts b/apps/red-ui/src/app/common/filter/utils/filter-utils.ts
index 31e40ef20..868ccddd7 100644
--- a/apps/red-ui/src/app/common/filter/utils/filter-utils.ts
+++ b/apps/red-ui/src/app/common/filter/utils/filter-utils.ts
@@ -1,4 +1,7 @@
import { FilterModel } from '../model/filter.model';
+import { FileStatusWrapper } from '../../../screens/file/model/file-status.wrapper';
+import * as moment from 'moment';
+import { ProjectWrapper } from '../../../state/app-state.service';
export function handleCheckedValue(filter: FilterModel) {
if (filter.filters && filter.filters.length) {
@@ -12,3 +15,59 @@ export function handleCheckedValue(filter: FilterModel) {
filter.indeterminate = false;
}
}
+
+export function checkFilter(entity: any, filters: FilterModel[], validate: Function) {
+ const hasChecked = filters.find((f) => f.checked);
+ if (!hasChecked) {
+ return true;
+ }
+ let filterMatched = false;
+ for (const filter of filters) {
+ if (filter.checked && validate(entity, filter)) {
+ filterMatched = true;
+ break;
+ }
+ }
+ return filterMatched;
+}
+
+export const keyChecker = (key: string) => (entity: any, filter: FilterModel) =>
+ entity[key] === filter.key;
+
+export const annotationFilterChecker = (f: FileStatusWrapper, filter: FilterModel) => {
+ const getter = 'has' + filter.key[0].toUpperCase() + filter.key.slice(1);
+ return f[getter];
+};
+
+export const fileAddedFilterChecker = (f: FileStatusWrapper, filter: FilterModel) =>
+ moment(f.added).format('DD/MM/YYYY') === filter.key;
+
+export const projectStatusChecker = (pw: ProjectWrapper, filter: FilterModel) =>
+ pw.hasStatus(filter.key);
+
+export const projectMemberChecker = (pw: ProjectWrapper, filter: FilterModel) => {
+ return pw.hasMember(filter.key);
+};
+
+export const dueDateChecker = (pw: ProjectWrapper, filter: FilterModel) =>
+ pw.dueDateMatches(filter.key);
+
+export const addedDateChecker = (pw: ProjectWrapper, filter: FilterModel) =>
+ pw.addedDateMatches(filter.key);
+
+export function getFilteredEntities(
+ entities: any[],
+ filters: { values: FilterModel[]; checker: Function }[]
+) {
+ const filteredEntities = [];
+ for (const entity of entities) {
+ let add = true;
+ for (const filter of filters) {
+ add = add && checkFilter(entity, filter.values, filter.checker);
+ }
+ if (add) {
+ filteredEntities.push(entity);
+ }
+ }
+ return filteredEntities;
+}
diff --git a/apps/red-ui/src/app/components/annotation-icon/annotation-icon.component.ts b/apps/red-ui/src/app/components/annotation-icon/annotation-icon.component.ts
index 18d7351d8..ec379aa5e 100644
--- a/apps/red-ui/src/app/components/annotation-icon/annotation-icon.component.ts
+++ b/apps/red-ui/src/app/components/annotation-icon/annotation-icon.component.ts
@@ -13,7 +13,7 @@ export class AnnotationIconComponent implements OnInit {
ngOnInit(): void {
if (this.typeValue?.type === 'request') {
- let styleSheet = document.styleSheets[0];
+ const styleSheet = document.styleSheets[0];
styleSheet.insertRule(
`.request:after { border-top-color: ${this.typeValue.hexColor} !important; }`,
styleSheet.cssRules.length
diff --git a/apps/red-ui/src/app/icons/icons.module.ts b/apps/red-ui/src/app/icons/icons.module.ts
index 81d515fe0..b09c0760f 100644
--- a/apps/red-ui/src/app/icons/icons.module.ts
+++ b/apps/red-ui/src/app/icons/icons.module.ts
@@ -31,6 +31,7 @@ export class IconsModule {
'lightning',
'logout',
'menu',
+ 'needs-work',
'pages',
'plus',
'preview',
diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts
index 7771d3457..3cc12487d 100644
--- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts
+++ b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts
@@ -8,6 +8,13 @@ import { DialogService } from '../../dialogs/dialog.service';
import { FilterModel } from '../../common/filter/model/filter.model';
import * as moment from 'moment';
import { SortingComponent, SortingOption } from '../../components/sorting/sorting.component';
+import {
+ addedDateChecker,
+ dueDateChecker,
+ getFilteredEntities,
+ projectMemberChecker,
+ projectStatusChecker
+} from '../../common/filter/utils/filter-utils';
@Component({
selector: 'redaction-project-listing-screen',
@@ -205,59 +212,17 @@ export class ProjectListingScreenComponent implements OnInit {
}
private _filterProjects() {
- const filteredProjects = [];
+ const filters = [
+ { values: this.statusFilters, checker: projectStatusChecker },
+ { values: this.peopleFilters, checker: projectMemberChecker },
+ { values: this.dueDateFilters, checker: dueDateChecker },
+ { values: this.addedDateFilters, checker: addedDateChecker }
+ ];
- for (const project of this.appStateService.allProjects) {
- const statusFilterMatched = this._checkFilter(
- project,
- this.statusFilters,
- (pw: ProjectWrapper, filter: FilterModel) => pw.hasStatus(filter.key)
- );
- const peopleFilterMatched = this._checkFilter(
- project,
- this.peopleFilters,
- (pw: ProjectWrapper, filter: FilterModel) => pw.hasMember(filter.key)
- );
- const dueDateFilterMatched = this._checkFilter(
- project,
- this.dueDateFilters,
- (pw: ProjectWrapper, filter: FilterModel) => pw.dueDateMatches(filter.key)
- );
- const addedFilterMatched = this._checkFilter(
- project,
- this.addedDateFilters,
- (pw: ProjectWrapper, filter: FilterModel) => pw.addedDateMatches(filter.key)
- );
-
- if (
- statusFilterMatched &&
- peopleFilterMatched &&
- addedFilterMatched &&
- dueDateFilterMatched
- ) {
- filteredProjects.push(project);
- }
- }
-
- this.displayedProjects = filteredProjects;
+ this.displayedProjects = getFilteredEntities(this.appStateService.allProjects, filters);
this._changeDetectorRef.detectChanges();
}
- private _checkFilter(project: ProjectWrapper, filters: FilterModel[], validate: Function) {
- const hasChecked = filters.find((f) => f.checked);
- if (!hasChecked) {
- return true;
- }
- let filterMatched = false;
- for (const filter of filters) {
- if (filter.checked && validate(project, filter)) {
- filterMatched = true;
- break;
- }
- }
- return filterMatched;
- }
-
public toggleProjectSelected($event: MouseEvent, pw: ProjectWrapper) {
$event.stopPropagation();
const idx = this._selectedProjectIds.indexOf(pw.project.projectId);
diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html
index db7f5cea1..ac2f72043 100644
--- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html
+++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html
@@ -34,6 +34,13 @@
[hasArrow]="false"
[icon]="'red:calendar'"
>
+
diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts
index a109f4bcb..7c83dca71 100644
--- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts
+++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts
@@ -22,6 +22,12 @@ import * as moment from 'moment';
import { SortingComponent, SortingOption } from '../../components/sorting/sorting.component';
import { ProjectDetailsComponent } from './project-details/project-details.component';
import { FileStatusWrapper } from '../file/model/file-status.wrapper';
+import {
+ annotationFilterChecker,
+ fileAddedFilterChecker,
+ getFilteredEntities,
+ keyChecker
+} from '../../common/filter/utils/filter-utils';
@Component({
selector: 'redaction-project-overview-screen',
@@ -35,6 +41,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
public statusFilters: FilterModel[];
public peopleFilters: FilterModel[];
public addedDateFilters: FilterModel[];
+ public needsWorkFilters: FilterModel[];
public displayedFiles: FileStatusWrapper[] = [];
@@ -260,6 +267,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
const allDistinctFileStatusWrapper = new Set();
const allDistinctPeople = new Set();
const allDistinctAddedDates = new Set();
+ const allDistinctNeedsWork = new Set();
// All people
this.appStateService.activeProject.project.memberIds.forEach((memberId) =>
@@ -276,6 +284,13 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY'))
);
+ // Needs work
+ this.appStateService.activeProject.files.forEach((file) => {
+ if (file.hasHints) allDistinctNeedsWork.add('hints');
+ if (file.hasRedactions) allDistinctNeedsWork.add('redactions');
+ if (file.hasRequests) allDistinctNeedsWork.add('requests');
+ });
+
this.statusFilters = [];
allDistinctFileStatusWrapper.forEach((status) => {
this.statusFilters.push({
@@ -299,6 +314,14 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
label: date
});
});
+
+ this.needsWorkFilters = [];
+ allDistinctNeedsWork.forEach((type) => {
+ this.needsWorkFilters.push({
+ key: type,
+ label: this._translateService.instant('filter.' + type)
+ });
+ });
}
filtersChanged() {
@@ -306,52 +329,19 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
}
private _filterFiles() {
- const filteredFiles = [];
-
- for (const file of this.appStateService.activeProject.files) {
- const statusFilterMatched = this._checkFilter(
- file,
- this.statusFilters,
- (f: FileStatusWrapper, filter: FilterModel) => f.status === filter.key
- );
-
- const peopleFilterMatched = this._checkFilter(
- file,
- this.peopleFilters,
- (f: FileStatusWrapper, filter: FilterModel) => f.currentReviewer === filter.key
- );
-
- const addedFilterMatched = this._checkFilter(
- file,
- this.addedDateFilters,
- (f: FileStatusWrapper, filter: FilterModel) =>
- moment(f.added).format('DD/MM/YYYY') === filter.key
- );
-
- if (statusFilterMatched && peopleFilterMatched && addedFilterMatched) {
- filteredFiles.push(file);
- }
- }
-
- this.displayedFiles = filteredFiles;
+ const filters = [
+ { values: this.statusFilters, checker: keyChecker('status') },
+ { values: this.peopleFilters, checker: keyChecker('currentReviewer') },
+ { values: this.addedDateFilters, checker: fileAddedFilterChecker },
+ { values: this.needsWorkFilters, checker: annotationFilterChecker }
+ ];
+ this.displayedFiles = getFilteredEntities(
+ this.appStateService.activeProject.files,
+ filters
+ );
this._changeDetectorRef.detectChanges();
}
- private _checkFilter(file: FileStatusWrapper, filters: FilterModel[], validate: Function) {
- const hasChecked = filters.find((f) => f.checked);
- if (!hasChecked) {
- return true;
- }
- let filterMatched = false;
- for (const filter of filters) {
- if (filter.checked && validate(file, filter)) {
- filterMatched = true;
- break;
- }
- }
- return filterMatched;
- }
-
requestApprovalOrApproveFile($event: MouseEvent, fileStatusWrapper: FileStatusWrapper) {
$event.stopPropagation();
this._fileActionService.requestApprovalOrApproveFile(fileStatusWrapper).subscribe(() => {
diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json
index 8f5e5094d..133db5695 100644
--- a/apps/red-ui/src/assets/i18n/en.json
+++ b/apps/red-ui/src/assets/i18n/en.json
@@ -89,7 +89,8 @@
"due-date": "Due Date",
"created-on": "Created On",
"project": "Project",
- "document": "Document"
+ "document": "Document",
+ "needs-work": "Needs Work"
},
"project-listing": {
"report": {
@@ -295,7 +296,11 @@
"dictionary": "Dictionary",
"content": "Content",
"page": "Page",
- "filter": {},
+ "filter": {
+ "hints": "Hints",
+ "redactions": "Redactions",
+ "requests": "Requests"
+ },
"annotation-filter": {
"super-type": {
"redaction": "Redaction",
diff --git a/apps/red-ui/src/assets/icons/general/needs-work.svg b/apps/red-ui/src/assets/icons/general/needs-work.svg
new file mode 100644
index 000000000..9f85f65ce
--- /dev/null
+++ b/apps/red-ui/src/assets/icons/general/needs-work.svg
@@ -0,0 +1,7 @@
+
+