diff --git a/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts b/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts index ddc5fbf37..8cfd550af 100644 --- a/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts @@ -1,3 +1,4 @@ +import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { AssetClass, AssetSubClass } from '@prisma/client'; @@ -19,6 +20,7 @@ export interface PortfolioPositionDetail { netPerformancePercent: number; orders: OrderWithAccount[]; quantity: number; + SymbolProfile: EnhancedSymbolProfile; symbol: string; transactionCount: number; value: number; diff --git a/apps/api/src/app/portfolio/portfolio.service-new.ts b/apps/api/src/app/portfolio/portfolio.service-new.ts index 04803d14b..d0db4169f 100644 --- a/apps/api/src/app/portfolio/portfolio.service-new.ts +++ b/apps/api/src/app/portfolio/portfolio.service-new.ts @@ -432,6 +432,7 @@ export class PortfolioServiceNew { orders: [], quantity: undefined, symbol: aSymbol, + SymbolProfile: undefined, transactionCount: undefined, value: undefined }; @@ -439,8 +440,11 @@ export class PortfolioServiceNew { const assetClass = orders[0].SymbolProfile?.assetClass; const assetSubClass = orders[0].SymbolProfile?.assetSubClass; - const positionCurrency = orders[0].currency; const name = orders[0].SymbolProfile?.name ?? ''; + const positionCurrency = orders[0].currency; + const [SymbolProfile] = await this.symbolProfileService.getSymbolProfiles([ + aSymbol + ]); const portfolioOrders: PortfolioOrder[] = orders .filter((order) => { @@ -569,6 +573,7 @@ export class PortfolioServiceNew { name, netPerformance, orders, + SymbolProfile, transactionCount, averagePrice: averagePrice.toNumber(), grossPerformancePercent: @@ -628,6 +633,7 @@ export class PortfolioServiceNew { minPrice, name, orders, + SymbolProfile, averagePrice: 0, currency: currentData[aSymbol]?.currency, firstBuyDate: undefined, diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 4b02e4d0a..09f10cf84 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -420,6 +420,7 @@ export class PortfolioService { orders: [], quantity: undefined, symbol: aSymbol, + SymbolProfile: undefined, transactionCount: undefined, value: undefined }; @@ -427,8 +428,11 @@ export class PortfolioService { const assetClass = orders[0].SymbolProfile?.assetClass; const assetSubClass = orders[0].SymbolProfile?.assetSubClass; - const positionCurrency = orders[0].currency; const name = orders[0].SymbolProfile?.name ?? ''; + const positionCurrency = orders[0].currency; + const [SymbolProfile] = await this.symbolProfileService.getSymbolProfiles([ + aSymbol + ]); const portfolioOrders: PortfolioOrder[] = orders .filter((order) => { @@ -555,6 +559,7 @@ export class PortfolioService { name, netPerformance, orders, + SymbolProfile, transactionCount, averagePrice: averagePrice.toNumber(), grossPerformancePercent: position.grossPerformancePercentage.toNumber(), @@ -613,6 +618,7 @@ export class PortfolioService { minPrice, name, orders, + SymbolProfile, averagePrice: 0, currency: currentData[aSymbol]?.currency, firstBuyDate: undefined, diff --git a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts index 02563afba..0db04ef9d 100644 --- a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts +++ b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts @@ -11,7 +11,7 @@ import { DataService } from '@ghostfolio/client/services/data.service'; import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface'; -import { AssetSubClass } from '@prisma/client'; +import { AssetSubClass, SymbolProfile } from '@prisma/client'; import { format, isSameMonth, isToday, parseISO } from 'date-fns'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -29,6 +29,9 @@ export class PositionDetailDialog implements OnDestroy, OnInit { public assetSubClass: AssetSubClass; public averagePrice: number; public benchmarkDataItems: LineChartItem[]; + public countries: { + [code: string]: { name: string; value: number }; + }; public currency: string; public firstBuyDate: string; public grossPerformance: number; @@ -44,7 +47,11 @@ export class PositionDetailDialog implements OnDestroy, OnInit { public orders: OrderWithAccount[]; public quantity: number; public quantityPrecision = 2; + public sectors: { + [name: string]: { name: string; value: number }; + }; public symbol: string; + public SymbolProfile: SymbolProfile; public transactionCount: number; public value: number; @@ -83,12 +90,14 @@ export class PositionDetailDialog implements OnDestroy, OnInit { orders, quantity, symbol, + SymbolProfile, transactionCount, value }) => { this.assetSubClass = assetSubClass; this.averagePrice = averagePrice; this.benchmarkDataItems = []; + this.countries = {}; this.currency = currency; this.firstBuyDate = firstBuyDate; this.grossPerformance = grossPerformance; @@ -115,10 +124,30 @@ export class PositionDetailDialog implements OnDestroy, OnInit { this.netPerformancePercent = netPerformancePercent; this.orders = orders; this.quantity = quantity; + this.sectors = {}; this.symbol = symbol; + this.SymbolProfile = SymbolProfile; this.transactionCount = transactionCount; this.value = value; + if (SymbolProfile?.countries?.length > 0) { + for (const country of SymbolProfile.countries) { + this.countries[country.code] = { + name: country.name, + value: country.weight + }; + } + } + + if (SymbolProfile?.sectors?.length > 0) { + for (const sector of SymbolProfile.sectors) { + this.sectors[sector.name] = { + name: sector.name, + value: sector.weight + }; + } + } + if (isToday(parseISO(this.firstBuyDate))) { // Add average price this.historicalDataItems.push({ diff --git a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html index db8f78bc3..9388c7a8a 100644 --- a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html +++ b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html @@ -122,6 +122,57 @@ [value]="transactionCount" > + + +
+ +
+
+ +
+
+ +
+
Countries
+ +
+
+
Sectors
+ +
+
+
diff --git a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.module.ts b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.module.ts index 4cd013fd4..72f80f065 100644 --- a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.module.ts +++ b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.module.ts @@ -6,6 +6,7 @@ import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-foote import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module'; import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module'; import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module'; +import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module'; import { GfValueModule } from '@ghostfolio/ui/value'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @@ -20,6 +21,7 @@ import { PositionDetailDialog } from './position-detail-dialog.component'; GfDialogFooterModule, GfDialogHeaderModule, GfLineChartModule, + GfPortfolioProportionChartModule, GfValueModule, MatButtonModule, MatDialogModule, diff --git a/libs/ui/src/lib/value/value.component.html b/libs/ui/src/lib/value/value.component.html index 282a99ca7..89237c329 100644 --- a/libs/ui/src/lib/value/value.component.html +++ b/libs/ui/src/lib/value/value.component.html @@ -34,12 +34,12 @@ {{ currency }} - +
- {{ formattedDate }} + {{ formattedValue }}
diff --git a/libs/ui/src/lib/value/value.component.ts b/libs/ui/src/lib/value/value.component.ts index 07fc2b136..29863e9bd 100644 --- a/libs/ui/src/lib/value/value.component.ts +++ b/libs/ui/src/lib/value/value.component.ts @@ -5,7 +5,7 @@ import { OnChanges } from '@angular/core'; import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config'; -import { format, isDate } from 'date-fns'; +import { format, isDate, parseISO } from 'date-fns'; import { isNumber } from 'lodash'; @Component({ @@ -28,10 +28,9 @@ export class ValueComponent implements OnChanges { @Input() value: number | string = ''; public absoluteValue = 0; - public formattedDate = ''; public formattedValue = ''; - public isDate = false; public isNumber = false; + public isString = false; public useAbsoluteValue = false; public constructor() {} @@ -39,8 +38,8 @@ export class ValueComponent implements OnChanges { public ngOnChanges() { if (this.value || this.value === 0) { if (isNumber(this.value)) { - this.isDate = false; this.isNumber = true; + this.isString = false; this.absoluteValue = Math.abs(this.value); if (this.colorizeSign) { @@ -98,17 +97,19 @@ export class ValueComponent implements OnChanges { this.formattedValue = this.formattedValue.replace(/^-/, ''); } } else { - try { - if (isDate(new Date(this.value))) { - this.isDate = true; - this.isNumber = false; + this.isNumber = false; + this.isString = true; - this.formattedDate = format( + try { + if (isDate(parseISO(this.value))) { + this.formattedValue = format( new Date(this.value), DEFAULT_DATE_FORMAT ); } - } catch {} + } catch { + this.formattedValue = this.value; + } } }