Browse Source

Add groups to activities filter component

pull/922/head
Thomas 3 years ago
parent
commit
68c28b8958
  1. 2
      apps/api/src/app/account/account.service.ts
  2. 2
      apps/api/src/app/order/order.service.ts
  3. 4
      apps/api/src/app/portfolio/portfolio.controller.ts
  4. 4
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  5. 2
      apps/client/src/app/services/data.service.ts
  6. 6
      libs/common/src/lib/interfaces/filter-group.interface.ts
  7. 2
      libs/common/src/lib/interfaces/filter.interface.ts
  8. 2
      libs/common/src/lib/interfaces/index.ts
  9. 14
      libs/ui/src/lib/activities-filter/activities-filter.component.html
  10. 86
      libs/ui/src/lib/activities-filter/activities-filter.component.ts
  11. 2
      libs/ui/src/lib/activities-table/activities-table.component.ts

2
apps/api/src/app/account/account.service.ts

@ -120,7 +120,7 @@ export class AccountService {
where.id = { where.id = {
in: filters in: filters
.filter(({ type }) => { .filter(({ type }) => {
return type === 'account'; return type === 'ACCOUNT';
}) })
.map(({ id }) => { .map(({ id }) => {
return id; return id;

2
apps/api/src/app/order/order.service.ts

@ -188,7 +188,7 @@ export class OrderService {
}): Promise<Activity[]> { }): Promise<Activity[]> {
const where: Prisma.OrderWhereInput = { userId }; const where: Prisma.OrderWhereInput = { userId };
const { account: filtersByAccount, tag: filtersByTag } = groupBy( const { ACCOUNT: filtersByAccount, TAG: filtersByTag } = groupBy(
filters, filters,
(filter) => { (filter) => {
return filter.type; return filter.type;

4
apps/api/src/app/portfolio/portfolio.controller.ts

@ -119,13 +119,13 @@ export class PortfolioController {
...accountIds.map((accountId) => { ...accountIds.map((accountId) => {
return <Filter>{ return <Filter>{
id: accountId, id: accountId,
type: 'account' type: 'ACCOUNT'
}; };
}), }),
...tagIds.map((tagId) => { ...tagIds.map((tagId) => {
return <Filter>{ return <Filter>{
id: tagId, id: tagId,
type: 'tag' type: 'TAG'
}; };
}) })
]; ];

4
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

@ -165,7 +165,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
return { return {
id, id,
label: name, label: name,
type: 'account' type: 'ACCOUNT'
}; };
}); });
@ -173,7 +173,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
return { return {
id, id,
label: name, label: name,
type: 'tag' type: 'TAG'
}; };
}); });

2
apps/client/src/app/services/data.service.ts

@ -187,7 +187,7 @@ export class DataService {
let params = new HttpParams(); let params = new HttpParams();
if (filters?.length > 0) { if (filters?.length > 0) {
const { account: filtersByAccount, tag: filtersByTag } = groupBy( const { ACCOUNT: filtersByAccount, TAG: filtersByTag } = groupBy(
filters, filters,
(filter) => { (filter) => {
return filter.type; return filter.type;

6
libs/common/src/lib/interfaces/filter-group.interface.ts

@ -0,0 +1,6 @@
import { Filter } from './filter.interface';
export interface FilterGroup {
filters: Filter[];
name: Filter['type'];
}

2
libs/common/src/lib/interfaces/filter.interface.ts

@ -1,5 +1,5 @@
export interface Filter { export interface Filter {
id: string; id: string;
label?: string; label?: string;
type: 'account' | 'tag'; type: 'ACCOUNT' | 'TAG';
} }

2
libs/common/src/lib/interfaces/index.ts

@ -8,6 +8,7 @@ import {
} from './admin-market-data.interface'; } from './admin-market-data.interface';
import { Coupon } from './coupon.interface'; import { Coupon } from './coupon.interface';
import { Export } from './export.interface'; import { Export } from './export.interface';
import { FilterGroup } from './filter-group.interface';
import { Filter } from './filter.interface'; import { Filter } from './filter.interface';
import { HistoricalDataItem } from './historical-data-item.interface'; import { HistoricalDataItem } from './historical-data-item.interface';
import { InfoItem } from './info-item.interface'; import { InfoItem } from './info-item.interface';
@ -41,6 +42,7 @@ export {
Coupon, Coupon,
Export, Export,
Filter, Filter,
FilterGroup,
HistoricalDataItem, HistoricalDataItem,
InfoItem, InfoItem,
PortfolioChart, PortfolioChart,

14
libs/ui/src/lib/activities-filter/activities-filter.component.html

@ -26,9 +26,17 @@
#autocomplete="matAutocomplete" #autocomplete="matAutocomplete"
(optionSelected)="onSelectFilter($event)" (optionSelected)="onSelectFilter($event)"
> >
<mat-option *ngFor="let filter of filters | async" [value]="filter"> <mat-optgroup
{{ filter.label | gfSymbol }} *ngFor="let filterGroup of filterGroups$ | async"
</mat-option> [label]="filterGroup.name"
>
<mat-option
*ngFor="let filter of filterGroup.filters"
[value]="filter.id"
>
{{ filter.label | gfSymbol }}
</mat-option>
</mat-optgroup>
</mat-autocomplete> </mat-autocomplete>
<mat-spinner <mat-spinner
matSuffix matSuffix

86
libs/ui/src/lib/activities-filter/activities-filter.component.ts

@ -17,7 +17,8 @@ import {
MatAutocompleteSelectedEvent MatAutocompleteSelectedEvent
} from '@angular/material/autocomplete'; } from '@angular/material/autocomplete';
import { MatChipInputEvent } from '@angular/material/chips'; import { MatChipInputEvent } from '@angular/material/chips';
import { Filter } from '@ghostfolio/common/interfaces'; import { Filter, FilterGroup } from '@ghostfolio/common/interfaces';
import { groupBy } from 'lodash';
import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -37,6 +38,7 @@ export class ActivitiesFilterComponent implements OnChanges, OnDestroy {
@ViewChild('autocomplete') matAutocomplete: MatAutocomplete; @ViewChild('autocomplete') matAutocomplete: MatAutocomplete;
@ViewChild('searchInput') searchInput: ElementRef<HTMLInputElement>; @ViewChild('searchInput') searchInput: ElementRef<HTMLInputElement>;
public filterGroups$: Subject<FilterGroup[]> = new BehaviorSubject([]);
public filters$: Subject<Filter[]> = new BehaviorSubject([]); public filters$: Subject<Filter[]> = new BehaviorSubject([]);
public filters: Observable<Filter[]> = this.filters$.asObservable(); public filters: Observable<Filter[]> = this.filters$.asObservable();
public searchControl = new FormControl(); public searchControl = new FormControl();
@ -50,40 +52,27 @@ export class ActivitiesFilterComponent implements OnChanges, OnDestroy {
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((filterOrSearchTerm: Filter | string) => { .subscribe((filterOrSearchTerm: Filter | string) => {
if (filterOrSearchTerm) { if (filterOrSearchTerm) {
this.filters$.next( const searchTerm =
this.allFilters typeof filterOrSearchTerm === 'string'
.filter((filter) => { ? filterOrSearchTerm
// Filter selected filters : filterOrSearchTerm?.label;
return !this.selectedFilters.some((selectedFilter) => {
return selectedFilter.id === filter.id; this.filterGroups$.next(this.getGroupedFilters(searchTerm));
}); } else {
}) this.filterGroups$.next(this.getGroupedFilters());
.filter((filter) => {
if (typeof filterOrSearchTerm === 'string') {
return filter.label
.toLowerCase()
.startsWith(filterOrSearchTerm.toLowerCase());
}
return filter.label
.toLowerCase()
.startsWith(filterOrSearchTerm?.label?.toLowerCase());
})
.sort((a, b) => a.label.localeCompare(b.label))
);
} }
}); });
} }
public ngOnChanges(changes: SimpleChanges) { public ngOnChanges(changes: SimpleChanges) {
if (changes.allFilters?.currentValue) { if (changes.allFilters?.currentValue) {
this.updateFilter(); this.updateFilters();
} }
} }
public onAddFilter({ input, value }: MatChipInputEvent): void { public onAddFilter({ input, value }: MatChipInputEvent): void {
if (value?.trim()) { if (value?.trim()) {
this.updateFilter(); this.updateFilters();
} }
// Reset the input value // Reset the input value
@ -99,12 +88,16 @@ export class ActivitiesFilterComponent implements OnChanges, OnDestroy {
return filter.id !== aFilter.id; return filter.id !== aFilter.id;
}); });
this.updateFilter(); this.updateFilters();
} }
public onSelectFilter(event: MatAutocompleteSelectedEvent): void { public onSelectFilter(event: MatAutocompleteSelectedEvent): void {
this.selectedFilters.push(event.option.value); this.selectedFilters.push(
this.updateFilter(); this.allFilters.find((filter) => {
return filter.id === event.option.value;
})
);
this.updateFilters();
this.searchInput.nativeElement.value = ''; this.searchInput.nativeElement.value = '';
this.searchControl.setValue(null); this.searchControl.setValue(null);
} }
@ -114,8 +107,8 @@ export class ActivitiesFilterComponent implements OnChanges, OnDestroy {
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private updateFilter() { private getGroupedFilters(searchTerm?: string) {
this.filters$.next( const filterGroupsMap = groupBy(
this.allFilters this.allFilters
.filter((filter) => { .filter((filter) => {
// Filter selected filters // Filter selected filters
@ -123,9 +116,42 @@ export class ActivitiesFilterComponent implements OnChanges, OnDestroy {
return selectedFilter.id === filter.id; return selectedFilter.id === filter.id;
}); });
}) })
.sort((a, b) => a.label.localeCompare(b.label)) .filter((filter) => {
if (searchTerm) {
// Filter by search term
return filter.label
.toLowerCase()
.includes(searchTerm.toLowerCase());
}
return filter;
})
.sort((a, b) => a.label.localeCompare(b.label)),
(filter) => {
return filter.type;
}
); );
const filterGroups: FilterGroup[] = [];
for (const type of Object.keys(filterGroupsMap)) {
filterGroups.push({
name: <Filter['type']>type,
filters: filterGroupsMap[type]
});
}
return filterGroups.map((filterGroup) => {
return {
...filterGroup,
filters: filterGroup.filters
};
});
}
private updateFilters() {
this.filterGroups$.next(this.getGroupedFilters());
// Emit an array with a new reference // Emit an array with a new reference
this.valueChanged.emit([...this.selectedFilters]); this.valueChanged.emit([...this.selectedFilters]);
} }

2
libs/ui/src/lib/activities-table/activities-table.component.ts

@ -107,7 +107,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
if (this.activities) { if (this.activities) {
this.allFilters = this.getSearchableFieldValues(this.activities).map( this.allFilters = this.getSearchableFieldValues(this.activities).map(
(label) => { (label) => {
return { label, id: label, type: 'tag' }; return { label, id: label, type: 'TAG' };
} }
); );

Loading…
Cancel
Save