mirror of https://github.com/ghostfolio/ghostfolio
Browse Source
* Improve usability: lazy load endpoints on tab change * Feature/improve portfolio summary (#285) * Update changelogpull/287/head
committed by
GitHub
27 changed files with 487 additions and 483 deletions
@ -1,74 +0,0 @@ |
|||||
<div class="container p-0"> |
|
||||
<div class="row px-3 py-1"> |
|
||||
<div class="d-flex flex-grow-1" i18n>Cash</div> |
|
||||
<div class="d-flex justify-content-end"> |
|
||||
<gf-value |
|
||||
class="justify-content-end" |
|
||||
[currency]="baseCurrency" |
|
||||
[locale]="locale" |
|
||||
[value]="isLoading ? undefined : overview?.cash" |
|
||||
></gf-value> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="row"> |
|
||||
<div class="col"><hr /></div> |
|
||||
</div> |
|
||||
<div class="row px-3 py-1"> |
|
||||
<div class="d-flex flex-grow-1" i18n>Buy</div> |
|
||||
<div class="d-flex justify-content-end"> |
|
||||
<gf-value |
|
||||
class="justify-content-end" |
|
||||
[currency]="baseCurrency" |
|
||||
[locale]="locale" |
|
||||
[value]="isLoading ? undefined : overview?.totalBuy" |
|
||||
></gf-value> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="row px-3 py-1"> |
|
||||
<div class="d-flex flex-grow-1" i18n>Sell</div> |
|
||||
<div class="d-flex justify-content-end"> |
|
||||
<span |
|
||||
*ngIf="overview?.totalSell || overview?.totalSell === 0" |
|
||||
class="mr-1" |
|
||||
>-</span |
|
||||
> |
|
||||
<gf-value |
|
||||
class="justify-content-end" |
|
||||
[currency]="baseCurrency" |
|
||||
[locale]="locale" |
|
||||
[value]="isLoading ? undefined : overview?.totalSell" |
|
||||
></gf-value> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="row"> |
|
||||
<div class="col"><hr /></div> |
|
||||
</div> |
|
||||
<div class="row px-3"> |
|
||||
<div class="d-flex flex-grow-1" i18n>Investment</div> |
|
||||
<div class="d-flex justify-content-end"> |
|
||||
<gf-value |
|
||||
class="justify-content-end" |
|
||||
[currency]="baseCurrency" |
|
||||
[locale]="locale" |
|
||||
[value]="isLoading ? undefined : overview?.committedFunds" |
|
||||
></gf-value> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="row"> |
|
||||
<div class="col"><hr /></div> |
|
||||
</div> |
|
||||
<div class="row px-3"> |
|
||||
<div class="d-flex flex-grow-1" i18n> |
|
||||
Fees for {{ overview?.ordersCount }} {overview?.ordersCount, plural, =1 |
|
||||
{order} other {orders}} |
|
||||
</div> |
|
||||
<div class="d-flex justify-content-end"> |
|
||||
<gf-value |
|
||||
class="justify-content-end" |
|
||||
[currency]="baseCurrency" |
|
||||
[locale]="locale" |
|
||||
[value]="isLoading ? undefined : overview?.fees" |
|
||||
></gf-value> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
@ -1,28 +0,0 @@ |
|||||
import { |
|
||||
ChangeDetectionStrategy, |
|
||||
Component, |
|
||||
Input, |
|
||||
OnChanges, |
|
||||
OnInit |
|
||||
} from '@angular/core'; |
|
||||
import { PortfolioOverview } from '@ghostfolio/common/interfaces'; |
|
||||
import { Currency } from '@prisma/client'; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'gf-portfolio-overview', |
|
||||
changeDetection: ChangeDetectionStrategy.OnPush, |
|
||||
templateUrl: './portfolio-overview.component.html', |
|
||||
styleUrls: ['./portfolio-overview.component.scss'] |
|
||||
}) |
|
||||
export class PortfolioOverviewComponent implements OnChanges, OnInit { |
|
||||
@Input() baseCurrency: Currency; |
|
||||
@Input() isLoading: boolean; |
|
||||
@Input() locale: string; |
|
||||
@Input() overview: PortfolioOverview; |
|
||||
|
|
||||
public constructor() {} |
|
||||
|
|
||||
public ngOnInit() {} |
|
||||
|
|
||||
public ngOnChanges() {} |
|
||||
} |
|
@ -1,54 +0,0 @@ |
|||||
<div class="container p-0"> |
|
||||
<div class="row no-gutters"> |
|
||||
<div class="flex-grow-1"></div> |
|
||||
<div *ngIf="isLoading" class="align-items-center d-flex"> |
|
||||
<ngx-skeleton-loader |
|
||||
animation="pulse" |
|
||||
class="mb-2" |
|
||||
[theme]="{ |
|
||||
height: '4rem', |
|
||||
width: '15rem' |
|
||||
}" |
|
||||
></ngx-skeleton-loader> |
|
||||
</div> |
|
||||
<div |
|
||||
[hidden]="isLoading" |
|
||||
class="display-4 font-weight-bold m-0 text-center value-container" |
|
||||
> |
|
||||
<span #value id="value"></span> |
|
||||
</div> |
|
||||
<div class="flex-grow-1 px-1"> |
|
||||
<ngx-skeleton-loader |
|
||||
*ngIf="isLoading" |
|
||||
animation="pulse" |
|
||||
[theme]="{ |
|
||||
height: '1.3rem', |
|
||||
width: '2.5rem' |
|
||||
}" |
|
||||
></ngx-skeleton-loader> |
|
||||
<div *ngIf="!isLoading"> |
|
||||
{{ unit }} |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div *ngIf="showDetails" class="row"> |
|
||||
<div class="d-flex col justify-content-end"> |
|
||||
<gf-value |
|
||||
[colorizeSign]="true" |
|
||||
[isCurrency]="true" |
|
||||
[locale]="locale" |
|
||||
[value]="isLoading ? undefined : performance?.currentNetPerformance" |
|
||||
></gf-value> |
|
||||
</div> |
|
||||
<div class="col"> |
|
||||
<gf-value |
|
||||
[colorizeSign]="true" |
|
||||
[isPercent]="true" |
|
||||
[locale]="locale" |
|
||||
[value]=" |
|
||||
isLoading ? undefined : performance?.currentNetPerformancePercent |
|
||||
" |
|
||||
></gf-value> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
@ -1,9 +0,0 @@ |
|||||
:host { |
|
||||
display: block; |
|
||||
|
|
||||
.value-container { |
|
||||
#value { |
|
||||
font-variant-numeric: tabular-nums; |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,65 +0,0 @@ |
|||||
import { |
|
||||
ChangeDetectionStrategy, |
|
||||
Component, |
|
||||
ElementRef, |
|
||||
Input, |
|
||||
OnChanges, |
|
||||
OnInit, |
|
||||
ViewChild |
|
||||
} from '@angular/core'; |
|
||||
import { PortfolioPerformance } from '@ghostfolio/common/interfaces'; |
|
||||
import { Currency } from '@prisma/client'; |
|
||||
import { CountUp } from 'countup.js'; |
|
||||
import { isNumber } from 'lodash'; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'gf-portfolio-performance-summary', |
|
||||
changeDetection: ChangeDetectionStrategy.OnPush, |
|
||||
templateUrl: './portfolio-performance-summary.component.html', |
|
||||
styleUrls: ['./portfolio-performance-summary.component.scss'] |
|
||||
}) |
|
||||
export class PortfolioPerformanceSummaryComponent implements OnChanges, OnInit { |
|
||||
@Input() baseCurrency: Currency; |
|
||||
@Input() isLoading: boolean; |
|
||||
@Input() locale: string; |
|
||||
@Input() performance: PortfolioPerformance; |
|
||||
@Input() showDetails: boolean; |
|
||||
|
|
||||
@ViewChild('value') value: ElementRef; |
|
||||
|
|
||||
public unit: string; |
|
||||
|
|
||||
public constructor() {} |
|
||||
|
|
||||
public ngOnInit() {} |
|
||||
|
|
||||
public ngOnChanges() { |
|
||||
if (this.isLoading) { |
|
||||
if (this.value?.nativeElement) { |
|
||||
this.value.nativeElement.innerHTML = ''; |
|
||||
} |
|
||||
} else { |
|
||||
if (isNumber(this.performance?.currentValue)) { |
|
||||
this.unit = this.baseCurrency; |
|
||||
|
|
||||
new CountUp('value', this.performance?.currentValue, { |
|
||||
decimalPlaces: 2, |
|
||||
duration: 1, |
|
||||
separator: `'` |
|
||||
}).start(); |
|
||||
} else if (this.performance?.currentValue === null) { |
|
||||
this.unit = '%'; |
|
||||
|
|
||||
new CountUp( |
|
||||
'value', |
|
||||
this.performance?.currentNetPerformancePercent * 100, |
|
||||
{ |
|
||||
decimalPlaces: 2, |
|
||||
duration: 0.75, |
|
||||
separator: `'` |
|
||||
} |
|
||||
).start(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,14 +0,0 @@ |
|||||
import { CommonModule } from '@angular/common'; |
|
||||
import { NgModule } from '@angular/core'; |
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; |
|
||||
|
|
||||
import { GfValueModule } from '../value/value.module'; |
|
||||
import { PortfolioPerformanceSummaryComponent } from './portfolio-performance-summary.component'; |
|
||||
|
|
||||
@NgModule({ |
|
||||
declarations: [PortfolioPerformanceSummaryComponent], |
|
||||
exports: [PortfolioPerformanceSummaryComponent], |
|
||||
imports: [CommonModule, GfValueModule, NgxSkeletonLoaderModule], |
|
||||
providers: [] |
|
||||
}) |
|
||||
export class GfPortfolioPerformanceSummaryModule {} |
|
@ -1,3 +1,9 @@ |
|||||
:host { |
:host { |
||||
display: block; |
display: block; |
||||
|
|
||||
|
.value-container { |
||||
|
#value { |
||||
|
font-variant-numeric: tabular-nums; |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
|
@ -0,0 +1,134 @@ |
|||||
|
<div class="container p-0"> |
||||
|
<div class="row px-3 py-1"> |
||||
|
<div class="d-flex flex-grow-1" i18n>Time in Market</div> |
||||
|
<div class="d-flex justify-content-end"> |
||||
|
{{ timeInMarket }} |
||||
|
<gf-value class="justify-content-end" [value]="timeInMarket"></gf-value> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col"><hr /></div> |
||||
|
</div> |
||||
|
<div class="row px-3"> |
||||
|
<div class="d-flex flex-grow-1" i18n> |
||||
|
Fees for {{ summary?.ordersCount }} {summary?.ordersCount, plural, =1 |
||||
|
{order} other {orders}} |
||||
|
</div> |
||||
|
<div class="d-flex justify-content-end"> |
||||
|
<gf-value |
||||
|
class="justify-content-end" |
||||
|
[currency]="baseCurrency" |
||||
|
[locale]="locale" |
||||
|
[value]="isLoading ? undefined : summary?.fees" |
||||
|
></gf-value> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col"><hr /></div> |
||||
|
</div> |
||||
|
<div class="row px-3 py-1"> |
||||
|
<div class="d-flex flex-grow-1" i18n>Buy</div> |
||||
|
<div class="d-flex justify-content-end"> |
||||
|
<gf-value |
||||
|
class="justify-content-end" |
||||
|
[currency]="baseCurrency" |
||||
|
[locale]="locale" |
||||
|
[value]="isLoading ? undefined : summary?.totalBuy" |
||||
|
></gf-value> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row px-3 py-1"> |
||||
|
<div class="d-flex flex-grow-1" i18n>Sell</div> |
||||
|
<div class="d-flex justify-content-end"> |
||||
|
<span *ngIf="summary?.totalSell || summary?.totalSell === 0" class="mr-1" |
||||
|
>-</span |
||||
|
> |
||||
|
<gf-value |
||||
|
class="justify-content-end" |
||||
|
[currency]="baseCurrency" |
||||
|
[locale]="locale" |
||||
|
[value]="isLoading ? undefined : summary?.totalSell" |
||||
|
></gf-value> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col"><hr /></div> |
||||
|
</div> |
||||
|
<div class="row px-3 py-1"> |
||||
|
<div class="d-flex flex-grow-1" i18n>Investment</div> |
||||
|
<div class="d-flex justify-content-end"> |
||||
|
<gf-value |
||||
|
class="justify-content-end" |
||||
|
[currency]="baseCurrency" |
||||
|
[locale]="locale" |
||||
|
[value]="isLoading ? undefined : summary?.committedFunds" |
||||
|
></gf-value> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row px-3 py-1"> |
||||
|
<div class="d-flex flex-grow-1" i18n>Absolute Performance</div> |
||||
|
<div class="d-flex justify-content-end"> |
||||
|
<gf-value |
||||
|
class="justify-content-end" |
||||
|
[currency]="baseCurrency" |
||||
|
[locale]="locale" |
||||
|
[value]="isLoading ? undefined : summary?.currentGrossPerformance" |
||||
|
></gf-value> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row px-3 py-1"> |
||||
|
<div class="d-flex flex-grow-1 ml-3" i18n>Performance (TWR)</div> |
||||
|
<div class="d-flex flex-column flex-wrap justify-content-end"> |
||||
|
<gf-value |
||||
|
class="justify-content-end" |
||||
|
position="end" |
||||
|
[colorizeSign]="true" |
||||
|
[isPercent]="true" |
||||
|
[locale]="locale" |
||||
|
[value]=" |
||||
|
isLoading ? undefined : summary?.currentGrossPerformancePercent |
||||
|
" |
||||
|
></gf-value> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col"><hr /></div> |
||||
|
</div> |
||||
|
<div class="row px-3 py-1"> |
||||
|
<div class="d-flex flex-grow-1" i18n>Value</div> |
||||
|
<div class="d-flex flex-column flex-wrap justify-content-end"> |
||||
|
<gf-value |
||||
|
class="justify-content-end" |
||||
|
position="end" |
||||
|
[currency]="baseCurrency" |
||||
|
[locale]="locale" |
||||
|
[value]="isLoading ? undefined : summary?.currentValue" |
||||
|
></gf-value> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row px-3 py-1"> |
||||
|
<div class="d-flex flex-grow-1" i18n>Cash</div> |
||||
|
<div class="d-flex justify-content-end"> |
||||
|
<gf-value |
||||
|
class="justify-content-end" |
||||
|
[currency]="baseCurrency" |
||||
|
[locale]="locale" |
||||
|
[value]="isLoading ? undefined : summary?.cash" |
||||
|
></gf-value> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col"><hr /></div> |
||||
|
</div> |
||||
|
<div class="row px-3 py-1"> |
||||
|
<div class="d-flex flex-grow-1" i18n>Net Worth</div> |
||||
|
<div class="d-flex justify-content-end"> |
||||
|
<gf-value |
||||
|
class="justify-content-end" |
||||
|
[currency]="baseCurrency" |
||||
|
[locale]="locale" |
||||
|
[value]="isLoading ? undefined : summary?.netWorth" |
||||
|
></gf-value> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
@ -0,0 +1,41 @@ |
|||||
|
import { |
||||
|
ChangeDetectionStrategy, |
||||
|
Component, |
||||
|
Input, |
||||
|
OnChanges, |
||||
|
OnInit |
||||
|
} from '@angular/core'; |
||||
|
import { PortfolioSummary } from '@ghostfolio/common/interfaces'; |
||||
|
import { Currency } from '@prisma/client'; |
||||
|
import { formatDistanceToNow } from 'date-fns'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'gf-portfolio-summary', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
templateUrl: './portfolio-summary.component.html', |
||||
|
styleUrls: ['./portfolio-summary.component.scss'] |
||||
|
}) |
||||
|
export class PortfolioSummaryComponent implements OnChanges, OnInit { |
||||
|
@Input() baseCurrency: Currency; |
||||
|
@Input() isLoading: boolean; |
||||
|
@Input() locale: string; |
||||
|
@Input() summary: PortfolioSummary; |
||||
|
|
||||
|
public timeInMarket: string; |
||||
|
|
||||
|
public constructor() {} |
||||
|
|
||||
|
public ngOnInit() {} |
||||
|
|
||||
|
public ngOnChanges() { |
||||
|
if (this.summary) { |
||||
|
if (this.summary.firstOrderDate) { |
||||
|
this.timeInMarket = formatDistanceToNow(this.summary.firstOrderDate); |
||||
|
} else { |
||||
|
this.timeInMarket = '-'; |
||||
|
} |
||||
|
} else { |
||||
|
this.timeInMarket = undefined; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
import { PortfolioPerformance } from './portfolio-performance.interface'; |
||||
|
|
||||
|
export interface PortfolioSummary extends PortfolioPerformance { |
||||
|
cash: number; |
||||
|
committedFunds: number; |
||||
|
fees: number; |
||||
|
firstOrderDate: Date; |
||||
|
netWorth: number; |
||||
|
ordersCount: number; |
||||
|
totalBuy: number; |
||||
|
totalSell: number; |
||||
|
} |
Loading…
Reference in new issue