diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 109b5cca59..6f3d0df202 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -596,6 +596,19 @@ export class PortfolioController { performanceInformation.performance, ['dividendInBaseCurrency', 'netPerformance'] ); + + if (groupBy === 'year') { + performanceInformation.chart = performanceInformation.chart.map( + (item) => { + return nullifyValuesInObject(item, [ + 'netPerformance', + 'netPerformanceInPercentage', + 'netPerformanceInPercentageWithCurrencyEffect', + 'netPerformanceWithCurrencyEffect' + ]); + } + ); + } } return performanceInformation; diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts index 460ac9de8e..1d532901ec 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts @@ -93,6 +93,7 @@ export class GfAnalysisPageComponent implements OnInit { protected isLoadingDividendTimelineChart: boolean; protected isLoadingInvestmentChart: boolean; protected isLoadingInvestmentTimelineChart: boolean; + protected isLoadingPerformanceTimelineChart: boolean; protected isLoadingPortfolioPrompt: boolean; protected readonly mode = signal('month'); protected readonly modeOptions: ToggleOption[] = [ @@ -103,6 +104,8 @@ export class GfAnalysisPageComponent implements OnInit { protected readonly PerformanceCalculationType = PerformanceCalculationType; protected performanceDataItems: LineChartItem[]; protected performanceDataItemsInPercentage: LineChartItem[]; + protected performancePercentagesByYear: InvestmentItem[]; + protected readonly performanceTimelineDataLabel = $localize`Net Performance`; protected readonly portfolioEvolutionDataLabel = $localize`Invested Capital`; protected precision = 2; 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() { this.isLoadingInvestmentChart = true; @@ -420,6 +454,10 @@ export class GfAnalysisPageComponent implements OnInit { this.fetchDividendsAndInvestments(); + if (this.user?.settings?.isExperimentalFeatures) { + this.fetchPerformanceByYear(); + } + this.changeDetectorRef.markForCheck(); } diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html index c39b774f4b..6f05dae069 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -430,6 +430,35 @@ + @if (user?.settings?.isExperimentalFeatures) { +
+
+
+
+ Performance Timeline + @if (user?.subscription?.type === 'Basic') { + + } +
+
+
+ +
+
+
+ } +
diff --git a/libs/ui/src/lib/services/data.service.ts b/libs/ui/src/lib/services/data.service.ts index f18b3eef13..d7cea71864 100644 --- a/libs/ui/src/lib/services/data.service.ts +++ b/libs/ui/src/lib/services/data.service.ts @@ -728,14 +728,21 @@ export class DataService { public fetchPortfolioPerformance({ filters, + groupBy, range, withExcludedAccounts = false }: { filters?: Filter[]; + groupBy?: Extract; range: DateRange; withExcludedAccounts?: boolean; }): Observable { let params = this.buildFiltersAsQueryParams({ filters }); + + if (groupBy) { + params = params.append('groupBy', groupBy); + } + params = params.append('range', range); if (withExcludedAccounts) {