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 @@ + + + Needs work + + + +