split listing component into services
This commit is contained in:
parent
7990636066
commit
3e400e6dd2
@ -1,11 +1,7 @@
|
||||
<section>
|
||||
<redaction-page-header
|
||||
(filtersChanged)="filtersChanged($event)"
|
||||
(filtersReset)="resetFilters()"
|
||||
(searchChanged)="executeSearch($event)"
|
||||
[filterConfigs]="filterConfigs"
|
||||
[buttonConfigs]="buttonConfigs"
|
||||
[showResetFilters]="showResetFilters"
|
||||
[searchPlaceholder]="'dossier-listing.search' | translate"
|
||||
></redaction-page-header>
|
||||
|
||||
@ -17,7 +13,7 @@
|
||||
<span class="all-caps-label">
|
||||
{{
|
||||
'dossier-listing.table-header.title'
|
||||
| translate: { length: displayedEntities.length || 0 }
|
||||
| translate: { length: (displayed$ | async)?.length || 0 }
|
||||
}}
|
||||
</span>
|
||||
|
||||
@ -54,14 +50,14 @@
|
||||
|
||||
<redaction-empty-state
|
||||
(action)="openAddDossierDialog()"
|
||||
*ngIf="!allEntities.length"
|
||||
*ngIf="(entities$ | async)?.length === 0"
|
||||
[showButton]="permissionsService.isManager()"
|
||||
icon="red:folder"
|
||||
screen="dossier-listing"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="allEntities.length && !displayedEntities.length"
|
||||
*ngIf="(entities$ | async)?.length && (displayed$ | async)?.length === 0"
|
||||
screen="dossier-listing"
|
||||
type="no-match"
|
||||
></redaction-empty-state>
|
||||
@ -69,7 +65,8 @@
|
||||
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="
|
||||
let dw of displayedEntities
|
||||
let dw of displayed$
|
||||
| async
|
||||
| sortBy: sortingOption.order:sortingOption.column
|
||||
"
|
||||
[class.pointer]="canOpenDossier(dw)"
|
||||
@ -145,7 +142,7 @@
|
||||
<div class="right-container" redactionHasScrollbar>
|
||||
<redaction-dossier-listing-details
|
||||
(filtersChanged)="filtersChanged($event)"
|
||||
*ngIf="allEntities.length"
|
||||
*ngIf="(entities$ | async)?.length !== 0"
|
||||
[documentsChartData]="documentsChartData"
|
||||
[dossiersChartData]="dossiersChartData"
|
||||
[filters]="detailsContainerFilters"
|
||||
|
||||
@ -7,14 +7,13 @@ import { groupBy } from '@utils/functions';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
import { Subscription, timer } from 'rxjs';
|
||||
import { Observable, Subscription, timer } from 'rxjs';
|
||||
import { filter, tap } from 'rxjs/operators';
|
||||
import { TranslateChartService } from '@services/translate-chart.service';
|
||||
import { RedactionFilterSorter } from '@utils/sorters/redaction-filter-sorter';
|
||||
import { StatusSorter } from '@utils/sorters/status-sorter';
|
||||
import { NavigationEnd, NavigationStart, Router } from '@angular/router';
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
import { BaseListingComponent } from '@shared/base/base-listing.component';
|
||||
import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
import {
|
||||
@ -27,13 +26,18 @@ import {
|
||||
import { UserPreferenceService } from '../../../../services/user-preference.service';
|
||||
import { FilterConfig } from '../../../shared/components/page-header/models/filter-config.model';
|
||||
import { ButtonConfig } from '../../../shared/components/page-header/models/button-config.model';
|
||||
import { FilterService } from '../../../shared/services/filter.service';
|
||||
import { SearchService } from '../../../shared/services/search.service';
|
||||
import { ScreenStateService } from '../../../shared/services/screen-state.service';
|
||||
import { NewBaseListingComponent } from '../../../shared/base/new-base-listing.component';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-listing-screen.component.html',
|
||||
styleUrls: ['./dossier-listing-screen.component.scss']
|
||||
styleUrls: ['./dossier-listing-screen.component.scss'],
|
||||
providers: [FilterService, SearchService, ScreenStateService]
|
||||
})
|
||||
export class DossierListingScreenComponent
|
||||
extends BaseListingComponent<DossierWrapper>
|
||||
extends NewBaseListingComponent<DossierWrapper>
|
||||
implements OnInit, OnDestroy, OnAttach, OnDetach
|
||||
{
|
||||
dossiersChartData: DoughnutChartConfig[] = [];
|
||||
@ -61,7 +65,6 @@ export class DossierListingScreenComponent
|
||||
|
||||
readonly itemSize = 85;
|
||||
|
||||
protected readonly _searchKey = 'name';
|
||||
protected readonly _sortKey = 'dossier-listing';
|
||||
|
||||
private _dossierAutoUpdateTimer: Subscription;
|
||||
@ -85,23 +88,26 @@ export class DossierListingScreenComponent
|
||||
) {
|
||||
super(_injector);
|
||||
this._appStateService.reset();
|
||||
this._searchService.searchKey = 'name';
|
||||
this._loadEntitiesFromState();
|
||||
}
|
||||
|
||||
get noData() {
|
||||
return this.allEntities.length === 0;
|
||||
get activeDossiersCount(): number {
|
||||
return this._screenStateService.entities.filter(
|
||||
p => p.dossier.status === Dossier.StatusEnum.ACTIVE
|
||||
).length;
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this._userService.user;
|
||||
get inactiveDossiersCount(): number {
|
||||
return this._screenStateService.entities.length - this.activeDossiersCount;
|
||||
}
|
||||
|
||||
get activeDossiersCount() {
|
||||
return this.allEntities.filter(p => p.dossier.status === Dossier.StatusEnum.ACTIVE).length;
|
||||
get displayed$(): Observable<DossierWrapper[]> {
|
||||
return this._screenStateService.displayedEntities$;
|
||||
}
|
||||
|
||||
get inactiveDossiersCount() {
|
||||
return this.allEntities.length - this.activeDossiersCount;
|
||||
get entities$(): Observable<DossierWrapper[]> {
|
||||
return this._screenStateService.entities$;
|
||||
}
|
||||
|
||||
protected get _filters(): {
|
||||
@ -129,32 +135,37 @@ export class DossierListingScreenComponent
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._calculateData();
|
||||
|
||||
this._dossierAutoUpdateTimer = timer(0, 10000)
|
||||
.pipe(
|
||||
tap(async () => {
|
||||
await this._appStateService.loadAllDossiers();
|
||||
this._loadEntitiesFromState();
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this._fileChangedSub = this._appStateService.fileChanged.subscribe(() => {
|
||||
try {
|
||||
this._calculateData();
|
||||
});
|
||||
|
||||
this._routerEventsScrollPositionSub = this._router.events
|
||||
.pipe(
|
||||
filter(
|
||||
events => events instanceof NavigationStart || events instanceof NavigationEnd
|
||||
this._dossierAutoUpdateTimer = timer(0, 10000)
|
||||
.pipe(
|
||||
tap(async () => {
|
||||
await this._appStateService.loadAllDossiers();
|
||||
this._loadEntitiesFromState();
|
||||
})
|
||||
)
|
||||
)
|
||||
.subscribe(event => {
|
||||
if (event instanceof NavigationStart && event.url !== '/main/dossiers') {
|
||||
this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top');
|
||||
}
|
||||
.subscribe();
|
||||
|
||||
this._fileChangedSub = this._appStateService.fileChanged.subscribe(() => {
|
||||
this._calculateData();
|
||||
});
|
||||
|
||||
this._routerEventsScrollPositionSub = this._router.events
|
||||
.pipe(
|
||||
filter(
|
||||
events =>
|
||||
events instanceof NavigationStart || events instanceof NavigationEnd
|
||||
)
|
||||
)
|
||||
.subscribe(event => {
|
||||
if (event instanceof NavigationStart && event.url !== '/main/dossiers') {
|
||||
this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top');
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnAttach() {
|
||||
@ -207,6 +218,10 @@ export class DossierListingScreenComponent
|
||||
this._calculateData();
|
||||
}
|
||||
|
||||
filtersChanged(event) {
|
||||
this._filterService.filtersChanged(event);
|
||||
}
|
||||
|
||||
protected _preFilter() {
|
||||
this.detailsContainerFilters = {
|
||||
statusFilters: this.statusFilters.map(f => ({ ...f }))
|
||||
@ -214,12 +229,18 @@ export class DossierListingScreenComponent
|
||||
}
|
||||
|
||||
private _loadEntitiesFromState() {
|
||||
this.allEntities = this._appStateService.allDossiers;
|
||||
this._screenStateService.setEntities(this._appStateService.allDossiers);
|
||||
}
|
||||
|
||||
private get _user() {
|
||||
return this._userService.user;
|
||||
}
|
||||
|
||||
private _calculateData() {
|
||||
this._computeAllFilters();
|
||||
this._filterEntities();
|
||||
this._filterService.filters = this._filters;
|
||||
this._filterService.preFilter = () => this._preFilter();
|
||||
this._filterService.filterEntities();
|
||||
this.dossiersChartData = [
|
||||
{ value: this.activeDossiersCount, color: 'ACTIVE', label: 'active' },
|
||||
{ value: this.inactiveDossiersCount, color: 'DELETED', label: 'archived' }
|
||||
@ -245,7 +266,8 @@ export class DossierListingScreenComponent
|
||||
const allDistinctPeople = new Set<string>();
|
||||
const allDistinctNeedsWork = new Set<string>();
|
||||
const allDistinctDossierTemplates = new Set<string>();
|
||||
this.allEntities.forEach(entry => {
|
||||
this._screenStateService.logCurrentState();
|
||||
this._screenStateService?.entities?.forEach(entry => {
|
||||
// all people
|
||||
entry.dossier.memberIds.forEach(f => allDistinctPeople.add(f));
|
||||
// file statuses
|
||||
@ -342,24 +364,24 @@ export class DossierListingScreenComponent
|
||||
private _createQuickFilters() {
|
||||
const filters: FilterModel[] = [
|
||||
{
|
||||
key: this.user.id,
|
||||
key: this._user.id,
|
||||
label: this._translateService.instant('dossier-listing.quick-filters.my-dossiers'),
|
||||
checker: (dw: DossierWrapper) => dw.ownerId === this.user.id
|
||||
checker: (dw: DossierWrapper) => dw.ownerId === this._user.id
|
||||
},
|
||||
{
|
||||
key: this.user.id,
|
||||
key: this._user.id,
|
||||
label: this._translateService.instant('dossier-listing.quick-filters.to-approve'),
|
||||
checker: (dw: DossierWrapper) => dw.approverIds.includes(this.user.id)
|
||||
checker: (dw: DossierWrapper) => dw.approverIds.includes(this._user.id)
|
||||
},
|
||||
{
|
||||
key: this.user.id,
|
||||
key: this._user.id,
|
||||
label: this._translateService.instant('dossier-listing.quick-filters.to-review'),
|
||||
checker: (dw: DossierWrapper) => dw.memberIds.includes(this.user.id)
|
||||
checker: (dw: DossierWrapper) => dw.memberIds.includes(this._user.id)
|
||||
},
|
||||
{
|
||||
key: this.user.id,
|
||||
key: this._user.id,
|
||||
label: this._translateService.instant('dossier-listing.quick-filters.other'),
|
||||
checker: (dw: DossierWrapper) => !dw.memberIds.includes(this.user.id)
|
||||
checker: (dw: DossierWrapper) => !dw.memberIds.includes(this._user.id)
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
<section *ngIf="!!activeDossier">
|
||||
<redaction-page-header
|
||||
(filtersChanged)="filtersChanged($event)"
|
||||
(filtersReset)="resetFilters()"
|
||||
(searchChanged)="executeSearch($event)"
|
||||
[filterConfigs]="filterConfigs"
|
||||
[actionConfigs]="actionConfigs"
|
||||
[showResetFilters]="showResetFilters"
|
||||
[showCloseButton]="true"
|
||||
[searchPlaceholder]="'dossier-overview.search' | translate"
|
||||
>
|
||||
|
||||
@ -40,11 +40,13 @@ import { FilterModel } from '@shared/components/filters/popup-filter/model/filte
|
||||
import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service';
|
||||
import { FilterConfig } from '@shared/components/page-header/models/filter-config.model';
|
||||
import { ActionConfig } from '@shared/components/page-header/models/action-config.model';
|
||||
import { FilterService } from '../../../shared/services/filter.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-overview-screen',
|
||||
templateUrl: './dossier-overview-screen.component.html',
|
||||
styleUrls: ['./dossier-overview-screen.component.scss']
|
||||
styleUrls: ['./dossier-overview-screen.component.scss'],
|
||||
providers: [FilterService]
|
||||
})
|
||||
export class DossierOverviewScreenComponent
|
||||
extends BaseListingComponent<FileStatusWrapper>
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { ChangeDetectorRef, Component, Injector, ViewChild } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { debounce } from '@utils/debounce';
|
||||
import { ScreenName, SortingOption, SortingService } from '@services/sorting.service';
|
||||
import { FilterModel } from '../components/filters/popup-filter/model/filter.model';
|
||||
import { getFilteredEntities } from '../components/filters/popup-filter/utils/filter-utils';
|
||||
import { QuickFiltersComponent } from '../components/filters/quick-filters/quick-filters.component';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { FilterService } from '../services/filter.service';
|
||||
import { SearchService } from '../services/search.service';
|
||||
import { ScreenStateService } from '../services/screen-state.service';
|
||||
import { getFilteredEntities } from '../components/filters/popup-filter/utils/filter-utils';
|
||||
import { debounce } from '../../../utils/debounce';
|
||||
|
||||
// Functionalities: Filter, search, select, sort
|
||||
|
||||
@ -24,6 +27,9 @@ export abstract class BaseListingComponent<T = any> {
|
||||
protected readonly _formBuilder: FormBuilder;
|
||||
protected readonly _changeDetectorRef: ChangeDetectorRef;
|
||||
protected readonly _sortingService: SortingService;
|
||||
protected readonly _filterService: FilterService<T>;
|
||||
protected readonly _searchService: SearchService<T>;
|
||||
protected readonly _screenStateService: ScreenStateService<T>;
|
||||
|
||||
// ----
|
||||
// Overwrite in child class:
|
||||
@ -40,6 +46,9 @@ export abstract class BaseListingComponent<T = any> {
|
||||
this._formBuilder = this._injector.get<FormBuilder>(FormBuilder);
|
||||
this._changeDetectorRef = this._injector.get<ChangeDetectorRef>(ChangeDetectorRef);
|
||||
this._sortingService = this._injector.get<SortingService>(SortingService);
|
||||
this._filterService = this._injector.get<FilterService<T>>(FilterService);
|
||||
this._searchService = this._injector.get<SearchService<T>>(SearchService);
|
||||
this._screenStateService = this._injector.get<ScreenStateService<T>>(ScreenStateService);
|
||||
}
|
||||
|
||||
get areAllEntitiesSelected() {
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
import { ChangeDetectorRef, Component, Injector, ViewChild } from '@angular/core';
|
||||
import { ScreenName, SortingOption, SortingService } from '@services/sorting.service';
|
||||
import { FilterModel } from '../components/filters/popup-filter/model/filter.model';
|
||||
import { QuickFiltersComponent } from '../components/filters/quick-filters/quick-filters.component';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { FilterService } from '../services/filter.service';
|
||||
import { SearchService } from '../services/search.service';
|
||||
import { ScreenStateService } from '../services/screen-state.service';
|
||||
|
||||
@Component({ template: '' })
|
||||
export abstract class NewBaseListingComponent<T = any> {
|
||||
@ViewChild(CdkVirtualScrollViewport) scrollViewport: CdkVirtualScrollViewport;
|
||||
|
||||
protected readonly _changeDetectorRef: ChangeDetectorRef;
|
||||
protected readonly _sortingService: SortingService;
|
||||
protected readonly _filterService: FilterService<T>;
|
||||
protected readonly _searchService: SearchService<T>;
|
||||
protected readonly _screenStateService: ScreenStateService<T>;
|
||||
|
||||
// ----
|
||||
// Overwrite in child class:
|
||||
protected readonly _sortKey: ScreenName;
|
||||
// Overwrite this in ngOnInit
|
||||
@ViewChild(QuickFiltersComponent)
|
||||
protected _quickFilters: QuickFiltersComponent;
|
||||
|
||||
protected constructor(protected readonly _injector: Injector) {
|
||||
this._changeDetectorRef = this._injector.get<ChangeDetectorRef>(ChangeDetectorRef);
|
||||
this._sortingService = this._injector.get<SortingService>(SortingService);
|
||||
this._filterService = this._injector.get<FilterService<T>>(FilterService);
|
||||
this._searchService = this._injector.get<SearchService<T>>(SearchService);
|
||||
this._screenStateService = this._injector.get<ScreenStateService<T>>(ScreenStateService);
|
||||
}
|
||||
|
||||
get sortingOption(): SortingOption {
|
||||
return this._sortingService.getSortingOption(this._getSortKey);
|
||||
}
|
||||
|
||||
protected get _filters(): {
|
||||
values: FilterModel[];
|
||||
checker: Function;
|
||||
matchAll?: boolean;
|
||||
checkerArgs?: any;
|
||||
}[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
private get _getSortKey(): ScreenName {
|
||||
if (!this._sortKey) throw new Error('Not implemented');
|
||||
|
||||
return this._sortKey;
|
||||
}
|
||||
|
||||
resetFilters() {
|
||||
this._quickFilters.deactivateFilters();
|
||||
this._filterService.reset();
|
||||
}
|
||||
|
||||
toggleSort($event) {
|
||||
this._sortingService.toggleSort(this._getSortKey, $event);
|
||||
}
|
||||
|
||||
// protected _filterEntities() {
|
||||
// this._preFilter();
|
||||
// this.filteredEntities = getFilteredEntities(this.allEntities, this._filters);
|
||||
// this.executeSearch(this._searchValue);
|
||||
// this._changeDetectorRef.detectChanges();
|
||||
// }
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
import { FilterModel } from './filter.model';
|
||||
|
||||
export interface FilterWrapper {
|
||||
values: FilterModel[];
|
||||
checker: Function;
|
||||
matchAll?: boolean;
|
||||
checkerArgs?: any;
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
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 { 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';
|
||||
|
||||
export function processFilters(oldFilters: FilterModel[], newFilters: FilterModel[]) {
|
||||
copySettings(oldFilters, newFilters);
|
||||
@ -175,11 +176,8 @@ export const addedDateChecker = (dw: DossierWrapper, filter: FilterModel) =>
|
||||
export const dossierApproverChecker = (dw: DossierWrapper, filter: FilterModel) =>
|
||||
dw.approverIds.includes(filter.key);
|
||||
|
||||
export function getFilteredEntities(
|
||||
entities: any[],
|
||||
filters: { values: FilterModel[]; checker: Function; matchAll?: boolean; checkerArgs?: any }[]
|
||||
) {
|
||||
const filteredEntities = [];
|
||||
export function getFilteredEntities<T>(entities: T[], filters: FilterWrapper[]) {
|
||||
const filteredEntities: T[] = [];
|
||||
for (const entity of entities) {
|
||||
let add = true;
|
||||
for (const filter of filters) {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
<ng-container *ngFor="let config of filterConfigs; trackBy: trackByLabel">
|
||||
<redaction-popup-filter
|
||||
(filtersChanged)="filtersChanged.emit($event)"
|
||||
(filtersChanged)="filterService.filtersChanged($event)"
|
||||
*ngIf="!config.hide"
|
||||
[filterLabel]="config.label"
|
||||
[icon]="config.icon"
|
||||
@ -16,7 +16,7 @@
|
||||
</ng-container>
|
||||
|
||||
<redaction-input-with-action
|
||||
[form]="searchForm"
|
||||
[form]="searchService.searchForm"
|
||||
[placeholder]="searchPlaceholder"
|
||||
type="search"
|
||||
></redaction-input-with-action>
|
||||
@ -50,6 +50,7 @@
|
||||
></redaction-circle-button>
|
||||
</ng-container>
|
||||
|
||||
<!-- Extra custom actions here -->
|
||||
<ng-content></ng-content>
|
||||
|
||||
<redaction-circle-button
|
||||
|
||||
@ -1,20 +1,12 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
QueryList,
|
||||
ViewChildren
|
||||
} from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Input, QueryList, ViewChildren } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
import { PopupFilterComponent } from '@shared/components/filters/popup-filter/popup-filter.component';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { FilterConfig } from '@shared/components/page-header/models/filter-config.model';
|
||||
import { ActionConfig } from '@shared/components/page-header/models/action-config.model';
|
||||
import { ButtonConfig } from '@shared/components/page-header/models/button-config.model';
|
||||
import { BaseHeaderConfig } from '@shared/components/page-header/models/base-config.model';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-page-header',
|
||||
@ -22,48 +14,39 @@ import { BaseHeaderConfig } from '@shared/components/page-header/models/base-con
|
||||
styleUrls: ['./page-header.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PageHeaderComponent {
|
||||
export class PageHeaderComponent<T> {
|
||||
@Input() pageLabel: string;
|
||||
@Input() showCloseButton: boolean;
|
||||
@Input() showResetFilters: boolean;
|
||||
@Input() filterConfigs: FilterConfig[];
|
||||
@Input() actionConfigs: ActionConfig[];
|
||||
@Input() buttonConfigs: ButtonConfig[];
|
||||
@Input() searchPlaceholder: string;
|
||||
@Output() filtersChanged = new EventEmitter<{
|
||||
primary: FilterModel[];
|
||||
secondary?: FilterModel[];
|
||||
}>();
|
||||
@Output() filtersReset = new EventEmitter<never>();
|
||||
@Output() searchChanged = new EventEmitter<string>();
|
||||
|
||||
readonly searchForm = this._formBuilder.group({
|
||||
query: ['']
|
||||
});
|
||||
|
||||
@ViewChildren(PopupFilterComponent)
|
||||
private readonly _filterComponents: QueryList<PopupFilterComponent>;
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _formBuilder: FormBuilder
|
||||
) {
|
||||
this.searchForm.valueChanges.subscribe(value => this.searchChanged.emit(value.query));
|
||||
}
|
||||
readonly filterService: FilterService<T>,
|
||||
readonly searchService: SearchService<T>
|
||||
) {}
|
||||
|
||||
get hasActiveFilters() {
|
||||
const hasActiveFilters = this._filterComponents?.reduce(
|
||||
(acc, component) => acc || component?.hasActiveFilters,
|
||||
false
|
||||
);
|
||||
return hasActiveFilters || this.searchForm.get('query').value || this.showResetFilters;
|
||||
return (
|
||||
hasActiveFilters ||
|
||||
this.searchService.searchValue ||
|
||||
this.filterService.showResetFilters
|
||||
);
|
||||
}
|
||||
|
||||
resetFilters() {
|
||||
this.filterService.reset();
|
||||
this._filterComponents.forEach(component => component?.deactivateFilters());
|
||||
|
||||
this.filtersReset.emit();
|
||||
this.searchForm.reset({ query: '' });
|
||||
this.searchService.reset();
|
||||
}
|
||||
|
||||
trackByLabel(index: number, item: BaseHeaderConfig) {
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
import { ChangeDetectorRef, Injectable } from '@angular/core';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
import { getFilteredEntities } from '@shared/components/filters/popup-filter/utils/filter-utils';
|
||||
import { FilterWrapper } 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';
|
||||
|
||||
@Injectable()
|
||||
export class FilterService<T> {
|
||||
showResetFilters = false;
|
||||
preFilter: () => void;
|
||||
filters: FilterWrapper[];
|
||||
|
||||
constructor(
|
||||
private readonly _screenStateService: ScreenStateService<T>,
|
||||
private readonly _searchService: SearchService<T>,
|
||||
private readonly _changeDetector: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
filtersChanged(filters?: { [key: string]: FilterModel[] } | FilterModel[]): void {
|
||||
console.log(filters);
|
||||
if (filters instanceof Array) this.showResetFilters = !!filters.find(f => f.checked);
|
||||
else
|
||||
for (const key of Object.keys(filters ?? {})) {
|
||||
for (let idx = 0; idx < this[key]?.length; ++idx) {
|
||||
this[key][idx] = filters[key][idx];
|
||||
}
|
||||
}
|
||||
|
||||
this.filterEntities();
|
||||
}
|
||||
|
||||
filterEntities(): void {
|
||||
if (this.preFilter) this.preFilter();
|
||||
this._screenStateService.setFilteredEntities(
|
||||
getFilteredEntities(this._screenStateService.entities, this.filters)
|
||||
);
|
||||
this._searchService.executeSearchImmediately();
|
||||
this._changeDetector.detectChanges();
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
// this._quickFilters.deactivateFilters();
|
||||
this.showResetFilters = false;
|
||||
this.filtersChanged();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class ScreenStateService<T> {
|
||||
entities$ = new BehaviorSubject<Array<T>>([]);
|
||||
filteredEntities$ = new BehaviorSubject<T[]>([]);
|
||||
displayedEntities$ = new BehaviorSubject<T[]>([]);
|
||||
selectedEntitiesIds: string[] = [];
|
||||
|
||||
private _selectionKey: string;
|
||||
get entities(): T[] {
|
||||
return Object.values(this.entities$.getValue());
|
||||
}
|
||||
|
||||
get filteredEntities(): T[] {
|
||||
return Object.values(this.filteredEntities$.getValue());
|
||||
}
|
||||
|
||||
get displayedEntities(): T[] {
|
||||
return Object.values(this.displayedEntities$.getValue());
|
||||
}
|
||||
//
|
||||
// select<K>(mapFn: (state: T[]) => K): Observable<K> {
|
||||
// return this.entities$.asObservable().pipe(
|
||||
// map((state: T[]) => mapFn(state)),
|
||||
// distinctUntilChanged()
|
||||
// );
|
||||
// }
|
||||
|
||||
setEntities(newEntities: Partial<T[]>) {
|
||||
this.entities$.next(newEntities);
|
||||
}
|
||||
|
||||
setFilteredEntities(newEntities: Partial<T[]>) {
|
||||
this.filteredEntities$.next(newEntities);
|
||||
}
|
||||
|
||||
setDisplayedEntities(newEntities: Partial<T[]>) {
|
||||
console.log(this.displayedEntities);
|
||||
this.displayedEntities$.next(newEntities);
|
||||
console.log(this.displayedEntities);
|
||||
}
|
||||
|
||||
set selectionKey(value: string) {
|
||||
this._selectionKey = value;
|
||||
}
|
||||
|
||||
get areAllEntitiesSelected() {
|
||||
return (
|
||||
this.displayedEntities.length !== 0 &&
|
||||
this.selectedEntitiesIds.length === this.displayedEntities.length
|
||||
);
|
||||
}
|
||||
|
||||
get areSomeEntitiesSelected() {
|
||||
return this.selectedEntitiesIds.length > 0;
|
||||
}
|
||||
|
||||
isSelected(entity: T) {
|
||||
return this.selectedEntitiesIds.indexOf(entity[this._getSelectionKey]) !== -1;
|
||||
}
|
||||
|
||||
toggleEntitySelected(entity: T) {
|
||||
const idx = this.selectedEntitiesIds.indexOf(entity[this._getSelectionKey]);
|
||||
if (idx === -1) {
|
||||
this.selectedEntitiesIds.push(entity[this._getSelectionKey]);
|
||||
} else {
|
||||
this.selectedEntitiesIds.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
toggleSelectAll() {
|
||||
if (this.areSomeEntitiesSelected) {
|
||||
this.selectedEntitiesIds = [];
|
||||
} else {
|
||||
this.selectedEntitiesIds = this.displayedEntities.map(
|
||||
entity => entity[this._getSelectionKey]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
updateSelection() {
|
||||
if (this._selectionKey) {
|
||||
this.selectedEntitiesIds = this.displayedEntities
|
||||
.map(entity => entity[this._getSelectionKey])
|
||||
.filter(id => this.selectedEntitiesIds.includes(id));
|
||||
}
|
||||
}
|
||||
|
||||
logCurrentState() {
|
||||
console.log('Entities', this.entities);
|
||||
console.log('Displayed', this.displayedEntities$.getValue());
|
||||
console.log('Filtered', this.filteredEntities$.getValue());
|
||||
console.log('Selected', this.selectedEntitiesIds);
|
||||
}
|
||||
|
||||
private get _getSelectionKey(): string {
|
||||
if (!this._selectionKey) throw new Error('Not implemented');
|
||||
|
||||
return this._selectionKey;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { debounce } from '@utils/debounce';
|
||||
import { ScreenStateService } from '@shared/services/screen-state.service';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
|
||||
@Injectable()
|
||||
export class SearchService<T> {
|
||||
private _searchValue = '';
|
||||
private _searchKey: string;
|
||||
|
||||
readonly searchForm = this._formBuilder.group({
|
||||
query: ['']
|
||||
});
|
||||
|
||||
constructor(
|
||||
private readonly _screenStateService: ScreenStateService<T>,
|
||||
private readonly _formBuilder: FormBuilder
|
||||
) {
|
||||
this.searchForm.valueChanges.subscribe(() => this.executeSearch());
|
||||
}
|
||||
|
||||
@debounce(200)
|
||||
executeSearch(): void {
|
||||
this._searchValue = this.searchValue;
|
||||
return this.executeSearchImmediately();
|
||||
}
|
||||
|
||||
executeSearchImmediately(): void {
|
||||
const displayed = (
|
||||
this._screenStateService.filteredEntities ?? this._screenStateService.entities
|
||||
).filter(entity =>
|
||||
this._searchField(entity).toLowerCase().includes(this._searchValue.toLowerCase())
|
||||
);
|
||||
|
||||
this._screenStateService.setDisplayedEntities(displayed);
|
||||
this._screenStateService.updateSelection();
|
||||
}
|
||||
|
||||
set searchKey(value: string) {
|
||||
this._searchKey = value;
|
||||
}
|
||||
|
||||
get searchValue(): string {
|
||||
return this.searchForm.get('query').value;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.searchForm.reset({ query: '' });
|
||||
}
|
||||
|
||||
private get _getSearchKey(): string {
|
||||
if (!this._searchKey) throw new Error('Not implemented');
|
||||
|
||||
return this._searchKey;
|
||||
}
|
||||
|
||||
protected _searchField(entity: T): string {
|
||||
return entity[this._getSearchKey];
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user