Feature/add top performers to analysis page (#613)
* Add Top 3 / Bottom 3 performers * Update changelog
This commit is contained in:
parent
313d2a2f79
commit
aca37a27f9
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added the _Top 3_ and _Bottom 3_ performers to the analysis page
|
||||||
|
|
||||||
## 1.99.0 - 01.01.2022
|
## 1.99.0 - 01.01.2022
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -2,9 +2,10 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
|||||||
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 { User } from '@ghostfolio/common/interfaces';
|
import { Position, User } from '@ghostfolio/common/interfaces';
|
||||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
|
import { sortBy } from 'lodash';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
@ -16,10 +17,12 @@ import { takeUntil } from 'rxjs/operators';
|
|||||||
templateUrl: './analysis-page.html'
|
templateUrl: './analysis-page.html'
|
||||||
})
|
})
|
||||||
export class AnalysisPageComponent implements OnDestroy, OnInit {
|
export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||||
|
public bottom3: Position[];
|
||||||
public daysInMarket: number;
|
public daysInMarket: number;
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
public investments: InvestmentItem[];
|
public investments: InvestmentItem[];
|
||||||
|
public top3: Position[];
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
@ -58,6 +61,26 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.fetchPositions({ range: 'max' })
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(({ positions }) => {
|
||||||
|
const positionsSorted = sortBy(
|
||||||
|
positions,
|
||||||
|
'netPerformancePercentage'
|
||||||
|
).reverse();
|
||||||
|
|
||||||
|
this.top3 = positionsSorted.slice(0, 3);
|
||||||
|
|
||||||
|
if (positions?.length > 3) {
|
||||||
|
this.bottom3 = positionsSorted.slice(-3).reverse();
|
||||||
|
} else {
|
||||||
|
this.bottom3 = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((state) => {
|
.subscribe((state) => {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<mat-card class="mb-3">
|
<mat-card class="mb-3">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title class="align-items-center d-flex" i18n
|
<mat-card-title class="align-items-center d-flex" i18n
|
||||||
>Timeline</mat-card-title
|
>Investment Timeline</mat-card-title
|
||||||
>
|
>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
@ -19,4 +19,82 @@
|
|||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title class="align-items-center d-flex" i18n
|
||||||
|
>Top 3</mat-card-title
|
||||||
|
>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<div *ngFor="let position of top3; let i = index" class="d-flex py-1">
|
||||||
|
<div class="flex-grow-1 mr-2 text-truncate">
|
||||||
|
{{ i + 1 }}. {{ position.name }}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<gf-value
|
||||||
|
class="justify-content-end"
|
||||||
|
position="end"
|
||||||
|
[colorizeSign]="true"
|
||||||
|
[isPercent]="true"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[value]="position.netPerformancePercentage"
|
||||||
|
></gf-value>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ngx-skeleton-loader
|
||||||
|
*ngIf="!top3"
|
||||||
|
animation="pulse"
|
||||||
|
[theme]="{
|
||||||
|
height: '1.5rem',
|
||||||
|
width: '100%'
|
||||||
|
}"
|
||||||
|
></ngx-skeleton-loader>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title class="align-items-center d-flex" i18n
|
||||||
|
>Bottom 3</mat-card-title
|
||||||
|
>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<div
|
||||||
|
*ngFor="let position of bottom3; let i = index"
|
||||||
|
class="d-flex py-1"
|
||||||
|
>
|
||||||
|
<div class="flex-grow-1 mr-2 text-truncate">
|
||||||
|
{{ i + 1 }}. {{ position.name }}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<gf-value
|
||||||
|
class="justify-content-end"
|
||||||
|
position="end"
|
||||||
|
[colorizeSign]="true"
|
||||||
|
[isPercent]="true"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[value]="position.netPerformancePercentage"
|
||||||
|
></gf-value>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ngx-skeleton-loader
|
||||||
|
*ngIf="!bottom3"
|
||||||
|
animation="pulse"
|
||||||
|
[theme]="{
|
||||||
|
height: '1.5rem',
|
||||||
|
width: '100%'
|
||||||
|
}"
|
||||||
|
></ngx-skeleton-loader>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,8 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { GfInvestmentChartModule } from '@ghostfolio/client/components/investment-chart/investment-chart.module';
|
import { GfInvestmentChartModule } from '@ghostfolio/client/components/investment-chart/investment-chart.module';
|
||||||
|
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||||
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
import { AnalysisPageRoutingModule } from './analysis-page-routing.module';
|
import { AnalysisPageRoutingModule } from './analysis-page-routing.module';
|
||||||
import { AnalysisPageComponent } from './analysis-page.component';
|
import { AnalysisPageComponent } from './analysis-page.component';
|
||||||
@ -13,7 +15,9 @@ import { AnalysisPageComponent } from './analysis-page.component';
|
|||||||
AnalysisPageRoutingModule,
|
AnalysisPageRoutingModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfInvestmentChartModule,
|
GfInvestmentChartModule,
|
||||||
MatCardModule
|
GfValueModule,
|
||||||
|
MatCardModule,
|
||||||
|
NgxSkeletonLoaderModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user