Sorting logic, added for rulesets listing

This commit is contained in:
Adina Țeudan 2021-04-19 21:55:44 +03:00
parent c7d76e3141
commit 7951e2e2cf
8 changed files with 100 additions and 140 deletions

View File

@ -30,13 +30,13 @@
<div class="select-all-container">
<div
(click)="toggleSelectAll()"
[class.active]="areAllRuleSetsSelected"
[class.active]="areAllEntitiesSelected"
class="select-oval always-visible"
*ngIf="!areAllRuleSetsSelected && !areSomeRuleSetsSelected"
*ngIf="!areAllEntitiesSelected && !areSomeEntitiesSelected"
></div>
<mat-icon *ngIf="areAllRuleSetsSelected" (click)="toggleSelectAll()" class="selection-icon active" svgIcon="red:radio-selected"></mat-icon>
<mat-icon *ngIf="areAllEntitiesSelected" (click)="toggleSelectAll()" class="selection-icon active" svgIcon="red:radio-selected"></mat-icon>
<mat-icon
*ngIf="areSomeRuleSetsSelected && !areAllRuleSetsSelected"
*ngIf="areSomeEntitiesSelected && !areAllEntitiesSelected"
(click)="toggleSelectAll()"
class="selection-icon"
svgIcon="red:radio-indeterminate"
@ -44,11 +44,11 @@
</div>
<span class="all-caps-label">
{{ 'project-templates-listing.table-header.title' | translate: { length: displayedRuleSets.length } }}
{{ 'project-templates-listing.table-header.title' | translate: { length: displayedEntities.length } }}
</span>
</div>
<div class="table-header" redactionSyncWidth="table-item" [class.no-data]="!ruleSets.length">
<div class="table-header" redactionSyncWidth="table-item" [class.no-data]="!allEntities.length">
<div class="select-oval-placeholder"></div>
<redaction-table-col-name
@ -76,10 +76,10 @@
<div class="scrollbar-placeholder"></div>
</div>
<redaction-empty-state *ngIf="!ruleSets.length" icon="red:template" screen="project-templates-listing"></redaction-empty-state>
<redaction-empty-state *ngIf="!allEntities.length" icon="red:template" screen="project-templates-listing"></redaction-empty-state>
<redaction-empty-state
*ngIf="ruleSets.length && !displayedRuleSets.length"
*ngIf="allEntities.length && !displayedEntities.length"
screen="project-templates-listing"
type="no-match"
></redaction-empty-state>
@ -87,12 +87,12 @@
<cdk-virtual-scroll-viewport [itemSize]="100" redactionHasScrollbar>
<div
class="table-item pointer"
*cdkVirtualFor="let ruleSet of displayedRuleSets | sortBy: sortingOption.order:sortingOption.column"
*cdkVirtualFor="let ruleSet of displayedEntities | sortBy: sortingOption.order:sortingOption.column"
[routerLink]="[ruleSet.ruleSetId, 'dictionaries']"
>
<div class="pr-0" (click)="toggleTemplateSelected($event, ruleSet)">
<div *ngIf="!isRuleSetSelected(ruleSet)" class="select-oval"></div>
<mat-icon class="selection-icon active" *ngIf="isRuleSetSelected(ruleSet)" svgIcon="red:radio-selected"></mat-icon>
<div class="pr-0" (click)="toggleEntitySelected($event, ruleSet)">
<div *ngIf="!isEntitySelected(ruleSet)" class="select-oval"></div>
<mat-icon class="selection-icon active" *ngIf="isEntitySelected(ruleSet)" svgIcon="red:radio-selected"></mat-icon>
</div>
<div>

View File

@ -1,57 +1,43 @@
import { Component, OnInit } from '@angular/core';
import { SortingOption, SortingService } from '../../../../services/sorting.service';
import { Component, Injector, OnInit } from '@angular/core';
import { AppStateService } from '../../../../state/app-state.service';
import { PermissionsService } from '../../../../services/permissions.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { debounce } from '../../../../utils/debounce';
import { RuleSetModel } from '@redaction/red-ui-http';
import { UserPreferenceService } from '../../../../services/user-preference.service';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
@Component({
selector: 'redaction-rule-sets-listing-screen',
templateUrl: './rule-sets-listing-screen.component.html',
styleUrls: ['./rule-sets-listing-screen.component.scss']
})
export class RuleSetsListingScreenComponent implements OnInit {
public ruleSets: RuleSetModel[];
public displayedRuleSets: RuleSetModel[];
public selectedRuleSetIds: string[] = [];
public searchForm: FormGroup;
export class RuleSetsListingScreenComponent extends BaseListingComponent implements OnInit {
protected readonly _searchKey = 'name';
protected readonly _selectionKey = 'ruleSetId';
protected readonly _sortKey = 'rule-sets-listing';
constructor(
private readonly _dialogService: AdminDialogService,
private readonly _sortingService: SortingService,
private readonly _formBuilder: FormBuilder,
private readonly _appStateService: AppStateService,
public readonly permissionsService: PermissionsService,
public readonly userPreferenceService: UserPreferenceService
public readonly userPreferenceService: UserPreferenceService,
protected readonly _injector: Injector
) {
this.searchForm = this._formBuilder.group({
query: ['']
});
this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value));
super(_injector);
}
ngOnInit(): void {
this.loadRuleSetsData();
}
@debounce(200)
private _executeSearch(value: { query: string }) {
this.displayedRuleSets = this.ruleSets.filter((pt) => pt.name.toLowerCase().includes(value.query.toLowerCase()));
}
public loadRuleSetsData() {
this._appStateService.reset();
this.ruleSets = this._appStateService.ruleSets;
this.displayedRuleSets = [...this.ruleSets];
this.allEntities = this._appStateService.ruleSets;
this._executeSearch();
this._loadRuleSetStats();
}
private _loadRuleSetStats() {
this.ruleSets.forEach((rs) => {
this.allEntities.forEach((rs) => {
const dictionaries = this._appStateService.dictionaryData[rs.ruleSetId];
if (dictionaries) {
rs.dictionariesCount = Object.keys(dictionaries)
@ -64,45 +50,7 @@ export class RuleSetsListingScreenComponent implements OnInit {
});
}
public get sortingOption(): SortingOption {
return this._sortingService.getSortingOption('rule-sets-listing');
}
public toggleSort($event) {
this._sortingService.toggleSort('rule-sets-listing', $event);
}
toggleTemplateSelected($event: MouseEvent, ruleSet: RuleSetModel) {
$event.stopPropagation();
const idx = this.selectedRuleSetIds.indexOf(ruleSet.ruleSetId);
if (idx === -1) {
this.selectedRuleSetIds.push(ruleSet.ruleSetId);
} else {
this.selectedRuleSetIds.splice(idx, 1);
}
}
public toggleSelectAll() {
if (this.areSomeRuleSetsSelected) {
this.selectedRuleSetIds = [];
} else {
this.selectedRuleSetIds = this.displayedRuleSets.map((rs) => rs.ruleSetId);
}
}
public get areAllRuleSetsSelected() {
return this.displayedRuleSets.length !== 0 && this.selectedRuleSetIds.length === this.displayedRuleSets.length;
}
public get areSomeRuleSetsSelected() {
return this.selectedRuleSetIds.length > 0;
}
public isRuleSetSelected(ruleSet: RuleSetModel) {
return this.selectedRuleSetIds.indexOf(ruleSet.ruleSetId) !== -1;
}
openAddRuleSetDialog() {
public openAddRuleSetDialog() {
this._dialogService.openAddEditRuleSetDialog(null, async (newRuleSet) => {
if (newRuleSet) {
this.loadRuleSetsData();

View File

@ -54,7 +54,7 @@
<div class="table-header" redactionSyncWidth="table-item">
<redaction-table-col-name
(toggleSort)="sortingService.toggleSort('project-listing', $event)"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="project.projectName"

View File

@ -13,7 +13,6 @@ import {
ruleSetChecker
} from '../../../shared/components/filter/utils/filter-utils';
import { TranslateService } from '@ngx-translate/core';
import { SortingOption, SortingService } from '../../../../services/sorting.service';
import { PermissionsService } from '../../../../services/permissions.service';
import { ProjectWrapper } from '../../../../state/model/project.wrapper';
import { Subscription, timer } from 'rxjs';
@ -32,6 +31,9 @@ import { BaseListingComponent } from '../../../shared/base/base-listing.componen
styleUrls: ['./project-listing-screen.component.scss']
})
export class ProjectListingScreenComponent extends BaseListingComponent<ProjectWrapper> implements OnInit, OnDestroy {
protected readonly _searchKey = 'name';
protected readonly _sortKey = 'project-listing';
public projectsChartData: DoughnutChartConfig[] = [];
public documentsChartData: DoughnutChartConfig[] = [];
@ -62,7 +64,6 @@ export class ProjectListingScreenComponent extends BaseListingComponent<ProjectW
private readonly _dialogService: ProjectsDialogService,
private readonly _translateService: TranslateService,
private readonly _router: Router,
public readonly sortingService: SortingService,
public readonly translateChartService: TranslateChartService,
private readonly _fileManagementControllerService: FileManagementControllerService,
protected readonly _injector: Injector
@ -95,10 +96,6 @@ export class ProjectListingScreenComponent extends BaseListingComponent<ProjectW
this.allEntities = this._appStateService.allProjects;
}
protected get _searchKey() {
return 'name';
}
public get noData() {
return this.allEntities.length === 0;
}
@ -132,10 +129,6 @@ export class ProjectListingScreenComponent extends BaseListingComponent<ProjectW
return this.userService.user;
}
public get sortingOption(): SortingOption {
return this.sortingService.getSortingOption('project-listing');
}
public get activeProjectsCount() {
return this.allEntities.filter((p) => p.project.status === Project.StatusEnum.ACTIVE).length;
}

View File

@ -108,6 +108,8 @@
[selectedFileIds]="selectedEntitiesIds"
(reload)="bulkActionPerformed()"
></redaction-project-overview-bulk-actions>
{{ selectedEntitiesIds.length }} selected
</div>
<div class="table-header" redactionSyncWidth="table-item" [class.no-data]="!allEntities.length">

View File

@ -13,7 +13,6 @@ import * as moment from 'moment';
import { ProjectDetailsComponent } from '../../components/project-details/project-details.component';
import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper';
import { annotationFilterChecker, keyChecker, processFilters } from '../../../shared/components/filter/utils/filter-utils';
import { SortingOption, SortingService } from '../../../../services/sorting.service';
import { PermissionsService } from '../../../../services/permissions.service';
import { UserService } from '../../../../services/user.service';
import { FileManagementControllerService, FileStatus } from '@redaction/red-ui-http';
@ -34,6 +33,10 @@ import { ProjectWrapper } from '../../../../state/model/project.wrapper';
styleUrls: ['./project-overview-screen.component.scss']
})
export class ProjectOverviewScreenComponent extends BaseListingComponent<FileStatusWrapper> implements OnInit, OnDestroy {
protected readonly _searchKey = 'filename';
protected readonly _selectionKey = 'fileId';
protected readonly _sortKey = 'project-overview';
public statusFilters: FilterModel[];
public peopleFilters: FilterModel[];
public needsWorkFilters: FilterModel[];
@ -56,7 +59,6 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
constructor(
private readonly _appStateService: AppStateService,
public readonly userService: UserService,
private readonly _sortingService: SortingService,
public readonly permissionsService: PermissionsService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _notificationService: NotificationService,
@ -100,14 +102,6 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
this.filesAutoUpdateTimer.unsubscribe();
}
protected get _searchKey() {
return 'filename';
}
protected get _selectionKey() {
return 'fileId';
}
public get activeProject(): ProjectWrapper {
return this._appStateService.activeProject;
}
@ -144,14 +138,6 @@ export class ProjectOverviewScreenComponent extends BaseListingComponent<FileSta
return [FileStatus.StatusEnum.REPROCESS, FileStatus.StatusEnum.FULLREPROCESS, FileStatus.StatusEnum.PROCESSING].includes(fileStatusWrapper.status);
}
public get sortingOption(): SortingOption {
return this._sortingService.getSortingOption('project-overview');
}
public toggleSort($event) {
this._sortingService.toggleSort('project-overview', $event);
}
protected get filterComponents(): FilterComponent[] {
return [this._statusFilterComponent, this._peopleFilterComponent, this._needsWorkFilterComponent];
}

View File

@ -1,11 +1,14 @@
import { ChangeDetectorRef, Component, Inject, Injector } from '@angular/core';
import { ChangeDetectorRef, Component, Injector } from '@angular/core';
import { FilterModel } from '../components/filter/model/filter.model';
import { getFilteredEntities } from '../components/filter/utils/filter-utils';
import { FilterComponent } from '../components/filter/filter.component';
import { FormBuilder, FormGroup } from '@angular/forms';
import { debounce } from '../../../utils/debounce';
import { ScreenName, SortingOption, SortingService } from '../../../services/sorting.service';
// Filter, search, select
// Functionalities: Filter, search, select, sort
// Usage: overwrite necessary methods/members in your component
@Component({ template: '' })
export class BaseListingComponent<T = any> {
@ -17,37 +20,57 @@ export class BaseListingComponent<T = any> {
protected readonly _formBuilder: FormBuilder;
protected readonly _changeDetectorRef: ChangeDetectorRef;
protected readonly _sortingService: SortingService;
// ----
// Overwrite in child class:
protected get _searchKey(): string {
throw new Error('Not implemented.');
}
protected get _selectionKey(): string {
throw new Error('Not implemented.');
}
protected readonly _searchKey: string;
protected readonly _selectionKey: string;
protected readonly _sortKey: ScreenName;
protected get filters(): { values: FilterModel[]; checker: Function; matchAll?: boolean; checkerArgs?: any }[] {
throw new Error('Not implemented.');
return [];
}
protected preFilter() {
throw new Error('Not implemented.');
return;
}
protected get filterComponents(): FilterComponent[] {
throw new Error('Not implemented.');
return [];
}
// ----
constructor(protected readonly _injector: Injector) {
this._formBuilder = this._injector.get<FormBuilder>(FormBuilder);
this._changeDetectorRef = this._injector.get<ChangeDetectorRef>(ChangeDetectorRef);
this._sortingService = this._injector.get<SortingService>(SortingService);
this._initSearch();
}
private get searchKey(): string {
if (!this._searchKey) {
throw new Error('Not implemented.');
}
return this._searchKey;
}
private get selectionKey(): string {
if (!this._selectionKey) {
throw new Error('Not implemented.');
}
return this._selectionKey;
}
private get sortKey(): ScreenName {
if (!this._sortKey) {
throw new Error('Not implemented.');
}
return this._sortKey;
}
// Search
private _initSearch() {
this.searchForm = this._formBuilder.group({
query: ['']
@ -56,6 +79,16 @@ export class BaseListingComponent<T = any> {
this.searchForm.valueChanges.subscribe(() => this._executeSearch());
}
@debounce(200)
protected _executeSearch() {
this.displayedEntities = (this.filters.length ? this.filteredEntities : this.allEntities).filter((entity) =>
entity[this.searchKey].toLowerCase().includes(this.searchForm.get('query').value.toLowerCase())
);
this.selectedEntitiesIds = this.displayedEntities.map((entity) => entity[this.selectionKey]).filter((id) => this.selectedEntitiesIds.includes(id));
}
// Filter
public filtersChanged(filters?: { [key: string]: FilterModel[] }): void {
if (filters) {
for (const key of Object.keys(filters)) {
@ -67,15 +100,9 @@ export class BaseListingComponent<T = any> {
this._filterEntities();
}
private get _filteredEntities(): T[] {
const filters = this.filters;
return getFilteredEntities(this.allEntities, filters);
}
protected _filterEntities() {
this.preFilter();
this.filteredEntities = this._filteredEntities;
this.selectedEntitiesIds = this.displayedEntities.map((entity) => entity[this._selectionKey]).filter((id) => this.selectedEntitiesIds.includes(id));
this.filteredEntities = getFilteredEntities(this.allEntities, this.filters);
this._executeSearch();
this._changeDetectorRef.detectChanges();
}
@ -92,19 +119,13 @@ export class BaseListingComponent<T = any> {
return this.filterComponents.reduce((prev, component) => prev || component?.hasActiveFilters, false) || this.searchForm.get('query').value;
}
@debounce(200)
protected _executeSearch() {
this.displayedEntities = this.filteredEntities.filter((entity) =>
entity[this._searchKey].toLowerCase().includes(this.searchForm.get('query').value.toLowerCase())
);
}
// Selection
toggleEntitySelected($event: MouseEvent, entity: T) {
console.log({ entity, key: entity[this._selectionKey] });
$event.stopPropagation();
const idx = this.selectedEntitiesIds.indexOf(entity[this._selectionKey]);
const idx = this.selectedEntitiesIds.indexOf(entity[this.selectionKey]);
if (idx === -1) {
this.selectedEntitiesIds.push(entity[this._selectionKey]);
this.selectedEntitiesIds.push(entity[this.selectionKey]);
} else {
this.selectedEntitiesIds.splice(idx, 1);
}
@ -114,7 +135,7 @@ export class BaseListingComponent<T = any> {
if (this.areSomeEntitiesSelected) {
this.selectedEntitiesIds = [];
} else {
this.selectedEntitiesIds = this.displayedEntities.map((entity) => entity[this._selectionKey]);
this.selectedEntitiesIds = this.displayedEntities.map((entity) => entity[this.selectionKey]);
}
}
@ -127,6 +148,16 @@ export class BaseListingComponent<T = any> {
}
public isEntitySelected(entity: T) {
return this.selectedEntitiesIds.indexOf(entity[this._selectionKey]) !== -1;
return this.selectedEntitiesIds.indexOf(entity[this.selectionKey]) !== -1;
}
// Sort
public get sortingOption(): SortingOption {
return this._sortingService.getSortingOption(this.sortKey);
}
public toggleSort($event) {
this._sortingService.toggleSort(this.sortKey, $event);
}
}

View File

@ -5,7 +5,7 @@ export class SortingOption {
column: string;
}
type Screen = 'project-listing' | 'project-overview' | 'dictionary-listing' | 'rule-sets-listing' | 'default-colors' | 'file-attributes-listing';
export type ScreenName = 'project-listing' | 'project-overview' | 'dictionary-listing' | 'rule-sets-listing' | 'default-colors' | 'file-attributes-listing';
@Injectable({
providedIn: 'root'
@ -22,7 +22,7 @@ export class SortingService {
constructor() {}
public toggleSort(screen: Screen, column: string) {
public toggleSort(screen: ScreenName, column: string) {
if (this._options[screen].column === column) {
const currentOrder = this._options[screen].order;
this._options[screen].order = currentOrder === 'asc' ? 'desc' : 'asc';
@ -31,7 +31,7 @@ export class SortingService {
}
}
public getSortingOption(screen: Screen) {
public getSortingOption(screen: ScreenName) {
return this._options[screen];
}
}