mirror of https://github.com/ghostfolio/ghostfolio
Browse Source
* Move holdings table to holdings tab of home page * Deprecate api/v1/portfolio/positions endpoint * Update changelogpull/3370/head
committed by
GitHub
32 changed files with 108 additions and 649 deletions
@ -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,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 {} |
|
@ -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; |
|
||||
} |
|
Loading…
Reference in new issue