From 1f4427b0e7b57f22dea3c74db3c73279162bf6f2 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Mon, 13 Apr 2026 22:30:25 +0700 Subject: [PATCH 01/11] Task/improve type safety of account detail dialog (#6731) * feat(client): resolve type errors * feat(client): migrate to inject function * feat(client): tighten up encapsulation * fix(client): remove unused activities --- .../account-detail-dialog.component.ts | 97 ++++++++++--------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index 5cd501cf8..f51315e91 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -12,7 +12,6 @@ import { } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; -import { OrderWithAccount } from '@ghostfolio/common/types'; import { GfAccountBalancesComponent } from '@ghostfolio/ui/account-balances'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; @@ -27,7 +26,7 @@ import { ChangeDetectorRef, Component, DestroyRef, - Inject, + inject, OnInit } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; @@ -78,41 +77,42 @@ import { AccountDetailDialogParams } from './interfaces/interfaces'; templateUrl: 'account-detail-dialog.html' }) export class GfAccountDetailDialogComponent implements OnInit { - public accountBalances: AccountBalancesResponse['balances']; - public activities: OrderWithAccount[]; - public activitiesCount: number; - public balance: number; - public balancePrecision = 2; - public currency: string; - public dataSource: MatTableDataSource; - public dividendInBaseCurrency: number; - public dividendInBaseCurrencyPrecision = 2; - public equity: number; - public equityPrecision = 2; - public hasPermissionToDeleteAccountBalance: boolean; - public historicalDataItems: HistoricalDataItem[]; - public holdings: PortfolioPosition[]; - public interestInBaseCurrency: number; - public interestInBaseCurrencyPrecision = 2; - public isLoadingActivities: boolean; - public isLoadingChart: boolean; - public name: string; - public platformName: string; - public sortColumn = 'date'; - public sortDirection: SortDirection = 'desc'; - public totalItems: number; - public user: User; - public valueInBaseCurrency: number; - - public constructor( - private changeDetectorRef: ChangeDetectorRef, - @Inject(MAT_DIALOG_DATA) public data: AccountDetailDialogParams, - private dataService: DataService, - private destroyRef: DestroyRef, - public dialogRef: MatDialogRef, - private router: Router, - private userService: UserService - ) { + protected accountBalances: AccountBalancesResponse['balances']; + protected activitiesCount: number; + protected balance: number; + protected balancePrecision = 2; + protected currency: string | null; + protected dataSource: MatTableDataSource; + protected dividendInBaseCurrency: number; + protected dividendInBaseCurrencyPrecision = 2; + protected equity: number | null; + protected equityPrecision = 2; + protected hasPermissionToDeleteAccountBalance: boolean; + protected historicalDataItems: HistoricalDataItem[]; + protected holdings: PortfolioPosition[]; + protected interestInBaseCurrency: number; + protected interestInBaseCurrencyPrecision = 2; + protected isLoadingActivities: boolean; + protected isLoadingChart: boolean; + protected name: string | null; + protected platformName: string; + protected sortColumn = 'date'; + protected sortDirection: SortDirection = 'desc'; + protected totalItems: number; + protected user: User; + protected valueInBaseCurrency: number; + + protected readonly data = inject(MAT_DIALOG_DATA); + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly dialogRef = + inject>(MatDialogRef); + private readonly router = inject(Router); + private readonly userService = inject(UserService); + + public constructor() { this.userService.stateChanged .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { @@ -135,7 +135,7 @@ export class GfAccountDetailDialogComponent implements OnInit { this.initialize(); } - public onCloneActivity(aActivity: Activity) { + protected onCloneActivity(aActivity: Activity) { this.router.navigate( internalRoutes.portfolio.subRoutes.activities.routerLink, { @@ -146,11 +146,11 @@ export class GfAccountDetailDialogComponent implements OnInit { this.dialogRef.close(); } - public onClose() { + protected onClose() { this.dialogRef.close(); } - public onAddAccountBalance(accountBalance: CreateAccountBalanceDto) { + protected onAddAccountBalance(accountBalance: CreateAccountBalanceDto) { this.dataService .postAccountBalance(accountBalance) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -159,7 +159,7 @@ export class GfAccountDetailDialogComponent implements OnInit { }); } - public onDeleteAccountBalance(aId: string) { + protected onDeleteAccountBalance(aId: string) { this.dataService .deleteAccountBalance(aId) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -168,7 +168,7 @@ export class GfAccountDetailDialogComponent implements OnInit { }); } - public onExport() { + protected onExport() { const activityIds = this.dataSource.data.map(({ id }) => { return id; }); @@ -180,7 +180,7 @@ export class GfAccountDetailDialogComponent implements OnInit { downloadAsFile({ content: data, fileName: `ghostfolio-export-${this.name - .replace(/\s+/g, '-') + ?.replace(/\s+/g, '-') .toLowerCase()}-${format( parseISO(data.meta.date), 'yyyyMMddHHmm' @@ -190,14 +190,14 @@ export class GfAccountDetailDialogComponent implements OnInit { }); } - public onSortChanged({ active, direction }: Sort) { + protected onSortChanged({ active, direction }: Sort) { this.sortColumn = active; this.sortDirection = direction; this.fetchActivities(); } - public onUpdateActivity(aActivity: Activity) { + protected onUpdateActivity(aActivity: Activity) { this.router.navigate( internalRoutes.portfolio.subRoutes.activities.routerLink, { @@ -208,7 +208,7 @@ export class GfAccountDetailDialogComponent implements OnInit { this.dialogRef.close(); } - public showValuesInPercentage() { + protected showValuesInPercentage() { return ( this.data.hasImpersonationId || this.user?.settings?.isRestrictedView ); @@ -330,7 +330,10 @@ export class GfAccountDetailDialogComponent implements OnInit { next: ({ accountBalances, portfolioPerformance }) => { this.accountBalances = accountBalances.balances; - if (portfolioPerformance.chart.length > 0) { + if ( + portfolioPerformance.chart && + portfolioPerformance.chart.length > 0 + ) { this.historicalDataItems = portfolioPerformance.chart.map( ({ date, netWorth, netWorthInPercentage }) => ({ date, From aef14a3d050110fb2814ea66596a1eb47b7862a3 Mon Sep 17 00:00:00 2001 From: Horvath-Fabian <146414748+Horvath-Fabian@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:50:51 +0200 Subject: [PATCH 02/11] Task/eliminate OnDestroy lifecycle hook from FAQ overview page component (#6734) Eliminate OnDestroy lifecycle hook --- .../faq/overview/faq-overview-page.component.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts b/apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts index 8faa8307e..1d59e67e8 100644 --- a/apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts +++ b/apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts @@ -7,11 +7,11 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy + DestroyRef } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; -import { Subject, takeUntil } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -21,21 +21,20 @@ import { Subject, takeUntil } from 'rxjs'; styleUrls: ['./faq-overview-page.scss'], templateUrl: './faq-overview-page.html' }) -export class GfFaqOverviewPageComponent implements OnDestroy { +export class GfFaqOverviewPageComponent { public pricingUrl = `https://ghostfol.io/${document.documentElement.lang}/${publicRoutes.pricing.path}`; public routerLinkFeatures = publicRoutes.features.routerLink; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private userService: UserService ) {} public ngOnInit() { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -44,9 +43,4 @@ export class GfFaqOverviewPageComponent implements OnDestroy { } }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } From 34dd8be2d59bb467d77e21459491594053b56432 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Wed, 15 Apr 2026 00:52:37 +0700 Subject: [PATCH 03/11] Task/improve type safety in home watchlist and create watchlist item dialog (#6724) * feat(client): implement CreateWatchlistItemForm * feat(client): implement inject function * feat(client): remove formBuilder injection * fix(client): use AssetProfileIdentifier instead of SymbolProfile * feat(client): implement defaultLocale to resolve type error * fix(client): replace deprecated getDeviceInfo with deviceInfo signal * feat(client): tighten up encapsulation and immutability --- .../create-watchlist-item-dialog.component.ts | 44 ++++++++------- .../interfaces/interfaces.ts | 8 +++ .../home-watchlist.component.ts | 54 ++++++++++--------- .../home-watchlist/home-watchlist.html | 2 +- 4 files changed, 60 insertions(+), 48 deletions(-) diff --git a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts index 4669a827d..847b2d4ab 100644 --- a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts +++ b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts @@ -1,9 +1,8 @@ +import type { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { - AbstractControl, - FormBuilder, FormControl, FormGroup, FormsModule, @@ -15,6 +14,8 @@ import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; +import { CreateWatchlistItemForm } from './interfaces/interfaces'; + @Component({ changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'h-100' }, @@ -30,44 +31,41 @@ import { MatFormFieldModule } from '@angular/material/form-field'; styleUrls: ['./create-watchlist-item-dialog.component.scss'], templateUrl: 'create-watchlist-item-dialog.html' }) -export class GfCreateWatchlistItemDialogComponent implements OnInit { - public createWatchlistItemForm: FormGroup; - - public constructor( - public readonly dialogRef: MatDialogRef, - public readonly formBuilder: FormBuilder - ) {} - - public ngOnInit() { - this.createWatchlistItemForm = this.formBuilder.group( +export class GfCreateWatchlistItemDialogComponent { + protected readonly createWatchlistItemForm: CreateWatchlistItemForm = + new FormGroup( { - searchSymbol: new FormControl(null, [Validators.required]) + searchSymbol: new FormControl(null, [ + Validators.required + ]) }, { validators: this.validator } ); - } - public onCancel() { + private readonly dialogRef = + inject>(MatDialogRef); + + protected onCancel() { this.dialogRef.close(); } - public onSubmit() { + protected onSubmit() { this.dialogRef.close({ dataSource: - this.createWatchlistItemForm.get('searchSymbol').value.dataSource, - symbol: this.createWatchlistItemForm.get('searchSymbol').value.symbol + this.createWatchlistItemForm.controls.searchSymbol.value?.dataSource, + symbol: this.createWatchlistItemForm.controls.searchSymbol.value?.symbol }); } - private validator(control: AbstractControl): ValidationErrors { - const searchSymbolControl = control.get('searchSymbol'); + private validator(control: CreateWatchlistItemForm): ValidationErrors { + const searchSymbolControl = control.controls.searchSymbol; if ( searchSymbolControl.valid && - searchSymbolControl.value.dataSource && - searchSymbolControl.value.symbol + searchSymbolControl.value?.dataSource && + searchSymbolControl.value?.symbol ) { return { incomplete: false }; } diff --git a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/interfaces/interfaces.ts index c0f74d022..965331326 100644 --- a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/interfaces/interfaces.ts @@ -1,4 +1,12 @@ +import type { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; + +import type { FormControl, FormGroup } from '@angular/forms'; + export interface CreateWatchlistItemDialogParams { deviceType: string; locale: string; } + +export type CreateWatchlistItemForm = FormGroup<{ + searchSymbol: FormControl; +}>; diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts index e6f366351..7deace7de 100644 --- a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts +++ b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts @@ -1,5 +1,6 @@ import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { locale as defaultLocale } from '@ghostfolio/common/config'; import { AssetProfileIdentifier, Benchmark, @@ -14,8 +15,10 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + computed, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, + inject, OnInit } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -45,26 +48,29 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/ templateUrl: './home-watchlist.html' }) export class GfHomeWatchlistComponent implements OnInit { - public deviceType: string; - public hasImpersonationId: boolean; - public hasPermissionToCreateWatchlistItem: boolean; - public hasPermissionToDeleteWatchlistItem: boolean; - public user: User; - public watchlist: Benchmark[]; - - public constructor( - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private destroyRef: DestroyRef, - private deviceService: DeviceDetectorService, - private dialog: MatDialog, - private impersonationStorageService: ImpersonationStorageService, - private route: ActivatedRoute, - private router: Router, - private userService: UserService - ) { - this.deviceType = this.deviceService.getDeviceInfo().deviceType; - + protected hasImpersonationId: boolean; + protected hasPermissionToCreateWatchlistItem: boolean; + protected hasPermissionToDeleteWatchlistItem: boolean; + protected user: User; + protected watchlist: Benchmark[]; + + protected readonly deviceType = computed( + () => this.deviceDetectorService.deviceInfo().deviceType + ); + + 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 impersonationStorageService = inject( + ImpersonationStorageService + ); + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly userService = inject(UserService); + + public constructor() { this.impersonationStorageService .onChangeHasImpersonation() .pipe(takeUntilDestroyed(this.destroyRef)) @@ -110,7 +116,7 @@ export class GfHomeWatchlistComponent implements OnInit { this.loadWatchlistData(); } - public onWatchlistItemDeleted({ + protected onWatchlistItemDeleted({ dataSource, symbol }: AssetProfileIdentifier) { @@ -148,10 +154,10 @@ export class GfHomeWatchlistComponent implements OnInit { >(GfCreateWatchlistItemDialogComponent, { autoFocus: false, data: { - deviceType: this.deviceType, - locale: this.user?.settings?.locale + deviceType: this.deviceType(), + locale: this.user?.settings?.locale ?? defaultLocale }, - width: this.deviceType === 'mobile' ? '100vw' : '50rem' + width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); dialogRef diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.html b/apps/client/src/app/components/home-watchlist/home-watchlist.html index 743d86c37..a17259000 100644 --- a/apps/client/src/app/components/home-watchlist/home-watchlist.html +++ b/apps/client/src/app/components/home-watchlist/home-watchlist.html @@ -11,7 +11,7 @@
Date: Wed, 15 Apr 2026 01:14:25 +0700 Subject: [PATCH 04/11] Task/improve type safety in asset profile dialog (#6722) * Improve type safety --- .../asset-profile-dialog.component.ts | 299 ++++++++++-------- .../asset-profile-dialog.html | 4 +- 2 files changed, 163 insertions(+), 140 deletions(-) diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index 6e2dab956..76f6cb9c2 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -87,6 +87,7 @@ import { readerOutline, serverOutline } from 'ionicons/icons'; +import { isBoolean } from 'lodash'; import ms from 'ms'; import { EMPTY } from 'rxjs'; import { catchError } from 'rxjs/operators'; @@ -129,25 +130,26 @@ export class GfAssetProfileDialogComponent implements OnInit { )};123.45`; @ViewChild('assetProfileFormElement') - assetProfileFormElement: ElementRef; + public readonly assetProfileFormElement: ElementRef; - public assetClassLabel: string; - public assetSubClassLabel: string; + protected assetClassLabel: string; + protected assetSubClassLabel: string; - public assetClassOptions: AssetClassSelectorOption[] = Object.keys(AssetClass) - .map((id) => { - return { id, label: translate(id) } as AssetClassSelectorOption; - }) - .sort((a, b) => { - return a.label.localeCompare(b.label); - }); + protected readonly assetClassOptions: AssetClassSelectorOption[] = + Object.keys(AssetClass) + .map((id) => { + return { id, label: translate(id) } as AssetClassSelectorOption; + }) + .sort((a, b) => { + return a.label.localeCompare(b.label); + }); - public assetSubClassOptions: AssetClassSelectorOption[] = []; - public assetProfile: AdminMarketDataDetails['assetProfile']; + protected assetSubClassOptions: AssetClassSelectorOption[] = []; + protected assetProfile: AdminMarketDataDetails['assetProfile']; - public assetProfileForm = this.formBuilder.group({ - assetClass: new FormControl(undefined), - assetSubClass: new FormControl(undefined), + protected readonly assetProfileForm = this.formBuilder.group({ + assetClass: new FormControl(null), + assetSubClass: new FormControl(null), comment: '', countries: ['', jsonValidator()], currency: '', @@ -156,11 +158,15 @@ export class GfAssetProfileDialogComponent implements OnInit { }), isActive: [true], name: ['', Validators.required], - scraperConfiguration: this.formBuilder.group({ - defaultMarketPrice: null, - headers: [JSON.stringify({}), jsonValidator()], + scraperConfiguration: this.formBuilder.group< + Omit & { + headers: FormControl; + } + >({ + defaultMarketPrice: undefined, + headers: new FormControl(JSON.stringify({}), jsonValidator()), locale: '', - mode: '', + mode: 'lazy', selector: '', url: '' }), @@ -169,12 +175,11 @@ export class GfAssetProfileDialogComponent implements OnInit { url: '' }); - public assetProfileIdentifierForm = this.formBuilder.group( + protected readonly assetProfileIdentifierForm = this.formBuilder.group( { - assetProfileIdentifier: new FormControl( - { symbol: null, dataSource: null }, - [Validators.required] - ) + assetProfileIdentifier: new FormControl< + AssetProfileIdentifier | { dataSource: null; symbol: null } + >({ dataSource: null, symbol: null }, [Validators.required]) }, { validators: (control) => { @@ -183,16 +188,15 @@ export class GfAssetProfileDialogComponent implements OnInit { } ); - public benchmarks: Partial[]; - public canEditAssetProfile = true; + protected canEditAssetProfile = true; - public countries: { + protected countries: { [code: string]: { name: string; value: number }; }; - public currencies: string[] = []; + protected currencies: string[] = []; - public dateRangeOptions = [ + protected readonly dateRangeOptions = [ { label: $localize`Current week` + ' (' + $localize`WTD` + ')', value: 'wtd' @@ -218,14 +222,14 @@ export class GfAssetProfileDialogComponent implements OnInit { value: 'max' } ]; - public historicalDataItems: LineChartItem[]; - public isBenchmark = false; - public isDataGatheringEnabled: boolean; - public isEditAssetProfileIdentifierMode = false; - public isUUID = isUUID; - public marketDataItems: MarketData[] = []; - - public modeValues = [ + protected historicalDataItems: LineChartItem[]; + protected isBenchmark = false; + protected isDataGatheringEnabled: boolean; + protected isEditAssetProfileIdentifierMode = false; + protected readonly isUUID = isUUID; + protected marketDataItems: MarketData[] = []; + + protected readonly modeValues = [ { value: 'lazy', viewValue: $localize`Lazy` + ' (' + $localize`end of day` + ')' @@ -236,20 +240,22 @@ export class GfAssetProfileDialogComponent implements OnInit { } ]; - public sectors: { + protected sectors: { [name: string]: { name: string; value: number }; }; - public user: User; + protected user: User; + + private benchmarks: Partial[]; public constructor( - public adminMarketDataService: AdminMarketDataService, + protected adminMarketDataService: AdminMarketDataService, private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, - @Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams, + @Inject(MAT_DIALOG_DATA) protected data: AssetProfileDialogParams, private dataService: DataService, private destroyRef: DestroyRef, - public dialogRef: MatDialogRef, + private dialogRef: MatDialogRef, private formBuilder: FormBuilder, private notificationService: NotificationService, private snackBar: MatSnackBar, @@ -264,7 +270,7 @@ export class GfAssetProfileDialogComponent implements OnInit { }); } - public get canSaveAssetProfileIdentifier() { + protected get canSaveAssetProfileIdentifier() { return !this.assetProfileForm.dirty && this.canEditAssetProfile; } @@ -277,8 +283,8 @@ export class GfAssetProfileDialogComponent implements OnInit { this.initialize(); } - public initialize() { - this.historicalDataItems = undefined; + protected initialize() { + this.historicalDataItems = []; this.adminService .fetchAdminData() @@ -298,10 +304,13 @@ export class GfAssetProfileDialogComponent implements OnInit { } }); - this.assetProfileForm - .get('assetClass') - .valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) + this.assetProfileForm.controls.assetClass.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((assetClass) => { + if (!assetClass) { + return; + } + const assetSubClasses = ASSET_CLASS_MAPPING.get(assetClass) ?? []; this.assetSubClassOptions = assetSubClasses @@ -313,7 +322,7 @@ export class GfAssetProfileDialogComponent implements OnInit { }) .sort((a, b) => a.label.localeCompare(b.label)); - this.assetProfileForm.get('assetSubClass').setValue(null); + this.assetProfileForm.controls.assetSubClass.setValue(null); this.changeDetectorRef.markForCheck(); }); @@ -327,8 +336,10 @@ export class GfAssetProfileDialogComponent implements OnInit { .subscribe(({ assetProfile, marketData }) => { this.assetProfile = assetProfile; - this.assetClassLabel = translate(this.assetProfile?.assetClass); - this.assetSubClassLabel = translate(this.assetProfile?.assetSubClass); + this.assetClassLabel = translate(this.assetProfile?.assetClass ?? ''); + this.assetSubClassLabel = translate( + this.assetProfile?.assetSubClass ?? '' + ); this.canEditAssetProfile = !isCurrency( getCurrencyFromSymbol(this.data.symbol) @@ -350,7 +361,10 @@ export class GfAssetProfileDialogComponent implements OnInit { this.marketDataItems = marketData; this.sectors = {}; - if (this.assetProfile?.countries?.length > 0) { + if ( + this.assetProfile?.countries && + this.assetProfile.countries.length > 0 + ) { for (const { code, name, weight } of this.assetProfile.countries) { this.countries[code] = { name, @@ -359,7 +373,10 @@ export class GfAssetProfileDialogComponent implements OnInit { } } - if (this.assetProfile?.sectors?.length > 0) { + if ( + this.assetProfile?.sectors && + this.assetProfile.sectors.length > 0 + ) { for (const { name, weight } of this.assetProfile.sectors) { this.sectors[name] = { name, @@ -377,12 +394,14 @@ export class GfAssetProfileDialogComponent implements OnInit { return { code, weight }; }) ?? [] ), - currency: this.assetProfile?.currency, + currency: this.assetProfile?.currency ?? null, historicalData: { csvString: GfAssetProfileDialogComponent.HISTORICAL_DATA_TEMPLATE }, - isActive: this.assetProfile?.isActive, - name: this.assetProfile.name ?? this.assetProfile.symbol, + isActive: isBoolean(this.assetProfile?.isActive) + ? this.assetProfile.isActive + : null, + name: this.assetProfile.name ?? this.assetProfile.symbol ?? null, scraperConfiguration: { defaultMarketPrice: this.assetProfile?.scraperConfiguration?.defaultMarketPrice ?? @@ -410,7 +429,7 @@ export class GfAssetProfileDialogComponent implements OnInit { }); } - public onCancelEditAssetProfileIdentifierMode() { + protected onCancelEditAssetProfileIdentifierMode() { this.isEditAssetProfileIdentifierMode = false; if (this.canEditAssetProfile) { @@ -420,17 +439,20 @@ export class GfAssetProfileDialogComponent implements OnInit { this.assetProfileIdentifierForm.reset(); } - public onClose() { + protected onClose() { this.dialogRef.close(); } - public onDeleteProfileData({ dataSource, symbol }: AssetProfileIdentifier) { + protected onDeleteProfileData({ + dataSource, + symbol + }: AssetProfileIdentifier) { this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol }); this.dialogRef.close(); } - public onGatherProfileDataBySymbol({ + protected onGatherProfileDataBySymbol({ dataSource, symbol }: AssetProfileIdentifier) { @@ -440,7 +462,7 @@ export class GfAssetProfileDialogComponent implements OnInit { .subscribe(); } - public onGatherSymbol({ + protected onGatherSymbol({ dataSource, range, symbol @@ -453,13 +475,13 @@ export class GfAssetProfileDialogComponent implements OnInit { .subscribe(); } - public onMarketDataChanged(withRefresh: boolean = false) { + protected onMarketDataChanged(withRefresh: boolean = false) { if (withRefresh) { this.initialize(); } } - public onSetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { + protected onSetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { this.dataService .postBenchmark({ dataSource, symbol }) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -472,50 +494,48 @@ export class GfAssetProfileDialogComponent implements OnInit { }); } - public onSetEditAssetProfileIdentifierMode() { + protected onSetEditAssetProfileIdentifierMode() { this.isEditAssetProfileIdentifierMode = true; this.assetProfileForm.disable(); } - public async onSubmitAssetProfileForm() { - let countries = []; - let scraperConfiguration: ScraperConfiguration = { + protected async onSubmitAssetProfileForm() { + let countries: Prisma.InputJsonArray = []; + let scraperConfiguration: Prisma.InputJsonObject | undefined = { selector: '', url: '' }; - let sectors = []; - let symbolMapping = {}; + let sectors: Prisma.InputJsonArray = []; + let symbolMapping: Record = {}; try { - countries = JSON.parse(this.assetProfileForm.get('countries').value); + countries = JSON.parse( + this.assetProfileForm.controls.countries.value ?? '[]' + ) as Prisma.InputJsonArray; } catch {} try { scraperConfiguration = { defaultMarketPrice: - (this.assetProfileForm.controls['scraperConfiguration'].controls[ - 'defaultMarketPrice' - ].value as number) || undefined, + this.assetProfileForm.controls.scraperConfiguration.controls + .defaultMarketPrice?.value ?? undefined, headers: JSON.parse( - this.assetProfileForm.controls['scraperConfiguration'].controls[ - 'headers' - ].value - ), + this.assetProfileForm.controls.scraperConfiguration.controls.headers + .value ?? '{}' + ) as Record, locale: - this.assetProfileForm.controls['scraperConfiguration'].controls[ - 'locale' - ].value || undefined, - mode: this.assetProfileForm.controls['scraperConfiguration'].controls[ - 'mode' - ].value as ScraperConfiguration['mode'], + this.assetProfileForm.controls.scraperConfiguration.controls.locale + ?.value ?? undefined, + mode: + this.assetProfileForm.controls.scraperConfiguration.controls.mode + ?.value ?? undefined, selector: - this.assetProfileForm.controls['scraperConfiguration'].controls[ - 'selector' - ].value, - url: this.assetProfileForm.controls['scraperConfiguration'].controls[ - 'url' - ].value + this.assetProfileForm.controls.scraperConfiguration.controls.selector + .value ?? '', + url: + this.assetProfileForm.controls.scraperConfiguration.controls.url + .value ?? '' }; if (!scraperConfiguration.selector || !scraperConfiguration.url) { @@ -536,28 +556,32 @@ export class GfAssetProfileDialogComponent implements OnInit { } try { - sectors = JSON.parse(this.assetProfileForm.get('sectors').value); + sectors = JSON.parse( + this.assetProfileForm.controls.sectors.value ?? '[]' + ) as Prisma.InputJsonArray; } catch {} try { symbolMapping = JSON.parse( - this.assetProfileForm.get('symbolMapping').value - ); + this.assetProfileForm.controls.symbolMapping.value ?? '{}' + ) as Record; } catch {} const assetProfile: UpdateAssetProfileDto = { countries, + scraperConfiguration, sectors, symbolMapping, - assetClass: this.assetProfileForm.get('assetClass').value, - assetSubClass: this.assetProfileForm.get('assetSubClass').value, - comment: this.assetProfileForm.get('comment').value || null, - currency: this.assetProfileForm.get('currency').value, - isActive: this.assetProfileForm.get('isActive').value, - name: this.assetProfileForm.get('name').value, - scraperConfiguration: - scraperConfiguration as unknown as Prisma.InputJsonObject, - url: this.assetProfileForm.get('url').value || null + assetClass: this.assetProfileForm.controls.assetClass.value ?? undefined, + assetSubClass: + this.assetProfileForm.controls.assetSubClass.value ?? undefined, + comment: this.assetProfileForm.controls.comment.value ?? undefined, + currency: this.assetProfileForm.controls.currency.value ?? undefined, + isActive: isBoolean(this.assetProfileForm.controls.isActive.value) + ? this.assetProfileForm.controls.isActive.value + : undefined, + name: this.assetProfileForm.controls.name.value ?? undefined, + url: this.assetProfileForm.controls.url.value ?? undefined }; try { @@ -600,7 +624,7 @@ export class GfAssetProfileDialogComponent implements OnInit { this.initialize(); }, - error: (error) => { + error: (error: HttpErrorResponse) => { console.error($localize`Could not save asset profile`, error); this.snackBar.open( @@ -614,12 +638,14 @@ export class GfAssetProfileDialogComponent implements OnInit { }); } - public async onSubmitAssetProfileIdentifierForm() { + protected async onSubmitAssetProfileIdentifierForm() { const assetProfileIdentifier: UpdateAssetProfileDto = { - dataSource: this.assetProfileIdentifierForm.get('assetProfileIdentifier') - .value.dataSource, - symbol: this.assetProfileIdentifierForm.get('assetProfileIdentifier') - .value.symbol + dataSource: + this.assetProfileIdentifierForm.controls.assetProfileIdentifier.value + ?.dataSource ?? undefined, + symbol: + this.assetProfileIdentifierForm.controls.assetProfileIdentifier.value + ?.symbol ?? undefined }; try { @@ -676,38 +702,33 @@ export class GfAssetProfileDialogComponent implements OnInit { }); } - public onTestMarketData() { + protected onTestMarketData() { this.adminService .testMarketData({ dataSource: this.data.dataSource, scraperConfiguration: { - defaultMarketPrice: this.assetProfileForm.controls[ - 'scraperConfiguration' - ].controls['defaultMarketPrice'].value as number, + defaultMarketPrice: + this.assetProfileForm.controls.scraperConfiguration.controls + .defaultMarketPrice?.value, headers: JSON.parse( - this.assetProfileForm.controls['scraperConfiguration'].controls[ - 'headers' - ].value - ), + this.assetProfileForm.controls.scraperConfiguration.controls.headers + .value ?? '{}' + ) as Record, locale: - this.assetProfileForm.controls['scraperConfiguration'].controls[ - 'locale' - ].value || undefined, - mode: this.assetProfileForm.controls['scraperConfiguration'].controls[ - 'mode' - ].value, + this.assetProfileForm.controls.scraperConfiguration.controls.locale + ?.value || undefined, + mode: this.assetProfileForm.controls.scraperConfiguration.controls + .mode?.value, selector: - this.assetProfileForm.controls['scraperConfiguration'].controls[ - 'selector' - ].value, - url: this.assetProfileForm.controls['scraperConfiguration'].controls[ - 'url' - ].value + this.assetProfileForm.controls.scraperConfiguration.controls + .selector.value, + url: this.assetProfileForm.controls.scraperConfiguration.controls.url + .value }, symbol: this.data.symbol }) .pipe( - catchError(({ error }) => { + catchError(({ error }: HttpErrorResponse) => { this.notificationService.alert({ message: error?.message, title: $localize`Error` @@ -723,26 +744,26 @@ export class GfAssetProfileDialogComponent implements OnInit { ' ' + price + ' ' + - this.assetProfileForm.get('currency').value + this.assetProfileForm.controls.currency.value }); }); } - public onToggleIsActive({ checked }: MatCheckboxChange) { + protected onToggleIsActive({ checked }: MatCheckboxChange) { if (checked) { - this.assetProfileForm.get('isActive')?.setValue(true); + this.assetProfileForm.controls.isActive.setValue(true); } else { - this.assetProfileForm.get('isActive')?.setValue(false); + this.assetProfileForm.controls.isActive.setValue(false); } if (checked === this.assetProfile.isActive) { - this.assetProfileForm.get('isActive')?.markAsPristine(); + this.assetProfileForm.controls.isActive.markAsPristine(); } else { - this.assetProfileForm.get('isActive')?.markAsDirty(); + this.assetProfileForm.controls.isActive.markAsDirty(); } } - public onUnsetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { + protected onUnsetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { this.dataService .deleteBenchmark({ dataSource, symbol }) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -755,15 +776,15 @@ export class GfAssetProfileDialogComponent implements OnInit { }); } - public onTriggerSubmitAssetProfileForm() { + protected onTriggerSubmitAssetProfileForm() { if (this.assetProfileForm.valid) { this.onSubmitAssetProfileForm(); } } - private isNewSymbolValid(control: AbstractControl): ValidationErrors { + private isNewSymbolValid(control: AbstractControl): ValidationErrors | null { const currentAssetProfileIdentifier: AssetProfileIdentifier | undefined = - control.get('assetProfileIdentifier').value; + control.get('assetProfileIdentifier')?.value; if ( currentAssetProfileIdentifier?.dataSource === this.data?.dataSource && @@ -773,5 +794,7 @@ export class GfAssetProfileDialogComponent implements OnInit { equalsPreviousProfileIdentifier: true }; } + + return null; } } diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index 9ae7f8064..cfae840a8 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -419,11 +419,11 @@ Url - @if (assetProfileForm.get('url').value) { + @if (assetProfileForm.controls.url.value) { } From 0e7efce96be51278a6a21e4cfea17afadb2ad2ec Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:31:57 +0700 Subject: [PATCH 05/11] Task/improve type safety in admin jobs component (#6739) Improve type safety --- .../admin-jobs/admin-jobs.component.ts | 74 ++++++++++--------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts index b9f4d590d..b4c228881 100644 --- a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts +++ b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts @@ -20,12 +20,13 @@ import { ChangeDetectorRef, Component, DestroyRef, + inject, OnInit, - ViewChild + viewChild } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { - FormBuilder, + FormControl, FormGroup, FormsModule, ReactiveFormsModule @@ -74,19 +75,25 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; templateUrl: './admin-jobs.html' }) export class GfAdminJobsComponent implements OnInit { - @ViewChild(MatSort) sort: MatSort; + protected readonly sort = viewChild.required(MatSort); - public DATA_GATHERING_QUEUE_PRIORITY_LOW = DATA_GATHERING_QUEUE_PRIORITY_LOW; - public DATA_GATHERING_QUEUE_PRIORITY_HIGH = + protected readonly DATA_GATHERING_QUEUE_PRIORITY_HIGH = DATA_GATHERING_QUEUE_PRIORITY_HIGH; - public DATA_GATHERING_QUEUE_PRIORITY_MEDIUM = + + protected readonly DATA_GATHERING_QUEUE_PRIORITY_LOW = + DATA_GATHERING_QUEUE_PRIORITY_LOW; + + protected readonly DATA_GATHERING_QUEUE_PRIORITY_MEDIUM = DATA_GATHERING_QUEUE_PRIORITY_MEDIUM; - public dataSource = new MatTableDataSource(); - public defaultDateTimeFormat: string; - public filterForm: FormGroup; + protected dataSource = new MatTableDataSource(); + protected defaultDateTimeFormat: string; + + protected readonly filterForm = new FormGroup({ + status: new FormControl(null) + }); - public displayedColumns = [ + protected readonly displayedColumns = [ 'index', 'type', 'symbol', @@ -99,21 +106,20 @@ export class GfAdminJobsComponent implements OnInit { 'actions' ]; - public hasPermissionToAccessBullBoard = false; - public isLoading = false; - public statusFilterOptions = QUEUE_JOB_STATUS_LIST; + protected hasPermissionToAccessBullBoard = false; + protected isLoading = false; + protected readonly statusFilterOptions = QUEUE_JOB_STATUS_LIST; private user: User; - public constructor( - private adminService: AdminService, - private changeDetectorRef: ChangeDetectorRef, - private destroyRef: DestroyRef, - private formBuilder: FormBuilder, - private notificationService: NotificationService, - private tokenStorageService: TokenStorageService, - private userService: UserService - ) { + private readonly adminService = inject(AdminService); + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly destroyRef = inject(DestroyRef); + private readonly notificationService = inject(NotificationService); + private readonly tokenStorageService = inject(TokenStorageService); + private readonly userService = inject(UserService); + + public constructor() { this.userService.stateChanged .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { @@ -148,21 +154,17 @@ export class GfAdminJobsComponent implements OnInit { } public ngOnInit() { - this.filterForm = this.formBuilder.group({ - status: [] - }); - this.filterForm.valueChanges .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { - const currentFilter = this.filterForm.get('status').value; + const currentFilter = this.filterForm.controls.status.value; this.fetchJobs(currentFilter ? [currentFilter] : undefined); }); this.fetchJobs(); } - public onDeleteJob(aId: string) { + protected onDeleteJob(aId: string) { this.adminService .deleteJob(aId) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -171,18 +173,18 @@ export class GfAdminJobsComponent implements OnInit { }); } - public onDeleteJobs() { - const currentFilter = this.filterForm.get('status').value; + protected onDeleteJobs() { + const currentFilter = this.filterForm.controls.status.value; this.adminService - .deleteJobs({ status: currentFilter ? [currentFilter] : undefined }) + .deleteJobs({ status: currentFilter ? [currentFilter] : [] }) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.fetchJobs(currentFilter ? [currentFilter] : undefined); }); } - public onExecuteJob(aId: string) { + protected onExecuteJob(aId: string) { this.adminService .executeJob(aId) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -191,7 +193,7 @@ export class GfAdminJobsComponent implements OnInit { }); } - public onOpenBullBoard() { + protected onOpenBullBoard() { const token = this.tokenStorageService.getToken(); document.cookie = [ @@ -203,13 +205,13 @@ export class GfAdminJobsComponent implements OnInit { window.open(BULL_BOARD_ROUTE, '_blank'); } - public onViewData(aData: AdminJobs['jobs'][0]['data']) { + protected onViewData(aData: AdminJobs['jobs'][0]['data']) { this.notificationService.alert({ title: JSON.stringify(aData, null, ' ') }); } - public onViewStacktrace(aStacktrace: AdminJobs['jobs'][0]['stacktrace']) { + protected onViewStacktrace(aStacktrace: AdminJobs['jobs'][0]['stacktrace']) { this.notificationService.alert({ title: JSON.stringify(aStacktrace, null, ' ') }); @@ -223,7 +225,7 @@ export class GfAdminJobsComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ jobs }) => { this.dataSource = new MatTableDataSource(jobs); - this.dataSource.sort = this.sort; + this.dataSource.sort = this.sort(); this.dataSource.sortingDataAccessor = get; this.isLoading = false; From 8b77dd368a7a0a702d6526118a7d1fc3c09b4fe4 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:31:08 +0200 Subject: [PATCH 06/11] Task/harmonize max activities to import in import controller (#6732) * Harmonize max activities to import * Update changelog --- CHANGELOG.md | 4 ---- apps/api/src/app/import/import.controller.ts | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f70d9150..ef65db2b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6044,10 +6044,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed the alias from the user interface as a preparation to remove it from the `User` database schema - Removed the activities import limit for users with a subscription -### Todo - -- Rename the environment variable from `MAX_ORDERS_TO_IMPORT` to `MAX_ACTIVITIES_TO_IMPORT` - ## 1.169.0 - 14.07.2022 ### Added diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index d5724bef2..521be56f7 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -3,6 +3,7 @@ import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard' import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { SubscriptionType } from '@ghostfolio/common/enums'; import { ImportResponse } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import type { RequestWithUser } from '@ghostfolio/common/types'; @@ -62,7 +63,7 @@ export class ImportController { if ( this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && - this.request.user.subscription.type === 'Premium' + this.request.user.subscription.type === SubscriptionType.Premium ) { maxActivitiesToImport = Number.MAX_SAFE_INTEGER; } @@ -100,6 +101,17 @@ export class ImportController { @Param('dataSource') dataSource: DataSource, @Param('symbol') symbol: string ): Promise { + let maxActivitiesToImport = this.configurationService.get( + 'MAX_ACTIVITIES_TO_IMPORT' + ); + + if ( + this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && + this.request.user.subscription.type === SubscriptionType.Premium + ) { + maxActivitiesToImport = Number.MAX_SAFE_INTEGER; + } + const activities = await this.importService.getDividends({ dataSource, symbol, @@ -107,6 +119,6 @@ export class ImportController { userId: this.request.user.id }); - return { activities }; + return { activities: activities.slice(0, maxActivitiesToImport) }; } } From 6d4cae31232386649e5ee8cda140b9dcbd2b6f2b Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:32:00 +0200 Subject: [PATCH 07/11] Bugfix/missing value column of accounts table component on mobile (#6740) * Fix missing value column * Update changelog --- CHANGELOG.md | 4 ++++ libs/ui/src/lib/accounts-table/accounts-table.component.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef65db2b6..ff6e5e8a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgraded `angular` from version `21.1.1` to `21.2.7` - Upgraded `Nx` from version `22.5.3` to `22.6.4` +### Fixed + +- Fixed the missing value column of the accounts table component on mobile + ## 2.254.0 - 2026-03-10 ### Added diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.ts index 99e68c679..aa104f795 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.ts @@ -64,7 +64,7 @@ export class GfAccountsTableComponent { public readonly showBalance = input(true); public readonly showFooter = input(true); public readonly showValue = input(true); - public readonly showValueInBaseCurrency = input(false); + public readonly showValueInBaseCurrency = input(true); public readonly totalBalanceInBaseCurrency = input(); public readonly totalValueInBaseCurrency = input(); From a78283fac8d2d9453885f4cbff51a5acf336fe02 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:48:47 +0200 Subject: [PATCH 08/11] Task/upgrade yahoo-finance2 to version 3.14.0 (#6727) * Upgrade yahoo-finance2 to version 3.14.0 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff6e5e8a3..5926bd4cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgraded `angular` from version `21.1.1` to `21.2.7` - Upgraded `Nx` from version `22.5.3` to `22.6.4` +- Upgraded `yahoo-finance2` from version `3.13.2` to `3.14.0` ### Fixed diff --git a/package-lock.json b/package-lock.json index 35fde3c6e..4027116d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,7 +93,7 @@ "svgmap": "2.19.2", "tablemark": "4.1.0", "twitter-api-v2": "1.29.0", - "yahoo-finance2": "3.13.2", + "yahoo-finance2": "3.14.0", "zone.js": "0.16.1" }, "devDependencies": { @@ -39587,9 +39587,9 @@ } }, "node_modules/yahoo-finance2": { - "version": "3.13.2", - "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.13.2.tgz", - "integrity": "sha512-aAOJEjuLClfDxVPRKxjcwFoyzMr8BE/svgUqr5IjnQR+kppYbKO92Wl3SbAGz5DRghy6FiUfqi5FBDSBA/e2jg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.14.0.tgz", + "integrity": "sha512-gsT/tqgeizKtMxbIIWFiFyuhM/6MZE4yEyNLmPekr88AX14JL2HWw0/QNMOR081jVtzTjihqDW0zV7IayH1Wcw==", "license": "MIT", "dependencies": { "@deno/shim-deno": "~0.18.0", diff --git a/package.json b/package.json index 177d142f7..e7a192af8 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,7 @@ "svgmap": "2.19.2", "tablemark": "4.1.0", "twitter-api-v2": "1.29.0", - "yahoo-finance2": "3.13.2", + "yahoo-finance2": "3.14.0", "zone.js": "0.16.1" }, "devDependencies": { From 8e6e236a25ae17037dfeda8c00a61d1031c6ca2d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:20:00 +0200 Subject: [PATCH 09/11] Task/sort activity types alphabetically in activities table component (#6736) * Sort activity types alphabetically * Update changelog --- CHANGELOG.md | 1 + .../lib/activities-table/activities-table.component.html | 2 +- .../src/lib/activities-table/activities-table.component.ts | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5926bd4cc..6b8cd0648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Sorted the activity types alphabetically on the activities page (experimental) - Upgraded `angular` from version `21.1.1` to `21.2.7` - Upgraded `Nx` from version `22.5.3` to `22.6.4` - Upgraded `yahoo-finance2` from version `3.13.2` to `3.14.0` diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html index b8fe962d7..14089f061 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -5,7 +5,7 @@ Type @for ( - activityType of activityTypesTranslationMap | keyvalue; + activityType of activityTypesTranslationMap | keyvalue: sortByValue; track activityType.key ) { diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts index ffc07f09c..766abcc23 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.ts @@ -353,6 +353,13 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit { this.activityToUpdate.emit(aActivity); } + public sortByValue( + a: { key: ActivityType; value: string }, + b: { key: ActivityType; value: string } + ) { + return a.value.localeCompare(b.value); + } + public toggleAllRows() { if (this.areAllRowsSelected()) { this.selectedRows.clear(); From d4abed37cafa5ade6cc4fd566a4577f9899df5e8 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 18 Apr 2026 19:13:14 +0200 Subject: [PATCH 10/11] Task/sort asset classes and tags of assistant component alphabetically (#6737) * Sort asset classes and tags alphabetically * Update changelog --- CHANGELOG.md | 2 ++ .../src/lib/assistant/assistant.component.ts | 25 ++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b8cd0648..470521134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Sorted the activity types alphabetically on the activities page (experimental) +- Sorted the asset classes of the assistant alphabetically +- Sorted the tags of the assistant alphabetically - Upgraded `angular` from version `21.1.1` to `21.2.7` - Upgraded `Nx` from version `22.5.3` to `22.6.4` - Upgraded `yahoo-finance2` from version `3.13.2` to `3.14.0` diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index cf1449254..5decd9d66 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -189,13 +189,17 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { } public ngOnInit() { - this.assetClasses = Object.keys(AssetClass).map((assetClass) => { - return { - id: assetClass, - label: translate(assetClass), - type: 'ASSET_CLASS' - }; - }); + this.assetClasses = Object.keys(AssetClass) + .map((assetClass) => { + return { + id: assetClass, + label: translate(assetClass), + type: 'ASSET_CLASS' + } satisfies Filter; + }) + .sort((a, b) => { + return a.label.localeCompare(b.label); + }); this.searchFormControl.valueChanges .pipe( @@ -435,12 +439,15 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ?.filter(({ isUsed }) => { return isUsed; }) - .map(({ id, name }) => { + ?.map(({ id, name }) => { return { id, label: translate(name), type: 'TAG' - }; + } satisfies Filter; + }) + ?.sort((a, b) => { + return a.label.localeCompare(b.label); }) ?? []; } From c858f41ac672907a9e032824587c8b132816ae8c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:14:23 +0200 Subject: [PATCH 11/11] Task/upgrade prettier to version 3.8.2 (#6733) * Upgrade prettier to version 3.8.2 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 470521134..1eb476b96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Sorted the tags of the assistant alphabetically - Upgraded `angular` from version `21.1.1` to `21.2.7` - Upgraded `Nx` from version `22.5.3` to `22.6.4` +- Upgraded `prettier` from version `3.8.1` to `3.8.2` - Upgraded `yahoo-finance2` from version `3.13.2` to `3.14.0` ### Fixed diff --git a/package-lock.json b/package-lock.json index 4027116d1..521c33dd0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -148,7 +148,7 @@ "jest-environment-jsdom": "30.2.0", "jest-preset-angular": "16.0.0", "nx": "22.6.4", - "prettier": "3.8.1", + "prettier": "3.8.2", "prettier-plugin-organize-attributes": "1.0.0", "prisma": "6.19.3", "react": "18.2.0", @@ -33003,9 +33003,9 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.2.tgz", + "integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index e7a192af8..2f633b30d 100644 --- a/package.json +++ b/package.json @@ -193,7 +193,7 @@ "jest-environment-jsdom": "30.2.0", "jest-preset-angular": "16.0.0", "nx": "22.6.4", - "prettier": "3.8.1", + "prettier": "3.8.2", "prettier-plugin-organize-attributes": "1.0.0", "prisma": "6.19.3", "react": "18.2.0",