From 02b38780f6fd3336df7cf68b1485a8452928c6e5 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 16 Jul 2021 14:43:44 +0300 Subject: [PATCH] add table header component, remove filteredEntities --- .../active-fields-listing.component.html | 2 +- ...ttributes-csv-import-dialog.component.html | 2 +- ...ier-attributes-listing-screen.component.ts | 2 +- ...sier-templates-listing-screen.component.ts | 2 +- ...ile-attributes-listing-screen.component.ts | 2 +- .../screens/trash/trash-screen.component.ts | 4 +- .../user-listing-screen.component.html | 2 +- .../user-listing-screen.component.ts | 2 +- .../dossier-details.component.html | 2 +- .../dossier-listing-screen.component.html | 26 +----- .../dossier-listing-screen.component.ts | 39 ++++++--- .../dossier-overview-screen.component.ts | 14 ++-- .../shared/base/base-listing.component.ts | 14 ++-- .../model/filter-wrapper.model.ts | 2 +- .../popup-filter/utils/filter-utils.ts | 4 +- .../quick-filters.component.html | 2 +- .../page-header/page-header.component.html | 2 +- .../page-header/page-header.component.ts | 6 +- .../simple-doughnut-chart.component.html | 2 +- .../table-col-name.component.ts | 10 +++ .../table-header/table-header.component.html | 22 +++++ .../table-header/table-header.component.scss | 0 .../table-header/table-header.component.ts | 16 ++++ .../shared/directives/sync-width.directive.ts | 22 ++--- .../sort-pipe => pipes}/sort-by.pipe.ts | 2 +- .../modules/shared/services/filter.service.ts | 66 +++++++-------- .../shared/services/screen-state.service.ts | 81 ++++++++++++------- .../modules/shared/services/search.service.ts | 9 +-- .../src/app/modules/shared/shared.module.ts | 7 +- 29 files changed, 213 insertions(+), 153 deletions(-) create mode 100644 apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.html create mode 100644 apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.scss create mode 100644 apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.ts rename apps/red-ui/src/app/modules/shared/{components/sort-pipe => pipes}/sort-by.pipe.ts (83%) diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.html b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.html index e32368ef4..c98448df1 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.html @@ -88,7 +88,7 @@
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html index c3d2ccc74..1b50e47f0 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html @@ -101,7 +101,7 @@ (click)="toggleFieldActive(field)" (mouseenter)="setHoveredColumn(field.csvColumn)" (mouseleave)="setHoveredColumn()" - *ngFor="let field of displayedEntities$ | async; trackBy: trackByPrimaryKey" + *ngFor="let field of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="csv-header-pill-wrapper" >
diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts index 6f441ae13..8a2e518f9 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts @@ -61,7 +61,7 @@ export class DossierAttributesListingScreenComponent extends BaseListingComponen this._loadingService.start(); const attributes = await this._dossierAttributesService.getConfig(); this.screenStateService.setEntities(attributes); - this.filterService.filterEntities(); + this.filterService.applyFilters(); this._loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts index f08902968..2db3ceb3a 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts @@ -54,7 +54,7 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent this._loadingService.start(); this._appStateService.reset(); this.screenStateService.setEntities(this._appStateService.dossierTemplates); - this.filterService.filterEntities(); + this.filterService.applyFilters(); this._loadDossierTemplateStats(); this._loadingService.stop(); } diff --git a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts index 472e56837..be078f557 100644 --- a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts @@ -100,7 +100,7 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent implemen this._loadingService.start(); await this.loadDossierTemplatesData(); - this.filterService.filterEntities(); + this.filterService.applyFilters(); this._loadingService.stop(); } @@ -74,6 +74,6 @@ export class TrashScreenComponent extends BaseListingComponent implemen const entities = this.screenStateService.allEntities.filter(e => !ids.includes(e.dossierId)); this.screenStateService.setEntities(entities); this.screenStateService.setSelectedEntities([]); - this.filterService.filterEntities(); + this.filterService.applyFilters(); } } diff --git a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html index 9ba8b390a..e122bb1b0 100644 --- a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html @@ -76,7 +76,7 @@ -
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts index ce7543fb2..37ca2be04 100644 --- a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts @@ -86,7 +86,7 @@ export class UserListingScreenComponent extends BaseListingComponent imple private async _loadData() { this.screenStateService.setEntities(await this._userControllerService.getAllUsers().toPromise()); await this.userService.loadAllUsers(); - this.filterService.filterEntities(); + this.filterService.applyFilters(); this._computeStats(); this._loadingService.stop(); } diff --git a/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.html b/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.html index 95b943ae7..9a0b8acae 100644 --- a/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.html +++ b/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.html @@ -49,7 +49,7 @@
diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html index 48c30411a..d3d8ca424 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html @@ -8,28 +8,10 @@
-
- - {{ 'dossier-listing.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) } }} - - - -
- -
- - - - - - - -
-
+ event instanceof NavigationStart && event.url !== '/main/dossiers'; @@ -40,9 +41,6 @@ const isLeavingScreen = event => event instanceof NavigationStart && event.url ! export class DossierListingScreenComponent extends BaseListingComponent implements OnInit, OnDestroy, OnAttach, OnDetach { readonly itemSize = 95; protected readonly _primaryKey = 'dossierName'; - - dossiersChartData: DoughnutChartConfig[] = []; - documentsChartData: DoughnutChartConfig[] = []; buttonConfigs: ButtonConfig[] = [ { label: this._translateService.instant('dossier-listing.add-new'), @@ -52,6 +50,27 @@ export class DossierListingScreenComponent extends BaseListingComponent quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false) }); - this.filterService.filterEntities(); + this.filterService.applyFilters(); } private _createQuickFilters() { diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.ts index 82e737991..980d64688 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.ts @@ -86,11 +86,11 @@ export class DossierOverviewScreenComponent } get checkedRequiredFilters() { - return this.filterService.getFilter('quickFilters')?.values.filter(f => f.required && f.checked); + return this.filterService.getFilterGroup('quickFilters')?.values.filter(f => f.required && f.checked); } get checkedNotRequiredFilters() { - return this.filterService.getFilter('quickFilters')?.values.filter(f => !f.required && f.checked); + return this.filterService.getFilterGroup('quickFilters')?.values.filter(f => !f.required && f.checked); } isLastOpenedFile({ fileId }: FileStatusWrapper): boolean { @@ -163,7 +163,7 @@ export class DossierOverviewScreenComponent this._loadEntitiesFromState(); this._computeAllFilters(); - this.filterService.filterEntities(); + this.filterService.applyFilters(); this._dossierDetailsComponent?.calculateChartConfig(); this._changeDetectorRef.detectChanges(); @@ -263,7 +263,7 @@ export class DossierOverviewScreenComponent label: this._translateService.instant(item) })); - this.filterService.addFilter({ + this.filterService.addFilterGroup({ slug: 'statusFilters', label: this._translateService.instant('filters.status'), icon: 'red:status', @@ -286,7 +286,7 @@ export class DossierOverviewScreenComponent label: this._userService.getNameForId(userId) }); }); - this.filterService.addFilter({ + this.filterService.addFilterGroup({ slug: 'peopleFilters', label: this._translateService.instant('filters.assigned-people'), icon: 'red:user', @@ -299,7 +299,7 @@ export class DossierOverviewScreenComponent label: this._translateService.instant('filter.' + item) })); - this.filterService.addFilter({ + this.filterService.addFilterGroup({ slug: 'needsWorkFilters', label: this._translateService.instant('filters.needs-work'), icon: 'red:needs-work', @@ -310,7 +310,7 @@ export class DossierOverviewScreenComponent checkerArgs: this.permissionsService }); - this.filterService.addFilter({ + this.filterService.addFilterGroup({ slug: 'quickFilters', values: this._createQuickFilters(), checker: (file: FileStatusWrapper) => diff --git a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts index 5fef97a9b..ee31643b0 100644 --- a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts +++ b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts @@ -6,7 +6,7 @@ import { SearchService } from '../services/search.service'; import { ScreenStateService } from '../services/screen-state.service'; import { combineLatest, Observable } from 'rxjs'; import { AutoUnsubscribeComponent } from './auto-unsubscribe.component'; -import { map } from 'rxjs/operators'; +import { distinctUntilChanged, map } from 'rxjs/operators'; import { PermissionsService } from '../../../services/permissions.service'; @Component({ template: '' }) @@ -50,10 +50,6 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i super.ngOnDestroy(); } - get displayedEntities$(): Observable { - return this.screenStateService.displayedEntities$; - } - get sortedDisplayedEntities$(): Observable { return this.screenStateService.displayedEntities$.pipe(map(entities => this.sortingService.defaultSort(entities))); } @@ -64,13 +60,15 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i get noMatch$(): Observable { return combineLatest([this.screenStateService.allEntitiesLength$, this.screenStateService.displayedLength$]).pipe( - map(([hasEntities, hasDisplayedEntities]) => hasEntities && !hasDisplayedEntities) + map(([hasEntities, hasDisplayedEntities]) => hasEntities && !hasDisplayedEntities), + distinctUntilChanged() ); } - canBulkDelete$(hasPermission = true) { + canBulkDelete$(hasPermission = true): Observable { return this.screenStateService.areSomeEntitiesSelected$.pipe( - map(areSomeEntitiesSelected => areSomeEntitiesSelected && hasPermission) + map(areSomeEntitiesSelected => areSomeEntitiesSelected && hasPermission), + distinctUntilChanged() ); } diff --git a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/model/filter-wrapper.model.ts b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/model/filter-wrapper.model.ts index 1b19edc1d..ccdb3e6a8 100644 --- a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/model/filter-wrapper.model.ts +++ b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/model/filter-wrapper.model.ts @@ -1,7 +1,7 @@ import { FilterModel } from './filter.model'; import { TemplateRef } from '@angular/core'; -export interface FilterWrapper { +export interface FilterGroup { slug: string; label?: string; icon?: string; diff --git a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/utils/filter-utils.ts b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/utils/filter-utils.ts index 753c65f68..720d31006 100644 --- a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/utils/filter-utils.ts +++ b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/utils/filter-utils.ts @@ -2,7 +2,7 @@ import { FilterModel } from '../model/filter.model'; import { FileStatusWrapper } from '@models/file/file-status.wrapper'; import { DossierWrapper } from '@state/model/dossier.wrapper'; import { PermissionsService } from '@services/permissions.service'; -import { FilterWrapper } from '@shared/components/filters/popup-filter/model/filter-wrapper.model'; +import { FilterGroup } from '@shared/components/filters/popup-filter/model/filter-wrapper.model'; export function processFilters(oldFilters: FilterModel[], newFilters: FilterModel[]) { copySettings(oldFilters, newFilters); @@ -159,7 +159,7 @@ export const addedDateChecker = (dw: DossierWrapper, filter: FilterModel) => dw. export const dossierApproverChecker = (dw: DossierWrapper, filter: FilterModel) => dw.approverIds.includes(filter.key); -export function getFilteredEntities(entities: T[], filters: FilterWrapper[]) { +export function getFilteredEntities(entities: T[], filters: FilterGroup[]) { const filteredEntities: T[] = []; for (const entity of entities) { let add = true; diff --git a/apps/red-ui/src/app/modules/shared/components/filters/quick-filters/quick-filters.component.html b/apps/red-ui/src/app/modules/shared/components/filters/quick-filters/quick-filters.component.html index 59f0d5177..a60ebcca1 100644 --- a/apps/red-ui/src/app/modules/shared/components/filters/quick-filters/quick-filters.component.html +++ b/apps/red-ui/src/app/modules/shared/components/filters/quick-filters/quick-filters.component.html @@ -1,6 +1,6 @@
diff --git a/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.html b/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.html index 38eb57cda..ffa5724af 100644 --- a/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.html +++ b/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.html @@ -6,7 +6,7 @@ { constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService) {} - get filters$(): Observable { - return this.filterService?.allFilters$.pipe(map(all => all.filter(f => f.icon))); + get filters$(): Observable { + return this.filterService?.filterGroups$.pipe(map(all => all.filter(f => f.icon))); } get showResetFilters$(): Observable { diff --git a/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html b/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html index 43f9c4c6c..94a662ac8 100644 --- a/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html +++ b/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html @@ -34,7 +34,7 @@ (click)="selectValue(val.key)" *ngFor="let val of config" [class.active]="filterService.filterChecked$('statusFilters', val.key) | async" - [class.filter-disabled]="(filterService.getFilter$('statusFilters') | async)?.length === 0" + [class.filter-disabled]="(filterService.getFilterModels$('statusFilters') | async)?.length === 0" > + + {{ tableHeaderLabel | translate: { length: (screenStateService.displayedLength$ | async) } }} + + + +
+ +
+ + +
+
diff --git a/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.scss b/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.ts b/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.ts new file mode 100644 index 000000000..e1b8b7b32 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.ts @@ -0,0 +1,16 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { TableColConfig } from '@shared/components/table-col-name/table-col-name.component'; +import { ScreenStateService } from '@shared/services/screen-state.service'; + +@Component({ + selector: 'redaction-table-header', + templateUrl: './table-header.component.html', + styleUrls: ['./table-header.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TableHeaderComponent { + @Input() tableHeaderLabel: string; + @Input() tableColConfigs: TableColConfig[]; + + constructor(readonly screenStateService: ScreenStateService) {} +} diff --git a/apps/red-ui/src/app/modules/shared/directives/sync-width.directive.ts b/apps/red-ui/src/app/modules/shared/directives/sync-width.directive.ts index 0f1b5f46b..8a2398c4d 100644 --- a/apps/red-ui/src/app/modules/shared/directives/sync-width.directive.ts +++ b/apps/red-ui/src/app/modules/shared/directives/sync-width.directive.ts @@ -1,11 +1,4 @@ -import { - AfterViewInit, - Directive, - ElementRef, - HostListener, - Input, - OnDestroy -} from '@angular/core'; +import { AfterViewInit, Directive, ElementRef, HostListener, Input, OnDestroy } from '@angular/core'; import { debounce } from '@utils/debounce'; @Directive({ @@ -32,9 +25,8 @@ export class SyncWidthDirective implements AfterViewInit, OnDestroy { @debounce(10) matchWidth() { const headerItems = this._elementRef.nativeElement.children; - const tableRows = this._elementRef.nativeElement.parentElement.getElementsByClassName( - this.redactionSyncWidth - ); + // const tableRows = document.getElementsByClassName(this.redactionSyncWidth); + const tableRows = this._elementRef.nativeElement.parentElement.getElementsByClassName(this.redactionSyncWidth); if (!tableRows || !tableRows.length) { return; @@ -48,12 +40,8 @@ export class SyncWidthDirective implements AfterViewInit, OnDestroy { for (let idx = 0; idx < length - hasExtraColumns - 1; ++idx) { if (headerItems[idx]) { - headerItems[idx].style.width = `${ - tableRow.children[idx].getBoundingClientRect().width - }px`; - headerItems[idx].style.minWidth = `${ - tableRow.children[idx].getBoundingClientRect().width - }px`; + headerItems[idx].style.width = `${tableRow.children[idx].getBoundingClientRect().width}px`; + headerItems[idx].style.minWidth = `${tableRow.children[idx].getBoundingClientRect().width}px`; } } diff --git a/apps/red-ui/src/app/modules/shared/components/sort-pipe/sort-by.pipe.ts b/apps/red-ui/src/app/modules/shared/pipes/sort-by.pipe.ts similarity index 83% rename from apps/red-ui/src/app/modules/shared/components/sort-pipe/sort-by.pipe.ts rename to apps/red-ui/src/app/modules/shared/pipes/sort-by.pipe.ts index bb2ade371..f9275b187 100644 --- a/apps/red-ui/src/app/modules/shared/components/sort-pipe/sort-by.pipe.ts +++ b/apps/red-ui/src/app/modules/shared/pipes/sort-by.pipe.ts @@ -1,5 +1,5 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { SortingService } from '@services/sorting.service'; +import { SortingService } from '../../../services/sorting.service'; @Pipe({ name: 'sortBy' }) export class SortByPipe implements PipeTransform { diff --git a/apps/red-ui/src/app/modules/shared/services/filter.service.ts b/apps/red-ui/src/app/modules/shared/services/filter.service.ts index 169e26655..b9f2f8ac2 100644 --- a/apps/red-ui/src/app/modules/shared/services/filter.service.ts +++ b/apps/red-ui/src/app/modules/shared/services/filter.service.ts @@ -1,7 +1,7 @@ import { ChangeDetectorRef, Injectable } from '@angular/core'; import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; import { getFilteredEntities, processFilters } from '@shared/components/filters/popup-filter/utils/filter-utils'; -import { FilterWrapper } from '@shared/components/filters/popup-filter/model/filter-wrapper.model'; +import { FilterGroup } from '@shared/components/filters/popup-filter/model/filter-wrapper.model'; import { ScreenStateService } from '@shared/services/screen-state.service'; import { SearchService } from '@shared/services/search.service'; import { BehaviorSubject, Observable } from 'rxjs'; @@ -9,7 +9,7 @@ import { distinctUntilChanged, filter, map } from 'rxjs/operators'; @Injectable() export class FilterService { - _allFilters$ = new BehaviorSubject([]); + _filterGroups$ = new BehaviorSubject([]); constructor( private readonly _screenStateService: ScreenStateService, @@ -17,70 +17,72 @@ export class FilterService { private readonly _changeDetector: ChangeDetectorRef ) {} - get filters() { - return Object.values(this._allFilters$.getValue()); + get filterGroups(): FilterGroup[] { + return Object.values(this._filterGroups$.getValue()); } - get showResetFilters$() { - return this.allFilters$.pipe( + get showResetFilters$(): Observable { + return this.filterGroups$.pipe( map(all => this._toFlatFilters(all)), map(f => !!f.find(el => el.checked)), distinctUntilChanged() ); } - filterChecked$(slug: string, key: string) { - const filters = this.getFilter$(slug); - return filters.pipe(map(all => all.find(f => f.key === key)?.checked)); + filterChecked$(slug: string, key: string): Observable { + return this.getFilterModels$(slug).pipe( + map(all => all.find(f => f.key === key)?.checked), + distinctUntilChanged() + ); } toggleFilter(slug: string, key: string) { - const filters = this.filters.find(f => f.slug === slug); + const filters = this.filterGroups.find(f => f.slug === slug); let found = filters.values.find(f => f.key === key); if (!found) found = filters.values.map(f => f.filters?.find(ff => ff.key === key))[0]; found.checked = !found.checked; - this._allFilters$.next(this.filters); - this.filterEntities(); + this._filterGroups$.next(this.filterGroups); + this.applyFilters(); } - filterEntities(): void { - const filtered = getFilteredEntities(this._screenStateService.allEntities, this.filters); - this._screenStateService.setFilteredEntities(filtered); + applyFilters(): void { + const filtered = getFilteredEntities(this._screenStateService.allEntities, this.filterGroups); + this._screenStateService.setDisplayedEntities(filtered); this._searchService.executeSearchImmediately(); this._changeDetector.detectChanges(); } - addFilter(value: FilterWrapper): void { - const oldFilters = this.getFilter(value.slug)?.values; - if (!oldFilters) return this._allFilters$.next([...this.filters, value]); + addFilterGroup(value: FilterGroup): void { + const oldFilters = this.getFilterGroup(value.slug)?.values; + if (!oldFilters) return this._filterGroups$.next([...this.filterGroups, value]); value.values = processFilters(oldFilters, value.values); - this._allFilters$.next([...this.filters.filter(f => f.slug !== value.slug), value]); + this._filterGroups$.next([...this.filterGroups.filter(f => f.slug !== value.slug), value]); } - getFilter(slug: string): FilterWrapper { - return this.filters.find(f => f?.slug === slug); + getFilterGroup(slug: string): FilterGroup { + return this.filterGroups.find(f => f?.slug === slug); } - getFilter$(slug: string): Observable { - return this.getFilterWrapper$(slug).pipe( + getFilterModels$(filterGroupSlug: string): Observable { + return this.getFilterGroup$(filterGroupSlug).pipe( filter(f => f !== null && f !== undefined), - map(f => f?.values) + map(f => f.values) ); } - getFilterWrapper$(slug: string): Observable { - return this.allFilters$.pipe(map(all => all.find(f => f?.slug === slug))); + getFilterGroup$(slug: string): Observable { + return this.filterGroups$.pipe(map(all => all.find(f => f?.slug === slug))); } - get allFilters$(): Observable { - return this._allFilters$.asObservable(); + get filterGroups$(): Observable { + return this._filterGroups$.asObservable(); } reset(): void { - this.filters.forEach(item => { + this.filterGroups.forEach(item => { item.values.forEach(child => { child.checked = false; child.indeterminate = false; @@ -90,11 +92,11 @@ export class FilterService { }); }); }); - this._allFilters$.next(this.filters); - this.filterEntities(); + this._filterGroups$.next(this.filterGroups); + this.applyFilters(); } - private _toFlatFilters(entities: FilterWrapper[]): FilterModel[] { + private _toFlatFilters(entities: FilterGroup[]): FilterModel[] { const flatChildren = (filters: FilterModel[]) => (filters ?? []).reduce((acc, f) => [...acc, ...(f?.filters ?? [])], []); return entities.reduce((acc, f) => [...acc, ...f.values, ...flatChildren(f.values)], []); diff --git a/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts b/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts index 80cc99002..d53fd4e83 100644 --- a/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts +++ b/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts @@ -4,78 +4,102 @@ import { distinctUntilChanged, map } from 'rxjs/operators'; @Injectable() export class ScreenStateService { - allEntities$ = new BehaviorSubject([]); - filteredEntities$ = new BehaviorSubject([]); - displayedEntities$ = new BehaviorSubject([]); - selectedEntities$ = new BehaviorSubject([]); + _allEntities$ = new BehaviorSubject([]); + _displayedEntities$ = new BehaviorSubject([]); + _selectedEntities$ = new BehaviorSubject([]); + + // constructor() { + // setInterval(() => { + // console.log('All entities subs: ', this._allEntities$.observers); + // console.log('Displayed entities subs: ', this._displayedEntities$.observers); + // console.log('Selected entities subs: ', this._selectedEntities$.observers); + // }, 10000); + // } get allEntities(): T[] { - return Object.values(this.allEntities$.getValue()); - } - - get filteredEntities(): T[] { - return Object.values(this.filteredEntities$.getValue()); + return Object.values(this._allEntities$.getValue()); } get selectedEntities(): T[] { - return Object.values(this.selectedEntities$.getValue()); + return Object.values(this._selectedEntities$.getValue()); } get displayedEntities(): T[] { - return Object.values(this.displayedEntities$.getValue()); + return Object.values(this._displayedEntities$.getValue()); + } + + get allEntities$(): Observable { + return this._allEntities$.asObservable(); + } + + get selectedEntities$(): Observable { + return this._selectedEntities$.asObservable(); + } + + get displayedEntities$(): Observable { + return this._displayedEntities$.asObservable(); } map(func: (state: T[]) => K): Observable { - return this.allEntities$.asObservable().pipe( + return this.allEntities$.pipe( map((state: T[]) => func(state)), distinctUntilChanged() ); } setEntities(newEntities: Partial): void { - this.allEntities$.next(newEntities); - } - - setFilteredEntities(newEntities: Partial): void { - this.filteredEntities$.next(newEntities); + this._allEntities$.next(newEntities); } setSelectedEntities(newEntities: Partial): void { - this.selectedEntities$.next(newEntities); + this._selectedEntities$.next(newEntities); } setDisplayedEntities(newEntities: Partial): void { - this.displayedEntities$.next(newEntities); + this._displayedEntities$.next(newEntities); } get noData$(): Observable { - return this.allEntitiesLength$.pipe(map(length => length === 0)); + return this.allEntitiesLength$.pipe( + map(length => length === 0), + distinctUntilChanged() + ); } /** * Returns the length of all entities */ get allEntitiesLength$(): Observable { - return this.allEntities$.pipe(map(all => all?.length ?? 0)); + return this.allEntities$.pipe( + map(all => all?.length ?? 0), + distinctUntilChanged() + ); } /** * Returns the length of the currently displayed entities */ get displayedLength$(): Observable { - return this.displayedEntities$.pipe(map(all => all?.length ?? 0)); + return this.displayedEntities$.pipe( + map(all => all?.length ?? 0), + distinctUntilChanged() + ); } /** * Returns the length of the selected entities */ get selectedLength$(): Observable { - return this.selectedEntities$.pipe(map(all => all?.length ?? 0)); + return this.selectedEntities$.pipe( + map(all => all?.length ?? 0), + distinctUntilChanged() + ); } get areAllEntitiesSelected$(): Observable { return combineLatest([this.displayedLength$, this.selectedLength$]).pipe( - map(([displayedLength, selectedLength]) => displayedLength && displayedLength === selectedLength) + map(([displayedLength, selectedLength]) => displayedLength && displayedLength === selectedLength), + distinctUntilChanged() ); } @@ -83,7 +107,10 @@ export class ScreenStateService { * Indicates that some entities are selected. If all are selected this returns true */ get areSomeEntitiesSelected$(): Observable { - return this.selectedLength$.pipe(map(value => value > 0)); + return this.selectedLength$.pipe( + map(value => value > 0), + distinctUntilChanged() + ); } /** @@ -91,7 +118,8 @@ export class ScreenStateService { */ get notAllEntitiesSelected$(): Observable { return combineLatest([this.areAllEntitiesSelected$, this.areSomeEntitiesSelected$]).pipe( - map(([allEntitiesAreSelected, someEntitiesAreSelected]) => !allEntitiesAreSelected && someEntitiesAreSelected) + map(([allEntitiesAreSelected, someEntitiesAreSelected]) => !allEntitiesAreSelected && someEntitiesAreSelected), + distinctUntilChanged() ); } @@ -114,7 +142,6 @@ export class ScreenStateService { logCurrentState(): void { console.log('Entities', this.allEntities); console.log('Displayed', this.displayedEntities); - console.log('Filtered', this.filteredEntities); console.log('Selected', this.selectedEntities); } diff --git a/apps/red-ui/src/app/modules/shared/services/search.service.ts b/apps/red-ui/src/app/modules/shared/services/search.service.ts index 78fbe8a0d..d3ef32824 100644 --- a/apps/red-ui/src/app/modules/shared/services/search.service.ts +++ b/apps/red-ui/src/app/modules/shared/services/search.service.ts @@ -29,14 +29,9 @@ export class SearchService { } executeSearchImmediately(): void { - const displayed = this._screenStateService.filteredEntities.length - ? this._screenStateService.filteredEntities - : this._screenStateService.allEntities; - - if (!this._searchKey) { - return this._screenStateService.setDisplayedEntities(displayed); - } + if (!this._searchKey) return; + const displayed = this._screenStateService.displayedEntities; this._screenStateService.setDisplayedEntities( displayed.filter(entity => this._searchField(entity).toLowerCase().includes(this._searchValue)) ); diff --git a/apps/red-ui/src/app/modules/shared/shared.module.ts b/apps/red-ui/src/app/modules/shared/shared.module.ts index d9f61970b..87a37d79e 100644 --- a/apps/red-ui/src/app/modules/shared/shared.module.ts +++ b/apps/red-ui/src/app/modules/shared/shared.module.ts @@ -24,7 +24,7 @@ import { DictionaryAnnotationIconComponent } from './components/dictionary-annot import { HiddenActionComponent } from './components/hidden-action/hidden-action.component'; import { ConfirmationDialogComponent } from './dialogs/confirmation-dialog/confirmation-dialog.component'; import { EmptyStateComponent } from './components/empty-state/empty-state.component'; -import { SortByPipe } from './components/sort-pipe/sort-by.pipe'; +import { SortByPipe } from './pipes/sort-by.pipe'; import { RoundCheckboxComponent } from './components/checkbox/round-checkbox.component'; import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core'; import { MomentDateAdapter } from '@angular/material-moment-adapter'; @@ -39,6 +39,7 @@ import { AssignUserDropdownComponent } from './components/assign-user-dropdown/a import { InputWithActionComponent } from '@shared/components/input-with-action/input-with-action.component'; import { PageHeaderComponent } from './components/page-header/page-header.component'; import { DatePipe } from '@shared/pipes/date.pipe'; +import { TableHeaderComponent } from './components/table-header/table-header.component'; const buttons = [ChevronButtonComponent, CircleButtonComponent, FileDownloadBtnComponent, IconButtonComponent, UserButtonComponent]; @@ -73,9 +74,9 @@ const utils = [HumanizePipe, DatePipe, SyncWidthDirective, HasScrollbarDirective const modules = [MatConfigModule, TranslateModule, ScrollingModule, IconsModule, FormsModule, ReactiveFormsModule]; @NgModule({ - declarations: [...components, ...utils], + declarations: [...components, ...utils, TableHeaderComponent], imports: [CommonModule, ...modules, MonacoEditorModule], - exports: [...modules, ...components, ...utils], + exports: [...modules, ...components, ...utils, TableHeaderComponent], providers: [ { provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] }, {