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