Browse Source

Task/improve type safety of benchmark component (#6555)

* Improve type safety
pull/6499/head^2
Kenrick Tandrian 1 week ago
committed by GitHub
parent
commit
27c50bf509
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts
  2. 16
      libs/ui/src/lib/benchmark/benchmark.component.html
  3. 147
      libs/ui/src/lib/benchmark/benchmark.component.ts

2
libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts

@ -3,7 +3,7 @@ import { ColorScheme } from '@ghostfolio/common/types';
import { DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
export interface BenchmarkDetailDialogParams { export interface BenchmarkDetailDialogParams {
colorScheme: ColorScheme; colorScheme?: ColorScheme;
dataSource: DataSource; dataSource: DataSource;
deviceType: string; deviceType: string;
locale: string; locale: string;

16
libs/ui/src/lib/benchmark/benchmark.component.html

@ -15,7 +15,7 @@
<div class="text-truncate"> <div class="text-truncate">
{{ element?.name }} {{ element?.name }}
</div> </div>
@if (showSymbol) { @if (showSymbol()) {
<div> <div>
<small class="text-muted">{{ element?.symbol }}</small> <small class="text-muted">{{ element?.symbol }}</small>
</div> </div>
@ -98,7 +98,7 @@
@if (element?.performances?.allTimeHigh?.date) { @if (element?.performances?.allTimeHigh?.date) {
<gf-value <gf-value
[isDate]="true" [isDate]="true"
[locale]="locale" [locale]="locale()"
[value]="element?.performances?.allTimeHigh?.date" [value]="element?.performances?.allTimeHigh?.date"
/> />
} }
@ -123,7 +123,7 @@
<gf-value <gf-value
class="d-inline-block justify-content-end" class="d-inline-block justify-content-end"
[isPercent]="true" [isPercent]="true"
[locale]="locale" [locale]="locale()"
[ngClass]="{ [ngClass]="{
'text-danger': 'text-danger':
element?.performances?.allTimeHigh?.performancePercent < 0, element?.performances?.allTimeHigh?.performancePercent < 0,
@ -150,7 +150,7 @@
<ng-container matColumnDef="actions" stickyEnd> <ng-container matColumnDef="actions" stickyEnd>
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell></th> <th *matHeaderCellDef class="px-1 text-center" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 text-center" mat-cell> <td *matCellDef="let element" class="px-1 text-center" mat-cell>
@if (hasPermissionToDeleteItem) { @if (hasPermissionToDeleteItem()) {
<button <button
class="mx-1 no-min-width px-2" class="mx-1 no-min-width px-2"
mat-button mat-button
@ -163,7 +163,7 @@
<mat-menu #benchmarkMenu="matMenu" xPosition="before"> <mat-menu #benchmarkMenu="matMenu" xPosition="before">
<button <button
mat-menu-item mat-menu-item
[disabled]="!hasPermissionToDeleteItem" [disabled]="!hasPermissionToDeleteItem()"
(click)=" (click)="
onDeleteItem({ onDeleteItem({
dataSource: element.dataSource, dataSource: element.dataSource,
@ -180,9 +180,9 @@
</td> </td>
</ng-container> </ng-container>
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr> <tr *matHeaderRowDef="displayedColumns()" mat-header-row></tr>
<tr <tr
*matRowDef="let row; columns: displayedColumns" *matRowDef="let row; columns: displayedColumns()"
class="cursor-pointer" class="cursor-pointer"
mat-row mat-row
(click)=" (click)="
@ -204,7 +204,7 @@
width: '100%' width: '100%'
}" }"
/> />
} @else if (benchmarks?.length === 0) { } @else if (benchmarks()?.length === 0) {
<div class="p-3 text-center text-muted"> <div class="p-3 text-center text-muted">
<small i18n>No data available</small> <small i18n>No data available</small>
</div> </div>

147
libs/ui/src/lib/benchmark/benchmark.component.ts

@ -16,13 +16,15 @@ import {
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
EventEmitter, DestroyRef,
Input, computed,
OnChanges, effect,
OnDestroy, inject,
Output, input,
ViewChild output,
viewChild
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
@ -34,7 +36,6 @@ import { addIcons } from 'ionicons';
import { ellipsisHorizontal, trashOutline } from 'ionicons/icons'; import { ellipsisHorizontal, trashOutline } from 'ionicons/icons';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Subject, takeUntil } from 'rxjs';
import { translate } from '../i18n'; import { translate } from '../i18n';
import { GfTrendIndicatorComponent } from '../trend-indicator/trend-indicator.component'; import { GfTrendIndicatorComponent } from '../trend-indicator/trend-indicator.component';
@ -61,41 +62,58 @@ import { BenchmarkDetailDialogParams } from './benchmark-detail-dialog/interface
styleUrls: ['./benchmark.component.scss'], styleUrls: ['./benchmark.component.scss'],
templateUrl: './benchmark.component.html' templateUrl: './benchmark.component.html'
}) })
export class GfBenchmarkComponent implements OnChanges, OnDestroy { export class GfBenchmarkComponent {
@Input() benchmarks: Benchmark[]; public readonly benchmarks = input.required<Benchmark[]>();
@Input() deviceType: string; public readonly deviceType = input.required<string>();
@Input() hasPermissionToDeleteItem: boolean; public readonly hasPermissionToDeleteItem = input<boolean>();
@Input() locale = getLocale(); public readonly locale = input(getLocale());
@Input() showSymbol = true; public readonly showSymbol = input(true);
@Input() user: User; public readonly user = input<User>();
@Output() itemDeleted = new EventEmitter<AssetProfileIdentifier>(); public readonly itemDeleted = output<AssetProfileIdentifier>();
@ViewChild(MatSort) sort: MatSort; protected readonly sort = viewChild(MatSort);
public dataSource = new MatTableDataSource<Benchmark>([]); protected readonly dataSource = new MatTableDataSource<Benchmark>([]);
public displayedColumns = [ protected readonly displayedColumns = computed(() => {
'name', return [
'date', 'name',
'change', ...(this.user()?.settings?.isExperimentalFeatures
'marketCondition', ? ['trend50d', 'trend200d']
'actions' : []),
]; 'date',
public isLoading = true; 'change',
public isNumber = isNumber; 'marketCondition',
public resolveMarketCondition = resolveMarketCondition; 'actions'
public translate = translate; ];
});
private unsubscribeSubject = new Subject<void>(); protected isLoading = true;
protected readonly isNumber = isNumber;
public constructor( protected readonly resolveMarketCondition = resolveMarketCondition;
private dialog: MatDialog, protected readonly translate = translate;
private notificationService: NotificationService,
private route: ActivatedRoute, private readonly destroyRef = inject(DestroyRef);
private router: Router private readonly dialog = inject(MatDialog);
) { private readonly notificationService = inject(NotificationService);
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
public constructor() {
effect(() => {
const benchmarks = this.benchmarks();
if (benchmarks) {
this.dataSource.data = benchmarks;
this.dataSource.sortingDataAccessor = getLowercase;
this.dataSource.sort = this.sort() ?? null;
this.isLoading = false;
}
});
this.route.queryParams this.route.queryParams
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((params) => { .subscribe((params) => {
if ( if (
params['benchmarkDetailDialog'] && params['benchmarkDetailDialog'] &&
@ -112,30 +130,7 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy {
addIcons({ ellipsisHorizontal, trashOutline }); addIcons({ ellipsisHorizontal, trashOutline });
} }
public ngOnChanges() { protected onDeleteItem({ dataSource, symbol }: AssetProfileIdentifier) {
if (this.benchmarks) {
this.dataSource.data = this.benchmarks;
this.dataSource.sortingDataAccessor = getLowercase;
this.dataSource.sort = this.sort;
this.isLoading = false;
}
if (this.user?.settings?.isExperimentalFeatures) {
this.displayedColumns = [
'name',
'trend50d',
'trend200d',
'date',
'change',
'marketCondition',
'actions'
];
}
}
public onDeleteItem({ dataSource, symbol }: AssetProfileIdentifier) {
this.notificationService.confirm({ this.notificationService.confirm({
confirmFn: () => { confirmFn: () => {
this.itemDeleted.emit({ dataSource, symbol }); this.itemDeleted.emit({ dataSource, symbol });
@ -145,17 +140,15 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy {
}); });
} }
public onOpenBenchmarkDialog({ dataSource, symbol }: AssetProfileIdentifier) { protected onOpenBenchmarkDialog({
dataSource,
symbol
}: AssetProfileIdentifier) {
this.router.navigate([], { this.router.navigate([], {
queryParams: { dataSource, symbol, benchmarkDetailDialog: true } queryParams: { dataSource, symbol, benchmarkDetailDialog: true }
}); });
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private openBenchmarkDetailDialog({ private openBenchmarkDetailDialog({
dataSource, dataSource,
symbol symbol
@ -167,17 +160,17 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy {
data: { data: {
dataSource, dataSource,
symbol, symbol,
colorScheme: this.user?.settings?.colorScheme, colorScheme: this.user()?.settings?.colorScheme,
deviceType: this.deviceType, deviceType: this.deviceType(),
locale: this.locale locale: this.locale()
}, },
height: this.deviceType === 'mobile' ? '98vh' : undefined, height: this.deviceType() === 'mobile' ? '98vh' : undefined,
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef dialogRef
.afterClosed() .afterClosed()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => { .subscribe(() => {
this.router.navigate(['.'], { relativeTo: this.route }); this.router.navigate(['.'], { relativeTo: this.route });
}); });

Loading…
Cancel
Save