|
|
@ -5,17 +5,20 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; |
|
|
import { |
|
|
import { |
|
|
DEFAULT_CURRENCY, |
|
|
DEFAULT_CURRENCY, |
|
|
DEFAULT_DATE_RANGE, |
|
|
DEFAULT_DATE_RANGE, |
|
|
|
|
|
DEFAULT_LOCALE, |
|
|
NUMERICAL_PRECISION_THRESHOLD_6_FIGURES |
|
|
NUMERICAL_PRECISION_THRESHOLD_6_FIGURES |
|
|
} from '@ghostfolio/common/config'; |
|
|
} from '@ghostfolio/common/config'; |
|
|
import { |
|
|
import { |
|
|
AssetProfileIdentifier, |
|
|
AssetProfileIdentifier, |
|
|
LineChartItem, |
|
|
LineChartItem, |
|
|
PortfolioPerformance, |
|
|
PortfolioPerformance, |
|
|
|
|
|
PortfolioPosition, |
|
|
User |
|
|
User |
|
|
} from '@ghostfolio/common/interfaces'; |
|
|
} from '@ghostfolio/common/interfaces'; |
|
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; |
|
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; |
|
|
import { internalRoutes } from '@ghostfolio/common/routes/routes'; |
|
|
import { internalRoutes } from '@ghostfolio/common/routes/routes'; |
|
|
import { hasScope, scopes } from '@ghostfolio/common/scopes'; |
|
|
import { hasScope, scopes } from '@ghostfolio/common/scopes'; |
|
|
|
|
|
import { GfHoldingsTableComponent } from '@ghostfolio/ui/holdings-table'; |
|
|
import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; |
|
|
import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; |
|
|
import { DataService } from '@ghostfolio/ui/services'; |
|
|
import { DataService } from '@ghostfolio/ui/services'; |
|
|
|
|
|
|
|
|
@ -30,15 +33,18 @@ import { |
|
|
} from '@angular/core'; |
|
|
} from '@angular/core'; |
|
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; |
|
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; |
|
|
import { MatButtonModule } from '@angular/material/button'; |
|
|
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'; |
|
|
import { DeviceDetectorService } from 'ngx-device-detector'; |
|
|
|
|
|
|
|
|
@Component({ |
|
|
@Component({ |
|
|
changeDetection: ChangeDetectionStrategy.OnPush, |
|
|
changeDetection: ChangeDetectionStrategy.OnPush, |
|
|
imports: [ |
|
|
imports: [ |
|
|
|
|
|
GfHoldingsTableComponent, |
|
|
GfLineChartComponent, |
|
|
GfLineChartComponent, |
|
|
GfPortfolioPerformanceComponent, |
|
|
GfPortfolioPerformanceComponent, |
|
|
MatButtonModule, |
|
|
MatButtonModule, |
|
|
|
|
|
MatCardModule, |
|
|
RouterModule |
|
|
RouterModule |
|
|
], |
|
|
], |
|
|
selector: 'gf-home-overview', |
|
|
selector: 'gf-home-overview', |
|
|
@ -46,9 +52,13 @@ import { DeviceDetectorService } from 'ngx-device-detector'; |
|
|
templateUrl: './home-overview.html' |
|
|
templateUrl: './home-overview.html' |
|
|
}) |
|
|
}) |
|
|
export class GfHomeOverviewComponent implements OnInit { |
|
|
export class GfHomeOverviewComponent implements OnInit { |
|
|
|
|
|
protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE; |
|
|
protected readonly errors = signal<AssetProfileIdentifier[]>([]); |
|
|
protected readonly errors = signal<AssetProfileIdentifier[]>([]); |
|
|
protected readonly hasImpersonationId = signal(false); |
|
|
protected readonly hasImpersonationId = signal(false); |
|
|
protected readonly historicalDataItems = signal<LineChartItem[] | null>(null); |
|
|
protected readonly historicalDataItems = signal<LineChartItem[] | null>(null); |
|
|
|
|
|
protected readonly holdings = signal<PortfolioPosition[] | undefined>( |
|
|
|
|
|
undefined |
|
|
|
|
|
); |
|
|
protected readonly isLoadingPerformance = signal(true); |
|
|
protected readonly isLoadingPerformance = signal(true); |
|
|
protected readonly performance = signal<PortfolioPerformance | null>(null); |
|
|
protected readonly performance = signal<PortfolioPerformance | null>(null); |
|
|
protected readonly performanceLabel = $localize`Performance`; |
|
|
protected readonly performanceLabel = $localize`Performance`; |
|
|
@ -77,13 +87,24 @@ export class GfHomeOverviewComponent implements OnInit { |
|
|
const user = this.user(); |
|
|
const user = this.user(); |
|
|
|
|
|
|
|
|
return user |
|
|
return user |
|
|
? !user.settings.isRestrictedView && user.settings.viewMode !== 'ZEN' |
|
|
? !user?.settings?.isRestrictedView && user?.settings?.viewMode !== 'ZEN' |
|
|
: false; |
|
|
: false; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
protected readonly showExperimentalHoldings = computed(() => { |
|
|
|
|
|
const user = this.user(); |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
user?.settings?.isExperimentalFeatures === true && |
|
|
|
|
|
user?.settings?.viewMode !== 'ZEN' |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
protected readonly unit = computed(() => { |
|
|
protected readonly unit = computed(() => { |
|
|
|
|
|
const user = this.user(); |
|
|
|
|
|
|
|
|
return this.showDetails() |
|
|
return this.showDetails() |
|
|
? (this.user()?.settings?.baseCurrency ?? DEFAULT_CURRENCY) |
|
|
? (user?.settings?.baseCurrency ?? DEFAULT_CURRENCY) |
|
|
: '%'; |
|
|
: '%'; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
@ -94,6 +115,7 @@ export class GfHomeOverviewComponent implements OnInit { |
|
|
ImpersonationStorageService |
|
|
ImpersonationStorageService |
|
|
); |
|
|
); |
|
|
private readonly layoutService = inject(LayoutService); |
|
|
private readonly layoutService = inject(LayoutService); |
|
|
|
|
|
private readonly router = inject(Router); |
|
|
private readonly userService = inject(UserService); |
|
|
private readonly userService = inject(UserService); |
|
|
|
|
|
|
|
|
public constructor() { |
|
|
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() { |
|
|
private update() { |
|
|
this.historicalDataItems.set(null); |
|
|
this.historicalDataItems.set(null); |
|
|
this.isLoadingPerformance.set(true); |
|
|
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 |
|
|
this.dataService |
|
|
.fetchPortfolioPerformance({ |
|
|
.fetchPortfolioPerformance({ |
|
|
range: this.user()?.settings?.dateRange ?? DEFAULT_DATE_RANGE |
|
|
range: this.user()?.settings?.dateRange ?? DEFAULT_DATE_RANGE |
|
|
|