Feature/introduce tabs with routing to home page (#495)
* Introduce tabs with routing * Update changelog
This commit is contained in:
parent
a24a094407
commit
2f402c0c8e
@ -9,7 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added tabs to the admin control panel
|
- Added tabs with routing to the admin control panel
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Introduced tabs with routing to the home page
|
||||||
|
|
||||||
## 1.81.0 - 27.11.2021
|
## 1.81.0 - 27.11.2021
|
||||||
|
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
|
import {
|
||||||
|
RANGE,
|
||||||
|
SettingsStorageService
|
||||||
|
} from '@ghostfolio/client/services/settings-storage.service';
|
||||||
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
|
import { Position, User } from '@ghostfolio/common/interfaces';
|
||||||
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
|
import { DateRange } from '@ghostfolio/common/types';
|
||||||
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'gf-home-holdings',
|
||||||
|
styleUrls: ['./home-holdings.scss'],
|
||||||
|
templateUrl: './home-holdings.html'
|
||||||
|
})
|
||||||
|
export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
||||||
|
public dateRange: DateRange;
|
||||||
|
public deviceType: string;
|
||||||
|
public hasPermissionToCreateOrder: boolean;
|
||||||
|
public positions: Position[];
|
||||||
|
public user: User;
|
||||||
|
|
||||||
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private dataService: DataService,
|
||||||
|
private deviceService: DeviceDetectorService,
|
||||||
|
private settingsStorageService: SettingsStorageService,
|
||||||
|
private userService: UserService
|
||||||
|
) {
|
||||||
|
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.changeDetectorRef.markForCheck();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the controller
|
||||||
|
*/
|
||||||
|
public ngOnInit() {
|
||||||
|
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
||||||
|
|
||||||
|
this.dateRange =
|
||||||
|
<DateRange>this.settingsStorageService.getSetting(RANGE) || 'max';
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.unsubscribeSubject.next();
|
||||||
|
this.unsubscribeSubject.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private update() {
|
||||||
|
this.dataService
|
||||||
|
.fetchPositions({ range: this.dateRange })
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((response) => {
|
||||||
|
this.positions = response.positions;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
<div class="container justify-content-center pb-3 px-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="align-items-center col-xs-12 col-md-8 offset-md-2">
|
||||||
|
<mat-card class="p-0">
|
||||||
|
<mat-card-content>
|
||||||
|
<gf-positions
|
||||||
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[deviceType]="deviceType"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[positions]="positions"
|
||||||
|
[range]="dateRange"
|
||||||
|
></gf-positions>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
<div *ngIf="hasPermissionToCreateOrder" class="text-center">
|
||||||
|
<a
|
||||||
|
class="mt-3"
|
||||||
|
i18n
|
||||||
|
mat-button
|
||||||
|
[routerLink]="['/portfolio', 'transactions']"
|
||||||
|
>Manage Transactions...</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { GfPositionsModule } from '@ghostfolio/client/components/positions/positions.module';
|
||||||
|
|
||||||
|
import { HomeHoldingsComponent } from './home-holdings.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [HomeHoldingsComponent],
|
||||||
|
exports: [],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
GfPositionsModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatCardModule,
|
||||||
|
RouterModule
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class GfHomeHoldingsModule {}
|
@ -0,0 +1,5 @@
|
|||||||
|
@import '~apps/client/src/styles/ghostfolio-style';
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
|
import { ghostfolioFearAndGreedIndexSymbol } from '@ghostfolio/common/config';
|
||||||
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
|
import { DataSource } from '@prisma/client';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'gf-home-market',
|
||||||
|
styleUrls: ['./home-market.scss'],
|
||||||
|
templateUrl: './home-market.html'
|
||||||
|
})
|
||||||
|
export class HomeMarketComponent implements OnDestroy, OnInit {
|
||||||
|
public fearAndGreedIndex: number;
|
||||||
|
public hasPermissionToAccessFearAndGreedIndex: boolean;
|
||||||
|
public isLoading = true;
|
||||||
|
public user: User;
|
||||||
|
|
||||||
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private dataService: DataService,
|
||||||
|
private userService: UserService
|
||||||
|
) {
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
this.userService.stateChanged
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((state) => {
|
||||||
|
if (state?.user) {
|
||||||
|
this.user = state.user;
|
||||||
|
|
||||||
|
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
|
||||||
|
this.user.permissions,
|
||||||
|
permissions.accessFearAndGreedIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.hasPermissionToAccessFearAndGreedIndex) {
|
||||||
|
this.dataService
|
||||||
|
.fetchSymbolItem({
|
||||||
|
dataSource: DataSource.RAKUTEN,
|
||||||
|
symbol: ghostfolioFearAndGreedIndexSymbol
|
||||||
|
})
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(({ marketPrice }) => {
|
||||||
|
this.fearAndGreedIndex = marketPrice;
|
||||||
|
this.isLoading = false;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the controller
|
||||||
|
*/
|
||||||
|
public ngOnInit() {}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.unsubscribeSubject.next();
|
||||||
|
this.unsubscribeSubject.complete();
|
||||||
|
}
|
||||||
|
}
|
25
apps/client/src/app/components/home-market/home-market.html
Normal file
25
apps/client/src/app/components/home-market/home-market.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<div
|
||||||
|
class="
|
||||||
|
align-items-center
|
||||||
|
container
|
||||||
|
d-flex
|
||||||
|
flex-grow-1
|
||||||
|
h-100
|
||||||
|
justify-content-center
|
||||||
|
w-100
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="row w-100">
|
||||||
|
<div class="col-xs-12 col-md-8 offset-md-2">
|
||||||
|
<mat-card class="h-100">
|
||||||
|
<mat-card-content>
|
||||||
|
<gf-fear-and-greed-index
|
||||||
|
class="d-flex justify-content-center"
|
||||||
|
[fearAndGreedIndex]="fearAndGreedIndex"
|
||||||
|
[hidden]="isLoading"
|
||||||
|
></gf-fear-and-greed-index>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,15 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { GfFearAndGreedIndexModule } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.module';
|
||||||
|
|
||||||
|
import { HomeMarketComponent } from './home-market.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [HomeMarketComponent],
|
||||||
|
exports: [],
|
||||||
|
imports: [CommonModule, GfFearAndGreedIndexModule, MatCardModule],
|
||||||
|
providers: [],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class GfHomeMarketModule {}
|
@ -0,0 +1,5 @@
|
|||||||
|
@import '~apps/client/src/styles/ghostfolio-style';
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
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 {
|
||||||
|
RANGE,
|
||||||
|
SettingsStorageService
|
||||||
|
} from '@ghostfolio/client/services/settings-storage.service';
|
||||||
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
|
import { PortfolioPerformance, User } from '@ghostfolio/common/interfaces';
|
||||||
|
import { DateRange } from '@ghostfolio/common/types';
|
||||||
|
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'gf-home-overview',
|
||||||
|
styleUrls: ['./home-overview.scss'],
|
||||||
|
templateUrl: './home-overview.html'
|
||||||
|
})
|
||||||
|
export class HomeOverviewComponent implements OnDestroy, OnInit {
|
||||||
|
public dateRange: DateRange;
|
||||||
|
public dateRangeOptions: ToggleOption[] = [
|
||||||
|
{ label: 'Today', value: '1d' },
|
||||||
|
{ label: 'YTD', value: 'ytd' },
|
||||||
|
{ label: '1Y', value: '1y' },
|
||||||
|
{ label: '5Y', value: '5y' },
|
||||||
|
{ label: 'Max', value: 'max' }
|
||||||
|
];
|
||||||
|
public hasImpersonationId: boolean;
|
||||||
|
public historicalDataItems: LineChartItem[];
|
||||||
|
public isAllTimeHigh: boolean;
|
||||||
|
public isAllTimeLow: boolean;
|
||||||
|
public isLoadingPerformance = true;
|
||||||
|
public performance: PortfolioPerformance;
|
||||||
|
public user: User;
|
||||||
|
|
||||||
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private dataService: DataService,
|
||||||
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
|
private settingsStorageService: SettingsStorageService,
|
||||||
|
private userService: UserService
|
||||||
|
) {
|
||||||
|
this.userService.stateChanged
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((state) => {
|
||||||
|
if (state?.user) {
|
||||||
|
this.user = state.user;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the controller
|
||||||
|
*/
|
||||||
|
public ngOnInit() {
|
||||||
|
this.impersonationStorageService
|
||||||
|
.onChangeHasImpersonation()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((aId) => {
|
||||||
|
this.hasImpersonationId = !!aId;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dateRange =
|
||||||
|
<DateRange>this.settingsStorageService.getSetting(RANGE) || 'max';
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public onChangeDateRange(aDateRange: DateRange) {
|
||||||
|
this.dateRange = aDateRange;
|
||||||
|
this.settingsStorageService.setSetting(RANGE, this.dateRange);
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.unsubscribeSubject.next();
|
||||||
|
this.unsubscribeSubject.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private update() {
|
||||||
|
this.isLoadingPerformance = true;
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.fetchChart({ range: this.dateRange })
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((chartData) => {
|
||||||
|
this.historicalDataItems = chartData.chart.map((chartDataItem) => {
|
||||||
|
return {
|
||||||
|
date: chartDataItem.date,
|
||||||
|
value: chartDataItem.value
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.isAllTimeHigh = chartData.isAllTimeHigh;
|
||||||
|
this.isAllTimeLow = chartData.isAllTimeLow;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.fetchPortfolioPerformance({ range: this.dateRange })
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((response) => {
|
||||||
|
this.performance = response;
|
||||||
|
this.isLoadingPerformance = false;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
<div
|
||||||
|
class="
|
||||||
|
align-items-center
|
||||||
|
container
|
||||||
|
d-flex
|
||||||
|
flex-column
|
||||||
|
h-100
|
||||||
|
justify-content-center
|
||||||
|
overview
|
||||||
|
position-relative
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="row w-100">
|
||||||
|
<div class="chart-container col">
|
||||||
|
<gf-line-chart
|
||||||
|
symbol="Performance"
|
||||||
|
[historicalDataItems]="historicalDataItems"
|
||||||
|
[showGradient]="true"
|
||||||
|
[showLoader]="false"
|
||||||
|
[showXAxis]="false"
|
||||||
|
[showYAxis]="false"
|
||||||
|
></gf-line-chart>
|
||||||
|
<div
|
||||||
|
*ngIf="historicalDataItems?.length === 0"
|
||||||
|
class="align-items-center d-flex h-100 justify-content-center w-100"
|
||||||
|
>
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<gf-no-transactions-info-indicator></gf-no-transactions-info-indicator>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overview-container row mt-1">
|
||||||
|
<div class="col">
|
||||||
|
<gf-portfolio-performance
|
||||||
|
class="pb-4"
|
||||||
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[isAllTimeHigh]="isAllTimeHigh"
|
||||||
|
[isAllTimeLow]="isAllTimeLow"
|
||||||
|
[isLoading]="isLoadingPerformance"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[performance]="performance"
|
||||||
|
[showDetails]="!hasImpersonationId && !user.settings.isRestrictedView"
|
||||||
|
></gf-portfolio-performance>
|
||||||
|
<div class="text-center">
|
||||||
|
<gf-toggle
|
||||||
|
[defaultValue]="dateRange"
|
||||||
|
[isLoading]="isLoadingPerformance"
|
||||||
|
[options]="dateRangeOptions"
|
||||||
|
(change)="onChangeDateRange($event.value)"
|
||||||
|
></gf-toggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { GfPortfolioPerformanceModule } from '@ghostfolio/client/components/portfolio-performance/portfolio-performance.module';
|
||||||
|
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
||||||
|
import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module';
|
||||||
|
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';
|
||||||
|
|
||||||
|
import { HomeOverviewComponent } from './home-overview.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [HomeOverviewComponent],
|
||||||
|
exports: [],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
GfLineChartModule,
|
||||||
|
GfNoTransactionsInfoModule,
|
||||||
|
GfPortfolioPerformanceModule,
|
||||||
|
GfToggleModule,
|
||||||
|
RouterModule
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class GfHomeOverviewModule {}
|
@ -0,0 +1,34 @@
|
|||||||
|
@import '~apps/client/src/styles/ghostfolio-style';
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
max-height: 50vh;
|
||||||
|
|
||||||
|
// Fallback for aspect-ratio (using padding hack)
|
||||||
|
@supports not (aspect-ratio: 16 / 9) {
|
||||||
|
&::before {
|
||||||
|
float: left;
|
||||||
|
padding-top: 56.25%;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gf-line-chart {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
|
import { PortfolioSummary, User } from '@ghostfolio/common/interfaces';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'gf-home-summary',
|
||||||
|
styleUrls: ['./home-summary.scss'],
|
||||||
|
templateUrl: './home-summary.html'
|
||||||
|
})
|
||||||
|
export class HomeSummaryComponent implements OnDestroy, OnInit {
|
||||||
|
public isLoading = true;
|
||||||
|
public summary: PortfolioSummary;
|
||||||
|
public user: User;
|
||||||
|
|
||||||
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private dataService: DataService,
|
||||||
|
private userService: UserService
|
||||||
|
) {
|
||||||
|
this.userService.stateChanged
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((state) => {
|
||||||
|
if (state?.user) {
|
||||||
|
this.user = state.user;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the controller
|
||||||
|
*/
|
||||||
|
public ngOnInit() {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.unsubscribeSubject.next();
|
||||||
|
this.unsubscribeSubject.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private update() {
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.fetchPortfolioSummary()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((response) => {
|
||||||
|
this.summary = response;
|
||||||
|
this.isLoading = false;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<div class="container pb-3 px-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-8 offset-md-2">
|
||||||
|
<mat-card class="h-100">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title i18n>Summary</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<gf-portfolio-summary
|
||||||
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[isLoading]="isLoading"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[summary]="summary"
|
||||||
|
></gf-portfolio-summary>
|
||||||
|
</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 { RouterModule } from '@angular/router';
|
||||||
|
import { GfPortfolioSummaryModule } from '@ghostfolio/client/components/portfolio-summary/portfolio-summary.module';
|
||||||
|
|
||||||
|
import { HomeSummaryComponent } from './home-summary.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [HomeSummaryComponent],
|
||||||
|
exports: [],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
GfPortfolioSummaryModule,
|
||||||
|
MatCardModule,
|
||||||
|
RouterModule
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class GfHomeSummaryModule {}
|
@ -0,0 +1,5 @@
|
|||||||
|
@import '~apps/client/src/styles/ghostfolio-style';
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
@ -1,11 +1,26 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { HomeHoldingsComponent } from '@ghostfolio/client/components/home-holdings/home-holdings.component';
|
||||||
|
import { HomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component';
|
||||||
|
import { HomeOverviewComponent } from '@ghostfolio/client/components/home-overview/home-overview.component';
|
||||||
|
import { HomeSummaryComponent } from '@ghostfolio/client/components/home-summary/home-summary.component';
|
||||||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||||
|
|
||||||
import { HomePageComponent } from './home-page.component';
|
import { HomePageComponent } from './home-page.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: HomePageComponent, canActivate: [AuthGuard] }
|
{
|
||||||
|
path: '',
|
||||||
|
component: HomePageComponent,
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
children: [
|
||||||
|
{ path: '', redirectTo: 'overview', pathMatch: 'full' },
|
||||||
|
{ path: 'overview', component: HomeOverviewComponent },
|
||||||
|
{ path: 'holdings', component: HomeHoldingsComponent },
|
||||||
|
{ path: 'summary', component: HomeSummaryComponent },
|
||||||
|
{ path: 'market', component: HomeMarketComponent }
|
||||||
|
]
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -1,35 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
|
||||||
HostBinding,
|
HostBinding,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit
|
||||||
ViewChild
|
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MatTabChangeEvent } from '@angular/material/tabs';
|
|
||||||
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 { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import {
|
|
||||||
RANGE,
|
|
||||||
SettingsStorageService
|
|
||||||
} from '@ghostfolio/client/services/settings-storage.service';
|
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { ghostfolioFearAndGreedIndexSymbol } from '@ghostfolio/common/config';
|
|
||||||
import {
|
|
||||||
PortfolioPerformance,
|
|
||||||
PortfolioSummary,
|
|
||||||
Position,
|
|
||||||
User
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { DateRange } from '@ghostfolio/common/types';
|
|
||||||
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
|
|
||||||
import { DataSource } from '@prisma/client';
|
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject, Subscription } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-home-page',
|
selector: 'gf-home-page',
|
||||||
@ -41,32 +23,9 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
|||||||
return this.canCreateAccount;
|
return this.canCreateAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewChild('positionsContainer') positionsContainer: ElementRef;
|
|
||||||
|
|
||||||
public canCreateAccount: boolean;
|
public canCreateAccount: boolean;
|
||||||
public currentTabIndex = 0;
|
|
||||||
public dateRange: DateRange;
|
|
||||||
public dateRangeOptions: ToggleOption[] = [
|
|
||||||
{ label: 'Today', value: '1d' },
|
|
||||||
{ label: 'YTD', value: 'ytd' },
|
|
||||||
{ label: '1Y', value: '1y' },
|
|
||||||
{ label: '5Y', value: '5y' },
|
|
||||||
{ label: 'Max', value: 'max' }
|
|
||||||
];
|
|
||||||
public deviceType: string;
|
|
||||||
public fearAndGreedIndex: number;
|
|
||||||
public hasImpersonationId: boolean;
|
|
||||||
public hasPermissionToAccessFearAndGreedIndex: boolean;
|
public hasPermissionToAccessFearAndGreedIndex: boolean;
|
||||||
public hasPermissionToCreateOrder: boolean;
|
public tabs: { iconName: string; path: string }[] = [];
|
||||||
public historicalDataItems: LineChartItem[];
|
|
||||||
public isAllTimeHigh: boolean;
|
|
||||||
public isAllTimeLow: boolean;
|
|
||||||
public isLoadingPerformance = true;
|
|
||||||
public isLoadingSummary = true;
|
|
||||||
public performance: PortfolioPerformance;
|
|
||||||
public positions: Position[];
|
|
||||||
public routeQueryParams: Subscription;
|
|
||||||
public summary: PortfolioSummary;
|
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
@ -76,16 +35,19 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
|||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private dataService: DataService,
|
|
||||||
private deviceService: DeviceDetectorService,
|
private deviceService: DeviceDetectorService,
|
||||||
private impersonationStorageService: ImpersonationStorageService,
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
private settingsStorageService: SettingsStorageService,
|
|
||||||
private userService: UserService
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((state) => {
|
.subscribe((state) => {
|
||||||
if (state?.user) {
|
if (state?.user) {
|
||||||
|
this.tabs = [
|
||||||
|
{ iconName: 'analytics-outline', path: 'overview' },
|
||||||
|
{ iconName: 'wallet-outline', path: 'holdings' },
|
||||||
|
{ iconName: 'reader-outline', path: 'summary' }
|
||||||
|
];
|
||||||
this.user = state.user;
|
this.user = state.user;
|
||||||
|
|
||||||
this.canCreateAccount = hasPermission(
|
this.canCreateAccount = hasPermission(
|
||||||
@ -99,24 +61,9 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (this.hasPermissionToAccessFearAndGreedIndex) {
|
if (this.hasPermissionToAccessFearAndGreedIndex) {
|
||||||
this.dataService
|
this.tabs.push({ iconName: 'newspaper-outline', path: 'market' });
|
||||||
.fetchSymbolItem({
|
|
||||||
dataSource: DataSource.RAKUTEN,
|
|
||||||
symbol: ghostfolioFearAndGreedIndexSymbol
|
|
||||||
})
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(({ marketPrice }) => {
|
|
||||||
this.fearAndGreedIndex = marketPrice;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasPermissionToCreateOrder = hasPermission(
|
|
||||||
this.user.permissions,
|
|
||||||
permissions.createOrder
|
|
||||||
);
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -125,93 +72,10 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
|||||||
/**
|
/**
|
||||||
* Initializes the controller
|
* Initializes the controller
|
||||||
*/
|
*/
|
||||||
public ngOnInit() {
|
public ngOnInit() {}
|
||||||
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
|
||||||
|
|
||||||
this.impersonationStorageService
|
|
||||||
.onChangeHasImpersonation()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((aId) => {
|
|
||||||
this.hasImpersonationId = !!aId;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dateRange =
|
|
||||||
<DateRange>this.settingsStorageService.getSetting(RANGE) || 'max';
|
|
||||||
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public onChangeDateRange(aDateRange: DateRange) {
|
|
||||||
this.dateRange = aDateRange;
|
|
||||||
this.settingsStorageService.setSetting(RANGE, this.dateRange);
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public onTabChanged(event: MatTabChangeEvent) {
|
|
||||||
this.currentTabIndex = event.index;
|
|
||||||
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
this.unsubscribeSubject.next();
|
this.unsubscribeSubject.next();
|
||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private update() {
|
|
||||||
if (this.currentTabIndex === 0) {
|
|
||||||
this.isLoadingPerformance = true;
|
|
||||||
|
|
||||||
this.dataService
|
|
||||||
.fetchChart({ range: this.dateRange })
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((chartData) => {
|
|
||||||
this.historicalDataItems = chartData.chart.map((chartDataItem) => {
|
|
||||||
return {
|
|
||||||
date: chartDataItem.date,
|
|
||||||
value: chartDataItem.value
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.isAllTimeHigh = chartData.isAllTimeHigh;
|
|
||||||
this.isAllTimeLow = chartData.isAllTimeLow;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dataService
|
|
||||||
.fetchPortfolioPerformance({ range: this.dateRange })
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((response) => {
|
|
||||||
this.performance = response;
|
|
||||||
this.isLoadingPerformance = false;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
} else if (this.currentTabIndex === 1) {
|
|
||||||
this.dataService
|
|
||||||
.fetchPositions({ range: this.dateRange })
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((response) => {
|
|
||||||
this.positions = response.positions;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
} else if (this.currentTabIndex === 2) {
|
|
||||||
this.isLoadingSummary = true;
|
|
||||||
|
|
||||||
this.dataService
|
|
||||||
.fetchPortfolioSummary()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((response) => {
|
|
||||||
this.summary = response;
|
|
||||||
this.isLoadingSummary = false;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,169 +1,14 @@
|
|||||||
<mat-tab-group
|
<router-outlet></router-outlet>
|
||||||
animationDuration="0ms"
|
|
||||||
class="position-absolute"
|
<nav mat-align-tabs="center" mat-tab-nav-bar>
|
||||||
headerPosition="below"
|
<a
|
||||||
mat-align-tabs="center"
|
*ngFor="let tab of tabs"
|
||||||
[disablePagination]="true"
|
#rla="routerLinkActive"
|
||||||
(selectedTabChange)="onTabChanged($event)"
|
mat-tab-link
|
||||||
>
|
routerLinkActive
|
||||||
<mat-tab>
|
[active]="rla.isActive"
|
||||||
<ng-template mat-tab-label>
|
[routerLink]="tab.path"
|
||||||
<ion-icon name="analytics-outline" size="large"></ion-icon>
|
>
|
||||||
</ng-template>
|
<ion-icon size="large" [name]="tab.iconName"></ion-icon>
|
||||||
<div
|
</a>
|
||||||
class="
|
</nav>
|
||||||
align-items-center
|
|
||||||
container
|
|
||||||
d-flex
|
|
||||||
flex-column
|
|
||||||
h-100
|
|
||||||
justify-content-center
|
|
||||||
overview
|
|
||||||
position-relative
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="row w-100">
|
|
||||||
<div class="chart-container col">
|
|
||||||
<gf-line-chart
|
|
||||||
class="mr-3"
|
|
||||||
symbol="Performance"
|
|
||||||
[historicalDataItems]="historicalDataItems"
|
|
||||||
[showGradient]="true"
|
|
||||||
[showLoader]="false"
|
|
||||||
[showXAxis]="false"
|
|
||||||
[showYAxis]="false"
|
|
||||||
></gf-line-chart>
|
|
||||||
<div
|
|
||||||
*ngIf="historicalDataItems?.length === 0"
|
|
||||||
class="
|
|
||||||
align-items-center
|
|
||||||
chart-container
|
|
||||||
d-flex
|
|
||||||
justify-content-center
|
|
||||||
w-100
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="d-flex justify-content-center">
|
|
||||||
<gf-no-transactions-info-indicator></gf-no-transactions-info-indicator>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="overview-container row mt-1">
|
|
||||||
<div class="col">
|
|
||||||
<gf-portfolio-performance
|
|
||||||
class="pb-4"
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
|
||||||
[isAllTimeHigh]="isAllTimeHigh"
|
|
||||||
[isAllTimeLow]="isAllTimeLow"
|
|
||||||
[isLoading]="isLoadingPerformance"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[performance]="performance"
|
|
||||||
[showDetails]="!hasImpersonationId && !user.settings.isRestrictedView"
|
|
||||||
></gf-portfolio-performance>
|
|
||||||
<div class="text-center">
|
|
||||||
<gf-toggle
|
|
||||||
[defaultValue]="dateRange"
|
|
||||||
[isLoading]="isLoadingPerformance"
|
|
||||||
[options]="dateRangeOptions"
|
|
||||||
(change)="onChangeDateRange($event.value)"
|
|
||||||
></gf-toggle>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<ion-icon name="wallet-outline" size="large"></ion-icon>
|
|
||||||
</ng-template>
|
|
||||||
<div class="container justify-content-center pb-3 px-3 positions">
|
|
||||||
<h3 class="d-flex justify-content-center mb-3" i18n>Holdings</h3>
|
|
||||||
<div class="row">
|
|
||||||
<div class="align-items-center col-xs-12 col-md-8 offset-md-2">
|
|
||||||
<div class="pb-2 text-center">
|
|
||||||
<gf-toggle
|
|
||||||
[defaultValue]="dateRange"
|
|
||||||
[isLoading]="isLoadingPerformance"
|
|
||||||
[options]="dateRangeOptions"
|
|
||||||
(change)="onChangeDateRange($event.value)"
|
|
||||||
></gf-toggle>
|
|
||||||
</div>
|
|
||||||
<mat-card class="p-0">
|
|
||||||
<mat-card-content>
|
|
||||||
<gf-positions
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
|
||||||
[deviceType]="deviceType"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[positions]="positions"
|
|
||||||
[range]="dateRange"
|
|
||||||
></gf-positions>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
<div *ngIf="hasPermissionToCreateOrder" class="text-center">
|
|
||||||
<a
|
|
||||||
class="mt-3"
|
|
||||||
i18n
|
|
||||||
mat-button
|
|
||||||
[routerLink]="['/portfolio', 'transactions']"
|
|
||||||
>Manage Transactions...</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<ion-icon name="reader-outline" size="large"></ion-icon>
|
|
||||||
</ng-template>
|
|
||||||
<div class="container pb-3 px-3 positions">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-md-8 offset-md-2">
|
|
||||||
<mat-card class="h-100">
|
|
||||||
<mat-card-header>
|
|
||||||
<mat-card-title i18n>Summary</mat-card-title>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<gf-portfolio-summary
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
|
||||||
[isLoading]="isLoadingSummary"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[summary]="summary"
|
|
||||||
></gf-portfolio-summary>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
<mat-tab *ngIf="hasPermissionToAccessFearAndGreedIndex">
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<ion-icon name="newspaper-outline" size="large"></ion-icon>
|
|
||||||
</ng-template>
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
align-items-center
|
|
||||||
container
|
|
||||||
d-flex
|
|
||||||
flex-grow-1
|
|
||||||
h-100
|
|
||||||
justify-content-center
|
|
||||||
w-100
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="row w-100">
|
|
||||||
<div class="col-xs-12 col-md-8 offset-md-2">
|
|
||||||
<mat-card class="h-100">
|
|
||||||
<mat-card-content>
|
|
||||||
<gf-fear-and-greed-index
|
|
||||||
class="d-flex justify-content-center"
|
|
||||||
[fearAndGreedIndex]="fearAndGreedIndex"
|
|
||||||
></gf-fear-and-greed-index>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
</mat-tab-group>
|
|
||||||
|
@ -1,17 +1,11 @@
|
|||||||
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 { MatButtonModule } from '@angular/material/button';
|
|
||||||
import { MatCardModule } from '@angular/material/card';
|
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { GfFearAndGreedIndexModule } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.module';
|
import { GfHomeHoldingsModule } from '@ghostfolio/client/components/home-holdings/home-holdings.module';
|
||||||
import { GfPerformanceChartDialogModule } from '@ghostfolio/client/components/performance-chart-dialog/performance-chart-dialog.module';
|
import { GfHomeMarketModule } from '@ghostfolio/client/components/home-market/home-market.module';
|
||||||
import { GfPortfolioPerformanceModule } from '@ghostfolio/client/components/portfolio-performance/portfolio-performance.module';
|
import { GfHomeOverviewModule } from '@ghostfolio/client/components/home-overview/home-overview.module';
|
||||||
import { GfPortfolioSummaryModule } from '@ghostfolio/client/components/portfolio-summary/portfolio-summary.module';
|
import { GfHomeSummaryModule } from '@ghostfolio/client/components/home-summary/home-summary.module';
|
||||||
import { GfPositionsModule } from '@ghostfolio/client/components/positions/positions.module';
|
|
||||||
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
|
||||||
import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module';
|
|
||||||
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';
|
|
||||||
|
|
||||||
import { HomePageRoutingModule } from './home-page-routing.module';
|
import { HomePageRoutingModule } from './home-page-routing.module';
|
||||||
import { HomePageComponent } from './home-page.component';
|
import { HomePageComponent } from './home-page.component';
|
||||||
@ -21,17 +15,11 @@ import { HomePageComponent } from './home-page.component';
|
|||||||
exports: [],
|
exports: [],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfFearAndGreedIndexModule,
|
GfHomeHoldingsModule,
|
||||||
GfLineChartModule,
|
GfHomeMarketModule,
|
||||||
GfNoTransactionsInfoModule,
|
GfHomeOverviewModule,
|
||||||
GfPerformanceChartDialogModule,
|
GfHomeSummaryModule,
|
||||||
GfPortfolioPerformanceModule,
|
|
||||||
GfPortfolioSummaryModule,
|
|
||||||
GfPositionsModule,
|
|
||||||
GfToggleModule,
|
|
||||||
HomePageRoutingModule,
|
HomePageRoutingModule,
|
||||||
MatButtonModule,
|
|
||||||
MatCardModule,
|
|
||||||
MatTabsModule,
|
MatTabsModule,
|
||||||
RouterModule
|
RouterModule
|
||||||
],
|
],
|
||||||
|
@ -2,109 +2,42 @@
|
|||||||
|
|
||||||
:host {
|
:host {
|
||||||
color: rgb(var(--dark-primary-text));
|
color: rgb(var(--dark-primary-text));
|
||||||
display: block;
|
display: flex;
|
||||||
min-height: calc(100vh - 5rem);
|
flex-direction: column;
|
||||||
position: relative;
|
height: calc(100vh - 5rem);
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
padding-bottom: constant(safe-area-inset-bottom);
|
||||||
|
|
||||||
&.with-create-account-container {
|
&.with-create-account-container {
|
||||||
min-height: calc(100vh - 5rem - 3.5rem);
|
height: calc(100vh - 5rem - 3.5rem);
|
||||||
}
|
|
||||||
|
|
||||||
.mat-tab-group {
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
margin-bottom: env(safe-area-inset-bottom);
|
|
||||||
margin-bottom: constant(safe-area-inset-bottom);
|
|
||||||
|
|
||||||
::ng-deep {
|
|
||||||
.mat-tab-body-wrapper {
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.container {
|
|
||||||
&.overview {
|
|
||||||
.chart-container {
|
|
||||||
aspect-ratio: 16 / 9;
|
|
||||||
max-height: 50vh;
|
|
||||||
|
|
||||||
// Fallback for aspect-ratio (using padding hack)
|
|
||||||
@supports not (aspect-ratio: 16 / 9) {
|
|
||||||
&::before {
|
|
||||||
float: left;
|
|
||||||
padding-top: 56.25%;
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
display: block;
|
|
||||||
content: '';
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gf-line-chart {
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.positions {
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-tab-header {
|
|
||||||
border-top: 0;
|
|
||||||
|
|
||||||
.mat-ink-bar {
|
|
||||||
visibility: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-tab-label-active {
|
|
||||||
color: rgba(var(--palette-primary-500), 1);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep {
|
::ng-deep {
|
||||||
.mat-form-field-infix {
|
gf-home-holdings,
|
||||||
border-top: 0 solid transparent !important;
|
gf-home-market,
|
||||||
|
gf-home-overview,
|
||||||
|
gf-home-summary {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-form-field-wrapper {
|
.mat-tab-header {
|
||||||
padding-bottom: 0 !important;
|
border-bottom: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.mat-form-field-underline {
|
.mat-ink-bar {
|
||||||
bottom: 0 !important;
|
visibility: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-form-field-appearance-outline .mat-select-arrow-wrapper {
|
.mat-tab-label-active {
|
||||||
transform: translateY(0);
|
color: rgba(var(--palette-primary-500), 1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context(.is-dark-theme) {
|
:host-context(.is-dark-theme) {
|
||||||
color: rgb(var(--light-primary-text));
|
color: rgb(var(--light-primary-text));
|
||||||
|
|
||||||
.container {
|
|
||||||
&.overview {
|
|
||||||
.button-container {
|
|
||||||
.mat-flat-button {
|
|
||||||
background-color: rgba(255, 255, 255, $alpha-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,22 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { HomeHoldingsComponent } from '@ghostfolio/client/components/home-holdings/home-holdings.component';
|
||||||
|
import { HomeOverviewComponent } from '@ghostfolio/client/components/home-overview/home-overview.component';
|
||||||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||||
|
|
||||||
import { ZenPageComponent } from './zen-page.component';
|
import { ZenPageComponent } from './zen-page.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: ZenPageComponent, canActivate: [AuthGuard] }
|
{
|
||||||
|
path: '',
|
||||||
|
component: ZenPageComponent,
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
children: [
|
||||||
|
{ path: '', redirectTo: 'overview', pathMatch: 'full' },
|
||||||
|
{ path: 'overview', component: HomeOverviewComponent },
|
||||||
|
{ path: 'holdings', component: HomeHoldingsComponent }
|
||||||
|
]
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -3,25 +3,12 @@ import {
|
|||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit
|
||||||
ViewChild
|
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MatTabChangeEvent } from '@angular/material/tabs';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
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 { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import {
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
PortfolioPerformance,
|
|
||||||
Position,
|
|
||||||
User
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
|
||||||
import { DateRange } from '@ghostfolio/common/types';
|
|
||||||
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
|
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { first, takeUntil } from 'rxjs/operators';
|
import { first, takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -31,19 +18,7 @@ import { first, takeUntil } from 'rxjs/operators';
|
|||||||
styleUrls: ['./zen-page.scss']
|
styleUrls: ['./zen-page.scss']
|
||||||
})
|
})
|
||||||
export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit {
|
export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit {
|
||||||
@ViewChild('positionsContainer') positionsContainer: ElementRef;
|
public tabs: { iconName: string; path: string }[] = [];
|
||||||
|
|
||||||
public currentTabIndex = 0;
|
|
||||||
public dateRange: DateRange = 'max';
|
|
||||||
public deviceType: string;
|
|
||||||
public hasImpersonationId: boolean;
|
|
||||||
public hasPermissionToCreateOrder: boolean;
|
|
||||||
public historicalDataItems: LineChartItem[];
|
|
||||||
public isAllTimeHigh: boolean;
|
|
||||||
public isAllTimeLow: boolean;
|
|
||||||
public isLoadingPerformance = true;
|
|
||||||
public performance: PortfolioPerformance;
|
|
||||||
public positions: Position[];
|
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
@ -54,9 +29,6 @@ export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private dataService: DataService,
|
|
||||||
private deviceService: DeviceDetectorService,
|
|
||||||
private impersonationStorageService: ImpersonationStorageService,
|
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private viewportScroller: ViewportScroller
|
private viewportScroller: ViewportScroller
|
||||||
) {
|
) {
|
||||||
@ -64,32 +36,18 @@ export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit {
|
|||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((state) => {
|
.subscribe((state) => {
|
||||||
if (state?.user) {
|
if (state?.user) {
|
||||||
|
this.tabs = [
|
||||||
|
{ iconName: 'analytics-outline', path: 'overview' },
|
||||||
|
{ iconName: 'wallet-outline', path: 'holdings' }
|
||||||
|
];
|
||||||
this.user = state.user;
|
this.user = state.user;
|
||||||
|
|
||||||
this.hasPermissionToCreateOrder = hasPermission(
|
|
||||||
this.user.permissions,
|
|
||||||
permissions.createOrder
|
|
||||||
);
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnInit() {
|
public ngOnInit() {}
|
||||||
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
|
||||||
|
|
||||||
this.impersonationStorageService
|
|
||||||
.onChangeHasImpersonation()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((aId) => {
|
|
||||||
this.hasImpersonationId = !!aId;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngAfterViewInit(): void {
|
public ngAfterViewInit(): void {
|
||||||
this.route.fragment
|
this.route.fragment
|
||||||
@ -97,57 +55,8 @@ export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit {
|
|||||||
.subscribe((fragment) => this.viewportScroller.scrollToAnchor(fragment));
|
.subscribe((fragment) => this.viewportScroller.scrollToAnchor(fragment));
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabChanged(event: MatTabChangeEvent) {
|
|
||||||
this.currentTabIndex = event.index;
|
|
||||||
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
this.unsubscribeSubject.next();
|
this.unsubscribeSubject.next();
|
||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private update() {
|
|
||||||
if (this.currentTabIndex === 0) {
|
|
||||||
this.isLoadingPerformance = true;
|
|
||||||
|
|
||||||
this.dataService
|
|
||||||
.fetchChart({ range: this.dateRange })
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((chartData) => {
|
|
||||||
this.historicalDataItems = chartData.chart.map((chartDataItem) => {
|
|
||||||
return {
|
|
||||||
date: chartDataItem.date,
|
|
||||||
value: chartDataItem.value
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.isAllTimeHigh = chartData.isAllTimeHigh;
|
|
||||||
this.isAllTimeLow = chartData.isAllTimeLow;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dataService
|
|
||||||
.fetchPortfolioPerformance({ range: this.dateRange })
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((response) => {
|
|
||||||
this.performance = response;
|
|
||||||
this.isLoadingPerformance = false;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
} else if (this.currentTabIndex === 1) {
|
|
||||||
this.dataService
|
|
||||||
.fetchPositions({ range: this.dateRange })
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((response) => {
|
|
||||||
this.positions = response.positions;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,94 +1,14 @@
|
|||||||
<mat-tab-group
|
<router-outlet></router-outlet>
|
||||||
animationDuration="0ms"
|
|
||||||
class="position-absolute"
|
|
||||||
headerPosition="below"
|
|
||||||
mat-align-tabs="center"
|
|
||||||
[disablePagination]="true"
|
|
||||||
(selectedTabChange)="onTabChanged($event)"
|
|
||||||
>
|
|
||||||
<mat-tab>
|
|
||||||
<ng-template mat-tab-label>
|
|
||||||
<ion-icon name="analytics-outline" size="large"></ion-icon>
|
|
||||||
</ng-template>
|
|
||||||
<div
|
|
||||||
class="
|
|
||||||
container
|
|
||||||
d-flex
|
|
||||||
flex-column
|
|
||||||
h-100
|
|
||||||
justify-content-center
|
|
||||||
overview
|
|
||||||
position-relative
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="row">
|
|
||||||
<div
|
|
||||||
class="chart-container d-flex flex-column col justify-content-center"
|
|
||||||
>
|
|
||||||
<gf-line-chart
|
|
||||||
class="mr-3"
|
|
||||||
symbol="Performance"
|
|
||||||
[historicalDataItems]="historicalDataItems"
|
|
||||||
[showGradient]="true"
|
|
||||||
[showLoader]="false"
|
|
||||||
[showXAxis]="false"
|
|
||||||
[showYAxis]="false"
|
|
||||||
></gf-line-chart>
|
|
||||||
<div
|
|
||||||
*ngIf="historicalDataItems?.length === 0"
|
|
||||||
class="d-flex justify-content-center"
|
|
||||||
>
|
|
||||||
<gf-no-transactions-info-indicator></gf-no-transactions-info-indicator>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="overview-container row mb-5 mt-1">
|
|
||||||
<div class="col">
|
|
||||||
<gf-portfolio-performance
|
|
||||||
class="pb-4"
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
|
||||||
[isAllTimeHigh]="isAllTimeHigh"
|
|
||||||
[isAllTimeLow]="isAllTimeLow"
|
|
||||||
[isLoading]="isLoadingPerformance"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[performance]="performance"
|
|
||||||
[showDetails]="!hasImpersonationId && !user.settings.isRestrictedView"
|
|
||||||
></gf-portfolio-performance>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
|
|
||||||
<mat-tab>
|
<nav mat-align-tabs="center" mat-tab-nav-bar>
|
||||||
<ng-template mat-tab-label>
|
<a
|
||||||
<ion-icon name="wallet-outline" size="large"></ion-icon>
|
*ngFor="let tab of tabs"
|
||||||
</ng-template>
|
#rla="routerLinkActive"
|
||||||
<div class="container justify-content-center pb-3 px-3 positions">
|
mat-tab-link
|
||||||
<h3 class="d-flex justify-content-center mb-3" i18n>Holdings</h3>
|
routerLinkActive
|
||||||
<div class="row">
|
[active]="rla.isActive"
|
||||||
<div class="align-items-center col">
|
[routerLink]="tab.path"
|
||||||
<mat-card class="p-0">
|
>
|
||||||
<mat-card-content>
|
<ion-icon size="large" [name]="tab.iconName"></ion-icon>
|
||||||
<gf-positions
|
</a>
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
</nav>
|
||||||
[deviceType]="deviceType"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[positions]="positions"
|
|
||||||
[range]="dateRange"
|
|
||||||
></gf-positions>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
<div *ngIf="hasPermissionToCreateOrder" class="text-center">
|
|
||||||
<a
|
|
||||||
class="mt-3"
|
|
||||||
i18n
|
|
||||||
mat-button
|
|
||||||
[routerLink]="['/portfolio', 'transactions']"
|
|
||||||
>Manage Transactions...</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
</mat-tab-group>
|
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
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 { MatButtonModule } from '@angular/material/button';
|
|
||||||
import { MatCardModule } from '@angular/material/card';
|
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { GfPortfolioPerformanceModule } from '@ghostfolio/client/components/portfolio-performance/portfolio-performance.module';
|
import { GfHomeHoldingsModule } from '@ghostfolio/client/components/home-holdings/home-holdings.module';
|
||||||
import { GfPositionsModule } from '@ghostfolio/client/components/positions/positions.module';
|
import { GfHomeOverviewModule } from '@ghostfolio/client/components/home-overview/home-overview.module';
|
||||||
import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module';
|
|
||||||
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';
|
|
||||||
|
|
||||||
import { ZenPageRoutingModule } from './zen-page-routing.module';
|
import { ZenPageRoutingModule } from './zen-page-routing.module';
|
||||||
import { ZenPageComponent } from './zen-page.component';
|
import { ZenPageComponent } from './zen-page.component';
|
||||||
@ -17,12 +13,8 @@ import { ZenPageComponent } from './zen-page.component';
|
|||||||
exports: [],
|
exports: [],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfLineChartModule,
|
GfHomeHoldingsModule,
|
||||||
GfNoTransactionsInfoModule,
|
GfHomeOverviewModule,
|
||||||
GfPortfolioPerformanceModule,
|
|
||||||
GfPositionsModule,
|
|
||||||
MatButtonModule,
|
|
||||||
MatCardModule,
|
|
||||||
MatTabsModule,
|
MatTabsModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
ZenPageRoutingModule
|
ZenPageRoutingModule
|
||||||
|
@ -2,72 +2,31 @@
|
|||||||
|
|
||||||
:host {
|
:host {
|
||||||
color: rgb(var(--dark-primary-text));
|
color: rgb(var(--dark-primary-text));
|
||||||
display: block;
|
display: flex;
|
||||||
min-height: calc(100vh - 5rem);
|
flex-direction: column;
|
||||||
position: relative;
|
height: calc(100vh - 5rem);
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
.mat-tab-group {
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
bottom: 0;
|
padding-bottom: constant(safe-area-inset-bottom);
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
margin-bottom: env(safe-area-inset-bottom);
|
::ng-deep {
|
||||||
margin-bottom: constant(safe-area-inset-bottom);
|
gf-home-holdings,
|
||||||
|
gf-home-overview {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
::ng-deep {
|
.mat-tab-header {
|
||||||
.mat-tab-body-wrapper {
|
border-bottom: 0;
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.container {
|
.mat-ink-bar {
|
||||||
&.overview {
|
visibility: hidden !important;
|
||||||
.chart-container {
|
|
||||||
aspect-ratio: 16 / 9;
|
|
||||||
max-height: 50vh;
|
|
||||||
|
|
||||||
// Fallback for aspect-ratio (using padding hack)
|
|
||||||
@supports not (aspect-ratio: 16 / 9) {
|
|
||||||
&::before {
|
|
||||||
float: left;
|
|
||||||
padding-top: 56.25%;
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
display: block;
|
|
||||||
content: '';
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gf-line-chart {
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.positions {
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-tab-header {
|
.mat-tab-label-active {
|
||||||
border-top: 0;
|
color: rgba(var(--palette-primary-500), 1);
|
||||||
|
opacity: 1;
|
||||||
.mat-ink-bar {
|
|
||||||
visibility: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-tab-label-active {
|
|
||||||
color: rgba(var(--palette-primary-500), 1);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,14 +34,4 @@
|
|||||||
|
|
||||||
:host-context(.is-dark-theme) {
|
:host-context(.is-dark-theme) {
|
||||||
color: rgb(var(--light-primary-text));
|
color: rgb(var(--light-primary-text));
|
||||||
|
|
||||||
.container {
|
|
||||||
&.overview {
|
|
||||||
.button-container {
|
|
||||||
.mat-flat-button {
|
|
||||||
background-color: rgba(255, 255, 255, $alpha-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user