add table header component, remove filteredEntities
This commit is contained in:
parent
27de88ac57
commit
02b38780f6
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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()"
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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) =>
|
||||
|
||||
@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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"
|
||||
>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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]="[
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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>
|
||||
@ -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>) {}
|
||||
}
|
||||
@ -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`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
@ -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)], []);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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))
|
||||
);
|
||||
|
||||
@ -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] },
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user