From e1e6ae7b5712619db74ee7dd00da2708357adf05 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sun, 17 May 2026 13:52:00 +0700 Subject: [PATCH] Task/improve type safety in admin market data component (#6885) Improve type safety --- .../admin-market-data.component.ts | 152 ++++++++---------- libs/common/src/lib/config.ts | 3 + .../treemap-chart.component.stories.ts | 4 +- 3 files changed, 75 insertions(+), 84 deletions(-) diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index 0abe9ea0f..72a3c337a 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -1,7 +1,8 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { + DEFAULT_COLOR_SCHEME, DEFAULT_PAGE_SIZE, - ghostfolioScraperApiSymbolPrefix + locale } from '@ghostfolio/common/config'; import { getDateFormatString } from '@ghostfolio/common/helper'; import { @@ -26,9 +27,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + computed, DestroyRef, + inject, OnInit, - ViewChild + viewChild } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; @@ -97,11 +100,8 @@ import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/in templateUrl: './admin-market-data.html' }) export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { - @ViewChild(MatPaginator) paginator: MatPaginator; - @ViewChild(MatSort) sort: MatSort; - - public activeFilters: Filter[] = []; - public allFilters: Filter[] = [ + protected readonly adminMarketDataService = inject(AdminMarketDataService); + protected readonly allFilters: Filter[] = [ ...Object.keys(AssetSubClass) .filter((assetSubClass) => { return assetSubClass !== 'CASH'; @@ -146,37 +146,39 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { type: 'PRESET_ID' as Filter['type'] } ]; - public benchmarks: Partial[]; - public currentDataSource: DataSource; - public currentSymbol: string; - public dataSource = new MatTableDataSource(); - public defaultDateFormat: string; - public deviceType: string; - public displayedColumns: string[] = []; - public filters$ = new Subject(); - public ghostfolioScraperApiSymbolPrefix = ghostfolioScraperApiSymbolPrefix; - public hasPermissionForSubscription: boolean; - public info: InfoItem; - public isLoading = true; - public isUUID = isUUID; - public placeholder = ''; - public pageSize = DEFAULT_PAGE_SIZE; - public selection: SelectionModel>; - public totalItems = 0; - public user: User; - - public constructor( - public adminMarketDataService: AdminMarketDataService, - private adminService: AdminService, - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private destroyRef: DestroyRef, - private deviceDetectorService: DeviceDetectorService, - private dialog: MatDialog, - private route: ActivatedRoute, - private router: Router, - private userService: UserService - ) { + protected dataSource = new MatTableDataSource(); + protected defaultDateFormat: string; + protected readonly displayedColumns: string[] = []; + protected readonly filters$ = new Subject(); + protected isLoading = true; + protected readonly isUUID = isUUID; + protected pageSize = DEFAULT_PAGE_SIZE; + protected placeholder = ''; + protected readonly selection = new SelectionModel(true); + protected totalItems = 0; + protected user: User; + + private activeFilters: Filter[] = []; + private benchmarks: Partial[]; + private readonly deviceType = computed( + () => this.deviceDetectorService.deviceInfo().deviceType + ); + private readonly hasPermissionForSubscription: boolean; + private readonly info: InfoItem; + private readonly paginator = viewChild.required(MatPaginator); + private readonly sort = viewChild.required(MatSort); + + private readonly adminService = inject(AdminService); + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly deviceDetectorService = inject(DeviceDetectorService); + private readonly dialog = inject(MatDialog); + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly userService = inject(UserService); + + public constructor() { this.info = this.dataService.fetchInfo(); this.hasPermissionForSubscription = hasPermission( @@ -255,14 +257,14 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { } public ngAfterViewInit() { - this.sort.sortChange.subscribe( + this.sort().sortChange.subscribe( ({ active: sortColumn, direction }: Sort) => { - this.paginator.pageIndex = 0; + this.paginator().pageIndex = 0; this.loadData({ sortColumn, sortDirection: direction, - pageIndex: this.paginator.pageIndex + pageIndex: this.paginator().pageIndex }); } ); @@ -272,24 +274,24 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { const { benchmarks } = this.dataService.fetchInfo(); this.benchmarks = benchmarks; - this.deviceType = this.deviceDetectorService.getDeviceInfo().deviceType; - - this.selection = new SelectionModel(true); } - public onChangePage(page: PageEvent) { + protected onChangePage(page: PageEvent) { this.loadData({ pageIndex: page.pageIndex, - sortColumn: this.sort.active, - sortDirection: this.sort.direction + sortColumn: this.sort().active, + sortDirection: this.sort().direction }); } - public onDeleteAssetProfile({ dataSource, symbol }: AssetProfileIdentifier) { + protected onDeleteAssetProfile({ + dataSource, + symbol + }: AssetProfileIdentifier) { this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol }); } - public onDeleteAssetProfiles() { + protected onDeleteAssetProfiles() { this.adminMarketDataService.deleteAssetProfiles( this.selection.selected.map(({ dataSource, symbol }) => { return { dataSource, symbol }; @@ -297,7 +299,7 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { ); } - public onGather7Days() { + protected onGather7Days() { this.adminService .gather7Days() .pipe(takeUntilDestroyed(this.destroyRef)) @@ -308,7 +310,7 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { }); } - public onGatherMax() { + protected onGatherMax() { this.adminService .gatherMax() .pipe(takeUntilDestroyed(this.destroyRef)) @@ -319,31 +321,14 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { }); } - public onGatherProfileData() { + protected onGatherProfileData() { this.adminService .gatherProfileData() .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } - public onGatherProfileDataBySymbol({ - dataSource, - symbol - }: AssetProfileIdentifier) { - this.adminService - .gatherProfileDataBySymbol({ dataSource, symbol }) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(); - } - - public onGatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) { - this.adminService - .gatherSymbol({ dataSource, symbol }) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(); - } - - public onOpenAssetProfileDialog({ + protected onOpenAssetProfileDialog({ dataSource, symbol }: AssetProfileIdentifier) { @@ -375,8 +360,8 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { ? Number.MAX_SAFE_INTEGER : DEFAULT_PAGE_SIZE; - if (pageIndex === 0 && this.paginator) { - this.paginator.pageIndex = 0; + if (pageIndex === 0 && this.paginator()) { + this.paginator().pageIndex = 0; } this.placeholder = @@ -406,7 +391,7 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { }; }) ); - this.dataSource.sort = this.sort; + this.dataSource.sort = this.sort(); this.isLoading = false; @@ -435,12 +420,13 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { data: { dataSource, symbol, - colorScheme: this.user?.settings.colorScheme, - deviceType: this.deviceType, - locale: this.user?.settings?.locale - }, - height: this.deviceType === 'mobile' ? '98vh' : '80vh', - width: this.deviceType === 'mobile' ? '100vw' : '50rem' + colorScheme: + this.user?.settings.colorScheme ?? DEFAULT_COLOR_SCHEME, + deviceType: this.deviceType(), + locale: this.user?.settings?.locale ?? locale + } satisfies AssetProfileDialogParams, + height: this.deviceType() === 'mobile' ? '98vh' : '80vh', + width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); dialogRef @@ -471,10 +457,10 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { >(GfCreateAssetProfileDialogComponent, { autoFocus: false, data: { - deviceType: this.deviceType, - locale: this.user?.settings?.locale - }, - width: this.deviceType === 'mobile' ? '100vw' : '50rem' + deviceType: this.deviceType(), + locale: this.user?.settings?.locale ?? locale + } satisfies CreateAssetProfileDialogParams, + width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); dialogRef diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 42e1f6b63..9d6122c6c 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -2,6 +2,8 @@ import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client'; import { JobOptions, JobStatus } from 'bull'; import ms from 'ms'; +import { ColorScheme } from './types'; + export const ghostfolioPrefix = 'GF'; export const ghostfolioScraperApiSymbolPrefix = `_${ghostfolioPrefix}_`; export const ghostfolioFearAndGreedIndexDataSourceCryptocurrencies = @@ -77,6 +79,7 @@ export const PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW = export const STATISTICS_GATHERING_QUEUE = 'STATISTICS_GATHERING_QUEUE'; +export const DEFAULT_COLOR_SCHEME: ColorScheme = 'LIGHT'; export const DEFAULT_CURRENCY = 'USD'; export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy'; export const DEFAULT_HOST = '0.0.0.0'; diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts index c8951ce6b..e98b85252 100644 --- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts +++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts @@ -1,3 +1,5 @@ +import { DEFAULT_COLOR_SCHEME } from '@ghostfolio/common/config'; + import { CommonModule } from '@angular/common'; import '@angular/localize/init'; import { moduleMetadata } from '@storybook/angular'; @@ -37,7 +39,7 @@ export const Default: Story = { args: { holdings, baseCurrency: 'USD', - colorScheme: 'LIGHT', + colorScheme: DEFAULT_COLOR_SCHEME, cursor: undefined, dateRange: 'mtd', locale: 'en-US'