Feature/move holdings table to holdings tab of home page (#3368)
* Move holdings table to holdings tab of home page * Deprecate api/v1/portfolio/positions endpoint * Update changelog
This commit is contained in:
parent
42b70ef568
commit
f3d961bc16
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Moved the holdings table to the holdings tab of the home page
|
||||||
- Improved the performance labels (with and without currency effects) in the position detail dialog
|
- Improved the performance labels (with and without currency effects) in the position detail dialog
|
||||||
|
|
||||||
## 2.78.0 - 2024-05-02
|
## 2.78.0 - 2024-05-02
|
||||||
|
@ -490,6 +490,9 @@ export class PortfolioController {
|
|||||||
return performanceInformation;
|
return performanceInformation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
@Get('positions')
|
@Get('positions')
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
@UseInterceptors(RedactValuesInResponseInterceptor)
|
@UseInterceptors(RedactValuesInResponseInterceptor)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { PositionDetailDialog } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.component';
|
import { PositionDetailDialogParams } from '@ghostfolio/client/components/position-detail-dialog/interfaces/interfaces';
|
||||||
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';
|
import { PositionDetailDialog } from '@ghostfolio/client/components/position-detail-dialog/position-detail-dialog.component';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { Position, User } from '@ghostfolio/common/interfaces';
|
import { PortfolioPosition, User } from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { DateRange } from '@ghostfolio/common/types';
|
import { HoldingType, ToggleOption } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
@ -15,19 +15,21 @@ import { DeviceDetectorService } from 'ngx-device-detector';
|
|||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
import { PositionDetailDialogParams } from '../position/position-detail-dialog/interfaces/interfaces';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-home-holdings',
|
selector: 'gf-home-holdings',
|
||||||
styleUrls: ['./home-holdings.scss'],
|
styleUrls: ['./home-holdings.scss'],
|
||||||
templateUrl: './home-holdings.html'
|
templateUrl: './home-holdings.html'
|
||||||
})
|
})
|
||||||
export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
||||||
public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS;
|
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
public hasPermissionToCreateOrder: boolean;
|
public hasPermissionToCreateOrder: boolean;
|
||||||
public positions: Position[];
|
public holdings: PortfolioPosition[];
|
||||||
|
public holdingType: HoldingType = 'ACTIVE';
|
||||||
|
public holdingTypeOptions: ToggleOption[] = [
|
||||||
|
{ label: $localize`Active`, value: 'ACTIVE' },
|
||||||
|
{ label: $localize`Closed`, value: 'CLOSED' }
|
||||||
|
];
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
@ -56,6 +58,17 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit() {
|
||||||
|
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
||||||
|
|
||||||
|
this.impersonationStorageService
|
||||||
|
.onChangeHasImpersonation()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((impersonationId) => {
|
||||||
|
this.hasImpersonationId = !!impersonationId;
|
||||||
|
});
|
||||||
|
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@ -68,37 +81,32 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
|||||||
permissions.createOrder
|
permissions.createOrder
|
||||||
);
|
);
|
||||||
|
|
||||||
this.update();
|
this.holdings = undefined;
|
||||||
|
|
||||||
|
this.fetchHoldings()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(({ holdings }) => {
|
||||||
|
this.holdings = holdings;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnInit() {
|
public onChangeHoldingType(aHoldingType: HoldingType) {
|
||||||
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
this.holdingType = aHoldingType;
|
||||||
|
|
||||||
this.impersonationStorageService
|
this.holdings = undefined;
|
||||||
.onChangeHasImpersonation()
|
|
||||||
|
this.fetchHoldings()
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((impersonationId) => {
|
.subscribe(({ holdings }) => {
|
||||||
this.hasImpersonationId = !!impersonationId;
|
this.holdings = holdings;
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public onChangeDateRange(dateRange: DateRange) {
|
this.changeDetectorRef.markForCheck();
|
||||||
this.dataService
|
|
||||||
.putUserSetting({ dateRange })
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(() => {
|
|
||||||
this.userService.remove();
|
|
||||||
|
|
||||||
this.userService
|
|
||||||
.get()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((user) => {
|
|
||||||
this.user = user;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +115,19 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
|||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fetchHoldings() {
|
||||||
|
const filters = this.userService.getFilters();
|
||||||
|
|
||||||
|
if (this.holdingType === 'CLOSED') {
|
||||||
|
filters.push({ id: 'CLOSED', type: 'HOLDING_TYPE' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.dataService.fetchPortfolioHoldings({
|
||||||
|
filters,
|
||||||
|
range: this.user?.settings?.dateRange
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private openPositionDialog({
|
private openPositionDialog({
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol
|
symbol
|
||||||
@ -147,19 +168,4 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private update() {
|
|
||||||
this.positions = undefined;
|
|
||||||
|
|
||||||
this.dataService
|
|
||||||
.fetchPositions({ range: this.user?.settings?.dateRange })
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(({ positions }) => {
|
|
||||||
this.positions = positions;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,38 @@
|
|||||||
<div class="container justify-content-center p-3">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="align-items-center col-xs-12 col-md-8 offset-md-2">
|
<div class="col">
|
||||||
<mat-card appearance="outlined">
|
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Holdings</h1>
|
||||||
<mat-card-content class="p-0">
|
</div>
|
||||||
<gf-positions
|
</div>
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
<div class="row">
|
||||||
[deviceType]="deviceType"
|
<div class="col-lg">
|
||||||
[hasPermissionToCreateOrder]="hasPermissionToCreateOrder"
|
<div class="d-flex justify-content-end">
|
||||||
[locale]="user?.settings?.locale"
|
<gf-toggle
|
||||||
[positions]="positions"
|
class="d-none d-lg-block"
|
||||||
[range]="user?.settings?.dateRange"
|
[defaultValue]="holdingType"
|
||||||
/>
|
[isLoading]="false"
|
||||||
</mat-card-content>
|
[options]="holdingTypeOptions"
|
||||||
</mat-card>
|
(change)="onChangeHoldingType($event.value)"
|
||||||
<div *ngIf="hasPermissionToCreateOrder" class="text-center">
|
/>
|
||||||
<a
|
|
||||||
class="mt-3"
|
|
||||||
i18n
|
|
||||||
mat-stroked-button
|
|
||||||
[routerLink]="['/portfolio', 'activities']"
|
|
||||||
>Manage Activities</a
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
<gf-holdings-table
|
||||||
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[deviceType]="deviceType"
|
||||||
|
[hasPermissionToCreateActivity]="hasPermissionToCreateOrder"
|
||||||
|
[holdings]="holdings"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
/>
|
||||||
|
@if (hasPermissionToCreateOrder && holdings?.length > 0) {
|
||||||
|
<div class="text-center">
|
||||||
|
<a
|
||||||
|
class="mt-3"
|
||||||
|
i18n
|
||||||
|
mat-stroked-button
|
||||||
|
[routerLink]="['/portfolio', 'activities']"
|
||||||
|
>Manage Activities</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { GfPositionDetailDialogModule } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.module';
|
|
||||||
import { GfPositionsModule } from '@ghostfolio/client/components/positions/positions.module';
|
|
||||||
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
||||||
|
import { GfHoldingsTableComponent } from '@ghostfolio/ui/holdings-table';
|
||||||
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { HomeHoldingsComponent } from './home-holdings.component';
|
import { HomeHoldingsComponent } from './home-holdings.component';
|
||||||
@ -14,11 +12,9 @@ import { HomeHoldingsComponent } from './home-holdings.component';
|
|||||||
declarations: [HomeHoldingsComponent],
|
declarations: [HomeHoldingsComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfPositionDetailDialogModule,
|
GfHoldingsTableComponent,
|
||||||
GfPositionsModule,
|
|
||||||
GfToggleModule,
|
GfToggleModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatCardModule,
|
|
||||||
RouterModule
|
RouterModule
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
<div class="container p-0">
|
|
||||||
<div class="flex-nowrap no-gutters row">
|
|
||||||
<a
|
|
||||||
class="d-flex p-3 w-100"
|
|
||||||
[ngClass]="{ 'cursor-default': isLoading }"
|
|
||||||
[queryParams]="{
|
|
||||||
dataSource: position?.dataSource,
|
|
||||||
positionDetailDialog: true,
|
|
||||||
symbol: position?.symbol
|
|
||||||
}"
|
|
||||||
[routerLink]="[]"
|
|
||||||
>
|
|
||||||
<div class="d-flex mr-2">
|
|
||||||
<gf-trend-indicator
|
|
||||||
class="d-flex"
|
|
||||||
size="large"
|
|
||||||
[isLoading]="isLoading"
|
|
||||||
[marketState]="position?.marketState"
|
|
||||||
[range]="range"
|
|
||||||
[value]="position?.netPerformancePercentageWithCurrencyEffect"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="isLoading" class="flex-grow-1">
|
|
||||||
<ngx-skeleton-loader
|
|
||||||
animation="pulse"
|
|
||||||
class="mb-1"
|
|
||||||
[theme]="{
|
|
||||||
height: '1.2rem',
|
|
||||||
width: '12rem'
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
<ngx-skeleton-loader
|
|
||||||
animation="pulse"
|
|
||||||
[theme]="{
|
|
||||||
height: '1rem',
|
|
||||||
width: '8rem'
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="!isLoading" class="flex-grow-1 text-truncate">
|
|
||||||
<div class="h6 m-0 text-truncate">{{ position?.name }}</div>
|
|
||||||
<div class="d-flex">
|
|
||||||
<small class="text-muted">{{ position?.symbol | gfSymbol }}</small>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex mt-1">
|
|
||||||
<gf-value
|
|
||||||
class="mr-3"
|
|
||||||
[colorizeSign]="true"
|
|
||||||
[isCurrency]="true"
|
|
||||||
[locale]="locale"
|
|
||||||
[unit]="baseCurrency"
|
|
||||||
[value]="position?.netPerformanceWithCurrencyEffect"
|
|
||||||
/>
|
|
||||||
<gf-value
|
|
||||||
[colorizeSign]="true"
|
|
||||||
[isPercent]="true"
|
|
||||||
[locale]="locale"
|
|
||||||
[value]="position?.netPerformancePercentageWithCurrencyEffect"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="align-items-center d-flex">
|
|
||||||
<ion-icon
|
|
||||||
*ngIf="!isLoading"
|
|
||||||
class="chevron text-muted"
|
|
||||||
name="chevron-forward-outline"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,13 +0,0 @@
|
|||||||
:host {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
.container {
|
|
||||||
gf-trend-indicator {
|
|
||||||
padding-top: 0.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chevron {
|
|
||||||
opacity: 0.33;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
|
||||||
import { getLocale } from '@ghostfolio/common/helper';
|
|
||||||
import { Position } from '@ghostfolio/common/interfaces';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
Input,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit
|
|
||||||
} from '@angular/core';
|
|
||||||
import { Subject } from 'rxjs';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'gf-position',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
templateUrl: './position.component.html',
|
|
||||||
styleUrls: ['./position.component.scss']
|
|
||||||
})
|
|
||||||
export class PositionComponent implements OnDestroy, OnInit {
|
|
||||||
@Input() baseCurrency: string;
|
|
||||||
@Input() deviceType: string;
|
|
||||||
@Input() isLoading: boolean;
|
|
||||||
@Input() locale = getLocale();
|
|
||||||
@Input() position: Position;
|
|
||||||
@Input() range: string;
|
|
||||||
|
|
||||||
public unknownKey = UNKNOWN_KEY;
|
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
|
||||||
|
|
||||||
public constructor() {}
|
|
||||||
|
|
||||||
public ngOnInit() {}
|
|
||||||
|
|
||||||
public ngOnDestroy() {
|
|
||||||
this.unsubscribeSubject.next();
|
|
||||||
this.unsubscribeSubject.complete();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
|
||||||
import { GfTrendIndicatorComponent } from '@ghostfolio/ui/trend-indicator';
|
|
||||||
import { GfValueComponent } from '@ghostfolio/ui/value';
|
|
||||||
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
|
||||||
import { RouterModule } from '@angular/router';
|
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
|
||||||
|
|
||||||
import { GfPositionDetailDialogModule } from './position-detail-dialog/position-detail-dialog.module';
|
|
||||||
import { PositionComponent } from './position.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [PositionComponent],
|
|
||||||
exports: [PositionComponent],
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
GfPositionDetailDialogModule,
|
|
||||||
GfSymbolModule,
|
|
||||||
GfTrendIndicatorComponent,
|
|
||||||
GfValueComponent,
|
|
||||||
MatDialogModule,
|
|
||||||
NgxSkeletonLoaderModule,
|
|
||||||
RouterModule
|
|
||||||
],
|
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
|
||||||
})
|
|
||||||
export class GfPositionModule {}
|
|
@ -1,35 +0,0 @@
|
|||||||
<div class="container p-0">
|
|
||||||
<div class="row no-gutters">
|
|
||||||
<div class="col">
|
|
||||||
<ng-container *ngIf="positions === undefined">
|
|
||||||
<gf-position [isLoading]="true" />
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="positions !== undefined">
|
|
||||||
<ng-container *ngIf="hasPositions">
|
|
||||||
<gf-position
|
|
||||||
*ngFor="let position of positionsWithPriority"
|
|
||||||
[baseCurrency]="baseCurrency"
|
|
||||||
[deviceType]="deviceType"
|
|
||||||
[locale]="locale"
|
|
||||||
[position]="position"
|
|
||||||
[range]="range"
|
|
||||||
/>
|
|
||||||
<gf-position
|
|
||||||
*ngFor="let position of positionsRest"
|
|
||||||
[baseCurrency]="baseCurrency"
|
|
||||||
[deviceType]="deviceType"
|
|
||||||
[locale]="locale"
|
|
||||||
[position]="position"
|
|
||||||
[range]="range"
|
|
||||||
/>
|
|
||||||
</ng-container>
|
|
||||||
<div
|
|
||||||
*ngIf="hasPermissionToCreateOrder && !hasPositions"
|
|
||||||
class="p-3 text-center"
|
|
||||||
>
|
|
||||||
<gf-no-transactions-info-indicator [hasBorder]="false" />
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,17 +0,0 @@
|
|||||||
:host {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
gf-position {
|
|
||||||
&:nth-child(even) {
|
|
||||||
background-color: rgba(0, 0, 0, var(--gf-theme-alpha-hover));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:host-context(.is-dark-theme) {
|
|
||||||
gf-position {
|
|
||||||
&:nth-child(even) {
|
|
||||||
background-color: rgba(255, 255, 255, var(--gf-theme-alpha-hover));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
import { getLocale } from '@ghostfolio/common/helper';
|
|
||||||
import { Position } from '@ghostfolio/common/interfaces';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
Input,
|
|
||||||
OnChanges,
|
|
||||||
OnInit
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'gf-positions',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
templateUrl: './positions.component.html',
|
|
||||||
styleUrls: ['./positions.component.scss']
|
|
||||||
})
|
|
||||||
export class PositionsComponent implements OnChanges, OnInit {
|
|
||||||
@Input() baseCurrency: string;
|
|
||||||
@Input() deviceType: string;
|
|
||||||
@Input() hasPermissionToCreateOrder: boolean;
|
|
||||||
@Input() locale = getLocale();
|
|
||||||
@Input() positions: Position[];
|
|
||||||
@Input() range: string;
|
|
||||||
|
|
||||||
public hasPositions: boolean;
|
|
||||||
public positionsRest: Position[] = [];
|
|
||||||
public positionsWithPriority: Position[] = [];
|
|
||||||
|
|
||||||
public constructor() {}
|
|
||||||
|
|
||||||
public ngOnInit() {}
|
|
||||||
|
|
||||||
public ngOnChanges() {
|
|
||||||
if (this.positions) {
|
|
||||||
this.hasPositions = this.positions.length > 0;
|
|
||||||
|
|
||||||
if (!this.hasPositions) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.positionsRest = [];
|
|
||||||
this.positionsWithPriority = [];
|
|
||||||
|
|
||||||
for (const portfolioPosition of this.positions) {
|
|
||||||
if (portfolioPosition.marketState === 'open' || this.range !== '1d') {
|
|
||||||
// Only show positions where the market is open in today's view
|
|
||||||
this.positionsWithPriority.push(portfolioPosition);
|
|
||||||
} else {
|
|
||||||
this.positionsRest.push(portfolioPosition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.positionsRest.sort((a, b) =>
|
|
||||||
(a.name || a.symbol)?.toLowerCase() >
|
|
||||||
(b.name || b.symbol)?.toLowerCase()
|
|
||||||
? 1
|
|
||||||
: -1
|
|
||||||
);
|
|
||||||
this.positionsWithPriority.sort((a, b) =>
|
|
||||||
(a.name || a.symbol)?.toLowerCase() >
|
|
||||||
(b.name || b.symbol)?.toLowerCase()
|
|
||||||
? 1
|
|
||||||
: -1
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.hasPositions = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
import { GfNoTransactionsInfoComponent } from '@ghostfolio/ui/no-transactions-info';
|
|
||||||
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
|
||||||
|
|
||||||
import { GfPositionModule } from '../position/position.module';
|
|
||||||
import { PositionsComponent } from './positions.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [PositionsComponent],
|
|
||||||
exports: [PositionsComponent],
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
GfNoTransactionsInfoComponent,
|
|
||||||
GfPositionModule,
|
|
||||||
MatButtonModule
|
|
||||||
],
|
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
|
||||||
})
|
|
||||||
export class GfPositionsModule {}
|
|
@ -6,7 +6,6 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
|||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
|
||||||
import { GfPositionModule } from '../position/position.module';
|
|
||||||
import { RulesComponent } from './rules.component';
|
import { RulesComponent } from './rules.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -15,7 +14,6 @@ import { RulesComponent } from './rules.component';
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfNoTransactionsInfoComponent,
|
GfNoTransactionsInfoComponent,
|
||||||
GfPositionModule,
|
|
||||||
GfRuleModule,
|
GfRuleModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatCardModule
|
MatCardModule
|
||||||
|
@ -22,6 +22,11 @@ const routes: Routes = [
|
|||||||
component: HomeHoldingsComponent,
|
component: HomeHoldingsComponent,
|
||||||
title: $localize`Holdings`
|
title: $localize`Holdings`
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'holdings',
|
||||||
|
component: HomeHoldingsComponent,
|
||||||
|
title: $localize`Holdings`
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'summary',
|
path: 'summary',
|
||||||
component: HomeSummaryComponent,
|
component: HomeSummaryComponent,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
|
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
|
||||||
import { PositionDetailDialogParams } from '@ghostfolio/client/components/position/position-detail-dialog/interfaces/interfaces';
|
import { PositionDetailDialogParams } from '@ghostfolio/client/components/position-detail-dialog/interfaces/interfaces';
|
||||||
import { PositionDetailDialog } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.component';
|
import { PositionDetailDialog } from '@ghostfolio/client/components/position-detail-dialog/position-detail-dialog.component';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { IcsService } from '@ghostfolio/client/services/ics/ics.service';
|
import { IcsService } from '@ghostfolio/client/services/ics/ics.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { AccountDetailDialog } from '@ghostfolio/client/components/account-detail-dialog/account-detail-dialog.component';
|
import { AccountDetailDialog } from '@ghostfolio/client/components/account-detail-dialog/account-detail-dialog.component';
|
||||||
import { AccountDetailDialogParams } from '@ghostfolio/client/components/account-detail-dialog/interfaces/interfaces';
|
import { AccountDetailDialogParams } from '@ghostfolio/client/components/account-detail-dialog/interfaces/interfaces';
|
||||||
import { PositionDetailDialogParams } from '@ghostfolio/client/components/position/position-detail-dialog/interfaces/interfaces';
|
import { PositionDetailDialogParams } from '@ghostfolio/client/components/position-detail-dialog/interfaces/interfaces';
|
||||||
import { PositionDetailDialog } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.component';
|
import { PositionDetailDialog } from '@ghostfolio/client/components/position-detail-dialog/position-detail-dialog.component';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
@ -103,7 +103,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private userService: UserService
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
route.queryParams
|
this.route.queryParams
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((params) => {
|
.subscribe((params) => {
|
||||||
if (params['accountId'] && params['accountDetailDialog']) {
|
if (params['accountId'] && params['accountDetailDialog']) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { PositionDetailDialogParams } from '@ghostfolio/client/components/position/position-detail-dialog/interfaces/interfaces';
|
import { PositionDetailDialogParams } from '@ghostfolio/client/components/position-detail-dialog/interfaces/interfaces';
|
||||||
import { PositionDetailDialog } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.component';
|
import { PositionDetailDialog } from '@ghostfolio/client/components/position-detail-dialog/position-detail-dialog.component';
|
||||||
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';
|
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
@ -80,7 +80,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
const { benchmarks } = this.dataService.fetchInfo();
|
const { benchmarks } = this.dataService.fetchInfo();
|
||||||
this.benchmarks = benchmarks;
|
this.benchmarks = benchmarks;
|
||||||
|
|
||||||
route.queryParams
|
this.route.queryParams
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((params) => {
|
.subscribe((params) => {
|
||||||
if (
|
if (
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
|
||||||
|
|
||||||
import { HoldingsPageComponent } from './holdings-page.component';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
canActivate: [AuthGuard],
|
|
||||||
component: HoldingsPageComponent,
|
|
||||||
path: '',
|
|
||||||
title: $localize`Holdings`
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [RouterModule.forChild(routes)],
|
|
||||||
exports: [RouterModule]
|
|
||||||
})
|
|
||||||
export class HoldingsPageRoutingModule {}
|
|
@ -1,171 +0,0 @@
|
|||||||
import { PositionDetailDialogParams } from '@ghostfolio/client/components/position/position-detail-dialog/interfaces/interfaces';
|
|
||||||
import { PositionDetailDialog } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.component';
|
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
|
||||||
import { PortfolioPosition, User } from '@ghostfolio/common/interfaces';
|
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
|
||||||
import { HoldingType, ToggleOption } from '@ghostfolio/common/types';
|
|
||||||
|
|
||||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { DataSource } from '@prisma/client';
|
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
|
||||||
import { Subject } from 'rxjs';
|
|
||||||
import { takeUntil } from 'rxjs/operators';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'gf-holdings-page',
|
|
||||||
styleUrls: ['./holdings-page.scss'],
|
|
||||||
templateUrl: './holdings-page.html'
|
|
||||||
})
|
|
||||||
export class HoldingsPageComponent implements OnDestroy, OnInit {
|
|
||||||
public deviceType: string;
|
|
||||||
public hasImpersonationId: boolean;
|
|
||||||
public hasPermissionToCreateOrder: boolean;
|
|
||||||
public holdings: PortfolioPosition[];
|
|
||||||
public holdingType: HoldingType = 'ACTIVE';
|
|
||||||
public holdingTypeOptions: ToggleOption[] = [
|
|
||||||
{ label: $localize`Active`, value: 'ACTIVE' },
|
|
||||||
{ label: $localize`Closed`, value: 'CLOSED' }
|
|
||||||
];
|
|
||||||
public user: User;
|
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
|
||||||
private dataService: DataService,
|
|
||||||
private deviceService: DeviceDetectorService,
|
|
||||||
private dialog: MatDialog,
|
|
||||||
private impersonationStorageService: ImpersonationStorageService,
|
|
||||||
private route: ActivatedRoute,
|
|
||||||
private router: Router,
|
|
||||||
private userService: UserService
|
|
||||||
) {
|
|
||||||
route.queryParams
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((params) => {
|
|
||||||
if (
|
|
||||||
params['dataSource'] &&
|
|
||||||
params['positionDetailDialog'] &&
|
|
||||||
params['symbol']
|
|
||||||
) {
|
|
||||||
this.openPositionDialog({
|
|
||||||
dataSource: params['dataSource'],
|
|
||||||
symbol: params['symbol']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnInit() {
|
|
||||||
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
|
||||||
|
|
||||||
this.impersonationStorageService
|
|
||||||
.onChangeHasImpersonation()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((impersonationId) => {
|
|
||||||
this.hasImpersonationId = !!impersonationId;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.userService.stateChanged
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((state) => {
|
|
||||||
if (state?.user) {
|
|
||||||
this.user = state.user;
|
|
||||||
|
|
||||||
this.hasPermissionToCreateOrder = hasPermission(
|
|
||||||
this.user.permissions,
|
|
||||||
permissions.createOrder
|
|
||||||
);
|
|
||||||
|
|
||||||
this.holdings = undefined;
|
|
||||||
|
|
||||||
this.fetchHoldings()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(({ holdings }) => {
|
|
||||||
this.holdings = holdings;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public onChangeHoldingType(aHoldingType: HoldingType) {
|
|
||||||
this.holdingType = aHoldingType;
|
|
||||||
|
|
||||||
this.holdings = undefined;
|
|
||||||
|
|
||||||
this.fetchHoldings()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(({ holdings }) => {
|
|
||||||
this.holdings = holdings;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnDestroy() {
|
|
||||||
this.unsubscribeSubject.next();
|
|
||||||
this.unsubscribeSubject.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
private fetchHoldings() {
|
|
||||||
const filters = this.userService.getFilters();
|
|
||||||
|
|
||||||
if (this.holdingType === 'CLOSED') {
|
|
||||||
filters.push({ id: 'CLOSED', type: 'HOLDING_TYPE' });
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.dataService.fetchPortfolioHoldings({
|
|
||||||
filters,
|
|
||||||
range: this.user?.settings?.dateRange
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private openPositionDialog({
|
|
||||||
dataSource,
|
|
||||||
symbol
|
|
||||||
}: {
|
|
||||||
dataSource: DataSource;
|
|
||||||
symbol: string;
|
|
||||||
}) {
|
|
||||||
this.userService
|
|
||||||
.get()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((user) => {
|
|
||||||
this.user = user;
|
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(PositionDetailDialog, {
|
|
||||||
autoFocus: false,
|
|
||||||
data: <PositionDetailDialogParams>{
|
|
||||||
dataSource,
|
|
||||||
symbol,
|
|
||||||
baseCurrency: this.user?.settings?.baseCurrency,
|
|
||||||
colorScheme: this.user?.settings?.colorScheme,
|
|
||||||
deviceType: this.deviceType,
|
|
||||||
hasImpersonationId: this.hasImpersonationId,
|
|
||||||
hasPermissionToReportDataGlitch: hasPermission(
|
|
||||||
this.user?.permissions,
|
|
||||||
permissions.reportDataGlitch
|
|
||||||
),
|
|
||||||
locale: this.user?.settings?.locale
|
|
||||||
},
|
|
||||||
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
|
||||||
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogRef
|
|
||||||
.afterClosed()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(() => {
|
|
||||||
this.router.navigate(['.'], { relativeTo: this.route });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Holdings</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg">
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<gf-toggle
|
|
||||||
class="d-none d-lg-block"
|
|
||||||
[defaultValue]="holdingType"
|
|
||||||
[isLoading]="false"
|
|
||||||
[options]="holdingTypeOptions"
|
|
||||||
(change)="onChangeHoldingType($event.value)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<gf-holdings-table
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
|
||||||
[deviceType]="deviceType"
|
|
||||||
[hasPermissionToCreateActivity]="hasPermissionToCreateOrder"
|
|
||||||
[holdings]="holdings"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
/>
|
|
||||||
@if (hasPermissionToCreateOrder && holdings?.length > 0) {
|
|
||||||
<div class="text-center">
|
|
||||||
<a
|
|
||||||
class="mt-3"
|
|
||||||
i18n
|
|
||||||
mat-stroked-button
|
|
||||||
[routerLink]="['/portfolio', 'activities']"
|
|
||||||
>Manage Activities</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,22 +0,0 @@
|
|||||||
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
|
||||||
import { GfHoldingsTableComponent } from '@ghostfolio/ui/holdings-table';
|
|
||||||
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
|
||||||
|
|
||||||
import { HoldingsPageRoutingModule } from './holdings-page-routing.module';
|
|
||||||
import { HoldingsPageComponent } from './holdings-page.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [HoldingsPageComponent],
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
GfHoldingsTableComponent,
|
|
||||||
GfToggleModule,
|
|
||||||
HoldingsPageRoutingModule,
|
|
||||||
MatButtonModule
|
|
||||||
],
|
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
|
||||||
})
|
|
||||||
export class HoldingsPageModule {}
|
|
@ -1,3 +0,0 @@
|
|||||||
:host {
|
|
||||||
display: block;
|
|
||||||
}
|
|
@ -16,13 +16,6 @@ const routes: Routes = [
|
|||||||
(m) => m.AnalysisPageModule
|
(m) => m.AnalysisPageModule
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'holdings',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./holdings/holdings-page.module').then(
|
|
||||||
(m) => m.HoldingsPageModule
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'activities',
|
path: 'activities',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
|
@ -34,11 +34,6 @@ export class PortfolioPageComponent implements OnDestroy, OnInit {
|
|||||||
label: $localize`Analysis`,
|
label: $localize`Analysis`,
|
||||||
path: ['/portfolio']
|
path: ['/portfolio']
|
||||||
},
|
},
|
||||||
{
|
|
||||||
iconName: 'wallet-outline',
|
|
||||||
label: $localize`Holdings`,
|
|
||||||
path: ['/portfolio', 'holdings']
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
iconName: 'swap-vertical-outline',
|
iconName: 'swap-vertical-outline',
|
||||||
label: $localize`Activities`,
|
label: $localize`Activities`,
|
||||||
|
@ -376,6 +376,9 @@ export class DataService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
public fetchPositions({
|
public fetchPositions({
|
||||||
filters,
|
filters,
|
||||||
range
|
range
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset-profile-icon/asset-profile-icon.component';
|
import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset-profile-icon/asset-profile-icon.component';
|
||||||
import { GfPositionDetailDialogModule } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.module';
|
import { GfPositionDetailDialogModule } from '@ghostfolio/client/components/position-detail-dialog/position-detail-dialog.module';
|
||||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
import { getLocale } from '@ghostfolio/common/helper';
|
import { getLocale } from '@ghostfolio/common/helper';
|
||||||
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user