diff --git a/CHANGELOG.md b/CHANGELOG.md index d45f93e66..d2615bc00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Aligned the x-axis of the dividend and investment timeline charts on the analysis page - Improved the check for duplicates in the preview step of the activities import (regardless of the account) - Improved the check for duplicates in the preview step of the import dividends dialog (regardless of the account) - Extended the activities import to reuse an existing account of the user by name and currency 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 424f1bf5b..d3e3d8a31 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 @@ -51,10 +51,11 @@ import { IonIcon } from '@ionic/angular/standalone'; import { SymbolProfile } from '@prisma/client'; import { addIcons } from 'ionicons'; import { copyOutline, ellipsisVertical } from 'ionicons/icons'; -import { isNumber, sortBy } from 'lodash'; +import { isNumber, keyBy, sortBy, union } from 'lodash'; import ms from 'ms'; import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import { forkJoin } from 'rxjs'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -254,53 +255,75 @@ export class GfAnalysisPageComponent implements OnInit { this.isLoadingDividendTimelineChart = true; this.isLoadingInvestmentTimelineChart = true; - this.dataService - .fetchDividends({ + forkJoin({ + dividends: this.dataService.fetchDividends({ filters: this.userService.getFilters(), groupBy: this.mode(), range: this.user?.settings?.dateRange ?? DEFAULT_DATE_RANGE - }) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(({ dividends }) => { - this.dividendsByGroup = dividends; - - this.isLoadingDividendTimelineChart = false; - - this.changeDetectorRef.markForCheck(); - }); - - this.dataService - .fetchInvestments({ + }), + investments: this.dataService.fetchInvestments({ filters: this.userService.getFilters(), groupBy: this.mode(), range: this.user?.settings?.dateRange ?? DEFAULT_DATE_RANGE }) + }) .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(({ investments, savingsRate, streaks }) => { - this.investmentsByGroup = investments; - this.savingsRatePerMonth = savingsRate; - this.streaks = streaks; - this.unitCurrentStreak = - this.mode() === 'year' - ? this.streaks?.currentStreak === 1 - ? translate('YEAR') - : translate('YEARS') - : this.streaks?.currentStreak === 1 - ? translate('MONTH') - : translate('MONTHS'); - this.unitLongestStreak = - this.mode() === 'year' - ? this.streaks?.longestStreak === 1 - ? translate('YEAR') - : translate('YEARS') - : this.streaks?.longestStreak === 1 - ? translate('MONTH') - : translate('MONTHS'); - - this.isLoadingInvestmentTimelineChart = false; + .subscribe( + ({ + dividends: { dividends }, + investments: { investments, savingsRate, streaks } + }) => { + // Expand both timelines to the union of their groups so that the + // charts share the same axis, independent of whether a dividend or + // an investment has been tracked in a given group + const dividendByDate = keyBy(dividends, 'date'); + const investmentByDate = keyBy(investments, 'date'); + + const dates = sortBy( + union(Object.keys(dividendByDate), Object.keys(investmentByDate)) + ); - this.changeDetectorRef.markForCheck(); - }); + this.dividendsByGroup = dates.map((date) => { + return { + date, + investment: dividendByDate[date]?.investment ?? 0 + }; + }); + + this.investmentsByGroup = dates.map((date) => { + return { + date, + investment: investmentByDate[date]?.investment ?? 0 + }; + }); + + this.savingsRatePerMonth = savingsRate; + this.streaks = streaks; + + this.unitCurrentStreak = + this.mode() === 'year' + ? this.streaks?.currentStreak === 1 + ? translate('YEAR') + : translate('YEARS') + : this.streaks?.currentStreak === 1 + ? translate('MONTH') + : translate('MONTHS'); + + this.unitLongestStreak = + this.mode() === 'year' + ? this.streaks?.longestStreak === 1 + ? translate('YEAR') + : translate('YEARS') + : this.streaks?.longestStreak === 1 + ? translate('MONTH') + : translate('MONTHS'); + + this.isLoadingDividendTimelineChart = false; + this.isLoadingInvestmentTimelineChart = false; + + this.changeDetectorRef.markForCheck(); + } + ); } private update() {