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..71141a64d 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,70 @@ export function handleCheckedValue(filter: FilterModel) {
filter.indeterminate = false;
}
}
+
+export function checkFilter(
+ entity: any,
+ filters: FilterModel[],
+ validate: Function,
+ matchAll: boolean = false
+) {
+ const hasChecked = filters.find((f) => f.checked);
+
+ if (!hasChecked) {
+ return true;
+ }
+
+ let filterMatched = matchAll;
+ for (const filter of filters) {
+ if (filter.checked) {
+ if (matchAll) {
+ filterMatched = filterMatched && validate(entity, filter);
+ } else {
+ filterMatched = filterMatched || validate(entity, filter);
+ }
+ }
+ }
+
+ 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; matchAll?: boolean }[]
+) {
+ const filteredEntities = [];
+ for (const entity of entities) {
+ let add = true;
+ for (const filter of filters) {
+ add = add && checkFilter(entity, filter.values, filter.checker, filter.matchAll);
+ }
+ if (add) {
+ filteredEntities.push(entity);
+ }
+ }
+ return filteredEntities;
+}
diff --git a/apps/red-ui/src/app/components/annotation-icon/annotation-icon.component.html b/apps/red-ui/src/app/components/annotation-icon/annotation-icon.component.html
index 44a90abcb..e66fe0a43 100644
--- a/apps/red-ui/src/app/components/annotation-icon/annotation-icon.component.html
+++ b/apps/red-ui/src/app/components/annotation-icon/annotation-icon.component.html
@@ -1,4 +1,9 @@
-
+
{{ (typeValue?.type)[0] }}
diff --git a/apps/red-ui/src/app/components/annotation-icon/annotation-icon.component.scss b/apps/red-ui/src/app/components/annotation-icon/annotation-icon.component.scss
index cb9a7dd42..e34c2741a 100644
--- a/apps/red-ui/src/app/components/annotation-icon/annotation-icon.component.scss
+++ b/apps/red-ui/src/app/components/annotation-icon/annotation-icon.component.scss
@@ -14,13 +14,13 @@
color: $white;
}
-.suggestion {
+.request {
width: 0;
height: 0;
border: 9px solid transparent;
- border-bottom-color: $grey-1;
position: relative;
top: -9px;
+ background-color: transparent !important;
&:after {
content: '';
@@ -30,7 +30,6 @@
width: 0;
height: 0;
border: 9px solid transparent;
- border-top-color: $grey-1;
}
span {
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 35dc28774..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
@@ -11,5 +11,17 @@ export class AnnotationIconComponent implements OnInit {
constructor() {}
- ngOnInit(): void {}
+ ngOnInit(): void {
+ if (this.typeValue?.type === 'request') {
+ const styleSheet = document.styleSheets[0];
+ styleSheet.insertRule(
+ `.request:after { border-top-color: ${this.typeValue.hexColor} !important; }`,
+ styleSheet.cssRules.length
+ );
+ styleSheet.insertRule(
+ `.request { border-bottom-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 7e3f45e22..a01107d44 100644
--- a/apps/red-ui/src/app/icons/icons.module.ts
+++ b/apps/red-ui/src/app/icons/icons.module.ts
@@ -32,6 +32,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.html b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html
index 1eef5059f..eaff2adb0 100644
--- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html
+++ b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.html
@@ -62,7 +62,7 @@
{{
'project-listing.table-header.title'
- | translate: { length: appStateService.allProjects?.length || 0 }
+ | translate: { length: displayedProjects.length || 0 }
}}
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..a3ff90a56 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'"
>
+
@@ -73,8 +80,7 @@
{{
'project-overview.table-header.title'
- | translate
- : { length: appStateService.activeProject?.files.length || 0 }
+ | translate: { length: displayedFiles.length || 0 }
}}
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..5be518bcc 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, matchAll: true }
+ ];
+ 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 77adc8130..bc891c6a0 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": {
@@ -297,7 +298,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 @@
+
+