mirror of https://github.com/ghostfolio/ghostfolio
Browse Source
* Start with implementation * Refactor AuthGuard, persist displayMode in user settings * Refactor DisplayMode to ViewMode * Update changelogpull/111/head
committed by
GitHub
22 changed files with 338 additions and 53 deletions
@ -1,7 +1,10 @@ |
|||||
import { Currency } from '@prisma/client'; |
import { Currency, ViewMode } from '@prisma/client'; |
||||
import { IsString } from 'class-validator'; |
import { IsString } from 'class-validator'; |
||||
|
|
||||
export class UpdateUserSettingsDto { |
export class UpdateUserSettingsDto { |
||||
@IsString() |
@IsString() |
||||
currency: Currency; |
baseCurrency: Currency; |
||||
|
|
||||
|
@IsString() |
||||
|
viewMode: ViewMode; |
||||
} |
} |
||||
|
@ -0,0 +1,15 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { RouterModule, Routes } from '@angular/router'; |
||||
|
import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; |
||||
|
|
||||
|
import { ZenPageComponent } from './zen-page.component'; |
||||
|
|
||||
|
const routes: Routes = [ |
||||
|
{ path: '', component: ZenPageComponent, canActivate: [AuthGuard] } |
||||
|
]; |
||||
|
|
||||
|
@NgModule({ |
||||
|
imports: [RouterModule.forChild(routes)], |
||||
|
exports: [RouterModule] |
||||
|
}) |
||||
|
export class ZenPageRoutingModule {} |
@ -0,0 +1,104 @@ |
|||||
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { LineChartItem } from '@ghostfolio/client/components/line-chart/interfaces/line-chart.interface'; |
||||
|
import { DataService } from '@ghostfolio/client/services/data.service'; |
||||
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; |
||||
|
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; |
||||
|
import { PortfolioPerformance, 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-zen-page', |
||||
|
templateUrl: './zen-page.html', |
||||
|
styleUrls: ['./zen-page.scss'] |
||||
|
}) |
||||
|
export class ZenPageComponent implements OnDestroy, OnInit { |
||||
|
public dateRange: DateRange = 'max'; |
||||
|
public deviceType: string; |
||||
|
public hasImpersonationId: boolean; |
||||
|
public hasPermissionToReadForeignPortfolio: boolean; |
||||
|
public historicalDataItems: LineChartItem[]; |
||||
|
public isLoadingPerformance = true; |
||||
|
public performance: PortfolioPerformance; |
||||
|
public user: User; |
||||
|
|
||||
|
private unsubscribeSubject = new Subject<void>(); |
||||
|
|
||||
|
/** |
||||
|
* @constructor |
||||
|
*/ |
||||
|
public constructor( |
||||
|
private cd: ChangeDetectorRef, |
||||
|
private dataService: DataService, |
||||
|
private deviceService: DeviceDetectorService, |
||||
|
private impersonationStorageService: ImpersonationStorageService, |
||||
|
private tokenStorageService: TokenStorageService |
||||
|
) { |
||||
|
this.tokenStorageService |
||||
|
.onChangeHasToken() |
||||
|
.pipe(takeUntil(this.unsubscribeSubject)) |
||||
|
.subscribe(() => { |
||||
|
this.dataService.fetchUser().subscribe((user) => { |
||||
|
this.user = user; |
||||
|
|
||||
|
this.hasPermissionToReadForeignPortfolio = hasPermission( |
||||
|
user.permissions, |
||||
|
permissions.readForeignPortfolio |
||||
|
); |
||||
|
|
||||
|
this.cd.markForCheck(); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Initializes the controller |
||||
|
*/ |
||||
|
public ngOnInit() { |
||||
|
this.deviceType = this.deviceService.getDeviceInfo().deviceType; |
||||
|
|
||||
|
this.impersonationStorageService |
||||
|
.onChangeHasImpersonation() |
||||
|
.subscribe((aId) => { |
||||
|
this.hasImpersonationId = !!aId; |
||||
|
}); |
||||
|
|
||||
|
this.update(); |
||||
|
} |
||||
|
|
||||
|
public ngOnDestroy() { |
||||
|
this.unsubscribeSubject.next(); |
||||
|
this.unsubscribeSubject.complete(); |
||||
|
} |
||||
|
|
||||
|
private update() { |
||||
|
this.isLoadingPerformance = true; |
||||
|
|
||||
|
this.dataService |
||||
|
.fetchChart({ range: this.dateRange }) |
||||
|
.subscribe((chartData) => { |
||||
|
this.historicalDataItems = chartData.map((chartDataItem) => { |
||||
|
return { |
||||
|
date: chartDataItem.date, |
||||
|
value: chartDataItem.value |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
this.cd.markForCheck(); |
||||
|
}); |
||||
|
|
||||
|
this.dataService |
||||
|
.fetchPortfolioPerformance({ range: this.dateRange }) |
||||
|
.subscribe((response) => { |
||||
|
this.performance = response; |
||||
|
this.isLoadingPerformance = false; |
||||
|
|
||||
|
this.cd.markForCheck(); |
||||
|
}); |
||||
|
|
||||
|
this.cd.markForCheck(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,25 @@ |
|||||
|
<div class="container"> |
||||
|
<div class="row"> |
||||
|
<div class="chart-container col mr-3"> |
||||
|
<gf-line-chart |
||||
|
symbol="Performance" |
||||
|
[historicalDataItems]="historicalDataItems" |
||||
|
[showLoader]="false" |
||||
|
[showXAxis]="false" |
||||
|
[showYAxis]="false" |
||||
|
></gf-line-chart> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="overview-container row mb-5 mt-1"> |
||||
|
<div class="col"> |
||||
|
<gf-portfolio-performance-summary |
||||
|
class="pb-4" |
||||
|
[baseCurrency]="user?.settings?.baseCurrency" |
||||
|
[isLoading]="isLoadingPerformance" |
||||
|
[locale]="user?.settings?.locale" |
||||
|
[performance]="performance" |
||||
|
[showDetails]="!hasImpersonationId || hasPermissionToReadForeignPortfolio" |
||||
|
></gf-portfolio-performance-summary> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
@ -0,0 +1,23 @@ |
|||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; |
||||
|
import { MatCardModule } from '@angular/material/card'; |
||||
|
import { GfLineChartModule } from '@ghostfolio/client/components/line-chart/line-chart.module'; |
||||
|
import { GfPortfolioPerformanceSummaryModule } from '@ghostfolio/client/components/portfolio-performance-summary/portfolio-performance-summary.module'; |
||||
|
|
||||
|
import { ZenPageRoutingModule } from './zen-page-routing.module'; |
||||
|
import { ZenPageComponent } from './zen-page.component'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ZenPageComponent], |
||||
|
exports: [], |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
GfLineChartModule, |
||||
|
GfPortfolioPerformanceSummaryModule, |
||||
|
MatCardModule, |
||||
|
ZenPageRoutingModule |
||||
|
], |
||||
|
providers: [], |
||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA] |
||||
|
}) |
||||
|
export class ZenPageModule {} |
@ -0,0 +1,38 @@ |
|||||
|
:host { |
||||
|
color: rgb(var(--dark-primary-text)); |
||||
|
display: block; |
||||
|
|
||||
|
.chart-container { |
||||
|
aspect-ratio: 16 / 9; |
||||
|
margin-top: 3rem; |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:host-context(.is-dark-theme) { |
||||
|
color: rgb(var(--light-primary-text)); |
||||
|
} |
@ -1,6 +1,7 @@ |
|||||
import { Currency } from '@prisma/client'; |
import { Currency, ViewMode } from '@prisma/client'; |
||||
|
|
||||
export interface UserSettings { |
export interface UserSettings { |
||||
baseCurrency: Currency; |
baseCurrency: Currency; |
||||
locale: string; |
locale: string; |
||||
|
viewMode: ViewMode; |
||||
} |
} |
||||
|
Loading…
Reference in new issue