diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index 9aab27dc0..28f5a6f9d 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -12,7 +12,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; -import { Market, MarketAdvanced } from '@ghostfolio/common/types'; +import { MarketAdvanced } from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; @@ -83,9 +83,7 @@ export class GfAllocationsPageComponent implements OnInit { > & { etfProvider: string; exchange?: string; value: number }; }; public isLoading = false; - public markets: { - [key in Market]: { id: Market; valueInPercentage: number }; - }; + public markets: PortfolioDetails['markets']; public marketsAdvanced: { [key in MarketAdvanced]: { id: MarketAdvanced; @@ -187,7 +185,7 @@ export class GfAllocationsPageComponent implements OnInit { public onAccountChartClicked({ accountId }: { accountId: string }) { if (accountId && accountId !== UNKNOWN_KEY) { - this.router.navigate([], { + void this.router.navigate([], { queryParams: { accountId, accountDetailDialog: true } }); } @@ -195,7 +193,7 @@ export class GfAllocationsPageComponent implements OnInit { public onSymbolChartClicked({ dataSource, symbol }: AssetProfileIdentifier) { if (dataSource && symbol) { - this.router.navigate([], { + void this.router.navigate([], { queryParams: { dataSource, symbol, holdingDetailDialog: true } }); } @@ -226,9 +224,9 @@ export class GfAllocationsPageComponent implements OnInit { name }: { assetSubClass: PortfolioPosition['assetProfile']['assetSubClass']; - name: string; + name?: string; }) { - if (assetSubClass === 'ETF') { + if (assetSubClass === 'ETF' && name) { const [firstWord] = name.split(' '); return firstWord; } @@ -298,7 +296,7 @@ export class GfAllocationsPageComponent implements OnInit { this.platforms = {}; this.portfolioDetails = { accounts: {}, - createdAt: undefined, + createdAt: new Date(), holdings: {}, platforms: {}, summary: undefined @@ -327,7 +325,7 @@ export class GfAllocationsPageComponent implements OnInit { let value = 0; if (this.showValuesInPercentage()) { - value = valueInPercentage; + value = valueInPercentage ?? 0; } else { value = valueInBaseCurrency; } @@ -347,18 +345,18 @@ export class GfAllocationsPageComponent implements OnInit { if (this.showValuesInPercentage()) { value = position.allocationInPercentage; } else { - value = position.valueInBaseCurrency; + value = position.valueInBaseCurrency ?? 0; } this.holdings[symbol] = { value, assetClass: position.assetProfile.assetClass || (UNKNOWN_KEY as AssetClass), - assetClassLabel: position.assetProfile.assetClassLabel || UNKNOWN_KEY, + assetClassLabel: position.assetProfile.assetClassLabel ?? UNKNOWN_KEY, assetSubClass: position.assetProfile.assetSubClass || (UNKNOWN_KEY as AssetSubClass), assetSubClassLabel: - position.assetProfile.assetSubClassLabel || UNKNOWN_KEY, + position.assetProfile.assetSubClassLabel ?? UNKNOWN_KEY, currency: this.extractCurrency(position.assetProfile), etfProvider: this.extractEtfProvider({ assetSubClass: position.assetProfile.assetSubClass, @@ -373,53 +371,45 @@ export class GfAllocationsPageComponent implements OnInit { if (position.assetProfile.countries.length > 0) { for (const country of position.assetProfile.countries) { const { code, continent, weight } = country; - - if (this.continents[continent]?.value) { - this.continents[continent].value += - weight * - (isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : position.valueInPercentage); + const val = + (isNumber(position.valueInBaseCurrency) + ? position.valueInBaseCurrency + : position.valueInPercentage) ?? 0; + + const continentData = this.continents[continent]; + if (continentData) { + continentData.value += weight * val; } else { this.continents[continent] = { name: translate(continent), - value: - weight * - (isNumber(position.valueInBaseCurrency) - ? this.portfolioDetails.holdings[symbol].valueInBaseCurrency - : this.portfolioDetails.holdings[symbol].valueInPercentage) + value: weight * val }; } - if (this.countries[code]?.value) { - this.countries[code].value += - weight * - (isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : position.valueInPercentage); + const countryData = this.countries[code]; + if (countryData) { + countryData.value += weight * val; } else { this.countries[code] = { name: getCountryName({ code }), - value: - weight * - (isNumber(position.valueInBaseCurrency) - ? this.portfolioDetails.holdings[symbol].valueInBaseCurrency - : this.portfolioDetails.holdings[symbol].valueInPercentage) + value: weight * val }; } } } else { - this.continents[UNKNOWN_KEY].value += isNumber( - position.valueInBaseCurrency - ) - ? this.portfolioDetails.holdings[symbol].valueInBaseCurrency - : this.portfolioDetails.holdings[symbol].valueInPercentage; - - this.countries[UNKNOWN_KEY].value += isNumber( - position.valueInBaseCurrency - ) - ? this.portfolioDetails.holdings[symbol].valueInBaseCurrency - : this.portfolioDetails.holdings[symbol].valueInPercentage; + const val = + (isNumber(position.valueInBaseCurrency) + ? position.valueInBaseCurrency + : position.valueInPercentage) ?? 0; + + const continentData = this.continents[UNKNOWN_KEY]; + if (continentData) { + continentData.value += val; + } + const countryData = this.countries[UNKNOWN_KEY]; + if (countryData) { + countryData.value += val; + } } if (position.assetProfile.holdings.length > 0) { @@ -429,21 +419,17 @@ export class GfAllocationsPageComponent implements OnInit { valueInBaseCurrency } of position.assetProfile.holdings) { const normalizedAssetName = this.normalizeAssetName(name); + const val = isNumber(valueInBaseCurrency) + ? valueInBaseCurrency + : allocationInPercentage * (position.valueInPercentage ?? 0); - if (this.topHoldingsMap[normalizedAssetName]?.value) { - this.topHoldingsMap[normalizedAssetName].value += isNumber( - valueInBaseCurrency - ) - ? valueInBaseCurrency - : allocationInPercentage * - this.portfolioDetails.holdings[symbol].valueInPercentage; + const holdingData = this.topHoldingsMap[normalizedAssetName]; + if (holdingData) { + holdingData.value += val; } else { this.topHoldingsMap[normalizedAssetName] = { name, - value: isNumber(valueInBaseCurrency) - ? valueInBaseCurrency - : allocationInPercentage * - this.portfolioDetails.holdings[symbol].valueInPercentage + value: val }; } } @@ -452,30 +438,31 @@ export class GfAllocationsPageComponent implements OnInit { if (position.assetProfile.sectors.length > 0) { for (const sector of position.assetProfile.sectors) { const { name, weight } = sector; - - if (this.sectors[name]?.value) { - this.sectors[name].value += - weight * - (isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : position.valueInPercentage); + const val = + (isNumber(position.valueInBaseCurrency) + ? position.valueInBaseCurrency + : position.valueInPercentage) ?? 0; + + const sectorData = this.sectors[name]; + if (sectorData) { + sectorData.value += weight * val; } else { this.sectors[name] = { name: translate(name), - value: - weight * - (isNumber(position.valueInBaseCurrency) - ? this.portfolioDetails.holdings[symbol].valueInBaseCurrency - : this.portfolioDetails.holdings[symbol].valueInPercentage) + value: weight * val }; } } } else { - this.sectors[UNKNOWN_KEY].value += isNumber( - position.valueInBaseCurrency - ) - ? this.portfolioDetails.holdings[symbol].valueInBaseCurrency - : this.portfolioDetails.holdings[symbol].valueInPercentage; + const val = + (isNumber(position.valueInBaseCurrency) + ? position.valueInBaseCurrency + : position.valueInPercentage) ?? 0; + + const sectorData = this.sectors[UNKNOWN_KEY]; + if (sectorData) { + sectorData.value += val; + } } if (this.holdings[symbol].assetSubClass === 'ETF') { @@ -484,23 +471,26 @@ export class GfAllocationsPageComponent implements OnInit { this.symbols[prettifySymbol(symbol)] = { dataSource: position.assetProfile.dataSource, - name: position.assetProfile.name, + name: position.assetProfile.name ?? '', symbol: prettifySymbol(symbol), - value: isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : position.valueInPercentage + value: + (isNumber(position.valueInBaseCurrency) + ? position.valueInBaseCurrency + : position.valueInPercentage) ?? 0 }; } this.markets = this.portfolioDetails.markets; - Object.values(this.portfolioDetails.marketsAdvanced).forEach( - ({ id, valueInBaseCurrency, valueInPercentage }) => { - this.marketsAdvanced[id].value = isNumber(valueInBaseCurrency) - ? valueInBaseCurrency - : valueInPercentage; - } - ); + if (this.portfolioDetails.marketsAdvanced) { + Object.values(this.portfolioDetails.marketsAdvanced).forEach( + ({ id, valueInBaseCurrency, valueInPercentage }) => { + this.marketsAdvanced[id].value = isNumber(valueInBaseCurrency) + ? valueInBaseCurrency + : valueInPercentage; + } + ); + } for (const [ id, @@ -509,7 +499,7 @@ export class GfAllocationsPageComponent implements OnInit { let value = 0; if (this.showValuesInPercentage()) { - value = valueInPercentage; + value = valueInPercentage ?? 0; } else { value = valueInBaseCurrency; } @@ -522,12 +512,11 @@ export class GfAllocationsPageComponent implements OnInit { } this.topHoldings = Object.values(this.topHoldingsMap) - .map(({ name, value }) => { + .map(({ name, value }): HoldingWithParents => { if (this.showValuesInPercentage()) { return { name, - allocationInPercentage: value, - valueInBaseCurrency: null + allocationInPercentage: value }; } @@ -547,11 +536,12 @@ export class GfAllocationsPageComponent implements OnInit { } ); - return currentParentHolding + return currentParentHolding && + isNumber(currentParentHolding.valueInBaseCurrency) ? { allocationInPercentage: currentParentHolding.valueInBaseCurrency / value, - name: holding.assetProfile.name, + name: holding.assetProfile.name ?? '', position: holding, symbol: prettifySymbol(symbol), valueInBaseCurrency: @@ -611,7 +601,7 @@ export class GfAllocationsPageComponent implements OnInit { .afterClosed() .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { - this.router.navigate(['.'], { relativeTo: this.route }); + void this.router.navigate(['.'], { relativeTo: this.route }); }); } diff --git a/libs/common/src/lib/interfaces/holding.interface.ts b/libs/common/src/lib/interfaces/holding.interface.ts index e963bc5a7..c63729a34 100644 --- a/libs/common/src/lib/interfaces/holding.interface.ts +++ b/libs/common/src/lib/interfaces/holding.interface.ts @@ -1,5 +1,5 @@ export interface Holding { allocationInPercentage: number; name: string; - valueInBaseCurrency: number; + valueInBaseCurrency?: number; }