Feature/add dialog for benchmarks in markets overview (#3493)

* Add benchmarks dialog in markets overview

* Update changelog
This commit is contained in:
Thomas Kaul
2024-06-15 09:49:54 +02:00
committed by GitHub
parent 79a7e12a9f
commit 519827045a
17 changed files with 302 additions and 13 deletions

View File

@@ -0,0 +1,12 @@
:host {
display: block;
.mat-mdc-dialog-content {
max-height: unset;
gf-line-chart {
aspect-ratio: 16 / 9;
margin: 0 -0.5rem;
}
}
}

View File

@@ -0,0 +1,87 @@
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
import { DataService } from '@ghostfolio/client/services/data.service';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import {
AdminMarketDataDetails,
LineChartItem
} from '@ghostfolio/common/interfaces';
import { GfLineChartComponent } from '@ghostfolio/ui/line-chart';
import { CommonModule } from '@angular/common';
import {
CUSTOM_ELEMENTS_SCHEMA,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Inject,
OnDestroy,
OnInit
} from '@angular/core';
import {
MAT_DIALOG_DATA,
MatDialogModule,
MatDialogRef
} from '@angular/material/dialog';
import { format } from 'date-fns';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { BenchmarkDetailDialogParams } from './interfaces/interfaces';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'd-flex flex-column h-100' },
imports: [
CommonModule,
GfDialogFooterModule,
GfDialogHeaderModule,
GfLineChartComponent,
MatDialogModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-benchmark-detail-dialog',
standalone: true,
styleUrls: ['./benchmark-detail-dialog.component.scss'],
templateUrl: 'benchmark-detail-dialog.html'
})
export class GfBenchmarkDetailDialogComponent implements OnDestroy, OnInit {
public assetProfile: AdminMarketDataDetails['assetProfile'];
public historicalDataItems: LineChartItem[];
private unsubscribeSubject = new Subject<void>();
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
public dialogRef: MatDialogRef<GfBenchmarkDetailDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: BenchmarkDetailDialogParams
) {}
public ngOnInit() {
this.dataService
.fetchAsset({
dataSource: this.data.dataSource,
symbol: this.data.symbol
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ assetProfile, marketData }) => {
this.assetProfile = assetProfile;
this.historicalDataItems = marketData.map(({ date, marketPrice }) => {
return { date: format(date, DATE_FORMAT), value: marketPrice };
});
this.changeDetectorRef.markForCheck();
});
}
public onClose() {
this.dialogRef.close();
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
}

View File

@@ -0,0 +1,30 @@
<gf-dialog-header
mat-dialog-title
position="center"
[deviceType]="data.deviceType"
[title]="assetProfile?.name ?? assetProfile?.symbol"
(closeButtonClicked)="onClose()"
/>
<div class="flex-grow-1" mat-dialog-content>
<div class="container p-0">
<gf-line-chart
benchmarkLabel="Average Unit Price"
class="mb-4"
[colorScheme]="data.colorScheme"
[historicalDataItems]="historicalDataItems"
[isAnimated]="true"
[locale]="data.locale"
[showGradient]="true"
[showXAxis]="true"
[showYAxis]="true"
[symbol]="data.symbol"
/>
</div>
</div>
<gf-dialog-footer
mat-dialog-actions
[deviceType]="data.deviceType"
(closeButtonClicked)="onClose()"
/>

View File

@@ -0,0 +1,11 @@
import { ColorScheme } from '@ghostfolio/common/types';
import { DataSource } from '@prisma/client';
export interface BenchmarkDetailDialogParams {
colorScheme: ColorScheme;
dataSource: DataSource;
deviceType: string;
locale: string;
symbol: string;
}

View File

@@ -110,5 +110,15 @@
</ng-container>
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
<tr *matRowDef="let row; columns: displayedColumns" mat-row></tr>
<tr
*matRowDef="let row; columns: displayedColumns"
class="cursor-pointer"
mat-row
(click)="
onOpenBenchmarkDialog({
dataSource: row.dataSource,
symbol: row.symbol
})
"
></tr>
</table>

View File

@@ -1,6 +1,8 @@
import { getLocale, resolveMarketCondition } from '@ghostfolio/common/helper';
import { Benchmark, User } from '@ghostfolio/common/interfaces';
import { Benchmark, UniqueAsset, User } from '@ghostfolio/common/interfaces';
import { translate } from '@ghostfolio/ui/i18n';
import { GfTrendIndicatorComponent } from '@ghostfolio/ui/trend-indicator';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common';
import {
@@ -8,13 +10,17 @@ import {
ChangeDetectionStrategy,
Component,
Input,
OnChanges
OnChanges,
OnDestroy
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatTableModule } from '@angular/material/table';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Subject, takeUntil } from 'rxjs';
import { GfTrendIndicatorComponent } from '../trend-indicator';
import { GfValueComponent } from '../value';
import { GfBenchmarkDetailDialogComponent } from './benchmark-detail-dialog/benchmark-detail-dialog.component';
import { BenchmarkDetailDialogParams } from './benchmark-detail-dialog/interfaces/interfaces';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -23,7 +29,8 @@ import { GfValueComponent } from '../value';
GfTrendIndicatorComponent,
GfValueComponent,
MatTableModule,
NgxSkeletonLoaderModule
NgxSkeletonLoaderModule,
RouterModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-benchmark',
@@ -31,8 +38,9 @@ import { GfValueComponent } from '../value';
styleUrls: ['./benchmark.component.scss'],
templateUrl: './benchmark.component.html'
})
export class GfBenchmarkComponent implements OnChanges {
export class GfBenchmarkComponent implements OnChanges, OnDestroy {
@Input() benchmarks: Benchmark[];
@Input() deviceType: string;
@Input() locale = getLocale();
@Input() user: User;
@@ -40,7 +48,28 @@ export class GfBenchmarkComponent implements OnChanges {
public resolveMarketCondition = resolveMarketCondition;
public translate = translate;
public constructor() {}
private unsubscribeSubject = new Subject<void>();
public constructor(
private dialog: MatDialog,
private route: ActivatedRoute,
private router: Router
) {
this.route.queryParams
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((params) => {
if (
params['benchmarkDetailDialog'] &&
params['dataSource'] &&
params['symbol']
) {
this.openBenchmarkDetailDialog({
dataSource: params['dataSource'],
symbol: params['symbol']
});
}
});
}
public ngOnChanges() {
if (this.user?.settings?.isExperimentalFeatures) {
@@ -54,4 +83,36 @@ export class GfBenchmarkComponent implements OnChanges {
];
}
}
public onOpenBenchmarkDialog({ dataSource, symbol }: UniqueAsset) {
this.router.navigate([], {
queryParams: { dataSource, symbol, benchmarkDetailDialog: true }
});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private openBenchmarkDetailDialog({ dataSource, symbol }: UniqueAsset) {
const dialogRef = this.dialog.open(GfBenchmarkDetailDialogComponent, {
data: <BenchmarkDetailDialogParams>{
dataSource,
symbol,
colorScheme: this.user?.settings?.colorScheme,
deviceType: this.deviceType,
locale: this.locale
},
height: this.deviceType === 'mobile' ? '97.5vh' : undefined,
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
});
dialogRef
.afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.router.navigate(['.'], { relativeTo: this.route });
});
}
}