add table header component, remove filteredEntities

This commit is contained in:
Dan Percic 2021-07-16 14:43:44 +03:00
parent 27de88ac57
commit 02b38780f6
29 changed files with 213 additions and 153 deletions

View File

@ -88,7 +88,7 @@
<div
(mouseenter)="setHoveredColumn.emit(field.csvColumn)"
(mouseleave)="setHoveredColumn.emit()"
*cdkVirtualFor="let field of displayedEntities$ | async; trackBy: trackByPrimaryKey"
*cdkVirtualFor="let field of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
class="table-item"
>
<div (click)="toggleEntitySelected($event, field)" class="selection-column">

View File

@ -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"
>
<div [class.selected]="isActive(field)" class="csv-header-pill">

View File

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

View File

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

View File

@ -100,7 +100,7 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
this.screenStateService.setEntities(response?.fileAttributeConfigs || []);
} catch (e) {
} finally {
this.filterService.filterEntities();
this.filterService.applyFilters();
this._loadingService.stop();
}
}

View File

@ -39,7 +39,7 @@ export class TrashScreenComponent extends BaseListingComponent<Dossier> 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<Dossier> 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();
}
}

View File

@ -76,7 +76,7 @@
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines -->
<div *cdkVirtualFor="let user of displayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
<div *cdkVirtualFor="let user of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
<div (click)="toggleEntitySelected($event, user)" class="selection-column">
<redaction-round-checkbox [active]="isSelected(user)"></redaction-round-checkbox>
</div>

View File

@ -86,7 +86,7 @@ export class UserListingScreenComponent extends BaseListingComponent<User> 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();
}

View File

@ -49,7 +49,7 @@
<div *ngIf="hasFiles" class="mt-24 legend pb-32">
<div
(click)="filterService.toggleFilter('needsWorkFilters', filter.key)"
*ngFor="let filter of filterService.getFilter$('needsWorkFilters') | async"
*ngFor="let filter of filterService.getFilterModels$('needsWorkFilters') | async"
[class.active]="filterService.filterChecked$('needsWorkFilters', filter.key) | async"
>
<redaction-type-filter [filter]="filter"></redaction-type-filter>

View File

@ -8,28 +8,10 @@
<div class="red-content-inner">
<div class="content-container">
<div class="header-item">
<span class="all-caps-label">
{{ 'dossier-listing.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) } }}
</span>
<redaction-quick-filters></redaction-quick-filters>
</div>
<div class="table-header" redactionSyncWidth="table-item">
<redaction-table-col-name
[withSort]="true"
column="dossierName"
label="dossier-listing.table-col-names.name"
></redaction-table-col-name>
<redaction-table-col-name label="dossier-listing.table-col-names.needs-work"></redaction-table-col-name>
<redaction-table-col-name class="user-column" label="dossier-listing.table-col-names.owner"></redaction-table-col-name>
<redaction-table-col-name class="flex-end" label="dossier-listing.table-col-names.status"></redaction-table-col-name>
<div class="scrollbar-placeholder"></div>
</div>
<redaction-table-header
[tableHeaderLabel]="'dossier-listing.table-header.title'"
[tableColConfigs]="tableColConfigs"
></redaction-table-header>
<redaction-empty-state
(action)="openAddDossierDialog()"

View File

@ -29,6 +29,7 @@ import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { SortingService } from '@services/sorting.service';
import { TableColConfig } from '../../../shared/components/table-col-name/table-col-name.component';
const isLeavingScreen = event => event instanceof NavigationStart && event.url !== '/main/dossiers';
@ -40,9 +41,6 @@ const isLeavingScreen = event => event instanceof NavigationStart && event.url !
export class DossierListingScreenComponent extends BaseListingComponent<DossierWrapper> 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<DossierW
type: 'primary'
}
];
tableColConfigs: TableColConfig[] = [
{
label: 'dossier-listing.table-col-names.name',
withSort: true,
column: 'dossierName'
},
{
label: 'dossier-listing.table-col-names.needs-work'
},
{
label: 'dossier-listing.table-col-names.owner',
class: 'user-column'
},
{
label: 'dossier-listing.table-col-names.status',
class: 'flex-end'
}
];
dossiersChartData: DoughnutChartConfig[] = [];
documentsChartData: DoughnutChartConfig[] = [];
private _lastScrollPosition: number;
@ -188,7 +207,7 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
label: this._translateService.instant(status)
}));
this.filterService.addFilter({
this.filterService.addFilterGroup({
slug: 'statusFilters',
label: this._translateService.instant('filters.status'),
icon: 'red:status',
@ -201,7 +220,7 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
label: this._userService.getNameForId(userId)
}));
this.filterService.addFilter({
this.filterService.addFilterGroup({
slug: 'peopleFilters',
label: this._translateService.instant('filters.people'),
icon: 'red:user',
@ -214,7 +233,7 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
label: `filter.${type}`
}));
this.filterService.addFilter({
this.filterService.addFilterGroup({
slug: 'needsWorkFilters',
label: this._translateService.instant('filters.needs-work'),
icon: 'red:needs-work',
@ -230,23 +249,23 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
label: this._appStateService.getDossierTemplateById(id).name
}));
this.filterService.addFilter({
this.filterService.addFilterGroup({
slug: 'dossierTemplateFilters',
label: this._translateService.instant('filters.dossier-templates'),
icon: 'red:template',
hide: this.filterService.getFilter('dossierTemplateFilters')?.values?.length <= 1,
hide: this.filterService.getFilterGroup('dossierTemplateFilters')?.values?.length <= 1,
values: dossierTemplateFilters,
checker: dossierTemplateChecker
});
const quickFilters = this._createQuickFilters();
this.filterService.addFilter({
this.filterService.addFilterGroup({
slug: 'quickFilters',
values: quickFilters,
checker: (dw: DossierWrapper) => quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false)
});
this.filterService.filterEntities();
this.filterService.applyFilters();
}
private _createQuickFilters() {

View File

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

View File

@ -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<T> extends AutoUnsubscribeComponent i
super.ngOnDestroy();
}
get displayedEntities$(): Observable<T[]> {
return this.screenStateService.displayedEntities$;
}
get sortedDisplayedEntities$(): Observable<T[]> {
return this.screenStateService.displayedEntities$.pipe(map(entities => this.sortingService.defaultSort(entities)));
}
@ -64,13 +60,15 @@ export abstract class BaseListingComponent<T> extends AutoUnsubscribeComponent i
get noMatch$(): Observable<boolean> {
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<boolean> {
return this.screenStateService.areSomeEntitiesSelected$.pipe(
map(areSomeEntitiesSelected => areSomeEntitiesSelected && hasPermission)
map(areSomeEntitiesSelected => areSomeEntitiesSelected && hasPermission),
distinctUntilChanged()
);
}

View File

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

View File

@ -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<T>(entities: T[], filters: FilterWrapper[]) {
export function getFilteredEntities<T>(entities: T[], filters: FilterGroup[]) {
const filteredEntities: T[] = [];
for (const entity of entities) {
let add = true;

View File

@ -1,6 +1,6 @@
<div
(click)="filterService.toggleFilter('quickFilters', filter.key)"
*ngFor="let filter of filterService.getFilter$('quickFilters') | async"
*ngFor="let filter of filterService.getFilterModels$('quickFilters') | async"
[class.active]="filter.checked"
class="quick-filter"
>

View File

@ -6,7 +6,7 @@
<ng-container *ngFor="let config of filters; trackBy: trackByLabel">
<redaction-popup-filter
(filtersChanged)="filterService.filterEntities()"
(filtersChanged)="filterService.applyFilters()"
*ngIf="!config.hide"
[filterLabel]="config.label"
[icon]="config.icon"

View File

@ -5,7 +5,7 @@ import { FilterService } from '@shared/services/filter.service';
import { SearchService } from '@shared/services/search.service';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { combineLatest, Observable } from 'rxjs';
import { FilterWrapper } from '@shared/components/filters/popup-filter/model/filter-wrapper.model';
import { FilterGroup } from '@shared/components/filters/popup-filter/model/filter-wrapper.model';
@Component({
selector: 'redaction-page-header',
@ -21,8 +21,8 @@ export class PageHeaderComponent<T> {
constructor(@Optional() readonly filterService: FilterService<T>, @Optional() readonly searchService: SearchService<T>) {}
get filters$(): Observable<FilterWrapper[]> {
return this.filterService?.allFilters$.pipe(map(all => all.filter(f => f.icon)));
get filters$(): Observable<FilterGroup[]> {
return this.filterService?.filterGroups$.pipe(map(all => all.filter(f => f.icon)));
}
get showResetFilters$(): Observable<boolean> {

View File

@ -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"
>
<redaction-status-bar
[config]="[

View File

@ -1,6 +1,16 @@
import { Component, Input, Optional } from '@angular/core';
import { SortingService } from '@services/sorting.service';
export interface TableColConfig {
readonly column?: string;
readonly label: string;
readonly withSort?: boolean;
readonly class?: string;
readonly leftIcon?: string;
readonly rightIcon?: string;
readonly rightIconTooltip?: string;
}
@Component({
selector: 'redaction-table-col-name',
templateUrl: './table-col-name.component.html',

View File

@ -0,0 +1,22 @@
<div class="header-item">
<span class="all-caps-label">
{{ tableHeaderLabel | translate: { length: (screenStateService.displayedLength$ | async) } }}
</span>
<redaction-quick-filters></redaction-quick-filters>
</div>
<div class="table-header" redactionSyncWidth="table-item">
<redaction-table-col-name
*ngFor="let config of tableColConfigs"
[withSort]="config.withSort"
[column]="config.column"
[label]="config.label"
[class]="config.class"
[leftIcon]="config.leftIcon"
[rightIcon]="config.rightIcon"
[rightIconTooltip]="config.rightIconTooltip"
></redaction-table-col-name>
<div class="scrollbar-placeholder"></div>
</div>

View File

@ -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<T> {
@Input() tableHeaderLabel: string;
@Input() tableColConfigs: TableColConfig[];
constructor(readonly screenStateService: ScreenStateService<T>) {}
}

View File

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

View File

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

View File

@ -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<T> {
_allFilters$ = new BehaviorSubject<FilterWrapper[]>([]);
_filterGroups$ = new BehaviorSubject<FilterGroup[]>([]);
constructor(
private readonly _screenStateService: ScreenStateService<T>,
@ -17,70 +17,72 @@ export class FilterService<T> {
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<boolean> {
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<boolean> {
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<FilterModel[]> {
return this.getFilterWrapper$(slug).pipe(
getFilterModels$(filterGroupSlug: string): Observable<FilterModel[]> {
return this.getFilterGroup$(filterGroupSlug).pipe(
filter(f => f !== null && f !== undefined),
map(f => f?.values)
map(f => f.values)
);
}
getFilterWrapper$(slug: string): Observable<FilterWrapper> {
return this.allFilters$.pipe(map(all => all.find(f => f?.slug === slug)));
getFilterGroup$(slug: string): Observable<FilterGroup> {
return this.filterGroups$.pipe(map(all => all.find(f => f?.slug === slug)));
}
get allFilters$(): Observable<FilterWrapper[]> {
return this._allFilters$.asObservable();
get filterGroups$(): Observable<FilterGroup[]> {
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<T> {
});
});
});
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)], []);

View File

@ -4,78 +4,102 @@ import { distinctUntilChanged, map } from 'rxjs/operators';
@Injectable()
export class ScreenStateService<T> {
allEntities$ = new BehaviorSubject<T[]>([]);
filteredEntities$ = new BehaviorSubject<T[]>([]);
displayedEntities$ = new BehaviorSubject<T[]>([]);
selectedEntities$ = new BehaviorSubject<T[]>([]);
_allEntities$ = new BehaviorSubject<T[]>([]);
_displayedEntities$ = new BehaviorSubject<T[]>([]);
_selectedEntities$ = new BehaviorSubject<T[]>([]);
// 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<T[]> {
return this._allEntities$.asObservable();
}
get selectedEntities$(): Observable<T[]> {
return this._selectedEntities$.asObservable();
}
get displayedEntities$(): Observable<T[]> {
return this._displayedEntities$.asObservable();
}
map<K>(func: (state: T[]) => K): Observable<K> {
return this.allEntities$.asObservable().pipe(
return this.allEntities$.pipe(
map((state: T[]) => func(state)),
distinctUntilChanged()
);
}
setEntities(newEntities: Partial<T[]>): void {
this.allEntities$.next(newEntities);
}
setFilteredEntities(newEntities: Partial<T[]>): void {
this.filteredEntities$.next(newEntities);
this._allEntities$.next(newEntities);
}
setSelectedEntities(newEntities: Partial<T[]>): void {
this.selectedEntities$.next(newEntities);
this._selectedEntities$.next(newEntities);
}
setDisplayedEntities(newEntities: Partial<T[]>): void {
this.displayedEntities$.next(newEntities);
this._displayedEntities$.next(newEntities);
}
get noData$(): Observable<boolean> {
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<number> {
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<number> {
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<number> {
return this.selectedEntities$.pipe(map(all => all?.length ?? 0));
return this.selectedEntities$.pipe(
map(all => all?.length ?? 0),
distinctUntilChanged()
);
}
get areAllEntitiesSelected$(): Observable<boolean> {
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<T> {
* Indicates that some entities are selected. If all are selected this returns true
*/
get areSomeEntitiesSelected$(): Observable<boolean> {
return this.selectedLength$.pipe(map(value => value > 0));
return this.selectedLength$.pipe(
map(value => value > 0),
distinctUntilChanged()
);
}
/**
@ -91,7 +118,8 @@ export class ScreenStateService<T> {
*/
get notAllEntitiesSelected$(): Observable<boolean> {
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<T> {
logCurrentState(): void {
console.log('Entities', this.allEntities);
console.log('Displayed', this.displayedEntities);
console.log('Filtered', this.filteredEntities);
console.log('Selected', this.selectedEntities);
}

View File

@ -29,14 +29,9 @@ export class SearchService<T> {
}
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))
);

View File

@ -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] },
{