From 0e1b77bcf7e6ab139ed225a5f32cafd52ffd434f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:28:05 +0200 Subject: [PATCH] Feature/add holdings to overview (#7861) * Add holdings * Update changelog --- CHANGELOG.md | 1 + .../home-overview/home-overview.component.ts | 50 +++++++++++++++-- .../home-overview/home-overview.html | 53 +++++++++++++------ .../home-overview/home-overview.scss | 14 +++++ .../holdings-table.component.html | 3 +- 5 files changed, 100 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3bb71d9a..3de6f69de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added the holdings table to the overview tab of the home page on desktop (experimental) - Added a tool to search for asset profiles to the server of the Model Context Protocol (MCP) (experimental) ### Changed diff --git a/apps/client/src/app/components/home-overview/home-overview.component.ts b/apps/client/src/app/components/home-overview/home-overview.component.ts index de83a3228..7715ef571 100644 --- a/apps/client/src/app/components/home-overview/home-overview.component.ts +++ b/apps/client/src/app/components/home-overview/home-overview.component.ts @@ -5,17 +5,20 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_CURRENCY, DEFAULT_DATE_RANGE, + DEFAULT_LOCALE, NUMERICAL_PRECISION_THRESHOLD_6_FIGURES } from '@ghostfolio/common/config'; import { AssetProfileIdentifier, LineChartItem, PortfolioPerformance, + PortfolioPosition, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { hasScope, scopes } from '@ghostfolio/common/scopes'; +import { GfHoldingsTableComponent } from '@ghostfolio/ui/holdings-table'; import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; import { DataService } from '@ghostfolio/ui/services'; @@ -30,15 +33,18 @@ import { } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; -import { RouterModule } from '@angular/router'; +import { MatCardModule } from '@angular/material/card'; +import { Router, RouterModule } from '@angular/router'; import { DeviceDetectorService } from 'ngx-device-detector'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, imports: [ + GfHoldingsTableComponent, GfLineChartComponent, GfPortfolioPerformanceComponent, MatButtonModule, + MatCardModule, RouterModule ], selector: 'gf-home-overview', @@ -46,9 +52,13 @@ import { DeviceDetectorService } from 'ngx-device-detector'; templateUrl: './home-overview.html' }) export class GfHomeOverviewComponent implements OnInit { + protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE; protected readonly errors = signal([]); protected readonly hasImpersonationId = signal(false); protected readonly historicalDataItems = signal(null); + protected readonly holdings = signal( + undefined + ); protected readonly isLoadingPerformance = signal(true); protected readonly performance = signal(null); protected readonly performanceLabel = $localize`Performance`; @@ -77,13 +87,24 @@ export class GfHomeOverviewComponent implements OnInit { const user = this.user(); return user - ? !user.settings.isRestrictedView && user.settings.viewMode !== 'ZEN' + ? !user?.settings?.isRestrictedView && user?.settings?.viewMode !== 'ZEN' : false; }); + protected readonly showExperimentalHoldings = computed(() => { + const user = this.user(); + + return ( + user?.settings?.isExperimentalFeatures === true && + user?.settings?.viewMode !== 'ZEN' + ); + }); + protected readonly unit = computed(() => { + const user = this.user(); + return this.showDetails() - ? (this.user()?.settings?.baseCurrency ?? DEFAULT_CURRENCY) + ? (user?.settings?.baseCurrency ?? DEFAULT_CURRENCY) : '%'; }); @@ -94,6 +115,7 @@ export class GfHomeOverviewComponent implements OnInit { ImpersonationStorageService ); private readonly layoutService = inject(LayoutService); + private readonly router = inject(Router); private readonly userService = inject(UserService); public constructor() { @@ -122,10 +144,32 @@ export class GfHomeOverviewComponent implements OnInit { }); } + protected onHoldingClicked({ dataSource, symbol }: AssetProfileIdentifier) { + if (dataSource && symbol) { + this.router.navigate([], { + queryParams: { dataSource, symbol, holdingDetailDialog: true } + }); + } + } + private update() { this.historicalDataItems.set(null); this.isLoadingPerformance.set(true); + if (this.showExperimentalHoldings()) { + this.holdings.set(undefined); + + this.dataService + .fetchPortfolioHoldings({ + filters: [{ id: 'ACTIVE', type: 'HOLDING_TYPE' }], + range: this.user()?.settings?.dateRange + }) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(({ holdings }) => { + this.holdings.set(holdings); + }); + } + this.dataService .fetchPortfolioPerformance({ range: this.user()?.settings?.dateRange ?? DEFAULT_DATE_RANGE 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 90a628a17..764877746 100644 --- a/apps/client/src/app/components/home-overview/home-overview.html +++ b/apps/client/src/app/components/home-overview/home-overview.html @@ -1,5 +1,5 @@
@if ( !hasImpersonationId() && @@ -61,8 +61,8 @@
} @else { -
-
+
+
+
+
+ +
+
-
-
-
- -
+ @if (showExperimentalHoldings()) { +
+ + + Holdings + + + + + +
+ }
}
diff --git a/apps/client/src/app/components/home-overview/home-overview.scss b/apps/client/src/app/components/home-overview/home-overview.scss index 3a692b28d..6d0484296 100644 --- a/apps/client/src/app/components/home-overview/home-overview.scss +++ b/apps/client/src/app/components/home-overview/home-overview.scss @@ -30,6 +30,20 @@ } } + mat-card { + &.holdings { + --mat-card-title-text-size: var(--mat-sys-title-medium-size); + + mat-card-content { + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + } + } + } + .introduction { max-width: 50rem; } diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.html b/libs/ui/src/lib/holdings-table/holdings-table.component.html index 2ed948478..5bc76f646 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.html +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -208,7 +208,8 @@ @if (isLoading()) {