Feature/add filters to analytics page (#1559)
* Add filters to analysis page * Update changelog
This commit is contained in:
@@ -8,6 +8,7 @@ import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||
import {
|
||||
Filter,
|
||||
HistoricalDataItem,
|
||||
Position,
|
||||
User
|
||||
@@ -15,12 +16,13 @@ import {
|
||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import { DateRange, GroupBy, ToggleOption } from '@ghostfolio/common/types';
|
||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||
import { translate } from '@ghostfolio/ui/i18n';
|
||||
import { AssetClass, DataSource, SymbolProfile } from '@prisma/client';
|
||||
import { differenceInDays } from 'date-fns';
|
||||
import { sortBy } from 'lodash';
|
||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
host: { class: 'page' },
|
||||
@@ -29,6 +31,8 @@ import { takeUntil } from 'rxjs/operators';
|
||||
templateUrl: './analysis-page.html'
|
||||
})
|
||||
export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
public activeFilters: Filter[] = [];
|
||||
public allFilters: Filter[];
|
||||
public benchmarkDataItems: HistoricalDataItem[] = [];
|
||||
public benchmarks: Partial<SymbolProfile>[];
|
||||
public bottom3: Position[];
|
||||
@@ -37,6 +41,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
public deviceType: string;
|
||||
public dividendsByMonth: InvestmentItem[];
|
||||
public dividendTimelineDataLabel = $localize`Dividend`;
|
||||
public filters$ = new Subject<Filter[]>();
|
||||
public firstOrderDate: Date;
|
||||
public hasImpersonationId: boolean;
|
||||
public investments: InvestmentItem[];
|
||||
@@ -50,6 +55,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
];
|
||||
public performanceDataItems: HistoricalDataItem[];
|
||||
public performanceDataItemsInPercentage: HistoricalDataItem[];
|
||||
public placeholder = '';
|
||||
public portfolioEvolutionDataLabel = $localize`Deposit`;
|
||||
public top3: Position[];
|
||||
public user: User;
|
||||
@@ -95,12 +101,63 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
this.hasImpersonationId = !!aId;
|
||||
});
|
||||
|
||||
this.filters$
|
||||
.pipe(
|
||||
distinctUntilChanged(),
|
||||
map((filters) => {
|
||||
this.activeFilters = filters;
|
||||
this.placeholder =
|
||||
this.activeFilters.length <= 0
|
||||
? $localize`Filter by account or tag...`
|
||||
: '';
|
||||
|
||||
this.update();
|
||||
}),
|
||||
takeUntil(this.unsubscribeSubject)
|
||||
)
|
||||
.subscribe(() => {});
|
||||
|
||||
this.userService.stateChanged
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((state) => {
|
||||
if (state?.user) {
|
||||
this.user = state.user;
|
||||
|
||||
const accountFilters: Filter[] = this.user.accounts
|
||||
.filter(({ accountType }) => {
|
||||
return accountType === 'SECURITIES';
|
||||
})
|
||||
.map(({ id, name }) => {
|
||||
return {
|
||||
id,
|
||||
label: name,
|
||||
type: 'ACCOUNT'
|
||||
};
|
||||
});
|
||||
|
||||
const assetClassFilters: Filter[] = [];
|
||||
for (const assetClass of Object.keys(AssetClass)) {
|
||||
assetClassFilters.push({
|
||||
id: assetClass,
|
||||
label: translate(assetClass),
|
||||
type: 'ASSET_CLASS'
|
||||
});
|
||||
}
|
||||
|
||||
const tagFilters: Filter[] = this.user.tags.map(({ id, name }) => {
|
||||
return {
|
||||
id,
|
||||
label: name,
|
||||
type: 'TAG'
|
||||
};
|
||||
});
|
||||
|
||||
this.allFilters = [
|
||||
...accountFilters,
|
||||
...assetClassFilters,
|
||||
...tagFilters
|
||||
];
|
||||
|
||||
this.update();
|
||||
}
|
||||
});
|
||||
@@ -198,6 +255,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
|
||||
this.dataService
|
||||
.fetchPortfolioPerformance({
|
||||
filters: this.activeFilters,
|
||||
range: this.user?.settings?.dateRange
|
||||
})
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
@@ -235,6 +293,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
|
||||
this.dataService
|
||||
.fetchDividends({
|
||||
filters: this.activeFilters,
|
||||
groupBy: 'month',
|
||||
range: this.user?.settings?.dateRange
|
||||
})
|
||||
@@ -247,6 +306,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
|
||||
this.dataService
|
||||
.fetchInvestments({
|
||||
filters: this.activeFilters,
|
||||
groupBy: 'month',
|
||||
range: this.user?.settings?.dateRange
|
||||
})
|
||||
@@ -258,7 +318,10 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
});
|
||||
|
||||
this.dataService
|
||||
.fetchPositions({ range: this.user?.settings?.dateRange })
|
||||
.fetchPositions({
|
||||
filters: this.activeFilters,
|
||||
range: this.user?.settings?.dateRange
|
||||
})
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ positions }) => {
|
||||
const positionsSorted = sortBy(
|
||||
|
@@ -8,6 +8,12 @@
|
||||
(change)="onChangeDateRange($event.value)"
|
||||
></gf-toggle>
|
||||
</div>
|
||||
<gf-activities-filter
|
||||
[allFilters]="allFilters"
|
||||
[isLoading]="isLoadingBenchmarkComparator || isLoadingInvestmentChart"
|
||||
[placeholder]="placeholder"
|
||||
(valueChanged)="filters$.next($event)"
|
||||
></gf-activities-filter>
|
||||
<div class="mb-5 row">
|
||||
<div class="col-lg">
|
||||
<gf-benchmark-comparator
|
||||
|
@@ -4,6 +4,7 @@ import { MatCardModule } from '@angular/material/card';
|
||||
import { GfBenchmarkComparatorModule } from '@ghostfolio/client/components/benchmark-comparator/benchmark-comparator.module';
|
||||
import { GfInvestmentChartModule } from '@ghostfolio/client/components/investment-chart/investment-chart.module';
|
||||
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
||||
import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module';
|
||||
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
|
||||
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
@@ -16,6 +17,7 @@ import { AnalysisPageComponent } from './analysis-page.component';
|
||||
imports: [
|
||||
AnalysisPageRoutingModule,
|
||||
CommonModule,
|
||||
GfActivitiesFilterModule,
|
||||
GfBenchmarkComparatorModule,
|
||||
GfInvestmentChartModule,
|
||||
GfPremiumIndicatorModule,
|
||||
|
@@ -102,14 +102,20 @@ export class DataService {
|
||||
}
|
||||
|
||||
public fetchDividends({
|
||||
filters,
|
||||
groupBy = 'month',
|
||||
range
|
||||
}: {
|
||||
filters?: Filter[];
|
||||
groupBy?: GroupBy;
|
||||
range: DateRange;
|
||||
}) {
|
||||
let params = this.buildFiltersAsQueryParams({ filters });
|
||||
params = params.append('groupBy', groupBy);
|
||||
params = params.append('range', range);
|
||||
|
||||
return this.http.get<PortfolioDividends>('/api/v1/portfolio/dividends', {
|
||||
params: { groupBy, range }
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
@@ -191,15 +197,21 @@ export class DataService {
|
||||
}
|
||||
|
||||
public fetchInvestments({
|
||||
filters,
|
||||
groupBy = 'month',
|
||||
range
|
||||
}: {
|
||||
filters?: Filter[];
|
||||
groupBy?: GroupBy;
|
||||
range: DateRange;
|
||||
}) {
|
||||
let params = this.buildFiltersAsQueryParams({ filters });
|
||||
params = params.append('groupBy', groupBy);
|
||||
params = params.append('range', range);
|
||||
|
||||
return this.http.get<PortfolioInvestments>(
|
||||
'/api/v1/portfolio/investments',
|
||||
{ params: { groupBy, range } }
|
||||
{ params }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -224,12 +236,17 @@ export class DataService {
|
||||
}
|
||||
|
||||
public fetchPositions({
|
||||
filters,
|
||||
range
|
||||
}: {
|
||||
filters?: Filter[];
|
||||
range: DateRange;
|
||||
}): Observable<PortfolioPositions> {
|
||||
let params = this.buildFiltersAsQueryParams({ filters });
|
||||
params = params.append('range', range);
|
||||
|
||||
return this.http.get<PortfolioPositions>('/api/v1/portfolio/positions', {
|
||||
params: { range }
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
@@ -284,12 +301,19 @@ export class DataService {
|
||||
}
|
||||
|
||||
public fetchPortfolioPerformance({
|
||||
filters,
|
||||
range
|
||||
}: {
|
||||
filters?: Filter[];
|
||||
range: DateRange;
|
||||
}): Observable<PortfolioPerformanceResponse> {
|
||||
let params = this.buildFiltersAsQueryParams({ filters });
|
||||
params = params.append('range', range);
|
||||
|
||||
return this.http
|
||||
.get<any>(`/api/v2/portfolio/performance`, { params: { range } })
|
||||
.get<any>(`/api/v2/portfolio/performance`, {
|
||||
params
|
||||
})
|
||||
.pipe(
|
||||
map((response) => {
|
||||
if (response.firstOrderDate) {
|
||||
|
Reference in New Issue
Block a user