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
Thomas
4 years ago
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,67 +1,54 @@ |
|||
<div class="container p-0"> |
|||
<div class="row px-3 py-2"> |
|||
<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 : performance?.currentValue" |
|||
></gf-value> |
|||
<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 class="row px-3 py-1"> |
|||
<div class="d-flex flex-grow-1" i18n>Absolute Performance</div> |
|||
<div class="d-flex flex-column flex-wrap justify-content-end"> |
|||
<div *ngIf="showDetails" class="row"> |
|||
<div class="d-flex col justify-content-end"> |
|||
<gf-value |
|||
class="justify-content-end" |
|||
position="end" |
|||
[colorizeSign]="true" |
|||
[currency]="baseCurrency" |
|||
[isCurrency]="true" |
|||
[locale]="locale" |
|||
[value]="isLoading ? undefined : performance?.currentGrossPerformance" |
|||
[value]="isLoading ? undefined : performance?.currentNetPerformance" |
|||
></gf-value> |
|||
</div> |
|||
</div> |
|||
<div class="row px-3 py-1"> |
|||
<div class="d-flex flex-grow-1" i18n>Performance (TWR)</div> |
|||
<div class="d-flex flex-column flex-wrap justify-content-end"> |
|||
<div class="col"> |
|||
<gf-value |
|||
class="justify-content-end" |
|||
position="end" |
|||
[colorizeSign]="true" |
|||
[isPercent]="true" |
|||
[locale]="locale" |
|||
[value]=" |
|||
isLoading ? undefined : performance?.currentGrossPerformancePercent |
|||
isLoading ? undefined : performance?.currentNetPerformancePercent |
|||
" |
|||
></gf-value> |
|||
</div> |
|||
</div> |
|||
<!-- |
|||
<div class="row px-3 py-2"> |
|||
<div class="d-flex flex-grow-1" i18n>Net performance</div> |
|||
<div class="d-flex flex-column flex-wrap justify-content-end"> |
|||
<gf-value |
|||
class="justify-content-end mb-2" |
|||
position="end" |
|||
[colorizeSign]="true" |
|||
[currency]="baseCurrency" |
|||
[locale]="locale" |
|||
[value]="isLoading ? undefined : performance?.currentNetPerformance" |
|||
></gf-value> |
|||
<gf-value |
|||
class="justify-content-end" |
|||
position="end" |
|||
[colorizeSign]="true" |
|||
[isPercent]="true" |
|||
[locale]="locale" |
|||
[value]=" |
|||
isLoading ? undefined : performance?.currentNetPerformancePercent |
|||
" |
|||
></gf-value> |
|||
</div> |
|||
</div> |
|||
--> |
|||
</div> |
|||
|
@ -1,3 +1,9 @@ |
|||
:host { |
|||
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