Pull request #30: UI updates

Merge in RED/ui from ui-updates to master

* commit '91b4e77c2ed51adcffa4c7f0c46d8ce7c36e4bcf':
  Needs work filter
  Refactor filters
  Style suggestion annotation icon
This commit is contained in:
Timo Bejan 2020-11-06 09:33:36 +01:00
commit 40845693a2
11 changed files with 162 additions and 102 deletions

View File

@ -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;
}

View File

@ -1,4 +1,9 @@
<div class="icon" [class.hint]="typeValue?.hint" [style.background-color]="typeValue?.hexColor">
<div
[class.hint]="typeValue?.hint"
[class.request]="typeValue?.type === 'request'"
[style.background-color]="typeValue?.hexColor"
class="icon"
>
<span>{{ (typeValue?.type)[0] }}</span>
<!-- {{typeValue | json }}-->
</div>

View File

@ -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 {

View File

@ -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
);
}
}
}

View File

@ -32,6 +32,7 @@ export class IconsModule {
'lightning',
'logout',
'menu',
'needs-work',
'pages',
'plus',
'preview',

View File

@ -62,7 +62,7 @@
<span class="all-caps-label">
{{
'project-listing.table-header.title'
| translate: { length: appStateService.allProjects?.length || 0 }
| translate: { length: displayedProjects.length || 0 }
}}
</span>
</div>

View File

@ -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);

View File

@ -34,6 +34,13 @@
[hasArrow]="false"
[icon]="'red:calendar'"
></redaction-filter>
<redaction-filter
(filtersChanged)="filtersChanged()"
[filterLabel]="'filters.needs-work'"
[filters]="needsWorkFilters"
[hasArrow]="false"
[icon]="'red:needs-work'"
></redaction-filter>
<!-- <button mat-button translate="filters.project">-->
<!-- <mat-icon svgIcon="red:folder"></mat-icon>-->
<!-- </button>-->
@ -73,8 +80,7 @@
<span class="all-caps-label">
{{
'project-overview.table-header.title'
| translate
: { length: appStateService.activeProject?.files.length || 0 }
| translate: { length: displayedFiles.length || 0 }
}}
</span>
</div>

View File

@ -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<string>();
const allDistinctPeople = new Set<string>();
const allDistinctAddedDates = new Set<string>();
const allDistinctNeedsWork = new Set<string>();
// 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(() => {

View File

@ -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",

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Needs work</title>
<g id="Needs-work" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M100,90 L100,100 L39,100 L39,90 L100,90 Z M70,0 L70,45 L60,45 L60,10 L10,10 L10,80 L30,80 L30,90 L0,90 L0,0 L70,0 Z M86,72 L86,82 L39,82 L39,72 L86,72 Z M62,54 L62,64 L39,64 L39,54 L62,54 Z" id="Combined-Shape" fill="currentColor" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 597 B