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'; |
|||
|
|||
export class UpdateUserSettingsDto { |
|||
@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 { |
|||
baseCurrency: Currency; |
|||
locale: string; |
|||
viewMode: ViewMode; |
|||
} |
|||
|
Loading…
Reference in new issue