From ba78c2783d9818debf9411051f24622983b91261 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 20 Jul 2024 11:17:36 +0200 Subject: [PATCH] Feature/improve numerical precision in holding detail dialog (#3584) * Improve numerical precision in holding detail dialog * Update changelog --- CHANGELOG.md | 4 +- .../holding-detail-dialog.component.ts | 68 +++++++++++++++---- .../holding-detail-dialog.html | 4 ++ .../home-overview/home-overview.component.ts | 22 ++++-- .../home-overview/home-overview.html | 1 + .../portfolio-performance.component.ts | 14 ++-- libs/common/src/lib/config.ts | 2 + libs/ui/src/lib/value/value.component.ts | 26 +++---- 8 files changed, 97 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bed509d02..bdc062102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the numerical precision in the holding detail dialog +- Improved the handling of the numerical precision in the value component - Optimized the 7d data gathering by prioritizing the currencies - Improved the language localization for German (`de`) - Upgraded `Node.js` from version `18` to `20` (`Dockerfile`) @@ -4829,7 +4831,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Added the attribute `precision` in the value component +- Added the attribute `precision` to the value component ### Fixed diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index 6ac058f7e..3ed2f13a5 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -4,6 +4,7 @@ import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-foote import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { NUMERICAL_PRECISION_THRESHOLD } from '@ghostfolio/common/config'; import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; import { DataProviderInfo, @@ -84,18 +85,22 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { public dataProviderInfo: DataProviderInfo; public dataSource: MatTableDataSource; public dividendInBaseCurrency: number; + public dividendInBaseCurrencyPrecision = 2; public dividendYieldPercentWithCurrencyEffect: number; public feeInBaseCurrency: number; public firstBuyDate: string; public historicalDataItems: LineChartItem[]; public investment: number; + public investmentPrecision = 2; public marketPrice: number; public maxPrice: number; public minPrice: number; public netPerformance: number; + public netPerformancePrecision = 2; public netPerformancePercent: number; public netPerformancePercentWithCurrencyEffect: number; public netPerformanceWithCurrencyEffect: number; + public netPerformanceWithCurrencyEffectPrecision = 2; public quantity: number; public quantityPrecision = 2; public reportDataGlitchMail: string; @@ -161,10 +166,20 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { this.dataProviderInfo = dataProviderInfo; this.dataSource = new MatTableDataSource(orders.reverse()); this.dividendInBaseCurrency = dividendInBaseCurrency; + + if ( + this.data.deviceType === 'mobile' && + this.dividendInBaseCurrency >= NUMERICAL_PRECISION_THRESHOLD + ) { + this.dividendInBaseCurrencyPrecision = 0; + } + this.dividendYieldPercentWithCurrencyEffect = dividendYieldPercentWithCurrencyEffect; + this.feeInBaseCurrency = feeInBaseCurrency; this.firstBuyDate = firstBuyDate; + this.historicalDataItems = historicalData.map( ({ averagePrice, date, marketPrice }) => { this.benchmarkDataItems.push({ @@ -178,17 +193,58 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { }; } ); + this.investment = investment; + + if ( + this.data.deviceType === 'mobile' && + this.investment >= NUMERICAL_PRECISION_THRESHOLD + ) { + this.investmentPrecision = 0; + } + this.marketPrice = marketPrice; this.maxPrice = maxPrice; this.minPrice = minPrice; this.netPerformance = netPerformance; + + if ( + this.data.deviceType === 'mobile' && + this.netPerformance >= NUMERICAL_PRECISION_THRESHOLD + ) { + this.netPerformancePrecision = 0; + } + this.netPerformancePercent = netPerformancePercent; + this.netPerformancePercentWithCurrencyEffect = netPerformancePercentWithCurrencyEffect; + this.netPerformanceWithCurrencyEffect = netPerformanceWithCurrencyEffect; + + if ( + this.data.deviceType === 'mobile' && + this.netPerformanceWithCurrencyEffect >= + NUMERICAL_PRECISION_THRESHOLD + ) { + this.netPerformanceWithCurrencyEffectPrecision = 0; + } + this.quantity = quantity; + + if (Number.isInteger(this.quantity)) { + this.quantityPrecision = 0; + } else if (this.SymbolProfile?.assetSubClass === 'CRYPTOCURRENCY') { + if (this.quantity < 1) { + this.quantityPrecision = 7; + } else if (this.quantity < 1000) { + this.quantityPrecision = 5; + } else if (this.quantity >= 10000000) { + this.quantityPrecision = 0; + } + } + this.reportDataGlitchMail = `mailto:hi@ghostfol.io?Subject=Ghostfolio Data Glitch Report&body=Hello%0D%0DI would like to report a data glitch for%0D%0DSymbol: ${SymbolProfile?.symbol}%0DData Source: ${SymbolProfile?.dataSource}%0D%0DAdditional notes:%0D%0DCan you please take a look?%0D%0DKind regards`; this.sectors = {}; this.SymbolProfile = SymbolProfile; @@ -282,18 +338,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { } ); - if (Number.isInteger(this.quantity)) { - this.quantityPrecision = 0; - } else if (this.SymbolProfile?.assetSubClass === 'CRYPTOCURRENCY') { - if (this.quantity < 1) { - this.quantityPrecision = 7; - } else if (this.quantity < 1000) { - this.quantityPrecision = 5; - } else if (this.quantity > 10000000) { - this.quantityPrecision = 0; - } - } - this.changeDetectorRef.markForCheck(); } ); diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html index 843901e5a..9afeef709 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -47,6 +47,7 @@ [colorizeSign]="true" [isCurrency]="true" [locale]="data.locale" + [precision]="netPerformanceWithCurrencyEffectPrecision" [unit]="data.baseCurrency" [value]="netPerformanceWithCurrencyEffect" >Change with currency effectChangeInvestmentDividend { this.update(); }); - - this.showDetails = - !this.user.settings.isRestrictedView && - this.user.settings.viewMode !== 'ZEN'; - - this.unit = this.showDetails ? this.user.settings.baseCurrency : '%'; } public onChangeDateRange(dateRange: DateRange) { @@ -134,6 +136,14 @@ export class HomeOverviewComponent implements OnDestroy, OnInit { } ); + if ( + this.deviceType === 'mobile' && + this.performance.currentValueInBaseCurrency >= + NUMERICAL_PRECISION_THRESHOLD + ) { + this.precision = 0; + } + this.isLoadingPerformance = false; this.changeDetectorRef.markForCheck(); diff --git a/apps/client/src/app/components/home-overview/home-overview.html b/apps/client/src/app/components/home-overview/home-overview.html index 671e73ef2..8cd317428 100644 --- a/apps/client/src/app/components/home-overview/home-overview.html +++ b/apps/client/src/app/components/home-overview/home-overview.html @@ -88,6 +88,7 @@ [isLoading]="isLoadingPerformance" [locale]="user?.settings?.locale" [performance]="performance" + [precision]="precision" [showDetails]="showDetails" [unit]="unit" /> diff --git a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts index 4d205b761..3083184bb 100644 --- a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts +++ b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts @@ -14,7 +14,6 @@ import { ElementRef, Input, OnChanges, - OnInit, ViewChild } from '@angular/core'; import { CountUp } from 'countup.js'; @@ -26,7 +25,7 @@ import { isNumber } from 'lodash'; templateUrl: './portfolio-performance.component.html', styleUrls: ['./portfolio-performance.component.scss'] }) -export class PortfolioPerformanceComponent implements OnChanges, OnInit { +export class PortfolioPerformanceComponent implements OnChanges { @Input() deviceType: string; @Input() errors: ResponseError['errors']; @Input() isAllTimeHigh: boolean; @@ -34,6 +33,7 @@ export class PortfolioPerformanceComponent implements OnChanges, OnInit { @Input() isLoading: boolean; @Input() locale = getLocale(); @Input() performance: PortfolioPerformance; + @Input() precision: number; @Input() showDetails: boolean; @Input() unit: string; @@ -41,9 +41,9 @@ export class PortfolioPerformanceComponent implements OnChanges, OnInit { public constructor() {} - public ngOnInit() {} - public ngOnChanges() { + this.precision = this.precision >= 0 ? this.precision : 2; + if (this.isLoading) { if (this.value?.nativeElement) { this.value.nativeElement.innerHTML = ''; @@ -52,11 +52,7 @@ export class PortfolioPerformanceComponent implements OnChanges, OnInit { if (isNumber(this.performance?.currentValueInBaseCurrency)) { new CountUp('value', this.performance?.currentValueInBaseCurrency, { decimal: getNumberFormatDecimal(this.locale), - decimalPlaces: - this.deviceType === 'mobile' && - this.performance?.currentValueInBaseCurrency >= 100000 - ? 0 - : 2, + decimalPlaces: this.precision, duration: 1, separator: getNumberFormatGroup(this.locale) }).start(); diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index b7f20a978..fe87e9482 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -91,6 +91,8 @@ export const HEADER_KEY_TOKEN = 'Authorization'; export const MAX_CHART_ITEMS = 365; export const MAX_TOP_HOLDINGS = 50; +export const NUMERICAL_PRECISION_THRESHOLD = 100000; + export const PROPERTY_BENCHMARKS = 'BENCHMARKS'; export const PROPERTY_BETTER_UPTIME_MONITOR_ID = 'BETTER_UPTIME_MONITOR_ID'; export const PROPERTY_COUNTRIES_OF_SUBSCRIBERS = 'COUNTRIES_OF_SUBSCRIBERS'; diff --git a/libs/ui/src/lib/value/value.component.ts b/libs/ui/src/lib/value/value.component.ts index 7dc427805..71bdfc7c3 100644 --- a/libs/ui/src/lib/value/value.component.ts +++ b/libs/ui/src/lib/value/value.component.ts @@ -29,7 +29,7 @@ export class GfValueComponent implements OnChanges { @Input() isPercent = false; @Input() locale: string; @Input() position = ''; - @Input() precision: number | undefined; + @Input() precision: number; @Input() size: 'large' | 'medium' | 'small' = 'small'; @Input() subLabel = ''; @Input() unit = ''; @@ -58,8 +58,8 @@ export class GfValueComponent implements OnChanges { this.formattedValue = this.absoluteValue.toLocaleString( this.locale, { - maximumFractionDigits: 2, - minimumFractionDigits: 2 + maximumFractionDigits: this.precision, + minimumFractionDigits: this.precision } ); } catch {} @@ -68,8 +68,8 @@ export class GfValueComponent implements OnChanges { this.formattedValue = (this.absoluteValue * 100).toLocaleString( this.locale, { - maximumFractionDigits: 2, - minimumFractionDigits: 2 + maximumFractionDigits: this.precision, + minimumFractionDigits: this.precision } ); } catch {} @@ -77,8 +77,8 @@ export class GfValueComponent implements OnChanges { } else if (this.isCurrency) { try { this.formattedValue = this.value?.toLocaleString(this.locale, { - maximumFractionDigits: 2, - minimumFractionDigits: 2 + maximumFractionDigits: this.precision, + minimumFractionDigits: this.precision }); } catch {} } else if (this.isPercent) { @@ -86,18 +86,11 @@ export class GfValueComponent implements OnChanges { this.formattedValue = (this.value * 100).toLocaleString( this.locale, { - maximumFractionDigits: 2, - minimumFractionDigits: 2 + maximumFractionDigits: this.precision, + minimumFractionDigits: this.precision } ); } catch {} - } else if (this.precision || this.precision === 0) { - try { - this.formattedValue = this.value?.toLocaleString(this.locale, { - maximumFractionDigits: this.precision, - minimumFractionDigits: this.precision - }); - } catch {} } else { this.formattedValue = this.value?.toLocaleString(this.locale); } @@ -136,6 +129,7 @@ export class GfValueComponent implements OnChanges { this.isNumber = false; this.isString = false; this.locale = this.locale || getLocale(); + this.precision = this.precision >= 0 ? this.precision : 2; this.useAbsoluteValue = false; } }