Browse Source

Add performance timeline

pull/8103/head
Thomas Kaul 5 days ago
parent
commit
3627267d01
  1. 13
      apps/api/src/app/portfolio/portfolio.controller.ts
  2. 38
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
  3. 29
      apps/client/src/app/pages/portfolio/analysis/analysis-page.html
  4. 7
      libs/ui/src/lib/services/data.service.ts

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

@ -596,6 +596,19 @@ export class PortfolioController {
performanceInformation.performance, performanceInformation.performance,
['dividendInBaseCurrency', 'netPerformance'] ['dividendInBaseCurrency', 'netPerformance']
); );
if (groupBy === 'year') {
performanceInformation.chart = performanceInformation.chart.map(
(item) => {
return nullifyValuesInObject(item, [
'netPerformance',
'netPerformanceInPercentage',
'netPerformanceInPercentageWithCurrencyEffect',
'netPerformanceWithCurrencyEffect'
]);
}
);
}
} }
return performanceInformation; return performanceInformation;

38
apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts

@ -93,6 +93,7 @@ export class GfAnalysisPageComponent implements OnInit {
protected isLoadingDividendTimelineChart: boolean; protected isLoadingDividendTimelineChart: boolean;
protected isLoadingInvestmentChart: boolean; protected isLoadingInvestmentChart: boolean;
protected isLoadingInvestmentTimelineChart: boolean; protected isLoadingInvestmentTimelineChart: boolean;
protected isLoadingPerformanceTimelineChart: boolean;
protected isLoadingPortfolioPrompt: boolean; protected isLoadingPortfolioPrompt: boolean;
protected readonly mode = signal<GroupBy>('month'); protected readonly mode = signal<GroupBy>('month');
protected readonly modeOptions: ToggleOption<GroupBy>[] = [ protected readonly modeOptions: ToggleOption<GroupBy>[] = [
@ -103,6 +104,8 @@ export class GfAnalysisPageComponent implements OnInit {
protected readonly PerformanceCalculationType = PerformanceCalculationType; protected readonly PerformanceCalculationType = PerformanceCalculationType;
protected performanceDataItems: LineChartItem[]; protected performanceDataItems: LineChartItem[];
protected performanceDataItemsInPercentage: LineChartItem[]; protected performanceDataItemsInPercentage: LineChartItem[];
protected performancePercentagesByYear: InvestmentItem[];
protected readonly performanceTimelineDataLabel = $localize`Net Performance`;
protected readonly portfolioEvolutionDataLabel = $localize`Invested Capital`; protected readonly portfolioEvolutionDataLabel = $localize`Invested Capital`;
protected precision = 2; protected precision = 2;
protected savingsRatePerMonth: number | undefined; protected savingsRatePerMonth: number | undefined;
@ -312,6 +315,37 @@ export class GfAnalysisPageComponent implements OnInit {
); );
} }
private fetchPerformanceByYear() {
this.isLoadingPerformanceTimelineChart = true;
this.dataService
.fetchPortfolioPerformance({
filters: this.userService.getFilters(),
groupBy: 'year',
range: this.user?.settings?.dateRange ?? DEFAULT_DATE_RANGE
})
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ chart }) => {
this.performancePercentagesByYear = [];
for (const {
date,
netPerformanceInPercentageWithCurrencyEffect
} of chart ?? []) {
if (isNumber(netPerformanceInPercentageWithCurrencyEffect)) {
this.performancePercentagesByYear.push({
date,
investment: netPerformanceInPercentageWithCurrencyEffect
});
}
}
this.isLoadingPerformanceTimelineChart = false;
this.changeDetectorRef.markForCheck();
});
}
private update() { private update() {
this.isLoadingInvestmentChart = true; this.isLoadingInvestmentChart = true;
@ -420,6 +454,10 @@ export class GfAnalysisPageComponent implements OnInit {
this.fetchDividendsAndInvestments(); this.fetchDividendsAndInvestments();
if (this.user?.settings?.isExperimentalFeatures) {
this.fetchPerformanceByYear();
}
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
} }

29
apps/client/src/app/pages/portfolio/analysis/analysis-page.html

@ -430,6 +430,35 @@
</div> </div>
</div> </div>
@if (user?.settings?.isExperimentalFeatures) {
<div class="mb-5 row">
<div class="col-lg">
<div class="align-items-center d-flex mb-4">
<div
class="align-items-center d-flex flex-grow-1 h5 mb-0 text-truncate"
>
<span i18n>Performance Timeline</span>
@if (user?.subscription?.type === 'Basic') {
<gf-premium-indicator class="ml-1" />
}
</div>
</div>
<div class="chart-container">
<gf-investment-chart
class="h-100"
groupBy="year"
[benchmarkDataItems]="performancePercentagesByYear"
[benchmarkDataLabel]="performanceTimelineDataLabel"
[colorScheme]="user?.settings?.colorScheme"
[isInPercentage]="true"
[isLoading]="isLoadingPerformanceTimelineChart"
[locale]="user?.settings?.locale"
/>
</div>
</div>
</div>
}
<div class="mb-5 row"> <div class="mb-5 row">
<div class="col-lg"> <div class="col-lg">
<div class="align-items-center d-flex mb-4"> <div class="align-items-center d-flex mb-4">

7
libs/ui/src/lib/services/data.service.ts

@ -728,14 +728,21 @@ export class DataService {
public fetchPortfolioPerformance({ public fetchPortfolioPerformance({
filters, filters,
groupBy,
range, range,
withExcludedAccounts = false withExcludedAccounts = false
}: { }: {
filters?: Filter[]; filters?: Filter[];
groupBy?: Extract<GroupBy, 'year'>;
range: DateRange; range: DateRange;
withExcludedAccounts?: boolean; withExcludedAccounts?: boolean;
}): Observable<PortfolioPerformanceResponse> { }): Observable<PortfolioPerformanceResponse> {
let params = this.buildFiltersAsQueryParams({ filters }); let params = this.buildFiltersAsQueryParams({ filters });
if (groupBy) {
params = params.append('groupBy', groupBy);
}
params = params.append('range', range); params = params.append('range', range);
if (withExcludedAccounts) { if (withExcludedAccounts) {

Loading…
Cancel
Save