Feature/restructure page hierarchy (#259)
* Introduce portfolio group, remove tools * Extract allocations to separate page * Update changelog
This commit is contained in:
parent
dfcf826b4f
commit
f743c03e17
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Restructured the page hierarchy
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed an issue with the currency conversion of the market price in the position detail dialog
|
- Fixed an issue with the currency conversion of the market price in the position detail dialog
|
||||||
|
@ -52,6 +52,41 @@ const routes: Routes = [
|
|||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./pages/home/home-page.module').then((m) => m.HomePageModule)
|
import('./pages/home/home-page.module').then((m) => m.HomePageModule)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'portfolio',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./pages/portfolio/portfolio-page.module').then(
|
||||||
|
(m) => m.PortfolioPageModule
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'portfolio/allocations',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./pages/portfolio/allocations/allocations-page.module').then(
|
||||||
|
(m) => m.AllocationsPageModule
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'portfolio/analysis',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./pages/portfolio/analysis/analysis-page.module').then(
|
||||||
|
(m) => m.AnalysisPageModule
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'portfolio/report',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./pages/portfolio/report/report-page.module').then(
|
||||||
|
(m) => m.ReportPageModule
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'portfolio/transactions',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./pages/portfolio/transactions/transactions-page.module').then(
|
||||||
|
(m) => m.TransactionsPageModule
|
||||||
|
)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'pricing',
|
path: 'pricing',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
@ -80,32 +115,6 @@ const routes: Routes = [
|
|||||||
(m) => m.LandingPageModule
|
(m) => m.LandingPageModule
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'tools',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./pages/tools/tools-page.module').then((m) => m.ToolsPageModule)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'tools/analysis',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./pages/tools/analysis/analysis-page.module').then(
|
|
||||||
(m) => m.AnalysisPageModule
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'tools/report',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./pages/tools/report/report-page.module').then(
|
|
||||||
(m) => m.ReportPageModule
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'transactions',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./pages/transactions/transactions-page.module').then(
|
|
||||||
(m) => m.TransactionsPageModule
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'webauthn',
|
path: 'webauthn',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
|
@ -22,27 +22,22 @@
|
|||||||
i18n
|
i18n
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'font-weight-bold':
|
'font-weight-bold': currentRoute === 'portfolio',
|
||||||
currentRoute === 'analysis' ||
|
'text-decoration-underline': currentRoute === 'portfolio'
|
||||||
currentRoute === 'report' ||
|
|
||||||
currentRoute === 'tools',
|
|
||||||
'text-decoration-underline':
|
|
||||||
currentRoute === 'analysis' ||
|
|
||||||
currentRoute === 'report' ||
|
|
||||||
currentRoute === 'tools'
|
|
||||||
}"
|
}"
|
||||||
[routerLink]="['/tools']"
|
[routerLink]="['/portfolio']"
|
||||||
>Tools</a
|
>Portfolio</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
*ngIf="user?.settings?.viewMode !== 'DEFAULT'"
|
||||||
class="d-none d-sm-block mx-1"
|
class="d-none d-sm-block mx-1"
|
||||||
i18n
|
i18n
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'font-weight-bold': currentRoute === 'transactions',
|
'font-weight-bold': currentRoute === 'portfolio',
|
||||||
'text-decoration-underline': currentRoute === 'transactions'
|
'text-decoration-underline': currentRoute === 'portfolio'
|
||||||
}"
|
}"
|
||||||
[routerLink]="['/transactions']"
|
[routerLink]="['/portfolio', 'transactions']"
|
||||||
>Transactions</a
|
>Transactions</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@ -166,22 +161,20 @@
|
|||||||
i18n
|
i18n
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'font-weight-bold':
|
'font-weight-bold': currentRoute === 'portfolio'
|
||||||
currentRoute === 'analysis' ||
|
|
||||||
currentRoute === 'report' ||
|
|
||||||
currentRoute === 'tools'
|
|
||||||
}"
|
}"
|
||||||
[routerLink]="['/tools']"
|
[routerLink]="['/portfolio']"
|
||||||
>Tools</a
|
>Portfolio</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
*ngIf="user?.settings?.viewMode !== 'DEFAULT'"
|
||||||
class="d-block d-sm-none"
|
class="d-block d-sm-none"
|
||||||
i18n
|
i18n
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'font-weight-bold': currentRoute === 'transactions'
|
'font-weight-bold': currentRoute === 'portfolio'
|
||||||
}"
|
}"
|
||||||
[routerLink]="['/transactions']"
|
[routerLink]="['/portfolio', 'transactions']"
|
||||||
>Transactions</a
|
>Transactions</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<a
|
<a
|
||||||
class="align-items-center justify-content-center"
|
class="align-items-center justify-content-center"
|
||||||
color="primary"
|
color="primary"
|
||||||
[routerLink]="['/transactions']"
|
[routerLink]="['/portfolio', 'transactions']"
|
||||||
mat-button
|
mat-button
|
||||||
>
|
>
|
||||||
<span i18n>Time to add your first transaction.</span>
|
<span i18n>Time to add your first transaction.</span>
|
||||||
|
@ -111,7 +111,7 @@
|
|||||||
class="my-3 text-center"
|
class="my-3 text-center"
|
||||||
>
|
>
|
||||||
<button i18n mat-stroked-button (click)="onShowAllPositions()">
|
<button i18n mat-stroked-button (click)="onShowAllPositions()">
|
||||||
Show all positions...
|
Show all...
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
<ion-icon name="wallet-outline" size="large"></ion-icon>
|
<ion-icon name="wallet-outline" size="large"></ion-icon>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="container justify-content-center pb-3 px-3 positions">
|
<div class="container justify-content-center pb-3 px-3 positions">
|
||||||
<h3 class="d-flex justify-content-center mb-3" i18n>Positions</h3>
|
<h3 class="d-flex justify-content-center mb-3" i18n>Holdings</h3>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="align-items-center col">
|
<div class="align-items-center col">
|
||||||
<div class="pb-2 text-center">
|
<div class="pb-2 text-center">
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||||
|
|
||||||
|
import { AllocationsPageComponent } from './allocations-page.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', component: AllocationsPageComponent, canActivate: [AuthGuard] }
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class AllocationsPageRoutingModule {}
|
@ -4,22 +4,17 @@ 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 { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
||||||
import {
|
import { PortfolioPosition, User } from '@ghostfolio/common/interfaces';
|
||||||
PortfolioItem,
|
|
||||||
PortfolioPosition,
|
|
||||||
User
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
|
||||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
|
||||||
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';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-analysis-page',
|
selector: 'gf-allocations-page',
|
||||||
templateUrl: './analysis-page.html',
|
templateUrl: './allocations-page.html',
|
||||||
styleUrls: ['./analysis-page.scss']
|
styleUrls: ['./allocations-page.scss']
|
||||||
})
|
})
|
||||||
export class AnalysisPageComponent implements OnDestroy, OnInit {
|
export class AllocationsPageComponent implements OnDestroy, OnInit {
|
||||||
public accounts: {
|
public accounts: {
|
||||||
[symbol: string]: Pick<PortfolioPosition, 'name'> & { value: number };
|
[symbol: string]: Pick<PortfolioPosition, 'name'> & { value: number };
|
||||||
};
|
};
|
||||||
@ -36,7 +31,6 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
{ label: 'Initial', value: 'original' },
|
{ label: 'Initial', value: 'original' },
|
||||||
{ label: 'Current', value: 'current' }
|
{ label: 'Current', value: 'current' }
|
||||||
];
|
];
|
||||||
public investments: InvestmentItem[];
|
|
||||||
public portfolioPositions: { [symbol: string]: PortfolioPosition };
|
public portfolioPositions: { [symbol: string]: PortfolioPosition };
|
||||||
public positions: { [symbol: string]: any };
|
public positions: { [symbol: string]: any };
|
||||||
public positionsArray: PortfolioPosition[];
|
public positionsArray: PortfolioPosition[];
|
||||||
@ -71,15 +65,6 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
this.hasImpersonationId = !!aId;
|
this.hasImpersonationId = !!aId;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dataService
|
|
||||||
.fetchInvestments()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((response) => {
|
|
||||||
this.investments = response;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchPortfolioPositions({})
|
.fetchPortfolioPositions({})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
@ -1,16 +1,14 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="mb-5 row">
|
<div class="mb-5 row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3 class="d-flex justify-content-center mb-3" i18n>Analysis</h3>
|
<h3 class="d-flex justify-content-center mb-3" i18n>Allocations</h3>
|
||||||
<div class="mb-4">
|
<gf-positions-table
|
||||||
<h4 class="m-0" i18n>Positions</h4>
|
class="mb-4"
|
||||||
<gf-positions-table
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[deviceType]="deviceType"
|
||||||
[deviceType]="deviceType"
|
[locale]="user?.settings?.locale"
|
||||||
[locale]="user?.settings?.locale"
|
[positions]="positionsArray"
|
||||||
[positions]="positionsArray"
|
></gf-positions-table>
|
||||||
></gf-positions-table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="proportion-charts row">
|
<div class="proportion-charts row">
|
||||||
@ -192,20 +190,4 @@
|
|||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="investment-chart row">
|
|
||||||
<div class="col-lg">
|
|
||||||
<mat-card class="mb-3">
|
|
||||||
<mat-card-header>
|
|
||||||
<mat-card-title class="align-items-center d-flex" i18n
|
|
||||||
>Timeline</mat-card-title
|
|
||||||
>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<gf-investment-chart
|
|
||||||
[investments]='investments'
|
|
||||||
></gf-investment-chart>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
@ -1,22 +1,20 @@
|
|||||||
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 { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { GfInvestmentChartModule } from '@ghostfolio/client/components/investment-chart/investment-chart.module';
|
|
||||||
import { PortfolioProportionChartModule } from '@ghostfolio/client/components/portfolio-proportion-chart/portfolio-proportion-chart.module';
|
import { PortfolioProportionChartModule } from '@ghostfolio/client/components/portfolio-proportion-chart/portfolio-proportion-chart.module';
|
||||||
import { GfPositionsTableModule } from '@ghostfolio/client/components/positions-table/positions-table.module';
|
import { GfPositionsTableModule } from '@ghostfolio/client/components/positions-table/positions-table.module';
|
||||||
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
||||||
import { GfWorldMapChartModule } from '@ghostfolio/client/components/world-map-chart/world-map-chart.module';
|
import { GfWorldMapChartModule } from '@ghostfolio/client/components/world-map-chart/world-map-chart.module';
|
||||||
|
|
||||||
import { AnalysisPageRoutingModule } from './analysis-page-routing.module';
|
import { AllocationsPageRoutingModule } from './allocations-page-routing.module';
|
||||||
import { AnalysisPageComponent } from './analysis-page.component';
|
import { AllocationsPageComponent } from './allocations-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AnalysisPageComponent],
|
declarations: [AllocationsPageComponent],
|
||||||
exports: [],
|
exports: [],
|
||||||
imports: [
|
imports: [
|
||||||
AnalysisPageRoutingModule,
|
AllocationsPageRoutingModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfInvestmentChartModule,
|
|
||||||
GfPositionsTableModule,
|
GfPositionsTableModule,
|
||||||
GfToggleModule,
|
GfToggleModule,
|
||||||
GfWorldMapChartModule,
|
GfWorldMapChartModule,
|
||||||
@ -26,4 +24,4 @@ import { AnalysisPageComponent } from './analysis-page.component';
|
|||||||
providers: [],
|
providers: [],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
})
|
})
|
||||||
export class AnalysisPageModule {}
|
export class AllocationsPageModule {}
|
@ -1,16 +1,4 @@
|
|||||||
:host {
|
:host {
|
||||||
.investment-chart {
|
|
||||||
.mat-card {
|
|
||||||
.mat-card-content {
|
|
||||||
aspect-ratio: 16 / 9;
|
|
||||||
|
|
||||||
gf-investment-chart {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.proportion-charts {
|
.proportion-charts {
|
||||||
.mat-card {
|
.mat-card {
|
||||||
.mat-card-content {
|
.mat-card-content {
|
@ -0,0 +1,92 @@
|
|||||||
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { ToggleOption } from '@ghostfolio/client/components/toggle/interfaces/toggle-option.type';
|
||||||
|
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 { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||||
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'gf-analysis-page',
|
||||||
|
templateUrl: './analysis-page.html',
|
||||||
|
styleUrls: ['./analysis-page.scss']
|
||||||
|
})
|
||||||
|
export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||||
|
public accounts: {
|
||||||
|
[symbol: string]: Pick<PortfolioPosition, 'name'> & { value: number };
|
||||||
|
};
|
||||||
|
public continents: {
|
||||||
|
[code: string]: { name: string; value: number };
|
||||||
|
};
|
||||||
|
public countries: {
|
||||||
|
[code: string]: { name: string; value: number };
|
||||||
|
};
|
||||||
|
public deviceType: string;
|
||||||
|
public hasImpersonationId: boolean;
|
||||||
|
public period = 'current';
|
||||||
|
public periodOptions: ToggleOption[] = [
|
||||||
|
{ label: 'Initial', value: 'original' },
|
||||||
|
{ label: 'Current', value: 'current' }
|
||||||
|
];
|
||||||
|
public investments: InvestmentItem[];
|
||||||
|
public portfolioPositions: { [symbol: string]: PortfolioPosition };
|
||||||
|
public positions: { [symbol: string]: any };
|
||||||
|
public sectors: {
|
||||||
|
[name: string]: { name: string; value: number };
|
||||||
|
};
|
||||||
|
public user: User;
|
||||||
|
|
||||||
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private dataService: DataService,
|
||||||
|
private deviceService: DeviceDetectorService,
|
||||||
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
|
private userService: UserService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the controller
|
||||||
|
*/
|
||||||
|
public ngOnInit() {
|
||||||
|
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
||||||
|
|
||||||
|
this.impersonationStorageService
|
||||||
|
.onChangeHasImpersonation()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((aId) => {
|
||||||
|
this.hasImpersonationId = !!aId;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.fetchInvestments()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((response) => {
|
||||||
|
this.investments = response;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.userService.stateChanged
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((state) => {
|
||||||
|
if (state?.user) {
|
||||||
|
this.user = state.user;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.unsubscribeSubject.next();
|
||||||
|
this.unsubscribeSubject.complete();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="investment-chart row">
|
||||||
|
<div class="col-lg">
|
||||||
|
<h3 class="d-flex justify-content-center mb-3" i18n>Analysis</h3>
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title class="align-items-center d-flex" i18n
|
||||||
|
>Timeline</mat-card-title
|
||||||
|
>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<gf-investment-chart
|
||||||
|
class="h-100"
|
||||||
|
[investments]="investments"
|
||||||
|
></gf-investment-chart>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,21 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { GfInvestmentChartModule } from '@ghostfolio/client/components/investment-chart/investment-chart.module';
|
||||||
|
|
||||||
|
import { AnalysisPageRoutingModule } from './analysis-page-routing.module';
|
||||||
|
import { AnalysisPageComponent } from './analysis-page.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [AnalysisPageComponent],
|
||||||
|
exports: [],
|
||||||
|
imports: [
|
||||||
|
AnalysisPageRoutingModule,
|
||||||
|
CommonModule,
|
||||||
|
GfInvestmentChartModule,
|
||||||
|
MatCardModule
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class AnalysisPageModule {}
|
@ -0,0 +1,9 @@
|
|||||||
|
:host {
|
||||||
|
.investment-chart {
|
||||||
|
.mat-card {
|
||||||
|
.mat-card-content {
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,14 +2,14 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||||
|
|
||||||
import { ToolsPageComponent } from './tools-page.component';
|
import { PortfolioPageComponent } from './portfolio-page.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: ToolsPageComponent, canActivate: [AuthGuard] }
|
{ path: '', component: PortfolioPageComponent, canActivate: [AuthGuard] }
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule]
|
||||||
})
|
})
|
||||||
export class ToolsPageRoutingModule {}
|
export class PortfolioPageRoutingModule {}
|
@ -0,0 +1,44 @@
|
|||||||
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'gf-portfolio-page',
|
||||||
|
templateUrl: './portfolio-page.html',
|
||||||
|
styleUrls: ['./portfolio-page.scss']
|
||||||
|
})
|
||||||
|
export class PortfolioPageComponent implements OnDestroy, OnInit {
|
||||||
|
public user: User;
|
||||||
|
|
||||||
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private userService: UserService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the controller
|
||||||
|
*/
|
||||||
|
public ngOnInit() {
|
||||||
|
this.userService.stateChanged
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((state) => {
|
||||||
|
if (state?.user) {
|
||||||
|
this.user = state.user;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.unsubscribeSubject.next();
|
||||||
|
this.unsubscribeSubject.complete();
|
||||||
|
}
|
||||||
|
}
|
83
apps/client/src/app/pages/portfolio/portfolio-page.html
Normal file
83
apps/client/src/app/pages/portfolio/portfolio-page.html
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<div class="container">
|
||||||
|
<h3 class="d-flex justify-content-center mb-3" i18n>Portfolio</h3>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-6">
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<h4 i18n>Transactions</h4>
|
||||||
|
<p class="mb-0">Manage your transactions.</p>
|
||||||
|
<p class="text-right">
|
||||||
|
<a
|
||||||
|
color="primary"
|
||||||
|
i18n
|
||||||
|
mat-button
|
||||||
|
[routerLink]="['/portfolio', 'transactions']"
|
||||||
|
>
|
||||||
|
Open Transactions →
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
*ngIf="user?.settings?.viewMode === 'DEFAULT'"
|
||||||
|
class="col-xs-12 col-md-6"
|
||||||
|
>
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<h4 i18n>Allocations</h4>
|
||||||
|
<p class="mb-0">Check the allocations of your portfolio.</p>
|
||||||
|
<p class="text-right">
|
||||||
|
<a
|
||||||
|
color="primary"
|
||||||
|
i18n
|
||||||
|
mat-button
|
||||||
|
[routerLink]="['/portfolio', 'allocations']"
|
||||||
|
>
|
||||||
|
Open Allocations →
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div
|
||||||
|
*ngIf="user?.settings?.viewMode === 'DEFAULT'"
|
||||||
|
class="col-xs-12 col-md-6"
|
||||||
|
>
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<h4 i18n>Analysis</h4>
|
||||||
|
<p class="mb-0">Ghostfolio Analysis visualizes your portfolio.</p>
|
||||||
|
<p class="text-right">
|
||||||
|
<a
|
||||||
|
color="primary"
|
||||||
|
i18n
|
||||||
|
mat-button
|
||||||
|
[routerLink]="['/portfolio', 'analysis']"
|
||||||
|
>
|
||||||
|
Open Analysis →
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
*ngIf="user?.settings?.viewMode === 'DEFAULT'"
|
||||||
|
class="col-xs-12 col-md-6"
|
||||||
|
>
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<h4 i18n>X-ray</h4>
|
||||||
|
<p class="mb-0">
|
||||||
|
Ghostfolio X-ray uses static analysis to identify potential issues and
|
||||||
|
risks in your portfolio.
|
||||||
|
</p>
|
||||||
|
<p class="text-right">
|
||||||
|
<a
|
||||||
|
color="primary"
|
||||||
|
i18n
|
||||||
|
mat-button
|
||||||
|
[routerLink]="['/portfolio', 'report']"
|
||||||
|
>
|
||||||
|
Open X-ray →
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -4,20 +4,20 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { ToolsPageRoutingModule } from './tools-page-routing.module';
|
import { PortfolioPageRoutingModule } from './portfolio-page-routing.module';
|
||||||
import { ToolsPageComponent } from './tools-page.component';
|
import { PortfolioPageComponent } from './portfolio-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [ToolsPageComponent],
|
declarations: [PortfolioPageComponent],
|
||||||
exports: [],
|
exports: [],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
RouterModule,
|
PortfolioPageRoutingModule,
|
||||||
ToolsPageRoutingModule
|
RouterModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
})
|
})
|
||||||
export class ToolsPageModule {}
|
export class PortfolioPageModule {}
|
@ -9,6 +9,7 @@ import { FormControl, Validators } from '@angular/forms';
|
|||||||
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
||||||
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
@ -19,7 +20,6 @@ import {
|
|||||||
takeUntil
|
takeUntil
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
import { DataService } from '../../../services/data.service';
|
|
||||||
import { CreateOrUpdateTransactionDialogParams } from './interfaces/interfaces';
|
import { CreateOrUpdateTransactionDialogParams } from './interfaces/interfaces';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
@ -1,21 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { Subject } from 'rxjs';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'gf-tools-page',
|
|
||||||
templateUrl: './tools-page.html',
|
|
||||||
styleUrls: ['./tools-page.scss']
|
|
||||||
})
|
|
||||||
export class ToolsPageComponent implements OnInit {
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
public constructor() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the controller
|
|
||||||
*/
|
|
||||||
public ngOnInit() {}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
<div class="container">
|
|
||||||
<h3 class="d-flex justify-content-center mb-3" i18n>Tools</h3>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-md-6">
|
|
||||||
<mat-card class="mb-3">
|
|
||||||
<h4 i18n>Analysis</h4>
|
|
||||||
<p class="mb-0">
|
|
||||||
Ghostfolio Analysis shows your positions and visualizes your
|
|
||||||
portfolio.
|
|
||||||
</p>
|
|
||||||
<p class="text-right">
|
|
||||||
<a
|
|
||||||
color="primary"
|
|
||||||
i18n
|
|
||||||
mat-button
|
|
||||||
[routerLink]="['/tools', 'analysis']"
|
|
||||||
>
|
|
||||||
Open Analysis →
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-md-6">
|
|
||||||
<mat-card class="mb-3">
|
|
||||||
<h4 i18n>X-ray</h4>
|
|
||||||
<p class="mb-0">
|
|
||||||
Ghostfolio X-ray uses static analysis to identify potential issues and
|
|
||||||
risks in your portfolio.
|
|
||||||
</p>
|
|
||||||
<p class="text-right">
|
|
||||||
<a
|
|
||||||
color="primary"
|
|
||||||
i18n
|
|
||||||
mat-button
|
|
||||||
[routerLink]="['/tools', 'report']"
|
|
||||||
>
|
|
||||||
Open X-ray →
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -60,7 +60,7 @@
|
|||||||
<ion-icon name="wallet-outline" size="large"></ion-icon>
|
<ion-icon name="wallet-outline" size="large"></ion-icon>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="container justify-content-center pb-3 px-3 positions">
|
<div class="container justify-content-center pb-3 px-3 positions">
|
||||||
<h3 class="d-flex justify-content-center mb-3" i18n>Positions</h3>
|
<h3 class="d-flex justify-content-center mb-3" i18n>Holdings</h3>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="align-items-center col">
|
<div class="align-items-center col">
|
||||||
<mat-card *ngIf="hasPositions === true" class="p-0">
|
<mat-card *ngIf="hasPositions === true" class="p-0">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user